编译原理(二)——语法分析(一)

本文围绕语法分析展开,介绍了上下文无关文法(CFG)的基本定义、推导、语法分析树等内容,对比了CFG与正则表达式的表达能力。还阐述了设计文法时消除二义性、左递归和提取左公因子的方法,以及自顶向下语法分析,包括FIRST和FOLLOW函数、LL(1)文法、预测分析表和非递归预测分析。

版权声明:本文为原创文章,未经博主允许不得用于商业用途。

语法分析

上下文无关文法(CFG)

1.1 基本定义

CFG包含如下四个组成部分:

  • 终结符号:组成串的基本符号(词法单元名,id,运算符)
  • 非终结符号:表示串的集合的语法符号(如expr,stmt)
  • 开始符号:某个被指定的非终结符(如expr)
  • 产生式:定义了使用非终结符和终结符狗构造串的方法。
    • 形式:头(左)部→\rightarrow体(右)部
    • 头部是一个非终结符,右部是一个符号串
1.2 例子(c语言产生式)

在这里插入图片描述

其中expressionexpressionexpression表示表达式,termtermterm表示术语(保留字,关键字)。

经过简化,此文法可以表示成:,其中’|'为文法描述符,不是文法符号。

1.3 推导

推导可以看作是对产生式的解读,如果产生式A→γA\rightarrow \gammaAγ,则αAβ⇒αγβ\alpha A\beta\Rightarrow \alpha \gamma\betaαAβαγβ表示“通过一步推导出”。因此推导可以看作是按照产生式规则替换。同理,定义⇒∗\overset{*}{\Rightarrow}表示经过零步或多步推导出。

在进行推导时,如果每次都解析最左(右)边的非终结符号,则称为最左(右)推导

  • 句型:一个文法可以推导出的所有串的集合
  • 句子:不包含非终结符号的句型
  • 语言:所有句子的集合
1.4 语法分析树

推导过程可以使用树状结构表示,其中:

  • 根结点的标号是文法的开始符号
  • 每个叶子结点的标号是非终结符号、终结符号或ε
  • 每个内部结点的标号是非终结符号
  • 每个内部结点表示某个产生式的一次应用
    • 结点的标号为产生式头,其子结点从左到右是产生式的体

一棵语法分析树可以对应多个推导序列,不过只具有一种最左(右)推导(即前序遍历和后序遍历唯一)。

1.5 上下文无关文法和正则表达式

上下文无关文法比正则表达式的表达能力更强:即CFG可以对两个个体的计数(最多两个个体)而RE(NFA)不能。

例如{anbn∣n≥1}\{a^nb^n|n\geq 1\}{anbnn1}(括号匹配)
  • CFG:S→aSb∣abS\rightarrow aSb|abSaSbab
  • 正则表达式:反证法,若可以表示,若存在一个具有2n个状态的NFA可以表示此语言,则对于an+1bn+1a^{n+1}b^{n+1}an+1bn+1,必有一个状态s0s_0s0有一条收到a时指向之前k个状态的边,因此其可以接受an+kbn+1a^{n+k}b^{n+1}an+kbn+1,不在此语言中。矛盾。

设计文法

消除二义性

对于一些语法,同一个句型可以对应多个语法分析树,如典型的if-else-then语法:

文法1:

stmt→if expr then stmt ∣ if expr then stmt else stmt ∣ other stmt\rightarrow \bold{if}\ expr\ \bold{then}\ stmt\ |\ \bold{if}\ expr\ \bold{then}\ stmt\ \bold{else}\ stmt\ |\ other stmtif expr then stmt  if expr then stmt else stmt  other

对于if E1 then if E2 then S1 else S2if\ E_1\ then\ if\ E_2\ then\ S_1\ else\ S_2if E1 then if E2 then S1 else S2有两种解读,即S2S_2S2对应的层次可以是外层if语句,也可以是内层if语句。

可以通过规定else和最近的未匹配then匹配消除二义性,体现在文法层面如下:

文法2:

stmt→matched_stmt ∣ open_stmt stmt\rightarrow matched\_stmt\ |\ open\_stmt stmtmatched_stmt  open_stmt

match_stmt→if expr then matched_stmt else matched_stmt ∣ other match\_stmt\rightarrow \bold{if}\ expr\ \bold{then}\ matched\_stmt\ \bold{else}\ matched\_stmt\ |\ other match_stmtif expr then matched_stmt else matched_stmt  other

open_stmt→if expr then stmt ∣ if expr then matched_stmt else open_stmt open\_stmt\rightarrow \bold{if}\ expr\ \bold{then}\ stmt\ |\ \bold{if}\ expr\ \bold{then}\ matched\_stmt\ \bold{else}\ open\_stmt open_stmtif expr then stmt  if expr then matched_stmt else open_stmt

消除左递归
  • 左递归:文法中存在非终结符A使得A⇒+AαA\overset{+}{\Rightarrow}A\alphaA+Aα
  • 立即左递归:文法中存在非终结符A使得A→AαA\rightarrow A\alphaAAα

自顶向下的语法分析无法处理左递归,因为在DFS时可以无限替换下去。

可以通过替换法消除左递归:

  • 立即左递归变为立即右递归:
    • 输入:A→Aα∣βA\rightarrow A\alpha|\betaAAαβ
    • 转化为:A→βA′A\rightarrow \beta A'AβAA′→αA′∣ϵA'\rightarrow \alpha A'|\epsilonAαAϵ
  • 多步左递归:
    • 算法思路:按照顺序替换产生式,直到立即左递归出现后,消除立即左递归。(相当于求了产生关系的闭包)
    • 在这里插入图片描述
提取左公因子

在CFG分析句型时,每次都通过查看下一个符号选择产生式,因此如果两个产生式具有相同的前缀时,则无法判断应该选择哪一个。

因此需要提取产生是的左公因子(相同前缀),并且将公因子转化为新的产生式消除相同前缀。

  • 原语法:A→αβ1∣αβ2A\rightarrow \alpha \beta_1|\alpha\beta_2Aαβ1αβ2
  • 提取左公因子后:A→αA′,A′→β1∣β2A\rightarrow \alpha A',A'\rightarrow \beta_1|\beta_2AαA,Aβ1β2

自顶向下的语法分析

自顶向下语法分析按照先根次序深度优先的创建节点(对应最左推导)建立语法分析树,一次读入一个字符,知道完成整个输入串的解析。

在这里插入图片描述

此算法类似贪心算法,显然当遇到一个错误时,不一定是语法错误,也可能是之前的产生式选择错误,因此可以通过回溯改良算法。

FIRST和FOLLOW

通常在选择产生式时使用FIRST和FOLLOW两个函数。

  • FIRST(α)FIRST(\alpha)FIRST(α)定义为可以从α\alphaα推导出的首符号的集合。
    • α\alphaα为终结符,则加入α\alphaα
    • α\alphaα为非终结符,且α→β1β2...βk\alpha \rightarrow \beta_1\beta_2...\beta_kαβ1β2...βk
      • θ∈FIRST(βi),ϵ∈FIRST(β1),FIRST(β2),...,FIRST(βi−1)\theta\in FIRST(\beta_i),\epsilon\in FIRST(\beta_1),FIRST(\beta_2),...,FIRST(\beta_{i-1})θFIRST(βi),ϵFIRST(β1),FIRST(β2),...,FIRST(βi1),加入θ\thetaθ
      • ϵ∈FIRST(β1),FIRST(β2),...,FIRST(βk)\epsilon\in FIRST(\beta_1),FIRST(\beta_2),...,FIRST(\beta_{k})ϵFIRST(β1),FIRST(β2),...,FIRST(βk),加入ϵ\epsilonϵ
    • α\alphaα为非终结符,且α→ϵ\alpha \rightarrow \epsilonαϵ,加入ϵ\epsilonϵ
  • FIRST(α1α2...αn)FIRST(\alpha_1\alpha_2...\alpha_n)FIRST(α1α2...αn)计算如下:
    • 加入FIRST(α1)FIRST(\alpha_1)FIRST(α1)中所有非ϵ\epsilonϵ符号
    • ϵ∉FIRST(αi),ϵ∈FIRST(α1),FIRST(α2),...,FIRST(αi−1)\epsilon\notin FIRST(\alpha_i),\epsilon\in FIRST(\alpha_1),FIRST(\alpha_2),...,FIRST(\alpha_{i-1})ϵ/FIRST(αi),ϵFIRST(α1),FIRST(α2),...,FIRST(αi1),加入FIRST(βi)FIRST(\beta_i)FIRST(βi)
    • ϵ∈FIRST(α1),FIRST(α2),...,FIRST(αn)\epsilon\in FIRST(\alpha_1),FIRST(\alpha_2),...,FIRST(\alpha_{n})ϵFIRST(α1),FIRST(α2),...,FIRST(αn),加入ϵ\epsilonϵ
  • FOLLOW(α)FOLLOW(\alpha)FOLLOW(α)定义了可以跟随在α\alphaα右边的终结符号集合
    • 例如:S→aαbβS\rightarrow a\alpha b\betaSaαbβ,则终结符b在FOLLOW(α)FOLLOW(\alpha)FOLLOW(α)
    • 计算:将右端结束标记$加入FOLLOW(B)中,保证非空
      • 存在S→αBβS\rightarrow \alpha B\betaSαBβ,则将FIRST(β)FIRST(\beta)FIRST(β)中的非ϵ\epsilonϵ符号加入
      • 存在A→αB or A→αBβ,ϵ∈FIRST(β)A\rightarrow \alpha B\ or\ A\rightarrow \alpha B\beta,\epsilon\in FIRST(\beta)AαB or AαBβ,ϵFIRST(β),则将FOLLOW(A)FOLLOW(A)FOLLOW(A)中所有符号加入
LL(1)文法

LL(1)文法中的LL和1分别代表从左向右扫描输入字符、最左推导、每一步只需向前看一个输入字符。

对于任意一个产生式A→α∣βA\rightarrow \alpha |\betaAαβ,满足如下两个条件:

  • FIRST(α)∩FIRST(β)=ΦFIRST(\alpha)\cap FIRST(\beta)=\PhiFIRST(α)FIRST(β)=Φ
  • ϵ∈FIRST(β),FIRST(α)∩FOLLOW(A)=Φ\epsilon\in FIRST(\beta),FIRST(\alpha)\cap FOLLOW(A)=\PhiϵFIRST(β),FIRST(α)FOLLOW(A)=Φ,反之亦然

对于满足上述条件的LL(1)文法,可以构造出不需要回溯的递归下降语法分析器。

预测分析表

为了避免重复计算,为语言构造预测分析表M,其中行表示非终结符号,列表示输入符号(终结符号),每个单元记录非终结符是否可以通过产生式推导出终结符。

构造算法:

  • 对于文法G中的每个产生式A→αA\rightarrow\alphaAα
    • 对于FIRST(α)FIRST(\alpha)FIRST(α)中的每个终结符号a,将A→αA\rightarrow \alphaAα写入M[A,a]M[A,a]M[A,a]单元中
    • 如果ϵ∈First(α)\epsilon\in First(\alpha)ϵFirst(α),对于FOLLOW(A)FOLLOW(A)FOLLOW(A)中的每个终结符号b,将A→αA\rightarrow \alphaAα写入M[A,b]M[A,b]M[A,b]单元中
  • 算法执行后,在空白单元格填入error

由于LL(1)语法不存在二义性,因此在执行递归算法时对于每个输入符号可以根据M唯一的选择产生式。

特别的,如果出现冲突,则说明G不满足LL(1)的条件,即具有二义性。

非递归预测分析

由于LL(1)文法时最左推导的,因此每次分析时只需记住尚未分析的产生式右侧部分即可。

可以通过一个栈实现预测分析。具体过程如下:

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值