luar

package module
v2.0.0-...-a258c70 Latest Latest
Warning

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

Go to latest
Published: Dec 14, 2016 License: MIT Imports: 8 Imported by: 0

README

Luar: Lua reflection bindings for Go

Luar is designed to make using Lua from Go more convenient. Go structs, slices and maps can be automatically converted to Lua tables and vice-versa. The resulting conversion can either be a copy or a proxy. In the latter case, any change made to the result will reflect on the source.

Any Go function can be made available to Lua scripts, without having to write C-style wrappers.

Luar support cyclic structures (map[string]interface{}, lists, etc.).

User-defined types can be made available to Lua as well: their exported methods can be called and usual operations such as indexing or arithmetic can be performed.

See the documentation for usage instructions and examples.

Installation

Install with

go get <repo>/luar

Luar uses Alessandro Arzilli's golua. See golua's homepage for further installation details.

REPL

An example REPL is available in the cmd folder.

Version 2

This is a rewrite of 1.0 with extended features and a cleaner API.

Warning: This is a development version, the API might change in the future.

Compatibility notice

The main differences with the previous version:

  • The function prototypes of GoToLua and LuaToGo are simpler and do not require the use of reflection from the callers. The dontproxify argument is gone, use GoToLuaProxy to control proxification.

  • The Copy* functions and GoLuaFunc are gone, use GoToLua and LuaToGo instead.

  • Use Register instead of RawRegister.

  • InitProxies is gone since it was not needed.

  • The LuaObject and LuaTableIter structure fields are unexported.

  • LuaObject methods not only work on Lua functions but also on anything with a __call metamethods. Idem for tables and the __index/__newindex metamethods.

  • Use NewLuaObjectFromName(L, "_G") instead of Global.

  • Lookup and Geti gone. Instead the Get and GetObject functions are variadic: each subfield argument can be any valid Lua key (string, integer...).

  • Use (*LuaObject) Call instead of (*LuaObject) Callf. The protoype of (*LuaObject) Call has changed in a fashion similar to GoToLua and LuaToGo. Types is gone as it is no longer needed.

  • Register ProxyIpairs and ProxyPairs instead of calling LuarSetup.

  • Register and use Unproxify instead of ArrayToTable, MapToTable, ProxyRaw, SliceToTable and StructToTable.

  • ComplexReal and ComplexImag have been replaced by the proxy attributes real and imag, respectively.

  • SliceSub and SliceAppend have been replaced by the proxy methods slice and append, respectively. Slice proxies have the cap metamethod alongside append and slice.

  • String proxies have a slice method just like slice proxies. They can be looped rune-by-rune over with ipairs.

The range of supported conversion has been extended:

  • LuaToGo can convert to interfaces and pointers with several levels of indirection.

  • LuaToGo can convert to non-empty maps and structs.

Documentation

Overview

Package luar provides a convenient interface between Lua and Go.

It uses Alessandro Arzilli's golua (https://github.com/aarzilli/golua).

Most Go values can be passed to Lua: basic types, strings, complex numbers, user-defined types, pointers, composite types, functions, channels, etc. Conversely, most Lua values can be converted to Go values.

Composite types are processed recursively.

Methods can be called on user-defined types. These methods will be callable using _dot-notation_ rather than colon notation.

Arrays, slices, maps and structs can be copied as tables, or alternatively passed over as Lua proxy objects which can be naturally indexed.

In the case of structs and string maps, fields have priority over methods. Use 'luar.method(<value>, <method>)(<params>...)' to call shadowed methods.

Unexported struct fields are ignored. The "lua" tag is used to match fields in struct conversion.

You may pass a Lua table to an imported Go function; if the table is 'array-like' then it is converted to a Go slice; if it is 'map-like' then it is converted to a Go map.

Pointer values encode as the value pointed to when unproxified.

Usual operators (arithmetic, string concatenation, pairs/ipairs, etc.) work on proxies too. The type of the result depends on the type of the operands. The rules are as follows:

- If the operands are of the same type, use this type.

- If one type is a Lua number, use the other, user-defined type.

- If the types are different and not Lua numbers, convert to a complex proxy, a Lua number, or a Lua string according to the result kind.

Channels

Channel proxies can be manipulated with the following methods:

- close(): Close the channel.

- recv() value: Fetch and return a value from the channel.

- send(x value): Send a value in the channel.

Complex numbers

Complex proxies can be manipulated with the following attributes:

- real: The real part.

- imag: The imaginary part.

Slices

Slice proxies can be manipulated with the following methods/attributes:

- append(x ...value) sliceProxy: Append the elements and return the new slice. The elements must be convertible to the slice element type.

- cap: The capacity of the slice.

- slice(i, j integer) sliceProxy: Return the sub-slice that ranges from 'i' to 'j' excluded, starting from 1.

Strings

String proxies can be browsed rune by rune with the pairs/ipairs functions. These runes are encoded as strings in Lua.

Indexing a string proxy (starting from 1) will return the corresponding byte as a Lua string.

String proxies can be manipulated with the following method:

- slice(i, j integer) sliceProxy: Return the sub-string that ranges from 'i' to 'j' excluded, starting from 1.

Example
package main

import (
	"fmt"

	"github.com/stevedonovan/luar"
)

func main() {
	const test = `
for i = 1, 3 do
		print(msg, i)
end
print(user)
print(user.Name, user.Age)
`

	type person struct {
		Name string
		Age  int
	}

	L := luar.Init()
	defer L.Close()

	user := &person{"Dolly", 46}

	luar.Register(L, "", luar.Map{
		// Go functions may be registered directly.
		"print": fmt.Println,
		// Constants can be registered.
		"msg": "foo",
		// And other values as well.
		"user": user,
	})

	L.DoString(test)
}
Output:

foo 1
foo 2
foo 3
&{Dolly 46}
Dolly 46
Example (Pointers)

Pointers to structs and structs within pointers are automatically dereferenced.

package main

import (
	"fmt"

	"github.com/stevedonovan/luar"
)

func main() {
	const test = `
local t = newRef()
print(t.Index, t.Number, t.Title)
`

	type Ref struct {
		Index  int
		Number *int
		Title  *string
	}

	newRef := func() *Ref {
		n := new(int)
		*n = 10
		t := new(string)
		*t = "foo"
		return &Ref{Index: 17, Number: n, Title: t}
	}

	L := luar.Init()
	defer L.Close()

	luar.Register(L, "", luar.Map{
		"print":  fmt.Println,
		"newRef": newRef,
	})

	L.DoString(test)
}
Output:

17 10 foo
Example (Slices)

Slices must be looped over with 'ipairs'.

package main

import (
	"fmt"

	"github.com/stevedonovan/luar"
)

func main() {
	const test = `
for i, v in ipairs(names) do
	 print(i, v)
end
`

	L := luar.Init()
	defer L.Close()

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

	luar.Register(L, "", luar.Map{
		"print": fmt.Println,
		"names": names,
	})

	L.DoString(test)
}
Output:

1 alfred
2 alice
3 bob
4 frodo

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	ErrLuaObjectCallResults   = errors.New("results must be a pointer to pointer/slice/struct")
	ErrLuaObjectCallable      = errors.New("LuaObject must be callable")
	ErrLuaObjectIndexable     = errors.New("not indexable")
	ErrLuaObjectUnsharedState = errors.New("LuaObjects must share the same state")
)
View Source
var ErrTableConv = errors.New("some table elements could not be converted")

ErrTableConv arises when some table entries could not be converted. The table conversion result is usable. TODO: Work out a more relevant name. TODO: Should it be a type instead embedding the actual error?

View Source
var (
	// Null is the definition of 'luar.null' which is used in place of 'nil' when
	// converting slices and structs.
	Null = NullT(0)
)

Functions

func Complex

func Complex(L *lua.State) int

Complex pushes a proxy to a Go complex on the stack.

Arguments: real (number), imag (number)

Returns: proxy (complex128)

func GoToLua

func GoToLua(L *lua.State, a interface{})

GoToLua pushes a Go value 'val' on the Lua stack.

It unboxes interfaces.

Pointers are followed recursively. Slices, structs and maps are copied over as tables.

Example

Init() is not needed when no proxy is involved.

package main

import (
	"fmt"

	"github.com/aarzilli/golua/lua"
	"github.com/stevedonovan/luar"
)

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

	input := "Hello world!"
	luar.GoToLua(L, input)
	L.SetGlobal("input")

	luar.GoToLua(L, fmt.Println)
	L.SetGlobal("print")
	L.DoString("print(input)")
}
Output:

Hello world!

func GoToLuaProxy

func GoToLuaProxy(L *lua.State, a interface{})

GoToLuaProxy is like GoToLua but pushes a proxy on the Lua stack when it makes sense.

A proxy is a Lua userdata that wraps a Go value.

Pointers are preserved.

Structs and arrays need to be passed as pointers to be proxified, otherwise they will be copied as tables.

Predeclared scalar types are never proxified as they have no methods.

func Init

func Init() *lua.State

Init makes and initializes a new pre-configured Lua state.

It populates the 'luar' table with some helper functions/values:

method: ProxyMethod
unproxify: Unproxify

chan: MakeChan
complex: MakeComplex
map: MakeMap
slice: MakeSlice

null: Null

It replaces the 'pairs'/'ipairs' functions with ProxyPairs/ProxyIpairs respectively, so that __pairs/__ipairs can be used, Lua 5.2 style. It allows for looping over Go composite types and strings.

It also replaces the 'type' function with ProxyType.

It is not required for using the 'GoToLua' and 'LuaToGo' functions.

Example

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

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 copy this proxy to a Lua table explicitly. There is also `luar.unproxify` on the Lua side to do this.

We initialize the state with Init() to register Unproxify() as 'luar.unproxify()'.

package main

import (
	"fmt"
	"strconv"

	"github.com/stevedonovan/luar"
)

func main() {
	const code = `
-- Lua tables auto-convert to slices in Go-function calls.
local res = foo {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.unproxify(res)
for k,v in pairs(res) do
	print(k,v)
end
`

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

	L := luar.Init()
	defer L.Close()

	luar.Register(L, "", luar.Map{
		"foo":   foo,
		"print": fmt.Println,
	})

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

400 900
1 400
0 100
3 1600
2 900

func LuaToGo

func LuaToGo(L *lua.State, idx int, a interface{}) error

LuaToGo converts the Lua value at index 'idx' to the Go value.

The Go value must be a non-nil pointer.

Conversions to string and numbers are straightforward.

Lua 'nil' is converted to the zero value of the specified Go value.

If the Lua value is non-nil, pointers are dereferenced (multiple times if required) and the pointed value is the one that is set. If 'nil', then the Go pointer is set to 'nil'. To set a pointer's value to its zero value, use 'luar.null'.

The Go value can be an interface, in which case the type is inferred. When converting a table to an interface, the Go value is a []interface{} slice if all its elements are indexed consecutively from 1, or a map[string]interface{} otherwise.

Existing entries in maps and structs are kept. Arrays and slices are reset.

Nil maps and slices are automatically allocated.

Proxies are unwrapped to the Go value, if convertible. Userdata that is not a proxy will be converted to a LuaObject if the Go value is an interface or a LuaObject.

func MakeChan

func MakeChan(L *lua.State) int

MakeChan creates a 'chan interface{}' proxy and pushes it on the stack.

Optional argument: size (number)

Returns: proxy (chan interface{})

Example
package main

import (
	"fmt"
	"log"
	"sync"

	"github.com/stevedonovan/luar"
)

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

	luar.MakeChan(L1)
	L1.SetGlobal("c")
	L1.GetGlobal("c")
	var c interface{}
	err := luar.LuaToGo(L1, -1, &c)
	L1.Pop(1)
	if err != nil {
		log.Fatal(err)
	}

	luar.Register(L2, "", luar.Map{"c": c})
	var wg sync.WaitGroup
	wg.Add(1)

	go func() {
		err := L1.DoString(`c.send(17)`)
		if err != nil {
			fmt.Println(err)
		}
		wg.Done()
	}()

	err = L2.DoString(`return c.recv()`)
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println(L2.ToNumber(-1))
	L2.Pop(1)
	wg.Wait()
}
Output:

17

func MakeMap

func MakeMap(L *lua.State) int

MakeMap creates a 'map[string]interface{}' proxy and pushes it on the stack.

Returns: proxy (map[string]interface{})

func MakeSlice

func MakeSlice(L *lua.State) int

MakeSlice creates a '[]interface{}' proxy and pushes it on the stack.

Optional argument: size (number)

Returns: proxy ([]interface{})

func ProxyIpairs

func ProxyIpairs(L *lua.State) int

ProxyIpairs implements Lua 5.2 'ipairs' functions. It respects the __ipairs metamethod.

It is only useful for compatibility with Lua 5.1.

func ProxyMethod

func ProxyMethod(L *lua.State) int

ProxyMethod pushes the proxy method on the stack.

Argument: proxy

Returns: method (function)

func ProxyPairs

func ProxyPairs(L *lua.State) int

ProxyPairs implements Lua 5.2 'pairs' functions. It respects the __pairs metamethod.

It is only useful for compatibility with Lua 5.1.

func ProxyType

func ProxyType(L *lua.State) int

ProxyType pushes the proxy type on the stack.

It behaves like Lua's "type" except for proxies for which it returns 'table<TYPE>', 'string<TYPE>' or 'number<TYPE>' with TYPE being the go type.

Argument: proxy

Returns: type (string)

func Register

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

Register makes a number of Go values available in Lua code. 'values' is a map of strings to Go values.

- 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 put the values in the global table (_G).

- If table is '*' then assume that the table is already on the stack.

Example (Sandbox)
package main

import (
	"fmt"
	"log"

	"github.com/stevedonovan/luar"
)

func main() {
	const code = `
print("foo")
print(io ~= nil)
print(os == nil)
`

	L := luar.Init()
	defer L.Close()

	res := L.LoadString(code)
	if res != 0 {
		msg := L.ToString(-1)
		fmt.Println("could not compile", msg)
	}

	// Create a empty sandbox.
	L.NewTable()
	// "*" means "use table on top of the stack."
	luar.Register(L, "*", luar.Map{
		"print": fmt.Println,
	})
	env := luar.NewLuaObject(L, -1)
	G := luar.NewLuaObjectFromName(L, "_G")
	defer env.Close()
	defer G.Close()

	// We can copy any Lua object from "G" to env with 'Set', e.g.:
	//   env.Set("print", G.Get("print"))
	// A more convenient and efficient way is to do a bulk copy with 'Setv':
	env.Setv(G, "type", "io", "table")

	// Set up sandbox.
	L.SetfEnv(-2)

	// Run 'code' chunk.
	err := L.Call(0, 0)
	if err != nil {
		log.Fatal(err)
	}
}
Output:

foo
true
true

func Unproxify

func Unproxify(L *lua.State) int

Unproxify converts a proxy to an unproxified Lua value.

Argument: proxy

Returns: value (Lua value)

Types

type ConvError

type ConvError struct {
	From interface{}
	To   interface{}
}

ConvError records a conversion error from value 'From' to value 'To'.

func (ConvError) Error

func (l ConvError) Error() string

type LuaObject

type LuaObject struct {
	// contains filtered or unexported fields
}

LuaObject encapsulates a Lua object like a table or a function.

We do not make the type distinction since metatables can make tables callable and functions indexable.

func NewLuaObject

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

NewLuaObject creates a new LuaObject from stack index.

Example

Another way to do parse configs: use a LuaObject to manipulate the table.

package main

import (
	"fmt"
	"log"
	"sort"

	"github.com/stevedonovan/luar"
)

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

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

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

	lo := luar.NewLuaObject(L, -1)
	defer lo.Close()
	// Can get the field itself as a Lua object, and so forth.
	opts, _ := lo.GetObject("options")
	marked, _ := lo.GetObject("marked")

	loPrint := func(lo *luar.LuaObject, subfields ...interface{}) {
		var a interface{}
		lo.Get(&a, subfields...)
		fmt.Printf("%#v\n", a)
	}
	loPrinti := func(lo *luar.LuaObject, idx int64) {
		var a interface{}
		lo.Get(&a, idx)
		fmt.Printf("%.1f\n", a)
	}
	loPrint(lo, "baggins")
	loPrint(lo, "name")
	loPrint(opts, "leave")
	// Note that these Get methods understand nested fields.
	loPrint(lo, "options", "leave")
	loPrint(lo, "options", "tags", "strong")
	// Non-existent nested fields don't panic but return nil.
	loPrint(lo, "options", "tags", "extra", "flakey")
	loPrinti(marked, 1)

	iter, err := lo.Iter()
	if err != nil {
		log.Fatal(err)
	}
	keys := []string{}
	for key := ""; iter.Next(&key, nil); {
		keys = append(keys, key)
	}
	if iter.Error() != nil {
		log.Fatal(iter.Error())
	}
	sort.Strings(keys)

	fmt.Println("Keys:")
	for _, v := range keys {
		fmt.Println(v)
	}
}
Output:

true
"dumbo"
true
true
true
<nil>
1.0
Keys:
age
baggins
marked
name
options

func NewLuaObjectFromName

func NewLuaObjectFromName(L *lua.State, subfields ...interface{}) *LuaObject

NewLuaObjectFromName creates a new LuaObject from the object designated by the sequence of 'subfields'.

func NewLuaObjectFromValue

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

NewLuaObjectFromValue creates a new LuaObject from a Go value. Note that this will convert any slices or maps into Lua tables.

Example
package main

import (
	"fmt"
	"log"

	"github.com/stevedonovan/luar"
)

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

	gsub := luar.NewLuaObjectFromName(L, "string", "gsub")
	defer gsub.Close()

	// We do have to explicitly copy the map to a Lua table, because `gsub`
	// will not handle userdata types.
	gmap := luar.NewLuaObjectFromValue(L, luar.Map{
		"NAME": "Dolly",
		"HOME": "where you belong",
	})
	defer gmap.Close()

	var res = new(string)
	err := gsub.Call(&res, "Hello $NAME, go $HOME", "%$(%u+)", gmap)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(*res)
}
Output:

Hello Dolly, go where you belong

func (*LuaObject) Call

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

Call calls a Lua function, given the desired results and the arguments. 'results' must be a pointer to a pointer/struct/slice.

- If a pointer, then only the first result is stored to that pointer.

- If a struct with 'n' fields, then the first n results are stored in the field.

- If a slice, then all the results are stored in the slice. The slice is re-allocated if necessary.

If the function returns more values than can be stored in the 'results' argument, they will be ignored.

If 'results' is nil, results will be discarded.

Example
package main

import (
	"fmt"
	"log"

	"github.com/stevedonovan/luar"
)

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

	const code = `
function return_strings()
	return 'one', luar.null, 'three'
end`

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

	fun := luar.NewLuaObjectFromName(L, "return_strings")
	defer fun.Close()

	// Using `Call` we would get a generic `[]interface{}`, which is awkward to
	// work with. But the return type can be specified:
	results := []string{}
	err = fun.Call(&results)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(results[0])
	// We get an empty string corresponding to a luar.null in a table,
	// since that's the empty 'zero' value for a string.
	fmt.Println(results[1])
	fmt.Println(results[2])
}
Output:

one

three

func (*LuaObject) Close

func (lo *LuaObject) Close()

Close frees the Lua reference of this object.

func (*LuaObject) Get

func (lo *LuaObject) Get(a interface{}, subfields ...interface{}) error

Get stores in 'a' the Lua value indexed at the sequence of 'subfields'. 'a' must be a pointer as in LuaToGo.

func (*LuaObject) GetObject

func (lo *LuaObject) GetObject(subfields ...interface{}) (*LuaObject, error)

GetObject returns the LuaObject indexed at the sequence of 'subfields'.

func (*LuaObject) Iter

func (lo *LuaObject) Iter() (*LuaTableIter, error)

Iter creates a Lua iterator.

func (*LuaObject) Push

func (lo *LuaObject) Push()

Push pushes this LuaObject on the stack.

func (*LuaObject) Set

func (lo *LuaObject) Set(a interface{}, subfields ...interface{}) error

Set sets the value at the sequence of 'subfields' with the value 'a'. Numeric indices start from 1, as in Lua: if we started from zero, access to index 0 or negative indices would be shifted awkwardly.

func (*LuaObject) Setv

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

Setv copies values between two tables in the same Lua state. It overwrites existing values.

type LuaTableIter

type LuaTableIter struct {
	// contains filtered or unexported fields
}

LuaTableIter is the Go equivalent of a Lua table iterator.

func (*LuaTableIter) Error

func (ti *LuaTableIter) Error() error

Error returns the error that happened during last iteration, if any.

func (*LuaTableIter) Next

func (ti *LuaTableIter) Next(key, value interface{}) bool

Next gets the next key/value pair from the indexable value.

'value' must be a valid argument for LuaToGo. As a special case, 'value' can be nil to make it possible to loop over keys without caring about associated values.

Example
package main

import (
	"fmt"
	"log"
	"sort"

	"github.com/stevedonovan/luar"
)

func main() {
	const code = `
return {
	foo = 17,
	bar = 18,
}
`

	L := luar.Init()
	defer L.Close()

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

	lo := luar.NewLuaObject(L, -1)
	defer lo.Close()

	iter, err := lo.Iter()
	if err != nil {
		log.Fatal(err)
	}
	keys := []string{}
	values := map[string]float64{}
	for key, value := "", 0.0; iter.Next(&key, &value); {
		keys = append(keys, key)
		values[key] = value
	}
	sort.Strings(keys)

	for _, v := range keys {
		fmt.Println(v, values[v])
	}
}
Output:

bar 18
foo 17

type Map

type Map map[string]interface{}

Map is an alias for map of strings.

Example
package main

import (
	"fmt"

	"github.com/stevedonovan/luar"
)

func main() {
	const code = `
print(#M)
print(M.one)
print(M.two)
print(M.three)
`

	L := luar.Init()
	defer L.Close()

	M := luar.Map{
		"one":   "eins",
		"two":   "zwei",
		"three": "drei",
	}

	luar.Register(L, "", luar.Map{
		"M":     M,
		"print": fmt.Println,
	})

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

3
eins
zwei
drei

type NullT

type NullT int

NullT is the type of Null. Having a dedicated type allows us to make the distinction between zero values and Null.

Jump to

Keyboard shortcuts

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