1128 lines
28 KiB
Go
1128 lines
28 KiB
Go
|
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))
|
||
|
}
|