5.lemon柠檬牌代码生成器
前面说了,词法的解析器最大的难点在与将语句中关键词与参数多样组合,与程序具体的处理逻辑一一对应。这是一个光想想就头大的事情,为此,我们找到了一个能很好的解决这个难题的方案,代码生成器。
lemon代码生成器,简单的说就是将我们写好的context-free grammar (CFG) 语法脚本,用来生产具体的c代码的逻辑,这东西是编写词法解析器与编译工具的利器。
以下来剖析下这种CFG语法脚本,怎么配置与lemon代码生成器中。
lemon语法文件是有一段一段的语法规则段构成的,每个语法规则段都是通过"::="组合起来的,后面跟and/or等逻辑表达式,每个语法规则段都是以句号结尾的。语法段的右边可以为空。规则可以已任意的顺序出现在文件中,除了%start的关键词例外,这个下面会提到,语法段大概像这样:
expr ::= expr PLUS expr.
expr ::= expr TIMES expr.
expr ::= LPAREN expr RPAREN.
expr ::= VALUE.
lemon语法文件另一个重要的功能,就是嵌入C语句,当满足当前的语法规则时执行{}内的C语句。
如下:
expr ::= expr PLUS expr. { printf("Doing an addition...\n"); }
另外,柠檬牌代码生成器还支持一下关键字,
* %destructor
* %extra_argument
* %include
* %left
* %name
* %nonassoc
* %parse_accept
* %parse_failure
* %right
* %stack_overflow
* %stack_size
* %start_symbol
* %syntax_error
* %token_destructor
* %token_prefix
* %token_type
* %type
* %left * %right 定义运算符关键字
例子,
%left OR.
%left AND.
%right NOT.
%left IS MATCH LIKE_KW BETWEEN IN ISNULL NOTNULL NE EQ.
%left GT LE LT GE.
%right ESCAPE.
%left BITAND BITOR LSHIFT RSHIFT.
%left PLUS MINUS.
%left STAR SLASH REM.
%left CONCAT.
%left COLLATE.
%right UMINUS UPLUS BITNOT
是的,上面的就是定义sql运算符的例子,left为左运算符,right为右运算符.
%type 定义函数的类型 如void int 等
%token_type {Token} 定义语句字符的类型
%default_type {Token} 默认语句字符的类型
%destructor 构析标识
%type nt {void*}
%destructor nt { free($$); }
nt(A) ::= ID NUM. { A = malloc( 100 ); }
这里%type定义了类型为void的函数,%destructor定义了当函数nt出堆时,程序必须执行的动作。
其他关键字不一一介绍,有兴趣的同学请参照文档 http://www.hwaci.com/sw/lemon/lemon.html
那么到这里,我们将配置好的语法规则导入lemon中,lemon会为我们生成两个C文件,一个头文件,与一个.c文件,头文件中包括我们定义的关键字,.c就是语法与逻辑映射的代码,而lemon为我们什么了几个方便的接口,让我们调用解析器
01 ParseTree *ParseFile(const char *zFilename){
02 Tokenizer *pTokenizer;
03 void *pParser;
04 Token sToken;
05 int hTokenId;
06 ParserState sState;
07
08 pTokenizer = TokenizerCreate(zFilename); ---解析词组
09 pParser = ParseAlloc( malloc ); ---开辟内存
10 InitParserState(&sState); ---初始化
11 while( GetNextToken(pTokenizer, &hTokenId, &sToken) ){
12 Parse(pParser, hTokenId, sToken, &sState); ---解析
13 }
14 Parse(pParser, 0, sToken, &sState);
15 ParseFree(pParser, free ); ---释放内存
16 TokenizerFree(pTokenizer);
17 return sState.treeRoot; ----返回语法树
18 }
是的,配好逻辑,我们不用写一句代码,我们完成了语法解析最复杂的部分,目标越来越近了。