14.2 第 0.5 版

在第 0.1 版的基础上升级,增加函数定义及调用语句、注释等功能,一共有 5 个文件:

词法分析文件: 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. void unterminate_string();
  8. #define _DUPTEXT {yylval = strdup(yytext);}
  9. %}
  10.  
  11. /* note \042 is '"' */
  12. WHITESPACE ([ \t\r\a]+)
  13. SINGLE_COMMENT1 ("//"[^\n]*)
  14. SINGLE_COMMENT2 ("#"[^\n]*)
  15. OPERATOR ([+*-/%=,;!<>(){}])
  16. INTEGER ([0-9]+)
  17. IDENTIFIER ([_a-zA-Z][_a-zA-Z0-9]*)
  18. UNTERM_STRING (\042[^\042\n]*)
  19. STRING (\042[^\042\n]*\042)
  20.  
  21. %%
  22.  
  23. \n { cur_line++; }
  24. {WHITESPACE} { /* ignore every whitespace */ }
  25. {SINGLE_COMMENT1} { /* skip for single line comment */ }
  26. {SINGLE_COMMENT2} { /* skip for single line comment */ }
  27.  
  28. {OPERATOR} { return yytext[0]; }
  29. "int" { return T_Int; }
  30. "void" { return T_Void; }
  31. "return" { return T_Return; }
  32. "print" { return T_Print; }
  33.  
  34. {INTEGER} { _DUPTEXT return T_IntConstant; }
  35. {STRING} { _DUPTEXT return T_StringConstant; }
  36. {IDENTIFIER} { _DUPTEXT return T_Identifier; }
  37.  
  38. {UNTERM_STRING} { unterminate_string(); }
  39. . { unrecognized_char(yytext[0]); }
  40.  
  41. %%
  42.  
  43. int yywrap(void) {
  44. return 1;
  45. }
  46.  
  47. void unrecognized_char(char c) {
  48. char buf[32] = "Unrecognized character: ?";
  49. buf[24] = c;
  50. yyerror(buf);
  51. }
  52.  
  53. void unterminate_string() {
  54. yyerror("Unterminate string constant");
  55. }
  56.  
  57. void yyerror(const char *msg) {
  58. fprintf(stderr, "Error at line %d:\n\t%s\n", cur_line, msg);
  59. exit(-1);
  60. }

语法分析文件: 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_Int T_Void T_Return T_Print T_IntConstant
  9. %token T_StringConstant T_Identifier
  10.  
  11. %left '+' '-'
  12. %left '*' '/'
  13. %right U_neg
  14.  
  15. %%
  16.  
  17. Program:
  18. /* empty */ { /* empty */ }
  19. | Program FuncDecl { /* empty */ }
  20. ;
  21.  
  22. FuncDecl:
  23. RetType FuncName '(' Args ')' '{' VarDecls Stmts '}'
  24. { printf("ENDFUNC\n\n"); }
  25. ;
  26.  
  27. RetType:
  28. T_Int { /* empty */ }
  29. | T_Void { /* empty */ }
  30. ;
  31.  
  32. FuncName:
  33. T_Identifier { printf("FUNC @%s:\n", $1); }
  34. ;
  35.  
  36. Args:
  37. /* empty */ { /* empty */ }
  38. | _Args { printf("\n\n"); }
  39. ;
  40.  
  41. _Args:
  42. T_Int T_Identifier { printf("arg %s", $2); }
  43. | _Args ',' T_Int T_Identifier
  44. { printf(", %s", $4); }
  45. ;
  46.  
  47. VarDecls:
  48. /* empty */ { /* empty */ }
  49. | VarDecls VarDecl ';' { printf("\n\n"); }
  50. ;
  51.  
  52. VarDecl:
  53. T_Int T_Identifier { printf("var %s", $2); }
  54. | VarDecl ',' T_Identifier
  55. { printf(", %s", $3); }
  56. ;
  57.  
  58. Stmts:
  59. /* empty */ { /* empty */ }
  60. | Stmts Stmt { /* empty */ }
  61. ;
  62.  
  63. Stmt:
  64. AssignStmt { /* empty */ }
  65. | PrintStmt { /* empty */ }
  66. | CallStmt { /* empty */ }
  67. | ReturnStmt { /* empty */ }
  68. ;
  69.  
  70. AssignStmt:
  71. T_Identifier '=' Expr ';'
  72. { printf("pop %s\n\n", $1); }
  73. ;
  74.  
  75. PrintStmt:
  76. T_Print '(' T_StringConstant PActuals ')' ';'
  77. { printf("print %s\n\n", $3); }
  78. ;
  79.  
  80. PActuals:
  81. /* empty */ { /* empty */ }
  82. | PActuals ',' Expr { /* empty */ }
  83. ;
  84.  
  85. CallStmt:
  86. CallExpr ';' { printf("pop\n\n"); }
  87. ;
  88.  
  89. CallExpr:
  90. T_Identifier '(' Actuals ')'
  91. { printf("$%s\n", $1); }
  92. ;
  93.  
  94. Actuals:
  95. /* empty */ { /* empty */ }
  96. | Expr PActuals { /* empty */ }
  97. ;
  98.  
  99. ReturnStmt:
  100. T_Return Expr ';' { printf("ret ~\n\n"); }
  101. | T_Return ';' { printf("ret\n\n"); }
  102. ;
  103.  
  104. Expr:
  105. Expr '+' Expr { printf("add\n"); }
  106. | Expr '-' Expr { printf("sub\n"); }
  107. | Expr '*' Expr { printf("mul\n"); }
  108. | Expr '/' Expr { printf("div\n"); }
  109. | '-' Expr %prec U_neg { printf("neg\n"); }
  110. | T_IntConstant { printf("push %s\n", $1); }
  111. | T_Identifier { printf("push %s\n", $1); }
  112. | CallExpr { /* empty */ }
  113. | '(' Expr ')' { /* empty */ }
  114. ;
  115.  
  116. %%
  117.  
  118. int main() {
  119. return yyparse();
  120. }

makefile 文件: makefile, 和第 0.1 版本中唯一不同的只有 “python pysim.py $< -a” 那一行有一个 “-a”

测试文件: test.c

  1. // tiny c test file
  2.  
  3. int main() {
  4. int a, b, c, d;
  5.  
  6. c = 2;
  7. d = c * 2;
  8.  
  9. a = sum(c, d);
  10. b = sum(a, d);
  11. print("c = %d, d = %d", c, d);
  12. print("a = sum(c, d) = %d, b = sum(a, d) = %d", a, b);
  13.  
  14. return 0;
  15. }
  16.  
  17. int sum(int a, int b) {
  18. int c, d;
  19. return a + b;
  20. }

Pcode 模拟器: pysim.py ,已经在第 4 章中介绍了。

这个版本在第 0.1 版本的基础上,进行了以下扩充:

词法分析文件中:

增加了 T_Void 和 T_Return 类型的 token ,以及相应的正则表达式;

增加了单行注释的过滤功能;增加了一个错误处理函数: unterminate_string ,该函数可以检查出未结束的字符串(不匹配的双引号)的词法错误。

语法分析文件中:

增加了 Program, FuncDecl, Args, Actuals, CallExpr 等非终结符以及相应的产生式,请注意各产生式的折叠顺序以及相应的 Pcode 生成顺序。

makefile 里面是编译和测试这个程序的命令,内容和第 0.1 版的基本一样,但增加了一些变量以便于扩充,另外,”python pysim.py…” 那一行最后的命令行参数是 “-a” 。在终端输入 make 后,将编译生成可执行文件 tcc ,然后输入 make test ,(相当于 ”./tcc < test.c > test.asm” ) ,将输出 test.asm 文件,内容如下:

  1. FUNC @main:
  2. var a, b, c, d
  3.  
  4. push 2
  5. pop c
  6.  
  7. push c
  8. push 2
  9. mul
  10. pop d
  11.  
  12. push c
  13. push d
  14. $sum
  15. pop a
  16.  
  17. push a
  18. push d
  19. $sum
  20. pop b
  21.  
  22. push c
  23. push d
  24. print "c = %d, d = %d"
  25.  
  26. push a
  27. push b
  28. print "a = sum(c, d) = %d, b = sum(a, d) = %d"
  29.  
  30. push 0
  31. ret ~
  32.  
  33. ENDFUNC
  34.  
  35. FUNC @sum:
  36. arg a, b
  37.  
  38. var c, d
  39.  
  40. push a
  41. push b
  42. add
  43. ret ~
  44.  
  45. ENDFUNC

可以看出 test.c 文件里的所有语句都被转换成相应的 Pcode 了。再用 Pcode 模拟器运行一下这些 Pcode ,在终端输入 “make simulate” (相当于 “python pysim.py test.asm -a” ,注意最后有一个 “-a” ) ,将输出:

  1. c = 2, d = 4
  2. a = sum(c, d) = 6, b = sum(a, d) = 10

有兴趣的读者还可以使用 “python pysim.py test.asm -da” 来逐句运行一下这个 Pcode 文件。