HNU-编译原理-实验2-Bison

news2024/11/19 6:23:53

编译原理实验2
Bison

计科210X 甘晴void 202108010XXX
在这里插入图片描述

实验要求

详细的实验项目文档为 https://gitee.com/coderwym/cminus_compiler-2023-fall/tree/master/Documentations/lab2

实验步骤

本次实验需要在 Lab1 已完成的 flex 词法分析器的基础上,进一步使用 bison 完成语法分析器。

1.了解 bison 基础知识和理解 Cminus-f 语法(重在了解如何将文法产生式转换为 bison 语句)
2.阅读 /src/common/SyntaxTree.c,对应头文件 /include/SyntaxTree.h(重在理解分析树如何生成)
3.了解 bison 与 flex 之间是如何协同工作,看懂pass_node函数并改写 Lab1 代码(提示:了解 yylval 是如何工作,在代码层面上如何将值传给$1、$2等)
4.补全 src/parser/syntax_analyzer.y 文件和 lexical_analyzer.l 文件
Tips:在未编译的代码文件中是无法看到关于协同工作部分的代码,建议先编译 1.3 给出的计算器样例代码,再阅读 /build/src/parser/ 中的 syntax_analyzer.h 与 syntax_analyzer.c 文件

思考题
本部分不算做实验分,出题的本意在于想要帮助同学们加深对实验细节的理解,欢迎有兴趣和余力的同学在报告中写下你的思考答案,或者在issue中分享出你的看法。

1.在1.3样例代码中存在左递归文法,为什么 bison 可以处理?(提示:不用研究bison内部运作机制,在下面知识介绍中有提到 bison 的一种属性,请结合课内知识思考)
2.请在代码层面上简述下 yylval 是怎么完成协同工作的。(提示:无需研究原理,只分析维护了什么数据结构,该数据结构是怎么和$1、$2等联系起来?)
3.请尝试使用1.3样例代码运行除法运算除数为0的例子(测试case中有)看下是否可以通过,如果不,为什么我们在case中把该例子认为是合法的?(请从语法与语义上简单思考)
4.能否尝试修改下1.3计算器文法,使得它支持除数0规避功能。

实验基础知识

1.Cminus-f语法

①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

②Cminus-f语法

$\text{program} \rightarrow \text{declaration-list}$
$\text{declaration-list} \rightarrow \text{declaration-list}\ \text{declaration}\ |\ \text{declaration}$
$\text{declaration} \rightarrow \text{var-declaration}\ |\ \text{fun-declaration}$
$\text{var-declaration}\ \rightarrow \text{type-specifier}\ \textbf{ID}\ \textbf{;}\ |\ \text{type-specifier}\ \textbf{ID}\ \textbf{[}\ \textbf{INTEGER}\ \textbf{]}\ \textbf{;}$
$\text{type-specifier} \rightarrow \textbf{int}\ |\ \textbf{float}\ |\ \textbf{void}$
$\text{fun-declaration} \rightarrow \text{type-specifier}\ \textbf{ID}\ \textbf{(}\ \text{params}\ \textbf{)}\ \text{compound-stmt}$
$\text{params} \rightarrow \text{param-list}\ |\ \textbf{void}$
$\text{param-list} \rightarrow \text{param-list}\ ,\ \text{param}\ |\ \text{param}$
$\text{param} \rightarrow \text{type-specifier}\ \textbf{ID}\ |\ \text{type-specifier}\ \textbf{ID}\ \textbf{[]}$
$\text{compound-stmt} \rightarrow \textbf{\{}\ \text{local-declarations}\ \text{statement-list} \textbf{\}}$
$\text{local-declarations} \rightarrow \text{local-declarations var-declaration}\ |\ \text{empty}$
$\text{statement-list} \rightarrow \text{statement-list}\ \text{statement}\ |\ \text{empty}$
$\begin{aligned}\text{statement} \rightarrow\ &\text{expression-stmt}\\ &|\ \text{compound-stmt}\\ &|\ \text{selection-stmt}\\ &|\ \text{iteration-stmt}\\ &|\ \text{return-stmt}\end{aligned}$
$\text{expression-stmt} \rightarrow \text{expression}\ \textbf{;}\ |\ \textbf{;}$
$\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}$
$\text{iteration-stmt} \rightarrow \textbf{while}\ \textbf{(}\ \text{expression}\ \textbf{)}\ \text{statement}$
$\text{return-stmt} \rightarrow \textbf{return}\ \textbf{;}\ |\ \textbf{return}\ \text{expression}\ \textbf{;}$
$\text{expression} \rightarrow \text{var}\ \textbf{=}\ \text{expression}\ |\ \text{simple-expression}$
$\text{var} \rightarrow \textbf{ID}\ |\ \textbf{ID}\ \textbf{[}\ \text{expression} \textbf{]}$
$\text{simple-expression} \rightarrow \text{additive-expression}\ \text{relop}\ \text{additive-expression}\ |\ \text{additive-expression}$
$\text{relop}\ \rightarrow \textbf{<=}\ |\ \textbf{<}\ |\ \textbf{>}\ |\ \textbf{>=}\ |\ \textbf{==}\ |\ \textbf{!=}$
$\text{additive-expression} \rightarrow \text{additive-expression}\ \text{addop}\ \text{term}\ |\ \text{term}$
$\text{addop} \rightarrow \textbf{+}\ |\ \textbf{-}$
$\text{term} \rightarrow \text{term}\ \text{mulop}\ \text{factor}\ |\ \text{factor}$
$\text{mulop} \rightarrow \textbf{*}\ |\ \textbf{/}$
$\text{factor} \rightarrow \textbf{(}\ \text{expression}\ \textbf{)}\ |\ \text{var}\ |\ \text{call}\ |\ \text{integer}\ |\ \text{float}$
$\text{integer} \rightarrow \textbf{INTEGER}$
$\text{float} \rightarrow \textbf{FLOATPOINT}$
$\text{call} \rightarrow \textbf{ID}\ \textbf{(}\ \text{args} \textbf{)}$
$\text{args} \rightarrow \text{arg-list}\ |\ \text{empty}$
$\text{arg-list} \rightarrow \text{arg-list}\ \textbf{,}\ \text{expression}\ |\ \text{expression}$

2.Bison基础知识

Bison 是一款解析器生成器(parser generator),它的作用是将 LALR 文法转换成可编译的 C 代码。

实验文档给出了如下的示例代码,简要示范了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)
{
	// 获取下一个待分析token
    }
}

void yyerror(const char *s)
{
    // 报错处理机制
}

//main函数不一定在.y中,可以通过链接实现
int main(void)
{
    yyparse(); // 启动解析
    return 0;
}

总结可知,Bison与Lex文件的编写规则类似,由%%区分的三部分构成,开头和结尾会被直接加入.c文件。

3.了解树的生成过程

阅读 /src/common/SyntaxTree.c,对应头文件 /include/SyntaxTree.h

首先看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 */

而SyntaxTree.c中是对应的具体实现,就不看了。

每个终结符都对应着一个叶子节点,这个叶子节点在词法分析时就可以产生。在自底向上的分析过程中,首先产生的是叶子节点,在用产生式进行规约时向上构建语法分析树。叶子节点的产生在词法分析器中的pass_node()函数中实现,创建一个新的节点,并将其指针赋值给yylval,节点名为其成分(非终结符名或终结符名),这样语法分析器就可以使用该节点构造语法分析树。

4.Bison和Flex的协同工作

在语法分析过程中,语法分析树的叶子节点是一个具体的语义值,该值的类型是YYSTYPE,在Bison中用%union指明。不同的节点对应着不同的终结符,可能为不同的类型,因此union中可以包含不同的数据类型。可以指明一个终结符或是非终结符的类型,以便后续的使用。可以使用%type <>或%token <>指明类型。其中%token是在声明词法单元名的同时指明类型,声明的token会由Bison导出到最终的.h文件中,让词法分析器也可以直接使用。

参考文档给出了一个计算器样例的实现代码,并对新出现的构造进行解释。

①YYSTYPE

在 bison 解析过程中,每个 symbol 最终都对应到一个语义值上。或者说,在 parse tree 上,每个节点都对应一个语义值,这个值的类型是 YYSTYPEYYSTYPE 的具体内容是由 %union 构造指出的。例如:

%union {
  char   op;
  double num;
}

会生成这样的代码

typedef union YYSTYPE {
  char op;
  double num;
} YYSTYPE;

使用union是为了让不同的类型读取同一片存储空间。因为不同节点可能需要不同类型的语义值。比如,上面的例子中,我们希望 ADDOP 的值是 char 类型,而 NUMBER 应该是 double 类型的。

②规约

term : term ADDOP factor
     {
        switch $2 {
        case '+': $$ = $1 + $3; break;
        case '-': $$ = $1 - $3; break;
        }
     }

前节点使用 $$ 代表,而已解析的节点则是从左到右依次编号,称作 $1, $2, $3

③%type <>

bison如何确定对于union的部分应该取哪个值?文件开始的%type%token给出了定义。例如term 应该使用 num 部分,那么我们就写

%type <num> term

遇到term时,bison就会去取其num。

④%token

当我们用 %token 声明一个 token 时,这个 token 就会导出到 .h 中,可以在 C 代码中直接使用(注意 token 名千万不要和别的东西冲突!),供 flex 使用。%token <op> ADDOP 与之类似,但顺便也将 ADDOP 传递给 %type

⑤yylval

这时候我们可以打开 .h 文件,看看里面有什么。除了 token 定义,最末尾还有一个 extern YYSTYPE yylval; 。这个变量我们上面已经使用了,通过这个变量,我们就可以在 lexer 里面设置某个 token 的值。

实验原理

由main.c下调用parse函数(在syntax_analyzer.y)从input_path中读取待处理的字符,使用syntax_analyzer.l中定义的词法处理规则处理token,更新pos和lines,将该token的yytext使用pass_node函数传递给yylval,并返回该token的类型。yylval负责将该token传递,并建立语法分析树。

实验过程

1.词法分析部分

目标对象:./src/parser/lexical_analyzer.l

①void analyzer()函数

在Lab1中是因为main.c函数调用了void analyzer(char* input_file, Token_Node* token_stream)这个函数,而现在Lab2中的main.c并没有使用该函数,故这个函数可以删去,但原来在这个函数中实现的功能需要迁移。

②有关others的处理(\n,注释,\t," "等)

在Lab1中也需要对于注释这些进行词法分析,并输出这些所在的位置,而在Lab2中占主导的是Bison,Lex主要干的事情只是辅助识别出token并将token转交给Bison构建语法分析树,故others其实并不需要返回,即识别到这些的这些东西只需要直接忽略就可以了(只是更新lines与pos),故这里需要删除对于others的return操作。

包括ERROR的处理,现在也是放在syntax_analyzer中的yyerror里,故这里的ERROR也可以删去return操作。

③添加pass_node(yytext)

在识别动作中要添加pass_node(yytext)产生词法单元叶子节点,

识别出来的正常token通过yylval传递给语法分析器

%%
 /* 运算 */
\+   {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+=yyleng; pass_node(yytext); return IDENTIFIER;}
[0-9]+    {pos_start = pos_end; pos_end+=yyleng; pass_node(yytext); return INTEGER;}
[0-9]+\.|[0-9]*\.[0-9]+ {pos_start = pos_end; pos_end+=yyleng; pass_node(yytext); return FLOATPOINT;}
"[]" {pos_start = pos_end; pos_end+=2; pass_node(yytext); return ARRAY;}
[a-zA-Z]  {pos_start = pos_end; pos_end++; pass_node(yytext); return LETTER;}

 /* others */
// 换行操作本来是在void analyzer(char* input_file, Token_Node* token_stream)这个函数内实现的,但是由于在Lab2该函数被删去,故需要挪至这里直接实现。
\n  {lines++; pos_end=1;}
\/\*[^*]*\*+([^/*][^*]*\*+)*\/  {
                for (int i=0;i<yyleng;i++){
                    if (yytext[i]=='\n'){   /*换行操作*/
                        lines++;
                        pos_end=1;
                    }
                    else pos_end++;
                }}
" " {pos_start = pos_end; pos_end+=yyleng;}
\t  {pos_start = pos_end; pos_end+=yyleng;}
. {}

2.语法分析部分

目标对象:./src/parser/lexical_analyzer.l

①完成yylval的定义

在union中只含有一个节点指针。

%union {
    syntax_tree_node *node;
}

②终结符(词法单元)的声明和非终结符的类型声明

它们的类型都是语法分析树的节点指针,其中终结符名要和词法分析部分中的token一致,非终结符名和Cminus-f的语法规则中一致。声明如下:

%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 program

③补充语法规则

规则按照给出的Cminus-f的语法编写,动作则是调用node()函数构造语法分析树的节点,参数为子节点个数和使用$n表示的子节点的指针,当产生式为空输入时,参数为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); }
         ;

完成了以上的补充后,语法分析和词法分析就应该都可以正常进行了。尝试编译时提示缺少yyin的声明,在语法分析函数parse中使用了yyin来进行读入,yyin是词法分析Flex产生的变量,这里需要引入,因此在开头补充引入该文件指针变量。

extern FILE *yyin;

实验结果验证

首先进行make操作,使用如下命令

make parser

若无问题,将出现如下结果

在这里插入图片描述

①给定案例验证

#验证easy案例
./tests/lab2/test_syntax.sh easy
diff ./tests/lab2/syntree_easy ./tests/lab2/syntree_easy_std

#验证normal案例
./tests/lab2/test_syntax.sh normal
diff ./tests/lab2/syntree_normal ./tests/lab2/syntree_normal_std

#实验还给出了其他自动化验证方法
$ ./tests/lab2/test_syntax.sh easy yes
  #分析所有 .cminus 文件并将结果与标准对比,仅输出有差异的文件名
$ ./tests/lab2/test_syntax.sh easy verbose
  #分析所有 .cminus 文件并将结果与标准对比,详细输出所有差异

先验证easy,运行截图如下,未出现问题。

在这里插入图片描述

再验证normal,运行截图如下,未出现问题。

在这里插入图片描述

②正确案例验证

对于自定义的案例验证,可以用重定向来进行

$ ./build/parser < test.cminus > out
#此时程序从 test.cminus 文件中读取输入,因此不需要输入任何内容。
#如果遇到了错误,将程序将报错并退出;否则,将输出解析树到 out 文件中。

或者我们不使用>out的话就会直接向终端输出结果

试试下面这个正确的程序。

# test1
int function(int a[], int n){
	int i;
	int sum;
    i = n;
    sum = 0;
    while(i>0){
        sum = sum+a[i];
        i--;
    }
    return ;
}

我们将程序保存到tests/lab2/test1.cminus,并使用代码执行运行

./build/parser < tests/lab2/test1.cminus

运行结果如下,能够正常解析。

在这里插入图片描述

③错误案例验证

编写一个存在语法错误的程序,cminus语法中变量不可以在一个声明语句声明多个同类型变量。同时,还有一个问题,–和++并没有在cminus语法中定义。

# test3
int function(int a[], int n){
	int i,sum;
    i = n;
    sum = 0;
    while(i>0){
        sum = sum+a[i];
        i--;
    }
    return ;
}

进行验证如下:

./build/parser < tests/lab2/test3.cminus

运行截图如下,能够找到问题所在

在这里插入图片描述

思考题

1.在1.3样例代码中存在左递归文法,为什么 bison 可以处理?(提示:不用研究bison内部运作机制,在下面知识介绍中有提到 bison 的一种属性,请结合课内知识思考)

因为bison是使用LALR(1)将文法转为解析器的 ,LALR使用了前看符号(在归约时通过FOLLOW(N)选择性归约),所以通过前看符号可以解决左递归文法出现的冲突;

2.请在代码层面上简述下 yylval 是怎么完成协同工作的。(提示:无需研究原理,只分析维护了什么数据结构,该数据结构是怎么和$1、$2等联系起来?)

flex通过正则表达式读到匹配的字符串后,将字符串转为对应非终结符的语义值,然后将这个语义值放在全局变量yylval中,yylval相当于一个栈,栈的类型可以由%union定义。Bison维护一个栈(这个栈中的每一个元素的值,都是由yylval所指定)来保存文法符号的语义值,当最后n个被移进的记号和语义值匹配某个语法规则时, 就将它们依次弹出栈,再将规则的左部压栈(归约)。

bison定义$和 和和n来引用栈中的元素:$$表示规则左部,即归约之后被重新压入栈中的元素;$n表示规则左边第n个部件的语义值,即归约之前栈中距离栈顶编号为i的元素;

3.请尝试使用1.3样例代码运行除法运算除数为0的例子(测试case中有)看下是否可以通过,如果不,为什么我们在case中把该例子认为是合法的?(请从语法与语义上简单思考)

可以通过;语法分析器认为除数为0是合法的,因为“2/0”可以由上面规定的文法推导出来,所以从语法上来说它是合法的,由于语法分析使用的是上下文无关文法,所以它不能判断语义是否合法;

4.能否尝试修改下1.3计算器文法,使得它支持除数0规避功能。

词法分析器在读到非终结符NUMBER时,先判断yytext获取到的值是否为0,不为0才将它的语义值压入到yylval.num中,否则不将其传到语法分析器中:

修改之后,若除数为0,则直接报错,支持除数0规避功能:

实验反馈

通过实现一个cminus-f语法分析器,我大致了解了bison的分析过程:
调用函数yyparse开始进行分析;
用词法分析器读取记号:yylex从输入流中识别记号并将记号类型的正值数字码(数字码用来确定需要解析的token类型)返回给语法分析器(数字码在bison编译.y文件时生成的.h文件里),并将这些记号和它们的语义值压入栈中(移进);
当最后n个被移进的记号和组匹配某个语法规则时, 可以由那个规则将它们结合起来(归约),这些记号被规则的左部取代。动作是处理归约的一部分, 因为动作会计算这个组的语意值;
当yyparse遇到输入结束或者不能恢复的错误就会返回;

参考文献

  • A橙:https://blog.csdn.net/Aaron503/article/details/128324964
  • https://blog.csdn.net/weixin_45428457/article/details/123095236

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

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

相关文章

车机联网

通过笔记本电脑&#xff0c;D-link给车机提供网络 因为笔记本用的无线网络上网&#xff0c;将无线网络连接设置为共享 设置后的效果 本地连接属性设置 Dlink连接电脑和车机&#xff1b;获取车机的动态ip&#xff08;动态ip每次开关机都会变化&#xff0c;注意更新&#xff09…

【python 的各种模块】(9) 在python使用PIL( 即pillow模块 ) 修改图片

目录 1 导入PIL模块&#xff08;pillow&#xff09; 1.1 PIL的全称&#xff1a;Python Imaging Library 1.2 导入PIL模块 1.2.1 可用的导入形式 1.2.2 常用的导入形式 1.2.3 PIL下面的常用子模块 2 PIL.Image的方法 (读入&#xff0c;生成和显示图片) 2.1 用 PIL.Image…

《教育》期刊是什么级别的期刊?是正规期刊吗?能评职称吗?

《教育》以教育行业的各类新闻为重点&#xff0c;积极推广各地教育部门改革经验及优秀成果&#xff0c;努力挖掘教育一线先进单位和个人&#xff0c;充分发挥新闻舆论的监督作用。 收录情况&#xff1a;知网收录 投稿方式&#xff1a;教育类&#xff5c;《教育》省级 出版周期&…

GPT2 GPT3

what is prompt 综述1.Pre-train, Prompt, and Predict: A Systematic Survey of Prompting Methods in Natural Language Processing(五星好评) 综述2. Paradigm Shift in Natural Language Processing(四星推荐) 综述3. Pre-Trained Models: Past, Present and Future Pro…

Vue学习笔记5-- nextTick | Vue封装的过渡与动画

一、nextTick(tick-工作&#xff0c;起作用&#xff1b;下次起作用&#xff09; 语法&#xff1a; this.$nextTick(回调函数&#xff09;作用&#xff1a;在下一次DOM更新结束后执行其指定的回调。什么时候用&#xff1a;当改变数据后&#xff0c;要基于更新后的新DOM进行某些…

虾皮电商 电商平台:虾皮(Shopee)东南亚领先的电子商务平台

在当今数字化时代&#xff0c;电子商务平台的兴起改变了人们的购物方式。虾皮&#xff08;Shopee&#xff09;作为东南亚地区领先的电子商务平台&#xff0c;为消费者提供了便捷、多样化的购物体验。由新加坡的Sea Group&#xff08;前称Garena&#xff09;于2015年创立&#x…

程序员书单|本月有哪些新书值得关注?

2024年的第一个月&#xff0c;看了一下计算机书籍的榜单&#xff0c;本周有这样几本新书上榜。 1、GPT图解 大模型是怎样构建的 带你从0到1构建大模型&#xff0c;突破语言奥秘&#xff0c;开启智能未来&#xff01;深入探索自然语言处理技术的核心原理&#xff0c;结合实战&a…

【Linux】第二十九站:再谈进程地址空间

文章目录 一、一些疑问二、程序没有加载前的地址&#xff08;程序)三、程序加载后的地址四、动态库的地址 一、一些疑问 什么是虚拟地址&#xff1f;什么是物理地址&#xff1f;CPU读到的指令里面用的地址&#xff0c;是什么地址&#xff1f;&#xff1f; 我们之前在使用动态…

将web如vue等项目部署到宝塔docker镜像中,以便能在任意浏览器访问

文章目录 一、准备工作二、具体步骤1、从已经推送的镜像中拉取镜像2、切换到宝塔-容器&#xff0c;添加容器3、启动容器4、将刚刚的端口号添加到防火墙白名单5、访问部署好的项目 参考资料 一、准备工作 仅需确认宝塔面板已经有docker镜像容器 目前新版宝塔面板都已经内置了d…

数据结构之list类

前言 list是列表类。从list 类开始&#xff0c;我们就要接触独属于 Python 的数据类型了。Python 简单、易用&#xff0c;很大一部分原因就是它对基础数据类型的设计各具特色又相辅相成。 话不多说&#xff0c;让我们开始学习第一个 Python 数据类型一list。 1. list的赋值 输…

做完十年数据分析后的思考与总结

种一棵树最好的时间是十年前&#xff0c;其次是现在。十年了&#xff0c;本次分享大多来自工作中的日常所思所想&#xff0c;欢迎自取。 01 数据分析的本质 数据是基础&#xff0c;分析才是重点。 行业内有专门的统计岗&#xff0c;就是只负责做好数据统计就可以了&#xff0…

使用pycocotools打印更多数据(注意,修改后最好再还原!最好是一次性使用)

文章目录 1 写在前面2 代码效果3 代码修改 1 写在前面 夹带私货&#xff0c;可能有用呢&#xff1a;YOLOv7-tiny&#xff0c;通过pycocotools包得到预测大中小尺寸目标的指标值 仅供参考&#xff01;写这个代码的目的是能够打印出iou0.50的AP、AR的小中大3个尺寸的值&#xff…

记录汇川:H5U与Factory IO测试14

现实53工位的物料运输。 设置了自动连续存启动&#xff1a;就是一个一个运&#xff0c;按照顺序将空的货架填满。 设置了自动连续存停止&#xff1a;就是完成当前循环后退出。 设置了自动连续取启动&#xff1a;就是一个一个运&#xff0c;按照顺序将有货的货架清空。 设置…

Linux系统:yum仓库

目录 一、yum 1、yum概述 2、yum仓库 3、yum实现过程原理 二、yum配置文件详解 1、主配置文件 2、yum仓库设置文件 3、yum日志文件 三、yum命令详解 1、查询 1.1 yum list [软件名] 1.2 yum info [软件名] 1.3 yum search <关键词> 1.4 yum provides <关…

从0到1:实验室设备借用小程序开发笔记

概论 实验室设备借用小程序&#xff0c;适合各大高校&#xff0c;科技园区&#xff0c;大型企业集团的实验室设备借用流程, 通过数字化的手段进一步提升相关单位设备保障水平&#xff0c;规范实验室和设备管理&#xff0c;用户通过手机小程序扫描设备的二维码&#xff0c;可以…

1 vue防抖和限流

简介 什么是防抖 防抖&#xff08;Debouncing&#xff09;是一种前端开发中常用的技术&#xff0c;用于限制函数的执行频率。在防抖的机制下&#xff0c;一个函数在一定时间内只会执行一次&#xff0c;即使它被频繁地调用。 具体来说&#xff0c;防抖的实现方式是设置一个定…

【数据库8.0备份还原】之Percona XtraBackup

目录 Percona XtraBackup备份数据库1、Percona XtraBackup的介绍2、Percona XtraBackup安装3、Percona XtraBackup8.0的使用1.全库备份和还原2.增量备份和还原3.差异备份和还原4.差异备份和增量备份的区别5.压缩备份和还原 Percona XtraBackup备份数据库 yum源安装&#xff1a…

【技术分享】远程透传网关-单网口快速实现西门子S7-300/400 PLC程序远程上下载

准备工作 一台可联网操作的电脑一台单网口的远程透传网关及博达远程透传配置工具网线一条&#xff0c;用于实现网络连接和连接PLC一台西门子S7- 300/400 PLC及其编程软件一张4G卡或WIFI天线实现通讯(使用4G联网则插入4G SIM卡&#xff0c;WIFI联网则将WIFI天线插入USB口&#…

H3C机架式服务器前/后视图

------H3C机架式服务器前视图------ ------------ ------H3C 机架式服务器后视图------ ------------

[WUSTCTF2020]alison_likes_jojo 1

BUUCTF:https://buuoj.cn/challenges 题目描述&#xff1a; 得到的 flag 请包上 flag{} 提交。 感谢 Iven Huang 师傅供题。 比赛平台&#xff1a;https://ctfgame.w-ais.cn/ 密文&#xff1a; 下载附件解压&#xff0c;得到两张jpg图片和一个文本文件。 解题思路&#x…