Shopify / go-lua

A Lua VM in Go
MIT License
3.09k stars 189 forks source link

Call LUA function from GO #124

Open WillianBR opened 2 years ago

WillianBR commented 2 years ago

Hi Folks,

I'm wondering if anybody was able to call a Lua function from GO, instead of just run all the file or a buffered script.

I'm aware of need to load all the file (and who any code outside a function will be executed), but I'm thinking about a way of load it and after it was done, We be able of call any function of the Lua script, any time with the desired arguments.

Without the need of recompile and than increasing the speed.

Does anyone here did it?

fbogsany commented 2 years ago

The usual approach is to return a table from your Lua script containing all the exported functions of your "module". E.g.:

local _M = {}

function _M.foo()
  return 42
end

return _M

and then something like:

l := lua.NewState()
lua.LoadString(l, myLuaScript)
l.ProtectedCall(0, 1, 0) // Run the script, leaves the module at the top of stack
l.Field(-1, "foo")       // Lookup the function 'foo' in the module
l.ProtectedCall(0, 0, 0) // Call the function 'foo' from the module
gabstv commented 2 years ago

or you could do:

function hello()
  return 42
end
l := lua.NewState()
lua.LoadString(l, myLuaScript)
l.Global("hello")
l.ProtectedCall(0, 1, 0)
myInt, ok := l.ToInteger(-1)
hschneider commented 1 month ago

I tried to get a function result but instead of 42 it returns 0:

    l := lua.NewState()
    lua.OpenLibraries(l)

    lua.LoadString(l, "function hello()\n  return 42\nend\n")
    l.Global("hello")
    l.ProtectedCall(0, 1, 0)
    myInt, _ := l.ToInteger(-1)
    fmt.Printf("Lua says %d", myInt)

Any hints, what I do wrong?

WillianBR commented 1 month ago

I tried to get a function result but instead of 42 it returns 0:

  l := lua.NewState()
  lua.OpenLibraries(l)

  lua.LoadString(l, "function hello()\n  return 42\nend\n")
  l.Global("hello")
  l.ProtectedCall(0, 1, 0)
  myInt, _ := l.ToInteger(-1)
  fmt.Printf("Lua says %d", myInt)

Any hints, what I do wrong?

@hschneider,

Please, change the call lua.LoadString() to lua.DoString() .

The demo file:

package main

import (
    "fmt"

    "github.com/Shopify/go-lua"
)

func main2() {
    l := lua.NewState()
    lua.OpenLibraries(l)

    // lua.LoadString(l, "function hello()\n  return 42\nend\n")
    lua.DoString(l, "function hello()\n  return 42\nend\n")
    l.Global("hello")
    l.ProtectedCall(0, 1, 0)
    myInt, errRet := l.ToInteger(-1)
    fmt.Printf("RC = %v\n", errRet)
    fmt.Printf("Lua says %d\n", myInt)
}

The output:

$ go run .
RC = true
Lua says 42

If you wanna test the error, comment the statement l.Global("hello"). The result will be 0:

$ go run .
RC = false
Lua says 0

And please, always save the error and use it. If the errRet is not true, the result cannot be trusted!

The End

hschneider commented 1 month ago

Perfect! THANKS :-)

hschneider commented 1 month ago

There is one more thing about calling a Lua function in an external script.

This code works fine:

    lua.DoFile(l, "script2.lua")
    l.Global("main")
    l.PushString("Hello World")
    l.ProtectedCall(1, 1, 0)
    res, err = l.ToString(-1)
    fmt.Printf("RC = %v\n", err)
    fmt.Printf("Lua says %s\n", res)

script2.lua:

function main(s)
    return s.." from script file!"
end

Output:

Lua says Hello World from script file!

While this one throws an error in json.lua:

    lua.DoFile(l, "script3.lua")
    l.Global("main")
    l.ProtectedCall(0, 1, 0)
    res, err = l.ToString(-1)
    fmt.Printf("RC = %v\n", err)
    fmt.Printf("Lua says %s\n", res)

script3.lua:

local json = require("json")

function main()
    local data = {
        name = "John Doe",
        age = 30,
        occupation = "Software Engineer"
    }

    local d = json.encode(data)
    return d
end

Output:

Lua says ./json.lua:102: attempt to call a nil value

json.lua is located in the same folder as the script. Is there some special way to deal with requirements?

WillianBR commented 1 month ago

Hello @hschneider, sorry for the delay. But the reported error is not in the shared code. It's inside the json package. The error message report the line 102, as the error point.

Lua says ./json.lua:102: attempt to call a nil value

Is this package proprietary or it can be shared?

The package was suceffuly imported?

There's a encode() method?

It seems the package only a converter of a Lua table into a JSON string representation. As I wrote befone, check if the conversion has an error status and check it before return the data. You should agree with a convention about the error. How to proceed if it fail!

Call me if you need!

hschneider commented 1 month ago

json.lua here: https://gist.github.com/hschneider/63078163ed3a4c1563c9d259234baa8a

If I call lua script3.luait works perfectly and json.lua imports fine. It only throws this error when I try to run it from Go.

Maybe the problem is, that the requirement is located in the same folder as the script?

WillianBR commented 1 month ago

@hschneider Sorry for the delay!

Let me tell the bad news my friend! The package string of go-lua does not implement the gsub() or gmatch() methods!

You can see by ourself at go-lua/string.go.

If you wish to use this package json.lua with the go-lua, it must be change to replace this call, by something available or write from scratch! Good luck!

local function encode_string(val)
  return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"'
end
hschneider commented 1 month ago

OK - thanks you so much for looking into it!