MCPcopy Index your code
hub / github.com/yuin/gopher-lua

github.com/yuin/gopher-lua @v1.1.2 sqlite

repository ↗ · DeepWiki ↗ · release v1.1.2 ↗
1,119 symbols 4,570 edges 42 files 211 documented · 19%
README

GopherLua: VM and compiler for Lua in Go.

PkgGoDev test Coverage Status

GopherLua is a Lua5.1(+ goto statement in Lua5.2) VM and compiler written in Go. GopherLua has a same goal with Lua: Be a scripting language with extensible semantics . It provides Go APIs that allow you to easily embed a scripting language to your Go host programs.

Table of Contents

Design principle

  • Be a scripting language with extensible semantics.
  • User-friendly Go API
    • The stack based API like the one used in the original Lua implementation will cause a performance improvements in GopherLua (It will reduce memory allocations and concrete type <-> interface conversions). GopherLua API is not the stack based API. GopherLua give preference to the user-friendliness over the performance.

How about performance?

GopherLua is not fast but not too slow, I think.

GopherLua has almost equivalent ( or little bit better ) performance as Python3 on micro benchmarks.

There are some benchmarks on the wiki page .

Installation

$ go get github.com/yuin/gopher-lua

GopherLua supports >= Go1.9.

Usage

GopherLua APIs perform in much the same way as Lua, but the stack is used only for passing arguments and receiving returned values.

GopherLua supports channel operations. See "Goroutines" section.

Import a package.

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

Run scripts in the VM.

L := lua.NewState()
defer L.Close()
if err := L.DoString(`print("hello")`); err != nil {
    panic(err)
}
L := lua.NewState()
defer L.Close()
if err := L.DoFile("hello.lua"); err != nil {
    panic(err)
}

Refer to Lua Reference Manual and Go doc for further information.

Note that elements that are not commented in Go doc equivalent to Lua Reference Manual , except GopherLua uses objects instead of Lua stack indices.

Data model

All data in a GopherLua program is an LValue . LValue is an interface type that has following methods.

  • String() string
  • Type() LValueType

Objects implement an LValue interface are

Type name Go type Type() value Constants
LNilType (constants) LTNil LNil
LBool (constants) LTBool LTrue, LFalse
LNumber float64 LTNumber -
LString string LTString -
LFunction struct pointer LTFunction -
LUserData struct pointer LTUserData -
LState struct pointer LTThread -
LTable struct pointer LTTable -
LChannel chan LValue LTChannel -

You can test an object type in Go way(type assertion) or using a Type() value.

lv := L.Get(-1) // get the value at the top of the stack
if str, ok := lv.(lua.LString); ok {
    // lv is LString
    fmt.Println(string(str))
}
if lv.Type() != lua.LTString {
    panic("string required.")
}
lv := L.Get(-1) // get the value at the top of the stack
if tbl, ok := lv.(*lua.LTable); ok {
    // lv is LTable
    fmt.Println(L.ObjLen(tbl))
}

Note that LBool , LNumber , LString is not a pointer.

To test LNilType and LBool, You must use pre-defined constants.

lv := L.Get(-1) // get the value at the top of the stack

if lv == lua.LTrue { // correct
}

if bl, ok := lv.(lua.LBool); ok && bool(bl) { // wrong
}

In Lua, both nil and false make a condition false. LVIsFalse and LVAsBool implement this specification.

lv := L.Get(-1) // get the value at the top of the stack
if lua.LVIsFalse(lv) { // lv is nil or false
}

if lua.LVAsBool(lv) { // lv is neither nil nor false
}

Objects that based on go structs(LFunction. LUserData, LTable) have some public methods and fields. You can use these methods and fields for performance and debugging, but there are some limitations.

  • Metatable does not work.
  • No error handlings.

Callstack & Registry size

The size of an LState's callstack controls the maximum call depth for Lua functions within a script (Go function calls do not count).

The registry of an LState implements stack storage for calling functions (both Lua and Go functions) and also for temporary variables in expressions. Its storage requirements will increase with callstack usage and also with code complexity.

Both the registry and the callstack can be set to either a fixed size or to auto size.

When you have a large number of LStates instantiated in a process, it's worth taking the time to tune the registry and callstack options.

Registry

The registry can have an initial size, a maximum size and a step size configured on a per LState basis. This will allow the registry to grow as needed. It will not shrink again after growing.

L := lua.NewState(lua.Options{
   RegistrySize: 1024 * 20,         // this is the initial size of the registry
   RegistryMaxSize: 1024 * 80,      // this is the maximum size that the registry can grow to. If set to `0` (the default) then the registry will not auto grow
   RegistryGrowStep: 32,            // this is how much to step up the registry by each time it runs out of space. The default is `32`.
})
defer L.Close()

A registry which is too small for a given script will ultimately result in a panic. A registry which is too big will waste memory (which can be significant if many LStates are instantiated). Auto growing registries incur a small performance hit at the point they are resized but will not otherwise affect performance.

Callstack

The callstack can operate in two different modes, fixed or auto size. A fixed size callstack has the highest performance and has a fixed memory overhead. An auto sizing callstack will allocate and release callstack pages on demand which will ensure the minimum amount of memory is in use at any time. The downside is it will incur a small performance impact every time a new page of callframes is allocated. By default an LState will allocate and free callstack frames in pages of 8, so the allocation overhead is not incurred on every function call. It is very likely that the performance impact of an auto resizing callstack will be negligible for most use cases.

L := lua.NewState(lua.Options{
    CallStackSize: 120,                 // this is the maximum callstack size of this LState
    MinimizeStackMemory: true,          // Defaults to `false` if not specified. If set, the callstack will auto grow and shrink as needed up to a max of `CallStackSize`. If not set, the callstack will be fixed at `CallStackSize`.
})
defer L.Close()

Option defaults

The above examples show how to customize the callstack and registry size on a per LState basis. You can also adjust some defaults for when options are not specified by altering the values of lua.RegistrySize, lua.RegistryGrowStep and lua.CallStackSize.

An LState object that has been created by *LState#NewThread() inherits the callstack & registry size from the parent LState object.

Miscellaneous lua.NewState options

  • Options.SkipOpenLibs bool(default false)
    • By default, GopherLua opens all built-in libraries when new LState is created.
    • You can skip this behaviour by setting this to true .
    • Using the various OpenXXX(L *LState) int functions you can open only those libraries that you require, for an example see below.
  • Options.IncludeGoStackTrace bool(default false)
    • By default, GopherLua does not show Go stack traces when panics occur.
    • You can get Go stack traces by setting this to true .

API

Refer to Lua Reference Manual and Go doc(LState methods) for further information.

Calling Go from Lua

func Double(L *lua.LState) int {
    lv := L.ToInt(1)             /* get argument */
    L.Push(lua.LNumber(lv * 2)) /* push result */
    return 1                     /* number of results */
}

func main() {
    L := lua.NewState()
    defer L.Close()
    L.SetGlobal("double", L.NewFunction(Double)) /* Original lua_setglobal uses stack... */
}
print(double(20)) -- > "40"

Any function registered with GopherLua is a lua.LGFunction, defined in value.go

type LGFunction func(*LState) int

Working with coroutines.

co, _ := L.NewThread() /* create a new thread */
fn := L.GetGlobal("coro").(*lua.LFunction) /* get function from lua */
for {
    st, err, values := L.Resume(co, fn)
    if st == lua.ResumeError {
        fmt.Println("yield break(error)")
        fmt.Println(err.Error())
        break
    }

    for i, lv := range values {
        fmt.Printf("%v : %v\n", i, lv)
    }

    if st == lua.ResumeOK {
        fmt.Println("yield break(ok)")
        break
    }
}

Opening a subset of builtin modules

The following demonstrates how to open a subset of the built-in modules in Lua, say for example to avoid enabling modules with access to local files or system calls.

main.go

func main() {
    L := lua.NewState(lua.Options{SkipOpenLibs: true})
    defer L.Close()
    for _, pair := range []struct {
        n string
        f lua.LGFunction
    }{
        {lua.LoadLibName, lua.OpenPackage}, // Must be first
        {lua.BaseLibName, lua.OpenBase},
        {lua.TabLibName, lua.OpenTable},
    } {
        if err := L.CallByParam(lua.P{
            Fn:      L.NewFunction(pair.f),
            NRet:    0,
            Protect: true,
        }, lua.LString(pair.n)); err != nil {
            panic(err)
        }
    }
    if err := L.DoFile("main.lua"); err != nil {
        panic(err)
    }
}

Creating a module by Go

mymodule.go

package mymodule

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

func Loader(L *lua.LState) int {
    // register functions to the table
    mod := L.SetFuncs(L.NewTable(), exports)
    // register other stuff
    L.SetField(mod, "name", lua.LString("value"))

    // returns the module
    L.Push(mod)
    return 1
}

var exports = map[string]lua.LGFunction{
    "myfunc": myfunc,
}

func myfunc(L *lua.LState) int {
    return 0
}

mymain.go

package main

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

func main() {
    L := lua.NewState()
    defer L.Close()
    L.PreloadModule("mymodule", mymodule.Loader)
    if err := L.DoFile("main.lua"); err != nil {
        panic(err)
    }
}

main.lua

local m = require("mymodule")
m.myfunc()
print(m.name)

Calling Lua from Go

L := lua.NewState()
defer L.Close()
if err := L.DoFile("double.lua"); err != nil {
    panic(err)
}
if err := L.CallByParam(lua.P{
    Fn: L.GetGlobal("double"),
    NRet: 1,
    Protect: true,
    }, lua.LNumber(10)); err != nil {
    panic(err)
}
ret := L.Get(-1) // returned value
L.Pop(1)  // remove received value

If Protect is false, GopherLua will panic instead of returning an error value.

User-Defined types

You can extend GopherLua with new types written in Go. LUserData is provided for this purpose.

type Person struct {
    Name string
}

const luaPersonTypeName = "person"

// Registers my person type to given L.
func registerPersonType(L *lua.LState) {
    mt := L.NewTypeMetatable(luaPersonTypeName)
    L.SetGlobal("person", mt)
    // static attributes
    L.SetField(mt, "new", L.NewFunction(newPerson))
    // methods
    L.SetField(mt, "__index", L.SetFuncs(L.NewTable(), personMethods))
}

// Constructor
func newPerson(L *lua.LState) int {
    person := &Person{L.CheckString(1)}
    ud := L.NewUserData()
    ud.Value = person
    L.SetMetatable(ud, L.GetTypeMetatable(luaPersonTypeName))
    L.Push(ud)
    return 1
}

// Checks whether the first lua argument is a *LUserData with *Person and returns this *Person.
func checkPerson(L *lua.LState) *Person {
    ud := L.CheckUserData(1)
    if v, ok := ud.Value.(*Person); ok {
        return v
    }
    L.ArgError(1, "person expected")
    return nil
}

var personMethods = map[string]lua.LGFunction{
    "name": personGetSetName,
}

// Getter and setter for the Person#Name
func personGetSetName(L *lua.LState) int {
    p := checkPerson(L)
    if L.GetTop() == 2 {
        p.Name = L.CheckString(2)
        return 0
    }
    L.Push(lua.LString(p.Name))
    return 1
}

func main() {
    L := lua.NewState()
    defer L.Close()
    registerPersonType(L)
    if err := L.DoString(`
        p = person.new("Steeve")
        print(p:name()) -- "Steeve"
        p:name("Alice")
        print(p:name()) -- "Alice"
    `); err != nil {
        panic(err)
    }
}

Terminating a running LState

GopherLua supports the [Go Concurrency Patterns:

Extension points exported contracts — how you extend this code

LValue (Interface)
(no doc) [5 implementers]
value.go
ConstExpr (Interface)
* ConstExprs {{{ */ [1 implementers]
ast/expr.go
PositionHolder (Interface)
(no doc) [1 implementers]
ast/ast.go
Stmt (Interface)
(no doc) [1 implementers]
ast/stmt.go
LGFunction (FuncType)
(no doc)
value.go
Expr (Interface)
(no doc) [1 implementers]
ast/expr.go

Core symbols most depended-on inside this repo

Push
called by 485
_state.go
Get
called by 194
state.go
Type
called by 118
value.go
String
called by 102
value.go
RaiseError
called by 101
state.go
sline
called by 88
compile.go
Close
called by 85
state.go
GetTop
called by 75
state.go

Shape

Method 505
Function 468
Struct 113
TypeAlias 17
Interface 13
FuncType 3

Languages

Go100%

Modules by API surface

state.go174 symbols
_state.go174 symbols
compile.go117 symbols
pm/pm.go50 symbols
value.go40 symbols
iolib.go39 symbols
auxlib.go39 symbols
state_test.go38 symbols
baselib.go33 symbols
parse/lexer.go32 symbols
mathlib.go30 symbols
ast/expr.go26 symbols

Dependencies from manifests, versioned

github.com/chzyer/logexv1.1.10 · 1×
github.com/chzyer/readlinev0.0.0-2018060313265 · 1×
github.com/chzyer/testv0.0.0-2018021303581 · 1×
golang.org/x/sysv0.0.0-2019020420370 · 1×

For agents

$ claude mcp add gopher-lua \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact