luar

package module
v0.0.0-...-3e9dd99 Latest Latest
Warning

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

Go to latest
Published: Apr 6, 2017 License: MPL-2.0 Imports: 6 Imported by: 0

README

gopher-luar GoDoc

custom type reflection for gopher-lua.

oscarhealth fork

This fork includes additional features targeted at modifying the behavior of reflected objects. In particular, the following are supported:

  • Immutability: Various types can be reflected as immutable, preventing any modification in Lua. This includes modification of struct fields, slices, maps, pointers, etc. A channel is prevented from being closed.
  • Transparent pointers: Objects can be reflected as if all pointer fields were plain value fields - removing the need for the ^ and - operators that gopher-luar typically requires for manipulating pointers.
  • Automatic population On top of transparent pointers, objects can optionally have nil fields automatically created on read. This allows the lua code to act as if there were no pointers in the way, and they just had the zero values.

See the documentation for usage.

License

MPL 2.0

Documentation

Overview

Package luar provides custom type reflection to gopher-lua.

Notice

This package is currently in development, and its behavior may change. This message will be removed once the package is considered stable.

Basic types

Go bool, number types, string types, and nil values are converted to the equivalent Lua type.

Example:

New(L, "Hello World")        =  lua.LString("Hello World")
New(L, uint(834))            =  lua.LNumber(uint(834))
New(L, map[string]int(nil))  =  lua.LNil

Channels

Channels have the following methods defined:

receive():    Receives data from the channel. Returns nil plus false if the
              channel is closed.
send(data):   Sends data to the channel.
close():      Closes the channel.

Taking the length (#) of a channel returns how many unread items are in its buffer.

Example:

ch := make(chan string)
L.SetGlobal("ch", New(L, ch))
---
ch:receive()      -- equivalent to v, ok := ch
ch:send("hello")  -- equivalent to ch <- "hello"
ch:close()        -- equivalent to close(ch)

Functions

Functions can be converted and called from Lua. The function arguments and return values are automatically converted from and to Lua types, respectively (see exception below).

Example:

fn := func(name string, age uint) string {
  return fmt.Sprintf("Hello %s, age %d", name, age)
}
L.SetGlobal("fn", New(L, fn))
---
print(fn("Tim", 5)) -- prints "Hello Tim, age 5"

A function that has the signature func(*luar.LState) int can bypass the automatic argument and return value conversion (see luar.LState documentation for example).

A special conversion case happens when function returns a lua.LValue slice. In that case, luar automatically unpacks the slice.

Example:

fn := func() []lua.LValue {
  return []lua.LValue{lua.LString("Hello"), lua.LNumber(2.5)}
}
L.SetGlobal("fn", New(L, fn))
---
x, y = fn()
print(x) -- prints "Hello"
print(y) -- prints "2.5"

Maps

Maps can be accessed and modified like a normal Lua table. The map's length can also be queried using the # operator.

Rather than using Lua's pairs function to create an map iterator, calling the value (e.g. map_variable()) returns an iterator for the map.

Example:

places := map[string]string{
  "NA": "North America",
  "EU": "European Union",
}
L.SetGlobal("places", New(L, places))
---
print(#places)       -- prints "2"
print(places.NA)     -- prints "North America"
print(places["EU"])  -- prints "European Union"
for k, v in places() do  -- prints all keys and values of places
  print(k .. ": " .. v)
end

Slices

Like maps, slices be indexed, be modified, and have their length queried. Additionally, the following methods are defined for slices:

append(items...):   Appends the items to the slice. Returns a slice with
                    the items appended.
capacity():         Returns the slice capacity.

For consistency with other Lua code, slices use one-based indexing.

Example:

letters := []string{"a", "e", "i"}
L.SetGlobal("letters", New(L, letters))
---
letters = letters:append("o", "u")

Like maps, calling a slice (e.g. slice()) returns an iterator over its values.

Arrays

Arrays can be indexed and have their length queried. Only pointers to arrays can their contents modified.

Like slices and maps, calling an array (e.g. array()) returns an iterator over its values.

Example:

var arr [2]string
L.SetGlobal("arr", New(L, &arr))
---
arr[1] = "Hello"
arr[2] = "World"

Structs

Structs can have their fields accessed and modified and their methods called.

Example:

type Person {
  Name string
}
func (p Person) SayHello() {
  fmt.Printf("Hello, %s\n", p.Name)
}

tim := Person{"Tim"}
L.SetGlobal("tim", New(L, tim))
---
tim:SayHello() -- same as tim:sayHello()

By default, the name of a struct field is determined by its tag:

"":   the field is accessed by its name and its name with a lowercase
      first letter
"-":  the field is not accessible
else: the field is accessed by that value

Example:

type Person struct {
  Name   string `luar:"name"`
  Age    int
  Hidden bool   `luar:"-"`
}
---
Person.Name   -> "name"
Person.Age    -> "Age", "age"
Person.Hidden -> Not accessible

Pointers

Pointers can be dereferenced using the unary minus (-) operator.

Example:

str := "hello"
L.SetGlobal("strptr", New(L, &str))
---
print(-strptr) -- prints "hello"

The pointed to value can changed using the pow (^) operator.

Example:

str := "hello"
L.SetGlobal("strptr", New(L, &str))
---
print(str^"world") -- prints "world", and str's value is now "world"

Pointers to struct and array values are returned when accessed via a struct field, array index, or slice index.

Transparent Pointers

You can instruct luar to transparently dereference pointers as if they are regular values, by passing ReflectOptions to New:

Example:

type Person struct {
  Name *string
}
name := "Tim"
tim := &Person{&name}
L.SetGlobal("tim", New(L, tim, ReflectOptions{TransparentPointers: true}))
---
print(tim.Name) -- prints "Tim"; note that no dereference via - is req'd
tim.Name = "Timothy" -- assignment works transparently too

Additionally, automatic population can be enabled; this takes care of populating zero values when pointers are first accessed. This is to mimic the behavior of zero values on non-pointer fields. Objects behave as if all fields are regular values.

Example:

type Parent struct {
  Person
  Child *Person
}
// No initialization of Name:
tim := &Parent{}
L.SetGlobal("tim", New(L, tim, ReflectOptions{
   TransparentPointers: true, AutoPopulate: true}))
---
-- prints an empty string; does not raise an error, even though Child and
-- Name are both nil when accessed
print(tim.Child.Name)
-- afterwards, tim.Child will be set to a Person object, with a pointer to
-- an empty string in Name

Any behavior is inherited by objects that are accessed through the original reflected item. For example, functions reflected with TransparentPointers will return objects that transparently dereference.

Type methods

Any array, channel, map, slice, or struct type that has methods defined on it can be called from Lua.

On maps with key strings, map elements are returned before type methods.

Example:

type mySlice []string
func (s mySlice) Len() int {
    return len(s)
}

var s mySlice = []string{"Hello", "world"}
L.SetGlobal("s", New(L, s))
---
print(s:len()) -- prints "2"

Lua to Go conversions

The Lua types are automatically converted to match the output Go type, as described below:

Lua type    Go kind/type
-----------------------------------------------------
LBool       bool
            string ("true" or "false")
LChannel    chan lua.LValue
LNumber     numeric value
            string (strconv.Itoa)
LFunction   func
LNilType    chan, func, interface, map, ptr, slice, unsafe pointer
LState      *lua.LState
LString     string
LTable      slice
            map
            struct
            *struct
LUserData   underlying lua.LUserData.Value type

Example creating a Go slice from Lua:

type Group struct {
    Names []string
}

g := new(Group)
L.SetGlobal("g", luar.New(L, g))
---
g.Names = {"Tim", "Frank", "George"}

New types

Type constructors can be created using NewType. When called, it returns a new variable which is of the same type that was passed to NewType. Its behavior is dependent on the kind of value passed, as described below:

Kind      Constructor arguments          Return value
-----------------------------------------------------
Channel   Buffer size (opt)              Channel
Map       None                           Map
Slice     Length (opt), Capacity (opt)   Slice
Default   None                           Pointer to the newly allocated value

Example:

type Person struct {
  Name string
}
L.SetGlobal("Person", NewType(L, Person{}))
---
p = Person()
p.Name = "John"
print("Hello, " .. p.Name)  // prints "Hello, John"

Immutability

luar supports making a number of reflected object types immutable. This means that e.g. a struct's fields cannot be modified, or a map cannot be assigned new values. Attempting such a modification results in an error. Types are still fully readable/writeable in Go, this only affects Lua access. It's useful for locking down inputs that you want to be read-only from Lua.

Example:

type Person struct {
  Name string
}
tim := &Person{"Tim"}
L.SetGlobal("tim", New(L, tim, ReflectOptions{Immutable: true})
---
tim.Name = "Bob" -- raises an error!

Calling a pointer method on a struct is invalid when using immutability, since pointer methods can modify the struct's internal state.

Thread safety

This package accesses and modifies the Lua state's registry. This happens when functions like New are called, and potentially when luar-created values are used. It is your responsibility to ensure that concurrent access of the state's registry does not happen.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func New

func New(L *lua.LState, value interface{}, opts ...ReflectOptions) lua.LValue

New creates and returns a new lua.LValue for the given value.

The following table shows how Go types are converted to Lua types:

Kind            gopher-lua Type  Custom Metatable
--------------------------------------------------
nil             LNil             No
Bool            LBool            No
Int             LNumber          No
Int8            LNumber          No
Int16           LNumber          No
Int32           LNumber          No
Int64           LNumber          No
Uint            LNumber          No
Uint8           LNumber          No
Uint16          LNumber          No
Uint32          LNumber          No
Uint64          LNumber          No
Uintptr         *LUserData       No
Float32         LNumber          No
Float64         LNumber          No
Complex64       *LUserData       No
Complex128      *LUserData       No
Array           *LUserData       Yes
Chan            *LUserData       Yes
Func            *lua.LFunction   No
Map             *LUserData       Yes
Ptr             *LUserData       Yes
Slice           *LUserData       Yes
String          LString          No
Struct          *LUserData       Yes
UnsafePointer   *LUserData       No

func NewType

func NewType(L *lua.LState, value interface{}) lua.LValue

NewType returns a new type creator for the given value's type.

When the returned lua.LValue is called, a new value will be created that is the same type as value's type.

Example
L := lua.NewState()
defer L.Close()

type Song struct {
	Title  string
	Artist string
}

L.SetGlobal("Song", NewType(L, Song{}))
L.DoString(`
		s = Song()
		s.Title = "Montana"
		s.Artist = "Tycho"
		print(s.Artist .. " - " .. s.Title)
	`)
Output:

Tycho - Montana

func ToReflect

func ToReflect(L *lua.LState, value lua.LValue, hint reflect.Type) (reflect.Value, bool)

ToReflect converts the lua.LValue to a reflect.Value.

Whenever possible, this will be a strongly-typed Go object matching an object that was passed to luar.New. A type hint must be specified to indicate the type that the variable is expected to be. This will be used to e.g. downcast an LString to a string if that is what is expected. A regular LTable will be converted to a map, slice, struct, etc (as per the hint) if possible.

Types

type Config

type Config struct {
	// The name generating function that defines under which names Go
	// struct fields will be accessed.
	//
	// If nil, the default behaviour is used:
	//   - if the "luar" tag of the field is "", the field name and its name
	//     with a lowercase first letter is returned
	//  - if the tag is "-", no name is returned (i.e. the field is not
	//    accessible)
	//  - for any other tag value, that value is returned
	FieldNames func(s reflect.Type, f reflect.StructField) []string

	// The name generating function that defines under which names Go
	// methods will be accessed.
	//
	// If nil, the default behaviour is used:
	//   - the method name and its name with a lowercase first letter
	MethodNames func(t reflect.Type, m reflect.Method) []string
	// contains filtered or unexported fields
}

Config is used to define luar behaviour for a particular *lua.LState.

func GetConfig

func GetConfig(L *lua.LState) *Config

GetConfig returns the configuration options for the given *lua.LState.

type LState

type LState struct {
	*lua.LState
}

LState is an wrapper for gopher-lua's LState. It should be used when you wish to have a function/method with the standard "func(*lua.LState) int" signature.

Example
const code = `
	print(sum(1, 2, 3, 4, 5))
	`

L := lua.NewState()
defer L.Close()

sum := func(L *LState) int {
	total := 0
	for i := 1; i <= L.GetTop(); i++ {
		total += L.CheckInt(i)
	}
	L.Push(lua.LNumber(total))
	return 1
}

L.SetGlobal("sum", New(L, sum))

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

15

type Metatable

type Metatable struct {
	*lua.LTable
}

Metatable holds the Lua metatable for a Go type.

func MT

func MT(L *lua.LState, value interface{}) *Metatable

MT returns the metatable for value's type. nil is returned if value's type does not use a custom metatable.

type ReflectOptions

type ReflectOptions struct {
	// Controls whether or not the value of the reflected object can be modified.
	// Only works for a subset of types that utilize a custom metatable - arrays,
	// channels, maps, pointers, slices and structs. Child elements/fields inherit
	// the immutable property, even when assigned to new variables. Immutable
	// channels may send/receive, but cannot be closed from Lua.
	Immutable bool
	// For structs, will auto-indirect pointer fields. This makes structs with
	// pointer fields behave like their non-pointer counterparts.  Fields can
	// have values assigned directly without use of the pow (^) operator. Note
	// that fields can only be set if the struct is reflected by reference.
	TransparentPointers bool
	// For structs, will auto-populate pointer fields with their appropriate Go
	// type. This is only applicable if TransparentPointers is on.
	AutoPopulate bool
}

ReflectOptions is a configuration that can be used to alter the behavior of a reflected gopher-luar object.

Jump to

Keyboard shortcuts

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