前言:个人感觉比第一次的难,借鉴了前辈的报告才勉强看懂在干嘛
lab2实验报告
实验要求
本次实验需要先将自己的 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的基本知识,懂得怎么将文法产生式转化为bison语句。
-
了解在SyntaxTree.c中如何构建解析树,以及在syntax_analyzer.y中syntax_tree_node *node(const char *name, int children_num, …)这个函数的用法和作用。
-
了解bison与flex之间是如何协同工作的,如何共享token等。
-
看懂yylval.node的含义。
实验设计
1.补充lexical_analyzer.l
观察到pass_node
函数,flex将数值存入yylval,而bison读取yylval之中的值,所以对于每个token,我们都应该创建其的结点。
void pass_node(char *text){
yylval.node = new_syntax_tree_node(text);
}
接着修改lab1中的lexical_analyzer.l
补充到lab2中的lexical_analyzer.l
,lab1中正则定义即token保持不变。但对于每个token匹配时对应的动作进行修改,都添加一个pass_node(yytext)。
因为语法分析树不考虑注释COMMENT,换行EOL以及空格BLANK,所以不用建立结点不进行返回,只需要把pos_end,pos_start进行修改即可。对于其他未定义的字符只需要printf(“error\n”)。同时lab1中的void analyzer函数也不需要。
按照如下样例进行修改:
/* Example for you :-) */
\+ { pos_start = pos_end; pos_end += 1; pass_node(yytext); return ADD; }
\+ { pos_start = pos_end; pos_end += 1; 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;}
[a-zA-Z]+ { pos_start = pos_end;pos_end += strlen(yytext); pass_node(yytext); return IDENTIFIER;}
[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;}
[\n]+ {pos_start = 1;pos_end = 1;lines+=strlen(yytext);}
\/\*([^\*]|(\*)*[^\*\/])*(\*)*\*\/ {
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 0;}
至此,lexical_analyzer.l 文件修改完毕
2.补充syntax_analyzer.y
syntax_analyzer.y
给出了一个参考样例:
%start program
program : declaration-list { $$ = node("program", 1, $1); gt->root = $$; }
实验文档提到program是起始符号,node的函数原型是syntax_tree_node *node(const char *node_name, int children_num, ...);
,说明program节点的类型为 syntax_tree_node*
。
同时实验文档给出$$符号代表当前节点,已解析的节点则是从左到右依次编号,称作$1, $2, $3… 。
$$ = node("program", 1, $1)
语句表示为当前识别到的 program 创建一个结点,并将结点赋给 program,作为识别到的语法单元 program 在语法分析树中的结点。结点名字为"program"
program : declaration-list
表示program具备一个子结点,子结点为 declaration-list
gt->root = $$
,将 当前节点(program)作为根节点,使其作为语法分析树的起始。
因此可以看出,对于语法的分析结构应该为如下的形式:
parent : son { $$ = node("parent", 1, $1);}
| daughter { $$ = node("parent", 1, $1);}
| son AND daughter { $$ = node("parent", 3, $1, $2, $3);}
其中 parent、son、daughter 都是 syntax_tree_node* 类型,都为非终结符;AND 也是 syntax_tree_node* 类型,为终结符。
(1)补充%union
实验文档提到在 bison 解析过程中,每个 symbol 最终都对应到一个语义值上。或者说,在 parse tree 上,每个节点都对应一个语义值,这个值的类型是 YYSTYPE
。YYSTYPE
的具体内容是由 %union
构造指出的。不管是%token(终结符)还是%token(非终结符),都应该是syntax_tree_node * node
类型,这样才能构建解析树。
%union {
syntax_tree_node * node;
}
(2)补充%token
和%type
实验文档给出了%token
和%type
具体用法。%token的符号是大写,代表终结符,对应词法分析,而%type的符号是小写,代表非终结符,结合词法分析中 lexical_analyzer.l 中提供的 token (终结符)
ADD SUB MUL DIV LT LTE GT GTE EQ NEQ ASSIN
SEMICOLON COMMA LPARENTHESE RPARENTHESE LBRACKET RBRACKET
LBRACE RBRACE ELSE IF INT FLOAT RETURN VOID WHILE
IDENTIFIER INTEGER FLOATPOINT ARRAY LETTER
参考实验资料 README.md 中的 Cminus-f 语法的介绍(非终结符):
program → declaration-list \text{program} \rightarrow \text{declaration-list} program→declaration-list
declaration-list → declaration-list declaration ∣ declaration \text{declaration-list} \rightarrow \text{declaration-list}\ \text{declaration}\ |\ \text{declaration} declaration-list→declaration-list declaration ∣ declaration
declaration → var-declaration ∣ fun-declaration \text{declaration} \rightarrow \text{var-declaration}\ |\ \text{fun-declaration} declaration→var-declaration ∣ fun-declaration
一个
程序
由一系列声明
组成,声明包括了函数声明
与变量声明
,它们可以以任意顺序排列。全局变量需要初始化为全 0
所有的变量必须在使用前先进行声明,所有的函数必须在使用前先进行定义
一个
程序
中至少要有一个声明
,且最后一个声明
必须是void main(void)
形式的函数声明
。因为没有原型这个概念,
cminus-f
不区分函数的声明和定义。
var-declaration → type-specifier ID ; ∣ type-specifier ID [ INTEGER ] ; \text{var-declaration}\ \rightarrow \text{type-specifier}\ \textbf{ID}\ \textbf{;}\ |\ \text{type-specifier}\ \textbf{ID}\ \textbf{[}\ \textbf{INTEGER}\ \textbf{]}\ \textbf{;} var-declaration →type-specifier ID ; ∣ type-specifier ID [ INTEGER ] ;
type-specifier → int ∣ float ∣ void \text{type-specifier} \rightarrow \textbf{int}\ |\ \textbf{float}\ |\ \textbf{void} type-specifier→int ∣ float ∣ void
cminus-f
的基础类型只有整型(int)、浮点型(float)和void
。而在变量声明中,只有整型和浮点型可以使用,void
仅用于函数声明。一个
变量声明
定义一个整型或者浮点型的变量,或者一个整型或浮点型的数组变量(这里整型指的是32位有符号整型,浮点数是指32位浮点数)。数组变量在声明时, INTEGER \textbf{INTEGER} INTEGER 应当大于0。
一次只能声明一个变量。
fun-declaration → type-specifier ID ( params ) compound-stmt \text{fun-declaration} \rightarrow \text{type-specifier}\ \textbf{ID}\ \textbf{(}\ \text{params}\ \textbf{)}\ \text{compound-stmt} fun-declaration→type-specifier ID ( params ) compound-stmt
params → param-list ∣ void \text{params} \rightarrow \text{param-list}\ |\ \textbf{void} params→param-list ∣ void
param-list → param-list , param ∣ param \text{param-list} \rightarrow \text{param-list}\ ,\ \text{param}\ |\ \text{param} param-list→param-list , param ∣ param
param → type-specifier ID ∣ type-specifier ID [] \text{param} \rightarrow \text{type-specifier}\ \textbf{ID}\ |\ \text{type-specifier}\ \textbf{ID}\ \textbf{[]} param→type-specifier ID ∣ type-specifier ID []
函数声明
包含了返回类型,标识符,由逗号分隔的形参
列表,还有一个复合语句
。当函数的返回类型是
void
时,函数不返回任何值。函数的参数可以是
void
,也可以是一个列表。当函数的形参
是void
时,调用该函数时不用传入任何参数。
形参
中跟着中括号代表数组参数,它们可以有不同长度。整型参数通过值来传入函数(pass by value),而数组参数通过引用来传入函数(pass by reference,即指针)。
函数的
形参
拥有和函数声明
的复合语句
相同的作用域,并且每次函数调用都会产生一组独立内存的参数。(和C语言一致)函数可以递归调用。
- compound-stmt → { local-declarations statement-list } \text{compound-stmt} \rightarrow \textbf{\{}\ \text{local-declarations}\ \text{statement-list} \textbf{\}} compound-stmt→{ local-declarations statement-list}
一个
复合语句
由一对大括号和其中的局部声明
与语句列表
组成
复合语句
的执行时,对包含着的语句按照语句列表
中的顺序执行
局部声明
拥有和复合语句
中的语句列表
一样的作用域,且其优先级高于任何同名的全局声明(常见的静态作用域)
local-declarations → local-declarations var-declaration ∣ empty \text{local-declarations} \rightarrow \text{local-declarations var-declaration}\ |\ \text{empty} local-declarations→local-declarations var-declaration ∣ empty
statement-list → statement-list statement ∣ empty \text{statement-list} \rightarrow \text{statement-list}\ \text{statement}\ |\ \text{empty} statement-list→statement-list statement ∣ empty
statement → expression-stmt ∣ compound-stmt ∣ selection-stmt ∣ iteration-stmt ∣ return-stmt \begin{aligned}\text{statement} \rightarrow\ &\text{expression-stmt}\\ &|\ \text{compound-stmt}\\ &|\ \text{selection-stmt}\\ &|\ \text{iteration-stmt}\\ &|\ \text{return-stmt}\end{aligned} statement→ expression-stmt∣ compound-stmt∣ selection-stmt∣ iteration-stmt∣ return-stmt
expression-stmt → expression ; ∣ ; \text{expression-stmt} \rightarrow \text{expression}\ \textbf{;}\ |\ \textbf{;} expression-stmt→expression ; ∣ ;
局部声明
和语句列表
都可以为空(empty表示空字符串,即 ε \varepsilon ε)
表达式语句
由一个可选的表达式
(即可以没有表达式
)和一个分号组成我们通常使用
表达式语句
中的表达式
计算时产生的副作用,所以这种语句
用于赋值和函数调用
- selection-stmt → if ( expression ) statement ∣ if ( expression ) statement else statement \begin{aligned}\text{selection-stmt} \rightarrow\ &\textbf{if}\ \textbf{(}\ \text{expression}\ \textbf{)}\ \text{statement}\\ &|\ \textbf{if}\ \textbf{(}\ \text{expression}\ \textbf{)}\ \text{statement}\ \textbf{else}\ \text{statement}\end{aligned} selection-stmt→ if ( expression ) statement∣ if ( expression ) statement else statement
if
语句中的表达式
将被求值,若结果的值等于0,则第二个语句
执行(如果存在的话),否则第一个语句
会执行。为了避免歧义, else 将会匹配最近的将会匹配最近的 if \textbf{else}将会匹配最近的将会匹配最近的\textbf{if} else将会匹配最近的将会匹配最近的if
- iteration-stmt → while ( expression ) statement \text{iteration-stmt} \rightarrow \textbf{while}\ \textbf{(}\ \text{expression}\ \textbf{)}\ \text{statement} iteration-stmt→while ( expression ) statement
while
语句是cminus-f
中唯一的迭代语句
。它执行时,会不断对表达式
进行求值,并且在对表达式
的求值结果等于 0 前,循环执行执下面的语句
- return-stmt → return ; ∣ return expression ; \text{return-stmt} \rightarrow \textbf{return}\ \textbf{;}\ |\ \textbf{return}\ \text{expression}\ \textbf{;} return-stmt→return ; ∣ return expression ;
return
语句可以返回值,也可以不返回值。未声明为 void \textbf{void} void类型的函数必须返回和函数返回类型相同的值
return
会将程序的控制转移给当前函数的调用者,而 ‘ main ‘ `\textbf{main}` ‘main‘函数的return
会使得程序终止
expression → var = expression ∣ simple-expression \text{expression} \rightarrow \text{var}\ \textbf{=}\ \text{expression}\ |\ \text{simple-expression} expression→var = expression ∣ simple-expression
var → ID ∣ ID [ expression ] \text{var} \rightarrow \textbf{ID}\ |\ \textbf{ID}\ \textbf{[}\ \text{expression} \textbf{]} var→ID ∣ ID [ expression]
一个
表达式
可以是一个变量引用(即var
)接着一个赋值符号(=)以及一个表达式,也可以是一个简单表达式
。
var
可以是一个整型变量、浮点变量,或者一个取了下标的数组变量。数组的下标值是整型,它的值是表达式计算结果或结果进行类型转换后的整型值
一个负的下标会导致程序终止,需要调用框架中的内置函数
neg_idx_except
(该内部函数会主动退出程序,只需要调用该函数即可),但是对于上界并不做检查。赋值语义为:先找到
var
代表的变量地址(如果是数组,需要先对下标表达式求值),然后对右侧的表达式进行求值,求值结果将在转换成变量类型后存储在先前找到的地址中。同时,存储在var
中的值将作为赋值表达式的求值结果。在
C
中,赋值对象(即var
)必须是左值,而左值可以通过多种方式获得。cminus-f
中,唯一的左值就是通过var
的语法得到的,因此cminus-f
通过语法限制了var
为左值,而不是像C
中一样通过类型检查,这也是为什么cminus-f
中不允许进行指针算数。
simple-expression → additive-expression relop additive-expression ∣ additive-expression \text{simple-expression} \rightarrow \text{additive-expression}\ \text{relop}\ \text{additive-expression}\ |\ \text{additive-expression} simple-expression→additive-expression relop additive-expression ∣ additive-expression
relop → <= ∣ < ∣ > ∣ >= ∣ == ∣ != \text{relop}\ \rightarrow \textbf{<=}\ |\ \textbf{<}\ |\ \textbf{>}\ |\ \textbf{>=}\ |\ \textbf{==}\ |\ \textbf{!=} relop →<= ∣ < ∣ > ∣ >= ∣ == ∣ !=
简单表达式由无结合的关系操作符组成(即无括号的表达式仅有一个关系操作符)。简单表达式在它不包含关系操作符时,其值是加法表达式的值,或者如果关系算式求值为ture,其值为1,求值为false时值为0。
additive-expression → additive-expression addop term ∣ term \text{additive-expression} \rightarrow \text{additive-expression}\ \text{addop}\ \text{term}\ |\ \text{term} additive-expression→additive-expression addop term ∣ term
addop → + ∣ - \text{addop} \rightarrow \textbf{+}\ |\ \textbf{-} addop→+ ∣ -
term → term mulop factor ∣ factor \text{term} \rightarrow \text{term}\ \text{mulop}\ \text{factor}\ |\ \text{factor} term→term mulop factor ∣ factor
mulop → * ∣ / \text{mulop} \rightarrow \textbf{*}\ |\ \textbf{/} mulop→* ∣ /
一个
简单表达式
是一个加法表达式
或者两个加法表达式
的关系运算。当它是加法表达式
时,它的值就是加法表达式
的值。而当它是关系运算时,如果关系运算结果为真则值为整型值 1,反之则值为整型值 0。
加法表达式
表现出了四则运算的结合性质与优先级顺序,四则运算的含义和C
中的整型运算一致。浮点数和整型一起运算时,整型值需要进行类型提升,转换成浮点数类型,且运算结果也是浮点数类型
- factor → ( expression ) ∣ var ∣ call ∣ integer ∣ float \text{factor} \rightarrow \textbf{(}\ \text{expression}\ \textbf{)}\ |\ \text{var}\ |\ \text{call}\ |\ \text{integer}\ |\ \text{float} factor→( expression ) ∣ var ∣ call ∣ integer ∣ float
因数
可以是一个括号包围的表达式
(此时它的值是表达式
的值),或者是一个变量
(此时它的值是变量
的值),或者是一个函数调用
(此时它的值是函数调用
的返回值),或者是一个数字字面量
(此时它的值为该字面量的值)。当因数
是数组变量时,除非此时它被用作一个函数调用
中的数组参数,否则它必须要带有下标。
integer → INTEGER \text{integer} \rightarrow \textbf{INTEGER} integer→INTEGER
float → FLOATPOINT \text{float} \rightarrow \textbf{FLOATPOINT} float→FLOATPOINT
call → ID ( args ) \text{call} \rightarrow \textbf{ID}\ \textbf{(}\ \text{args} \textbf{)} call→ID ( args)
args → arg-list ∣ empty \text{args} \rightarrow \text{arg-list}\ |\ \text{empty} args→arg-list ∣ empty
arg-list → arg-list , expression ∣ expression \text{arg-list} \rightarrow \text{arg-list}\ \textbf{,}\ \text{expression}\ |\ \text{expression} arg-list→arg-list , expression ∣ expression
函数调用
由一个函数的标识符
与一组括号包围的实参
组成。实参
可以为空,也可以是由逗号分隔的的表达式
组成的列表,这些表达式代表着函数调用时,传给形参
的值。函数调用时
的实参
数量和类型必须与函数声明
中的形参
一致,必要时需要进行类型转换。
补充后的结果如下:
%token <node> ADD SUB MUL DIV LT LTE GT GTE EQ NEQ ASSIN
SEMICOLON COMMA LPARENTHESE RPARENTHESE LBRACKET RBRACKET
LBRACE RBRACE ELSE IF INT FLOAT RETURN VOID WHILE
IDENTIFIER INTEGER FLOATPOINT ARRAY LETTER
%type <node> program declaration-list declaration var-declaration
type-specifier fun-declaration params param-list param
compound-stmt local-declarations statement-list
statement expression-stmt selection-stmt iteration-stmt
return-stmt expression var simple-expression relop
additive-expression addop term mulop factor integer float call
args arg-list
(3) 根据实验中所给的Cminus-f语法,补充每个%type
的文法解析。
基本结构为:前面写出产生式左部,在:后面写出产生式右部,然后再在{}里面进行赋值定义,node的第一个参数为产生式左部名称,第二个参数代表有多少个子节点,然后再在后面的参数分别标上序号。需要注意的是,当解析为空时,node函数所传参数为(name,0),name对应字符串,而0表示孩子为空。 需要补充的文法解析详细内容在上一步已经给出,下面是补充完的结果。
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); }
实验结果验证
1.执行命令make parser
进行编译
报错说明 yyin
变量未声明。 需要在syntax_analyzer.y
头部加上 对 yyin 添加声明extern FILE *yyin;
。再次编译,成功编译。
2.执行命令./tests/lab2/test_syntax.sh easy
和./tests/lab2/test_syntax.sh normal
生成语法解析树
可以看到,能识别出源程序中错误的语法部分
3.使用 diff 命令进行验证,将自己的生成结果和助教提供的syntree_easy_std
和syntree_normal_std
进行比较。结果完全正确,没有任何输出结果。
4.自行设计testcase
进行测试。
测试程序如下:
int fun(int n){
return n*2;
}
int main(void) {
int a;
int b;
float c;
a=1+2*3;
b=(1+2)*3;
if(2>1){
a=a+1;
}
else b=b+1;
while(a>1){
a=a-1;
}
return fun(b);
}
主要测试的地方在于函数声明,函数调用,变量声明,+和*的优先级和括号改变运算优先级,if判断和while循环和return等地方。
输入命令./build/parser < tests/lab2/test1.cminus > tests/lab2/test1_tree
默认情况下先计算*再计算+
加了括号情况下先计算括号内的内容
此外还测试了一些错误的语句。
-
void fun(){}
是错误的,void fun(void){}
是正确的,因为函数参数要么为void要么为由逗号分割的表达式 -
n=2>1;
语句的意思是将简单表达式的计算结果赋给n,如果为真,赋值1;为假赋值0。这与c++的用法是相同的 -
还有一些错误的地方,比如
void fun(void){ int n; n=1; int c; }
是错误的,而
void fun(void){ int n; int c; n=1; }
是正确的。语法要求声明变量时要一次性声明。
int fun(void){ int a; int b=a; }
因为文法规则中不能在定义变量的时候赋值,所以
int b=a;
语句会出现语法错误。 -
函数的返回类型只能为int,void和float,如果为其他类型会报错
long fun(void){}
文法没给出long的定义,这里使用肯定会报错,同样用long声明变量也会报错
long a; a=1;
-
比如
a+=1;
和a++
等在c++惯用的用法也会报错。 -
但这个文法还是有些问题的,比如下面这个程序就不会报错
int main(void) { float c; c=1.2; c=fun(c); return 0; } int fun(int n){ return n*2; }
首先main函数调用fun函数,则fun函数应该在main前就声明。第二个错误是fun函数参数为int型,当传入float型变量时也不会报错。
实验反馈
在修改 lexical_analyzer.l 的部分,可以直接根据实验文档给的例子模仿补充,且也给出了pass_node函数的原型,使得修改自 Lab1 中 copy 的词法部分变得简单了许多。只不过需要考虑一些特殊的,例如 EOL、COMMENT、BLANK。一开始,对于这些 others,我是直接删去的。但后来在验证的时候,发现如果删去,则无法识别空格、换行符等无关信息,也无法识别注释等内容。这些内容会原封不动地进行输出,而导致输出结果错误(前面空了一大堆,或是存在注释)。随后,我把这些 others 加了回去,且考虑到这些只需要识别,而不需要做语法分析(在语法分析树中不占用内容),故直接去除 return 即可。
比较困难的地方在于补充syntax_analyzer.y
。首先实验文档只给出了文法,却没给出其具体含义。所以只能先按照样例补充完整,然后通过测试来求出各个文法的含义。
README.md 文档非常实用,写的很详细,为做实验提供了极大的帮助;源代码中的注释和样例也非常重要,减少了不少的弯路和尝试。