lex与yacc(二)计算器的实现

2011年09月24日 18:44:24 ecbtnrt 阅读数 4311

构建一个c语言的编译器并不是一件容易的事,我想每个人在学习编译原理的时候并不会常见得它非常简单.
下面将会学习编译器的两个重要组成部分:词法分析器flex和语法分析器yacc
flex是gun实现的fast lex(lexical anslysis)
yacc实现有gun的bison和berkeley的byacc
flex与yacc程序是由下面三部分组成


 

  1. %{
  2. /*注释用空白缩进
  3. */
  4. %}
  5. %%标记这一部分结束
  6. %%第二部分与第三部分分割

第一部分
定义段,拷贝到最终程序中的原始的c程序代码,如头文件.
第二部分
规则段,由两部分组成:模式和动作,空白分开,记法分析程序识别出某个模式时,将执行相应的动作
模式为unix样式的正则表达式
第三部分
用户子程序段,合法c代码组成.在lex生成代码结束之后复制到c文件.
calc.l


1. %{
2. #include "y.tab.h"
3. #include "ch3hdr2.h"
4. #include <math.h>
5. %}
6.  
7.  
8. %%
9. ([0-9]+|([0-9]*\.[0-9]+)([eE][-+]?[0-9]+)?) {
10. yylval.dval = atof(yytext);
11. return NUMBER;
12. }
13.  
14.  
15. [ \t] ; /* ignore white space */
16.  
17.  
18. [A-Za-z][A-Za-z0-9]* { /* return symbol pointer */
19. struct symtab *sp = symlook(yytext);
20.  
21.  
22. yylval.symp = sp;
23. return NAME;
24. }
25.  
26.  
27. "{1}quot; { return 0; }
28.  
29.  
30. \n |
31. . return yytext[0];
32. %%
在编写好规则后,调用yylex来根据进行规则词法分析.
 而这些规则是由正则表达式写成的.
 calc.y 
 
 
1. %{
2. #include "ch3hdr2.h"
3. #include <string.h>
4. #include <math.h>
5. %}
6.  
7.  
8. %union {
9. double dval;
10. struct symtab *symp;
11. }
12. %token <symp> NAME
13. %token <dval> NUMBER
14. %left '-' '+'
15. %left '*' '/'
16. %nonassoc UMINUS
17.  
18.  
19. %type <dval> expression
20. %%
21. statement_list: statement '\n'
22. | statement_list statement '\n'
23. ;
24.  
25.  
26. statement: NAME '=' expression { $1->value = $3; }
27. | expression { printf("= %g\n", $1); }
28. ;
29.  
30.  
31. expression: expression '+' expression { $ = $1 + $3; }
32. | expression '-' expression { $ = $1 - $3; }
33. | expression '*' expression { $ = $1 * $3; }
34. | expression '/' expression
35. { if($3 == 0.0)
36. yyerror("divide by zero");
37. else
38. $ = $1 / $3;
39. }
40. | '-' expression %prec UMINUS { $ = -$2; }
41. | '(' expression ')' { $ = $2; }
42. | NUMBER
43. | NAME { $ = $1->value; }
44. | NAME '(' expression ')' {
45. if($1->funcptr)
46. $ = ($1->funcptr)($3);
47. else {
48. printf("%s not a function\n", $1->name);
49. $ = 0.0;
50. }
51. }
52. ;
53. %%
54. /* look up a symbol table entry, add if not present */
55. struct symtab *
56. symlook(s)
57. char *s;
58. {
59. char *p;
60. struct symtab *sp;
61.  
62.  
63. for(sp = symtab; sp < &symtab[NSYMS]; sp++) {
64. /* is it already here? */
65. if(sp->name && !strcmp(sp->name, s))
66. return sp;
67.  
68.  
69. /* is it free */
70. if(!sp->name) {
71. sp->name = strdup(s);
72. return sp;
73. }
74. /* otherwise continue to next */
75. }
76. yyerror("Too many symbols");
77. exit(1); /* cannot continue */
78. } /* symlook */
79.  
80.  
81. addfunc(name, func)
82. char *name;
83. double (*func)();
84. {
85. struct symtab *sp = symlook(name);
86. sp->funcptr = func;
87. }
88.  
89.  
90. main()
91. {
92. extern double sqrt(), exp(), log();
93.  
94.  
95. addfunc("sqrt", sqrt);
96. addfunc("exp", exp);
97. addfunc("log", log);
98. yyparse();
99. }

lex将输入流分成块,然后yacc取得这些块并将它们逻辑性地归组到一起.
yacc语法分析器yyparse()重复尝试分析句子直到输入结束.

需要输入时调用词法分析程序yylex(),把匹配的标记返回给程序.要注意的是这里的递归规则.

编译:


 

1. lex calc.l
2. yacc -d calc.y
3. gcc y.tab.c lex.yy.c -ly -ll

语法分析程序分析过程移动与归约

yacc语法分析程序是通过寻找可以匹配目前为止所看到的标记的规则来工作.yacc处理语法分析程序时创建了一组状态,每个状态反映一个或多个部分地被分析的规则中的一个可能位置.当语法分析程序读取标记时,每次它读取一个没完成规则的标记,就把它压入内部堆栈中并切换到一种反映它刚刚读取的标记的新状态,这个动作称为移进(shift).当它发现组成某条规则右侧的全部符号时,它就把右侧符号弹出堆栈,而将左侧符号压入堆栈中,并切换到反映堆栈上新符号的新状态,这个动作被称为归约(reduction).因为它通常减少堆栈上项目的数目.无论yacc什么时候归约规则,它都执行与这条规则有关的用户代码.