Giter Site home page Giter Site logo

gopher-lua's Introduction

GopherLua: VM and compiler for Lua in Go.

https://github.com/yuin/gopher-lua/workflows/test/badge.svg?branch=master&event=push https://coveralls.io/repos/github/yuin/gopher-lua/badge.svg?branch=master Join the chat at https://gitter.im/yuin/gopher-lua

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.

  • 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.

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 .

go get github.com/yuin/gopher-lua

GopherLua supports >= Go1.9.

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: Context .

L := lua.NewState()
defer L.Close()
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
// set the context to our LState
L.SetContext(ctx)
err := L.DoString(`
  local clock = os.clock
  function sleep(n)  -- seconds
    local t0 = clock()
    while clock() - t0 <= n do end
  end
  sleep(3)
`)
// err.Error() contains "context deadline exceeded"

With coroutines

L := lua.NewState()
defer L.Close()
ctx, cancel := context.WithCancel(context.Background())
L.SetContext(ctx)
defer cancel()
L.DoString(`
    function coro()
          local i = 0
          while true do
            coroutine.yield(i)
                i = i+1
          end
          return i
    end
`)
co, cocancel := L.NewThread()
defer cocancel()
fn := L.GetGlobal("coro").(*LFunction)

_, err, values := L.Resume(co, fn) // err is nil

cancel() // cancel the parent context

_, err, values = L.Resume(co, fn) // err is NOT nil : child context was canceled

Note that using a context causes performance degradation.

time ./glua-with-context.exe fib.lua
9227465
0.01s user 0.11s system 1% cpu 7.505 total

time ./glua-without-context.exe fib.lua
9227465
0.01s user 0.01s system 0% cpu 5.306 total

Sharing Lua byte code between LStates

Calling DoFile will load a Lua script, compile it to byte code and run the byte code in a LState.

If you have multiple LStates which are all required to run the same script, you can share the byte code between them, which will save on memory. Sharing byte code is safe as it is read only and cannot be altered by lua scripts.

// CompileLua reads the passed lua file from disk and compiles it.
func CompileLua(filePath string) (*lua.FunctionProto, error) {
    file, err := os.Open(filePath)
    defer file.Close()
    if err != nil {
        return nil, err
    }
    reader := bufio.NewReader(file)
    chunk, err := parse.Parse(reader, filePath)
    if err != nil {
        return nil, err
    }
    proto, err := lua.Compile(chunk, filePath)
    if err != nil {
        return nil, err
    }
    return proto, nil
}

// DoCompiledFile takes a FunctionProto, as returned by CompileLua, and runs it in the LState. It is equivalent
// to calling DoFile on the LState with the original source file.
func DoCompiledFile(L *lua.LState, proto *lua.FunctionProto) error {
    lfunc := L.NewFunctionFromProto(proto)
    L.Push(lfunc)
    return L.PCall(0, lua.MultRet, nil)
}

// Example shows how to share the compiled byte code from a lua script between multiple VMs.
func Example() {
    codeToShare := CompileLua("mylua.lua")
    a := lua.NewState()
    b := lua.NewState()
    c := lua.NewState()
    DoCompiledFile(a, codeToShare)
    DoCompiledFile(b, codeToShare)
    DoCompiledFile(c, codeToShare)
}

Goroutines

The LState is not goroutine-safe. It is recommended to use one LState per goroutine and communicate between goroutines by using channels.

Channels are represented by channel objects in GopherLua. And a channel table provides functions for performing channel operations.

Some objects can not be sent over channels due to having non-goroutine-safe objects inside itself.

  • a thread(state)
  • a function
  • an userdata
  • a table with a metatable

You must not send these objects from Go APIs to channels.

func receiver(ch, quit chan lua.LValue) {
    L := lua.NewState()
    defer L.Close()
    L.SetGlobal("ch", lua.LChannel(ch))
    L.SetGlobal("quit", lua.LChannel(quit))
    if err := L.DoString(`
    local exit = false
    while not exit do
      channel.select(
        {"|<-", ch, function(ok, v)
          if not ok then
            print("channel closed")
            exit = true
          else
            print("received:", v)
          end
        end},
        {"|<-", quit, function(ok, v)
            print("quit")
            exit = true
        end}
      )
    end
  `); err != nil {
        panic(err)
    }
}

func sender(ch, quit chan lua.LValue) {
    L := lua.NewState()
    defer L.Close()
    L.SetGlobal("ch", lua.LChannel(ch))
    L.SetGlobal("quit", lua.LChannel(quit))
    if err := L.DoString(`
    ch:send("1")
    ch:send("2")
  `); err != nil {
        panic(err)
    }
    ch <- lua.LString("3")
    quit <- lua.LTrue
}

func main() {
    ch := make(chan lua.LValue)
    quit := make(chan lua.LValue)
    go receiver(ch, quit)
    go sender(ch, quit)
    time.Sleep(3 * time.Second)
}
Go API

ToChannel, CheckChannel, OptChannel are available.

Refer to Go doc(LState methods) for further information.

Lua API
  • channel.make([buf:int]) -> ch:channel
    • Create new channel that has a buffer size of buf. By default, buf is 0.
  • channel.select(case:table [, case:table, case:table ...]) -> {index:int, recv:any, ok}
    • Same as the select statement in Go. It returns the index of the chosen case and, if that case was a receive operation, the value received and a boolean indicating whether the channel has been closed.
    • case is a table that outlined below.
      • receiving: {"|<-", ch:channel [, handler:func(ok, data:any)]}
      • sending: {"<-|", ch:channel, data:any [, handler:func(data:any)]}
      • default: {"default" [, handler:func()]}

channel.select examples:

local idx, recv, ok = channel.select(
  {"|<-", ch1},
  {"|<-", ch2}
)
if not ok then
    print("closed")
elseif idx == 1 then -- received from ch1
    print(recv)
elseif idx == 2 then -- received from ch2
    print(recv)
end
channel.select(
  {"|<-", ch1, function(ok, data)
    print(ok, data)
  end},
  {"<-|", ch2, "value", function(data)
    print(data)
  end},
  {"default", function()
    print("default action")
  end}
)
  • channel:send(data:any)
    • Send data over the channel.
  • channel:receive() -> ok:bool, data:any
    • Receive some data over the channel.
  • channel:close()
    • Close the channel.
The LState pool pattern

To create per-thread LState instances, You can use the sync.Pool like mechanism.

type lStatePool struct {
    m     sync.Mutex
    saved []*lua.LState
}

func (pl *lStatePool) Get() *lua.LState {
    pl.m.Lock()
    defer pl.m.Unlock()
    n := len(pl.saved)
    if n == 0 {
        return pl.New()
    }
    x := pl.saved[n-1]
    pl.saved = pl.saved[0 : n-1]
    return x
}

func (pl *lStatePool) New() *lua.LState {
    L := lua.NewState()
    // setting the L up here.
    // load scripts, set global variables, share channels, etc...
    return L
}

func (pl *lStatePool) Put(L *lua.LState) {
    pl.m.Lock()
    defer pl.m.Unlock()
    pl.saved = append(pl.saved, L)
}

func (pl *lStatePool) Shutdown() {
    for _, L := range pl.saved {
        L.Close()
    }
}

// Global LState pool
var luaPool = &lStatePool{
    saved: make([]*lua.LState, 0, 4),
}

Now, you can get per-thread LState objects from the luaPool .

func MyWorker() {
   L := luaPool.Get()
   defer luaPool.Put(L)
   /* your code here */
}

func main() {
    defer luaPool.Shutdown()
    go MyWorker()
    go MyWorker()
    /* etc... */
}

Goroutines

  • GopherLua supports channel operations.
    • GopherLua has a type named channel.
    • The channel table provides functions for performing channel operations.

Unsupported functions

  • string.dump
  • os.setlocale
  • lua_Debug.namewhat
  • package.loadlib
  • debug hooks

Miscellaneous notes

  • collectgarbage does not take any arguments and runs the garbage collector for the entire Go program.
  • file:setvbuf does not support a line buffering.
  • Daylight saving time is not supported.
  • GopherLua has a function to set an environment variable : os.setenv(name, value)
  • GopherLua support goto and ::label:: statement in Lua5.2.
    • goto is a keyword and not a valid variable name.

Lua has an interpreter called lua . GopherLua has an interpreter called glua .

go get github.com/yuin/gopher-lua/cmd/glua

glua has same options as lua .

See Guidlines for contributors .

  • gopher-luar : Simplifies data passing to and from gopher-lua
  • gluamapper : Mapping a Lua table to a Go struct
  • gluare : Regular expressions for gopher-lua
  • gluahttp : HTTP request module for gopher-lua
  • gopher-json : A simple JSON encoder/decoder for gopher-lua
  • gluayaml : Yaml parser for gopher-lua
  • glua-lfs : Partially implements the luafilesystem module for gopher-lua
  • gluaurl : A url parser/builder module for gopher-lua
  • gluahttpscrape : A simple HTML scraper module for gopher-lua
  • gluaxmlpath : An xmlpath module for gopher-lua
  • gmoonscript : Moonscript Compiler for the Gopher Lua VM
  • loguago : Zerolog wrapper for Gopher-Lua
  • gluacrypto : A native Go implementation of crypto library for the GopherLua VM.
  • gluasql : A native Go implementation of SQL client for the GopherLua VM.
  • purr : A http mock testing tool.
  • vadv/gopher-lua-libs : Some usefull libraries for GopherLua VM.
  • gluasocket : A native Go implementation of LuaSocket for the GopherLua VM.
  • glua-async : An async/await implement for gopher-lua.
  • gopherlua-debugger : A debugger for gopher-lua
  • gluamahonia : An encoding converter for gopher-lua

BTC: 1NEDSyUmo4SMTDP83JJQSWi1MvQUGGNMZB

MIT

Yusuke Inuzuka

gopher-lua's People

Contributors

0x501d avatar alaingilbert avatar aleksi avatar bbigras avatar cathalgarvey avatar cjoudrey avatar drauschenbach avatar eclipseo avatar erikdubbelboer avatar felipejfc avatar ferbivore avatar hajimehoshi avatar haraldnordgren avatar jlauinger avatar joesonw avatar johejo avatar jtolio avatar kohkimakimoto avatar lionnatsu avatar lonng avatar lucasew avatar mdogan avatar mzki avatar mzxdream avatar tongson avatar ttys3 avatar tul avatar waschik avatar xtaci avatar yuin 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  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

gopher-lua's Issues

How to extend string builtins

How would I go about to implement the following Lua function in Go? I can't figure out how to extend the string global.

function string:split(sep, cb)
    local sep, fields = sep or ":", {}
    local pattern = string.format("([^%s]+)", sep)

    if cb then
        assert(type(cb) == "function")
        self:gsub(pattern, cb)
    end

    self:gsub(pattern, function(c) fields[#fields+1] = c end)

    i = 0
    n = table.getn(fields)
    return function()
        i = i + 1
        if i <= n then return fields[i] end
    end
end

io.read panic

I have the following code:

package main

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

var luaProgram = `
    -- defines a factorial function
    function fact (n)
      if n == 0 then
        return 1
      else
        return n * fact(n-1)
      end
    end

    print("enter a number:")
    a = io.read("*number")        -- read a number
    print(fact(5))
`

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

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

When I run it, I get the following error:

enter a number:
15
panic: <string>:12: bad argument #2 to read (invalid options:u)
stack traceback:
        [G]: in read
        <string>:12: in function 'main chunk'
        [G]: ?

goroutine 1 [running]:
main.main()
        C:/Users/Bruno.Panuto/Desktop/lua.go:27 +0xc7

By using the glua interpreter, I get the same panic.
Using Windows may be the case, but I am just wondering what went wrong.

Thanks!

Changes for Lua 5.2/5.3 compliance?

Do you have any plan to make the changes necessary to be a compliant environment for newer (minor) versions of Lua? Of course Lua 5.3.2 being the latest version as of today. I don't really have a need for it. But I think it would be nice.

One thing I have noticed as a difference between the lua C programs is that the lua5.3 interactive interpreter (REPL) works much better than the lua5.1 repl. Surely more work has gone into it's presentation but, having implemented a simple REPL in 5.1 and seen the limitations, I wasn't sure if it was actually just easier to build a good REPL using the 5.3 C API.

From what I understand the Go API has some functions that correspond to functions added to the C API in Lua 5.2. But I'm not entirely sure about C API changes in Lua 5.3.

Is the existing Lua module API strictly compliant exactly with Lua 5.1? Or is in somewhere in-between versions?

`os.execute` doesn't work.

I found a bug: os.execute doesn't work.

I created like the following lua file.

-- execute_test.lua
os.execute("echo OK")

And I tried to run it, but I got a error.

$ go build cmd/glua/glua.go
$ ./glua execute_test.lua
-c: echo OK: No such file or directory

NOTE:My go version is 1.4.2

$ go version
go version go1.4.2 darwin/amd64

gsub(".", ...) does not iterate over newlines

I was trying to iterate over a string's characters with gsub and found this particular implementation difference (which I assume is considered a bug):

Lua 5.1.5  Copyright (C) 1994-2012 Lua.org, PUC-Rio
> function newline(str) str:gsub(".", function(c) print(c == '\n') end) end
> newline("a\nb")
false
true
false

GopherLua 0.1 Copyright (C) 2015 Yusuke Inuzuka
> function newline(str) str:gsub(".", function(c) print(c == '\n') end) end
> newline("a\nb")
false
false

Turns out RE2 regular expressions do not match newlines by default - the s flag needs to be set. As far as I can tell, this is done by prepending (?s) to the regex string. In other words:

--- a/utils.go
+++ b/utils.go
@@ -286,7 +286,7 @@ func compileLuaRegex(pattern string) (*regexp.Regexp, error) {
-       gopattern := sc.String()
+       gopattern := "(?s)" + sc.String()

Pull request incoming.

pcall doesn't work in conjection with coroutines.

It seems that coroutines and pcalls don't quite work right when nested. I'm not sure why it is, but pcalls and coroutine don't play nice together.

This is a complete example program, that reproduces the problem. It should be spitting out several Tick: lines, instead it's spitting nothing out at all.

If you comment out the pcall stuff, it functions as expected.

package main

import (
    "fmt"

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

func main() {
    state := lua.NewState()
    state.OpenLibs()

    main, err := state.LoadString(threadLua)
    if err != nil {
        panic(err)
    }

    thread := state.NewThread()

    s, err, _ := state.Resume(thread, main)
    if err != nil {
        panic(err)
    }

    for i := 0; i < 20; i++ {
        if s != lua.ResumeError {
            s, err, _ = state.Resume(thread, main, lua.LString("tick"), lua.LNumber(i))

            if err != nil {
                fmt.Println("Error: ", err)

                break
            }
        }
    }
}

var threadLua = `
local ok, err = pcall(
  function()
    local evt, n = coroutine.yield()

        print("Tick: " .. n)

    if n > 10 then
      error("N is > 10")
    end
  end)

if not ok then
  print("Got Error: " .. err)
  error("Dying.")
end
`

how do you think of supporting closure serialization of lua?

Closure serialization, which means code and it's context partly evaluated.
In my business, If a closure can be serialized, it can be passed to remote service to apply with the input provided by the remote service.

a closure(args1...N) -----> remote service( apply(closure,input) )

This feature is quite useful in distributed system, for example:

  1. Data broker
  2. Remote data warehouse analyze

Without this, I can only implement Context(some arguments provided) + Code Snippet String (not evaluated) to remote service and Apply(Context, Args, Code)

How do you think of this?

PS:
https://github.com/jeremeamia/super_closure

inspect.lua doesn't work

Hi, I've just played with inspect.lua (https://github.com/kikito/inspect.lua) but it seems it doesn't work correctly.

for example)

macbook% ../bin/glua bug.lua

inspect.lua:300: attempt to index a non-table object(nil)
stack traceback:
inspect.lua:300: in inspect.lua:297
(tailcall): ?
bug.lua:2: in main chunk
[G]: ?
macbook% lua bug.lua
"hello"

https://gist.github.com/chobie/bcff5c74c915422a3075

also, I've tested above script with lua 5.1.5 and lua 5.2.3 and it works fine.
can you check this if you have a chance?

Thanks,

Is there a way to clone an LState?

I have an LState in which a user script has defined several functions. Is there a way to clone this LState in order to have two individual LStates that can get executed concurrently safely (they can eventually get killed individually, without affecting the other)?

Example:

L := lua.NewState()
L.DoString(script)

L2 := L.Clone()

go run(L)
go run(L2)
// "run()" is an arbitrary function that may call anything on L or L2 and also might crash

Accessing comments through AST

I would like to annotate Lua code through comments, much like Go to generate documentation. However, I don't see the comments when I call parse.Dump.

How should I access the comments through the AST?

-- relase <var> [optionalVar]
function target.relase(var, optionalVar)

end

Format error in baseLoadFile

The baseLoadFile function in baselib.go is using the incorrect Sprint version of the fmt package.

L.Push(LString(fmt.Sprint("can not open file: %v", chunkname)))

Should either be:

L.Push(LString(fmt.Sprintf("can not open file: %v", chunkname)))

or:

L.Push(LString(fmt.Sprint("can not open file:", chunkname)))

Golua compatibillity?

Can gopher-lua be made API compatible with golua?
The reason I ask is that it would be very nice to have Luar working with this!

I want to write a web framework module that can be called by gopher-lua, but some problems are encountered.

  • [YES] GopherLua is a Lua5.1 implementation. You should be familiar with Lua programming language. Have you read Lua 5.1 reference manual carefully?
  • [YES] GopherLua is a Lua5.1 implementation. In Lua, to keep it simple, it is more important to remove functionalities rather than to add functionalities unlike other languages . If you are going to introduce some new cool functionalities into the GopherLua code base and the functionalities can be implemented by existing APIs, It should be implemented as a library.

Please answer the following before submitting your issue:

  1. What version of GopherLua are you using? :
    lastest version
  2. What version of Go are you using? :
    Go 1.7.3
  3. What operating system and processor architecture are you using? :
    Linux (Ubuntu 14.04) 64bit
  4. What did you do? :
    I want to write a web framework module that can be called by gopher-lua.
  5. What did you expect to see? :
    Web framework in the normal work of gopher-lua
  6. What did you see instead? :
    Long time access (Press F5) or wrk stress tests will die

This is the test code:

package main

import (
    "fmt"
    "sync"
    "github.com/hoisie/web"
    lua "github.com/yuin/gopher-lua"
)

type lStatePool struct {
    m     sync.Mutex
    saved []*lua.LState
}

func (pl *lStatePool) Get() *lua.LState {
    pl.m.Lock()
    defer pl.m.Unlock()
    n := len(pl.saved)
    if n == 0 {
        return pl.New()
    }
    x := pl.saved[n-1]
    pl.saved = pl.saved[0 : n-1]
    return x
}

func (pl *lStatePool) New() *lua.LState {
    L := lua.NewState(lua.Options{
        IncludeGoStackTrace: true,
    })
    // setting the L up here.
    // load scripts, set global variables, share channels, etc...
    L.PreloadModule("web", Loader)

    return L
}

func (pl *lStatePool) Put(L *lua.LState) {
    pl.m.Lock()
    defer pl.m.Unlock()
    pl.saved = append(pl.saved, L)
}

func (pl *lStatePool) Shutdown() {
    for _, L := range pl.saved {
        L.Close()
    }
}

// Global LState pool
var luaPool = &lStatePool{
    saved: make([]*lua.LState, 0, 4),
}

func main() {

    defer func() {
        luaPool.Shutdown()
    }()
    /*
        L := lua.NewState(lua.Options{
            IncludeGoStackTrace: true,
        })
        defer L.Close()

        L.PreloadModule("web", Loader)
    */
    go vm()

    for {}
}

func vm() {
    L := luaPool.Get()
    defer luaPool.Put(L)
    if err := L.DoString(`
local web = require("web")
print("lua>> web", web.version)

print("lua>> web.app()")
local app = web.app()
local hello = 0
print("lua>> web.app:get()")
app:get("/", function ()
    hello = hello + 1
    print("lua>print:",hello)
    return ctx.render(hello)
end)

print("lua>> web.app:listen()")
app:listen("0.0.0.0", "8000")
`); err != nil {
        panic(err)
    }
}

//----------------------------------------------------//

const (
    lContextTypeName = "context"
    lAppClassName    = "App"
)

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

var appMethods = map[string]lua.LGFunction{
    "get":    get,
    "listen": listen,
}

func Loader(L *lua.LState) int {
    mod := L.SetFuncs(L.NewTable(), exports)
    L.SetField(mod, "version", lua.LString("0.0.1"))

    // returns the module
    L.Push(mod)

    mod2 := L.SetFuncs(L.NewTable(), selfMethods)
    L.SetGlobal("ctx", mod2)

    // returns the module
    L.Push(mod2)
    return 2
}

var selfMethods = map[string]lua.LGFunction{
    "render": render,
}

func render(L *lua.LState) int {
    s := L.CheckNumber(1)
    L.Push(lua.LNumber(s))
    return 1
}

func app(L *lua.LState) int {

    mt := L.NewTypeMetatable(lAppClassName)
    mt.RawSetString("__index", mt)
    L.SetFuncs(mt, appMethods)

    s := web.NewServer()
    ud := L.NewUserData()
    ud.Value = s
    L.SetMetatable(ud, L.GetTypeMetatable(lAppClassName))
    L.Push(ud)
    return 1

}

func checkServer(L *lua.LState) *web.Server {
    fmt.Println("L.GetTop():", L.GetTop())
    ud := L.CheckUserData(1)
    if v, ok := ud.Value.(*web.Server); ok {
        return v
    }
    L.ArgError(1, "*web.Server expected")
    return nil
}

func get(L *lua.LState) int {
    s := checkServer(L)

    path := L.CheckString(2)
    fn := L.CheckFunction(3)
    s.Get(path, func(self *web.Context) {
        L.Push(fn)
        L.Call(0, 1)
        hello := L.CheckNumber(L.GetTop())
        self.WriteString(hello.String())
    })
    return 0
}

func listen(L *lua.LState) int {

    s := checkServer(L)
    host := L.CheckString(2)
    port := L.CheckString(3)
    addr := fmt.Sprintf("%v:%v", host, port)

    s.Run(addr)
    return 0

}

Disallow nil table index

Currently, the following code runs without error:

local x = {}
x[nil] = "test"

To be consistent with Lua 5.1, it should raise a "table index is nil" error.

Weird memory leak

Im currently running a simple LUA script with a module

func (c *CharacterModule) characterDeaths(L *lua.LState) int {
    name := L.ToString(1)
    deaths, err := models.GetCharacterDeaths(name)
    if err != nil {
        L.Push(lua.LString(err.Error()))
        L.Push(lua.LNil)
        return 2
    }
    death_list := &lua.LTable{}
    for _, val := range deaths {
        t := &lua.LTable{}
        t.RawSetString("time", lua.LNumber(val.Time))
        t.RawSetString("level", lua.LNumber(val.Level))
        t.RawSetString("killed_by", lua.LString(val.Killed_by))
        t.RawSetString("most_damage_by", lua.LString(val.Mostdamage_by))
        death_list.Append(t)
    }
    L.Push(lua.LBool(true))
    L.Push(death_list)
    return 2
}
local character_module = require("character")
local errorx, deaths = character_module.getCharacterDeaths("Kassem G")
if(errorx ~= true) then
    return false
end
local template = {}
template["list"] = {}
template["list"]["killed_by"] = "load test"
return template

Im using a http load test tool and at a certain points my app goes from 0-1% CPU usage to 50%
Im doing something wrong while creating the lua table?

Im sure the error is in the getCharacterDeaths function since removing it makes my CPU usage go quiet

Also im sure its not this part

models.GetCharacterDeaths(name)

Since I teste that one alone and nothing happened

So maybe im missing something while working with lua tables?

README.rst issues

Two examples from the reference are wrong:
1、This example has two wrong. One is missing 'lua' package name for 'LTrue'. Another is 'bl,ok == ' should be ':='
lv := L.Get(-1) // get the value at the top of the stack

if lv == LTrue { // correct
}

if bl, ok == lv.(lua.LBool); ok && bool(bl) { // wrong
}
2、Missing 'lua' package again for the two functions.
lv := L.Get(-1) // get the value at the top of the stack
if LVIsFalse(lv) { // lv is nil or false
}

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

a large key of lua table not worked

local t = {
[123456789] = 'A',
}
if I set lua table key = 123456789, it is not worked

local t = {
[12345678] = 'A',
}
if i set lua table key = 12345678, it takes long time, almost 7 sec

how can i fix this problem??

Does not work `error` function in a loaded file by using `dofile`.

I tested the following 2 files code by using glua.

a.lua

dofile "b.lua"

b.lua

error("error!")

Run the command. Got a incorrect message: nil as the following.

$ glua a.lua
nil
stack traceback:
    [G]: in error
    b.lua:1: in <b.lua:0>
    [G]: in dofile
    a.lua:1: in function 'main chunk'
    [G]: ?

And I also tested running the original C lua5.1 in the same way. Got a correct error message.

$ lua5.1 a.lua
lua5.1: b.lua:1: error!
stack traceback:
    [C]: in function 'error'
    b.lua:1: in main chunk
    [C]: in function 'dofile'
    a.lua:1: in main chunk
    [C]: ?

coroutine can't yield after a require call

I have a simple test:

--test.lua
local a,b

a = function ()
require "inityx_dtba"
print("a")
coroutine.yield()
b()
end

b = function ()
print("b")
end

local co = coroutine.create(a)
local ok = coroutine.resume(co)
while ok do
ok = coroutine.resume(co)
end

It's ok in clua5.1, but when run in glua, I got these error info
It's very important for me to load module in coroutine, how can solve this issue?

[G]: can not yield from outside of a coroutine
stack traceback:
[G]: in yield
test.lua:11: in main chunk
[G]: ?
goroutine 1 [running]:
github.com/yuin/gopher-lua.func·007()
e:/fei/go/src/github.com/yuin/gopher-lua/state.go:1365 +0x17e
github.com/yuin/gopher-lua.func·010()
e:/fei/go/src/github.com/yuin/gopher-lua/vm.go:118 +0x1a2
github.com/yuin/gopher-lua.func·002(0xc082014410)
e:/fei/go/src/github.com/yuin/gopher-lua/coroutinelib.go:75 +0x72
github.com/yuin/gopher-lua.(_LState).raiseError(0xc082014410, 0x1, 0x650570, 0x2
9, 0x0, 0x0, 0x0)
e:/fei/go/src/github.com/yuin/gopher-lua/state.go:354 +0x364
github.com/yuin/gopher-lua.(_LState).RaiseError(0xc082014410, 0x650570, 0x29, 0x
0, 0x0, 0x0)
e:/fei/go/src/github.com/yuin/gopher-lua/state.go:1065 +0x6a
github.com/yuin/gopher-lua.switchToParentThread(0xc082014410, 0x0, 0xc0820d0000)

    e:/fei/go/src/github.com/yuin/gopher-lua/vm.go:44 +0x71

github.com/yuin/gopher-lua.callGFunction(0xc082014410, 0x0, 0xc082006dc0)
e:/fei/go/src/github.com/yuin/gopher-lua/vm.go:74 +0xe6
github.com/yuin/gopher-lua.func·033(0xc082014410, 0xc070000201, 0x0, 0x0)
e:/fei/go/src/github.com/yuin/gopher-lua/vm.go:493 +0x22c
github.com/yuin/gopher-lua.mainLoop(0xc082014410, 0x0)
e:/fei/go/src/github.com/yuin/gopher-lua/vm.go:27 +0x119
github.com/yuin/gopher-lua.threadRun(0xc082014410)
e:/fei/go/src/github.com/yuin/gopher-lua/vm.go:122 +0x96
github.com/yuin/gopher-lua.coResume(0xc0820143c0, 0x0)
e:/fei/go/src/github.com/yuin/gopher-lua/coroutinelib.go:82 +0x450
github.com/yuin/gopher-lua.callGFunction(0xc0820143c0, 0x0, 0xc082006e00)
e:/fei/go/src/github.com/yuin/gopher-lua/vm.go:67 +0x40
github.com/yuin/gopher-lua.func·033(0xc0820143c0, 0xc0700c0402, 0x0, 0x0)
e:/fei/go/src/github.com/yuin/gopher-lua/vm.go:493 +0x22c
github.com/yuin/gopher-lua.mainLoop(0xc0820143c0, 0x0)
e:/fei/go/src/github.com/yuin/gopher-lua/vm.go:27 +0x119
github.com/yuin/gopher-lua.(_LState).callR(0xc0820143c0, 0x0, 0xffffffffffffffff
, 0x0)
e:/fei/go/src/github.com/yuin/gopher-lua/state.go:712 +0x21f
github.com/yuin/gopher-lua.(_LState).Call(0xc0820143c0, 0x0, 0xffffffffffffffff)

    e:/fei/go/src/github.com/yuin/gopher-lua/state.go:1348 +0x4c

github.com/yuin/gopher-lua.(_LState).PCall(0xc0820143c0, 0x0, 0xffffffffffffffff
, 0x0, 0x0, 0x0)
e:/fei/go/src/github.com/yuin/gopher-lua/state.go:1396 +0x15b
github.com/yuin/gopher-lua.(_LState).DoFile(0xc0820143c0, 0xc0820021f0, 0x8, 0x0
, 0x0)
e:/fei/go/src/github.com/yuin/gopher-lua/auxlib.go:385 +0xec
main.main()
e:/fei/go/src/github.com/yuin/gopher-lua/cmd/glua/glua.go:89 +0x769

debug.traceback: does not support level argument

The debug.traceback function does not support the level argument as described in the reference manual.

This seems like it would be helpful for writing error handling functions (edit: specifically for use with PCall). As it is now, I think the error handling function is included in any stracktrace the error handling function produces.

GopherLua does not support shebang.

Original Lua5.1 implementation supports shebang.
But GopherLua does not support it.

Example: example.lua

#!/path/to/glua
print("hoge")
$ ./example.lua
./example.lua line:1(column:1) near '#':   syntax error

my environment:

go version go1.6 darwin/amd64

__call on module doesn't work

Hi, I'm trying to define a __call on a module like:

    mt := L.NewTypeMetatable("CUSTOM_MAP")

    L.SetGlobal("Map", mt)

    // static attributes
    L.SetField(mt, "__call", L.NewFunction(newLuaMap))
    L.SetField(mt, "create", L.NewFunction(newLuaMap))

If I call:

m = Map.create()

everything works as expected. But if I do

m = Map()

I get attempt to call a non-function object.

Is there a way I can get this to work?

How to limit access to filesystem?

If I wanted to "lockdown" the Lua thread running so that it only had access to a select number of files that are predetermined or determined by an outside source how would I go about doing so?

I can't seem to find any functions that would allow this behavior so the only course of action I can see is replacing loRequire with something else (reassigning that global) and removing the load* series and dofile methods.

Do you have a clean way to do this other than what my thoughts are?

Also, secondary, but you don't provide any examples of LUserData on the README, perhaps add some? As I become more familiar with this library, if you haven't done them already I'll see about doing it to help out.

Does not work `LState.Close()` after `os.Exit()`

Hello yuin.

defer doesn't run when you use os.Exit() to terminate a process.
Therefore, LState.Close() doesn't run in some cases.

This behavior causes an issue. For instance. A temporary file that is created by io.tmpfile() is not deleted automatically by using glua. Please see the following steps.

Create test.lua

-- test.lua
local t = io.tmpfile()
t:write("This is a tempfile.")

Run the test.lua file by glua.

glua test.lua

Look for the file. At my environment(OSX 10.10.4), the temporary file generated under the /var/folders/bt/xwh9qmcj00dctz53_rxclgtr0000gn/T. It is not deleted.

ls -ltr /var/folders/bt/xwh9qmcj00dctz53_rxclgtr0000gn/T

...
-rw-------   1 kohkimakimoto  staff       6  8 19 13:41 311400111
-rw-------   1 kohkimakimoto  staff       6  8 19 13:42 445911440
-rw-------   1 kohkimakimoto  staff       6  8 19 13:44 305149766
-rw-------   1 kohkimakimoto  staff       6  8 19 13:45 960108122
-rw-------   1 kohkimakimoto  staff       6  8 19 13:45 607108666
-rw-------   1 kohkimakimoto  staff       6  8 19 13:45 731094900
-rw-------   1 kohkimakimoto  staff       6  8 19 13:45 166451425
-rw-------   1 kohkimakimoto  staff       8  8 19 13:48 977724667
-rw-r--r--   1 kohkimakimoto  staff     132  8 19 18:53 lsuseractivityd.log
drwx------@  3 kohkimakimoto  staff     102  8 19 19:59 com.apple.mail
drwx------@  2 kohkimakimoto  staff      68  8 20 06:08 com.apple.corerecents.recentsd
-rw-------   1 kohkimakimoto  staff    4011  8 20 06:12 xcrun_db
drwxr-xr-x   2 kohkimakimoto  staff      68  8 20 06:24 TemporaryItems
-rw-------   1 kohkimakimoto  staff      19  8 20 07:39 440567778

cat 440567778
This is a tempfile.

thread-safety and convenience of `basePrint`

The basePrint function in baselib.go currently writes its output directly to os.Stdout through fmt.Print and friends. This will cause problems when calling print from multiple goroutines/Lua threads. Notably, that multiple Print calls can have their output interwoven on the console when they both run in parallel. For example:

fmt.Println("foo and bar")
go fmt.Println("bar and foo")

Can, under certain conditions, give output like this:

foo abar and foo
nd bar

Additionally, it would be nice if the host application could specify a different output target, aside from the default os.Stdout. For this reason, I'm wondering if it is not more convenient and safer to have it use Go's log package instead. This package takes care of both problems at once.

The changes to this package can be as simple as:

func basePrint(L *LState) int {
    var buf bytes.Buffer

    top := L.GetTop()
    for i := 1; i <= top; i++ {
        fmt.Fprint(&buf, L.Get(i).String(), " ")
    }

    log.Println(buf.String())
    return 0
}

The host now has the guarantee that logging stuff is thread safe and the output can be redirected to any target by calling log.SetOutput(io.Writer).

Readme - concurrency.

Could you write a readme heading describing how go routines interact with gopher-lua, and what is/isn't thread safe.

Table index 0 causes infinite loop

The following code causes an infinite loop for me on master/Go 1.7/Linux:

local tbl = {
        [-1] = "a",
        [0] = "b",
        [1] = "c",
}
for k, v in pairs(tbl) do
        print(k, v)
end

Output:

-1      a
0       b
1       c
-1      a
0       b
1       c
... and so on

It only happens with numeric zero (not string). I know Lua indexing starts with 1 instead of 0. But in some calculations a zero may be stored in tables, and this loop does not happen in reference Lua 5.1.

Minimal test case:

local tbl = { [0] = true }
assert(next(tbl, 0) == nil) -- should be nil, but is 0

Possible stack issue

I discovered this when I was integrating gopher-lua and I'm able to reproduce it with glua as well. To work around the issue I had to assign the string.format to a temporary variable and then do the var1 = var1 or var2 statement.

test.lua

function test(a, b, c)
    b = b or string.format("test %v", a)
    c = c or string.format("test %v", a)

    print("a:", a)
    print("b:", b)
    print("c:", c)
end

Run

glua test.lua hello

Expected result:

a:  hello
b:  test hello
c:  test hello

Actual result:

a:  hello
b:  test hello
c:  test %v

Consistent, structured returned error value

Hi. Really loving gopher-lua so far and excited to keep using it! Thanks for making it.

I'm struggling with error handling at the moment. Basically, I'd like to do my own error reporting. The error returned from the Do functions could be a concrete type that has structured access to the line and column number and error message.

if err := L.DoString(luaCode); err != nil {
    // access to line and column number here would be awesome!
}

If you're too busy, I'm willing to make the changes but am not sure the best way to go about it. The DoString function could still return error type, but the underlying type (after a type assertion) would ideally expose the line, column numbers as well as the error message and maybe error type (syntax, etc.). Right now the error is like this:

&lua.ApiError{Type:0, Object:"<string> line:3(column:5) near 'if':   syntax error\n", StackTrace:""}

But I'd like to be able to do this:

if err := L.DoString(luaCode); err != nil {
    luaErr := err.(lua.Error) // or whatever
    fmt.Println("Error type:", luaErr.Type)
    fmt.Println("Line:", luaErr.Line)
    fmt.Println("Col:", luaErr.Column)
    fmt.Println("Message:", luaErr.Message)
}

What do you think?

Allow installing some but not all of the standard modules.

Currently you can have all, or no standard modules. This produces problems when, for example, you need everything but os and io to be available.

The fix for this is trivial, just export the openXYZ functions so that users can install modules piecemeal if they wish.

Wrong value/infinite loop in for-loop when using iterators with tail calls

Oh well, sorry, I ran into a second issue :)

When I use a coroutine together with a for-loop, the second value is wrong - the first value is duplicated. If I call the coroutine manually, everything is fine.

Example:

local cr = function()
        return coroutine.wrap(function()
                coroutine.yield(1, "a")
                coroutine.yield(2, "b")
        end)
end

for k, v in cr() do
        print("for:", k, v)
end

print("------")

local f = cr()
print("call:", f())
print("call:", f())

Output:

for:    1       1
for:    2       2
------
call:   1       a
call:   2       b

Thanks again!

Sandbox environment

Hi,

gopher-lua works great. I actually found out about it when Shopify announced its version (go-lua).
One thing I'd like to be able to do it to provide a sandbox environment for executing user scripts, e.g. preventing access to files and executing commands. Is this possible with gopher-lua?

Thanks!

Non-latin characters not working in regular expressions

I'm writing a plugin for Piepan which uses gopher-lua for its Go Lua plugins. I use string.match() in such a plugin with a regular expression to sanitize user input. However, this does not work with non-latin characters such as the Danish æ, ø and å. Regular expression implementations usually support this.

A trivial example:

print(string.match("abc", "abc"))
abc

print(string.match("æøå", "æøå"))
nil

Is this a bug in gopher-lua? Or do I need to do something special to be able to use these non-latin characters?

(I originally filed this bug against Piepan here: layeh/piepan#31. The author sent me here.)

Problem to Add nested LTable in LTable

When using RawSet function to add a LTable inside another LTable, I got error like

cannot use *llTable (type lua.LTable) as type lua.LValue in argument to ltable.RawSet: lua.LTable does not implement lua.LValue (String method has pointer receiver)

but I see lua.LTable do implement lua.LValue interface. any idea?

Best way to set LUA_PATH?

Hi,
I love working with gopher-lua so far. The documentation is lacking tough.
What would be the best way to set the LUA_PATH? In order to load modules from subdirectories located relative to the executed .lua file, I need to set it to the directory of that .lua file.
I discovered that the Lua vm uses system env variables set via os.Setenv(). Is there a way to set / append to the LUA_PATH just for one vm (e.g. vm.Setenv())?

Thanks

FYI

Thanks for writing gopher-lua, I'm using in it algernon and it works great!

Best regards,
Alexander F Rødseth

Golang packages

Hi,

I want integrate it on my project:
https://github.com/prsolucoes/goci

It is a continuous integration system that use script files to make the build steps. I want use LUA now. How i can export some Go packages to this implementation? How multiple return works in this LUA implementation?

Thanks.

table.remove does not return deleted element

From the Lua 5.1 manual (emphasis mine):

table.remove (table [, pos])

Removes from table the element at position pos, shifting down other elements to close the space, if necessary. Returns the value of the removed element. The default value for pos is n, where n is the length of the table, so that a call table.remove(t) removes the last element of table t.

This should probably be fixed for capability.

Call Go from Lua

I started playing with this library, and have really liked it so far. Except for this annoying method of creating Go funcs that can be called from Lua. Its been constructed that you have to write these methods with the lua state in mind.

I have written a proof on concept function that essentially replaces LState.NewFunction with an interface{} that is typed checked to ensure it has been passed a function, then essentially does some reflection magic to automatically get all the required arguments from the state in their proper types, call the function with the arguments, and push any and all of the results back onto the stack in their respective orders.

Heres an example

func Double(i int) int {
    return i * 2
}

func main() { 
    L := lua.NewState()
    defer L.Close()
    L.SetGlobal("double", L.NewFunc(Double)) /* wraps Double and called newLFunction */
}

If this is something that you'd accept into the codebase I will finish my implementation, currently it only works with ints as arguments and returns (Not for any particular purpose, I just chose a type to test with)

os.clock should not use wall time

The current implementation of os.clock uses a simple "seconds since the app started" but the LUA reading material I'm seeing (e.g. http://www.lua.org/pil/22.1.html) says it should be CPU-seconds.

I don't know of a clean way to get this via the Go runtime, so this would require a kernel call to getrusage (http://linux.die.net/man/2/getrusage). For some reason getrusage returns wall clock time on Windows, so you'll need to use GetProcessTimes (https://msdn.microsoft.com/en-us/library/windows/desktop/ms683223.aspx).

Debug hooks

Hi! Awesome work on this! I've been prototyping a project which uses gopher-lua to run user-defined scripts in a sandbox, and everything has been going pretty smoothly so far, however I would like to implement some resource constraints on scripts which are run, and based on previous experience, the way this is done is with debug hooks. Is there a specific reason why they are not exposed in gopher-lua? Is it possible to add them? I see that the shopify/go-lua project does implement them, but they are missing other things that gopher-lua has, and as far as I can tell, is not particularly active. Rather than cut over to using shopify/go-lua, I'm wondering if it's feasible to implement the debug hooks in gopher-lua, and wanted to get some background on why they were omitted before I started working on anything.

Thanks!

string.find, string.sub with 0 index

test case:

print(string.find('hello.world', '.', 0))
print('str = ' .. string.sub('hello.world', 0, 2))

Output of Lua:

1    1
str = he

Output of gopher-lua:

nil
str =

0 start index is 1 in Lua.
0 start index is the end(length) of the string in gopher-lua.

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.