3.2 语法和文法

思维导图:

语言和文法:编程语言设计的核心

在编程语言的设计和编译器的构建中,文法扮演着中心角色。它不仅为语言提供了一种精确且易于理解的语法描述,而且还支持自动化工具的开发,这些工具能高效地分析代码并转换为目标程序。本篇博客将深入探讨文法在编程语言设计中的优势、限制,以及正规式和上下文无关文法(CFG)的比较。

文法的优势
  1. 精确的语法描述:文法使得语言设计者能够准确地定义语言的结构,确保语法的一致性和可理解性。
  2. 自动化分析器的生成:对于某些类别的文法,存在工具可以自动生成分析器。这不仅提高了开发效率,还有助于在设计初期发现文法的二义性和其他潜在问题。
  3. 翻译和错误诊断:如果文法设计得当,其非终结符可以直接对应到语言的具体构造(如过程、语句和表达式等),这对于将源程序翻译成目标代码以及进行错误诊断非常有用。
  4. 语言的扩展性:文法为语言的进一步发展和扩展提供了便利。通过添加或修改产生式,可以轻松引入新的语言特性。
文法的限制

尽管文法在语言设计中非常有用,但它们也有局限性。上下文无关文法不能描述所有编程语言的语法规则,尤其是那些上下文相关的限制,如标识符必须先声明再使用的规则。这意味着在语法分析之后,还需要进行进一步的分析(通常在静态语义检查阶段)来确保代码符合这些规则。

正规式和上下文无关文法的比较
  • 正规式和上下文无关文法都是形式语言理论中的重要概念,用于描述语言的语法结构。
  • 所有能用正规式描述的语言也可以用上下文无关文法来描述。例如,正规式 (a|b)*ab 和上下文无关文法都能描述相同的语言,但CFG提供了更强大的表达能力,能描述更复杂的语法结构,如嵌套和递归。
  • 通过将非确定有限自动机(NFA)转换为上下文无关文法,我们可以构造出能生成与自动机接受相同语言的文法。这一过程展示了从状态转换到语法规则的直接映射,进一步证明了CFG在描述语言方面的灵活性和强大能力。
结论

文法是编程语言设计和编译器构建中不可或缺的工具。它们不仅为语言提供了精确和清晰的语法结构,还支持自动化分析和代码转换的工具开发。虽然有其局限性,但通过结合其他分析技术,文法使得我们能够高效且准确地处理编程语言的复杂性。正规式和上下文无关文法的比较进一步展示了文法在形式语言理论中的核心地位,为我们理解和设计更复杂的语言结构提供了坚实的基础。

 

分离词法分析器的理由

在编程语言设计和编译器构建中,词法分析和语法分析是两个关键的过程。虽然所有正规集都是上下文无关语言的一部分,且理论上上下文无关文法(CFG)足以描述整个语言,包括其词法规则,但在实践中,词法规则通常使用正规式来定义,而将词法分析作为一个独立于语法分析的步骤。以下是分离词法分析器的主要理由:

简单性和效率
  1. 词法规则的简单性:词法规则相对简单,使用功能更强大的上下文无关文法来描述它们是不必要的。正规式能够以更简洁易懂的方式表达这些规则。
  2. 描述的简洁性:正规式提供的词法记号描述比上下文无关文法更为简洁和直观。
  3. 自动构造的有效性:从正规式自动构造出的词法分析器通常比从上下文无关文法构造出的词法分析器更为高效。
软件工程的考量
  1. 提升编译器效率:词法分析的独立简化了词法分析器的设计,使得可以构造专门化且更高效的词法分析器。由于编译过程中相当一部分时间用于读取源程序和将其分割成记号,专用技术可以显著提高处理速度。
  2. 增强编译器的可移植性:通过将与输入字符集相关的特殊性和其他设备相关的不规则性限制在词法分析器中处理,可以提高编译器的可移植性。
  3. 便于模块化设计:将语言的语法结构划分为词法和语法两部分,为编译器前端的模块化设计提供了便利。
正规式与上下文无关文法的应用领域
  • 正规式:最适合描述简单的词法结构,如标识符、常数和关键字。
  • 上下文无关文法:最适合描述复杂的句法结构,如括号匹配、beginend的配对、语句和表达式的嵌套,这些结构无法通过正规式描述。
词法分析与语法分析的合并

尽管理论上可能直接基于字符流进行语法分析,将词法分析并入语法分析中,但这在实践中非常困难。如果不通过独立的词法分析步骤,直接在语法分析中处理诸如注释和空白的规则,将大大增加文法的复杂度,并使分析器的实现更为困难。

结论

分离词法分析和语法分析不仅是出于理论上的考量,更基于实际的软件工程需求。这种分离提高了编译器的效率、可移植性,并促进了模块化设计。同时,通过利用正规式和上下文无关文法各自的优势,可以更有效地处理编程语言的词法和语法规则。

验证文法产生的语言:配对括号串的例子

验证一个给定的文法确实产生特定的语言集合是形式语言理论中的一个重要任务。这不仅确保了文法的正确性,而且帮助理解文法与语言之间的关系。通过例3.5的配对括号串文法,我们可以探讨如何验证一个文法确实产生了所有(且仅产生)符合条件的句子。

文法定义

给定文法: S→(S)S∣ε

这个文法旨在生成所有正确配对的括号串。验证这个文法包括两个部分:

  1. 证明文法生成的所有句子都是配对括号串
  2. 证明所有配对括号串都可以由文法生成
验证过程
1. 文法生成的是配对括号串
  • 归纳基础:直接从文法出发,可以看到通过一步推导可以得到的唯一终结符串是空串(ε),显然是配对的括号串。
  • 归纳假设:假设所有通过少于n步推导得到的句子都是配对的括号串。
  • 归纳步骤:考虑通过n步的最左推导得到的句子。这个推导必定是形式S→(S)S=∗(x)S=∗(x)y的,其中x和y都是通过少于n步推导得到的配对括号串。因此,(x)y也是配对的括号串。
2. 所有配对括号串都可以由文法生成
  • 归纳基础:空串可以通过一步推导得到,满足配对括号串的定义。
  • 归纳假设:假设所有长度小于2n的配对括号串都可以由S推导出。
  • 归纳步骤:考虑一个长度为2n的配对括号串w。因为w是配对的,所以它可以被分解为(x)y的形式,其中x和y都是较短的配对括号串。根据归纳假设,x和y都可以由S推导出。因此,w=(x)y也可以由S推导出。
结论

通过上述归纳证明,我们可以确信给定的文法确实产生了所有正确配对的括号串,并且只产生这样的串。这种证明技术虽然不是初学者必须掌握的,但它提供了一种强有力的工具来设计和验证文法的正确性。在设计编程语言的文法时,采用这种方法思考可以显著增加设计出正确文法的可能性。

构造适当的表达式文法

在语法分析中,为了避免二义性并正确反映算符的优先级和结合性,需要精心设计表达式的文法。通过定义不同的非终结符来代表不同层次的表达式和子表达式,我们可以构造一个无二义性的表达式文法,该文法能够准确体现通常的算符优先级和算符结合性。

设计无二义表达式文法
  1. 非终结符的设置

    • expr(开始符号):表示更高层次的加法表达式。
    • term:表示乘法表达式或更低层次的子表达式。
    • factor:产生表达式的基本单位,如标识符或括号内的表达式。
  2. 基本单位的产生式

    • factor → id | (expr)factor可以是标识符id或者是括号内的更高层次表达式expr
  3. 乘法算符的处理

    • term → term * factor | factor:由于乘法算符*具有较高的优先级,并且是左结合的,这个产生式确保了乘法表达式能够正确解析。
  4. 加法算符的处理

    • expr → expr + term | term:这个产生式处理由加法算符+隔开的表达式,保证了加法表达式是左结合的,并正确体现了其相对于乘法的较低优先级。
表达式文法的无二义性

通过上述设计,构造的表达式文法是无二义的,它能够生成与通常算符优先级和结合性相一致的分析树。例如,对于句子id*id*idid+id*id,分析树准确地反映了乘法算符相对于加法算符的较高优先级,以及同一优先级算符的左结合性。

考虑不同结合性的文法修改

如果语言的语义规定算符优先级或结合性与通常规定不同,例如,如果*+是右结合的,则需要相应地修改文法,以确保分析树能够反映这种不同的规定。修改后的文法可能如下所示:

  • expr → term + expr | term
  • term → factor * term | factor

这种修改确保了在右结合性规定下,表达式能夥被正确解析,并生成反映这一结合性的分析树。

结论

通过精心设计无二义性的表达式文法,我们可以确保语法分析过程能够准确地处理各种算符的优先级和结合性。这对于后续的语义分析和代码生成阶段至关重要,因为只有正确理解了表达式的结构,编译器才能生成准确的目标代码。在设计编程语言的语法时,考虑算符优先级和结合性是构造有效文法的关键。

消除二义性:悬空else问题

二义性是编程语言设计和编译器实现中的一个关键问题。特别是在处理条件语句时,如何正确地处理“悬空else”问题成为消除二义性的一个经典案例。这个问题涉及到如何将else分支正确地关联到相应的if语句。

悬空else的二义性

在给定的二义文法中,形式如if expr then if expr then stmt else stmt的嵌套条件语句存在两种解析方式,导致了二义性。这种二义性源自于else可以与任何一个if关联的事实。

解决方案:重写文法

为了解决这个问题,可以通过重写文法来明确else应该与哪个if关联,即每个else与左边最近的还没有匹配的then配对。这可以通过区分“匹配语句(matched_stmt)”和“未匹配语句(unmatched_stmt)”来实现:

  • 匹配语句:指那些既不是if-then也不是if-then-else语句,以及那些if-then-else语句,其中else明确与一个if匹配的语句。
  • 未匹配语句:指if-then语句,以及if-then-else语句,其中else与另一个ifthen匹配。
无二义文法的构造

通过引入这两种语句类型,可以构造一个无二义的文法来精确地描述条件语句的结构:

stmt → matched_stmt | unmatched_stmt
matched_stmt → if expr then matched_stmt else matched_stmt | other
unmatched_stmt → if expr then stmt | if expr then matched_stmt else unmatched_stmt

这个文法确保了每个else都与最近的未匹配的then配对,从而消除了悬空else的二义性。

编程语言中的选择

尽管无二义文法(如3.7)更精确地规定了else的配对规则,许多编程语言仍然采用简洁但二义的文法(如3.5),并通过编译器实现中的额外规则来消除二义性。这种做法的优点是保持了文法的简洁性,但要求编译器能够正确处理二义性。

结论

虽然文法的二义性在理论上是需要避免的,但在实际语言设计和编译器实现中,通过明确的规则和编译器逻辑来处理这种二义性是可行且常见的做法。重要的是要确保编译器能够一致和准确地将源代码翻译成预期的目标代码,无论采用何种文法描述方式。

左递归是自上而下的语法分析方法(如递归下降分析)中的一个问题,因为它可能导致分析器陷入无限循环。因此,消除文法中的左递归是设计自上而下分析器时的一个重要步骤。

直接左递归的消除

直接左递归的产生式形如 A → Aα,可以通过引入新的非终结符和改写产生式来消除。将产生式改写为不再产生左递归的形式,同时保持语言的串集不变,是消除直接左递归的关键。例如,将形如 A → Aα | β 的直接左递归产生式改写为:

A → βA'
A' → αA' | ε

例子:算术表达式文法的左递归消除

考虑算术表达式文法:

E → E + T | T
T → T * F | F
F → (E) | id

为消除 ET 的直接左递归,产生式可以改写为:

E → TE'
E' → +TE' | ε
T → FT'
T' → *FT' | ε
F → (E) | id

这种改写消除了直接左递归,同时引入了新的非终结符 E'T' 来处理递归定义的后续部分。

消除间接左递归

间接左递归涉及多个步骤的递归,例如文法 S → Aa | bA → Sd | e 中的 S。通过替换和重组产生式,也可以消除间接左递归。对于间接左递归的消除,可能需要先将间接左递归转换为直接左递归形式,然后再应用上述方法进行消除。

结论

消除左递归是自上而下语法分析方法准备过程中的一个重要步骤。通过改写产生式,可以有效地消除直接和间接左递归,从而使得语法分析器能够正确地处理语言,避免无限循环的问题。这一过程不仅对于保持语言的串集不变很重要,也对于编写有效的语法分析器至关重要。

提左因子:优化自上而下分析的文法变换

提左因子是一种重要的文法变换技术,用于优化自上而下的分析方法。这种变换通过重写产生式来推迟分析器对于非终结符替换选择的决定,直到分析器看到足够的输入信息为止。这样做可以减少在分析过程中遇到的二义性和回溯,提高自上而下分析的效率。

提左因子的原理

当文法中存在如下形式的两个或多个产生式时:

A → αβ₁ | αβ₂

其中 A 是非终结符,α 是共同的左因子,β₁β₂ 是不同的序列。由于 α 的存在,导致在自上而下分析时,一旦输入与 α 匹配,分析器无法立即决定选择哪一个产生式进行扩展。

通过提取左因子 α 并引入新的非终结符 A' 来推迟选择,可以将原来的产生式改写为:

A → αA'
A' → β₁ | β₂

这样,分析器首先扩展 AαA',等到处理完 α 后,再根据接下来的输入选择 β₁β₂ 进行扩展。

例子:处理条件语句的提左因子

考虑处理“悬空else”的条件语句:

stmt → if expr then stmt else stmt | if expr then stmt

由于两个产生式都以 if expr then stmt 开始,这导致了在看到输入 if 时,无法立即决定使用哪个产生式。通过提左因子,可以将文法改写为:

stmt → if expr then stmt optional_else_part
optional_else_part → else stmt | ε

这样的改写使得在看到 if expr then stmt 之后,可以推迟决定是否扩展 else stmt 或什么都不做(表示为 ε),直到有足够的输入信息为止。

结论

提左因子是自上而下语法分析优化的有效方法。它通过推迟决策和减少回溯,使得分析过程更加高效和直接。特别是在设计递归下降分析器时,合理地应用提左因子技术可以显著改善分析器的性能和可维护性。

编程语言中的某些构造不能仅用上下文无关文法(CFG)来精确描述。这些构造通常涉及更复杂的语言特性,如标识符的声明与引用的关系,或特定模式中元素的数量匹配。这部分内容探讨了非上下文无关语言构造的例子,并解释了为什么这些构造超出了CFG的表达能力。

非上下文无关的语言构造示例

  1. 标识符的声明和引用:语言 L1​={wcw∣w∈(a∣b)∗} 描述了一种情况,其中一个字符串 w 被另一个相同的字符串 w 跟随,两者之间由字符 c 隔开。这种模式无法通过CFG准确描述,因为CFG无法确保两个 w 之间的直接关系和相等性。这在编程语言中对应于标识符必须先声明后使用的规则。

  2. 数量匹配:语言 L2​={anbncmdm∣n≥0,m≥0} 和 L3​={anbncn∣n≥0} 分别描述了需要匹配数量的场景。L2​ 可以通过CFG描述,因为它要求两组元素(a 和 b,c 和 d)数量匹配,而 L3​ 不能,因为它涉及三组元素的数量匹配,超出了CFG的能力。

  3. 双重限制:语言 L4​={anbmcndm∣n≥1,m≥1} 也提出了一个挑战,尽管它是上下文无关的,因为它要求两对元素数量匹配,这可以通过特定的CFG来描述。

消除左递归和提左因子

这些技术用于优化文法,使其适合自上而下的分析方法。左递归的存在会导致自上而下的分析器进入无限循环,而提左因子则是为了减少在分析过程中的二义性和不确定性,通过重写产生式,推迟选择的决定直到收集到足够的信息。

结论

虽然CFG在描述编程语言的大多数语法结构方面非常有用,但某些特定的语言构造,如标识符的声明与引用关系,以及数量匹配问题,展示了CFG的局限性。这些情况通常要求在语义分析阶段处理,或通过引入额外的机制来确保规则的遵守。这些挑战强调了在编程语言设计和编译器实现中理解和处理语言的复杂性的重要性。

形式语言概览

形式语言理论为理解和设计编程语言提供了一个强大的理论基础。乔姆斯基的文法分类是这一理论的核心,它将文法分为四种类型:0型(无限制文法)、1型(上下文有关文法)、2型(上下文无关文法)、3型(正规文法)。这个分类体系不仅帮助我们理解不同类型文法的能力,还揭示了计算机科学中的一些基本概念,如计算复杂性和语言的可计算性。

文法类型和特点
  • 0型文法(无限制文法):最强大的文法类型,能够生成任何递归可枚举集,等价于图灵机的计算能力。
  • 1型文法(上下文有关文法):允许根据上下文替换符号,能够描述更复杂的语言结构,但不如0型文法强大。
  • 2型文法(上下文无关文法):非终结符的替换不需要考虑上下文,广泛用于编程语言的语法描述。
  • 3型文法(正规文法):等价于正规表达式,通常用于描述编程语言中的简单词法结构。
非上下文无关语言的例子
  • 标识符声明和引用:如 L1​={wcw∣w∈(a∣b)∗} 描述的语言,需要确保标识符的声明先于引用,这种模式超出了上下文无关文法的表达能力。
  • 数量匹配问题:如 L2​={anbncn∣n≥0} 描述的语言,要求三种符号的数量相等,这也无法用上下文无关文法来描述。
上下文有关文法的应用

上下文有关文法的能力强于上下文无关文法,可以用来描述一些上下文无关文法难以处理的语言构造,例如要求符号数量匹配的模式。这种文法虽然在编程语言设计中不常直接使用,但对于理解语言的某些复杂特性非常有帮助。

形式语言理论的影响

自乔姆斯基提出文法分类以来,形式语言理论在计算机科学中的地位越来越重要,对编程语言设计、编译方法、计算复杂性等领域产生了深远的影响。理解这些理论对于深入研究编程语言和编译器构建至关重要。

结论

虽然本文主要关注上下文无关文法,但形式语言理论提供的整体框架使我们能够更好地理解编程语言的语法结构和计算机科学的基本问题。对于有兴趣深入了解形式语言和自动机理论的读者,推荐进一步阅读相关书籍,以获得更全面的理解。

  • 26
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

夏驰和徐策

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值