3.02 使用bison在语法分析中构建抽象语法树AST

如果你已经储备bison的相关基础知识,阅读理解下面的代码会轻松得多。没有bison基础的同学请点击查看bison基本的语法规则及相关介绍

在文章1.06 使用Flex和Bison手写词法分析器和语法分析器,实现一个简单的计算器中,已经做过类似的事情。但与前一篇文章不同的是,本文将在bison语法分析过程中构建AST,让整个计算器项目更像是一个小型的“编译器”。
整个程序的目录结构如下所示,源码请看:

├── ch3
│   ├── 3.02
│   │   ├── 3.02_create_AST_with_bison.y
│   │   ├── 3.02lexer.y
│   │   ├── CMakeLists.txt
│   │   ├── fb3.02.c
│   │   └── fb3.02.h
│   ├── CMakeLists.txt
│   └── README.md

下面将列出程序的代码,我已经将各个重要的代码添加上了注释,希望对你有帮助

文件: fb3.02.h,声明语法分析词法分析会用到的数据类型及函数的头文件:

/* 计算器的声明部分 */

/* 与词法分析器的接口 */
extern int yylineno;

void yyerror(char *s, ...);

enum NodeT { // 声明所有语法树节点类型的枚举值
  NT_UNDEF,
  NT_ADD,
  NT_SUB,
  NT_MUL,
  NT_DIV,
  NT_ABS,
  NT_NEG,
  NT_NUM,
};

/* 抽象语法树中的节点 */
struct ast {
  int nodetype;
  struct ast *lft;
  struct ast *rht;
};

struct numval {
  int nodetype; /* 类型K 表明常量 */
  double number;
};

/* 构造抽象语法树 */
struct ast *newast(int nodetype, struct ast *lft, struct ast *rht);

struct ast *newnum(double d);

/* 计算抽象语法树 */
double eval(struct ast *);

/* 删除和释放抽象语法树 */
void treefree(struct ast *);

文件: fb3.02.c,对应头文件的具体实现代码:

#include 
#include 
#include 
#include "fb3.02.h"

int yyparse(void);

/* 为操作符,创建一个子树 */
struct ast *newast(int nodetype, struct ast *lft, struct ast *rht) {
  struct ast *a = malloc(sizeof(struct ast));
  if (!a) {
    yyerror("out of memory!");
    exit(0);
  }
  a->nodetype = nodetype;
  a->lft = lft;
  a->rht = rht;
  return a;
}

/* 为一个值,创建一个叶子节点 */
struct ast *newnum(double d) {
  struct numval *a = malloc(sizeof(struct numval));
  if (!a) {
    yyerror("out of memory!");
    exit(0);
  }
  a->nodetype = NT_NUM;
  a->number = d;

  // 此处将"struct numval"的类型强制转换为"struct ast"类型,是因为能够统一适配eval函数的入参类型;
  // 而eval函数能够正确运行得益于"struct numval"类型和"struct ast"类型中nodetype字段在内存中的相对位置是一致的。
  // 通过nodetype字段识别到"struct numval"类型的实例时,要使用该实例需要强制转换回来才行。
  return (struct ast *) a;
}

/* 递归计算各个子树的值 */
double eval(struct ast *a) {
  double v = 0; // result of eval

  switch (a->nodetype) {
    case NT_NUM:
      v = ((struct numval *) a)->number;
      break;
    case NT_ADD:
      v = eval(a->lft) + eval(a->rht);
      break;
    case NT_SUB:
      v = eval(a->lft) - eval(a->rht);
      break;
    case NT_MUL:
      v = eval(a->lft) * eval(a->rht);
      break;
    case NT_DIV:
      v = eval(a->lft) / eval(a->rht);
      break;
    case NT_ABS:
      v = eval(a->lft);
      v = v < 0 ? v * -1.0f : v;
      break;
    case NT_NEG: // 处理负数求值
      v = eval(a->lft) * -1.0f;
      break;

    default:
      printf("internal error:bad nodetype=%c,%d\n",
             a->nodetype, a->nodetype);
      break;
  }
  return v;
}

/* 递归释放各个树节点 */
void treefree(struct ast *a) {
  switch (a->nodetype) {
    case NT_ADD:/* 2颗子树 */
    case NT_SUB:
    case NT_MUL:
    case NT_DIV:
      treefree(a->lft);
      treefree(a->rht);
      break;
    case NT_ABS: /* 1颗子树 */
    case NT_NEG:
      treefree(a->lft);
      break;
    case NT_NUM:/* 没有子树 */
      free(a);
      a = NULL;
      break;
    default:
      printf("internal error:bad nodetype=%c,%d\n",
             a->nodetype, a->nodetype);
      break;
  }
}

void yyerror(char *s, ...) {
  va_list ap;
  va_start(ap, s);
  fprintf(stderr, "%d: error: ", yylineno);
  vfprintf(stderr, s, ap);
  fprintf(stderr, "\n");
}

int main(int argc, char **argv) {
  printf("> ");
  return yyparse();
}

文件:3.02lexer.y,词法分析代码:

/* 不使用-lfl定义的默认main函数,使用自定义的main函数,你就不需要链接-lfl了。 */
%option noyywrap

%{
#include "fb3.02.h"
#include "3.02_create_AST_with_bison.tab.h"
%}

/* 此处声明模式的公共部分,用于简化匹配模式的书写。*/
EXP ([Ee][-+]?[0-9]+) // 浮点数指数部分

%%
"+" |
"-" |
"*" |
"/" |
"|" |
"(" |
")" { return yytext[0]; }

[0-9]+("."[0-9]*{EXP}?|"."?[0-9]+{EXP}?)? { yylval.d = atof(yytext); return NUMBER; }

\n          { return EOL; }
"//".*\n
[ \t]       { /* 忽略空白字符 */}
.           { yyerror("Mystery character %c\n", *yytext); }
%%

文件: 3.02_create_AST_with_bison.y,语法分析代码:

/* file: 3.02_create_AST_with_bison.y */

%{
#include 
#include 
#include "fb3.02.h"
%}

/* %union关键字声明了语法规则中所有语法值可能会用到的数据类型的一个集合 */
%union {
  struct ast *a;
  double d;
}

%token <d> NUMBER /* 声明记号,而""指定了该记号的数据类型 */
%token EOL

/* 当使用了union声明了一组数据类型集合后,你必须为所有的非终结符指定其值类型。使用"%type"关键字*/
%type <a> exp factor term

%start calclist /* 指定起始符号(start symbol)有时也称为目标符号(goal symbol) */

%%
calclist: /* -- 空规则,起始符号必须具备一个空规则,旨在让起始符号必须匹配整个输入 -- */
        | calclist exp EOL { printf("= %4.4g\n", eval($2)); treefree($2); printf("> "); }
        | calclist EOL { printf("> "); } /* 空行或注释 */
        ;

exp: factor
   | exp '+' factor { $$ = newast(NT_ADD, $1, $3); }
   | exp '-' factor { $$ = newast(NT_SUB, $1, $3); }
   ;

factor: term
      | factor '*' term { $$ = newast(NT_MUL, $1, $3); }
      | factor '/' term { $$ = newast(NT_DIV, $1, $3); }
      ;

term: NUMBER { $$ = newnum($1); }
    | '|' exp '|' { $$ = newast(NT_ABS, $2, NULL); }     // 使用绝对值符号的语法规则
    | '(' exp ')' { $$ = $2; }                           // 使用圆括号的语法规则
    | '-' term    { $$ = newast(NT_NEG, $2, NULL); }     // 使用负号的规则
    ;
%%

你可能感兴趣的:(Flex&Bison)