自己动手写编译器:创建由 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.北极星是腾讯开源的服务治理平台,致力于解决分…...
Android Wi-Fi 连接失败日志分析
1. Android wifi 关键日志总结 (1) Wi-Fi 断开 (CTRL-EVENT-DISCONNECTED reason3) 日志相关部分: 06-05 10:48:40.987 943 943 I wpa_supplicant: wlan0: CTRL-EVENT-DISCONNECTED bssid44:9b:c1:57:a8:90 reason3 locally_generated1解析: CTR…...
设计模式和设计原则回顾
设计模式和设计原则回顾 23种设计模式是设计原则的完美体现,设计原则设计原则是设计模式的理论基石, 设计模式 在经典的设计模式分类中(如《设计模式:可复用面向对象软件的基础》一书中),总共有23种设计模式,分为三大类: 一、创建型模式(5种) 1. 单例模式(Sing…...
Admin.Net中的消息通信SignalR解释
定义集线器接口 IOnlineUserHub public interface IOnlineUserHub {/// 在线用户列表Task OnlineUserList(OnlineUserList context);/// 强制下线Task ForceOffline(object context);/// 发布站内消息Task PublicNotice(SysNotice context);/// 接收消息Task ReceiveMessage(…...

【HarmonyOS 5.0】DevEco Testing:鸿蒙应用质量保障的终极武器
——全方位测试解决方案与代码实战 一、工具定位与核心能力 DevEco Testing是HarmonyOS官方推出的一体化测试平台,覆盖应用全生命周期测试需求,主要提供五大核心能力: 测试类型检测目标关键指标功能体验基…...
mongodb源码分析session执行handleRequest命令find过程
mongo/transport/service_state_machine.cpp已经分析startSession创建ASIOSession过程,并且验证connection是否超过限制ASIOSession和connection是循环接受客户端命令,把数据流转换成Message,状态转变流程是:State::Created 》 St…...

Opencv中的addweighted函数
一.addweighted函数作用 addweighted()是OpenCV库中用于图像处理的函数,主要功能是将两个输入图像(尺寸和类型相同)按照指定的权重进行加权叠加(图像融合),并添加一个标量值&#x…...
在四层代理中还原真实客户端ngx_stream_realip_module
一、模块原理与价值 PROXY Protocol 回溯 第三方负载均衡(如 HAProxy、AWS NLB、阿里 SLB)发起上游连接时,将真实客户端 IP/Port 写入 PROXY Protocol v1/v2 头。Stream 层接收到头部后,ngx_stream_realip_module 从中提取原始信息…...
使用van-uploader 的UI组件,结合vue2如何实现图片上传组件的封装
以下是基于 vant-ui(适配 Vue2 版本 )实现截图中照片上传预览、删除功能,并封装成可复用组件的完整代码,包含样式和逻辑实现,可直接在 Vue2 项目中使用: 1. 封装的图片上传组件 ImageUploader.vue <te…...

dify打造数据可视化图表
一、概述 在日常工作和学习中,我们经常需要和数据打交道。无论是分析报告、项目展示,还是简单的数据洞察,一个清晰直观的图表,往往能胜过千言万语。 一款能让数据可视化变得超级简单的 MCP Server,由蚂蚁集团 AntV 团队…...
rnn判断string中第一次出现a的下标
# coding:utf8 import torch import torch.nn as nn import numpy as np import random import json""" 基于pytorch的网络编写 实现一个RNN网络完成多分类任务 判断字符 a 第一次出现在字符串中的位置 """class TorchModel(nn.Module):def __in…...