关于flex&bison(lex&yacc)解析JSON的探索

关于flex&bison(lex&yacc)解析JSON的探索

前言

关于lua,有句话很出名:“它使我们很脸红”……

像lua、TCC这种,又小又好用的东西,然而我们却写不出来这样的东西,确实是很可惜的……

其实我还是研究过lua的源码一段时间的……比如说lua1.0版本比较简单,里面就是用lex&yacc工具辅助进行解析的……

此外我还对wxBasic的实现也比较感兴趣(好歹我也是自称BASIC出身的程序媛啊……惭愧惭愧),它也是用lex&yacc工具辅助进行解析的……

编译原理这门课我还真没学过,然而自学了三年,还是停留在第一章,只能说意志不坚啊……

话说直到前几天,在群里有个同事问,JSON的解析需要什么算法么?我说与其说需要算法,不如说需要编译原理吧……可惜我对编译原理也是略通啊……

但是这也并不能阻止我用lex&yacc去写一个JSON解析器啊……当然,我们的目标不在于实现,而在于弄清楚其原理……

同事的求问激起了我的强烈兴趣,我写一个JSON解析器尝试一下……以此来打开我进入新世界的大门~

先说说工具和环境,工具用的是UnxUtils中的flex&bison工具,环境用的还是万年不变的MinGW……关于JSON的说明资料……

词法解析器(lex/flex)

说干就干,先是用flex工具,写一个词法解析器,这一步还是相对简单的……

当然我们的目标不是写一个相对完整的JSON,所以,在对Number的正则上也是随便意思一下……

但是对于String类型的词法解析还是比较复杂的,我凑了半天没凑出正则了,后来我就参考了这篇文章《Lex识别C风格字符串和注释》……

总体来说还是没有什么难点的……我们先写一些测试输出看看拼凑的正则对不对,感受一下lex神器带来的优越感~

当然,在这儿我们用的工具是flex……到词法解析这一步,整体还是挺顺风顺水的……

testjson.l文件

%{
#include <string.h>
char* yylval;
%}
def [_a-zA-Z][_a-zA-Z0-9]*
str \"(\\\"|[^\"])*\"
num [0-9]+(\.[0-9]+)?
arrs \[
arre \]
objs \{
obje \}
split ,
desc :
%%
{str} { yylval = strdup(yytext);
printf("[STR:%s]",yylval); 
free(yylval);}
{def} { yylval = strdup(yytext);
printf("[DEF:%s]",yylval);
free(yylval);}
{num} { yylval = strdup(yytext);
printf("[NUM:%s]",yylval); 
free(yylval);}
{arrs} {printf("[ARRS]");}
{arre} {printf("[ARRE]");}
{objs} { printf("[OBJS]"); }
{obje} { printf("[OBJE]"); }
{desc} { printf("[DESC]"); }
{split} { printf("[SPLIT]");}
%%

然后用flex工具生成C文件,用gcc编译一下,相关命令:

flex -o testjson.c testjson.l
gcc -o testjson.exe testjson.c -lfl
当然,我写了个批处理来编译运行它,内容如下:

@echo off
color 1E
echo 当前目录:%~dp0
cd "%~dp0"
echo flex编译testjson.l生成testjson.c...
flex -o testjson.c testjson.l
echo gcc编译testjson.c链接libfl.a输出testjson.exe...
gcc -o testjson.exe testjson.c -L. -lfl
echo 运行testjson.exe输入json.txt作为测试文件...
echo 输出结果:
testjson.exe<json.txt
pause

其中的示例json.txt内容如下(后面再不声明的情况下,都是用的这个串):

{
	a:[1,2,3],
	b:["a\tbc","12  3","4,5\"6",{
					x:1,
					y:"cc\ncc"
				},4.56],
	"text":"I'm OK~",
	"1-2":234
}

运行批处理生成结果如下:

当前目录:C:\jsonParse\lexTest\

flex编译testjson.l生成testjson.c...
gcc编译testjson.c链接libfl.a输出testjson.exe...
运行testjson.exe输入json.txt作为测试文件...
输出结果:
[OBJS]
        [DEF:a][DESC][ARRS][NUM:1][SPLIT][NUM:2][SPLIT][NUM:3][ARRE][SPLIT]
        [DEF:b][DESC][ARRS][STR:"a\tbc"][SPLIT][STR:"12  3"][SPLIT][STR:"4,5\"6"
][SPLIT][OBJS]
                                        [DEF:x][DESC][NUM:1][SPLIT]
                                        [DEF:y][DESC][STR:"cc\ncc"]
                                [OBJE][SPLIT][NUM:4.56][ARRE][SPLIT]
        [STR:"text"][DESC][STR:"I'm OK~"][SPLIT]
        [STR:"1-2"][DESC][NUM:234]
[OBJE]
请按任意键继续. . .

输出的结果感觉还是挺优雅的~

特别是给批处理配上颜色之后,大家感受一下效果图:



语法解析器(yacc/bison)

然后,就是语法解析了……轮到yacc神器出场了,当然,我们在这儿用的工具是bison……

对于yacc来说,最关键的东西就是语法生成树了……语法生成树的构造还是挺学问的……

我尝试了一些写法……总之,终于凑出一个写法来结果是对的……

因为编译原理自学的不好,所以搞出一些奇怪的异常来……

在此要提醒的有这些:yacc源文件中的“#define YYSTYPE char*”放得高点比较好,还有就是那三个函数的定义,不写上会报warnning,尽管没什么影响……

好了,先要改写json.l文件,使其的词法解析结果(token)作为json.y的输入:

%{
#include <string.h>
#include "json.tab.h"
extern char* yylval;
%}
space [ \t\n]+
def [_a-zA-Z][_a-zA-Z0-9]*
str \"(\\\"|[^\"])*\"
num [0-9]+(\.[0-9]+)?
arrs \[
arre \]
objs \{
obje \}
split ,
desc :
%%
{str} { yylval = strdup(yytext);
return STR; }
{def} {
	if(strcmp(yytext,"true")==0){
		return TRUE;
	}else if(strcmp(yytext,"false")==0){
		return FALSE;
	}else if(strcmp(yytext,"null")==0){
		return NIL;
	}else{
		yylval = strdup(yytext);
		return STR; 
	}
}
{num} { yylval = strdup(yytext);
return NUM; }
{arrs} { return ARRS; }
{arre} { return ARRE; }
{objs} { return OBJS; }
{obje} { return OBJE; }
{desc} { return DESC; }
{split} { return SPLIT; }
{space} {}
%%
然后是json.y的语法树解析,输出一下对于解析结果的一些初步规划,比如说push和add之类的,这样可以查看结果跟预想的是不是一样:

%{
#define YYSTYPE char*
#include <stdio.h>
#include "lex.yy.c"
int yyparse(void);
int yyerror(const char* msg);
int yywrap();
%}
%token STR NUM DESC SPLIT ARRS OBJS ARRE OBJE FALSE TRUE NIL
%%
command : value {printf("end\n");}
value: STR {printf("value:[string]%s\n",$1);free($1);}
	|NUM {printf("value:[number]%s\n",$1);free($1);}
	|FALSE {printf("value:FALSE\n");}
	|TRUE {printf("value:TRUE\n");}
	|NIL {printf("value:NULL\n");}
	|object {printf("value:OBJECT\n");}
	|array {printf("value:ARRAY\n");}
;
arrs: ARRS {printf("array_start\n");};
array : arrs ARRE {printf("[empty]\n"); }
	| arrs values ARRE {printf("[...]\n"); }
;
objs: OBJS {printf("object_start\n");};
object : objs OBJE {printf("{empty}\n");}
	| objs pairs OBJE {printf("{...}\n");}
;
values : values SPLIT value {printf("add %s\n",$3);}
	|value {printf("add %s\n",$1);}
	|values SPLIT
;
pairs : pairs SPLIT pair {printf("put\n");}
	|pair {printf("put\n");}
	|pairs SPLIT
;
pair : STR DESC value {printf("key %s\n",$1);}
;
%%
int main()
{
    return yyparse();
}
int yyerror( const char* msg)
{
	fprintf (stderr,"%s\n",msg);
	return 0;
}
int yywrap()
{
    return 1;
}

同样,写个脚本运行一下,同样的测试字符串:

@echo off
color 1E
echo 当前目录:%~dp0
cd "%~dp0"
echo flex编译json.l生成lex.yy.c...
flex json.l
echo bison编译json.y生成json.tab.h、json.tab.c...
bison -d json.y
echo gcc编译json.tab.c输出json.exe...
gcc json.tab.c -o json.exe
echo 运行json.exe输入json.txt作为测试文件...
echo 输出结果:
json.exe<json.txt
pause

输出的结果是这个样子的:



还是挺有感觉的……配合堆栈以及Map和List的复式结构,就可以把这个json中表现的内容存起来了~

配套堆栈处理

语法解析是第一道坎,而相关的堆栈操作和处理便是第二道坎……

每一步执行什么操作都是要有所设计,做到心中有数的……

刚才已经看到了语法解析的处理结果,必须要配合堆栈才能把数据处理好……

可惜,C语言里要写个Stack或者是Map是挺麻烦的……我并不准备这么干……

因为我们的目标是弄清楚编译原理,而不是要实现各什么东西……

于是我选择了语法解析之后,将结果转为JS语句,将JSON输出形成一个返回JS对象的函数……

听上去还是挺酷的~相关的json.l文件不用变动,其中的json.y文件变成了这样:

%{
#define YYSTYPE char*
#include <stdio.h>
#include "lex.yy.c"
int yyparse(void);
int yyerror(const char* msg);
int yywrap();
%}
%token STR NUM DESC SPLIT ARRS OBJS ARRE OBJE FALSE TRUE NIL
%%
command : value {printf("\treturn curValue;\n");}
value: STR {printf("\tcurValue=\"%s\"\n",$1);free($1);}
	|NUM {printf("\tcurValue=%s\n",$1);free($1);}
	|FALSE {printf("\tcurValue=false;\n");}
	|TRUE {printf("\tcurValue=true;\n");}
	|NIL {printf("\tcurValue=null;\n");}
	|object {printf("\tcurValue=curObj;\n\tcurObj=stack[stack.length-1];\n\n");}
	|array {printf("\tcurValue=curObj;\n\tcurObj=stack[stack.length-1];\n\n");}
;
arrs: ARRS {printf("\tcurObj=[];\n\tstack.push(curObj);\n");};
array : arrs ARRE {printf("\tcurObj=stack.pop();\n"); }
	| arrs values ARRE {printf("\tcurObj=stack.pop();\n"); }
;
objs: OBJS {printf("\tcurObj={};\n\tstack.push(curObj);\n");};
object : objs OBJE {printf("\tcurObj=stack.pop();\n");}
	| objs pairs OBJE {printf("\tcurObj=stack.pop();\n");}
;
values : values SPLIT value {printf("\tcurObj.push(curValue);\n");}
	|value {printf("\tcurObj.push(curValue);\n");}
	|values SPLIT
;
pairs : pairs SPLIT STR DESC value {printf("\tcurObj[\"%s\"]=curValue;\n",$3);}
	|STR DESC value {printf("\tcurObj[\"%s\"]=curValue;\n",$1);}
	|pairs SPLIT
;
;
%%
int main()
{
    printf("(function(){\n\tvar stack = [];\n\tvar curObj = null;\n\tvar curValue=null;\n\n");
    int r = yyparse();
    printf("})()");
    return r;
}
int yyerror( const char* msg)
{
	fprintf (stderr,"%s\n",msg);
	return 0;
}
int yywrap()
{
    return 1;
}

相应的测试批处理文件内容:

@echo off
color 1E
echo 当前目录:%~dp0
cd "%~dp0"
echo flex编译json.l生成lex.yy.c...
flex json.l
echo bison编译json.y生成json.tab.h、json.tab.c...
bison -d json.y
echo gcc编译json.tab.c输出json.exe...
gcc json.tab.c -o json.exe
echo 运行json.exe输入json.txt输出“生成结果.txt”...
json.exe<json.txt>生成结果.txt
pause

生成的结果:

(function(){
	var stack = [];
	var curObj = null;
	var curValue=null;

	curObj={};
	stack.push(curObj);
	curObj=[];
	stack.push(curObj);
	curValue=1
	curObj.push(curValue);
	curValue=2
	curObj.push(curValue);
	curValue=3
	curObj.push(curValue);
	curObj=stack.pop();
	curValue=curObj;
	curObj=stack[stack.length-1];

	curObj["a"]=curValue;
	curObj=[];
	stack.push(curObj);
	curValue="a\tbc"
	curObj.push(curValue);
	curValue="12  3"
	curObj.push(curValue);
	curValue="4,5\"6"
	curObj.push(curValue);
	curObj={};
	stack.push(curObj);
	curValue=1
	curObj["x"]=curValue;
	curValue="cc\ncc"
	curObj["y"]=curValue;
	curObj=stack.pop();
	curValue=curObj;
	curObj=stack[stack.length-1];

	curObj.push(curValue);
	curValue=4.56
	curObj.push(curValue);
	curObj=stack.pop();
	curValue=curObj;
	curObj=stack[stack.length-1];

	curObj["b"]=curValue;
	curValue="I'm OK~"
	curObj["text"]=curValue;
	curValue=234
	curObj["1-2"]=curValue;
	curValue=false;
	curObj["mybool"]=curValue;
	curValue=null;
	curObj["mynull"]=curValue;
	curValue=true;
	curObj["myreal"]=curValue;
	curObj=stack.pop();
	curValue=curObj;
	curObj=stack[stack.length-1];

	return curValue;
})()

既然生成了js,就可以在浏览器里执行一下看看效果有什么不同:


从效果图上来看,结果还是挺棒的~~~~~

这里,来讲解一下这段代码跟上面那段代码不同的地方。

刚才说了,C里面写堆栈、list和map啰里啰嗦的,还是js方便……js中用变量stack作为json解析的处理堆栈……

注意,在语法解析时,yacc里面其实是有堆栈的,一个是运行堆栈,一个是值堆栈,这是语法解析用的堆栈……

而js中这个stack是真正的运行时堆栈……传说中的runtime对象……只是听上去高大上而已……

stack的出栈入栈就这么被体现出来了~

还有一个是curValue、curObj,是保存当前值的变量……这两个变量赋值来赋值去的,看上去挺烦的……

但是我觉得虽然很繁琐,但是这么写很好用啊~

然后就这么出栈入栈,赋值来赋值去,一个生成js对象得函数就这么生成了……是不是很神奇?

示例程序下载

还是老规矩,聊到最后要给一个下载链接嘛~

后话

我就这么通宵一晚上用flex&bison工具完成了JSON解析的大致原理性的实现……

然后第二天去群里向那个求问的同事显摆,然而那个同事连看都不看一眼,表示,要用java写才行……还要用原生JDK方法,不能引入第三方包……

我苦逼的问为啥,他说写完java版的json解析,就可以去阿里工作了!

我哈哈一笑,去阿里这么简单?我既然能写个flex&bison版的,我肯定就能写个java版的呀……

你看,我这看了1/10本的编译原理的人都能写出来,这阿里的门槛不早被踏破了?

我说你等着,我很快就给你出个java版本的……于是我又通宵一晚上写了个java版本的……

至于这个java版本的,留到下一篇博客中再说吧……


  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值