go Antlr重构脚本解释器实现示例

目录
  • 前言
  • Antlr
  • 升级 xjson
  • 总结

前言

在上一个版本实现的脚本解释器 GScript 中实现了基本的四则运算以及 AST 的生成。

当我准备再新增一个 % 取模的运算符时,会发现工作很繁琐而且几乎都是重复的;主要是两步:

  • 需要在词法解析器中新增对 % 符号的支持。
  • 在语法解析器遍历 AST 时对 % token 实现具体逻辑。

其中的词法解析和遍历 AST 完全是重复工作,所以我们可否能够简化这两步呢?

Antlr

Antlr 就是做帮我们解决这些问题的常用工具,利用它我们只需要编写词法文件,然后就可以自动生成词法、语法解析器,并且可以生成不同语言的代码。

下面以 GScript 的示例来看看 antlr 是如何帮我们生成词法分析器的。

func TestGScriptVisitor_Visit_Lexer(t *testing.T) {
	expression := "(2+3) * 2"
	input := antlr.NewInputStream(expression)
	lexer := parser.NewGScriptLexer(input)
	for {
		t := lexer.NextToken()
		if t.GetTokenType() == antlr.TokenEOF {
			break
		}
		fmt.Printf("%s (%q) %d\n",
			lexer.SymbolicNames[t.GetTokenType()], t.GetText(),t.GetColumn())
	}
}
//output:
 ("(") 0
DECIMAL_LITERAL ("2") 1
PLUS ("+") 2
DECIMAL_LITERAL ("3") 3
 (")") 4
MULT ("*") 6
DECIMAL_LITERAL ("2") 8

Antlr 会自动将我们的表达式解析为 token,遍历 token 时还能拿到该 token 所在的代码行数、位置等信息,在编译期间做语法检查非常有用。

要实现这些我们只需要编写词法、语法规则文件即可。

刚才的示例所对应的词法、语法规则如下:

expr
    : '(' expr ')'                        #NestedExpr
    | liter=literal #Liter
    | lhs=expr bop=( MULT | DIV ) rhs=expr #MultDivExpr
    | lhs=expr bop=MOD rhs=expr            #ModExpr
    | lhs=expr bop=( PLUS | SUB ) rhs=expr #PlusSubExpr
    | expr bop=(LE | GE | GT | LT ) expr # GLe
    | expr bop=(EQUAL | NOTEQUAL) expr # EqualOrNot
    ;
DECIMAL_LITERAL:    ('0' | [1-9] (Digits? | '_'+ Digits)) [lL]?;

完整规则:github.com/crossoverJi…

运行:

antlr -Dlanguage=Go -o parser -visitor -no-listener GScript.g4

而我们要实现具体的语法逻辑时只需要实现相关的接口,Antlr 会自动遍历 AST(当然也可以手动控制),同时在访问不同的 AST 节点时会回调我们自己实现的接口,这样我们就能编写自己的语法规则了。

以这里的新增的取模运算为例:

func (v *GScriptVisitor) VisitModExpr(ctx *parser.ModExprContext) interface{} {
	lhs := v.Visit(ctx.GetLhs())
	rhs := v.Visit(ctx.GetRhs())
	return lhs.(int) % rhs.(int)
}

Antlr 回调 VisitModExpr 方法时,便能获取到 % 符号左右两侧的数据,这时只需要做相关运算即可。

基于这个模式这次新增了一个 statement,具体语法如下:

func TestGScriptVisitor_VisitIfElse8(t *testing.T) {
	expression := `
if(3!=(1+2)){
	return 1+3
} else {
	return false
}`
	input := antlr.NewInputStream(expression)
	lexer := parser.NewGScriptLexer(input)
	stream := antlr.NewCommonTokenStream(lexer, 0)
	parser := parser.NewGScriptParser(stream)
	parser.BuildParseTrees = true
	tree := parser.Prog()
	visitor := GScriptVisitor{}
	var result = visitor.Visit(tree)
	fmt.Println(expression, " result:", result)
	assert.Equal(t, result, false)
}

Antlr 还有其他各种优势,比如可以解决:

  • 左递归。
  • 二义性。
  • 优先级。

等问题。

这里也推荐在 IDE 中安装 Antlr 的插件,这样就可以直观的查看 AST 语法树,可以帮我们更好的调试代码。

升级 xjson

借助 GScript 提供的 statementxjson 也提供了有些有意思的写法:

因为 xjson 的四则运算语法没有使用 Antlr 生成,所以为了能支持 GScript 提供的 statement 需要手写许多词法代码。

这也体现了 Antlr 这类前端工具的重要性,效率提升是非常明显的。

总结

借助于 Antlr 后续 GScript 会继续支持函数调用、更完善的类型系统、面向对象等特性;感兴趣的朋友请持续关注。

源码地址:

github.com/crossoverJi…

github.com/crossoverJi…

以上就是go Antlr重构脚本解释器实现示例的详细内容,更多关于go Antlr脚本解释器的资料请关注我们其它相关文章!

(0)

相关推荐

  • go实现脚本解释器gscript

    目录 前言 效果 实现 规划 前言 最近又在重新学习编译原理了,其实两年前也复习过,当初是为了能实现通过 MySQL 的 DDL 生成 Python 中 sqlalchemy 的 model. 相关文章在这里:手写一个词法分析器 虽然完成了相关功能,但现在看来其实实现的比较糙的,而且也只运用到了词法分析:所以这次我的目的是可以通过词法分析->语法分析->语义分析 最终能实现一个功能完善的脚本”语言”. 效果 现在也有了一些阶段性的成果,如下图所示: 目前具备以下基本功能: 变量声明与赋值(只支

  • Go语言中通过Lua脚本操作Redis的方法

    前言 为了在我的一个基本库中降低与Redis的通讯成本,我将一系列操作封装到LUA脚本中,借助Redis提供的EVAL命令来简化操作. EVAL能够提供的特性: 可以在LUA脚本中封装若干操作,如果有多条Redis指令,封装好之后只需向Redis一次性发送所有参数即可获得结果 Redis可以保证Lua脚本运行期间不会有其他命令插入执行,提供像数据库事务一样的原子性 Redis会根据脚本的SHA值缓存脚本,已经缓存过的脚本不需要再次传输Lua代码,减少了通信成本,此外在自己代码中改变Lua脚本,执

  • Golang使用lua脚本实现redis原子操作

    目录 [redis 调用Lua脚本](#redis 调用Lua脚本) [redis+lua 实现评分排行榜实时更新](#redis+lua 实现评分排行榜实时更新) [lua 脚本](#lua 脚本) Golang调用redis+lua示例 byte切片与string的转换优化 redis 调用Lua脚本 EVAL命令 redis调用Lua脚本需要使用EVAL命令. redis EVAL命令格式: redis 127.0.0.1:6379> EVAL script numkeys key [ke

  • go web 预防跨站脚本的实现方式

    一 点睛 现在的网站包含大量的动态内容以提高用户体验,比过去要复杂得多.所谓动态内容,就是根据用户环境和需要,Web 应用程序能够输出相应的内容.动态站点会受到一种名为"跨站脚本攻击"(Cross Site Scripting, 安全专家们通常将其缩写成 XSS)的威胁,而静态站点则完全不受其影响. 攻击者通常会在有漏洞的程序中插入 JavaScript.VBScript. ActiveX 或 Flash 以欺骗用户.一旦得手,他们可以盗取用户帐户信息,修改用户设置,盗取或污染 coo

  • go Antlr重构脚本解释器实现示例

    目录 前言 Antlr 升级 xjson 总结 前言 在上一个版本实现的脚本解释器 GScript 中实现了基本的四则运算以及 AST 的生成. 当我准备再新增一个 % 取模的运算符时,会发现工作很繁琐而且几乎都是重复的:主要是两步: 需要在词法解析器中新增对 % 符号的支持. 在语法解析器遍历 AST 时对 % token 实现具体逻辑. 其中的词法解析和遍历 AST 完全是重复工作,所以我们可否能够简化这两步呢? Antlr Antlr 就是做帮我们解决这些问题的常用工具,利用它我们只需要编

  • Vue自动构建发布脚本的方法示例

    简介 使用cross-env, scp2两个插件完成 cross-env cross-env这是一款运行跨平台设置和使用环境变量的脚本. 为什么需要cross-env? NODE_ENV=production 像这样设置环境变量时,大多数Windows命令提示符都会阻塞 .(Windows上的Bash是例外,它使用本机Bash.)同样,Windows和POSIX命令使用环境变量的方式也有所不同.对于POSIX,您可以使用: $ENV_VAR 和在Windows上可以使用 %ENV_VAR% .

  • shell之定时周期性执行脚本的方法示例

    示例 1.编写测试脚本脚本 time .sh $ cat time.sh #! /bin/bash echo $(date +%s) >> /home/ocean/out.txt 保存完毕后记得给予权限 chmod 777 test.sh 2.添加定时任务 $ crontab -e no crontab for ocean - using an empty one Select an editor. To change later, run 'select-editor'. 1. /bin/n

  • Selenium执行JavaScript脚本的方法示例

    JavaScript是运行在客户端(浏览器)和服务器端的脚本语言,允许将静态网页转换为交互式网页.可以通过 Python Selenium WebDriver 执行 JavaScript 语句,在Web页面中进行js交互.那么js能做的事,Selenium应该大部分也能做.WebDriver是模拟终端用户的交互,所以就不能点击不可见的元素,有时可见元素也不能点击.在这些情况下,我们就可以通过WebDriver 执行JavaScript来点击或者执行页面元素.本文将介绍如何使用 WebDriver

  • jmeter执行python脚本的实现示例

    目录 概要 一.环境准备 1. 测试工具 2. 测试资源 二.实现过程 1. 在jmeter中设置参数 2. 通过jmeter修改py模板 3. 通过jmeter执行py脚本 4. 使用获得的输出信息 三.运行调试 1. 运行前 2. 运行后 概要 本文主要介绍一个通过jmeter执行python脚本的测试场景.主要过程如下: 在jmeter中设置参数: 通过jmeter修改py模板文件,并创建后续需执行的py脚本: 通过jmeter执行py脚本文件,获得py文件的执行输出结果,并设置为变量:

  • C#调用Python脚本的简单示例

    IronPython是一种在 .NET及 Mono上的 Python实现,由微软的 Jim Hugunin所发起,是一个开源的项目,基于微软的 DLR引擎.IronPython的在CodePlex上的主页:http://ironpython.codeplex.com/ 使用场景: 如果你的小伙伴会写Python脚本,而且已经实现大部分项目的功能不需要再用C# 实现.现在缺少窗体,此时Python+C#的组合就可以完美的结局问题啦! 示例: 借由IronPython,就可以利用.NET执行存储在P

  • Java实现的执行python脚本工具类示例【使用jython.jar】

    本文实例讲述了Java实现的执行python脚本工具类.分享给大家供大家参考,具体如下: 这里java中执行python脚本工具类,需要使用jython.jar java中执行python脚本工具类,学习的时候写着玩: import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.List; i

  • 使用 React Hooks 重构类组件的示例详解

    目录 1. 管理和更新组件状态 2. 状态更新后的操作 3. 获取数据 4. 卸载组件时清理副作用 5.  防止组件重新渲染 6. Context API 7. 跨重新渲染保留值 8. 如何向父组件传递状态和方法? 9. 小结 最初,在 React 中可以使用 createClass 来创建组件,后来被类组件所取代.在 React 16.8 版本中,新增的 Hooks 功能彻底改变了我们编写 React 程序的方式,使用 Hooks 可以编写更简洁.更清晰的代码,并为创建可重用的有状态逻辑提供了

  • Tomcat监测脚本的实现示例

    实现效果 通过 Tomcat 监测脚本 按照一定的时间频度来检测Tomcat应用是否正常,如果进程不存在,则启动 Tomcat:如果进程存在而访问不正常,则杀掉进程,再启动 Tomcat. 脚本的主体 vi /home/dev/ctl/tomcat-inspector/image.sh #!/bin/bash echo $(date '+%Y-%m-%d %H:%M:%S') whoami JAVA_HOME=/opt/java/jdk1.8.0_40 PATH=$JAVA_HOME/bin:$

随机推荐