src.dualinventive.com/go/devsim/vendor/github.com/dop251/goja/compiler.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")
}
}