简介
本章依然专注于使用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