14.3 第 1.0 版

继续在第 0.5 版的基础上升级,增加 if 和 while 语句、比较运算符和逻辑运算符以及 readint 命令,就形成了完整的 TinyC 前端。一共有 7 个文件:

词法分析文件: 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. "readint" { return T_ReadInt; }
  34. "while" { return T_While; }
  35. "if" { return T_If; }
  36. "else" { return T_Else; }
  37. "break" { return T_Break; }
  38. "continue" { return T_Continue; }
  39. "<=" { return T_Le; }
  40. ">=" { return T_Ge; }
  41. "==" { return T_Eq; }
  42. "!=" { return T_Ne; }
  43. "&&" { return T_And; }
  44. "||" { return T_Or; }
  45.  
  46. {INTEGER} { _DUPTEXT return T_IntConstant; }
  47. {STRING} { _DUPTEXT return T_StringConstant; }
  48. {IDENTIFIER} { _DUPTEXT return T_Identifier; }
  49.  
  50. {UNTERM_STRING} { unterminate_string(); }
  51. . { unrecognized_char(yytext[0]); }
  52.  
  53. %%
  54.  
  55. int yywrap(void) {
  56. return 1;
  57. }
  58.  
  59. void unrecognized_char(char c) {
  60. char buf[32] = "Unrecognized character: ?";
  61. buf[24] = c;
  62. yyerror(buf);
  63. }
  64.  
  65. void unterminate_string() {
  66. yyerror("Unterminate string constant");
  67. }
  68.  
  69. void yyerror(const char *msg) {
  70. fprintf(stderr, "Error at line %d:\n\t%s\n", cur_line, msg);
  71. exit(-1);
  72. }

语法分析文件: parser.y

  1. %{
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. void yyerror(const char*);
  5. #define YYSTYPE char *
  6.  
  7. int ii = 0, itop = -1, istack[100];
  8. int ww = 0, wtop = -1, wstack[100];
  9.  
  10. #define _BEG_IF {istack[++itop] = ++ii;}
  11. #define _END_IF {itop--;}
  12. #define _i (istack[itop])
  13.  
  14. #define _BEG_WHILE {wstack[++wtop] = ++ww;}
  15. #define _END_WHILE {wtop--;}
  16. #define _w (wstack[wtop])
  17.  
  18. %}
  19.  
  20. %token T_Int T_Void T_Return T_Print T_ReadInt T_While
  21. %token T_If T_Else T_Break T_Continue T_Le T_Ge T_Eq T_Ne
  22. %token T_And T_Or T_IntConstant T_StringConstant T_Identifier
  23.  
  24. %left '='
  25. %left T_Or
  26. %left T_And
  27. %left T_Eq T_Ne
  28. %left '<' '>' T_Le T_Ge
  29. %left '+' '-'
  30. %left '*' '/' '%'
  31. %left '!'
  32.  
  33. %%
  34.  
  35. Program:
  36. /* empty */ { /* empty */ }
  37. | Program FuncDecl { /* empty */ }
  38. ;
  39.  
  40. FuncDecl:
  41. RetType FuncName '(' Args ')' '{' VarDecls Stmts '}'
  42. { printf("ENDFUNC\n\n"); }
  43. ;
  44.  
  45. RetType:
  46. T_Int { /* empty */ }
  47. | T_Void { /* empty */ }
  48. ;
  49.  
  50. FuncName:
  51. T_Identifier { printf("FUNC @%s:\n", $1); }
  52. ;
  53.  
  54. Args:
  55. /* empty */ { /* empty */ }
  56. | _Args { printf("\n\n"); }
  57. ;
  58.  
  59. _Args:
  60. T_Int T_Identifier { printf("\targ %s", $2); }
  61. | _Args ',' T_Int T_Identifier
  62. { printf(", %s", $4); }
  63. ;
  64.  
  65. VarDecls:
  66. /* empty */ { /* empty */ }
  67. | VarDecls VarDecl ';' { printf("\n\n"); }
  68. ;
  69.  
  70. VarDecl:
  71. T_Int T_Identifier { printf("\tvar %s", $2); }
  72. | VarDecl ',' T_Identifier
  73. { printf(", %s", $3); }
  74. ;
  75.  
  76. Stmts:
  77. /* empty */ { /* empty */ }
  78. | Stmts Stmt { /* empty */ }
  79. ;
  80.  
  81. Stmt:
  82. AssignStmt { /* empty */ }
  83. | PrintStmt { /* empty */ }
  84. | CallStmt { /* empty */ }
  85. | ReturnStmt { /* empty */ }
  86. | IfStmt { /* empty */ }
  87. | WhileStmt { /* empty */ }
  88. | BreakStmt { /* empty */ }
  89. | ContinueStmt { /* empty */ }
  90. ;
  91.  
  92. AssignStmt:
  93. T_Identifier '=' Expr ';'
  94. { printf("\tpop %s\n\n", $1); }
  95. ;
  96.  
  97. PrintStmt:
  98. T_Print '(' T_StringConstant PActuals ')' ';'
  99. { printf("\tprint %s\n\n", $3); }
  100. ;
  101.  
  102. PActuals:
  103. /* empty */ { /* empty */ }
  104. | PActuals ',' Expr { /* empty */ }
  105. ;
  106.  
  107. CallStmt:
  108. CallExpr ';' { printf("\tpop\n\n"); }
  109. ;
  110.  
  111. CallExpr:
  112. T_Identifier '(' Actuals ')'
  113. { printf("\t$%s\n", $1); }
  114. ;
  115.  
  116. Actuals:
  117. /* empty */ { /* empty */ }
  118. | Expr PActuals { /* empty */ }
  119. ;
  120.  
  121. ReturnStmt:
  122. T_Return Expr ';' { printf("\tret ~\n\n"); }
  123. | T_Return ';' { printf("\tret\n\n"); }
  124. ;
  125.  
  126. IfStmt:
  127. If TestExpr Then StmtsBlock EndThen EndIf
  128. { /* empty */ }
  129. | If TestExpr Then StmtsBlock EndThen Else StmtsBlock EndIf
  130. { /* empty */ }
  131. ;
  132.  
  133. TestExpr:
  134. '(' Expr ')' { /* empty */ }
  135. ;
  136.  
  137. StmtsBlock:
  138. '{' Stmts '}' { /* empty */ }
  139. ;
  140.  
  141. If:
  142. T_If { _BEG_IF; printf("_begIf_%d:\n", _i); }
  143. ;
  144.  
  145. Then:
  146. /* empty */ { printf("\tjz _elIf_%d\n", _i); }
  147. ;
  148.  
  149. EndThen:
  150. /* empty */ { printf("\tjmp _endIf_%d\n_elIf_%d:\n", _i, _i); }
  151. ;
  152.  
  153. Else:
  154. T_Else { /* empty */ }
  155. ;
  156.  
  157. EndIf:
  158. /* empty */ { printf("_endIf_%d:\n\n", _i); _END_IF; }
  159. ;
  160.  
  161. WhileStmt:
  162. While TestExpr Do StmtsBlock EndWhile
  163. { /* empty */ }
  164. ;
  165.  
  166. While:
  167. T_While { _BEG_WHILE; printf("_begWhile_%d:\n", _w); }
  168. ;
  169.  
  170. Do:
  171. /* empty */ { printf("\tjz _endWhile_%d\n", _w); }
  172. ;
  173.  
  174. EndWhile:
  175. /* empty */ { printf("\tjmp _begWhile_%d\n_endWhile_%d:\n\n",
  176. _w, _w); _END_WHILE; }
  177. ;
  178.  
  179. BreakStmt:
  180. T_Break ';' { printf("\tjmp _endWhile_%d\n", _w); }
  181. ;
  182.  
  183. ContinueStmt:
  184. T_Continue ';' { printf("\tjmp _begWhile_%d\n", _w); }
  185. ;
  186.  
  187. Expr:
  188. Expr '+' Expr { printf("\tadd\n"); }
  189. | Expr '-' Expr { printf("\tsub\n"); }
  190. | Expr '*' Expr { printf("\tmul\n"); }
  191. | Expr '/' Expr { printf("\tdiv\n"); }
  192. | Expr '%' Expr { printf("\tmod\n"); }
  193. | Expr '>' Expr { printf("\tcmpgt\n"); }
  194. | Expr '<' Expr { printf("\tcmplt\n"); }
  195. | Expr T_Ge Expr { printf("\tcmpge\n"); }
  196. | Expr T_Le Expr { printf("\tcmple\n"); }
  197. | Expr T_Eq Expr { printf("\tcmpeq\n"); }
  198. | Expr T_Ne Expr { printf("\tcmpne\n"); }
  199. | Expr T_Or Expr { printf("\tor\n"); }
  200. | Expr T_And Expr { printf("\tand\n"); }
  201. | '-' Expr %prec '!' { printf("\tneg\n"); }
  202. | '!' Expr { printf("\tnot\n"); }
  203. | T_IntConstant { printf("\tpush %s\n", $1); }
  204. | T_Identifier { printf("\tpush %s\n", $1); }
  205. | ReadInt { /* empty */ }
  206. | CallExpr { /* empty */ }
  207. | '(' Expr ')' { /* empty */ }
  208. ;
  209.  
  210. ReadInt:
  211. T_ReadInt '(' T_StringConstant ')'
  212. { printf("\treadint %s\n", $3); }
  213. ;
  214.  
  215. %%
  216.  
  217. int main() {
  218. return yyparse();
  219. }

makefile 文件: makefile ,内容和 第 0.5 版是一样的。

测试文件: test.c ,就是第二章的的示例源程序。

  1. #include "for_gcc_build.hh" // only for gcc, TinyC will ignore it.
  2.  
  3. int main() {
  4. int i;
  5. i = 0;
  6. while (i < 10) {
  7. i = i + 1;
  8. if (i == 3 || i == 5) {
  9. continue;
  10. }
  11. if (i == 8) {
  12. break;
  13. }
  14. print("%d! = %d", i, factor(i));
  15. }
  16. return 0;
  17. }
  18.  
  19. int factor(int n) {
  20. if (n < 2) {
  21. return 1;
  22. }
  23. return n * factor(n - 1);
  24. }

测试文件包:samples.zip ,包含了 7 个测试文件。

测试脚本: test_samples.sh

Pcode 模拟器: pysim.py

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

词法分析文件中:

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

语法分析文件中:

增加了 IfStmt, WhileStmt, BreakStmt, ContinueStmt, ReadInt 等非终结符以及相应的产生式,请注意各产生式的折叠顺序以及相应的 Pcode 生成顺序;

增加了比较运算符、逻辑运算符,以及相应的优先级;

在 Declarations 段,增加了几个全局变量和宏:

  1. int ii = 0, itop = -1, istack[100];int ww = 0, wtop = -1, wstack[100];#define _BEG_IF {istack[++itop] = ++ii;}#define _END_IF {itop—;}#define _i (istack[itop])#define _BEG_WHILE {wstack[++wtop] = ++ww;}#define _END_WHILE {wtop—;}#define _w (wstack[wtop])

这些全局变量和宏配合后面的 if/while 语句产生式中的 action 使用,是该文件中的最精妙的部分,它们的作用是:在生成 if 和 while 语句块的 Pcode 的过程中,给相应的 Label 进行编号。它们给每个 if 语句块和每个 while 语句块一个唯一的编号,使不同的 if/while 语句块的 jmp 不相互冲突。其中 _i 永远是当前的 if 语句块的编号, _w 永远是当前的 while 语句块的编号; ii/ww 永远是目前解析到的 if/while 语句块的总数。

将以上所有文件都放在当前目录,在终端直接输入 make test ,将自动编译生成 TinyC 前端: tcc ,并自动调用 tcc 将 test.c 编译成 test.asm 文件,内容如下,和第 5 章的手工编译的结果差不多吧:

  1. FUNC @main:
  2. var i
  3.  
  4. push 0
  5. pop i
  6.  
  7. _begWhile_1:
  8. push i
  9. push 10
  10. cmplt
  11. jz _endWhile_1
  12. push i
  13. push 1
  14. add
  15. pop i
  16.  
  17. _begIf_1:
  18. push i
  19. push 3
  20. cmpeq
  21. push i
  22. push 5
  23. cmpeq
  24. or
  25. jz _elIf_1
  26. jmp _begWhile_1
  27. jmp _endIf_1
  28. _elIf_1:
  29. _endIf_1:
  30.  
  31. _begIf_2:
  32. push i
  33. push 8
  34. cmpeq
  35. jz _elIf_2
  36. jmp _endWhile_1
  37. jmp _endIf_2
  38. _elIf_2:
  39. _endIf_2:
  40.  
  41. push i
  42. push i
  43. $factor
  44. print "%d! = %d"
  45.  
  46. jmp _begWhile_1
  47. _endWhile_1:
  48.  
  49. push 0
  50. ret ~
  51.  
  52. ENDFUNC
  53.  
  54. FUNC @factor:
  55. arg n
  56.  
  57. _begIf_3:
  58. push n
  59. push 2
  60. cmplt
  61. jz _elIf_3
  62. push 1
  63. ret ~
  64.  
  65. jmp _endIf_3
  66. _elIf_3:
  67. _endIf_3:
  68.  
  69. push n
  70. push n
  71. push 1
  72. sub
  73. $factor
  74. mul
  75. ret ~
  76.  
  77. ENDFUNC

再输入 “make simulate”,将输出:

  1. 1! = 1
  2. 2! = 2
  3. 4! = 24
  4. 6! = 720
  5. 7! = 5040

和第二章中用 gcc 编译并运行此文件的结果完全一样。

再把测试文件包里的所有源文件全部测试一遍,将 samples.zip 解压到 samples 目录下,测试脚本 test_samples.sh 将分别调用 tcc 和 gcc 编译测试文件包中的每一个文件,并分别使用 pysim.py 和 操作系统 运行编译得到的目标文件,内容如下:

  1. for src in $(ls samples/*.c)
  2. do
  3. clear
  4. file=${src%%.c}
  5. echo build with tcc
  6. ./tcc < $file.c > $file.asm
  7. python pysim.py $file.asm -a
  8. echo
  9. echo build with gcc
  10. gcc -o $file $file.c
  11. ./$file
  12. echo
  13. echo press any key to continue...
  14. read -n 1
  15. done

在终端输入 bash ./test_samples.sh ,将分别输出一系列的结果,典型输出如下,可以看到 gcc 和 tcc 编译运行的结果完全一致。

  1. build with tcc, the output are:
  2. The first 10 number of the fibonacci sequence:
  3. fib(1)=1
  4. fib(2)=1
  5. fib(3)=2
  6. fib(4)=3
  7. fib(5)=5
  8. fib(6)=8
  9. fib(7)=13
  10. fib(8)=21
  11. fib(9)=34
  12. fib(10)=55
  13.  
  14. build with gcc, the output are:
  15. The first 10 number of the fibonacci sequence:
  16. fib(1)=1
  17. fib(2)=1
  18. fib(3)=2
  19. fib(4)=3
  20. fib(5)=5
  21. fib(6)=8
  22. fib(7)=13
  23. fib(8)=21
  24. fib(9)=34
  25. fib(10)=55

至此 TinyC 前端完成。

第 14 章完