470 lines
11 KiB
Go
470 lines
11 KiB
Go
|
package goja
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"github.com/dop251/goja/unistring"
|
||
|
"math"
|
||
|
"reflect"
|
||
|
"sort"
|
||
|
)
|
||
|
|
||
|
type templatePropFactory func(*Runtime) Value
|
||
|
|
||
|
type objectTemplate struct {
|
||
|
propNames []unistring.String
|
||
|
props map[unistring.String]templatePropFactory
|
||
|
|
||
|
symProps map[*Symbol]templatePropFactory
|
||
|
symPropNames []*Symbol
|
||
|
|
||
|
protoFactory func(*Runtime) *Object
|
||
|
}
|
||
|
|
||
|
type templatedObject struct {
|
||
|
baseObject
|
||
|
tmpl *objectTemplate
|
||
|
|
||
|
protoMaterialised bool
|
||
|
}
|
||
|
|
||
|
type templatedFuncObject struct {
|
||
|
templatedObject
|
||
|
|
||
|
f func(FunctionCall) Value
|
||
|
construct func(args []Value, newTarget *Object) *Object
|
||
|
}
|
||
|
|
||
|
// This type exists because Array.prototype is supposed to be an array itself and I could not find
|
||
|
// a different way of implementing it without either introducing another layer of interfaces or hoisting
|
||
|
// the templates to baseObject both of which would have had a negative effect on the performance.
|
||
|
// The implementation is as simple as possible and is not optimised in any way, but I very much doubt anybody
|
||
|
// uses Array.prototype as an actual array.
|
||
|
type templatedArrayObject struct {
|
||
|
templatedObject
|
||
|
}
|
||
|
|
||
|
func newObjectTemplate() *objectTemplate {
|
||
|
return &objectTemplate{
|
||
|
props: make(map[unistring.String]templatePropFactory),
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (t *objectTemplate) putStr(name unistring.String, f templatePropFactory) {
|
||
|
t.props[name] = f
|
||
|
t.propNames = append(t.propNames, name)
|
||
|
}
|
||
|
|
||
|
func (t *objectTemplate) putSym(s *Symbol, f templatePropFactory) {
|
||
|
if t.symProps == nil {
|
||
|
t.symProps = make(map[*Symbol]templatePropFactory)
|
||
|
}
|
||
|
t.symProps[s] = f
|
||
|
t.symPropNames = append(t.symPropNames, s)
|
||
|
}
|
||
|
|
||
|
func (r *Runtime) newTemplatedObject(tmpl *objectTemplate, obj *Object) *templatedObject {
|
||
|
if obj == nil {
|
||
|
obj = &Object{runtime: r}
|
||
|
}
|
||
|
o := &templatedObject{
|
||
|
baseObject: baseObject{
|
||
|
class: classObject,
|
||
|
val: obj,
|
||
|
extensible: true,
|
||
|
},
|
||
|
tmpl: tmpl,
|
||
|
}
|
||
|
obj.self = o
|
||
|
o.init()
|
||
|
return o
|
||
|
}
|
||
|
|
||
|
func (o *templatedObject) materialiseProto() {
|
||
|
if !o.protoMaterialised {
|
||
|
if o.tmpl.protoFactory != nil {
|
||
|
o.prototype = o.tmpl.protoFactory(o.val.runtime)
|
||
|
}
|
||
|
o.protoMaterialised = true
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (o *templatedObject) getStr(name unistring.String, receiver Value) Value {
|
||
|
ownProp := o.getOwnPropStr(name)
|
||
|
if ownProp == nil {
|
||
|
o.materialiseProto()
|
||
|
}
|
||
|
return o.getStrWithOwnProp(ownProp, name, receiver)
|
||
|
}
|
||
|
|
||
|
func (o *templatedObject) getSym(s *Symbol, receiver Value) Value {
|
||
|
ownProp := o.getOwnPropSym(s)
|
||
|
if ownProp == nil {
|
||
|
o.materialiseProto()
|
||
|
}
|
||
|
return o.getWithOwnProp(ownProp, s, receiver)
|
||
|
}
|
||
|
|
||
|
func (o *templatedObject) getOwnPropStr(p unistring.String) Value {
|
||
|
if v, exists := o.values[p]; exists {
|
||
|
return v
|
||
|
}
|
||
|
if f := o.tmpl.props[p]; f != nil {
|
||
|
v := f(o.val.runtime)
|
||
|
o.values[p] = v
|
||
|
return v
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (o *templatedObject) materialiseSymbols() {
|
||
|
if o.symValues == nil {
|
||
|
o.symValues = newOrderedMap(nil)
|
||
|
for _, p := range o.tmpl.symPropNames {
|
||
|
o.symValues.set(p, o.tmpl.symProps[p](o.val.runtime))
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (o *templatedObject) getOwnPropSym(s *Symbol) Value {
|
||
|
if o.symValues == nil && o.tmpl.symProps[s] == nil {
|
||
|
return nil
|
||
|
}
|
||
|
o.materialiseSymbols()
|
||
|
return o.baseObject.getOwnPropSym(s)
|
||
|
}
|
||
|
|
||
|
func (o *templatedObject) materialisePropNames() {
|
||
|
if o.propNames == nil {
|
||
|
o.propNames = append(([]unistring.String)(nil), o.tmpl.propNames...)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (o *templatedObject) setOwnStr(p unistring.String, v Value, throw bool) bool {
|
||
|
existing := o.getOwnPropStr(p) // materialise property (in case it's an accessor)
|
||
|
if existing == nil {
|
||
|
o.materialiseProto()
|
||
|
o.materialisePropNames()
|
||
|
}
|
||
|
return o.baseObject.setOwnStr(p, v, throw)
|
||
|
}
|
||
|
|
||
|
func (o *templatedObject) setOwnSym(name *Symbol, val Value, throw bool) bool {
|
||
|
o.materialiseSymbols()
|
||
|
o.materialiseProto()
|
||
|
return o.baseObject.setOwnSym(name, val, throw)
|
||
|
}
|
||
|
|
||
|
func (o *templatedObject) setForeignStr(name unistring.String, val, receiver Value, throw bool) (bool, bool) {
|
||
|
ownProp := o.getOwnPropStr(name)
|
||
|
if ownProp == nil {
|
||
|
o.materialiseProto()
|
||
|
}
|
||
|
return o._setForeignStr(name, ownProp, val, receiver, throw)
|
||
|
}
|
||
|
|
||
|
func (o *templatedObject) proto() *Object {
|
||
|
o.materialiseProto()
|
||
|
return o.prototype
|
||
|
}
|
||
|
|
||
|
func (o *templatedObject) setProto(proto *Object, throw bool) bool {
|
||
|
o.protoMaterialised = true
|
||
|
ret := o.baseObject.setProto(proto, throw)
|
||
|
if ret {
|
||
|
o.protoMaterialised = true
|
||
|
}
|
||
|
return ret
|
||
|
}
|
||
|
|
||
|
func (o *templatedObject) setForeignIdx(name valueInt, val, receiver Value, throw bool) (bool, bool) {
|
||
|
return o.setForeignStr(name.string(), val, receiver, throw)
|
||
|
}
|
||
|
|
||
|
func (o *templatedObject) setForeignSym(name *Symbol, val, receiver Value, throw bool) (bool, bool) {
|
||
|
o.materialiseProto()
|
||
|
o.materialiseSymbols()
|
||
|
return o.baseObject.setForeignSym(name, val, receiver, throw)
|
||
|
}
|
||
|
|
||
|
func (o *templatedObject) hasPropertyStr(name unistring.String) bool {
|
||
|
if o.val.self.hasOwnPropertyStr(name) {
|
||
|
return true
|
||
|
}
|
||
|
o.materialiseProto()
|
||
|
if o.prototype != nil {
|
||
|
return o.prototype.self.hasPropertyStr(name)
|
||
|
}
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
func (o *templatedObject) hasPropertySym(s *Symbol) bool {
|
||
|
if o.hasOwnPropertySym(s) {
|
||
|
return true
|
||
|
}
|
||
|
o.materialiseProto()
|
||
|
if o.prototype != nil {
|
||
|
return o.prototype.self.hasPropertySym(s)
|
||
|
}
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
func (o *templatedObject) hasOwnPropertyStr(name unistring.String) bool {
|
||
|
if v, exists := o.values[name]; exists {
|
||
|
return v != nil
|
||
|
}
|
||
|
|
||
|
_, exists := o.tmpl.props[name]
|
||
|
return exists
|
||
|
}
|
||
|
|
||
|
func (o *templatedObject) hasOwnPropertySym(s *Symbol) bool {
|
||
|
if o.symValues != nil {
|
||
|
return o.symValues.has(s)
|
||
|
}
|
||
|
_, exists := o.tmpl.symProps[s]
|
||
|
return exists
|
||
|
}
|
||
|
|
||
|
func (o *templatedObject) defineOwnPropertyStr(name unistring.String, descr PropertyDescriptor, throw bool) bool {
|
||
|
existingVal := o.getOwnPropStr(name)
|
||
|
if v, ok := o._defineOwnProperty(name, existingVal, descr, throw); ok {
|
||
|
o.values[name] = v
|
||
|
if existingVal == nil {
|
||
|
o.materialisePropNames()
|
||
|
names := copyNamesIfNeeded(o.propNames, 1)
|
||
|
o.propNames = append(names, name)
|
||
|
}
|
||
|
return true
|
||
|
}
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
func (o *templatedObject) defineOwnPropertySym(s *Symbol, descr PropertyDescriptor, throw bool) bool {
|
||
|
o.materialiseSymbols()
|
||
|
return o.baseObject.defineOwnPropertySym(s, descr, throw)
|
||
|
}
|
||
|
|
||
|
func (o *templatedObject) deleteStr(name unistring.String, throw bool) bool {
|
||
|
if val := o.getOwnPropStr(name); val != nil {
|
||
|
if !o.checkDelete(name, val, throw) {
|
||
|
return false
|
||
|
}
|
||
|
o.materialisePropNames()
|
||
|
o._delete(name)
|
||
|
if _, exists := o.tmpl.props[name]; exists {
|
||
|
o.values[name] = nil // white hole
|
||
|
}
|
||
|
}
|
||
|
return true
|
||
|
}
|
||
|
|
||
|
func (o *templatedObject) deleteSym(s *Symbol, throw bool) bool {
|
||
|
o.materialiseSymbols()
|
||
|
return o.baseObject.deleteSym(s, throw)
|
||
|
}
|
||
|
|
||
|
func (o *templatedObject) materialiseProps() {
|
||
|
for name, f := range o.tmpl.props {
|
||
|
if _, exists := o.values[name]; !exists {
|
||
|
o.values[name] = f(o.val.runtime)
|
||
|
}
|
||
|
}
|
||
|
o.materialisePropNames()
|
||
|
}
|
||
|
|
||
|
func (o *templatedObject) iterateStringKeys() iterNextFunc {
|
||
|
o.materialiseProps()
|
||
|
return o.baseObject.iterateStringKeys()
|
||
|
}
|
||
|
|
||
|
func (o *templatedObject) iterateSymbols() iterNextFunc {
|
||
|
o.materialiseSymbols()
|
||
|
return o.baseObject.iterateSymbols()
|
||
|
}
|
||
|
|
||
|
func (o *templatedObject) stringKeys(all bool, keys []Value) []Value {
|
||
|
if all {
|
||
|
o.materialisePropNames()
|
||
|
} else {
|
||
|
o.materialiseProps()
|
||
|
}
|
||
|
return o.baseObject.stringKeys(all, keys)
|
||
|
}
|
||
|
|
||
|
func (o *templatedObject) symbols(all bool, accum []Value) []Value {
|
||
|
o.materialiseSymbols()
|
||
|
return o.baseObject.symbols(all, accum)
|
||
|
}
|
||
|
|
||
|
func (o *templatedObject) keys(all bool, accum []Value) []Value {
|
||
|
return o.symbols(all, o.stringKeys(all, accum))
|
||
|
}
|
||
|
|
||
|
func (r *Runtime) newTemplatedFuncObject(tmpl *objectTemplate, obj *Object, f func(FunctionCall) Value, ctor func([]Value, *Object) *Object) *templatedFuncObject {
|
||
|
if obj == nil {
|
||
|
obj = &Object{runtime: r}
|
||
|
}
|
||
|
o := &templatedFuncObject{
|
||
|
templatedObject: templatedObject{
|
||
|
baseObject: baseObject{
|
||
|
class: classFunction,
|
||
|
val: obj,
|
||
|
extensible: true,
|
||
|
},
|
||
|
tmpl: tmpl,
|
||
|
},
|
||
|
f: f,
|
||
|
construct: ctor,
|
||
|
}
|
||
|
obj.self = o
|
||
|
o.init()
|
||
|
return o
|
||
|
}
|
||
|
|
||
|
func (f *templatedFuncObject) source() String {
|
||
|
return newStringValue(fmt.Sprintf("function %s() { [native code] }", nilSafe(f.getStr("name", nil)).toString()))
|
||
|
}
|
||
|
|
||
|
func (f *templatedFuncObject) export(*objectExportCtx) interface{} {
|
||
|
return f.f
|
||
|
}
|
||
|
|
||
|
func (f *templatedFuncObject) assertCallable() (func(FunctionCall) Value, bool) {
|
||
|
if f.f != nil {
|
||
|
return f.f, true
|
||
|
}
|
||
|
return nil, false
|
||
|
}
|
||
|
|
||
|
func (f *templatedFuncObject) vmCall(vm *vm, n int) {
|
||
|
var nf nativeFuncObject
|
||
|
nf.f = f.f
|
||
|
nf.vmCall(vm, n)
|
||
|
}
|
||
|
|
||
|
func (f *templatedFuncObject) assertConstructor() func(args []Value, newTarget *Object) *Object {
|
||
|
return f.construct
|
||
|
}
|
||
|
|
||
|
func (f *templatedFuncObject) exportType() reflect.Type {
|
||
|
return reflectTypeFunc
|
||
|
}
|
||
|
|
||
|
func (f *templatedFuncObject) typeOf() String {
|
||
|
return stringFunction
|
||
|
}
|
||
|
|
||
|
func (f *templatedFuncObject) hasInstance(v Value) bool {
|
||
|
return hasInstance(f.val, v)
|
||
|
}
|
||
|
|
||
|
func (r *Runtime) newTemplatedArrayObject(tmpl *objectTemplate, obj *Object) *templatedArrayObject {
|
||
|
if obj == nil {
|
||
|
obj = &Object{runtime: r}
|
||
|
}
|
||
|
o := &templatedArrayObject{
|
||
|
templatedObject: templatedObject{
|
||
|
baseObject: baseObject{
|
||
|
class: classArray,
|
||
|
val: obj,
|
||
|
extensible: true,
|
||
|
},
|
||
|
tmpl: tmpl,
|
||
|
},
|
||
|
}
|
||
|
obj.self = o
|
||
|
o.init()
|
||
|
return o
|
||
|
}
|
||
|
|
||
|
func (a *templatedArrayObject) getLenProp() *valueProperty {
|
||
|
lenProp, _ := a.getOwnPropStr("length").(*valueProperty)
|
||
|
if lenProp == nil {
|
||
|
panic(a.val.runtime.NewTypeError("missing length property"))
|
||
|
}
|
||
|
return lenProp
|
||
|
}
|
||
|
|
||
|
func (a *templatedArrayObject) _setOwnIdx(idx uint32) {
|
||
|
lenProp := a.getLenProp()
|
||
|
l := uint32(lenProp.value.ToInteger())
|
||
|
if idx >= l {
|
||
|
lenProp.value = intToValue(int64(idx) + 1)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (a *templatedArrayObject) setLength(l uint32, throw bool) bool {
|
||
|
lenProp := a.getLenProp()
|
||
|
oldLen := uint32(lenProp.value.ToInteger())
|
||
|
if l == oldLen {
|
||
|
return true
|
||
|
}
|
||
|
if !lenProp.writable {
|
||
|
a.val.runtime.typeErrorResult(throw, "length is not writable")
|
||
|
return false
|
||
|
}
|
||
|
ret := true
|
||
|
if l < oldLen {
|
||
|
a.materialisePropNames()
|
||
|
a.fixPropOrder()
|
||
|
i := sort.Search(a.idxPropCount, func(idx int) bool {
|
||
|
return strToArrayIdx(a.propNames[idx]) >= l
|
||
|
})
|
||
|
for j := a.idxPropCount - 1; j >= i; j-- {
|
||
|
if !a.deleteStr(a.propNames[j], false) {
|
||
|
l = strToArrayIdx(a.propNames[j]) + 1
|
||
|
ret = false
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
lenProp.value = intToValue(int64(l))
|
||
|
return ret
|
||
|
}
|
||
|
|
||
|
func (a *templatedArrayObject) setOwnStr(name unistring.String, value Value, throw bool) bool {
|
||
|
if name == "length" {
|
||
|
return a.setLength(a.val.runtime.toLengthUint32(value), throw)
|
||
|
}
|
||
|
if !a.templatedObject.setOwnStr(name, value, throw) {
|
||
|
return false
|
||
|
}
|
||
|
if idx := strToArrayIdx(name); idx != math.MaxUint32 {
|
||
|
a._setOwnIdx(idx)
|
||
|
}
|
||
|
return true
|
||
|
}
|
||
|
|
||
|
func (a *templatedArrayObject) setOwnIdx(p valueInt, v Value, throw bool) bool {
|
||
|
if !a.templatedObject.setOwnStr(p.string(), v, throw) {
|
||
|
return false
|
||
|
}
|
||
|
if idx := toIdx(p); idx != math.MaxUint32 {
|
||
|
a._setOwnIdx(idx)
|
||
|
}
|
||
|
return true
|
||
|
}
|
||
|
|
||
|
func (a *templatedArrayObject) defineOwnPropertyStr(name unistring.String, descr PropertyDescriptor, throw bool) bool {
|
||
|
if name == "length" {
|
||
|
return a.val.runtime.defineArrayLength(a.getLenProp(), descr, a.setLength, throw)
|
||
|
}
|
||
|
if !a.templatedObject.defineOwnPropertyStr(name, descr, throw) {
|
||
|
return false
|
||
|
}
|
||
|
if idx := strToArrayIdx(name); idx != math.MaxUint32 {
|
||
|
a._setOwnIdx(idx)
|
||
|
}
|
||
|
return true
|
||
|
}
|
||
|
|
||
|
func (a *templatedArrayObject) defineOwnPropertyIdx(p valueInt, desc PropertyDescriptor, throw bool) bool {
|
||
|
if !a.templatedObject.defineOwnPropertyStr(p.string(), desc, throw) {
|
||
|
return false
|
||
|
}
|
||
|
if idx := toIdx(p); idx != math.MaxUint32 {
|
||
|
a._setOwnIdx(idx)
|
||
|
}
|
||
|
return true
|
||
|
}
|