Golang程序中如何解析并执行外部代码

Golang程序中如何解析并执行外部代码 我正在尝试编写一个程序,能够接收文本格式的代码并解析执行。

要解析的程序不需要非常复杂(否则就需要完整的编译器)——只需要一个更简单的编程语言(非常简单就可以)。我考虑使用 “go/parser” 包将代码解析成抽象语法树,不知道是否有人尝试过执行抽象语法树……

谢谢!

5 回复

基本上你想要创建一个解释型语言。不确定 Go 的抽象语法树是否有用:它是为 Go 语言设计的,而 Go 可能对你的需求来说不够简单。

你的需求是什么?你需要解释器用来做什么?

显然已经有一些用 Go 编写的解释器:JavaScript、Lisp、声明式配置语法等。

更多关于Golang程序中如何解析并执行外部代码的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


根据我的理解,你在这里寻找的本质上是一种嵌入式编程语言。Lua 是这方面的绝佳选择,而且有多个 Go 语言的实现!我现在正在用手机,你直接搜索 Lua 和 Go 就能找到相关信息。等我回到电脑前会尝试找一个链接。

func main() {
    fmt.Println("hello world")
}

是的,我明白你的意思。但是不,AST(抽象语法树)是不可执行的。Go编译器在生成可执行文件之前还需要做很多其他工作。实际上,看似直接解释Go文件的"go run"命令,实际上是先编译并将结果写入临时文件再执行(基本上就是执行"go build && ./binary-file"这一套流程)。

简单的解决方案:将Go编译器(即"go"命令)作为程序的依赖项并按需编译。这仍然是一个相当可疑的解决方案,但在某些使用场景下是可行的。

对于普通的插件功能,比动态加载更好的是,你可以直接运行其他二进制文件并通过标准输入输出与它们通信。可以看看"pie"这个项目:https://github.com/natefinch/pie

是否有办法执行抽象语法树?或者能否将抽象语法树转换为某种可执行的形式?

我希望程序的某些部分(逻辑)能够自适应,因此不想将其硬编码到程序(二进制文件)中。将这些逻辑放入Go插件是一种选择。但这仍然需要开发者构建并提供插件。相反,这些逻辑(开始时很简单)可以用这种受限的语法语言编写,并上传到程序中。程序将逻辑转换为能够快速执行的格式(虽然不如完全编译和优化的代码快)…

我能想到的一个类似例子是正则表达式如何首先编译成有限状态机,然后该状态机可以在输入字符串上执行以判断是否匹配(https://golang.org/pkg/regexp/#Compile)

我知道存在安全考虑,因为这可能成为攻击点…但我首先关注功能,之后会确保其安全性。

谢谢!

在Go语言中,使用标准库的go/parsergo/ast包可以解析Go代码并生成抽象语法树(AST),但直接执行AST需要额外的工作,因为Go的AST主要用于静态分析而非动态执行。不过,可以通过以下方法实现一个简单的解释器来执行AST。

以下是一个基本示例,演示如何解析一个简单的算术表达式并执行计算:

package main

import (
	"fmt"
	"go/ast"
	"go/parser"
	"go/token"
	"strconv"
)

// eval 函数递归计算AST节点的值
func eval(expr ast.Expr) (int, error) {
	switch node := expr.(type) {
	case *ast.BasicLit:
		// 处理字面量数字
		if node.Kind != token.INT {
			return 0, fmt.Errorf("expected integer, got %s", node.Kind)
		}
		return strconv.Atoi(node.Value)
	case *ast.BinaryExpr:
		// 递归计算左右操作数
		left, err := eval(node.X)
		if err != nil {
			return 0, err
		}
		right, err := eval(node.Y)
		if err != nil {
			return 0, err
		}
		// 根据操作符执行运算
		switch node.Op {
		case token.ADD:
			return left + right, nil
		case token.SUB:
			return left - right, nil
		case token.MUL:
			return left * right, nil
		case token.QUO:
			if right == 0 {
				return 0, fmt.Errorf("division by zero")
			}
			return left / right, nil
		default:
			return 0, fmt.Errorf("unsupported operator %s", node.Op)
		}
	default:
		return 0, fmt.Errorf("unsupported node type %T", expr)
	}
}

func main() {
	// 要解析的代码字符串
	code := "3 + 5 * 2"
	
	// 创建token.FileSet用于解析
	fset := token.NewFileSet()
	
	// 解析表达式
	expr, err := parser.ParseExpr(code)
	if err != nil {
		fmt.Printf("Parse error: %v\n", err)
		return
	}
	
	// 执行计算
	result, err := eval(expr)
	if err != nil {
		fmt.Printf("Eval error: %v\n", err)
		return
	}
	
	fmt.Printf("Result: %d\n", result) // 输出: Result: 13
}

对于更复杂的语言特性(如变量、函数、控制流),需要扩展eval函数来处理更多AST节点类型。例如,添加对标识符(变量)的支持:

type Environment map[string]int

func evalWithEnv(expr ast.Expr, env Environment) (int, error) {
	switch node := expr.(type) {
	case *ast.Ident:
		// 处理变量引用
		if value, ok := env[node.Name]; ok {
			return value, nil
		}
		return 0, fmt.Errorf("undefined variable: %s", node.Name)
	case *ast.BasicLit:
		if node.Kind != token.INT {
			return 0, fmt.Errorf("expected integer, got %s", node.Kind)
		}
		return strconv.Atoi(node.Value)
	case *ast.BinaryExpr:
		left, err := evalWithEnv(node.X, env)
		if err != nil {
			return 0, err
		}
		right, err := evalWithEnv(node.Y, env)
		if err != nil {
			return 0, err
		}
		switch node.Op {
		case token.ADD:
			return left + right, nil
		case token.SUB:
			return left - right, nil
		case token.MUL:
			return left * right, nil
		case token.QUO:
			if right == 0 {
				return 0, fmt.Errorf("division by zero")
			}
			return left / right, nil
		default:
			return 0, fmt.Errorf("unsupported operator %s", node.Op)
		}
	default:
		return 0, fmt.Errorf("unsupported node type %T", expr)
	}
}

func main() {
	code := "x + 5"
	fset := token.NewFileSet()
	expr, err := parser.ParseExpr(code)
	if err != nil {
		fmt.Printf("Parse error: %v\n", err)
		return
	}
	
	env := Environment{"x": 10}
	result, err := evalWithEnv(expr, env)
	if err != nil {
		fmt.Printf("Eval error: %v\n", err)
		return
	}
	
	fmt.Printf("Result: %d\n", result) // 输出: Result: 15
}

这种方法适用于简单的领域特定语言(DSL)。对于完整的编程语言实现,需要考虑语句、作用域、函数调用等更复杂的结构,这需要处理ast.Fileast.FuncDecl等节点类型,并实现完整的解释器或编译器。

回到顶部