浅谈如何使用 github.com/yuin/gopher-lua

打印 上一主题 下一主题

主题 765|帖子 765|积分 2295

最近熟悉 go 项目时,发现项目中有用到 github.com/yuin/gopher-lua这个包,之前并没有接触过,特意去看了官方文档和找了些网上的资料,特此记录下。
本次介绍计划分为两篇文章,这一次主要介绍  github.com/yuin/gopher-lua 这个包的介绍以及基础使用,下一边将介绍  github.com/yuin/gopher-lua 是如何在项目中使用的。如有不对的地方,请不吝赐教,谢谢。
文章中的 gopher-lua 如果没有特别说明,即为:github.com/yuin/gopher-lua。
1、 gopher-lua 基础介绍

我们先开看看官方是如何介绍自己的:
  1. GopherLua is a Lua5.1(+ goto statement in Lua5.2) VM and compiler written in Go. GopherLua has a same goal with Lua: Be a scripting language with extensible semantics . It provides Go APIs that allow you to easily embed a scripting language to your Go host programs.
复制代码
GopherLua是一个Lua5.1(Lua5.2中的+goto语句)虚拟机和用Go编写的编译器。GopherLua与Lua有着相同的目标:成为一种具有可扩展语义的脚本语言。它提供了Go API,允许您轻松地将脚本语言嵌入到Go主机程序中。
看上面的翻译还是有点抽象,说说自己的理解。  github.com/yuin/gopher-lua  是一个纯 Golang 实现的 Lua 虚拟机,它能够很轻松的在 go 写的程序中调用 lua 脚本。另外提一嘴,使用插件后,也能够在 lua 脚本中调用 go 写好的代码。挺秀的!
接下来我们看一看,   github.com/yuin/gopher-lua  的性能如何,这里就直接引用官方自己做的测试来介绍。详情见 wiki page  链接。点进链接过后,发现性能还不错,执行效率和性能仅比 C 实现的 bindings 差点。
官方测试例子是生成斐波那契数列,测试执行结果如下:
progtimeanko182.73sotto173.32sgo-lua8.13sPython3.45.84sGopherLua5.40slua5.1.41.71s2、 gopher-lua 基础介绍

下面的介绍,都是基于 v1.1.0 版本进行的。
  1. go get github.com/yuin/gopher-lua@v1.1.0
复制代码
Go的版本需要 >= 1.9
2.1 gopher-lua 中的 hello world

这里写一个简单的程序,了解 gopher-lua 是如何使用的。
  1. package main
  2. import (
  3.         lua "github.com/yuin/gopher-lua"
  4. )
  5. func main() {
  6.         // 1、创建 lua 的虚拟机
  7.         L := lua.NewState()
  8.         // 执行完毕后关闭虚拟机
  9.         defer L.Close()
  10.         // 2、加载fib.lua
  11.         if err := L.DoString(`print("hello world")`); err != nil {
  12.                 panic(err)
  13.         }
  14. }
复制代码
执行结果:
  1. hello world
复制代码
看到这里,感觉没啥特别的地方,接下来,我们看一看 gopher-lua  如何调用事先写好的 lua脚本。
fib.lua 脚本内容:
  1. function fib(n)
  2.     if n < 2 then return n end
  3.     return fib(n-1) + fib(n-2)
  4. end
复制代码
main.go
  1. package main
  2. import (
  3.         "fmt"
  4.         lua "github.com/yuin/gopher-lua"
  5. )
  6. func main() {
  7.         // 1、创建 lua 的虚拟机
  8.         L := lua.NewState()
  9.         defer L.Close()
  10.         // 加载fib.lua
  11.         if err := L.DoFile(`fib.lua`); err != nil {
  12.                 panic(err)
  13.         }
  14.         // 调用fib(n)
  15.         err := L.CallByParam(lua.P{
  16.                 Fn:      L.GetGlobal("fib"), // 获取fib函数引用
  17.                 NRet:    1,                  // 指定返回值数量
  18.                 Protect: true,               // 如果出现异常,是panic还是返回err
  19.         }, lua.LNumber(10)) // 传递输入参数n
  20.         if err != nil {
  21.                 panic(err)
  22.         }
  23.         // 获取返回结果
  24.         ret := L.Get(-1)
  25.         // 从堆栈中扔掉返回结果
  26.     // 这里一定要注意,不调用此方法,后续再调用 L.Get(-1) 获取的还是上一次执行的结果
  27.     // 这里大家可以自己测试下
  28.         L.Pop(1)
  29.         // 打印结果
  30.         res, ok := ret.(lua.LNumber)
  31.         if ok {
  32.                 fmt.Println(int(res))
  33.         } else {
  34.                 fmt.Println("unexpected result")
  35.         }
  36. }
复制代码
执行结果:
55
从上面我们已经能够感受到部分  gopher-lua  的魅力了。接下来,我们就一起详细的学习学习  gopher-lua  。
2.2 gopher-lua 中的数据类型

All data in a GopherLua program is an LValue . LValue is an interface type that has following methods.
GopherLua程序中的所有数据都是一个LValue。LValue是一种具有以下方法的接口类型。


  • String() string
  • Type() LValueType
  1. // value.go:29
  2. type LValue interface {
  3.         String() string
  4.         Type() LValueType
  5.         // to reduce `runtime.assertI2T2` costs, this method should be used instead of the type assertion in heavy paths(typically inside the VM).
  6.         assertFloat64() (float64, bool)
  7.         // to reduce `runtime.assertI2T2` costs, this method should be used instead of the type assertion in heavy paths(typically inside the VM).
  8.         assertString() (string, bool)
  9.         // to reduce `runtime.assertI2T2` costs, this method should be used instead of the type assertion in heavy paths(typically inside the VM).
  10.         assertFunction() (*LFunction, bool)
  11. }
复制代码
上面来自官方的介绍,接下来我们看看 gopher-lua  支持那些数据类型。
Type nameGo typeType() valueConstantsLNilType(constants)LTNilLNilLBool(constants)LTBoolLTrue, LFalseLNumberfloat64LTNumber-LStringstringLTString-LFunctionstruct pointerLTFunction-LUserDatastruct pointerLTUserData-LStatestruct pointerLTThread-LTablestruct pointerLTTable-LChannelchan LValueLTChannel-具体的实现,大家有兴趣,可以自己去看看源码,这里就不做分析了。
那我们是如何知道 go 调用 lua 函数后,得到结果的类型呢?我们可以通过以下方式来知道:
  1. package main
  2. import (
  3.         "fmt"
  4.         lua "github.com/yuin/gopher-lua"
  5. )
  6. func main() {
  7.         // 1、创建 lua 的虚拟机
  8.         L := lua.NewState()
  9.         defer L.Close()
  10.         // 加载fib.lua
  11.         if err := L.DoFile(`fib.lua`); err != nil {
  12.                 panic(err)
  13.         }
  14.         TestString(L)
  15. }
  16. func TestString(L *lua.LState) {
  17.         err := L.CallByParam(lua.P{
  18.                 Fn:      L.GetGlobal("TestLString"), // 获取函数引用
  19.                 NRet:    1,                          // 指定返回值数量
  20.                 Protect: true,                       // 如果出现异常,是panic还是返回err
  21.         })
  22.         if err != nil {
  23.                 panic(err)
  24.         }
  25.         lv := L.Get(-1) // get the value at the top of the stack
  26.         // 从堆栈中扔掉返回结果
  27.         L.Pop(1)
  28.         if str, ok := lv.(lua.LString); ok {
  29.                 // lv is LString
  30.                 fmt.Println(string(str))
  31.         }
  32.         if lv.Type() != lua.LTString {
  33.                 panic("string required.")
  34.         }
  35. }
复制代码
fib.lua中的代码:
  1. function TestLString()
  2.     return "this is test"
  3. end
复制代码
接下来看看指针类型是如何判断的:
  1. lv := L.Get(-1) // get the value at the top of the stack
  2. if tbl, ok := lv.(*lua.LTable); ok {
  3.     // lv is LTable
  4.     fmt.Println(L.ObjLen(tbl))
  5. }
复制代码
特别注意:

  • LBool , LNumber , LString 这三类不是指针类型,其他的都属于指针类型。
  • LNilType and LBool  这里没看懂官方在说什么,知道的可以告知下,谢谢。
  • lua 中,nil和false都是认为是错误的情况。nil表示一个无效值(在条件表达式中相当于false)。
大家有不明白的地方,推荐去看看官方怎么说的。
2.3 gopher-lua 中的调用堆栈和注册表大小

官方还介绍了性能优化这块的内容,我就不介绍了,大家感兴趣可以去看官方。
主要是对于我这种非科班出生的菜鸟来说,还是有点难度的,这里就不瞎说了,免得误导大家。哈哈......
一般来说,使用默认的方式,性能不会太差。对性能没有特别高的要求,也没有必要去折腾这个。
3、gopher-lua 中常用的API

3.1 lua 调用 Go 中的代码

test.lua 脚本内容:
  1. print(double(100))
复制代码
main.go 中的内容:
  1. package main
  2. import (
  3.         "fmt"
  4.         lua "github.com/yuin/gopher-lua"
  5. )
  6. func main() {
  7.         L := lua.NewState()
  8.         defer L.Close()
  9.         L.SetGlobal("double", L.NewFunction(Double)) /* Original lua_setglobal uses stack... */
  10.         L.DoFile("test.lua")
  11. }
  12. func Double(L *lua.LState) int {
  13.         fmt.Println("coming go code.............")
  14.         lv := L.ToInt(1)            /* get argument */
  15.         L.Push(lua.LNumber(lv * 2)) /* push result */
  16.         return 1                    /* number of results */
  17. }
复制代码
执行结果:
  1. coming go code.............
  2. 200
复制代码
上面我们已经实现了一个简单的 lua 脚本中调用 go 代码的功能。
3.2 使用Go创建模块给lua使用

上面介绍了 lua 中调用 Go中的代码,Go提供的功能不多还好,直接使用即可,但是实际项目中,既然使用到了Go和lua结合的模式,必然会存在Go提供基础功能,lua来编写业务的方式,这个时候如果还是使用上面的方式,使用起来将非常不方便。这里提供了一种方式,将Go中的功能封装成一个模块,提供给 lua 使用,这样就方便许多。
接下来我们一起看看怎么做。
mymodule.go 的内容:
  1. package main
  2. import (
  3.         "fmt"
  4.         lua "github.com/yuin/gopher-lua"
  5. )
  6. func Loader(L *lua.LState) int {
  7.         // register functions to the table
  8.         mod := L.SetFuncs(L.NewTable(), exports)
  9.         // register other stuff
  10.         L.SetField(mod, "name", lua.LString("testName"))
  11.         // returns the module
  12.         L.Push(mod)
  13.         return 1
  14. }
  15. var exports = map[string]lua.LGFunction{
  16.         "MyAdd": MyAdd,
  17. }
  18. func MyAdd(L *lua.LState) int {
  19.         fmt.Println("coming custom MyAdd")
  20.         x, y := L.ToInt(1), L.ToInt(2)
  21.         // 原谅我还不知道怎么把计算结果返回给 lua ,太菜了啦
  22.     // 不过用上另外一个包后,我知道,具体看实战篇。
  23.         fmt.Println(x)
  24.         fmt.Println(y)
  25.         return 1
  26. }
复制代码
main.go 的内容:
  1. package main
  2. import lua "github.com/yuin/gopher-lua"
  3. func main() {
  4.         L := lua.NewState()
  5.         defer L.Close()
  6.         L.PreloadModule("myModule", Loader)
  7.         if err := L.DoFile("main.lua"); err != nil {
  8.                 panic(err)
  9.         }
  10. }
复制代码
main.lua 的内容:
  1. local m = require("myModule")
  2. m.MyAdd(10, 20)
  3. print(m.name)
复制代码
运行 main.go 得到执行结果:
  1. coming custom MyAdd
  2. 10      
  3. 20      
  4. testName
复制代码
3.3 Go 调用 lua 中的代码

lua 中的代码
  1. function TestGoCallLua(x, y)
  2.     return x+y, x*y
  3. end
复制代码
go 中的代码
  1. func TestTestGoCallLua(L *lua.LState) {
  2.         err := L.CallByParam(lua.P{
  3.                 Fn:      L.GetGlobal("TestGoCallLua"), // 获取函数引用
  4.                 NRet:    2,                            // 指定返回值数量,注意这里的值是 2
  5.                 Protect: true,                         // 如果出现异常,是panic还是返回err
  6.         }, lua.LNumber(10), lua.LNumber(20))
  7.         if err != nil {
  8.                 panic(err)
  9.         }
  10.         multiplicationRet := L.Get(-1)
  11.         addRet := L.Get(-2)
  12.         if str, ok := multiplicationRet.(lua.LNumber); ok {
  13.                 fmt.Println("multiplicationRet is: ", int(str))
  14.         }
  15.         if str, ok := addRet.(lua.LNumber); ok {
  16.                 fmt.Println("addRet is: ", int(str))
  17.         }
  18. }
复制代码
具体的可以看 xxx 中的 TestTestGoCallLua 函数。
执行结果:
  1. multiplicationRet is:  200
  2. addRet is:  30
复制代码
3.4 lua中使用go中定义好的类型

这里我们直接使用官方的例子:
  1. type Person struct {
  2.     Name string
  3. }
  4. const luaPersonTypeName = "person"
  5. // Registers my person type to given L.
  6. func registerPersonType(L *lua.LState) {
  7.     mt := L.NewTypeMetatable(luaPersonTypeName)
  8.     L.SetGlobal("person", mt)
  9.     // static attributes
  10.     L.SetField(mt, "new", L.NewFunction(newPerson))
  11.     // methods
  12.     L.SetField(mt, "__index", L.SetFuncs(L.NewTable(), personMethods))
  13. }
  14. // Constructor
  15. func newPerson(L *lua.LState) int {
  16.     person := &Person{L.CheckString(1)}
  17.     ud := L.NewUserData()
  18.     ud.Value = person
  19.     L.SetMetatable(ud, L.GetTypeMetatable(luaPersonTypeName))
  20.     L.Push(ud)
  21.     return 1
  22. }
  23. // Checks whether the first lua argument is a *LUserData with *Person and returns this *Person.
  24. func checkPerson(L *lua.LState) *Person {
  25.     ud := L.CheckUserData(1)
  26.     if v, ok := ud.Value.(*Person); ok {
  27.         return v
  28.     }
  29.     L.ArgError(1, "person expected")
  30.     return nil
  31. }
  32. var personMethods = map[string]lua.LGFunction{
  33.     "name": personGetSetName,
  34. }
  35. // Getter and setter for the Person#Name
  36. func personGetSetName(L *lua.LState) int {
  37.     p := checkPerson(L)
  38.     if L.GetTop() == 2 {
  39.         p.Name = L.CheckString(2)
  40.         return 0
  41.     }
  42.     L.Push(lua.LString(p.Name))
  43.     return 1
  44. }
  45. func main() {
  46.     L := lua.NewState()
  47.     defer L.Close()
  48.     registerPersonType(L)
  49.     if err := L.DoString(`
  50.         p = person.new("Steeve")
  51.         print(p:name()) -- "Steeve"
  52.         p:name("Alice")
  53.         print(p:name()) -- "Alice"
  54.     `); err != nil {
  55.         panic(err)
  56.     }
  57. }
复制代码
官方还讲解了如何使用 go 中的context 来结束lua代码的执行,这里我就不演示了,大家自行研究。
3.5 gopher_lua 中goroutine的说明

这里直接放官方的文档,大家自行理解
  1. The LState is not goroutine-safe. It is recommended to use one LState per goroutine and communicate between goroutines by using channels.
复制代码
LState不是goroutine安全的。建议每个goroutine使用一个LState,并通过使用通道在goroutine之间进行通信。
  1. Channels are represented by channel objects in GopherLua. And a channel table provides functions for performing channel operations.
复制代码
在GopherLua中,通道由通道对象表示。通道表提供了执行通道操作的函数。这意味着,我们可以使用通道对象来创建、发送和接收消息,并使用通道表中的函数来控制通道的行为。通道是一种非常有用的并发编程工具,可以帮助我们在不同的goroutine之间进行通信和同步。通过使用GopherLua中的通道对象和通道表,我们可以轻松地在Lua代码中实现并发编程。
  1. Some objects can not be sent over channels due to having non-goroutine-safe objects inside itself.
复制代码
某些对象无法通过通道发送,因为其内部有非goroutine安全的对象。


  • a thread(state)
  • a function
  • an userdata
  • a table with a metatable
上面这四种类型就不支持往通道中发送。
[code]package mainimport (        lua "github.com/yuin/gopher-lua"        "time")func receiver(ch, quit chan lua.LValue) {        L := lua.NewState()        defer L.Close()        L.SetGlobal("ch", lua.LChannel(ch))        L.SetGlobal("quit", lua.LChannel(quit))        if err := L.DoString(`    local exit = false    while not exit do      -- 这个 channel 的写法是固定的 ??      channel.select(        {"|
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

前进之路

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表