HNU编译原理实验二cminus_compiler-2022-fall

前言:个人感觉比第一次的难,借鉴了前辈的报告才勉强看懂在干嘛

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
|  |  |  |  |  |  |  |  >--* ;
|  |  |  |  |  >--* }

请注意,上述解析树含有每个解析规则的所有子成分,包括诸如 ; { } 这样的符号,请在编写规则时务必不要忘了它们。

实验难点

  1. 需要了解bison的基本知识,懂得怎么将文法产生式转化为bison语句。

  2. 了解在SyntaxTree.c中如何构建解析树,以及在syntax_analyzer.y中syntax_tree_node *node(const char *name, int children_num, …)这个函数的用法和作用。

  3. 了解bison与flex之间是如何协同工作的,如何共享token等。

  4. 看懂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 上,每个节点都对应一个语义值,这个值的类型是 YYSTYPEYYSTYPE 的具体内容是由 %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 语法的介绍(非终结符):

  1. program → declaration-list \text{program} \rightarrow \text{declaration-list} programdeclaration-list

  2. declaration-list → declaration-list declaration  ∣  declaration \text{declaration-list} \rightarrow \text{declaration-list}\ \text{declaration}\ |\ \text{declaration} declaration-listdeclaration-list declaration  declaration

  3. declaration → var-declaration  ∣  fun-declaration \text{declaration} \rightarrow \text{var-declaration}\ |\ \text{fun-declaration} declarationvar-declaration  fun-declaration

一个程序由一系列声明组成,声明包括了函数声明变量声明,它们可以以任意顺序排列。

全局变量需要初始化为全 0

所有的变量必须在使用前先进行声明,所有的函数必须在使用前先进行定义

一个程序中至少要有一个声明,且最后一个声明必须是 void main(void) 形式的函数声明

因为没有原型这个概念, cminus-f 不区分函数的声明和定义。

  1. 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 ] ;

  2. type-specifier → int   ∣   float   ∣   void \text{type-specifier} \rightarrow \textbf{int}\ |\ \textbf{float}\ |\ \textbf{void} type-specifierint  float  void

cminus-f 的基础类型只有整型(int)、浮点型(float)和 void。而在变量声明中,只有整型和浮点型可以使用,void 仅用于函数声明。

一个变量声明定义一个整型或者浮点型的变量,或者一个整型或浮点型的数组变量(这里整型指的是32位有符号整型,浮点数是指32位浮点数)。

数组变量在声明时, INTEGER \textbf{INTEGER} INTEGER 应当大于0。

一次只能声明一个变量。

  1. 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-declarationtype-specifier ID ( params ) compound-stmt

  2. params → param-list  ∣   void \text{params} \rightarrow \text{param-list}\ |\ \textbf{void} paramsparam-list  void

  3. param-list → param-list  ,  param  ∣  param \text{param-list} \rightarrow \text{param-list}\ ,\ \text{param}\ |\ \text{param} param-listparam-list , param  param

  4. param → type-specifier  ID   ∣  type-specifier  ID   [] \text{param} \rightarrow \text{type-specifier}\ \textbf{ID}\ |\ \text{type-specifier}\ \textbf{ID}\ \textbf{[]} paramtype-specifier ID  type-specifier ID []

函数声明包含了返回类型,标识符,由逗号分隔的形参列表,还有一个复合语句

当函数的返回类型是 void 时,函数不返回任何值。

函数的参数可以是 void ,也可以是一个列表。当函数的形参void时,调用该函数时不用传入任何参数。

形参中跟着中括号代表数组参数,它们可以有不同长度。

整型参数通过值来传入函数(pass by value),而数组参数通过引用来传入函数(pass by reference,即指针)。

函数的形参拥有和函数声明复合语句相同的作用域,并且每次函数调用都会产生一组独立内存的参数。(和C语言一致)

函数可以递归调用。

  1. compound-stmt → {  local-declarations statement-list } \text{compound-stmt} \rightarrow \textbf{\{}\ \text{local-declarations}\ \text{statement-list} \textbf{\}} compound-stmt{ local-declarations statement-list}

一个复合语句由一对大括号和其中的局部声明语句列表组成

复合语句的执行时,对包含着的语句按照语句列表中的顺序执行

局部声明拥有和复合语句中的语句列表一样的作用域,且其优先级高于任何同名的全局声明(常见的静态作用域)

  1. local-declarations → local-declarations var-declaration  ∣  empty \text{local-declarations} \rightarrow \text{local-declarations var-declaration}\ |\ \text{empty} local-declarationslocal-declarations var-declaration  empty

  2. statement-list → statement-list statement  ∣  empty \text{statement-list} \rightarrow \text{statement-list}\ \text{statement}\ |\ \text{empty} statement-liststatement-list statement  empty

  3. 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

  4. expression-stmt → expression  ;   ∣   ; \text{expression-stmt} \rightarrow \text{expression}\ \textbf{;}\ |\ \textbf{;} expression-stmtexpression ;  ;

局部声明语句列表都可以为空(empty表示空字符串,即 ε \varepsilon ε

表达式语句由一个可选的表达式(即可以没有表达式)和一个分号组成

我们通常使用表达式语句中的表达式计算时产生的副作用,所以这种语句用于赋值和函数调用

  1. 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

  1. iteration-stmt → while   (  expression  )  statement \text{iteration-stmt} \rightarrow \textbf{while}\ \textbf{(}\ \text{expression}\ \textbf{)}\ \text{statement} iteration-stmtwhile ( expression ) statement

while语句是 cminus-f 中唯一的迭代语句。它执行时,会不断对表达式进行求值,并且在对表达式的求值结果等于 0 前,循环执行执下面的语句

  1. return-stmt → return   ;   ∣   return  expression  ; \text{return-stmt} \rightarrow \textbf{return}\ \textbf{;}\ |\ \textbf{return}\ \text{expression}\ \textbf{;} return-stmtreturn ;  return expression ;

return语句可以返回值,也可以不返回值。

未声明为 void \textbf{void} void类型的函数必须返回和函数返回类型相同的值

return会将程序的控制转移给当前函数的调用者,而 ‘ main ‘ `\textbf{main}` main函数的return会使得程序终止

  1. expression → var  =  expression  ∣  simple-expression \text{expression} \rightarrow \text{var}\ \textbf{=}\ \text{expression}\ |\ \text{simple-expression} expressionvar = expression  simple-expression

  2. var → ID   ∣   ID   [  expression ] \text{var} \rightarrow \textbf{ID}\ |\ \textbf{ID}\ \textbf{[}\ \text{expression} \textbf{]} varID  ID [ expression]

一个表达式可以是一个变量引用(即var)接着一个赋值符号(=)以及一个表达式,也可以是一个简单表达式

var 可以是一个整型变量、浮点变量,或者一个取了下标的数组变量。

数组的下标值是整型,它的值是表达式计算结果或结果进行类型转换后的整型值

一个负的下标会导致程序终止,需要调用框架中的内置函数neg_idx_except (该内部函数会主动退出程序,只需要调用该函数即可),但是对于上界并不做检查。

赋值语义为:先找到 var 代表的变量地址(如果是数组,需要先对下标表达式求值),然后对右侧的表达式进行求值,求值结果将在转换成变量类型后存储在先前找到的地址中。同时,存储在 var 中的值将作为赋值表达式的求值结果。

C 中,赋值对象(即 var )必须是左值,而左值可以通过多种方式获得。cminus-f中,唯一的左值就是通过 var 的语法得到的,因此 cminus-f 通过语法限制了 var 为左值,而不是像 C 中一样通过类型检查,这也是为什么 cminus-f 中不允许进行指针算数。

  1. 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-expressionadditive-expression relop additive-expression  additive-expression

  2. relop  → <=   ∣   <   ∣   >   ∣   >=   ∣   ==   ∣   != \text{relop}\ \rightarrow \textbf{<=}\ |\ \textbf{<}\ |\ \textbf{>}\ |\ \textbf{>=}\ |\ \textbf{==}\ |\ \textbf{!=} relop <=  <  >  >=  ==  !=

简单表达式由无结合的关系操作符组成(即无括号的表达式仅有一个关系操作符)。简单表达式在它不包含关系操作符时,其值是加法表达式的值,或者如果关系算式求值为ture,其值为1,求值为false时值为0。

  1. additive-expression → additive-expression addop term  ∣  term \text{additive-expression} \rightarrow \text{additive-expression}\ \text{addop}\ \text{term}\ |\ \text{term} additive-expressionadditive-expression addop term  term

  2. addop → +   ∣   - \text{addop} \rightarrow \textbf{+}\ |\ \textbf{-} addop+  -

  3. term → term mulop factor  ∣  factor \text{term} \rightarrow \text{term}\ \text{mulop}\ \text{factor}\ |\ \text{factor} termterm mulop factor  factor

  4. mulop → *   ∣   / \text{mulop} \rightarrow \textbf{*}\ |\ \textbf{/} mulop*  /

一个简单表达式是一个加法表达式或者两个加法表达式的关系运算。当它是加法表达式时,它的值就是加法表达式的值。而当它是关系运算时,如果关系运算结果为真则值为整型值 1,反之则值为整型值 0。

加法表达式表现出了四则运算的结合性质与优先级顺序,四则运算的含义和C中的整型运算一致。

浮点数和整型一起运算时,整型值需要进行类型提升,转换成浮点数类型,且运算结果也是浮点数类型

  1. 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

因数可以是一个括号包围的表达式(此时它的值是表达式的值),或者是一个变量(此时它的值是变量的值),或者是一个函数调用(此时它的值是函数调用的返回值),或者是一个数字字面量(此时它的值为该字面量的值)。当因数是数组变量时,除非此时它被用作一个函数调用中的数组参数,否则它必须要带有下标。

  1. integer → INTEGER \text{integer} \rightarrow \textbf{INTEGER} integerINTEGER

  2. float → FLOATPOINT \text{float} \rightarrow \textbf{FLOATPOINT} floatFLOATPOINT

  3. call → ID   (  args ) \text{call} \rightarrow \textbf{ID}\ \textbf{(}\ \text{args} \textbf{)} callID ( args)

  4. args → arg-list  ∣  empty \text{args} \rightarrow \text{arg-list}\ |\ \text{empty} argsarg-list  empty

  5. arg-list → arg-list  ,  expression  ∣  expression \text{arg-list} \rightarrow \text{arg-list}\ \textbf{,}\ \text{expression}\ |\ \text{expression} arg-listarg-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_stdsyntree_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

默认情况下先计算*再计算+

在这里插入图片描述

加了括号情况下先计算括号内的内容

在这里插入图片描述

此外还测试了一些错误的语句。

  1. void fun(){}是错误的,void fun(void){}是正确的,因为函数参数要么为void要么为由逗号分割的表达式

  2. n=2>1;语句的意思是将简单表达式的计算结果赋给n,如果为真,赋值1;为假赋值0。这与c++的用法是相同的

  3. 还有一些错误的地方,比如

    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;语句会出现语法错误。

  4. 函数的返回类型只能为int,void和float,如果为其他类型会报错

    long fun(void){}
    

    文法没给出long的定义,这里使用肯定会报错,同样用long声明变量也会报错

    long a;
    a=1;
    
  5. 比如a+=1;a++等在c++惯用的用法也会报错。

  6. 但这个文法还是有些问题的,比如下面这个程序就不会报错

    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 文档非常实用,写的很详细,为做实验提供了极大的帮助;源代码中的注释和样例也非常重要,减少了不少的弯路和尝试。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值