123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461 |
- 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")
- }
- }
|