词法分析

  • Post author:
  • Post category:其他




词法分析



词法分析的任务

字符流到记号流

  • 字符流:

    • 和被编译的语言密切相关(ASCII, Unicode, or …)
  • 记号流:

    • 编译器内部定义的数据结构,编码所识别出的词法单元

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-A1TcpRn7-1629003388273)(https://i.bmp.ovh/imgs/2020/09/f35759edf271af84.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8ikLBZin-1629003388275)(https://i.bmp.ovh/imgs/2020/09/74017cb7ba8fcd04.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-La6WeT3f-1629003388276)(https://i.bmp.ovh/imgs/2020/09/3d619125d4e36e47.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Kfjyskor-1629003388279)(https://i.bmp.ovh/imgs/2020/09/a42e5578aa5e0db1.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-huDypTwN-1629003388281)(https://i.bmp.ovh/imgs/2020/09/9ec263bed47ae85b.png)]



记号的数据结构定义

enum kind {IF, LPAREN, ID, INTLIT, ...};
struct token {
    enum kind k;
    char *lexeme;
};



词法分析器–手工构造法

至少两种实现方案:

  • 手工编码实现法

    • 相对复杂、且容易出错
    • 但是目前非常流行的实现方法

      • GCC, LLVM, …
  • 词法分析器的生成器

    • 可快速原型、代码量较少
    • 但较难控制细节



转移图

图片

token nextToken()
    c = getChar();
    switch (c)
        case '<':
            c = getChar();
            switch (c)
                case '=': return LE;
                case '>': return NE;
                default: rollback();    return LT;
        case '=':   return EQ;
        case '>':
            c = getChar();
            switch (c): // similar



标识符的转移图

图片

token nextToken()
    c = getChar();
    switch (c)
        // continued from above cases...
        case 'a', ..., 'z', 'A', ..., 'Z', '_':
            c = getChar();
            while (c == 'a' || c == 'b' || ... || c == '_')
                c = getChar();



标识符和关键字

  • 很多语言中的标识符和关键字有交集

    • 从词法分析的角度看,关键字是标识符的一部分
  • 以C语言为例:

    • 标识符:以字母或下划线开头,后跟零个或多个字母、下划线、或数字
    • 关键字:if, while, else, …

图片



关键字表算法

  • 对给定语言中所有的关键字,构造关键字构成的哈希表H
  • 对所有的标识符和关键字,先统一按标识符的转移图进行识别
  • 识别完成后,进一步查表H看是否是关键字
  • 通过合理的构造哈希表H(

    完美哈希

    ),可以O(1)时间完成



正则表达式

图片

  • 对给定的字符集∑={c1, c2, …, cn}
  • 归纳定义:

    • 空串ε是正则表达式
    • 对于任意c∈∑,c是正则表达式
    • 如果M和N是正则表达式, 则以下也是正则表达式

      • 选择 M | N = {M, N}
      • 连接 MN = {mn | m∈M, n∈N}
      • 闭包 M* = {ε, M, MM, MMM, …}

图片



语法糖

  • 可以引入更多的语法糖,来简化构造

    • [c1-cn] == c1|c2|…|cn
    • e+ == 一个或多个e
    • e? == 零个或一个e
    • “a*” == a* 自身, 不是a的Kleen闭包
    • e{i, j} == i到j个e的连接
    • . == 除‘\n’外的任意字符



有限状态自动机

图片

图片

图片

  • 确定状态有限自动机DFA

    • 对任意的字符,最多有一个状态可以转移

      • δ: S x Σ → S
  • 非确定的有限状态自动机NFA

    • 对任意的字符,有多于一个状态可以转移

      • δ: S x (Σ∪ε) → 2^S (幂集)



正则表达式到非确定有限状态自动机

图片



Thompson 算法

  • 基于对RE的结构做归纳

    • 对基本的RE直接构造
    • 对复合的RE递归构造
  • 递归算法,容易实现

图片

图片



NFA 到 DFA

图片

图片



子集构造算法

/* 子集构造算法:工作表算法 */
q0 <- eps_closure(n0)
Q <- { q0 }
workList <- q0
while (workList != [])
  remove q from workList
  foreach (character c)
    t <- eps_closure(delta(q, c))
    D[q, c] <- t
    if (t \not\in Q)
      add t to Q and workList



对算法的讨论

  • 不动点算法

    • 算法为什么能够运行终止
  • 时间复杂度

    • 最坏情况 O(2N)
    • 但在实际中不常发生

      • 因为并不是每个子集都会出现



ε 的闭包运算



深度优先

/* 基于深度优先遍历的算法 */
set closure = {};

void eps_closure (x)
  closure += {x}
  foreach (y : x -- ε --> y)
    if(!visited(y))
      eps_closure (y)



广度优先

/* 基于广度优先的算法 */
set closure = {};
Q = []; // queue

void eps_closure(x)
  Q = [x];
  while (Q not empty)
    q <- deQueue (Q)
    closure += q
    foreach (y : q -- ε --> y)
      if (!visited(y))
        enQueue(Q, y)



DFA 的最小化

图片



Hopcroft 算法

// 基于等价类的思想
split(S)
  foreach (character c)
    if (c can split S)
      split S into T1, ..., Tk

hopcroft()
  split all nodes into N, A
  while (set is still changes)
    split(S)

图片

图片



算法的讨论

  • 不动点算法

    • 算法为什么能够运行终止
  • 时间复杂度

    • 最坏情况O(2N)?
    • 实际中运行可能会更快

      • 因为并不是每个子集都会分裂



DFA 的代码表示

  • 概念上讲, DFA是一个有向图
  • 实际上,有不同的DFA的代码表示

    • 转移表(类似于邻接矩阵)
    • 哈希表
    • 跳转表
  • 取决于在实际实现中,对时间空间的权衡



转移表

图片



驱动代码

nextToken()
  state = 0
  stack = []
  while (state != ERROR)
    c = getChar()
    if (state is ACCEPT)
      clear(stack)
    push(state)
    state = table[state][c]

  while (state is not ACCEPT)
    state = pop()
    rollback()

图片



跳转表

图片

nextToken()
  state = 0
  stack = []
  goto q0

q0:
  c = getChar()
  if (state is ACCEPT)
    clear(stack)
  push(state)
  if(c == 'a')
    goto q1

q1:
  c = getChar()
  if (state is ACCEPT)
    clear(stack)
  push(state)
  if (c == 'b' || c == 'c')
    goto q1



版权声明:本文为u011714517原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。