package goja import ( "github.com/dop251/goja/ast" "github.com/dop251/goja/file" "github.com/dop251/goja/token" "github.com/dop251/goja/unistring" ) func (c *compiler) compileStatement(v ast.Statement, needResult bool) { switch v := v.(type) { case *ast.BlockStatement: c.compileBlockStatement(v, needResult) case *ast.ExpressionStatement: c.compileExpressionStatement(v, needResult) case *ast.VariableStatement: c.compileVariableStatement(v) case *ast.LexicalDeclaration: c.compileLexicalDeclaration(v) case *ast.ReturnStatement: c.compileReturnStatement(v) case *ast.IfStatement: c.compileIfStatement(v, needResult) case *ast.DoWhileStatement: c.compileDoWhileStatement(v, needResult) case *ast.ForStatement: c.compileForStatement(v, needResult) case *ast.ForInStatement: c.compileForInStatement(v, needResult) case *ast.ForOfStatement: c.compileForOfStatement(v, needResult) case *ast.WhileStatement: c.compileWhileStatement(v, needResult) case *ast.BranchStatement: c.compileBranchStatement(v) case *ast.TryStatement: c.compileTryStatement(v, needResult) case *ast.ThrowStatement: c.compileThrowStatement(v) case *ast.SwitchStatement: c.compileSwitchStatement(v, needResult) case *ast.LabelledStatement: c.compileLabeledStatement(v, needResult) case *ast.EmptyStatement: c.compileEmptyStatement(needResult) case *ast.FunctionDeclaration: c.compileStandaloneFunctionDecl(v) // note functions inside blocks are hoisted to the top of the block and are compiled using compileFunctions() case *ast.ClassDeclaration: c.compileClassDeclaration(v) case *ast.WithStatement: c.compileWithStatement(v, needResult) case *ast.DebuggerStatement: default: c.assert(false, int(v.Idx0())-1, "Unknown statement type: %T", v) panic("unreachable") } } func (c *compiler) compileLabeledStatement(v *ast.LabelledStatement, needResult bool) { label := v.Label.Name if c.scope.strict { c.checkIdentifierName(label, int(v.Label.Idx)-1) } for b := c.block; b != nil; b = b.outer { if b.label == label { c.throwSyntaxError(int(v.Label.Idx-1), "Label '%s' has already been declared", label) } } switch s := v.Statement.(type) { case *ast.ForInStatement: c.compileLabeledForInStatement(s, needResult, label) case *ast.ForOfStatement: c.compileLabeledForOfStatement(s, needResult, label) case *ast.ForStatement: c.compileLabeledForStatement(s, needResult, label) case *ast.WhileStatement: c.compileLabeledWhileStatement(s, needResult, label) case *ast.DoWhileStatement: c.compileLabeledDoWhileStatement(s, needResult, label) default: c.compileGenericLabeledStatement(s, needResult, label) } } func (c *compiler) updateEnterBlock(enter *enterBlock) { scope := c.scope stashSize, stackSize := 0, 0 if scope.dynLookup { stashSize = len(scope.bindings) enter.names = scope.makeNamesMap() } else { for _, b := range scope.bindings { if b.inStash { stashSize++ } else { stackSize++ } } } enter.stashSize, enter.stackSize = uint32(stashSize), uint32(stackSize) } func (c *compiler) compileTryStatement(v *ast.TryStatement, needResult bool) { c.block = &block{ typ: blockTry, outer: c.block, } var lp int var bodyNeedResult bool var finallyBreaking *block if v.Finally != nil { lp, finallyBreaking = c.scanStatements(v.Finally.List) } if finallyBreaking != nil { c.block.breaking = finallyBreaking if lp == -1 { bodyNeedResult = finallyBreaking.needResult } } else { bodyNeedResult = needResult } lbl := len(c.p.code) c.emit(nil) if needResult { c.emit(clearResult) } c.compileBlockStatement(v.Body, bodyNeedResult) var catchOffset int if v.Catch != nil { lbl2 := len(c.p.code) // jump over the catch block c.emit(nil) catchOffset = len(c.p.code) - lbl if v.Catch.Parameter != nil { c.block = &block{ typ: blockScope, outer: c.block, } c.newBlockScope() list := v.Catch.Body.List funcs := c.extractFunctions(list) if _, ok := v.Catch.Parameter.(ast.Pattern); ok { // add anonymous binding for the catch parameter, note it must be first c.scope.addBinding(int(v.Catch.Idx0()) - 1) } c.createBindings(v.Catch.Parameter, func(name unistring.String, offset int) { if c.scope.strict { switch name { case "arguments", "eval": c.throwSyntaxError(offset, "Catch variable may not be eval or arguments in strict mode") } } c.scope.bindNameLexical(name, true, offset) }) enter := &enterBlock{} c.emit(enter) if pattern, ok := v.Catch.Parameter.(ast.Pattern); ok { c.scope.bindings[0].emitGet() c.emitPattern(pattern, func(target, init compiledExpr) { c.emitPatternLexicalAssign(target, init) }, false) } for _, decl := range funcs { c.scope.bindNameLexical(decl.Function.Name.Name, true, int(decl.Function.Name.Idx1())-1) } c.compileLexicalDeclarations(list, true) c.compileFunctions(funcs) c.compileStatements(list, bodyNeedResult) c.leaveScopeBlock(enter) if c.scope.dynLookup || c.scope.bindings[0].inStash { c.p.code[lbl+catchOffset] = &enterCatchBlock{ names: enter.names, stashSize: enter.stashSize, stackSize: enter.stackSize, } } else { enter.stackSize-- } c.popScope() } else { c.emit(pop) c.compileBlockStatement(v.Catch.Body, bodyNeedResult) } c.p.code[lbl2] = jump(len(c.p.code) - lbl2) } var finallyOffset int if v.Finally != nil { c.emit(enterFinally{}) finallyOffset = len(c.p.code) - lbl // finallyOffset should not include enterFinally if bodyNeedResult && finallyBreaking != nil && lp == -1 { c.emit(clearResult) } c.compileBlockStatement(v.Finally, false) c.emit(leaveFinally{}) } else { c.emit(leaveTry{}) } c.p.code[lbl] = try{catchOffset: int32(catchOffset), finallyOffset: int32(finallyOffset)} c.leaveBlock() } func (c *compiler) addSrcMap(node ast.Node) { c.p.addSrcMap(int(node.Idx0()) - 1) } func (c *compiler) compileThrowStatement(v *ast.ThrowStatement) { c.compileExpression(v.Argument).emitGetter(true) c.addSrcMap(v) c.emit(throw) } func (c *compiler) compileDoWhileStatement(v *ast.DoWhileStatement, needResult bool) { c.compileLabeledDoWhileStatement(v, needResult, "") } func (c *compiler) compileLabeledDoWhileStatement(v *ast.DoWhileStatement, needResult bool, label unistring.String) { c.block = &block{ typ: blockLoop, outer: c.block, label: label, needResult: needResult, } start := len(c.p.code) c.compileStatement(v.Body, needResult) c.block.cont = len(c.p.code) c.emitExpr(c.compileExpression(v.Test), true) c.emit(jeq(start - len(c.p.code))) c.leaveBlock() } func (c *compiler) compileForStatement(v *ast.ForStatement, needResult bool) { c.compileLabeledForStatement(v, needResult, "") } func (c *compiler) compileForHeadLexDecl(decl *ast.LexicalDeclaration, needResult bool) *enterBlock { c.block = &block{ typ: blockIterScope, outer: c.block, needResult: needResult, } c.newBlockScope() enterIterBlock := &enterBlock{} c.emit(enterIterBlock) c.createLexicalBindings(decl) c.compileLexicalDeclaration(decl) return enterIterBlock } func (c *compiler) compileLabeledForStatement(v *ast.ForStatement, needResult bool, label unistring.String) { loopBlock := &block{ typ: blockLoop, outer: c.block, label: label, needResult: needResult, } c.block = loopBlock var enterIterBlock *enterBlock switch init := v.Initializer.(type) { case nil: // no-op case *ast.ForLoopInitializerLexicalDecl: enterIterBlock = c.compileForHeadLexDecl(&init.LexicalDeclaration, needResult) case *ast.ForLoopInitializerVarDeclList: for _, expr := range init.List { c.compileVarBinding(expr) } case *ast.ForLoopInitializerExpression: c.compileExpression(init.Expression).emitGetter(false) default: c.assert(false, int(v.For)-1, "Unsupported for loop initializer: %T", init) panic("unreachable") } if needResult { c.emit(clearResult) // initial result } if enterIterBlock != nil { c.emit(jump(1)) } start := len(c.p.code) var j int testConst := false if v.Test != nil { expr := c.compileExpression(v.Test) if expr.constant() { r, ex := c.evalConst(expr) if ex == nil { if r.ToBoolean() { testConst = true } else { leave := c.enterDummyMode() c.compileStatement(v.Body, false) if v.Update != nil { c.compileExpression(v.Update).emitGetter(false) } leave() goto end } } else { expr.addSrcMap() c.emitThrow(ex.val) goto end } } else { expr.emitGetter(true) j = len(c.p.code) c.emit(nil) } } if needResult { c.emit(clearResult) } c.compileStatement(v.Body, needResult) loopBlock.cont = len(c.p.code) if enterIterBlock != nil { c.emit(jump(1)) } if v.Update != nil { c.compileExpression(v.Update).emitGetter(false) } if enterIterBlock != nil { if c.scope.needStash || c.scope.isDynamic() { c.p.code[start-1] = copyStash{} c.p.code[loopBlock.cont] = copyStash{} } else { if l := len(c.p.code); l > loopBlock.cont { loopBlock.cont++ } else { c.p.code = c.p.code[:l-1] } } } c.emit(jump(start - len(c.p.code))) if v.Test != nil { if !testConst { c.p.code[j] = jne(len(c.p.code) - j) } } end: if enterIterBlock != nil { c.leaveScopeBlock(enterIterBlock) c.popScope() } c.leaveBlock() } func (c *compiler) compileForInStatement(v *ast.ForInStatement, needResult bool) { c.compileLabeledForInStatement(v, needResult, "") } func (c *compiler) compileForInto(into ast.ForInto, needResult bool) (enter *enterBlock) { switch into := into.(type) { case *ast.ForIntoExpression: c.compileExpression(into.Expression).emitSetter(&c.enumGetExpr, false) case *ast.ForIntoVar: if c.scope.strict && into.Binding.Initializer != nil { c.throwSyntaxError(int(into.Binding.Initializer.Idx0())-1, "for-in loop variable declaration may not have an initializer.") } switch target := into.Binding.Target.(type) { case *ast.Identifier: c.compileIdentifierExpression(target).emitSetter(&c.enumGetExpr, false) case ast.Pattern: c.emit(enumGet) c.emitPattern(target, c.emitPatternVarAssign, false) default: c.throwSyntaxError(int(target.Idx0()-1), "unsupported for-in var target: %T", target) } case *ast.ForDeclaration: c.block = &block{ typ: blockIterScope, outer: c.block, needResult: needResult, } c.newBlockScope() enter = &enterBlock{} c.emit(enter) switch target := into.Target.(type) { case *ast.Identifier: b := c.createLexicalIdBinding(target.Name, into.IsConst, int(into.Idx)-1) c.emit(enumGet) b.emitInitP() case ast.Pattern: c.createLexicalBinding(target, into.IsConst) c.emit(enumGet) c.emitPattern(target, func(target, init compiledExpr) { c.emitPatternLexicalAssign(target, init) }, false) default: c.assert(false, int(into.Idx)-1, "Unsupported ForBinding: %T", into.Target) } default: c.assert(false, int(into.Idx0())-1, "Unsupported for-into: %T", into) panic("unreachable") } return } func (c *compiler) compileLabeledForInOfStatement(into ast.ForInto, source ast.Expression, body ast.Statement, iter, needResult bool, label unistring.String) { c.block = &block{ typ: blockLoopEnum, outer: c.block, label: label, needResult: needResult, } enterPos := -1 if forDecl, ok := into.(*ast.ForDeclaration); ok { c.block = &block{ typ: blockScope, outer: c.block, needResult: false, } c.newBlockScope() enterPos = len(c.p.code) c.emit(jump(1)) c.createLexicalBinding(forDecl.Target, forDecl.IsConst) } c.compileExpression(source).emitGetter(true) if enterPos != -1 { s := c.scope used := len(c.block.breaks) > 0 || s.isDynamic() if !used { for _, b := range s.bindings { if b.useCount() > 0 { used = true break } } } if used { // We need the stack untouched because it contains the source. // This is not the most optimal way, but it's an edge case, hopefully quite rare. for _, b := range s.bindings { b.moveToStash() } enter := &enterBlock{} c.p.code[enterPos] = enter c.leaveScopeBlock(enter) } else { c.block = c.block.outer } c.popScope() } if iter { c.emit(iterateP) } else { c.emit(enumerate) } if needResult { c.emit(clearResult) } start := len(c.p.code) c.block.cont = start c.emit(nil) enterIterBlock := c.compileForInto(into, needResult) if needResult { c.emit(clearResult) } c.compileStatement(body, needResult) if enterIterBlock != nil { c.leaveScopeBlock(enterIterBlock) c.popScope() } c.emit(jump(start - len(c.p.code))) if iter { c.p.code[start] = iterNext(len(c.p.code) - start) } else { c.p.code[start] = enumNext(len(c.p.code) - start) } c.emit(enumPop, jump(2)) c.leaveBlock() c.emit(enumPopClose) } func (c *compiler) compileLabeledForInStatement(v *ast.ForInStatement, needResult bool, label unistring.String) { c.compileLabeledForInOfStatement(v.Into, v.Source, v.Body, false, needResult, label) } func (c *compiler) compileForOfStatement(v *ast.ForOfStatement, needResult bool) { c.compileLabeledForOfStatement(v, needResult, "") } func (c *compiler) compileLabeledForOfStatement(v *ast.ForOfStatement, needResult bool, label unistring.String) { c.compileLabeledForInOfStatement(v.Into, v.Source, v.Body, true, needResult, label) } func (c *compiler) compileWhileStatement(v *ast.WhileStatement, needResult bool) { c.compileLabeledWhileStatement(v, needResult, "") } func (c *compiler) compileLabeledWhileStatement(v *ast.WhileStatement, needResult bool, label unistring.String) { c.block = &block{ typ: blockLoop, outer: c.block, label: label, needResult: needResult, } if needResult { c.emit(clearResult) } start := len(c.p.code) c.block.cont = start expr := c.compileExpression(v.Test) testTrue := false var j int if expr.constant() { if t, ex := c.evalConst(expr); ex == nil { if t.ToBoolean() { testTrue = true } else { c.compileStatementDummy(v.Body) goto end } } else { c.emitThrow(ex.val) goto end } } else { expr.emitGetter(true) j = len(c.p.code) c.emit(nil) } if needResult { c.emit(clearResult) } c.compileStatement(v.Body, needResult) c.emit(jump(start - len(c.p.code))) if !testTrue { c.p.code[j] = jne(len(c.p.code) - j) } end: c.leaveBlock() } func (c *compiler) compileEmptyStatement(needResult bool) { if needResult { c.emit(clearResult) } } func (c *compiler) compileBranchStatement(v *ast.BranchStatement) { switch v.Token { case token.BREAK: c.compileBreak(v.Label, v.Idx) case token.CONTINUE: c.compileContinue(v.Label, v.Idx) default: c.assert(false, int(v.Idx0())-1, "Unknown branch statement token: %s", v.Token.String()) panic("unreachable") } } func (c *compiler) findBranchBlock(st *ast.BranchStatement) *block { switch st.Token { case token.BREAK: return c.findBreakBlock(st.Label, true) case token.CONTINUE: return c.findBreakBlock(st.Label, false) } return nil } func (c *compiler) findBreakBlock(label *ast.Identifier, isBreak bool) (res *block) { if label != nil { var found *block for b := c.block; b != nil; b = b.outer { if res == nil { if bb := b.breaking; bb != nil { res = bb if isBreak { return } } } if b.label == label.Name { found = b break } } if !isBreak && found != nil && found.typ != blockLoop && found.typ != blockLoopEnum { c.throwSyntaxError(int(label.Idx)-1, "Illegal continue statement: '%s' does not denote an iteration statement", label.Name) } if res == nil { res = found } } else { // find the nearest loop or switch (if break) L: for b := c.block; b != nil; b = b.outer { if bb := b.breaking; bb != nil { return bb } switch b.typ { case blockLoop, blockLoopEnum: res = b break L case blockSwitch: if isBreak { res = b break L } } } } return } func (c *compiler) emitBlockExitCode(label *ast.Identifier, idx file.Idx, isBreak bool) *block { block := c.findBreakBlock(label, isBreak) if block == nil { c.throwSyntaxError(int(idx)-1, "Could not find block") panic("unreachable") } contForLoop := !isBreak && block.typ == blockLoop L: for b := c.block; b != block; b = b.outer { switch b.typ { case blockIterScope: // blockIterScope in 'for' loops is shared across iterations, so // continue should not pop it. if contForLoop && b.outer == block { break L } fallthrough case blockScope: b.breaks = append(b.breaks, len(c.p.code)) c.emit(nil) case blockTry: c.emit(leaveTry{}) case blockWith: c.emit(leaveWith) case blockLoopEnum: c.emit(enumPopClose) } } return block } func (c *compiler) compileBreak(label *ast.Identifier, idx file.Idx) { block := c.emitBlockExitCode(label, idx, true) block.breaks = append(block.breaks, len(c.p.code)) c.emit(nil) } func (c *compiler) compileContinue(label *ast.Identifier, idx file.Idx) { block := c.emitBlockExitCode(label, idx, false) block.conts = append(block.conts, len(c.p.code)) c.emit(nil) } func (c *compiler) compileIfBody(s ast.Statement, needResult bool) { if !c.scope.strict { if s, ok := s.(*ast.FunctionDeclaration); ok && !s.Function.Async && !s.Function.Generator { c.compileFunction(s) if needResult { c.emit(clearResult) } return } } c.compileStatement(s, needResult) } func (c *compiler) compileIfBodyDummy(s ast.Statement) { leave := c.enterDummyMode() defer leave() c.compileIfBody(s, false) } func (c *compiler) compileIfStatement(v *ast.IfStatement, needResult bool) { test := c.compileExpression(v.Test) if needResult { c.emit(clearResult) } if test.constant() { r, ex := c.evalConst(test) if ex != nil { test.addSrcMap() c.emitThrow(ex.val) return } if r.ToBoolean() { c.compileIfBody(v.Consequent, needResult) if v.Alternate != nil { c.compileIfBodyDummy(v.Alternate) } } else { c.compileIfBodyDummy(v.Consequent) if v.Alternate != nil { c.compileIfBody(v.Alternate, needResult) } else { if needResult { c.emit(clearResult) } } } return } test.emitGetter(true) jmp := len(c.p.code) c.emit(nil) c.compileIfBody(v.Consequent, needResult) if v.Alternate != nil { jmp1 := len(c.p.code) c.emit(nil) c.p.code[jmp] = jne(len(c.p.code) - jmp) c.compileIfBody(v.Alternate, needResult) c.p.code[jmp1] = jump(len(c.p.code) - jmp1) } else { if needResult { c.emit(jump(2)) c.p.code[jmp] = jne(len(c.p.code) - jmp) c.emit(clearResult) } else { c.p.code[jmp] = jne(len(c.p.code) - jmp) } } } func (c *compiler) compileReturnStatement(v *ast.ReturnStatement) { if s := c.scope.nearestFunction(); s != nil && s.funcType == funcClsInit { c.throwSyntaxError(int(v.Return)-1, "Illegal return statement") } if v.Argument != nil { c.emitExpr(c.compileExpression(v.Argument), true) } else { c.emit(loadUndef) } for b := c.block; b != nil; b = b.outer { switch b.typ { case blockTry: c.emit(saveResult, leaveTry{}, loadResult) case blockLoopEnum: c.emit(enumPopClose) } } if s := c.scope.nearestFunction(); s != nil && s.funcType == funcDerivedCtor { b := s.boundNames[thisBindingName] c.assert(b != nil, int(v.Return)-1, "Derived constructor, but no 'this' binding") b.markAccessPoint() } c.emit(ret) } func (c *compiler) checkVarConflict(name unistring.String, offset int) { for sc := c.scope; sc != nil; sc = sc.outer { if b, exists := sc.boundNames[name]; exists && !b.isVar && !(b.isArg && sc != c.scope) { c.throwSyntaxError(offset, "Identifier '%s' has already been declared", name) } if sc.isFunction() { break } } } func (c *compiler) emitVarAssign(name unistring.String, offset int, init compiledExpr) { c.checkVarConflict(name, offset) if init != nil { b, noDyn := c.scope.lookupName(name) if noDyn { c.emitNamedOrConst(init, name) c.p.addSrcMap(offset) b.emitInitP() } else { c.emitVarRef(name, offset, b) c.emitNamedOrConst(init, name) c.p.addSrcMap(offset) c.emit(initValueP) } } } func (c *compiler) compileVarBinding(expr *ast.Binding) { switch target := expr.Target.(type) { case *ast.Identifier: c.emitVarAssign(target.Name, int(target.Idx)-1, c.compileExpression(expr.Initializer)) case ast.Pattern: c.compileExpression(expr.Initializer).emitGetter(true) c.emitPattern(target, c.emitPatternVarAssign, false) default: c.throwSyntaxError(int(target.Idx0()-1), "unsupported variable binding target: %T", target) } } func (c *compiler) emitLexicalAssign(name unistring.String, offset int, init compiledExpr) { b := c.scope.boundNames[name] c.assert(b != nil, offset, "Lexical declaration for an unbound name") if init != nil { c.emitNamedOrConst(init, name) c.p.addSrcMap(offset) } else { if b.isConst { c.throwSyntaxError(offset, "Missing initializer in const declaration") } c.emit(loadUndef) } b.emitInitP() } func (c *compiler) emitPatternVarAssign(target, init compiledExpr) { id := target.(*compiledIdentifierExpr) c.emitVarAssign(id.name, id.offset, init) } func (c *compiler) emitPatternLexicalAssign(target, init compiledExpr) { id := target.(*compiledIdentifierExpr) c.emitLexicalAssign(id.name, id.offset, init) } func (c *compiler) emitPatternAssign(target, init compiledExpr) { if id, ok := target.(*compiledIdentifierExpr); ok { b, noDyn := c.scope.lookupName(id.name) if noDyn { c.emitNamedOrConst(init, id.name) b.emitSetP() } else { c.emitVarRef(id.name, id.offset, b) c.emitNamedOrConst(init, id.name) c.emit(putValueP) } } else { target.emitRef() c.emitExpr(init, true) c.emit(putValueP) } } func (c *compiler) compileLexicalBinding(expr *ast.Binding) { switch target := expr.Target.(type) { case *ast.Identifier: c.emitLexicalAssign(target.Name, int(target.Idx)-1, c.compileExpression(expr.Initializer)) case ast.Pattern: c.compileExpression(expr.Initializer).emitGetter(true) c.emitPattern(target, func(target, init compiledExpr) { c.emitPatternLexicalAssign(target, init) }, false) default: c.throwSyntaxError(int(target.Idx0()-1), "unsupported lexical binding target: %T", target) } } func (c *compiler) compileVariableStatement(v *ast.VariableStatement) { for _, expr := range v.List { c.compileVarBinding(expr) } } func (c *compiler) compileLexicalDeclaration(v *ast.LexicalDeclaration) { for _, e := range v.List { c.compileLexicalBinding(e) } } func (c *compiler) isEmptyResult(st ast.Statement) bool { switch st := st.(type) { case *ast.EmptyStatement, *ast.VariableStatement, *ast.LexicalDeclaration, *ast.FunctionDeclaration, *ast.ClassDeclaration, *ast.BranchStatement, *ast.DebuggerStatement: return true case *ast.LabelledStatement: return c.isEmptyResult(st.Statement) case *ast.BlockStatement: for _, s := range st.List { if _, ok := s.(*ast.BranchStatement); ok { return true } if !c.isEmptyResult(s) { return false } } return true } return false } func (c *compiler) scanStatements(list []ast.Statement) (lastProducingIdx int, breakingBlock *block) { lastProducingIdx = -1 for i, st := range list { if bs, ok := st.(*ast.BranchStatement); ok { if blk := c.findBranchBlock(bs); blk != nil { breakingBlock = blk } break } if !c.isEmptyResult(st) { lastProducingIdx = i } } return } func (c *compiler) compileStatementsNeedResult(list []ast.Statement, lastProducingIdx int) { if lastProducingIdx >= 0 { for _, st := range list[:lastProducingIdx] { if _, ok := st.(*ast.FunctionDeclaration); ok { continue } c.compileStatement(st, false) } c.compileStatement(list[lastProducingIdx], true) } var leave func() defer func() { if leave != nil { leave() } }() for _, st := range list[lastProducingIdx+1:] { if _, ok := st.(*ast.FunctionDeclaration); ok { continue } c.compileStatement(st, false) if leave == nil { if _, ok := st.(*ast.BranchStatement); ok { leave = c.enterDummyMode() } } } } func (c *compiler) compileStatements(list []ast.Statement, needResult bool) { lastProducingIdx, blk := c.scanStatements(list) if blk != nil { needResult = blk.needResult } if needResult { c.compileStatementsNeedResult(list, lastProducingIdx) return } for _, st := range list { if _, ok := st.(*ast.FunctionDeclaration); ok { continue } c.compileStatement(st, false) } } func (c *compiler) compileGenericLabeledStatement(v ast.Statement, needResult bool, label unistring.String) { c.block = &block{ typ: blockLabel, outer: c.block, label: label, needResult: needResult, } c.compileStatement(v, needResult) c.leaveBlock() } func (c *compiler) compileBlockStatement(v *ast.BlockStatement, needResult bool) { var scopeDeclared bool funcs := c.extractFunctions(v.List) if len(funcs) > 0 { c.newBlockScope() scopeDeclared = true } c.createFunctionBindings(funcs) scopeDeclared = c.compileLexicalDeclarations(v.List, scopeDeclared) var enter *enterBlock if scopeDeclared { c.block = &block{ outer: c.block, typ: blockScope, needResult: needResult, } enter = &enterBlock{} c.emit(enter) } c.compileFunctions(funcs) c.compileStatements(v.List, needResult) if scopeDeclared { c.leaveScopeBlock(enter) c.popScope() } } func (c *compiler) compileExpressionStatement(v *ast.ExpressionStatement, needResult bool) { c.emitExpr(c.compileExpression(v.Expression), needResult) if needResult { c.emit(saveResult) } } func (c *compiler) compileWithStatement(v *ast.WithStatement, needResult bool) { if c.scope.strict { c.throwSyntaxError(int(v.With)-1, "Strict mode code may not include a with statement") return } c.compileExpression(v.Object).emitGetter(true) c.emit(enterWith) c.block = &block{ outer: c.block, typ: blockWith, needResult: needResult, } c.newBlockScope() c.scope.dynamic = true c.compileStatement(v.Body, needResult) c.emit(leaveWith) c.leaveBlock() c.popScope() } func (c *compiler) compileSwitchStatement(v *ast.SwitchStatement, needResult bool) { c.block = &block{ typ: blockSwitch, outer: c.block, needResult: needResult, } c.compileExpression(v.Discriminant).emitGetter(true) var funcs []*ast.FunctionDeclaration for _, s := range v.Body { f := c.extractFunctions(s.Consequent) funcs = append(funcs, f...) } var scopeDeclared bool if len(funcs) > 0 { c.newBlockScope() scopeDeclared = true c.createFunctionBindings(funcs) } for _, s := range v.Body { scopeDeclared = c.compileLexicalDeclarations(s.Consequent, scopeDeclared) } var enter *enterBlock var db *binding if scopeDeclared { c.block = &block{ typ: blockScope, outer: c.block, needResult: needResult, } enter = &enterBlock{} c.emit(enter) // create anonymous variable for the discriminant bindings := c.scope.bindings var bb []*binding if cap(bindings) == len(bindings) { bb = make([]*binding, len(bindings)+1) } else { bb = bindings[:len(bindings)+1] } copy(bb[1:], bindings) db = &binding{ scope: c.scope, isConst: true, isStrict: true, } bb[0] = db c.scope.bindings = bb } c.compileFunctions(funcs) if needResult { c.emit(clearResult) } jumps := make([]int, len(v.Body)) for i, s := range v.Body { if s.Test != nil { if db != nil { db.emitGet() } else { c.emit(dup) } c.compileExpression(s.Test).emitGetter(true) c.emit(op_strict_eq) if db != nil { c.emit(jne(2)) } else { c.emit(jne(3), pop) } jumps[i] = len(c.p.code) c.emit(nil) } } if db == nil { c.emit(pop) } jumpNoMatch := -1 if v.Default != -1 { if v.Default != 0 { jumps[v.Default] = len(c.p.code) c.emit(nil) } } else { jumpNoMatch = len(c.p.code) c.emit(nil) } for i, s := range v.Body { if s.Test != nil || i != 0 { c.p.code[jumps[i]] = jump(len(c.p.code) - jumps[i]) } c.compileStatements(s.Consequent, needResult) } if jumpNoMatch != -1 { c.p.code[jumpNoMatch] = jump(len(c.p.code) - jumpNoMatch) } if enter != nil { c.leaveScopeBlock(enter) enter.stackSize-- c.popScope() } c.leaveBlock() } func (c *compiler) compileClassDeclaration(v *ast.ClassDeclaration) { c.emitLexicalAssign(v.Class.Name.Name, int(v.Class.Class)-1, c.compileClassLiteral(v.Class, false)) }