HNU_编译原理_实验2(2021级)

lab2 实验报告

一、实验要求

        本次实验需要各位同学首先将自己的 lab1 的词法部分复制到 /src/parser 目录的 lexical_analyzer.l并合理修改相应部分,然后根据 cminus-f 的语法补全 syntax_analyer.y 文件,完成语法分析器,要求最终能够输出解析树。

主要工作

  1. 了解 bison 基础知识和理解 Cminus-f 语法(重在了解如何将文法产生式转换为 bison 语句)

  2. 阅读 /src/common/SyntaxTree.c,对应头文件 /include/SyntaxTree.h(重在理解分析树如何生成)

  3. 了解 bisonflex 之间是如何协同工作,看懂pass_node函数并改写 Lab1 代码(提示:了解 yylval 是如何工作,在代码层面上如何将值传给$1$2等)

  4. 补全 src/parser/syntax_analyzer.y 文件和 lexical_analyzer.l 文件

二、实验难点

1.Bison文件的编写

        Bison是一个语法分析器的生成工具, 可以将 LALR 文法转换成可编译的 C 代码,文件扩展名为 .y。在Bison文件中给出LALR文法以及一些分析动作,编译就可以产生一个语法分析器。

        每个 Bison 文件由 %% 分成三部分(以我们要补全的syntax_analyzer.y为例)。

(1)一些 bison 指令

union: 在语法分析过程中,语法分析树的叶子节点是一个具体的语义值,该值的类型是YYSTYPE,在Bison中用%union指明。

        不同的节点对应着不同的终结符,可能为不同的类型,因此union中可以包含不同的数据类型。比如,我们可能希望 ADDOP 的值是 char 类型,而 NUMBER 应该是 double 类型的。

%start 指令指定起始符号,用 %token 定义一个 token

%{
/* 这里是序曲 */
/* 这部分代码会被原样拷贝到生成的 .c 文件的开头 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include "syntax_tree.h"
​
// external functions from lex
//提供一个 yylex 来获取下一个 token
extern int yylex();
​
// external variables from lexical_analyzer module
extern int lines;
extern char *yytext;
extern int pos_end;
extern int pos_start;
​
// Global syntax tree
syntax_tree *gt;
​
// Error reporting
//提供一个 yyerror 来提供合适的报错机制。
void yyerror(const char *s);
​
// Helper functions written for you with love
syntax_tree_node *node(const char *node_name, int children_num, ...);
%}
​
​
/* 这些地方可以输入一些 bison 指令 */
/* TODO: Complete this definition. */
/*在 bison 解析过程中,每个 symbol 最终都对应到一个语义值上。或者说,在 parse tree 上,每个节点都对应一个语义值,这个值的类型是 YYSTYPE。YYSTYPE 的具体内容是由 %union 构造指出的。
*/
%union {}
​
/* TODO: Your tokens here. */
/* 比如用 %start 指令指定起始符号,用 %token 定义一个 token */
%start program
%token <num> NUMBER     /*声明词法单元名,同时在<>中指明类型*/
%type  <typex> expr     /*指明类型*/
(2)解析规则

产生式 {动作代码}。先给出一个简单的例子:

E → E+E     {E=E1+E2}
在bison第二部分“解析规则”中的实现:
term : term ADDOP factor
     {
        switch $2 {
        case '+': $$ = $1 + $3; break;
        case '-': $$ = $1 - $3; break;
        }
     }
//当前节点使用 $$ 代表,而已解析的节点则是从左到右依次编号,称作 $1, $2, $3.
//节点union的类型: 采用自底向上分析(LALR)文法,子节点是已经解析的,当前节点则是规约产生的。使用节点union的哪个类型操作,是已经用<>在开头的%token和%type中指明的。
%type <num> top line expr term factor
%%
/* 从这里开始,下面是解析规则 */
/* TODO: Your rules here. */
​
program : declaration-list { $$ = node("program", 1, $1); gt->root = $$; }
/*
program : declaration-list 表示program具备一个子结点,子结点为 declaration-list.
​
$$ = node("program", 1, $1) 语句表示为当前识别到的 program 创建一个结点,并将结点赋给 program,作为识别到的语法单元 program 在语法分析树中的结点。结点名字为"program".
​
gt->root = $$,将 当前节点(program)作为根节点,使其作为语法分析树的起始.
*/
%%
(3)辅助函数
/* 这里是尾声 */
/* 这部分代码会被原样拷贝到生成的 .c 文件的末尾 */
/// The error reporting function.
void yyerror(const char *s)
{
    // TO STUDENTS: This is just an example.
    // You can customize it as you like.
    fprintf(stderr, "error at line %d column %d: %s\n", lines, pos_start, s);
}
​
/// Parse input from file `input_path`, and prints the parsing results
/// to stdout.  If input_path is NULL, read from stdin.
///
/// This function initializes essential states before running yyparse().
syntax_tree *parse(const char *input_path)
{
    if (input_path != NULL) {
        if (!(yyin = fopen(input_path, "r"))) {
            fprintf(stderr, "[ERR] Open input file %s failed.\n", input_path);
            exit(1);
        }
    } else {
        yyin = stdin;
    }
​
    lines = pos_start = pos_end = 1;
    gt = new_syntax_tree();
    yyrestart(yyin);
    yyparse();
    return gt;
}
​
/// A helper function to quickly construct a tree node.
///
/// e.g.
///     $$ = node("program", 1, $1);
///     $$ = node("local-declarations", 0);
syntax_tree_node *node(const char *name, int children_num, ...)
{
    syntax_tree_node *p = new_syntax_tree_node(name);
    syntax_tree_node *child;
    if (children_num == 0) {
        child = new_syntax_tree_node("epsilon");
        syntax_tree_add_child(p, child);
    } else {
        va_list ap;
        va_start(ap, children_num);
        for (int i = 0; i < children_num; ++i) {
            child = va_arg(ap, syntax_tree_node *);
            syntax_tree_add_child(p, child);
        }
        va_end(ap);
    }
    return p;
}

2.修改lexical_analyzer.l

        每个终结符都对应着一个叶子节点,这个叶子节点在词法分析时就可以产生。在自底向上的分析过程中,首先产生的是叶子节点,在用产生式进行归约时向上构建语法分析树。

        叶子节点的产生在词法分析器中的pass_node()函数中实现,创建一个新的节点,并将其指针赋值给yylval,节点名为其成分(非终结符名或终结符名),这样语法分析器就可以使用该节点构造语法分析树。

//生成节点并存入yylval传递给语法分析器
void pass_node(char *text){
     yylval.node = new_syntax_tree_node(text);
}
//识别词法单元时调用pass_node
\+  { pos_start = pos_end; pos_end += 1; pass_node(yytext); return ADD; }

        注意,在这里,我并没有发现yyval的声明,就被使用了。查阅资料,发现:

        在典型的 Lex 文件中,yylval 被认为是由 Bison 生成的 C 代码提供的,而不是由 Lex 文件显式声明的。在 Bison 的头文件中,YYSTYPE 的定义和 %union 部分中的定义是一致的,从而确保 yylval 被正确声明。

        在 Bison 的生成代码中,yylval 通常是通过 $$ 来访问的。

3.分析树如何生成

        查看/include/SyntaxTree.h,

        分析树的节点记录了父节点,子节点的指针,以及子节点数和节点名信息。 相关的方法包括生成新的节点,添加子节点,创建语法树等。

//语法分析树的节点
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);              //删除节点
syntax_tree* new_syntax_tree();                                                 //创建语法分析树
void del_syntax_tree(syntax_tree * tree);                                       //删除分析树
void print_syntax_tree(FILE * fout, syntax_tree * tree);                        //输出分析树
 

        词法分析完成了叶子节点的产生,剩下的工作就由语法分析来完成了。构建的过程就是在每使用一个产生式进行规约时,建立一个新的节点表示当前产生式的非终结符,然后将产生式中的成分,也就是子节点的指针存入这个新节点中。

        当最后使用初始产生式规约时,产生的新节点就是语法分析树的根节点,就完成了向上构建语法分析树的工作。

/*产生一个语法分析树新节点的函数;
参数为产生式的非终结符名,产生式成分个数(也即子节点个数),子节点的指针。*/
syntax_tree_node *node(const char *node_name, int children_num, ...);
//应用该函数构造语法分析树,根节点的构造
program : declaration-list { $$ = node("program", 1, $1); gt->root = $$; }

三、实验设计

1.词法分析lexical_analyzer.l

        修改./src/parser/lexical_analyzer.l文件。

        在识别动作中添加pass_node(yytext)产生词法单元叶子节点。flex将数值存入yylval,而bison读取yylval之中的值。

        语法分析树不考虑注释COMMENT,换行EOL以及空格BLANK,所以不用建立结点不进行返回,只需要把pos_end,pos_start进行修改即可。

        对于其他未定义的字符只需要printf(“error\n”)。同时lab1中的void analyzer函数也不需要。

%%
​
\+  { 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;}
[a-zA-Z]  {pos_start = pos_end; pos_end++; pass_node(yytext); return LETTER;}  
[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  {lines++;pos_end = 1;} 
"/*"([^*]|\*+[^*/])*\*+"/"  {
    for(int i=0;i<strlen(yytext);i++){
        if(yytext[i]=='\n') {
            lines++;
            pos_end = 1;    //pos_start由pos_end得到,这里就不需要置1了
        }
        else pos_end++;
    }
}
[" "|\t] {pos_start = pos_end; pos_end+=strlen(yytext);}
. {pos_start = pos_end; pos_end+=strlen(yytext);printf("lexical analyze error at line %d pos %d\n",lines,pos_start);}
​
%%

2.语法分析syntax_analyzer.y

修改./src/parser/syntax_analyzer.y文件。

(1)union

节点类型就是一个node指针:

%union {
    syntax_tree_node *node;
}
(2)start,token,type

        终结符(词法单元)和非终结符的类型声明,类型都是语法分析树的节点指针。其中终结符名要和词法分析部分中的token一致,非终结符名和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。

声明如下:

%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
(3)解析规则

        前面写出产生式左部,在:后面写出产生式右部,然后再在{}里面进行赋值定义.

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

/*
cminus-f 的基础类型只有整型(int)、浮点型(float)和 void。
一个变量声明定义一个整型或者浮点型的变量或者数组变量。
数组变量在声明时,INTEGER 应当大于0。
一次只能声明一个变量。
*/
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); }
               ;

/*
函数声明包含了返回类型,标识符,由逗号分隔的形参列表,复合语句。
函数的参数可以是 void ,也可以是一个列表。当函数的形参是void时,调用该函数时不用传入任何参数。
形参中跟着中括号代表数组参数,它们可以有不同长度。
*/
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); }
      ;

/*
一个复合语句由一对大括号和其中的局部声明与语句列表组成;
复合语句的执行时,对包含着的语句按照语句列表中的顺序执行
局部声明和语句列表都可以为空(empty表示空字符串,即ε \varepsilonε)
*/
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); }
          ;
/*
表达式语句由一个可选的表达式(即可以没有表达式)和一个分号组成

if语句中的表达式将被求值,若结果的值等于0,则第二个语句执行(如果存在的话),否则第一个语句会执行。
为了避免歧义,else 将会匹配最近的将会匹配最近的 if

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

return语句可以返回值,也可以不返回值。
未声明为void类型的函数必须返回和函数返回类型相同的值,
return会将程序的控制转移给当前函数的调用者,而‘ main ‘ 函数的return会使得程序终止
*/
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); }
            ;
/*
一个表达式可以是一个变量引用(即var)接着一个赋值符号(=)以及一个表达式,也可以是一个简单表达式。

var 可以是一个整型变量、浮点变量,或者一个取了下标的数组变量。
*/
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); }
    ;
/*
一个简单表达式是一个加法表达式或者两个加法表达式的关系运算。
当它是加法表达式时,它的值就是加法表达式的值。
当它是关系运算时,如果关系运算结果为真则值为整型值 1,反之为整型值 0。
*/
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); }
      ;

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

浮点数和整型一起运算时,整型值需要进行类型提升,转换成浮点数类型,且运算结果也是浮点数类型
*/
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

(1)string的错误

解决: 在ast.hpp文件中添加#include<string>.

        但是与其他同学交流时,有些同学好像没有遇到这个问题。以为是自己的g++版本比较低,但是通过g++ --version查看,发现是11.4.0,比没有遇到这个问题的同学的g++版本还高,目前还不知道是什么原因。

(2)yyin的错误

解决: 在syntax_analyzer.y中的第一部分添加extern FILE *yyin

(3)正确

2.运行测试

(1)./tests/lab2/test_syntax.sh easy

(2)./tests/lab2/test_syntax.sh normal

(3)diff ./tests/lab2/test_syntree_easy ./tests/lab2/syntree_easy_std

3.mytest

(1)mytest1

测试代码:

int main(void){
    int i;int a[10];int j;
    i=0;j=10;
    quicksort(a,i,j);
    return 0;
}
void quicksort(int a[],int l, int r){
    int p;
    if(l<r){
        p = partition(a,l,r);
        quicksort(a,l,p-1);
        quicksort(a,l,p+1);
    }
}
int partition(int a[],int l,int r){
    int temp;
    temp = a[l];
    while(l<r){
        while(temp<=a[r]){
            r = r-1;
            if(l==r){
                a[l] = temp;
                return l;
            }
        }
        a[l] = a[r];
        while(temp>=a[l]){
            l = l+1;
            if(l==r){
                a[l] = temp;
                return l;
            }
        }
    }
}

测试结果:

(2)mytest2

测试代码:

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

测试结果:

五、实验反馈

        在修改 lexical_analyzer.l 的部分时,了解了yylval的用法,其 被认为是由 Bison 生成的 C 代码提供的,而不是由 Lex 文件显式声明的。在 Bison 的生成代码中,yylval 通常是通过 $$ 来访问的。

        补充syntax_analyzer.y是比较困难的部分,代码量也比较大。但通过查阅资料,也基本了解了各个产生式及其动作,对于语法分析、语法制导翻译、语义分析,有了更深的了解。

        另外,markdown和git的使用也渐渐熟悉。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值