2024-09-20 16:50:35 +08:00
package goja
import (
2024-09-24 13:34:32 +08:00
"apigo.cc/ai/ai/goja/token"
2024-09-20 16:50:35 +08:00
"fmt"
"sort"
2024-09-24 13:34:32 +08:00
"apigo.cc/ai/ai/goja/ast"
"apigo.cc/ai/ai/goja/file"
"apigo.cc/ai/ai/goja/unistring"
2024-09-20 16:50:35 +08:00
)
type blockType int
const (
blockLoop blockType = iota
blockLoopEnum
blockTry
blockLabel
blockSwitch
blockWith
blockScope
blockIterScope
blockOptChain
)
const (
maskConst = 1 << 31
maskVar = 1 << 30
maskDeletable = 1 << 29
maskStrict = maskDeletable
maskTyp = maskConst | maskVar | maskDeletable
)
type varType byte
const (
varTypeVar varType = iota
varTypeLet
varTypeStrictConst
varTypeConst
)
const thisBindingName = " this" // must not be a valid identifier
type CompilerError struct {
Message string
File * file . File
Offset int
}
type CompilerSyntaxError struct {
CompilerError
}
type CompilerReferenceError struct {
CompilerError
}
type srcMapItem struct {
pc int
srcPos int
}
// Program is an internal, compiled representation of code which is produced by the Compile function.
// This representation is not linked to a runtime in any way and can be used concurrently.
// It is always preferable to use a Program over a string when running code as it skips the compilation step.
type Program struct {
code [ ] instruction
funcName unistring . String
src * file . File
srcMap [ ] srcMapItem
}
type compiler struct {
p * Program
scope * scope
block * block
classScope * classScope
enumGetExpr compiledEnumGetExpr
evalVM * vm // VM used to evaluate constant expressions
ctxVM * vm // VM in which an eval() code is compiled
codeScratchpad [ ] instruction
stringCache map [ unistring . String ] Value
}
type binding struct {
scope * scope
name unistring . String
accessPoints map [ * scope ] * [ ] int
isConst bool
isStrict bool
isArg bool
isVar bool
inStash bool
}
func ( b * binding ) getAccessPointsForScope ( s * scope ) * [ ] int {
m := b . accessPoints [ s ]
if m == nil {
a := make ( [ ] int , 0 , 1 )
m = & a
if b . accessPoints == nil {
b . accessPoints = make ( map [ * scope ] * [ ] int )
}
b . accessPoints [ s ] = m
}
return m
}
func ( b * binding ) markAccessPointAt ( pos int ) {
scope := b . scope . c . scope
m := b . getAccessPointsForScope ( scope )
* m = append ( * m , pos - scope . base )
}
func ( b * binding ) markAccessPointAtScope ( scope * scope , pos int ) {
m := b . getAccessPointsForScope ( scope )
* m = append ( * m , pos - scope . base )
}
func ( b * binding ) markAccessPoint ( ) {
scope := b . scope . c . scope
m := b . getAccessPointsForScope ( scope )
* m = append ( * m , len ( scope . prg . code ) - scope . base )
}
func ( b * binding ) emitGet ( ) {
b . markAccessPoint ( )
if b . isVar && ! b . isArg {
b . scope . c . emit ( loadStack ( 0 ) )
} else {
b . scope . c . emit ( loadStackLex ( 0 ) )
}
}
func ( b * binding ) emitGetAt ( pos int ) {
b . markAccessPointAt ( pos )
if b . isVar && ! b . isArg {
b . scope . c . p . code [ pos ] = loadStack ( 0 )
} else {
b . scope . c . p . code [ pos ] = loadStackLex ( 0 )
}
}
func ( b * binding ) emitGetP ( ) {
if b . isVar && ! b . isArg {
// no-op
} else {
// make sure TDZ is checked
b . markAccessPoint ( )
b . scope . c . emit ( loadStackLex ( 0 ) , pop )
}
}
func ( b * binding ) emitSet ( ) {
if b . isConst {
if b . isStrict || b . scope . c . scope . strict {
b . scope . c . emit ( throwAssignToConst )
}
return
}
b . markAccessPoint ( )
if b . isVar && ! b . isArg {
b . scope . c . emit ( storeStack ( 0 ) )
} else {
b . scope . c . emit ( storeStackLex ( 0 ) )
}
}
func ( b * binding ) emitSetP ( ) {
if b . isConst {
if b . isStrict || b . scope . c . scope . strict {
b . scope . c . emit ( throwAssignToConst )
}
return
}
b . markAccessPoint ( )
if b . isVar && ! b . isArg {
b . scope . c . emit ( storeStackP ( 0 ) )
} else {
b . scope . c . emit ( storeStackLexP ( 0 ) )
}
}
func ( b * binding ) emitInitP ( ) {
if ! b . isVar && b . scope . outer == nil {
b . scope . c . emit ( initGlobalP ( b . name ) )
} else {
b . markAccessPoint ( )
b . scope . c . emit ( initStackP ( 0 ) )
}
}
func ( b * binding ) emitInit ( ) {
if ! b . isVar && b . scope . outer == nil {
b . scope . c . emit ( initGlobal ( b . name ) )
} else {
b . markAccessPoint ( )
b . scope . c . emit ( initStack ( 0 ) )
}
}
func ( b * binding ) emitInitAt ( pos int ) {
if ! b . isVar && b . scope . outer == nil {
b . scope . c . p . code [ pos ] = initGlobal ( b . name )
} else {
b . markAccessPointAt ( pos )
b . scope . c . p . code [ pos ] = initStack ( 0 )
}
}
func ( b * binding ) emitInitAtScope ( scope * scope , pos int ) {
if ! b . isVar && scope . outer == nil {
scope . c . p . code [ pos ] = initGlobal ( b . name )
} else {
b . markAccessPointAtScope ( scope , pos )
scope . c . p . code [ pos ] = initStack ( 0 )
}
}
func ( b * binding ) emitInitPAtScope ( scope * scope , pos int ) {
if ! b . isVar && scope . outer == nil {
scope . c . p . code [ pos ] = initGlobalP ( b . name )
} else {
b . markAccessPointAtScope ( scope , pos )
scope . c . p . code [ pos ] = initStackP ( 0 )
}
}
func ( b * binding ) emitGetVar ( callee bool ) {
b . markAccessPoint ( )
if b . isVar && ! b . isArg {
b . scope . c . emit ( & loadMixed { name : b . name , callee : callee } )
} else {
b . scope . c . emit ( & loadMixedLex { name : b . name , callee : callee } )
}
}
func ( b * binding ) emitResolveVar ( strict bool ) {
b . markAccessPoint ( )
if b . isVar && ! b . isArg {
b . scope . c . emit ( & resolveMixed { name : b . name , strict : strict , typ : varTypeVar } )
} else {
var typ varType
if b . isConst {
if b . isStrict {
typ = varTypeStrictConst
} else {
typ = varTypeConst
}
} else {
typ = varTypeLet
}
b . scope . c . emit ( & resolveMixed { name : b . name , strict : strict , typ : typ } )
}
}
func ( b * binding ) moveToStash ( ) {
if b . isArg && ! b . scope . argsInStash {
b . scope . moveArgsToStash ( )
} else {
b . inStash = true
b . scope . needStash = true
}
}
func ( b * binding ) useCount ( ) ( count int ) {
for _ , a := range b . accessPoints {
count += len ( * a )
}
return
}
type scope struct {
c * compiler
prg * Program
outer * scope
nested [ ] * scope
boundNames map [ unistring . String ] * binding
bindings [ ] * binding
base int
numArgs int
// function type. If not funcNone, this is a function or a top-level lexical environment
funcType funcType
// in strict mode
strict bool
// eval top-level scope
eval bool
// at least one inner scope has direct eval() which can lookup names dynamically (by name)
dynLookup bool
// at least one binding has been marked for placement in stash
needStash bool
// is a variable environment, i.e. the target for dynamically created var bindings
variable bool
// a function scope that has at least one direct eval() and non-strict, so the variables can be added dynamically
dynamic bool
// arguments have been marked for placement in stash (functions only)
argsInStash bool
// need 'arguments' object (functions only)
argsNeeded bool
}
type block struct {
typ blockType
label unistring . String
cont int
breaks [ ] int
conts [ ] int
outer * block
breaking * block // set when the 'finally' block is an empty break statement sequence
needResult bool
}
func ( c * compiler ) leaveScopeBlock ( enter * enterBlock ) {
c . updateEnterBlock ( enter )
leave := & leaveBlock {
stackSize : enter . stackSize ,
popStash : enter . stashSize > 0 ,
}
c . emit ( leave )
for _ , pc := range c . block . breaks {
c . p . code [ pc ] = leave
}
c . block . breaks = nil
c . leaveBlock ( )
}
func ( c * compiler ) leaveBlock ( ) {
lbl := len ( c . p . code )
for _ , item := range c . block . breaks {
c . p . code [ item ] = jump ( lbl - item )
}
if t := c . block . typ ; t == blockLoop || t == blockLoopEnum {
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 {
c : c ,
prg : c . p ,
outer : c . scope ,
strict : strict ,
}
}
func ( c * compiler ) newBlockScope ( ) {
c . newScope ( )
if outer := c . scope . outer ; outer != nil {
outer . nested = append ( outer . nested , c . scope )
}
c . scope . base = len ( c . p . code )
}
func ( c * compiler ) popScope ( ) {
c . scope = c . scope . outer
}
func ( c * compiler ) emitLiteralString ( s String ) {
key := s . string ( )
if c . stringCache == nil {
c . stringCache = make ( map [ unistring . String ] Value )
}
internVal := c . stringCache [ key ]
if internVal == nil {
c . stringCache [ key ] = s
internVal = s
}
c . emit ( loadVal { internVal } )
}
func ( c * compiler ) emitLiteralValue ( v Value ) {
if s , ok := v . ( String ) ; ok {
c . emitLiteralString ( s )
return
}
c . emit ( loadVal { v } )
}
func newCompiler ( ) * compiler {
c := & compiler {
p : & Program { } ,
}
c . enumGetExpr . init ( c , file . Idx ( 0 ) )
return c
}
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 { } ) ) {
dumpInitFields := func ( initFields * Program ) {
i := indent + ">"
logger ( "%s ---- init_fields:" , i )
initFields . _dumpCode ( i , logger )
logger ( "%s ----" , i )
}
for pc , ins := range p . code {
logger ( "%s %d: %T(%v)" , indent , pc , ins , ins )
var prg * Program
switch f := ins . ( type ) {
case newFuncInstruction :
prg = f . getPrg ( )
case * newDerivedClass :
if f . initFields != nil {
dumpInitFields ( f . initFields )
}
prg = f . ctor
case * newClass :
if f . initFields != nil {
dumpInitFields ( f . initFields )
}
prg = f . ctor
case * newStaticFieldInit :
if f . initFields != nil {
dumpInitFields ( f . initFields )
}
}
if prg != nil {
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 ( p * Program ) addSrcMap ( srcPos int ) {
if len ( p . srcMap ) > 0 && p . srcMap [ len ( p . srcMap ) - 1 ] . srcPos == srcPos {
return
}
p . srcMap = append ( p . srcMap , srcMapItem { pc : len ( p . code ) , srcPos : srcPos } )
}
func ( s * scope ) lookupName ( name unistring . String ) ( binding * binding , noDynamics bool ) {
noDynamics = true
toStash := false
for curScope := s ; ; curScope = curScope . outer {
if curScope . outer != nil {
if b , exists := curScope . boundNames [ name ] ; exists {
if toStash && ! b . inStash {
b . moveToStash ( )
}
binding = b
return
}
} else {
noDynamics = false
return
}
if curScope . dynamic {
noDynamics = false
}
if name == "arguments" && curScope . funcType != funcNone && curScope . funcType != funcArrow {
if curScope . funcType == funcClsInit {
s . c . throwSyntaxError ( 0 , "'arguments' is not allowed in class field initializer or static initialization block" )
}
curScope . argsNeeded = true
binding , _ = curScope . bindName ( name )
return
}
if curScope . isFunction ( ) {
toStash = true
}
}
}
func ( s * scope ) lookupThis ( ) ( * binding , bool ) {
toStash := false
for curScope := s ; curScope != nil ; curScope = curScope . outer {
if curScope . outer == nil {
if curScope . eval {
return nil , true
}
}
if b , exists := curScope . boundNames [ thisBindingName ] ; exists {
if toStash && ! b . inStash {
b . moveToStash ( )
}
return b , false
}
if curScope . isFunction ( ) {
toStash = true
}
}
return nil , false
}
func ( s * scope ) ensureBoundNamesCreated ( ) {
if s . boundNames == nil {
s . boundNames = make ( map [ unistring . String ] * binding )
}
}
func ( s * scope ) addBinding ( offset int ) * binding {
if len ( s . bindings ) >= ( 1 << 24 ) - 1 {
s . c . throwSyntaxError ( offset , "Too many variables" )
}
b := & binding {
scope : s ,
}
s . bindings = append ( s . bindings , b )
return b
}
func ( s * scope ) bindNameLexical ( name unistring . String , unique bool , offset int ) ( * binding , bool ) {
if b := s . boundNames [ name ] ; b != nil {
if unique {
s . c . throwSyntaxError ( offset , "Identifier '%s' has already been declared" , name )
}
return b , false
}
b := s . addBinding ( offset )
b . name = name
s . ensureBoundNamesCreated ( )
s . boundNames [ name ] = b
return b , true
}
func ( s * scope ) createThisBinding ( ) * binding {
thisBinding , _ := s . bindNameLexical ( thisBindingName , false , 0 )
thisBinding . isVar = true // don't check on load
return thisBinding
}
func ( s * scope ) bindName ( name unistring . String ) ( * binding , bool ) {
if ! s . isFunction ( ) && ! s . variable && s . outer != nil {
return s . outer . bindName ( name )
}
b , created := s . bindNameLexical ( name , false , 0 )
if created {
b . isVar = true
}
return b , created
}
func ( s * scope ) bindNameShadow ( name unistring . String ) ( * binding , bool ) {
if ! s . isFunction ( ) && s . outer != nil {
return s . outer . bindNameShadow ( name )
}
_ , exists := s . boundNames [ name ]
b := & binding {
scope : s ,
name : name ,
}
s . bindings = append ( s . bindings , b )
s . ensureBoundNamesCreated ( )
s . boundNames [ name ] = b
return b , ! exists
}
func ( s * scope ) nearestFunction ( ) * scope {
for sc := s ; sc != nil ; sc = sc . outer {
if sc . isFunction ( ) {
return sc
}
}
return nil
}
func ( s * scope ) nearestThis ( ) * scope {
for sc := s ; sc != nil ; sc = sc . outer {
if sc . eval || sc . isFunction ( ) && sc . funcType != funcArrow {
return sc
}
}
return nil
}
func ( s * scope ) finaliseVarAlloc ( stackOffset int ) ( stashSize , stackSize int ) {
argsInStash := false
if f := s . nearestFunction ( ) ; f != nil {
argsInStash = f . argsInStash
}
stackIdx , stashIdx := 0 , 0
allInStash := s . isDynamic ( )
var derivedCtor bool
if fs := s . nearestThis ( ) ; fs != nil && fs . funcType == funcDerivedCtor {
derivedCtor = true
}
for i , b := range s . bindings {
var this bool
if b . name == thisBindingName {
this = true
}
if allInStash || b . inStash {
for scope , aps := range b . accessPoints {
var level uint32
for sc := scope ; sc != nil && sc != s ; sc = sc . outer {
if sc . needStash || sc . isDynamic ( ) {
level ++
}
}
if level > 255 {
s . c . throwSyntaxError ( 0 , "Maximum nesting level (256) exceeded" )
}
idx := ( level << 24 ) | uint32 ( stashIdx )
base := scope . base
code := scope . prg . code
if this {
if derivedCtor {
for _ , pc := range * aps {
ap := & code [ base + pc ]
switch ( * ap ) . ( type ) {
case loadStack :
* ap = loadThisStash ( idx )
case initStack :
* ap = initStash ( idx )
case resolveThisStack :
* ap = resolveThisStash ( idx )
case _ret :
* ap = cret ( idx )
default :
s . c . assert ( false , s . c . p . sourceOffset ( pc ) , "Unsupported instruction for 'this'" )
}
}
} else {
for _ , pc := range * aps {
ap := & code [ base + pc ]
switch ( * ap ) . ( type ) {
case loadStack :
* ap = loadStash ( idx )
case initStack :
* ap = initStash ( idx )
default :
s . c . assert ( false , s . c . p . sourceOffset ( pc ) , "Unsupported instruction for 'this'" )
}
}
}
} else {
for _ , pc := range * aps {
ap := & code [ base + pc ]
switch i := ( * ap ) . ( type ) {
case loadStack :
* ap = loadStash ( idx )
case storeStack :
* ap = storeStash ( idx )
case storeStackP :
* ap = storeStashP ( idx )
case loadStackLex :
* ap = loadStashLex ( idx )
case storeStackLex :
* ap = storeStashLex ( idx )
case storeStackLexP :
* ap = storeStashLexP ( idx )
case initStackP :
* ap = initStashP ( idx )
case initStack :
* ap = initStash ( idx )
case * loadMixed :
i . idx = idx
case * loadMixedLex :
i . idx = idx
case * resolveMixed :
i . idx = idx
default :
s . c . assert ( false , s . c . p . sourceOffset ( pc ) , "Unsupported instruction for binding: %T" , i )
}
}
}
}
stashIdx ++
} else {
var idx int
if ! this {
if i < s . numArgs {
idx = - ( i + 1 )
} else {
stackIdx ++
idx = stackIdx + stackOffset
}
}
for scope , aps := range b . accessPoints {
var level int
for sc := scope ; sc != nil && sc != s ; sc = sc . outer {
if sc . needStash || sc . isDynamic ( ) {
level ++
}
}
if level > 255 {
s . c . throwSyntaxError ( 0 , "Maximum nesting level (256) exceeded" )
}
code := scope . prg . code
base := scope . base
if this {
if derivedCtor {
for _ , pc := range * aps {
ap := & code [ base + pc ]
switch ( * ap ) . ( type ) {
case loadStack :
* ap = loadThisStack { }
case initStack :
// no-op
case resolveThisStack :
// no-op
case _ret :
// no-op, already in the right place
default :
s . c . assert ( false , s . c . p . sourceOffset ( pc ) , "Unsupported instruction for 'this'" )
}
}
} / * else {
no - op
} * /
} else if argsInStash {
for _ , pc := range * aps {
ap := & code [ base + pc ]
switch i := ( * ap ) . ( type ) {
case loadStack :
* ap = loadStack1 ( idx )
case storeStack :
* ap = storeStack1 ( idx )
case storeStackP :
* ap = storeStack1P ( idx )
case loadStackLex :
* ap = loadStack1Lex ( idx )
case storeStackLex :
* ap = storeStack1Lex ( idx )
case storeStackLexP :
* ap = storeStack1LexP ( idx )
case initStackP :
* ap = initStack1P ( idx )
case initStack :
* ap = initStack1 ( idx )
case * loadMixed :
* ap = & loadMixedStack1 { name : i . name , idx : idx , level : uint8 ( level ) , callee : i . callee }
case * loadMixedLex :
* ap = & loadMixedStack1Lex { name : i . name , idx : idx , level : uint8 ( level ) , callee : i . callee }
case * resolveMixed :
* ap = & resolveMixedStack1 { typ : i . typ , name : i . name , idx : idx , level : uint8 ( level ) , strict : i . strict }
default :
s . c . assert ( false , s . c . p . sourceOffset ( pc ) , "Unsupported instruction for binding: %T" , i )
}
}
} else {
for _ , pc := range * aps {
ap := & code [ base + pc ]
switch i := ( * ap ) . ( type ) {
case loadStack :
* ap = loadStack ( idx )
case storeStack :
* ap = storeStack ( idx )
case storeStackP :
* ap = storeStackP ( idx )
case loadStackLex :
* ap = loadStackLex ( idx )
case storeStackLex :
* ap = storeStackLex ( idx )
case storeStackLexP :
* ap = storeStackLexP ( idx )
case initStack :
* ap = initStack ( idx )
case initStackP :
* ap = initStackP ( idx )
case * loadMixed :
* ap = & loadMixedStack { name : i . name , idx : idx , level : uint8 ( level ) , callee : i . callee }
case * loadMixedLex :
* ap = & loadMixedStackLex { name : i . name , idx : idx , level : uint8 ( level ) , callee : i . callee }
case * resolveMixed :
* ap = & resolveMixedStack { typ : i . typ , name : i . name , idx : idx , level : uint8 ( level ) , strict : i . strict }
default :
s . c . assert ( false , s . c . p . sourceOffset ( pc ) , "Unsupported instruction for binding: %T" , i )
}
}
}
}
}
}
for _ , nested := range s . nested {
nested . finaliseVarAlloc ( stackIdx + stackOffset )
}
return stashIdx , stackIdx
}
func ( s * scope ) moveArgsToStash ( ) {
for _ , b := range s . bindings {
if ! b . isArg {
break
}
b . inStash = true
}
s . argsInStash = true
s . needStash = true
}
func ( c * compiler ) trimCode ( delta int ) {
src := c . p . code [ delta : ]
newCode := make ( [ ] instruction , len ( src ) )
copy ( newCode , src )
if cap ( c . codeScratchpad ) < cap ( c . p . code ) {
c . codeScratchpad = c . p . code [ : 0 ]
}
c . p . code = newCode
}
func ( s * scope ) trimCode ( delta int ) {
s . c . trimCode ( delta )
if delta != 0 {
srcMap := s . c . p . srcMap
for i := range srcMap {
srcMap [ i ] . pc -= delta
}
s . adjustBase ( - delta )
}
}
func ( s * scope ) adjustBase ( delta int ) {
s . base += delta
for _ , nested := range s . nested {
nested . adjustBase ( delta )
}
}
func ( s * scope ) makeNamesMap ( ) map [ unistring . String ] uint32 {
l := len ( s . bindings )
if l == 0 {
return nil
}
names := make ( map [ unistring . String ] uint32 , l )
for i , b := range s . bindings {
idx := uint32 ( i )
if b . isConst {
idx |= maskConst
if b . isStrict {
idx |= maskStrict
}
}
if b . isVar {
idx |= maskVar
}
names [ b . name ] = idx
}
return names
}
func ( s * scope ) isDynamic ( ) bool {
return s . dynLookup || s . dynamic
}
func ( s * scope ) isFunction ( ) bool {
return s . funcType != funcNone && ! s . eval
}
func ( s * scope ) deleteBinding ( b * binding ) {
idx := 0
for i , bb := range s . bindings {
if bb == b {
idx = i
goto found
}
}
return
found :
delete ( s . boundNames , b . name )
copy ( s . bindings [ idx : ] , s . bindings [ idx + 1 : ] )
l := len ( s . bindings ) - 1
s . bindings [ l ] = nil
s . bindings = s . bindings [ : l ]
}
func ( c * compiler ) compile ( in * ast . Program , strict , inGlobal bool , evalVm * vm ) {
c . ctxVM = evalVm
eval := evalVm != nil
c . p . src = in . File
c . newScope ( )
scope := c . scope
scope . dynamic = true
scope . eval = eval
if ! strict && len ( in . Body ) > 0 {
strict = c . isStrict ( in . Body ) != nil
}
scope . strict = strict
ownVarScope := eval && strict
ownLexScope := ! inGlobal || eval
if ownVarScope {
c . newBlockScope ( )
scope = c . scope
scope . variable = true
}
if eval && ! inGlobal {
for s := evalVm . stash ; s != nil ; s = s . outer {
if ft := s . funcType ; ft != funcNone && ft != funcArrow {
scope . funcType = ft
break
}
}
}
funcs := c . extractFunctions ( in . Body )
c . createFunctionBindings ( funcs )
numFuncs := len ( scope . bindings )
if inGlobal && ! ownVarScope {
if numFuncs == len ( funcs ) {
c . compileFunctionsGlobalAllUnique ( funcs )
} else {
c . compileFunctionsGlobal ( funcs )
}
}
c . compileDeclList ( in . DeclarationList , false )
numVars := len ( scope . bindings ) - numFuncs
vars := make ( [ ] unistring . String , len ( scope . bindings ) )
for i , b := range scope . bindings {
vars [ i ] = b . name
}
if len ( vars ) > 0 && ! ownVarScope && ownLexScope {
if inGlobal {
c . emit ( & bindGlobal {
vars : vars [ numFuncs : ] ,
funcs : vars [ : numFuncs ] ,
deletable : eval ,
} )
} else {
c . emit ( & bindVars { names : vars , deletable : eval } )
}
}
var enter * enterBlock
if c . compileLexicalDeclarations ( in . Body , ownVarScope || ! ownLexScope ) {
if ownLexScope {
c . block = & block {
outer : c . block ,
typ : blockScope ,
needResult : true ,
}
enter = & enterBlock { }
c . emit ( enter )
}
}
if len ( scope . bindings ) > 0 && ! ownLexScope {
var lets , consts [ ] unistring . String
for _ , b := range c . scope . bindings [ numFuncs + numVars : ] {
if b . isConst {
consts = append ( consts , b . name )
} else {
lets = append ( lets , b . name )
}
}
c . emit ( & bindGlobal {
vars : vars [ numFuncs : ] ,
funcs : vars [ : numFuncs ] ,
lets : lets ,
consts : consts ,
} )
}
if ! inGlobal || ownVarScope {
c . compileFunctions ( funcs )
}
c . compileStatements ( in . Body , true )
if enter != nil {
c . leaveScopeBlock ( enter )
c . popScope ( )
}
scope . finaliseVarAlloc ( 0 )
c . stringCache = nil
}
func ( c * compiler ) compileDeclList ( v [ ] * ast . VariableDeclaration , inFunc bool ) {
for _ , value := range v {
c . createVarBindings ( value , inFunc )
}
}
func ( c * compiler ) extractLabelled ( st ast . Statement ) ast . Statement {
if st , ok := st . ( * ast . LabelledStatement ) ; ok {
return c . extractLabelled ( st . Statement )
}
return st
}
func ( c * compiler ) extractFunctions ( list [ ] ast . Statement ) ( funcs [ ] * ast . FunctionDeclaration ) {
for _ , st := range list {
var decl * ast . FunctionDeclaration
switch st := c . extractLabelled ( st ) . ( type ) {
case * ast . FunctionDeclaration :
decl = st
case * ast . LabelledStatement :
if st1 , ok := st . Statement . ( * ast . FunctionDeclaration ) ; ok {
decl = st1
} else {
continue
}
default :
continue
}
funcs = append ( funcs , decl )
}
return
}
func ( c * compiler ) createFunctionBindings ( funcs [ ] * ast . FunctionDeclaration ) {
s := c . scope
if s . outer != nil {
unique := ! s . isFunction ( ) && ! s . variable && s . strict
if ! unique {
hasNonStandard := false
for _ , decl := range funcs {
if ! decl . Function . Async && ! decl . Function . Generator {
s . bindNameLexical ( decl . Function . Name . Name , false , int ( decl . Function . Name . Idx1 ( ) ) - 1 )
} else {
hasNonStandard = true
}
}
if hasNonStandard {
for _ , decl := range funcs {
if decl . Function . Async || decl . Function . Generator {
s . bindNameLexical ( decl . Function . Name . Name , true , int ( decl . Function . Name . Idx1 ( ) ) - 1 )
}
}
}
} else {
for _ , decl := range funcs {
s . bindNameLexical ( decl . Function . Name . Name , true , int ( decl . Function . Name . Idx1 ( ) ) - 1 )
}
}
} else {
for _ , decl := range funcs {
s . bindName ( decl . Function . Name . Name )
}
}
}
func ( c * compiler ) compileFunctions ( list [ ] * ast . FunctionDeclaration ) {
for _ , decl := range list {
c . compileFunction ( decl )
}
}
func ( c * compiler ) compileFunctionsGlobalAllUnique ( list [ ] * ast . FunctionDeclaration ) {
for _ , decl := range list {
c . compileFunctionLiteral ( decl . Function , false ) . emitGetter ( true )
}
}
func ( c * compiler ) compileFunctionsGlobal ( list [ ] * ast . FunctionDeclaration ) {
m := make ( map [ unistring . String ] int , len ( list ) )
for i := len ( list ) - 1 ; i >= 0 ; i -- {
name := list [ i ] . Function . Name . Name
if _ , exists := m [ name ] ; ! exists {
m [ name ] = i
}
}
idx := 0
for i , decl := range list {
name := decl . Function . Name . Name
if m [ name ] == i {
c . compileFunctionLiteral ( decl . Function , false ) . emitGetter ( true )
c . scope . bindings [ idx ] = c . scope . boundNames [ name ]
idx ++
} else {
leave := c . enterDummyMode ( )
c . compileFunctionLiteral ( decl . Function , false ) . emitGetter ( false )
leave ( )
}
}
}
func ( c * compiler ) createVarIdBinding ( name unistring . String , offset int , inFunc bool ) {
if c . scope . strict {
c . checkIdentifierLName ( name , offset )
c . checkIdentifierName ( name , offset )
}
if ! inFunc || name != "arguments" {
c . scope . bindName ( name )
}
}
func ( c * compiler ) createBindings ( target ast . Expression , createIdBinding func ( name unistring . String , offset int ) ) {
switch target := target . ( type ) {
case * ast . Identifier :
createIdBinding ( target . Name , int ( target . Idx ) - 1 )
case * ast . ObjectPattern :
for _ , prop := range target . Properties {
switch prop := prop . ( type ) {
case * ast . PropertyShort :
createIdBinding ( prop . Name . Name , int ( prop . Name . Idx ) - 1 )
case * ast . PropertyKeyed :
c . createBindings ( prop . Value , createIdBinding )
default :
c . throwSyntaxError ( int ( target . Idx0 ( ) - 1 ) , "unsupported property type in ObjectPattern: %T" , prop )
}
}
if target . Rest != nil {
c . createBindings ( target . Rest , createIdBinding )
}
case * ast . ArrayPattern :
for _ , elt := range target . Elements {
if elt != nil {
c . createBindings ( elt , createIdBinding )
}
}
if target . Rest != nil {
c . createBindings ( target . Rest , createIdBinding )
}
case * ast . AssignExpression :
c . createBindings ( target . Left , createIdBinding )
default :
c . throwSyntaxError ( int ( target . Idx0 ( ) - 1 ) , "unsupported binding target: %T" , target )
}
}
func ( c * compiler ) createVarBinding ( target ast . Expression , inFunc bool ) {
c . createBindings ( target , func ( name unistring . String , offset int ) {
c . createVarIdBinding ( name , offset , inFunc )
} )
}
func ( c * compiler ) createVarBindings ( v * ast . VariableDeclaration , inFunc bool ) {
for _ , item := range v . List {
c . createVarBinding ( item . Target , inFunc )
}
}
func ( c * compiler ) createLexicalIdBinding ( name unistring . String , isConst bool , offset int ) * binding {
if name == "let" {
c . throwSyntaxError ( offset , "let is disallowed as a lexically bound name" )
}
if c . scope . strict {
c . checkIdentifierLName ( name , offset )
c . checkIdentifierName ( name , offset )
}
b , _ := c . scope . bindNameLexical ( name , true , offset )
if isConst {
b . isConst , b . isStrict = true , true
}
return b
}
func ( c * compiler ) createLexicalIdBindingFuncBody ( name unistring . String , isConst bool , offset int , calleeBinding * binding ) * binding {
if name == "let" {
c . throwSyntaxError ( offset , "let is disallowed as a lexically bound name" )
}
if c . scope . strict {
c . checkIdentifierLName ( name , offset )
c . checkIdentifierName ( name , offset )
}
paramScope := c . scope . outer
parentBinding := paramScope . boundNames [ name ]
if parentBinding != nil {
if parentBinding != calleeBinding && ( name != "arguments" || ! paramScope . argsNeeded ) {
c . throwSyntaxError ( offset , "Identifier '%s' has already been declared" , name )
}
}
b , _ := c . scope . bindNameLexical ( name , true , offset )
if isConst {
b . isConst , b . isStrict = true , true
}
return b
}
func ( c * compiler ) createLexicalBinding ( target ast . Expression , isConst bool ) {
c . createBindings ( target , func ( name unistring . String , offset int ) {
c . createLexicalIdBinding ( name , isConst , offset )
} )
}
func ( c * compiler ) createLexicalBindings ( lex * ast . LexicalDeclaration ) {
for _ , d := range lex . List {
c . createLexicalBinding ( d . Target , lex . Token == token . CONST )
}
}
func ( c * compiler ) compileLexicalDeclarations ( list [ ] ast . Statement , scopeDeclared bool ) bool {
for _ , st := range list {
if lex , ok := st . ( * ast . LexicalDeclaration ) ; ok {
if ! scopeDeclared {
c . newBlockScope ( )
scopeDeclared = true
}
c . createLexicalBindings ( lex )
} else if cls , ok := st . ( * ast . ClassDeclaration ) ; ok {
if ! scopeDeclared {
c . newBlockScope ( )
scopeDeclared = true
}
c . createLexicalIdBinding ( cls . Class . Name . Name , false , int ( cls . Class . Name . Idx ) - 1 )
}
}
return scopeDeclared
}
func ( c * compiler ) compileLexicalDeclarationsFuncBody ( list [ ] ast . Statement , calleeBinding * binding ) {
for _ , st := range list {
if lex , ok := st . ( * ast . LexicalDeclaration ) ; ok {
isConst := lex . Token == token . CONST
for _ , d := range lex . List {
c . createBindings ( d . Target , func ( name unistring . String , offset int ) {
c . createLexicalIdBindingFuncBody ( name , isConst , offset , calleeBinding )
} )
}
} else if cls , ok := st . ( * ast . ClassDeclaration ) ; ok {
c . createLexicalIdBindingFuncBody ( cls . Class . Name . Name , false , int ( cls . Class . Name . Idx ) - 1 , calleeBinding )
}
}
}
func ( c * compiler ) compileFunction ( v * ast . FunctionDeclaration ) {
name := v . Function . Name . Name
b := c . scope . boundNames [ name ]
if b == nil || b . isVar {
e := & compiledIdentifierExpr {
name : v . Function . Name . Name ,
}
e . init ( c , v . Function . Idx0 ( ) )
e . emitSetter ( c . compileFunctionLiteral ( v . Function , false ) , false )
} else {
c . compileFunctionLiteral ( v . Function , false ) . emitGetter ( true )
b . emitInitP ( )
}
}
func ( c * compiler ) compileStandaloneFunctionDecl ( v * ast . FunctionDeclaration ) {
if v . Function . Async {
c . throwSyntaxError ( int ( v . Idx0 ( ) ) - 1 , "Async functions can only be declared at top level or inside a block." )
}
if v . Function . Generator {
c . throwSyntaxError ( int ( v . Idx0 ( ) ) - 1 , "Generators can only be declared at top level or inside a block." )
}
if c . scope . strict {
c . throwSyntaxError ( int ( v . Idx0 ( ) ) - 1 , "In strict mode code, functions can only be declared at top level or inside a block." )
}
c . throwSyntaxError ( int ( v . Idx0 ( ) ) - 1 , "In non-strict mode code, functions can only be declared at top level, inside a block, or as the body of an if statement." )
}
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 ) * ast . StringLiteral {
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 e
}
} else {
break
}
} else {
break
}
}
return nil
}
func ( c * compiler ) isStrictStatement ( s ast . Statement ) * ast . StringLiteral {
if s , ok := s . ( * ast . BlockStatement ) ; ok {
return c . isStrict ( s . List )
}
return nil
}
func ( c * compiler ) checkIdentifierName ( name unistring . 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 unistring . String , offset int ) {
switch name {
case "eval" , "arguments" :
c . throwSyntaxError ( offset , "Assignment to eval or arguments is not allowed in strict mode" )
}
}
// Enter a 'dummy' compilation mode. Any code produced after this method is called will be discarded after
// leaveFunc is called with no additional side effects. This is useful for compiling code inside a
// constant falsy condition 'if' branch or a loop (i.e 'if (false) { ... } or while (false) { ... }).
// Such code should not be included in the final compilation result as it's never called, but it must
// still produce compilation errors if there are any.
// TODO: make sure variable lookups do not de-optimise parent scopes
func ( c * compiler ) enterDummyMode ( ) ( leaveFunc func ( ) ) {
savedBlock , savedProgram := c . block , c . p
if savedBlock != nil {
c . block = & block {
typ : savedBlock . typ ,
label : savedBlock . label ,
outer : savedBlock . outer ,
breaking : savedBlock . breaking ,
}
}
c . p = & Program {
src : c . p . src ,
}
c . newScope ( )
return func ( ) {
c . block , c . p = savedBlock , savedProgram
c . popScope ( )
}
}
func ( c * compiler ) compileStatementDummy ( statement ast . Statement ) {
leave := c . enterDummyMode ( )
c . compileStatement ( statement , false )
leave ( )
}
func ( c * compiler ) assert ( cond bool , offset int , msg string , args ... interface { } ) {
if ! cond {
c . throwSyntaxError ( offset , "Compiler bug: " + msg , args ... )
}
}
func privateIdString ( desc unistring . String ) unistring . String {
return asciiString ( "#" ) . Concat ( stringValueFromRaw ( desc ) ) . string ( )
}
type privateName struct {
idx int
isStatic bool
isMethod bool
hasGetter , hasSetter bool
}
type resolvedPrivateName struct {
name unistring . String
idx uint32
level uint8
isStatic bool
isMethod bool
}
func ( r * resolvedPrivateName ) string ( ) unistring . String {
return privateIdString ( r . name )
}
type privateEnvRegistry struct {
fields , methods [ ] unistring . String
}
type classScope struct {
c * compiler
privateNames map [ unistring . String ] * privateName
instanceEnv , staticEnv privateEnvRegistry
outer * classScope
}
func ( r * privateEnvRegistry ) createPrivateMethodId ( name unistring . String ) int {
r . methods = append ( r . methods , name )
return len ( r . methods ) - 1
}
func ( r * privateEnvRegistry ) createPrivateFieldId ( name unistring . String ) int {
r . fields = append ( r . fields , name )
return len ( r . fields ) - 1
}
func ( s * classScope ) declarePrivateId ( name unistring . String , kind ast . PropertyKind , isStatic bool , offset int ) {
pn := s . privateNames [ name ]
if pn != nil {
if pn . isStatic == isStatic {
switch kind {
case ast . PropertyKindGet :
if pn . hasSetter && ! pn . hasGetter {
pn . hasGetter = true
return
}
case ast . PropertyKindSet :
if pn . hasGetter && ! pn . hasSetter {
pn . hasSetter = true
return
}
}
}
s . c . throwSyntaxError ( offset , "Identifier '#%s' has already been declared" , name )
panic ( "unreachable" )
}
var env * privateEnvRegistry
if isStatic {
env = & s . staticEnv
} else {
env = & s . instanceEnv
}
pn = & privateName {
isStatic : isStatic ,
hasGetter : kind == ast . PropertyKindGet ,
hasSetter : kind == ast . PropertyKindSet ,
}
if kind != ast . PropertyKindValue {
pn . idx = env . createPrivateMethodId ( name )
pn . isMethod = true
} else {
pn . idx = env . createPrivateFieldId ( name )
}
if s . privateNames == nil {
s . privateNames = make ( map [ unistring . String ] * privateName )
}
s . privateNames [ name ] = pn
}
func ( s * classScope ) getDeclaredPrivateId ( name unistring . String ) * privateName {
if n := s . privateNames [ name ] ; n != nil {
return n
}
s . c . assert ( false , 0 , "getDeclaredPrivateId() for undeclared id" )
panic ( "unreachable" )
}
func ( c * compiler ) resolvePrivateName ( name unistring . String , offset int ) ( * resolvedPrivateName , * privateId ) {
level := 0
for s := c . classScope ; s != nil ; s = s . outer {
if len ( s . privateNames ) > 0 {
if pn := s . privateNames [ name ] ; pn != nil {
return & resolvedPrivateName {
name : name ,
idx : uint32 ( pn . idx ) ,
level : uint8 ( level ) ,
isStatic : pn . isStatic ,
isMethod : pn . isMethod ,
} , nil
}
level ++
}
}
if c . ctxVM != nil {
for s := c . ctxVM . privEnv ; s != nil ; s = s . outer {
if id := s . names [ name ] ; id != nil {
return nil , id
}
}
}
c . throwSyntaxError ( offset , "Private field '#%s' must be declared in an enclosing class" , name )
panic ( "unreachable" )
}