自己动手写编译器:创建由 C 语言编译而成的语法解析器
在上一章节,我们完成了由 c 语言设计的输入系统,本节我们看看如何在前一节的基础上完成一个由 c 语言设计并编译出来的词法解析器。整个解析器的基本设计思路是:
1,由我们上一节设计的输入系统将字符串从文件中读入。
2,由我们前面 GoLex 程序设计生成的状态机代码负责读入步骤 1 读入的字符串进行识别。
3,由 c 语言设计的模板代码驱动步骤1 和 2 的执行
我们看看具体的操作情况。首先我们需要将上一节设计的输入系统对应的函数放入头文件,在 CLex 项目中增加一个头文件l.h,其代码内容如下:
#ifndef __L_H
#define __L_H
extern int ii_newfile(char* name);
extern unsigned char* ii_text();
extern int ii_flush(int force);
extern int ii_length();
extern int ii_lineno();
extern unsigned char* ii_ptext();
extern int ii_plength();
extern int ii_plineno();
extern unsigned char* ii_mark_start();
extern unsigned char* ii_mark_end();
extern unsigned char* ii_move_start();
extern unsigned char* ii_to_mark();
extern unsigned char* ii_mark_prev();
extern int ii_advance();
extern int ii_flush(int force);
extern int ii_fillbuf(unsigned char* starting_at);
extern int ii_look(int n);
extern int ii_pushback(int n);
extern void ii_term();
extern void ii_unterm();
extern int ii_input();
extern void ii_unput(int c);
extern int ii_lookahead(int n);
extern int ii_flushbuf();
#endif
接着在 GoLex 中生成状态机的 c 语言代码,在 main.go 中代码如下(这些代码我们在前面章节讲解和调试过):
func main() {lexReader, _ := nfa.NewLexReader("input.lex", "output.py")lexReader.Head()parser, _ := nfa.NewRegParser(lexReader)start := parser.Parse()parser.PrintNFA(start)nfaConverter := nfa.NewNfaDfaConverter()nfaConverter.MakeDTran(start)nfaConverter.PrintDfaTransition()nfaConverter.MinimizeDFA()fmt.Println("---------new DFA transition table ----")nfaConverter.PrintMinimizeDFATran()//nfaConverter.DoPairCompression()nfaConverter.DoSquash()nfaConverter.PrintDriver()
}
上面代码运行后,我们会在本地生成 lex.yy.c 的文件,我们将文件中的所有代码拷贝到 CLex 的 main.c 文件中。接下来我们需要一段驱动输入系统读入数据,然后调用生成的状态机代码进行字符串识别的”胶水“代码,其名为 yylex,依然在 main.c 中,输入 yylex 函数对应的代码如下:
int yylex() {static int yystate = -1;int yylastaccept;int yyprev;int yynstate;int yylook; //预读取字符int yyanchor;if(yystate == -1) {//将数据读入缓冲区ii_advance();//ii_advance 使得 Next 指针移动了一位,因此在我们还没有读入任何字符时,需要将其后退回去ii_pushback(1);}yystate = 0;yyprev = 0;yylastaccept = 0;ii_unterm();ii_mark_start();while(1) {/** 这里我们采取贪婪算法,如果当前识别的字符串已经进入识别状态,* 但是还有字符可以读取,那么我们先缓存当前识别状态,然后继续识别后续字符,* 直到文件抵达末尾或者输入的字符导致识别失败,此时我们再返回到上次识别状态* 进行处理,这种方法让我们尽可能获得能进入完成状态的最长字符串*/while(1) {yylook = ii_look(1);if (yylook != EOF) {yynstate = yy_next(yystate, yylook);break;} else {if (yylastaccept) {/** 如果文件数据读取完毕,并且我们抵达过完成状态,那么设置下一个状态为* 非法状态*/yynstate = YYF;break;}else if(yywrap()) {yytext = "";yyleng = 0;return 0;}else {ii_advance();ii_pushback(1);}}}// inner whileif (yynstate != YYF) {//跳转到下一个有效状态printf("Transation from state %d ", yystate);printf(" to state %d on <%c>\n", yynstate, yylook);if (ii_advance() < 0) {//缓冲区已满printf("Line %d, lexeme too long. Discarding extra characters.\n", ii_lineno());ii_flush(1);}yyanchor = Yyaccept[yynstate];if (yyanchor) {yyprev = yystate;yylastaccept = yynstate;ii_mark_end(); //完成一个字符串的识别}yystate = yynstate;} else {//跳转到无效状态,说明输入字符串不合法if (!yylastaccept) {//忽略掉非法字符printf("Ignoring bad input\n");ii_advance();} else {//回退到上一次接受状态ii_to_mark();if (yyanchor & 2) {//末尾匹配,将末尾回车符号放回缓冲区ii_pushback(1);}if (yyanchor & 1) {//开头匹配,忽略掉字符串开头的回车符号ii_move_start();}ii_term();//获取当前识别的字符串,极其长度和所在行号yytext = (char*) ii_text();yyleng = ii_length();yylineno = ii_lineno();printf("Accepting state%d, ", yylastaccept);printf("line %d: <%s>\n", yylineno, yytext);switch (yylastaccept) {/** 这里根据接受状态执行其对应的代码,实际上这里的代码* 后面会由 GoLex 生成*/case 3:case 4:printf("%s is a float number", yytext);return FCON;default:printf("internal error, yylex: unkonw accept state %d.\n", yylastaccept);break;}}ii_unterm();yylastaccept = 0;yystate = yyprev;}}// outer while
}
最后我们在 main 函数中调用 yylex 函数,驱动识别进程,main 函数内容如下:
int main() {int fd = ii_newfile("/Users/my/Documents/CLex/num.txt");if (fd == -1) {printf("value of errno: %d\n", errno);}yylex();return 0;
}
完成上面代码后,我们就对 c 语言代码进行编译,生成可执行文件,注意在上面代码中,我们使用输入系统的 ii_newfile 函数读入了一个名为 num.txt 的文件,这个文件的内容包含要识别的字符串,实际上这个文件地址可以作为程序参数输入,这里为了简单,我们直接写入代码中,在本地创建文件 num.txt,在里面输入一个数字字符串 3.14 然后保存,最后我们执行 c 语言代码编译的程序,输出结果如下:
Transation from state 0 to state 4 on <3>
Transation from state 4 to state 3 on <.>
Transation from state 3 to state 3 on <1>
Transation from state 3 to state 3 on <4>
Accepting state3, line 1: <
3.14>
这里我们可以看到,创建的 c 语言代码能正确的识别给定文件里的字符串为浮点数,同时他打印出了状态机在识别每个字符时的状态跳转,由此基本断定,我们 c 语言代码的设计基本正确,下一节我们的目的是将当前”手动“的阶段全部用程序来替代,例如将 GoLex 生成的代码进行粘贴等操作我们都用代码来完成,当这些代码生成和代码粘贴的动作都由 GoLex 完成后,那么它就变成了在编译原理工具链里有名的 Flex 应用,更多详细内容,请大家在 b 站搜索 Coding 迪斯尼,代码下载:
链接: https://pan.baidu.com/s/1MHkg0qNV8QIEqtC_y0XjnA 提取码: 1r4x
相关文章:
自己动手写编译器:创建由 C 语言编译而成的语法解析器
在上一章节,我们完成了由 c 语言设计的输入系统,本节我们看看如何在前一节的基础上完成一个由 c 语言设计并编译出来的词法解析器。整个解析器的基本设计思路是: 1,由我们上一节设计的输入系统将字符串从文件中读入。 2࿰…...
接口设计-增删改查
关于增删改查的 接口设计,比较简单,有一些固定的做法可以使用。 查询列表 查询列表的接口,带上分页的入参: pageNo,pageSize,非必选,并设置默认值。 入参为 dto,根据 dto 从数据库…...
持续持续集成部署-k8s-配置与存储-配置管理:Secret 的应用
持续持续集成部署-k8s-配置与存储-配置管理:Secret 的应用 1. 简介2. 创建 Secret3. docker-registry 的使用1. 简介 与 ConfigMap 类似,用于存储配置信息,但是主要用于存储敏感信息、需要加密的信息,Secret 可以提供数据加密、解密功能。 在创建 Secret 时,要注意如果要…...
ZYNQ7020开发(一):开发环境搭建
文章目录 一、配置Ubuntu 编译环境二、安装Petalinux三、安装JTAG驱动四、安装Vitis一、配置Ubuntu 编译环境 虚拟机环境:VMware Workstation 16 Pro 16.1.0 build-17198959Ubuntu 版本:No LSB modules are available. Distributor ID: Ubuntu Description: Ubuntu 20.04.6 L…...
Spring Boot插件化开发概念原理及实现
Spring Boot 是一个开源的Java框架,它简化了基于Spring框架的应用程序的开发和部署过程。它提供了一种快速、简单的方式来构建独立的、可执行的Spring应用程序。在Spring Boot中,插件化开发是一种强大的开发模式,它允许开发人员将应用程序的不…...
Ps:PSDT 模板文件
自 Photoshop CC 2015.5 版以后,Ps 中新增了一种文件格式:.PSDT。 说明: PSD、PDD、PSDT 都是 Ps 的专用文件格式,需要继续在 Ps 中进行编辑的文件可存为此类格式。 PSD Photoshop document Photoshop 默认文档格式,支…...
Linux-----nginx的简介,nginx搭载负载均衡以及nginx部署前后端分离项目
目录 nginx的简介 是什么 nginx的特点以及功能 Nginx负载均衡 下载 安装 负载均衡 nginx的简介 是什么 Nginx是一个高性能的开源Web服务器和反向代理服务器。它的设计目标是为了解决C10k问题,即在同一时间内支持上万个并发连接。 Nginx采用事件驱动的异…...
presto插件机制揭秘:探索无限可能的数据处理舞台
文章目录 1. 前言2. Presto插件架构3. Plugin接口3.1 插件协议3.2 插件实现类 4. 插件加载过程4.1 PluginManager 5. 插件应用6. 总结 关键词:Presto Plugin 1. 前言 本文源码环境: presto: prestoDb 0.275版本 在Presto框架中插件机制设计是一种非常常见…...
acwing算法基础之数据结构--并查集算法
目录 1 基础知识2 模板3 工程化 1 基础知识 并查集支持O(1)时间复杂度实现: 将两个集合合并。询问两个元素是否在一个集合中。 基本原理:每个集合用一颗树来表示。树根的编号就是整个集合的编号。每个结点存储它的父结点,p[x]表示x的父结点…...
k8s:二进制搭建 Kubernetes v1.20
目录 1 操作系统初始化配置 2 部署 etcd 集群 2.1 准备签发证书环境 2.2 生成Etcd证书 3 部署 docker引擎 4 部署 Master 组件 5 部署 Worker Node 组件 k8s集群master01:192.168.30.105 kube-apiserver kube-controller-manager kube-scheduler etcd k8s集…...
SpringBoot系列-1启动流程
背景 本文作为SpringBoot系列的开篇,介绍SpringBoot的启动流程,包括Spring容器和Tomcat启动过程。SpringBoot作为流行的微服务框架,其是基于约定和自动装配机制对Spring的封装和增强。 由于前面的Spring系列对Spring容器已经进行了较为细致的…...
【记】一次common模块导入无效的bug
首先Maven clean install无用 然后idea清除缓存重启无用 pom.xml文件重载无效 正确解决路径: 1.检查common模块的父工程导入和自身模块的声明是否正确 默认是继承父工程的groupid,可以不用再声明 2.检查子工程是否引入正确的common,org不要…...
1.Netty概述
原生NIO存在的问题(Netty要解决的问题) 虽然JAVA NIO 和 JAVA AIO框架提供了多路复用IO/异步IO的支持,但是并没有提供给上层“信息格式”的良好封装。JAVA NIO 的 API 使用麻烦,需要熟练掌握 ByteBuffer、Channel、Selector等 , 所以用这些API实现一款真正的网络应…...
YOLO目标检测——真实道路车辆检测数据集【含对应voc、coco和yolo三种格式标签】
实际项目应用:自动驾驶技术研发、交通安全监控数据集说明:真实道路车辆检测数据集,真实场景的高质量图片数据,数据场景丰富标签说明:使用lableimg标注软件标注,标注框质量高,含voc(xml)、coco(j…...
【Solidity】Solidity中的基本数据类型和复合数据类型
1. 基本数据类型 1.1 整数类型 Solidity支持有符号整数和无符号整数,可以指定位数和范围。以下是一些整数类型的示例: int:有符号整数,可以是正数或负数。2,-45,2023 uint:无符号整数&#x…...
Flutter Set存储自定义对象时 如何保证唯一
在Flutter中,Set和List是两种不同的集合类型,List中存储的元素可以重复,Set中存储的元素不可重复。 如果你想在Set中存储自定义对象,你需要确保对象的唯一性。 这可以通过在自定义类中实现hashCode方法和equals方法来实现。 has…...
Docker容器中执行throttle.sh显示权限报错:RTNETLINK answers: Operation not permitted
在模拟通信环境时,我执行了一下命令: bash ./throttle.sh wan但是,出现了权限的报错:RTNETLINK answers: Operation not permitted 解决方案说简单也挺简单,只需要两步完成。但是其实又蛮繁琐,因为需要将…...
【Linux】jdk、tomcat、MySQL环境搭建的配置安装,Linux更改后端端口
一、作用 工具的组合为开发者和系统管理员提供了构建和运行Java应用程序以及存储和管理数据的完整环境。 JDK(Java Development Kit):JDK是Java开发工具包,它提供了开发和运行Java应用程序所需的工具和库。通过安装JDK,…...
【WinForm详细教程七】WinForm中的DataGridView控件
文章目录 1.主要属性DataSource行(Row 相关属性)列(Column 相关属性)单元格(Cell 相关属性)逻辑删除AllowUserToAddRowsAllowUserToDeleteRowsAllowUserToOrderColumns其他布局和行为属性 2.控件中的行、列…...
SpringCloudTencent(上)
SpringCloudTencent 1.PolarisMesh介绍2.北极星具备的功能3.北极星包含的组件4.功能特性1.服务管理1.服务注册2.服务发现3.健康检查 2.配置管理 5.代码实战1.环境准备2.服务注册与发现3.远程调用 1.PolarisMesh介绍 1.北极星是腾讯开源的服务治理平台,致力于解决分…...
SpringBoot-17-MyBatis动态SQL标签之常用标签
文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…...
Linux应用开发之网络套接字编程(实例篇)
服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …...
智慧工地云平台源码,基于微服务架构+Java+Spring Cloud +UniApp +MySql
智慧工地管理云平台系统,智慧工地全套源码,java版智慧工地源码,支持PC端、大屏端、移动端。 智慧工地聚焦建筑行业的市场需求,提供“平台网络终端”的整体解决方案,提供劳务管理、视频管理、智能监测、绿色施工、安全管…...
23-Oracle 23 ai 区块链表(Blockchain Table)
小伙伴有没有在金融强合规的领域中遇见,必须要保持数据不可变,管理员都无法修改和留痕的要求。比如医疗的电子病历中,影像检查检验结果不可篡改行的,药品追溯过程中数据只可插入无法删除的特性需求;登录日志、修改日志…...
循环冗余码校验CRC码 算法步骤+详细实例计算
通信过程:(白话解释) 我们将原始待发送的消息称为 M M M,依据发送接收消息双方约定的生成多项式 G ( x ) G(x) G(x)(意思就是 G ( x ) G(x) G(x) 是已知的)࿰…...
python/java环境配置
环境变量放一起 python: 1.首先下载Python Python下载地址:Download Python | Python.org downloads ---windows -- 64 2.安装Python 下面两个,然后自定义,全选 可以把前4个选上 3.环境配置 1)搜高级系统设置 2…...
(二)TensorRT-LLM | 模型导出(v0.20.0rc3)
0. 概述 上一节 对安装和使用有个基本介绍。根据这个 issue 的描述,后续 TensorRT-LLM 团队可能更专注于更新和维护 pytorch backend。但 tensorrt backend 作为先前一直开发的工作,其中包含了大量可以学习的地方。本文主要看看它导出模型的部分&#x…...
Golang dig框架与GraphQL的完美结合
将 Go 的 Dig 依赖注入框架与 GraphQL 结合使用,可以显著提升应用程序的可维护性、可测试性以及灵活性。 Dig 是一个强大的依赖注入容器,能够帮助开发者更好地管理复杂的依赖关系,而 GraphQL 则是一种用于 API 的查询语言,能够提…...
【碎碎念】宝可梦 Mesh GO : 基于MESH网络的口袋妖怪 宝可梦GO游戏自组网系统
目录 游戏说明《宝可梦 Mesh GO》 —— 局域宝可梦探索Pokmon GO 类游戏核心理念应用场景Mesh 特性 宝可梦玩法融合设计游戏构想要素1. 地图探索(基于物理空间 广播范围)2. 野生宝可梦生成与广播3. 对战系统4. 道具与通信5. 延伸玩法 安全性设计 技术选…...
Golang——6、指针和结构体
指针和结构体 1、指针1.1、指针地址和指针类型1.2、指针取值1.3、new和make 2、结构体2.1、type关键字的使用2.2、结构体的定义和初始化2.3、结构体方法和接收者2.4、给任意类型添加方法2.5、结构体的匿名字段2.6、嵌套结构体2.7、嵌套匿名结构体2.8、结构体的继承 3、结构体与…...
