flex bison 笔记

来源:互联网 发布:亚马逊读书软件 编辑:程序博客网 时间:2024/05/22 00:04

前言


  • flex选择更长的匹配,如果两个模式都匹配,选择首先出现的模式;
  • 文法与语法分析:构建语法分析树,找出输入记号之间的关系;
  • 上下文无关语法,bison中token一般使用大写字母。bison包含三部分构成:声明部分+ 规则部分和C代码部分。使用分号代表规则的结束;
  • 语法分析器返回记号时,记号值总被存储在yyval中
  • 当规则缺少显示动作时,语法分析器将1$
  • bison不分析二义性语法,例如加减法的先后顺序,这里有点绕;后面可以事先设定优先级和分组,解决二义性

几个概念

  • 语法起始符号:第一条规则左边的语法符号,整个输入必须被他匹配
  • 目标符号:冒号左边的语法符号,用$$代替,右侧语法符号的语义值依次为$1 $2…
  • 记号:token,用%token声明
  • 动作代码:在每条规则之后,用花括号括起

使用FLEX

正则表达式

  • . 匹配除换行符以外任意单一字符
  • [] 字符类,匹配方括号内任意一个字符,若起始为^,反向匹配
  • [a-z]{-}[jv] 匹配前一个字符类减去后一个字符类的结果
  • * 匹配零个或多个紧接在前面的表达式
  • + 匹配一个或多个…
  • ? 匹配零个或一个…
  • “…” 引号内字符被基于字面意义被解释
  • () 一系列的正则表达式组成新的正则表达式
  • [ \t]* 匹配空字符串
  • | 匹配前面的正则表达式或者后面的,作用范围左边所有或右边所有
  • / 尾部上下文,匹配斜线前的表达式,并对其后的表达式进行要求,但并不消耗掉其后字符
  • {} 当花括号中有一个或两个数字时,表示前一个模式可以匹配的最小最大次数;当里面带有名字时,指向以这个名字命名的模式
  • (?a:pattern)或者(?a-x:pattern):Perl风格的模式修饰符。前者用修饰符a修饰模式,后者使用修饰符a但不包含修饰符x来修饰模式。
  • 修饰符包含i s x;i标示大小写无关;s将所有字符作为单行匹配,即.可以匹配\n;x忽略空白字符和C风格注释
  • 支持POSIX字符类,[:alnum:] [:alpha:]等
    详见P138
    处理二义性

  • 重复匹配的操作符总是针对邻近的表达式

  • 负号的用法,当匹配正负号时[-+]?放在前面,避免被误认为范围
  • flex允许两个不同模式匹配相同的输入,dangerous
  • 复杂的匹配模式并不会降低其速度,除了尾部上下文符号
  • 匹配关键字的模式先于匹配标识符的模式,就能正确匹配关键字
  • 上下文相关的词法分析,例如pascal

输入

  • -lfl flex提供的库,定义了main例程和yywarp
  • yywrap:当lex词法分析器到达yyin的结束位置时,它有另一个文件时,调整yyin的值且返回0,重新开始词法分析,%option noyywarp可以关闭默认yywarp
  • 通过判断输入是否是终端,选择性开启预读机制

三种层次管理输入:

  • 设置yyin
  • 创建并使用YY_BUFFER_STATE数据结构处理输入
  • 重新定义读取输入到缓冲区的宏YY_INPUT,对于某些输入无法预先载入字符串

    YY_BUFFER_STATE bp;
    bp = yy_create_buffer(yyin,YY_BUF_SIZE);//YY_BUF_SIZE由flex定义,通常是16k
    yy_switch_to_buffer(bp);

输出:flex有默认的输出规则,使用%option nodefault禁用默认规则

起始状态和嵌套输入文件

  • 起始状态指定在特定时刻哪些模式可以用来匹配,尖括号括起的起始状态之后的模式为其指定的模式
  • %x指定独占的起始状态,当该状态被激活时,只有它的模式可以进行匹配;
    %s声明包含的起始状态,允许未被标记的模式匹配
  • 宏BEGIN用来切换到另一个起始状态
  • 切换缓冲区的步骤:保存当前缓冲区,然后yy_switch_buffer切换缓冲区,设置状态INITIATE后,使用正常模式进行匹配

使用符号表

  • 线性探测哈希函数查找对应符号条目
  • flex和bison中难以跟踪字符串存储管理,yytext每次都会被新的词法符号所替换

内置变量

  • yyin: 输入缓冲流的文件指针,可以被替换以实现解析某个自定义的文件
  • yyleng: 当前匹配字串的长度
  • yylex: 调用词法分析器进行分析
  • yylineno: 保存当前行号
  • yyout: 输出流的指针
  • yyrestart(fp):重启解析文件fp,但会丢失已经读入缓冲区的输入
  • yytext: 当前匹配的字串
  • yyterminate: 结束词法分析器,返回YY_NULL即0,bison把它作为输入的结束
  • yywrap: 解析一个文件完毕之后,会调用yywrap:返回1表示结束,0表示继续扫描
  • YY_INPUT: 当输入缓冲区为空时,调用此接口

Flex状态

  • INITIAL: 默认起始状态
  • BEGIN: 切换到指定的初始状态

字符常量
\’([^”\]\[‘”?\abfnrtv]|\[0-7]{1,3}|\[Xx][0-9a-fA-F]+|{UCN})+\’
数字——
**[+-]?[0-9]+

QA
P47使用input先读完行剩余内容,从包含文件处理完后继续处理? 已解决
切换到INITIAL状态后,在当前缓冲区继续执行,直到匹配到EOF,进行相应动作

线性探测哈希函数的原理:*9^char

P60没有匹配文件名前后的标点符号?

第二章,练习1答案:使用^.*匹配会忽略include语句。

tips
linux命令成功返回0,否则非零

使用bison


  • LHS:规则的左部,语法符号都是非终结符
  • LRS:规则的右部
  • 终结符:在输入中出现并被词法分析器返回的符号
  • 语法由一系列规则组成,语法的起始符号作为语法分析树的根存在

移进:当语法分析器读到的记号无法结束一条规则时,将该记号压入内部堆栈;

归约:当压入堆栈的语法符号已经可以组成规则的右部时,弹出所有右部符号,把对应的左部符号入栈;归约后会执行规则关联的代码

LALR(1)无法分析的语法
LALR:自左向右向前查看一个记号,它不能处理有歧义的语法,例如相同输入可以匹配多棵语法树的情况,也不能处理需要向前查看多个记号的语法

bison语法分析器
bison规范由三部分构成:定义部分;语法分析器的规则;C代码

抽象语法树

  • 可编写递归子程序遍历抽象语法树,分析树中不包含不需要关注的规则节点
  • 使用%union声明符号值类型(yylval),并使用<>定义符号的类型;记号及非终结符不需要为其声明类型
  • P59calclist规则动作中,printf的设置影响输出结果的精度

移进/归约冲突和操作符优先级

  • 可以再语法规则外单独描述优先级,%left %right %noassoc的出现顺序决定了由低到高的优先级
  • %left表示左结合、%right右结合、%noassoc表示没有结合性
  • UMINUS单目负号操作的伪标记
  • %prec UMINUS将较低优先级的单目负号拥有比乘法更高的优先级
  • 不要滥用优先级规则,除了表达式语法或者解决if/then/else语言结构的”dangling else”冲突;尽量修正语法解决冲突

P67笔误:sym域(应为syms)域指向任意多个虚拟参数

  • %start声明了顶层规则,不需要将规则放到语法分析器的开始部分
  • 右递归更容易构建从头到尾的语句链表;缺点是将所有语句放到语法分析器的堆栈中,有堆栈溢出的风险
  • 单引号引起的字符可以作为记号;可以用文字字符记号表示操作符

简单的语法分析器错误恢复:伪记号error确定了错误恢复点,通常在第一个错误后,抑制后续的分析错误消息,直到成功在一行内移进三个记号

一个高级计算器

  • 使用单一记号表达多个语义相近的操作符可以帮助缩短语法长度
  • EOL匹配续行,一个斜线和一个换行符
  • 核心例程eval计算抽象语法树。采用类似深度优先的树遍历算法计算表达式的值
  • 预读终结符存储在yychar,而其语义和位置存储在yyval和yylloc中

分析SQL


逆波兰式:便于语法分析树的建立。
初始状态的使用:使用状态BTWEEN控制AND语义是逻辑与还是BETWEEN…AND

flex规范参考


结构规范程序由三部分构成:定义部分、规则部分、用户子例程。

...定义部分...%%...规则部分...%%...用户子例程...

定义部分:包含选项、文字块、定义、开始条件和转换,空白符开始的行被原样拷贝到C文件中;

规则部分

  • 包含模式行和C代码,以空白字符开始的或在%{和%}之间的认为是C代码,会被直接拷贝到yylex()中
  • 规则部分开头出现的C代码也会出现在yylex的开头,一般是变量声明;其他部分的C代码一般是注释,因为你无法确定其在词法分析器的位置
  • 模式行的C代码,若是超过一条语句,必须用括号括起
  • 若输入字符无法匹配任何模式,默认匹配ECHO模式,记号的拷贝被输出

用户子例程:子例程的C代码会被原样拷贝到C文件,大型程序中,支撑代码放在另一个单独的源文件中比较方便,减少lex文件修改后重新编译的内容
BEGIN宏切换起始状态,该状态值为0,也被称为INITIAL,其他状态需要用%s(包含)或%x(独占)命名
上下文相关性
左上下文相关的实现方法:特殊的行首模式字符、起始状态和显式代码。

  • ^可以只在行首匹配模式
  • 起始状态可以要求某个token必须出现在另一个token之前,下面的second必定出现在first之后

    %s MYSTATE
    %%
    first {BEGIN MYSTATE;}

    second {BEGIN 0;}

  • 使用标志伪造左上下文相关性,详见P125

右上下文相关的实现方法:特殊的行尾模式字符、斜线、yyless()

  • $匹配行尾模式,即下一个字符是\n
  • 模式中/字符可以包含显式尾部上下文,例如abc/de,仅匹配紧跟着de的abc字符,但de不会出现在yytext中,也不会被yyleng计算
  • yyless可以推回刚读到的记号,需要标明需要保留的字符个数,例如abcde {yyless(3);}与使用/作用一致

定义对全部或部分正则表达式进行命名,并通过名字引用他们{NAME}
ECHO对于未匹配的记号,输出到yyout中,相当于frpintf(yyout,"%s",yytext),可以通过%option nodefault取消这个默认动作:
输入缓冲区

  • YY_BUFFER_STATE:词法分析器读取输入的结构,通过yy_create_buffer创建
  • yy_switch_to_buffer:切换缓冲区结构
  • yy_flush_buffer:放弃缓冲区中的内容,可以在交互式词法分析器中做错误恢复
  • yy_delete_buffer:释放缓冲区
  • 字符串输入“yy_scan_bytes” “yy_scan_string” “yy_scan_buffer”
  • input():提供输入字符给词法分析器,flex为了性能跳过该函数,但可以利用它处理动作例程,例如超长注释的处理(flex通常只有15k缓冲区),详见p129
  • YY_INPUT宏读取输入到缓冲区,对于非终端非文件的输入,重定义该宏是很有必要的
  • isatty()确定输入是否来自终端
  • yylineno:可以自动更新行号,但读取多个文件时需要自己初始化行号。通过%option yylineno打开该变量
  • yywarp()在词法分析器到达文件末尾时调用,若返回1,则词法分析器返回零记号表明文件结束;若返回0,则继续分析,flex标准版yywarp总返回1。通过调整yyin指向新的文件,然后自定义yywarp可以继续分析。
  • 避免和c++库冲突,在C++词法分析器中input和output更改为yyinput yyoutput
  • yyleng:匹配记号的长度,相当于strlen(yytext)
  • yyless():如果当前匹配的字符串的末尾部分需要重新处理,那么可以调用yyless(n)将这部分子串”退回”给输入串,下次再匹配处理。注意n是不退回的字符个数,即退回的字符个数是yyleng-n
  • yymore当需下一次被匹配的字符串被添加在当前识别出的字符串后面,即不使下一次的输入替换yytext中已有的内容而是接在它的内容之后,必须在当前的动作中调用yymore()
  • YY_USER_ACTION:宏在语法分析器动作的代码前被展开
    文字块使用%{和%}括起的C代码,包含变量及函数声明和引入的头文件;若使用%top{则会将代码拷贝到程序的头部附近
    可重入词法分析器:也称为纯语法分析器,详见p213
    REJECT:对于重叠的记号,可以分析每个出现的记号

Bison规范参考


语法结构由三部分构成:定义部分、规则部分、用户子例程。

...定义部分...%%...规则部分...%%...用户子例程...

符号:error标示错误恢复,其他bison没有预先设定。

bison未处理一个语法分析器时,创建一组状态,每个状态反映出一个或多个部分分析过的规则中可能的位置。当读到的记号无法结束一条规则时将这个记号压入一个内部堆栈,切换到一个新的状态,这个状态能够反映出刚刚读取的记号,这种行为叫移进

  • 终结符(记号):词法分析器产生的符号,记号也可以是引号引起的文字字符”+”
  • 非终结符:规则左部定义的符号
  • 嵌入动作:规则中间的动作,被构造一条新的规则。但有时会造成移进归约冲突
  • GLR无限制向前查看
  • 冲突类型有两种:移进/归约和归约/归约
  • %expect预计冲突个数,若bison分析的结果不一致则报编译错误
  • 若LALR分析算法很难处理,则使用LR语法分析

bison程序的问题

  • 无限递归
  • 互换优先级:
  • 嵌入动作:匿名规则有时会导致难以预料的移进、归约冲突P146

C++语法分析器
将bison文件产生对应的C++语法分析器,例如bison文件为bfile.yxx,使用-o参数可以指定输出文件,例如-do bfile.c++则会产生bfile.c++和头文件bfile.h++。其中-d选项指定产生头文件;-p(-prefix)指定语法分析器的命名空间,默认是yy。

%code:bison在定义模块可以接受%{之间的C代码,会被拷贝到生成的C源文件靠近开头的位置,在yyparse()开头之前。通过限定符place可以指定代码在生成程序中的特定位置。place的值可以为top、provides和requires,分别表示文件顶部,YYSTYPE与YYLTYPE的定义之前和定义之后。

%code[place]{    ...code...}

结束标记:$end,标记输入的结束
%destructor:需要特殊处理时,借助该标记获取控制权
继承属性:bison符号值可以作为继承属性,例如从语法分析树根节点向叶节点传递信息。10等表示当前规则左部符号之前的值。

  • %type:值声明技术,继承属性的符号类型P153
  • %inital-action{some coed}将初始化代码放到yyparse的开始部分,在标准初始化之后。注意不应放变量声明的代码,无法被访问。
  • 语法反馈:从语法分析器反馈上下文信息到词法分析器;如果语法较为简单且记号不会出现在多个上下文中,可以使用词法分析器完成必要的转换;如果足够复杂,可以在语法分析器中处理。
  • 文字记号:‘(’,可以使用别名识别记号
  • 位置:可以使用%location显式激活位置信息,词法分析器在返回记号前记录当前行列息,在yylloc中设置。每次归约时,执行一个默认规则设置左部符号的位置范围,是右部符号的开始行列到最后一个右部符号的结束行与列。使用@$符号代表左部符号位置,第一个右部符号为@1,
  • %parse-param:在语法分析器设置分析参数
  • %require “2.4”设置bison的最低版本
  • 库文件,bison库文件包含main和yyerror,较大的语法分析器都会自带上述两个例程

优先级和结合性声明:解决语法歧义和冲突。%prec声明规则的优先级,移进和归约冲突时,比较移进记号和归约规则的优先级,若优先级相同则检查结合性,左结合则归约,右结合则移进。典型应用:if/then/else
dangling else ambiguity
假定我们正在分析一个语言,其中有if-then和if-then-else语句,对应的规则如下:

if_stmt: IF expr THEN stmt| IF expr THEN stmt ELSE stmt;

这里我们假设IF,THEN和ELSE是特别的关键字终结符。
当ELSE终结符读入后作为一个预读终结符时,堆栈中的内容(假设输入是合法的)正好可以归约到第一条规则上。但是把它移进堆栈也是合理的,因为那样根据第二条规则就会导致最后的归约。
在这种情况下,移进或者归约都是合法的,称为移进-归约冲突(shift-reduce conflict)。Bison的设计是,用移进来解决冲突,除非有操作符优先级声明的指令。为了解释如此选择的理由,让我们与其它可选办法进行一个比较。
既然分析器更倾向移进ELSE,那么其结果是把else子句连接到最内层的if语句,从而使得下面两种输入是等价的:

if x then if y then win (); else lose;if x then do; if y then win (); else lose; end;

如果分析器选择归约而不是移进,那么其结果将是把else子句连接到最外层的if语句,从而导致下面两个输入是等价的:

if x then if y then win (); else lose;if x then do; if y then win (); end; else lose;

冲突的存在是因为文法有二义性:简单的嵌套的if语句的任一种解析都是合理的。已有的惯例是这种二义性的解决是通过把else子句连接到最内层的if语句而获得的;Bison是选择移进而不是归约来实现的。(一种更清晰的做法是写出无二义性的文法,但对于这种情况来说是非常困难的。)这种特殊的二义性首次出现在Algol 60的规范中,被称作’dangling else ambiguity’。
递归规则:每个规则至少有一条非递归的规则(不指向自身)
左递归:内部堆栈占用较少;右递归:占用堆栈空间与记号多少相关。

%{#define YYMAXDEPTH  1000%}

设置堆栈长度的最大值,也可以定义YYINITDEPTH控制语法分析器堆栈的长度。每个堆栈元素大小是语义值的大小(%union元素的最大长度)加上2个字节的记号编号,如果使用位置信息还需要加上16字节。

规则:

  • 规则由一个非终结符开始,冒号和可能为空的符号、文字记号和动作的列表构成。
  • 连续几个规则拥有相同左部,使用竖线分隔
  • 规则末尾显式声明优先级

符号值:

声明符号类型:通过%union列出所有可能的类型,被typedef为YYSTYPE。对于在动作代码中需要使用的符号,必须声明其类型。可以使用%type声明非终结符

显式符号类型:通过$<>显式声明符号类型,一般在继承属性和使用嵌入动作返回值的时候使用。

记号:可以是通过%token定义的符号,或者是单引号中的各个字符。

记号编号:符号记号的编号通常由bison负责,一般大于任何字符编码的值,避免冲突;文字记号一般使用相应的C字符常量。P166
记号值:保存在yylval中,不同符号会有不同的值类型

匹配浮点数
[0-9]+.[0-9]*

%type声明非终结符类型,可以使用除了%union定义的符号类型:将自己声明的YYSTYPE放到某个头文件中,定义部分include进来,但在使用时至少有一个%type或其他符号类型声明来告知bison你在使用显式声明的符号类型。

可变语法和多重语法
合并的语法分析器优缺点:代码较少,可以共享部分语法;无法同时激活两个语法分析器,可能因为相同的符号造成错误
多重语法分析器:关键技巧在于重新命名bison所用的函数名和变量名。
具体方法:通过%name-prefix “name”更改语法分析器的名字前缀,或使用-p标志实现。-d指定生成生成C文件的前缀

  • yyparse语法分析程序,可以递归地进行分析
  • y.output日志文件,展示了语法分析器的所有状态和状态的变迁,可以使用–report=all,同时记录了冲突的移进和归约动作
  • bison库文件 -ly,包含例程main()和yyerror
  • yyerror提供错误报告例程
  • 语义动作YYABORT;当语义动作存在非常严重错误,无法继续分析时,使得yyparse立即返回非零值。注意:包含它的规则动作会等到语法分析器读到另一个记号才会归约。
  • YYACCEPT:使得yyparse返回零值,表明成功。当词法分析器无法判定输入结束而语法分析器可以时比较有用。包含它的规则动作会等到语法分析器读到另一个记号才会归约。P172
  • yyclearin放弃被预读的记号,在交互式语法分析器的错误恢复中非常有用。
  • YYDEBUG/yydebug跟踪代码,可以通过-t标志或将C预处理符号YYDEBUG定义为非零
  • yyerrok用于错误恢复,使语法分析器回到正常状态
  • YYERROR:通过动作代码判断错误并产生,与语法分析器检测到的错误效果一致
  • yyparse语法分析器的入口函数,成功返回零,否则返回非零值。每次调用都从头开始。通常该函数无参数,但可以使用%parse-param或全局变量传入信息
  • YYRECOVERING当语法分析器处于错误恢复模式,该宏返回非零值,否则返回零,可以协助确定是否需要报告发现的错误。
0 0
原创粉丝点击