词法分析(理论篇)

写在前面


从源代码到可执行文件要经历几个过程:

  1. 词法分析
  2. 语法分析
  3. 语义分析
  4. 中间代码生成
  5. 代码优化

词法分析有点像中文分词,是将输入结构化的第一步:从字符串序列变为词法单元序列,它的输出将作为语法分析的输入。在词法单元中主要涉及到的概念有:

  1. DFA:有穷确定状态机。
  2. NFA:有穷不确定状态机。

确定和不确定的区别在于:当遇到字符的时候状态的变化是不是确定的,下面是一个NFA的例子:

NFA

对于这样一个状态机可以构造出一个转换表如下:

状态 a b
1 {1,2} {1}
2 {3}
3

对于DFA来说每个状态上的转换都是确定的,那么其对应的代码的样子如下:

s = s0;
c = nextChar();
while(c != eof){
    s = move(s, c); // 根据状态转换表
    c = nextChar();
}
return s in F ? "yes" : "no";

从代码上来看DFA要比NFA简单不少,那么下面接着来看。

从NFA到DFA


既然DFA看起来要比NFA用起来简单很多,那么如果能将NFA转换为DFA的话问题不就变简单了?

子集构造法是一种比较简单、直观的转化方法,既然在NFA中状态1根据字符a可以到状态1,也可以到状态2,那么如果将状态{1,2}合并看做一个节点,那得到结果不就是DFA了~

从NFA到DFA

在实际操作的时候需要用到三个集合:

  1. ε-closure(s):从NFA中状态s开始只通过ε得到的状态的集合。
  2. ε-closure(T):从T中的某个状态s开始只通过ε得到的状态的集合。
  3. move(T, a):从T中某个状态s开始通过a得到的状态集合。

在NFA上构造完成三个集合之后,那么可以通过如下流程进行转换:

// 在最开始Dstatus中只有ε-closure(s0)一种状态,并未未加标记
while(在Dstatus中有一个未标记的状态T){
    给T加上标记;
    for(每个输入符号a) {
        U = ε-closure(move(T, a));
        if(U 不在Dstates中)
            将U加入到Dstatus中// 不加标记
        Dtran[T, a] = U;// 转换表
    }
}

在实际上DFA和NFA的状态数都是差不多的,但是理论上转换后的DFA可能是原本的NFA状态数的指数级,(a|b)*a(a|b)^(n-1)就是这样一个例子:

DFA状态数为NFA的指数倍

从正则到NFA


很多的词法分析器都是用正则来描述规则,一般正则基本上能搞定所有的需求,而且足够简单。但是在识别输入的字符序列的时候需要用到自动机,那么就需要从正则到NFA or DFA的转换。对于正则的每种行为都有对应的NFA中的转换可以代替:

  1. r=s|t:通过ε实现。
  2. r=st:将s的接受状态和t的初始状态进行合并。
  3. r=s*:将s的接受状态和初始状态相连,从而达到循环的效果。

达到的效果如下:

从正则到NFA

对于(a|b)*a这样的正则按照上面的方法得到的NFA如下:

(a|b)*a对应的NFA

有了这种方法,根据正则的AST就能很容易递归生成NFA。

从正则到DFA


DFA是比较简单的,那么在解析正则的一个思路是正则->NFA->DFA,但是这样做比较麻烦,我们肯定是希望能够直接转换。这里需要四个集合:

  1. nullable(n):节点n的子表达式中包含空字符串。
  2. firstpos(n):节点n为根的子表达式中某个串的第一个符号的位置的集合。
  3. lastpos(n):节点n为根的子表达式中某个串的末一个符号的位置的集合。
  4. followpos(p):如果L((r)#)中的某个串x=a1..an,使得我们在解释为什么x属于L((r)#)时,可以将x中的某个ai和AST中的位置p匹配,且位置ai+1和位置q匹配,那么q在该集合中。

这样看起来比较难理解,来看一个例子:

集合例子

那么对于框中的根节点来说:

  1. nullable(n):false。
  2. firstpos(n):{1,2,3}。
  3. lastpos(n):{3}。
  4. followpos(1):{1,2,3}。

这些集合可以递归得到,比如较为麻烦的followpos的计算方法如下:

  1. 如果n是一个cat节点,其左右子节点分别为c1、c2,那么对于lastpos(c1)中的每个位置i,firstpos(c2)中的所有位置都在followpos(i)中。
  2. 如果n是一个star节点,并且i是lastpos(n)中的一个位置,那么firstpos(n)中所有的位置都在followpos(i)中。

其中关键的followpos其实就是NFA中的状态转换,那么下面的正则到DFA的过程就很容易理解了:

// 构造D的状态集Dstates和D的转换函数Dtran。
初始化Dstates,使之只包含未标记状态firstpos(n0),其中n0是(r)#的抽象语法树的根节点
while(Dstates中存在未标记状态S){
    标记S;
    for(每个输入符号a){
        令U为S中和a对应的所有位置p的followpos(p)的并集;
        if(U不在Dstates中)
            将U作为未标记的状态加入到Dstates中;
        Dtran[S,a] = U;
    }
}

在实际中DFA确实有优势,但是还有进一步优化的空间,接着往下看。

DFA状态最小化


在DFA中有一些状态是的等价的,等价的意思就是没有区别的,那有区别的意义在于:

从状态s、t出发,沿着标号为x的路径到达两个状态,分别为接受状态和非接受状态。

下面是一个简单的最小化的例子:

状态最小化

那么最小化呢?上面的这个定义太抽象了,如果直接找路径的话估计会累死的。想到每条路径都是由字符一点一点拼接成的,那么对应的方法也就比较容易能想到了:

  1. 将所有状态根据接受、非接受分成两组。
  2. 遍历分组、字符,如果同一组内的状态根据该字符到达了不同的组,那么继续将当前的组进行分割。
  3. 重复执行2,直到没有变化。
  4. 每个组中选择一个代表状态,重新构造DFA,最小化完成。

最后给出一个结论:任何一个正则表达式都有唯一的状态数最少的DFA

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,835评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,598评论 1 295
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,569评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,159评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,533评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,710评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,923评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,674评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,421评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,622评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,115评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,428评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,114评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,097评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,875评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,753评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,649评论 2 271

推荐阅读更多精彩内容

  • 转载说明 一、介绍 浏览器可以被认为是使用最广泛的软件,本文将介绍浏览器的工作原理,我们将看到,从你在地址栏输入g...
    17碎那年阅读 2,375评论 0 22
  • 标题: Rakudo and NQP Internals子标题: The guts tormented imple...
    焉知非鱼阅读 1,277评论 1 3
  • 【天天棒棒】20171106学习力七期践行D20 阅读《小猪唏哩呼噜》30分钟,听古诗20分钟。《小猪唏哩呼噜》已...
    gxl水月亮阅读 99评论 0 0
  • 神啊 我苟延残喘的做一个好人 信所望之事实底,未见之事确据。 因信得见恩典,欢喜盼望你的荣耀 我爱这众生如你爱我一...
    持镜者阅读 432评论 0 0
  • 今天我跟妈妈画了个娃娃。娃娃原来是白色的,我用颜料和画笔画好娃娃,我涂色,妈妈帮我修补了。我跟妈妈画的,...
    月康日健阅读 292评论 0 0