462 lines
9.5 KiB
Go
462 lines
9.5 KiB
Go
package goja
|
|
|
|
import (
|
|
"fmt"
|
|
"github.com/dop251/goja/ast"
|
|
"github.com/dop251/goja/file"
|
|
"sort"
|
|
"strconv"
|
|
)
|
|
|
|
const (
|
|
blockLoop = iota
|
|
blockTry
|
|
blockBranch
|
|
blockSwitch
|
|
blockWith
|
|
)
|
|
|
|
type CompilerError struct {
|
|
Message string
|
|
File *SrcFile
|
|
Offset int
|
|
}
|
|
|
|
type CompilerSyntaxError struct {
|
|
CompilerError
|
|
}
|
|
|
|
type CompilerReferenceError struct {
|
|
CompilerError
|
|
}
|
|
|
|
type srcMapItem struct {
|
|
pc int
|
|
srcPos int
|
|
}
|
|
|
|
type Program struct {
|
|
code []instruction
|
|
values []Value
|
|
|
|
funcName string
|
|
src *SrcFile
|
|
srcMap []srcMapItem
|
|
}
|
|
|
|
type compiler struct {
|
|
p *Program
|
|
scope *scope
|
|
block *block
|
|
blockStart int
|
|
|
|
enumGetExpr compiledEnumGetExpr
|
|
|
|
evalVM *vm
|
|
}
|
|
|
|
type scope struct {
|
|
names map[string]uint32
|
|
outer *scope
|
|
strict bool
|
|
eval bool
|
|
lexical bool
|
|
dynamic bool
|
|
accessed bool
|
|
argsNeeded bool
|
|
thisNeeded bool
|
|
|
|
namesMap map[string]string
|
|
lastFreeTmp int
|
|
}
|
|
|
|
type block struct {
|
|
typ int
|
|
label string
|
|
needResult bool
|
|
cont int
|
|
breaks []int
|
|
conts []int
|
|
outer *block
|
|
}
|
|
|
|
func (c *compiler) leaveBlock() {
|
|
lbl := len(c.p.code)
|
|
for _, item := range c.block.breaks {
|
|
c.p.code[item] = jump(lbl - item)
|
|
}
|
|
if c.block.typ == blockLoop {
|
|
for _, item := range c.block.conts {
|
|
c.p.code[item] = jump(c.block.cont - item)
|
|
}
|
|
}
|
|
c.block = c.block.outer
|
|
}
|
|
|
|
func (e *CompilerSyntaxError) Error() string {
|
|
if e.File != nil {
|
|
return fmt.Sprintf("SyntaxError: %s at %s", e.Message, e.File.Position(e.Offset))
|
|
}
|
|
return fmt.Sprintf("SyntaxError: %s", e.Message)
|
|
}
|
|
|
|
func (e *CompilerReferenceError) Error() string {
|
|
return fmt.Sprintf("ReferenceError: %s", e.Message)
|
|
}
|
|
|
|
func (c *compiler) newScope() {
|
|
strict := false
|
|
if c.scope != nil {
|
|
strict = c.scope.strict
|
|
}
|
|
c.scope = &scope{
|
|
outer: c.scope,
|
|
names: make(map[string]uint32),
|
|
strict: strict,
|
|
namesMap: make(map[string]string),
|
|
}
|
|
}
|
|
|
|
func (c *compiler) popScope() {
|
|
c.scope = c.scope.outer
|
|
}
|
|
|
|
func newCompiler() *compiler {
|
|
c := &compiler{
|
|
p: &Program{},
|
|
}
|
|
|
|
c.enumGetExpr.init(c, file.Idx(0))
|
|
|
|
c.newScope()
|
|
c.scope.dynamic = true
|
|
return c
|
|
}
|
|
|
|
func (p *Program) defineLiteralValue(val Value) uint32 {
|
|
for idx, v := range p.values {
|
|
if v.SameAs(val) {
|
|
return uint32(idx)
|
|
}
|
|
}
|
|
idx := uint32(len(p.values))
|
|
p.values = append(p.values, val)
|
|
return idx
|
|
}
|
|
|
|
func (p *Program) dumpCode(logger func(format string, args ...interface{})) {
|
|
p._dumpCode("", logger)
|
|
}
|
|
|
|
func (p *Program) _dumpCode(indent string, logger func(format string, args ...interface{})) {
|
|
logger("values: %+v", p.values)
|
|
for pc, ins := range p.code {
|
|
logger("%s %d: %T(%v)", indent, pc, ins, ins)
|
|
if f, ok := ins.(*newFunc); ok {
|
|
f.prg._dumpCode(indent+">", logger)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (p *Program) sourceOffset(pc int) int {
|
|
i := sort.Search(len(p.srcMap), func(idx int) bool {
|
|
return p.srcMap[idx].pc > pc
|
|
}) - 1
|
|
if i >= 0 {
|
|
return p.srcMap[i].srcPos
|
|
}
|
|
|
|
return 0
|
|
}
|
|
|
|
func (s *scope) isFunction() bool {
|
|
if !s.lexical {
|
|
return s.outer != nil
|
|
}
|
|
return s.outer.isFunction()
|
|
}
|
|
|
|
func (s *scope) lookupName(name string) (idx uint32, found, noDynamics bool) {
|
|
var level uint32 = 0
|
|
noDynamics = true
|
|
for curScope := s; curScope != nil; curScope = curScope.outer {
|
|
if curScope != s {
|
|
curScope.accessed = true
|
|
}
|
|
if curScope.dynamic {
|
|
noDynamics = false
|
|
} else {
|
|
var mapped string
|
|
if m, exists := curScope.namesMap[name]; exists {
|
|
mapped = m
|
|
} else {
|
|
mapped = name
|
|
}
|
|
if i, exists := curScope.names[mapped]; exists {
|
|
idx = i | (level << 24)
|
|
found = true
|
|
return
|
|
}
|
|
}
|
|
if name == "arguments" && !s.lexical && s.isFunction() {
|
|
s.argsNeeded = true
|
|
s.accessed = true
|
|
idx, _ = s.bindName(name)
|
|
found = true
|
|
return
|
|
}
|
|
level++
|
|
}
|
|
return
|
|
}
|
|
|
|
func (s *scope) bindName(name string) (uint32, bool) {
|
|
if s.lexical {
|
|
return s.outer.bindName(name)
|
|
}
|
|
|
|
if idx, exists := s.names[name]; exists {
|
|
return idx, false
|
|
}
|
|
idx := uint32(len(s.names))
|
|
s.names[name] = idx
|
|
return idx, true
|
|
}
|
|
|
|
func (s *scope) bindNameShadow(name string) (uint32, bool) {
|
|
if s.lexical {
|
|
return s.outer.bindName(name)
|
|
}
|
|
|
|
unique := true
|
|
|
|
if idx, exists := s.names[name]; exists {
|
|
unique = false
|
|
// shadow the var
|
|
delete(s.names, name)
|
|
n := strconv.Itoa(int(idx))
|
|
s.names[n] = idx
|
|
}
|
|
idx := uint32(len(s.names))
|
|
s.names[name] = idx
|
|
return idx, unique
|
|
}
|
|
|
|
func (c *compiler) markBlockStart() {
|
|
c.blockStart = len(c.p.code)
|
|
}
|
|
|
|
func (c *compiler) compile(in *ast.Program) {
|
|
c.p.src = NewSrcFile(in.File.Name(), in.File.Source(), in.SourceMap)
|
|
|
|
if len(in.Body) > 0 {
|
|
if !c.scope.strict {
|
|
c.scope.strict = c.isStrict(in.Body)
|
|
}
|
|
}
|
|
|
|
c.compileDeclList(in.DeclarationList, false)
|
|
c.compileFunctions(in.DeclarationList)
|
|
|
|
c.markBlockStart()
|
|
c.compileStatements(in.Body, true)
|
|
|
|
c.p.code = append(c.p.code, halt)
|
|
code := c.p.code
|
|
c.p.code = make([]instruction, 0, len(code)+len(c.scope.names)+2)
|
|
if c.scope.eval {
|
|
if !c.scope.strict {
|
|
c.emit(jne(2), newStash)
|
|
} else {
|
|
c.emit(pop, newStash)
|
|
}
|
|
}
|
|
l := len(c.p.code)
|
|
c.p.code = c.p.code[:l+len(c.scope.names)]
|
|
for name, nameIdx := range c.scope.names {
|
|
c.p.code[l+int(nameIdx)] = bindName(name)
|
|
}
|
|
|
|
c.p.code = append(c.p.code, code...)
|
|
for i, _ := range c.p.srcMap {
|
|
c.p.srcMap[i].pc += len(c.scope.names)
|
|
}
|
|
|
|
}
|
|
|
|
func (c *compiler) compileDeclList(v []ast.Declaration, inFunc bool) {
|
|
for _, value := range v {
|
|
switch value := value.(type) {
|
|
case *ast.FunctionDeclaration:
|
|
c.compileFunctionDecl(value)
|
|
case *ast.VariableDeclaration:
|
|
c.compileVarDecl(value, inFunc)
|
|
default:
|
|
panic(fmt.Errorf("Unsupported declaration: %T", value))
|
|
}
|
|
}
|
|
}
|
|
|
|
func (c *compiler) compileFunctions(v []ast.Declaration) {
|
|
for _, value := range v {
|
|
if value, ok := value.(*ast.FunctionDeclaration); ok {
|
|
c.compileFunction(value)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (c *compiler) compileVarDecl(v *ast.VariableDeclaration, inFunc bool) {
|
|
for _, item := range v.List {
|
|
if c.scope.strict {
|
|
c.checkIdentifierLName(item.Name, int(item.Idx)-1)
|
|
c.checkIdentifierName(item.Name, int(item.Idx)-1)
|
|
}
|
|
if !inFunc || item.Name != "arguments" {
|
|
idx, ok := c.scope.bindName(item.Name)
|
|
_ = idx
|
|
//log.Printf("Define var: %s: %x", item.Name, idx)
|
|
if !ok {
|
|
// TODO: error
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (c *compiler) addDecls() []instruction {
|
|
code := make([]instruction, len(c.scope.names))
|
|
for name, nameIdx := range c.scope.names {
|
|
code[nameIdx] = bindName(name)
|
|
}
|
|
return code
|
|
}
|
|
|
|
func (c *compiler) convertInstrToStashless(instr uint32, args int) (newIdx int, convert bool) {
|
|
level := instr >> 24
|
|
idx := instr & 0x00FFFFFF
|
|
if level > 0 {
|
|
level--
|
|
newIdx = int((level << 24) | idx)
|
|
} else {
|
|
iidx := int(idx)
|
|
if iidx < args {
|
|
newIdx = -iidx - 1
|
|
} else {
|
|
newIdx = iidx - args + 1
|
|
}
|
|
convert = true
|
|
}
|
|
return
|
|
}
|
|
|
|
func (c *compiler) convertFunctionToStashless(code []instruction, args int) {
|
|
code[0] = enterFuncStashless{stackSize: uint32(len(c.scope.names) - args), args: uint32(args)}
|
|
for pc := 1; pc < len(code); pc++ {
|
|
instr := code[pc]
|
|
if instr == ret {
|
|
code[pc] = retStashless
|
|
}
|
|
switch instr := instr.(type) {
|
|
case getLocal:
|
|
if newIdx, convert := c.convertInstrToStashless(uint32(instr), args); convert {
|
|
code[pc] = loadStack(newIdx)
|
|
} else {
|
|
code[pc] = getLocal(newIdx)
|
|
}
|
|
case setLocal:
|
|
if newIdx, convert := c.convertInstrToStashless(uint32(instr), args); convert {
|
|
code[pc] = storeStack(newIdx)
|
|
} else {
|
|
code[pc] = setLocal(newIdx)
|
|
}
|
|
case setLocalP:
|
|
if newIdx, convert := c.convertInstrToStashless(uint32(instr), args); convert {
|
|
code[pc] = storeStackP(newIdx)
|
|
} else {
|
|
code[pc] = setLocalP(newIdx)
|
|
}
|
|
case getVar:
|
|
level := instr.idx >> 24
|
|
idx := instr.idx & 0x00FFFFFF
|
|
level--
|
|
instr.idx = level<<24 | idx
|
|
code[pc] = instr
|
|
case setVar:
|
|
level := instr.idx >> 24
|
|
idx := instr.idx & 0x00FFFFFF
|
|
level--
|
|
instr.idx = level<<24 | idx
|
|
code[pc] = instr
|
|
}
|
|
}
|
|
}
|
|
|
|
func (c *compiler) compileFunctionDecl(v *ast.FunctionDeclaration) {
|
|
idx, ok := c.scope.bindName(v.Function.Name.Name)
|
|
if !ok {
|
|
// TODO: error
|
|
}
|
|
_ = idx
|
|
// log.Printf("Define function: %s: %x", v.Function.Name.Name, idx)
|
|
}
|
|
|
|
func (c *compiler) compileFunction(v *ast.FunctionDeclaration) {
|
|
e := &compiledIdentifierExpr{
|
|
name: v.Function.Name.Name,
|
|
}
|
|
e.init(c, v.Function.Idx0())
|
|
e.emitSetter(c.compileFunctionLiteral(v.Function, false))
|
|
c.emit(pop)
|
|
}
|
|
|
|
func (c *compiler) emit(instructions ...instruction) {
|
|
c.p.code = append(c.p.code, instructions...)
|
|
}
|
|
|
|
func (c *compiler) throwSyntaxError(offset int, format string, args ...interface{}) {
|
|
panic(&CompilerSyntaxError{
|
|
CompilerError: CompilerError{
|
|
File: c.p.src,
|
|
Offset: offset,
|
|
Message: fmt.Sprintf(format, args...),
|
|
},
|
|
})
|
|
}
|
|
|
|
func (c *compiler) isStrict(list []ast.Statement) bool {
|
|
for _, st := range list {
|
|
if st, ok := st.(*ast.ExpressionStatement); ok {
|
|
if e, ok := st.Expression.(*ast.StringLiteral); ok {
|
|
if e.Literal == `"use strict"` || e.Literal == `'use strict'` {
|
|
return true
|
|
}
|
|
} else {
|
|
break
|
|
}
|
|
} else {
|
|
break
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (c *compiler) isStrictStatement(s ast.Statement) bool {
|
|
if s, ok := s.(*ast.BlockStatement); ok {
|
|
return c.isStrict(s.List)
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (c *compiler) checkIdentifierName(name string, offset int) {
|
|
switch name {
|
|
case "implements", "interface", "let", "package", "private", "protected", "public", "static", "yield":
|
|
c.throwSyntaxError(offset, "Unexpected strict mode reserved word")
|
|
}
|
|
}
|
|
|
|
func (c *compiler) checkIdentifierLName(name string, offset int) {
|
|
switch name {
|
|
case "eval", "arguments":
|
|
c.throwSyntaxError(offset, "Assignment to eval or arguments is not allowed in strict mode")
|
|
}
|
|
}
|