From c9db13a0ffd61b31f659121a7c154942762e06c2 Mon Sep 17 00:00:00 2001 From: yuin Date: Tue, 3 Jan 2023 00:15:36 +0900 Subject: [PATCH 01/36] Fix #355 --- compile.go | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/compile.go b/compile.go index 75c75550..7e94c133 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 {{{ */ @@ -1468,7 +1469,15 @@ func compileLogicalOpExprAux(context *funcContext, reg int, expr ast.Expr, ec *e a := reg sreg := savereg(ec, a) - if !hasnextcond && thenlabel == elselabel { + + if ident, ok := expr.(*ast.IdentExpr); ok && getIdentRefType(context, context, ident) == ecLocal && ((elselabel == lb.e && thenlabel != elselabel) || (hasnextcond && thenlabel == lb.e)) { + b := context.FindLocalVar(ident.Value) + if sreg != b { + code.AddABC(OP_TESTSET, sreg, b, 0^flip, sline(expr)) + } else { + code.AddABC(OP_TEST, 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,11 +1487,7 @@ func compileLogicalOpExprAux(context *funcContext, reg int, expr ast.Expr, ec *e } } else { reg += compileExpr(context, reg, expr, ecnone(0)) - if sreg == a { - code.AddABC(OP_TEST, a, 0, 0^flip, sline(expr)) - } else { - code.AddABC(OP_TESTSET, sreg, a, 0^flip, sline(expr)) - } + code.AddABC(OP_TEST, a, 0, 0^flip, sline(expr)) } code.AddASbx(OP_JMP, 0, jumplabel, sline(expr)) } // }}} From 3f43d29aa0628fa37fc665c10141293299de3ad5 Mon Sep 17 00:00:00 2001 From: yuin Date: Tue, 3 Jan 2023 00:22:52 +0900 Subject: [PATCH 02/36] Fix #315 --- _glua-tests/issues.lua | 41 +++++++++++++++++++++++++++++++++++++++++ compile.go | 7 ++----- 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/_glua-tests/issues.lua b/_glua-tests/issues.lua index a4b830b6..274530c7 100644 --- a/_glua-tests/issues.lua +++ b/_glua-tests/issues.lua @@ -404,3 +404,44 @@ 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() diff --git a/compile.go b/compile.go index 7e94c133..a4d128df 100644 --- a/compile.go +++ b/compile.go @@ -526,8 +526,7 @@ func compileStmt(context *funcContext, stmt ast.Stmt) { // {{{ 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) @@ -538,9 +537,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: From 6db614b2569e67fd6737021fa2f258be9fea6835 Mon Sep 17 00:00:00 2001 From: yuin Date: Tue, 3 Jan 2023 00:42:53 +0900 Subject: [PATCH 03/36] Migrate to Github Actions --- .travis.yml | 18 ------------------ Makefile | 2 +- README.rst | 12 ++++++------ 3 files changed, 7 insertions(+), 25 deletions(-) delete mode 100644 .travis.yml 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/Makefile b/Makefile index 6d9e55c3..41e91fb9 100644 --- a/Makefile +++ b/Makefile @@ -7,4 +7,4 @@ glua: *.go pm/*.go cmd/glua/glua.go ./_tools/go-inline *.go && go fmt . && go build cmd/glua/glua.go test: - ./_tools/go-inline *.go && go fmt . && go test + ./_tools/go-inline *.go && go fmt . && go test -v ./... -covermode=count -coverprofile=coverage.out -coverpkg=./... diff --git a/README.rst b/README.rst index 2b6de325..201f335a 100644 --- a/README.rst +++ b/README.rst @@ -3,14 +3,14 @@ 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://pkg.go.dev/badge/github.com/yuin/gopher-lua.svg + :target: https://pkg.go.dev/github.com/yuin/gopher-lua -.. image:: https://travis-ci.org/yuin/gopher-lua.svg - :target: https://travis-ci.org/yuin/gopher-lua +.. image:: https://github.com/yuin/gopher-lua/workflows/test/badge.svg?branch=master&event=push + :target: https://github.com/yuin/gopher-lua/actions?query=workflow:test -.. image:: https://coveralls.io/repos/yuin/gopher-lua/badge.svg - :target: https://coveralls.io/r/yuin/gopher-lua +.. image:: https://coveralls.io/repos/github/yuin/gopher-lua/badge.svg?branch=master + :target: https://coveralls.io/github/yuin/gopher-lua .. image:: https://badges.gitter.im/Join%20Chat.svg :alt: Join the chat at https://gitter.im/yuin/gopher-lua From 2d9b0c13ea688070e77fc127dbafaf0e514bfb2c Mon Sep 17 00:00:00 2001 From: yuin Date: Tue, 3 Jan 2023 00:43:45 +0900 Subject: [PATCH 04/36] Migrate to Github Actions --- .github/workflows/test.yaml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 .github/workflows/test.yaml diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 00000000..08bd9a88 --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,26 @@ +on: [push, pull_request] +name: test +jobs: + test: + strategy: + fail-fast: false + matrix: + go-version: [1.18.x, 1.19.x] + platform: [ubuntu-latest, macos-latest, windows-latest] + runs-on: ${{ matrix.platform }} + steps: + - name: Install Go + uses: actions/setup-go@v1 + with: + go-version: ${{ matrix.go-version }} + - name: Checkout code + uses: actions/checkout@v1 + - name: Run tests + run: make test + - name: Send coverage + if: "matrix.platform == 'ubuntu-latest'" + env: + COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + GO111MODULE=off go get github.com/mattn/goveralls + $(go env GOPATH)/bin/goveralls -coverprofile=coverage.out -service=github From 7f64c101a493d40925174e2f2e36c78db5684f82 Mon Sep 17 00:00:00 2001 From: yuin Date: Tue, 3 Jan 2023 00:47:17 +0900 Subject: [PATCH 05/36] Migrate to Github Actions --- .github/workflows/test.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 08bd9a88..8e904684 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -23,4 +23,5 @@ jobs: COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | GO111MODULE=off go get github.com/mattn/goveralls + ./_tools/go-inline *.go && go fmt . $(go env GOPATH)/bin/goveralls -coverprofile=coverage.out -service=github From b4ade3d40ceb41dcd97330849b3f6b6f190eaeeb Mon Sep 17 00:00:00 2001 From: yuin Date: Tue, 3 Jan 2023 00:52:12 +0900 Subject: [PATCH 06/36] Migrate to Github Actions --- .github/workflows/test.yaml | 2 +- Makefile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 8e904684..eed3390c 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -16,7 +16,7 @@ jobs: - name: Checkout code uses: actions/checkout@v1 - name: Run tests - run: make test + run: ./_tools/go-inline *.go && go fmt . && go test -v ./... -covermode=count -coverprofile=coverage.out -coverpkg=$(go list ./... | sed 's/\n/,/g') - name: Send coverage if: "matrix.platform == 'ubuntu-latest'" env: diff --git a/Makefile b/Makefile index 41e91fb9..6d9e55c3 100644 --- a/Makefile +++ b/Makefile @@ -7,4 +7,4 @@ glua: *.go pm/*.go cmd/glua/glua.go ./_tools/go-inline *.go && go fmt . && go build cmd/glua/glua.go test: - ./_tools/go-inline *.go && go fmt . && go test -v ./... -covermode=count -coverprofile=coverage.out -coverpkg=./... + ./_tools/go-inline *.go && go fmt . && go test From 804e438d3ce89d0d58604185e219365b1f102c15 Mon Sep 17 00:00:00 2001 From: yuin Date: Thu, 5 Jan 2023 06:32:42 +0900 Subject: [PATCH 07/36] Add 'goto' statement --- README.rst | 4 +- _glua-tests/goto.lua | 173 ++++++++++++ ast/stmt.go | 12 + compile.go | 267 ++++++++++++++++--- parse/lexer.go | 16 +- parse/parser.go | 615 ++++++++++++++++++++++--------------------- parse/parser.go.y | 12 +- script_test.go | 4 +- 8 files changed, 761 insertions(+), 342 deletions(-) create mode 100644 _glua-tests/goto.lua diff --git a/README.rst b/README.rst index 201f335a..27edf29a 100644 --- a/README.rst +++ b/README.rst @@ -19,7 +19,7 @@ GopherLua: VM and compiler for Lua in Go. | -GopherLua is a Lua5.1 VM and compiler written in Go. GopherLua has a same goal +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. @@ -830,6 +830,8 @@ Miscellaneous notes - ``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 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/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/compile.go b/compile.go index a4d128df..90863095 100644 --- a/compile.go +++ b/compile.go @@ -95,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 { @@ -135,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 @@ -329,16 +355,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() @@ -346,33 +374,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++ @@ -401,6 +532,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) @@ -432,7 +570,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) } @@ -448,6 +586,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 @@ -472,9 +614,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) } } // }}} @@ -486,13 +636,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) @@ -502,7 +660,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) @@ -520,6 +678,10 @@ 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) } } // }}} @@ -823,7 +985,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() @@ -838,7 +1000,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()) @@ -914,7 +1076,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() @@ -947,7 +1109,7 @@ func compileGenericForStmt(context *funcContext, stmt *ast.GenericForStmt) { // } context.SetLabelPc(bodylabel, code.LastPC()) - compileChunk(context, stmt.Stmts) + compileChunk(context, stmt.Stmts, false) context.LeaveBlock() @@ -958,6 +1120,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) @@ -1147,10 +1327,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() @@ -1466,8 +1647,10 @@ func compileLogicalOpExprAux(context *funcContext, reg int, expr ast.Expr, ec *e a := reg sreg := savereg(ec, a) + isLastAnd := elselabel == lb.e && thenlabel != elselabel + isLastOr := thenlabel == lb.e && hasnextcond - if ident, ok := expr.(*ast.IdentExpr); ok && getIdentRefType(context, context, ident) == ecLocal && ((elselabel == lb.e && thenlabel != elselabel) || (hasnextcond && thenlabel == lb.e)) { + if ident, ok := expr.(*ast.IdentExpr); ok && getIdentRefType(context, context, ident) == ecLocal && (isLastAnd || isLastOr) { b := context.FindLocalVar(ident.Value) if sreg != b { code.AddABC(OP_TESTSET, sreg, b, 0^flip, sline(expr)) @@ -1671,6 +1854,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/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/script_test.go b/script_test.go index 4a0abe0e..3c0675d6 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{ From 80c50bc2cdff5f748572494227d278743b310c69 Mon Sep 17 00:00:00 2001 From: Yusuke Inuzuka Date: Fri, 6 Jan 2023 06:28:40 +0900 Subject: [PATCH 08/36] Update README.rst --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 27edf29a..5d50d948 100644 --- a/README.rst +++ b/README.rst @@ -830,7 +830,7 @@ Miscellaneous notes - ``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. +- GopherLua support ``goto`` and ``::label::`` statement in Lua5.2. - `goto` is a keyword and not a valid variable name. ---------------------------------------------------------------- From 1a665bc0c54a2330fa48f8170b0d0df7c3a4e568 Mon Sep 17 00:00:00 2001 From: Yusuke Inuzuka Date: Fri, 6 Jan 2023 06:29:27 +0900 Subject: [PATCH 09/36] Update README.rst --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 5d50d948..cc936311 100644 --- a/README.rst +++ b/README.rst @@ -831,7 +831,7 @@ Miscellaneous notes - 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. + - `goto` is a keyword and not a valid variable name. ---------------------------------------------------------------- Standalone interpreter From fa815b5cd712a146016c373261cda69942ec74bb Mon Sep 17 00:00:00 2001 From: yuin Date: Sun, 22 Jan 2023 22:28:43 +0900 Subject: [PATCH 10/36] Fix #423 --- _glua-tests/issues.lua | 12 ++++++++++++ compile.go | 16 ++++++++++------ 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/_glua-tests/issues.lua b/_glua-tests/issues.lua index 274530c7..514d0b7a 100644 --- a/_glua-tests/issues.lua +++ b/_glua-tests/issues.lua @@ -445,3 +445,15 @@ function test() 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 diff --git a/compile.go b/compile.go index 90863095..c3736777 100644 --- a/compile.go +++ b/compile.go @@ -1650,13 +1650,13 @@ func compileLogicalOpExprAux(context *funcContext, reg int, expr ast.Expr, ec *e isLastAnd := elselabel == lb.e && thenlabel != elselabel isLastOr := thenlabel == lb.e && hasnextcond - if ident, ok := expr.(*ast.IdentExpr); ok && getIdentRefType(context, context, ident) == ecLocal && (isLastAnd || isLastOr) { + if ident, ok := expr.(*ast.IdentExpr); ok && (isLastAnd || isLastOr) && getIdentRefType(context, context, ident) == ecLocal { b := context.FindLocalVar(ident.Value) - if sreg != b { - code.AddABC(OP_TESTSET, sreg, b, 0^flip, sline(expr)) - } else { - code.AddABC(OP_TEST, sreg, b, 0^flip, sline(expr)) + 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() @@ -1667,7 +1667,11 @@ func compileLogicalOpExprAux(context *funcContext, reg int, expr ast.Expr, ec *e } } else { reg += compileExpr(context, reg, expr, ecnone(0)) - code.AddABC(OP_TEST, a, 0, 0^flip, sline(expr)) + if !hasnextcond { + code.AddABC(OP_TEST, a, 0, 0^flip, sline(expr)) + } else { + code.AddABC(OP_TESTSET, sreg, a, 0^flip, sline(expr)) + } } code.AddASbx(OP_JMP, 0, jumplabel, sline(expr)) } // }}} From 410e9bd702235bd94efc96d3f99cfbbb50b83f12 Mon Sep 17 00:00:00 2001 From: Mehmet Dogan Date: Fri, 10 Sep 2021 13:58:41 +0300 Subject: [PATCH 11/36] Convert math params to number instead of failing with error --- .gitignore | 1 + auxlib.go | 5 +++++ auxlib_test.go | 4 +++- 3 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..485dee64 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.idea diff --git a/auxlib.go b/auxlib.go index 61a3b8b6..603fdafe 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 } diff --git a/auxlib_test.go b/auxlib_test.go index 2218db67..a2de37cc 100644 --- a/auxlib_test.go +++ b/auxlib_test.go @@ -36,8 +36,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") } From 71163b697a8f07f32b7f6d2898726b198f25ba20 Mon Sep 17 00:00:00 2001 From: Sebastian Waschik Date: Sun, 19 Feb 2023 11:39:05 +0100 Subject: [PATCH 12/36] Use O_TRUNC for files opened with "w" --- iolib.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iolib.go b/iolib.go index 4a86f893..b59e82b2 100644 --- a/iolib.go +++ b/iolib.go @@ -629,7 +629,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 From 34670637ab84e5df640d4d0295c6e94d4173dd6a Mon Sep 17 00:00:00 2001 From: Sebastian Waschik Date: Sun, 19 Feb 2023 16:28:07 +0100 Subject: [PATCH 13/36] Add test for file writing --- oslib_test.go | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 oslib_test.go 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) + } +} From 2a0d34edb3dcbff96a6dffb7fee14a324a75b42d Mon Sep 17 00:00:00 2001 From: ttyS3 Date: Wed, 12 Apr 2023 19:23:41 +0800 Subject: [PATCH 14/36] fix: fix os.date("!*t", os.time()) will not get UTC date time issue --- baselib_test.go | 141 ++++++++++++++++++++++++++++++++++++++++++++++++ oslib.go | 8 ++- 2 files changed, 147 insertions(+), 2 deletions(-) create mode 100644 baselib_test.go 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/oslib.go b/oslib.go index 256c8811..5448cc1f 100644 --- a/oslib.go +++ b/oslib.go @@ -23,7 +23,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 +106,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())) From 63a49a48b75ea97f7fbfaea77db3ae8c99e63193 Mon Sep 17 00:00:00 2001 From: deflinhec Date: Fri, 19 May 2023 01:00:48 +0000 Subject: [PATCH 15/36] Fix lua coroutine context leak --- _state.go | 4 ++++ state.go | 4 ++++ value.go | 1 + 3 files changed, 9 insertions(+) diff --git a/_state.go b/_state.go index 960e8810..645589fe 100644 --- a/_state.go +++ b/_state.go @@ -769,6 +769,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 +1405,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 } diff --git a/state.go b/state.go index a1ee672e..50e70ed6 100644 --- a/state.go +++ b/state.go @@ -815,6 +815,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 +1564,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 } diff --git a/value.go b/value.go index 0d4af808..94bec482 100644 --- a/value.go +++ b/value.go @@ -218,6 +218,7 @@ 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) } From e61dab48becc7a561b395e5de8e31c15be7093be Mon Sep 17 00:00:00 2001 From: ttyS3 Date: Tue, 30 May 2023 14:38:05 +0800 Subject: [PATCH 16/36] test: rollback to use env default TZ variable --- baselib_test.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/baselib_test.go b/baselib_test.go index ba773343..7c78ed9e 100644 --- a/baselib_test.go +++ b/baselib_test.go @@ -7,7 +7,6 @@ import ( ) func TestOsDateFormatUTCWithTwoParam(t *testing.T) { - t.Setenv("TZ", "Asia/Tokyo") ls := NewState() g := ls.GetGlobal("os") @@ -82,7 +81,6 @@ func TestOsDateFormatUTCWithTwoParam(t *testing.T) { } func TestOsDateFormatLocalWithTwoParam(t *testing.T) { - t.Setenv("TZ", "Asia/Tokyo") ls := NewState() g := ls.GetGlobal("os") From d21032d8ca695dd25797161a7e31c8cff933650a Mon Sep 17 00:00:00 2001 From: ttyS3 Date: Tue, 30 May 2023 20:43:17 +0800 Subject: [PATCH 17/36] Revert "test: rollback to use env default TZ variable" This reverts commit e61dab48becc7a561b395e5de8e31c15be7093be. --- baselib_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/baselib_test.go b/baselib_test.go index 7c78ed9e..ba773343 100644 --- a/baselib_test.go +++ b/baselib_test.go @@ -7,6 +7,7 @@ import ( ) func TestOsDateFormatUTCWithTwoParam(t *testing.T) { + t.Setenv("TZ", "Asia/Tokyo") ls := NewState() g := ls.GetGlobal("os") @@ -81,6 +82,7 @@ func TestOsDateFormatUTCWithTwoParam(t *testing.T) { } func TestOsDateFormatLocalWithTwoParam(t *testing.T) { + t.Setenv("TZ", "Asia/Tokyo") ls := NewState() g := ls.GetGlobal("os") From 729b5dbf31b26e5b5705b43620e9ec0beb95da9e Mon Sep 17 00:00:00 2001 From: mzki <28139969+mzki@users.noreply.github.com> Date: Sat, 12 Aug 2023 08:14:21 +0000 Subject: [PATCH 18/36] Issue #452 : fix xpcall with error in error handler returns (nil, nil) --- _glua-tests/issues.lua | 11 +++++++++++ _state.go | 3 +++ state.go | 3 +++ state_test.go | 22 ++++++++++++++++++++++ 4 files changed, 39 insertions(+) diff --git a/_glua-tests/issues.lua b/_glua-tests/issues.lua index 514d0b7a..7abcd936 100644 --- a/_glua-tests/issues.lua +++ b/_glua-tests/issues.lua @@ -457,3 +457,14 @@ function test() assert(c == 1) assert(type(c) == "number") end + +-- 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() \ No newline at end of file diff --git a/_state.go b/_state.go index 645589fe..df169259 100644 --- a/_state.go +++ b/_state.go @@ -1855,6 +1855,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.go b/state.go index 50e70ed6..8e0acbb3 100644 --- a/state.go +++ b/state.go @@ -2014,6 +2014,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) { From 6543bc9e02b9557b5638b11b548a2d32b60dcc66 Mon Sep 17 00:00:00 2001 From: mzki <28139969+mzki@users.noreply.github.com> Date: Sat, 12 Aug 2023 08:17:23 +0000 Subject: [PATCH 19/36] add missing call of test() for #423 --- _glua-tests/issues.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/_glua-tests/issues.lua b/_glua-tests/issues.lua index 7abcd936..0bb77bcd 100644 --- a/_glua-tests/issues.lua +++ b/_glua-tests/issues.lua @@ -457,6 +457,7 @@ function test() assert(c == 1) assert(type(c) == "number") end +test() -- issue #452 function test() From 1543e9dd7f91a69a8aaa390a6e54172e73d472de Mon Sep 17 00:00:00 2001 From: Pavel Balaev Date: Mon, 9 Oct 2023 16:29:57 +0300 Subject: [PATCH 20/36] popen: support nil in second argument In the C implementation of the Lua interpreter, in the io.popen function the second argument can be nil: Lua 5.1.5 Copyright (C) 1994-2012 Lua.org, PUC-Rio: > x,y = io.popen("ls", nil) > assert(x) > assert(y == nil) > Gopher lua throws an exception: bad argument #2 to popen (string expected, got nil) Closes #459 --- _glua-tests/issues.lua | 11 +++++++++++ iolib.go | 3 +++ 2 files changed, 14 insertions(+) diff --git a/_glua-tests/issues.lua b/_glua-tests/issues.lua index 514d0b7a..aafb5d7c 100644 --- a/_glua-tests/issues.lua +++ b/_glua-tests/issues.lua @@ -457,3 +457,14 @@ function test() assert(c == 1) assert(type(c) == "number") end + +-- 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/iolib.go b/iolib.go index b59e82b2..3f5f295c 100644 --- a/iolib.go +++ b/iolib.go @@ -658,6 +658,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 From 1e3d646a9a84be71af3031c9a3d6ff5b3fa15d28 Mon Sep 17 00:00:00 2001 From: yuin Date: Sat, 2 Dec 2023 19:19:32 +0900 Subject: [PATCH 21/36] Inlining regstry.Set* regstry.Set* were very simple functions at the beginning of the project, but recently not. It seems inlining these functions make improvements better. --- _state.go | 47 ++-- _vm.go | 100 ++++--- state.go | 101 +++++-- vm.go | 829 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 4 files changed, 945 insertions(+), 132 deletions(-) diff --git a/_state.go b/_state.go index df169259..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) diff --git a/_vm.go b/_vm.go index 687fe797..bf69ae5b 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 @@ -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++ } @@ -677,13 +689,14 @@ func init() { if limit, ok2 := reg.Get(RA + 1).assertFloat64(); ok2 { if step, ok3 := reg.Get(RA + 2).assertFloat64(); 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") @@ -705,7 +718,7 @@ func init() { 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)) + // +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++ @@ -829,9 +842,11 @@ func opArith(L *LState, inst uint32, baseframe *callFrame) int { //OP_ADD, OP_SU v1, ok1 := lhs.assertFloat64() v2, ok2 := rhs.assertFloat64() 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,19 +899,19 @@ 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.assertFunction(); ok { L.reg.Push(op) L.reg.Push(lhs) L.reg.Push(rhs) L.Call(2, 1) return L.reg.Pop() } - if str, ok := lhs.(LString); ok { + if str, ok := lhs.assertString(); ok { if lnum, err := parseNumber(string(str)); err == nil { lhs = lnum } } - if str, ok := rhs.(LString); ok { + if str, ok := rhs.assertString(); ok { if rnum, err := parseNumber(string(str)); err == nil { rhs = rnum } @@ -972,12 +987,13 @@ 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: diff --git a/state.go b/state.go index 8e0acbb3..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) diff --git a/vm.go b/vm.go index 470855f2..4159051b 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 @@ -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++ } @@ -1312,13 +1799,81 @@ func init() { if limit, ok2 := reg.Get(RA + 1).assertFloat64(); ok2 { if step, ok3 := reg.Get(RA + 2).assertFloat64(); 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") @@ -1340,7 +1895,26 @@ func init() { 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)) + // 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,54 @@ 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.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,19 +2317,19 @@ 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.assertFunction(); ok { L.reg.Push(op) L.reg.Push(lhs) L.reg.Push(rhs) L.Call(2, 1) return L.reg.Pop() } - if str, ok := lhs.(LString); ok { + if str, ok := lhs.assertString(); ok { if lnum, err := parseNumber(string(str)); err == nil { lhs = lnum } } - if str, ok := rhs.(LString); ok { + if str, ok := rhs.assertString(); ok { if rnum, err := parseNumber(string(str)); err == nil { rhs = rnum } @@ -1665,12 +2405,13 @@ 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: From 1388221efeb4a239a053e5932c3d755699055684 Mon Sep 17 00:00:00 2001 From: yuin Date: Sat, 2 Dec 2023 19:27:43 +0900 Subject: [PATCH 22/36] Remove LValue.assert* functions. GopherLua is a relatively old project. Old Go runtime had slow interface type assertions, but now Go runtime better its performance. --- _vm.go | 40 ++++++++++++++++----------------- auxlib.go | 2 +- debuglib.go | 4 ++-- value.go | 65 +++++++++++++---------------------------------------- vm.go | 38 +++++++++++++++---------------- 5 files changed, 57 insertions(+), 92 deletions(-) diff --git a/_vm.go b/_vm.go index bf69ae5b..ee2be040 100644 --- a/_vm.go +++ b/_vm.go @@ -495,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()) @@ -572,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 { @@ -598,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 { @@ -685,9 +685,9 @@ 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 v := LNumber(init) // +inline-call reg.SetNumber RA v @@ -716,8 +716,8 @@ 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 { + 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") @@ -839,8 +839,8 @@ 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 { v := numberArith(L, opcode, LNumber(v1), LNumber(v2)) // +inline-call reg.SetNumber RA v @@ -899,25 +899,25 @@ func objectArith(L *LState, opcode int, lhs, rhs LValue) LValue { event = "__pow" } op := L.metaOp2(lhs, rhs, event) - if _, ok := op.assertFunction(); ok { + if _, ok := op.(*LFunction); ok { L.reg.Push(op) L.reg.Push(lhs) L.reg.Push(rhs) L.Call(2, 1) return L.reg.Pop() } - if str, ok := lhs.assertString(); ok { + if str, ok := lhs.(LString); ok { if lnum, err := parseNumber(string(str)); err == nil { lhs = lnum } } - if str, ok := rhs.assertString(); ok { + if str, ok := rhs.(LString); ok { if rnum, err := parseNumber(string(str)); err == nil { 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)) } } @@ -966,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()) @@ -997,8 +997,8 @@ func equals(L *LState, lhs, rhs LValue, raw bool) bool { 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/auxlib.go b/auxlib.go index 603fdafe..a022bdd8 100644 --- a/auxlib.go +++ b/auxlib.go @@ -418,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/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/value.go b/value.go index 94bec482..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 @@ -221,11 +197,8 @@ type LState struct { 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{} @@ -233,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 4159051b..97335a75 100644 --- a/vm.go +++ b/vm.go @@ -1085,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()) @@ -1181,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 { @@ -1324,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 { @@ -1795,9 +1795,9 @@ 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 v := LNumber(init) // this section is inlined by go-inline @@ -1893,8 +1893,8 @@ 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 { + 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' { @@ -2217,8 +2217,6 @@ 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 { @@ -2317,25 +2315,25 @@ func objectArith(L *LState, opcode int, lhs, rhs LValue) LValue { event = "__pow" } op := L.metaOp2(lhs, rhs, event) - if _, ok := op.assertFunction(); ok { + if _, ok := op.(*LFunction); ok { L.reg.Push(op) L.reg.Push(lhs) L.reg.Push(rhs) L.Call(2, 1) return L.reg.Pop() } - if str, ok := lhs.assertString(); ok { + if str, ok := lhs.(LString); ok { if lnum, err := parseNumber(string(str)); err == nil { lhs = lnum } } - if str, ok := rhs.assertString(); ok { + if str, ok := rhs.(LString); ok { if rnum, err := parseNumber(string(str)); err == nil { 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)) } } @@ -2384,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()) @@ -2415,8 +2413,8 @@ func equals(L *LState, lhs, rhs LValue, raw bool) bool { 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)) From e1b215fe0d309a15f1e4ab071ccddb4aad3e46b6 Mon Sep 17 00:00:00 2001 From: David Rauschenbach Date: Sun, 3 Dec 2023 11:57:27 -0800 Subject: [PATCH 23/36] Re-add link to relocated gluasocket library; remove broken link to gluaperiphery library --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index cc936311..1ac57dca 100644 --- a/README.rst +++ b/README.rst @@ -869,7 +869,7 @@ Libraries for GopherLua - `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). +- `gluasocket `_ : A native Go implementation of LuaSocket for the GopherLua VM. - `glua-async `_ : An async/await implement for gopher-lua. - `gopherlua-debugger `_ : A debugger for gopher-lua - `gluamahonia `_ : An encoding converter for gopher-lua From 5b5b27fca523a503041a723ed5543cb098a9d25b Mon Sep 17 00:00:00 2001 From: Pavel Balaev Date: Thu, 28 Sep 2023 13:03:14 +0300 Subject: [PATCH 24/36] iolib: fix different behavior in read function $ lua Lua 5.1.5 Copyright (C) 1994-2012 Lua.org, PUC-Rio > file = "/tmp" > fd, _, code = io.open(file, "r") > _, _, ecode = fd:read(1) > print(ecode) 21 > gopher-lua throws an exception: read /tmp: is a directory stack traceback: [G]: in function 'read' extra/wrapper.lua:17: in function 'exec' :1: in main chunk [G]: ? This patch results in behavior similar to the vanilla lua implementation. Closes #455 --- _glua-tests/issues.lua | 10 ++++++++++ iolib.go | 8 ++++---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/_glua-tests/issues.lua b/_glua-tests/issues.lua index 65a2c7f4..6d6343ab 100644 --- a/_glua-tests/issues.lua +++ b/_glua-tests/issues.lua @@ -470,6 +470,16 @@ function test() 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) diff --git a/iolib.go b/iolib.go index 3f5f295c..781b18ff 100644 --- a/iolib.go +++ b/iolib.go @@ -404,10 +404,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"} From 182b189c9b113d09aafe0dfb4a9056e753cfbd7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philip=20Dub=C3=A9?= Date: Tue, 19 Mar 2024 05:40:52 +0000 Subject: [PATCH 25/36] Add github.com/PeerDB-io/gluabit32 to README.rst https://github.com/BixData/gluabit32 is a 404 now, so implement a fresh bit32 for gopher-lua --- README.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 1ac57dca..65b71c98 100644 --- a/README.rst +++ b/README.rst @@ -847,7 +847,7 @@ Lua has an interpreter called ``lua`` . GopherLua has an interpreter called ``gl ---------------------------------------------------------------- How to Contribute ---------------------------------------------------------------- -See `Guidlines for contributors `_ . +See `Guidelines for contributors `_ . ---------------------------------------------------------------- Libraries for GopherLua @@ -865,6 +865,7 @@ Libraries for GopherLua - `gluaxmlpath `_ : An xmlpath module for gopher-lua - `gmoonscript `_ : Moonscript Compiler for the Gopher Lua VM - `loguago `_ : Zerolog wrapper for Gopher-Lua +- `gluabit32 `_ : [Port of Lua 5.2 bit32](https://www.lua.org/manual/5.2/manual.html#6.7) - `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. From ed6d26a0fa5668b725aebbdbd0b01dc61e7de4c3 Mon Sep 17 00:00:00 2001 From: Root lee Date: Tue, 30 Apr 2024 16:52:42 +0800 Subject: [PATCH 26/36] Update README.rst fix README format error --- README.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/README.rst b/README.rst index 1ac57dca..3534ce6a 100644 --- a/README.rst +++ b/README.rst @@ -873,6 +873,7 @@ Libraries for GopherLua - `glua-async `_ : An async/await implement for gopher-lua. - `gopherlua-debugger `_ : A debugger for gopher-lua - `gluamahonia `_ : An encoding converter for gopher-lua + ---------------------------------------------------------------- Donation ---------------------------------------------------------------- From c5204ed6ac00279397923710aee7c8a1244d6902 Mon Sep 17 00:00:00 2001 From: Root-lee Date: Sat, 4 May 2024 01:10:29 +0800 Subject: [PATCH 27/36] doc: add awesome-gopher-lua --- README.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/README.rst b/README.rst index 3534ce6a..e42770b3 100644 --- a/README.rst +++ b/README.rst @@ -873,6 +873,7 @@ Libraries for GopherLua - `glua-async `_ : An async/await implement for gopher-lua. - `gopherlua-debugger `_ : A debugger for gopher-lua - `gluamahonia `_ : An encoding converter for gopher-lua +- `awesome-gopher-lua `_ : Collections of awesome libraries for GopherLua. ---------------------------------------------------------------- Donation From b72019000c0c0487ce6ce6868a17dc3eb90eb8ca Mon Sep 17 00:00:00 2001 From: Mark Tully Date: Fri, 26 Jul 2024 22:16:53 +0100 Subject: [PATCH 28/36] fix compiler producing incorrect LOADNIL byte code --- compile.go | 2 +- script_test.go | 84 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 1 deletion(-) diff --git a/compile.go b/compile.go index c3736777..e46e0d75 100644 --- a/compile.go +++ b/compile.go @@ -237,7 +237,7 @@ 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 { + if opGetOpCode(last) == OP_LOADNIL && (opGetArgB(last)+1) == a { cd.SetB(cd.LastPC(), b) } else { cd.AddABC(OP_LOADNIL, a, b, 0, line) diff --git a/script_test.go b/script_test.go index 3c0675d6..48f48b3c 100644 --- a/script_test.go +++ b/script_test.go @@ -4,6 +4,7 @@ import ( "fmt" "os" "runtime" + "strings" "sync/atomic" "testing" "time" @@ -146,3 +147,86 @@ func TestGlua(t *testing.T) { func TestLua(t *testing.T) { testScriptDir(t, luaTests, "_lua5.1-tests") } + +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) + } +} + +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) + } +} From ec734472f272ee64f275c2d31cddd092c209edba Mon Sep 17 00:00:00 2001 From: warjiang <1096409085@qq.com> Date: Tue, 30 Jul 2024 16:28:11 +0800 Subject: [PATCH 29/36] fix: typo error for CompileLua example --- README.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 1ac57dca..00c0a016 100644 --- a/README.rst +++ b/README.rst @@ -595,7 +595,10 @@ Sharing byte code is safe as it is read only and cannot be altered by lua script // Example shows how to share the compiled byte code from a lua script between multiple VMs. func Example() { - codeToShare := CompileLua("mylua.lua") + codeToShare, err := CompileLua("mylua.lua") + if err != nil { + panic(err) + } a := lua.NewState() b := lua.NewState() c := lua.NewState() From b5e3b3fc3c4adceb7bdf93cdb7e1bcc2ddd829b8 Mon Sep 17 00:00:00 2001 From: Carlos Cobo <699969+toqueteos@users.noreply.github.com> Date: Thu, 1 Aug 2024 18:51:28 +0200 Subject: [PATCH 30/36] Remove io/ioutil usages (deprecated since Go 1.16) --- auxlib_test.go | 7 +++---- iolib.go | 5 ++--- oslib.go | 3 +-- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/auxlib_test.go b/auxlib_test.go index a2de37cc..240435e2 100644 --- a/auxlib_test.go +++ b/auxlib_test.go @@ -1,7 +1,6 @@ package lua import ( - "io/ioutil" "os" "testing" ) @@ -300,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) @@ -321,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/iolib.go b/iolib.go index 781b18ff..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 @@ -704,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 5448cc1f..b8ebaea5 100644 --- a/oslib.go +++ b/oslib.go @@ -1,7 +1,6 @@ package lua import ( - "io/ioutil" "os" "strings" "time" @@ -223,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") } From b3dcd3f01f1dcf52d0303e9431bbbd51aa5772b6 Mon Sep 17 00:00:00 2001 From: Mark Tully Date: Fri, 10 Jan 2025 12:05:26 +0000 Subject: [PATCH 31/36] disable LOADNIL code generation optimisation The `AddLoadNil` 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. --- compile.go | 13 +++--- script_test.go | 120 ++++++++++++++++++++++++++++++------------------- 2 files changed, 81 insertions(+), 52 deletions(-) diff --git a/compile.go b/compile.go index e46e0d75..f9fbf576 100644 --- a/compile.go +++ b/compile.go @@ -236,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 && (opGetArgB(last)+1) == 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) { diff --git a/script_test.go b/script_test.go index 48f48b3c..3358b362 100644 --- a/script_test.go +++ b/script_test.go @@ -4,7 +4,6 @@ import ( "fmt" "os" "runtime" - "strings" "sync/atomic" "testing" "time" @@ -148,6 +147,33 @@ 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 := ` @@ -185,48 +211,50 @@ func TestMergingLoadNilBug(t *testing.T) { } } -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) - } -} +// 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) +// } +//} From 4911e3986a774e4f2f081a9ed8f2a8359c6135c1 Mon Sep 17 00:00:00 2001 From: Serhii Mudryk Date: Wed, 19 Feb 2025 14:47:48 +0200 Subject: [PATCH 32/36] PS-3239: error on too complex regex pattern (#1) --- pm/pm.go | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/pm/pm.go b/pm/pm.go index e5c651f9..4b15d958 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 = 100000 +) /* 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 { From 66d0cb780329aa016dfda5c6924d238ff0630482 Mon Sep 17 00:00:00 2001 From: yuin Date: Wed, 1 Apr 2026 09:43:51 +0900 Subject: [PATCH 33/36] chore: bump go version --- .github/workflows/test.yaml | 6 +- _tools/go-inline | 150 ++++++++++++++++++++---------------- go.mod | 2 +- pm/pm.go | 2 +- 4 files changed, 88 insertions(+), 72 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index eed3390c..7f855ad8 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -5,16 +5,16 @@ jobs: strategy: fail-fast: false matrix: - go-version: [1.18.x, 1.19.x] + 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@v1 + uses: actions/setup-go@v4 with: go-version: ${{ matrix.go-version }} - name: Checkout code - uses: actions/checkout@v1 + 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: Send coverage 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/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/pm/pm.go b/pm/pm.go index 4b15d958..9abf57ec 100644 --- a/pm/pm.go +++ b/pm/pm.go @@ -8,7 +8,7 @@ import ( const ( EOS = -1 _UNKNOWN = -2 - maxRecursionLevel = 100000 + maxRecursionLevel = 1000000 ) /* Error {{{ */ From b59f85c5fd5d1d5c182b0e3ab0d7975784abe3fa Mon Sep 17 00:00:00 2001 From: yuin Date: Wed, 1 Apr 2026 09:44:16 +0900 Subject: [PATCH 34/36] docs: convert README from rst to md --- README.md | 833 +++++++++++++++++++++++++++++++++++++++++++++++++ README.rst | 896 ----------------------------------------------------- 2 files changed, 833 insertions(+), 896 deletions(-) create mode 100644 README.md delete mode 100644 README.rst diff --git a/README.md b/README.md new file mode 100644 index 00000000..c51026cd --- /dev/null +++ b/README.md @@ -0,0 +1,833 @@ +# 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 +``` + +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. + +```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 618bc31c..00000000 --- a/README.rst +++ /dev/null @@ -1,896 +0,0 @@ - -=============================================================================== -GopherLua: VM and compiler for Lua in Go. -=============================================================================== - -.. image:: https://pkg.go.dev/badge/github.com/yuin/gopher-lua.svg - :target: https://pkg.go.dev/github.com/yuin/gopher-lua - -.. image:: https://github.com/yuin/gopher-lua/workflows/test/badge.svg?branch=master&event=push - :target: https://github.com/yuin/gopher-lua/actions?query=workflow:test - -.. image:: https://coveralls.io/repos/github/yuin/gopher-lua/badge.svg?branch=master - :target: https://coveralls.io/github/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(+ `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. - -.. 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, 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. - - - -.. 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)`` -- 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`` . - -.. code-block:: bash - - go get github.com/yuin/gopher-lua/cmd/glua - -``glua`` has same options as ``lua`` . - ----------------------------------------------------------------- -How to Contribute ----------------------------------------------------------------- -See `Guidelines 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 -- `gluabit32 `_ : [Port of Lua 5.2 bit32](https://www.lua.org/manual/5.2/manual.html#6.7) -- `gluacrypto `_ : A native Go implementation of crypto library for the GopherLua VM. -- `gluasql `_ : A native Go implementation of SQL client for the GopherLua VM. -- `purr `_ : A http mock testing tool. -- `vadv/gopher-lua-libs `_ : Some usefull libraries for GopherLua VM. -- `gluasocket `_ : A native Go implementation of LuaSocket for the GopherLua VM. -- `glua-async `_ : An async/await implement for gopher-lua. -- `gopherlua-debugger `_ : A debugger for gopher-lua -- `gluamahonia `_ : An encoding converter for gopher-lua -- `awesome-gopher-lua `_ : Collections of awesome libraries for GopherLua. - ----------------------------------------------------------------- -Donation ----------------------------------------------------------------- - -BTC: 1NEDSyUmo4SMTDP83JJQSWi1MvQUGGNMZB - ----------------------------------------------------------------- -License ----------------------------------------------------------------- -MIT - ----------------------------------------------------------------- -Author ----------------------------------------------------------------- -Yusuke Inuzuka From b87eac29661715e48e1a2868d76b853e0e757c4c Mon Sep 17 00:00:00 2001 From: yuin Date: Wed, 1 Apr 2026 09:48:02 +0900 Subject: [PATCH 35/36] chore: update test workflow --- .github/workflows/test.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 7f855ad8..0fc3f6e1 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -17,11 +17,12 @@ jobs: 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: | - GO111MODULE=off go get github.com/mattn/goveralls - ./_tools/go-inline *.go && go fmt . $(go env GOPATH)/bin/goveralls -coverprofile=coverage.out -service=github From 75f497656b1c6864139dd2a7d88cf96d09550814 Mon Sep 17 00:00:00 2001 From: yuin Date: Wed, 1 Apr 2026 09:54:10 +0900 Subject: [PATCH 36/36] docs: update README --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index c51026cd..832eb641 100644 --- a/README.md +++ b/README.md @@ -45,8 +45,6 @@ There are some benchmarks on the [wiki page](https://github.com/yuin/gopher-lua/ $ 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