zoukankan      html  css  js  c++  java
  • 语法分析

    语法分析的作用是处理词法分析得到的记号流建立语法树(又称分析树), 并且建立符号表处理语法错误。

    本文约定大写英文字母A、B、C等表示非终结符;小写英文字母a、b、c等表示终结符;小写希腊字母α、β、δ等表示任意记号序列

    上下文无关文法

    上下文无关文法(Context Free Grammar,CFG)可以表示大多数程序设计语言的语法,又足够简单让我们实现相应的分析器。

    文法由四元组定义:

    • 非终结符(Nonterminals)集合

    • 终结符(Terminals)集合

    • 开始符号(Start Symbol)

    • 产生式(productions)

    所谓产生式是替换规则, 在进行推导时产生式左侧的记号序列可以由右侧记号替换。如产生式E=>E+E中表示表达式可以表示为两个表达式相加。

    产生式的推导具有自反性和传递性。若产生式右侧为空串则称为空产生式。

    所谓上下文无关文法是指所有产生式左侧仅有一个非终结符, 上下文有关文法中存在形如aAb=>α的产生式。

    上下文无关文法允许我们任意情况下将左侧记号替换为右侧序列,让我们更容易构建分析器。BNF(巴克斯-诺尔范式)经常用来表达上下文无关文法。

    定义算术表达式中的产生式:

    • E=>(E)

    • E => E+E

    • E => -E

    展示一下算术表达式-(a+b)的推导过程:

     E => -E => -(E) => -(E+E) => -(E+b) => -(a+b)
    

    或者以分析树的形式表示:

    自底向上看分析树我们对原表达式采用了从右到左的分析顺序,这种顺序称为最右推导或正规推导。对应地, 有最左推导的定义。

    我们尝试对算术表达式a*b+c按照不同优先级不同推导方式不同分别建立语法树:

    • 最左推导乘法优先: E => E+E => E*E+E => a*E+E => a*b+E => a*b+c

    • 最左推导加法优先:E => E*E => E*(E+E) => a*(E+E) => a*(b+E) => a*(b+c)

    • 最右推导乘法优先:E => E+E => E*E+E => E*E+c => E*b+C => a*b+c

    • 最右推导加法优先:E => E*E => E*(E+E) => E*(E+c) => E*(b+c) => a*(b+c)

    可以看出推导的结果仅与文法和句子有关与推导方法无关。自底向上看语法树, 高优先级的生成式会被先处理。

    若某一文法对一个句子有多棵分析树则称文法是二义的。避免二义性需要规定产生式的优先级和结合性或者修改文法。

    作为表达能力更强的上下文无关文法一定可以表示正规式, 但是正规式不一定可以表示上下文无关文法。

    将正规式转换为上下文无关文法的流程如下:

    • 构造正规式的NFA;

    • 若0为初态,则A0为开始符号;

    • 对于move(i,a)=j,引入产生式Ai => aAj;

    • 对于move(i,ε)=j,引入产生式 A i=> Aj;

    • 若i是终态,则引入产生式Ai => ε。

    示例, 从正规式r=(a|b)*abb的NFA构造CFG:

    A0 => aA0|bA0|aA1
    A1 => bA2
    A2 => bA3
    A3 => ε 
    

    自顶向下语法分析

    语法分析是对输入序列进行最左推导,尝试建立它的语法树,最终得到一条合法句子或发现错误的过程。

    自上而下分析是一个试探回溯的过程, 尝试一切可能建立与输入序列匹配的语法树。

    消除左递归

    若存在形如A=>Aα|β的生成式时我们称文法存在直接左递归, 直接左递归会使最左推导语法分析陷入死循环中必须加以消除。

    A=>Aα|β中A不是β的前缀(即β不以A开头)则可以将产生式用两个产生式替换:

    A  => βA'
    A' => αA' | ε
    

    这样原来的左递归被替换为右递归, 而右递归不会导致最左推导陷入死循环。

    文法E => E+T | T 存在直接左递归, 根据代换规则替换为两个产生式:

    E => TE'
    E'=> +TE' | ε
    

    用原文法分析a+b: E => E+T => a+T => a+b, 当然我们根据经验避免了左递归。

    使用消除左递归后的文法分析a+b: E => TE' => T+TE' => T+T => a+T => a+b

    有些文法不含直接左递归,但通过推导可以得到左递归产生式:

    1. S => Qc | c
    2. Q => Rb | b
    3. R => Sa | a

    经过推导可以得到左递归产生式: S => Qc => Rbc => Sabc

    消除间接左递归的基本思想是将间接左递归转换为直接左递归, 再将直接左递归转换为右递归。

    首先产生式将产生式重新排列为可以迭代的顺序: A[i] => A[i+1]α | β; A[i+1] => A[i+2]α | β; ...。上文中的示例已经满足该条件不必调整。

    将3.代入2.中得到4.: Q => (Sa | a)b | b => Sab | ab | b

    将4.代入1.中得到5.: S => (Sab | ab | b)c | c => Sabc | abc | bc | c

    消除5.中的直接左递归得到6.: S => abcS’| bcS'| cS'; S' => abcS' | ε

    从开始符号S出发不会经过QR, 可以将1,2,3, 4删去仅保留5,6作为新文法。

    递归下降分析

    递归下降分析法是指为记号流中特定非终结符编写一个子程序, 当发现该终结符时即调用该子程序进行分析。

    我们规定*优先从左向右处理记号流, 尝试分析1*1+1*1

    自左向右扫描记号流, 发现相应运算符即交由子程序处理。注意因为*优先,则+更靠近分析树根部, 所以应优先调用+的子程序, 其次为*子程序, 最后对的1处理完成后返回。

    调用栈即代表语法树:

    ![](http://images2015.cnblogs.com/blog/793413/201611/793413-20161129172840427-2027606437.png)
    

    LL文法分析

    LL分析器是一种处理上下文无关文法的自顶向下的语法分析器, 它从左到右处理输入,再对句型执行最左推导出语法树故称为LL分析器。

    LL分析器是一种预测分析器, 与普通递归下降分析器不同,预测分析器会向后探查来决定当前语法树结构从而避免回溯。

    若一个LL分析器向前探查k个记号(token), 那我们称该分析器为LL(k)分析器, 其中最常用是设计简单且可以处理大多数情况的LL(1)分析器。

    给定上下文无关文法:

    (1) S => E
    (2) S => (S + E)
    (3) E => i
    

    试图对((i+i)+i)进行最左推导:

    S => (S+E) => ((S+E)+E) => ((E+E)+E) => ((i+E)+E) => ((i+i)+E) => ((i+i)+i)
    

    从状态2(S+E)推导状态3时我们有两个选择S=>(S+E)或者S=>E。在示例中选择任何一种推导都无关紧要, 但在某些情况下做出错误选择后不得不进行回溯。

    LL分析法可以通过探查后面的记号来做出决定, 理论上说对任何上下文无关语言总存在k,使得LL(k)无法识别的记号流可以被LL(k+1)识别。

    LL分析器依赖分析表来进行决策, 分析表根据后面的记号和当前状态决定下一步状态转移:

    - + ( ) i
    S - 2 - 1
    E - - - 3


    LL分析器维护一个记号栈, 用于保存待处理的记号. LL分析器首先在栈中压入语法树的根元素, 然后根据记号流中的下一个记号对栈顶元素进行推导, 当栈顶元素无需继续处理时弹出栈顶。当栈为空时语法分析结束。

    我们尝试使用LL(1)分析器对(i+i)进行分析:

    1. 记号栈首先压入语法树的根元素S, 记号栈为:[S]

    2. 从记号流读入(,读取栈顶S, 根据分析表应用规则(2)将S改写为(S+E), 记号栈为: [(,S,+,E,)]

    3. 从记号流读入i, 栈顶(无需推导弹出, 处理新栈顶S: 根据分析表应用规则(1)将S改写为E,记号栈为:
      [E,+,E,)]

    4. 处理栈顶E, 应用规则(3)将记号栈改写为: [i, +, E, ) ]

    5. 根据读入的i+依次丢弃栈顶i+

    6. 根据栈顶E和读入的i, 改写记号栈: [i,)]

    7. 读入), 弹出栈顶分析完毕。

    自底向上语法分析

    实际应用中语法分析器更多地从记号流开始建立分析树, 而非从根元素开始建立语法树。加上自底向上分析法更强大, 大多数实用编译器均采用自底向上分析法。

    LR分析器是典型的自底向上分析法, 它从左向右处理记号流并尝试进行最右推导。

    给定上下文无关文法:

    (1) S => E
    (2) S => S + E
    (3) E => i
    

    LR分析器可以执行几种特定动作:

    • r: reduce应用语法规则化简

    • p: push将下一个记号入栈

    • a: accept分析完成, 记号流被文法接受

    • e: error发现语法错误

    若记号流分析完毕没有发现错误,则认为记号流被文法接受。

    LR分析器同样需要一个记号栈和一个分析表。分析表根据当前状态和下一个记号决定动作, 为了便于描述, 我们根据记号栈来描述状态:

    - i +
    [] r(3),r(1) p
    [ S ] e p
    [S, +] r(3) e
    [S, +, E] r(2) e

    尝试对i+i进行LR分析

    1. 读入i压栈,依次逆用(3), (1)此时记号栈为: [S]

    2. 读入+压栈, 此时记号栈为: [S, +]

    3. 读入i压栈, 逆用(3)此时记号栈为: [S,+, E]

    4. 逆用(2), 此时记号栈为[S]

    至此分析结束, 得到语法树:

    S => S + E => S + i => E + i => i + i
  • 相关阅读:
    win10+CUDA8.0+vs2013配置
    TX2更新源失败的问题
    并发编程--乐观锁与悲观锁
    并发编程--线程池
    并发编程--多线程基础(02)
    并发编程--多线程基础(01)
    搭建redis集群的过程中遇到的问题
    redis集群搭建(伪集群)
    关于maven项目中修改的JS不生效的解决方案
    【转载】IntelliJ IDEA 2017常用快捷键
  • 原文地址:https://www.cnblogs.com/Finley/p/6114519.html
Copyright © 2011-2022 走看看