编译器设计-有限自动机

编译器设计-有限自动机

Compiler Design - Finite Automata

有限自动机是一种状态机,它以一串符号作为输入,并相应地改变其状态。有限自动机是正则表达式的识别器。当正则表达式字符串被输入到有限自动机中时,它会为每个文本更改其状态。如果输入字符串成功处理并且自动机达到其最终状态,则接受它,即刚刚输入的字符串被认为是当前语言的有效标记。             

有限自动机的数学模型包括:

  • Finite set of states (Q)
  • Finite set of input symbols (Σ)
  • One Start state (q0)
  • Set of final states (qf)
  • Transition function (δ)

L(r)是有限自动机(FA)识别的正则语言。

状态StatesFA的状态用圆圈表示。州名写在圆圈里。             

开始状态Start state:自动机开始的状态,称为开始状态。开始状态有一个箭头指向它。             

中间状态Intermediate states :所有中间状态至少有两个箭头;一个指向外,另一个指向内(自己)。             

最终状态Final state:如果成功解析输入字符串,则自动机应处于此状态。最终状态用双圆表示。它可能有任意奇数个指向它的箭头和偶数个指向它的箭头。奇数箭头的数目比偶数大一个,即奇数=偶数+1。             

转换Transition:当在输入中找到所需的符号时,从一个状态转换到另一个状态。在转换时,自动机可以移动到下一个状态,也可以保持在相同的状态。从一个状态到另一个状态的移动显示为定向箭头,箭头指向目标状态。如果自动机保持在相同的状态,则会绘制一个从状态指向自身的箭头。

Example : We assume FA accepts any three digit binary value ending in digit 1. FA = {Q(q0, qf), Σ(0,1), q0, qf, δ}

语法分析或分析是编译器的第二个阶段。在本章中,我们将学习构造解析器时使用的基本概念。             

我们已经看到,词法分析器可以借助正则表达式和模式规则来识别标记。但是由于正则表达式的限制,词法分析器无法检查给定句子的语法。正则表达式无法检查平衡标记,如括号。因此,这个阶段使用上下文无关语法(CFG),这是由下推自动机识别的。             

另一方面,CFG是正则语法的超集,如下所示:

这意味着每一种规则语法都是上下文无关的,但也存在一些超出规则语法范围的问题。CFG是描述编程语言语法的有用工具。

上下文无关语法Context-Free Grammar

在本节中,我们将首先看到上下文无关语法的定义,并介绍用于解析技术的术语。             

上下文无关语法有四个组成部分:

一组非终端(V)。非终结符是表示字符串集的语法变量。非终端定义字符串集,帮助定义由语法生成的语言。             

一组标记,称为终端符号(∑)。终端是构成字符串的基本符号。             

一组产品(P)。语法的产生指定了终端和非终端可以组合成字符串的方式。每个产品都由一个称为产品左侧的非终端、一个箭头和一系列令牌和/或终端(称为产品右侧)组成。             

其中一个非终端被指定为开始符号;从那里开始生产。

字符串是从开始符号派生的,方法是重复地将非终端(最初是开始符号)替换为生产的右侧(对于该非终端)。             

例子             

我们讨论回文语言的问题,它不能用正则表达式来描述。也就是说,L={w | w=wR}不是常规语言。但可以通过CFG来描述,如下所示:

G = ( V, Σ, P, S )

其中

V = { Q, Z, N }
Σ = { 0, 1 }
P = { Q → Z | Q → N | Q →  | Z → 0Q0 | N → 1Q1 }
S = { Q }

此语法描述回文语言,如:1001、11100111、00100、1010101、11111等。             

语法分析器syntax analyzer              

语法分析器或解析器以令牌流的形式接受词法分析器的输入。解析器根据生产规则分析源代码(令牌流),以检测代码中的任何错误。这个阶段的输出是一个解析树。

这样,解析器完成两个任务,即解析代码、查找错误和生成解析树作为阶段的输出。             

即使程序中存在错误,解析器也需要解析整个代码。解析器使用错误恢复策略,我们将在本章后面学习。

派生Derivation             

派生基本上是一系列产生式规则,以便获取输入字符串。在解析过程中,我们对某些句子形式的输入做出两个决定:             

决定要更换的非终端。             

决定生产规则,用它来代替非终端。             

要决定用生产规则替换哪个非终端,我们可以有两个选择。             

最左端派生Left-most Derivation            

如果从左到右扫描并替换输入的句子形式,则称之为最左派生。由最左边派生出来的句子式叫做左边的句子形式。             

最右派生             

如果我们Right-most Derivation

从右到左扫描并用产生式规则替换输入,则称为最右派生。从最右边派生出来的句子形式叫做右边的句子形式。             

例子             

生产规则

E  E + E
E  E * E
E  id 

Input string: id + id * id

The left-most derivation is:

E  E * E
E  E + E * E
E  id + E * E
E  id + id * E
E  id + id * id

请注意,最左边的非终端总是先处理。             

最正确的推导是:

E  E + E
E  E + E * E
E  E + E * id
E  E + id * id
E  id + id * id

Parse Tree

请注意,最左边的非终端总是先处理。             

解析树             

解析树是派生的图形描述。可以方便地看到字符串是如何从开始符号派生的。派生的开始符号将成为解析树的根。让我们从上一个主题的一个例子来看这个问题。             

我们取a+b*c的最左边的导出             

最左边的推导是:

E  E * E
E  E + E * E
E  id + E * E
E  id + id * E
E  id + id * id

在解析树中

  • All leaf nodes are terminals.
  • All interior nodes are non-terminals.
  • In-order traversal gives original input string.

所有叶节点都是终端。             
所有内部节点都不是终端。             

按顺序遍历给出原始输入字符串。

按顺序遍历给出原始输入字符串。解析树描述运算符的关联性和优先级。最深的子树首先遍历,因此该子树中的运算符优先于父节点中的运算符。             

模棱两可             

如果语法G对于至少一个字符串有多个解析树(左派生或右派生),则称它是不明确的。

Example

E  E + E
E  E  E
E  id

对于字符串id+id–id,上面的语法生成两个解析树:

由歧义语法生成的语言被称为天生的歧义。语法中的歧义不利于编译器构造。没有一种方法能够自动检测和消除歧义,但是可以通过重新编写整个语法而不产生歧义,或者通过设置和遵循关联性和优先约束来消除歧义。

关联性Associativity             

如果一个操作数的两边都有运算符,则运算符接受此操作数的那一边由这些运算符的关联性决定。如果操作是左关联的,则操作数将由左运算符执行;如果操作是右关联的,则右运算符将执行操作数。             

例子             

加法、乘法、减法和除法等运算是左关联的。如果表达式包含:

id op id op id

it will be evaluated as:

(id op id) op id

例如,(id+id)+id             

指数运算之类的运算是右关联的,即同一表达式中的求值顺序为:

id op (id op id)

For example, id ^ (id ^ id)

优先Presedence              

如果两个不同的运算符共享同一个操作数,则由运算符的优先级决定哪个将成为该操作数。也就是说,2+3*4可以有两个不同的解析树,一个对应于(2+3)*4,另一个对应于2+(3*4)。通过设置运算符之间的优先级,可以很容易地解决此问题。如上例所示,数学上*(乘法)优先于+(加法),因此表达式2+3*4将始终解释为:

2 + (3 * 4)

这些方法减少了语言或语法中的歧义。             

左递归 Left Recursion            

如果语法有任何非终端“A”,其派生包含“A”本身作为最左边的符号,则语法将变为左递归。对于自顶向下的解析器来说,左递归语法被认为是一个有问题的情况。自上而下的解析器从开始符号开始解析,开始符号本身是非终端的。因此,当解析器在派生过程中遇到同一个非终端时,就很难判断何时停止解析左边的非终端,从而进入无限循环。

Example:

(1) A => Aα | β
 
(2) S => Aα | β 
    A => Sd 

1) 是立即左递归的一个例子,其中A是任何非终端符号,α表示一个非终端字符串。             

2) 是间接左递归的一个例子。

自上而下的解析器将首先解析A,然后A将产生一个由A本身组成的字符串,解析器可能永远进入一个循环。             

删除左递归             

删除左递归的一种方法是使用以下技术:             

生产

A => Aα | β

is converted into following productions

A => βA'
A'=> αA' | ε
这不会影响从语法派生的字符串,但会删除立即左递归。              
第二种方法是使用下面的算法,它应该消除所有直接和间接的左递归。
START
 
Arrange non-terminals in some order like A1, A2, A3,…, An
 
   for each i from 1 to n
      {
      for each j from 1 to i-1
         {
         replace each production of form Ai Aj𝜸
         with Ai  δ1𝜸  | δ2𝜸 | δ3𝜸 |…| 𝜸
         where Aj  δ1 | δ2|…| δn  are current Aj productions
         }
      }
   eliminate immediate left-recursion
   
END

Example

The production set

S => Aα | β 
A => Sd

after applying the above algorithm, should become

S => Aα | β 
A => Aαd | βd

and then, remove immediate left recursion using the first technique.

A  => βdA'
A' => αdA' | ε

Now none of the production has either direct or indirect left recursion.

左因子分解 Left Factoring             
如果多个语法生成规则具有公共前缀字符串,则自上而下的解析器无法选择应使用哪个生成来解析手头的字符串。              
例子              
如果自顶向下的解析器遇到
A  αβ | α𝜸 | …
然后,由于两个产品都从同一个终端(或非终端)开始,它无法确定要遵循哪个产品来分析字符串。为了消除这种混乱,我们使用了一种称为左因子分解的技术。              
左阶乘转换语法,使其对自顶向下的解析器有用。在这种技术中,我们为每个公共前缀生成一个结果,其余的派生结果由新的结果添加。              
例子              
以上作品可以写成
A => αA'
A'=> β | 𝜸 | … 
现在解析器每个前缀只有一个产品,这使得决策更容易。              
第一组和第二组              
解析器表构造的一个重要部分是创建第一个集合和后续集合。这些集合可以提供派生中任何终端的实际位置。这样做是为了创建解析表,其中用一些产生式规则替换T[A,T]=α的决定。              
第一组             
 创建此集合是为了知道非终端在第一个位置派生的终端符号。例如,α → t β
即α在第一个位置导出t(终端)。所以,t∈第一(α)。              
计算第一组的算法              
看第一(α)组的定义:              
如果α是终端,那么首先(α)={α}。              
如果α是非终端且α→ℇ是产物,则首先(α)={}。              
如果α是非终端且α→𝜸1𝜸2𝜸3…𝜸n且任何第一个(𝜸)包含t,则t在第一个(α)中。              
第一组可以看作:

跟随集合Follow Set              
同样地,我们计算在产生式规则中,哪个终端符号紧跟非终端α。我们不考虑非终端可以生成什么,而是看到在非终端生成之后的下一个终端符号是什么。              
计算跟随集的算法:             
如果α是开始符号,则FOLLOW()=$              
如果α是非终端且具有产物α→a B,则除ℇ外,先(B)在后(a)中。              
如果α是非终端的,并且具有产生α→a B,其中Bℇ,则FOLLOW(a)在FOLLOW(α)中。              
Follow集可以看作:Follow(α)={t | S*αt*}
语法分析器的局限性Limitations of Syntax Analyzers              
语法分析器以标记的形式从词汇分析器接收它们的输入。词法分析器负责语法分析器提供的令牌的有效性。语法分析器有以下缺点-              
它无法确定令牌是否有效,              
它无法确定令牌在使用前是否已声明,              
它无法确定令牌在使用前是否已初始化,              
它无法确定对令牌类型执行的操作是否有效。              
这些任务是由语义分析器完成的,我们将在语义分析中对其进行研究。

 

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