標籤:
簡介
本章依然專註於使用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}+)?
相當於是給一些經常使用的Regex起一個別名,然後利用別名即可以構造更複雜的Regex。例如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
小白說編譯原理-5-變數支援計算機