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

简介

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

运行效果

小白说编译原理-5-变量支持计算器_第1张图片

本人lpstudy,转载请注明出处: http://blog.csdn.net/lpstudy/article/details/51328851

你可能感兴趣的:(编译原理)