bison 采用自底向上 (bottom-up) 的分析方法。它用到一个分析栈 (parser stack),关键有两个动作:
1. 移进 (shift)
读取的 token 移进到分析栈中。
2. 归约 (reduce)
当分析栈顶的 n 个符号匹配某规则的右端时,用该规则的左端取代之。
如:当规则为
vardef: INT ids ';'
;
若分析栈此时为 INT ids 时,当下一个读入的语素为 ';' 时,分析栈为 INT ids ';' 此时跟上面的规则符合,所以可以左端取代右端,即用 vardef 取代 INT ids ';'
自底向上算法要做的事情是,对于一个接一个读入的 token,何时移进,何时归约。LR(1) 中的 1表示,只需预读 1个语素,就可以确定是移进,还是归约。
在移进和归约的过程中,可能出现两类冲突。
1. 移进/归约冲突 (shift/reduce conflicts)
在某一时刻,可以移进,也可以归约。是选择移进,还是归约?这就是移进/归约冲突。这种冲突可以接受。
在出现移进/归约冲突时,bison 选择移进。
2. 归约/归约冲突 (Reduce/Reduce Conflicts)
当分析栈顶的符号序列可归约到多于一个规则时,选择哪一个规则?这就是归约/归约冲突。这种冲突不可接受。这是文法中的严重错误,必须修改文法。
在出现归约/归约冲突时,bison 选择归约到第一个匹配的规则。这十分冒险。
实例分析:
下面的例子都是根据这个文法规则的:
%%
stmt: vardef
| stmt_assign
| stmt_print
| stmt_if
| stmt_while
| stmt_block
| stmt_return
| error '\n' { yyerrok; } // 宏yyerrok跳过错误
;
stmt_assign: ID { symbol_use($1, k_VAR); }//注意:这里为$2
'=' expr ';' { code_assign($1, $4);}
;
stmt_print: PRINT expr ';' {code_print($2);}
;
vardef: INT ids ';'
;
ids: ID { symbol_def($1, k_VAR); }
| ids ',' ID { symbol_def($3, k_VAR); }
;
stmt_if: IF '(' expr ')' { code_cond($3); }
stmt
elsepart { code_if_end();}
;
elsepart:
| ELSE { code_else(); }
stmt
;
stmt_while: WHILE { code_while_begin(); }
'(' expr ')' { code_cond($4); }
stmt { code_while_end(); }
;
stmt_return: RETURN expr ';' { code_return($2); }
;
//expr: 冒号前的expr 为临时变量 $$
expr: NUM { $$ = $1; }
| ID { $$ = $1; symbol_use($1, k_VAR); }
| expr '+' expr { $$ = code_add($1, $3); }
| expr '-' expr { $$ = code_sub($1, $3); }
| expr '*' expr { $$ = code_mul($1, $3); }
| expr '/' expr { $$ = code_div($1, $3); }
| expr '<' expr { $$ = code_cmp($1, "<", $3); }
| expr LE expr { $$ = code_cmp($1, "<=", $3); }
| expr '>' expr { $$ = code_cmp($1, ">", $3); }
| expr GE expr { $$ = code_cmp($1, ">=", $3); }
| expr EQ expr { $$ = code_cmp($1, "==", $3); }
| expr NE expr { $$ = code_cmp($1, "!=", $3); }
| '-' expr %prec NEG { $$ = code_neg($2); }
| '(' expr ')' { $$ = $2; }
;
%%
while (k < n)
k = k * 2;
bison 分析如下:
词素 语素 动作 分析栈
while WHILE 移进 WHILE
( '(' 移进 WHILE '('
k ID 移进 WHILE '(' ID
归约 WHILE '(' expr
< '<' 移进 WHILE '(' expr '<'
n ID 移进 WHILE '(' expr '<' ID
归约 WHILE '(' expr '<' expr
归约 WHILE '(' expr
) ')' 移进 WHILE '(' expr ')'
k ID 移进 WHILE '(' expr ')' ID
= '=' 移进 WHILE '(' expr ')' ID '='
k ID 移进 WHILE '(' expr ')' ID '=' ID
归约 WHILE '(' expr ')' ID '=' expr
* '*' 移进 WHILE '(' expr ')' ID '=' expr '*'
2 NUM 移进 WHILE '(' expr ')' ID '=' expr '*' NUM
归约 WHILE '(' expr ')' ID '=' expr '*' exp
归约 WHILE '(' expr ')' ID '=' expr
; ';' 移进 WHILE '(' expr ')' ID '=' expr ';'
归约 WHILE '(' expr ')' stmt_assign
归约 WHILE '(' expr ')' stmt
归约 stmt_while
归约 stmt
基本做法就是移进一个语素时,查看此时的分析栈是否可以归约。
当不可以归约时,选择移进。
当可以归约时,同时还有查看文法规则,观察是否可以移进,若此时也可以移进,出现移进/归约冲突,选择移进。
若可以归约时,同时没有符合的文法规则移进时,选择归约。
分析栈在开始的时候为空,所以第一个语素 WHILE 为移进。
分析栈:WHILE
第一个词素移进之后,分析栈中存在WHILE,根据上面的文法规则:
stmt_while: WHILE { code_while_begin(); }
'(' expr ')' { code_cond($4); }
stmt { code_while_end(); }
;
下一个语素为'(', 符合此文法规则,所以移进。
分析栈:WHILE '('
此时分析栈中只存在WHILE '(', 查看文法规则可知,此时没有合适的规则可以归约。
所以词素k,即语素ID只能继续移进
分析栈:WHILE '(' ID
此时分析栈为 WHILE '(' ID ,由文法规则:
expr: NUM { $$ = $1; }
| ID { $$ = $1; symbol_use($1, k_VAR); }
...
可知,此时ID匹配此规则的右端,所以用该规则的expr左端取代之,即ID归约为expr
(归约定义:当分析栈顶的 n 个符号匹配某规则的右端时,用该规则的左端取代之。)
分析栈:WHILE '(' expr
根据文法规则 stmt_while, 下一个语素')', 应选择移进。
分析栈:WHILE '(' expr '<'
词素n,即语素ID,同上述的词素k一样原理先移进
分析栈:WHILE '(' expr '<' ID
根据文法规则:expr
此分析栈中的ID应该归约为expr
分析栈:WHILE '(' expr '<' expr
查看文法规则,根据文法规则:
expr: NUM { $$ = $1; }
...
...
| expr '<' expr { $$ = code_cmp($1, "<", $3); }
...
此时分析栈的 expr '<' expr 需归约为 expr
分析栈:WHILE '(' expr
此时分析栈不可归约,所以 ')'选择移进。
分析栈:WHILE '(' expr ')'
此时分析栈同样不可归约,所以词素k,即语素ID,选择移进。
分析栈:WHILE '(' expr ')' ID
此时分析栈中ID,根据文法规则expr和
stmt_assign: ID { symbol_use($1, k_VAR); }//注意:这里为$2
'=' expr ';' { code_assign($1, $4);}
;
此时分析栈的ID,可以归约为expr,同时也可以根据stmt_assign规则,继续选择移进
所以此时出现移进/归约冲突 (shift/reduce conflicts)
根据bison的处理方式,此时bison会选择继续移进,即此时的ID不进行归约
(为什么开始的词素k、n,即词素ID需要选择归约而不同这里一样选择移进呢?
因为前面的两个语素后面的语素都不符合文法规则stmt_assign,只符合expr规则,即只能归约而不可以继续移进)
根据上一步的分析,此时的 '=' 选择移进。
分析栈:WHILE '(' expr ')' ID '='
此时分析栈不可归约,所以词素k,即语素 ID 继续移进。
分析栈:WHILE '(' expr ')' ID '=' ID
此时分析栈中顶层 ID,由于只符合规则 expr,所以选择归约为 expr。
分析栈:WHILE '(' expr ')' ID '=' expr
此时分析栈不可归约,所以语素 '*' 继续移进。
分析栈:WHILE '(' expr ')' ID '=' expr '*'
此时分析栈不可归约,所以词素 2,即语素NUM继续移进。
分析栈:WHILE '(' expr ')' ID '=' expr '*' NUM
此时分析栈中的NUM,匹配符合文法规则 expr,所以此时NUM归约为expr。
分析栈:WHILE '(' expr ')' ID '=' expr '*' expr
此时分析栈中的 expr '*' expr ,匹配符合文法规则 expr,所以此时 expr '*' expr 归约为 expr。
分析栈:WHILE '(' expr ')' ID '=' expr
此时分析栈不可归约,所以语素';'继续移进。
分析栈:WHILE '(' expr ')' ID '=' expr ';'
此时分析栈中的 ID '=' expr ';' 匹配符合 stmt_assign 文法规则,所以此时 ID '=' expr ';' 归约为 stmt_assign。
分析栈:WHILE '(' expr ')' stmt_assign
此时分析栈中的 stmt_assign 匹配符合 stmt, 所以此时 stmt_assign 归约为 stmt
分析栈:WHILE '(' expr ')'stmt
此时分析栈中的 WHILE '(' expr ')'stmt 匹配符合文法规则 stmt_while, 所以此时 WHILE '(' expr ')'stmt 归约为 stmt_while
分析栈:stmt_while
此时分析栈中的 stmt_while 匹配符合 stmt, 所以此时 stmt_assign 归约为 stmt
分析栈:stmt
实例二:
y = 3 + x * 8;
bison 分析如下:
词素 语素 动作 分析栈
y ID 移进 ID
= '=' 移进 ID '='
3 NUM 移进 ID '=' NUM
归约 ID '=' exp
+ '+' 移进 ID '=' exp '+'
x ID 移进 ID '=' exp '+' ID
归约 ID '=' exp '+' exp
* '*' 移进 ID '=' exp '+' exp '*'
8 NUM 移进 ID '=' exp '+' exp '*' NUM
归约 ID '=' exp '+' exp '*' exp
归约 ID '=' exp '+' exp
归约 ID '=' exp
; ';' 移进 ID '=' exp ';'
归约 stmt_assign
归约 stmt
实例三:
print 4 * (x + 1);
bison 分析如下:
词素 语素 动作 分析栈
print PRINT 移进 PRINT
4 NUM 移进 PRINT NUM
归约 PRINT exp
* '*' 移进 PRINT exp '*'
( '(' 移进 PRINT exp '*' '('
x ID 移进 PRINT exp '*' '(' ID
归约 PRINT exp '*' '(' exp
+ '+' 移进 PRINT exp '*' '(' exp '+'
1 NUM 移进 PRINT exp '*' '(' exp '+' NUM
归约 PRINT exp '*' '(' exp '+' exp
归约 PRINT exp '*' '(' exp
) ')' 移进 PRINT exp '*' '(' exp ')'
归约 PRINT exp '*' exp
归约 PRINT exp
; ';' 移进 PRINT exp ';'
归约 stmt_print
归约 stmt