自己动手写编译器:属性语法的实现

上一节我们研究了增强语法,本节我们看看何为属性语法。属性语法实则是在语法规则上附带上一些重要的解析信息,随着语法解析的进行,我们可以利用附带的解析信息去进行一系列操作,例如利用解析信息实现代码生成。我们先看属性语法的一个实例:

NUMBER("156", 156)

NUMBER 是语法解析中的终结符,他附带有两个属性,一个是该标签对应字符串的内容“156”,另一个是他对应的数值也就是 156,如果符号是 ID,也就是变量,那么它可以附带一个属性就是一个指针,指向符号表的入口,该符号表包含了该变量的字符串名称,该变量对应的数据等等。

属性信息分为两种,一种是继承属性,也就是属性从语法表达式箭头左边的符号传递给右边的符号,另一种是综合属性,属性信息从箭头右边符号汇总后传递给左边符号。从前面代码中我们看到,语法解析本质上就是函数的调用,例如语法:

expr -> term expr_prime

对应的代码实现就是:

expr() {term()expr_prime()}

对于继承属性,那就是父函数expr 在调用是被输入了某些参数,这些参数再传递给里面的 term,和 expr_prime,例如:

expr(param) {term(param)expr_prime(param)
}

而综合属性就是子函数有返回值,父函数获取子函数的返回值后综合起来处理,例如:

expr() {val_term := term()val_expr_prime := expr_prime(param)do_something(val_term, val_expr_prime)}

在上一节我们使用增强语法来生成代码时,代码生成所需要的信息例如寄存器等,是从全局函数或全局变量(例如全局寄存器数组等)中获取,在属性语法中我们就可以把这些信息作为参数传递给特定的语法解析函数,这样在生成代码时就能更灵活。我们看具体的实现你就能更明白什么叫属性语法,我们还是利用上一节识别算术表达式的语法:

stmt -> epsilon | expr SEMI stmt
expr -> term expr_prime
expr_prime -> PLUS term expr_prime
term -> factor term_prime
term_prime -> MUL factor term_prime | epsilon
factor -> NUMBER | LEFT_PAREN expr RIGHT_PAREN

在原有项目中创建新文件夹 attribute_parser,在里面创建文件 attribute_parser.go,添加代码如下:

package attribute_parserimport ("fmt""lexer"
)type AttributeParser struct {parserLexer  lexer.LexerreverseToken []lexer.Token//用于存储虚拟寄存器的名字registerNames []string//存储当前已分配寄存器的名字regiserStack []string//当前可用寄存器名字的下标registerNameIdx int
}func NewAttributeParser(parserLexer lexer.Lexer) *AttributeParser {return &AttributeParser{parserLexer:     parserLexer,reverseToken:    make([]lexer.Token, 0),registerNames:   []string{"t0", "t1", "t2", "t3", "t4", "t5", "t6", "t7"},regiserStack:    make([]string, 0),registerNameIdx: 0,}
}func (a *AttributeParser) putbackToken(token lexer.Token) {a.reverseToken = append(a.reverseToken, token)
}func (a *AttributeParser) getToken() lexer.Token {//先看看有没有上次退回去的 tokenif len(a.reverseToken) > 0 {token := a.reverseToken[len(a.reverseToken)-1]a.reverseToken = a.reverseToken[0 : len(a.reverseToken)-1]return token}token, err := a.parserLexer.Scan()if err != nil && token.Tag != lexer.EOF {sErr := fmt.Sprintf("get token with err:%s\n", err)panic(sErr)}return token
}func (a *AttributeParser) match(tag lexer.Tag) bool {token := a.getToken()if token.Tag != tag {a.putbackToken(token)return false}return true
}func (a *AttributeParser) newName() string {//返回一个寄存器的名字if a.registerNameIdx >= len(a.registerNames) {//没有寄存器可用panic("register name running out")}name := a.registerNames[a.registerNameIdx]a.registerNameIdx += 1return name
}func (a *AttributeParser) freeName(name string) {//释放当前寄存器名字if a.registerNameIdx > len(a.registerNames) {panic("register name index out of bound")}if a.registerNameIdx == 0 {panic("register name is full")}a.registerNameIdx -= 1a.registerNames[a.registerNameIdx] = name
}func (a *AttributeParser) Parse() {a.stmt()
}func (a *AttributeParser) stmt() {for a.match(lexer.EOF) != true {t := a.newName()a.expr(t)a.freeName(t)if a.match(lexer.SEMI) != true {panic("missing ; at the end of expression")}}
}func (a *AttributeParser) expr(t string) {a.term(t)a.expr_prime(t)
}func (a *AttributeParser) expr_prime(t string) {if a.match(lexer.PLUS) {t2 := a.newName()a.term(t2)fmt.Printf("%s += %s\n", t, t2)a.freeName(t2)a.expr_prime(t)}
}func (a *AttributeParser) term(t string) {a.factor(t)a.term_prime(t)
}func (a *AttributeParser) term_prime(t string) {if a.match(lexer.MUL) {t2 := a.newName()a.factor(t2)fmt.Printf("%s *= %s\n", t, t2)a.freeName(t2)a.term_prime(t)}
}func (a *AttributeParser) factor(t string) {if a.match(lexer.NUM) {fmt.Printf("%s = %s\n", t, a.parserLexer.Lexeme)} else if a.match(lexer.LEFT_BRACKET) {a.expr(t)if a.match(lexer.RIGHT_BRACKET) != true {panic("missing ) for expr")}}
}

我们可以看到 AttributeParser 跟我们前面实现的 AugmentedParser 区别不大,一个明显区别是,解析函数接受一个传进来的参数,这个参数可以看做是语法属性,他由语法表达式左边符号对应的函数创建然后传递给右边符号对应的函数。我们看如下代码:

func (a *AttributeParser) stmt() {for a.match(lexer.EOF) != true {t := a.newName()a.expr(t)a.freeName(t)if a.match(lexer.SEMI) != true {panic("missing ; at the end of expression")}}
}

stmt 函数在调用时创建了一个寄存器名称,然后调用 expr 时将该名称作为参数传入,在语法表达上相当于:

stmt_(t) -> expr_(t) SEMI stmt

其中 t 是左边 stmt 符号附带的参数,他将该参数传递给右边符号 expr,expr 利用该传过来的符号在语法解析时进行代码生成。从上面代码我们也能看出,它实际上是增强语法和属性语法的结合体,例如代码将属性作为参数传入,同时在解析的过程中又在特定位置执行特定步骤,因此上面的解析过程其实可以对应成如下的“增强属性语法”:

stmt -> epsilon | {t=newName()} expt_(t) SEMI stmt
expr_(t) -> term_(t) expr_prime_(t)
expr_prime_(t) -> PLUS {t2 = newName()} term_(t2) {print(%s+=%s\n",t,t2) freenName(t2)} expr_prime_(t) | epsilon
term_(t) -> factor term_prime
term_prime_(t) -> MUL {t2 = newName()} factor_(t2) {print("%s+=%s\n",t,t2) freeName(t2)} term_prime_(t)
factor_(t) -> NUM {print("%s=*%s\n",t, lexeme)} | LEFT_PAREN expr_(t) RIGHT_PAREN

最后我们在 main.go 中调用属性语法解析器看看运行结果:

package mainimport ("attribute_parser""lexer"
)func main() {exprLexer := lexer.NewLexer("1+2*(4+3);")attributeParser := attribute_parser.NewAttributeParser(exprLexer)attributeParser.Parse()
}

上面代码运行后结果如下:

t0 = 1
t1 = 2
t2 = 4
t3 = 3
t2 += t3
t1 *= t2
t0 += t1

可以看到生成的结果跟我们上一节一样。更多内容请在 b 站搜索 coding 迪斯尼。代码下载:
https://github.com/wycl16514/compiler-attribute-grammar.git

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

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

相关文章

免费ai绘画软件选择哪个?

对于免费AI绘画软件的选择,因为每个软件都有其独特的优点和适用场景,可以根据个人的需求和技能水平来决定。以下是被广泛认可的AI绘画软件: 1、建e网AI-一款为建筑室内设计师提供AI绘图的智能工具,具有文字生图,方案优…

Python学习之路-Tornado基础:深入Tornado

Python学习之路-Tornado基础:深入Tornado Application settings 前面的学习中,我们在创建tornado.web.Application的对象时,传入了第一个参数——路由映射列表。实际上Application类的构造函数还接收很多关于tornado web应用的配置参数,在…

值得收藏的上千个涉及各个领域各个方面的免费的API接口服务,全网盘点并统计了网上诸多的免费API

值得收藏的上千个涉及各个领域各个方面的免费的API接口服务,全网盘点并统计了网上诸多的免费API。 一位开发者在GitHub上维护的免费API文档,不定期收录了互联网上开放的各种API接口。这些接口有些是来自第三方服务,你只需要在第三方注册成为会…

如何使用 FOFA 搜索引擎保姆级教程(附链接)

一、介绍 FOFA(Fingerprinting Organizations with Advanced Tools)是一家总部位于中国的网络安全公司提供的一款网络搜索引擎,专注于帮助用户收集和分析互联网上的设备和服务信息。FOFA 的主要特点包括: 设备指纹识别&#xff1…

面试150 颠倒二进制位 位运算分治 逻辑右移

Problem: 190. 颠倒二进制位 文章目录 思路复杂度位运算分治法 思路 👨‍🏫 参考题解 >>>:逻辑右移(符号位一起移动,高位补零) 复杂度 时间复杂度: O ( log ⁡ n ) O(\log{n}) O(logn) 空间…

Win10系统搭建个人hMailServer邮件服务结合内网穿透远程发邮件

文章目录 前言1. 安装hMailServer2. 设置hMailServer3. 客户端安装添加账号4. 测试发送邮件5. 安装cpolar6. 创建公网地址7. 测试远程发送邮件8. 固定连接公网地址9. 测试固定远程地址发送邮件 前言 hMailServer 是一个邮件服务器,通过它我们可以搭建自己的邮件服务,通过cpola…

基于IATF思想构建网络安全治理体系

文章目录 前言一、企业网络安全现状(一)防御碎片化。(二)常见网络安全问题频发。(三)安全意识教育难度大。二、“纵深防御”式综合治理体系建设方案(一)三个核心要素(二)四个保障领域1、网络和基础设施2、区域边界3、计算环境4、支撑性基础设施总结前言 近年来,国家…

计算机网络_1.6.1 常见的三种计算机网络体系结构

1.6.1 常见的三种计算机网络体系结构 1、OSI(七层协议)标准失败的原因2、TCP/IP参考模型3、三种网络体系结构对比 笔记来源: B站 《深入浅出计算机网络》课程 1、OSI(七层协议)标准失败的原因 (1&#xf…

Django的web框架Django Rest_Framework精讲(四)

文章目录 1.DRF认证组件Authentication2.权限Permissions3.限流Throttling4.过滤Filtering5.排序6.分页Pagination7.异常处理 Exceptions8.自动生成接口文档 大家好,我是景天,今天我们继续DRF的最后一讲,Django的web框架Django Rest_Framewor…

STM32--揭秘中断(简易土货版)

抢占优先级响应优先级 视频学习--中断​​​​​​​

正则表达式可视化工具regex-vis

什么是正则表达式 ? 正则表达式是对字符串操作的一种逻辑公式,就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个“规则字符串”,这个“规则字符串”用来表达对字符串的一种过滤逻辑。【百度百科】 正则表达式用简短…

Java和JavaScript的区别与联系

JavaScript 和 Java 是两种完全不同的编程语言,它们的名称虽然相似,但并没有直接的关系。 JavaScript 的名字来源于它最初被命名为 LiveScript,并在 1995 年被 Netscape 公司更名为 JavaScript,以吸引更多使用 Java 的开发者。这…

Ubuntu下的文件压缩与解压:gzip、bzip2、tar、rar 和 zip详解

Ubuntu下的文件压缩与解压:gzip、bzip2、tar、rar 和 zip详解 在 Ubuntu 操作系统中,文件压缩与解压是日常操作中常见的任务之一。本文将介绍五种常用的压缩和解压工具:gzip、bzip2、tar、rar 和 zip,在 Ubuntu 中的使用方法和常…

【图论】基环树

基环树其实并不是树,是指有n个点n条边的图,我们知道n个点n-1条边的连通图是树,再加一条边就会形成一个环,所以基环树中一定有一个环,长下面这样: 由基环树可以引申出基环内向树和基环外向树 基环内向树如…

【新书推荐】5.2 位运算符

本节必须掌握的知识点: 位运算 示例十七 代码分析 汇编解析 5.2.1 位运算 位运算符如表5-2所示: 运算符 作用 示例 & 按位与 两个操作数同时为1,结果为1; | 按位或 两个操作数只要有一个为1,结果就为1&a…

【lesson38】让minishell支持重定向

文章目录 minishell支持重定向minishell完整代码 minishell支持重定向 支持重定向的核心逻辑: 1.分析字符串是否含有重定向的符号,并且提取文件名。 #define INPUT_REDIR 0 //输入重定向 #define OUTPUT_REDIR 1 //输出重定向 #define APPEND_REDIR…

ROS方向第二次总汇报

文章目录 1.本阶段学习内容:2.过程中遇到的问题及解决方法: 本篇链接:https://blog.csdn.net/m0_54470078/article/details/136019940?spm1001.2014.3001.5501 本人主页::https://blog.csdn.net/m0_54470078?spm1011.2124.3001.5343 1.本阶段学习内容&…

使用Rsync软件工具将Linux服务器上的文件同步到Windows 服务器

使用Rsync软件工具将linux服务器上的文件同步到Windows 服务器 测试环境(推送:Linux 推送到Windows): Windows 服务器:172.20.26.97 Linux服务器:172.20.26.34 一、在172.20.26.97上安装cwRsyncServer-v…

电脑上常见的绘图软件有哪些?

现在在电脑上绘图很流行,不仅可以随时更改,还可以提高绘图效率,绘图软件中有很多工具。市场上的计算机绘图软件种类繁多。包括艺术设计、工业绘图和3D绘图。那么每个绘图软件都有自己的特点。那么,哪个更适合计算机绘画软件呢&…

React Hook之钩子调用规则(不在循环、条件判断或者嵌套函数中调用)

文章目录 React Hook之钩子调用规则(不在循环、条件判断或者嵌套函数中调用)错误使用案例案例具体解决方法 React Hook之钩子调用规则(不在循环、条件判断或者嵌套函数中调用) hooks使用规则 只能在函数最外层调用 Hook。不要在…