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

news2024/11/19 17:40:29

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/124790.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

3.3 直接耦合放大电路

工业控制中的很多物理量均为模拟量&#xff0c;如温度、流量、压力、液面、长度等&#xff0c;它们通过各种不同传感器转化成电量后也均为缓慢变化的非周期性信号&#xff0c;而且比较微弱&#xff0c;因而这类信号一般均需通过直接耦合放大电路后才能驱动负载。 一、直接耦合…

二叉树,红黑树,B树、B+树的区别

树的概念 树的演变 二叉搜索树 二叉搜索树可以提高查询效率&#xff0c;左小右大&#xff0c;但是他不好掌握根节点的数字是哪个&#xff0c;容易一边倒&#xff0c;导致层数变多&#xff0c;降低效率 平衡二叉搜索树 平衡二叉搜索树在二叉搜索树的基础上,通过控制任意一个节…

Shell“语言程序设计基础......“

Shell语言也有设计基础吗?...... 没有所谓的语言程序设计基础往往是一种就是菜鸡互啄的一种状态......哦,据说当年,发明笔记本电脑的人(想出发明笔记本电脑这个创意的人)一开始的工作是写说明书的Linux 或者 Unix 发明的那个年代应该还没有这种充满了营销的或者应试的表述方法…

密码学 密钥管理

密钥管理 出发点&#xff1a;在一种安全策略指导下的密钥产生&#xff0c;存储&#xff0c;分配&#xff0c;删除&#xff0c;归档和应用方案。 目的&#xff1a;维持系统各实体之间的密钥关系&#xff0c;抗击各种威胁&#xff1a; 1.密钥泄露 2.密钥和公钥身份真实性丧失 3…

计算机内存机制精讲

全文目录1、一个程序在计算机中到底是如何运行的&#xff1f;2、虚拟内存到底是什么&#xff1f;虚拟地址中间层思想3、虚拟地址空间以及编译模式CPU的数据处理能力编译模式32位编译模式64位编译模式4、内存对齐&#xff0c;提高寻址效率5、内存分页机制&#xff0c;完成虚拟地…

react-native学习过程记录

1、关于react-native init 创建项目报错 cli.init is not a function 问题解决 直接采用npx react-native init chapter2 --version 0.68.2 创建项目&#xff08;即指定version&#xff09; 参考地址&#xff1a;https://blog.csdn.net/qq_42231156/article/details/126396576…

【云原生进阶之容器】第二章Controller Manager原理剖析--2.1节Controller Manager综述

1 K8S Controller Manager原理解析 1.1 Controller Manager作用简述 一般来说,智能系统和自动系统通常会通过一个“操作系统”不断修正系统的工作状态。在Kubernetes集群中,每个Controller都是这样的一个"操作系统",它们通过APIServer 提供的(List-Watch)接口实…

小程序集成Three.js开发常见问题

1.加载模型后开发者工具卡顿 我想很多小伙伴都遇到过这个问题&#xff0c;网上很多方法&#xff0c;说什么清缓存&#xff0c;清内存&#xff0c;基本作用都不大。在不断的摸索中&#xff0c;我逐渐找到了一些解决的办法&#xff0c;希望对你有帮助。 (1) 截至发文时间&#…

Java-集合(1)

什么是集合&#xff1f; 在前面的学习中&#xff0c;保存多个数据&#xff0c;用的是数组。 但是数组有很多不同的地方&#xff1a; 1.长度开始时必须指定&#xff0c;且一旦指定无法更改 2.保存的必须为同一类型元素&#xff0c;虽说可以多态保存&#xff0c;但是限制性也很大…

再学C语言17:类型转换

语句和表达式通常只应该使用一种类型的常量和变量 如果使用混合类型&#xff0c;C将会使用一个规则集合自动完成类型转换 一、基本的规则 1&#xff09;当出现在表达式中时&#xff0c;有符号和无符号的char类型和short类型都将自动被转换为int&#xff08;在需要的情况下将…

新华三“智・行中国2022”|大厂行动,如何擘画“内循环”的数字未来?

作者 | 曾响铃 文 | 响铃说 继农业经济、工业经济之后&#xff0c;数字经济登上历史的舞台&#xff0c;成为大国的主要经济形态。在我国&#xff0c;根据中国信息通信研究院发布的《中国数字经济发展白皮书&#xff08;2022年&#xff09;》&#xff0c;截至2021年&#xff0…

关于浙大MPA复试的一些常规问题

2022年联考笔试刚刚落下帷幕&#xff0c;就收到很多小伙伴关于浙大MPA项目复试的问题咨询&#xff0c;趁着今天有空就大家问的一些问题在这里统一做个回复哦~1、问&#xff1a;正常情况下多少分可以进入浙大MPA项目复试&#xff1f; 答&#xff1a;统计了2018——2022浙大MPA项…

Harmony/OpenHarmony应用开发-转场动画组件内转场

组件内转场主要通过transition属性配置转场参数&#xff0c;在组件插入和删除时显示过渡动效&#xff0c;主要用于容器组件中的子组件插入和删除时&#xff0c;提升用户体验&#xff08;需要配合animateTo才能生效&#xff0c;动效时长、曲线、延时跟随animateTo中的配置&#…

GitHub官网下载Axios.js步骤

目录 前言必读 一、下载步骤 1.进入GitHub官网 2.搜索axios 3.点击这个axios/axios 4.点击压缩包下载 5.解压&#xff0c;进入到dist文件里面就找到了 二、使用方法 前言必读 读者手册&#xff08;必读&#xff09;_云边的快乐猫的博客-CSDN博客 一、下载步骤 1.进入…

数据在内存中的存储(11)

目录 1、数据类型介绍 1、类型的基本归类 1、整形家族&#xff1a; 2、浮点数家族&#xff1a; 3、构造类型&#xff08;自定义类型&#xff09;&#xff1a; 4、指针类型&#xff1a; 5、空类型&#xff1a; 2、整形在内存中的存储 1、原码、反码、补码 2、大小端介…

[框架]Mybatis的使用

目录一、数据持久化的概念及ORM&#xff08;Object Relational Mapping “对象关系映射”&#xff09;的原理1.1 持久化1.2 ORM&#xff08;Object Relational Mapping&#xff09;1.3 ORM解决方案&#xff08;包含四个部分&#xff09;二、MyBatis简介2.1 简介2.2 特点2.3 MyB…

数据结构-考研难点代码突破(C++实现无向图图最小生成树算法(Prim,Kruskal)图解操作细节(引自C语言中文网))

以代码的方式复习考研数据结构知识点&#xff0c;这里在考研不以代码为重点&#xff0c;而是以实现过程为重点 文章目录1. 无向图最小生成树算法Kruskal算法C代码实现Prim算法C代码实现1. 无向图最小生成树算法 常见基本概念记忆&#xff1a; 生成树定义&#xff1a; 无向图中…

MySQL索引相关知识

1、什么是索引&#xff1f; 索引是存储引擎用于提高数据库表的访问速度的一种数据结构。通过给字段​​添加索引​​​可以​​提高数据的读取速度​​​&#xff0c;提高项目的并发能力和抗压能力。​​索引优化​​​时mysql中的一种优化方式。索引的作用相当于​​图书的目录…

一文搞懂Linux内核进程原理及系统调用机制

进程四要素 有一段程序代其执行有进程专用的系统堆栈空间在内核有task_struct数据结构进程有独立的存储空间&#xff0c;拥有专有的用户空间 如果具备前三点缺少第四条&#xff0c;称为“线程”&#xff1b;如果完全没有用户空间&#xff0c;称为“内核线程”;如果共享用户空间…

中国剩余定理

最近总是用到中国剩余定理&#xff0c;以前对于这个定理非常的模糊&#xff0c;有时间静下心来简单的学习一下中国剩余定理&#xff0c;文章没有深度&#xff0c;写下这篇博客以作记录。 中国剩余定理CRT前言一、描述二、中国剩余定理求解方法1.除以三余二2.除以五余三3.除以七…