编译原理 SLR(1) 语法分析器的构建

news2024/11/24 21:07:46

编译原理 SLR(1) 语法分析器的构建

在我的博客查看:https://chenhaotian.top/study/compilation-principle-slr1/

实验三 自底向上语法分析器的构建

项目代码:https://github.com/chen2438/zstu-study/tree/main/%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86/%E5%AE%9E%E9%AA%8C/%E5%AE%9E%E9%AA%8C%E4%B8%89/

一、 实验要求

运用SLR(1)或者LR(1)分析法,针对给定的上下文无关文法,给出实验方案。预估实验中可能出现的问题。

二、 实验方案

(评价依据实验方案设计是否合理,包括输入输出的设计)

使用SLR(1)文法。逐步完成Augmented Grammar、First&Follow Set、DFA、Parse Table。

然后将分析表处理为程序可读数据,程序根据分析表的内容得出分析过程和结果。

输入设计:

Parse Table、Augmented Grammar。

输出设计:

带有Parsing Stack、Input和Action的分析过程。

三、 预估问题

(是否有预估的问题,预估的问题是否合理)

  1. 程序需要事先获得Augmented Grammar、Parse Table、Terminals、Non-terminals的具体内容,这些数据都要被处理为适当的格式,过程比较繁琐。
  2. 可以将每个(non)terminal映射为数字,便于直接调用table(i,(non)terminal)。
  3. 注意非终结符id的特殊处理。

理论基础(评价依据 理论知识非常清楚)

image-20230531191404565

image-20230531191425101

四、 内容和步骤

1、考虑简单算术表达式文法G:

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

试设计SLR(1)或者LR(1)分析程序,以输入的 (a+b)*c+(d+e) 符号串进行语法分析。

2、实验具体步骤

实验三-1

实验三-2

输入数据:

image-20230531191245481

五、 实验结果:

1、 代码

#include <iostream>
#include <iomanip>
#include <stack>
#include <string>

using namespace std;

int table[20][20][2];// I,(non)terminals,action
int mp[200];//映射
string terminals = "+*()i$ETF";
string inputString = "(a+b)*c+(d+e)";
stack<int> pStk, iStk; //parse stack, input stack
stack<int> reduceResult;//存储规约结果, 用于分析树
string reduce[7][2]; //规约
int step = 1;

namespace Graph { //处理分析树
    const int N = 1000, M = N * 2;
    int nodemap[N] = { 0, 'E' };
    int depth[N];

    struct Edge {
        int to, nxt;
    }e[M];

    int adt, head[N];

    void add(int u, int v) {
        e[++adt] = { v,head[u] };
        head[u] = adt;
    }

    int fa[N];

    void dfs(int p1) {//输出分析树
        for (int i = 0; i < depth[p1]; i++) {
            cout << "   |";
        }
        cout << "--" << (char)nodemap[p1] << endl;
        for (int i = head[p1]; i != 0; i = e[i].nxt) {
            int p2 = e[i].to;
            if (p2 == fa[p1]) continue;
            fa[p2] = p1;
            dfs(p2);
        }
    }

    void parseTree() {
        int vst[1000] = { 0 };
        depth[1] = 0;
        int cnt = 2;
        while (!reduceResult.empty()) {//读出规约结果
            int rTop = reduceResult.top(); reduceResult.pop();
            int leftChar = reduce[rTop][0][0];//产生式左部
            string rightString = reduce[rTop][1];//产生式右部
            int oldCnt = cnt;
            for (int j = oldCnt - 1; j >= 1; j--) {//从右往左匹配父节点
                if (nodemap[j] == leftChar and !vst[j]) {
                    vst[j] = 1;
                    for (char k : rightString) {
                        nodemap[cnt] = k;//给节点编号 加映射
                        depth[cnt] = depth[j] + 1;
                        add(j, cnt);
                        cnt++;
                    }
                    break;
                }
            }
        }
        dfs(1);
    }
}


stack<int> reverse(stack<int> s) {
    stack<int> tmp;
    while (!s.empty()) {
        tmp.push(s.top());
        s.pop();
    }
    s = tmp;
    return s;
}

string reverse(string s) {//禁止使用引用
    reverse(s.begin(), s.end());
    return s;
}

void show(stack<int> ps, stack<int> is) {
    ps = reverse(ps);
    int width = 25;
    string str1, str2;
    int odd = 1;
    while (!ps.empty()) {
        if (odd == 1)  str1 += (char)ps.top();
        else str1 += to_string(ps.top());
        odd *= -1;
        ps.pop();
    }
    while (!is.empty()) {
        str2 += (char)is.top();
        is.pop();
    }
    string strBlank(width - str1.size() - str2.size(), ' ');
    cout << str1 << strBlank << str2;
}

void init() {
    for (int i = 0; i < terminals.size(); i++) {
        mp[terminals[i]] = i; // 映射 (non)terminals -> number
        if (terminals[i] == 'i') {// id = {a ~ z}
            for (int j = 'a'; j <= 'z'; j++) {
                mp[j] = i;
            }
        }
    }
    for (int i = 0; i <= 11; i++) {
        for (int j = 0; j < terminals.size(); j++) {
            char c; cin >> c;
            table[i][j][0] = c;
        }
    }
    for (int i = 0; i <= 11; i++) {
        for (int j = 0; j < terminals.size(); j++) {
            int num; cin >> num;
            table[i][j][1] = num;
        }
    }
    for (int i = 0; i < 7; i++) {
        cin >> reduce[i][0] >> reduce[i][1];
        reduce[i][1] = reverse(reduce[i][1]);
    }
}

int parseTable() {
    pStk.push('$');
    pStk.push(0);
    iStk.push('$');
    for (int i = inputString.size() - 1; i >= 0; i--) {
        iStk.push(inputString[i]);
    }
    cout << "---------------------------"
        << "--------------------------" << endl;
    cout << "Step   Parsing Stack       Input    Action" << endl;
    while (!iStk.empty() and !pStk.empty()) {
        cout << step << "     ";
        if (step++ < 10) putchar(' ');
        show(pStk, iStk);
        int pTop = pStk.top(), iTop = iStk.top();
        int action[2] = { table[pTop][mp[iTop]][0],
                          table[pTop][mp[iTop]][1] };
        if (action[0] == 'A') {// 接受
            cout << "    Accept" << endl;
            return 200;
        } else if (action[0] == 'S') {// 移进
            cout << "    Shift " << action[1] << endl;
            pStk.push(iTop);
            pStk.push(action[1]);
            iStk.pop();
        } else if (action[0] == 'R') {// 规约
            reduceResult.push(action[1]);

            cout << "    Reduce " << action[1]
                << ": " + reduce[action[1]][0] + " -> "
                << reverse(reduce[action[1]][1]) << endl;

            for (auto i : reduce[action[1]][1]) {
                while (!pStk.empty()) {
                    int c = pStk.top();
                    pStk.pop();
                    if (c == i) break;
                    if (i == 'i') {
                        if ('a' <= c && c <= 'z') break;
                    }
                }
            }
            int pTop1 = pStk.top();
            pStk.push(reduce[action[1]][0][0]);
            int pTop2 = pStk.top();
            pStk.push(table[pTop1][mp[pTop2]][1]);
        } else {
            return 500;
        }
    }
    return 500;
}

void solve() {
    init();
    int res = parseTable();
    if (res == 200) {
        puts("\nParsing Success");
    } else {
        puts("\nParsing Failed");
    }
    puts("\nParsing Tree:");
    Graph::parseTree();
}

int main() {
    FILE* fp;
    freopen_s(&fp, "input.txt", "r", stdin);
    solve();
    fclose(fp);
}

input.txt 放在程序同目录下

n n S n S n Y Y Y
S n n n n A n n n
R S n R n R n n n
R R n R n R n n n
n n S n S n Y Y Y
R R n R n R n n n
n n S n S n n Y Y
n n S n S n n n Y
S n n S n n n n n
R S n R n R n n n
R R n R n R n n n
R R n R n R n n n

 0 0 4 0 5 0 1 2 3
 6 0 0 0 0 0 0 0 0
 2 7 0 2 0 2 0 0 0
 4 4 0 4 0 4 0 0 0
 0 0 4 0 5 0 8 2 3
 6 6 0 6 0 6 0 0 0
 0 0 4 0 5 0 0 9 3
 0 0 4 0 5 0 0 0 10
6 0 0 11 0 0 0 0 0
 1 7 0 1 0 1 0 0 0
 3 3 0 3 0 3 0 0 0
 5 5 0 5 0 5 0 0 0

E' E
E E+T
E T
T T*F
T F
F (E)
F i

2、 截图

image-20230531191310815

image-20230531191321449

六、 实验结论:

1 、实验结论

(是否能够准确描述实验的结论)

本实验使用SLR(1)文法,根据给定上下文无关文法,完成它的分析程序,并在结果中给出分析过程。

程序可以处理给定token序列不满足给定文法的情况。

此程序的优势是可以快速地修改以适用于不同的SLR(1)文法。

2、分析和总结

1)对输入设计的结论

Augmented Grammar使用string reduce[7][2];存储用于规约。

Parse Table使用int table[20][20][2];存储。分两次读入。

Terminals、Non-terminals直接硬编码到代码中。

2)对输出设计的结论

注意输出格式、栈的展示方向、数据左右对齐。

输出分析树时,先根据规约结果生成分析树存到邻接表中,然后DFS遍历整张图并输出。

3)对SLR(1)或者LR(1)分析法的结论

LR(0):见到First集就移进,见到终态就归约

SLR(1)见到First集就移进,见到终态先看Follow集,与Follow集对应的项目归约,其它报错。

SLR分析法包含的展望信息是体现在利用了Follow(A)信息,可以解决“归约-归约”冲突

SLR分析法没有包含足够的展望信息,不能完成解决“移进-归约”冲突,需要改进。

LALR同心集合并不会产生“移进-归约”冲突 ,但会产生“归约-归约”冲突

3、 对预估问题的结论

  1. 程序需要事先获得Augmented Grammar、Parse Table、Terminals、Non-terminals的具体内容,这些数据都要被处理为适当的格式,过程比较繁琐。

​ Augmented Grammar使用string reduce[7][2];存储用于规约。

​ Parse Table使用int table[20][20][2];存储。分两次读入。

​ Terminals、Non-terminals直接硬编码到代码中。

  1. 可以将每个(non)terminal映射为数字,便于直接调用table(i,(non)terminal)。
  2. 注意非终结符id的特殊处理。

以下代码解决2、3问题

  for (int i = 0; i < terminals.size(); i++) {
    mp[terminals[i]] = i; // 映射 (non)terminals -> number
    if (terminals[i] == 'i') {// id = {a ~ z}
      for (int j = 'a'; j <= 'z'; j++) {
        mp[j] = i;
      }
    }
}

在问题3处,还需注意规约时的字符替换处理。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/609001.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

冈萨雷斯DIP第10章知识点

文章目录 10.2 点、线和边缘检测10.2.2 孤立点的检测10.2.3 线检测10.2.4 边缘模型 10.3 阈值处理10.3.4 使用图像平滑改进全局阈值处理10.3.5 使用边缘改进全局阈值处理10.4 使用区域生长、区域分离与聚合进行分割 分割依据的灰度值基本性质是&#xff1a;不连续性和相似性。本…

计算机网络第二章——物理层(下)

提示&#xff1a;君子可内敛不可懦弱&#xff0c;面不公可起而论之 文章目录 2.1.7 数据交换方式为什么要进行数据交换数据交换的方式电路交换电路交换的优缺点报文交换报文交换的优缺分组交换分组交换的优缺点数据交换方式的选择数据报方式虚电路方式虚电路方式的特点数据报VS…

HJ29 字符串加解密

描述 对输入的字符串进行加解密&#xff0c;并输出。 加密方法为&#xff1a; 当内容是英文字母时则用该英文字母的后一个字母替换&#xff0c;同时字母变换大小写,如字母a时则替换为B&#xff1b;字母Z时则替换为a&#xff1b; 当内容是数字时则把该数字加1&#xff0c…

深入理解设计原则之依赖反转原则(DIP)【软件架构设计】

系列文章目录 C高性能优化编程系列 深入理解软件架构设计系列 深入理解设计模式系列 高级C并发线程编程 DIP&#xff1a;依赖反转原则 系列文章目录1、依赖反转原则的定义和解读2、稳定的抽象层3、依赖倒置原则和控制反转、依赖注入的联系小结 1、依赖反转原则的定义和解读 …

多线程事务回滚方法

多线程事务回滚方法 介绍案例演示线程池配置异常类实体类控制层业务层mapper工具类验证 解决方案使用sqlSession控制手动提交事务SqlSessionTemplate注入容器中改造业务层验证成功操作示例业务层改造 介绍 1.最近有一个大数据量插入的操作入库的业务场景&#xff0c;需要先做一…

Matcher: Segment Anything with One Shot Using All-Purpose Feature Matching 论文精读

Matcher: Segment Anything with One Shot Using All-Purpose Feature Matching 论文链接&#xff1a;[2305.13310] Matcher: Segment Anything with One Shot Using All-Purpose Feature Matching (arxiv.org) 代码链接&#xff1a;aim-uofa/Matcher: Matcher: Segment Anyt…

STM32 HAL库开发——基础篇

目录 一、基础知识 1.1 Cortex--M系列介绍 1.2 什么是stm32 1.3 数据手册查看 1.4 最小系统和 IO 分配 1.4.1 电源电路 1.4.2 复位电路 1.4.3 BOOT 启动电路 1.4.4 晶振电路 1.4.5 下载调试电路 1.4.6 串口一键下载电路 1.4.7 IO 分配 1.4.8 总结 1.5 开发工…

Spring:Spring框架中的核心类 ③

一、解读思想 1、用轮廓解读体系。 2、关注细节&#xff0c;不执着细节。 二、核心类设计 1、 容器接口和实现类 ApplicationContext 接口&#xff08;容器&#xff09; ①.读取配置文件 ②.注解形成bean 哪种形式的bean统一核心管理使用中心类。 2、 ApplicationCont…

MySQL 子查询

文章目录 子查询单行子查询多行子查询相关子查询 exists 子查询 所谓子查询就是 select 查询语句中还有 select 查询语句&#xff0c;里面的称为子查询或内查询&#xff0c;外面的称为主查询或外查询。 根据查询结果记录数量&#xff0c;子查询可以分为两类&#xff1a; 单行…

机器学习 | 分类问题

目录 一、K近邻算法 二、决策树 1.一些原理介绍 2.决策树案例与实践 三、距离 一、K近邻算法 我们引入accuracy_score&#xff0c;利用score()的方法评估准确性。k近邻算法中的k是一个超参数&#xff0c;需要事先进行定义。 k值得选取经验做法是一般低于训练样本得平方根…

排书 dfs 迭代加深 IDA* 剪枝 java

&#x1f351; 算法题解专栏 &#x1f351; 排书 给定 n n n 本书&#xff0c;编号为 1 ∼ n 1 \sim n 1∼n。 在初始状态下&#xff0c;书是任意排列的。 在每一次操作中&#xff0c;可以抽取其中连续的一段&#xff0c;再把这段插入到其他某个位置。 我们的目标状态是把…

【云原生-K8s】k8s可视化管理界面安装配置及比较【Kuboard篇】

总览 安装了k8s控制面板&#xff0c;方便日常的问题处理&#xff0c;查看资源状态信息&#xff0c;也可以增加子账号进行开放给其他人员使用&#xff0c;减少命令操作&#xff0c;提升工作效率 前置条件 须有一个正常使用的k8s集群附k8s v1.23版本搭建&#xff1a;https://…

amis框架实现sdk中使用tsx

1.开发过程中&#xff0c;由于自己和同事用的不同方式使用&#xff0c;本人使用react搭建的amis框架&#xff0c;同事用sdk使用方式搭建 2.开发过程中遇到问题&#xff0c;如果需求中出现amis无法满足的组件&#xff0c;需要自己进行自定义组件&#xff0c;而不同使用方式的am…

JVM内存变化分析实战

最近在一次项目压力测试时&#xff0c;监测到JVM内存明显的变化&#xff0c;由于之前开发工作中没有涉及到JVM相关的问题分析&#xff0c;所以特此借这个机会学习和记录。项目使用的JDK版本为 OpenJdk 1.8&#xff0c;虚拟机为 HotSpot。 1. 内存变化情况 在压力测试进行2H48…

Java008——Java关键字和标识符的简单认识

一、Java关键字 围绕以下3点介绍&#xff1a; 1、什么是Java关键字&#xff1f; 2、Java有哪些关键字&#xff1f; 3、Java关键字的作用&#xff1f; 4、Java关键字的使用&#xff1f;后面文章再做介绍 1.1、什么是Java关键字&#xff1f; 定义&#xff1a;被Java语言赋予了…

github开源化课程体系推荐 浙江大学 计算机考研必备408资料汇总 北京大学计算机系资料整理

github漫游指南 github漫游指南 *所有开源课程资料网站整理在文末 什么是GitHub Wiki 百科上是这么说的 GitHub 是一个共享虚拟主机服务&#xff0c;用于存放使用Git版本控制的软件代码和内容项目。它由GitHub公司&#xff08;曾称Logical Awesome&#xff09;的开发者Chr…

【手撕Spring源码】深度理解SpringMVC【下】

文章目录 控制器方法执行流程ControllerAdvice 之 ModelAttribute返回值处理器MessageConverterControllerAdvice 之 ResponseBodyAdviceBeanNameUrlHandlerMapping 与 SimpleControllerHandlerAdapterRouterFunctionMapping 与 HandlerFunctionAdapterSimpleUrlHandlerMapping…

Elasticsearch:节点角色 - node roles

你可能已经知道 Elasticsearch 集群由一个或多个节点组成。 每个节点将数据存储在分片上&#xff0c;每个分片存储在一个节点上。 到目前为止&#xff0c;你看到的每个节点都至少存储了一个分片&#xff0c;但值得注意的是&#xff0c;节点并不总是必须存储分片。 这是因为每个…

【Unity3D】运动模糊特效

1 运动模糊原理 开启混合&#xff08;Blend&#xff09;后&#xff0c;通过 Alpha 通道控制当前屏幕纹理与历史屏幕纹理进行混合&#xff0c;当有物体运动时&#xff0c;就会将当前位置的物体影像与历史位置的物体影像进行混合&#xff0c;从而实现运动模糊效果&#xff0c;即模…

javascript基础二十二:举例说明你对尾递归的理解,有哪些应用场景

一、递归 递归&#xff08;英语&#xff1a;Recursion&#xff09; 在数学与计算机科学中&#xff0c;是指在函数的定义中使用函数自身的方法 在函数内部&#xff0c;可以调用其他函数。如果一个函数在内部调用自身本身&#xff0c;这个函数就是递归函数 其核心思想是把一个大型…