golang实现Lua 5.1虚拟机和编译器的插件库gopher-lua的使用

Golang 实现 Lua 5.1 虚拟机和编译器的插件库 gopher-lua 使用指南

GopherLua 是一个用 Go 语言实现的 Lua 5.1 虚拟机(包含 Lua 5.2 的 goto 语句)。它的目标与 Lua 相同:成为一个具有可扩展语义的脚本语言。它提供了 Go API,可以轻松地将脚本语言嵌入到您的 Go 宿主程序中。

设计原则

  • 成为一个具有可扩展语义的脚本语言
  • 用户友好的 Go API
    • GopherLua API 不是基于栈的 API,而是优先考虑用户友好性而非性能

性能

GopherLua 的性能不算快但也不算太慢,在微基准测试中与 Python3 性能相当或略好。

安装

go get github.com/yuin/gopher-lua

GopherLua 支持 Go1.9 及以上版本。

基本用法

导入包

import (
    "github.com/yuin/gopher-lua"
)

运行 Lua 脚本

L := lua.NewState()
defer L.Close()
if err := L.DoString(`print("hello")`); err != nil {
    panic(err)
}

或者从文件运行:

L := lua.NewState()
defer L.Close()
if err := L.DoFile("hello.lua"); err != nil {
    panic(err)
}

数据模型

GopherLua 中的所有数据都是 LValue 类型。LValue 是一个接口类型,具有以下方法:

  • String() string
  • Type() LValueType

数据类型

类型名 Go 类型 Type() 值 常量
LNilType (常量) LTNil LNil
LBool (常量) LTBool LTrue, LFalse
LNumber float64 LTNumber -
LString string LTString -
LFunction struct 指针 LTFunction -
LUserData struct 指针 LTUserData -
LState struct 指针 LTThread -
LTable struct 指针 LTTable -
LChannel chan LValue LTChannel -

类型检查示例

lv := L.Get(-1) // 获取栈顶的值
if str, ok := lv.(lua.LString); ok {
    // lv 是 LString
    fmt.Println(string(str))
}
if lv.Type() != lua.LTString {
    panic("string required.")
}

完整示例

从 Go 调用 Lua 函数

func main() {
    L := lua.NewState()
    defer L.Close()
    
    // 加载并执行 Lua 脚本
    if err := L.DoString(`
        function add(a, b)
            return a + b
        end
    `); err != nil {
        panic(err)
    }
    
    // 调用 Lua 函数
    if err := L.CallByParam(lua.P{
        Fn: L.GetGlobal("add"),  // 获取函数
        NRet: 1,                 // 指定返回值数量
        Protect: true,           // 如果出错返回错误
    }, lua.LNumber(10), lua.LNumber(20)); err != nil {
        panic(err)
    }
    
    // 获取返回值
    ret := L.Get(-1)
    L.Pop(1)  // 从栈中移除返回值
    
    if num, ok := ret.(lua.LNumber); ok {
        fmt.Println("Result:", float64(num)) // 输出: Result: 30
    }
}

从 Lua 调用 Go 函数

func Double(L *lua.LState) int {
    lv := L.ToInt(1)             // 获取第一个参数
    L.Push(lua.LNumber(lv * 2))  // 将结果压入栈
    return 1                     // 返回结果数量
}

func main() {
    L := lua.NewState()
    defer L.Close()
    
    // 将 Go 函数注册为 Lua 全局函数
    L.SetGlobal("double", L.NewFunction(Double))
    
    // 调用 Lua 代码
    if err := L.DoString(`print(double(20))`); err != nil {
        panic(err)
    }
    // 输出: 40
}

创建自定义模块

mymodule.go:

package mymodule

import "github.com/yuin/gopher-lua"

func Loader(L *lua.LState) int {
    // 注册函数到表
    mod := L.SetFuncs(L.NewTable(), exports)
    // 注册其他内容
    L.SetField(mod, "name", lua.LString("value"))

    // 返回模块
    L.Push(mod)
    return 1
}

var exports = map[string]lua.LGFunction{
    "myfunc": myfunc,
}

func myfunc(L *lua.LState) int {
    return 0
}

main.go:

package main

import (
    "./mymodule"
    "github.com/yuin/gopher-lua"
)

func main() {
    L := lua.NewState()
    defer L.Close()
    L.PreloadModule("mymodule", mymodule.Loader)
    if err := L.DoFile("main.lua"); err != nil {
        panic(err)
    }
}

main.lua:

local m = require("mymodule")
m.myfunc()
print(m.name)  -- 输出: value

协程支持

L := lua.NewState()
defer L.Close()

// 加载协程函数
L.DoString(`
    function coro()
        local i = 0
        while true do
            coroutine.yield(i)
            i = i+1
        end
        return i
    end
`)

// 创建新线程
co, _ := L.NewThread()
fn := L.GetGlobal("coro").(*lua.LFunction)

// 恢复协程执行
for {
    st, err, values := L.Resume(co

更多关于golang实现Lua 5.1虚拟机和编译器的插件库gopher-lua的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于golang实现Lua 5.1虚拟机和编译器的插件库gopher-lua的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


使用gopher-lua实现Golang中的Lua 5.1虚拟机

gopher-lua是一个纯Go实现的Lua 5.1虚拟机,它允许在Go应用程序中嵌入Lua脚本功能。下面我将详细介绍如何使用这个库。

基本安装

首先安装gopher-lua:

go get github.com/yuin/gopher-lua

基本用法示例

package main

import (
	"fmt"
	"github.com/yuin/gopher-lua"
)

func main() {
	// 创建新的Lua虚拟机
	L := lua.NewState()
	defer L.Close() // 确保虚拟机被关闭

	// 执行简单的Lua代码
	if err := L.DoString(`print("Hello from Lua!")`); err != nil {
		panic(err)
	}

	// 更复杂的示例
	script := `
		function add(a, b)
			return a + b
		end
		
		result = add(10, 20)
		print("The result is: "..result)
	`

	if err := L.DoString(script); err != nil {
		panic(err)
	}

	// 从Go中获取Lua变量
	lv := L.GetGlobal("result")
	if lv.Type() != lua.LTNumber {
		panic("result is not a number")
	}
	if v, ok := lv.(lua.LNumber); ok {
		fmt.Printf("Result from Go: %.0f\n", v)
	}
}

Go与Lua交互

从Go调用Lua函数

func main() {
	L := lua.NewState()
	defer L.Close()

	script := `
		function greet(name)
			return "Hello, " .. name
		end
	`

	if err := L.DoString(script); err != nil {
		panic(err)
	}

	// 调用Lua函数
	if err := L.CallByParam(lua.P{
		Fn:      L.GetGlobal("greet"), // 获取函数
		NRet:    1,                    // 指定返回值数量
		Protect: true,                 // 如果为true,调用在panic时会被恢复
	}, lua.LString("Gopher")); err != nil {
		panic(err)
	}

	// 获取返回值
	ret := L.Get(-1)
	L.Pop(1) // 从栈中移除返回值
	fmt.Println(ret.String())
}

从Lua调用Go函数

func main() {
	L := lua.NewState()
	defer L.Close()

	// 注册Go函数到Lua
	L.SetGlobal("add", L.NewFunction(func(L *lua.LState) int {
		a := L.ToInt(1) // 获取第一个参数
		b := L.ToInt(2) // 获取第二个参数
		L.Push(lua.LNumber(a + b)) // 压入返回值
		return 1 // 返回值的数量
	}))

	// 在Lua中调用Go函数
	if err := L.DoString(`
		local result = add(10, 20)
		print("10 + 20 = " .. result)
	`); err != nil {
		panic(err)
	}
}

加载Lua模块

gopher-lua支持加载预编译的Lua模块:

func main() {
	L := lua.NewState()
	defer L.Close()

	// 预加载模块
	L.PreloadModule("mymodule", func(L *lua.LState) int {
		mod := L.SetFuncs(L.NewTable(), map[string]lua.LGFunction{
			"hello": func(L *lua.LState) int {
				L.Push(lua.LString("Hello from module!"))
				return 1
			},
		})
		L.Push(mod)
		return 1
	})

	// 在Lua中使用模块
	if err := L.DoString(`
		local m = require("mymodule")
		print(m.hello())
	`); err != nil {
		panic(err)
	}
}

性能考虑

  1. 复用Lua虚拟机:创建和销毁Lua虚拟机开销较大,应该尽可能复用
  2. 预编译脚本:对于频繁执行的脚本,可以预编译
  3. 限制执行时间:对于不可信脚本,应该设置执行超时
func main() {
	L := lua.NewState()
	defer L.Close()

	// 预编译脚本
	chunk, err := L.LoadString(`return 1 + 1`)
	if err != nil {
		panic(err)
	}

	// 多次执行预编译的脚本
	for i := 0; i < 5; i++ {
		L.Push(chunk)
		if err := L.PCall(0, 1, nil); err != nil {
			panic(err)
		}
		ret := L.Get(-1)
		L.Pop(1)
		fmt.Println(ret)
	}
}

错误处理

func main() {
	L := lua.NewState()
	defer L.Close()

	// 设置错误处理函数
	L.SetGlobal("error_handler", L.NewFunction(func(L *lua.LState) int {
		err := L.ToString(1)
		fmt.Printf("Lua error: %s\n", err)
		return 0
	}))

	// 在Lua中使用pcall捕获错误
	if err := L.DoString(`
		function risky()
			error("something went wrong")
		end
		
		local ok, err = pcall(risky)
		if not ok then
			error_handler(err)
		end
	`); err != nil {
		panic(err)
	}
}

实际应用场景

gopher-lua适用于:

  • 游戏脚本系统
  • 业务规则引擎
  • 动态配置系统
  • 插件系统

通过gopher-lua,你可以轻松地在Go应用中嵌入Lua脚本功能,实现高度灵活的配置和扩展能力。

回到顶部