13.3 bison 示例 2

再来看一个稍微复杂一点的示例,一共有 4 个文件:

词法分析文件: scanner.l

  1. %{
  2. #define YYSTYPE char *
  3. #include "y.tab.h"
  4. int cur_line = 1;
  5. void yyerror(const char *msg);
  6. void unrecognized_char(char c);
  7. %}
  8.  
  9. OPERATOR [-/+*()=;]
  10. INTEGER [0-9]+
  11. IDENTIFIER [_a-zA-Z][_a-zA-Z0-9]*
  12. WHITESPACE [ \t]*
  13.  
  14. %%
  15. {OPERATOR} { return yytext[0]; }
  16. {INTEGER} { yylval = strdup(yytext); return T_IntConstant; }
  17. {IDENTIFIER} { yylval = strdup(yytext); return T_Identifier; }
  18. {WHITESPACE} { /* ignore every whitespcace */ }
  19. \n { cur_line++; }
  20. . { unrecognized_char(yytext[0]); }
  21. %%
  22.  
  23. int yywrap(void) {
  24. return 1;
  25. }
  26.  
  27. void unrecognized_char(char c) {
  28. char buf[32] = "Unrecognized character: ?";
  29. buf[24] = c;
  30. yyerror(buf);
  31. }
  32.  
  33. void yyerror(const char *msg) {
  34. printf("Error at line %d:\n\t%s\n", cur_line, msg);
  35. exit(1);
  36. }

语法分析文件: parser.y

  1. %{
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. void yyerror(const char*);
  5. #define YYSTYPE char *
  6. %}
  7.  
  8. %token T_IntConstant T_Identifier
  9.  
  10. %left '+' '-'
  11. %left '*' '/'
  12. %right U_neg
  13.  
  14. %%
  15.  
  16. S : Stmt
  17. | S Stmt
  18. ;
  19.  
  20. Stmt: T_Identifier '=' E ';' { printf("pop %s\n\n", $1); }
  21. ;
  22.  
  23. E : E '+' E { printf("add\n"); }
  24. | E '-' E { printf("sub\n"); }
  25. | E '*' E { printf("mul\n"); }
  26. | E '/' E { printf("div\n"); }
  27. | '-' E %prec U_neg { printf("neg\n"); }
  28. | T_IntConstant { printf("push %s\n", $1); }
  29. | T_Identifier { printf("push %s\n", $1); }
  30. | '(' E ')' { /* empty */ }
  31. ;
  32.  
  33. %%
  34.  
  35. int main() {
  36. return yyparse();
  37. }

makefile 文件: makefile

  1. CC = gcc
  2. OUT = tcc
  3. OBJ = lex.yy.o y.tab.o
  4. SCANNER = scanner.l
  5. PARSER = parser.y
  6.  
  7. build: $(OUT)
  8.  
  9. run: $(OUT)
  10. ./$(OUT) < test.c > test.asm
  11.  
  12. clean:
  13. rm -f *.o lex.yy.c y.tab.c y.tab.h y.output $(OUT)
  14.  
  15. $(OUT): $(OBJ)
  16. $(CC) -o $(OUT) $(OBJ)
  17.  
  18. lex.yy.c: $(SCANNER) y.tab.c
  19. flex $<
  20.  
  21. y.tab.c: $(PARSER)
  22. bison -vdty $<

测试文件: test.c

  1. a = 1 + 2 * ( 2 + 2 );
  2. b = c + d;
  3. e = f + 7 * 8 / 9;

这个示例对第一个示例进行了一些扩充。

词法分析文件中:

增加了 T_Identifier 类型的 token ,整数类型的 token 名改为了 T_IntConstant ;

增加了一个全局变量 cur_line ,表示扫描所在的位置(行);

增加了错误处理函数 unrecognized_char 和 yyerror 函数,前者在扫描到非法字符时被执行,并将相关信息传递给后者,后者则打印出错误信息以及当前位置,并退出程序;

第二行增加了 define YYSTYPE char ,上一节中说过了,全局变量 yylval 的类型是 YYSTYPE ,而 YYSTYPE 默认为 int ,添加了这一行后, YYSTYPE 变成了 char ,这样 yylval 的类型就变成了 char 了;

当扫描到完整的整数或标识符时, yylval = strdup(yytext) 被执行,扫描到的字符串被拷贝一份传给了 yylval ,到语法分析时,这个字符串将被绑定到终结符 T_IntConstant 或 T_Identifier 上面。

语法分析文件中:

语法规则有所扩充,增加了终结符 T_Identifier 和非终结符 Stmt , Stmt 可表示一个赋值表达式;

符号优先级那一段,增加了 %left U_neg ,表示增加一个符号,该符号为左结合,且优先级在 和 / 之上,这个符号在 ‘-‘ E %prec U_neg 那一行被使用,%prec 命令可以给一个产生式定义一个优先级,”%prec U_neg” 表示这个产生式的优先级和 U_neg 一样(而不是等于产生式中最右边的、定义了优先级的符号的优先级),当出现 shift/reduce 冲突时,将利用 U_neg 的优先级和 lookahead 的优先级比较,然后根据比较结果来选择动作;

大部分产生式的后面的 action 都是执行一个 printf 函数,表示这些产生式被 reduce 时所打印的字符串。

makefile 里面是编译这个程序的命令,在终端输入 make 后,将编译生成可执行文件 tcc ,然后用 test.c 文件来测试一下:

  1. ./tcc < test.c > test.asm

test.asm 文件中的输出内容如下:

  1. push 1
  2. push 2
  3. push 2
  4. push 2
  5. add
  6. mul
  7. add
  8. pop a
  9.  
  10. push c
  11. push d
  12. add
  13. pop b
  14.  
  15. push f
  16. push 7
  17. push 8
  18. mul
  19. push 9
  20. div
  21. add
  22. pop e

可以看出 test.c 文件里的所有赋值表达式都被转换成相应的 Pcode 了,是不是很神奇?这个程序相当于我们的 TinyC 前端的一个雏形了。在这个雏形前端中请注意源文件的解析过程中各产生式折叠的先后顺序,中间代码就是按照产生式折叠的顺序生成的。

第 13 章完