我是靠谱客的博主 热心大门,最近开发中收集的这篇文章主要介绍flex写Java词法分析_如何用flex+bison写语法分析器,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

背景

这个星期,项目中要使用C++或C语言解析JSON格式的数据,把解析的结果放到一个通用的数据结构。这个通用的数据结构,实际上是作为web服务层(这一层大家可以认为是类似于PHP服务器或webpy的服务器容器)到web页面层(这一层是语法类似PHP脚本或者tornardo模板)的数据传输的协议。 之所以要这样处理, 主要是因为这个web类的项目(一般的web类项目也是如此)需求变化较快,而web的服务层使用是采用C++进行开发的,为了使当web服务层的数据格式变化不影响web页面层,所以双方使用统一的通用的数据结构。而之所以交代这么多的背景是, 为了让大家了解为什么我们不使用类似rapidjson或jsoncpp来实现json的解析而需要手写解析器。 因为使用类似rapidJson或者是jsoncpp之类的Json解析器,相当于我们要做:

JSON文档 -> json DOM -> 通用数据结构。

而如果手写解析器,只需要做:

JSON文档 -> 通用数据结构。

少一层转换能换来很多效率的提升。

说了这么多,下面开始进入正题。   以前学编译原理的时候,老师推荐过LEX /YACC来写编译器,其实这是古老的UNIX软件。 LINUX上有他们的GNU版本 FLEX、BISON。 这两个东西一个是词法分析器,一个是语法分析器。词法分析器的作用是把字符解析成单词。一般的把单词称为token, 而语法分析器则是把单词解析成语法树。

词法分析

首先来看flex的使用:简单来说分为两步: 1 先定义一个flex的输入文件,描述词法。2 用flex程序处理这个文件,生成对应的C语言源代码文件。

一般flex的输入文件以.l文件结尾, 比如这个文件json.l。

%{

#define YYSTYPE _EasyTData*

#include

#include "stdio.h"

#include "easytdata.h"

#include "json.y.hpp"

%}

int [+-]*[0-9]+

num [+-]*([0-9]|.)*

string "(\.|[^\"])*"

ignore_char [ trn]

identifier [a-zA-Z_][a-zA-Z0-9_]*

%%

{identifier} {

if(strcmp(yytext,"true")==0)

{

json2tdata_lval=ed_factory_bool(true);

return TRUE;

}

else if(strcmp(yytext,"false")==0)

{

json2tdata_lval=ed_factory_bool(false);

return FALSE;

}

else if(strcmp(yytext,"null")==0)

{

json2tdata_lval=ed_factory_none();

return NIL;

}

else

{

json2tdata_lval=ed_factory_string(yytext);

return IDENTIFIER;

}

}

{num} {

json2tdata_lval=ed_factory_int(atoi(yytext));

return NUM;

}/*要区分浮点数*/

{string} {

/*去掉前后引号的处理,存到TData里面不需要引号*/

/**/

json2tdata_pre_process_string(yytext);

json2tdata_lval=ed_factory_string(yytext);

return STRING;

}

"{" {return L_BRACE;}

"}" {return R_BRACE;}

"[" {return L_BRACKET;}

"]" {return R_BRACKET;}

":" {return COLON;}

";" {return SEMICOLON;}

"," {return COMMA;}

{ignore_char}

%%

文件分成三个部分。第一部分是从%{到 }%标记的部分。 这个部分会原封不动的复制到flex的生成代码中。文件开头定义了一个YYSTYPE宏。每个TOKEN可以有一个lval值属性,YYSTYPE定义类型就是token的lval的类型。_EasyTData是我们的web服务层和web页面层公用的通用数据结构。然后就是一些要include的头文件,第一部分就完了。

flex的输入文件的第二部分,是从%}到%%之间的部分,这部分用正则表达式定义了一些数据类型。 比如int num string ignore_char identifier等。 int型的定义就是(+-号)后面跟着一些重复的数字。注意这里使用的正则表达式的形式是ERE而不是BRE。 ERE与BRE比较明显的区别就是,ERE使用+表示字符重复一次以上,*表示字符重复0次以上。BRE使用{1,}这种方式表示字符重复1次以上。

flex的输入文件的第三部分,是%%到%%的部分。这里定义了词法分析器在解析的处理动作。yytext是一个flex内部的标识符,表示匹配到的字符串。上文介绍了,lval也是一个内部标识符,表示TOKEN的值。json2tdata_是标识符的前缀, 在执行flex的时候,用-P指定。 flex输入文件写完之后,使用下面这条命令,就可以把flex的输入文件转换为C语言的源代码了。

flex -P"json2tdata_" -o json.l.cpp json.l

语法分析

语法分析是使用bison工具。使用bison工具也是分为两步,第一步写bison的输入文件,第二步用bison程序生成C语言源码。

bison的输入文件一般用.y作为后缀名,比如下面这个json.y, 看下bison的输入文件长什么样子。

%{

#include "stdio.h"

#include "easytdata.h"

//#define YYDEBUG 1

#define YYSTYPE _EasyTData*

extern int json2tdata_lex();

void json2tdata_error(const char*msg);

_EasyTdata *g_oJsonData; //结果存放点 %}

%token INT NUM STRING IGNORE_CHAR L_BRACE R_BRACE L_BRACKET R_BRACKET COLON SEMICOLON COMMA IDENTIFIER TRUE FALSE NIL

%%

Json: Value {g_oJsonData=$1;/*printf("=========nResult ToJson():%s",g_oJsonData.ToJson().c_str());*/}

Object: L_BRACE Pairs R_BRACE {$$=$2;}

| L_BRACE R_BRACE {$$=ed_factory_map();}

Array: L_BRACKET Elements R_BRACKET {$$=$2;}

| L_BRACKET R_BRACKET {$$=ed_factory_vector();}

ID: NUM {$$=$1;}

| STRING {$$=$1;}

| IDENTIFIER{$$=$1;}

Pair: ID COLON Value

{

$$ = ed_factory_pair($1, $3);

}

Pairs: Pairs COMMA Pair {

ed_map_add_pair($1,$3);

$$ = $1;

}

| Pair {

$$=ed_factory_map();

ed_map_add_pair($$, $1);

}

Value: NUM {

$$=$1;

}

| STRING {

$$=$1;

}

| Object {$$=$1;}

| Array {$$=$1;}

| FALSE {

$$=$1;

}

| TRUE {

$$=$1;

}

| NIL {

$$=$1;

}

Elements: Elements COMMA Value {

ed_vector_add($1,$3);

$$ = $1;

}

| Value {

$$ = ed_factory_vector();

ed_vector_add($$,$1);

}

%%

和flex的词法分析输入文件类似,bison的输入文件也是分成3部分。第一部分%{和%}之间,是原封不动拷贝到输出的C语言源文件中的。 json2tdata_lex这个函数是flex生成的。 json2tdata_error是用来处理错误信息的函数。通过定义和实现这个函数你可以把错误信息写到任何地方。与flex类似,json2tdata也是自定义的前缀。

第二部分是%token INT NUM STRING IGNORE_CHAR L_BRACE R_BRACE L_BRACKET R_BRACKET COLON SEMICOLON COMMA IDENTIFIER TRUE FALSE NIL这一行, 这一行的作用就是声明在flex中定义的那些TOKEN。

第三部分是%% %%包围的部分。这部分就是语法的推导过程。  可以比较轻松的看出,这部分主要就是采用BNF对语法进行描述。比如Array, 它有两种形式。 第一种是 L_BRACKET ELEMENTS R_BRACKET, 第二种则是L_BRACKET R_BRACKET, 这表示一个空的Array。Bison能够完全支持LR(1)文法。 这种文法的特点是只要多向前看一个TOKEN,就能够决定如何解析。 因此如果bison告诉你语法ambiguous的时候,可以想一想如何把自己的文法改成LR(1)型文法。另外,每一条规则的后面可以用{}来定义解析的动作。bison用$$表示规则左边的对象,用$1 $2 $3 等依次表示规则右边的对象。

比如:

Elements: Elements COMMA Value {

ed_vector_add($1,$3);

$$ = $1;

} 在执行这条规则的时候,就会用ed_vector_add函数将 Value加入到  Elements中去,然后把$1赋值给$$。

bison的输入文件可以用下面这样的命令转换成C语言的源文件:

bison -d -o json.y.cpp json.l -p"json2tdata_"

-p "json2tdata_"是给语法分析器加一个前缀。 有这个选项,就会生成json2tdata_parse等以json2tdata开头的函数。

总结

1 用flex+bison可以自己写语法分析器。对于程序效率要求高的地方,可以考虑这么做。

2 用java的同学如果也要写语法分析,可以考虑用javacc。

最后

以上就是热心大门为你收集整理的flex写Java词法分析_如何用flex+bison写语法分析器的全部内容,希望文章能够帮你解决flex写Java词法分析_如何用flex+bison写语法分析器所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(45)

评论列表共有 0 条评论

立即
投稿
返回
顶部