1 / 36

使用Bison

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

sidney
Télécharger la présentation

使用Bison

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. 使用Bison 第3章

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

  3. 如何匹配输入 • bison可以基于给定语法来生成一个可以识别该语法中有效“语句“(如:C程序)的语法分析器 • 程序可能在语法上正确但是语义上有问题,如: • 把一个字符串赋值给整型变量 • bison只处理语法的正确性 • 语法分析器基于语法的规则来识别语法上正确地输入 • 表示语句分析的通常方法是语法树或抽象语法树

  4. Bison的工作原理 • 语法分析器通过查找能够匹配当前记号的规则来运作,进行移进/归约分析 • 当bison处理语法分析器时,会创建一组状态,每个状态都反映出一个或者多个部分分析过的规则中可能的位置 • 当语法分析器读取记号时 • 若读到的记号无法结束一条规则时,将记号压入堆栈并切换到一个新状态,该状态能反映出刚读取的记号(移进) • 当发现压入的所有语法符号已经可以组成规则的右部时,将右部符号弹栈,然后把左部语法符号压栈,状态也做相应转换(归约) • 归约时会执行该规则相关联的用户代码

  5. bison的分析方法 • 可使用两种分析方法 • LALR(1)分析(Look-Ahead,Left to Right, Rightmost derivation) • GLR分析(generalized left-to-right) • 大多数语法分析器采用LALR(1),它不如GLR强大但更快、更易于使用

  6. 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就能够处理

  7. 基于抽象语法树的改进的计算器——例3-1 • 文件清单如下: • fb3-1.h • fb3-1.y • fb3-1.l • fb3-1funcs.c

  8. 语法符号的值与类型 • bison中每个文法符号(包括终结符和非终结符)都可以有一个相应的值,默认类型是int • 实际应用中常需要更多有价值的符号值,可以用union来为符号声明联合类型 • 再为每种符号指定其使用的值类型,方法如下: • %token <联合类型的相应成员名> 记号列表 • %type <联合类型的相应成员名> 非终结符列表

  9. 语法符号的值与类型举例 • %union { • struct ast *a; • double d; • } • /* declare tokens */ • %token <d> NUMBER • %token EOL • %type <a> exp factor term

  10. 辅助函数(fb3-1funcs.c) • 构造语法树节点 • newast,newnum • 申请空间 • 对语法树节点的各个字段进行赋值 • 计算语法树的值 eval • 删除和释放语法树treefree • 报错yyerror • VA_LIST 是在C语言中解决变参问题的一组宏 • VA_START获取可变参数列表的第一个参数的地址(ap是类型为va_list的指针,v是可变参数最左边的参数)

  11. 编译基于抽象语法树的计算器——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

  12. 编译基于抽象语法树的计算器 • 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 • 生成可执行文件 • 执行可执行文件

  13. 执行效果 输出结果全都错误

  14. 调试并分析原因 • 通过设置断点调试发现在执行"."?[0-9]+{EXP}? { yylval.d = atof(yytext); return NUMBER; } 后yylval.d的值错误 • 可能是atof没起作用 • 再加上编译时的报警信息 • 查阅资料在fb3-1.lex.c加上#include <stdlib.h> • 重新编译运行 运行结果正确!

  15. 二义性的处理——优先级与结合性 • 表达式分析器引入了三个不同的语法符号exp、factor和term来设置操作符的优先级和结合性 • 但随着具有更多不同优先级的操作符被加入到算法中,整个算法将难于阅读和维护 • bison允许显式地指定优先级和结合性,例: • %left '+' '-' • %left '*' '/' • %nonassoc '|' UMINUS • %type <a> exp • 注: • %right 右结合 • 优先级相同的在同一行说明 • 声明在后面行的操作符比声明在前面行的优先级高

  16. %% • ... • 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); } • ;

  17. 什么时候不应该使用优先级规则 • 虽然可以使用优先级规则解决语法中出现的任何移进/归约冲突,但有时难于明白改动给语法带来的后果 • 只应在两种场合使用优先级规则: • 表达式语法 • if语句的悬挂else问题 • 只要有可能,应通过修正语法来解决冲突 • 冲突说明文法有二义性 • 除了前面两种场合,它表明语言定义有问题

  18. 一个高级计算器 • 扩展了前例 • 添加了命名变量和赋值 • 比较表达式 • if/then/else和do/while控制流程 • 内置和用户自定义的函数 • 一点错误恢复机制 • 充分利用抽象语法树实现流程控制和用户自定义函数 • 文件清单如下: • fb3-2.h • fb3-2.y • fb3-2.l • fb3-2funcs.c

  19. 高级计算器声明部分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 虚拟参数列表*/ • };

  20. 高级计算器声明部分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);/*释放符号表*/

  21. /* 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 • */

  22. enum bifs { /* built-in functions */ • B_sqrt = 1, • B_exp, • B_log, • B_print • };

  23. /* 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; • };

  24. 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 */ • };

  25. 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 */ • };

  26. /* 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);

  27. /* 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);

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

  29. 简单的错误恢复 • 由于bison本身的工作原理,它不太值得花精力来尝试错误恢复 • 但至少可能在错误发生时把语法分析器恢复到可以继续工作的状态 • 方法如下: • 引入伪记号error确定错误恢复点 • 当bison语法分析器遇到一个错误时,它开始从语法分析器堆栈中放弃各种语法符号,直到到达一个记号error为有效的点 • 然后开始忽略后续输入记号,直到它找到一个在当前状态下可以被移进的记号,然后从这一点开始继续分析。 • 如果又发生分析错误,它将放弃更多堆栈中的语法符号和输入记号,直到它可以重新恢复分析,或者堆栈为空而分析失败

  30. 简单的错误恢复 • 为避免大段误导性的错误信息,语法分析器常在第一个错误产生后就抑制后续的分析错误信息,直到它能够成功地在一行里移进三个记号 • 宏yyerrorok告诉语法分析器恢复已经完成,这样后续的错误信息可以顺利产生 • 记号error几乎总是在顶层递归规则的标点符号处被用于进行同步

  31. 哈希函数 • /* 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

  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 */ • } 线性探测法解决冲突

  33. 抽象语法树节点构造过程 • 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; • }

  34. 其他辅助函数 • 计算器核心例程eval • 深度优先遍历抽象语法树来计算表达式的值 • 释放符号列表symlistfree • 释放一棵抽象语法树treefree • 递归地遍历一棵抽象语法树,同时释放这棵树的所有节点 • 调用内置函数callbuiltin • 调用用户自定义函数calluser • 调试模式时输出抽象语法树信息dumpast

  35. 执行

More Related