diff --git a/gnovm/pkg/gnolang/helpers.go b/gnovm/pkg/gnolang/helpers.go index b163b6a52a7..428cfaa902a 100644 --- a/gnovm/pkg/gnolang/helpers.go +++ b/gnovm/pkg/gnolang/helpers.go @@ -612,6 +612,12 @@ func Return(results ...Expr) *ReturnStmt { } } +func BlockS(body Body) *BlockStmt { + return &BlockStmt{ + Body: body, + } +} + func Continue(label interface{}) *BranchStmt { return &BranchStmt{ Op: CONTINUE, @@ -795,6 +801,17 @@ func SIf(cond bool, then_, else_ Stmt) Stmt { } } +func InjectStmts(lvs *CapturedLoopVariables) []Stmt { + stmts := []Stmt{} + for _, lv := range lvs.loopVars { + lhs := Nx(lv) + rhs := Nx(lv) + as := A(lhs, ":=", rhs) + stmts = append(stmts, as) + } + return stmts +} + // ---------------------------------------- // chop functions diff --git a/gnovm/pkg/gnolang/machine.go b/gnovm/pkg/gnolang/machine.go index 68eb44290e2..45c6179959b 100644 --- a/gnovm/pkg/gnolang/machine.go +++ b/gnovm/pkg/gnolang/machine.go @@ -228,7 +228,7 @@ func (m *Machine) PreprocessAllFilesAndSaveBlockNodes() { PredefineFileSet(m.Store, pn, fset) for _, fn := range fset.Files { // Save Types to m.Store (while preprocessing). - fn = Preprocess(m.Store, pn, fn).(*FileNode) + fn = Preprocess(m.Store, pn, fn, PHASE_VARLOOP).(*FileNode) // Save BlockNodes to m.Store. SaveBlockNodes(m.Store, fn) } @@ -539,7 +539,7 @@ func (m *Machine) runFiles(fns ...*FileNode) { // runtime package value via PrepareNewValues. Then, // non-constant var declarations and file-level imports // are re-set in runDeclaration(,true). - fn = Preprocess(m.Store, pn, fn).(*FileNode) + fn = Preprocess(m.Store, pn, fn, PHASE_VARLOOP).(*FileNode) if debug { debug.Printf("PREPROCESSED FILE: %v\n", fn) } @@ -726,7 +726,7 @@ func (m *Machine) Eval(x Expr) []TypedValue { // x already creates its own scope. } // Preprocess x. - x = Preprocess(m.Store, last, x).(Expr) + x = Preprocess(m.Store, last, x, PHASE_CORE).(Expr) // Evaluate x. start := m.NumValues m.PushOp(OpHalt) @@ -798,7 +798,7 @@ func (m *Machine) EvalStaticTypeOf(last BlockNode, x Expr) Type { func (m *Machine) RunStatement(s Stmt) { sn := m.LastBlock().GetSource(m.Store) - s = Preprocess(m.Store, sn, s).(Stmt) + s = Preprocess(m.Store, sn, s, PHASE_CORE).(Stmt) m.PushOp(OpHalt) m.PushStmt(s) m.PushOp(OpExec) @@ -815,7 +815,7 @@ func (m *Machine) RunDeclaration(d Decl) { // Preprocess input using package block. There should only // be one block right now, and it's a *PackageNode. pn := m.LastBlock().GetSource(m.Store).(*PackageNode) - d = Preprocess(m.Store, pn, d).(Decl) + d = Preprocess(m.Store, pn, d, PHASE_CORE).(Decl) // do not SaveBlockNodes(m.Store, d). pn.PrepareNewValues(m.Package) m.runDeclaration(d) diff --git a/gnovm/pkg/gnolang/nodes.go b/gnovm/pkg/gnolang/nodes.go index 8f2c5054a8a..6846bcdb5bd 100644 --- a/gnovm/pkg/gnolang/nodes.go +++ b/gnovm/pkg/gnolang/nodes.go @@ -776,6 +776,7 @@ type ForStmt struct { Cond Expr // condition; or nil Post Stmt // post iteration (simple) statement; or nil Body + LoopVars *CapturedLoopVariables } type GoStmt struct { @@ -817,6 +818,7 @@ type RangeStmt struct { IsMap bool // if X is map type IsString bool // if X is string type IsArrayPtr bool // if X is array-pointer type + LoopVars *CapturedLoopVariables } type ReturnStmt struct { @@ -870,6 +872,12 @@ type SwitchClauseStmt struct { // ---------------------------------------- // bodyStmt (persistent) +// loopVar of for/range-loop can be captured or not, +// recored the captured ones. +type CapturedLoopVariables struct { + loopVars []Name +} + // NOTE: embedded in Block. type bodyStmt struct { Attributes @@ -1403,7 +1411,7 @@ func (x *PackageNode) DefineNative(n Name, ps, rs FieldTypeExprs, native func(*M } fd := FuncD(n, ps, rs, nil) - fd = Preprocess(nil, x, fd).(*FuncDecl) + fd = Preprocess(nil, x, fd, PHASE_CORE).(*FuncDecl) ft := evalStaticType(nil, x, &fd.Type).(*FuncType) if debug { if ft == nil { @@ -1459,6 +1467,7 @@ type BlockNode interface { GetNumNames() uint16 GetParentNode(Store) BlockNode GetPathForName(Store, Name) ValuePath + GetLoopNodeForName(Store, Name) BlockNode GetIsConst(Store, Name) bool GetLocalIndex(Name) (uint16, bool) GetValueRef(Store, Name) *TypedValue @@ -1500,6 +1509,19 @@ func (sb *StaticBlock) revertToOld() { sb.oldValues = nil } +func (sb *StaticBlock) reset() { + sb.Block = Block{ + Source: nil, + Values: nil, + Parent: nil, + } + sb.NumNames = 0 + sb.Names = nil + sb.Consts = nil + sb.Externs = nil + return +} + // Implements BlockNode func (sb *StaticBlock) InitStaticBlock(source BlockNode, parent BlockNode) { if sb.Names != nil || sb.Block.Source != nil { @@ -1557,6 +1579,7 @@ func (sb *StaticBlock) GetBlockNames() (ns []Name) { } // Implements BlockNode. +// NOTE: Extern names may also be local, if declared later. func (sb *StaticBlock) GetExternNames() (ns []Name) { return sb.Externs // copy? } @@ -1598,6 +1621,8 @@ func (sb *StaticBlock) GetPathForName(store Store, n Name) ValuePath { } // Register as extern. // NOTE: uverse names are externs too. + // NOTE: if a name is later declared in this block later, it is both an + // extern name with depth > 1, as well as local name with depth == 1. if !isFile(sb.GetSource(store)) { sb.GetStaticBlock().addExternName(n) } @@ -1626,6 +1651,43 @@ func (sb *StaticBlock) GetPathForName(store Store, n Name) ValuePath { panic(fmt.Sprintf("name %s not declared", n)) } +// Get loop blockNode where a name is declared, return nil if not exist. +func (sb *StaticBlock) GetLoopNodeForName(store Store, n Name) BlockNode { + if n == "_" { + return nil + } + parent := sb.GetParentNode(store) + path := parent.GetPathForName(store, n) + if path.Type != VPBlock { + return nil + } + + // NOTE: path.Depth == 1 means it's in bn. + bn := sb.GetSource(store) + for i := 1; i <= int(path.Depth); i++ { + bn = bn.GetParentNode(store) + } + + if !isLoopNode(store, bn) { + return nil + } + + return bn +} + +func isLoopNode(store Store, bn BlockNode) bool { + if bn != nil { + switch bn.(type) { + case *ForStmt: + return true + case *RangeStmt: + return true + default: + } + } + return false +} + // Returns whether a name defined here in in ancestry is a const. // This is not the same as whether a name's static type is // untyped -- as in c := a == b, a name may be an untyped non-const. diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index 12cf78cb7fa..1bc6cc078a5 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -95,6 +95,34 @@ func PredefineFileSet(store Store, pn *PackageNode, fset *FileSet) { } } +// attributes of `goto` block +type LoopInfo struct { + isGotoLoop bool + labelLine int + gotoLine int + label Name +} + +// intermediate state of preprocessing, can be +// extended as the preprocessing is further +// expanded and split +type PreprocessState struct { + loopInfos map[Name][]*LoopInfo +} + +// determine if reprocess is needed based on +// the state of last preprocess phase +func (s *PreprocessState) needsReprocess() bool { + if len(s.loopInfos) != 0 { + return true + } + return false +} + +func (s *PreprocessState) reset() { + s.loopInfos = make(map[Name][]*LoopInfo) +} + // This counter ensures (during testing) that certain functions // (like ConvertUntypedTo() for bigints and strings) // are only called during the preprocessing stage. @@ -119,7 +147,7 @@ var preprocessing atomic.Int32 // List of what Preprocess() does: // - Assigns BlockValuePath to NameExprs. // - TODO document what it does. -func Preprocess(store Store, ctx BlockNode, n Node) Node { +func Preprocess(store Store, ctx BlockNode, n Node, phase PreprocessPhase) Node { // Increment preprocessing counter while preprocessing. preprocessing.Add(1) defer preprocessing.Add(-1) @@ -129,11 +157,14 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { // panic("Preprocess requires context") } - // if n is file node, set node locations recursively. - if fn, ok := n.(*FileNode); ok { - pkgPath := ctx.(*PackageNode).PkgPath - fileName := string(fn.Name) - SetNodeLocations(pkgPath, fileName, fn) + // record closure infos + var closureStack []BlockNode = make([]BlockNode, 0, 32) + + // record loop extern infos while preprocess. + // loopInfo is aggregated according to their host funcDecl, + // to make it convenient for the following up handling. + preprocessState := &PreprocessState{ + loopInfos: make(map[Name][]*LoopInfo), } // create stack of BlockNodes. @@ -142,6 +173,27 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { lastpn := packageOf(last) stack = append(stack, last) + // check varloop scenario file wise + nn := doPreprocess(store, last, n, lastpn, stack, closureStack, phase, preprocessState) + + // if n is file node, set node locations recursively. + if fn, ok := n.(*FileNode); ok { + pkgPath := ctx.(*PackageNode).PkgPath + fileName := string(fn.Name) + SetNodeLocations(pkgPath, fileName, fn) + + // var loop exists, reprocess(find target var loop, inject code, wipe and reprocess) + // Handles each file node individually. + if preprocessState.needsReprocess() { + reprocess(store, fn, preprocessState) + preprocessState.reset() + } + } + + return nn +} + +func doPreprocess(store Store, last BlockNode, n Node, lastpn *PackageNode, stack []BlockNode, closureStack []BlockNode, phase PreprocessPhase, preprocessState *PreprocessState) Node { // iterate over all nodes recursively and calculate // BlockValuePath for each NameExpr. nn := Transcribe(n, func(ns []Node, ftype TransField, index int, n Node, stage TransStage) (Node, TransCtrl) { @@ -291,7 +343,7 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { // NOTE: preprocess it here, so type can // be used to set n.IsMap/IsString and // define key/value. - n.X = Preprocess(store, last, n.X).(Expr) + n.X = Preprocess(store, last, n.X, PHASE_CORE).(Expr) xt := evalStaticTypeOf(store, last, n.X) switch xt.Kind() { case MapKind: @@ -364,6 +416,8 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { last.Define(Name(rn), anyValue(rf.Type)) } } + // record, mostly used by goto... that with funcLit embedded within. + closureStack = append(closureStack, last) // TRANS_BLOCK ----------------------- case *SelectCaseStmt: @@ -414,7 +468,7 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { // evaluate case types. for i, cx := range n.Cases { cx = Preprocess( - store, last, cx).(Expr) + store, last, cx, PHASE_CORE).(Expr) var ct Type if cxx, ok := cx.(*ConstExpr); ok { if !cxx.IsUndefined() { @@ -450,7 +504,7 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { // check or convert case types to tt. for i, cx := range n.Cases { cx = Preprocess( - store, last, cx).(Expr) + store, last, cx, PHASE_CORE).(Expr) checkOrConvertType(store, last, &cx, tt, false) // #nosec G601 n.Cases[i] = cx } @@ -593,7 +647,7 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { // NOTE: TRANS_BLOCK2 ensures after .Init. // Preprocess and convert tag if const. if n.X != nil { - n.X = Preprocess(store, last, n.X).(Expr) + n.X = Preprocess(store, last, n.X, PHASE_CORE).(Expr) convertIfConst(store, last, n.X) } } @@ -625,6 +679,16 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { // elide composite lit element (nested) composite types. elideCompositeElements(clx, clt) } + switch n.(type) { + // TRANS_LEAVE (deferred)--------- + // NOTE: DO NOT USE TRANS_SKIP WITHIN BLOCK + // NODES, AS TRANS_LEAVE WILL BE SKIPPED; OR + // POP BLOCK YOURSELF. + case BlockNode: + // Pop block. + stack = stack[:len(stack)-1] + last = stack[len(stack)-1] + } }() // The main TRANS_LEAVE switch. @@ -745,6 +809,108 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { cx := evalConst(store, last, n) return cx, TRANS_CONTINUE + // TRANS_LEAVE ----------------------- + case *FuncLitExpr: + // XXX, cumbersome, better way? + if phase == PHASE_VARLOOP { + // Step 1: quick check to identify no closure + inLoop := isBlockNodeInLoop(store, n) + if !inLoop { + return n, TRANS_CONTINUE + } + + // Step 2: gather all extern names declared in for/range-loops. + var ( + leNames []Name + loopNode BlockNode + ) + + // outer first order by default + externNames := n.GetExternNames() + for _, name := range externNames { + loopNode = n.GetLoopNodeForName(store, name) + if loopNode == nil { + continue + } + // if an extern name if defined in a for/range loop, it probably + // escaped the loop, but it's not a sufficient condition. one + // counter case is that is this funcLit is called before the loop + // ends, the extern does not escape. + // NOTE: this logic is missing now. + // TODO: add this check. + leNames = append(leNames, name) + loopVars := []Name{} // per loop + + // find loop var of for-loop + if fs, ok := loopNode.(*ForStmt); ok { + if as, ok := fs.Init.(*AssignStmt); ok { + if as.Op == DEFINE { + if len(as.Lhs) != 1 { + panic("incorrect length of lhs in for init stmt") + } + if nx, ok := as.Lhs[0].(*NameExpr); ok { + if nx.Name != "_" { + loopVars = append(loopVars, nx.Name) + } + } + } + } + } + // find loopVars for range-loop + if rs, ok := loopNode.(*RangeStmt); ok { + if rs.Op == DEFINE { + if nx, ok := rs.Key.(*NameExpr); ok { + if nx.Name != "_" { + loopVars = append(loopVars, nx.Name) + } + } + if nx, ok := rs.Value.(*NameExpr); ok { + if nx.Name != "_" { + loopVars = append(loopVars, nx.Name) + } + } + } + } + + // prepare to pack closure(with captured names) + lvs := &CapturedLoopVariables{} + // check if loopVar is captured + for _, lv := range loopVars { + for _, ln := range leNames { + if lv == ln { + lvs.loopVars = append(lvs.loopVars, lv) + break + } + } + } + + if debug { + for _, name := range lvs.loopVars { + debug.Println("loopVar: ", name) + } + } + + switch ln := loopNode.(type) { + case *ForStmt: + ln.LoopVars = lvs // attach it to the host for further handling + case *RangeStmt: + ln.LoopVars = lvs + } + + // loopInfo + loop := &LoopInfo{} + // maybe initialized by other places, or not + + lastFn, err := findLastFn(n) + if err == nil { + // global loop infos recorded. fileNode-wise. + // will be handled while trans_leave FileNode. + preprocessState.loopInfos[lastFn] = append(preprocessState.loopInfos[lastFn], loop) + } + } + } + return n, TRANS_CONTINUE + // TRANS_LEAVE ----------------------- case *BinaryExpr: lt := evalStaticTypeOf(store, last, n.Left) @@ -760,7 +926,7 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { Op: n.Op, Right: rn, } - resn := Preprocess(store, last, n2) + resn := Preprocess(store, last, n2, PHASE_CORE) return resn, TRANS_CONTINUE } @@ -819,7 +985,7 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { Right: rn, } resn := Node(Call(tx, n2)) - resn = Preprocess(store, last, resn) + resn = Preprocess(store, last, resn, PHASE_CORE) return resn, TRANS_CONTINUE // NOTE: binary operations are always computed in // gno, never with reflect. @@ -865,7 +1031,7 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { Right: n.Right, } resn := Node(Call(tx, n2)) - resn = Preprocess(store, last, resn) + resn = Preprocess(store, last, resn, PHASE_CORE) return resn, TRANS_CONTINUE // NOTE: binary operations are always computed in // gno, never with reflect. @@ -915,7 +1081,7 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { Right: rn, } resn := Node(Call(tx, n2)) - resn = Preprocess(store, last, resn) + resn = Preprocess(store, last, resn, PHASE_CORE) return resn, TRANS_CONTINUE // NOTE: binary operations are always // computed in gno, never with @@ -1028,7 +1194,7 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { if evalStaticTypeOf(store, last, args1).Kind() == StringKind { bsx := constType(nil, gByteSliceType) args1 = Call(bsx, args1) - args1 = Preprocess(nil, last, args1).(Expr) + args1 = Preprocess(nil, last, args1, PHASE_CORE).(Expr) n.Args[1] = args1 } } else { @@ -1052,7 +1218,7 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { } // Convert to the array type. arg1 := Call(tx, arg) - n.Args[i+1] = Preprocess(nil, last, arg1).(Expr) + n.Args[i+1] = Preprocess(nil, last, arg1, PHASE_CORE).(Expr) } } } else if fv.PkgPath == uversePkgPath && fv.Name == "copy" { @@ -1063,7 +1229,7 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { if evalStaticTypeOf(store, last, args1).Kind() == StringKind { bsx := constType(nil, gByteSliceType) args1 = Call(bsx, args1) - args1 = Preprocess(nil, last, args1).(Expr) + args1 = Preprocess(nil, last, args1, PHASE_CORE).(Expr) n.Args[1] = args1 } } @@ -1257,7 +1423,7 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { Op: n.Op, } resn := Node(Call(tx, n2)) - resn = Preprocess(store, last, resn) + resn = Preprocess(store, last, resn, PHASE_CORE) return resn, TRANS_CONTINUE // NOTE: like binary operations, unary operations are // always computed in gno, never with reflect. @@ -1375,7 +1541,7 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { } } // recursively preprocess new n.X. - n.X = Preprocess(store, last, nx2).(Expr) + n.X = Preprocess(store, last, nx2, PHASE_CORE).(Expr) } // nxt2 may not be xt anymore. // (even the dereferenced of xt and nxt2 may not @@ -1678,9 +1844,33 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { } findBranchLabel(last, n.Label) case GOTO: - _, depth, index := findGotoLabel(last, n.Label) + _, depth, index, labelLine := findGotoLabel(last, n.Label) n.Depth = depth n.BodyIndex = index + // identify closure pattern + if phase == PHASE_VARLOOP { + gotoLine := n.GetLine() + if labelLine < gotoLine { + for i := len(closureStack) - 1; i >= 0; i-- { // outermost one + if fx, ok := closureStack[i].(*FuncLitExpr); ok { + if labelLine < fx.GetLine() && fx.GetLine() < gotoLine { + loop := &LoopInfo{ + labelLine: labelLine, + gotoLine: gotoLine, + label: n.Label, + isGotoLoop: true, + } + + lastFn, err := findLastFn(last) // the host Fn + if err == nil { + preprocessState.loopInfos[lastFn] = append(preprocessState.loopInfos[lastFn], loop) + } + break + } + } + } + } + } case FALLTHROUGH: if swchC, ok := last.(*SwitchClauseStmt); ok { // last is a switch clause, find its index in the switch and assign @@ -1701,8 +1891,26 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { // TRANS_LEAVE ----------------------- case *ForStmt: - // Cond consts become bool *ConstExprs. - checkOrConvertType(store, last, &n.Cond, BoolType, false) + if phase == PHASE_VARLOOP { + lvs := n.LoopVars + if lvs != nil { + // inject i := i for loop extern + stmts := append(InjectStmts(lvs), n.Body...) + // wrap body with {} + nn := BlockS(stmts) + // set loc info + loc := n.GetLocation() + loc.Line = stmts[0].GetLine() + nn.SetLocation(loc) + // set standalone line info + nn.SetLine(stmts[0].GetLine()) + // replace with wrapped blockStmt + n.Body = []Stmt{nn} + } + // preprocess n + // Cond consts become bool *ConstExprs. + checkOrConvertType(store, last, &n.Cond, BoolType, false) + } // TRANS_LEAVE ----------------------- case *IfStmt: @@ -1712,6 +1920,22 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { // TRANS_LEAVE ----------------------- case *RangeStmt: // NOTE: k,v already defined @ TRANS_BLOCK. + if phase == PHASE_VARLOOP { + lvs := n.LoopVars + if lvs != nil { + stmts := append(InjectStmts(lvs), n.Body...) + // wrap body with {} + nn := BlockS(stmts) + // set loc and line + loc := n.GetLocation() + loc.Line = stmts[0].GetLine() + nn.SetLocation(loc) + // set line + nn.SetLine(stmts[0].GetLine()) + // replace with wrapped blockStmt + n.Body = []Stmt{nn} + } + } // TRANS_LEAVE ----------------------- case *ReturnStmt: @@ -1915,9 +2139,9 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { } } } - // TODO make note of constance in static block for - // future use, or consider "const paths". set as - // preprocessed. + // TODO make note of constance in static block for + // future use, or consider "const paths". set as + // preprocessed. // TRANS_LEAVE ----------------------- case *TypeDecl: @@ -1972,19 +2196,19 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { // Replace the type with *constTypeExpr{}, // otherwise methods would be un at runtime. n.Type = constType(n.Type, dst) + + // // TRANS_LEAVE ----------------------- + //case *FileNode: + // if len(loopInfos) != 0 { + // reProcess(store, last, loopInfos) + // loopInfos = nil + // } } // end type switch statement + // END TRANS_LEAVE ----------------------- - // TRANS_LEAVE ----------------------- - // finalization. - if _, ok := n.(BlockNode); ok { - // Pop block. - stack = stack[:len(stack)-1] - last = stack[len(stack)-1] - return n, TRANS_CONTINUE - } else { - return n, TRANS_CONTINUE - } + // Convenience return in case not already returned. + return n, TRANS_CONTINUE } panic(fmt.Sprintf( @@ -2279,6 +2503,18 @@ func funcOf(last BlockNode) (BlockNode, *FuncTypeExpr) { } } +// findLastFn searches for the last function declaration in a chain of block nodes. +// It returns the name of the function if found, or an error if no function declaration exists. +func findLastFn(last BlockNode) (Name, error) { + for last != nil { + if n, ok := last.(*FuncDecl); ok { + return n.Name, nil + } + last = last.GetParentNode(nil) + } + return "", fmt.Errorf("no function declaration found") +} + func findBranchLabel(last BlockNode, label Name) ( bn BlockNode, depth uint8, bodyIdx int, ) { @@ -2320,8 +2556,9 @@ func findBranchLabel(last BlockNode, label Name) ( } func findGotoLabel(last BlockNode, label Name) ( - bn BlockNode, depth uint8, bodyIdx int, + bn BlockNode, depth uint8, bodyIdx int, line int, ) { + var ls Stmt // label stmt for { switch cbn := last.(type) { case *IfStmt, *SwitchStmt: @@ -2333,9 +2570,10 @@ func findGotoLabel(last BlockNode, label Name) ( panic("unexpected package blocknode") case *FuncLitExpr, *FuncDecl: body := cbn.GetBody() - _, bodyIdx = body.GetLabeledStmt(label) + ls, bodyIdx = body.GetLabeledStmt(label) if bodyIdx != -1 { bn = cbn + line = ls.GetLine() return } else { panic(fmt.Sprintf( @@ -2344,9 +2582,10 @@ func findGotoLabel(last BlockNode, label Name) ( } case *BlockStmt, *ForStmt, *IfCaseStmt, *RangeStmt, *SelectCaseStmt, *SwitchClauseStmt: body := cbn.GetBody() - _, bodyIdx = body.GetLabeledStmt(label) + ls, bodyIdx = body.GetLabeledStmt(label) if bodyIdx != -1 { bn = cbn + line = ls.GetLine() return } else { last = cbn.GetParentNode(nil) @@ -2457,7 +2696,7 @@ func checkOrConvertType(store Store, last BlockNode, x *Expr, t Type, autoNative } } cx := Expr(Call(constType(nil, t), *x)) - cx = Preprocess(store, last, cx).(Expr) + cx = Preprocess(store, last, cx, PHASE_CORE).(Expr) *x = cx } } @@ -2807,7 +3046,7 @@ func findUndefined2(store Store, last BlockNode, x Expr, t Type) (un Name) { // way. This cannot be done asynchronously, cuz undefined // names ought to be returned immediately to let the caller // predefine it. - cx.Type = Preprocess(store, last, cx.Type).(Expr) // recursive + cx.Type = Preprocess(store, last, cx.Type, PHASE_CORE).(Expr) // recursive ct = evalStaticType(store, last, cx.Type) // elide composite lit element (nested) composite types. elideCompositeElements(cx, ct) @@ -3027,8 +3266,8 @@ func predefineNow2(store Store, last BlockNode, d Decl, m map[Name]struct{}) (De // NOTE: document somewhere. cd.Recv.Name = ".recv" } - cd.Recv = *Preprocess(store, last, &cd.Recv).(*FieldTypeExpr) - cd.Type = *Preprocess(store, last, &cd.Type).(*FuncTypeExpr) + cd.Recv = *Preprocess(store, last, &cd.Recv, PHASE_CORE).(*FieldTypeExpr) + cd.Type = *Preprocess(store, last, &cd.Type, PHASE_CORE).(*FuncTypeExpr) rft := evalStaticType(store, last, &cd.Recv).(FieldType) rt := rft.Type ft := evalStaticType(store, last, &cd.Type).(*FuncType) @@ -3080,7 +3319,7 @@ func predefineNow2(store Store, last BlockNode, d Decl, m map[Name]struct{}) (De } else { ftv := pkg.GetValueRef(store, cd.Name) ft := ftv.T.(*FuncType) - cd.Type = *Preprocess(store, last, &cd.Type).(*FuncTypeExpr) + cd.Type = *Preprocess(store, last, &cd.Type, PHASE_CORE).(*FuncTypeExpr) ft2 := evalStaticType(store, last, &cd.Type).(*FuncType) if !ft.IsZero() { // redefining function. @@ -3100,9 +3339,9 @@ func predefineNow2(store Store, last BlockNode, d Decl, m map[Name]struct{}) (De // Full type declaration/preprocessing already done in tryPredefine return d, false case *ValueDecl: - return Preprocess(store, last, cd).(Decl), true + return Preprocess(store, last, cd, PHASE_CORE).(Decl), true case *TypeDecl: - return Preprocess(store, last, cd).(Decl), true + return Preprocess(store, last, cd, PHASE_CORE).(Decl), true default: return d, false } @@ -3673,6 +3912,172 @@ func findDependentNames(n Node, dst map[Name]struct{}) { } } +func isBlockNodeInLoop(store Store, bn BlockNode) bool { + for bn != nil { + bn = bn.GetParentNode(store) + switch bn.(type) { + case *FuncLitExpr: + return false + case *ForStmt: + return true + case *RangeStmt: + return true + default: + continue + } + } + return false +} + +// reset value path, static block, preprocessed attr, etc. +func resetStaticBlock(bn BlockNode) { + Transcribe(bn, func(ns []Node, ftype TransField, index int, n Node, stage TransStage) (Node, TransCtrl) { + switch stage { + case TRANS_ENTER: + switch cn := n.(type) { + case *NameExpr: + cn.Path = NewValuePath(VPUverse, 0, 0, cn.Name) + cn.SetAttribute(ATTR_PREPROCESSED, false) + return cn, TRANS_CONTINUE + case BlockNode: + cn.GetStaticBlock().reset() + cn.SetAttribute(ATTR_PREPROCESSED, false) + return cn, TRANS_CONTINUE + default: + cn.SetAttribute(ATTR_PREPROCESSED, false) + return cn, TRANS_CONTINUE + } + default: + return n, TRANS_CONTINUE + } + }) +} + +// for goto... +// wrap goto loop into block stmt. +func rebuildBody(b Body, gloop *LoopInfo, loc Location) Body { + preBody := []Stmt{} + loopBody := []Stmt{} + postBody := []Stmt{} + + for _, s := range b { + if s.GetLine() < gloop.labelLine { + preBody = append(preBody, s) + } else if s.GetLine() >= gloop.labelLine && s.GetLine() <= gloop.gotoLine { + loopBody = append(loopBody, s) + } else { + postBody = append(postBody, s) + } + } + + nn := BlockS(loopBody) + nn.SetLabel(gloop.label) // set label on wrapper block stmt + + // some loc info inherit from parent block + nLoc := loc + nLoc.Line = loopBody[0].GetLine() + nn.SetLocation(nLoc) + nn.SetLine(loopBody[0].GetLine()) + + nBody := append(preBody, nn) + nBody = append(nBody, postBody...) + return nBody +} + +// this is only for loops formed by goto... +func checkAndRebuildBody(body Body, loops []*LoopInfo, loc Location) Body { + for _, loop := range loops { + if loop.isGotoLoop { // for/range has done the work + // find label stmt + lblstmt, idx := body.GetLabeledStmt(loop.label) + if lblstmt == nil { + continue + } else { + // clear origin label, set this label on blockStmt later + body[idx].SetLabel("") + nBody := rebuildBody(body, loop, loc) + body = nBody + } + } + } + return body +} + +// core reprocess logic, this is handled per fileNode. +// traverse from root node, find goto loop, rebuild body. +// (for/range-loop body has already been rebuilt before) +// finally get all work done on trans_leave funcDecl. +func reprocess(store Store, bn BlockNode, preprocessState *PreprocessState) { + var ( + loops []*LoopInfo // per funcDecl + targetFn Name // outer function that contains the loop node + ) + + Transcribe(bn, func(ns []Node, ftype TransField, index int, n Node, stage TransStage) (Node, TransCtrl) { + // TODO: optimize. if there's no goto loop exist, no need for the trans_enter stage. + switch stage { + case TRANS_ENTER: // find target goto loop, and rebuild body, for/range no need this stage. + switch cn := n.(type) { + case *FuncDecl: + for name, lfs := range preprocessState.loopInfos { + if cn.Name == name { + loops = lfs + targetFn = name + } else { + continue + } + } + cn.Body = checkAndRebuildBody(cn.GetBody(), loops, cn.GetLocation()) + return cn, TRANS_CONTINUE + case *BlockStmt: // target goto loop may be embedded in multi kind of block + cn.Body = checkAndRebuildBody(cn.GetBody(), loops, cn.GetLocation()) + return cn, TRANS_CONTINUE + case *FuncLitExpr: + cn.Body = checkAndRebuildBody(cn.GetBody(), loops, cn.GetLocation()) + return cn, TRANS_CONTINUE + case *ForStmt: + cn.Body = checkAndRebuildBody(cn.GetBody(), loops, cn.GetLocation()) + return cn, TRANS_CONTINUE + case *IfCaseStmt: + cn.Body = checkAndRebuildBody(cn.GetBody(), loops, cn.GetLocation()) + return cn, TRANS_CONTINUE + case *RangeStmt: + cn.Body = checkAndRebuildBody(cn.GetBody(), loops, cn.GetLocation()) + return cn, TRANS_CONTINUE + case *SwitchClauseStmt: + cn.Body = checkAndRebuildBody(cn.GetBody(), loops, cn.GetLocation()) + return cn, TRANS_CONTINUE + default: + return cn, TRANS_CONTINUE + } + case TRANS_LEAVE: + switch cn := n.(type) { + case *FuncDecl: // all reProcess happens in the root funcDecl, for convenience + if cn.Name == targetFn { + cn = wipeAndReprocess(store, bn, cn).(*FuncDecl) + } + delete(preprocessState.loopInfos, targetFn) + if len(preprocessState.loopInfos) == 0 { + return cn, TRANS_EXIT + } + return cn, TRANS_CONTINUE + default: + return cn, TRANS_CONTINUE + } + default: + return n, TRANS_CONTINUE + } + }) + + return +} + +func wipeAndReprocess(store Store, last BlockNode, bn BlockNode) Node { + resetStaticBlock(bn) + nn := Preprocess(store, last, bn, PHASE_CORE) + return nn +} + // ---------------------------------------- // SetNodeLocations diff --git a/gnovm/pkg/gnolang/transcribe.go b/gnovm/pkg/gnolang/transcribe.go index c5b72336c83..abd8f6fc6b6 100644 --- a/gnovm/pkg/gnolang/transcribe.go +++ b/gnovm/pkg/gnolang/transcribe.go @@ -11,6 +11,13 @@ type ( TransField uint8 ) +type PreprocessPhase uint8 + +const ( + PHASE_VARLOOP PreprocessPhase = iota + PHASE_CORE +) + const ( TRANS_CONTINUE TransCtrl = iota TRANS_SKIP diff --git a/gnovm/pkg/gnolang/types.go b/gnovm/pkg/gnolang/types.go index e1814e8f243..1702b18db67 100644 --- a/gnovm/pkg/gnolang/types.go +++ b/gnovm/pkg/gnolang/types.go @@ -2528,7 +2528,7 @@ func applySpecifics(lookup map[Name]Type, tmpl Type) (Type, bool) { } // Parse generic to expr. gx := MustParseExpr(string(generic)) - gx = Preprocess(nil, bs, gx).(Expr) + gx = Preprocess(nil, bs, gx, PHASE_CORE).(Expr) // Evaluate type from generic expression. m := NewMachine("", nil) tv := m.EvalStatic(bs, gx) diff --git a/gnovm/pkg/gnolang/values.go b/gnovm/pkg/gnolang/values.go index 948730c4697..1775bfebf92 100644 --- a/gnovm/pkg/gnolang/values.go +++ b/gnovm/pkg/gnolang/values.go @@ -587,11 +587,9 @@ func (fv *FuncValue) GetType(store Store) *FuncType { } func (fv *FuncValue) GetBodyFromSource(store Store) []Stmt { - if fv.body == nil { - source := fv.GetSource(store) - fv.body = source.GetBody() - return fv.body - } + // XXX, always get body from source that the origin body might be modified + source := fv.GetSource(store) + fv.body = source.GetBody() return fv.body } diff --git a/gnovm/tests/files/closure10.gno b/gnovm/tests/files/closure10.gno new file mode 100644 index 00000000000..1a788d9e0b2 --- /dev/null +++ b/gnovm/tests/files/closure10.gno @@ -0,0 +1,26 @@ +package main + +import "fmt" + +func bar() func() func() int { + x := 1 + + // First level of closure, modifies x + return func() func() int { + //x++ + // Second level of closure, returns x + return func() int { + return x + } + } +} + +func main() { + f := bar() // f is the first-level closure + g := f() // g is the second-level closure, x is incremented here + + fmt.Println(g()) // prints the value of x after being modified by the first-level closure +} + +// Output: +// 1 diff --git a/gnovm/tests/files/closure10_a.gno b/gnovm/tests/files/closure10_a.gno new file mode 100644 index 00000000000..45ad06c6945 --- /dev/null +++ b/gnovm/tests/files/closure10_a.gno @@ -0,0 +1,26 @@ +package main + +import "fmt" + +func bar() func() func() int { + x := 1 + + // First level of closure, modifies x + return func() func() int { + x++ + // Second level of closure, returns x + return func() int { + return x + } + } +} + +func main() { + f := bar() // f is the first-level closure + g := f() // g is the second-level closure, x is incremented here + + fmt.Println(g()) // prints the value of x after being modified by the first-level closure +} + +// Output: +// 2 diff --git a/gnovm/tests/files/closure11.gno b/gnovm/tests/files/closure11.gno new file mode 100644 index 00000000000..1ea6e013b0d --- /dev/null +++ b/gnovm/tests/files/closure11.gno @@ -0,0 +1,25 @@ +package main + +func main() { + var fns []func() int + + for i := 0; i < 2; i++ { + x := i + f := func() int { + if true { + if true { + return x + } + } + return 0 + } + fns = append(fns, f) + } + for _, fn := range fns { + println(fn()) + } +} + +// Output: +// 0 +// 1 diff --git a/gnovm/tests/files/closure11_a.gno b/gnovm/tests/files/closure11_a.gno new file mode 100644 index 00000000000..2ce3df96097 --- /dev/null +++ b/gnovm/tests/files/closure11_a.gno @@ -0,0 +1,23 @@ +package main + +func main() { + var fns []func() int + + for i := 0; i < 2; i++ { + x := i + f := func() int { + if true { + return x + } + return 0 + } + fns = append(fns, f) + } + for _, fn := range fns { + println(fn()) + } +} + +// Output: +// 0 +// 1 diff --git a/gnovm/tests/files/closure11_b.gno b/gnovm/tests/files/closure11_b.gno new file mode 100644 index 00000000000..427c6f075f9 --- /dev/null +++ b/gnovm/tests/files/closure11_b.gno @@ -0,0 +1,27 @@ +package main + +func main() { + var fns []func() int + + for i := 0; i < 2; i++ { + x := i + y := 0 + f := func() int { + if true { + x += y + if true { + return x + } + } + return 0 + } + fns = append(fns, f) + } + for _, fn := range fns { + println(fn()) + } +} + +// Output: +// 0 +// 1 diff --git a/gnovm/tests/files/closure11_c.gno b/gnovm/tests/files/closure11_c.gno new file mode 100644 index 00000000000..427c6f075f9 --- /dev/null +++ b/gnovm/tests/files/closure11_c.gno @@ -0,0 +1,27 @@ +package main + +func main() { + var fns []func() int + + for i := 0; i < 2; i++ { + x := i + y := 0 + f := func() int { + if true { + x += y + if true { + return x + } + } + return 0 + } + fns = append(fns, f) + } + for _, fn := range fns { + println(fn()) + } +} + +// Output: +// 0 +// 1 diff --git a/gnovm/tests/files/closure12.gno b/gnovm/tests/files/closure12.gno new file mode 100644 index 00000000000..44717e05cc0 --- /dev/null +++ b/gnovm/tests/files/closure12.gno @@ -0,0 +1,22 @@ +package main + +func main() { + var fns []func() int + + for i := 0; i < 2; i++ { + x := i + f := func() int { + { + return x + } + } + fns = append(fns, f) + } + for _, fn := range fns { + println(fn()) + } +} + +// Output: +// 0 +// 1 diff --git a/gnovm/tests/files/closure12_a.gno b/gnovm/tests/files/closure12_a.gno new file mode 100644 index 00000000000..1e3eb9fe815 --- /dev/null +++ b/gnovm/tests/files/closure12_a.gno @@ -0,0 +1,23 @@ +package main + +func main() { + var fns []func() int + + for i := 0; i < 2; i++ { + x := i + f := func() int { + for i := 0; i < 1; i++ { + x++ + } + return x + } + fns = append(fns, f) + } + for _, fn := range fns { + println(fn()) + } +} + +// Output: +// 1 +// 2 diff --git a/gnovm/tests/files/closure12_b.gno b/gnovm/tests/files/closure12_b.gno new file mode 100644 index 00000000000..5164defd0f8 --- /dev/null +++ b/gnovm/tests/files/closure12_b.gno @@ -0,0 +1,25 @@ +package main + +func main() { + var fns []func() int + + for i := 0; i < 2; i++ { + x := i + s := []int{1, 2} + + f := func() int { + for _, v := range s { + x += v + } + return x + } + fns = append(fns, f) + } + for _, fn := range fns { + println(fn()) + } +} + +// Output: +// 3 +// 4 diff --git a/gnovm/tests/files/closure12_c.gno b/gnovm/tests/files/closure12_c.gno new file mode 100644 index 00000000000..3e42e207649 --- /dev/null +++ b/gnovm/tests/files/closure12_c.gno @@ -0,0 +1,27 @@ +package main + +func main() { + var fns []func() int + + for i := 0; i < 2; i++ { + x := i + y := 1 + f := func() int { + switch y { + case 1: + x += 1 + default: + x += 0 + } + return x + } + fns = append(fns, f) + } + for _, fn := range fns { + println(fn()) + } +} + +// Output: +// 1 +// 2 diff --git a/gnovm/tests/files/closure12_e.gno b/gnovm/tests/files/closure12_e.gno new file mode 100644 index 00000000000..17490510b26 --- /dev/null +++ b/gnovm/tests/files/closure12_e.gno @@ -0,0 +1,27 @@ +package main + +type queueOnePass struct { + sparse []uint32 + dense []uint32 + size, nextIndex uint32 +} + +func newQueue(size int) (q *queueOnePass) { + return &queueOnePass{ + sparse: make([]uint32, size), + dense: make([]uint32, size), + } +} +func main() { + var ( + visitQueue = newQueue(10) + ) + f := func() { + println(visitQueue.size) + } + + f() +} + +// Output: +// 0 diff --git a/gnovm/tests/files/closure12_f.gno b/gnovm/tests/files/closure12_f.gno new file mode 100644 index 00000000000..91e7e194104 --- /dev/null +++ b/gnovm/tests/files/closure12_f.gno @@ -0,0 +1,20 @@ +package main + +func main() { + s := []int{1, 2} + + f := func() { + for i, v := range s { + println(i) + println(v) + } + } + + f() +} + +// Output: +// 0 +// 1 +// 1 +// 2 diff --git a/gnovm/tests/files/closure12_g.gno b/gnovm/tests/files/closure12_g.gno new file mode 100644 index 00000000000..bf998c163d8 --- /dev/null +++ b/gnovm/tests/files/closure12_g.gno @@ -0,0 +1,14 @@ +package main + +func main() { + f := func(a int) bool { + println(a) + return true + } + + println(f(5)) +} + +// Output: +// 5 +// true diff --git a/gnovm/tests/files/closure12_h.gno b/gnovm/tests/files/closure12_h.gno new file mode 100644 index 00000000000..c9f1f6e97e8 --- /dev/null +++ b/gnovm/tests/files/closure12_h.gno @@ -0,0 +1,25 @@ +package main + +func main() { + s := 1 + f := func() { + i := 0 // no capture for i + var j = s // s should be captured, j not + k := s // s should be captured, k not + m, n := s, 0 + println(i) + println(j) + println(k) + println(m) + println(n) + } + + f() +} + +// Output: +// 0 +// 1 +// 1 +// 1 +// 0 diff --git a/gnovm/tests/files/closure12_i.gno b/gnovm/tests/files/closure12_i.gno new file mode 100644 index 00000000000..acf1b53b32a --- /dev/null +++ b/gnovm/tests/files/closure12_i.gno @@ -0,0 +1,15 @@ +package main + +func main() { + s := []int{1, 2} + + f := func() { + if len(s) == 2 { + println("ok") + } + } + f() +} + +// Output: +// ok diff --git a/gnovm/tests/files/closure13.gno b/gnovm/tests/files/closure13.gno new file mode 100644 index 00000000000..8345c9ed48d --- /dev/null +++ b/gnovm/tests/files/closure13.gno @@ -0,0 +1,24 @@ +package main + +import "fmt" + +func main() { + var recursiveFunc func(int) int + var recursiveFunc2 func(int) int + + recursiveFunc = func(num int) int { + recursiveFunc2 = recursiveFunc + + if num <= 0 { + return 1 + } + + return num * recursiveFunc2(num-1) + } + + result := recursiveFunc(5) + fmt.Println("Factorial of 5 is:", result) // Output: 120 +} + +// Output: +// Factorial of 5 is: 120 diff --git a/gnovm/tests/files/closure13_a0.gno b/gnovm/tests/files/closure13_a0.gno new file mode 100644 index 00000000000..ece19dd87f6 --- /dev/null +++ b/gnovm/tests/files/closure13_a0.gno @@ -0,0 +1,22 @@ +package main + +import "fmt" + +func main() { + // Define a function that returns a closure + var recursiveFunc func(int) int + recursiveFunc = func(num int) int { + if num <= 0 { + return 1 + } + // Closure calling itself recursively + return num * recursiveFunc(num-1) + } + + // Use the recursive closure + result := recursiveFunc(5) + fmt.Println("Factorial of 5 is:", result) // Output: 120 +} + +// Output: +// Factorial of 5 is: 120 diff --git a/gnovm/tests/files/closure13_b0.gno b/gnovm/tests/files/closure13_b0.gno new file mode 100644 index 00000000000..4bfe864cc8e --- /dev/null +++ b/gnovm/tests/files/closure13_b0.gno @@ -0,0 +1,26 @@ +package main + +func main() { + var buf []byte + + // when eval this, buf is still nil, + expectRead := func(size int, expected string, err error) { + b := buf[0:size] + println(b) + println(len(buf)) + println(cap(buf)) + } + + // buf should be captured here, here it's volatile, not where it defined + // namely, the vp should point here, -> so mutate offset + withFooBar := func() { + buf = make([]byte, 20) + expectRead(5, "foo ", nil) + } + withFooBar() +} + +// Output: +// slice[0x0000000000] +// 20 +// 20 diff --git a/gnovm/tests/files/closure13_b1.gno b/gnovm/tests/files/closure13_b1.gno new file mode 100644 index 00000000000..9218ee16fa3 --- /dev/null +++ b/gnovm/tests/files/closure13_b1.gno @@ -0,0 +1,26 @@ +package main + +func main() { + var buf []byte + + // when eval this, buf is still nil, + expectRead := func(size int, expected string, eerr error) { + b := buf[0:size] + println(b) + } + + // buf should be captured here, here it's volatile, not where it defined + // namely, the vp should point here, -> so mutate offset + withFooBar := func() func() { + buf = make([]byte, 20) + return func() { + b := buf[0:4] + println(b) + } + } + withFooBar() + println("ok") +} + +// Output: +// ok diff --git a/gnovm/tests/files/closure14.gno b/gnovm/tests/files/closure14.gno new file mode 100644 index 00000000000..e6b89f9528a --- /dev/null +++ b/gnovm/tests/files/closure14.gno @@ -0,0 +1,26 @@ +package main + +import "fmt" + +func foo() (err error) { + defer func() { + if r := recover(); r != nil { + switch v := r.(type) { + case error: + err = v + default: + err = fmt.Errorf("%s", v) + } + } + }() + + panic("xxx") +} + +func main() { + err := foo() + println(err.Error()) +} + +// Output: +// xxx diff --git a/gnovm/tests/files/closure14_a.gno b/gnovm/tests/files/closure14_a.gno new file mode 100644 index 00000000000..706e2bd7e8b --- /dev/null +++ b/gnovm/tests/files/closure14_a.gno @@ -0,0 +1,27 @@ +package main + +import ( + "errors" +) + +func foo() (err error) { + y := 1 + defer func() { + if r := recover(); r != nil { + switch y { + case 1: + err = errors.New("ok") + default: + err = nil + } + } + }() + panic(y) +} + +func main() { + println(foo()) +} + +// Output: +// ok diff --git a/gnovm/tests/files/closure15.gno b/gnovm/tests/files/closure15.gno new file mode 100644 index 00000000000..78546462dc4 --- /dev/null +++ b/gnovm/tests/files/closure15.gno @@ -0,0 +1,37 @@ +package main + +import "fmt" + +// recursive closure does not capture +func main() { + var fns []func(int) int + var recursiveFunc func(int) int + + for i := 0; i < 3; i++ { + recursiveFunc = func(num int) int { + x := i + println("value of x: ", x) + if num <= 0 { + return 1 + } + return num * recursiveFunc(num-1) + } + fns = append(fns, recursiveFunc) + } + + for i, r := range fns { + result := r(i) + fmt.Printf("Factorial of %d is: %d \n", i, result) + } +} + +// Output: +// value of x: 0 +// Factorial of 0 is: 1 +// value of x: 1 +// value of x: 2 +// Factorial of 1 is: 1 +// value of x: 2 +// value of x: 2 +// value of x: 2 +// Factorial of 2 is: 2 diff --git a/gnovm/tests/files/closure16.gno b/gnovm/tests/files/closure16.gno new file mode 100644 index 00000000000..8a472ffb410 --- /dev/null +++ b/gnovm/tests/files/closure16.gno @@ -0,0 +1,21 @@ +package main + +func main() { + var fns []func() int + s := []int{1, 2, 3} + for i, _ := range s { + x := i + f := func() int { + return x + } + fns = append(fns, f) + } + for _, fn := range fns { + println(fn()) + } +} + +// Output: +// 0 +// 1 +// 2 diff --git a/gnovm/tests/files/closure16_a.gno b/gnovm/tests/files/closure16_a.gno new file mode 100644 index 00000000000..ee375bc719c --- /dev/null +++ b/gnovm/tests/files/closure16_a.gno @@ -0,0 +1,20 @@ +package main + +func main() { + var fns []func() int + m := map[string]int{"a": 1, "b": 2} + for _, v := range m { + x := v + f := func() int { + return x + } + fns = append(fns, f) + } + for _, fn := range fns { + println(fn()) + } +} + +// Output: +// 1 +// 2 diff --git a/gnovm/tests/files/closure16_a1.gno b/gnovm/tests/files/closure16_a1.gno new file mode 100644 index 00000000000..857cc11d7da --- /dev/null +++ b/gnovm/tests/files/closure16_a1.gno @@ -0,0 +1,19 @@ +package main + +func main() { + var fns []func() int + m := map[string]int{"a": 1, "b": 2} + for _, v := range m { + f := func() int { + return v + } + fns = append(fns, f) + } + for _, fn := range fns { + println(fn()) + } +} + +// Output: +// 1 +// 2 diff --git a/gnovm/tests/files/closure16_b.gno b/gnovm/tests/files/closure16_b.gno new file mode 100644 index 00000000000..5d26a2c6dc0 --- /dev/null +++ b/gnovm/tests/files/closure16_b.gno @@ -0,0 +1,23 @@ +package main + +func main() { + var fns []func() int + s := "hello" + for i, _ := range s { + x := i + f := func() int { + return x + } + fns = append(fns, f) + } + for _, fn := range fns { + println(fn()) + } +} + +// Output: +// 0 +// 1 +// 2 +// 3 +// 4 diff --git a/gnovm/tests/files/closure16_b1.gno b/gnovm/tests/files/closure16_b1.gno new file mode 100644 index 00000000000..a7ddb99504f --- /dev/null +++ b/gnovm/tests/files/closure16_b1.gno @@ -0,0 +1,22 @@ +package main + +func main() { + var fns []func() int + s := "hello" + for i, _ := range s { + f := func() int { + return i + } + fns = append(fns, f) + } + for _, fn := range fns { + println(fn()) + } +} + +// Output: +// 0 +// 1 +// 2 +// 3 +// 4 diff --git a/gnovm/tests/files/closure17_io2.gno.gno b/gnovm/tests/files/closure17_io2.gno.gno new file mode 100644 index 00000000000..24655f5040c --- /dev/null +++ b/gnovm/tests/files/closure17_io2.gno.gno @@ -0,0 +1,21 @@ +package main + +import ( + "fmt" + "io" + "log" + "strings" +) + +func main() { + r := strings.NewReader("Go is a general-purpose language designed with systems programming in mind.") + + b, err := io.ReadAll(r) + if err != nil { + log.Fatal(err) + } + fmt.Printf("%s", b) +} + +// Output: +// Go is a general-purpose language designed with systems programming in mind. diff --git a/gnovm/tests/files/closure17_recover4.gno b/gnovm/tests/files/closure17_recover4.gno new file mode 100644 index 00000000000..5a6da4261a2 --- /dev/null +++ b/gnovm/tests/files/closure17_recover4.gno @@ -0,0 +1,25 @@ +package main + +import "fmt" + +func div(a, b int) (result int) { + defer func() { + r := recover() + + fmt.Printf("r = %#v\n", r) + + if r != nil { + result = 0 + } + }() + + return a / b +} + +func main() { + println(div(30, 2)) +} + +// Output: +// r = +// 15 diff --git a/gnovm/tests/files/closure17_recover6.gno b/gnovm/tests/files/closure17_recover6.gno new file mode 100644 index 00000000000..0b304369764 --- /dev/null +++ b/gnovm/tests/files/closure17_recover6.gno @@ -0,0 +1,30 @@ +package main + +import ( + "errors" +) + +func main() { + println(f(false)) + println(f(true)) +} + +func f(dopanic bool) (err error) { + defer func() { + if x := recover(); x != nil { + err = x.(error) + } + }() + q(dopanic) + return +} + +func q(dopanic bool) { + if dopanic { + panic(errors.New("wtf")) + } +} + +// Output: +// undefined +// wtf diff --git a/gnovm/tests/files/closure17_sort_search_efficiency.gno b/gnovm/tests/files/closure17_sort_search_efficiency.gno new file mode 100644 index 00000000000..6cb1c537055 --- /dev/null +++ b/gnovm/tests/files/closure17_sort_search_efficiency.gno @@ -0,0 +1,22 @@ +package main + +func Search(n int, f func(int) bool) int { + f(1) + return 0 +} + +// TODO: exclude this pattern +func main() { + for x := 0; x < 2; x++ { + count := 0 + println(" first: count: ", count) + Search(1, func(i int) bool { count++; return i >= x }) + println("second: count: ", count) + } +} + +// Output: +// first: count: 0 +// second: count: 1 +// first: count: 0 +// second: count: 1 diff --git a/gnovm/tests/files/closure17_zregexp_stdlibs.gno b/gnovm/tests/files/closure17_zregexp_stdlibs.gno new file mode 100644 index 00000000000..10bb6f937d3 --- /dev/null +++ b/gnovm/tests/files/closure17_zregexp_stdlibs.gno @@ -0,0 +1,19 @@ +// MAXALLOC: 100000000 +// max total allocation of 100 mb. +package main + +import "regexp" + +var reName = regexp.MustCompile(`^[a-z]+[_a-z0-9]*$`) + +func main() { + for j := 0; j < 100; j++ { + if !(reName.MatchString("thisisatestname")) { + panic("error") + } + } + println(true) +} + +// Output: +// true diff --git a/gnovm/tests/files/closure9.gno b/gnovm/tests/files/closure9.gno new file mode 100644 index 00000000000..091e2b0a7c5 --- /dev/null +++ b/gnovm/tests/files/closure9.gno @@ -0,0 +1,25 @@ +package main + +func main() { + var fns []func() int + for i := 0; i < 3; i++ { + x := i + f := func() int { + return x + } + fns = append(fns, f) + } + for _, fn := range fns { + println(fn()) + } +} + +// Go Output: +// 0 +// 1 +// 2 + +// Output: +// 0 +// 1 +// 2 diff --git a/gnovm/tests/files/closure9_a0.gno b/gnovm/tests/files/closure9_a0.gno new file mode 100644 index 00000000000..ac3026072be --- /dev/null +++ b/gnovm/tests/files/closure9_a0.gno @@ -0,0 +1,21 @@ +package main + +func main() { + var fns []func() int + for i := 0; i < 5; i++ { + f := func() int { + return i + } + fns = append(fns, f) + } + for _, fn := range fns { + println(fn()) + } +} + +// Output: +// 0 +// 1 +// 2 +// 3 +// 4 diff --git a/gnovm/tests/files/closure9_a1.gno b/gnovm/tests/files/closure9_a1.gno new file mode 100644 index 00000000000..82ca810b13c --- /dev/null +++ b/gnovm/tests/files/closure9_a1.gno @@ -0,0 +1,20 @@ +package main + +func main() { + var fns []func() int + for i := 0; i < 5; i++ { + x := i + f := func() int { + return x + } + fns = append(fns, f) + } + println(fns[3]()) + println(fns[2]()) + println(fns[0]()) +} + +// Output: +// 3 +// 2 +// 0 diff --git a/gnovm/tests/files/closure9_a2.gno b/gnovm/tests/files/closure9_a2.gno new file mode 100644 index 00000000000..a2c29389b4a --- /dev/null +++ b/gnovm/tests/files/closure9_a2.gno @@ -0,0 +1,30 @@ +package main + +func main() { + var fns []func() int + var x int + for i := 0; i < 5; i++ { + x = i + f := func() int { + return x + } + fns = append(fns, f) + } + for _, fn := range fns { + println(fn()) + } +} + +// Go Output: +// 4 +// 4 +// 4 +// 4 +// 4 + +// Output: +// 4 +// 4 +// 4 +// 4 +// 4 diff --git a/gnovm/tests/files/closure9_b.gno b/gnovm/tests/files/closure9_b.gno new file mode 100644 index 00000000000..0822368fe3a --- /dev/null +++ b/gnovm/tests/files/closure9_b.gno @@ -0,0 +1,22 @@ +package main + +func main() { + var fns []func() int + for i := 0; i < 2; i++ { + x := i + y := 0 + f := func() int { + x += y + x += 1 + return x + } + fns = append(fns, f) + } + for _, fn := range fns { + println(fn()) + } +} + +// Output: +// 1 +// 2 diff --git a/gnovm/tests/files/closure9_c.gno b/gnovm/tests/files/closure9_c.gno new file mode 100644 index 00000000000..e5c61c59089 --- /dev/null +++ b/gnovm/tests/files/closure9_c.gno @@ -0,0 +1,18 @@ +package main + +func main() { + var fns []func() int + for i := 0; i < 2; i++ { + f := func() int { + return i + } + fns = append(fns, f) + } + for _, fn := range fns { + println(fn()) + } +} + +// Output: +// 0 +// 1 diff --git a/gnovm/tests/files/closure9_d.gno b/gnovm/tests/files/closure9_d.gno new file mode 100644 index 00000000000..185961c741f --- /dev/null +++ b/gnovm/tests/files/closure9_d.gno @@ -0,0 +1,29 @@ +package main + +func main() { + var fns []func() int + for i := 0; i < 5; i++ { + x := i + f := func() int { + x := 5 + return x + } + println(x) + fns = append(fns, f) + } + for _, fn := range fns { + println(fn()) + } +} + +// Output: +// 0 +// 1 +// 2 +// 3 +// 4 +// 5 +// 5 +// 5 +// 5 +// 5 diff --git a/gnovm/tests/files/closure9_f.gno b/gnovm/tests/files/closure9_f.gno new file mode 100644 index 00000000000..33143cbe267 --- /dev/null +++ b/gnovm/tests/files/closure9_f.gno @@ -0,0 +1,23 @@ +package main + +func main() { + var fns []func() int + for i := 0; i < 5; i++ { + var x int + f := func() int { + return x + } + x = i + fns = append(fns, f) + } + for _, fn := range fns { + println(fn()) + } +} + +// Output: +// 0 +// 1 +// 2 +// 3 +// 4 diff --git a/gnovm/tests/files/closure9_g.gno b/gnovm/tests/files/closure9_g.gno new file mode 100644 index 00000000000..bd8acbe02a0 --- /dev/null +++ b/gnovm/tests/files/closure9_g.gno @@ -0,0 +1,24 @@ +package main + +func main() { + var fns []func() int + for i := 0; i < 5; i++ { + x := i + { // another block + f := func() int { + return x + } + fns = append(fns, f) + } + } + for _, fn := range fns { + println(fn()) + } +} + +// Output: +// 0 +// 1 +// 2 +// 3 +// 4 diff --git a/gnovm/tests/files/closure9_h.gno b/gnovm/tests/files/closure9_h.gno new file mode 100644 index 00000000000..23cb5ff9f40 --- /dev/null +++ b/gnovm/tests/files/closure9_h.gno @@ -0,0 +1,30 @@ +package main + +func main() { + var fns []func() int + for i := 0; i < 2; i++ { + x := i + for j := 0; j < 2; j++ { + y := j + f := func() int { + return x + y + } + fns = append(fns, f) + } + } + for _, fn := range fns { + println(fn()) + } +} + +// Go Output: +// 0 +// 1 +// 1 +// 2 + +// Output: +// 0 +// 1 +// 1 +// 2 diff --git a/gnovm/tests/files/closure9_h_0.gno b/gnovm/tests/files/closure9_h_0.gno new file mode 100644 index 00000000000..08619554b61 --- /dev/null +++ b/gnovm/tests/files/closure9_h_0.gno @@ -0,0 +1,22 @@ +package main + +func main() { + var fns []func() int + for i := 0; i < 2; i++ { + for j := 0; j < 2; j++ { + f := func() int { + return i + j + } + fns = append(fns, f) + } + } + for _, fn := range fns { + println(fn()) + } +} + +// Output: +// 0 +// 1 +// 1 +// 2 diff --git a/gnovm/tests/files/closure9_i.gno b/gnovm/tests/files/closure9_i.gno new file mode 100644 index 00000000000..42a4d11d862 --- /dev/null +++ b/gnovm/tests/files/closure9_i.gno @@ -0,0 +1,31 @@ +package main + +func main() { + var fns []func() int + var x int + for i := 0; i < 2; i++ { + x = i + for j := 0; j < 2; j++ { + y := j + f := func() int { + return x + y + } + fns = append(fns, f) + } + } + for _, fn := range fns { + println(fn()) + } +} + +// Go Output: +// 1 +// 2 +// 1 +// 2 + +// Output: +// 1 +// 2 +// 1 +// 2 diff --git a/gnovm/tests/files/closure9_j.gno b/gnovm/tests/files/closure9_j.gno new file mode 100644 index 00000000000..926caf5090c --- /dev/null +++ b/gnovm/tests/files/closure9_j.gno @@ -0,0 +1,21 @@ +package main + +func main() { + var fns []func() int + for i := 0; i < 2; i++ { + x := i + y := 0 + f := func() int { + x += y + return x + } + fns = append(fns, f) + } + for _, fn := range fns { + println(fn()) + } +} + +// Output: +// 0 +// 1 diff --git a/gnovm/tests/files/closure_18.gno b/gnovm/tests/files/closure_18.gno new file mode 100644 index 00000000000..a570637bf28 --- /dev/null +++ b/gnovm/tests/files/closure_18.gno @@ -0,0 +1,33 @@ +package main + +func main() { + var fns []func() int + + println("start for loop") + for i := 0; i < 2; i++ { + defer func() { + println("defer") + for _, fn := range fns { + println(fn()) + } + }() + + x := i + f := func() int { + return x + } + + fns = append(fns, f) + } + println("end for loop") +} + +// Output: +// start for loop +// end for loop +// defer +// 0 +// 1 +// defer +// 0 +// 1 diff --git a/gnovm/tests/files/closure_19.gno b/gnovm/tests/files/closure_19.gno new file mode 100644 index 00000000000..a0d86d16403 --- /dev/null +++ b/gnovm/tests/files/closure_19.gno @@ -0,0 +1,26 @@ +package main + +func main() { + var y, counter int + var f []func() + defer func() { + for _, ff := range f { + ff() + } + }() + + // this is actually an implicit for loop +LABEL_1: + if counter == 2 { + return + } + x := y + f = append(f, func() { println(x) }) + y++ + counter++ + goto LABEL_1 +} + +// Output: +// 0 +// 1 diff --git a/gnovm/tests/files/closure_19a.gno b/gnovm/tests/files/closure_19a.gno new file mode 100644 index 00000000000..4f254760d49 --- /dev/null +++ b/gnovm/tests/files/closure_19a.gno @@ -0,0 +1,53 @@ +package main + +import "fmt" + +func main() { + counter0 := 0 + counter1 := 0 + + y := 0 + + var fs []func() + + defer func() { + for _, ff := range fs { + ff() + } + }() + +LOOP_START: + if counter0 < 2 { + counter1 = 0 + fmt.Printf("Outer loop start: counter0=%d\n", counter0) + + NESTED_LOOP_START: + if counter1 < 2 { + fmt.Printf(" Nested loop: counter1=%d\n", counter1) + counter1++ + goto NESTED_LOOP_START + } + + x := y + fs = append(fs, func() { println(x) }) + + fmt.Println("Exiting nested loop") + counter0++ + y++ + goto LOOP_START + } else { + return + } +} + +// Output: +// Outer loop start: counter0=0 +// Nested loop: counter1=0 +// Nested loop: counter1=1 +// Exiting nested loop +// Outer loop start: counter0=1 +// Nested loop: counter1=0 +// Nested loop: counter1=1 +// Exiting nested loop +// 0 +// 1 diff --git a/gnovm/tests/files/closure_19b.gno b/gnovm/tests/files/closure_19b.gno new file mode 100644 index 00000000000..df83c026ac7 --- /dev/null +++ b/gnovm/tests/files/closure_19b.gno @@ -0,0 +1,38 @@ +package main + +func main() { + var y, counter int + var f []func() + defer func() { + for _, ff := range f { + ff() + } + }() + +LABEL_1: + x := y + if counter == 2 { + counter = 0 + goto LABEL_2 + } + f = append(f, func() { println(x) }) + y++ + counter++ + goto LABEL_1 + +LABEL_2: + if counter == 2 { + return + } + z := y + f = append(f, func() { println(z) }) + y++ + counter++ + goto LABEL_2 +} + +// Output: +// 0 +// 1 +// 2 +// 3 diff --git a/gnovm/tests/files/closure_19c.gno b/gnovm/tests/files/closure_19c.gno new file mode 100644 index 00000000000..08a8d0bf3ac --- /dev/null +++ b/gnovm/tests/files/closure_19c.gno @@ -0,0 +1,40 @@ +package main + +func main() { + var y, counter int + var f []func() + defer func() { + for _, ff := range f { + ff() + } + }() + + { + LABEL_1: + x := y + if counter == 2 { + counter = 0 + goto LABEL_2 + } + f = append(f, func() { println(x) }) + y++ + counter++ + goto LABEL_1 + } + +LABEL_2: + if counter == 2 { + return + } + z := y + f = append(f, func() { println(z) }) + y++ + counter++ + goto LABEL_2 +} + +// Output: +// 0 +// 1 +// 2 +// 3 diff --git a/gnovm/tests/files/closure_19d.gno b/gnovm/tests/files/closure_19d.gno new file mode 100644 index 00000000000..0ee80f36e83 --- /dev/null +++ b/gnovm/tests/files/closure_19d.gno @@ -0,0 +1,43 @@ +package main + +var y, counter int +var f []func() + +func main() { +LABEL_1: + x := y + if counter == 2 { + counter = 0 + bar() + for _, ff := range f { // XXX, why defer on this not work + ff() + } + return + } + f = append(f, func() { println(x) }) + y++ + counter++ + goto LABEL_1 +} + +func bar() { + println("---bar---") +LABEL_2: + if counter == 2 { + println("---end---") + return + } + z := y + f = append(f, func() { println(z) }) + y++ + counter++ + goto LABEL_2 +} + +// Output: +// ---bar--- +// ---end--- +// 0 +// 1 +// 2 +// 3 diff --git a/gnovm/tests/files/closure_19e.gno b/gnovm/tests/files/closure_19e.gno new file mode 100644 index 00000000000..ff0d0fe482c --- /dev/null +++ b/gnovm/tests/files/closure_19e.gno @@ -0,0 +1,55 @@ +package main + +import "fmt" + +func main() { + counter0 := 0 + counter1 := 0 + + y := 0 + + var fs []func() + + defer func() { + for _, ff := range fs { + ff() + } + }() + +LOOP_START: + if counter0 < 2 { + x := y + counter1 = 0 + fmt.Printf("Outer loop start: counter0=%d\n", counter0) + + NESTED_LOOP_START: + if counter1 < 2 { + fmt.Printf(" Nested loop: counter1=%d\n", counter1) + fs = append(fs, func() { println(x) }) + + counter1++ + goto NESTED_LOOP_START + } + + fmt.Println("Exiting nested loop") + counter0++ + y++ + goto LOOP_START + } else { + return + } +} + +// Output: +// Outer loop start: counter0=0 +// Nested loop: counter1=0 +// Nested loop: counter1=1 +// Exiting nested loop +// Outer loop start: counter0=1 +// Nested loop: counter1=0 +// Nested loop: counter1=1 +// Exiting nested loop +// 0 +// 0 +// 1 +// 1 diff --git a/gnovm/tests/files/closure_19f.gno b/gnovm/tests/files/closure_19f.gno new file mode 100644 index 00000000000..629df4d6b74 --- /dev/null +++ b/gnovm/tests/files/closure_19f.gno @@ -0,0 +1,55 @@ +package main + +import "fmt" + +func main() { + counter0 := 0 + counter1 := 0 + + y := 0 + + var fs []func() + + defer func() { + for _, ff := range fs { + ff() + } + }() + +LOOP_START: + x := y + if counter0 < 2 { + counter1 = 0 + fmt.Printf("Outer loop start: counter0=%d\n", counter0) + + NESTED_LOOP_START: + if counter1 < 2 { + fmt.Printf(" Nested loop: counter1=%d\n", counter1) + fs = append(fs, func() { println(x) }) + + counter1++ + goto NESTED_LOOP_START + } + + fmt.Println("Exiting nested loop") + counter0++ + y++ + goto LOOP_START + } else { + return + } +} + +// Output: +// Outer loop start: counter0=0 +// Nested loop: counter1=0 +// Nested loop: counter1=1 +// Exiting nested loop +// Outer loop start: counter0=1 +// Nested loop: counter1=0 +// Nested loop: counter1=1 +// Exiting nested loop +// 0 +// 0 +// 1 +// 1 diff --git a/gnovm/tests/files/closure_19g.gno b/gnovm/tests/files/closure_19g.gno new file mode 100644 index 00000000000..e272ec02cfb --- /dev/null +++ b/gnovm/tests/files/closure_19g.gno @@ -0,0 +1,27 @@ +package main + +func main() { + var y, counter int + var f []func() + defer func() { + for _, ff := range f { + ff() + } + }() + + { + LABEL_1: + if counter == 2 { + return + } + x := y + f = append(f, func() { println(x) }) + y++ + counter++ + goto LABEL_1 + } +} + +// Output: +// 0 +// 1 diff --git a/gnovm/tests/files/closure_19h.gno b/gnovm/tests/files/closure_19h.gno new file mode 100644 index 00000000000..4ac715584b7 --- /dev/null +++ b/gnovm/tests/files/closure_19h.gno @@ -0,0 +1,27 @@ +package main + +func main() { + var y, counter int + var f []func() func() int + defer func() { + for _, ff := range f { + println(ff()()) + } + }() + +LABEL_1: + if counter == 2 { + return + } + x := y + f = append(f, func() func() int { + return func() int { return x } + }) + y++ + counter++ + goto LABEL_1 +} + +// Output: +// 0 +// 1 diff --git a/gnovm/tests/files/closure_19i.gno b/gnovm/tests/files/closure_19i.gno new file mode 100644 index 00000000000..ef13f91f886 --- /dev/null +++ b/gnovm/tests/files/closure_19i.gno @@ -0,0 +1,29 @@ +package main + +func main() { + var y, counter int + var f []func() (int, func() int) + defer func() { + for _, ff := range f { + n, f := ff() + println(n + f()) + } + }() + +LABEL_1: + if counter == 2 { + return + } + x := y + z := y + f = append(f, func() (int, func() int) { // NOTE: in this case, outer funcLitExpr is also wrapped in {...} + return z, func() int { return x } + }) + y++ + counter++ + goto LABEL_1 +} + +// Output: +// 0 +// 2 diff --git a/gnovm/tests/files/closure_19j.gno b/gnovm/tests/files/closure_19j.gno new file mode 100644 index 00000000000..731b5cdb219 --- /dev/null +++ b/gnovm/tests/files/closure_19j.gno @@ -0,0 +1,28 @@ +package main + +func main() { + var y, counter int + var f []func() + defer func() { + for _, ff := range f { + ff() + } + }() + + f1 := func() { + LABEL_1: + if counter == 2 { + return + } + x := y + f = append(f, func() { println(x) }) + y++ + counter++ + goto LABEL_1 + } + f1() +} + +// Output: +// 0 +// 1 diff --git a/gnovm/tests/files/closure_19k.gno b/gnovm/tests/files/closure_19k.gno new file mode 100644 index 00000000000..f5a6594acd0 --- /dev/null +++ b/gnovm/tests/files/closure_19k.gno @@ -0,0 +1,30 @@ +package main + +func main() { + var y, counter int + var f []func() + defer func() { + for _, ff := range f { + ff() + } + }() + + for i := 0; i < 2; i++ { + counter = 0 + LABEL_1: + if counter == 2 { + continue + } + x := y + f = append(f, func() { println(x) }) + y++ + counter++ + goto LABEL_1 + } +} + +// Output: +// 0 +// 1 +// 2 +// 3 diff --git a/gnovm/tests/files/closure_19l.gno b/gnovm/tests/files/closure_19l.gno new file mode 100644 index 00000000000..d293bc4ef29 --- /dev/null +++ b/gnovm/tests/files/closure_19l.gno @@ -0,0 +1,25 @@ +package main + +func main() { + var y int + var f []func() + defer func() { + for _, ff := range f { + ff() + } + }() + + for i := 0; i < 2; i++ { + for j := 0; j < 2; j++ { // every inner loop will create a new block too, see 1_05 + x := y + f = append(f, func() { println(x) }) + y++ + } + } +} + +// Output: +// 0 +// 1 +// 2 +// 3 diff --git a/gnovm/tests/files/closure_19m.gno b/gnovm/tests/files/closure_19m.gno new file mode 100644 index 00000000000..acb075e7cbe --- /dev/null +++ b/gnovm/tests/files/closure_19m.gno @@ -0,0 +1,30 @@ +package main + +func main() { + counter1 := 0 + + y := 0 + + var fs []func() + + defer func() { + for _, ff := range fs { + ff() + } + }() + +LOOP: + x := y + if counter1 < 2 { + fs = append(fs, func() { println(x) }) + y++ + counter1++ + goto LOOP + } else { + return + } +} + +// Output: +// 0 +// 1 diff --git a/gnovm/tests/files/closure_19n.gno b/gnovm/tests/files/closure_19n.gno new file mode 100644 index 00000000000..069e10fbfff --- /dev/null +++ b/gnovm/tests/files/closure_19n.gno @@ -0,0 +1,30 @@ +package main + +func main() { + counter1 := 0 + + y := 0 + + var fs []func() + + defer func() { + for _, ff := range fs { + ff() + } + }() + +LOOP: + y + if counter1 < 2 { + fs = append(fs, func() { println(y) }) + y++ + counter1++ + goto LOOP + } else { + return + } +} + +// Output: +// 2 +// 2 diff --git a/gnovm/tests/files/closure_20.gno b/gnovm/tests/files/closure_20.gno new file mode 100644 index 00000000000..e77d7cc481a --- /dev/null +++ b/gnovm/tests/files/closure_20.gno @@ -0,0 +1,65 @@ +package main + +import ( + "fmt" +) + +func main() { + i := 0 + var fs []func() + + defer func() { + for _, ff := range fs { + ff() + } + }() + + if i == 0 { + goto Start + } + +Start: + if i <= 5 { + x := i + fmt.Println("i: ", i) + i++ + fs = append(fs, func() { println(x) }) + + goto Start // Jumps back to the label "Start" + } + + if i <= 10 { + x := i + fmt.Println("Counting past 5:", i) + i++ + fs = append(fs, func() { println(x) }) + goto Start // Another jump back to the label "Start" + } + + fmt.Println("Finished counting") +} + +// Output: +// i: 0 +// i: 1 +// i: 2 +// i: 3 +// i: 4 +// i: 5 +// Counting past 5: 6 +// Counting past 5: 7 +// Counting past 5: 8 +// Counting past 5: 9 +// Counting past 5: 10 +// Finished counting +// 0 +// 1 +// 2 +// 3 +// 4 +// 5 +// 6 +// 7 +// 8 +// 9 +// 10 diff --git a/gnovm/tests/files/closure_9k.gno b/gnovm/tests/files/closure_9k.gno new file mode 100644 index 00000000000..eaef398debe --- /dev/null +++ b/gnovm/tests/files/closure_9k.gno @@ -0,0 +1,23 @@ +package main + +func main() { + var y int + var f []func() + + defer func() { + for _, ff := range f { + ff() + } + }() + + for i := 0; i < 3; i++ { + x := y + f = append(f, func() { println(x) }) + y++ + } +} + +// Output: +// 0 +// 1 +// 2 diff --git a/gnovm/tests/files/closure_9l.gno b/gnovm/tests/files/closure_9l.gno new file mode 100644 index 00000000000..60449d19e4d --- /dev/null +++ b/gnovm/tests/files/closure_9l.gno @@ -0,0 +1,23 @@ +package main + +func main() { + var y int + var f []func() + + defer func() { + for _, ff := range f { + ff() + } + }() + + for i := 0; i < 3; i++ { + x := y + f = append(f, func() { println(x + y) }) + y++ + } +} + +// Output: +// 3 +// 4 +// 5 diff --git a/gnovm/tests/files/closure_9m.gno b/gnovm/tests/files/closure_9m.gno new file mode 100644 index 00000000000..9c1b937c8fe --- /dev/null +++ b/gnovm/tests/files/closure_9m.gno @@ -0,0 +1,28 @@ +package main + +func main() { + var y int + var f []func() + + defer func() { + for _, ff := range f { + ff() + } + }() + + for i := 0; i < 3; i++ { + //{ + // i := i + f = append(f, func() { + x := i // i is loop extern, see 1a3 + println(x) + }) + y++ + //} + } +} + +// Output: +// 0 +// 1 +// 2 diff --git a/gnovm/tests/files/closure_9n.gno b/gnovm/tests/files/closure_9n.gno new file mode 100644 index 00000000000..c1e5b6ca207 --- /dev/null +++ b/gnovm/tests/files/closure_9n.gno @@ -0,0 +1,25 @@ +package main + +func main() { + var y int + var f []func() + + defer func() { + for _, ff := range f { + ff() + } + }() + + for i := 0; i < 3; i++ { + a := i + f = append(f, func() { + println(a) + }) + y++ + } +} + +// Output: +// 0 +// 1 +// 2 diff --git a/gnovm/tests/files/closure_9o.gno b/gnovm/tests/files/closure_9o.gno new file mode 100644 index 00000000000..b3d5f219abf --- /dev/null +++ b/gnovm/tests/files/closure_9o.gno @@ -0,0 +1,26 @@ +package main + +func main() { + var y int + var f []func() + + defer func() { + for _, ff := range f { + ff() + } + }() + + s := []int{1, 2, 3} + for i, v := range s { + f = append(f, func() { + x := i // i is loop extern, see 1a3 + println(x*100 + v) + }) + y++ + } +} + +// Output: +// 1 +// 102 +// 203 diff --git a/gnovm/tests/files/closure_9p.gno b/gnovm/tests/files/closure_9p.gno new file mode 100644 index 00000000000..0dd52ec316f --- /dev/null +++ b/gnovm/tests/files/closure_9p.gno @@ -0,0 +1,25 @@ +package main + +func main() { + var y int + var f []func() + + defer func() { + for _, ff := range f { + ff() + } + }() + + s := []int{1, 2, 3} + for i, v := range s { + f = append(f, func() { + println(i*100 + v) + }) + y++ + } +} + +// Output: +// 1 +// 102 +// 203 diff --git a/gnovm/tests/files/closure_9q.gno b/gnovm/tests/files/closure_9q.gno new file mode 100644 index 00000000000..67e9e04d64b --- /dev/null +++ b/gnovm/tests/files/closure_9q.gno @@ -0,0 +1,26 @@ +package main + +func main() { + var y int + var f []func() + + defer func() { + for _, ff := range f { + ff() + } + }() + + i := 0 + for i = 0; i < 3; i++ { + f = append(f, func() { + x := i // i is loop extern, see 1a3 + println(x) + }) + y++ + } +} + +// Output: +// 3 +// 3 +// 3 diff --git a/gnovm/tests/files/closure_9r.gno b/gnovm/tests/files/closure_9r.gno new file mode 100644 index 00000000000..9fe03ea8feb --- /dev/null +++ b/gnovm/tests/files/closure_9r.gno @@ -0,0 +1,25 @@ +package main + +func main() { + var y int + var f []func() + + defer func() { + for _, ff := range f { + ff() + } + }() + + for i := 0; i < 3; i++ { + x := y + { + f = append(f, func() { println(x) }) + } + y++ + } +} + +// Output: +// 0 +// 1 +// 2 diff --git a/gnovm/tests/files/closure_9s.gno b/gnovm/tests/files/closure_9s.gno new file mode 100644 index 00000000000..9d1506e250b --- /dev/null +++ b/gnovm/tests/files/closure_9s.gno @@ -0,0 +1,24 @@ +package main + +func main() { + var fns []func() int + + for i := 0; i < 5; i++ { + x := i + f := func() int { + return x + } + fns = append(fns, f) + x += 1 + } + for _, fn := range fns { + println(fn()) + } +} + +// Output: +// 1 +// 2 +// 3 +// 4 +// 5 diff --git a/gnovm/tests/files/closure_9t.gno b/gnovm/tests/files/closure_9t.gno new file mode 100644 index 00000000000..1ea6e013b0d --- /dev/null +++ b/gnovm/tests/files/closure_9t.gno @@ -0,0 +1,25 @@ +package main + +func main() { + var fns []func() int + + for i := 0; i < 2; i++ { + x := i + f := func() int { + if true { + if true { + return x + } + } + return 0 + } + fns = append(fns, f) + } + for _, fn := range fns { + println(fn()) + } +} + +// Output: +// 0 +// 1