返回顶部
首页 > 资讯 > 后端开发 > 其他教程 >C++编译原理之求解First集合
  • 397
分享到

C++编译原理之求解First集合

2024-04-02 19:04:59 397人浏览 独家记忆
摘要

目录1、上机要求2、原理3、一点思路及优化4、代码4.1 lan.txt文件内容4.2 lan.txt文件内容1、上机要求 目的:熟练掌握自上而下的语法分析方法,并能用程序实现。 要

1、上机要求

目的:熟练掌握自上而下的语法分析方法,并能用程序实现。

要求:

例如,使用的文法如下:
编写First函数,实现其求解过程。

E -> TE'
E' -> +TE' | #
T -> FT'
T' -> *FT' | #
F -> (E) | id
end

提示:

  • 非终结符为 大写字母;或 后面带'的大写字母
  • 终结符为 小写字母和符号(+、*)
  • 推导符号→为或->
  • 用end结束文法。

不针对特定文法,编写求first函数。

2、原理

A -> a, 则将 a 加入 First(A)
A -> Y1Y2···Yn

First(Y1) 除空串外的字符加入到First(A)中,若 1 =< i < n - 1,Y1,Y2, Yi中均含有空串,则将First(Yi + 1)加入到First(A)中,若Y1Y2,···,Yn都有空串,则将空串加入到First(A)

First(a) = {a}

3、一点思路及优化

将输入格式化(扫描输入)
将产生式转换为哈希map

  • 对任一产生式: A -> body_1 | body_2 | ··· | body_n
  • 将 A 作为mapkey
  • map的value为一个string类的向量(vector<string> ),
  • body_1body_2,···,body_n 都加入value中。
  • 求解First(str)
  • 特殊情况处理,str为空或str不在产生式的key中,返回空;str的首个字符是终结符,返回首个字符构成的集合
  • 一般情况,获取str推导产生的产生体集bodys(其中的每个产生体为body),遍历产生体集合求解First集
  • 针对空串,我们加入标记hasBlank = true,往下遍历body的字符
  • body的首个字符为终结符,直接将该字符加入first集,记hasBlank = false以便遍历下一body(如果有的话)。
  • body的首个字符为非终结符,递归求解该非终结符first集,记为temp,同时将空串标记记为false,将temp的中除空串外的字符加入first集;若temp中有空串,记空串标记为true,继续遍历当前body的字符,理解上可以将body后面的字符串视为一个新的body继续进行求解步骤。
  • body的字符遍历结束后若空串标记hasBlank仍然为true,则将空串加入first集。
  • 优化:递归求解的中间结果可以放在全局哈希First(或者换个名字避免冲突)中,避免重复的迭代(本代码没实现,下次一定)。

4、代码




#include <iOStream>
#include <unordered_map>
#include <vector>
#include <string>
#include <fstream>
#include <unordered_set>

using namespace std;

unordered_map<string, vector<string>> P; //产生式P的集合

void scan(){
    //scan函数实现从文件扫描文法,将对应的产生式加入到映射P中
    fstream fs;
    string input;
    fs.open("lan.txt");
    if(!fs.is_open()){ // 文件打开失败
        cout << "Error: Could not open the file" << endl;
        exit(-1);
    }
    fs >> input;
    while(input != "end"){
        string VN = input; // 产生式的非终结符

        fs >> input; //跳过推导符号
        if (input != "->" && input != "→"){
            cout << "Error: undefined symbol [" << input << "]" << endl;
            exit(-2);
        }

        fs >> input; //产生体拆开后加入到set集合中,默认推导符号后必有一个产生体
        P[VN].emplace_back(input);
        while( fs >> input && input == "|"){
                fs >> input;
                P[VN].emplace_back(input);
        }
    }
}

// void generate(){
// }

unordered_set<char> First(const string& str){
    // 终结符以及空串情况下, whether has the VN or not
    if(str == "" || str == "#" || P.find(str) == P.end())
        return {};
    if(!(str[0] >= 'A' && str[0] <= 'Z'))
        return {str[0]};

    vector<string> bodys = P[str]; // str -> bodys
    unordered_set<char> res = {};
    for(auto &s: bodys){
        bool hasBlank = true;//是否含有空串,是否继续读产生体
        for (int i = 0; i < s.size() && hasBlank; ++i){
            if(s[i] >= 'A' && s[i] <= 'Z'){//是否为终结符
                unordered_set<char> temp = {};//递归的临时集
                string next;
                if(i < s.size() - 1 && s[i + 1] == '\''){ // 大写字母 + ' 的非终结符
                    next = s.substr(i, 2);
                    ++i;
                }else{ //仅仅是大写字母的终结符
                    next = s[i];
                }
                if(next != str){ //避免无限递归,默认自身是含有空串(hasBlank为True)
                    temp = First(next); //递归求解
                    hasBlank = false; //先默认temp中没有空串
                    for(auto &c : temp)
                        if(c == '#')
                            hasBlank = true;//temp中发现了空串
                        else
                            res.emplace(c);
                }
            }else{
                res.emplace(s[i]);
                hasBlank = false;//默认连接的终结符不为空,故此终结符后不会再有新元素加入First集
            }
        }
        if(hasBlank) //产生体中所有非终结符都包含空串,则将空串加入first集中
            res.emplace('#');
    }
    return res;
}

 

int main(){
    // unordered_map<string, vector<char>> First; //First集合
    scan();
    cout << "输入的产生式如下:\n"
         << "********************************\n";
    for(auto &[vn, bodys]: P){
        cout << vn << " -> " << bodys[0];
        for (int i = 1; i < bodys.size(); ++i)
            cout << " | " << bodys[i];
        cout << endl;
    }
    cout << "********************************\n";

    for(auto &[vn,_]: P){
        unordered_set<char> f = First(vn);
        cout << "First(" << vn << ") : ";
        auto iter = f.begin();

        if(iter != f.end()){
            cout << *iter;
            while(++iter != f.end()){
                cout << " , " << *iter;
            }
        }
        cout << endl;
    }

    return 0;
}

4.1 lan.txt文件内容


E -> TE'
E' -> +TE' | #
T -> FT'
T' -> *FT' | #
F -> (E) | id
end

运行结果

4.2 lan.txt文件内容


S -> SaRb | #
R -> RSQ | #
Q -> e
end

运行结果

到此这篇关于c++/编译原理之求解First集合的文章就介绍到这了,更多相关C++ 求解First集合内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: C++编译原理之求解First集合

本文链接: https://lsjlt.com/news/154778.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • C++编译原理之求解First集合
    目录1、上机要求2、原理3、一点思路及优化4、代码4.1 lan.txt文件内容4.2 lan.txt文件内容1、上机要求 目的:熟练掌握自上而下的语法分析方法,并能用程序实现。 要...
    99+
    2024-04-02
  • Golang编译原理解析
    Golang编译原理解析与具体代码示例 在现代编程语言中,编译原理是一个至关重要的领域,它涉及到将高级语言代码转换为机器能够理解和执行的低级指令的过程。作为一门流行的编程语言,Gola...
    99+
    2024-03-06
    原理 编译 golang go语言
  • Go编译原理之函数内联
    目录前言函数内联概述函数内联底层实现visitBottomUpcaninlinlcalls前言 在前一篇文章中分享了编译器优化的变量捕获部分,本文分享编译器优化的另一个内容&mdas...
    99+
    2024-04-02
  • 如何理解C++编译器编译功能
    如何理解C++编译器编译功能,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。下面深度讲解C++中的大规模C++编译器,C++编译器具有很强的复杂性,并且源程序的行数也是非常多...
    99+
    2023-06-17
  • Go语言编译原理之源码调试
    目录前言Goland的debug调试Go源码dlv工具调试Go源码安装常用命令dlv调试抽象语法树构建前言 在前边几篇文章中分享了Go编译过程中的源码实现,本文主要是想分享一下我是怎...
    99+
    2024-04-02
  • Go语言编译原理之变量捕获
    目录前言变量捕获概述变量捕获底层实现总结前言 在前边的几篇文章中已经基本分享完了编译器前端的一些工作,后边的几篇主要是关于编译器对抽象语法树进行分析和重构,然后完成一系列的优化,其中...
    99+
    2024-04-02
  • vue学习之聊聊模板编译原理
    **在AST中找出所有静态根节点并打上标记 ** 静态根节点:子节点全是静态节点的节点 使用递归从上向下寻找,在寻找的过程中遇见的第一个静态节点就为静态根节点,同时不继续往下找。如果一个静态根节点的子节点只有一个文本节点或没有子节点,那么不...
    99+
    2023-05-14
    模板编译 Vue
  • Go语言编译器实现原理与编译过程详解
    标题:Go语言编译器实现原理与编译过程详解 在计算机编程领域中,编译器是一种非常重要的工具,它负责将我们编写的高级语言代码转换为目标机器能够执行的机器码。Go语言作为一种快速、高效的编...
    99+
    2024-03-11
    编译器 go语言 实现
  • Java基础学习之集合底层原理
    目录一、Collection集合二、List接口三、Set(Set底层是由Map实现的,所以一般都是问Map)四、Map一、Collection集合 Collection接口是单列...
    99+
    2024-04-02
  • Java常用集合与原理解析
    目录迭代器集合框架中的接口具体集合散列码树集队列优先队列映射基本映射映射视图弱散列映射链接散列集合映射枚举集与映射标识散列映射Java 最初版本只为常用的数据结构提供了很少的一组类:...
    99+
    2024-04-02
  • 深入理解 JVM 之——动手编译 JDK
    更好的阅读体验 \huge{\color{red}{更好的阅读体验}} 更好的阅读体验 本篇为深入理解 Java 虚拟机第一章的实战内...
    99+
    2023-08-30
    java jvm
  • c/c++单例模式类的混合编译案例详解
    目录C/C++混合编译解决方案:中间层调用log案例解决方案:源代码C/C++混合编译 难点:c++支持重载,因此g++编译后的函数名有额外信息,在gcc编译的c文件中无法识别符号,...
    99+
    2024-04-02
  • C/C++并查集的查询与合并实现原理
    目录一、并查集的概念二、并查集的实现1.并查集不同集合(树)的形成2.find()函数找一个元素集合的编号3.合并两个不同集合(合并两棵不同的树)4.查询两个元素是否在一个集合5.并...
    99+
    2023-02-13
    C++并查集的查询与合并 C语言并查集的合并与查询
  • C语言编程之预处理过程与define及条件编译
    目录名示常量#define重定义常量在#define中使用参数预处理器粘合剂:##运算符变参宏:… 和_ _ VAG_ARGS_ _宏与函数预处理指令#undef指令从C预处理器的角...
    99+
    2024-04-02
  • C语言深入探究程序的编译之预处理
    目录1.程序的翻译环境和执行环境2.详解编译与链接2.1翻译环境2.2编译本身也分为几个阶段2.3运行环境3.预处理详解3.1预处理符号3.2#define3.2.1#define定...
    99+
    2024-04-02
  • @RequestBody注解AjaxpostjsonList集合数据请求400/415的处理
    目录@RequestBody注解Ajax post json List集合数据请求400/4151.post发送的json数据错误2.Spring或maven版本过低导致jackso...
    99+
    2022-11-13
    @RequestBody注解 Ajax post json List集合数据 集合数据请求400 415
  • Android编程之分辨率处理相关代码段合集
    本文实例讲述了Android编程之分辨率处理相关代码段。分享给大家供大家参考,具体如下: 1. 通常我们所说的屏幕分辨率如800x480、960x540等。...
    99+
    2022-06-06
    程之 分辨率 Android
  • Go语言编译器原理解析与应用探讨
    Go语言编译器原理解析与应用探讨 一、Go语言编译器的基本原理 Go语言是一种开发人员使用的高效、可靠且简单的编程语言,同时也具有并行性和并发性。Go语言的编译器是将Go语言代码转换为...
    99+
    2024-03-11
    应用 编译器 go语言
  • C语言程序的编译与预处理详解
    目录一、程序的编译1、 编译阶段2、链接二、预处理详解1、预定义符号2、#define定义的标识符3、#define定义的宏4、#unef总结一、程序的编译 我们写的源文件(*.c)...
    99+
    2024-04-02
  • Mysql执行原理之索引合并详解
    mysql执行原理之索引合并详解 我们前边说过MySQL在一般情况下执行一个查询时最多只会用到单个二级索引,但存在有特殊情况,在这些特殊情况下也可能在一个查询中使用到多个二级索引,MySQL中这种使用到多个索引来完成一次...
    99+
    2022-12-20
    Mysql索引合并 Mysql执行原理
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作