标签(空格分隔): 未分类
本章我们主要讨论如何构建一个词法分析器
词法分析器生成工具(lexical-analyzer generator)
Lex
分析器生成工具正则表达式
我们将介绍正则表达式进行转换:
驱动程序
就是模拟这些自动机的代码,使用自动机确定下一个词法单元.
驱动程序和自动机的规约形成词法分析器的核心部分.
词法分析是编译的第一阶段.
词法分析器的主要任务是
getNextToken
所指示的调用使得词法单元从它的输入不断读取字符,直到识别到下一个词素为止.词法分析其余任务
有时候,词法分析分成两个级联处理
把编译阶段的分析部分化为词法分析和语法分析阶段有如下几个原因:
在很多程序设计语言中,下面类别覆盖了大多数词法单元
标识符
的属性值是一个指向符号表中该标识符条目的指针.如果没有其他组件帮助,词法分析器很难发现源代码的错误.
比如:
fi(a==f(x))
恐慌模式
:所有词法单元都无法和剩余输入的某个前缀相匹配时的策略
<float> <id, limitedSquaare> <(> <id, x> <)> <{>
<float> <id, x>
<return> <(> <id, x> <op,"<="> ||"> <id, x> <op, ">="> <num, 10.0> <)> <op, "?"> <num, 100> <op, ":"> <id, x> <op, "*"> <id, x>
<}>
<text, "Here is a photo of"> <nodestart, b> <text, "my house"> <nodeend, b>
<nodestart, p> <selfendnode, img> <selfendnode, br>
<text, "see"> <nodestart, a> <text, "More Picture"> <nodeend, a>
<text, "if you liked that one."> <nodeend, p>
讨论几种可以加快源程序读入速度的方法.
-
,=
,<
可能是->
,==
,<=
这样双字符运算符的开始.哨兵标记
来节约检查缓冲区末端的时间.由于在编译一个大型程序需要处理大量的字符,处理这些字符需要很多时间,由此开发了一些特殊的缓冲技术来减少用于处理单个输入字符的时间开销.一种重要机制是利用两个交替读入的缓冲区.
lexemeBegin指针
:该指针指向当前词素的开始处.当前我们正试图确定这个词素的结尾.forward指针
:它一直向前扫,直到发现某个模式被匹配到为止 一旦确定下一个词素,forward
指针将指向该词素结尾的字符.
lexemeBegin
指针指向刚找到词素后的第一个位置forward
指针也会前移一个位置如果forward
指针前移超过缓冲区末尾(哨兵标记优化的地方)
forward
指针指向这个新载入符的头部.思考之前的有两步操作
在缓冲区末尾扩展一个绝对不会使用的符号,叫做哨兵(sentinel)
字符,一个自然的选择是eof
.
正则表达式是一种用来描述词素模式的重要表示方法.
这一节我们将研究正则表达式的形式化表示方法
在3.5节中 我们将看到如何将正则表达式用到词法分析生成工具中.
字母表(alphabet)
是一个有限的符号集合.
{0,1}
,ASCII
,Unicode
.某个字母表的串(string)
是该字母表符号的有穷序列.
在词法分析,最重要的语言上的运算是并
,连接
,和闭包运算
.
正则表达式可以由较小的正则表达式按照如下规则递归地构建.
某个字母表Σ上的正则表达式以及这些表达式所表示的语言
根据优先级丢掉括号
*
是最高级,并且是左结合.|
的游戏级最低,也是左结合.(a)|((b)*(c))
改写为a|b*c
正则定义(regular definition)
是具有如下形式的定义序列: di
都是一个新符号,不在Σ中,并且各不相同.ri
是字母表`Σ U {d1,d2,…di-1}上的正则表达式.除了以上的运算,在现代像Lex
这样的实用Unix程序都有对正则的扩展
+
+
表示一个正则表达式及其语言的正闭包. (r)+
意思为 (L(r))+
+
与*
有相同的优先级?
?
的意思是零个和一个出现.r?
等价于r|空集
?
与+
与*
有相同的运算集[]
a1 | a2 |...|an
可以缩写为[a1a2...an]
[a1-an]
[1-9]
,[a-z]
第三题
\/\*([^*"]*|".*"|\*+[^/])*\*\/
第四题
先解决比较简单的{0,1,2}
SB解法
0?1?2?|0?2?1?|1?0?2?|1?2?0?|2?0?1?|2?1?0?|
正确解法
want -> 0|A?0?1(A0?1|01)*A?0?|A0?
A -> 0?2(02)*
证明如下面的图
5-7太难
第八题:
b*(a+b?)*
第九题:
b* | b*a+ | b*a+ba*
我们学习如何根据各个需要识别的词法单元的模式来构造一段代码.
状态转换图
状态转换图
有一组被称为状态
的结点和圆圈.我们可以用两种方法处理像标识符的保留字.
有几种方法根据一组状态图构造出词法分析器.
函数fail()
具体操作依赖于全局恢复策略
forward
指针重置为lexemeBegin
的值fail()
启动一个错误纠正步骤.状态8,带有*
,输入指针会回退,c放回输入流
retract()
完成状态图的执行
介绍一个名为Lex
的工具,在最近的实现中也称为Flex
.
Lex语言
,工具本身是Lex编译器
.a.out
通常是语法分析器调用的子例程yylval
中 声明部分包括变量和明示常量(manifest con-stant)和正则定义
Lex的转换规则有如下形式
模式 {动作}
Lex第三个部分包含各个动作要的辅助函数.
Pi
匹配的前缀.Ai
. Ai
会返回语法分析器yylval
传递词素附加信息某些时候,我们希望仅仅词素后面跟随特定字符,才能和模式匹配.
/
之后跟随表示一个附加的模式.揭示Lex如何将输入程序转换为词法分析器,核心在于有穷自动机
这些自动机本质和转换状态图类似,但也有以下不同
确定和不确定的能识别的语言的集合是相同的,恰好也是正则表达式能识别的集合,这个集合的语言叫做正则语言
一个NFA由以下部分组成
S
Σ
,即输入字母表. S
中的s0
被称为开始状态S
的一个子集F
被称为接受状态跟转换图有以下区别
(a|b)*abb
的NFA转换图
一个NFA**接受输入字符串x,当且仅当对应的转换图中存在一条开始状态到某个接受状态的路径,使得路径中各条变上的标号组成字符串x.
注意:路径的ε
被忽略
我们可以用L(A)
表示自动机A接受的语言
首先介绍如何把NFA转换为DFA.
子集构造法
的技术给出一个直接模拟NFA的算法 接着,我们介绍正则表达式转为NFA
最后讨论不同正则表达式实现技术的时间-空间权衡,并说明如何选择正确的方法.
子集构造法的基本思想:是让构造得到的DFA的每个状态对应于NFA的一个状态集合.
DFA在读入输入a1a2...an
之后到达的状态对应于相应NFA从开始状态出发,沿着以a1a2...an
为标号走到的路径能够到达状态的集合.
DFA
状态数可能是NFA
状态数的指数,此时试图实现这个DNA有点困难.
然而,基于自动机的词法分析方法的处理能力部分基于这个事实:
输入: 一个NFA N
输出: 一个接受同样语言的 DFA D
方法: 我们的算法为D
构造一个转换表Dtran
.
D的每个状态是一个NFA
状态集合,我们将构造Dtran
,使得D “并行的” 模拟N在遇到一个给定输入串可能执行的所有动作.
第一个问题:正确处理N的`转换.
给出一些基本操作 s
表示N的单个状态,T表示一个状态集.
算法代码(有ACM功底很容易懂):
D的开始状态是ε-closure(s0)
,D的接受状态是所有至少包含N
的一个接受状态的状态集合,
ε-closure(T)
代码,一个简单的深搜而已
许多的文本编辑器使用的策略是根据一个正则表达式构造出相应的NFA
,然后使用类似于on the fly
(边构造边)的子集构造法来模拟这个NFA的执行.
也类似广搜,一步一步把所有情况跑一遍.
需要以下数据结构
两个堆栈,其中每个堆栈都存放了一个NFA状态集合.
oldStates
存放 “当前状态集合”newStates
存放 “下一个状态集合”一个以NFA状态为下标的布尔数组 alreadyOn
,指示那个状态已经在newStates
.
一个二维数组 move[s,a]
,保存了这个NFA的转换表,是一个邻接表(因为转换表单元格有多个元素).
O(k(m+n))
复杂度…那就是吧现在给出一个算法,它可以将任何正则表达式转为接受相同语言的NFA
基本规则
r=s|t
r=st
r=s*
几个有趣的性质
子集构造法 : O(|r|^2 * DFA状态数)
DFA状态数一般是|r|
模拟算法: O(|r|*|x|)
Lex
这样生成工具的体系NFA
和DFA
的方法,后者实质上就是Lex
的实现方法本节给出三个算法,用于实现和优化根据正则表达式构建的模式匹配器
O(nlogn)
如果一个NFA状态有一个标号非ε
的离开转换,则称这个状态是重要状态(important state)
ε-closure(move(T,a))
,它只使用了集合T中的重要状态.s
是重要状态时,move(s,a)
才可能是非空的.在子集构造法中,两个NFA状态可以被认为是一致的的条件是:
如果NFA由正则表达式生成,还有更多关于重要状态的性质
(r)#
,使得原本的接受状态变为重要状态,使得在构造过程中,不要考虑接受状态. (r)#
又叫扩展的正则变道时使用抽象语法树表示扩展的正则表达式.
a
有1和3nullable
,firstpos
,lastpos
和followpos
. (r)#
下进行的.ε
返回真 q
在 followpos(p)
中当且仅当存在L((r)#)
中的某个串x=a1a2...an
,使得我们在解释为什么x属于L((r)#)
时,可以将x中某个ai
可以和位置p匹配,且将位置ai+1和位置q匹配只有两种情况,正则表达式某个位置会跟在另一个位置之后
cat
节点,且左右节点是c1
,c2
. lastpos(c1)
中的每个位置i
,firstpos(c2)
中的所有位置都在followpos(i)
中.lastpos(n)
中的一个位置,那么firstpos(n)
中的所有位置都在followpos(i)
中.(r)#
构造出一颗抽象语法树T
nullable
,firstpos
,lastpos
,followpos
D
的状态集Dstates
和D
的转换函数Dtran
(数电中有提及)
任何正则语言都一个唯一的(不计同构)状态数目最少的DFA.,从任意一个接受相同语言的DFA出发,通过分组合并等价的状态,我们总是构建这个状态数最少的DFA.
该算法首先创建输入DFA的状态的集合的划分.
输入串如何区分各个状态:
DFA状态最小化的工作原理是将一个DFA的状态集合分划成多个组,每个组中的各个状态之间相互不可区分.
之后就类似于缩点的算法.