文章目录
实验要求
本次实验需要各位同学首先将自己的 lab1 的词法部分复制到 /src/parser
目录的 lexical_analyzer.l并合理修改相应部分,然后根据 cminus-f
的语法补全 syntax_analyer.y 文件,完成语法分析器,要求最终能够输出解析树。如:
输入:
int bar;
float foo(void) { return 1.0; }
则 parser
将输出如下解析树:
>--+ program
| >--+ declaration-list
| | >--+ declaration-list
| | | >--+ declaration
| | | | >--+ var-declaration
| | | | | >--+ type-specifier
| | | | | | >--* int
| | | | | >--* bar
| | | | | >--* ;
| | >--+ declaration
| | | >--+ fun-declaration
| | | | >--+ type-specifier
| | | | | >--* float
| | | | >--* foo
| | | | >--* (
| | | | >--+ params
| | | | | >--* void
| | | | >--* )
| | | | >--+ compound-stmt
| | | | | >--* {
| | | | | >--+ local-declarations
| | | | | | >--* epsilon
| | | | | >--+ statement-list
| | | | | | >--+ statement-list
| | | | | | | >--* epsilon
| | | | | | >--+ statement
| | | | | | | >--+ return-stmt
| | | | | | | | >--* return
| | | | | | | | >--+ expression
| | | | | | | | | >--+ simple-expression
| | | | | | | | | | >--+ additive-expression
| | | | | | | | | | | >--+ term
| | | | | | | | | | | | >--+ factor
| | | | | | | | | | | | | >--+ float
| | | | | | | | | | | | | | >--* 1.0
| | | | | | | | >--* ;
| | | | | >--* }
请注意,上述解析树含有每个解析规则的所有子成分,包括诸如 ;
{
}
这样的符号,请在编写规则时务必不要忘了它们。
主要工作
- 了解
bison
基础知识和理解 Cminus-f 语法(重在了解如何将文法产生式转换为bison
语句) - 阅读
/src/common/SyntaxTree.c
,对应头文件/include/SyntaxTree.h
(重在理解分析树如何生成) - 了解
bison
与flex
之间是如何协同工作,看懂pass_node函数并改写 Lab1 代码(提示:了解yylval
是如何工作,在代码层面上如何将值传给$1
、$2
等) - 补全
src/parser/syntax_analyzer.y
文件和lexical_analyzer.l
文件
Tips:在未编译的代码文件中是无法看到关于协同工作部分的代码,建议先编译 1.3 给出的计算器样例代码,再阅读 /build/src/parser/
中的 syntax_analyzer.h
与 syntax_analyzer.c
文件
实验难点
1、Bison文件
Bison是一个语法分析器的生成工具, 可以将 LALR 文法转换成可编译的 C 代码,文件扩展名为 .y
。在Bison文件中给出LALR文法以及一些分析动作,编译就可以产生一个语法分析器。
每个 Bison 文件由 %%
分成三部分,如实验给出的例子:
%{
#include <stdio.h>
/* 这里是序曲 */
/* 这部分代码会被原样拷贝到生成的 .c 文件的开头 */
int yylex(void);
void yyerror(const char *s);
%}
/* 这些地方可以输入一些 bison 指令 */
/* 比如用 %start 指令指定起始符号,用 %token 定义一个 token */
%start reimu
%token REIMU
%%
/* 从这里开始,下面是解析规则 */
reimu : marisa { /* 这里写与该规则对应的处理代码 */ puts("rule1"); }
| REIMU { /* 这里写与该规则对应的处理代码 */ puts("rule2"); }
; /* 规则最后不要忘了用分号结束哦~ */
/* 这种写法表示 ε —— 空输入 */
marisa : { puts("Hello!"); }
%%
/* 这里是尾声 */
/* 这部分代码会被原样拷贝到生成的 .c 文件的末尾 */
int yylex(void)
{
int c = getchar(); // 从 stdin 获取下一个字符
switch (c) {
case EOF: return YYEOF;
case 'R': return REIMU;
default: return 0; // 返回无效 token 值,迫使 bison 报错
}
}
void yyerror(const char *s)
{
fprintf(stderr, "%s\n", s);
}
int main(void)
{
yyparse(); // 启动解析
return 0;
}
Bison与lab1中Lex文件的编写规则类似,由%%区分的三部分构成,开头和结尾会被拷贝进.c文件,我们主要关注第二部分解析规则。
2、分析树的生成
查看/include/SyntaxTree.h
文件:
#ifndef __SYNTAXTREE_H__
#define __SYNTAXTREE_H__
#include <stdio.h>
#define SYNTAX_TREE_NODE_NAME_MAX 30
struct _syntax_tree_node {
struct _syntax_tree_node * parent;
struct _syntax_tree_node * children[10];
int children_num;
char name[SYNTAX_TREE_NODE_NAME_MAX];
};
typedef struct _syntax_tree_node syntax_tree_node;
syntax_tree_node * new_anon_syntax_tree_node();
syntax_tree_node * new_syntax_tree_node(const char * name);
int syntax_tree_add_child(syntax_tree_node * parent, syntax_tree_node * child);
void del_syntax_tree_node(syntax_tree_node * node, int recursive);
struct _syntax_tree {
syntax_tree_node * root;
};
typedef struct _syntax_tree syntax_tree;
syntax_tree* new_syntax_tree();
void del_syntax_tree(syntax_tree * tree);
void print_syntax_tree(FILE * fout, syntax_tree * tree);
#endif /* SyntaxTree.h */
这段代码定义了一个简单的语法树(Syntax Tree)及其操作接口,适用于编译器、解释器或其他需要树状结构的数据管理场景。语法树由多个节点组成,每个节点用 _syntax_tree_node
表示,包含指向父节点的 parent
指针、最多 10 个子节点的 children
数组、子节点数量的 children_num
和节点名称 name
。
通过函数 new_anon_syntax_tree_node
和 new_syntax_tree_node
,可以创建匿名或指定名称的节点;通过 syntax_tree_add_child
将子节点添加到父节点;通过 del_syntax_tree_node
删除节点,支持递归删除所有子节点。此外,代码提供了 _syntax_tree
结构,用于表示一棵语法树,其中 root
为根节点,并通过 new_syntax_tree
和 del_syntax_tree
创建或销毁整棵树。函数 print_syntax_tree
支持将语法树的结构输出到文件流,用于调试或展示。
每个终结符都对应着一个叶子节点,词法分析时会完成叶子节点的产生。在自底向上的分析过程中,首先产生的是叶子节点,剩下构建语法分析树的过程由语法分析来负责。叶子节点的产生在./src/parser/lexical_analyzer.l
的pass_node()
函数中实现,创建一个新的节点,并将其指针赋值给yylval,节点名为其成分(非终结符名或终结符名)。
3、理解Bison 和 Flex 的关系
参考文档给出了一个样例代码,我们可以先对其分析解释:
①YYSTYPE
YYSTYPE
: 在 bison 解析过程中,每个 symbol 最终都对应到一个语义值上。或者说,在 parse tree 上,每个节点都对应一个语义值,这个值的类型是 YYSTYPE
。YYSTYPE
的具体内容是由 %union
构造指出的, 不同的节点对应着不同的终结符,可能为不同的类型,因此union
中可以包含不同的数据类型。比如下面代码,我们可能希望ADDOP
的值是 char
类型,而 NUMBER
应该是 double
类型的。
%union {
char op;
double num;
}
会生成类似这样的代码:
typedef union YYSTYPE {
char op;
double num;
} YYSTYPE;
②归约
以加法为例:
term : term ADDOP factor
{
switch $2 {
case '+': $$ = $1 + $3; break;
case '-': $$ = $1 - $3; break;
}
}
当前节点使用 $$
代表,而已解析的节点则是从左到右依次编号,称作 $1
, $2
, $3
…
③%type
term
应该使用 num
部分,那么我们就写成:
%type <num> term
而不是类似结构体定义的那样term.op
,term.num
。
④%token
当我们用 %token
声明一个 token 时,这个 token 就会导出到 .h 中,可以在 C 代码中直接使用,供 flex 使用。%token <op> ADDOP
与之类似,但顺便也将 ADDOP
传递给 %type
。
⑤yylval
这时候我们可以打开 .h
文件,看看里面有什么。除了 token 定义,最末尾还有一个 extern YYSTYPE yylval;
。这个变量我们上面已经使用了,通过这个变量,我们就可以在 lexer 里面设置某个 token 的值。
实验设计
1、补充lexical_analyzer.l
我们要将lab1中完成的lexical_analyzer.l
放进./src/parser/lexical_analyzer.l
文件中,但不是完全照搬。在词法分析的识别过程中,每次识别要生成一个词法单元叶子结点,所以要添加一个pass_node()
以生成节点。
flex会将识别出来的结果交给Basin来构建语法树,注意到lab1中我们考虑了注释,空格等特殊符号,而语法树的构建是不用考虑这些的,所以对于这些token不用生成节点。
同时lab1中的void analyzer函数也不再需要显示地声明。
代码中已经为我们给出了一个样例:
\+ {pos_start = pos_end; pos_end++; pass_node(yytext); return ADD;}
我们可以仿照它来修改:
%%
/* 运算 */
\+ {pos_start = pos_end; pos_end++; pass_node(yytext); return ADD;}
\- {pos_start = pos_end; pos_end++; pass_node(yytext);return SUB;}
\* {pos_start = pos_end; pos_end++; pass_node(yytext);return MUL;}
\/ {pos_start = pos_end; pos_end++; pass_node(yytext);return DIV;}
\< {pos_start = pos_end; pos_end++; pass_node(yytext);return LT;}
"<=" {pos_start = pos_end; pos_end+=2; pass_node(yytext);return LTE;}
\> {pos_start = pos_end; pos_end++; pass_node(yytext);return GT;}
">=" {pos_start = pos_end; pos_end+=2; pass_node(yytext);return GTE;}
"==" {pos_start = pos_end; pos_end+=2; pass_node(yytext);return EQ;}
"!=" {pos_start = pos_end; pos_end+=2; pass_node(yytext);return NEQ;}
\= {pos_start = pos_end; pos_end++; pass_node(yytext);return ASSIN;}
/* 符号 */
\; {pos_start = pos_end; pos_end++; pass_node(yytext);return SEMICOLON;}
\, {pos_start = pos_end; pos_end++; pass_node(yytext);return COMMA;}
\( {pos_start = pos_end; pos_end++; pass_node(yytext);return LPARENTHESE;}
\) {pos_start = pos_end; pos_end++; pass_node(yytext);return RPARENTHESE;}
\[ {pos_start = pos_end; pos_end++; pass_node(yytext);return LBRACKET;}
\] {pos_start = pos_end; pos_end++; pass_node(yytext);return RBRACKET;}
\{ {pos_start = pos_end; pos_end++; pass_node(yytext);return LBRACE;}
\} {pos_start = pos_end; pos_end++; pass_node(yytext);return RBRACE;}
/* 关键字 */
else {pos_start = pos_end; pos_end+=4; pass_node(yytext);return ELSE;}
if {pos_start = pos_end; pos_end+=2; pass_node(yytext);return IF;}
int {pos_start = pos_end; pos_end+=3; pass_node(yytext);return INT;}
float {pos_start = pos_end; pos_end+=5; pass_node(yytext);return FLOAT;}
return {pos_start = pos_end; pos_end+=6; pass_node(yytext);return RETURN;}
void {pos_start = pos_end; pos_end+=4; pass_node(yytext);return VOID;}
while {pos_start = pos_end; pos_end+=5; pass_node(yytext);return WHILE;}
/* ID & NUM */
[a-zA-Z]+ {pos_start = pos_end; pos_end+=strlen(yytext); pass_node(yytext);return IDENTIFIER;}
[a-zA-Z] {pos_start = pos_end; pos_end++; pass_node(yytext);return LETTER;}
[0-9]+ {pos_start = pos_end; pos_end+=strlen(yytext); pass_node(yytext);return INTEGER;}
[0-9]+\.|[0-9]*\.[0-9]+ {pos_start = pos_end; pos_end+=strlen(yytext); pass_node(yytext);return FLOATPOINT;}
\[\] {pos_start = pos_end; pos_end+=2; pass_node(yytext);return ARRAY;}
/* others */
\n {lines++; pos_start = 1; pos_end = 1;}
\/\*([^\*]|(\*)*[^\*\/])*(\*)*\*\/
{
for(int i=0;i<strlen(yytext);i++)
{
if(yytext[i]=='\n')
{
pos_start=1;
pos_end=1;
lines++;
}
else pos_end++;
}
}
[ \f\n\r\t\v] {pos_start = pos_end;pos_end+=strlen(yytext);}
. {return ERROR;}
%%
2、补充syntax_analyzer.y
(1)union
我们在上面已经讨论过,在 bison 解析过程中,每个 symbol 最终都对应到一个语义值上。或者说,在 parse tree 上,每个节点都对应一个语义值,这个值的类型是 YYSTYPE
。YYSTYPE
的具体内容是由 %union
构造指出的, 不同的节点对应着不同的终结符,可能为不同的类型,因此union
中可以包含不同的数据类型。
对于节点的声明,在union里加入已经定义好的syntax_tree_node *node
即可:
%union {
syntax_tree_node *node;
}
(2)%token与%type
在实验文档给出的代码中,我们可以看到token与type的具体用法,%token的符号是大写,代表终结符,%type的符号是小写,代表非终结符。终结符名要与我们刚刚补齐的代码中的名字相同,而非终结符名可以从实验文档中找到:
我们将 Cminus-f 的所有规则分为五类:
1.字面量、关键字、运算符与标识符
id
type-specifier
relop
addop
mulop
2.声明
declaration-list
declaration
var-declaration
fun-declaration
local-declarations
3.语句
compound-stmt
statement-list
statement
expression-stmt
iteration-stmt
selection-stmt
return-stmt
4.表达式
expression
var
additive-expression
term
factor
integer
float
call
5.其他
params
param-list
param
args
arg-list
起始符号是 program。
声明结果如下:
%start program
%token <node> ADD SUB MUL DIV
%token <node> LT LTE GT GTE EQ NEQ ASSIN
%token <node> SEMICOLON COMMA LPARENTHESE RPARENTHESE LBRACKET RBRACKET LBRACE RBRACE
%token <node> ELSE IF INT FLOAT RETURN VOID WHILE IDENTIFIER LETTER INTEGER FLOATPOINT ARRAY
%type <node> type-specifier relop addop mulop
%type <node> declaration-list declaration var-declaration fun-declaration local-declarations
%type <node> compound-stmt statement-list statement expression-stmt iteration-stmt selection-stmt return-stmt
%type <node> simple-expression expression var additive-expression term factor integer float call
%type <node> params param-list param args arg-list
%type <node> program
(4)%type的解析规则
源代码中同样给出了一个实例:
program : declaration-list { $$ = node("program", 1, $1); gt->root = $$; }
这句表示 program
规则的语法动作部分。前面写出规则名称,在规则后面的 { }
内部分是语法动作部分,也就是 Bison 在匹配到该规则时执行的 C 代码。
$$ = node("program", 1, $1);
$$
是当前规则(program
)的语法值,表示整个程序节点的值。node("program", 1, $1)
是创建一个新的语法树节点的函数调用,表示在语法树中创建一个名为"program"
的节点,并且该节点有一个子节点($1
),它是declaration-list
的值。1
表示这个节点的子节点个数。
gt->root = $$;
root
是gt
结构中的一个字段,表示语法树的根节点。$$
是刚刚创建的program
节点,它作为程序的根节点被赋值给gt->root
,将整个程序的语法树根节点存储在gt
中。
对于其他规则的编写,可以参考实验文档给出的Cminus-f
语法规则:
‘program→declaration-list‘
//表示一个程序由 declaration-list(声明列表)组成。
‘declaration-list→declaration-list declaration | declaration‘
//declaration-list 是一个声明的序列,可以是多个声明组合,也可以是单独一个声明。
‘declaration→var-declaration | fun-declaration‘
//声明可以是变量声明(var-declaration)或函数声明(fun-declaration)
‘var-declaration →type-specifier ID ; | type-specifier ID [ INTEGER ] ;‘
//变量声明可以是以下两种形式:
//type-specifier ID ;:声明一个简单变量,例如 int x;。
//type-specifier ID [ INTEGER ] ;:声明一个数组变量,例如 int arr[10];。
‘type-specifier→int | float | void‘
//类型说明符可以是 int、float 或 void。
‘fun-declaration→type-specifier ID ( params ) compound-stmt‘
//函数声明的结构如下:
//返回类型(type-specifier)。
//函数名(ID)。
//参数列表(params)。
//函数体(compound-stmt)。
‘params→param-list | void‘
//数参数可以是参数列表(param-list)或 void(表示无参数)。
‘param-list→param-list , param | param‘
//参数列表可以是多个参数(用逗号分隔)或单个参数。
‘param→type-specifier ID | type-specifier ID []‘
//每个参数可以是简单变量(int x)或数组(int arr[])。
‘compound-stmt→{ local-declarations statement-list}‘
//复合语句由局部声明(local-declarations)和语句列表(statement-list)组成,包含在花括号 {} 中。
‘local-declarations→local-declarations var-declaration | empty‘
//局部声明可以包含多个变量声明,也可以为空。
‘statement-list→statement-list statement | empty‘
//语句列表可以包含多个语句,也可以为空。
‘statement→ expression-stmt| compound-stmt| selection-stmt| iteration-stmt| return-stmt‘
//语句包括表达式语句、复合语句、选择语句、循环语句和返回语句。
‘expression-stmt→expression ; | ;‘
//表达式语句可以是一个表达式加分号,或者仅是一个分号(空语句)。
‘selection-stmt→ if ( expression ) statement| if ( expression ) statement else statement‘
//选择语句是 if 语句或 if-else 语句。
‘iteration-stmt→while ( expression ) statement‘
//循环语句是 while 循环。
‘return-stmt→return ; | return expression ;‘
//返回语句可以是不带值的 return 或带值的 return expression。
‘expression→var = expression | simple-expression‘
//表达式可以是赋值操作,也可以是简单表达式。
‘var→ID | ID [ expression]‘
//变量可以是简单变量或数组元素。
‘simple-expression→additive-expression relop additive-expression | additive-expression‘
//简单表达式可以是加法表达式之间用关系运算符连接,或者是单独一个加法表达式。
‘relop →<= | < | > | >= | == | !=‘
//关系运算符包括:小于等于、小于、大于、大于等于、等于、不等于。
‘additive-expression→additive-expression addop term | term‘
//加法表达式由加法运算符连接的项组成,或者是单独一个项
‘addop→+ | -‘
//加法运算符是 + 或 -。
‘term→term mulop factor | factor‘
//项是由乘法运算符连接的因子组成,或者是单独一个因子。
‘mulop→* | /‘
//乘法运算符是 * 或 /。
‘factor→( expression ) | var | call | integer | float‘
//因子可以是括号中的表达式、变量、函数调用、整数或浮点数。
‘integer→INTEGER‘
‘float→FLOATPOINT‘
‘call→ID ( args)‘
//函数调用由函数名(ID)和参数(args)组成
‘args→arg-list | empty‘
//函数参数可以是参数列表或为空。
‘arg-list→arg-list , expression | expression‘
//参数列表是多个用逗号分隔的表达式。
补充完的解析规则如下:
program : declaration-list { $$ = node("program", 1, $1); gt->root = $$; }
declaration-list : declaration-list declaration { $$ = node("declaration-list", 2, $1, $2);}
| declaration { $$ = node("declaration-list", 1, $1); }
declaration : var-declaration { $$ = node("declaration", 1, $1); }
| fun-declaration { $$ = node("declaration", 1, $1); }
var-declaration : type-specifier IDENTIFIER SEMICOLON { $$ = node("var-declaration", 3, $1, $2, $3); }
| type-specifier IDENTIFIER LBRACKET INTEGER RBRACKET SEMICOLON { $$ = node("var-declaration", 6, $1, $2, $3, $4, $5, $6); }
type-specifier : INT { $$ = node("type-specifier", 1, $1); }
| FLOAT { $$ = node("type-specifier", 1, $1); }
| VOID { $$ = node("type-specifier", 1, $1); }
fun-declaration : type-specifier IDENTIFIER LPARENTHESE params RPARENTHESE compound-stmt { $$ = node("fun-declaration", 6, $1, $2, $3, $4, $5, $6); }
params : param-list { $$ = node("params", 1, $1); }
| VOID { $$ = node("params", 1, $1); }
param-list : param-list COMMA param { $$ = node("param-list", 3, $1, $2, $3); }
| param { $$ = node("param-list", 1, $1); }
param : type-specifier IDENTIFIER { $$ = node("param", 2, $1, $2); }
| type-specifier IDENTIFIER ARRAY { $$ = node("param", 3, $1, $2, $3); }
compound-stmt : LBRACE local-declarations statement-list RBRACE { $$ = node("compound-stmt", 4, $1, $2, $3, $4); }
local-declarations : { $$ = node("local-declarations", 0); }
| local-declarations var-declaration { $$ = node("local-declarations", 2, $1, $2); }
statement-list : { $$ = node("statement-list", 0); }
| statement-list statement { $$ = node("statement-list", 2, $1, $2); }
statement : expression-stmt { $$ = node("statement", 1, $1); }
| compound-stmt { $$ = node("statement", 1, $1); }
| selection-stmt { $$ = node("statement", 1, $1); }
| iteration-stmt { $$ = node("statement", 1, $1); }
| return-stmt { $$ = node("statement", 1, $1); }
expression-stmt : expression SEMICOLON { $$ = node("expression-stmt", 2, $1, $2); }
| SEMICOLON { $$ = node("expression-stmt", 1, $1); }
selection-stmt : IF LPARENTHESE expression RPARENTHESE statement { $$ = node("selection-stmt", 5, $1, $2, $3, $4, $5); }
| IF LPARENTHESE expression RPARENTHESE statement ELSE statement { $$ = node("selection-stmt", 7, $1, $2, $3, $4, $5, $6, $7); }
iteration-stmt : WHILE LPARENTHESE expression RPARENTHESE statement { $$ = node("iteration-stmt", 5, $1, $2, $3, $4, $5); }
return-stmt : RETURN SEMICOLON { $$ = node("return-stmt", 2, $1, $2); }
| RETURN expression SEMICOLON { $$ = node("return-stmt", 3, $1, $2, $3); }
expression : var ASSIN expression { $$ = node("expression", 3, $1, $2, $3); }
| simple-expression { $$ = node("expression", 1, $1); }
var : IDENTIFIER { $$ = node("var", 1, $1); }
| IDENTIFIER LBRACKET expression RBRACKET { $$ = node("var", 4, $1, $2, $3, $4); }
simple-expression : additive-expression relop additive-expression { $$ = node("simple-expression", 3, $1, $2, $3); }
| additive-expression { $$ = node("simple-expression", 1, $1); }
relop : LTE { $$ = node("relop", 1, $1); }
| LT { $$ = node("relop", 1, $1); }
| GT { $$ = node("relop", 1, $1); }
| GTE { $$ = node("relop", 1, $1); }
| EQ { $$ = node("relop", 1, $1); }
| NEQ { $$ = node("relop", 1, $1); }
additive-expression : additive-expression addop term { $$ = node("additive-expression", 3, $1, $2, $3); }
| term { $$ = node("additive-expression", 1, $1); }
addop : ADD { $$ = node("addop", 1, $1); }
| SUB { $$ = node("addop", 1, $1); }
term : term mulop factor { $$ = node("term", 3, $1, $2, $3); }
| factor { $$ = node("term", 1, $1); }
mulop : MUL { $$ = node("mulop", 1, $1); }
| DIV { $$ = node("mulop", 1, $1); }
factor : LPARENTHESE expression RPARENTHESE { $$ = node("factor", 3, $1, $2, $3); }
| var { $$ = node("factor", 1, $1); }
| call { $$ = node("factor", 1, $1); }
| integer { $$ = node("factor", 1, $1); }
| float { $$ = node("factor", 1, $1); }
integer : INTEGER { $$ = node("integer", 1, $1); }
float : FLOATPOINT { $$ = node("float", 1, $1); }
call : IDENTIFIER LPARENTHESE args RPARENTHESE { $$ = node("call", 4, $1, $2, $3, $4); }
args : { $$ = node("args", 0); }
| arg-list { $$ = node("args", 1, $1); }
arg-list : arg-list COMMA expression { $$ = node("arg-list", 3, $1, $2, $3); }
| expression { $$ = node("arg-list", 1, $1); }
可以选择其中一个解释一番:
var-declaration : type-specifier IDENTIFIER SEMICOLON
{ $$ = node("var-declaration", 3, $1, $2, $3); }
| type-specifier IDENTIFIER LBRACKET INTEGER RBRACKET SEMICOLON
{ $$ = node("var-declaration", 6, $1, $2, $3, $4, $5, $6); }
var-declaration : type-specifier IDENTIFIER SEMICOLON
表示一个简单变量声明,
type-specifier
是变量的类型(如 int
、float
)。
IDENTIFIER
是变量的标识符(如 x
、y
)。
SEMICOLON
是分号,表示声明结束。
var-declaration : type-specifier IDENTIFIER LBRACKET INTEGER RBRACKET SEMICOLON
表示一个数组变量声明,
type-specifier
是数组的类型(如 int
、float
)。
IDENTIFIER
是数组的标识符(如 arr
、nums
)。
LBRACKET
和 RBRACKET
分别是左方括号和右方括号。
INTEGER
是数组的大小(如 10
、20
)。
SEMICOLON
表示声明结束。
实验结果验证
1、执行命令make parser
进行编译
根据显示的错误,在ast.hpp
文件中添加#include<string>
.
再次编译:
报错说明 yyin
变量未声明。在syntax_analyzer.y
头部加上对 yyin
添加声明extern FILE *yyin;
。再次编译:
2、test验证
执行命令./tests/lab2/test_syntax.sh easy
和./tests/lab2/test_syntax.sh normal
生成语法解析树
出现 Permission denied
的错误是因为 test_syntax.sh
脚本文件缺乏可执行权限
运行ls -l ./tests/lab2/test_syntax.sh
,检查 test_syntax.sh
的当前权限,发现无执行权限,将x
权限赋予它:
再次测试:
可以看到,能识别出源程序中错误的语法部分
3、用diff 命令进行验证
分别用diff ./tests/lab2/syntree_easy ./tests/lab2/syntree_easy_std
和
diff ./tests/lab2/syntree_normal ./tests/lab2/syntree_normal_std
将自己的生成结果和助教提供的syntree_easy_std
和syntree_normal_std
进行比较。没有任何输出结果,说明结果正确。
4、自测样例
mytest1.cminus
int a;
int cmp(int a, int b)
{
int c;
c=3.1415926+2*3;
a=(3.141526+2)*3;
c = a/*
this is a comment */ * /**/ b;
return c;
}
生成的语法树为:
>--+ program
| >--+ declaration-list
| | >--+ declaration-list
| | | >--+ declaration
| | | | >--+ var-declaration
| | | | | >--+ type-specifier
| | | | | | >--* int
| | | | | >--* a
| | | | | >--* ;
| | >--+ declaration
| | | >--+ fun-declaration
| | | | >--+ type-specifier
| | | | | >--* int
| | | | >--* cmp
| | | | >--* (
| | | | >--+ params
| | | | | >--+ param-list
| | | | | | >--+ param-list
| | | | | | | >--+ param
| | | | | | | | >--+ type-specifier
| | | | | | | | | >--* int
| | | | | | | | >--* a
| | | | | | >--* ,
| | | | | | >--+ param
| | | | | | | >--+ type-specifier
| | | | | | | | >--* int
| | | | | | | >--* b
| | | | >--* )
| | | | >--+ compound-stmt
| | | | | >--* {
| | | | | >--+ local-declarations
| | | | | | >--+ local-declarations
| | | | | | | >--* epsilon
| | | | | | >--+ var-declaration
| | | | | | | >--+ type-specifier
| | | | | | | | >--* int
| | | | | | | >--* c
| | | | | | | >--* ;
| | | | | >--+ statement-list
| | | | | | >--+ statement-list
| | | | | | | >--+ statement-list
| | | | | | | | >--+ statement-list
| | | | | | | | | >--+ statement-list
| | | | | | | | | | >--* epsilon
| | | | | | | | | >--+ statement
| | | | | | | | | | >--+ expression-stmt
| | | | | | | | | | | >--+ expression
| | | | | | | | | | | | >--+ var
| | | | | | | | | | | | | >--* c
| | | | | | | | | | | | >--* =
| | | | | | | | | | | | >--+ expression
| | | | | | | | | | | | | >--+ simple-expression
| | | | | | | | | | | | | | >--+ additive-expression
| | | | | | | | | | | | | | | >--+ additive-expression
| | | | | | | | | | | | | | | | >--+ term
| | | | | | | | | | | | | | | | | >--+ factor
| | | | | | | | | | | | | | | | | | >--+ float
| | | | | | | | | | | | | | | | | | | >--* 3.1415926
| | | | | | | | | | | | | | | >--+ addop
| | | | | | | | | | | | | | | | >--* +
| | | | | | | | | | | | | | | >--+ term
| | | | | | | | | | | | | | | | >--+ term
| | | | | | | | | | | | | | | | | >--+ factor
| | | | | | | | | | | | | | | | | | >--+ integer
| | | | | | | | | | | | | | | | | | | >--* 2
| | | | | | | | | | | | | | | | >--+ mulop
| | | | | | | | | | | | | | | | | >--* *
| | | | | | | | | | | | | | | | >--+ factor
| | | | | | | | | | | | | | | | | >--+ integer
| | | | | | | | | | | | | | | | | | >--* 3
| | | | | | | | | | | >--* ;
| | | | | | | | >--+ statement
| | | | | | | | | >--+ expression-stmt
| | | | | | | | | | >--+ expression
| | | | | | | | | | | >--+ var
| | | | | | | | | | | | >--* a
| | | | | | | | | | | >--* =
| | | | | | | | | | | >--+ expression
| | | | | | | | | | | | >--+ simple-expression
| | | | | | | | | | | | | >--+ additive-expression
| | | | | | | | | | | | | | >--+ term
| | | | | | | | | | | | | | | >--+ term
| | | | | | | | | | | | | | | | >--+ factor
| | | | | | | | | | | | | | | | | >--* (
| | | | | | | | | | | | | | | | | >--+ expression
| | | | | | | | | | | | | | | | | | >--+ simple-expression
| | | | | | | | | | | | | | | | | | | >--+ additive-expression
| | | | | | | | | | | | | | | | | | | | >--+ additive-expression
| | | | | | | | | | | | | | | | | | | | | >--+ term
| | | | | | | | | | | | | | | | | | | | | | >--+ factor
| | | | | | | | | | | | | | | | | | | | | | | >--+ float
| | | | | | | | | | | | | | | | | | | | | | | | >--* 3.141526
| | | | | | | | | | | | | | | | | | | | >--+ addop
| | | | | | | | | | | | | | | | | | | | | >--* +
| | | | | | | | | | | | | | | | | | | | >--+ term
| | | | | | | | | | | | | | | | | | | | | >--+ factor
| | | | | | | | | | | | | | | | | | | | | | >--+ integer
| | | | | | | | | | | | | | | | | | | | | | | >--* 2
| | | | | | | | | | | | | | | | | >--* )
| | | | | | | | | | | | | | | >--+ mulop
| | | | | | | | | | | | | | | | >--* *
| | | | | | | | | | | | | | | >--+ factor
| | | | | | | | | | | | | | | | >--+ integer
| | | | | | | | | | | | | | | | | >--* 3
| | | | | | | | | | >--* ;
| | | | | | | >--+ statement
| | | | | | | | >--+ expression-stmt
| | | | | | | | | >--+ expression
| | | | | | | | | | >--+ var
| | | | | | | | | | | >--* c
| | | | | | | | | | >--* =
| | | | | | | | | | >--+ expression
| | | | | | | | | | | >--+ simple-expression
| | | | | | | | | | | | >--+ additive-expression
| | | | | | | | | | | | | >--+ term
| | | | | | | | | | | | | | >--+ term
| | | | | | | | | | | | | | | >--+ factor
| | | | | | | | | | | | | | | | >--+ var
| | | | | | | | | | | | | | | | | >--* a
| | | | | | | | | | | | | | >--+ mulop
| | | | | | | | | | | | | | | >--* *
| | | | | | | | | | | | | | >--+ factor
| | | | | | | | | | | | | | | >--+ var
| | | | | | | | | | | | | | | | >--* b
| | | | | | | | | >--* ;
| | | | | | >--+ statement
| | | | | | | >--+ return-stmt
| | | | | | | | >--* return
| | | | | | | | >--+ expression
| | | | | | | | | >--+ simple-expression
| | | | | | | | | | >--+ additive-expression
| | | | | | | | | | | >--+ term
| | | | | | | | | | | | >--+ factor
| | | | | | | | | | | | | >--+ var
| | | | | | | | | | | | | | >--* c
| | | | | | | | >--* ;
| | | | | >--* }
mytest2.cminus
这组样例测试的是语法不支持的识别语句:
① 变量不支持初始化
int main(int a)
{
int a=0;
int b=1;
return function(a,b);
}
②不支持的操作+=
int main(int a,int b)
{
a+=b;
return a;
}
③不支持的变量类型
int main(int a)
{
integer a;
int b;
return function(a,b);
}
还有很多可以测试的样例,这里就不一一列举了。
实验反馈
- 在补充
lexical_analyzer.l
文件时,其实只要根据实例给出的example就能很好的编写代码,唯一需要注意的是lab1的other的token,对于空格,注释等,我们不需要为其建立语法树中的节点,程序遇到这些内容的操作是跳过,但也不能直接把这些删去,因为程序要先识别出这些内容才能选择跳过,所以只要把return删去就行。 - 对于
syntax_analyzer.y
的补充,首先要做的是理解实验文档中给出的语法规则,代码的编写都是依赖于这些规则的。在处理解析规则时,最麻烦的是声明部分,把这一部分写好了,后面的节点就很简单了,节点的个数就是{ }
前声明类型的个数,直接数就行,节点的编号按顺序就OK的。