794 lines
18 KiB
Go
794 lines
18 KiB
Go
package goja
|
|
|
|
import (
|
|
"fmt"
|
|
"github.com/dop251/goja/ast"
|
|
"github.com/dop251/goja/file"
|
|
"github.com/dop251/goja/token"
|
|
"strconv"
|
|
)
|
|
|
|
func (c *compiler) compileStatement(v ast.Statement, needResult bool) {
|
|
// log.Printf("compileStatement(): %T", v)
|
|
|
|
switch v := v.(type) {
|
|
case *ast.BlockStatement:
|
|
c.compileBlockStatement(v, needResult)
|
|
case *ast.ExpressionStatement:
|
|
c.compileExpressionStatement(v, needResult)
|
|
case *ast.VariableStatement:
|
|
c.compileVariableStatement(v, needResult)
|
|
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.WhileStatement:
|
|
c.compileWhileStatement(v, needResult)
|
|
case *ast.BranchStatement:
|
|
c.compileBranchStatement(v, needResult)
|
|
case *ast.TryStatement:
|
|
c.compileTryStatement(v)
|
|
if needResult {
|
|
c.emit(loadUndef)
|
|
}
|
|
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.WithStatement:
|
|
c.compileWithStatement(v, needResult)
|
|
case *ast.DebuggerStatement:
|
|
default:
|
|
panic(fmt.Errorf("Unknown statement type: %T", v))
|
|
}
|
|
}
|
|
|
|
func (c *compiler) compileLabeledStatement(v *ast.LabelledStatement, needResult bool) {
|
|
label := v.Label.Name
|
|
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.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(v.Statement, needResult, label)
|
|
}
|
|
}
|
|
|
|
func (c *compiler) compileTryStatement(v *ast.TryStatement) {
|
|
if c.scope.strict && v.Catch != nil {
|
|
switch v.Catch.Parameter.Name {
|
|
case "arguments", "eval":
|
|
c.throwSyntaxError(int(v.Catch.Parameter.Idx)-1, "Catch variable may not be eval or arguments in strict mode")
|
|
}
|
|
}
|
|
c.block = &block{
|
|
typ: blockTry,
|
|
outer: c.block,
|
|
}
|
|
lbl := len(c.p.code)
|
|
c.emit(nil)
|
|
c.compileStatement(v.Body, false)
|
|
c.emit(halt)
|
|
lbl2 := len(c.p.code)
|
|
c.emit(nil)
|
|
var catchOffset int
|
|
dynamicCatch := true
|
|
if v.Catch != nil {
|
|
dyn := nearestNonLexical(c.scope).dynamic
|
|
accessed := c.scope.accessed
|
|
c.newScope()
|
|
c.scope.bindName(v.Catch.Parameter.Name)
|
|
c.scope.lexical = true
|
|
start := len(c.p.code)
|
|
c.emit(nil)
|
|
catchOffset = len(c.p.code) - lbl
|
|
c.emit(enterCatch(v.Catch.Parameter.Name))
|
|
c.compileStatement(v.Catch.Body, false)
|
|
dyn1 := c.scope.dynamic
|
|
accessed1 := c.scope.accessed
|
|
c.popScope()
|
|
if !dyn && !dyn1 && !accessed1 {
|
|
c.scope.accessed = accessed
|
|
dynamicCatch = false
|
|
code := c.p.code[start+1:]
|
|
m := make(map[uint32]uint32)
|
|
remap := func(instr uint32) uint32 {
|
|
level := instr >> 24
|
|
idx := instr & 0x00FFFFFF
|
|
if level > 0 {
|
|
level--
|
|
return (level << 24) | idx
|
|
} else {
|
|
// remap
|
|
newIdx, exists := m[idx]
|
|
if !exists {
|
|
exname := " __tmp" + strconv.Itoa(c.scope.lastFreeTmp)
|
|
c.scope.lastFreeTmp++
|
|
newIdx, _ = c.scope.bindName(exname)
|
|
m[idx] = newIdx
|
|
}
|
|
return newIdx
|
|
}
|
|
}
|
|
for pc, instr := range code {
|
|
switch instr := instr.(type) {
|
|
case getLocal:
|
|
code[pc] = getLocal(remap(uint32(instr)))
|
|
case setLocal:
|
|
code[pc] = setLocal(remap(uint32(instr)))
|
|
case setLocalP:
|
|
code[pc] = setLocalP(remap(uint32(instr)))
|
|
}
|
|
}
|
|
if catchVarIdx, exists := m[0]; exists {
|
|
c.p.code[start] = setLocal(catchVarIdx)
|
|
c.p.code[start+1] = pop
|
|
catchOffset--
|
|
} else {
|
|
c.p.code[start+1] = nil
|
|
catchOffset++
|
|
}
|
|
} else {
|
|
c.scope.accessed = true
|
|
}
|
|
|
|
/*
|
|
if true/*sc.dynamic/ {
|
|
dynamicCatch = true
|
|
c.scope.accessed = true
|
|
c.newScope()
|
|
c.scope.bindName(v.Catch.Parameter.Name)
|
|
c.scope.lexical = true
|
|
c.emit(enterCatch(v.Catch.Parameter.Name))
|
|
c.compileStatement(v.Catch.Body, false)
|
|
c.popScope()
|
|
} else {
|
|
exname := " __tmp" + strconv.Itoa(c.scope.lastFreeTmp)
|
|
c.scope.lastFreeTmp++
|
|
catchVarIdx, _ := c.scope.bindName(exname)
|
|
c.emit(setLocal(catchVarIdx), pop)
|
|
saved, wasSaved := c.scope.namesMap[v.Catch.Parameter.Name]
|
|
c.scope.namesMap[v.Catch.Parameter.Name] = exname
|
|
c.compileStatement(v.Catch.Body, false)
|
|
if wasSaved {
|
|
c.scope.namesMap[v.Catch.Parameter.Name] = saved
|
|
} else {
|
|
delete(c.scope.namesMap, v.Catch.Parameter.Name)
|
|
}
|
|
c.scope.lastFreeTmp--
|
|
}*/
|
|
c.emit(halt)
|
|
}
|
|
var finallyOffset int
|
|
if v.Finally != nil {
|
|
lbl1 := len(c.p.code)
|
|
c.emit(nil)
|
|
finallyOffset = len(c.p.code) - lbl
|
|
c.compileStatement(v.Finally, false)
|
|
c.emit(halt, retFinally)
|
|
c.p.code[lbl1] = jump(len(c.p.code) - lbl1)
|
|
}
|
|
c.p.code[lbl] = try{catchOffset: int32(catchOffset), finallyOffset: int32(finallyOffset), dynamic: dynamicCatch}
|
|
c.p.code[lbl2] = jump(len(c.p.code) - lbl2)
|
|
c.leaveBlock()
|
|
}
|
|
|
|
func (c *compiler) compileThrowStatement(v *ast.ThrowStatement) {
|
|
//c.p.srcMap = append(c.p.srcMap, srcMapItem{pc: len(c.p.code), srcPos: int(v.Throw) - 1})
|
|
c.compileExpression(v.Argument).emitGetter(true)
|
|
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 string) {
|
|
c.block = &block{
|
|
typ: blockLoop,
|
|
outer: c.block,
|
|
label: label,
|
|
needResult: needResult,
|
|
}
|
|
|
|
if needResult {
|
|
c.emit(jump(2))
|
|
}
|
|
start := len(c.p.code)
|
|
if needResult {
|
|
c.emit(pop)
|
|
}
|
|
c.markBlockStart()
|
|
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) compileLabeledForStatement(v *ast.ForStatement, needResult bool, label string) {
|
|
c.block = &block{
|
|
typ: blockLoop,
|
|
outer: c.block,
|
|
label: label,
|
|
needResult: needResult,
|
|
}
|
|
|
|
if v.Initializer != nil {
|
|
c.compileExpression(v.Initializer).emitGetter(false)
|
|
}
|
|
if needResult {
|
|
c.emit(loadUndef) // initial result
|
|
}
|
|
start := len(c.p.code)
|
|
c.markBlockStart()
|
|
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 {
|
|
// TODO: Properly implement dummy compilation (no garbage in block, scope, etc..)
|
|
/*
|
|
p := c.p
|
|
c.p = &program{}
|
|
c.compileStatement(v.Body, false)
|
|
if v.Update != nil {
|
|
c.compileExpression(v.Update).emitGetter(false)
|
|
}
|
|
c.p = p*/
|
|
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(pop) // remove last result
|
|
}
|
|
c.markBlockStart()
|
|
c.compileStatement(v.Body, needResult)
|
|
c.block.cont = len(c.p.code)
|
|
if v.Update != nil {
|
|
c.compileExpression(v.Update).emitGetter(false)
|
|
}
|
|
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:
|
|
c.leaveBlock()
|
|
c.markBlockStart()
|
|
}
|
|
|
|
func (c *compiler) compileForInStatement(v *ast.ForInStatement, needResult bool) {
|
|
c.compileLabeledForInStatement(v, needResult, "")
|
|
}
|
|
|
|
func (c *compiler) compileLabeledForInStatement(v *ast.ForInStatement, needResult bool, label string) {
|
|
c.block = &block{
|
|
typ: blockLoop,
|
|
outer: c.block,
|
|
label: label,
|
|
needResult: needResult,
|
|
}
|
|
|
|
c.compileExpression(v.Source).emitGetter(true)
|
|
c.emit(enumerate)
|
|
if needResult {
|
|
c.emit(loadUndef)
|
|
}
|
|
start := len(c.p.code)
|
|
c.markBlockStart()
|
|
c.block.cont = start
|
|
c.emit(nil)
|
|
c.compileExpression(v.Into).emitSetter(&c.enumGetExpr)
|
|
c.emit(pop)
|
|
if needResult {
|
|
c.emit(pop) // remove last result
|
|
}
|
|
c.markBlockStart()
|
|
c.compileStatement(v.Body, needResult)
|
|
c.emit(jump(start - len(c.p.code)))
|
|
c.p.code[start] = enumNext(len(c.p.code) - start)
|
|
c.leaveBlock()
|
|
c.markBlockStart()
|
|
c.emit(enumPop)
|
|
}
|
|
|
|
func (c *compiler) compileWhileStatement(v *ast.WhileStatement, needResult bool) {
|
|
c.compileLabeledWhileStatement(v, needResult, "")
|
|
}
|
|
|
|
func (c *compiler) compileLabeledWhileStatement(v *ast.WhileStatement, needResult bool, label string) {
|
|
c.block = &block{
|
|
typ: blockLoop,
|
|
outer: c.block,
|
|
label: label,
|
|
needResult: needResult,
|
|
}
|
|
|
|
if needResult {
|
|
c.emit(loadUndef)
|
|
}
|
|
start := len(c.p.code)
|
|
c.markBlockStart()
|
|
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 {
|
|
p := c.p
|
|
c.p = &Program{}
|
|
c.compileStatement(v.Body, false)
|
|
c.p = p
|
|
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(pop)
|
|
}
|
|
c.markBlockStart()
|
|
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()
|
|
c.markBlockStart()
|
|
}
|
|
|
|
func (c *compiler) compileEmptyStatement(needResult bool) {
|
|
if needResult {
|
|
if len(c.p.code) == c.blockStart {
|
|
// first statement in block, use undefined as result
|
|
c.emit(loadUndef)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (c *compiler) compileBranchStatement(v *ast.BranchStatement, needResult bool) {
|
|
switch v.Token {
|
|
case token.BREAK:
|
|
c.compileBreak(v.Label, v.Idx)
|
|
case token.CONTINUE:
|
|
c.compileContinue(v.Label, v.Idx)
|
|
default:
|
|
panic(fmt.Errorf("Unknown branch statement token: %s", v.Token.String()))
|
|
}
|
|
}
|
|
|
|
func (c *compiler) findBranchBlock(st *ast.BranchStatement) *block {
|
|
switch st.Token {
|
|
case token.BREAK:
|
|
return c.findBreakBlock(st.Label)
|
|
case token.CONTINUE:
|
|
return c.findContinueBlock(st.Label)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *compiler) findContinueBlock(label *ast.Identifier) (block *block) {
|
|
if label != nil {
|
|
for b := c.block; b != nil; b = b.outer {
|
|
if b.typ == blockLoop && b.label == label.Name {
|
|
block = b
|
|
break
|
|
}
|
|
}
|
|
} else {
|
|
// find the nearest loop
|
|
for b := c.block; b != nil; b = b.outer {
|
|
if b.typ == blockLoop {
|
|
block = b
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (c *compiler) findBreakBlock(label *ast.Identifier) (block *block) {
|
|
if label != nil {
|
|
for b := c.block; b != nil; b = b.outer {
|
|
if b.label == label.Name {
|
|
block = b
|
|
break
|
|
}
|
|
}
|
|
} else {
|
|
// find the nearest loop or switch
|
|
L:
|
|
for b := c.block; b != nil; b = b.outer {
|
|
switch b.typ {
|
|
case blockLoop, blockSwitch:
|
|
block = b
|
|
break L
|
|
}
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (c *compiler) compileBreak(label *ast.Identifier, idx file.Idx) {
|
|
var block *block
|
|
if label != nil {
|
|
for b := c.block; b != nil; b = b.outer {
|
|
switch b.typ {
|
|
case blockTry:
|
|
c.emit(halt)
|
|
case blockWith:
|
|
c.emit(leaveWith)
|
|
}
|
|
if b.label == label.Name {
|
|
block = b
|
|
break
|
|
}
|
|
}
|
|
} else {
|
|
// find the nearest loop or switch
|
|
L:
|
|
for b := c.block; b != nil; b = b.outer {
|
|
switch b.typ {
|
|
case blockTry:
|
|
c.emit(halt)
|
|
case blockWith:
|
|
c.emit(leaveWith)
|
|
case blockLoop, blockSwitch:
|
|
block = b
|
|
break L
|
|
}
|
|
}
|
|
}
|
|
|
|
if block != nil {
|
|
if len(c.p.code) == c.blockStart && block.needResult {
|
|
c.emit(loadUndef)
|
|
}
|
|
block.breaks = append(block.breaks, len(c.p.code))
|
|
c.emit(nil)
|
|
} else {
|
|
c.throwSyntaxError(int(idx)-1, "Undefined label '%s'", label.Name)
|
|
}
|
|
}
|
|
|
|
func (c *compiler) compileContinue(label *ast.Identifier, idx file.Idx) {
|
|
var block *block
|
|
if label != nil {
|
|
for b := c.block; b != nil; b = b.outer {
|
|
if b.typ == blockTry {
|
|
c.emit(halt)
|
|
} else if b.typ == blockLoop && b.label == label.Name {
|
|
block = b
|
|
break
|
|
}
|
|
}
|
|
} else {
|
|
// find the nearest loop
|
|
for b := c.block; b != nil; b = b.outer {
|
|
if b.typ == blockTry {
|
|
c.emit(halt)
|
|
} else if b.typ == blockLoop {
|
|
block = b
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
if block != nil {
|
|
if len(c.p.code) == c.blockStart && block.needResult {
|
|
c.emit(loadUndef)
|
|
}
|
|
block.conts = append(block.conts, len(c.p.code))
|
|
c.emit(nil)
|
|
} else {
|
|
c.throwSyntaxError(int(idx)-1, "Undefined label '%s'", label.Name)
|
|
}
|
|
}
|
|
|
|
func (c *compiler) compileIfStatement(v *ast.IfStatement, needResult bool) {
|
|
test := c.compileExpression(v.Test)
|
|
if test.constant() {
|
|
r, ex := c.evalConst(test)
|
|
if ex != nil {
|
|
test.addSrcMap()
|
|
c.emitThrow(ex.val)
|
|
return
|
|
}
|
|
if r.ToBoolean() {
|
|
c.markBlockStart()
|
|
c.compileStatement(v.Consequent, needResult)
|
|
if v.Alternate != nil {
|
|
p := c.p
|
|
c.p = &Program{}
|
|
c.markBlockStart()
|
|
c.compileStatement(v.Alternate, false)
|
|
c.p = p
|
|
}
|
|
} else {
|
|
// TODO: Properly implement dummy compilation (no garbage in block, scope, etc..)
|
|
p := c.p
|
|
c.p = &Program{}
|
|
c.compileStatement(v.Consequent, false)
|
|
c.p = p
|
|
if v.Alternate != nil {
|
|
c.compileStatement(v.Alternate, needResult)
|
|
} else {
|
|
if needResult {
|
|
c.emit(loadUndef)
|
|
}
|
|
}
|
|
}
|
|
return
|
|
}
|
|
test.emitGetter(true)
|
|
jmp := len(c.p.code)
|
|
c.emit(nil)
|
|
c.markBlockStart()
|
|
c.compileStatement(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.markBlockStart()
|
|
c.compileStatement(v.Alternate, needResult)
|
|
c.p.code[jmp1] = jump(len(c.p.code) - jmp1)
|
|
c.markBlockStart()
|
|
} else {
|
|
c.p.code[jmp] = jne(len(c.p.code) - jmp)
|
|
c.markBlockStart()
|
|
if needResult {
|
|
c.emit(loadUndef)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (c *compiler) compileReturnStatement(v *ast.ReturnStatement) {
|
|
if v.Argument != nil {
|
|
c.compileExpression(v.Argument).emitGetter(true)
|
|
//c.emit(checkResolve)
|
|
} else {
|
|
c.emit(loadUndef)
|
|
}
|
|
for b := c.block; b != nil; b = b.outer {
|
|
if b.typ == blockTry {
|
|
c.emit(halt)
|
|
}
|
|
}
|
|
c.emit(ret)
|
|
}
|
|
|
|
func (c *compiler) compileVariableStatement(v *ast.VariableStatement, needResult bool) {
|
|
for _, expr := range v.List {
|
|
c.compileExpression(expr).emitGetter(false)
|
|
}
|
|
if needResult {
|
|
c.emit(loadUndef)
|
|
}
|
|
}
|
|
|
|
func (c *compiler) getFirstNonEmptyStatement(st ast.Statement) ast.Statement {
|
|
switch st := st.(type) {
|
|
case *ast.BlockStatement:
|
|
return c.getFirstNonEmptyStatementList(st.List)
|
|
case *ast.LabelledStatement:
|
|
return c.getFirstNonEmptyStatement(st.Statement)
|
|
}
|
|
return st
|
|
}
|
|
|
|
func (c *compiler) getFirstNonEmptyStatementList(list []ast.Statement) ast.Statement {
|
|
for _, st := range list {
|
|
switch st := st.(type) {
|
|
case *ast.EmptyStatement:
|
|
continue
|
|
case *ast.BlockStatement:
|
|
return c.getFirstNonEmptyStatementList(st.List)
|
|
case *ast.LabelledStatement:
|
|
return c.getFirstNonEmptyStatement(st.Statement)
|
|
}
|
|
return st
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *compiler) compileStatements(list []ast.Statement, needResult bool) {
|
|
if len(list) > 0 {
|
|
cur := list[0]
|
|
for idx := 0; idx < len(list); {
|
|
var next ast.Statement
|
|
// find next non-empty statement
|
|
for idx++; idx < len(list); idx++ {
|
|
if _, empty := list[idx].(*ast.EmptyStatement); !empty {
|
|
next = list[idx]
|
|
break
|
|
}
|
|
}
|
|
|
|
if next != nil {
|
|
bs := c.getFirstNonEmptyStatement(next)
|
|
if bs, ok := bs.(*ast.BranchStatement); ok {
|
|
block := c.findBranchBlock(bs)
|
|
if block != nil {
|
|
c.compileStatement(cur, block.needResult)
|
|
cur = next
|
|
continue
|
|
}
|
|
}
|
|
c.compileStatement(cur, false)
|
|
cur = next
|
|
} else {
|
|
c.compileStatement(cur, needResult)
|
|
}
|
|
}
|
|
} else {
|
|
if needResult {
|
|
c.emit(loadUndef)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (c *compiler) compileGenericLabeledStatement(v ast.Statement, needResult bool, label string) {
|
|
c.block = &block{
|
|
typ: blockBranch,
|
|
outer: c.block,
|
|
label: label,
|
|
needResult: needResult,
|
|
}
|
|
c.compileStatement(v, needResult)
|
|
c.leaveBlock()
|
|
}
|
|
|
|
func (c *compiler) compileBlockStatement(v *ast.BlockStatement, needResult bool) {
|
|
c.compileStatements(v.List, needResult)
|
|
}
|
|
|
|
func (c *compiler) compileExpressionStatement(v *ast.ExpressionStatement, needResult bool) {
|
|
expr := c.compileExpression(v.Expression)
|
|
if expr.constant() {
|
|
c.emitConst(expr, needResult)
|
|
} else {
|
|
expr.emitGetter(needResult)
|
|
}
|
|
}
|
|
|
|
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.newScope()
|
|
c.scope.dynamic = true
|
|
c.scope.lexical = 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)
|
|
|
|
jumps := make([]int, len(v.Body))
|
|
|
|
for i, s := range v.Body {
|
|
if s.Test != nil {
|
|
c.emit(dup)
|
|
c.compileExpression(s.Test).emitGetter(true)
|
|
c.emit(op_strict_eq)
|
|
c.emit(jne(3), pop)
|
|
jumps[i] = len(c.p.code)
|
|
c.emit(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.markBlockStart()
|
|
}
|
|
nr := false
|
|
c.markBlockStart()
|
|
if needResult {
|
|
if i < len(v.Body)-1 {
|
|
st := c.getFirstNonEmptyStatementList(v.Body[i+1].Consequent)
|
|
if st, ok := st.(*ast.BranchStatement); ok && st.Token == token.BREAK {
|
|
if c.findBreakBlock(st.Label) != nil {
|
|
stmts := append(s.Consequent, st)
|
|
c.compileStatements(stmts, false)
|
|
continue
|
|
}
|
|
}
|
|
} else {
|
|
nr = true
|
|
}
|
|
}
|
|
c.compileStatements(s.Consequent, nr)
|
|
}
|
|
if jumpNoMatch != -1 {
|
|
c.p.code[jumpNoMatch] = jump(len(c.p.code) - jumpNoMatch)
|
|
if len(v.Body) == 0 && needResult {
|
|
c.emit(loadUndef)
|
|
}
|
|
}
|
|
c.leaveBlock()
|
|
c.markBlockStart()
|
|
}
|