Bison 移进-归约分析

    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

 

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