mirror of https://github.com/tidwall/tile38.git
cfc65a13f6
This commit includes updates that affects the build, testing, and deployment of Tile38. - The root level build.sh has been broken up into multiple scripts and placed in the "scripts" directory. - The vendor directory has been updated to follow the Go modules rules, thus `make` should work on isolated environments. Also some vendored packages may have been updated to a later version, if needed. - The Makefile has been updated to allow for making single binaries such as `make tile38-server`. There is some scaffolding during the build process, so from now on all binaries should be made using make. For example, to run a development version of the tile38-cli binary, do this: make tile38-cli && ./tile38-cli not this: go run cmd/tile38-cli/main.go - Travis.CI docker push script has been updated to address a change to Docker's JSON repo meta output, which in turn fixes a bug where new Tile38 versions were not being properly pushed to Docker |
||
---|---|---|
.. | ||
ast | ||
parse | ||
pm | ||
.travis.yml | ||
LICENSE | ||
Makefile | ||
README.rst | ||
_state.go | ||
_vm.go | ||
alloc.go | ||
auxlib.go | ||
baselib.go | ||
channellib.go | ||
compile.go | ||
config.go | ||
coroutinelib.go | ||
debuglib.go | ||
function.go | ||
iolib.go | ||
linit.go | ||
loadlib.go | ||
mathlib.go | ||
opcode.go | ||
oslib.go | ||
package.go | ||
state.go | ||
stringlib.go | ||
table.go | ||
tablelib.go | ||
utils.go | ||
value.go | ||
vm.go |
README.rst
=============================================================================== GopherLua: VM and compiler for Lua in Go. =============================================================================== .. image:: https://godoc.org/github.com/yuin/gopher-lua?status.svg :target: http://godoc.org/github.com/yuin/gopher-lua .. image:: https://travis-ci.org/yuin/gopher-lua.svg :target: https://travis-ci.org/yuin/gopher-lua .. image:: https://coveralls.io/repos/yuin/gopher-lua/badge.svg :target: https://coveralls.io/r/yuin/gopher-lua .. image:: https://badges.gitter.im/Join%20Chat.svg :alt: Join the chat at https://gitter.im/yuin/gopher-lua :target: https://gitter.im/yuin/gopher-lua?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge | GopherLua is a Lua5.1 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. .. contents:: :depth: 1 ---------------------------------------------------------------- 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 <https://github.com/yuin/gopher-lua/wiki/Benchmarks>`_ . ---------------------------------------------------------------- Installation ---------------------------------------------------------------- .. code-block:: bash go get github.com/yuin/gopher-lua GopherLua supports >= Go1.7. ---------------------------------------------------------------- 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. .. code-block:: go import ( "github.com/yuin/gopher-lua" ) Run scripts in the VM. .. code-block:: go L := lua.NewState() defer L.Close() if err := L.DoString(`print("hello")`); err != nil { panic(err) } .. code-block:: go L := lua.NewState() defer L.Close() if err := L.DoFile("hello.lua"); err != nil { panic(err) } Refer to `Lua Reference Manual <http://www.lua.org/manual/5.1/>`_ and `Go doc <http://godoc.org/github.com/yuin/gopher-lua>`_ for further information. Note that elements that are not commented in `Go doc <http://godoc.org/github.com/yuin/gopher-lua>`_ equivalent to `Lua Reference Manual <http://www.lua.org/manual/5.1/>`_ , 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. .. code-block:: go 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.") } .. code-block:: go 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. .. code-block:: go 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. .. code-block:: go 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 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Size of the callstack & registry is **fixed** for mainly performance. You can change the default size of the callstack & registry. .. code-block:: go lua.RegistrySize = 1024 * 20 lua.CallStackSize = 1024 L := lua.NewState() defer L.Close() You can also create an LState object that has the callstack & registry size specified by ``Options`` . .. code-block:: go L := lua.NewState(lua.Options{ CallStackSize: 120, RegistrySize: 120*20, }) 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 <http://www.lua.org/manual/5.1/>`_ and `Go doc(LState methods) <http://godoc.org/github.com/yuin/gopher-lua>`_ for further information. +++++++++++++++++++++++++++++++++++++++++ Calling Go from Lua +++++++++++++++++++++++++++++++++++++++++ .. code-block:: go 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... */ } .. code-block:: lua print(double(20)) -- > "40" Any function registered with GopherLua is a ``lua.LGFunction``, defined in ``value.go`` .. code-block:: go type LGFunction func(*LState) int Working with coroutines. .. code-block:: go 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 .. code-block:: 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 .. code-block:: 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 .. code-block:: 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 .. code-block:: lua local m = require("mymodule") m.myfunc() print(m.name) +++++++++++++++++++++++++++++++++++++++++ Calling Lua from Go +++++++++++++++++++++++++++++++++++++++++ .. code-block:: 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. .. code-block:: go 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 <https://blog.golang.org/context>`_ . .. code-block:: go 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 .. code-block:: go 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.** .. code-block:: 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 +++++++++++++++++++++++++++++++++++++++++ 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. .. code-block:: go 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) <http://godoc.org/github.com/yuin/gopher-lua>`_ 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: .. code-block:: lua 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 .. code-block:: lua 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. .. code-block:: go 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`` . .. code-block:: go func MyWorker() { L := luaPool.Get() defer luaPool.Put(L) /* your code here */ } func main() { defer luaPool.Shutdown() go MyWorker() go MyWorker() /* etc... */ } ---------------------------------------------------------------- Differences between Lua and GopherLua ---------------------------------------------------------------- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 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)`` ---------------------------------------------------------------- Standalone interpreter ---------------------------------------------------------------- Lua has an interpreter called ``lua`` . GopherLua has an interpreter called ``glua`` . .. code-block:: bash go get github.com/yuin/gopher-lua/cmd/glua ``glua`` has same options as ``lua`` . ---------------------------------------------------------------- How to Contribute ---------------------------------------------------------------- See `Guidlines for contributors <https://github.com/yuin/gopher-lua/tree/master/.github/CONTRIBUTING.md>`_ . ---------------------------------------------------------------- Libraries for GopherLua ---------------------------------------------------------------- - `gopher-luar <https://github.com/layeh/gopher-luar>`_ : Custom type reflection for gopher-lua - `gluamapper <https://github.com/yuin/gluamapper>`_ : Mapping a Lua table to a Go struct - `gluare <https://github.com/yuin/gluare>`_ : Regular expressions for gopher-lua - `gluahttp <https://github.com/cjoudrey/gluahttp>`_ : HTTP request module for gopher-lua - `gopher-json <https://github.com/layeh/gopher-json>`_ : A simple JSON encoder/decoder for gopher-lua - `gluayaml <https://github.com/kohkimakimoto/gluayaml>`_ : Yaml parser for gopher-lua - `glua-lfs <https://github.com/layeh/gopher-lfs>`_ : Partially implements the luafilesystem module for gopher-lua - `gluaurl <https://github.com/cjoudrey/gluaurl>`_ : A url parser/builder module for gopher-lua - `gluahttpscrape <https://github.com/felipejfc/gluahttpscrape>`_ : A simple HTML scraper module for gopher-lua - `gluaxmlpath <https://github.com/ailncode/gluaxmlpath>`_ : An xmlpath module for gopher-lua - `gluasocket <https://github.com/BixData/gluasocket>`_ : A LuaSocket library for the GopherLua VM ---------------------------------------------------------------- Donation ---------------------------------------------------------------- BTC: 1NEDSyUmo4SMTDP83JJQSWi1MvQUGGNMZB ---------------------------------------------------------------- License ---------------------------------------------------------------- MIT ---------------------------------------------------------------- Author ---------------------------------------------------------------- Yusuke Inuzuka