首页 > 其他分享 >编译器 help-assignment

编译器 help-assignment

时间:2024-10-24 16:17:44浏览次数:7  
标签:终结符 help assignment token 编译器 Bison expr calc 定义

Bison 是一款 LALR 文法解析器生成器,可转换为可编译的 C 代码,减轻手动设计解析器的工作。它重新实现了早期 Unix 上的 Yacc 工具,文件扩展名为 .y(Yacc 意为 Yet Another Compiler Compiler)。

Flex 和 Bison 是 Linux 下生成词法分析器和语法分析器的工具,用于处理结构化输入,协同工作解析复杂文件。Flex 将文本文件拆分为有意义的词法记号(token),而 Bison 根据语法规则生成抽象语法树(AST),Bison 在协同工作中担任主导角色,而 Flex 辅助生成 yylex 函数。

注意:

Bison 根据语法规则生成的抽象语法树(AST)在我们这次实验中为 syntax_tree,是一个较为复杂的结构,因此在 lab1 中我们还会将其简化,即我们得到的 AST 是 Bison 输出简化的结果。

以计算器程序(该程序即为下文的一个复杂的 Bison 程序)为例,用户在界面输入 2 + 2 * 2,Flex 将输入识别为 token 流,其中 2 被识别为 number+ 被识别为 ADD* 被识别为 MUL。接下来,Bison 负责根据语法规则将这些 token 组织成 AST 树,流程如下图所示:

Bison 程序结构

Bison 源程序的一般结构如下所示。Bison 源程序后缀一般为 .y

定义部分

语法规则部分

C 代码部分

定义部分 可以分为以下两个部分:

  1. 包括 C 语言代码、头文件引用、宏定义、全局变量定义和函数声明等内容,位于 %{ 和 %} 之间。

  2. 终结符和非终结符声明:用于定义语法中使用的终结符(也称为记号)和非终结符,常见声明包括 %token%union%start%type%left%right%nonassoc 等。

  3. %token 定义终结符。定义形式:%token TOKEN1 TOKEN2。一行可定义多个终结符,空格分隔。一般约定终结符都是大写,非终结符的名字是小写。

  4. %type 定义非终结符。

  5. %left%right%nonassoc 定义终结符的结合性和优先级关系。定义形式与 %token 类似。先定义的优先级低,最后定义的优先级最高,同时定义的优先级相同。%left 表示左结 合(如“+”、“-”、“*”、“/”);%right 表示右结合(例如“=”);%nonassoc 表示不可结合(即它定义的终结符不能连续出现。例如“-”负号。如下定义中,优先级关系为:AA = BB < CC < DD;表示结合性为:AA、BB 左结合,DD 右结合,CC 不可结合。

    %left AA BB
    %nonassoc CC
    %right DD
    
  6. %union 定义了语法符号的语义值类型的集合。在 Bison 中,每个符号,包括记号和非终结符,都有一个不同数据类型的语义值,并且这些值通过 yylval 变量在移进和归约操作中传递。默认情况下,YYSTYPE(宏定义)为 yylval 的类型,通常为 int。但通过使用 %union,你可以重新定义符号的类型。使用 union 是因为不同节点可能需要不同类型的语义值,比如下面的定义,希望 ADDOP 的 值是 char 类型,而 NUMBER 应该是 double 类型。

  1. %token <num> NUMBER
    %token <op> ADDOP MULOP LPAREN RPAREN
    %union {
     char op;
     double num;
    }
    
    # 注意:一旦 %union 被定义,需要指定 Bison 每种符号使用的值类型,值类型通过放在尖括号中的 union 类型对应的成员名称确定,如 %token <num>。
    
  2. 使用 %start 指定文法的开始符号,表示最终需要规约成的符号,例如 %start program。如果不使用 %start 定义文法开始符 号,则默认在第二部分规则段中定义的第一条生产式规则的左部非终结符为开始符号。

语法规则部分:

  • 语法规则部分由归约规则和动作组成。规则基本按照巴科斯范式(BNF)描述。规则中目标或非终端符放在左边,后跟一个冒号:然后是 产生式的右边,之后是对应的动作(用 {} 包含)。Bison 的语法树是按自下而上的归约方式进行构建的。如下所示:
%%
 program: program expr '\n' { printf("%d\n", $2); }
 ;
 expr: expr '+' expr { $$ = $1 + $3; }
 | expr '-' expr { $$ = $1 - $3}
 ;
%%
/* 动作中“$1”表示右边的第一个标记的值,“$2”表示右边的第二个标记的值,依
次类推。“$$”表示归约后的值。以“expr: expr '+' expr { $$ = $1 + $3; }”,说明“$$”表示从左
向右第一个 expr,即规约的结果,“$1”表示从左向右第二个 expr,“$2”表示“+”加号,“$3”
表示从左向右第三个 expr。“|”符号表示其他的规约规则。 */

C 代码部分:

  • C 代码部分为 C 代码,会被原样复制到 c 文件中,这里一般自定义一些函数。主要包括调用 Bison 的语法分析程序 yyparse()。其中 yyparse 函数由 Bison 根据语法规则自动生成,用于语法分析。

一个简单 Bison 程序

我们首先展示一个简单的 Bison 程序 bison_demo.y,帮助你理解 Bison,请仔细阅读文件内容。该程序没有联动 Flex,直接由用户提供 yylex 函数进行 token 分析,yylex 函数将在 Bison 生产的函数 yyparse 中被调用。

/* file name : bison_demo.y */
%{
#include <stdio.h>
/* 定义部分 */
/* 这部分代码会被原样拷贝到生成的 .c 文件的开头 */
int yylex(void);
void yyerror(const char *s);
%}

/* 定义部分 */
/* 对语法的终结符和非终结符进行声明 */
%start reimu
%token REIMU

/* 从这里开始,下面是解析规则 */
%%
reimu : marisa { /* 这里写与该规则对应的处理代码 */ puts("rule1"); }
      | REIMU  { /* 这里写与该规则对应的处理代码 */ puts("rule2"); }
      ; /* 规则最后不要忘了用分号结束哦~ */

 /* 这种写法表示 ε —— 空输入 */
marisa : { puts("Hello!"); }
%%


/* 以下是 C 代码部分 */
/* 在这个 Bison 程序中,我们没有联动 Flex,所以手写一个 yylex 函数 */
int yylex(void)
{
    int c = getchar(); // 从 stdin 获取下一个字符
    switch (c) {
    case EOF: return YYEOF;
    case 'R': return REIMU;
    default:  return YYUNDEF;     // 报告 token 未定义,迫使 bison 报错。
    // 由于 bison 不同版本有不同的定义。如果这里 YYUNDEF 未定义,请尝试 YYUNDEFTOK 或使用一个随意的整数。
    }
}

void yyerror(const char *s)
{
    fprintf(stderr, "%s\n", s);
}

int main(void)
{
    yyparse(); // 启动解析
    return 0;
}

编译和运行:

$ bison bison_demo.y

# 查看生产的 c 程序
$ ls bison_demo.tab.c
bison_demo.tab.c

$ gcc bison_demo.tab.c
$ ./a.out
R # <-- 不要回车,在这里按 Ctrl-D
rule2

$ ./a.out
# <-- 不要回车,在这里按 Ctrl-D
Hello!
rule1

$ ./a.out
blablabla # <-- 不要回车,在这里按 Ctrl-D
Hello!
rule1
syntax error <-- 发现了错误

一个复杂的 Bison 程序

我们接下来展示一个复杂的 Bison 程序,该程序同时使用 Flex 和 Bison,使用 Flex 生产的 yylex 函数进行字符串分析,Bison 生成的 yyparse 进行语法树构建。共涉及 2 个文件,calc.y 和 calc.l。其功能是实现一个数值计算器。

/* calc.y */
%{
#include <stdio.h>
    int yylex(void);
    void yyerror(const char *s);
%}

%token RET
%token <num> NUMBER
%token <op> ADDOP MULOP LPAREN RPAREN
%type <num> top line expr term factor

%start top

%union {
    char   op;
    double num;
}

%%

top
: top line {}
| {}

line
: expr RET
{
    printf(" = %f\n", $1);
}

expr
: term
{
    $$ = $1;
}
| expr ADDOP term
{
    switch ($2) {
    case '+': $$ = $1 + $3; break;
    case '-': $$ = $1 - $3; break;
    }
}

term
: factor
{
    $$ = $1;
}
| term MULOP factor
{
    switch ($2) {
    case '*': $$ = $1 * $3; break;
    case '/': $$ = $1 / $3; break; // 这里会出什么问题?
    }
}

factor
: LPAREN expr RPAREN
{
    $$ = $2;
}
| NUMBER
{
    $$ = $1;
}

%%

void yyerror(const char *s)
{
    fprintf(stderr, "%s\n", s);
}

int main()
{
    yyparse();
    return 0;
}
/* calc.l */
%option noyywrap

%{
/* 引入 calc.y 定义的 token */
/* calc.tab.h 文件由 Bison 生成 */
/* 当我们在.y 文件中使用 %token 声明一个 token 时,这个 token 就会导出到 .h 中,
   可以在 C 代码中直接使用,供 Flex 使用。就如 .l 文件中的\( { return LPAREN; },
   其中 LPAREN 定义来自 calc.tab.h,由对应的 .y 文件生成 */
#include "calc.tab.h"
%}


/* 规则部分 yylval 同样来自 calc.tab.h 文件,其类型为 YYSTYPE,用于 token 的相关属性,比如 NUMBER 对应的实际数值 */
/* 在这个例子中,YYSTYPE 定义如下

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

其同样由 .y 文件根据 %union 生成,在文件中我们的 %union 定义如下

%union {
    char   op;
    double num;
}
*/

%%
\( { return LPAREN; }
\) { return RPAREN; }
"+"|"-" { yylval.op = yytext[0]; return ADDOP; }
"*"|"/" { yylval.op = yytext[0]; return MULOP; }
[0-9]+|[0-9]+\.[0-9]*|[0-9]*\.[0-9]+ { yylval.num = atof(yytext); return NUMBER; }
" "|\t {  }
\r\n|\n|\r { return RET; }
%%

使用如下命令构建并测试程序:

# 生成 calc.tab.c 和 calc.tab.h。如果不给出 -d 参数,则不会生成 .h 文件。
$ bison -d calc.y
# 生成 lex.yy.c
$ flex calc.l

$ ls calc.tab.c calc.tab.h lex.yy.c
calc.tab.c  calc.tab.h  lex.yy.c

# 编译
$ gcc lex.yy.c calc.tab.c -o calc
$ ./calc
1+1
 = 2.000000
2*(1+1)
 = 4.000000
2*1+1
 = 3.000000

标签:终结符,help,assignment,token,编译器,Bison,expr,calc,定义
From: https://blog.csdn.net/m0_74315230/article/details/143212012

相关文章

  • Mybatisplus TableInfoHelper:获取entity对应的数据表字段列表
    如题,调用TableInfoHelper#getTableInfo(clazz)这个工具方法可以得到entity类所对应的数据表的字段列表。importcom.baomidou.mybatisplus.core.metadata.TableInfoHelper;importcom.baomidou.mybatisplus.core.metadata.TableFieldInfo;importcom.baomidou.mybatisplus.co......
  • PageHelper 分页插件使用中的那些“坑”
    PageHelper分页插件使用中的那些“坑”引言在项目开发过程中,分页查询是常见的需求之一。PageHelper是一个MyBatis的分页插件,它能够自动完成MyBatis的分页功能。然而,在使用过程中可能会遇到一些问题,特别是当手动在SQL中使用了LIMIT进行分页的情况下。本文将探讨这些问......
  • 用C++构建自己的编译器:从词法分析到代码生成
    解锁Python编程的无限可能:《奇妙的Python》带你漫游代码世界本文将带领读者从零开始构建一个简单的C++编译器。我们将逐步讲解如何进行词法分析、语法分析,以及如何将这些结果转换为目标代码。这篇文章的目标是帮助读者理解编译器的基本构成和工作原理,并提供可扩展的编译器......
  • AOT漫谈专题(第五篇): 如何劫持.NET AOT编译器 进行源码级调试
    一:背景1.讲故事上篇聊到了C#程序编译成Native代码的宏观过程,有粉丝朋友提了一个问题,能不能在dotnetpublish发布的过程中对AOT编译器拦截进行源码级调试,这是一个好问题,也是深度研究的必经之路,这篇我们就来分享下吧。二:托管和非托管调试器1.调试器介绍相信大家现在都知......
  • 三大编译器
    编译器的一般构成三个部分:前端(frontEnd)+优化器(Optimizer)+后端(backEnd)前端:词法和语法分析优化器:承前基础+优化代码=更加高效后端:将中间代码转化为各个平台的机器代码!GCC可以处理C++FortranPascalObjective-CJavaAda等LLVM(LowLevelV......
  • 手把手教你学 GPU SoC 芯片(8.1)--GPU SOC芯片编译器优化的编译器选项和标志
    目录常见的编译器优化选项示例:使用nvcc编译CUDA程序示例:使用GCC编译CPU程序特定于GPU编译器的优化选项NVIDIAnvccAMDROCm结论GPUSoC(SystemonChip)芯片的编译器优化对于提高性能和效率至关重要。不同的编译器可能支持不同的优化选项和标志,但大多数现代编译器都提......
  • GCC 编译器 与 GDB 调试器的基本操作
    一、GCC编译器1.什么是GCCGCC是GNUCompilerCollection(GNU编译器套装)的简称,目前GCC可以支持C,C++,ADA,JAVA,Fortran,PASCAL等多种高级语言。支持主流的CPU平台,完成从源程序向特定CPU硬件平台上自标代码的转换。2.GCC编译流程2.1方法一:四步完成编译1)预处理对......
  • Stanford CS149 -- Assignment 3: A Simple CUDA Renderer
    作业描述及代码参见:CS149-asst3实验环境:WSL2;GeForceMX350;Cuda12.6第一部分:CUDA热身练习1:SAXPY实验结果:相比基于CPU的实现,性能明显下降。这是由于SAXPY属于I/O密集型任务,计算量较小,主要的时间耗费在数据的转移。第二部分:CUDA热身练习2:并行前缀和第三部分:简单......
  • Stanford CS149 -- Assignment 4: NanoGPT149
    作业描述及代码参见:cs149gptWarm-Up:访问张量张量/数组都是按行存储的,四维数组可以看作元素为三维数组的数组,元素大小即为三维数组内元素总数,以此类推。第1部分:简单(但不太高效)的注意力机制实现主要实现两个矩阵乘法和一个softmax运算。第2部分:块矩阵乘法和UnfusedSof......
  • Stanford CS149 -- Assignment 2: Building A Task Execution Library from the Groun
    作业描述及代码参见:CS149-asst2PartAStep1只需要实现一个简单的任务系统,在run()的开始生成工作线程,并在run()返回之前从主线程合并这些线程。任务的分配方式采用动态分配,即每个线程每次取一个任务完成,能者多劳。每个线程的核心实现为:while(true){inttaskID=done+......