• 语法分析:LL(1)语法分析的实现及扩展的巴科斯范式


    终于港到实现。。


    LL(1)文法与LL(1)分析

    当一个文法满足以下条件时,称这个语法为LL(1)语法,LL(1)语法可以通过LL(1)的分析方法进行分析

    • 文法不含有左递归
    • 同一非终结符的FIRST集合两两不相交
    • 非终结符的FIRST集若包含 ε,则不能与FOLLOW集相交

    LL(1)分析:

    • 若当前待匹配符号属于当前非终结符的某个产生式的候选首符集中,则将该非终结符按此产生式推导
    • 若当前待匹配符号 a 不属于当前非终结符 A 的任何一个候选首符集,
      • 若存在产生式 A → ε 且 a 属于FOLLOW(A),则将 A 推导为 ε;
      • 否则 a 在此处为语法错误

    LL(1)语法分析是一种自上而下分析的方法,从文法开始符号出发匹配输入符号,语法树自上而下生成
    第一个L指从左向右扫描输入串,第二个L指最左推导,(1)指每次向前看1个输入符号进行语法分析


    LL(1)语法分析的实现

    递归下降分析程序

    顾名思义,采用递归的方式向下构建语法树
    每个非终结符的推导都作为一个函数过程

    例,有LL(1)文法G(E)如下,
    E → T EE
    EE → + T EE | ε
    T → F TT
    TT → * F TT | ε
    F → (E) | i

    void parser(){
    	token = lexer();//词法分析获取下一token
    	E();
    }
    
    void E(){//用E匹配当前token   E → T EE
    	T();
    	EE();
    }
    
    void T(){//用T匹配当前token   T → F TT
    	F();
    	TT();
    }
    
    void EE(){//EE → + T EE | ε
    	if(token == '+'){
    		token = lexer();//当前token ‘+’ 得到匹配,继续分析
    		T();
    		EE();
    	}
    	//无操作即可匹配 ε
    	
    	//也可以显式匹配 ε
    	//else if(FOLLOW(EE).contains(token))return;
    	//else printfError();
    	
    	//或者else if(!FOLLOW(EE).contains(token)){ printfError(); }
    }
    
    void F(){//F → (E) | i
    	if(token == '('){
    		token = lexer();
    		E();
    		if(token == ')'){ token = lexer(); }
    		else printfError();
    	}
    	else if(token == 'i'){ token = lexer(); }
    	else printError();
    }
    
    void TT(){//TT → * F TT | ε
    	if(token == '*'){
    		token = lexer();
    		F();
    		TT();
    	}
    	//无操作即将TT推导为 ε,是否可以推导为 ε 交给在后面的语法分析中验证
    }
    

    递归下降由此得名


    预测分析程序

    有一个问题,如果我们用于编写parser的语言不支持递归,该如何实现LL(1)的语法分析呢

    递归程序一般可改写为栈+数据表的形式

    下面是预测分析程序的构成

    • 控制程序。最早的压栈处理、根据现行栈顶符号和当前输入符号执行动作
    • 分析表M[A,a]矩阵,A ∈ VN,a ∈ VT 或 a == #(假设#是输入串结束标志),当要用非终结符 A,
      匹配输入符号 a 时,查表M[A,a],根据M[A,a]的情况,进行响应的操作
    • 分析栈,存放当前推导过程中的文法符号,用于当前匹配的符号均在栈顶

    设栈顶元素为X,当前输入符号为a,分析过程如下:

    • 假设输入串结束标志为 #,开始时在栈中压入 # 与文法开始符号 S
    • 若 X == a == #,则分析结束且成功
    • 若 X == a ≠ #,则 X 与 a 匹配,X 出栈,将下一输入符号保存至 a
    • 若 X 是一个非终结符,则查表M[A,a]
      • 若M[A,a]为一个产生式,则将 X 出栈,将产生式的右部反序入栈(若产生式右部为 ε 则不用入栈)
      • 若M[A,a]中为出错标志,则调用出错诊察程序

    与词法分析的表驱动法类似,预测分析程序也是使用 查表 + 流程控制 实现

    LL(1)文法保证了语法分析的每一步都是确定的,因此我们就可以“预测”文法符号的推导方向
    这大概是预测分析程序的“预测分析”由来

    void parser(){
    	int unfinished = 1;
    	Stack stack = new Stack();
    	stack.push("#");
    	stack.push("S");//文法开始符号S
    	while(unfinished){
    		a = lexer();//获得待匹配输入符号
    		if(X ∈ 终结符){
    			if(X == a){ stack.pop(); }
    			else printError();
    		}
    		else {
    			if(X == "#"){ 
    				if(a == "#"){ unfinished = 0; }
    				else printError();
    			}
    			else if(M[X,a] == {X → X1X2X3…Xn}){
    				if(X1 == ε)continue;
    				stack.push(Xn);
    				......
    				stack.push(X3);
    				stack.push(X2);
    				stack.push(X1);
    			}else printError();
    		}
    	}
    }
    

    那么这个分析表是什么亚子呢?

    预测分析表的构造
    以文法G(E)为例
    E → TE/
    E/ → + TE/ | ε
    T → FT/
    T/ → * FT/ | ε
    F → (E) | i

    i + * ( ) #
    E E → TE/ E → TE/
    E/ E/ → + TE/ E/ → ε E/ → ε
    T T → FT/ T → FT/
    T/ T/ → ε T/ → * FT/ T/ → ε T/ → ε
    F F → i F → (E)

    相信很容易就可以发现规律,如果输入符号 a 包含在非终结符 A 的某个FIRST集中,
    那么M[A,a] = 该FIRST集对应的产生式
    如果 A 没有任何一个FIRST集 包含 a,
    - 如果存在产生式 A → ε,且 a ∈ FOLLOW(A),则M[A,a] = A → ε
    - 否则,a 不能够被分析表接受,出现语法错误,M[A,a] = 出错标志


    预测分析程序的匹配过程
    以输入串 i1 * i2 + i3 为例,预测分析程序的匹配过程如下
    在这里插入图片描述


    扩展的巴科斯范式

    在元符号 → 或 ::= 和 | 的基础上,扩充几个元语言符号:

    • 用花括号{α}表示闭包运算α*
    • 用 {α}n0 表示可以任意重复[0,n]次
    • 用方括号 [α] 表示{α}10,即 α | ε,α 可有可无,或者说是可选的

    扩充的巴克斯范式,语义更强,便于表示左递归的消去和因子的提取
    如我们一直用来举例的文法
    E → T | E + T
    T → F| T*F
    F → i | (E)
    用扩展的巴科斯范式可以表示成
    E → T { +T }
    T → F { *F }
    F → i | (E)

    然后有什么用呢?

    可以画出下面的语法图奥
    在这里插入图片描述
    很清楚了吧,懂我的意思吧

    void parser(){
    	token = lexer();//词法分析得到下一单词符号
    	E();
    }
    
    void E(){
    	T();
    	while(token == "+"){
    		token = lexer();
    		T();
    	}
    }
    
    void T(){
    	F();
    	while(token == "*"){
    		token = lexer();
    		F();
    	}
    }
    
    void F(){
    	if(token == "i"){
    		token = lexer();
    	}
    	else if(token == "("){
    		token = lexer();
    		E();
    		if(token == ")"){ token = lexer(); }
    		else printError();
    	}
    	else printError();
    }
    

    扩展的巴科斯范式引入了循环,使得语法描述中的重复可以直观地使用 循环 来描述,进而使用循环实现,而不一定是递归
    显然,扩展的巴克斯范式给出的递归下降分析程序更加直观,没有更多引入的非终结符

    斯巴拉西


    LL(1)文法与二义性

    奈斯,我们现在晓得如何构造分析表、实现预测分析程序了
    然而某些语法,消除左递归、提取左公共因子后仍然不满足LL(1)文法的条件,因为它有二义性

    比如描述选择结构的文法
    S → if Condition S | if Condition S else S | statement
    Condition → bool

    提取左公因式后
    S → if Condition SS/ | statement
    S/ → else S | ε
    Condition → bool

    斯巴拉西
    看哈这个文法的FIRST集和FOLLOW集

    FIRST(S) → { { if },{ statement } }   FOLLOW(S) → { #,else }
    FIRST(S/) → { else,ε }       FOLLOW(S/) → { #,else }
    FIRST(Condition) → { bool }     FOLLOW(Condition) → { if,statement }

    明显可以看到,FIRST(S/)∩FOLLOW(S/) = { else },不符合LL(1)文法的条件
    因为在用非终结符 S/ 匹配输入符号 else 时,可以选择把 S/ 推导为 else S,进一步即使 else 得到匹配,
    也可以选择把 S/ 推导为 ε,让后面的符号来匹配这个 else,
    也就是说,在非终结符 S/ 匹配输入符号 else 时,对应着两种不同的语法树,会产生歧义

    这个二义性文法的分析表如下,可以看到分析表中有多重定义入口(一个格子里有多个产生式)

    if bool else statement #
    S S → if Condition SS/ S → statement
    S/ S/ → else S
    S/ → ε
    S/ → ε
    Condition Condition → bool

    上面这个问题是编程语言普遍存在的问题,它是 else 与 if 的结合次序的问题

    if()
    if(){}
    else{}
    

    那么下面的 else 是与第一个 if 匹配,还是与第二个 if 匹配呢
    在这里插入图片描述
    即在上图情况中,token⑥是由内层S/匹配与 if③ 结合,还是将内层S/推导为 ε 使⑥与外层的 if① 结合?

    也就是说,
    如果使用产生式 S/ → else S,那么 else 将与最近的未匹配 if 匹配
    如果使用产生式 S/ → ε,那么 else 将与最远的未匹配 if 匹配

    上面的例子表明,存在多重入口的分析表会产生不同的语法树,对应着不同的语义(二义性)

    如果语言规范指明了,else 会与最近/远的 if 匹配,那么就可以通过人为方式解决冲突(删除不符合语义的产生式)

    实际上大部分程序设计语言都是这么做的
    一般的,程序设计语言都选择就近匹配,即只保留产生式 S/ → else S

    if bool else statement #
    S S → if Condition SS/ S → statement
    S/ S/ → else S S/ → ε
    Condition Condition → bool

    如下图
    在这里插入图片描述

    onemore thing,

    G为LL(1)文法 ⇔ G的预测分析表不含有多重定义入口
    LL(1)文法不是二义的
    证明略


    所以说,即使语言是二义的,文法是二义的,构造出来的分析表也有冲突,也可以根据合理的语言规范消除冲突


    2019/8/13

  • 相关阅读:
    GSM和GPRS的区别
    IP规划和VLSM子网划分例题
    20190806-sed面试题
    yum.rpm一点点
    实验:基于http的yum源
    vim编辑二进制文件
    关于find的-perm
    误删tree命令如何恢复
    删除Linux的依赖库并进入救援模式恢复
    第六天、用户、组、权限、grep
  • 原文地址:https://www.cnblogs.com/kafm/p/12721798.html
Copyright © 2020-2023  润新知