Code Monkey home page Code Monkey logo

gopher-luar's People

Contributors

guogeer avatar tomnz avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

gopher-luar's Issues

methods and pointer vs non-pointer receivers, take 2

Thanks for working on my previous bug report. I think the fix was too specific and not quite right. That's probably my fault, as I didn't reduce the code that reproduced to bug to its minimum. It is still the case that calling a method that has a pointer-receiver on an lvalue doesn't always work with luar while it works in Go. Example code:

package main

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

type Foo struct {}

func (f *Foo) Hello() {
    fmt.Printf("Hello\n")
}

const code = `
      somevar:Hello()
`

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

    var somevar Foo
    L.SetGlobal("somevar", luar.New(L, somevar))

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

This results in:

panic: <string>:2: attempt to call a non-function object
stack traceback:
    <string>:2: in main chunk
    [G]: ?

I have tried to fix this myself, and I have a proof of concept that works, at least for structs. The code is at https://github.com/miquels/gopher-luar/commit/c2c3f66f865516d517c19865c9dfe4c7dd1b9b64

It is rough and wrong, I know. I think that what I did in struct.go might also have to be done in other places where mt.method / mt.ptrMethod are used together. However I think this better illustrates the issue.

Thanks

Mike.

methods and pointer vs non-pointer receivers

First, I'm not sure if this is a bug, or correct behaviour of luar.

I have an array of structs. These structs have methods with a pointer receiver. In Go, if you call a method on a lvalue instead of a reference, Go will automatically call (&lvalue).method.

"A method call x.m() is valid if the method set of (the type of) x contains m and the argument list can be assigned to the parameter list of m. If x is addressable and &xโ€™s method set contains m, x.m() is shorthand for (&x).m()".

This does not work automatically with luar. The below code will print:
panic: :3: attempt to call a non-function object

Changing the Hello method to have a non-pointer receiver makes it work as expected.

package main

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

type Foo struct {}

func (f *Foo) Hello() {
        fmt.Printf("Hello\n")
}

const code = `
          f = somevar[1]
          f:Hello()
        `

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

        somevar := []Foo { Foo{} }
        L.SetGlobal("somevar", luar.New(L, somevar))

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

Convert LUA types to go

gopher-luar is able to automatically convert lua-types into go - an example for that is given in the documentation.

However, this mechanism is not exposed in the API. While there is a luar.New(), the counterpart is missing.
This becomes necessary when calling lua-functions from go, and trying to get the function's return value:

// Convert from go-to-LUA
var args []interface{} = ...
lArgs := make([]lua.LValue, len(args))
for i, a := range args {
    lArgs[i] = luar.New(L, a)
}

var numberOfReturnArgs = 4
vm.state.CallByParam(lua.P{
    Fn:      L.GetGlobal("functionName"),
    NRet:    numberOfReturnArgs,
}, lArgs...)

// Collect return values:
retValues := make([]interface{}, returnArgs)
for i := returnArgs; i > 0; i-- {
    ret := vm.state.Get(-1)
    vm.state.Pop(1)

    retValues[i-1] = ret // <--- how to convert to go-type?
}

How can I convert these return-values to go-types?

Is there a reason the NewType doesn't create a table with a "new" function defined?

In Lua code that I've seen written before, and some tutorial, it seems that Objects are generally created where the Table has a method called "new" defined on it. Allowing code like:

d = Dog:new()

While the NewType function in luar adds a type where you create instances by calling the named function like:

d = Dog()

Was this done for a specific reason, would it be simple to provide the first option as an alternative?

Properly handing errors?

In Go it is a common practice to have functions, which return errors in case something went wrong, e.g. func Foo() (FooResult, error).

Doing so it is up to the caller to decide whether this is a fatal error or something that can be handled properly.

Considering the following Go code, which registers a function in Lua, what would be the best way to handle errors? Should we just panic, which would raise an exception in Lua or is there a better way to do that?

package main

import (
        "log"
        "os/user"

        "github.com/layeh/gopher-luar"
        "github.com/yuin/gopher-lua"
)

type Person struct {
        Name string
        Home string
}

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

        person := L.NewTable()
        person.RawSetH(lua.LString("new"), luar.New(L, NewPerson))
        L.SetGlobal("person", person)

        if err := L.DoFile("create-person.lua"); err != nil {
                log.Fatal(err)
        }
}

func NewPerson(name string) *Person {
        user, err := user.Lookup(name)
        if err != nil {
                // How to propagate the error to Lua ??
                // panic ??
        }

        p := &Person{
                Name: name,
                Home: user.HomeDir,
        }

        return p
}

And the following example Lua code.

p = person.new("nonexisting-user")
print("username: " .. p.Name)
print("homedir: " .. p.Home)

Thanks,
Marin

Adding custom metamethods to types?

I'm trying to find a way to add some metamethods such as __len to my Go types, so I can extend their functionality.

Considering the following code below I'd like to retain access to my Go type's methods, but also add a __len metamethod to it, so I can get the length of items/persons using the hash # operator.

Here's the example code I have.

package main

import (
    "log"

    "github.com/layeh/gopher-luar"
    "github.com/yuin/gopher-lua"
)

type Person struct {
    Name string `luar:"name"`
    Age  int    `luar:"age"`
}

type People struct {
    everyone []*Person `luar:"-"` // Hidden field
}

func (p *People) Add(who ...*Person) {
    p.everyone = append(p.everyone, who...)
}

func (p *People) Len() int {
    return len(p.everyone)
}

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

    people := &People{
        everyone: make([]*Person, 0),
    }

    L.SetGlobal("Person", luar.NewType(L, Person{}))
    L.SetGlobal("People", luar.New(L, people))

    code := `
    kevin = Person()
    kevin.name = "Kevin"
    kevin.age = 30

    bob = Person()
    bob.name = "Bob"
    bob.age = 31

    People:add(kevin, bob)
    print("The number of persons I know is " .. People:len())

    -- How to add __len metamethod to People so we can use #People as well?
    `

    if err := L.DoString(code); err != nil {
        log.Fatal(err)
    }
}

Considering this code, is there a way to get the length of persons using #People instead of calling my custom People.Len() method?

Thanks,
Marin

calling go method from lua that returns a slice

I'm calling a Go method on an object that returns a slice.. but it seems that slice is not converted to a lua table. What am I doing wrong?

package main

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

type Foo struct {}

func (f *Foo) GetValues() (v []string) {
        return []string{ "one", "two", "three" }
}

const code = `
          f = somevar:GetValues()
          for i, v in ipairs(f) do
            print (v)
          end
        `

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

        somevar := &Foo{}
        L.SetGlobal("somevar", luar.New(L, somevar))

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

This results in
panic: :3: bad argument #1 to ipairs (table expected, got userdata)

If I replace ipairs(f) with f(), I get:
panic: :3: attempt to call a non-function object

Thanks

Mike.

Test_metatable broken on master?

I'm seeing the above test failing in master, not sure if it's just something in my setup...

	helpers_test.go:80: error substring 'attempt to call' not found in 'runtime error: invalid memory address or nil pointer dereference
		stack traceback:
			<string>:1: in main chunk
			[G]: ?'
FAIL
FAIL	layeh.com/gopher-luar	4.028s```

how to modify Config

since there is FieldNames and MethodNames in Config, and the default export the upper case and lower case field and method name. but in my usage, only upper case is needed. so is there a way to set the two field in Config and how? thanks for your help.

I can't call functions of structs that are created via NewType()

Go:

type GameEntity struct {
	ID     int
	Components map[string]Component
}

func (e *GameEntity) setupComponentsMap() {
	e.Components = make(map[string]Component)
}


L.SetGlobal("Ent", luar.NewType(L, GameEntity{}))

Lua:

--test Go interop 
ent = Ent()
print(ent)
--(calling functions)
ent:setupComponentsMap()

error: attempt to call a non-function object

If I create ent on Go side and follow e.g. the example in the readme, functions are called no problem. This kind of beats the whole purpose of using luar in the first place to avoid manually writing glue code...

Consider name generation being configurable.

I love this library, but currently am using a personally modified version of it so that when I inject Go values with methods/fields I get only one style of methods in return. It would be awesome to have the the exported style and un-exported style behind a set of options. The default can be it's current behavior.

Perhaps something along the lines of:

luar.Configure(luar.Options{
        MethodNames: luar.ExportedOnly,
})

Other values could be luar.UnexportedOnly, or luar.Default (I'm drawing a blank at a better name for this one).

panic: attempt to call a non-function object when iterating a map.

I'm trying to iterate over a Go maps keys and values but I keep running into this problem.

The simple version of the Lua

function doWork(config)
  for k, v = config() do
    print(k .. ": " .. v)
  end
end

The Go:

func main() {
        // .. set up state, load script, etc...
        config := map[string]interface{}{
                "from": "name",
                "to":   "other_name",
        }
        err := L.CallByParam(lua.P{
                Fn:      L.GetGlobal("doWork"),
                NRet:    0,
                Protect: true,
        }, luar.New(L, config))
        if err != nil {
                panic(err) // Panic happens here
        }
}

Not sure what I'm doing wrong, this is per the documentation. I've also tried a map[string]string but ultimately this data is fed in from a JSON parse so it's a map[string]interface{}

As a side note, if I call config() outside of the loop header everything works fine. It just returns a function. I'm not quite sure what's breaking here, it's only when trying to do this loop.

Error handling with closures?

This one is related to #10 and how to handle errors from Go functions in Lua.

I have a program, which registers new Lua types from Go as providers, which return some result and an error.

As I don't want to do the error handling in Lua, I thought I'd use a closure and close the Go provider function where I could do the error handling and raise errors in Lua in case I have an error from my provider function. That way I only return the result from the Go function to Lua and do the error handling in the wrapper function.

What I've noticed though is that whenever I register my providers in Lua the wrapper function is always the same, resulting in overwriting the wrappers for previous providers.

Here's an example code:

package main

import (
    "fmt"

    "github.com/layeh/gopher-luar"
    "github.com/yuin/gopher-lua"
)

// provider type
type provider func(name string) (string, error)

// registry contains all registered providers
var registry = map[string]provider{
    "provider1": provider_1,
    "provider2": provider_2,
}

func provider_1(name string) (string, error) {
    return "provider_1 result", nil
}

func provider_2(name string) (string, error) {
    return "provider_2 results", nil
}

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

    // Register the providers in Lua
    for typ, provider := range registry {
        wrapper := func(L *lua.LState) int {
            result, err := provider(L.CheckString(1))
            if err != nil {
                L.RaiseError(err.Error()) // Do the error handling here, so that we don't have to return the error to Lua
            }

            L.Push(luar.New(L, result))
            return 1 // Number of arguments
        }

        tbl := L.NewTable()
        tbl.RawSetH(lua.LString("new"), L.NewFunction(wrapper)) // New wrapper overwrites all previous ones
        L.SetGlobal(typ, tbl)

        fmt.Printf("type %s -> wrapper %s\n", typ, wrapper)
    }

    // Run some Lua code
    code := `
    print(provider1.new("some input for provider1"))
    print(provider2.new("some input for provider2"))
    `

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

And here's the output it generates.

type provider1 -> wrapper %!s(func(*lua.LState) int=0x401a90)
type provider2 -> wrapper %!s(func(*lua.LState) int=0x401a90)
provider_2 results
provider_2 results

As you can see the wrapper function is always the same one, but I'd expect them to be different on each iteration. As a result the last wrapper function overwrites all previous ones, and the returned result is always from the last registered provider (in this case provider2).

Is this something I've overlooked or a bug in gopher-luar?

Thanks,
Marin

Failed to import - expects import "layeh.com/gopher-luar"

Hi,

I am trying to build the gopher-luar with newest version of go on mac, and it fails with error expecting import of layeh.com/gopher-luar. Please see below:

$ make                                                                                                                                                                           gopher-luar/git/master
go test -coverprofile=coverage.out
can't load package: package github.com/layeh/gopher-luar: code in directory /Users/gpolek/workspace/src/github.com/layeh/gopher-luar expects import "layeh.com/gopher-luar"

Why the comments introduced in 24588ee were added in first place? It breaks the build for me. After removing // import "layeh.com/gopher-luar" everything compiles correctly.

Here is my version of go:

go version
go version go1.7.4 darwin/amd64

Best,
Greg

argument of method cannot be set for go function because the type error is not thrown

issue: using an table carrying string value to set a struct that is expecting a integer will cause panic instead of throwing type error
go Version: go.17
luar version: layeh.com/gopher-luar v1.0.10
gopher-lua: github.com/yuin/gopher-lua v0.0.0-20190206043414-8bfc7677f583

example:

type B struct {
	I int
}

type A struct {
	B *B
}

func test(a *A) {
	if a != nil && a.B != nil {
		print(a.B.I)
	}
}

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

	L.SetGlobal("test", luar.New(L, test))

	code := `
		local a = { B = {I = "10"} }
		test(a)
	`
	if err := L.DoString(code); err != nil {
		panic(err)
	}
}

result in

panic: reflect: call of reflect.Value.Set on zero Value
stack traceback:
        [G]: in function 'test'
        <string>:3: in main chunk
        [G]: ?

goroutine 1 [running]:
main.main()

caused by this line:
https://github.com/layeh/gopher-luar/blob/6b0665c71bb633083e19fff076ce70c384b71a9c/luar.go#LL444C33-L444C33

Any way to "hide" methods?

Hey Layeh, thanks for Luar, it's amazing. :)

I've just encountered a security problem, though, which I was hoping you could help with. I'm using luar with my new project, listless, which implements a discussion-mailing-list scripted in Lua.

At present, there are three separate lua contexts;

  • A script-as-configuration-file, from which a config struct is created and the engine destroyed
  • An event-loop which creates a fresh Lua thread for each incoming mail and executes a script
  • An execute-once mode used for configuring lists at setup time and for maintenance.

All of these modes are fairly owner-controlled, and it's always in the power of the owner to open security holes unwittingly through bad code. So, I'm leaving io and os and debug alone, and the latter two scripts even get full database access.

I'd also like to implement a 4th mode, where moderators can email the list to issue commands in a more restricted lua environment; no io, no os, no debug, and more closely limited options, because email is a poorly authenticated platform and abuse would be too easy otherwise.

Coming to the problem:

I just realised that several of the objects I'm exposing to the Lua engines are embedded-struct wrappers around rich structs that have Go io access, and that potentially the parent methods are exposed. So, I tried shadowing one of these dangerous methods, email.Email.AttachFile(filename) (*email.Attachment, error), then went to Lua and accessed message.AttachFile and message.Email.AttachFile; different memory addresses.

So, it seems Luar is doing (as it should) a great job of iterating over methods including parent struct methods. This behaviour would normally be great, but in this case it's a problem!

Is there any way in luar to either:

  1. Prevent accessing embedded structs directly, or
  2. Prevent luar from enumerating embedded structs at reflection-time?

Thanks, sorry for the long post but I figure extra context helps frame the question!

Automatic conversion of Lua table to Go slice?

If I have a Go struct with one field in it - a slice of strings - is there a way to automatically convert a Lua table to Go slice, if I want to change the contents of my struct from Lua?

I've noticed that stevedonovan/luar does this, so wondering if layeh/gopher-luar supports this as well?

Thanks,
Marin

Plain struct still being treated as userdata

I am trying the following code

package main

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

const script = `
print(issue)
`

type Issue struct {
	ID int
	Title string
}

func main() {

	issues := []Issue{
		{
			ID: 1,
			Title: "uno",
		},
		{
			ID: 2,
			Title: "due",
		},
	}


	for _, issue := range issues {
		L := lua.NewState()
		defer L.Close()

		L.SetGlobal("issue", luar.New(L, issue))

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

I would expect to have tables in Lua, but I get userdata. Is this expected? is there a way to automatically map a simple struct to a table in Lua?

I also tried using luar.NewType but it doesn't work either

bad argument #1 to <function name> (userdata expected, got string)

Hi,

This is a fantastic project thanks for it.

I'm having some trouble that I think is being caused by luar's translation of my go functions.

panic:
[G]: bad argument #1 to Respond (userdata expected, got string)
stack traceback:
    [G]: in Respond
    test.lua:6: in main chunk
    [G]: ?

test .lua is this:

synfunc = function (msg)
    msg.reply("ack")
end

robot.Respond("syn",synfunc)

The general idea is that users will call robot.Respond with a regex they want to match, and a reference to a lua function they want to be called when the regex is detected by the upstream go program.

robot is a very simple struct type that enables the upstream Go to index this request back to the lua state that made it (there will be many):

type Robot struct{
   ID    int
}

the respond function on the go-side is also pretty simple:

func (r Robot)Respond(pat string, lfunc lua.LValue){
   newMsgCallback(r.ID, pat, lfunc, true)
}

As you can see in the error message, this panic's in runtime because the lua state seems to think my Respond function wants lua.LUserData instead of a string, which I assume(?) is probably being caused by luar's translation.

I've written some test scripts and I don't seem to be able to replicate this issue (methods on go structs with this signature (string,lval) seem to work in general), so I'm not sure if I'm doing something wrong in this codebase or if I've found a bug or what. You can take a look at the actual codebase here

Thanks in advance for your help.

-dave

Array type conversion

Hi. I executed this snippet:

package main

import (
	"fmt"

	lua "github.com/yuin/gopher-lua"
	luar "layeh.com/gopher-luar"
)

const script = `
acceptArray({16.24, 13.23})
`

func acceptArray(array interface{}) {
	fmt.Println(array)
}

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

	state.SetGlobal("acceptArray", luar.New(state, acceptArray))

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

I placed a breakpoint on the line with fmt.Println(array) and inspected a value of array. The Go debugger from VS Code tells me it's (unreadable could not resolve interface type):

image

I expected it to be []interface{}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.