小白说编译原理-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
相关文章推荐
- Docker实现跨主机容器实例网络通信(2)——利用OpenVSwitch构建多主机Docker网络
- navicat连接mysql
- 进程控制exec函数族
- GlobalSign 多域型(SNAs) SSL 证书
- 进制转换心得
- 第九周-实践项目
- JBoss最大连接数
- 查看Linux系统信息命令
- weblogic10上部署Hibernate应用的冲突解决方法
- 微信开发,access_token,时间上没有过期,但已失效的问题
- PHP+Jquery与ajax相结合实现下拉淡出瀑布流效果【无需插件】
- 链表的操作(纯C语言版)
- 《Motion Design for iOS》(十二)
- HDU 2668 Daydream(最长不重复子序列)
- 使用spring web.xml里的配置 - 请求处理器DispatcherServlet
- openwrt: Makefile 框架分析
- 沙盒,plist文件,偏好设置
- 链表排序——选择排序法(纯C语言版)
- Jquery的attr方法实现checkbox的选中状态所带来的问题解析
- 棋盘问题