您的位置:首页 > 其它

小白说编译原理-5-变量支持计算器

2016-05-06 09:25 393 查看

简介

本章依然专注于使用yacc实现计算器,主要的特点是给算术运算增加变量支持。

模块拆分

它主要分为3个模块

1. lex词法分析器

2. yacc语法分析器

3. 符号表

功能描述

1. lex词法分析器

正规式的定义如下:

delim [ \t]
ws    {delim}+
letter [a-zA-Z]
digit [0-9]
id    {letter}({letter}|{digit})*
/* can support 12.34 */
number {digit}+(\.{digit}+)?


相当于是给一些经常使用的正则表达式起一个别名,然后利用别名即可以构造更复杂的正则表达式。例如id标示符是由letter以及digit的组合形成的,表示必须以字母开头,后面可以字母和数字的任意组合。

词法分析器的动作定义(转换规则)

{ws} {/* do nothing */}
"int"  {print_token(INT, yytext); return INT;}
"double"  {print_token(DOUBLE, yytext);}
"char"  {print_token(CHAR, yytext);}

"+"         {print_token(PLUS, yytext); return PLUS;}
"-"         {print_token(MINUS, yytext); return MINUS;}
"*"         {print_token(TIMES, yytext); return TIMES;}
"/"         {print_token(OVER, yytext); return OVER;}
"("         {return LP;}
")"         {return RP;}
"\n"        {return EOL;}
"="         {return ASSIGN;}
{id}        {
int p = sym_table.lookup(yytext);
if(p == -1){//not find
p = sym_table.insert(yytext);//insert the default value 0.0
}
yylval = p;//return the position
return ID;
}
{number}    {yylval = atof(yytext);return NUMBER;}//yylval保存数字的值,NUMBER只是token标记
"//".*      {return COMMENT;}
"."         {printf("Mystery character %s\n", yytext); }


上述每一组代表一个转换规则,对于一个规则,它的左边代表要匹配的模式串,右边表示要执行的词法动作。例如对于规则 “+” {print_token(PLUS, yytext); return PLUS;}, 它表示在解析出”+”后,它将执行print_token,并返回PLUS标记(PLUS在yacc中定义)。

运算符识别:

从上面定义的规则可以看出,此词法分析支持+,-,*,/,(,)这些基本的算术运算。

number的识别:

{number} {yylval = atof(yytext);return NUMBER;} //yylval保存数字的值,NUMBER只是token标记

不再像(小白说编译原理-3)那样,将number的识别放在yacc中,利用cin的方式得到数字,而是利用词法分析的正规式的表达,将得到的结果赋值给yylval**(yylval = atof(yytext))**,然后返回NUMBER标记。

标示符的识别:

这个规则的执行动作比较复杂,它利用了一个符号表的模块(后面讲述),当识别到标示符后,它向符号表中查找(没找到,就插入一条)并返回它的位置信息。

{id}        {
int p = sym_table.lookup(yytext);
if(p == -1){//not find
p = sym_table.insert(yytext);//insert the default value 0.0
}
yylval = p;//return the position
return ID;
}


yytext是标示符的字符串,p保存它在符号表的位置,当找到后将位置信息赋值给yylval,同时返回标示符类型ID。 yylval和ID信息都会在yacc实现中用到。

2. yacc语法分析器

token定义如下:

%token NUMBER ID
%token PLUS MINUS TIMES OVER
%token LP RP EOL COMMENT
%TOKEN INT DOUBLE CHAR
%token ASSIGN

%left PLUS MINUS
%left TIMES OVER
%right UMINUS


上述定义的token在lex词法分析器中使用。

yacc语法转换规则定义如下:

lines   :   lines expr EOL  { printf("%g\n", $2); }
|   lines EOL
|   lines COMMENT
|
;

expr    :   expr PLUS expr  { $$ = $1 + $3; }
|	expr MINUS expr	{ $$ = $1 - $3; }
|	expr TIMES expr	{ $$ = $1 * $3; }
|	expr OVER expr	{ $$ = $1 / $3; }
|	LP expr RP	{ $$ = $2; }
|	'-' expr %prec UMINUS	{ $$ = -$2; }
|	NUMBER {$$=$1;} //$$=$1 can be ignored
|   ID {$$ = sym_table.getValue($1);}//get value from sym_table
|   ID ASSIGN expr {sym_table.setValue($1, $3); $$=$3; }//modify the value


与词法分析器类似的是依然是根据规则是否匹配,然后执行相应的语法动作,具体请参见小白说编译原理-3(http://blog.csdn.net/lpstudy/article/details/51225953)中的yacc的描述。

数字的动作:

NUMBER {$$=$1;} //$$=$1 can be ignored

它在识别出数字之后,将此数字的值(词法分析器得到的yylval)赋值给$$,$$表示当前结果。

注意$1表示是第一个对应的语法规则中第一个token的value,同理,对于$2,$3也是如此,表示第2个,第3个token的value。 而对于本规则,$1就是NUMBER的value。这个value是在词法分析中赋值的yylval(yylval=atof(yytext))。

标示符的动作:

标示符的动作分为两类,一种是赋值动作,一种是取值动作。 例如a=2,这表示给变量a赋值为2, 然后a+4表示将a变量加上4,因此结果为6. 简单起见,本程序不考虑变量的定义操作(例如c语言中的int a;),所有的变量默认值为0.0,可直接使用,可使用赋值运算修改它的值。

赋值动作

ID ASSIGN expr {sym_table.setValue($1, $3); $$\=\$3; }//modify the value

上述规则表明如果遇到a=2这样的输入后,会执行符号表的setValue方法,\$1表示词法分析器返回ID时候设置的yylval值(标示符的位置), \$3表示语法分析中expr的结果,这样setValue就会将\$1位置上的标示符设置为\$3.

取值动作

ID {\$\$ = sym_table.getValue(\$1);}//get value from sym_table

上述规则说明:首先词法分析器返回的ID标示符,同时\$1中存储标示符的位置,根据位置取出相应的value并赋值给\$\$当前值。

lex和yacc协同策略

lex传递到yacc两个重要的信息,类型和值, 类型由return实现,值由yylval存储。

yacc中的语法规则碰到的token标记是由lex的return得到的,而通过$number取值实际上取出的是lex中的yylval值。

$1和$3这种是根据前面规则中标记的位置来确定1和3的,它们的值或者由词法分析器通过yylval给出,或者由赋值$$得到。例如 ID ASSIGN expr中expr的值为$3,它不是由词法分析器给出的,而是使用了expr进行分析时候得到的$$值。

3. 符号表

符号表是支撑词法和语法分析的数据保存区。 词法分析过程中遇到id标示符,需要将其插入到符号表中,并设置默认值为0.0, 语法分析过程中遇到取值id标示符,使用符号表提供的取值函数得到符号的值,当遇到赋值id,则更新符号表中对应符号的值。示例代码,使用数组进行了简单实现。

#include <iostream>
#include <map>
#include <vector>
#include "yacc.h"
#include "lex.h"
using namespace std;

struct Node
{
string name;
double value;
};
class SymTable
{
public:
SymTable(){

}
public:
int lookup(const string& name){
for (int i = 0; i < idValueTable.size(); ++i)
{
if(idValueTable[i].name.compare(name) == 0){
return i;
}
}
return -1;//not find
}

int insert(const string& name){//when parser x=2 (current we get x)
Node node;
node.name = name;
node.value = 0.0;
idValueTable.push_back(node);
return idValueTable.size()-1;
}
void setValue(int pos, double value){//when parser x=2 (current we get x)
idValueTable[pos].value = value;
}
double getValue(int pos){
return idValueTable[pos].value;
}
private:
vector<Node> idValueTable;
};
extern SymTable sym_table;


运行效果



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