370 likes | 590 Vues
使用Bison. 第3章. bison的用途. flex可以识别正则表达式,生成词法分析器 bison可以识别语法,生成语法分析器 词法分析将输入流分解为若干个记号 而语法分析则分析这些记号并基于逻辑进行组合. 如何匹配输入. bison可以基于给定语法来生成一个可以识别该语法中有效“语句“(如:C程序)的语法分析器 程序可能在语法上正确但是语义上有问题,如: 把一个字符串赋值给整型变量 bison只处理语法的正确性 语法分析器基于语法的规则来识别语法上正确地输入 表示语句分析的通常方法是语法树或抽象语法树. Bison的工作原理.
E N D
使用Bison 第3章
bison的用途 • flex可以识别正则表达式,生成词法分析器 • bison可以识别语法,生成语法分析器 • 词法分析将输入流分解为若干个记号 • 而语法分析则分析这些记号并基于逻辑进行组合
如何匹配输入 • bison可以基于给定语法来生成一个可以识别该语法中有效“语句“(如:C程序)的语法分析器 • 程序可能在语法上正确但是语义上有问题,如: • 把一个字符串赋值给整型变量 • bison只处理语法的正确性 • 语法分析器基于语法的规则来识别语法上正确地输入 • 表示语句分析的通常方法是语法树或抽象语法树
Bison的工作原理 • 语法分析器通过查找能够匹配当前记号的规则来运作,进行移进/归约分析 • 当bison处理语法分析器时,会创建一组状态,每个状态都反映出一个或者多个部分分析过的规则中可能的位置 • 当语法分析器读取记号时 • 若读到的记号无法结束一条规则时,将记号压入堆栈并切换到一个新状态,该状态能反映出刚读取的记号(移进) • 当发现压入的所有语法符号已经可以组成规则的右部时,将右部符号弹栈,然后把左部语法符号压栈,状态也做相应转换(归约) • 归约时会执行该规则相关联的用户代码
bison的分析方法 • 可使用两种分析方法 • LALR(1)分析(Look-Ahead,Left to Right, Rightmost derivation) • GLR分析(generalized left-to-right) • 大多数语法分析器采用LALR(1),它不如GLR强大但更快、更易于使用
LALR分析无法处理的语法 • 二义性文法 • 需向前查看多个记号才能确定是否匹配规则的语法,例: • phrase: cart_animal AND CART • | work_animal AND PLOW • cart_animal: HORSE | GOAT • work_animal: HORSE | OX • 无二义性 • 但对于输入HORSE AND CART这样的输入,在没有看到CART之前无法区别HORSE是一个cart_animal还是一个work_animal。 • 若将第一条规则改为: • phrase: cart_animal CART • | work_animal PLOW • 则只需向前查看一个记号,bison就能够处理
基于抽象语法树的改进的计算器——例3-1 • 文件清单如下: • fb3-1.h • fb3-1.y • fb3-1.l • fb3-1funcs.c
语法符号的值与类型 • bison中每个文法符号(包括终结符和非终结符)都可以有一个相应的值,默认类型是int • 实际应用中常需要更多有价值的符号值,可以用union来为符号声明联合类型 • 再为每种符号指定其使用的值类型,方法如下: • %token <联合类型的相应成员名> 记号列表 • %type <联合类型的相应成员名> 非终结符列表
语法符号的值与类型举例 • %union { • struct ast *a; • double d; • } • /* declare tokens */ • %token <d> NUMBER • %token EOL • %type <a> exp factor term
辅助函数(fb3-1funcs.c) • 构造语法树节点 • newast,newnum • 申请空间 • 对语法树节点的各个字段进行赋值 • 计算语法树的值 eval • 删除和释放语法树treefree • 报错yyerror • VA_LIST 是在C语言中解决变参问题的一组宏 • VA_START获取可变参数列表的第一个参数的地址(ap是类型为va_list的指针,v是可变参数最左边的参数)
编译基于抽象语法树的计算器——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
编译基于抽象语法树的计算器 • 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 • 生成可执行文件 • 执行可执行文件
执行效果 输出结果全都错误
调试并分析原因 • 通过设置断点调试发现在执行"."?[0-9]+{EXP}? { yylval.d = atof(yytext); return NUMBER; } 后yylval.d的值错误 • 可能是atof没起作用 • 再加上编译时的报警信息 • 查阅资料在fb3-1.lex.c加上#include <stdlib.h> • 重新编译运行 运行结果正确!
二义性的处理——优先级与结合性 • 表达式分析器引入了三个不同的语法符号exp、factor和term来设置操作符的优先级和结合性 • 但随着具有更多不同优先级的操作符被加入到算法中,整个算法将难于阅读和维护 • bison允许显式地指定优先级和结合性,例: • %left '+' '-' • %left '*' '/' • %nonassoc '|' UMINUS • %type <a> exp • 注: • %right 右结合 • 优先级相同的在同一行说明 • 声明在后面行的操作符比声明在前面行的优先级高
%% • ... • 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); } • ;
什么时候不应该使用优先级规则 • 虽然可以使用优先级规则解决语法中出现的任何移进/归约冲突,但有时难于明白改动给语法带来的后果 • 只应在两种场合使用优先级规则: • 表达式语法 • if语句的悬挂else问题 • 只要有可能,应通过修正语法来解决冲突 • 冲突说明文法有二义性 • 除了前面两种场合,它表明语言定义有问题
一个高级计算器 • 扩展了前例 • 添加了命名变量和赋值 • 比较表达式 • if/then/else和do/while控制流程 • 内置和用户自定义的函数 • 一点错误恢复机制 • 充分利用抽象语法树实现流程控制和用户自定义函数 • 文件清单如下: • fb3-2.h • fb3-2.y • fb3-2.l • fb3-2funcs.c
高级计算器声明部分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 虚拟参数列表*/ • };
高级计算器声明部分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);/*释放符号表*/
/* 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 • */
enum bifs { /* built-in functions */ • B_sqrt = 1, • B_exp, • B_log, • B_print • };
/* 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; • };
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 */ • };
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 */ • };
/* 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);
/* define a function */ • void dodef(struct symbol *name, struct symlist *syms, struct ast *stmts); • /* 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);
关于值的约定 • 每个抽象语法树都有相应值 • 赋值表达式的值为右边表达式的值 • 对于if/then/else而言,其值为其所选择分支的值 • while/do的值则是do语句列表的最后一条语句的值 • 表达式列表的值由最后一个表达式确定 • fb3-2.y • fb3-2.l
简单的错误恢复 • 由于bison本身的工作原理,它不太值得花精力来尝试错误恢复 • 但至少可能在错误发生时把语法分析器恢复到可以继续工作的状态 • 方法如下: • 引入伪记号error确定错误恢复点 • 当bison语法分析器遇到一个错误时,它开始从语法分析器堆栈中放弃各种语法符号,直到到达一个记号error为有效的点 • 然后开始忽略后续输入记号,直到它找到一个在当前状态下可以被移进的记号,然后从这一点开始继续分析。 • 如果又发生分析错误,它将放弃更多堆栈中的语法符号和输入记号,直到它可以重新恢复分析,或者堆栈为空而分析失败
简单的错误恢复 • 为避免大段误导性的错误信息,语法分析器常在第一个错误产生后就抑制后续的分析错误信息,直到它能够成功地在一行里移进三个记号 • 宏yyerrorok告诉语法分析器恢复已经完成,这样后续的错误信息可以顺利产生 • 记号error几乎总是在顶层递归规则的标点符号处被用于进行同步
哈希函数 • /* hash a symbol */ • static unsigned • symhash(char *sym) • { • unsigned int hash = 0; • unsigned c; • while(c = *sym++) hash = hash*9 ^ c; • return hash; • } 问题:可能会溢出 c=75 9^75≈3.6e+71>2^32
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 */ • } 线性探测法解决冲突
抽象语法树节点构造过程 • 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; • }
其他辅助函数 • 计算器核心例程eval • 深度优先遍历抽象语法树来计算表达式的值 • 释放符号列表symlistfree • 释放一棵抽象语法树treefree • 递归地遍历一棵抽象语法树,同时释放这棵树的所有节点 • 调用内置函数callbuiltin • 调用用户自定义函数calluser • 调试模式时输出抽象语法树信息dumpast