lighttpd的配置文件需要用到lemon来分析,lemon是一个LALR(1)的语法分析生成器。
本来以为要用到LALR(1)的知识,刚好上个学期学了编译原理这门课,但是由于课时
不够,Bottom-Up Parsing只讲了LR(0)和SLR(1),LR(1)和LALR(1)没讲到,所以在
尝试使用lemon之前特意花了一个小时去看了LR(1)和LALR(1)的知识。好在基础的
知识还没有还给老师,所以理解起来没有什么困难。不过看完lemon的文档之后发现
只需要知道一点编译原理的知识就可以了,不熟悉也问题不大。
先到这里把lemon.c和lempar.c下载下来,再打开Documentation来学习一下。
其中lemon.c就是整个lemon parser generator的源代码了,而lempar.c则是
lemon默认使用的代码生成模板(从源代码中看出来的),一般来说用这个就
可以了。编译的话很简单:
$gcc lemon.c -o lemon
void *pParser = ParseAlloc( malloc );
ParseFree(pParser, free);
Parse(pParser, hTokenID, sTokenData, pArg);
还有一个用来调试的接口:
ParseTrace(FILE *stream, char *zPrefix);
使用的一般套路是这样:
ParseFile(){
pParser = ParseAlloc( malloc );
while( GetNextToken(pTokenizer,&hTokenId, &sToken) ){
Parse(pParser, hTokenId, sToken);
}
Parse(pParser, 0, sToken);
ParseFree(pParser, free );
}
其中GetNextToken的方法有很多,简单的话可以自己手动切Token。
详细的内容可以自行参考lemon的Documentation,其实看例子比看文档
要直观,看完一个例子基本上自己可以模仿着用了。
网上介绍lemon的文章基本上都以实现一个简单的计算器作为例子,那我
就照般吧,呵呵。
使用lemon的主要工作其实就是根据需要编写自己的rules,下面我们看一下
一个简单的计算器的rules (parser.y):
%include {
#include /* lemon需要用到assert这个宏 */
}
%token_type {int} /* 变量的类型 */
%syntax_error { /* 当出现语法错误的时候,{}里面的语句就会执行 */
fprintf(stderr, "Syntax error\n");
}
/* left是左结合的意思,
PLUS和MINUS放在TIMES和DIVIDE之前表示PLUS和MINUS的优先级比TIMES和DIVIDE低 */
%left PLUS MINUS.
%left TIMES DIVIDE.
/* 下面的第一条rule的左边(program)默认是start symbol,
每一条rule后面的{}里面的是C代码,这些C代码会在它们reduce的时候被执行。*/
program ::= expr(A). { printf("Result = %d\n", A); }
expr(A) ::= expr(B) PLUS expr(C). { A = B + C; }
expr(A) ::= expr(B) MINUS expr(C). { A = B - C; }
expr(A) ::= expr(B) TIMES expr(C). { A = B * C; }
expr(A) ::= expr(B) DIVIDE expr(C). {
if (C != 0)
A = B / C;
else
fprintf(stderr, "divide by zero\n");
}
expr(A) ::= LPAR expr(B) RPAR. { A = (B); }
expr(A) ::= INTEGER(B). { A = B; }
然后让lemon来生成我们需要的代码:
$./lemon parser.y
没有错误的话就会生成三个文件,parser.h, parser.c, parser.out
然后我们还需要自己编写一个main函数(calc.c)来调用上面提到的那些接口函数。
#include
#include
#include "parser.h"
int main(int argc, char *argv[])
{
void *pParser;
char *c;
int value;
if (argc != 2) {
fprintf(stderr, "usage: %s \n", argv[0]);
exit(EXIT_FAILURE);
}
pParser = (void *)ParseAlloc(malloc);
for (c = argv[1]; *c; c++) {
switch(*c) {
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
for (value = 0; *c && *c >= '0' && *c <= '9'; c++)
value = value * 10 + (*c - '0');
c--;
Parse(pParser, INTEGER, value);
break;
case '+':
Parse(pParser, PLUS, 0);
break;
case '-':
Parse(pParser, MINUS, 0);
break;
case '*':
Parse(pParser, TIMES, 0);
break;
case '/':
Parse(pParser, DIVIDE, 0);
break;
case '(':
Parse(pParser, LPAR, 0);
break;
case ')':
Parse(pParser, RPAR, 0);
break;
case ' ': /* ignore whitespace */
break;
default:
fprintf(stderr, "Unknown symbol: %c\n", *c);
return -1;
}
}
Parse(pParser, 0, 0);
ParseFree(pParser, free);
return 0;
}
main函数除了调用那些接口之外,主要就是要自己手动切Token了,也很简单。
最后我们就可以编译得到我们的计算器了哦:
$gcc calc.c parser.c -o calc
好的,如无意外我们的计算器就做好了哦。现在回想起我大一写计算器的时候,
面对那些表达式调试了好几天才做好呢,现在估计半个小时就可以了,^_^
试用一下看看:
$./calc "(123 + 456) * 10 + 9"
Result = 5799