zoukankan      html  css  js  c++  java
  • 编译原理实验4——SLR(1)分析器的生成

    本篇博客用来记录完成的编译原理实验4的学习过程以及最终成果

    实验要求

    1.必做功能:
      (1)要提供一个源程序编辑界面,让用户输入文法规则(可保存、打开存有文法规则的文件)
      (2)检查该文法是否需要进行文法的扩充。
      (3)求出该文法各非终结符号的first集合与follow集合,并提供窗口以便用户可以查看这些集合结果。
      (4)需要提供窗口以便用户可以查看文法对应的LR(0)DFA图。(可以用画图的方式呈现,也可用表格方式呈现该图点与边的关系数据)
      (5)需要提供窗口以便用户可以查看该文法是否为SLR(1)文法。(如果非SLR(1)文法,可查看其原因)
      (6)需要提供窗口以便用户可以查看文法对应的SLR(1)分析表。(如果该文法为SLR(1)文法时)
      (7)应该书写完善的软件文档

    2.选做功能。如果是组队完成实验,则是必做功能,即必须把下面的两个功能也要实现。
      (1)需要提供窗口以便用户输入需要分析的句子。
      (2)需要提供窗口以便用户查看使用SLR(1)分析该句子的过程。【可以使用表格的形式逐行显示分析过程】

    学习

    问题:LR(0) 和 SLR(1)是什么
    LR(0) 和 SLR(1)都是自底向下的语法分析方法的一个算法
    项目
    假设有文法

    S'->S
    S->(S)S|ε
    

    那么下面列举出LR(0)的8个项目(可以把每一个项目看成是旅游地图的一部分,而其中的点是游客当前所处的位置)

    S'->.S
    S'->S.
    S->.(S)
    S->(.S)S
    S->(S.)S
    S->(S).S
    S->(S)S.
    S->.
    

    每个项目可以看成一个状态。如果某个状态可以通过输入文法中的终结符、非终结符得到新状态,例如

    S'->.S ——S——> S'->S.
    

    则称该状态为移进状态;如果某个状态点已经到了末尾,需要原路返回,则称该状态为归约状态,例如

    S->S. 为归约项
    

    最终形成一个NFA表,NFA表通过ε闭包的改写则可成为DFA表(一个状态可能有多个项目),通过DFA表就可以对句子进行LR(0)分析(移进归约)。
    LR(0)文法:如果一个文法产生的DFA表中的每一个状态包含的都是移进项目或者只有一个归约项,那么该文法成为LR(0)文法。
    SLR(1)文法:如果一个文法产生的DFA表中的每一个状态包含的归约项目的Follow集合交集为空和移进的符号不属于任何一个归约项目,则称该文法为SLR(1)文法

    实践

    项目基于C++实现,可视化是使用QT。所有用到的编程知识点都在此链接
    项目地址

    文法规则的存储结构

    对于下面的文法规则,我们要如何存储呢?

    E' -> E
    E -> E + n | n
    

    首先给出定义如下:
    (1)产生式: 若干条产生式构成一个文法规则。例如E -> E + n | n为一条产生式
    (2)key,values: 对于一条产生式来说,->左边的符号我们称作key,右边的符号序列我们称作values。例如,对于产生式E -> E + n | n来说,E为key,E + n | n为values
    (3)value: 若干个value构成一个values。例如对于valuesE + n | n来说,|符号分割出了两个value,分别为E + nn


    给出定义之后,接下来就来分析一下具体的存储结构?
    (1)对于每个符号(不包括->|),使用string对象来存储,因为一个符号可以为多个字符。
    (2)对于一个value,使用vector< string>对象来存储,因为它本质为string的序列。之所以用vector对象而不用数组,是因为value的大小不确定。
    (3)对于一个values,使用vector<vector< string>>来存储,本质上就是value序列。
    (4)对于一个文法, 使用map<string, vector<vector< string>>>对象来存储,因为产生式本质为一个键值对,多个键值对就构成一个文法。


    既然已经知道了文法的存储结构,下面就需要解决如何把用户输入的文法字符串(转换前需要先进行文法扩充)转换为上面的存储结构。首先我们必须给用户输入文法字符串给予一定的约束,因为不同的输入形式会对应不同的转换算法。我的字符串约束为:

    E' -> E
    E -> (E) + n | n
    

    (1)每个产生式用' '分割
    (2)每一个value用'|'分割
    (3)符号之间用' '分割,但是左右括号除外
    (4)key为开始符号的产生式必须置于最前面(开始符号会在转换的过程中通过string对象保存起来)


    得到了文法的存储结构后,我们可以很容易求得文法规则的非终结符集,通过set< string>对象来存储

    所有非终结符的first集合

    首先说明一下用原来自顶向下的方法来求first集合是行不通的,因为自底向上的文法规则可以有左递归,该算法无法解决有左递归的情况甚至一些不是左递归的情况。比如下面的文法。

    A -> A s | ε
    
    A -> B A | ε
    B -> b | ε
    

    所有非终结符first集合的存储结构

    (1)一个非终结符的first集合用set< string>来存储
    (2)所有非终结符的first集合用map<string, set< string>>来存储,因为通过映射我们可以直接找到某个非终结符的first集合

    求解所有非终结符first集合的算法

    算法描述如下:

    初始化所有非终结符对应的first集合(置为空)
    while 存在非终结符的first集合发生改变:
        for 产生式 in 文法规则:
            定义产生式对应的key, values
            for value in values:
                for value[k] in value: //遍历value中每一个符号,value[k]中为value的第k个符号(编号从0开始)
                    定义符号value[k]的first集合为k_first_set
                    if value[k]为终结符:
                        k_first_set = {value[k]}
                    else:
                        k_first_set = 非终结符value[k]对应的first集合
                    add k_first_set - {"ε"} to 非终结符key对应的first集合
                    if "ε" not in k_first_set:
                        break;
               if k == value.size: //value元素的大小
                    add {"ε"} to 非终结符key对应的first集合
    

    所有非终结符的follow集合

    下面算法求解必须在first集合求解完毕后进行

    求解所有非终结符follow集合的存储结构

    (1)一个非终结符的follow集合用set< string>来存储
    (2)所有非终结符的follow集合用map<string, set< string>>来存储,因为通过映射我们可以直接找到某个非终结符的follow集合

    求解所有非终结符follow集合的算法

    我们需要先定义求解一个符号序列first集合的算法,因为求解所有非终结符的follow集合需要用到
    first(符号序列)算法描述如下:

    定义空集合set
    for s[k] in 符号序列: //遍历符号序列的每一个符号,其中s[k]为符号序列第k个符号(编号从0开始)
        求解s[k]的first集合k_set //s[k]如果为终结符就是{s[k]}, 如果为非终结符就在原来的存储first集合的地方获取
        add k_set - {"ε"} to set
        if "ε" not in k_set:
            break;
    if k == 符号序列.size:
        add {"ε"} to set
    return set
    

    求解所有非终结符的follow集合的算法描述如下:

    初始化所有非终结符的follow集合(置为空)
    while 存在一个非终结符的follow集合发生改变:
        for 产生式 in 文法:
            定义产生式对应的key, values
            for value[k] in values:
                if value[k]为非终结符:
                    add first(value[k+1, ]) to 非终结符value[k]对应的follow集合 //value[k+1, ]是value1的子序列
                    if "ε" in first(value[k+1, ]):
                        add 非终结符key对应的follow集合 to 非终结符value[k]对应的follow集合
    

    DFA图、SLR(1)分析表

    项目的存储结构

    首先给出项目的定义:
    项目就是key -> 被加上了'.'的value
    例如,对于文法规则

    E' -> E
    E -> E + n | n
    

    来说,对应的8个项目为

    E' -> .E
    E' -> E.
    E -> .E + n
    E -> E. + n
    E -> E +. n
    E -> E + n.
    E -> .n
    E -> n.
    

    项目还有不同的类型
    形如E -> n.的项目称为归约项, 其'.'在项目最后
    形如E -> .n的项目称为移进项, 其'.'不在项目最后

    那么,该如何存储一个项目呢?
    首先我们发现一个项目由key,value,'.'的位置唯一确定,为了节省存储空间,value可以用在key对应的values下的编号表示
    所以我们定义一个数据结构Project来存储项目

    Class Project{
        string key;
        int value_num; //在key对应的values下value的编号
        int index;  //'.'的位置编号,表示'.'在value中第index个符号前面
        int type; //项目的类型: 1为移进项 2为归约项
    }
    

    结点的存储结构

    首先给出结点的定义:
    一个结点如图所示:若个项目的集合为结点
    (1)单个结点的存储结构: 因为结点是若干个项目的集合,所以我们使用vector< Project>对象来存储

    (2)所有结点的存储结构: 用vector<vector< Project>>来存储,这样我们也可以通过vector的下标得到结点编号

    注意:结点的存储结构应该使用排序树会更好,因为后面算法有搜索步骤。

    移进关系(DFA的图边关系)的存储结构

    移进关系就是结点间的转移关系,就是上图中的那些边。存储结构为:
    (1)某个结点(作为起点)的转移关系用map<string, int>对象存储,其中string存储移进符号,int存储转移后的结点编号
    (2)所有结点(作为起点)的转移关系用map<int, map<string, int>对象来存储,其中int是起点的结点编号,map<string, int>存储着对应结点的转移关系

    归约关系的存储结构

    归约关系是某个结点的回退操作(与移进相对)。存储结构为:
    (1)某个结点(作为回退起点)的归约关系用map<string, int>对象存储,其中string存储导致归约发生的下一个符号(follow),int存储归约项在结点中的编号。
    (2)所有结点(作为回退起点)的归约关系用map<int, map<string, int>>对象存储

    结点、移进关系、归约关系、判断是否为SLR(1)文法的求解算法

    首先通过举例的方式定义扩展这个概念:对于文法规则

    E' -> E
    E -> E + n | n
    

    项目E' -> .E扩展得到

    E' -> .E
    E -> .E + n
    E -> n
    

    就是把'.'后面的非终结符用其对应的项目('.'在value最前面)来具体化。就像你走到门口推开大门看到里面具体的风景一样。

    然后描述算法:

    初始化结点序列(只有一个结点, 并且结点只有一个项目,项目=Project(开始符号,0,0,1))  
    for 结点 in 结点序列:
        初始化该结点对应的移进关系 //置为空字典
        初始化该结点对应的归约关系 //置为空字典
        扩展该结点(中的每一个项目)
        for 项目j in 结点:
            if 项目为移进项:
                定义该项目的转换符号为t
                定义移进后的新项目为p
                if t未在移进关系的关键字集合里面:
                    if p in 结点k:
                        将t:k键值对加入移进关系中
                    else:
                        向结点集合插入一个含有p的新结点k
                        将t:k键值对加入移进关系中
                else: //t已经在移进关系的关键字集合里面
                    获取转移到的结点k
                    if p not in 结点k:
                        p加入结点k中
            else:  //项目为归约项
                获取项目j.key对应的follow集合follow
                for t in follow:
                    if t:j 已经存在:
                        说明文法不是SLR(1)文法   //因为不满足SLR(1)结点中归约项.key的follow集合交集为空的原则
                    将t:j加入移进关系
    for 结点 in 结点序列:
        if 结点的移进符号集和归约符号集有交集:
            说明文法不是SLR(1)文法  //因为不满足SLR(1)结点中移进符号不存在于该结点中任何一个归约项key的follow集合
    

    用结点、移进关系生成LR(0)DFA图

    有了结点序列和所有结点的移进关系,就已经包含了LR(0)DFA图的所有信息,自己想怎么生成就怎么生成

    用结点、移进关系、归约关系生成SLR(1)表

    有了结点序列和所有结点的移进关系、归约关系,就已经包含了SLR(1)表的所有信息,自己想怎么生成就怎么生成

    分析句子

    实现句子的分析需要一个分析栈和输入队列,存储结构我选择vector< string>,之所以不用stack和queue,是因为stack和queue没有迭代器,没有办法遍历,而vector只要在使用时给一定约束就可以当作stack或queue使用。
    算法描述如下:

    定义分析栈为stack,用户队列为queue
    初始化stack(0压入栈顶)
    初始化queue(用户输入的句子分割成一个个符号后加入queue,最后再加入"$")
    for i in range(0, 无穷大):
        输出步骤i
        输出分析栈
        输出输入队列
        获取栈顶元素f
        获取队列头元素s
        if s为结点f的移进字符:
           弹出队首元素
           t = 结点k由s转换后的结点编号
           s、t分别入栈
           输出"移进s"
        else:
           t = 结点f的归约项目编号
           得到归约项p
           定义p对应的key -> value
           if key为开始符号:
               输出"接受"
               break
           if value[0] != "ε":
               弹出stack的2*value.size个元素
           f = 栈顶元素
           s = key
           t = 结点k由s转换后的结点编号
           s、t入栈
           输出"归约key -> value"     
    

    运行效果

  • 相关阅读:
    User Get 'Access Denied' with Excel Service WebPart
    How To Search and Restore files from Site Collection Recycle Bin
    How To Collect ULS Log from SharePoint Farm
    How To Restart timer service on all servers in farm
    How to Operate SharePoint User Alerts with PowerShell
    How to get Timer Job History
    Synchronization Service Manager
    SharePoint 2007 Full Text Searching PowerShell and CS file content with SharePoint Search
    0x80040E14 Caused by Max Url Length bug
    SharePoint 2007 User Re-created in AD with new SID issue on MySite
  • 原文地址:https://www.cnblogs.com/Serenaxy/p/14145468.html
Copyright © 2011-2022 走看看