luar

package
v0.0.0-...-dd27f12 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 29, 2015 License: MIT, Zlib Imports: 5 Imported by: 0

README

luar Lua Reflection Bindings for Go

luar is designed to make using Lua from Go more convenient.

Direct bindings to Lua already exist - luago has about 16 forks, and I'm using Alessandro Arzilli's up-to-date fork which does not use makefiles and simply requires that pkg-config exists and there is a lua5.1 package. So on Debian/Ubuntu it is directly go-gettable if the Lua package is installed.

As a consequence, luar is also go-gettable.

go get github.com/stevedonovan/luar

However, pkg-config is not universal, and Lua 5.1 may not be available as a lua5.1 package. However, cgo does not make any great demands on pkg-config. For instance, it works on Windows if you copy this nonsense to a file pkg-config.bat on your %PATH%:

@echo off
set LUAPATH=/Users/steve/lua/lua-5.1.4/src
if "%1"=="--cflags" echo -I%LUAPATH%
if "%1"=="--libs"  echo %LUAPATH%/lua51.dll

We link against the DLL, as is recommended for Mingw, and then everything works if a copy of lua51.dll is on your DLL path.

More sophisticated operating system users should have no difficulty in emulating this trick!

Binding to Go functions by Reflection

luago is pretty much a plain bridge to the C API and manages some of the GC issues and so forth. luar attempts to go further. Any Go function can be made available to Lua scripts, without having to write C-style wrappers. This can be done because Go has a powerful type reflection system:

The first convenience is that ordinary Go functions may be registered directly:

package main

import "fmt"
import "github.com/stevedonovan/luar"

const test = `
for i = 1,10 do
    Print(MSG,i)
end
`

func main() {
    L := luar.Init()
    defer L.Close()

    luar.Register(L,"",luar.Map{
        "Print":fmt.Println,
        "MSG":"hello",  // can also register constants
    })

    L.DoString(test)

}

This example shows how Go slices and maps are marshalled to Lua tables and vice versa:

package main

import "fmt"
import "strconv"
import "github.com/stevedonovan/luar"

func GoFun (args []int) (res map[string]int) {
    res = make(map[string]int)
    for i,val := range args {
        res[strconv.Itoa(i)] = val*val
    }
    return
}

const code = `
print 'here we go'
--// Lua tables auto-convert to slices
local res = GoFun {10,20,30,40}
--// the result is a map-proxy
print(res['1'],res['2'])
--// which we may explicitly convert to a table
res = luar.map2table(res)
for k,v in pairs(res) do
      print(k,v)
end
`
func main() {
    L := luar.Init()
    defer L.Close()

    // arbitrary Go functions can be registered
    // to be callable from Lua
    luar.Register(L,"",luar.Map{
        "GoFun":GoFun,
    })

    res := L.DoString(code)
    if res != nil {
        fmt.Println("Error:",res)
    }
}

So an arbitrary Go function is callable from Lua, and list-like tables become slices on the Go side. The Go function returns a map, which is wrapped as a proxy object. You can however then copy this to a Lua table explicitly (there is also luar.slice2table)

You may pass a Lua table to an imported Go function; if the table is 'array-like' then it can be converted to a Go slice; if it is 'map-like' then it is converted to a Go map. Usually non-primitive Go values are passed to Lua as wrapped userdata which can be naturally indexed if they represent slices, maps or structs. Methods defined on structs can be called, again using reflection.

The consequence is that a person wishing to use Lua from Go does not have to use the old-fashioned tedious method needed for C or C++, but at some cost in speed and memory.

luar for Configuration

Here is luar used for reading in configuration information in Lua format:

const setup = `
return {
    baggins = true,
   age = 24,
   name = 'dumbo' ,
   marked = {1,2},
   options = {
       leave = true,
       cancel = 'always'
    }
}
`

....
 res = L.DoString(setup)
 // there will be a table on the stack!
 v := luar.CopyTableToMap(L,nil,-1)
 fmt.Println("returned map",v)
 m := v.(map[string]interface{})
 for k,v := range m {
       fmt.Println(k,v)
 }

The examples directory covers most of luar's features.

luar for Calling Lua Functions

Any Lua value can be wrapped inside a luar.LuaObject. These have Get and Set methods for accessing table-like objects, and a Call method for calling functions.

Here is the very flexible Lua function string.gsub being called from Go (examples/luar3.go):

    gsub := luar.NewLuaObjectFromName(L,"string.gsub")
    gmap := luar.NewLuaObjectFromvalue(luar.Map {
        "NAME": "Dolly",
        "HOME": "where you belong",
    })    
    res,err := gsub.Call("hello $NAME go $HOME","%$(%u+)",gmap)
    --> res is now "hello Dolly go where you belong"

Here we do have to explicitly copy the map to a Lua table, because gsub will not handle userdata types. These functions are rather verbose, but it's easy to create aliases:

    var lookup = luar.NewLuaObjectFromName
    var lcopy = luar.NewLuaObjectFromValue
    ....

luar.Callf is used whenever:

  • the Lua function has multiple return values
  • and/or you have exact types for these values

For instance, in the tests the following Lua function is defined:

function Libs.return_strings()
    return {'one','two','three'}
end

Using Call we would get a generic []interface{}, which is awkward to work with. But the return type can be specified:

    fun := luar.NewLuaObjectFromName(L,"Libs.return_strings")
    returns := luar.Types([]string{})  // --> []reflect.Type
    results,err := fun.Callf(returns)    // -> []interface{}
    // first returned result should be a slice of strings
    strs := results[0].([]string)

The first argument may be nil and can be used to access multiple return values without caring about the exact conversion.

An interactive REPL for Golua

luar.go in the examples directory provides a useful Lua REPL for exploring Go in Lua. You will need to do go get github.com/GeertJohan/go.linenoise to get line history and tab completion. This is an extended REPL and comes with pretty-printing:

$ ./luar
luar prompt
Lua 5.1.4  Copyright (C) 1994-2008 Lua.org, PUC-Rio
> = 10,'10',{10}
10	"10"	{10}

One use for the luar REPL is to explore Go libraries. regexp.Compile is exported as regexp, so we can do this. note that the endlessly useful fmt.Println is available as println from Lua. Starting a line with a period ('dot') wraps that line in println; starting a line with '=' wraps it with print (as is usual with the standard Lua prompt.)

> p = regexp '[a-z]+\\S*'
> ms =  p.FindAllString('boo woo koo',99)
> = #ms
3
> println(ms)
[boo woo koo]
> . ms
[boo woo koo]

The next session explores the luar function slice, which generates a Go slice. This is automatically wrapped as a proxy object. Note that the indexing is one-based, and that Go slices have a fixed size! The metatable for slice proxies has an __ipairs metamethod. Although luar is (currently) based on Lua 5.1, it loads code to provide a 5.2-compatible pairs and ipairs.

The inverse of slice is slice2table.

> s = luar.slice(2) // create a Go slice
> = #s
2
> = s[1]
nil
> = s[2]
nil
> = s[3] // has exactly two elements!
[string "print( s[3])"]:1:  slice get: index out of range
> = s
[]interface {}
> for i,v in ipairs(s) do print (i,v) end
1	10
2	20
> = luar.slice2table(s)
{10,20}
> println(s)
[10 20]
> . s
[10 20]

A similar operation is luar.map (with corresponding luar.map2table). Using luar.type we can find the Go type of a proxy (it returns nil if this isn't a Go type). By getting the type of a value we can then do reflection and find out what methods a type has, etc.

> m = luar.map()
> m.one = 1
> m.two = 2
> m.three = 3
> println(m)
map[one:1 two:2 three:3]
> mt = luar.type(m)
> = mt.String()
"map[string]interface {}"
> = mt.Key().String()
"string"
> mtt = luar.type(mt)
> = mtt.String()
"*reflect.rtype"
> = mtt.NumMethod()
31

tab-completion is implemented in such Lua code: the Lua completion code merely requires that a type implement __pairs. This allows tab to expand mtt.S to mtt.String in the last example.

local function sdump(st)
    local t = luar.type(st)
    local val = luar.value(st)
    local nm = t.NumMethod()
    local mt = t --// type to find methods on ptr receiver
    if t.Kind() == 22 then --// pointer!
        t = t.Elem()
        val = val.Elem()
    end
    local n = t.NumField()
    local cc = {}
    for i = 1,n do
        local f,v = t.Field(i-1)
        if f.PkgPath == "" then --// only public fields!
            v = val.Field(i-1)    
            cc[f.Name] = v.Interface()
        end
    end
    --// then public methods...
    for i = 1,nm do
        local m = mt.Method(i-1)
        if m.PkgPath == "" then --// again, only public
            cc[m.Name] = true
        end
    end
    return cc
end
        
mt = getmetatable(__DUMMY__)
mt.__pairs = function(st)
    local cc = sdump(st)
    return pairs(cc)
end

sdump is pretty much the way this would be encoded in Go itself; again, the eccentric dot-notation makes it more familiar.

Documentation

Overview

luar provides a more convenient way to access Lua from Go, using
Alessandro Arzilli's  golua (https://github.com/aarzilli/golua).
Plain Go functions can be registered with luar and they will be called by reflection;
methods on Go structs likewise.

 package main

import "fmt"
import "github.com/stevedonovan/luar"

type MyStruct struct {
  Name string
  Age int
}

const test = `
for i = 1,5 do
    Print(MSG,i)
end
Print(ST)
print(ST.Name,ST.Age)
--// slices!
for i,v in pairs(S) do
   print(i,v)
end
`

func main() {
    L := luar.Init()
    defer L.Close()

    S := []string {"alfred","alice","bob","frodo"}

    ST := &MyStruct{"Dolly",46}

    luar.Register(L,"",luar.Map{
        "Print":fmt.Println,
        "MSG":"hello",  // can also register constants
        "ST":ST, // and other values
        "S":S,
    })

    L.DoString(test)

 }

Go types like slices, maps and structs are passed over as Lua proxy objects,
or alternatively copied as tables.

Copyright Steve Donovan 2013

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func CopyMapToTable

func CopyMapToTable(L *lua.State, vmap reflect.Value) int

Copy a Go map to a Lua table, Defines luar.map2table

func CopySliceToTable

func CopySliceToTable(L *lua.State, vslice reflect.Value) int

Copy a Go slice to a Lua table. nils in both slices and structs are represented as luar.null. Defines luar.slice2table

func CopyTableToMap

func CopyTableToMap(L *lua.State, t reflect.Type, idx int) interface{}

Return the Lua table at 'idx' as a copied Go map. If 't' is nil then the map type is map[string]interface{}

func CopyTableToSlice

func CopyTableToSlice(L *lua.State, t reflect.Type, idx int) interface{}

Return the Lua table at 'idx' as a copied Go slice. If 't' is nil then the slice type is []interface{}

func CopyTableToStruct

func CopyTableToStruct(L *lua.State, t reflect.Type, idx int) interface{}

Copy matching Lua table entries to a struct, given the struct type and the index on the Lua stack.

func GoLua

func GoLua(L *lua.State) int

func GoLuaFunc

func GoLuaFunc(L *lua.State, fun interface{}) lua.LuaGoFunction

GoLuaFunc converts an arbitrary Go function into a Lua-compatible GoFunction. There are special optimized cases for functions that go from strings to strings, and doubles to doubles, but otherwise Go reflection is used to provide a generic wrapper function

func GoToLua

func GoToLua(L *lua.State, t reflect.Type, val reflect.Value, dontproxify bool)

Push a Go value 'val' of type 't' on the Lua stack. If we haven't been given a concrete type, use the type of the value and unbox any interfaces. You can force slices and maps to be copied over as tables by setting 'dontproxify' to true.

func Init

func Init() *lua.State

Make and initialize a new Lua state. Populates luar table with five functions: map2table, slice2table, map, slice, type, and value. This makes customized pairs/ipairs // functions available so that __pairs/__ipairs can be used, Lua 5.2 style.

func Lookup

func Lookup(L *lua.State, path string, idx int)

Look up a Lua value by its full name. If idx is 0, then this name is assumed to start in the global table, e.g. "string.gsub". With non-zero idx, can be used to look up subfields of a table. It terminates with a nil value if we cannot continue the lookup...

func LuaToGo

func LuaToGo(L *lua.State, t reflect.Type, idx int) interface{}

Convert a Lua value 'idx' on the stack to the Go value of desired type 't'. Handles numerical and string types in a straightforward way, and will convert tables to either map or slice types.

func MakeChannel

func MakeChannel(L *lua.State) int

func RaiseError

func RaiseError(L *lua.State, msg string)

raise a Lua error from Go code

func RawRegister

func RawRegister(L *lua.State, table string, values Map)

Make a number of 'raw' Go functions or values available in Lua code. (Raw Go functions access the Lua state directly and have signature (L *lua.State) int.)

func Register

func Register(L *lua.State, table string, values Map)

Make a number of Go functions or values available in Lua code. If table is non-nil, then create or reuse a global table of that name and put the values in it. If table is '*' then assume that the table is already on the stack. values is a map of strings to Go values.

func Types

func Types(values ...interface{}) []reflect.Type

Convenience function for converting a set of values into a corresponding slice of their types

Types

type LuaObject

type LuaObject struct {
	L    *lua.State
	Ref  int
	Type string
}

Encapsulates a Lua object like a table or a function

func Global

func Global(L *lua.State) *LuaObject

new LuaObject refering to the global environment

func NewLuaObject

func NewLuaObject(L *lua.State, idx int) *LuaObject

A new LuaObject from stack index.

func NewLuaObjectFromName

func NewLuaObjectFromName(L *lua.State, path string) *LuaObject

A new LuaObject from global qualified name, using Lookup.

func NewLuaObjectFromValue

func NewLuaObjectFromValue(L *lua.State, val interface{}) *LuaObject

A new LuaObject from a Go value. Note that this _will_ convert any slices or maps into Lua tables.

func (*LuaObject) Call

func (lo *LuaObject) Call(args ...interface{}) (res interface{}, err error)

Call a Lua function, and return a single value, converted in a default way.

func (*LuaObject) Callf

func (lo *LuaObject) Callf(rtypes []reflect.Type, args ...interface{}) (res []interface{}, err error)

Call a Lua function, given the desired return types and the arguments.

func (*LuaObject) Close

func (lo *LuaObject) Close()

free the Lua reference of this object

func (*LuaObject) Get

func (lo *LuaObject) Get(key string) interface{}

Index the Lua object using a string key, returning Go equivalent

func (*LuaObject) GetObject

func (lo *LuaObject) GetObject(key string) *LuaObject

Index the Lua object using a string key, returning Lua object

func (*LuaObject) Geti

func (lo *LuaObject) Geti(idx int64) interface{}

Index the Lua object using integer index

func (*LuaObject) Iter

func (lo *LuaObject) Iter() *LuaTableIter

Create a Lua table iterator

func (*LuaObject) Push

func (lo *LuaObject) Push()

Push this Lua object on the stack

func (*LuaObject) Set

func (lo *LuaObject) Set(idx interface{}, val interface{}) interface{}

Set the value at a given idx

func (*LuaObject) Setv

func (lo *LuaObject) Setv(src *LuaObject, keys ...string)

Copy values between two tables in the same state

type LuaTableIter

type LuaTableIter struct {
	Key   interface{}
	Value interface{}
	// contains filtered or unexported fields
}

func (*LuaTableIter) Next

func (ti *LuaTableIter) Next() bool

Get next key/value pair from table

iter := lo.Iter()
keys := []string{}
for iter.Next() {
   keys = append(keys, iter.Key.(string))
}

type Map

type Map map[string]interface{}

Useful alias for passing maps of strings to values to luar

type Null

type Null int

Directories

Path Synopsis
A golua REPL with line editing, pretty-printing and tab completion.
A golua REPL with line editing, pretty-printing and tab completion.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL