Lex Yacc应用初探

了解Lex和Yacc,对于当下流行的各种解释性编译器原理的理解有很大帮助,同时lex和yacc强大的字符串处理能力,也可以为其他相关方向的应用提供参考。

我所用的Lex和Yacc的实用工具分别是Flex和Bison。刚刚接触,希望大家不吝指教。

Lex有很多个版本,比如AT&T Lex、MKS Lex、Flex、Abraxas pclex、POSIX lex等。当然,flex不是Adobe的那个flex,是'fast lex'的缩写。感兴趣的读者可以查找相关的背景知识。据说AT&T Lex是一个AT&T暑期实行生的写的,不禁汗颜,虽然据说那个词法分析器不是很健全,但是足以让我产生敬畏感了。

Yacc也有几个对应的版本,AT&T yacc、 Berkeley yacc、GNU Bison。其中GNU Bison来源于GNU yacc。

了解词法分析和语法分析的预备知识是“正则表达式”和“语法生成树”这两个概念。更深一些的比如LR文法、上下文无关文法等,这些都是编译原理的基本知识。

Lex通过正则表达式来匹配字符串,并将符合条件的字符串经过分析,反馈信息给语法分析器。

Yacc分析的是用BNF表述的一个上下文无关文法,并且这个文法满足LALR(1)。由此可以看出,yacc只能向右查看一个输入单元,所以在设计移进归约的时候要小心。

Lex和Yacc通过一个union类型的yylval变量来传递信息,用union的好处在于,不同时刻Lex可以传递不同类型的信息给Yacc,比如:传递一个整形的数值、传提一个指针、传递一个浮点类型数值等等。

本文提供的例子过程如下:

当lex遇到变量名时,yacc在符号表nameList中查找,如果有,则返回该变量名对应的结构体 struct list,若没有则在队尾创建一个。

当lex遇到数值型字符串时,将其转换为float型数字,返回给yacc。

当yacc查找到一个内置函数后,查找符号表nameList,通过找到的函数指针执行对应的函数。


---

当符号个数很多时,符号表nameList采用数组会使查询效率降低,采用哈希散列的方式比较好。

下面列出一个具体的例子来说明问题:


tarr.h

#define MAX 256
typedef double (*FUN)(double);
	
struct list {
	char *name;      /*保存变量名,本例中可能是函数名*/
	FUN fun;          /*保存函数指针,当yacc归约为一个函数名(name)时,查找nameList,将name对应的函数指针返回*/
	double value;   /*变量所赋的值,是函数名的情况下,该值忽略*/
} nameList[MAX];

struct list *getName(const char*);
void addFun(const char*,FUN);

词法分析程序:【test2.l】 

%{
#include "test2.tab.h"
#include "tarr.h"
%}

%option noyywrap

%%
[0-9]+("."[0-9]*)?        | 
([0-9]+)?"."[0-9]+        | 
[0-9]+("."[0-9]*)?[eE][+-]?[0-9]+ | 
([0-9]+)?"."[0-9]+[eE][+-]?[0-9]+ {
		yylval.dval = atof(yytext);  /*浮点数*/    /*当找到匹配的数字型字符串时,就将其转换为float型,并通过yylval变量传递给语法分析程序*/
		return NUMBER;
	}

[ \t]	;

[A-Za-z][A-Za-z0-9]*	{
		yylval.nameLST = getName(yytext);   /*变量名*/  /*对于一个变量名,先查找nameList中是否已经有这个变量,若没有,则添加进nameList中,参考语法分析中的函数getName */
		return NAME;
	}

"$"	{ return 0; /*结束符*/}

\n|.	return yytext[0];
%%

语法分析程序:【test2.y】

%{
#include "tarr.h"
#include "stdio.h"
#include "string.h"
#include "math.h"
%}

%union {
	double dval;
	struct list *nameLST;
}

%start list

%token <nameLST> NAME
%token <dval> NUMBER
%left '-' '+'                                 /*优先级声明  低--->高*/
%left '*' '/'
%nonassoc UMINUS

%type <dval> expr
%%
list:	
	|stmt '\n'
	|list stmt '\n'
	;

stmt:NAME '=' expr		{ $1->value = $3; }  /*给变量赋值*/ 
	|expr			{ printf("= %g\n", $1); } /*归约到底,输出结果*/
	;

expr:	expr'+' expr 	{ $$ = $1 + $3; }   /*$$是返回给':'左侧expr的值*/ 
	|expr '-' expr 	{ $$ = $1 - $3; }
	|expr '*' expr 	{ $$ = $1 * $3; }
	|expr '/' expr	{	
				if($3 == 0.0)
					yyerror("divided by zero\n");
				else
					$$ = $1 / $3;
			}

	|'-' expr %prec UMINUS	{ $$ = -$2; }
	|'(' expr ')'		{ $$ = $2; }
	|NUMBER		{$$=$1;}
	|NAME			{ $$ = $1->value; }      /*NAME本身($1)绑定的是一个struct list* */ 
	|NAME'('expr')'{
			if($1->fun){$$=($1->fun)($3);}   /*如果是函数,则执行这个函数*/
			else{printf("%s is not a function\n",$1->name);$$=0.0;}
		}
	;

%%

double sqrt(double n)   /*自定义的内置函数*/
{
	return sqrt(n);
}


int main(int argc,char *argv[])
{
	addFun("sqrt",sqrt);  /*添加自定义的内置函数*/
	yyparse();
	return 0;
}

struct list *getName(const char *s)
{
	struct list *p;
	for(p = nameList; p < &nameList[MAX]; p++) {
		if(p->name && !strcmp(p->name, s))   /*符号表中有这个变量名,则返回 */
			return p;
		if(!p->name) {
			p->name = strdup(s);    /*没有则创建一个*/
			return p;
		}
	}
	yyerror("not enough memory\n");
	return 0;
} 

void addFun(const char* name,FUN fun) 
{ 
     struct list *functionItem=getName(name); 
     functionItem->fun=fun;     
} 



yyerror(char *s)
{
	fprintf(stderr, "error: %s\n", s);
}

makefile文件:

#makfile
test1:test2.l test2.y
	bison -d test2.y
	flex test2.l
	cc -o $@ test2.tab.c lex.yy.c -lfl

运行结果:

frank@ubuntu:~/FlexBison$ make
bison -d test2.y
test2.y: conflicts: 4 shift/reduce
flex test2.l
cc -o test1 test2.tab.c lex.yy.c -lfl
frank@ubuntu:~/FlexBison$ ./test1
a=4
b=50
c=-(3+sqrt(a))+b/5.0
b=1.5
a+b+c
= 10.5
$
frank@ubuntu:~/FlexBison$ 

通过运行结果可以看到,有一个移进归约冲突,当遇到移进归约冲突时,Bison默认的处理方式是移进。

这个冲突是:

|NAME		
|NAME'('expr')'

感兴趣的可以改下。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值