编译原理-学习记录9

ch4.(1)自顶向下语法分析(续)

LL(1)文法

  回顾first集和follow集的定义:

  first集:first( α i \alpha_i αi)={ a ∣ α i ⇒ ∗ a . . . ,   a ∈ V T a|\alpha_i\mathop\Rightarrow\limits^* a...,\ a\in V_T aαia..., aVT},即从 α i \alpha_i αi出发,能够推导出的排在首位的终结符的集合。特别地,如果 α i ⇒ ∗ ϵ \alpha_i\mathop\Rightarrow\limits^*\epsilon αiϵ,则令 ϵ ∈ \epsilon\in ϵfirst( α i \alpha_i αi)

  follow集(后随终结符号集合):follow( U U U)={ a ∣ S ⇒ ∗ . . . U a . . . ,   a ∈ V T a|S\mathop\Rightarrow\limits^*...Ua...,\ a\in V_T aS...Ua..., aVT},即可能在某些句型中紧跟在U右边的终结符号的集合。特别地,当 S ⇒ ∗ . . . U S\mathop\Rightarrow\limits^*...U S...U时,规定输入结束符$ ∈ \in follow( U U U)

  现对LL(1)文法进行定义:

  定义:对于文法中任一形如 U → α 1 ∣ α 2 ∣ … ∣ α n U\rightarrow \alpha_1|\alpha_2|\dots|\alpha_n Uα1α2αn的产生式,如果满足:
  1、 α 1 ,   α 2 ,   …   ,   α n \alpha_1,\ \alpha_2,\ \dots\ ,\ \alpha_n α1, α2,  , αn的first集(终结首符号集)两两不相交
  2、当存在 α j ⇒ ∗ ϵ \alpha_j\mathop\Rightarrow\limits^*\epsilon αjϵ时,文法还同时满足first( α i \alpha_i αi) ∩ \cap follow(U)= ∅ \empty (i和j可以相等)

  则该文法称作LL(1)文法

  定义中的第二条,主要是为了解决能够推导出空串的情形。在这种情形下,是需要看后面那个符号的情况的,因此引入了follow集的定义。

举例:判断文法是否为LL(1)文法

  现有一文法:
G [ S ] : S → e T ∣ R T R → d R ∣ ϵ T → D R ∣ ϵ D → a ∣ b d \begin{aligned}G[S]:&\\ S&\rightarrow eT|RT\\ R&\rightarrow dR|\epsilon\\ T&\rightarrow DR|\epsilon\\ D&\rightarrow a|bd\end{aligned} G[S]:SRTDeTRTdRϵDRϵabd

  因为判断是否为LL(1)文法,只需判断是否满足LL(1)的两个定义。对于定义1,先求出各产生式右部的first集(如果右部只有一种选择,则对于定义1而言,无需担心first集相交的问题):

  first(eT)={e}
  first(RT)={d, ϵ \epsilon ϵ, a, b}(对于第一个符号R,可以推导出的首字符为d,而第一个符号R取 ϵ \epsilon ϵ时,就看第二个符号能推导出哪些首字符,发现T可以推导出 ϵ \epsilon ϵ a a a b b b这三种首字符)
  first(dR)={d}(无论如何推导,首字符已经固定为d)
  first( ϵ \epsilon ϵ)={ ϵ \epsilon ϵ}
  first(DR)={a, b}
  first(a)={a}
  first(bd)={b}

  而对于定义2,需要求各产生式左部的follow集:

  对于follow(S),因为S是开始符号,在右部中也未出现S,所以S后的符号只有结束符号$,因此follow(S)={$}
  对于follow( R R R),R在第一个产生式中出现在右部RT中,因此follow( R R R)包含first(T)-{ ϵ \epsilon ϵ}。而R也出现在第二个产生式的右部dR中,因此follow( R R R)也包含follow( R R R)。同样地,出现在第三个产生式的右部DR中,说明follow( R R R)同样包含follow(T)。此外,由于T可以推导得到 ϵ \epsilon ϵ,所以对于第一条产生式的右部RT,可以说follow( R R R)也包含follow(S)。因此:follow( R R R)=(first(T)- ϵ \epsilon ϵ) ∪ \cup follow( R R R) ∪ \cup follow(T) ∪ \cup follow(S)=(first(T)-{ ϵ \epsilon ϵ}) ∪ \cup follow(T) ∪ \cup follow(S)
  对于follow(T),T分别出现在了第一个产生式的右部eT和RT中,由于右边没有其他符号了,所以follow(T)包含follow(S)。follow(T)=follow(S)={$}
  对于follow(D),D出现在了第三个产生式右部中的DR中,右边有R,并且R可以推导出 ϵ \epsilon ϵ。因此,follow(D)=follow(T) ∪ \cup (first( R R R)-{ ϵ \epsilon ϵ})

  随后求出follow( R R R)={a, b, $}(first(T)={a, b, ϵ \epsilon ϵ}),follow(D)={d, $}(first( R R R)={d, ϵ \epsilon ϵ})

  现按定义进行判断,发现均无交集,因此,文法G[S]是LL(1)文法。

非LL(1)文法的改造

  现在关注如何将非LL(1)文法改造为LL(1)文法。

  有左递归存在的,一定不是LL(1)语法。例如,有左递归产生式 A → A a ∣ b A\rightarrow Aa|b AAab,则first(Aa)={b},first(b)={b},显然两个first集相交。

  因此,对非LL(1)文法进行改造,就应该首先消除左递归

  还有一个重要的操作是提取左公因子:将 A → α β ∣ α γ A\rightarrow\alpha\beta|\alpha\gamma Aαβαγ变换为:

A → α B B → β ∣ γ \begin{aligned}A&\rightarrow\alpha B\\ B&\rightarrow\beta|\gamma\end{aligned} ABαBβγ

预测分析器的构造

递归子程序方法

  此方法是对真实推导过程的直接模拟,因此编写程序会较为容易,早期的编译器也基本用的都是递归子程序方法。

  基本思想:对每一个语法成分(非终结符号),构造相应的分析子程序。
  因为语法成分之间含有递归,所以分析子程序之间也会有递归调用,所以这个方法也叫做递归子程序方法。

  构造分析子程序

  对于产生式 U → x 1 ∣ x 2 ∣ . . . ∣ x n U\rightarrow x_1|x_2|...|x_n Ux1x2...xn,可以构造如下形式的子程序p(U):

IF ch IN First(x1) THEN p(x1)
ELSE IF ch IN First(x2) THEN p(x2)
    ELSE …
        …
            IF ch IN First(xn) THEN p(xn)
            ELSE IF not(ch IN Follow(U)) THEN Error

  对于产生式 x → y 1 y 2 . . . y m x\rightarrow y_1y_2...y_m xy1y2...ym,可以构造如下形式的子程序p(x):

begin p(y1); p(y2); ...; p(ym) end

  而对于产生式 x → { y } x\rightarrow \{y\} x{y},在程序中则是一个循环

  约定

  每进入一个分析子程序前,已读到该子程序相应的非终结符号推导出的第一个终结符号/终结首符号。例如,有产生式 C → e ∣ d C C\rightarrow e|dC CedC,则在进入子程序p( C C C)前,先读取一个字符,随后进入到子程序中,判断符号是否为’e’或’d’。如果不是,则直接报错;否则,再读取一个字符,进入到下一个子程序p( C C C)中。

  缺点:过于依赖分析程序,需要频繁改动分析程序的源程序。因此,新版的编译器就没有多少使用这个方法的了。

LL(1)分析法

  基本思想
  只需向输入符号串中查看一个输入符号,便能唯一确定当前选择的表达式,这便是LL(1)分析法的命名来源。
  如果写出分析过程的每一步,就会发现实质上是在不断地替换符号表达式的最左非终结符号,这个过程和栈这个数据结构的出栈、入栈操作非常地相似。因此,可以使用符号栈S来存储这些符号。

  分析表

  前面也提到过,递归方法主要问题就在于过于依赖分析程序。而此处的LL(1)分析法则是通过分析表M的使用,来避免这样的依赖。

  定义一个分析表M,分析表的元素M[U,a]为一条关于该非终结符号U的产生式,指出当该非终结符号U面临输入符号a时应选择的产生式,分析表的元素也可能是一个出错标志,指出非终结符号U不能面临终结符号a。

  例如,有文法:

G [ E ] : E → T E ′ E ′ → + T E ′ ∣ ϵ T → F T ′ T ′ → ∗ F T ′ ∣ ϵ F → ( E ) ∣ i \begin{aligned}G[E]&:\\ &E\rightarrow TE'\\ &E'\rightarrow +TE'|\epsilon\\ &T\rightarrow FT'\\ &T'\rightarrow *FT'|\epsilon\\ &F\rightarrow (E)|i\end{aligned} G[E]:ETEE+TEϵTFTTFTϵF(E)i

  则根据前述的first和follow集特性,构造出相对应的分析表M(如果a ∈ \in first(U),则说明可以选用左部为U的这条产生式来推导得到a首字符;如果U可以推导得出 ϵ \epsilon ϵ,而a ∈ \in follow(U),则也说明可以选用左部为U的这条产生式来推导得到a首字符):

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Qpi6jLxd-1604736910975)(C:\Users\蔡三圈\AppData\Roaming\Typora\typora-user-images\image-20201107160321829.png)]

分析表

  现在,如果在分析的过程中,符号栈S的栈顶元素为E,而输入符号串的当前符号为i。当前任务为试图从E推导得到首字符为i的语法,因此进行查表,发现对应的元素为产生式 E → T E ′ E\rightarrow TE' ETE,因此选用这条产生式,并将栈顶元素E换为TE’并逆序入栈(保证T在栈顶),同时读取符号串的下一个输入字符。

  LL(1)分析算法的思路大致如下:

BEGIN
    ‘$’、开始符号依次入栈; 读第一个输入符号到b; FLAG:=TRUE;
    WHILE FLAG DO //尚未结束
        BEGIN
            IF 栈顶符号X是终结符号
            THEN IF X=b //匹配
                THEN 把下一个输入符号读进b //处理下个符号
                ELSE ERROR //出错
            ELSE IF X =‘$’ //结束标志
                THEN IF X=b THEN FLAG:=FALSE //结束
                	ELSE ERROR //出错
                ELSE IF M[X,b]=“ X→X1X2..XK” //查分析表
                    THEN 把XK,X K-1,..,X1依次入栈//推导
                    ELSE ERROR //出错
    END OF WHILE;
END
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值