diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 00000000..0fc3f6e1 --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,28 @@ +on: [push, pull_request] +name: test +jobs: + test: + strategy: + fail-fast: false + matrix: + go-version: [1.24.x, 1.25.x] + platform: [ubuntu-latest, macos-latest, windows-latest] + runs-on: ${{ matrix.platform }} + steps: + - name: Install Go + uses: actions/setup-go@v4 + with: + go-version: ${{ matrix.go-version }} + - name: Checkout code + uses: actions/checkout@v3 + - name: Run tests + run: ./_tools/go-inline *.go && go fmt . && go test -v ./... -covermode=count -coverprofile=coverage.out -coverpkg=$(go list ./... | sed 's/\n/,/g') + - name: Install goveralls + if: "matrix.platform == 'ubuntu-latest'" + run: go install github.com/mattn/goveralls@latest + - name: Send coverage + if: "matrix.platform == 'ubuntu-latest'" + env: + COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + $(go env GOPATH)/bin/goveralls -coverprofile=coverage.out -service=github diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..485dee64 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.idea diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 68df5e7b..00000000 --- a/.travis.yml +++ /dev/null @@ -1,18 +0,0 @@ -language: go - -go: - - "1.9.x" - - "1.10.x" - - "1.11.x" -env: - global: - GO111MODULE=off - -before_install: - - go get github.com/axw/gocov/gocov - - go get github.com/mattn/goveralls - - if ! go get code.google.com/p/go.tools/cmd/cover; then go get golang.org/x/tools/cmd/cover; fi -install: - - go get -u -v $(go list -f '{{join .Imports "\n"}}{{"\n"}}{{join .TestImports "\n"}}' ./... | sort | uniq | grep '\.' | grep -v gopher-lua) -script: - - $HOME/gopath/bin/goveralls -service=travis-ci diff --git a/README.md b/README.md new file mode 100644 index 00000000..832eb641 --- /dev/null +++ b/README.md @@ -0,0 +1,831 @@ +# GopherLua: VM and compiler for Lua in Go. + +[![PkgGoDev](https://pkg.go.dev/badge/github.com/yuin/gopher-lua.svg)](https://pkg.go.dev/github.com/yuin/gopher-lua) [![test](https://github.com/yuin/gopher-lua/workflows/test/badge.svg?branch=master&event=push)](https://github.com/yuin/gopher-lua/actions?query=workflow:test) [![Coverage Status](https://coveralls.io/repos/github/yuin/gopher-lua/badge.svg?branch=master)](https://coveralls.io/github/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. + +## Table of Contents + +- [Design principle](#design-principle) +- [How about performance?](#how-about-performance) +- [Installation](#installation) +- [Usage](#usage) +- [Differences between Lua and GopherLua](#differences-between-lua-and-gopherlua) +- [Standalone interpreter](#standalone-interpreter) +- [How to Contribute](#how-to-contribute) +- [Libraries for GopherLua](#libraries-for-gopherlua) +- [Donation](#donation) +- [License](#license) +- [Author](#author) + +## 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 + +```bash +$ go get github.com/yuin/gopher-lua +``` + +## 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. + +```go +import ( + "github.com/yuin/gopher-lua" +) +``` + +Run scripts in the VM. + +```go +L := lua.NewState() +defer L.Close() +if err := L.DoString(`print("hello")`); err != nil { + panic(err) +} +``` + +```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. + +```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.") +} +``` + +```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. + +```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. + +```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 + +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. + +```go +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. + +```go +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](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 + +```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... */ +} +``` + +```lua +print(double(20)) -- > "40" +``` + +Any function registered with GopherLua is a `lua.LGFunction`, defined in `value.go` + +```go +type LGFunction func(*LState) int +``` + +Working with coroutines. + +```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 + +```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 + +```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 + +```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 + +```lua +local m = require("mymodule") +m.myfunc() +print(m.name) +``` + +#### Calling Lua from Go + +```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. + +```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) . + +```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 + +```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.** + +``` +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. + +```go +// 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, err := CompileLua("mylua.lua") + if err != nil { + panic(err) + } + 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. + +```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: + +```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 +``` + +```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. + +```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` . + +```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)` +- GopherLua support `goto` and `::label::` statement in Lua5.2. + - `goto` is a keyword and not a valid variable name. + +## Standalone interpreter + +Lua has an interpreter called `lua` . GopherLua has an interpreter called `glua` . + +```bash +go get github.com/yuin/gopher-lua/cmd/glua +``` + +`glua` has same options as `lua` . + +## How to Contribute + +See [Guidelines for contributors](https://github.com/yuin/gopher-lua/tree/master/.github/CONTRIBUTING.md) . + +## Libraries for GopherLua + +- [gopher-luar](https://github.com/layeh/gopher-luar) : Simplifies data passing to and from 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 +- [gmoonscript](https://github.com/rucuriousyet/gmoonscript) : Moonscript Compiler for the Gopher Lua VM +- [loguago](https://github.com/rucuriousyet/loguago) : Zerolog wrapper for Gopher-Lua +- [gluabit32](https://github.com/PeerDB-io/gluabit32) : [Port of Lua 5.2 bit32](https://www.lua.org/manual/5.2/manual.html#6.7) +- [gluacrypto](https://github.com/tengattack/gluacrypto) : A native Go implementation of crypto library for the GopherLua VM. +- [gluasql](https://github.com/tengattack/gluasql) : A native Go implementation of SQL client for the GopherLua VM. +- [purr](https://github.com/leyafo/purr) : A http mock testing tool. +- [vadv/gopher-lua-libs](https://github.com/vadv/gopher-lua-libs) : Some usefull libraries for GopherLua VM. +- [gluasocket](https://gitlab.com/megalithic-llc/gluasocket) : A native Go implementation of LuaSocket for the GopherLua VM. +- [glua-async](https://github.com/CuberL/glua-async) : An async/await implement for gopher-lua. +- [gopherlua-debugger](https://github.com/edolphin-ydf/gopherlua-debugger) : A debugger for gopher-lua +- [gluamahonia](https://github.com/super1207/gluamahonia) : An encoding converter for gopher-lua +- [awesome-gopher-lua](https://github.com/Root-lee/awesome-gopher-lua) : Collections of awesome libraries for GopherLua. + +## Donation + +BTC: 1NEDSyUmo4SMTDP83JJQSWi1MvQUGGNMZB + +## License + +MIT + +## Author + +Yusuke Inuzuka diff --git a/README.rst b/README.rst deleted file mode 100644 index 2b6de325..00000000 --- a/README.rst +++ /dev/null @@ -1,888 +0,0 @@ - -=============================================================================== -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 `_ . - ----------------------------------------------------------------- -Installation ----------------------------------------------------------------- - -.. code-block:: bash - - 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. - -.. 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 `_ 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. - -.. 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 -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -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. - -.. code-block:: go - - 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. - -.. code-block:: go - - 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 -+++++++++++++++++++++++++++++++++++++++++ - -.. 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 `_ . - - -.. 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 - -+++++++++++++++++++++++++++++++++++++++++ -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. - -.. code-block:: go - - // 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. - - - -.. 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) `_ 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 `_ . - ----------------------------------------------------------------- -Libraries for GopherLua ----------------------------------------------------------------- - -- `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. -- `gluaperiphery `_ : A periphery library for the GopherLua VM (GPIO, SPI, I2C, MMIO, and Serial peripheral I/O for Linux). -- `glua-async `_ : An async/await implement for gopher-lua. -- `gopherlua-debugger `_ : A debugger for gopher-lua -- `gluamahonia `_ : An encoding converter for gopher-lua ----------------------------------------------------------------- -Donation ----------------------------------------------------------------- - -BTC: 1NEDSyUmo4SMTDP83JJQSWi1MvQUGGNMZB - ----------------------------------------------------------------- -License ----------------------------------------------------------------- -MIT - ----------------------------------------------------------------- -Author ----------------------------------------------------------------- -Yusuke Inuzuka diff --git a/_glua-tests/goto.lua b/_glua-tests/goto.lua new file mode 100644 index 00000000..1e640613 --- /dev/null +++ b/_glua-tests/goto.lua @@ -0,0 +1,173 @@ +local function errmsg (code, m) + local st, msg = loadstring(code) + assert(not st and string.find(msg, m)) +end + +-- cannot see label inside block +errmsg([[ goto l1; do ::l1:: end ]], "label 'l1'") +errmsg([[ do ::l1:: end goto l1; ]], "label 'l1'") + +-- repeated label +errmsg([[ ::l1:: ::l1:: ]], "label 'l1'") + + +-- undefined label +errmsg([[ goto l1; local aa ::l1:: ::l2:: print(3) ]], "local 'aa'") + +-- jumping over variable definition +errmsg([[ +do local bb, cc; goto l1; end +local aa +::l1:: print(3) +]], "local 'aa'") + +-- jumping into a block +errmsg([[ do ::l1:: end goto l1 ]], "label 'l1'") +errmsg([[ goto l1 do ::l1:: end ]], "label 'l1'") + +-- cannot continue a repeat-until with variables +errmsg([[ + repeat + if x then goto cont end + local xuxu = 10 + ::cont:: + until xuxu < x +]], "local 'xuxu'") + +-- simple gotos +local x +do + local y = 12 + goto l1 + ::l2:: x = x + 1; goto l3 + ::l1:: x = y; goto l2 +end +::l3:: ::l3_1:: assert(x == 13) + + +-- long labels +do + local prog = [[ + do + local a = 1 + goto l%sa; a = a + 1 + ::l%sa:: a = a + 10 + goto l%sb; a = a + 2 + ::l%sb:: a = a + 20 + return a + end + ]] + local label = string.rep("0123456789", 40) + prog = string.format(prog, label, label, label, label) + assert(assert(loadstring(prog))() == 31) +end + +-- goto to correct label when nested +do goto l3; ::l3:: end -- does not loop jumping to previous label 'l3' + +-- ok to jump over local dec. to end of block +do + goto l1 + local a = 23 + x = a + ::l1::; +end + +while true do + goto l4 + goto l1 -- ok to jump over local dec. to end of block + goto l1 -- multiple uses of same label + local x = 45 + ::l1:: ;;; +end +::l4:: assert(x == 13) + +if print then + goto l1 -- ok to jump over local dec. to end of block + error("should not be here") + goto l2 -- ok to jump over local dec. to end of block + local x + ::l1:: ; ::l2:: ;; +else end + +-- to repeat a label in a different function is OK +local function foo () + local a = {} + goto l3 + ::l1:: a[#a + 1] = 1; goto l2; + ::l2:: a[#a + 1] = 2; goto l5; + ::l3:: + ::l3a:: a[#a + 1] = 3; goto l1; + ::l4:: a[#a + 1] = 4; goto l6; + ::l5:: a[#a + 1] = 5; goto l4; + ::l6:: assert(a[1] == 3 and a[2] == 1 and a[3] == 2 and + a[4] == 5 and a[5] == 4) + if not a[6] then a[6] = true; goto l3a end -- do it twice +end + +::l6:: foo() + + + +-------------------------------------------------------------------------------- +-- testing closing of upvalues + +local function foo () + local a = {} + do + local i = 1 + local k = 0 + a[0] = function (y) k = y end + ::l1:: do + local x + if i > 2 then goto l2 end + a[i] = function (y) if y then x = y else return x + k end end + i = i + 1 + goto l1 + end + end + ::l2:: return a +end + +local a = foo() +a[1](10); a[2](20) +assert(a[1]() == 10 and a[2]() == 20 and a[3] == nil) +a[0](13) +assert(a[1]() == 23 and a[2]() == 33) + +-------------------------------------------------------------------------------- +-- testing if x goto optimizations + +local function testG (a) + if a == 1 then + goto l1 + error("should never be here!") + elseif a == 2 then goto l2 + elseif a == 3 then goto l3 + elseif a == 4 then + goto l1 -- go to inside the block + error("should never be here!") + ::l1:: a = a + 1 -- must go to 'if' end + else + goto l4 + ::l4a:: a = a * 2; goto l4b + error("should never be here!") + ::l4:: goto l4a + error("should never be here!") + ::l4b:: + end + do return a end + ::l2:: do return "2" end + ::l3:: do return "3" end + ::l1:: return "1" +end + +assert(testG(1) == "1") +assert(testG(2) == "2") +assert(testG(3) == "3") +assert(testG(4) == 5) +assert(testG(5) == 10) +-------------------------------------------------------------------------------- + + +print'OK' diff --git a/_glua-tests/issues.lua b/_glua-tests/issues.lua index a4b830b6..6d6343ab 100644 --- a/_glua-tests/issues.lua +++ b/_glua-tests/issues.lua @@ -404,3 +404,89 @@ function test() assert(2 % 2 == 0) end test() + +-- issue #355 +function test() + local x = "valid" + assert(x == "valid") + assert(zzz == nil) + x = zzz and "not-valid" or x + assert(x == "valid") +end +test() + +function test() + local x = "valid" + local z = nil + assert(x == "valid") + assert(z == nil) + x = z and "not-valid" or x + assert(x == "valid") +end +test() + +function test() + local x = "valid" + assert(x == "valid") + assert(zzz == nil) + x = zzz and "not-valid" or "still " .. x + assert(x == "still valid") +end +test() + +-- issue #315 +function test() + local a = {} + local d = 'e' + local f = 1 + + f, a.d = f, d + + assert(f..", "..a.d == "1, e") +end +test() + +-- issue #423 +function test() + local a, b, c = "1", "3", "1" + a, b, c= tonumber(a), tonumber(b) or a, tonumber(c) + assert(a == 1) + assert(type(a) == "number") + assert(b == 3) + assert(type(b) == "number") + assert(c == 1) + assert(type(c) == "number") +end +test() + +-- issue #452 +function test() + local ok, msg = pcall(function() + local ok, msg = xpcall(function() error("fn") end, function(err) error("handler") end) + assert(not ok and msg) + error("expected to reach this.") + end) + assert(not ok) +end +test() + +-- issue #455 +function test() + local path = "." + local fd, _, code = io.open(path, "r") + assert(fd ~= nil) + local _, _, ecode = fd:read(1) + assert(ecode == 1) +end +test() + +-- issue #459 +function test() + local a, b = io.popen("ls", nil) + assert(a) + assert(b == nil) + local a, b = io.popen("ls", nil, nil) + assert(a) + assert(b == nil) +end +test() diff --git a/_state.go b/_state.go index 960e8810..6e9febfb 100644 --- a/_state.go +++ b/_state.go @@ -398,17 +398,18 @@ func (rg *registry) forceResize(newSize int) { copy(newSlice, rg.array[:rg.top]) // should we copy the area beyond top? there shouldn't be any valid values there so it shouldn't be necessary. rg.array = newSlice } -func (rg *registry) SetTop(top int) { - // +inline-call rg.checkSize top - oldtop := rg.top - rg.top = top - for i := oldtop; i < rg.top; i++ { + +func (rg *registry) SetTop(topi int) { // +inline-start + // +inline-call rg.checkSize topi + oldtopi := rg.top + rg.top = topi + for i := oldtopi; i < rg.top; i++ { rg.array[i] = LNil } // values beyond top don't need to be valid LValues, so setting them to nil is fine // setting them to nil rather than LNil lets us invoke the golang memclr opto - if rg.top < oldtop { - nilRange := rg.array[rg.top:oldtop] + if rg.top < oldtopi { + nilRange := rg.array[rg.top:oldtopi] for i := range nilRange { nilRange[i] = nil } @@ -416,7 +417,7 @@ func (rg *registry) SetTop(top int) { //for i := rg.top; i < oldtop; i++ { // rg.array[i] = LNil //} -} +} // +inline-end func (rg *registry) Top() int { return rg.top @@ -498,34 +499,34 @@ func (rg *registry) FillNil(regm, n int) { // +inline-start func (rg *registry) Insert(value LValue, reg int) { top := rg.Top() if reg >= top { - rg.Set(reg, value) + // +inline-call rg.Set reg value return } top-- for ; top >= reg; top-- { // FIXME consider using copy() here if Insert() is called enough - rg.Set(top+1, rg.Get(top)) + // +inline-call rg.Set top+1 rg.Get(top) } - rg.Set(reg, value) + // +inline-call rg.Set reg value } -func (rg *registry) Set(reg int, val LValue) { - newSize := reg + 1 +func (rg *registry) Set(regi int, vali LValue) { // +inline-start + newSize := regi + 1 // +inline-call rg.checkSize newSize - rg.array[reg] = val - if reg >= rg.top { - rg.top = reg + 1 + rg.array[regi] = vali + if regi >= rg.top { + rg.top = regi + 1 } -} +} // +inline-end -func (rg *registry) SetNumber(reg int, val LNumber) { - newSize := reg + 1 +func (rg *registry) SetNumber(regi int, vali LNumber) { // +inline-start + newSize := regi + 1 // +inline-call rg.checkSize newSize - rg.array[reg] = rg.alloc.LNumber2I(val) - if reg >= rg.top { - rg.top = reg + 1 + rg.array[regi] = rg.alloc.LNumber2I(vali) + if regi >= rg.top { + rg.top = regi + 1 } -} +} // +inline-end func (rg *registry) IsFull() bool { return rg.top >= cap(rg.array) @@ -769,6 +770,9 @@ func (ls *LState) isStarted() bool { func (ls *LState) kill() { ls.Dead = true + if ls.ctxCancelFn != nil { + ls.ctxCancelFn() + } } func (ls *LState) indexToReg(idx int) int { @@ -1402,6 +1406,7 @@ func (ls *LState) NewThread() (*LState, context.CancelFunc) { if ls.ctx != nil { thread.mainLoop = mainLoopWithContext thread.ctx, f = context.WithCancel(ls.ctx) + thread.ctxCancelFn = f } return thread, f } @@ -1851,6 +1856,9 @@ func (ls *LState) PCall(nargs, nret int, errfunc *LFunction) (err error) { err = rcv.(*ApiError) err.(*ApiError).StackTrace = ls.stackTrace(0) } + ls.stack.SetSp(sp) + ls.currentFrame = ls.stack.Last() + ls.reg.SetTop(base) } }() ls.Call(1, 1) diff --git a/_tools/go-inline b/_tools/go-inline index 0fb97299..bb54975b 100755 --- a/_tools/go-inline +++ b/_tools/go-inline @@ -1,82 +1,98 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # Simple and indolent text processor to do inline go function calling import sys, re files = sys.argv + + class inline(object): - def __init__(self, name): - self.name = name - self.receiver = "" - self.args = [] - self.contents = [] - self.file = "" - self.original = "" + def __init__(self, name): + self.name = name + self.receiver = "" + self.args = [] + self.contents = [] + self.file = "" + self.original = "" + inlines = {} for file in sorted(files, reverse=True): - contents = open(file).read().splitlines() - i = 0 - name = "" - while i < len(contents): - line = contents[i] - m = re.match(".*\/\/ \+inline-start", line) - if m: - m2 = re.match("^func\s*(\([\*\s\w]+\))?\s*(\w+)\(([^\)]*)\)", line) - name = m2.group(2) - tinline = inline(name) - tinline.original = line.split("//")[0].strip().rstrip("{") - tinline.file = file - if m2.group(1): - tinline.receiver = m2.group(1).split("(")[1].split(" ")[0] - tinline.args = [arg.strip().split(" ")[0] for arg in m2.group(3).split(",")] - inlines[name] = tinline - else: - if re.match(".*\/\/\s*\+inline-end", line): - inlines[name].contents = "\n".join(inlines[name].contents) - name = "" - elif len(name) > 0: - inlines[name].contents.append(line) - i += 1 + contents = open(file).read().splitlines() + i = 0 + name = "" + while i < len(contents): + line = contents[i] + m = re.match(".*\/\/ \+inline-start", line) + if m: + m2 = re.match("^func\s*(\([\*\s\w]+\))?\s*(\w+)\(([^\)]*)\)", line) + name = m2.group(2) + tinline = inline(name) + tinline.original = line.split("//")[0].strip().rstrip("{") + tinline.file = file + if m2.group(1): + tinline.receiver = m2.group(1).split("(")[1].split(" ")[0] + tinline.args = [arg.strip().split(" ")[0] for arg in m2.group(3).split(",")] + inlines[name] = tinline + else: + if re.match(".*\/\/\s*\+inline-end", line): + inlines[name].contents = "\n".join(inlines[name].contents) + name = "" + elif len(name) > 0: + inlines[name].contents.append(line) + i += 1 + def do_inlining(text): - contents = text.splitlines() - buf = [] - i = 0 - while i < len(contents): - line = contents[i] - m = re.match("\s*\/\/\s*\+inline-call\s+([\w\.]+)\s+(.*)", line) - if m: - inlinet = inlines[m.group(1).split(".")[-1]] - buf.append("// this section is inlined by go-inline") - buf.append("// source function is '{}' in '{}'".format(inlinet.original, inlinet.file)) - buf.append("{") - if len(inlinet.receiver) > 0 and inlinet.receiver != m.group(1).split(".")[0]: - buf.append("{} := {}".format(inlinet.receiver, ".".join(m.group(1).split(".")[0:-1]))) + contents = text.splitlines() + buf = [] + i = 0 + while i < len(contents): + line = contents[i] + m = re.match("\s*\/\/\s*\+inline-call\s+([\w\.]+)\s+(.*)", line) + if m: + inlinet = inlines[m.group(1).split(".")[-1]] + buf.append("// this section is inlined by go-inline") + buf.append( + "// source function is '{}' in '{}'".format( + inlinet.original, inlinet.file + ) + ) + buf.append("{") + if ( + len(inlinet.receiver) > 0 + and inlinet.receiver != m.group(1).split(".")[0] + ): + buf.append( + "{} := {}".format( + inlinet.receiver, ".".join(m.group(1).split(".")[0:-1]) + ) + ) + + callargs = [arg.strip() for arg in m.group(2).split(" ")] + for j in range(len(callargs)): + if inlinet.args[j] != callargs[j]: + buf.append("{} := {}".format(inlinet.args[j], callargs[j])) + buf.append(do_inlining(inlinet.contents)) + buf.append("}") + else: + buf.append(line) + i += 1 + return "\n".join(buf) - callargs = [arg.strip() for arg in m.group(2).split(" ")] - for j in range(len(callargs)): - if inlinet.args[j] != callargs[j]: - buf.append("{} := {}".format(inlinet.args[j], callargs[j])) - buf.append(do_inlining(inlinet.contents)) - buf.append("}") - else: - buf.append(line) - i += 1 - return "\n".join(buf) for file in files: - if not file.startswith("_"): - continue - contents = open(file).read() - with open(file.lstrip("_"), "w") as io: - inlined = do_inlining(contents).split("\n") - for i in range(len(inlined)): - if i == 1: - io.write("////////////////////////////////////////////////////////\n") - io.write("// This file was generated by go-inline. DO NOT EDIT. //\n") - io.write("////////////////////////////////////////////////////////\n") - io.write(inlined[i]) - io.write("\n") - io.write("\n") + if not file.startswith("_"): + continue + contents = open(file).read() + with open(file.lstrip("_"), "w") as io: + inlined = do_inlining(contents).split("\n") + for i in range(len(inlined)): + if i == 1: + io.write("////////////////////////////////////////////////////////\n") + io.write("// This file was generated by go-inline. DO NOT EDIT. //\n") + io.write("////////////////////////////////////////////////////////\n") + io.write(inlined[i]) + io.write("\n") + io.write("\n") diff --git a/_vm.go b/_vm.go index 687fe797..ee2be040 100644 --- a/_vm.go +++ b/_vm.go @@ -173,7 +173,8 @@ func init() { A := int(inst>>18) & 0xff //GETA RA := lbase + A B := int(inst & 0x1ff) //GETB - reg.Set(RA, reg.Get(lbase+B)) + v := reg.Get(lbase + B) + // +inline-call reg.Set RA v return 0 }, func(L *LState, inst uint32, baseframe *callFrame) int { //OP_MOVEN @@ -183,7 +184,8 @@ func init() { A := int(inst>>18) & 0xff //GETA B := int(inst & 0x1ff) //GETB C := int(inst>>9) & 0x1ff //GETC - reg.Set(lbase+A, reg.Get(lbase+B)) + v := reg.Get(lbase + B) + // +inline-call reg.Set lbase+A v code := cf.Fn.Proto.Code pc := cf.Pc for i := 0; i < C; i++ { @@ -191,7 +193,8 @@ func init() { pc++ A = int(inst>>18) & 0xff //GETA B = int(inst & 0x1ff) //GETB - reg.Set(lbase+A, reg.Get(lbase+B)) + v := reg.Get(lbase + B) + // +inline-call reg.Set lbase+A v } cf.Pc = pc return 0 @@ -203,7 +206,8 @@ func init() { A := int(inst>>18) & 0xff //GETA RA := lbase + A Bx := int(inst & 0x3ffff) //GETBX - reg.Set(RA, cf.Fn.Proto.Constants[Bx]) + v := cf.Fn.Proto.Constants[Bx] + // +inline-call reg.Set RA v return 0 }, func(L *LState, inst uint32, baseframe *callFrame) int { //OP_LOADBOOL @@ -215,9 +219,9 @@ func init() { B := int(inst & 0x1ff) //GETB C := int(inst>>9) & 0x1ff //GETC if B != 0 { - reg.Set(RA, LTrue) + // +inline-call reg.Set RA LTrue } else { - reg.Set(RA, LFalse) + // +inline-call reg.Set RA LFalse } if C != 0 { cf.Pc++ @@ -232,7 +236,7 @@ func init() { RA := lbase + A B := int(inst & 0x1ff) //GETB for i := RA; i <= lbase+B; i++ { - reg.Set(i, LNil) + // +inline-call reg.Set i LNil } return 0 }, @@ -243,7 +247,8 @@ func init() { A := int(inst>>18) & 0xff //GETA RA := lbase + A B := int(inst & 0x1ff) //GETB - reg.Set(RA, cf.Fn.Upvalues[B].Value()) + v := cf.Fn.Upvalues[B].Value() + // +inline-call reg.Set RA v return 0 }, func(L *LState, inst uint32, baseframe *callFrame) int { //OP_GETGLOBAL @@ -254,7 +259,8 @@ func init() { RA := lbase + A Bx := int(inst & 0x3ffff) //GETBX //reg.Set(RA, L.getField(cf.Fn.Env, cf.Fn.Proto.Constants[Bx])) - reg.Set(RA, L.getFieldString(cf.Fn.Env, cf.Fn.Proto.stringConstants[Bx])) + v := L.getFieldString(cf.Fn.Env, cf.Fn.Proto.stringConstants[Bx]) + // +inline-call reg.Set RA v return 0 }, func(L *LState, inst uint32, baseframe *callFrame) int { //OP_GETTABLE @@ -265,7 +271,8 @@ func init() { RA := lbase + A B := int(inst & 0x1ff) //GETB C := int(inst>>9) & 0x1ff //GETC - reg.Set(RA, L.getField(reg.Get(lbase+B), L.rkValue(C))) + v := L.getField(reg.Get(lbase+B), L.rkValue(C)) + // +inline-call reg.Set RA v return 0 }, func(L *LState, inst uint32, baseframe *callFrame) int { //OP_GETTABLEKS @@ -276,7 +283,8 @@ func init() { RA := lbase + A B := int(inst & 0x1ff) //GETB C := int(inst>>9) & 0x1ff //GETC - reg.Set(RA, L.getFieldString(reg.Get(lbase+B), L.rkString(C))) + v := L.getFieldString(reg.Get(lbase+B), L.rkString(C)) + // +inline-call reg.Set RA v return 0 }, func(L *LState, inst uint32, baseframe *callFrame) int { //OP_SETGLOBAL @@ -330,7 +338,8 @@ func init() { RA := lbase + A B := int(inst & 0x1ff) //GETB C := int(inst>>9) & 0x1ff //GETC - reg.Set(RA, newLTable(B, C)) + v := newLTable(B, C) + // +inline-call reg.Set RA v return 0 }, func(L *LState, inst uint32, baseframe *callFrame) int { //OP_SELF @@ -342,8 +351,9 @@ func init() { B := int(inst & 0x1ff) //GETB C := int(inst>>9) & 0x1ff //GETC selfobj := reg.Get(lbase + B) - reg.Set(RA, L.getFieldString(selfobj, L.rkString(C))) - reg.Set(RA+1, selfobj) + v := L.getFieldString(selfobj, L.rkString(C)) + // +inline-call reg.Set RA v + // +inline-call reg.Set RA+1 selfobj return 0 }, opArith, // OP_ADD @@ -361,17 +371,17 @@ func init() { B := int(inst & 0x1ff) //GETB unaryv := L.rkValue(B) if nm, ok := unaryv.(LNumber); ok { - reg.SetNumber(RA, -nm) + // +inline-call reg.Set RA -nm } else { op := L.metaOp1(unaryv, "__unm") if op.Type() == LTFunction { reg.Push(op) reg.Push(unaryv) L.Call(1, 1) - reg.Set(RA, reg.Pop()) + // +inline-call reg.Set RA reg.Pop() } else if str, ok1 := unaryv.(LString); ok1 { if num, err := parseNumber(string(str)); err == nil { - reg.Set(RA, -num) + // +inline-call reg.Set RA -num } else { L.RaiseError("__unm undefined") } @@ -389,9 +399,9 @@ func init() { RA := lbase + A B := int(inst & 0x1ff) //GETB if LVIsFalse(reg.Get(lbase + B)) { - reg.Set(RA, LTrue) + // +inline-call reg.Set RA LTrue } else { - reg.Set(RA, LFalse) + // +inline-call reg.Set RA LFalse } return 0 }, @@ -404,7 +414,7 @@ func init() { B := int(inst & 0x1ff) //GETB switch lv := L.rkValue(B).(type) { case LString: - reg.SetNumber(RA, LNumber(len(lv))) + // +inline-call reg.SetNumber RA LNumber(len(lv)) default: op := L.metaOp1(lv, "__len") if op.Type() == LTFunction { @@ -413,12 +423,13 @@ func init() { L.Call(1, 1) ret := reg.Pop() if ret.Type() == LTNumber { - reg.SetNumber(RA, ret.(LNumber)) + v, _ := ret.(LNumber) + // +inline-call reg.SetNumber RA v } else { - reg.Set(RA, ret) + // +inline-call reg.Set RA ret } } else if lv.Type() == LTTable { - reg.SetNumber(RA, LNumber(lv.(*LTable).Len())) + // +inline-call reg.SetNumber RA LNumber(lv.(*LTable).Len()) } else { L.RaiseError("__len undefined") } @@ -435,7 +446,8 @@ func init() { C := int(inst>>9) & 0x1ff //GETC RC := lbase + C RB := lbase + B - reg.Set(RA, stringConcat(L, RC-RB+1, RC)) + v := stringConcat(L, RC-RB+1, RC) + // +inline-call reg.Set RA v return 0 }, func(L *LState, inst uint32, baseframe *callFrame) int { //OP_JMP @@ -483,8 +495,8 @@ func init() { rhs := L.rkValue(C) ret := false - if v1, ok1 := lhs.assertFloat64(); ok1 { - if v2, ok2 := rhs.assertFloat64(); ok2 { + if v1, ok1 := lhs.(LNumber); ok1 { + if v2, ok2 := rhs.(LNumber); ok2 { ret = v1 <= v2 } else { L.RaiseError("attempt to compare %v with %v", lhs.Type().String(), rhs.Type().String()) @@ -538,7 +550,7 @@ func init() { B := int(inst & 0x1ff) //GETB C := int(inst>>9) & 0x1ff //GETC if value := reg.Get(lbase + B); LVAsBool(value) != (C == 0) { - reg.Set(RA, value) + // +inline-call reg.Set RA value } else { cf.Pc++ } @@ -560,7 +572,7 @@ func init() { nret := C - 1 var callable *LFunction var meta bool - if fn, ok := lv.assertFunction(); ok { + if fn, ok := lv.(*LFunction); ok { callable = fn meta = false } else { @@ -586,7 +598,7 @@ func init() { lv := reg.Get(RA) var callable *LFunction var meta bool - if fn, ok := lv.assertFunction(); ok { + if fn, ok := lv.(*LFunction); ok { callable = fn meta = false } else { @@ -673,17 +685,18 @@ func init() { lbase := cf.LocalBase A := int(inst>>18) & 0xff //GETA RA := lbase + A - if init, ok1 := reg.Get(RA).assertFloat64(); ok1 { - if limit, ok2 := reg.Get(RA + 1).assertFloat64(); ok2 { - if step, ok3 := reg.Get(RA + 2).assertFloat64(); ok3 { + if init, ok1 := reg.Get(RA).(LNumber); ok1 { + if limit, ok2 := reg.Get(RA + 1).(LNumber); ok2 { + if step, ok3 := reg.Get(RA + 2).(LNumber); ok3 { init += step - reg.SetNumber(RA, LNumber(init)) + v := LNumber(init) + // +inline-call reg.SetNumber RA v if (step > 0 && init <= limit) || (step <= 0 && init >= limit) { Sbx := int(inst&0x3ffff) - opMaxArgSbx //GETSBX cf.Pc += Sbx - reg.SetNumber(RA+3, LNumber(init)) + // +inline-call reg.SetNumber RA+3 v } else { - reg.SetTop(RA + 1) + // +inline-call reg.SetTop RA+1 } } else { L.RaiseError("for statement step must be a number") @@ -703,9 +716,9 @@ func init() { A := int(inst>>18) & 0xff //GETA RA := lbase + A Sbx := int(inst&0x3ffff) - opMaxArgSbx //GETSBX - if init, ok1 := reg.Get(RA).assertFloat64(); ok1 { - if step, ok2 := reg.Get(RA + 2).assertFloat64(); ok2 { - reg.SetNumber(RA, LNumber(init-step)) + if init, ok1 := reg.Get(RA).(LNumber); ok1 { + if step, ok2 := reg.Get(RA + 2).(LNumber); ok2 { + // +inline-call reg.SetNumber RA LNumber(init-step) } else { L.RaiseError("for statement step must be a number") } @@ -723,13 +736,13 @@ func init() { RA := lbase + A C := int(inst>>9) & 0x1ff //GETC nret := C - reg.SetTop(RA + 3 + 2) - reg.Set(RA+3+2, reg.Get(RA+2)) - reg.Set(RA+3+1, reg.Get(RA+1)) - reg.Set(RA+3, reg.Get(RA)) + // +inline-call reg.SetTop RA+3+2 + // +inline-call reg.Set RA+3+2 reg.Get(RA+2) + // +inline-call reg.Set RA+3+1 reg.Get(RA+1) + // +inline-call reg.Set RA+3 reg.Get(RA) L.callR(2, nret, RA+3) if value := reg.Get(RA + 3); value != LNil { - reg.Set(RA+2, value) + // +inline-call reg.Set RA+2 value pc := cf.Fn.Proto.Code[cf.Pc] cf.Pc += int(pc&0x3ffff) - opMaxArgSbx } @@ -776,7 +789,7 @@ func init() { Bx := int(inst & 0x3ffff) //GETBX proto := cf.Fn.Proto.FunctionPrototypes[Bx] closure := newLFunctionL(proto, cf.Fn.Env, int(proto.NumUpvalues)) - reg.Set(RA, closure) + // +inline-call reg.Set RA closure for i := 0; i < int(proto.NumUpvalues); i++ { inst = cf.Fn.Proto.Code[cf.Pc] cf.Pc++ @@ -826,12 +839,14 @@ func opArith(L *LState, inst uint32, baseframe *callFrame) int { //OP_ADD, OP_SU C := int(inst>>9) & 0x1ff //GETC lhs := L.rkValue(B) rhs := L.rkValue(C) - v1, ok1 := lhs.assertFloat64() - v2, ok2 := rhs.assertFloat64() + v1, ok1 := lhs.(LNumber) + v2, ok2 := rhs.(LNumber) if ok1 && ok2 { - reg.SetNumber(RA, numberArith(L, opcode, LNumber(v1), LNumber(v2))) + v := numberArith(L, opcode, LNumber(v1), LNumber(v2)) + // +inline-call reg.SetNumber RA v } else { - reg.Set(RA, objectArith(L, opcode, lhs, rhs)) + v := objectArith(L, opcode, lhs, rhs) + // +inline-call reg.Set RA v } return 0 } @@ -884,7 +899,7 @@ func objectArith(L *LState, opcode int, lhs, rhs LValue) LValue { event = "__pow" } op := L.metaOp2(lhs, rhs, event) - if op.Type() == LTFunction { + if _, ok := op.(*LFunction); ok { L.reg.Push(op) L.reg.Push(lhs) L.reg.Push(rhs) @@ -901,8 +916,8 @@ func objectArith(L *LState, opcode int, lhs, rhs LValue) LValue { rhs = rnum } } - if v1, ok1 := lhs.assertFloat64(); ok1 { - if v2, ok2 := rhs.assertFloat64(); ok2 { + if v1, ok1 := lhs.(LNumber); ok1 { + if v2, ok2 := rhs.(LNumber); ok2 { return numberArith(L, opcode, LNumber(v1), LNumber(v2)) } } @@ -951,8 +966,8 @@ func stringConcat(L *LState, total, last int) LValue { func lessThan(L *LState, lhs, rhs LValue) bool { // optimization for numbers - if v1, ok1 := lhs.assertFloat64(); ok1 { - if v2, ok2 := rhs.assertFloat64(); ok2 { + if v1, ok1 := lhs.(LNumber); ok1 { + if v2, ok2 := rhs.(LNumber); ok2 { return v1 < v2 } L.RaiseError("attempt to compare %v with %v", lhs.Type().String(), rhs.Type().String()) @@ -972,17 +987,18 @@ func lessThan(L *LState, lhs, rhs LValue) bool { } func equals(L *LState, lhs, rhs LValue, raw bool) bool { - if lhs.Type() != rhs.Type() { + lt := lhs.Type() + if lt != rhs.Type() { return false } ret := false - switch lhs.Type() { + switch lt { case LTNil: ret = true case LTNumber: - v1, _ := lhs.assertFloat64() - v2, _ := rhs.assertFloat64() + v1, _ := lhs.(LNumber) + v2, _ := rhs.(LNumber) ret = v1 == v2 case LTBool: ret = bool(lhs.(LBool)) == bool(rhs.(LBool)) diff --git a/ast/stmt.go b/ast/stmt.go index 56ea6d1a..e24880b6 100644 --- a/ast/stmt.go +++ b/ast/stmt.go @@ -93,3 +93,15 @@ type ReturnStmt struct { type BreakStmt struct { StmtBase } + +type LabelStmt struct { + StmtBase + + Name string +} + +type GotoStmt struct { + StmtBase + + Label string +} diff --git a/auxlib.go b/auxlib.go index 61a3b8b6..a022bdd8 100644 --- a/auxlib.go +++ b/auxlib.go @@ -40,6 +40,11 @@ func (ls *LState) CheckNumber(n int) LNumber { if lv, ok := v.(LNumber); ok { return lv } + if lv, ok := v.(LString); ok { + if num, err := parseNumber(string(lv)); err == nil { + return num + } + } ls.TypeError(n, LTNumber) return 0 } @@ -413,7 +418,7 @@ func (ls *LState) DoString(source string) error { // ToStringMeta returns string representation of given LValue. // This method calls the `__tostring` meta method if defined. func (ls *LState) ToStringMeta(lv LValue) LValue { - if fn, ok := ls.metaOp1(lv, "__tostring").assertFunction(); ok { + if fn, ok := ls.metaOp1(lv, "__tostring").(*LFunction); ok { ls.Push(fn) ls.Push(lv) ls.Call(1, 1) diff --git a/auxlib_test.go b/auxlib_test.go index 2218db67..240435e2 100644 --- a/auxlib_test.go +++ b/auxlib_test.go @@ -1,7 +1,6 @@ package lua import ( - "io/ioutil" "os" "testing" ) @@ -36,8 +35,10 @@ func TestCheckNumber(t *testing.T) { errorIfGFuncNotFail(t, L, func(L *LState) int { L.Push(LNumber(10)) errorIfNotEqual(t, LNumber(10), L.CheckNumber(2)) + L.Push(LString("11")) + errorIfNotEqual(t, LNumber(11), L.CheckNumber(3)) L.Push(LString("aaa")) - L.CheckNumber(3) + L.CheckNumber(4) return 0 }, "number expected, got string") } @@ -298,10 +299,10 @@ func TestOptChannel(t *testing.T) { } func TestLoadFileForShebang(t *testing.T) { - tmpFile, err := ioutil.TempFile("", "") + tmpFile, err := os.CreateTemp("", "") errorIfNotNil(t, err) - err = ioutil.WriteFile(tmpFile.Name(), []byte(`#!/path/to/lua + err = os.WriteFile(tmpFile.Name(), []byte(`#!/path/to/lua print("hello") `), 0644) errorIfNotNil(t, err) @@ -319,7 +320,7 @@ print("hello") } func TestLoadFileForEmptyFile(t *testing.T) { - tmpFile, err := ioutil.TempFile("", "") + tmpFile, err := os.CreateTemp("", "") errorIfNotNil(t, err) defer func() { diff --git a/baselib_test.go b/baselib_test.go new file mode 100644 index 00000000..ba773343 --- /dev/null +++ b/baselib_test.go @@ -0,0 +1,141 @@ +package lua + +import ( + "strconv" + "testing" + "time" +) + +func TestOsDateFormatUTCWithTwoParam(t *testing.T) { + t.Setenv("TZ", "Asia/Tokyo") + ls := NewState() + + g := ls.GetGlobal("os") + fn := ls.GetField(g, "date") + + int64ptr := func(i int64) *int64 { + return &i + } + cases := []struct { + Name string + Local time.Time + Now time.Time + Format string + Timestamp *int64 + }{ + { + "UTCWithTwoParam", + time.Now(), + time.Now().UTC(), + "!*t", + int64ptr(time.Now().UTC().Unix()), + }, + { + "LocalWithTwoParam", + time.Now(), + time.Now(), + "*t", + int64ptr(time.Now().Unix()), + }, + { + "UTCWithOnlyFormatParam", + time.Now(), + time.Now().UTC(), + "!*t", + nil, + }, + { + "LocalWithOnlyFormatParam", + time.Now(), + time.Now(), + "*t", + nil, + }, + } + + for _, c := range cases { + t.Run(c.Name, func(t *testing.T) { + args := make([]LValue, 0) + args = append(args, LString(c.Format)) + if c.Timestamp != nil { + args = append(args, LNumber(*c.Timestamp)) + } + err := ls.CallByParam(P{ + Fn: fn, + NRet: 1, + Protect: true, + }, args...) + if err != nil { + t.Fatal(err) + } + + result := ls.ToTable(-1) + + resultMap := make(map[string]string) + result.ForEach(func(key LValue, value LValue) { + resultMap[key.String()] = value.String() + assertOsDateFields(t, key, value, c.Now) + }) + t.Logf("%v resultMap=%+v\nnow=%+v\nLocal=%+v\nUTC=%v", c.Name, resultMap, c.Now, c.Local, c.Now.UTC()) + }) + } +} + +func TestOsDateFormatLocalWithTwoParam(t *testing.T) { + t.Setenv("TZ", "Asia/Tokyo") + ls := NewState() + + g := ls.GetGlobal("os") + fn := ls.GetField(g, "date") + + nowLocal := time.Now() + nowUTC := nowLocal.UTC() + + err := ls.CallByParam(P{ + Fn: fn, + NRet: 1, + Protect: true, + }, LString("*t"), LNumber(nowLocal.Unix())) + if err != nil { + t.Fatal(err) + } + + result := ls.ToTable(-1) + + resultMap := make(map[string]string) + result.ForEach(func(key LValue, value LValue) { + t.Logf("key=%v, value=%v", key, value) + resultMap[key.String()] = value.String() + assertOsDateFields(t, key, value, nowLocal) + }) + t.Logf("resultMap=%+v, nowLocal=%+v, nowUTC=%v", resultMap, nowLocal, nowUTC) +} + +func assertOsDateFields(t *testing.T, key LValue, value LValue, expect time.Time) { + switch key.String() { + case "year": + if value.String() != strconv.Itoa(expect.Year()) { + t.Errorf("year=%v, expect.Year=%v", value.String(), expect.Year()) + } + case "month": + if value.String() != strconv.Itoa(int(expect.Month())) { + t.Errorf("month=%v, expect.Month=%v", value.String(), expect.Month()) + } + case "day": + if value.String() != strconv.Itoa(expect.Day()) { + t.Errorf("day=%v, expect.Day=%v", value.String(), expect.Day()) + } + case "hour": + if value.String() != strconv.Itoa(expect.Hour()) { + t.Errorf("hour=%v, expect.Hour=%v", value.String(), expect.Hour()) + } + case "min": + if value.String() != strconv.Itoa(expect.Minute()) { + t.Errorf("min=%v, expect.Minute=%v", value.String(), expect.Minute()) + } + case "sec": + if value.String() != strconv.Itoa(expect.Second()) { + t.Errorf("sec=%v, expect.Second=%v", value.String(), expect.Second()) + } + } +} diff --git a/compile.go b/compile.go index 75c75550..f9fbf576 100644 --- a/compile.go +++ b/compile.go @@ -2,9 +2,10 @@ package lua import ( "fmt" - "github.com/yuin/gopher-lua/ast" "math" "reflect" + + "github.com/yuin/gopher-lua/ast" ) /* internal constants & structs {{{ */ @@ -94,7 +95,11 @@ func sline(pos ast.PositionHolder) int { } func eline(pos ast.PositionHolder) int { - return pos.LastLine() + line := pos.LastLine() + if line == 0 { + return pos.Line() + } + return line } func savereg(ec *expcontext, reg int) int { @@ -134,6 +139,28 @@ func lnumberValue(expr ast.Expr) (LNumber, bool) { /* utilities }}} */ +type gotoLabelDesc struct { // {{{ + Id int + Name string + Pc int + Line int + NumActiveLocalVars int +} + +func newLabelDesc(id int, name string, pc, line, n int) *gotoLabelDesc { + return &gotoLabelDesc{ + Id: id, + Name: name, + Pc: pc, + Line: line, + NumActiveLocalVars: n, + } +} + +func (l *gotoLabelDesc) SetNumActiveLocalVars(n int) { + l.NumActiveLocalVars = n +} // }}} + type CompileError struct { // {{{ context *funcContext Line int @@ -209,12 +236,13 @@ func (cd *codeStore) PropagateMV(top int, save *int, reg *int, inc int) { } func (cd *codeStore) AddLoadNil(a, b, line int) { - last := cd.Last() - if opGetOpCode(last) == OP_LOADNIL && (opGetArgA(last)+opGetArgB(last)) == a { - cd.SetB(cd.LastPC(), b) - } else { - cd.AddABC(OP_LOADNIL, a, b, 0, line) - } + // this method used to merge multiple consecutive LOADNIL instructions + // of consecutive registers into a single LOADNIL instruction, but it + // caused issues when the merged instructions were JMP targets, and so + // generated invalid code; so the merging functionality has been removed. + // It is safe to merge the LOADNIL instructions under certain conditions, + // but additional logic / complexity would be needed here. + cd.AddABC(OP_LOADNIL, a, b, 0, line) } func (cd *codeStore) SetOpCode(pc int, v int) { @@ -328,16 +356,18 @@ func (vp *varNamePool) Register(name string) int { /* FuncContext {{{ */ type codeBlock struct { - LocalVars *varNamePool - BreakLabel int - Parent *codeBlock - RefUpvalue bool - LineStart int - LastLine int + LocalVars *varNamePool + BreakLabel int + Parent *codeBlock + RefUpvalue bool + LineStart int + LastLine int + labels map[string]*gotoLabelDesc + firstGotoIndex int } -func newCodeBlock(localvars *varNamePool, blabel int, parent *codeBlock, pos ast.PositionHolder) *codeBlock { - bl := &codeBlock{localvars, blabel, parent, false, 0, 0} +func newCodeBlock(localvars *varNamePool, blabel int, parent *codeBlock, pos ast.PositionHolder, firstGotoIndex int) *codeBlock { + bl := &codeBlock{localvars, blabel, parent, false, 0, 0, map[string]*gotoLabelDesc{}, firstGotoIndex} if pos != nil { bl.LineStart = pos.Line() bl.LastLine = pos.LastLine() @@ -345,33 +375,136 @@ func newCodeBlock(localvars *varNamePool, blabel int, parent *codeBlock, pos ast return bl } +func (b *codeBlock) AddLabel(label *gotoLabelDesc) *gotoLabelDesc { + if old, ok := b.labels[label.Name]; ok { + return old + } + b.labels[label.Name] = label + return nil +} + +func (b *codeBlock) GetLabel(label string) *gotoLabelDesc { + if v, ok := b.labels[label]; ok { + return v + } + return nil +} + +func (b *codeBlock) LocalVarsCount() int { + count := 0 + for block := b; block != nil; block = block.Parent { + count += len(block.LocalVars.Names()) + } + return count +} + type funcContext struct { - Proto *FunctionProto - Code *codeStore - Parent *funcContext - Upvalues *varNamePool - Block *codeBlock - Blocks []*codeBlock - regTop int - labelId int - labelPc map[int]int + Proto *FunctionProto + Code *codeStore + Parent *funcContext + Upvalues *varNamePool + Block *codeBlock + Blocks []*codeBlock + regTop int + labelId int + labelPc map[int]int + gotosCount int + unresolvedGotos map[int]*gotoLabelDesc } func newFuncContext(sourcename string, parent *funcContext) *funcContext { fc := &funcContext{ - Proto: newFunctionProto(sourcename), - Code: &codeStore{make([]uint32, 0, 1024), make([]int, 0, 1024), 0}, - Parent: parent, - Upvalues: newVarNamePool(0), - Block: newCodeBlock(newVarNamePool(0), labelNoJump, nil, nil), - regTop: 0, - labelId: 1, - labelPc: map[int]int{}, + Proto: newFunctionProto(sourcename), + Code: &codeStore{make([]uint32, 0, 1024), make([]int, 0, 1024), 0}, + Parent: parent, + Upvalues: newVarNamePool(0), + Block: newCodeBlock(newVarNamePool(0), labelNoJump, nil, nil, 0), + regTop: 0, + labelId: 1, + labelPc: map[int]int{}, + gotosCount: 0, + unresolvedGotos: map[int]*gotoLabelDesc{}, } fc.Blocks = []*codeBlock{fc.Block} return fc } +func (fc *funcContext) CheckUnresolvedGoto() { + for i := fc.Block.firstGotoIndex; i < fc.gotosCount; i++ { + gotoLabel, ok := fc.unresolvedGotos[i] + if !ok { + continue + } + raiseCompileError(fc, fc.Proto.LastLineDefined, "no visible label '%s' for at line %d", gotoLabel.Name, gotoLabel.Line) + } +} + +func (fc *funcContext) AddUnresolvedGoto(label *gotoLabelDesc) { + fc.unresolvedGotos[fc.gotosCount] = label + fc.gotosCount++ +} + +func (fc *funcContext) AddNamedLabel(label *gotoLabelDesc) { + if old := fc.Block.AddLabel(label); old != nil { + raiseCompileError(fc, label.Line+1, "label '%s' already defined on line %d", label.Name, old.Line) + } + fc.SetLabelPc(label.Id, label.Pc) +} + +func (fc *funcContext) GetNamedLabel(name string) *gotoLabelDesc { + return fc.Block.GetLabel(name) +} + +func (fc *funcContext) ResolveGoto(from, to *gotoLabelDesc, index int) { + if from.NumActiveLocalVars < to.NumActiveLocalVars { + varName := fc.Block.LocalVars.Names()[len(fc.Block.LocalVars.Names())-1] + raiseCompileError(fc, to.Line+1, " at line %d jumps into the scope of local '%s'", to.Name, from.Line, varName) + } + fc.Code.SetSbx(from.Pc, to.Id) + delete(fc.unresolvedGotos, index) +} + +func (fc *funcContext) FindLabel(block *codeBlock, gotoLabel *gotoLabelDesc, i int) bool { + target := block.GetLabel(gotoLabel.Name) + if target != nil { + if gotoLabel.NumActiveLocalVars > target.NumActiveLocalVars && block.RefUpvalue { + fc.Code.SetA(gotoLabel.Pc-1, target.NumActiveLocalVars) + } + fc.ResolveGoto(gotoLabel, target, i) + return true + } + return false +} + +func (fc *funcContext) ResolveCurrentBlockGotosWithParentBlock() { + blockActiveLocalVars := fc.Block.Parent.LocalVarsCount() + for i := fc.Block.firstGotoIndex; i < fc.gotosCount; i++ { + gotoLabel, ok := fc.unresolvedGotos[i] + if !ok { + continue + } + if gotoLabel.NumActiveLocalVars > blockActiveLocalVars { + if fc.Block.RefUpvalue { + fc.Code.SetA(gotoLabel.Pc-1, blockActiveLocalVars) + } + gotoLabel.SetNumActiveLocalVars(blockActiveLocalVars) + } + fc.FindLabel(fc.Block.Parent, gotoLabel, i) + } +} + +func (fc *funcContext) ResolveForwardGoto(target *gotoLabelDesc) { + for i := fc.Block.firstGotoIndex; i <= fc.gotosCount; i++ { + gotoLabel, ok := fc.unresolvedGotos[i] + if !ok { + continue + } + if gotoLabel.Name == target.Name { + fc.ResolveGoto(gotoLabel, target, i) + } + } +} + func (fc *funcContext) NewLabel() int { ret := fc.labelId fc.labelId++ @@ -400,6 +533,13 @@ func (fc *funcContext) ConstIndex(value LValue) int { } return v } +func (fc *funcContext) BlockLocalVarsCount() int { + count := 0 + for block := fc.Block; block != nil; block = block.Parent { + count += len(block.LocalVars.Names()) + } + return count +} func (fc *funcContext) RegisterLocalVar(name string) int { ret := fc.Block.LocalVars.Register(name) @@ -431,7 +571,7 @@ func (fc *funcContext) LocalVars() []varNamePoolValue { } func (fc *funcContext) EnterBlock(blabel int, pos ast.PositionHolder) { - fc.Block = newCodeBlock(newVarNamePool(fc.RegTop()), blabel, fc.Block, pos) + fc.Block = newCodeBlock(newVarNamePool(fc.RegTop()), blabel, fc.Block, pos, fc.gotosCount) fc.Blocks = append(fc.Blocks, fc.Block) } @@ -447,6 +587,10 @@ func (fc *funcContext) CloseUpvalues() int { func (fc *funcContext) LeaveBlock() int { closed := fc.CloseUpvalues() fc.EndScope() + + if fc.Block.Parent != nil { + fc.ResolveCurrentBlockGotosWithParentBlock() + } fc.Block = fc.Block.Parent fc.SetRegTop(fc.Block.LocalVars.LastIndex()) return closed @@ -471,9 +615,17 @@ func (fc *funcContext) RegTop() int { /* FuncContext }}} */ -func compileChunk(context *funcContext, chunk []ast.Stmt) { // {{{ - for _, stmt := range chunk { - compileStmt(context, stmt) +func compileChunk(context *funcContext, chunk []ast.Stmt, untilFollows bool) { // {{{ + for i, stmt := range chunk { + lastStmt := true + for j := i + 1; j < len(chunk); j++ { + _, ok := chunk[j].(*ast.LabelStmt) + if !ok { + lastStmt = false + break + } + } + compileStmt(context, stmt, lastStmt && !untilFollows) } } // }}} @@ -485,13 +637,21 @@ func compileBlock(context *funcContext, chunk []ast.Stmt) { // {{{ ph.SetLine(sline(chunk[0])) ph.SetLastLine(eline(chunk[len(chunk)-1])) context.EnterBlock(labelNoJump, ph) - for _, stmt := range chunk { - compileStmt(context, stmt) + for i, stmt := range chunk { + lastStmt := true + for j := i + 1; j < len(chunk); j++ { + _, ok := chunk[j].(*ast.LabelStmt) + if !ok { + lastStmt = false + break + } + } + compileStmt(context, stmt, lastStmt) } context.LeaveBlock() } // }}} -func compileStmt(context *funcContext, stmt ast.Stmt) { // {{{ +func compileStmt(context *funcContext, stmt ast.Stmt, isLastStmt bool) { // {{{ switch st := stmt.(type) { case *ast.AssignStmt: compileAssignStmt(context, st) @@ -501,7 +661,7 @@ func compileStmt(context *funcContext, stmt ast.Stmt) { // {{{ compileFuncCallExpr(context, context.RegTop(), st.Expr.(*ast.FuncCallExpr), ecnone(-1)) case *ast.DoBlockStmt: context.EnterBlock(labelNoJump, st) - compileChunk(context, st.Stmts) + compileChunk(context, st.Stmts, false) context.LeaveBlock() case *ast.WhileStmt: compileWhileStmt(context, st) @@ -519,14 +679,17 @@ func compileStmt(context *funcContext, stmt ast.Stmt) { // {{{ compileNumberForStmt(context, st) case *ast.GenericForStmt: compileGenericForStmt(context, st) + case *ast.LabelStmt: + compileLabelStmt(context, st, isLastStmt) + case *ast.GotoStmt: + compileGotoStmt(context, st) } } // }}} func compileAssignStmtLeft(context *funcContext, stmt *ast.AssignStmt) (int, []*assigncontext) { // {{{ reg := context.RegTop() acs := make([]*assigncontext, 0, len(stmt.Lhs)) - for i, lhs := range stmt.Lhs { - islast := i == len(stmt.Lhs)-1 + for _, lhs := range stmt.Lhs { switch st := lhs.(type) { case *ast.IdentExpr: identtype := getIdentRefType(context, context, st) @@ -537,9 +700,7 @@ func compileAssignStmtLeft(context *funcContext, stmt *ast.AssignStmt) (int, []* case ecUpvalue: context.Upvalues.RegisterUnique(st.Value) case ecLocal: - if islast { - ec.reg = context.FindLocalVar(st.Value) - } + ec.reg = context.FindLocalVar(st.Value) } acs = append(acs, &assigncontext{ec, 0, 0, false, false}) case *ast.AttrGetExpr: @@ -825,7 +986,7 @@ func compileWhileStmt(context *funcContext, stmt *ast.WhileStmt) { // {{{ compileBranchCondition(context, context.RegTop(), stmt.Condition, thenlabel, elselabel, false) context.SetLabelPc(thenlabel, context.Code.LastPC()) context.EnterBlock(elselabel, stmt) - compileChunk(context, stmt.Stmts) + compileChunk(context, stmt.Stmts, false) context.CloseUpvalues() context.Code.AddASbx(OP_JMP, 0, condlabel, eline(stmt)) context.LeaveBlock() @@ -840,7 +1001,7 @@ func compileRepeatStmt(context *funcContext, stmt *ast.RepeatStmt) { // {{{ context.SetLabelPc(initlabel, context.Code.LastPC()) context.SetLabelPc(elselabel, context.Code.LastPC()) context.EnterBlock(thenlabel, stmt) - compileChunk(context, stmt.Stmts) + compileChunk(context, stmt.Stmts, true) compileBranchCondition(context, context.RegTop(), stmt.Condition, thenlabel, elselabel, false) context.SetLabelPc(thenlabel, context.Code.LastPC()) @@ -916,7 +1077,7 @@ func compileNumberForStmt(context *funcContext, stmt *ast.NumberForStmt) { // {{ context.RegisterLocalVar(stmt.Name) bodypc := code.LastPC() - compileChunk(context, stmt.Stmts) + compileChunk(context, stmt.Stmts, false) context.LeaveBlock() @@ -949,7 +1110,7 @@ func compileGenericForStmt(context *funcContext, stmt *ast.GenericForStmt) { // } context.SetLabelPc(bodylabel, code.LastPC()) - compileChunk(context, stmt.Stmts) + compileChunk(context, stmt.Stmts, false) context.LeaveBlock() @@ -960,6 +1121,24 @@ func compileGenericForStmt(context *funcContext, stmt *ast.GenericForStmt) { // context.SetLabelPc(endlabel, code.LastPC()) } // }}} +func compileLabelStmt(context *funcContext, stmt *ast.LabelStmt, isLastStmt bool) { // {{{ + labelId := context.NewLabel() + label := newLabelDesc(labelId, stmt.Name, context.Code.LastPC(), sline(stmt), context.BlockLocalVarsCount()) + context.AddNamedLabel(label) + if isLastStmt { + label.SetNumActiveLocalVars(context.Block.Parent.LocalVarsCount()) + } + context.ResolveForwardGoto(label) +} // }}} + +func compileGotoStmt(context *funcContext, stmt *ast.GotoStmt) { // {{{ + context.Code.AddABC(OP_CLOSE, 0, 0, 0, sline(stmt)) + context.Code.AddASbx(OP_JMP, 0, labelNoJump, sline(stmt)) + label := newLabelDesc(-1, stmt.Label, context.Code.LastPC(), sline(stmt), context.BlockLocalVarsCount()) + context.AddUnresolvedGoto(label) + context.FindLabel(context.Block, label, context.gotosCount-1) +} // }}} + func compileExpr(context *funcContext, reg int, expr ast.Expr, ec *expcontext) int { // {{{ code := context.Code sreg := savereg(ec, reg) @@ -1149,10 +1328,11 @@ func compileFunctionExpr(context *funcContext, funcexpr *ast.FunctionExpr, ec *e context.Proto.IsVarArg |= VarArgIsVarArg } - compileChunk(context, funcexpr.Stmts) + compileChunk(context, funcexpr.Stmts, false) context.Code.AddABC(OP_RETURN, 0, 1, 0, eline(funcexpr)) context.EndScope() + context.CheckUnresolvedGoto() context.Proto.Code = context.Code.List() context.Proto.DbgSourcePositions = context.Code.PosList() context.Proto.DbgUpvalues = context.Upvalues.Names() @@ -1468,7 +1648,17 @@ func compileLogicalOpExprAux(context *funcContext, reg int, expr ast.Expr, ec *e a := reg sreg := savereg(ec, a) - if !hasnextcond && thenlabel == elselabel { + isLastAnd := elselabel == lb.e && thenlabel != elselabel + isLastOr := thenlabel == lb.e && hasnextcond + + if ident, ok := expr.(*ast.IdentExpr); ok && (isLastAnd || isLastOr) && getIdentRefType(context, context, ident) == ecLocal { + b := context.FindLocalVar(ident.Value) + op := OP_TESTSET + if sreg == b { + op = OP_TEST + } + code.AddABC(op, sreg, b, 0^flip, sline(expr)) + } else if !hasnextcond && thenlabel == elselabel { reg += compileExpr(context, reg, expr, &expcontext{ec.ctype, intMax(a, sreg), ec.varargopt}) last := context.Code.Last() if opGetOpCode(last) == OP_MOVE && opGetArgA(last) == a { @@ -1478,7 +1668,7 @@ func compileLogicalOpExprAux(context *funcContext, reg int, expr ast.Expr, ec *e } } else { reg += compileExpr(context, reg, expr, ecnone(0)) - if sreg == a { + if !hasnextcond { code.AddABC(OP_TEST, a, 0, 0^flip, sline(expr)) } else { code.AddABC(OP_TESTSET, sreg, a, 0^flip, sline(expr)) @@ -1669,6 +1859,10 @@ func Compile(chunk []ast.Stmt, name string) (proto *FunctionProto, err error) { err = nil parlist := &ast.ParList{HasVargs: true, Names: []string{}} funcexpr := &ast.FunctionExpr{ParList: parlist, Stmts: chunk} + if len(chunk) > 0 { + funcexpr.SetLastLine(sline(chunk[0])) + funcexpr.SetLastLine(eline(chunk[len(chunk)-1]) + 1) + } context := newFuncContext(name, nil) compileFunctionExpr(context, funcexpr, ecnone(0)) proto = context.Proto diff --git a/debuglib.go b/debuglib.go index 41f883f1..da8f5254 100644 --- a/debuglib.go +++ b/debuglib.go @@ -155,8 +155,8 @@ func debugTraceback(L *LState) int { level := L.OptInt(2, 1) ls := L if L.GetTop() > 0 { - if s, ok := L.Get(1).assertString(); ok { - msg = s + if s, ok := L.Get(1).(LString); ok { + msg = string(s) } if l, ok := L.Get(1).(*LState); ok { ls = l diff --git a/go.mod b/go.mod index f501c77b..83842901 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/yuin/gopher-lua -go 1.17 +go 1.23 require github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e diff --git a/iolib.go b/iolib.go index 4a86f893..ba0b8334 100644 --- a/iolib.go +++ b/iolib.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" "io" - "io/ioutil" "os" "os/exec" "syscall" @@ -373,7 +372,7 @@ func fileReadAux(L *LState, file *lFile, idx int) int { L.Push(v) case 'a': var buf []byte - buf, err = ioutil.ReadAll(file.reader) + buf, err = io.ReadAll(file.reader) if err == io.EOF { L.Push(emptyLString) goto normalreturn @@ -404,10 +403,10 @@ normalreturn: return L.GetTop() - top errreturn: - L.RaiseError(err.Error()) - //L.Push(LNil) - //L.Push(LString(err.Error())) - return 2 + L.Push(LNil) + L.Push(LString(err.Error())) + L.Push(LNumber(1)) // C-Lua compatibility: Original Lua pushes errno to the stack + return 3 } var fileSeekOptions = []string{"set", "cur", "end"} @@ -629,7 +628,7 @@ func ioOpenFile(L *LState) int { mode = os.O_RDONLY writable = false case "w", "wb": - mode = os.O_WRONLY | os.O_CREATE + mode = os.O_WRONLY | os.O_TRUNC | os.O_CREATE readable = false case "a", "ab": mode = os.O_WRONLY | os.O_APPEND | os.O_CREATE @@ -658,6 +657,9 @@ func ioPopen(L *LState) int { cmd := L.CheckString(1) if L.GetTop() == 1 { L.Push(LString("r")) + } else if L.GetTop() > 1 && (L.Get(2)).Type() == LTNil { + L.SetTop(1) + L.Push(LString("r")) } var file *LUserData var err error @@ -701,7 +703,7 @@ func ioType(L *LState) int { } func ioTmpFile(L *LState) int { - file, err := ioutil.TempFile("", "") + file, err := os.CreateTemp("", "") if err != nil { L.Push(LNil) L.Push(LString(err.Error())) diff --git a/oslib.go b/oslib.go index 256c8811..b8ebaea5 100644 --- a/oslib.go +++ b/oslib.go @@ -1,7 +1,6 @@ package lua import ( - "io/ioutil" "os" "strings" "time" @@ -23,7 +22,7 @@ func getIntField(L *LState, tb *LTable, key string, v int) int { slv := string(lv) slv = strings.TrimLeft(slv, " ") if strings.HasPrefix(slv, "0") && !strings.HasPrefix(slv, "0x") && !strings.HasPrefix(slv, "0X") { - //Standard lua interpreter only support decimal and hexadecimal + // Standard lua interpreter only support decimal and hexadecimal slv = strings.TrimLeft(slv, "0") if slv == "" { return 0 @@ -106,16 +105,20 @@ func osExit(L *LState) int { func osDate(L *LState) int { t := time.Now() + isUTC := false cfmt := "%c" if L.GetTop() >= 1 { cfmt = L.CheckString(1) if strings.HasPrefix(cfmt, "!") { - t = time.Now().UTC() cfmt = strings.TrimLeft(cfmt, "!") + isUTC = true } if L.GetTop() >= 2 { t = time.Unix(L.CheckInt64(2), 0) } + if isUTC { + t = t.UTC() + } if strings.HasPrefix(cfmt, "*t") { ret := L.NewTable() ret.RawSetString("year", LNumber(t.Year())) @@ -219,7 +222,7 @@ func osTime(L *LState) int { } func osTmpname(L *LState) int { - file, err := ioutil.TempFile("", "") + file, err := os.CreateTemp("", "") if err != nil { L.RaiseError("unable to generate a unique filename") } diff --git a/oslib_test.go b/oslib_test.go new file mode 100644 index 00000000..5af92625 --- /dev/null +++ b/oslib_test.go @@ -0,0 +1,33 @@ +package lua + +import ( + "testing" +) + +// correctly gc-ed. There was a bug in gopher lua where local vars were not being gc-ed in all circumstances. +func TestOsWrite(t *testing.T) { + s := ` + local function write(filename, content) + local f = assert(io.open(filename, "w")) + f:write(content) + assert(f:close()) + end + + local filename = os.tmpname() + write(filename, "abc") + write(filename, "d") + local f = assert(io.open(filename, "r")) + local content = f:read("*all"):gsub("%s+", "") + f:close() + os.remove(filename) + local expected = "d" + if content ~= expected then + error(string.format("Invalid content: Expecting \"%s\", got \"%s\"", expected, content)) + end +` + L := NewState() + defer L.Close() + if err := L.DoString(s); err != nil { + t.Error(err) + } +} diff --git a/parse/lexer.go b/parse/lexer.go index 6ad57cee..c20a0bdd 100644 --- a/parse/lexer.go +++ b/parse/lexer.go @@ -4,11 +4,12 @@ import ( "bufio" "bytes" "fmt" - "github.com/yuin/gopher-lua/ast" "io" "reflect" "strconv" "strings" + + "github.com/yuin/gopher-lua/ast" ) const EOF = -1 @@ -286,7 +287,7 @@ var reservedWords = map[string]int{ "end": TEnd, "false": TFalse, "for": TFor, "function": TFunction, "if": TIf, "in": TIn, "local": TLocal, "nil": TNil, "not": TNot, "or": TOr, "return": TReturn, "repeat": TRepeat, "then": TThen, "true": TTrue, - "until": TUntil, "while": TWhile} + "until": TUntil, "while": TWhile, "goto": TGoto} func (sc *Scanner) Scan(lexer *Lexer) (ast.Token, error) { redo: @@ -408,7 +409,16 @@ redo: tok.Type = '.' } tok.Str = buf.String() - case '+', '*', '/', '%', '^', '#', '(', ')', '{', '}', ']', ';', ':', ',': + case ':': + if sc.Peek() == ':' { + tok.Type = T2Colon + tok.Str = "::" + sc.Next() + } else { + tok.Type = ch + tok.Str = string(rune(ch)) + } + case '+', '*', '/', '%', '^', '#', '(', ')', '{', '}', ']', ';', ',': tok.Type = ch tok.Str = string(rune(ch)) default: diff --git a/parse/parser.go b/parse/parser.go index c7565816..aee06c8c 100644 --- a/parse/parser.go +++ b/parse/parser.go @@ -54,16 +54,18 @@ const TThen = 57363 const TTrue = 57364 const TUntil = 57365 const TWhile = 57366 -const TEqeq = 57367 -const TNeq = 57368 -const TLte = 57369 -const TGte = 57370 -const T2Comma = 57371 -const T3Comma = 57372 -const TIdent = 57373 -const TNumber = 57374 -const TString = 57375 -const UNARY = 57376 +const TGoto = 57367 +const TEqeq = 57368 +const TNeq = 57369 +const TLte = 57370 +const TGte = 57371 +const T2Comma = 57372 +const T3Comma = 57373 +const T2Colon = 57374 +const TIdent = 57375 +const TNumber = 57376 +const TString = 57377 +const UNARY = 57378 var yyToknames = [...]string{ "$end", @@ -90,12 +92,14 @@ var yyToknames = [...]string{ "TTrue", "TUntil", "TWhile", + "TGoto", "TEqeq", "TNeq", "TLte", "TGte", "T2Comma", "T3Comma", + "T2Colon", "TIdent", "TNumber", "TString", @@ -128,7 +132,7 @@ const yyEofCode = 1 const yyErrCode = 2 const yyInitialStackSize = 16 -//line parser.go.y:517 +//line parser.go.y:525 func TokenName(c int) string { if c >= TAnd && c-TAnd < len(yyToknames) { @@ -144,200 +148,207 @@ var yyExca = [...]int8{ -1, 1, 1, -1, -2, 0, - -1, 17, - 46, 31, - 47, 31, - -2, 68, - -1, 93, - 46, 32, - 47, 32, - -2, 68, + -1, 19, + 48, 33, + 49, 33, + -2, 70, + -1, 97, + 48, 34, + 49, 34, + -2, 70, } const yyPrivate = 57344 -const yyLast = 579 +const yyLast = 616 var yyAct = [...]uint8{ - 24, 88, 50, 23, 45, 84, 56, 65, 137, 153, - 136, 113, 52, 142, 54, 53, 33, 134, 65, 132, - 62, 63, 32, 61, 108, 109, 48, 111, 106, 41, - 42, 105, 49, 155, 166, 81, 82, 83, 138, 104, - 22, 91, 131, 80, 95, 92, 162, 74, 48, 85, - 150, 99, 165, 148, 49, 149, 75, 76, 77, 78, - 79, 67, 80, 107, 106, 148, 114, 115, 116, 117, - 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, - 128, 129, 72, 73, 71, 70, 74, 65, 39, 40, - 47, 139, 133, 68, 69, 75, 76, 77, 78, 79, - 60, 80, 141, 144, 143, 146, 145, 31, 67, 147, - 9, 48, 110, 97, 48, 152, 151, 49, 38, 62, - 49, 17, 66, 77, 78, 79, 96, 80, 59, 72, - 73, 71, 70, 74, 154, 102, 91, 156, 55, 157, - 68, 69, 75, 76, 77, 78, 79, 21, 80, 187, - 94, 20, 26, 184, 37, 179, 163, 112, 25, 35, - 178, 93, 170, 172, 27, 171, 164, 173, 19, 159, - 175, 174, 29, 89, 28, 39, 40, 20, 182, 181, - 100, 34, 135, 183, 67, 39, 40, 47, 186, 64, - 51, 1, 90, 87, 36, 130, 86, 30, 66, 18, - 46, 44, 43, 8, 58, 72, 73, 71, 70, 74, - 57, 67, 168, 169, 167, 3, 68, 69, 75, 76, - 77, 78, 79, 160, 80, 66, 4, 2, 0, 0, - 0, 158, 72, 73, 71, 70, 74, 0, 0, 0, - 0, 0, 0, 68, 69, 75, 76, 77, 78, 79, - 26, 80, 37, 0, 0, 0, 25, 35, 140, 0, - 0, 0, 27, 0, 0, 0, 0, 0, 0, 0, - 29, 21, 28, 39, 40, 20, 26, 0, 37, 34, - 0, 0, 25, 35, 0, 0, 0, 0, 27, 0, - 0, 0, 36, 98, 0, 0, 29, 89, 28, 39, - 40, 20, 26, 0, 37, 34, 0, 0, 25, 35, - 0, 0, 0, 0, 27, 67, 90, 176, 36, 0, - 0, 0, 29, 21, 28, 39, 40, 20, 0, 66, - 0, 34, 0, 0, 0, 0, 72, 73, 71, 70, - 74, 0, 67, 0, 36, 0, 0, 68, 69, 75, - 76, 77, 78, 79, 0, 80, 66, 0, 177, 0, - 0, 0, 0, 72, 73, 71, 70, 74, 0, 67, - 0, 185, 0, 0, 68, 69, 75, 76, 77, 78, - 79, 0, 80, 66, 0, 161, 0, 0, 0, 0, - 72, 73, 71, 70, 74, 0, 67, 0, 0, 0, - 0, 68, 69, 75, 76, 77, 78, 79, 0, 80, - 66, 0, 0, 180, 0, 0, 0, 72, 73, 71, - 70, 74, 0, 67, 0, 0, 0, 0, 68, 69, - 75, 76, 77, 78, 79, 0, 80, 66, 0, 0, - 103, 0, 0, 0, 72, 73, 71, 70, 74, 0, - 67, 0, 101, 0, 0, 68, 69, 75, 76, 77, - 78, 79, 0, 80, 66, 0, 0, 0, 0, 0, - 0, 72, 73, 71, 70, 74, 0, 67, 0, 0, - 0, 0, 68, 69, 75, 76, 77, 78, 79, 0, - 80, 66, 0, 0, 0, 0, 0, 0, 72, 73, - 71, 70, 74, 0, 0, 0, 0, 0, 0, 68, - 69, 75, 76, 77, 78, 79, 0, 80, 72, 73, - 71, 70, 74, 0, 0, 0, 0, 0, 0, 68, - 69, 75, 76, 77, 78, 79, 0, 80, 7, 10, - 0, 0, 0, 0, 14, 15, 13, 0, 16, 0, - 0, 0, 6, 12, 0, 0, 0, 11, 0, 0, - 0, 0, 0, 0, 21, 0, 0, 0, 20, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 5, + 26, 92, 52, 25, 47, 88, 158, 58, 142, 118, + 141, 167, 54, 69, 56, 55, 35, 139, 41, 42, + 49, 160, 34, 67, 63, 137, 50, 64, 112, 113, + 69, 109, 51, 48, 46, 45, 147, 85, 86, 87, + 115, 110, 24, 95, 43, 44, 99, 96, 78, 136, + 50, 171, 143, 103, 69, 108, 51, 84, 79, 80, + 81, 82, 83, 89, 84, 111, 110, 41, 42, 49, + 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, + 129, 130, 131, 132, 133, 134, 81, 82, 83, 170, + 84, 153, 155, 33, 154, 144, 9, 138, 40, 23, + 153, 19, 62, 22, 114, 101, 146, 149, 148, 151, + 150, 71, 100, 152, 66, 50, 65, 61, 50, 157, + 156, 51, 57, 64, 51, 70, 116, 106, 192, 21, + 173, 174, 172, 76, 77, 75, 74, 78, 98, 159, + 189, 95, 161, 97, 162, 72, 73, 79, 80, 81, + 82, 83, 68, 84, 184, 183, 177, 169, 164, 104, + 140, 168, 117, 53, 1, 91, 135, 175, 32, 20, + 176, 8, 178, 60, 59, 180, 179, 3, 165, 28, + 4, 39, 2, 187, 186, 27, 37, 0, 188, 0, + 0, 29, 0, 191, 71, 0, 0, 0, 0, 0, + 31, 0, 93, 30, 41, 42, 22, 0, 70, 0, + 36, 0, 0, 0, 0, 0, 76, 77, 75, 74, + 78, 94, 0, 38, 71, 90, 0, 0, 72, 73, + 79, 80, 81, 82, 83, 0, 84, 0, 70, 0, + 0, 0, 0, 163, 0, 0, 76, 77, 75, 74, + 78, 0, 0, 0, 0, 0, 0, 0, 72, 73, + 79, 80, 81, 82, 83, 28, 84, 39, 0, 0, + 0, 27, 37, 145, 0, 0, 0, 29, 0, 0, + 0, 0, 0, 0, 0, 0, 31, 0, 23, 30, + 41, 42, 22, 28, 0, 39, 36, 0, 0, 27, + 37, 0, 0, 0, 0, 29, 0, 0, 0, 38, + 102, 0, 0, 0, 31, 0, 93, 30, 41, 42, + 22, 28, 0, 39, 36, 0, 0, 27, 37, 0, + 0, 0, 0, 29, 0, 94, 71, 38, 181, 0, + 0, 0, 31, 0, 23, 30, 41, 42, 22, 0, + 70, 0, 36, 0, 0, 0, 0, 0, 76, 77, + 75, 74, 78, 71, 0, 38, 0, 0, 0, 0, + 72, 73, 79, 80, 81, 82, 83, 70, 84, 0, + 0, 182, 0, 0, 0, 76, 77, 75, 74, 78, + 71, 0, 190, 0, 0, 0, 0, 72, 73, 79, + 80, 81, 82, 83, 70, 84, 0, 0, 166, 0, + 0, 0, 76, 77, 75, 74, 78, 71, 0, 0, + 0, 0, 0, 0, 72, 73, 79, 80, 81, 82, + 83, 70, 84, 0, 185, 0, 0, 0, 0, 76, + 77, 75, 74, 78, 71, 0, 0, 0, 0, 0, + 0, 72, 73, 79, 80, 81, 82, 83, 70, 84, + 0, 107, 0, 0, 0, 0, 76, 77, 75, 74, + 78, 71, 0, 105, 0, 0, 0, 0, 72, 73, + 79, 80, 81, 82, 83, 70, 84, 0, 0, 0, + 0, 0, 0, 76, 77, 75, 74, 78, 71, 0, + 0, 0, 0, 0, 0, 72, 73, 79, 80, 81, + 82, 83, 70, 84, 0, 0, 0, 0, 0, 0, + 76, 77, 75, 74, 78, 0, 0, 0, 0, 0, + 0, 0, 72, 73, 79, 80, 81, 82, 83, 0, + 84, 7, 10, 0, 0, 0, 0, 14, 15, 13, + 0, 16, 71, 0, 0, 6, 12, 0, 0, 0, + 11, 18, 0, 0, 0, 0, 0, 0, 17, 23, + 0, 0, 0, 22, 76, 77, 75, 74, 78, 0, + 0, 0, 0, 5, 0, 0, 72, 73, 79, 80, + 81, 82, 83, 0, 84, 76, 77, 75, 74, 78, + 0, 0, 0, 0, 0, 0, 0, 72, 73, 79, + 80, 81, 82, 83, 0, 84, } var yyPact = [...]int16{ - -1000, -1000, 533, -5, -1000, -1000, 292, -1000, -17, 152, - -1000, 292, -1000, 292, 107, 97, 88, -1000, -1000, -1000, - 292, -1000, -1000, -29, 473, -1000, -1000, -1000, -1000, -1000, - -1000, 152, -1000, -1000, 292, 292, 292, 14, -1000, -1000, - 142, 292, 116, 292, 95, -1000, 82, 240, -1000, -1000, - 171, -1000, 446, 112, 419, -7, 17, 14, -24, -1000, - 81, -19, -1000, 104, -42, 292, 292, 292, 292, 292, - 292, 292, 292, 292, 292, 292, 292, 292, 292, 292, - 292, -1, -1, -1, -1000, -11, -1000, -37, -1000, -8, - 292, 473, -29, -1000, 152, 207, -1000, 55, -1000, -40, - -1000, -1000, 292, -1000, 292, 292, 34, -1000, 24, 19, - 14, 292, -1000, -1000, 473, 57, 493, 18, 18, 18, - 18, 18, 18, 18, 83, 83, -1, -1, -1, -1, - -44, -1000, -1000, -14, -1000, 266, -1000, -1000, 292, 180, - -1000, -1000, -1000, 160, 473, -1000, 338, 40, -1000, -1000, - -1000, -1000, -29, -1000, 157, 22, -1000, 473, -12, -1000, - 205, 292, -1000, 154, -1000, -1000, 292, -1000, -1000, 292, - 311, 151, -1000, 473, 146, 392, -1000, 292, -1000, -1000, - -1000, 144, 365, -1000, -1000, -1000, 140, -1000, + -1000, -1000, 536, -5, -1000, -1000, 311, -1000, -4, -17, + -1000, 311, -1000, 311, 89, 84, 90, 83, 81, -1000, + -1000, -1000, 311, -1000, -1000, -36, 494, -1000, -1000, -1000, + -1000, -1000, -1000, -17, -1000, -1000, 311, 311, 311, 26, + -1000, -1000, 169, 311, 66, 311, 79, -1000, 72, 255, + -1000, -1000, 150, -1000, 467, 104, 440, 7, 17, 26, + -22, -1000, 71, -8, -1000, 94, -1000, 107, -46, 311, + 311, 311, 311, 311, 311, 311, 311, 311, 311, 311, + 311, 311, 311, 311, 311, 11, 11, 11, -1000, -6, + -1000, -39, -1000, 4, 311, 494, -36, -1000, -17, 220, + -1000, 32, -1000, -19, -1000, -1000, 311, -1000, 311, 311, + 67, -1000, 61, 59, 26, 311, -1000, -1000, -1000, 494, + 548, 569, 18, 18, 18, 18, 18, 18, 18, 44, + 44, 11, 11, 11, 11, -49, -1000, -1000, -28, -1000, + 283, -1000, -1000, 311, 190, -1000, -1000, -1000, 149, 494, + -1000, 359, 5, -1000, -1000, -1000, -1000, -36, -1000, 148, + 58, -1000, 494, 3, -1000, 123, 311, -1000, 147, -1000, + -1000, 311, -1000, -1000, 311, 332, 146, -1000, 494, 145, + 413, -1000, 311, -1000, -1000, -1000, 131, 386, -1000, -1000, + -1000, 119, -1000, } var yyPgo = [...]uint8{ - 0, 190, 227, 2, 226, 223, 215, 210, 204, 203, - 118, 6, 3, 0, 22, 107, 168, 199, 4, 197, - 5, 195, 16, 193, 1, 182, + 0, 163, 182, 2, 180, 178, 177, 174, 173, 171, + 98, 7, 3, 0, 22, 93, 129, 169, 4, 168, + 5, 166, 16, 165, 1, 160, } var yyR1 = [...]int8{ 0, 1, 1, 1, 2, 2, 2, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, - 4, 4, 5, 5, 6, 6, 6, 7, 7, 8, - 8, 9, 9, 10, 10, 10, 11, 11, 12, 12, + 4, 4, 4, 4, 5, 5, 6, 6, 6, 7, + 7, 8, 8, 9, 9, 10, 10, 10, 11, 11, + 12, 12, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, - 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, - 13, 13, 13, 13, 13, 13, 13, 14, 15, 15, - 15, 15, 17, 16, 16, 18, 18, 18, 18, 19, - 20, 20, 21, 21, 21, 22, 22, 23, 23, 23, - 24, 24, 24, 25, 25, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 14, + 15, 15, 15, 15, 17, 16, 16, 18, 18, 18, + 18, 19, 20, 20, 21, 21, 21, 22, 22, 23, + 23, 23, 24, 24, 24, 25, 25, } var yyR2 = [...]int8{ 0, 1, 2, 3, 0, 2, 2, 1, 3, 1, 3, 5, 4, 6, 8, 9, 11, 7, 3, 4, - 4, 2, 0, 5, 1, 2, 1, 1, 3, 1, - 3, 1, 3, 1, 4, 3, 1, 3, 1, 3, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 3, 3, 3, 3, 2, 2, 2, 1, 1, 1, - 1, 3, 3, 2, 4, 2, 3, 1, 1, 2, - 5, 4, 1, 1, 3, 2, 3, 1, 3, 2, - 3, 5, 1, 1, 1, + 4, 2, 3, 2, 0, 5, 1, 2, 1, 1, + 3, 1, 3, 1, 3, 1, 4, 3, 1, 3, + 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 2, 2, 2, 1, + 1, 1, 1, 3, 3, 2, 4, 2, 3, 1, + 1, 2, 5, 4, 1, 1, 3, 2, 3, 1, + 3, 2, 3, 5, 1, 1, 1, } var yyChk = [...]int16{ - -1000, -1, -2, -6, -4, 45, 19, 5, -9, -15, - 6, 24, 20, 13, 11, 12, 15, -10, -17, -16, - 35, 31, 45, -12, -13, 16, 10, 22, 32, 30, - -19, -15, -14, -22, 39, 17, 52, 12, -10, 33, - 34, 46, 47, 50, 49, -18, 48, 35, -22, -14, - -3, -1, -13, -3, -13, 31, -11, -7, -8, 31, - 12, -11, 31, -13, -16, 47, 18, 4, 36, 37, - 28, 27, 25, 26, 29, 38, 39, 40, 41, 42, - 44, -13, -13, -13, -20, 35, 54, -23, -24, 31, - 50, -13, -12, -10, -15, -13, 31, 31, 53, -12, - 9, 6, 23, 21, 46, 14, 47, -20, 48, 49, - 31, 46, 53, 53, -13, -13, -13, -13, -13, -13, + -1000, -1, -2, -6, -4, 47, 19, 5, -9, -15, + 6, 24, 20, 13, 11, 12, 15, 32, 25, -10, + -17, -16, 37, 33, 47, -12, -13, 16, 10, 22, + 34, 31, -19, -15, -14, -22, 41, 17, 54, 12, + -10, 35, 36, 48, 49, 52, 51, -18, 50, 37, + -22, -14, -3, -1, -13, -3, -13, 33, -11, -7, + -8, 33, 12, -11, 33, 33, 33, -13, -16, 49, + 18, 4, 38, 39, 29, 28, 26, 27, 30, 40, + 41, 42, 43, 44, 46, -13, -13, -13, -20, 37, + 56, -23, -24, 33, 52, -13, -12, -10, -15, -13, + 33, 33, 55, -12, 9, 6, 23, 21, 48, 14, + 49, -20, 50, 51, 33, 48, 32, 55, 55, -13, -13, -13, -13, -13, -13, -13, -13, -13, -13, -13, - -21, 53, 30, -11, 54, -25, 47, 45, 46, -13, - 51, -18, 53, -3, -13, -3, -13, -12, 31, 31, - 31, -20, -12, 53, -3, 47, -24, -13, 51, 9, - -5, 47, 6, -3, 9, 30, 46, 9, 7, 8, - -13, -3, 9, -13, -3, -13, 6, 47, 9, 9, - 21, -3, -13, -3, 9, 6, -3, 9, + -13, -13, -13, -13, -13, -21, 55, 31, -11, 56, + -25, 49, 47, 48, -13, 53, -18, 55, -3, -13, + -3, -13, -12, 33, 33, 33, -20, -12, 55, -3, + 49, -24, -13, 53, 9, -5, 49, 6, -3, 9, + 31, 48, 9, 7, 8, -13, -3, 9, -13, -3, + -13, 6, 49, 9, 9, 21, -3, -13, -3, 9, + 6, -3, 9, } var yyDef = [...]int8{ - 4, -2, 1, 2, 5, 6, 24, 26, 0, 9, - 4, 0, 4, 0, 0, 0, 0, -2, 69, 70, - 0, 33, 3, 25, 38, 40, 41, 42, 43, 44, - 45, 46, 47, 48, 0, 0, 0, 0, 68, 67, - 0, 0, 0, 0, 0, 73, 0, 0, 77, 78, - 0, 7, 0, 0, 0, 36, 0, 0, 27, 29, - 0, 21, 36, 0, 70, 0, 0, 0, 0, 0, + 4, -2, 1, 2, 5, 6, 26, 28, 0, 9, + 4, 0, 4, 0, 0, 0, 0, 0, 0, -2, + 71, 72, 0, 35, 3, 27, 40, 42, 43, 44, + 45, 46, 47, 48, 49, 50, 0, 0, 0, 0, + 70, 69, 0, 0, 0, 0, 0, 75, 0, 0, + 79, 80, 0, 7, 0, 0, 0, 38, 0, 0, + 29, 31, 0, 21, 38, 0, 23, 0, 72, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 64, 65, 66, 79, 0, 85, 0, 87, 33, - 0, 92, 8, -2, 0, 0, 35, 0, 75, 0, - 10, 4, 0, 4, 0, 0, 0, 18, 0, 0, - 0, 0, 71, 72, 39, 49, 50, 51, 52, 53, - 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, - 0, 4, 82, 83, 86, 89, 93, 94, 0, 0, - 34, 74, 76, 0, 12, 22, 0, 0, 37, 28, - 30, 19, 20, 4, 0, 0, 88, 90, 0, 11, - 0, 0, 4, 0, 81, 84, 0, 13, 4, 0, - 0, 0, 80, 91, 0, 0, 4, 0, 17, 14, - 4, 0, 0, 23, 15, 4, 0, 16, + 0, 0, 0, 0, 0, 66, 67, 68, 81, 0, + 87, 0, 89, 35, 0, 94, 8, -2, 0, 0, + 37, 0, 77, 0, 10, 4, 0, 4, 0, 0, + 0, 18, 0, 0, 0, 0, 22, 73, 74, 41, + 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, + 61, 62, 63, 64, 65, 0, 4, 84, 85, 88, + 91, 95, 96, 0, 0, 36, 76, 78, 0, 12, + 24, 0, 0, 39, 30, 32, 19, 20, 4, 0, + 0, 90, 92, 0, 11, 0, 0, 4, 0, 83, + 86, 0, 13, 4, 0, 0, 0, 82, 93, 0, + 0, 4, 0, 17, 14, 4, 0, 0, 25, 15, + 4, 0, 16, } var yyTok1 = [...]int8{ 1, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 3, 3, 3, 3, 3, 52, 3, 42, 3, 3, - 35, 53, 40, 38, 47, 39, 49, 41, 3, 3, - 3, 3, 3, 3, 3, 3, 3, 3, 48, 45, - 37, 46, 36, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 54, 3, 44, 3, 3, + 37, 55, 42, 40, 49, 41, 51, 43, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 50, 47, + 39, 48, 38, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 3, 50, 3, 51, 44, 3, 3, 3, 3, 3, + 3, 52, 3, 53, 46, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 3, 3, 3, 34, 3, 54, + 3, 3, 3, 36, 3, 56, } var yyTok2 = [...]int8{ 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, - 32, 33, 43, + 32, 33, 34, 35, 45, } var yyTok3 = [...]int8{ @@ -856,61 +867,75 @@ yydefault: yyVAL.stmt.SetLine(yyDollar[1].token.Pos.Line) } case 22: + yyDollar = yyS[yypt-3 : yypt+1] +//line parser.go.y:191 + { + yyVAL.stmt = &ast.LabelStmt{Name: yyDollar[2].token.Str} + yyVAL.stmt.SetLine(yyDollar[1].token.Pos.Line) + } + case 23: + yyDollar = yyS[yypt-2 : yypt+1] +//line parser.go.y:195 + { + yyVAL.stmt = &ast.GotoStmt{Label: yyDollar[2].token.Str} + yyVAL.stmt.SetLine(yyDollar[1].token.Pos.Line) + } + case 24: yyDollar = yyS[yypt-0 : yypt+1] -//line parser.go.y:193 +//line parser.go.y:201 { yyVAL.stmts = []ast.Stmt{} } - case 23: + case 25: yyDollar = yyS[yypt-5 : yypt+1] -//line parser.go.y:196 +//line parser.go.y:204 { yyVAL.stmts = append(yyDollar[1].stmts, &ast.IfStmt{Condition: yyDollar[3].expr, Then: yyDollar[5].stmts}) yyVAL.stmts[len(yyVAL.stmts)-1].SetLine(yyDollar[2].token.Pos.Line) } - case 24: + case 26: yyDollar = yyS[yypt-1 : yypt+1] -//line parser.go.y:202 +//line parser.go.y:210 { yyVAL.stmt = &ast.ReturnStmt{Exprs: nil} yyVAL.stmt.SetLine(yyDollar[1].token.Pos.Line) } - case 25: + case 27: yyDollar = yyS[yypt-2 : yypt+1] -//line parser.go.y:206 +//line parser.go.y:214 { yyVAL.stmt = &ast.ReturnStmt{Exprs: yyDollar[2].exprlist} yyVAL.stmt.SetLine(yyDollar[1].token.Pos.Line) } - case 26: + case 28: yyDollar = yyS[yypt-1 : yypt+1] -//line parser.go.y:210 +//line parser.go.y:218 { yyVAL.stmt = &ast.BreakStmt{} yyVAL.stmt.SetLine(yyDollar[1].token.Pos.Line) } - case 27: + case 29: yyDollar = yyS[yypt-1 : yypt+1] -//line parser.go.y:216 +//line parser.go.y:224 { yyVAL.funcname = yyDollar[1].funcname } - case 28: + case 30: yyDollar = yyS[yypt-3 : yypt+1] -//line parser.go.y:219 +//line parser.go.y:227 { yyVAL.funcname = &ast.FuncName{Func: nil, Receiver: yyDollar[1].funcname.Func, Method: yyDollar[3].token.Str} } - case 29: + case 31: yyDollar = yyS[yypt-1 : yypt+1] -//line parser.go.y:224 +//line parser.go.y:232 { yyVAL.funcname = &ast.FuncName{Func: &ast.IdentExpr{Value: yyDollar[1].token.Str}} yyVAL.funcname.Func.SetLine(yyDollar[1].token.Pos.Line) } - case 30: + case 32: yyDollar = yyS[yypt-3 : yypt+1] -//line parser.go.y:228 +//line parser.go.y:236 { key := &ast.StringExpr{Value: yyDollar[3].token.Str} key.SetLine(yyDollar[3].token.Pos.Line) @@ -918,278 +943,278 @@ yydefault: fn.SetLine(yyDollar[3].token.Pos.Line) yyVAL.funcname = &ast.FuncName{Func: fn} } - case 31: + case 33: yyDollar = yyS[yypt-1 : yypt+1] -//line parser.go.y:237 +//line parser.go.y:245 { yyVAL.exprlist = []ast.Expr{yyDollar[1].expr} } - case 32: + case 34: yyDollar = yyS[yypt-3 : yypt+1] -//line parser.go.y:240 +//line parser.go.y:248 { yyVAL.exprlist = append(yyDollar[1].exprlist, yyDollar[3].expr) } - case 33: + case 35: yyDollar = yyS[yypt-1 : yypt+1] -//line parser.go.y:245 +//line parser.go.y:253 { yyVAL.expr = &ast.IdentExpr{Value: yyDollar[1].token.Str} yyVAL.expr.SetLine(yyDollar[1].token.Pos.Line) } - case 34: + case 36: yyDollar = yyS[yypt-4 : yypt+1] -//line parser.go.y:249 +//line parser.go.y:257 { yyVAL.expr = &ast.AttrGetExpr{Object: yyDollar[1].expr, Key: yyDollar[3].expr} yyVAL.expr.SetLine(yyDollar[1].expr.Line()) } - case 35: + case 37: yyDollar = yyS[yypt-3 : yypt+1] -//line parser.go.y:253 +//line parser.go.y:261 { key := &ast.StringExpr{Value: yyDollar[3].token.Str} key.SetLine(yyDollar[3].token.Pos.Line) yyVAL.expr = &ast.AttrGetExpr{Object: yyDollar[1].expr, Key: key} yyVAL.expr.SetLine(yyDollar[1].expr.Line()) } - case 36: - yyDollar = yyS[yypt-1 : yypt+1] -//line parser.go.y:261 - { - yyVAL.namelist = []string{yyDollar[1].token.Str} - } - case 37: - yyDollar = yyS[yypt-3 : yypt+1] -//line parser.go.y:264 - { - yyVAL.namelist = append(yyDollar[1].namelist, yyDollar[3].token.Str) - } case 38: yyDollar = yyS[yypt-1 : yypt+1] //line parser.go.y:269 { - yyVAL.exprlist = []ast.Expr{yyDollar[1].expr} + yyVAL.namelist = []string{yyDollar[1].token.Str} } case 39: yyDollar = yyS[yypt-3 : yypt+1] //line parser.go.y:272 { - yyVAL.exprlist = append(yyDollar[1].exprlist, yyDollar[3].expr) + yyVAL.namelist = append(yyDollar[1].namelist, yyDollar[3].token.Str) } case 40: yyDollar = yyS[yypt-1 : yypt+1] //line parser.go.y:277 { - yyVAL.expr = &ast.NilExpr{} - yyVAL.expr.SetLine(yyDollar[1].token.Pos.Line) + yyVAL.exprlist = []ast.Expr{yyDollar[1].expr} } case 41: - yyDollar = yyS[yypt-1 : yypt+1] -//line parser.go.y:281 + yyDollar = yyS[yypt-3 : yypt+1] +//line parser.go.y:280 { - yyVAL.expr = &ast.FalseExpr{} - yyVAL.expr.SetLine(yyDollar[1].token.Pos.Line) + yyVAL.exprlist = append(yyDollar[1].exprlist, yyDollar[3].expr) } case 42: yyDollar = yyS[yypt-1 : yypt+1] //line parser.go.y:285 { - yyVAL.expr = &ast.TrueExpr{} + yyVAL.expr = &ast.NilExpr{} yyVAL.expr.SetLine(yyDollar[1].token.Pos.Line) } case 43: yyDollar = yyS[yypt-1 : yypt+1] //line parser.go.y:289 { - yyVAL.expr = &ast.NumberExpr{Value: yyDollar[1].token.Str} + yyVAL.expr = &ast.FalseExpr{} yyVAL.expr.SetLine(yyDollar[1].token.Pos.Line) } case 44: yyDollar = yyS[yypt-1 : yypt+1] //line parser.go.y:293 { - yyVAL.expr = &ast.Comma3Expr{} + yyVAL.expr = &ast.TrueExpr{} yyVAL.expr.SetLine(yyDollar[1].token.Pos.Line) } case 45: yyDollar = yyS[yypt-1 : yypt+1] //line parser.go.y:297 { - yyVAL.expr = yyDollar[1].expr + yyVAL.expr = &ast.NumberExpr{Value: yyDollar[1].token.Str} + yyVAL.expr.SetLine(yyDollar[1].token.Pos.Line) } case 46: yyDollar = yyS[yypt-1 : yypt+1] -//line parser.go.y:300 +//line parser.go.y:301 { - yyVAL.expr = yyDollar[1].expr + yyVAL.expr = &ast.Comma3Expr{} + yyVAL.expr.SetLine(yyDollar[1].token.Pos.Line) } case 47: yyDollar = yyS[yypt-1 : yypt+1] -//line parser.go.y:303 +//line parser.go.y:305 { yyVAL.expr = yyDollar[1].expr } case 48: yyDollar = yyS[yypt-1 : yypt+1] -//line parser.go.y:306 +//line parser.go.y:308 { yyVAL.expr = yyDollar[1].expr } case 49: - yyDollar = yyS[yypt-3 : yypt+1] -//line parser.go.y:309 + yyDollar = yyS[yypt-1 : yypt+1] +//line parser.go.y:311 { - yyVAL.expr = &ast.LogicalOpExpr{Lhs: yyDollar[1].expr, Operator: "or", Rhs: yyDollar[3].expr} - yyVAL.expr.SetLine(yyDollar[1].expr.Line()) + yyVAL.expr = yyDollar[1].expr } case 50: - yyDollar = yyS[yypt-3 : yypt+1] -//line parser.go.y:313 + yyDollar = yyS[yypt-1 : yypt+1] +//line parser.go.y:314 { - yyVAL.expr = &ast.LogicalOpExpr{Lhs: yyDollar[1].expr, Operator: "and", Rhs: yyDollar[3].expr} - yyVAL.expr.SetLine(yyDollar[1].expr.Line()) + yyVAL.expr = yyDollar[1].expr } case 51: yyDollar = yyS[yypt-3 : yypt+1] //line parser.go.y:317 { - yyVAL.expr = &ast.RelationalOpExpr{Lhs: yyDollar[1].expr, Operator: ">", Rhs: yyDollar[3].expr} + yyVAL.expr = &ast.LogicalOpExpr{Lhs: yyDollar[1].expr, Operator: "or", Rhs: yyDollar[3].expr} yyVAL.expr.SetLine(yyDollar[1].expr.Line()) } case 52: yyDollar = yyS[yypt-3 : yypt+1] //line parser.go.y:321 { - yyVAL.expr = &ast.RelationalOpExpr{Lhs: yyDollar[1].expr, Operator: "<", Rhs: yyDollar[3].expr} + yyVAL.expr = &ast.LogicalOpExpr{Lhs: yyDollar[1].expr, Operator: "and", Rhs: yyDollar[3].expr} yyVAL.expr.SetLine(yyDollar[1].expr.Line()) } case 53: yyDollar = yyS[yypt-3 : yypt+1] //line parser.go.y:325 { - yyVAL.expr = &ast.RelationalOpExpr{Lhs: yyDollar[1].expr, Operator: ">=", Rhs: yyDollar[3].expr} + yyVAL.expr = &ast.RelationalOpExpr{Lhs: yyDollar[1].expr, Operator: ">", Rhs: yyDollar[3].expr} yyVAL.expr.SetLine(yyDollar[1].expr.Line()) } case 54: yyDollar = yyS[yypt-3 : yypt+1] //line parser.go.y:329 { - yyVAL.expr = &ast.RelationalOpExpr{Lhs: yyDollar[1].expr, Operator: "<=", Rhs: yyDollar[3].expr} + yyVAL.expr = &ast.RelationalOpExpr{Lhs: yyDollar[1].expr, Operator: "<", Rhs: yyDollar[3].expr} yyVAL.expr.SetLine(yyDollar[1].expr.Line()) } case 55: yyDollar = yyS[yypt-3 : yypt+1] //line parser.go.y:333 { - yyVAL.expr = &ast.RelationalOpExpr{Lhs: yyDollar[1].expr, Operator: "==", Rhs: yyDollar[3].expr} + yyVAL.expr = &ast.RelationalOpExpr{Lhs: yyDollar[1].expr, Operator: ">=", Rhs: yyDollar[3].expr} yyVAL.expr.SetLine(yyDollar[1].expr.Line()) } case 56: yyDollar = yyS[yypt-3 : yypt+1] //line parser.go.y:337 { - yyVAL.expr = &ast.RelationalOpExpr{Lhs: yyDollar[1].expr, Operator: "~=", Rhs: yyDollar[3].expr} + yyVAL.expr = &ast.RelationalOpExpr{Lhs: yyDollar[1].expr, Operator: "<=", Rhs: yyDollar[3].expr} yyVAL.expr.SetLine(yyDollar[1].expr.Line()) } case 57: yyDollar = yyS[yypt-3 : yypt+1] //line parser.go.y:341 { - yyVAL.expr = &ast.StringConcatOpExpr{Lhs: yyDollar[1].expr, Rhs: yyDollar[3].expr} + yyVAL.expr = &ast.RelationalOpExpr{Lhs: yyDollar[1].expr, Operator: "==", Rhs: yyDollar[3].expr} yyVAL.expr.SetLine(yyDollar[1].expr.Line()) } case 58: yyDollar = yyS[yypt-3 : yypt+1] //line parser.go.y:345 { - yyVAL.expr = &ast.ArithmeticOpExpr{Lhs: yyDollar[1].expr, Operator: "+", Rhs: yyDollar[3].expr} + yyVAL.expr = &ast.RelationalOpExpr{Lhs: yyDollar[1].expr, Operator: "~=", Rhs: yyDollar[3].expr} yyVAL.expr.SetLine(yyDollar[1].expr.Line()) } case 59: yyDollar = yyS[yypt-3 : yypt+1] //line parser.go.y:349 { - yyVAL.expr = &ast.ArithmeticOpExpr{Lhs: yyDollar[1].expr, Operator: "-", Rhs: yyDollar[3].expr} + yyVAL.expr = &ast.StringConcatOpExpr{Lhs: yyDollar[1].expr, Rhs: yyDollar[3].expr} yyVAL.expr.SetLine(yyDollar[1].expr.Line()) } case 60: yyDollar = yyS[yypt-3 : yypt+1] //line parser.go.y:353 { - yyVAL.expr = &ast.ArithmeticOpExpr{Lhs: yyDollar[1].expr, Operator: "*", Rhs: yyDollar[3].expr} + yyVAL.expr = &ast.ArithmeticOpExpr{Lhs: yyDollar[1].expr, Operator: "+", Rhs: yyDollar[3].expr} yyVAL.expr.SetLine(yyDollar[1].expr.Line()) } case 61: yyDollar = yyS[yypt-3 : yypt+1] //line parser.go.y:357 { - yyVAL.expr = &ast.ArithmeticOpExpr{Lhs: yyDollar[1].expr, Operator: "/", Rhs: yyDollar[3].expr} + yyVAL.expr = &ast.ArithmeticOpExpr{Lhs: yyDollar[1].expr, Operator: "-", Rhs: yyDollar[3].expr} yyVAL.expr.SetLine(yyDollar[1].expr.Line()) } case 62: yyDollar = yyS[yypt-3 : yypt+1] //line parser.go.y:361 { - yyVAL.expr = &ast.ArithmeticOpExpr{Lhs: yyDollar[1].expr, Operator: "%", Rhs: yyDollar[3].expr} + yyVAL.expr = &ast.ArithmeticOpExpr{Lhs: yyDollar[1].expr, Operator: "*", Rhs: yyDollar[3].expr} yyVAL.expr.SetLine(yyDollar[1].expr.Line()) } case 63: yyDollar = yyS[yypt-3 : yypt+1] //line parser.go.y:365 { - yyVAL.expr = &ast.ArithmeticOpExpr{Lhs: yyDollar[1].expr, Operator: "^", Rhs: yyDollar[3].expr} + yyVAL.expr = &ast.ArithmeticOpExpr{Lhs: yyDollar[1].expr, Operator: "/", Rhs: yyDollar[3].expr} yyVAL.expr.SetLine(yyDollar[1].expr.Line()) } case 64: - yyDollar = yyS[yypt-2 : yypt+1] + yyDollar = yyS[yypt-3 : yypt+1] //line parser.go.y:369 + { + yyVAL.expr = &ast.ArithmeticOpExpr{Lhs: yyDollar[1].expr, Operator: "%", Rhs: yyDollar[3].expr} + yyVAL.expr.SetLine(yyDollar[1].expr.Line()) + } + case 65: + yyDollar = yyS[yypt-3 : yypt+1] +//line parser.go.y:373 + { + yyVAL.expr = &ast.ArithmeticOpExpr{Lhs: yyDollar[1].expr, Operator: "^", Rhs: yyDollar[3].expr} + yyVAL.expr.SetLine(yyDollar[1].expr.Line()) + } + case 66: + yyDollar = yyS[yypt-2 : yypt+1] +//line parser.go.y:377 { yyVAL.expr = &ast.UnaryMinusOpExpr{Expr: yyDollar[2].expr} yyVAL.expr.SetLine(yyDollar[2].expr.Line()) } - case 65: + case 67: yyDollar = yyS[yypt-2 : yypt+1] -//line parser.go.y:373 +//line parser.go.y:381 { yyVAL.expr = &ast.UnaryNotOpExpr{Expr: yyDollar[2].expr} yyVAL.expr.SetLine(yyDollar[2].expr.Line()) } - case 66: + case 68: yyDollar = yyS[yypt-2 : yypt+1] -//line parser.go.y:377 +//line parser.go.y:385 { yyVAL.expr = &ast.UnaryLenOpExpr{Expr: yyDollar[2].expr} yyVAL.expr.SetLine(yyDollar[2].expr.Line()) } - case 67: + case 69: yyDollar = yyS[yypt-1 : yypt+1] -//line parser.go.y:383 +//line parser.go.y:391 { yyVAL.expr = &ast.StringExpr{Value: yyDollar[1].token.Str} yyVAL.expr.SetLine(yyDollar[1].token.Pos.Line) } - case 68: + case 70: yyDollar = yyS[yypt-1 : yypt+1] -//line parser.go.y:389 +//line parser.go.y:397 { yyVAL.expr = yyDollar[1].expr } - case 69: + case 71: yyDollar = yyS[yypt-1 : yypt+1] -//line parser.go.y:392 +//line parser.go.y:400 { yyVAL.expr = yyDollar[1].expr } - case 70: + case 72: yyDollar = yyS[yypt-1 : yypt+1] -//line parser.go.y:395 +//line parser.go.y:403 { yyVAL.expr = yyDollar[1].expr } - case 71: + case 73: yyDollar = yyS[yypt-3 : yypt+1] -//line parser.go.y:398 +//line parser.go.y:406 { if ex, ok := yyDollar[2].expr.(*ast.Comma3Expr); ok { ex.AdjustRet = true @@ -1197,161 +1222,161 @@ yydefault: yyVAL.expr = yyDollar[2].expr yyVAL.expr.SetLine(yyDollar[1].token.Pos.Line) } - case 72: + case 74: yyDollar = yyS[yypt-3 : yypt+1] -//line parser.go.y:407 +//line parser.go.y:415 { yyDollar[2].expr.(*ast.FuncCallExpr).AdjustRet = true yyVAL.expr = yyDollar[2].expr } - case 73: + case 75: yyDollar = yyS[yypt-2 : yypt+1] -//line parser.go.y:413 +//line parser.go.y:421 { yyVAL.expr = &ast.FuncCallExpr{Func: yyDollar[1].expr, Args: yyDollar[2].exprlist} yyVAL.expr.SetLine(yyDollar[1].expr.Line()) } - case 74: + case 76: yyDollar = yyS[yypt-4 : yypt+1] -//line parser.go.y:417 +//line parser.go.y:425 { yyVAL.expr = &ast.FuncCallExpr{Method: yyDollar[3].token.Str, Receiver: yyDollar[1].expr, Args: yyDollar[4].exprlist} yyVAL.expr.SetLine(yyDollar[1].expr.Line()) } - case 75: + case 77: yyDollar = yyS[yypt-2 : yypt+1] -//line parser.go.y:423 +//line parser.go.y:431 { if yylex.(*Lexer).PNewLine { yylex.(*Lexer).TokenError(yyDollar[1].token, "ambiguous syntax (function call x new statement)") } yyVAL.exprlist = []ast.Expr{} } - case 76: + case 78: yyDollar = yyS[yypt-3 : yypt+1] -//line parser.go.y:429 +//line parser.go.y:437 { if yylex.(*Lexer).PNewLine { yylex.(*Lexer).TokenError(yyDollar[1].token, "ambiguous syntax (function call x new statement)") } yyVAL.exprlist = yyDollar[2].exprlist } - case 77: + case 79: yyDollar = yyS[yypt-1 : yypt+1] -//line parser.go.y:435 +//line parser.go.y:443 { yyVAL.exprlist = []ast.Expr{yyDollar[1].expr} } - case 78: + case 80: yyDollar = yyS[yypt-1 : yypt+1] -//line parser.go.y:438 +//line parser.go.y:446 { yyVAL.exprlist = []ast.Expr{yyDollar[1].expr} } - case 79: + case 81: yyDollar = yyS[yypt-2 : yypt+1] -//line parser.go.y:443 +//line parser.go.y:451 { yyVAL.expr = &ast.FunctionExpr{ParList: yyDollar[2].funcexpr.ParList, Stmts: yyDollar[2].funcexpr.Stmts} yyVAL.expr.SetLine(yyDollar[1].token.Pos.Line) yyVAL.expr.SetLastLine(yyDollar[2].funcexpr.LastLine()) } - case 80: + case 82: yyDollar = yyS[yypt-5 : yypt+1] -//line parser.go.y:450 +//line parser.go.y:458 { yyVAL.funcexpr = &ast.FunctionExpr{ParList: yyDollar[2].parlist, Stmts: yyDollar[4].stmts} yyVAL.funcexpr.SetLine(yyDollar[1].token.Pos.Line) yyVAL.funcexpr.SetLastLine(yyDollar[5].token.Pos.Line) } - case 81: + case 83: yyDollar = yyS[yypt-4 : yypt+1] -//line parser.go.y:455 +//line parser.go.y:463 { yyVAL.funcexpr = &ast.FunctionExpr{ParList: &ast.ParList{HasVargs: false, Names: []string{}}, Stmts: yyDollar[3].stmts} yyVAL.funcexpr.SetLine(yyDollar[1].token.Pos.Line) yyVAL.funcexpr.SetLastLine(yyDollar[4].token.Pos.Line) } - case 82: + case 84: yyDollar = yyS[yypt-1 : yypt+1] -//line parser.go.y:462 +//line parser.go.y:470 { yyVAL.parlist = &ast.ParList{HasVargs: true, Names: []string{}} } - case 83: + case 85: yyDollar = yyS[yypt-1 : yypt+1] -//line parser.go.y:465 +//line parser.go.y:473 { yyVAL.parlist = &ast.ParList{HasVargs: false, Names: []string{}} yyVAL.parlist.Names = append(yyVAL.parlist.Names, yyDollar[1].namelist...) } - case 84: + case 86: yyDollar = yyS[yypt-3 : yypt+1] -//line parser.go.y:469 +//line parser.go.y:477 { yyVAL.parlist = &ast.ParList{HasVargs: true, Names: []string{}} yyVAL.parlist.Names = append(yyVAL.parlist.Names, yyDollar[1].namelist...) } - case 85: + case 87: yyDollar = yyS[yypt-2 : yypt+1] -//line parser.go.y:476 +//line parser.go.y:484 { yyVAL.expr = &ast.TableExpr{Fields: []*ast.Field{}} yyVAL.expr.SetLine(yyDollar[1].token.Pos.Line) } - case 86: + case 88: yyDollar = yyS[yypt-3 : yypt+1] -//line parser.go.y:480 +//line parser.go.y:488 { yyVAL.expr = &ast.TableExpr{Fields: yyDollar[2].fieldlist} yyVAL.expr.SetLine(yyDollar[1].token.Pos.Line) } - case 87: + case 89: yyDollar = yyS[yypt-1 : yypt+1] -//line parser.go.y:487 +//line parser.go.y:495 { yyVAL.fieldlist = []*ast.Field{yyDollar[1].field} } - case 88: + case 90: yyDollar = yyS[yypt-3 : yypt+1] -//line parser.go.y:490 +//line parser.go.y:498 { yyVAL.fieldlist = append(yyDollar[1].fieldlist, yyDollar[3].field) } - case 89: + case 91: yyDollar = yyS[yypt-2 : yypt+1] -//line parser.go.y:493 +//line parser.go.y:501 { yyVAL.fieldlist = yyDollar[1].fieldlist } - case 90: + case 92: yyDollar = yyS[yypt-3 : yypt+1] -//line parser.go.y:498 +//line parser.go.y:506 { yyVAL.field = &ast.Field{Key: &ast.StringExpr{Value: yyDollar[1].token.Str}, Value: yyDollar[3].expr} yyVAL.field.Key.SetLine(yyDollar[1].token.Pos.Line) } - case 91: + case 93: yyDollar = yyS[yypt-5 : yypt+1] -//line parser.go.y:502 +//line parser.go.y:510 { yyVAL.field = &ast.Field{Key: yyDollar[2].expr, Value: yyDollar[5].expr} } - case 92: + case 94: yyDollar = yyS[yypt-1 : yypt+1] -//line parser.go.y:505 +//line parser.go.y:513 { yyVAL.field = &ast.Field{Value: yyDollar[1].expr} } - case 93: + case 95: yyDollar = yyS[yypt-1 : yypt+1] -//line parser.go.y:510 +//line parser.go.y:518 { yyVAL.fieldsep = "," } - case 94: + case 96: yyDollar = yyS[yypt-1 : yypt+1] -//line parser.go.y:513 +//line parser.go.y:521 { yyVAL.fieldsep = ";" } diff --git a/parse/parser.go.y b/parse/parser.go.y index 9a9f831e..52bcd55f 100644 --- a/parse/parser.go.y +++ b/parse/parser.go.y @@ -52,10 +52,10 @@ import ( } /* Reserved words */ -%token TAnd TBreak TDo TElse TElseIf TEnd TFalse TFor TFunction TIf TIn TLocal TNil TNot TOr TReturn TRepeat TThen TTrue TUntil TWhile +%token TAnd TBreak TDo TElse TElseIf TEnd TFalse TFor TFunction TIf TIn TLocal TNil TNot TOr TReturn TRepeat TThen TTrue TUntil TWhile TGoto /* Literals */ -%token TEqeq TNeq TLte TGte T2Comma T3Comma TIdent TNumber TString '{' '(' +%token TEqeq TNeq TLte TGte T2Comma T3Comma T2Colon TIdent TNumber TString '{' '(' /* Operators */ %left TOr @@ -187,6 +187,14 @@ stat: TLocal namelist { $$ = &ast.LocalAssignStmt{Names: $2, Exprs:[]ast.Expr{}} $$.SetLine($1.Pos.Line) + } | + T2Colon TIdent T2Colon { + $$ = &ast.LabelStmt{Name: $2.Str} + $$.SetLine($1.Pos.Line) + } | + TGoto TIdent { + $$ = &ast.GotoStmt{Label: $2.Str} + $$.SetLine($1.Pos.Line) } elseifs: diff --git a/pm/pm.go b/pm/pm.go index e5c651f9..9abf57ec 100644 --- a/pm/pm.go +++ b/pm/pm.go @@ -5,8 +5,11 @@ import ( "fmt" ) -const EOS = -1 -const _UNKNOWN = -2 +const ( + EOS = -1 + _UNKNOWN = -2 + maxRecursionLevel = 1000000 +) /* Error {{{ */ @@ -524,7 +527,11 @@ func compilePattern(p pattern, ps ...*iptr) []inst { // Simple recursive virtual machine based on the // "Regular Expression Matching: the Virtual Machine Approach" (https://swtch.com/~rsc/regexp/regexp2.html) -func recursiveVM(src []byte, insts []inst, pc, sp int, ms ...*MatchData) (bool, int, *MatchData) { +func recursiveVM(src []byte, insts []inst, pc, sp, recLevel int, ms ...*MatchData) (bool, int, *MatchData) { + recLevel++ + if recLevel > maxRecursionLevel { + panic(newError(_UNKNOWN, "pattern/input too complex")) + } var m *MatchData if len(ms) == 0 { m = newMatchState() @@ -549,14 +556,14 @@ redo: pc = inst.Operand1 goto redo case opSplit: - if ok, nsp, _ := recursiveVM(src, insts, inst.Operand1, sp, m); ok { + if ok, nsp, _ := recursiveVM(src, insts, inst.Operand1, sp, recLevel, m); ok { return true, nsp, m } pc = inst.Operand2 goto redo case opSave: s := m.setCapture(inst.Operand1, sp) - if ok, nsp, _ := recursiveVM(src, insts, pc+1, sp, m); ok { + if ok, nsp, _ := recursiveVM(src, insts, pc+1, sp, recLevel, m); ok { return true, nsp, m } m.restoreCapture(inst.Operand1, s) @@ -620,7 +627,7 @@ func Find(p string, src []byte, offset, limit int) (matches []*MatchData, err er insts := compilePattern(pat) matches = []*MatchData{} for sp := offset; sp <= len(src); { - ok, nsp, ms := recursiveVM(src, insts, 0, sp) + ok, nsp, ms := recursiveVM(src, insts, 0, sp, 0) sp++ if ok { if sp < nsp { diff --git a/script_test.go b/script_test.go index 4a0abe0e..3358b362 100644 --- a/script_test.go +++ b/script_test.go @@ -2,12 +2,13 @@ package lua import ( "fmt" - "github.com/yuin/gopher-lua/parse" "os" "runtime" "sync/atomic" "testing" "time" + + "github.com/yuin/gopher-lua/parse" ) const maxMemory = 40 @@ -22,6 +23,7 @@ var gluaTests []string = []string{ "vm.lua", "math.lua", "strings.lua", + "goto.lua", } var luaTests []string = []string{ @@ -144,3 +146,115 @@ func TestGlua(t *testing.T) { func TestLua(t *testing.T) { testScriptDir(t, luaTests, "_lua5.1-tests") } + +func TestMergingLoadNilBug2(t *testing.T) { + // there was a bug where the LOADNIL merging optimisation would merge LOADNILs that were the targets of + // JMP instructions, causing the JMP to jump to the wrong location and breaking the logic and resulting in + // a panic. + s := ` + id = "foo" + + function get_def() + return {} + end + + function test() + local def = id ~= nil and get_def() or nil + if def ~= nil then + print("def is not nil") + end + end + + test() +` + L := NewState() + defer L.Close() + if err := L.DoString(s); err != nil { + t.Error(err) + } +} + +func TestMergingLoadNilBug(t *testing.T) { + // there was a bug where a multiple load nils were being incorrectly merged, and the following code exposed it + s := ` + function test() + local a = 0 + local b = 1 + local c = 2 + local d = 3 + local e = 4 -- reg 4 + local f = 5 + local g = 6 + local h = 7 + + if e == 4 then + e = nil -- should clear reg 4, but clears regs 4-8 by mistake + end + if f == nil then + error("bad f") + end + if g == nil then + error("bad g") + end + if h == nil then + error("bad h") + end + end + + test() +` + + L := NewState() + defer L.Close() + if err := L.DoString(s); err != nil { + t.Error(err) + } +} + +// This test is disabled because the LOADNIL merging optimisation has been disabled. See the comment in the +// AddLoadNil() function for more information. +//func TestMergingLoadNil(t *testing.T) { +// // multiple nil assignments to consecutive registers should be merged +// s := ` +// function test() +// local a = 0 +// local b = 1 +// local c = 2 +// +// -- this should generate just one LOADNIL byte code instruction +// a = nil +// b = nil +// c = nil +// +// print(a,b,c) +// end +// +// test()` +// +// chunk, err := parse.Parse(strings.NewReader(s), "test") +// if err != nil { +// t.Fatal(err) +// } +// +// compiled, err := Compile(chunk, "test") +// if err != nil { +// t.Fatal(err) +// } +// +// if len(compiled.FunctionPrototypes) != 1 { +// t.Fatal("expected 1 function prototype") +// } +// +// // there should be exactly 1 LOADNIL instruction in the byte code generated for the above +// // anymore, and the LOADNIL merging is not working correctly +// count := 0 +// for _, instr := range compiled.FunctionPrototypes[0].Code { +// if opGetOpCode(instr) == OP_LOADNIL { +// count++ +// } +// } +// +// if count != 1 { +// t.Fatalf("expected 1 LOADNIL instruction, found %d", count) +// } +//} diff --git a/state.go b/state.go index a1ee672e..292f93b4 100644 --- a/state.go +++ b/state.go @@ -402,24 +402,25 @@ func (rg *registry) forceResize(newSize int) { copy(newSlice, rg.array[:rg.top]) // should we copy the area beyond top? there shouldn't be any valid values there so it shouldn't be necessary. rg.array = newSlice } -func (rg *registry) SetTop(top int) { + +func (rg *registry) SetTop(topi int) { // +inline-start // this section is inlined by go-inline // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' { - requiredSize := top + requiredSize := topi if requiredSize > cap(rg.array) { rg.resize(requiredSize) } } - oldtop := rg.top - rg.top = top - for i := oldtop; i < rg.top; i++ { + oldtopi := rg.top + rg.top = topi + for i := oldtopi; i < rg.top; i++ { rg.array[i] = LNil } // values beyond top don't need to be valid LValues, so setting them to nil is fine // setting them to nil rather than LNil lets us invoke the golang memclr opto - if rg.top < oldtop { - nilRange := rg.array[rg.top:oldtop] + if rg.top < oldtopi { + nilRange := rg.array[rg.top:oldtopi] for i := range nilRange { nilRange[i] = nil } @@ -427,7 +428,7 @@ func (rg *registry) SetTop(top int) { //for i := rg.top; i < oldtop; i++ { // rg.array[i] = LNil //} -} +} // +inline-end func (rg *registry) Top() int { return rg.top @@ -530,19 +531,73 @@ func (rg *registry) FillNil(regm, n int) { // +inline-start func (rg *registry) Insert(value LValue, reg int) { top := rg.Top() if reg >= top { - rg.Set(reg, value) + // this section is inlined by go-inline + // source function is 'func (rg *registry) Set(regi int, vali LValue) ' in '_state.go' + { + regi := reg + vali := value + newSize := regi + 1 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + rg.array[regi] = vali + if regi >= rg.top { + rg.top = regi + 1 + } + } return } top-- for ; top >= reg; top-- { // FIXME consider using copy() here if Insert() is called enough - rg.Set(top+1, rg.Get(top)) + // this section is inlined by go-inline + // source function is 'func (rg *registry) Set(regi int, vali LValue) ' in '_state.go' + { + regi := top + 1 + vali := rg.Get(top) + newSize := regi + 1 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + rg.array[regi] = vali + if regi >= rg.top { + rg.top = regi + 1 + } + } + } + // this section is inlined by go-inline + // source function is 'func (rg *registry) Set(regi int, vali LValue) ' in '_state.go' + { + regi := reg + vali := value + newSize := regi + 1 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + rg.array[regi] = vali + if regi >= rg.top { + rg.top = regi + 1 + } } - rg.Set(reg, value) } -func (rg *registry) Set(reg int, val LValue) { - newSize := reg + 1 +func (rg *registry) Set(regi int, vali LValue) { // +inline-start + newSize := regi + 1 // this section is inlined by go-inline // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' { @@ -551,14 +606,14 @@ func (rg *registry) Set(reg int, val LValue) { rg.resize(requiredSize) } } - rg.array[reg] = val - if reg >= rg.top { - rg.top = reg + 1 + rg.array[regi] = vali + if regi >= rg.top { + rg.top = regi + 1 } -} +} // +inline-end -func (rg *registry) SetNumber(reg int, val LNumber) { - newSize := reg + 1 +func (rg *registry) SetNumber(regi int, vali LNumber) { // +inline-start + newSize := regi + 1 // this section is inlined by go-inline // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' { @@ -567,11 +622,11 @@ func (rg *registry) SetNumber(reg int, val LNumber) { rg.resize(requiredSize) } } - rg.array[reg] = rg.alloc.LNumber2I(val) - if reg >= rg.top { - rg.top = reg + 1 + rg.array[regi] = rg.alloc.LNumber2I(vali) + if regi >= rg.top { + rg.top = regi + 1 } -} +} // +inline-end func (rg *registry) IsFull() bool { return rg.top >= cap(rg.array) @@ -815,6 +870,9 @@ func (ls *LState) isStarted() bool { func (ls *LState) kill() { ls.Dead = true + if ls.ctxCancelFn != nil { + ls.ctxCancelFn() + } } func (ls *LState) indexToReg(idx int) int { @@ -1561,6 +1619,7 @@ func (ls *LState) NewThread() (*LState, context.CancelFunc) { if ls.ctx != nil { thread.mainLoop = mainLoopWithContext thread.ctx, f = context.WithCancel(ls.ctx) + thread.ctxCancelFn = f } return thread, f } @@ -2010,6 +2069,9 @@ func (ls *LState) PCall(nargs, nret int, errfunc *LFunction) (err error) { err = rcv.(*ApiError) err.(*ApiError).StackTrace = ls.stackTrace(0) } + ls.stack.SetSp(sp) + ls.currentFrame = ls.stack.Last() + ls.reg.SetTop(base) } }() ls.Call(1, 1) diff --git a/state_test.go b/state_test.go index bfe93b93..7b7aa53d 100644 --- a/state_test.go +++ b/state_test.go @@ -291,17 +291,39 @@ func TestPCall(t *testing.T) { })) errorIfFalse(t, strings.Contains(err.Error(), "by handler"), "") + L.Push(L.GetGlobal("f1")) err = L.PCall(0, 0, L.NewFunction(func(L *LState) int { L.RaiseError("error!") return 1 })) errorIfFalse(t, strings.Contains(err.Error(), "error!"), "") + L.Push(L.GetGlobal("f1")) err = L.PCall(0, 0, L.NewFunction(func(L *LState) int { panic("panicc!") return 1 })) errorIfFalse(t, strings.Contains(err.Error(), "panicc!"), "") + + // Issue #452, expected to be revert back to previous call stack after any error. + currentFrame, currentTop, currentSp := L.currentFrame, L.GetTop(), L.stack.Sp() + L.Push(L.GetGlobal("f1")) + err = L.PCall(0, 0, nil) + errorIfFalse(t, err != nil, "") + errorIfFalse(t, L.currentFrame == currentFrame, "") + errorIfFalse(t, L.GetTop() == currentTop, "") + errorIfFalse(t, L.stack.Sp() == currentSp, "") + + currentFrame, currentTop, currentSp = L.currentFrame, L.GetTop(), L.stack.Sp() + L.Push(L.GetGlobal("f1")) + err = L.PCall(0, 0, L.NewFunction(func(L *LState) int { + L.RaiseError("error!") + return 1 + })) + errorIfFalse(t, err != nil, "") + errorIfFalse(t, L.currentFrame == currentFrame, "") + errorIfFalse(t, L.GetTop() == currentTop, "") + errorIfFalse(t, L.stack.Sp() == currentSp, "") } func TestCoroutineApi1(t *testing.T) { diff --git a/value.go b/value.go index 0d4af808..4156e9d5 100644 --- a/value.go +++ b/value.go @@ -29,12 +29,6 @@ func (vt LValueType) String() string { type LValue interface { String() string Type() LValueType - // to reduce `runtime.assertI2T2` costs, this method should be used instead of the type assertion in heavy paths(typically inside the VM). - assertFloat64() (float64, bool) - // to reduce `runtime.assertI2T2` costs, this method should be used instead of the type assertion in heavy paths(typically inside the VM). - assertString() (string, bool) - // to reduce `runtime.assertI2T2` costs, this method should be used instead of the type assertion in heavy paths(typically inside the VM). - assertFunction() (*LFunction, bool) } // LVIsFalse returns true if a given LValue is a nil or false otherwise false. @@ -80,11 +74,8 @@ func LVAsNumber(v LValue) LNumber { type LNilType struct{} -func (nl *LNilType) String() string { return "nil" } -func (nl *LNilType) Type() LValueType { return LTNil } -func (nl *LNilType) assertFloat64() (float64, bool) { return 0, false } -func (nl *LNilType) assertString() (string, bool) { return "", false } -func (nl *LNilType) assertFunction() (*LFunction, bool) { return nil, false } +func (nl *LNilType) String() string { return "nil" } +func (nl *LNilType) Type() LValueType { return LTNil } var LNil = LValue(&LNilType{}) @@ -96,21 +87,15 @@ func (bl LBool) String() string { } return "false" } -func (bl LBool) Type() LValueType { return LTBool } -func (bl LBool) assertFloat64() (float64, bool) { return 0, false } -func (bl LBool) assertString() (string, bool) { return "", false } -func (bl LBool) assertFunction() (*LFunction, bool) { return nil, false } +func (bl LBool) Type() LValueType { return LTBool } var LTrue = LBool(true) var LFalse = LBool(false) type LString string -func (st LString) String() string { return string(st) } -func (st LString) Type() LValueType { return LTString } -func (st LString) assertFloat64() (float64, bool) { return 0, false } -func (st LString) assertString() (string, bool) { return string(st), true } -func (st LString) assertFunction() (*LFunction, bool) { return nil, false } +func (st LString) String() string { return string(st) } +func (st LString) Type() LValueType { return LTString } // fmt.Formatter interface func (st LString) Format(f fmt.State, c rune) { @@ -133,10 +118,7 @@ func (nm LNumber) String() string { return fmt.Sprint(float64(nm)) } -func (nm LNumber) Type() LValueType { return LTNumber } -func (nm LNumber) assertFloat64() (float64, bool) { return float64(nm), true } -func (nm LNumber) assertString() (string, bool) { return "", false } -func (nm LNumber) assertFunction() (*LFunction, bool) { return nil, false } +func (nm LNumber) Type() LValueType { return LTNumber } // fmt.Formatter interface func (nm LNumber) Format(f fmt.State, c rune) { @@ -168,11 +150,8 @@ type LTable struct { k2i map[LValue]int } -func (tb *LTable) String() string { return fmt.Sprintf("table: %p", tb) } -func (tb *LTable) Type() LValueType { return LTTable } -func (tb *LTable) assertFloat64() (float64, bool) { return 0, false } -func (tb *LTable) assertString() (string, bool) { return "", false } -func (tb *LTable) assertFunction() (*LFunction, bool) { return nil, false } +func (tb *LTable) String() string { return fmt.Sprintf("table: %p", tb) } +func (tb *LTable) Type() LValueType { return LTTable } type LFunction struct { IsG bool @@ -183,11 +162,8 @@ type LFunction struct { } type LGFunction func(*LState) int -func (fn *LFunction) String() string { return fmt.Sprintf("function: %p", fn) } -func (fn *LFunction) Type() LValueType { return LTFunction } -func (fn *LFunction) assertFloat64() (float64, bool) { return 0, false } -func (fn *LFunction) assertString() (string, bool) { return "", false } -func (fn *LFunction) assertFunction() (*LFunction, bool) { return fn, true } +func (fn *LFunction) String() string { return fmt.Sprintf("function: %p", fn) } +func (fn *LFunction) Type() LValueType { return LTFunction } type Global struct { MainThread *LState @@ -218,13 +194,11 @@ type LState struct { hasErrorFunc bool mainLoop func(*LState, *callFrame) ctx context.Context + ctxCancelFn context.CancelFunc } -func (ls *LState) String() string { return fmt.Sprintf("thread: %p", ls) } -func (ls *LState) Type() LValueType { return LTThread } -func (ls *LState) assertFloat64() (float64, bool) { return 0, false } -func (ls *LState) assertString() (string, bool) { return "", false } -func (ls *LState) assertFunction() (*LFunction, bool) { return nil, false } +func (ls *LState) String() string { return fmt.Sprintf("thread: %p", ls) } +func (ls *LState) Type() LValueType { return LTThread } type LUserData struct { Value interface{} @@ -232,16 +206,10 @@ type LUserData struct { Metatable LValue } -func (ud *LUserData) String() string { return fmt.Sprintf("userdata: %p", ud) } -func (ud *LUserData) Type() LValueType { return LTUserData } -func (ud *LUserData) assertFloat64() (float64, bool) { return 0, false } -func (ud *LUserData) assertString() (string, bool) { return "", false } -func (ud *LUserData) assertFunction() (*LFunction, bool) { return nil, false } +func (ud *LUserData) String() string { return fmt.Sprintf("userdata: %p", ud) } +func (ud *LUserData) Type() LValueType { return LTUserData } type LChannel chan LValue -func (ch LChannel) String() string { return fmt.Sprintf("channel: %p", ch) } -func (ch LChannel) Type() LValueType { return LTChannel } -func (ch LChannel) assertFloat64() (float64, bool) { return 0, false } -func (ch LChannel) assertString() (string, bool) { return "", false } -func (ch LChannel) assertFunction() (*LFunction, bool) { return nil, false } +func (ch LChannel) String() string { return fmt.Sprintf("channel: %p", ch) } +func (ch LChannel) Type() LValueType { return LTChannel } diff --git a/vm.go b/vm.go index 470855f2..97335a75 100644 --- a/vm.go +++ b/vm.go @@ -307,7 +307,27 @@ func init() { A := int(inst>>18) & 0xff //GETA RA := lbase + A B := int(inst & 0x1ff) //GETB - reg.Set(RA, reg.Get(lbase+B)) + v := reg.Get(lbase + B) + // this section is inlined by go-inline + // source function is 'func (rg *registry) Set(regi int, vali LValue) ' in '_state.go' + { + rg := reg + regi := RA + vali := v + newSize := regi + 1 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + rg.array[regi] = vali + if regi >= rg.top { + rg.top = regi + 1 + } + } return 0 }, func(L *LState, inst uint32, baseframe *callFrame) int { //OP_MOVEN @@ -317,7 +337,27 @@ func init() { A := int(inst>>18) & 0xff //GETA B := int(inst & 0x1ff) //GETB C := int(inst>>9) & 0x1ff //GETC - reg.Set(lbase+A, reg.Get(lbase+B)) + v := reg.Get(lbase + B) + // this section is inlined by go-inline + // source function is 'func (rg *registry) Set(regi int, vali LValue) ' in '_state.go' + { + rg := reg + regi := lbase + A + vali := v + newSize := regi + 1 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + rg.array[regi] = vali + if regi >= rg.top { + rg.top = regi + 1 + } + } code := cf.Fn.Proto.Code pc := cf.Pc for i := 0; i < C; i++ { @@ -325,7 +365,27 @@ func init() { pc++ A = int(inst>>18) & 0xff //GETA B = int(inst & 0x1ff) //GETB - reg.Set(lbase+A, reg.Get(lbase+B)) + v := reg.Get(lbase + B) + // this section is inlined by go-inline + // source function is 'func (rg *registry) Set(regi int, vali LValue) ' in '_state.go' + { + rg := reg + regi := lbase + A + vali := v + newSize := regi + 1 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + rg.array[regi] = vali + if regi >= rg.top { + rg.top = regi + 1 + } + } } cf.Pc = pc return 0 @@ -337,7 +397,27 @@ func init() { A := int(inst>>18) & 0xff //GETA RA := lbase + A Bx := int(inst & 0x3ffff) //GETBX - reg.Set(RA, cf.Fn.Proto.Constants[Bx]) + v := cf.Fn.Proto.Constants[Bx] + // this section is inlined by go-inline + // source function is 'func (rg *registry) Set(regi int, vali LValue) ' in '_state.go' + { + rg := reg + regi := RA + vali := v + newSize := regi + 1 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + rg.array[regi] = vali + if regi >= rg.top { + rg.top = regi + 1 + } + } return 0 }, func(L *LState, inst uint32, baseframe *callFrame) int { //OP_LOADBOOL @@ -349,9 +429,47 @@ func init() { B := int(inst & 0x1ff) //GETB C := int(inst>>9) & 0x1ff //GETC if B != 0 { - reg.Set(RA, LTrue) + // this section is inlined by go-inline + // source function is 'func (rg *registry) Set(regi int, vali LValue) ' in '_state.go' + { + rg := reg + regi := RA + vali := LTrue + newSize := regi + 1 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + rg.array[regi] = vali + if regi >= rg.top { + rg.top = regi + 1 + } + } } else { - reg.Set(RA, LFalse) + // this section is inlined by go-inline + // source function is 'func (rg *registry) Set(regi int, vali LValue) ' in '_state.go' + { + rg := reg + regi := RA + vali := LFalse + newSize := regi + 1 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + rg.array[regi] = vali + if regi >= rg.top { + rg.top = regi + 1 + } + } } if C != 0 { cf.Pc++ @@ -366,7 +484,26 @@ func init() { RA := lbase + A B := int(inst & 0x1ff) //GETB for i := RA; i <= lbase+B; i++ { - reg.Set(i, LNil) + // this section is inlined by go-inline + // source function is 'func (rg *registry) Set(regi int, vali LValue) ' in '_state.go' + { + rg := reg + regi := i + vali := LNil + newSize := regi + 1 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + rg.array[regi] = vali + if regi >= rg.top { + rg.top = regi + 1 + } + } } return 0 }, @@ -377,7 +514,27 @@ func init() { A := int(inst>>18) & 0xff //GETA RA := lbase + A B := int(inst & 0x1ff) //GETB - reg.Set(RA, cf.Fn.Upvalues[B].Value()) + v := cf.Fn.Upvalues[B].Value() + // this section is inlined by go-inline + // source function is 'func (rg *registry) Set(regi int, vali LValue) ' in '_state.go' + { + rg := reg + regi := RA + vali := v + newSize := regi + 1 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + rg.array[regi] = vali + if regi >= rg.top { + rg.top = regi + 1 + } + } return 0 }, func(L *LState, inst uint32, baseframe *callFrame) int { //OP_GETGLOBAL @@ -388,7 +545,27 @@ func init() { RA := lbase + A Bx := int(inst & 0x3ffff) //GETBX //reg.Set(RA, L.getField(cf.Fn.Env, cf.Fn.Proto.Constants[Bx])) - reg.Set(RA, L.getFieldString(cf.Fn.Env, cf.Fn.Proto.stringConstants[Bx])) + v := L.getFieldString(cf.Fn.Env, cf.Fn.Proto.stringConstants[Bx]) + // this section is inlined by go-inline + // source function is 'func (rg *registry) Set(regi int, vali LValue) ' in '_state.go' + { + rg := reg + regi := RA + vali := v + newSize := regi + 1 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + rg.array[regi] = vali + if regi >= rg.top { + rg.top = regi + 1 + } + } return 0 }, func(L *LState, inst uint32, baseframe *callFrame) int { //OP_GETTABLE @@ -399,7 +576,27 @@ func init() { RA := lbase + A B := int(inst & 0x1ff) //GETB C := int(inst>>9) & 0x1ff //GETC - reg.Set(RA, L.getField(reg.Get(lbase+B), L.rkValue(C))) + v := L.getField(reg.Get(lbase+B), L.rkValue(C)) + // this section is inlined by go-inline + // source function is 'func (rg *registry) Set(regi int, vali LValue) ' in '_state.go' + { + rg := reg + regi := RA + vali := v + newSize := regi + 1 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + rg.array[regi] = vali + if regi >= rg.top { + rg.top = regi + 1 + } + } return 0 }, func(L *LState, inst uint32, baseframe *callFrame) int { //OP_GETTABLEKS @@ -410,7 +607,27 @@ func init() { RA := lbase + A B := int(inst & 0x1ff) //GETB C := int(inst>>9) & 0x1ff //GETC - reg.Set(RA, L.getFieldString(reg.Get(lbase+B), L.rkString(C))) + v := L.getFieldString(reg.Get(lbase+B), L.rkString(C)) + // this section is inlined by go-inline + // source function is 'func (rg *registry) Set(regi int, vali LValue) ' in '_state.go' + { + rg := reg + regi := RA + vali := v + newSize := regi + 1 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + rg.array[regi] = vali + if regi >= rg.top { + rg.top = regi + 1 + } + } return 0 }, func(L *LState, inst uint32, baseframe *callFrame) int { //OP_SETGLOBAL @@ -464,7 +681,27 @@ func init() { RA := lbase + A B := int(inst & 0x1ff) //GETB C := int(inst>>9) & 0x1ff //GETC - reg.Set(RA, newLTable(B, C)) + v := newLTable(B, C) + // this section is inlined by go-inline + // source function is 'func (rg *registry) Set(regi int, vali LValue) ' in '_state.go' + { + rg := reg + regi := RA + vali := v + newSize := regi + 1 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + rg.array[regi] = vali + if regi >= rg.top { + rg.top = regi + 1 + } + } return 0 }, func(L *LState, inst uint32, baseframe *callFrame) int { //OP_SELF @@ -476,8 +713,47 @@ func init() { B := int(inst & 0x1ff) //GETB C := int(inst>>9) & 0x1ff //GETC selfobj := reg.Get(lbase + B) - reg.Set(RA, L.getFieldString(selfobj, L.rkString(C))) - reg.Set(RA+1, selfobj) + v := L.getFieldString(selfobj, L.rkString(C)) + // this section is inlined by go-inline + // source function is 'func (rg *registry) Set(regi int, vali LValue) ' in '_state.go' + { + rg := reg + regi := RA + vali := v + newSize := regi + 1 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + rg.array[regi] = vali + if regi >= rg.top { + rg.top = regi + 1 + } + } + // this section is inlined by go-inline + // source function is 'func (rg *registry) Set(regi int, vali LValue) ' in '_state.go' + { + rg := reg + regi := RA + 1 + vali := selfobj + newSize := regi + 1 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + rg.array[regi] = vali + if regi >= rg.top { + rg.top = regi + 1 + } + } return 0 }, opArith, // OP_ADD @@ -495,17 +771,74 @@ func init() { B := int(inst & 0x1ff) //GETB unaryv := L.rkValue(B) if nm, ok := unaryv.(LNumber); ok { - reg.SetNumber(RA, -nm) + // this section is inlined by go-inline + // source function is 'func (rg *registry) Set(regi int, vali LValue) ' in '_state.go' + { + rg := reg + regi := RA + vali := -nm + newSize := regi + 1 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + rg.array[regi] = vali + if regi >= rg.top { + rg.top = regi + 1 + } + } } else { op := L.metaOp1(unaryv, "__unm") if op.Type() == LTFunction { reg.Push(op) reg.Push(unaryv) L.Call(1, 1) - reg.Set(RA, reg.Pop()) + // this section is inlined by go-inline + // source function is 'func (rg *registry) Set(regi int, vali LValue) ' in '_state.go' + { + rg := reg + regi := RA + vali := reg.Pop() + newSize := regi + 1 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + rg.array[regi] = vali + if regi >= rg.top { + rg.top = regi + 1 + } + } } else if str, ok1 := unaryv.(LString); ok1 { if num, err := parseNumber(string(str)); err == nil { - reg.Set(RA, -num) + // this section is inlined by go-inline + // source function is 'func (rg *registry) Set(regi int, vali LValue) ' in '_state.go' + { + rg := reg + regi := RA + vali := -num + newSize := regi + 1 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + rg.array[regi] = vali + if regi >= rg.top { + rg.top = regi + 1 + } + } } else { L.RaiseError("__unm undefined") } @@ -523,9 +856,47 @@ func init() { RA := lbase + A B := int(inst & 0x1ff) //GETB if LVIsFalse(reg.Get(lbase + B)) { - reg.Set(RA, LTrue) + // this section is inlined by go-inline + // source function is 'func (rg *registry) Set(regi int, vali LValue) ' in '_state.go' + { + rg := reg + regi := RA + vali := LTrue + newSize := regi + 1 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + rg.array[regi] = vali + if regi >= rg.top { + rg.top = regi + 1 + } + } } else { - reg.Set(RA, LFalse) + // this section is inlined by go-inline + // source function is 'func (rg *registry) Set(regi int, vali LValue) ' in '_state.go' + { + rg := reg + regi := RA + vali := LFalse + newSize := regi + 1 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + rg.array[regi] = vali + if regi >= rg.top { + rg.top = regi + 1 + } + } } return 0 }, @@ -538,7 +909,26 @@ func init() { B := int(inst & 0x1ff) //GETB switch lv := L.rkValue(B).(type) { case LString: - reg.SetNumber(RA, LNumber(len(lv))) + // this section is inlined by go-inline + // source function is 'func (rg *registry) SetNumber(regi int, vali LNumber) ' in '_state.go' + { + rg := reg + regi := RA + vali := LNumber(len(lv)) + newSize := regi + 1 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + rg.array[regi] = rg.alloc.LNumber2I(vali) + if regi >= rg.top { + rg.top = regi + 1 + } + } default: op := L.metaOp1(lv, "__len") if op.Type() == LTFunction { @@ -547,12 +937,70 @@ func init() { L.Call(1, 1) ret := reg.Pop() if ret.Type() == LTNumber { - reg.SetNumber(RA, ret.(LNumber)) + v, _ := ret.(LNumber) + // this section is inlined by go-inline + // source function is 'func (rg *registry) SetNumber(regi int, vali LNumber) ' in '_state.go' + { + rg := reg + regi := RA + vali := v + newSize := regi + 1 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + rg.array[regi] = rg.alloc.LNumber2I(vali) + if regi >= rg.top { + rg.top = regi + 1 + } + } } else { - reg.Set(RA, ret) + // this section is inlined by go-inline + // source function is 'func (rg *registry) Set(regi int, vali LValue) ' in '_state.go' + { + rg := reg + regi := RA + vali := ret + newSize := regi + 1 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + rg.array[regi] = vali + if regi >= rg.top { + rg.top = regi + 1 + } + } } } else if lv.Type() == LTTable { - reg.SetNumber(RA, LNumber(lv.(*LTable).Len())) + // this section is inlined by go-inline + // source function is 'func (rg *registry) SetNumber(regi int, vali LNumber) ' in '_state.go' + { + rg := reg + regi := RA + vali := LNumber(lv.(*LTable).Len()) + newSize := regi + 1 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + rg.array[regi] = rg.alloc.LNumber2I(vali) + if regi >= rg.top { + rg.top = regi + 1 + } + } } else { L.RaiseError("__len undefined") } @@ -569,7 +1017,27 @@ func init() { C := int(inst>>9) & 0x1ff //GETC RC := lbase + C RB := lbase + B - reg.Set(RA, stringConcat(L, RC-RB+1, RC)) + v := stringConcat(L, RC-RB+1, RC) + // this section is inlined by go-inline + // source function is 'func (rg *registry) Set(regi int, vali LValue) ' in '_state.go' + { + rg := reg + regi := RA + vali := v + newSize := regi + 1 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + rg.array[regi] = vali + if regi >= rg.top { + rg.top = regi + 1 + } + } return 0 }, func(L *LState, inst uint32, baseframe *callFrame) int { //OP_JMP @@ -617,8 +1085,8 @@ func init() { rhs := L.rkValue(C) ret := false - if v1, ok1 := lhs.assertFloat64(); ok1 { - if v2, ok2 := rhs.assertFloat64(); ok2 { + if v1, ok1 := lhs.(LNumber); ok1 { + if v2, ok2 := rhs.(LNumber); ok2 { ret = v1 <= v2 } else { L.RaiseError("attempt to compare %v with %v", lhs.Type().String(), rhs.Type().String()) @@ -672,7 +1140,26 @@ func init() { B := int(inst & 0x1ff) //GETB C := int(inst>>9) & 0x1ff //GETC if value := reg.Get(lbase + B); LVAsBool(value) != (C == 0) { - reg.Set(RA, value) + // this section is inlined by go-inline + // source function is 'func (rg *registry) Set(regi int, vali LValue) ' in '_state.go' + { + rg := reg + regi := RA + vali := value + newSize := regi + 1 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + rg.array[regi] = vali + if regi >= rg.top { + rg.top = regi + 1 + } + } } else { cf.Pc++ } @@ -694,7 +1181,7 @@ func init() { nret := C - 1 var callable *LFunction var meta bool - if fn, ok := lv.assertFunction(); ok { + if fn, ok := lv.(*LFunction); ok { callable = fn meta = false } else { @@ -837,7 +1324,7 @@ func init() { lv := reg.Get(RA) var callable *LFunction var meta bool - if fn, ok := lv.assertFunction(); ok { + if fn, ok := lv.(*LFunction); ok { callable = fn meta = false } else { @@ -1308,17 +1795,85 @@ func init() { lbase := cf.LocalBase A := int(inst>>18) & 0xff //GETA RA := lbase + A - if init, ok1 := reg.Get(RA).assertFloat64(); ok1 { - if limit, ok2 := reg.Get(RA + 1).assertFloat64(); ok2 { - if step, ok3 := reg.Get(RA + 2).assertFloat64(); ok3 { + if init, ok1 := reg.Get(RA).(LNumber); ok1 { + if limit, ok2 := reg.Get(RA + 1).(LNumber); ok2 { + if step, ok3 := reg.Get(RA + 2).(LNumber); ok3 { init += step - reg.SetNumber(RA, LNumber(init)) + v := LNumber(init) + // this section is inlined by go-inline + // source function is 'func (rg *registry) SetNumber(regi int, vali LNumber) ' in '_state.go' + { + rg := reg + regi := RA + vali := v + newSize := regi + 1 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + rg.array[regi] = rg.alloc.LNumber2I(vali) + if regi >= rg.top { + rg.top = regi + 1 + } + } if (step > 0 && init <= limit) || (step <= 0 && init >= limit) { Sbx := int(inst&0x3ffff) - opMaxArgSbx //GETSBX cf.Pc += Sbx - reg.SetNumber(RA+3, LNumber(init)) + // this section is inlined by go-inline + // source function is 'func (rg *registry) SetNumber(regi int, vali LNumber) ' in '_state.go' + { + rg := reg + regi := RA + 3 + vali := v + newSize := regi + 1 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + rg.array[regi] = rg.alloc.LNumber2I(vali) + if regi >= rg.top { + rg.top = regi + 1 + } + } } else { - reg.SetTop(RA + 1) + // this section is inlined by go-inline + // source function is 'func (rg *registry) SetTop(topi int) ' in '_state.go' + { + rg := reg + topi := RA + 1 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := topi + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + oldtopi := rg.top + rg.top = topi + for i := oldtopi; i < rg.top; i++ { + rg.array[i] = LNil + } + // values beyond top don't need to be valid LValues, so setting them to nil is fine + // setting them to nil rather than LNil lets us invoke the golang memclr opto + if rg.top < oldtopi { + nilRange := rg.array[rg.top:oldtopi] + for i := range nilRange { + nilRange[i] = nil + } + } + //for i := rg.top; i < oldtop; i++ { + // rg.array[i] = LNil + //} + } } } else { L.RaiseError("for statement step must be a number") @@ -1338,9 +1893,28 @@ func init() { A := int(inst>>18) & 0xff //GETA RA := lbase + A Sbx := int(inst&0x3ffff) - opMaxArgSbx //GETSBX - if init, ok1 := reg.Get(RA).assertFloat64(); ok1 { - if step, ok2 := reg.Get(RA + 2).assertFloat64(); ok2 { - reg.SetNumber(RA, LNumber(init-step)) + if init, ok1 := reg.Get(RA).(LNumber); ok1 { + if step, ok2 := reg.Get(RA + 2).(LNumber); ok2 { + // this section is inlined by go-inline + // source function is 'func (rg *registry) SetNumber(regi int, vali LNumber) ' in '_state.go' + { + rg := reg + regi := RA + vali := LNumber(init - step) + newSize := regi + 1 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + rg.array[regi] = rg.alloc.LNumber2I(vali) + if regi >= rg.top { + rg.top = regi + 1 + } + } } else { L.RaiseError("for statement step must be a number") } @@ -1358,13 +1932,118 @@ func init() { RA := lbase + A C := int(inst>>9) & 0x1ff //GETC nret := C - reg.SetTop(RA + 3 + 2) - reg.Set(RA+3+2, reg.Get(RA+2)) - reg.Set(RA+3+1, reg.Get(RA+1)) - reg.Set(RA+3, reg.Get(RA)) + // this section is inlined by go-inline + // source function is 'func (rg *registry) SetTop(topi int) ' in '_state.go' + { + rg := reg + topi := RA + 3 + 2 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := topi + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + oldtopi := rg.top + rg.top = topi + for i := oldtopi; i < rg.top; i++ { + rg.array[i] = LNil + } + // values beyond top don't need to be valid LValues, so setting them to nil is fine + // setting them to nil rather than LNil lets us invoke the golang memclr opto + if rg.top < oldtopi { + nilRange := rg.array[rg.top:oldtopi] + for i := range nilRange { + nilRange[i] = nil + } + } + //for i := rg.top; i < oldtop; i++ { + // rg.array[i] = LNil + //} + } + // this section is inlined by go-inline + // source function is 'func (rg *registry) Set(regi int, vali LValue) ' in '_state.go' + { + rg := reg + regi := RA + 3 + 2 + vali := reg.Get(RA + 2) + newSize := regi + 1 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + rg.array[regi] = vali + if regi >= rg.top { + rg.top = regi + 1 + } + } + // this section is inlined by go-inline + // source function is 'func (rg *registry) Set(regi int, vali LValue) ' in '_state.go' + { + rg := reg + regi := RA + 3 + 1 + vali := reg.Get(RA + 1) + newSize := regi + 1 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + rg.array[regi] = vali + if regi >= rg.top { + rg.top = regi + 1 + } + } + // this section is inlined by go-inline + // source function is 'func (rg *registry) Set(regi int, vali LValue) ' in '_state.go' + { + rg := reg + regi := RA + 3 + vali := reg.Get(RA) + newSize := regi + 1 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + rg.array[regi] = vali + if regi >= rg.top { + rg.top = regi + 1 + } + } L.callR(2, nret, RA+3) if value := reg.Get(RA + 3); value != LNil { - reg.Set(RA+2, value) + // this section is inlined by go-inline + // source function is 'func (rg *registry) Set(regi int, vali LValue) ' in '_state.go' + { + rg := reg + regi := RA + 2 + vali := value + newSize := regi + 1 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + rg.array[regi] = vali + if regi >= rg.top { + rg.top = regi + 1 + } + } pc := cf.Fn.Proto.Code[cf.Pc] cf.Pc += int(pc&0x3ffff) - opMaxArgSbx } @@ -1430,7 +2109,26 @@ func init() { Bx := int(inst & 0x3ffff) //GETBX proto := cf.Fn.Proto.FunctionPrototypes[Bx] closure := newLFunctionL(proto, cf.Fn.Env, int(proto.NumUpvalues)) - reg.Set(RA, closure) + // this section is inlined by go-inline + // source function is 'func (rg *registry) Set(regi int, vali LValue) ' in '_state.go' + { + rg := reg + regi := RA + vali := closure + newSize := regi + 1 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + rg.array[regi] = vali + if regi >= rg.top { + rg.top = regi + 1 + } + } for i := 0; i < int(proto.NumUpvalues); i++ { inst = cf.Fn.Proto.Code[cf.Pc] cf.Pc++ @@ -1519,12 +2217,52 @@ func opArith(L *LState, inst uint32, baseframe *callFrame) int { //OP_ADD, OP_SU C := int(inst>>9) & 0x1ff //GETC lhs := L.rkValue(B) rhs := L.rkValue(C) - v1, ok1 := lhs.assertFloat64() - v2, ok2 := rhs.assertFloat64() + v1, ok1 := lhs.(LNumber) + v2, ok2 := rhs.(LNumber) if ok1 && ok2 { - reg.SetNumber(RA, numberArith(L, opcode, LNumber(v1), LNumber(v2))) + v := numberArith(L, opcode, LNumber(v1), LNumber(v2)) + // this section is inlined by go-inline + // source function is 'func (rg *registry) SetNumber(regi int, vali LNumber) ' in '_state.go' + { + rg := reg + regi := RA + vali := v + newSize := regi + 1 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + rg.array[regi] = rg.alloc.LNumber2I(vali) + if regi >= rg.top { + rg.top = regi + 1 + } + } } else { - reg.Set(RA, objectArith(L, opcode, lhs, rhs)) + v := objectArith(L, opcode, lhs, rhs) + // this section is inlined by go-inline + // source function is 'func (rg *registry) Set(regi int, vali LValue) ' in '_state.go' + { + rg := reg + regi := RA + vali := v + newSize := regi + 1 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + rg.array[regi] = vali + if regi >= rg.top { + rg.top = regi + 1 + } + } } return 0 } @@ -1577,7 +2315,7 @@ func objectArith(L *LState, opcode int, lhs, rhs LValue) LValue { event = "__pow" } op := L.metaOp2(lhs, rhs, event) - if op.Type() == LTFunction { + if _, ok := op.(*LFunction); ok { L.reg.Push(op) L.reg.Push(lhs) L.reg.Push(rhs) @@ -1594,8 +2332,8 @@ func objectArith(L *LState, opcode int, lhs, rhs LValue) LValue { rhs = rnum } } - if v1, ok1 := lhs.assertFloat64(); ok1 { - if v2, ok2 := rhs.assertFloat64(); ok2 { + if v1, ok1 := lhs.(LNumber); ok1 { + if v2, ok2 := rhs.(LNumber); ok2 { return numberArith(L, opcode, LNumber(v1), LNumber(v2)) } } @@ -1644,8 +2382,8 @@ func stringConcat(L *LState, total, last int) LValue { func lessThan(L *LState, lhs, rhs LValue) bool { // optimization for numbers - if v1, ok1 := lhs.assertFloat64(); ok1 { - if v2, ok2 := rhs.assertFloat64(); ok2 { + if v1, ok1 := lhs.(LNumber); ok1 { + if v2, ok2 := rhs.(LNumber); ok2 { return v1 < v2 } L.RaiseError("attempt to compare %v with %v", lhs.Type().String(), rhs.Type().String()) @@ -1665,17 +2403,18 @@ func lessThan(L *LState, lhs, rhs LValue) bool { } func equals(L *LState, lhs, rhs LValue, raw bool) bool { - if lhs.Type() != rhs.Type() { + lt := lhs.Type() + if lt != rhs.Type() { return false } ret := false - switch lhs.Type() { + switch lt { case LTNil: ret = true case LTNumber: - v1, _ := lhs.assertFloat64() - v2, _ := rhs.assertFloat64() + v1, _ := lhs.(LNumber) + v2, _ := rhs.(LNumber) ret = v1 == v2 case LTBool: ret = bool(lhs.(LBool)) == bool(rhs.(LBool))