HNU-编译原理实验-cminus_compiler-2021-fall-master【2】-Lab2

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

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

二、实验难点

  • 如何识别空串产生式

空串产生式的产生,可以通过如下的结构表示:

parent : 
    | son
    | daughter

上述的代码表示 p a r e n t → ε   ∣   s o n   ∣   d a u g h t e r parent\to \varepsilon\ |\ son\ |\ daughter parentε  son  daughter 的语法产生式。

而对于此实验,在语法分析树中,还需添加表示空串的叶结点,用于表示语法单元选择了生成空串的产生式:

parent : { $$ = node("parent", 0);}
    | son		{ $$ = node("parent", 1, $1);}
    | daughter	{ $$ = node("parent", 1, $1);}
  • 识别形如 +的字符

在最开始的时候,我参考了 Cmiuns-f 的语法后,设计的代码中,对于+等字符的词法单元识别是如下的:

addop : '+'
   | '-'

但这样的设计,在添加对应的动作,即{ $$ = node("addop", 1, $1);}时,出现了 bug:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-a83Ewkml-1646219178467)(Markdown图片包/image-20211125112011879.png)]

尝试了很多解决方法后,要么是not declared的 bug,要么是类型不匹配(string 类型和 node 类型不匹配)的 bug。在仔细学习了 bison 和 flex 的协作模式后,才发现,+等字符实际上是对应 flex 中的词法单元,是由其识别,并返回 ADD等识别结果(token)。而返回的 token 才是 bison 中的语法单元。

因此,将代码修改如下:

addop : ADD { $$ = node("addop", 1, $1); }
   | SUB { $$ = node("addop", 1, $1); }

并设置 ADD、SUB 等为 token,类型设置为 node,编译通过,结果正确。

  • 调试信息的输出

原代码中的 yyerror 函数中,对于错误信息的输出fprintf(stderr, "error at line %d column %d: %s\n", lines, pos_start, s)包含内容不够全面,对于调试并不方便。

因此,参考 Lab1 中的错误信息输出代码,将此修改为fprintf(stderr, "[ERR]: unable to analysize %s at %d line, from %d to %d: %s\n", yytext, lines, pos_start, pos_end, s),便于调试。

  • 区别[ ][]

对于[,对应修改为终结符 LBRACKET;对于],对应修改为终结符 RBRACKET;而[],对应修改为终结符 ARRAY。

对应的代码样例为:

param : type-specifier IDENTIFIER { $$ = node("param", 2, $1, $2); }
   | type-specifier IDENTIFIER ARRAY { $$ = node("param", 3, $1, $2, $3); }

var : IDENTIFIER { $$ = node("var", 1, $1); }
 | IDENTIFIER LBRACKET expression RBRACKET { $$ = node("var", 4, $1, $2, $3, $4); }

三、实验设计

  • 先补齐 lexical_analyzer.l 文件

将 Lab1 中的词法部分复制到 Lab2 中的 lexical_analyzer.l,并做修改,构成 parser 的词法分析部分。

对于修改,参考样例:

/* Example for you :-) */
\+  { pos_start = pos_end; pos_end += 1; pass_node(yytext); return ADD; }

pos_start、pos_end 等部分不做改变,添加 pass_node 函数,用于 bison 和flex 之间的协作:

void pass_node(char *text){
  yylval.node = new_syntax_tree_node(text);
}

因此,将词法分析部分修改如下:

/******** 运算 ********/
/* ADD */
\+  { pos_start = pos_end; pos_end += 1; pass_node(yytext); return ADD; }
/* SUB */
\- { pos_start = pos_end;pos_end++; pass_node(yytext); return SUB;}
/* MUL */
\* { pos_start = pos_end;pos_end++; pass_node(yytext); return MUL;}
/* DIV */
\/ { pos_start = pos_end;pos_end++; pass_node(yytext); return DIV;}
/* LT,less */
\< { pos_start = pos_end;pos_end++; pass_node(yytext); return LT;}
/* LTE,less and equal */
\<\= { pos_start = pos_end;pos_end += 2; pass_node(yytext); return LTE;}
/* GT,greater */
\> { pos_start = pos_end;pos_end++; pass_node(yytext); return GT;}
/* GTE,greater and qual */
\>\= { pos_start = pos_end;pos_end += 2; pass_node(yytext); return GTE;}
/* EQ,equal */
\=\= { pos_start = pos_end;pos_end += 2; pass_node(yytext); return EQ;}
/* NEQ,not equal */
\!\= { pos_start = pos_end;pos_end += 2; pass_node(yytext); return NEQ;}
/* ASSIN,= */
\= { pos_start = pos_end;pos_end++; pass_node(yytext); return ASSIN;}

/******** 符号 ********/
/* SEMICOLON,; */
\; { pos_start = pos_end;pos_end++; pass_node(yytext); return SEMICOLON;}
/* COMMA */
\, { pos_start = pos_end;pos_end++; pass_node(yytext); return COMMA;}
/* LPARENTHESE */
\( { pos_start = pos_end;pos_end++; pass_node(yytext); return LPARENTHESE;}
/* RPARENTHESE */
\) { pos_start = pos_end;pos_end++; pass_node(yytext); return RPARENTHESE;}
/* LBRACKET */
\[ { pos_start = pos_end;pos_end++; pass_node(yytext); return LBRACKET;}
/* RBRACKET */
\] { pos_start = pos_end;pos_end++; pass_node(yytext); return RBRACKET;}
/* LBRACE */
\{ { pos_start = pos_end;pos_end++; pass_node(yytext); return LBRACE;}
/* RBRACE */
\} { pos_start = pos_end;pos_end++; pass_node(yytext); return RBRACE;}

/******** 关键字 ********/
/* ELSE */
else { pos_start = pos_end;pos_end += 4; pass_node(yytext); return ELSE;}
/* IF */
if { pos_start = pos_end;pos_end += 2; pass_node(yytext); return IF;}
/* INT */
int { pos_start = pos_end;pos_end += 3; pass_node(yytext); return INT;}
/* FLOAT */
float { pos_start = pos_end;pos_end += 5; pass_node(yytext); return FLOAT;}
/* RETURN */
return { pos_start = pos_end;pos_end += 6; pass_node(yytext); return RETURN;}
/* VOID */
void { pos_start = pos_end;pos_end += 4; pass_node(yytext); return VOID;}
/* WHILE */
while { pos_start = pos_end;pos_end += 5; pass_node(yytext); return WHILE;}

/******** ID和NUM ********/
/* IDENTIFIER */
[a-zA-Z]+ { pos_start = pos_end;pos_end += strlen(yytext); pass_node(yytext); return IDENTIFIER;}
/* INTEGER */
[0-9]+ { pos_start = pos_end;pos_end += strlen(yytext); pass_node(yytext); return INTEGER;}
/* FLOATPOINT */
[0-9]+\.|[0-9]*\.[0-9]+ { pos_start = pos_end;pos_end += strlen(yytext); pass_node(yytext); return FLOATPOINT;}
/* ARRAY */
\[\] { pos_start = pos_end;pos_end += 2; pass_node(yytext); return ARRAY;}
/* LETTER */
[a-zA-Z] { pos_start = pos_end;pos_end++; pass_node(yytext); return LETTER;}

同时对于 others 中的 EOL、COMMENT、BLANK,由于并不会在语法分析树中体现,但仍需要读取识别,故保持原有的正则表达式以及 pos_start、pos_end 等的代码,但删去 return 代码(即不返回,仅读取识别),也不添加 pass_node(因为无需传到 bison 中):

/******** others ********/
/* EOL */
[\n] { pos_start = 1;pos_end = 1;lines++;}
/* COMMENT */
\/\*([*]*(([^*/])+([/])*)*)*\*\/ {}
/* BLANK */
[ \f\n\r\t\v] { pos_end++;pos_start = pos_end;}

至此,lexical_analyzer.l 文件修改完毕


  • 再补全 syntax_analyzer.y 文件

分析参考样例:

%start program
program : declaration-list { $$ = node("program", 1, $1); gt->root = $$; }

再查看 node 函数的函数原型syntax_tree_node *node(const char *node_name, int children_num, ...),结合实验资料 README.md 中对于 bison 语法的介绍,可以看出 program 为语法分析的起始,该结点类型为 syntax_tree_node*。

$$ = node("program", 1, $1)表示为当前识别到的 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* 类型,为终结符。

再参考实验资料 README.md 中的 Cminus-f 语法的介绍:

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

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

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

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

………………

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

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

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

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

arg-list → arg-list  ,  expression  ∣  expression \text{arg-list} \rightarrow \text{arg-list}\ \textbf{,}\ \text{expression}\ |\ \text{expression} arg-listarg-list , expression  expression

并结合词法分析中 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

可以编写出对应的语法分析代码:

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

由于这些语法单元的类型均为 syntax_tree_node* 类型,其中部分为终结符,部分为非终结符,故可在语法分析中为各语法单元设置类型,并设置 token:

%union {
    syntax_tree_node* node
}
%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

在编译的过程中,发现yyin变量未声明。经过查找对应资料后,对 yyin 添加声明extern FILE *yyin;。再次编译,成功编译。

四、实验结果验证

编译,输入指令make parser

在这里插入图片描述

可以看到,编译成功

验证,输入指令./tests/lab2/test_syntax.sh easy yes

在这里插入图片描述

验证,输入指令./tests/lab2/test_syntax.sh normal yes

在这里插入图片描述

可以看到,normal 例子都测试正确。

至此,代码编写正确,通过测试样例,结果正确!

五、实验反馈

总体来说,有了 Lab1 的铺垫,这个 Lab2 的难度上较为适中。

在修改 lexical_analyzer.l 的部分,由于有了+的例子,且也写了pass_node函数的原型,使得修改自 Lab1 中 copy 的词法部分变得简单了许多。只不过需要考虑一些特殊的,例如 EOL、COMMENT、BLANK。一开始,对于这些 others,我是直接删去的。但后来在验证的时候,发现如果删去,则无法识别空格、换行符等无关信息,也无法识别注释等内容。这些内容会原封不动地进行输出,而导致输出结果错误(前面空了一大堆,或是存在注释)。随后,我把这些 others 加了回去,且考虑到这些只需要识别,而不需要做语法分析(在语法分析树中不占用内容),故直接去除 return 即可。

在编写 syntax_analyzer.y 的时候,走了不少弯路。因为 README.md 文档中给的例子和我所需要写的,不太一样,故按照那个写的时候,经常报错。特别是处理空串的部分,直到我搜索了很多资料,看了大量论坛上的样例的写法,才知道如何处理空串。

而对于+ -等字符,我所需写的语法分析也和样例中的很不一样。因为样例中的+-不是由词法分析部分识别,而是语法部分直接字符匹配,故直接写出;但我的+ - 乃至<=等字符,是在词法分析中处理的,故在语法分析中,是用 ADD、SUB 等词法单元代替这些。而具体的传输,则是通过 yylval,其 node 中的 name 为 yytext,从而使词法分析得到的结果进行传输。而其他内容,例如 pos_start 等,则是通过设定为全局变量,在词法分析部分用 exern 进行调用。

在写词法分析部分的时候,由于内容比较多且繁杂,故需要十分仔细。我在编写的过程中,由于在设置子结点时将设置数目写为 4,但仅传入了 3 个子结点,而导致了违规访问 Segmentation Fault。为了这个 bug,查了好久,有点可惜。

README.md 文档非常实用,写的很详细,为做实验提供了极大的帮助;源代码中的注释和样例也非常重要,减少了不少的弯路和尝试。

最后,实验成功做完,完结撒花!

  • 6
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值