自己动手写编译器:实现命令行模块

在前面一系列章节中,我们完成了词法解析的各种算法。包括解析正则表达式字符串,构建 NFA 状态就,从 NFA 转换为 DFA 状态机,最后实现状态机最小化,接下来我们注重词法解析模块的工程化实现,也就是我们将所有算法集合起来完成一个可用的程序,由此在接下来的章节中,我们将重点放在工程实现上而不是编译原理算法上。

为何我们一个强调编译原理算法的专栏会花费大力气在工程实现上呢。英语有句俗语"you don’t know it if you can’t build it",也就是你做不出来就意味着你没有掌握它,这一点是我们传统教育的痛点,你上了计算机课程中的编译原理,操作系统,你掌握了一堆名词和算法描述,但完成这些课程,考试通过,那意味着你掌握这些知识了吗?如果学了操作系统,你不能做出一个可运行的系统,学了编译原理,你搞不出一个能编译代码的编译器,那说明你对所学知识根本没有真正掌握,你只是模模糊糊,一知半解。

为了真正掌握,我们必须构建出一个可运行的具体实体。在实现这个具体实体过程中,我们会发现很多我们以为理解了的算法或概念,实际上我们根本就没有掌握。本节开始我们要为 GoLex 添加更多复杂功能,当我们完成 GoLex 工具后,它的作用如下:
请添加图片描述
GoLex 程序运行时需要输入两个文件,分别为 input.lex 和 lex.par,其中 input.lex 我们已经认识过,lex.par 其实是一个 c 语言模板文件,它的内容我们在后面章节中会花很大力气去剖析和实现,GoLex 会读取这两个文件的内容,然后生成两个文件 lex.yy.c 和 lex.yy.h,这两个文件是给定语言词法解析器的代码,假设我们要开发一个能识别 sql 语言词法的程序,那么我们把识别 sql 语言中关键字,变量名等字符串对应的正则表达式放在 input.lex 中,然后调用 GoLex 生成 lex.yy.c,lex.yy.h 两个 c 语言源代码文件,然后再使用 gcc 对这些文件进行编译,最后得到的可执行文件 a.out 就是能用于对 sql 代码文件进行词法解析的可执行文件,也就是说 GoLex 其实是用于生成另一个可执行程序源代码的程序,这类似于微积分中的二阶求导。

废话少说,能动手就不要逼逼。首先在工程目录下创建一个名为 cmd 的文件夹,然后创建一个名为 cmd.go 的文件,实现代码如下:

package command_lineimport ("fmt""time"
)type CommandLine struct {
}func NewCommandLine() *CommandLine {return &CommandLine{}
}func (c *CommandLine) Signon() {//这里设置当前时间date := time.Now()//这里设置你的名字name := "yichen"fmt.Printf("GoLex 1.0 [%s] . (c) %s, All rights reserved\n", date.Format("01-02-2006"), name)
}

上面代码运行后会打印出一行”版权“信息,它能让我们感觉好像搞了什么牛逼得不行的东西,有一种老子是大神的牛逼哄哄获得感。下面我们提供一个函数叫 PrintHeader,它的作用是输出对未压缩 DFA 的 C语言注释,首先我们把原来在 main 函数中的那些代码挪到 CommandLine 对象的构造函数中,相关代码如下:

package command_lineimport ("fmt""nfa""time"
)type CommandLine struct {lexerReader  *nfa.LexReaderparser       *nfa.RegParsernfaConverter *nfa.NfaDfaConverter
}func NewCommandLine() *CommandLine {lexReader, _ := nfa.NewLexReader("input.lex", "output.py")lexReader.Head()parser, _ := nfa.NewRegParser(lexReader)start := parser.Parse()nfaConverter := nfa.NewNfaDfaConverter()nfaConverter.MakeDTran(start)nfaConverter.PrintDfaTransition()return &CommandLine{lexerReader:  lexReader,parser:       parser,nfaConverter: nfaConverter,}
}func (c *CommandLine) PrintHeader() {//针对未压缩的 DFA 状态就,输出对应的 c 语言注释c.nfaConverter.PrintUnCompressedDFA()//打印基于 c 语言的跳转表c.nfaConverter.PrintDriver()
}func (c *CommandLine) Signon() {//这里设置当前时间date := time.Now()//这里设置你的名字name := "yichen"fmt.Printf("GoLex 1.0 [%s] . (c) %s, All rights reserved\n", date.Format("01-02-2006"), name)
}

然后我们进入文件 nfa_to_dfa,在类NfaDfaConverter中增加上面调用到的两个函数,其实现如下:

func (n *NfaDfaConverter) PrintUnCompressedDFA() {fmt.Fprint(n.fp, "ifdef __NEVER__\n")fmt.Fprint(n.fp, "/*------------------------------------------------\n")fmt.Fprint(n.fp, "DFA (start state is 0) is :\n *\n")nrows := n.nstatescharsPrinted := 0for i := 0; i < nrows; i++ {dstate := n.dstates[i]if dstate.isAccepted == false {fmt.Fprintf(n.fp, "* State %d [nonaccepting]", dstate.state)} else {//这里需要输出行数//fmt.Fprintf(n.fp, "* State %d [accepting, line %d <", i, )fmt.Fprintf(n.fp, "* State %d [accepting, line %d, <%s>]\n", i, dstate.LineNo, dstate.acceptString)if dstate.anchor != NONE {start := ""end := ""if (dstate.anchor & START) != NONE {start = "start"}if (dstate.anchor & END) != NONE {end = "end"}fmt.Fprintf(n.fp, " Anchor: %s %s", start, end)}}lastTransition := -1for j := 0; j < MAX_CHARS; j++ {if n.dtrans[i][j] != F {if n.dtrans[i][j] != lastTransition {fmt.Fprintf(n.fp, "\n * goto %d on ", n.dtrans[i][j])charsPrinted = 0}fmt.Fprintf(n.fp, "%s", n.BinToAscii(j))charsPrinted += len(n.BinToAscii(j))if charsPrinted > 56 {//16 个空格fmt.Fprintf(n.fp, "\n *                ")charsPrinted = 0}lastTransition = n.dtrans[i][j]}}fmt.Fprintf(n.fp, "\n")}fmt.Fprintf(n.fp, "*/ \n\n")fmt.Fprintf(n.fp, "#endif\n")
}func (n *NfaDfaConverter) PrintDriver() {text := "输出基于 DFA 的跳转表,首先我们将生成一个 Yyaccept数组,如果 Yyaccept[i]取值为 0," +"\n\t那表示节点 i 不是接收态,如果它的值不是 0,那么节点是接受态,此时他的值对应以下几种情况:" +"\n\t1 表示节点对应的正则表达式需要开头匹配,也就是正则表达式以符号^开始," +"2 表示正则表达式需要\n\t末尾匹配,也就是表达式以符号$结尾,3 表示同时开头和结尾匹配,4 表示不需要开头或结尾匹配"comments := make([]string, 0)comments = append(comments, text)n.comment(comments)//YYPRIVATE YY_TTYPE 是 c 语言代码中的宏定义,我们将在后面代码提供其定义//YYPRIVATE 对应 static, YY_TTYPE 对应 unsigned charfmt.Fprintf(n.fp, "YYPRIATE YY_TTYPE Yyaccept[]=\n")fmt.Fprintf(n.fp, "{\n")for i := 0; i < n.nstates; i++ {if n.dstates[i].isAccepted == false {//如果节点i 不是接收态,Yyaccept[i] = 0fmt.Fprintf(n.fp, "\t0  ")} else {anchor := 4if n.dstates[i].anchor != NONE {anchor = int(n.dstates[i].anchor)}fmt.Fprintf(n.fp, "\t%-3d", anchor)}if i == n.nstates-1 {fmt.Fprint(n.fp, "   ")} else {fmt.Fprint(n.fp, ",  ")}fmt.Fprintf(n.fp, "/*State %-3d*/\n", i)}fmt.Fprintf(n.fp, "};\n\n")//接下来的部分要在实现函数 DoFile 之后才好实现//TODO
}

这里需要注意的是,PrintDriver我们只实现了一部分,剩余部分我们还需在后面章节实现 C 语言代码模板后,上面的 TODO 部分才能接着实现,不过在完成上面代码后,我们已经能看到 lex.yy.c 文件的部分内容了,在 main.go 中输入代码如下:

package mainimport ("command_line"
)func main() {cmd := command_line.NewCommandLine()cmd.PrintHeader()
}

完成上面代码后,执行起来,我们会得到一个 lex.yy.c 的文件,其内容如下所示:

ifdef __NEVER__
/*------------------------------------------------
DFA (start state is 0) is :*
* State 0 [nonaccepting]* goto 1 on .* goto 2 on 0123456789
* State 1 [nonaccepting]* goto 3 on 0123456789
* State 2 [nonaccepting]* goto 4 on .* goto 5 on 0123456789
* State 3 [accepting, line 6, <  {printf("%s is a float number", yytext); return FCON;}>]* State 4 [accepting, line 6, <  {printf("%s is a float number", yytext); return FCON;}>]* goto 6 on 0123456789
* State 5 [nonaccepting]* goto 1 on .* goto 5 on 0123456789
* State 6 [accepting, line 6, <  {printf("%s is a float number", yytext); return FCON;}>]* goto 7 on 0123456789
* State 7 [accepting, line 6, <  {printf("%s is a float number", yytext); return FCON;}>]* goto 7 on 0123456789
*/ #endif/*--------------------------------------* 输出基于 DFA 的跳转表,首先我们将生成一个 Yyaccept数组,如果 Yyaccept[i]取值为 0,那表示节点 i 不是接收态,如果它的值不是 0,那么节点是接受态,此时他的值对应以下几种情况:1 表示节点对应的正则表达式需要开头匹配,也就是正则表达式以符号^开始,2 表示正则表达式需要末尾匹配,也就是表达式以符号$结尾,3 表示同时开头和结尾匹配,4 表示不需要开头或结尾匹配*/YYPRIATE YY_TTYPE Yyaccept[]=
{0  ,  /*State 0  */0  ,  /*State 1  */0  ,  /*State 2  */4  ,  /*State 3  */4  ,  /*State 4  */0  ,  /*State 5  */4  ,  /*State 6  */4     /*State 7  */
};

可以看到,在输出的 c 语言文件中,我们首先使用注释输出了跳转表的内容,然后输出一个接收状态数组,如果节点 i 是接收状态,那么数组 Yyaccept[i]对应的值就不是 0,要不然它对应的值就是 0,下一节我们将深入研究 c 语言模板代码,然后完成本节的 TODO 部分代码,更多内容请在 B 站搜索 coding 迪斯尼,以便获取更加详细的调试演示视频。

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

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

相关文章

【信创】麒麟v10(arm)-mysql8-mongo-redis-oceanbase

Win10/Win11 借助qume模拟器安装arm64麒麟v10 前言 近两年的国产化进程一直在推进&#xff0c;基于arm架构的国产系统也在积极发展&#xff0c;这里记录一下基于麒麟v10arm版安装常见数据库的方案。 麒麟软件介绍: 银河麒麟高级服务器操作系统V10 - 国产操作系统、银河麒麟、中…

树概念及结构

.1树的概念 树是一种非线性的数据结构&#xff0c;它是由n&#xff08;n>0&#xff09;个有限结点组成一个具有层次关系的集合。把它叫做树是因 为它看起来像一棵倒挂的树&#xff0c;也就是说它是根朝上&#xff0c;而叶朝下的。 有一个特殊的结点&#xff0c;称为根结点&a…

springcloud:四、nacos介绍+启动+服务分级存储模型/集群+NacosRule负载均衡

nacos介绍 nacos是阿里巴巴提供的SpringCloud的一个组件&#xff0c;算是eureka的替代品。 nacos启动 安装过程这里不再赘述&#xff0c;相关安装或启动的问题可以见我的另一篇博客&#xff1a; http://t.csdn.cn/tcQ76 单价模式启动命令&#xff1a;进入bin目录&#xff0…

14:00面试测试岗,14:06就出来了,问的问题有点变态。。。

从小厂出来&#xff0c;没想到在另一家公司又寄了。 到这家公司开始上班&#xff0c;加班是每天必不可少的&#xff0c;看在钱给的比较多的份上&#xff0c;就不太计较了。没想到9月一纸通知&#xff0c;所有人不准加班&#xff0c;加班费不仅没有了&#xff0c;薪资还要降40%,…

Kotlin异常处理runCatching,getOrNull,onFailure,onSuccess(1)

Kotlin异常处理runCatching&#xff0c;getOrNull&#xff0c;onFailure&#xff0c;onSuccess&#xff08;1&#xff09; fun main(args: Array<String>) {var s1 runCatching {1 / 1}.getOrNull()println(s1) //s11&#xff0c;打印1println("-")var s2 ru…

Academic accumulation|英文文献速读

一、英文文献速读法 &#xff08;一&#xff09;明确目的 建议大家阅读一篇论文之前先问一下自己是出于怎样的目的来阅读这篇文章&#xff0c;是为了找选题方向、学某个问题的研究设计、学某种研究方法、学文章写作还是别的。不同的阅读目的会导致不同的关注重点&#xff0c;例…

嵌入式学习笔记(41)SD卡启动详解

内存和外存的区别&#xff1a;一般是把这种RAM(random access memory,随机访问存储器&#xff0c;特点是任意字节读写&#xff0c;掉电丢失)叫内存&#xff0c;把ROM&#xff08;read only memory&#xff0c;只读存储器&#xff0c;类似于Flash SD卡之类的&#xff0c;用来存储…

建站软件WordPress和phpcms体验

一、网站程序 什么是网站程序? 网站程序是由程序员编写的一个网站安装包,程序是网站内容的载体。 常见的网站程序有: dedecms , phpcms ,帝国cms ,米拓cms , WordPress , discuz , ECShop ,shopex , z-blog等,根据不同类型的网站我们来选择不同的网站程序。 比如说搭建一个…

【生物信息学】基因差异分析Deg(数据读取、数据处理、差异分析、结果可视化)

目录 一、实验介绍 二、实验环境 1. 配置虚拟环境 2. 库版本介绍 3. IDE 三、实验内容 0. 导入必要的工具包 1. 定义一些阈值和参数 2. 读取数据 normal_data.csv部分展示 tumor_data.csv部分展示 3. 绘制箱型图 4. 删除表达量低于阈值的基因 5. 计算差异显著的基…

成都瀚网科技:抖音上线地方方言自动翻译功能

为了让很多方言的地域历史、文化、习俗能够以短视频的形式生产、传播和保存&#xff0c;解决方言难以被更多用户阅读和理解的问题&#xff0c;平台正式上线推出当地方言自动翻译功能。创作者可以利用该功能&#xff0c;将多个方言视频“一键”转换为普通话字幕供大众观看。 具体…

【视频去噪】基于全变异正则化最小二乘反卷积是最标准的图像处理、视频去噪研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

Redis是否要分库的实践

Redis的分库其实没有带来任何效率上的提升&#xff0c;只是提供了一个命名空间&#xff0c;而这个命名空间可以完全通过key的设计来避开这个问题。 一个优雅的Redis的key的设计如下

Windows历史版本下载

1、微PE工具箱&#xff08;非广告本人常用&#xff09; 常用安装Windows系统的微PE工具 地址&#xff1a;https://www.wepe.com.cn/download.html 2、Windows系统下载地址&#xff08;非微软官方&#xff09; 地址&#xff1a;MSDN, 我告诉你 - 做一个安静的工具站 下载&…

【嵌入式】使用MultiButton开源库驱动按键并控制多级界面切换

目录 一 背景说明 二 参考资料 三 MultiButton开源库移植 四 设计实现--驱动按键 五 设计实现--界面处理 一 背景说明 需要做一个通过不同按键控制多级界面切换以及界面动作的程序。 查阅相关资料&#xff0c;发现网上大多数的应用都比较繁琐&#xff0c;且对于多级界面的…

并查集LRUCache

文章目录 并查集1.概念2. 实现 LRUCache1. 概念2. 实现使用标准库实现自主实现 并查集 1.概念 并查集是一个类似于森林的数据结构&#xff0c;并、查、集指的是多个不相干的集合直接的合并和查找&#xff0c;并查集使用于N个集合。适用于将多个元素分成多个集合&#xff0c;在…

[FineReport]安装与使用(连接Hive3.1.2)

一、安装(对应hive3.1.2) 注&#xff1a;服务器的和本地的要同时安装。本地是测试环境&#xff0c;服务器的是生产环境 1、服务器安装 1、下载 免费下载FineReport - FineReport报表官网 向下滑找到 2、解压 [rootck1 /home/data_warehouse/software]# tar -zxvf tomcat…

数据挖掘(1)概述

一、数据仓库和数据挖掘概述 1.1 数据仓库的产生 数据仓库与数据挖掘&#xff1a; 数据仓库和联机分析处理技术(存储)。数据挖掘&#xff1a;在大量的数据中心挖掘感兴趣的知识、规则、规律、模式、约束(分析)。数据仓库用于决策分析&#xff1a; 数据仓库&#xff1a;是在数…

机器学习算法基础--K-means应用实战--图像分割

目录 1.项目内容介绍 2.项目关键代码 3.项目效果展示 1.项目内容介绍 本项目是将一张图片进行k-means分类&#xff0c;根据色彩k进行分类&#xff0c;最后比较和原图的效果。 题目还是比较简单的&#xff0c;我们只要通过k-means聚类&#xff0c;一类就是一种色彩得出聚类之…

快速上手kettle(三)壶中可以放些啥?

序言 快速上手kettle开篇中,我们将kettle比作壶,并对这个壶做了简单介绍。 而上一期中我们实现了①将csv文件通过kettle转换成excel文件; ②将excel文件通过kettle写入到MySQL数据库表中 这两个案例。 相信大家跟我一样,对kettle已经有了初步认识,并且对这强大的工具产…

CV面试知识点总结

一.卷积操作和图像处理中的中值滤波操作有什么区别&#xff1f; 1.1卷积操作 卷积操作是一种线性操作&#xff0c;通常用于特征的提取&#xff0c;通过卷积核的加权求和来得到新的像素值。1.2中值滤波 原文&#xff1a; https://blog.csdn.net/weixin_51571728/article/detai…