任何评论都值得赞赏。
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 $< > $@
#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
评论
什么是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仍在继续开发。