36
使使 Bison 使3使

使用Bison

  • Upload
    sidney

  • View
    52

  • Download
    2

Embed Size (px)

DESCRIPTION

使用Bison. 第3章. bison的用途. flex可以识别正则表达式,生成词法分析器 bison可以识别语法,生成语法分析器 词法分析将输入流分解为若干个记号 而语法分析则分析这些记号并基于逻辑进行组合. 如何匹配输入. bison可以基于给定语法来生成一个可以识别该语法中有效“语句“(如:C程序)的语法分析器 程序可能在语法上正确但是语义上有问题,如: 把一个字符串赋值给整型变量 bison只处理语法的正确性 语法分析器基于语法的规则来识别语法上正确地输入 表示语句分析的通常方法是语法树或抽象语法树. Bison的工作原理. - PowerPoint PPT Presentation

Citation preview

Page 1: 使用Bison

使用 Bison• 第 3 章

Page 2: 使用Bison

bison 的用途• flex 可以识别正则表达式,生成词法分析器• bison 可以识别语法,生成语法分析器• 词法分析将输入流分解为若干个记号• 而语法分析则分析这些记号并基于逻辑进行组合

Page 3: 使用Bison

如何匹配输入• bison 可以基于给定语法来生成一个可以识别该语法中有

效“语句“(如: C 程序)的语法分析器• 程序可能在语法上正确但是语义上有问题,如:

•把一个字符串赋值给整型变量• bison 只处理语法的正确性• 语法分析器基于语法的规则来识别语法上正确地输入• 表示语句分析的通常方法是语法树或抽象语法树

Page 4: 使用Bison

Bison 的工作原理• 语法分析器通过查找能够匹配当前记号的规则来运作,进

行移进 / 归约分析• 当 bison 处理语法分析器时,会创建一组状态,每个状态

都反映出一个或者多个部分分析过的规则中可能的位置• 当语法分析器读取记号时

•若读到的记号无法结束一条规则时,将记号压入堆栈并切换到一个新状态,该状态能反映出刚读取的记号(移进)

•当发现压入的所有语法符号已经可以组成规则的右部时,将右部符号弹栈,然后把左部语法符号压栈,状态也做相应转换(归约)

•归约时会执行该规则相关联的用户代码

Page 5: 使用Bison

bison 的分析方法• 可使用两种分析方法

•LALR ( 1 )分析( Look-Ahead , Left to Right, Rightmost derivation )

•GLR 分析( generalized left-to-right )• 大多数语法分析器采用 LALR ( 1 ),它不如 GLR 强大

但更快、更易于使用

Page 6: 使用Bison

LALR 分析无法处理的语法• 二义性文法• 需向前查看多个记号才能确定是否匹配规则的语法,例:

•phrase: cart_animal AND CART•| work_animal AND PLOW•cart_animal: HORSE | GOAT•work_animal: HORSE | OX•无二义性•但对于输入 HORSE AND CART 这样的输入,在没有看到 CAR

T 之前无法区别 HORSE 是一个 cart_animal 还是一个 work_animal 。

•若将第一条规则改为:•phrase: cart_animal CART•| work_animal PLOW•则只需向前查看一个记号, bison 就能够处理

Page 7: 使用Bison

基于抽象语法树的改进的计算器——例 3-1• 文件清单如下:

•fb3-1.h•fb3-1.y•fb3-1.l•fb3-1funcs.c

Page 8: 使用Bison

语法符号的值与类型• bison 中每个文法符号(包括终结符和非终结符)都可以

有一个相应的值,默认类型是 int

• 实际应用中常需要更多有价值的符号值,可以用 union来为符号声明联合类型

• 再为每种符号指定其使用的值类型,方法如下:•%token <联合类型的相应成员名 > 记号列表•%type <联合类型的相应成员名 > 非终结符列表

Page 9: 使用Bison

语法符号的值与类型举例• %union {• struct ast *a;• double d;• }

• /* declare tokens */• %token <d> NUMBER• %token EOL

• %type <a> exp factor term

Page 10: 使用Bison

辅助函数( fb3-1funcs.c )

• 构造语法树节点•newast , newnum•申请空间•对语法树节点的各个字段进行赋值

• 计算语法树的值 eval

• 删除和释放语法树 treefree

• 报错 yyerror•VA_LIST 是在 C 语言中解决变参问题的一组宏•VA_START获取可变参数列表的第一个参数的地址( ap 是类

型为 va_list 的指针, v 是可变参数最左边的参数)

Page 11: 使用Bison

编译基于抽象语法树的计算器——Makefile.ch3• fb3-1: fb3-1.l fb3-1.y fb3-1.h fb3-

1funcs.c• bison -d fb3-1.y• flex -ofb3-1.lex.c fb3-1.l• cc -o $@ fb3-1.tab.c fb3-1.lex.c fb3-

1funcs.c

Page 12: 使用Bison

编译基于抽象语法树的计算器

• bison -d fb3-1.y•将生成 fb3-1.tab.c , fb3-1.tab.h

• flex -ofb3-1.lex.c fb3-1.l•将生成 fb3-1.lex.c

• 建立一个工程,加入相关文件•fb3-1.tab.c , fb3-1.tab.h•fb3-1.lex.c , fb3-2funcs.c

• 生成可执行文件• 执行可执行文件

Page 13: 使用Bison

执行效果

输出结果全都错误

Page 14: 使用Bison

调试并分析原因• 通过设置断点调试发现在

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

• 可能是 atof 没起作用• 再加上编译时的报警信息• 查阅资料在 fb3-1.lex.c加上#include <stdlib.h>

• 重新编译运行

• 后 yylval.d 的值错误

运行结果正确!

Page 15: 使用Bison

二义性的处理——优先级与结合性• 表达式分析器引入了三个不同的语法符号 exp 、 factor和 term 来设置操作符的优先级和结合性

• 但随着具有更多不同优先级的操作符被加入到算法中,整个算法将难于阅读和维护

• bison允许显式地指定优先级和结合性,例:•%left '+' '-'•%left '*' '/'•%nonassoc '|' UMINUS•%type <a> exp

注:•%right 右结合•优先级相同的在同一行说明•声明在后面行的操作符比声明在前面行的优先级高

Page 16: 使用Bison

• %%• ...• exp: exp '+' exp { $$ = newast('+', $1,$3); }• | exp '-' exp { $$ = newast('-', $1,$3);}| exp

'*' exp { $$ = newast('*', $1,$3); }• | exp '/' exp { $$ = newast('/', $1,$3); }• | '|' exp { $$ = newast('|', $2, NULL); }• | '(' exp ')' { $$ = $2; }• | '-' exp %prec UMINUS{ $$ = newast('M',

NULL, $2); }• | NUMBER { $$ = newnum($1); }• ;

Page 17: 使用Bison

什么时候不应该使用优先级规则• 虽然可以使用优先级规则解决语法中出现的任何移进 / 归

约冲突,但有时难于明白改动给语法带来的后果• 只应在两种场合使用优先级规则:

•表达式语法•if 语句的悬挂 else 问题

• 只要有可能,应通过修正语法来解决冲突•冲突说明文法有二义性•除了前面两种场合,它表明语言定义有问题

Page 18: 使用Bison

一个高级计算器• 扩展了前例

•添加了命名变量和赋值•比较表达式•if/then/else和 do/while控制流程•内置和用户自定义的函数•一点错误恢复机制•充分利用抽象语法树实现流程控制和用户自定义函数

• 文件清单如下:•fb3-2.h•fb3-2.y•fb3-2.l•fb3-2funcs.c

Page 19: 使用Bison

高级计算器声明部分 fb3-2.h• /* 符号表 */• struct symbol { /* a variable name */• char *name;• double value;• struct ast *func; /* stmt for the function 函数体指针 */

• struct symlist *syms; /* list of dummy args 虚拟参数列表 */

• };

Page 20: 使用Bison

高级计算器声明部分 fb3-2.h• /* simple symtab of fixed size */• #define NHASH 9997• struct symbol symtab[NHASH];

• struct symbol *lookup(char*);/*在符号表中查找一个符号 */

• /* list of symbols, for an argument list */• struct symlist {• struct symbol *sym;• struct symlist *next;• };

• struct symlist *newsymlist(struct symbol *sym, struct symlist *next);/*把一个符号插入符号表中 */

• void symlistfree(struct symlist *sl);/* 释放符号表 */

Page 21: 使用Bison

• /* node types• * + - * / |• * 0-7 comparison ops, bit coded 04 equal, 02 less,

01 greater• * M unary minus• * L statement list• * I IF statement• * W WHILE statement• * N symbol ref• * = assignment• * S list of symbols• * F built in function call• * C user function call• */

Page 22: 使用Bison

• enum bifs { /* built-in functions */

• B_sqrt = 1,• B_exp,• B_log,• B_print• };

Page 23: 使用Bison

• /* nodes in the Abstract Syntax Tree */• /* all have common initial nodetype */

• struct ast { /*用于 +, - , *, |和语句序列 */• int nodetype;• struct ast *l;• struct ast *r;• };

• struct fncall { /* built-in function */• int nodetype; /* type F */• struct ast *l; /* list of arguments */• enum bifs functype;• };

Page 24: 使用Bison

• struct ufncall { /* user function */• int nodetype; /* type C */• struct ast *l; /* list of arguments */• struct symbol *s; /* 指向自定义函数符号表入口的指针 */• };

• struct flow {• int nodetype; /* type I or W */• struct ast *cond; /* condition */• struct ast *tl; /* then or do list */• struct ast *el; /* optional else list */• };

Page 25: 使用Bison

• struct numval {• int nodetype; /* type K */• double number;• };

• struct symref {• int nodetype; /* type N */• struct symbol *s;• };

• struct symasgn {• int nodetype; /* type = */• struct symbol *s;• struct ast *v; /* value */• };

Page 26: 使用Bison

• /* build an AST */• struct ast *newast(int nodetype, struct ast *l, struct

ast *r);• struct ast *newcmp(int cmptype, struct ast *l, struct

ast *r);• struct ast *newfunc(int functype, struct ast *l);• struct ast *newcall(struct symbol *s, struct ast *l);• struct ast *newref(struct symbol *s);• struct ast *newasgn(struct symbol *s, struct ast *v);• struct ast *newnum(double d);• struct ast *newflow(int nodetype, struct ast *cond,

struct ast *tl, struct ast *tr);

Page 27: 使用Bison

• /* define a function */• void dodef(struct symbol *name, struct symlist *syms, struct ast *st

mts);

• /* evaluate an AST */• double eval(struct ast *);

• /* delete and free an AST */• void treefree(struct ast *);

• /* interface to the lexer */• extern int yylineno; /* from lexer */• void yyerror(char *s, ...);

• extern int debug; /*是否调试 */• void dumpast(struct ast *a, int level);

Page 28: 使用Bison

关于值的约定• 每个抽象语法树都有相应值• 赋值表达式的值为右边表达式的值• 对于 if/then/else 而言,其值为其所选择分支的值• while/do 的值则是 do 语句列表的最后一条语句的值• 表达式列表的值由最后一个表达式确定

fb3-2.yfb3-2.l

Page 29: 使用Bison

简单的错误恢复• 由于 bison本身的工作原理,它不太值得花精力来尝试错误恢复

• 但至少可能在错误发生时把语法分析器恢复到可以继续工作的状态

• 方法如下:•引入伪记号 error 确定错误恢复点•当 bison 语法分析器遇到一个错误时,它开始从语法分析器堆

栈中放弃各种语法符号,直到到达一个记号 error 为有效的点•然后开始忽略后续输入记号,直到它找到一个在当前状态下可

以被移进的记号,然后从这一点开始继续分析。•如果又发生分析错误,它将放弃更多堆栈中的语法符号和输入

记号,直到它可以重新恢复分析,或者堆栈为空而分析失败

Page 30: 使用Bison

简单的错误恢复• 为避免大段误导性的错误信息,语法分析器常在第一个错误产生后就抑制后续的分析错误信息,直到它能够成功地在一行里移进三个记号

• 宏 yyerrorok告诉语法分析器恢复已经完成,这样后续的错误信息可以顺利产生

• 记号 error几乎总是在顶层递归规则的标点符号处被用于进行同步

Page 31: 使用Bison

哈希函数• /* hash a symbol */• static unsigned• symhash(char *sym)• {• unsigned int hash = 0;• unsigned c;

• while(c = *sym++) hash = hash*9 ^ c;

• return hash;• }

问题:可能会溢出c=759^75≈3.6e+71>2^32

Page 32: 使用Bison

• struct symbol *• lookup(char* sym)• {• struct symbol *sp = &symtab[symhash(sym)%NHASH];• int scount = NHASH; /* how many have we looked at */• while(--scount >= 0) {• if(sp->name && !strcmp(sp->name, sym)) { return sp; }

• if(!sp->name) { /* new entry */• sp->name = strdup(sym);• sp->value = 0;• sp->func = NULL;• sp->syms = NULL;• return sp;• }

• if(++sp >= symtab+NHASH) sp = symtab; /* try the next entry */

• }• yyerror("symbol table overflow\n");• abort(); /* tried them all, table is full */

• }

线性探测法解决冲突

Page 33: 使用Bison

抽象语法树节点构造过程• newast~newsymlist等过程,基本步骤如下:

•分配一个节点•然后基于节点类型恰当地填充各个域

• struct ast *• newast(int nodetype, struct ast *l, struct ast *r)• {• struct ast *a = malloc(sizeof(struct ast));• • if(!a) {• yyerror("out of space");• exit(0);• }• a->nodetype = nodetype;• a->l = l;• a->r = r;• return a;• }

Page 34: 使用Bison

其他辅助函数• 计算器核心例程 eval

•深度优先遍历抽象语法树来计算表达式的值• 释放符号列表 symlistfree• 释放一棵抽象语法树 treefree

•递归地遍历一棵抽象语法树,同时释放这棵树的所有节点• 调用内置函数 callbuiltin• 调用用户自定义函数 calluser• 调试模式时输出抽象语法树信息 dumpast

Page 35: 使用Bison
Page 36: 使用Bison

执行