编译器设计-自下而上分析器-误差恢复-语义分析

编译器设计-自下而上分析器-误差恢复-语义分析

Compiler Design - Bottom-Up Parser

Compiler Design - Error Recovery

Compiler Design - Semantic Analysis

一.Compiler Design - Bottom-Up Parser

自底向上的解析从树的叶节点开始,向上工作直到到达根节点。在这里,我们从一个句子开始,然后以相反的方式应用产生式规则,以达到开始符号。下面给出的图像描述了可用的自下而上的解析器。

 移位减少分析             

Shift-reduce解析使用两个独特的步骤进行自底向上的解析。这些步骤称为移位步骤和减少步骤。             

移位步骤:移位步骤是指输入指针向前移动到下一个输入符号,称为移位符号。这个符号被推到堆栈上。移位的符号被视为解析树的单个节点。             

Reduce step:当解析器找到一个完整的语法规则(RHS)并将其替换为(LHS)时,称为reducestep。当堆栈顶部包含句柄时发生这种情况。为了减少,在堆栈上执行POP函数,该函数从句柄弹出,并用LHS非终端符号替换它。

LR分析器             

LR解析器是一个非递归、移位减少、自底向上的解析器。它使用了大量的上下文无关语法,这使得它成为最有效的语法分析技术。L R解析器也称为LR(k)解析器,其中L表示从左到右扫描输入流;R表示反向构造最右边的派生,k表示要做出决策的先行符号的数量。             

有三种广泛使用的算法可用于构造LR解析器:

  • SLR(1) – Simple LR Parser:
    • Works on smallest class of grammar
    • Few number of states, hence very small table
    • Simple and fast construction
  • LR(1) – LR Parser:
    • Works on complete set of LR(1) Grammar
    • Generates large table and large number of states
    • Slow construction
  • LALR(1) – Look-Ahead LR Parser:
    • Works on intermediate size of grammar
    • Number of states are same as in SLR(1)

LR解析算法             

这里我们描述一个LR解析器的骨架算法:

token = next_token()

repeat forever

   s = top of stack

  

   if action[s, token] = “shift si” then

      PUSH token

      PUSH si

      token = next_token()   

   else if action[s, token] = “reduce A::= β“ then

      POP 2 * |β| symbols

      s = top of stack

      PUSH A

      PUSH goto[s,A]   

   else if action[s, token] = “accept” then

      return

   else

      error()

LL vs. LR

二.Compiler Design - Error Recovery

解析器应该能够检测并报告程序中的任何错误。当遇到错误时,解析器应该能够处理它并继续解析其余的输入。大多数情况下,解析器需要检查错误,但在编译过程的各个阶段可能会遇到错误。一个程序在不同阶段可能有以下几种错误:             

词法:某个标识符的名称键入不正确             

语法:缺少分号或括号不平衡             

语义:不兼容的值赋值             

逻辑:代码不可访问,无限循环             

可以在解析器中实现四种常见的错误恢复策略来处理代码中的错误。             

紧急模式             

当解析器在语句中的任何地方遇到错误时,它将忽略语句的其余部分,不处理从错误输入到分隔符(如分号)的输入。这是最简单的错误恢复方法,而且还可以防止解析器开发无限循环。

语句模式             

当解析器遇到错误时,它会尝试采取纠正措施,以便语句的其余输入允许解析器提前解析。例如,插入一个缺少的分号,用分号替换逗号等等。解析器设计者在这里必须小心,因为一个错误的更正可能导致无限循环。             

错误产品             

编译器设计器知道代码中可能出现的一些常见错误。此外,设计人员可以创建要使用的扩充语法,作为在遇到错误时生成错误构造的产品。             

整体校正             

解析器将手边的程序视为一个整体,并试图找出程序的意图,并试图找出与之最接近的匹配项,这是无错误的。当错误的输入(语句)X被馈送时,它会为某个最接近的无错误语句Y创建一个解析树。这可能允许解析器对源代码进行最小的更改,但由于此策略的复杂性(时间和空间),它尚未在实践中实现。

抽象语法树             

解析树表示不容易被编译器解析,因为它们包含的细节比实际需要的多。以下面的解析树为例:

如果仔细观察,我们会发现大多数叶节点都是父节点的单个子节点。在将信息传送到下一阶段之前,可以消除这些信息。通过隐藏额外信息,我们可以获得如下所示的树:

Abstract tree can be represented as:

ASTs是编译器中重要的数据结构,具有最少的不必要信息。ASTs比解析树更紧凑,编译器可以很容易地使用它。

三.Compiler Design - Semantic Analysis

在语法分析阶段,我们学习了解析器如何构造解析树。在那个阶段构造的纯解析树通常对编译器没有用处,因为它不携带任何关于如何计算树的信息。上下文无关语法的产生,决定了语言的规则,不适应如何解释它们。

For example

E  E + T

上面的CFG产品没有与之相关联的语义规则,它无法帮助理解产品。             

语义学             

语言的语义为其结构提供了意义,如标记和语法结构。语义有助于解释符号、它们的类型以及它们之间

CFG + semantic rules = Syntax Directed Definitions

For example:

int a = value”;

不应在词汇和语法分析阶段出现错误,因为它在词汇和结构上都是正确的,但应在赋值类型不同时产生语义错误。这些规则由语言的语法设定,并在语义分析中进行评估。语义分析应完成以下任务:             

范围分辨率             

类型检查             

数组绑定检查

语义错误             

我们已经提到了一些语义分析器需要识别的语义错误:             

类型不匹配             

未声明的变量             

保留标识符误用。             

作用域中变量的多重声明。             

访问范围外变量。             

实际参数与形式参数不匹配。

属性语法             

属性语法是上下文无关语法的一种特殊形式,它将一些附加信息(属性)附加到一个或多个非终端,以提供上下文敏感信息。每个属性都有定义良好的值域,如integer、float、character、string和expressions。             

属性语法是为上下文无关语法提供语义的一种媒介,它可以帮助指定编程语言的语法和语义。属性语法(当被视为解析树时)可以在树的节点之间传递值或信息。

Example:

E  E + T { E.value = E.value + T.value }

CFG的右边部分包含语义规则,这些规则指定如何解释语法。这里,将非终端E和T的值相加,结果被复制到非终端E。             

语义属性可以在解析时从其域中分配给其值,并在分配或条件时进行计算。根据属性获取值的方式,它们可以大致分为两类:合成属性和继承属性。             

综合属性             

这些属性从其子节点的属性值中获取值。为了说明这一点,假设以下产品:

S  ABC

如果S从它的子节点(A、B、C)中获取值,则它被称为合成属性,因为ABC的值被合成为S。              

如前一个示例(E→E+T)所示,父节点E从其子节点获取其值。合成属性从不从其父节点或任何同级节点获取值。             

继承的属性             

与合成属性不同,继承属性可以从父属性和/或兄弟属性中获取值。在接下来的制作中,

S  ABC

A可以从S、B和C中获取值。B可以从S、A和C中获取值。同样,C可以从S、A和B中获取值。             

扩展:根据语法规则将非终端扩展为终端时

归约:根据语法规则将一个终端归约为其对应的非终端。语法树从上到下和从左到右进行分析。每当进行约简时,我们都会应用相应的语义规则(动作)。             

语义分析使用语法导向的翻译来执行上述任务。             

语义分析器从其前一阶段(语法分析)接收AST(抽象语法树)。             

语义分析器将属性信息附加到AST中,称为属性AST。             

属性是两个元组值,<attribute name,attribute value>

For example:

int value  = 5;
<type, integer”>
<presentvalue, 5”>

对于每个产品,我们都附加一个语义规则。             

S属性SDT             

如果SDT只使用合成属性,则称为S属性SDT。这些属性使用S属性的SDTs进行评估,这些sdt在产品(右侧)之后编写语义操作。

如上所述,S属性的sdt中的属性在自下而上的解析中求值,因为父节点的值依赖于子节点的值。             

L属性SDTs             

这种形式的SDTs既使用合成属性,也使用继承属性,并且限制不从正确的兄弟节点获取值。             

L属性的SDTs中,非终端可以从其父节点、子节点和同级节点获取值。在下面的生产中

S  ABC

S可以取A、B和C(合成)的值。A只能从S获取值。B可以从S获取值,A可以从S、A和B获取值。任何非终端都不能从其右边的同级中获取值。             

L属性SDT中的属性是通过深度优先和从左到右的解析方式计算的。

我们可以得出结论,如果一个定义是S属性的,那么它也是L属性的,因为L属性的定义包含S属性的定义。

 

posted @ 2020-06-23 19:15  吴建明wujianming  阅读(443)  评论(0编辑  收藏  举报