这是我昨晚整理的JSON解析器的框架。

任何评论都值得赞赏。


JsonLexer.l:将输入分解为lexemes
JsonParser.y:理解语言语法
JsonParser.h:将所有内容组合在一起的头文件
main.cpp:测试工具,以便对其进行测试。

测试:

echo '{ "Plop": "Test" }' | ./test


JsonLexer.l

%option c++
%option noyywrap

%{
#define  IN_LEXER
#include "JsonParser.tab.hpp"
%}

DIGIT           [0-9]
DIGIT1          [1-9]
INTNUM          {DIGIT1}{DIGIT}*
FRACT           "."{DIGIT}+
FLOAT           ({INTNUM}|0){FRACT}?
EXP             [eE][+-]?{DIGIT}+
NUMBER          -?{FLOAT}{EXP}?

UNICODE         \u[A-Fa-f0-9]{4}
ESCAPECHAR      \["\/bfnrt]
CHAR            [^"\]|{ESCAPECHAR}|{UNICODE}
STRING          \"{CHAR}*\"

WHITESPACE      [ \t\n]


%%

\{              {LOG("LEX({)");     return '{';}
\}              {LOG("LEX(})");     return '}';}
\[              {LOG("LEX([)");     return '[';}
\]              {LOG("LEX(])");     return ']';}
,               {LOG("LEX(,)");     return ',';}
:               {LOG("LEX(:)");     return ':';}
true            {LOG("LEX(true)");  return yy::JsonParser::token::JSON_TRUE;}
false           {LOG("LEX(false)"); return yy::JsonParser::token::JSON_FALSE;}
null            {LOG("LEX(null)");  return yy::JsonParser::token::JSON_NULL;}
{STRING}        {LOG("LEX(String)");return yy::JsonParser::token::JSON_STRING;}
{NUMBER}        {LOG("LEX(Number)");return yy::JsonParser::token::JSON_NUMBER;}

{WHITESPACE}    {/*IGNORE*/}

%%


JsonParser.y

%skeleton "lalr1.cc"
%require "2.1a"
%defines
%define "parser_class_name" "JsonParser"

%{

#include "JsonParser.h"
#include <stdexcept>

%}

%parse-param {FlexLexer& lexer}
%lex-param   {FlexLexer& lexer}

%token  JSON_STRING
%token  JSON_NUMBER
%token  JSON_TRUE
%token  JSON_FALSE
%token  JSON_NULL


%%

JsonObject              :   JsonMap                                 {LOG("JsonObject: JsonMap");}
                        |   JsonArray                               {LOG("JsonObject: JsonArray");}

JsonMap                 :   '{' JsonMapValueListOpt '}'             {LOG("JsonMap: { JsonMapValueListOpt }");}
JsonMapValueListOpt     :                                           {LOG("JsonMapValueListOpt: EMPTY");}
                        |   JsonMapValueList                        {LOG("JsonMapValueListOpt: JsonMapValueList");}
JsonMapValueList        :   JsonMapValue                            {LOG("JsonMapValueList: JsonMapValue");}
                        |   JsonMapValueList ',' JsonMapValue       {LOG("JsonMapValueList: JsonMapValueList , JsonMapValue");}
JsonMapValue            :   JSON_STRING ':' JsonValue               {LOG("JsonMapValue: JSON_STRING : JsonValue");}    

JsonArray               :   '[' JsonArrayValueListOpt ']'           {LOG("JsonArray: [ JsonArrayValueListOpt ]");}
JsonArrayValueListOpt   :                                           {LOG("JsonArrayValueListOpt: EMPTY");}
                        |   JsonArrayValueList                      {LOG("JsonArrayValueListOpt: JsonArrayValueList");}
JsonArrayValueList      :   JsonValue                               {LOG("JsonArrayValueList: JsonValue");}
                        |   JsonArrayValueList ',' JsonValue        {LOG("JsonArrayValueList: JsonArrayValueList , JsonValue");}

JsonValue               :   JsonMap                                 {LOG("JsonValue: JsonMap");}
                        |   JsonArray                               {LOG("JsonValue: JsonArray");}
                        |   JSON_STRING                             {LOG("JsonValue: JSON_STRING");}
                        |   JSON_NUMBER                             {LOG("JsonValue: JSON_NUMBER");}
                        |   JSON_TRUE                               {LOG("JsonValue: JSON_TRUE");}
                        |   JSON_FALSE                              {LOG("JsonValue: JSON_FALSE");}
                        |   JSON_NULL                               {LOG("JsonValue: JSON_NULL");}


%%

int yylex(int*, FlexLexer& lexer)
{
    return lexer.yylex();
}

void yy::JsonParser::error(yy::location const&, std::string const& msg)
{
    throw std::runtime_error(msg);
}



#include "JsonParser.tab.hpp"
#include <iostream>

int main()
{
    try
    {
        yyFlexLexer         lexer(&std::cin, &std::cout);
        yy::JsonParser      parser(lexer);

        std::cout << (parser.parse() == 0 ? "OK" : "FAIL") << "\n";
    }
    catch(std::exception const& e)
    {
        std::cout << "Exception: " << e.what() << "\n";
    }
}


JsonParser.h

#ifndef THORSANVIL_JSON_PARSER_H
#define THORSANVIL_JSON_PARSER_H

#ifndef IN_LEXER
#include <FlexLexer.h>
#endif

int yylex(int*, FlexLexer& lexer);

#ifdef DEBUG_LOG
#include <iostream>
#define LOG(x)      std::cout << x << "\n"
#else
#define LOG(x)      0 /*Empty Statement that will be optimized out*/
#endif

#endif


Makefile

YACC            = bison
LEX             = flex
CXX             = g++

CXXFLAGS        = -DDEBUG_LOG

TARGET          = json
LEX_SRC         = $(wildcard *.l)
YACC_SRC        = $(wildcard *.y)
CPP_SRC         = $(filter-out %.lex.cpp %.tab.cpp,$(wildcard *.cpp))
SRC             = $(patsubst %.y,%.tab.cpp,$(YACC_SRC)) $(patsubst %.l,%.lex.cpp,$(LEX_SRC)) $(CPP_SRC)
OBJ             = $(patsubst %.cpp,%.o,$(SRC))


all: $(OBJ)
    $(CXX) -o test $(OBJ) -lFl

clean:
    rm $(OBJ) $(patsubst %.y,%.tab.cpp,$(YACC_SRC)) $(patsubst %.l,%.lex.cpp,$(LEX_SRC))


$(TARGET):  $(OBJ)
    $(CXX)  -o $* $(OBJ)

.PRECIOUS: %.tab.cpp

%.tab.cpp: %.y
    $(YACC) -o $@ -d $<

.PRECIOUS: %.lex.cpp

%.lex.cpp: %.l
    $(LEX) -t $< > $@


评论

什么是C ++或面向对象的呢? Flex&Bison生成C代码。看到的唯一C ++代码是main.cpp中编写的测试代码。您可以将其称为C解析器,或者将flex和bison等效于C ++

@apeirogon:实际上flex和bison都生成C ++类,请参见%option c ++和%skeleton“ lalr1.cc”。但这并不是真正相关的。词法分析器和扫描器提供给世界的接口使代码变为OO。当前的接口是OO,使您可以在非常面向对象的庄园中独立使用解析器/词法分析器。

@apeirogon:您提供的链接实际上还表明那些工具已经萎缩,而真正的flex和bison仍在继续开发。 但是发行版于1993年结束,随着C ++,gcc,flex和bison的不断发展,flex ++和bison ++已经过时了。因此,我认为我将坚持使用真正的开发工具,而不是某些废弃的项目。

我之所以放弃C ++而不是C的真正原因是我不再写C了。因此胶水代码是C ++。

@LokiAstari:请问,为什么不再写C了?这个话题引起我的兴趣,因为我看到多个不同的人既批评C ++的弱点,也赞扬C ++的长处,其中许多对我来说似乎是有效的论据。

#1 楼

我已经犹豫了好一阵子,但决定有几点值得评论。

首先,也是最明显的是,(按设计)这并没有多大作用。它可以记录您在JSON中遇到的语句类型,仅此而已。要真正发挥作用,通常需要创建至少与AST类似的东西,将JSON输入数据存储为一组节点,并在节点中描述数据的属性。

,您可以构建某种回调样式框架,在该框架中,解析器会在检测到每种类型的输入时调用特定的函数,这取决于客户端代码来决定如何存储数据(以及存储哪些数据) ..

但是,它目前所能产生的最多就是对输入文件整体结构的描述。如果这真的是您想要的,那么我认为值得考虑的是,您是要生成要被人读取还是由机器读取以进行进一步处理的输出。我想我希望在打印出数据之前对数据进行更多处理。例如,按现在的样子,像[1, 2, 3, 4]这样的数组将为JSON数组生成一行输出,为值列表生成另一行,为数组中的每个项目生成另一行。

要读取文件,我想我通常希望将那些日志条目合并成类似Array (4 Numbers)的内容。鉴于此意图是为了解析JSON,在输出的一个位置指定“ JSON”就足够了,而不是在输出的每一行前都加上“ JSON”作为前缀。简而言之,就目前而言,这会产生非常冗长的输出,导致信息密度非常低,因此读者需要阅读和消化大量的输出才能理解甚至相对较少的输入-实际上,我敢肯定,直接读取输入文件通常会更容易。

如果您的主要目的是产生输出供计算机读取以进行进一步处理,则无需进行额外的工作来合并信息,但对于保持信息的紧凑性仍然很有用。鉴于JSON文件中的可能性很小,我可能会为解析器现在可以生成的每个字符串分配一个字母,然后将它们写出来。或者,也可以在此处进行一些合并,以便通过模式(如果括号中的字母大于一个字母,则在括号中)后跟一个数字来表示相同模式的重复。例如,由4个字符串/数字对组成的映射后跟6个数字组成的数组可能会出来:M(SN)[4]AN[6]。这仍然有些人类可读(如有必要),并且接收端的解析器可以更快地整理出来(更不用说存储,传输等数据量少了)1 />就代码本身的风格而言,我实际上只有几条评论,而其中的评论很少。

production : 
           | Alternative1
           | Alternative2


我喜欢添加分号来表示列表的结尾:

production : 
           | Alternative1
           | Alternative2
           ;



这是大规模的,甚至可能超出您的控制范围,但是我找到了以下代码:像这样将返回值和异常结合在一起,并且必须对两者都进行响应,这使得它看起来似乎……不连贯。我宁愿在整个过程中都采用一种样式,因此您可以依靠总是通过抛出异常来通知失败,或者始终通过返回值来通知失败。就目前情况来看,我们不仅会导致此代码相当丑陋,而且还会导致错误报告不均-通过异常报告的错误可能包含相当详细的错误消息,但通过返回值报告的错误都会产生相同(可能无益)的“失败”。




1.我并没有尝试过确保嵌套结构保持明确的所有细节,但是至少从一方面来说,这似乎并不困难。


评论


\ $ \ begingroup \ $
是的。 LOG()仅用于调试目的(不适用于实际输出)。在上面的状态下,它什么也不做(故意);这就是为什么我使用术语框架。因此,您需要在此处插入更多代码以使其有用。
\ $ \ endgroup \ $
–马丁·约克
2014年12月30日在18:08

\ $ \ begingroup \ $
鉴于以上第二点,我绝对同意。异常和返回代码的组合不是一个好的样式。它使使用变得可怕。
\ $ \ endgroup \ $
–马丁·约克
2014年12月30日18:09