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

news2024/11/23 13:34:27

在前面一系列章节中,我们完成了词法解析的各种算法。包括解析正则表达式字符串,构建 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_line

import (
	"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_line

import (
	"fmt"
	"nfa"
	"time"
)

type CommandLine struct {
	lexerReader  *nfa.LexReader
	parser       *nfa.RegParser
	nfaConverter *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.nstates
	charsPrinted := 0
	for 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 := -1
		for 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 char
	fmt.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] = 0
			fmt.Fprintf(n.fp, "\t0  ")
		} else {
			anchor := 4
			if 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 main

import (
	"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.coloradmin.cn/o/1054376.html

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

相关文章

CCF-CSP真题《202309-1 坐标变换(其一)》思路+python,c++,java满分题解

想查看其他题的真题及题解的同学可以前往查看&#xff1a;CCF-CSP真题附题解大全 试题编号&#xff1a;202309-1试题名称&#xff1a;坐标变换&#xff08;其一&#xff09;时间限制&#xff1a;1.0s内存限制&#xff1a;512.0MB问题描述&#xff1a; 问题描述 对于平面直角坐标…

11链表-迭代与递归

目录 LeetCode之路——206. 反转链表 分析&#xff1a; 解法一&#xff1a;迭代 解法二&#xff1a;递归 LeetCode之路——206. 反转链表 给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表。 示例 1&#xff1a; 输入&#xff1a;head […

git你学“废”了吗?——git撤销操作指令详解

git你学“废”了吗&#xff1f;——git撤销操作指令详解&#x1f60e; 前言&#x1f64c;撤销的本质撤销修改情况一&#xff1a;撤销工作区的修改方式一&#xff1a;方式二&#xff1a;演示截图&#xff1a; 撤销修改情况二&#xff1a;撤销暂存区和工作区的修改操作截图&#…

【Java 进阶篇】JDBC DriverManager 详解

JDBC&#xff08;Java Database Connectivity&#xff09;是 Java 标准库中用于与数据库进行交互的 API。它允许 Java 应用程序连接到各种不同的数据库管理系统&#xff08;DBMS&#xff09;&#xff0c;执行 SQL 查询和更新操作&#xff0c;以及处理数据库事务。在 JDBC 中&am…

链表经典面试题(一)

面试题 1.反转链表的题目2.反转链表的图文分析3.反转链表的代码实现 1.反转链表的题目 2.反转链表的图文分析 我们在实现反转链表的时候,是将后面的元素变前面&#xff0c;前面的元素变后面&#xff0c;那么我们是否可以理解为&#xff0c;用头插法的思想来完成反转链表呢&…

力扣:116. 填充每个节点的下一个右侧节点指针(Python3)

题目&#xff1a; 给定一个 完美二叉树 &#xff0c;其所有叶子节点都在同一层&#xff0c;每个父节点都有两个子节点。二叉树定义如下&#xff1a; struct Node {int val;Node *left;Node *right;Node *next; } 填充它的每个 next 指针&#xff0c;让这个指针指向其下一个右侧…

计组--总线

一、概念 总线是一组能为多个部件分时共享的公共信息传送线路。 共享是指总线上可以挂接多个部件&#xff0c;各个部件之间互相交换的信息都可以通过这组线路分时共享。 分时是指同一时刻只允许有一个部件向总线发送信息&#xff0c;如果系统中有多个部件&#xff0c;则它们…

qt常用控件1

QLabel QLabel用于显示文本或图像。不提供用户交互功能。标签的视觉外观可以通过多种方式进行配置&#xff0c;并且可用于为另一个小组件指定焦点助记键。 常用API介绍&#xff1a; 获取对应的文本信息&#xff1a; 设置对其方式&#xff1a; 设置能否进行换行 获取及设置标…

mysql面试题9:MySQL中的SQL常见的查询语句有哪些?有哪些对SQL语句优化的方法?

该文章专注于面试,面试只要回答关键点即可,不需要对框架有非常深入的回答,如果你想应付面试,是足够了,抓住关键点 面试官:MySQL中的SQL常见的查询语句有哪些? 常见的SQL查询语句包括: SELECT:用于从一个或多个表中获取数据。 FROM:指定要查询的表名或视图名。 WHER…

ssh爆破分析

1. 2.日志分析 1.系统账号信息 2.确认攻击情况 3.管理员登录情况 4.处理措施

网络基础入门(认识网络 网络传输 概念举例详解)

本篇文章主要是对网络初学的概念进行解释&#xff0c;可以让你对网络有一个大概整体的认知。 文章目录 一、简单认识网络 1、1 什么是网络 1、2 网络分类 二、网络模型 2、1OSI七层模型 2、1、1 简单认识协议 2、1、2 OSI七层模型解释 2、2 TCP/IP五层(或四层)模型 三、网络传…

【生物信息学】计算图网络中节点的中心性指标:聚集系数、介数中心性、度中心性

目录 一、实验介绍 二、实验环境 1. 配置虚拟环境 2. 库版本介绍 3. IDE 三、实验内容 0. 导入必要的工具 1. 生成邻接矩阵simulate_G: 2. 计算节点的聚集系数 CC(G): 3.计算节点的介数中心性 BC(G) 4. 计算节点的度中心性 DC(G) 5. 综合centrality(G) 6. 代…

《 新手》web前端(axios)后端(java-springboot)对接简解

文章目录 <font color red>1.何为前后端对接?2.对接中关于http的关键点2.1. 请求方法2.2. 请求参数设置简解&#xff1a; 3.对接中的跨域(CROS)问题**为什么后端处理跨域尽量在业务之前进行&#xff1f;**3.总结 1.何为前后端对接? “前后端对接” 是指前端和后端两个…

腾讯云中使用ubuntu安装属于自己的overleaf

在自己的云服务器上安装overleaf的需求是从写论文开始的&#xff0c;总担心自己的论文放在一个网站上被泄露&#xff0c;所以想要在自己的服务器上安装自己的overleaf&#xff0c;正好手边有一个云服务器&#xff0c;现在开始。 配置腾讯云 因为使用overleaf的优势就是在不同…

【小沐学前端】Node.js实现基于Protobuf协议的UDP通信(UDP/TCP)

文章目录 1、简介1.1 node1.2 Protobuf 2、下载和安装2.1 node2.2 Protobuf2.2.1 安装2.2.2 工具 3、node 代码示例3.1 HTTP3.2 UDP单播3.4 UDP广播 4、Protobuf 代码示例4.1 例子: awesome.proto4.1.1 加载.proto文件方式4.1.2 加载.json文件方式4.1.3 加载.js文件方式 4.2 例…

【知识梳理】多级页表的原理分析【地址形成过程】【扩充思考】

多级页表的地址形成过程 首先每个进程中都至少有一个页表&#xff08;段页式可以有多个页表&#xff09;&#xff0c;都有一个页表基地址寄存器&#xff08;PTBR&#xff09;&#xff0c;以下针对三级页表进行分析。 level1&#xff1a;PTBR代表的是一级页表的基地址&#xf…

链表经典面试题(三)

合并两个有序链表 1.题目2.图文分析3.代码实现 1.题目 2.图文分析 我们通过两个连接和指向的中介&#xff0c;我们可以将两个链表依次连接起来&#xff0c;并且当其中一个链表走完后&#xff0c;另一个链表剩余的值都会比另一个链表的值大&#xff0c;所以直接将指向中介指向他…

小谈设计模式(11)—模板方法模式

小谈设计模式&#xff08;11&#xff09;—模板方法模式 专栏介绍专栏地址专栏介绍 模板方法模式角色分类抽象类&#xff08;Abstract Class&#xff09;具体子类&#xff08;Concrete Class&#xff09;抽象方法&#xff08;Abstract Method&#xff09;具体方法&#xff08;C…

java复习回顾

文章目录 0 开发工具1. 对象和类、三大特性2. 成员/实例变量和实例变量&#xff08;this关键字&#xff09;3. 方法重载overload4. 构造方法和this关键字5. 继承6. 访问修饰符7. 方法重写8. 继承下的构造执行9. 多态9.1 向上转型9.2 向下转型9.3 多态的应用 0 开发工具 Maven是…

[DS资源推荐] Data Structure 严书配套代码

下图引入自康建伟老师博客 Github地址 使用说明&#xff1a;康老师博客 使用感受&#xff1a;Orz&#xff01;非常非常非常全面&#xff01;终于能看得下去严书了…