您的位置:首页 > 其它

小白说编译原理-3-yacc基本用法

2016-04-23 13:40 453 查看

前提说明

前面lex的例子说明了可以通过定义正规式的方式,实现一个词法分析器。 它主要是通过lex编译器将用户定义的正规式以及相应的action编译成对应的c++代码,交给c++编译器生成可执行程序exe。

同样的,应该可以定义语法规则,并由语法编译器将语法规则以及action编译成相应的c++代码,并执行。

这就是我们这节要说明的yacc。

yacc规则

声明部分

C++头文件,函数声明

文法记号的声明(类似于lex中词法token的声明),例如%token DIGIT

%%

翻译规则

%%

翻译规则主要文法产生式和对应的动作组成。

例如 expr : expr ‘+’ term {$$ = $1 + $2}

其他c++语言的代码

yacc示例,简单计算器

%{

%}

%name yacc
// class definition
{
// place any extra class members here
virtual int yygettoken();
}
// constructor
{
// place any extra initialisation code here
}

// destructor
{
// place any extra cleanup code here
}

// place any declarations here
%include {
#ifndef YYSTYPE
#define YYSTYPE double
#endif
}

%%

lines : lines expr '\n' {printf("%f\n", $2);}
| lines '\n'
|//empty line
;

expr : expr '+' term {$$ = $1 + $3;}
| expr '-' term {$$ = $1 - $3;}
| term
;
term : term '*' factor {$$ = $1 * $3;}
| term '/' factor {$$ = $1 / $3;}
| factor {$$=$1;}
;
factor : '(' expr ')'   {$$=$2;}
| DIGIT
;
DIGIT	:	'0'				{ $$ = 0.0; }
|	'1'				{ $$ = 1.0; }
|	'2'				{ $$ = 2.0; }
|	'3'				{ $$ = 3.0; }
|	'4'				{ $$ = 4.0; }
|	'5'				{ $$ = 5.0; }
|	'6'				{ $$ = 6.0; }
|	'7'				{ $$ = 7.0; }
|	'8'				{ $$ = 8.0; }
|	'9'				{ $$ = 9.0; }
;
%%

int YYPARSERNAME::yygettoken()
{
return getchar();
}
int main()
{
//create a lexer, and call the lex function.
//it will read from stdin and parser the tokens.
int n = 1;

YYPARSERNAME parser;
if (parser.yycreate())
{
n = parser.yyparse();
}
return n;
}


计算器解释

添加gettoken函数

virtual int yygettoken();

expr : expr ‘+’ term {$$ = $1 + $3;}

计算加法,$$表示当返回值,\$1表示第一个参数,\$3表示第三个参数

main

创建parser语法分析器,调用gettoken分析,即可。

巧妙的地方

1,支持多行

多行的关键在于lines的表示,lines既可以表示为空,又可以表示为lines expr ‘\n’

2,支持算符优先级

term和factor的使用,先使用term,再使用factor

3,支持括号

最终的factor因子的表示,既可以表示单个数字,还可以表示(expr)。

3,只支持单个数字的加减乘除

这是由于对于DIGIT的解释只支持单个数字

更新版本支持浮点数

%{
#include <stdio.h>
%}

%name yacc
// class definition
{
// place any extra class members here
virtual int yygettoken();
double yyvalue;
}
// constructor
{
// place any extra initialisation code here
}

// destructor
{
// place any extra cleanup code here
}

// place any declarations here
%include {
#ifndef YYSTYPE
#define YYSTYPE double
#endif
}

%token NUMBER

%%

lines : lines expr '\n' {printf("%f\n", $2);}
| lines '\n'
|//empty line
;

expr : expr '+' term {$$ = $1 + $3;}
| expr '-' term {$$ = $1 - $3;}
| term
;
term : term '*' factor {$$ = $1 * $3;}
| term '/' factor {$$ = $1 / $3;}
| factor {$$=$1;}
;
factor : '(' expr ')'   {$$=$2;}
| NUMBER {$$=yyvalue;}
;
%%

int YYPARSERNAME::yygettoken()
{
int c;
double value;
while((c = getchar())  == ' ');//jump the empty

//here not the blank
if(isdigit(c) || c == '.'){
ungetc(c, stdin);
cin>>value;
yyvalue = value;
return NUMBER;
}

return c;
}
int main()
{
//create a lexer, and call the lex function.
//it will read from stdin and parser the tokens.
int n = 1;

YYPARSERNAME parser;
if (parser.yycreate())
{
n = parser.yyparse();
}
return n;
}


修改的地方

删除了DIGIT采用语法分析器进行的表达,过去gettoken直接返回char型字符,由DIGIT或者+,-等进行语法翻译。现在使用一个token标记NUMBER, 它代表一个数字。这个NUMBER标记是由对应的词法分析gettoken进行返回的。

gettoken中,如果遇到数字,则使用cin方式读取double型变量,保存到变量yylvalue中,这样当语法分析的过程中,遇到number,将yylvalue赋值给当前返回即可,即可以读取到浮点值。

为了更好的适应性,词法分析中还进行了跳过空格的操作,对于非数字不进行解析,直接返回。

本人lpstudy,转载请注明出处: http://blog.csdn.net/lpstudy/article/details/51225953
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: