Browse Source

Implemented Map, Set, WeakMap, WeakSet (#130)

Dmitry Panov 5 years ago
parent
commit
0a7d410a91
16 changed files with 1630 additions and 33 deletions
  1. 1 0
      builtin_array.go
  2. 2 12
      builtin_date.go
  3. 267 0
      builtin_map.go
  4. 242 0
      builtin_set.go
  5. 202 0
      builtin_weakmap.go
  6. 33 0
      builtin_weakmap_test.go
  7. 177 0
      builtin_weakset.go
  8. 88 0
      builtin_weakset_test.go
  9. 163 0
      map.go
  10. 196 0
      map_test.go
  11. 76 0
      object.go
  12. 75 5
      runtime.go
  13. 7 0
      string_ascii.go
  14. 8 0
      string_unicode.go
  15. 42 16
      tc39_test.go
  16. 51 0
      value.go

+ 1 - 0
builtin_array.go

@@ -881,6 +881,7 @@ func (r *Runtime) createArrayProto(val *Object) objectImpl {
 	valuesFunc := r.newNativeFunc(r.arrayproto_values, nil, "values", nil, 0)
 	o._putProp("values", valuesFunc, true, false, true)
 	o.put(symIterator, valueProp(valuesFunc, false, false, true), true)
+	r.global.arrayValues = valuesFunc
 
 	return o
 }

+ 2 - 12
builtin_date.go

@@ -111,7 +111,7 @@ func (r *Runtime) builtin_newDate(args []Value) *Object {
 	return r.newDateTime(args, time.Local)
 }
 
-func (r *Runtime) builtin_date(call FunctionCall) Value {
+func (r *Runtime) builtin_date(FunctionCall) Value {
 	return asciiString(dateFormat(r.now()))
 }
 
@@ -131,7 +131,7 @@ func (r *Runtime) date_UTC(call FunctionCall) Value {
 	return intToValue(timeToMsec(t))
 }
 
-func (r *Runtime) date_now(call FunctionCall) Value {
+func (r *Runtime) date_now(FunctionCall) Value {
 	return intToValue(timeToMsec(r.now()))
 }
 
@@ -910,16 +910,6 @@ func (r *Runtime) createDate(val *Object) objectImpl {
 	return o
 }
 
-func (r *Runtime) newLazyObject(create func(*Object) objectImpl) *Object {
-	val := &Object{runtime: r}
-	o := &lazyObject{
-		val:    val,
-		create: create,
-	}
-	val.self = o
-	return val
-}
-
 func (r *Runtime) initDate() {
 	//r.global.DatePrototype = r.newObject()
 	//o := r.global.DatePrototype.self

+ 267 - 0
builtin_map.go

@@ -0,0 +1,267 @@
+package goja
+
+type mapObject struct {
+	baseObject
+	m *orderedMap
+}
+
+type mapIterObject struct {
+	baseObject
+	iter *orderedMapIter
+	kind iterationKind
+}
+
+func (o *mapIterObject) next() Value {
+	if o.iter == nil {
+		return o.val.runtime.createIterResultObject(_undefined, true)
+	}
+
+	entry := o.iter.next()
+	if entry == nil {
+		o.iter = nil
+		return o.val.runtime.createIterResultObject(_undefined, true)
+	}
+
+	var result Value
+	switch o.kind {
+	case iterationKindKey:
+		result = entry.key
+	case iterationKindValue:
+		result = entry.value
+	default:
+		result = o.val.runtime.newArrayValues([]Value{entry.key, entry.value})
+	}
+
+	return o.val.runtime.createIterResultObject(result, false)
+}
+
+func (mo *mapObject) init() {
+	mo.baseObject.init()
+	mo.m = newOrderedMap()
+}
+
+func (r *Runtime) mapProto_clear(call FunctionCall) Value {
+	thisObj := r.toObject(call.This)
+	mo, ok := thisObj.self.(*mapObject)
+	if !ok {
+		panic(r.NewTypeError("Method Map.prototype.clear called on incompatible receiver %s", thisObj.String()))
+	}
+
+	mo.m.clear()
+
+	return _undefined
+}
+
+func (r *Runtime) mapProto_delete(call FunctionCall) Value {
+	thisObj := r.toObject(call.This)
+	mo, ok := thisObj.self.(*mapObject)
+	if !ok {
+		panic(r.NewTypeError("Method Map.prototype.delete called on incompatible receiver %s", thisObj.String()))
+	}
+
+	return r.toBoolean(mo.m.remove(call.Argument(0)))
+}
+
+func (r *Runtime) mapProto_get(call FunctionCall) Value {
+	thisObj := r.toObject(call.This)
+	mo, ok := thisObj.self.(*mapObject)
+	if !ok {
+		panic(r.NewTypeError("Method Map.prototype.get called on incompatible receiver %s", thisObj.String()))
+	}
+
+	return nilSafe(mo.m.get(call.Argument(0)))
+}
+
+func (r *Runtime) mapProto_has(call FunctionCall) Value {
+	thisObj := r.toObject(call.This)
+	mo, ok := thisObj.self.(*mapObject)
+	if !ok {
+		panic(r.NewTypeError("Method Map.prototype.has called on incompatible receiver %s", thisObj.String()))
+	}
+	if mo.m.has(call.Argument(0)) {
+		return valueTrue
+	}
+	return valueFalse
+}
+
+func (r *Runtime) mapProto_set(call FunctionCall) Value {
+	thisObj := r.toObject(call.This)
+	mo, ok := thisObj.self.(*mapObject)
+	if !ok {
+		panic(r.NewTypeError("Method Map.prototype.set called on incompatible receiver %s", thisObj.String()))
+	}
+	mo.m.set(call.Argument(0), call.Argument(1))
+	return call.This
+}
+
+func (r *Runtime) mapProto_entries(call FunctionCall) Value {
+	return r.createMapIterator(call.This, iterationKindKeyValue)
+}
+
+func (r *Runtime) mapProto_forEach(call FunctionCall) Value {
+	thisObj := r.toObject(call.This)
+	mo, ok := thisObj.self.(*mapObject)
+	if !ok {
+		panic(r.NewTypeError("Method Map.prototype.forEach called on incompatible receiver %s", thisObj.String()))
+	}
+	callbackFn, ok := r.toObject(call.Argument(0)).self.assertCallable()
+	if !ok {
+		panic(r.NewTypeError("object is not a function %s"))
+	}
+	t := call.Argument(1)
+	iter := mo.m.newIter()
+	for {
+		entry := iter.next()
+		if entry == nil {
+			break
+		}
+		callbackFn(FunctionCall{This: t, Arguments: []Value{entry.value, entry.key, thisObj}})
+	}
+
+	return _undefined
+}
+
+func (r *Runtime) mapProto_keys(call FunctionCall) Value {
+	return r.createMapIterator(call.This, iterationKindKey)
+}
+
+func (r *Runtime) mapProto_values(call FunctionCall) Value {
+	return r.createMapIterator(call.This, iterationKindValue)
+}
+
+func (r *Runtime) mapProto_getSize(call FunctionCall) Value {
+	thisObj := r.toObject(call.This)
+	mo, ok := thisObj.self.(*mapObject)
+	if !ok {
+		panic(r.NewTypeError("Method get Map.prototype.size called on incompatible receiver %s", thisObj.String()))
+	}
+	return intToValue(int64(mo.m.size))
+}
+
+func (r *Runtime) builtin_newMap(args []Value) *Object {
+	o := &Object{runtime: r}
+
+	mo := &mapObject{}
+	mo.class = classMap
+	mo.val = o
+	mo.extensible = true
+	o.self = mo
+	mo.prototype = r.global.MapPrototype
+	mo.init()
+	if len(args) > 0 {
+		if arg := args[0]; arg != nil && arg != _undefined && arg != _null {
+			adder := mo.getStr("set")
+			iter := r.getIterator(arg.ToObject(r), nil)
+			i0 := intToValue(0)
+			i1 := intToValue(1)
+			if adder == r.global.mapAdder {
+				r.iterate(iter, func(item Value) {
+					itemObj := r.toObject(item)
+					k := itemObj.self.get(i0)
+					v := itemObj.self.get(i1)
+					mo.m.set(k, v)
+				})
+			} else {
+				adderFn := toMethod(adder)
+				if adderFn == nil {
+					panic(r.NewTypeError("Map.set in missing"))
+				}
+				r.iterate(iter, func(item Value) {
+					itemObj := r.toObject(item)
+					k := itemObj.self.get(i0)
+					v := itemObj.self.get(i1)
+					adderFn(FunctionCall{This: o, Arguments: []Value{k, v}})
+				})
+			}
+		}
+	}
+	return o
+}
+
+func (r *Runtime) createMapIterator(mapValue Value, kind iterationKind) Value {
+	obj := r.toObject(mapValue)
+	mapObj, ok := obj.self.(*mapObject)
+	if !ok {
+		panic(r.NewTypeError("Object is not a Map"))
+	}
+
+	o := &Object{runtime: r}
+
+	mi := &mapIterObject{
+		iter: mapObj.m.newIter(),
+		kind: kind,
+	}
+	mi.class = classMapIterator
+	mi.val = o
+	mi.extensible = true
+	o.self = mi
+	mi.prototype = r.global.MapIteratorPrototype
+	mi.init()
+
+	return o
+}
+
+func (r *Runtime) mapIterProto_next(call FunctionCall) Value {
+	thisObj := r.toObject(call.This)
+	if iter, ok := thisObj.self.(*mapIterObject); ok {
+		return iter.next()
+	}
+	panic(r.NewTypeError("Method Map Iterator.prototype.next called on incompatible receiver %s", thisObj.String()))
+}
+
+func (r *Runtime) createMapProto(val *Object) objectImpl {
+	o := newBaseObjectObj(val, r.global.ObjectPrototype, classObject)
+
+	o._putProp("constructor", r.global.Map, true, false, true)
+	o._putProp("clear", r.newNativeFunc(r.mapProto_clear, nil, "clear", nil, 0), true, false, true)
+	r.global.mapAdder = r.newNativeFunc(r.mapProto_set, nil, "set", nil, 2)
+	o._putProp("set", r.global.mapAdder, true, false, true)
+	o._putProp("delete", r.newNativeFunc(r.mapProto_delete, nil, "delete", nil, 1), true, false, true)
+	o._putProp("forEach", r.newNativeFunc(r.mapProto_forEach, nil, "forEach", nil, 1), true, false, true)
+	o._putProp("has", r.newNativeFunc(r.mapProto_has, nil, "has", nil, 1), true, false, true)
+	o._putProp("get", r.newNativeFunc(r.mapProto_get, nil, "get", nil, 1), true, false, true)
+	o.putStr("size", &valueProperty{
+		getterFunc:   r.newNativeFunc(r.mapProto_getSize, nil, "get size", nil, 0),
+		accessor:     true,
+		writable:     true,
+		configurable: true,
+	}, true)
+	o._putProp("keys", r.newNativeFunc(r.mapProto_keys, nil, "keys", nil, 0), true, false, true)
+	o._putProp("values", r.newNativeFunc(r.mapProto_values, nil, "values", nil, 0), true, false, true)
+
+	entriesFunc := r.newNativeFunc(r.mapProto_entries, nil, "entries", nil, 0)
+	o._putProp("entries", entriesFunc, true, false, true)
+	o.put(symIterator, valueProp(entriesFunc, true, false, true), true)
+	o.put(symToStringTag, valueProp(asciiString(classMap), false, false, true), true)
+
+	return o
+}
+
+func (r *Runtime) createMap(val *Object) objectImpl {
+	o := r.newNativeFuncObj(val, r.constructorThrower("Map"), r.builtin_newMap, "Map", r.global.MapPrototype, 0)
+	o.putSym(symSpecies, &valueProperty{
+		getterFunc:   r.newNativeFunc(r.returnThis, nil, "get [Symbol.species]", nil, 0),
+		accessor:     true,
+		configurable: true,
+	}, true)
+
+	return o
+}
+
+func (r *Runtime) createMapIterProto(val *Object) objectImpl {
+	o := newBaseObjectObj(val, r.global.IteratorPrototype, classObject)
+
+	o._putProp("next", r.newNativeFunc(r.mapIterProto_next, nil, "next", nil, 0), true, false, true)
+	o.put(symToStringTag, valueProp(asciiString(classMapIterator), false, false, true), true)
+
+	return o
+}
+
+func (r *Runtime) initMap() {
+	r.global.MapIteratorPrototype = r.newLazyObject(r.createMapIterProto)
+
+	r.global.MapPrototype = r.newLazyObject(r.createMapProto)
+	r.global.Map = r.newLazyObject(r.createMap)
+
+	r.addToGlobal("Map", r.global.Map)
+}

+ 242 - 0
builtin_set.go

@@ -0,0 +1,242 @@
+package goja
+
+type setObject struct {
+	baseObject
+	m *orderedMap
+}
+
+type setIterObject struct {
+	baseObject
+	iter *orderedMapIter
+	kind iterationKind
+}
+
+func (o *setIterObject) next() Value {
+	if o.iter == nil {
+		return o.val.runtime.createIterResultObject(_undefined, true)
+	}
+
+	entry := o.iter.next()
+	if entry == nil {
+		o.iter = nil
+		return o.val.runtime.createIterResultObject(_undefined, true)
+	}
+
+	var result Value
+	switch o.kind {
+	case iterationKindValue:
+		result = entry.key
+	default:
+		result = o.val.runtime.newArrayValues([]Value{entry.key, entry.key})
+	}
+
+	return o.val.runtime.createIterResultObject(result, false)
+}
+
+func (so *setObject) init() {
+	so.baseObject.init()
+	so.m = newOrderedMap()
+}
+
+func (r *Runtime) setProto_add(call FunctionCall) Value {
+	thisObj := r.toObject(call.This)
+	so, ok := thisObj.self.(*setObject)
+	if !ok {
+		panic(r.NewTypeError("Method Set.prototype.add called on incompatible receiver %s", thisObj.String()))
+	}
+
+	so.m.set(call.Argument(0), nil)
+	return call.This
+}
+
+func (r *Runtime) setProto_clear(call FunctionCall) Value {
+	thisObj := r.toObject(call.This)
+	so, ok := thisObj.self.(*setObject)
+	if !ok {
+		panic(r.NewTypeError("Method Set.prototype.clear called on incompatible receiver %s", thisObj.String()))
+	}
+
+	so.m.clear()
+	return _undefined
+}
+
+func (r *Runtime) setProto_delete(call FunctionCall) Value {
+	thisObj := r.toObject(call.This)
+	so, ok := thisObj.self.(*setObject)
+	if !ok {
+		panic(r.NewTypeError("Method Set.prototype.delete called on incompatible receiver %s", thisObj.String()))
+	}
+
+	return r.toBoolean(so.m.remove(call.Argument(0)))
+}
+
+func (r *Runtime) setProto_entries(call FunctionCall) Value {
+	return r.createSetIterator(call.This, iterationKindKeyValue)
+}
+
+func (r *Runtime) setProto_forEach(call FunctionCall) Value {
+	thisObj := r.toObject(call.This)
+	so, ok := thisObj.self.(*setObject)
+	if !ok {
+		panic(r.NewTypeError("Method Set.prototype.forEach called on incompatible receiver %s", thisObj.String()))
+	}
+	callbackFn, ok := r.toObject(call.Argument(0)).self.assertCallable()
+	if !ok {
+		panic(r.NewTypeError("object is not a function %s"))
+	}
+	t := call.Argument(1)
+	iter := so.m.newIter()
+	for {
+		entry := iter.next()
+		if entry == nil {
+			break
+		}
+		callbackFn(FunctionCall{This: t, Arguments: []Value{entry.key, entry.key, thisObj}})
+	}
+
+	return _undefined
+}
+
+func (r *Runtime) setProto_has(call FunctionCall) Value {
+	thisObj := r.toObject(call.This)
+	so, ok := thisObj.self.(*setObject)
+	if !ok {
+		panic(r.NewTypeError("Method Set.prototype.has called on incompatible receiver %s", thisObj.String()))
+	}
+
+	return r.toBoolean(so.m.has(call.Argument(0)))
+}
+
+func (r *Runtime) setProto_getSize(call FunctionCall) Value {
+	thisObj := r.toObject(call.This)
+	so, ok := thisObj.self.(*setObject)
+	if !ok {
+		panic(r.NewTypeError("Method get Set.prototype.size called on incompatible receiver %s", thisObj.String()))
+	}
+
+	return intToValue(int64(so.m.size))
+}
+
+func (r *Runtime) setProto_values(call FunctionCall) Value {
+	return r.createSetIterator(call.This, iterationKindValue)
+}
+
+func (r *Runtime) builtin_newSet(args []Value) *Object {
+	o := &Object{runtime: r}
+
+	so := &setObject{}
+	so.class = classSet
+	so.val = o
+	so.extensible = true
+	o.self = so
+	so.prototype = r.global.SetPrototype
+	so.init()
+	if len(args) > 0 {
+		if arg := args[0]; arg != nil && arg != _undefined && arg != _null {
+			adder := so.getStr("add")
+			iter := r.getIterator(arg.ToObject(r), nil)
+			if adder == r.global.setAdder {
+				r.iterate(iter, func(item Value) {
+					so.m.set(item, nil)
+				})
+			} else {
+				adderFn := toMethod(adder)
+				if adderFn == nil {
+					panic(r.NewTypeError("Set.add in missing"))
+				}
+				r.iterate(iter, func(item Value) {
+					adderFn(FunctionCall{This: o, Arguments: []Value{item}})
+				})
+			}
+		}
+	}
+	return o
+}
+
+func (r *Runtime) createSetIterator(setValue Value, kind iterationKind) Value {
+	obj := r.toObject(setValue)
+	setObj, ok := obj.self.(*setObject)
+	if !ok {
+		panic(r.NewTypeError("Object is not a Set"))
+	}
+
+	o := &Object{runtime: r}
+
+	si := &setIterObject{
+		iter: setObj.m.newIter(),
+		kind: kind,
+	}
+	si.class = classSetIterator
+	si.val = o
+	si.extensible = true
+	o.self = si
+	si.prototype = r.global.SetIteratorPrototype
+	si.init()
+
+	return o
+}
+
+func (r *Runtime) setIterProto_next(call FunctionCall) Value {
+	thisObj := r.toObject(call.This)
+	if iter, ok := thisObj.self.(*setIterObject); ok {
+		return iter.next()
+	}
+	panic(r.NewTypeError("Method Set Iterator.prototype.next called on incompatible receiver %s", thisObj.String()))
+}
+
+func (r *Runtime) createSetProto(val *Object) objectImpl {
+	o := newBaseObjectObj(val, r.global.ObjectPrototype, classObject)
+
+	o._putProp("constructor", r.global.Set, true, false, true)
+	r.global.setAdder = r.newNativeFunc(r.setProto_add, nil, "add", nil, 1)
+	o._putProp("add", r.global.setAdder, true, false, true)
+
+	o._putProp("clear", r.newNativeFunc(r.setProto_clear, nil, "clear", nil, 0), true, false, true)
+	o._putProp("delete", r.newNativeFunc(r.setProto_delete, nil, "delete", nil, 1), true, false, true)
+	o._putProp("forEach", r.newNativeFunc(r.setProto_forEach, nil, "forEach", nil, 1), true, false, true)
+	o._putProp("has", r.newNativeFunc(r.setProto_has, nil, "has", nil, 1), true, false, true)
+	o.putStr("size", &valueProperty{
+		getterFunc:   r.newNativeFunc(r.setProto_getSize, nil, "get size", nil, 0),
+		accessor:     true,
+		writable:     true,
+		configurable: true,
+	}, true)
+
+	valuesFunc := r.newNativeFunc(r.setProto_values, nil, "values", nil, 0)
+	o._putProp("values", valuesFunc, true, false, true)
+	o._putProp("keys", valuesFunc, true, false, true)
+	o._putProp("entries", r.newNativeFunc(r.setProto_entries, nil, "entries", nil, 0), true, false, true)
+	o.put(symIterator, valueProp(valuesFunc, true, false, true), true)
+	o.put(symToStringTag, valueProp(asciiString(classSet), false, false, true), true)
+
+	return o
+}
+
+func (r *Runtime) createSet(val *Object) objectImpl {
+	o := r.newNativeFuncObj(val, r.constructorThrower("Set"), r.builtin_newSet, "Set", r.global.SetPrototype, 0)
+	o.putSym(symSpecies, &valueProperty{
+		getterFunc:   r.newNativeFunc(r.returnThis, nil, "get [Symbol.species]", nil, 0),
+		accessor:     true,
+		configurable: true,
+	}, true)
+
+	return o
+}
+
+func (r *Runtime) createSetIterProto(val *Object) objectImpl {
+	o := newBaseObjectObj(val, r.global.IteratorPrototype, classObject)
+
+	o._putProp("next", r.newNativeFunc(r.setIterProto_next, nil, "next", nil, 0), true, false, true)
+	o.put(symToStringTag, valueProp(asciiString(classSetIterator), false, false, true), true)
+
+	return o
+}
+
+func (r *Runtime) initSet() {
+	r.global.SetIteratorPrototype = r.newLazyObject(r.createSetIterProto)
+
+	r.global.SetPrototype = r.newLazyObject(r.createSetProto)
+	r.global.Set = r.newLazyObject(r.createSet)
+
+	r.addToGlobal("Set", r.global.Set)
+}

+ 202 - 0
builtin_weakmap.go

@@ -0,0 +1,202 @@
+package goja
+
+import "sync"
+
+type weakMap struct {
+	// need to synchronise access to the data map because it may be accessed
+	// from the finalizer goroutine
+	sync.Mutex
+	data map[uintptr]Value
+}
+
+type weakMapObject struct {
+	baseObject
+	m *weakMap
+}
+
+func newWeakMap() *weakMap {
+	return &weakMap{
+		data: make(map[uintptr]Value),
+	}
+}
+
+func (wmo *weakMapObject) init() {
+	wmo.baseObject.init()
+	wmo.m = newWeakMap()
+}
+
+func (wm *weakMap) removePtr(ptr uintptr) {
+	wm.Lock()
+	delete(wm.data, ptr)
+	wm.Unlock()
+}
+
+func (wm *weakMap) set(key *Object, value Value) {
+	refs := key.getWeakCollRefs()
+	wm.Lock()
+	wm.data[refs.id()] = value
+	wm.Unlock()
+	refs.add(wm)
+}
+
+func (wm *weakMap) get(key *Object) Value {
+	refs := key.weakColls
+	if refs == nil {
+		return nil
+	}
+	wm.Lock()
+	ret := wm.data[refs.id()]
+	wm.Unlock()
+	return ret
+}
+
+func (wm *weakMap) remove(key *Object) bool {
+	refs := key.weakColls
+	if refs == nil {
+		return false
+	}
+	id := refs.id()
+	wm.Lock()
+	_, exists := wm.data[id]
+	if exists {
+		delete(wm.data, id)
+	}
+	wm.Unlock()
+	if exists {
+		refs.remove(wm)
+	}
+	return exists
+}
+
+func (wm *weakMap) has(key *Object) bool {
+	refs := key.weakColls
+	if refs == nil {
+		return false
+	}
+	id := refs.id()
+	wm.Lock()
+	_, exists := wm.data[id]
+	wm.Unlock()
+	return exists
+}
+
+func (r *Runtime) weakMapProto_delete(call FunctionCall) Value {
+	thisObj := r.toObject(call.This)
+	wmo, ok := thisObj.self.(*weakMapObject)
+	if !ok {
+		panic(r.NewTypeError("Method WeakMap.prototype.delete called on incompatible receiver %s", thisObj.String()))
+	}
+	key, ok := call.Argument(0).(*Object)
+	if ok && wmo.m.remove(key) {
+		return valueTrue
+	}
+	return valueFalse
+}
+
+func (r *Runtime) weakMapProto_get(call FunctionCall) Value {
+	thisObj := r.toObject(call.This)
+	wmo, ok := thisObj.self.(*weakMapObject)
+	if !ok {
+		panic(r.NewTypeError("Method WeakMap.prototype.get called on incompatible receiver %s", thisObj.String()))
+	}
+	var res Value
+	if key, ok := call.Argument(0).(*Object); ok {
+		res = wmo.m.get(key)
+	}
+	if res == nil {
+		return _undefined
+	}
+	return res
+}
+
+func (r *Runtime) weakMapProto_has(call FunctionCall) Value {
+	thisObj := r.toObject(call.This)
+	wmo, ok := thisObj.self.(*weakMapObject)
+	if !ok {
+		panic(r.NewTypeError("Method WeakMap.prototype.has called on incompatible receiver %s", thisObj.String()))
+	}
+	key, ok := call.Argument(0).(*Object)
+	if ok && wmo.m.has(key) {
+		return valueTrue
+	}
+	return valueFalse
+}
+
+func (r *Runtime) weakMapProto_set(call FunctionCall) Value {
+	thisObj := r.toObject(call.This)
+	wmo, ok := thisObj.self.(*weakMapObject)
+	if !ok {
+		panic(r.NewTypeError("Method WeakMap.prototype.set called on incompatible receiver %s", thisObj.String()))
+	}
+	key := r.toObject(call.Argument(0))
+	wmo.m.set(key, call.Argument(1))
+	return call.This
+}
+
+func (r *Runtime) builtin_newWeakMap(args []Value) *Object {
+	o := &Object{runtime: r}
+
+	wmo := &weakMapObject{}
+	wmo.class = classWeakMap
+	wmo.val = o
+	wmo.extensible = true
+	o.self = wmo
+	wmo.prototype = r.global.WeakMapPrototype
+	wmo.init()
+	if len(args) > 0 {
+		if arg := args[0]; arg != nil && arg != _undefined && arg != _null {
+			adder := wmo.getStr("set")
+			iter := r.getIterator(arg.ToObject(r), nil)
+			i0 := intToValue(0)
+			i1 := intToValue(1)
+			if adder == r.global.weakMapAdder {
+				r.iterate(iter, func(item Value) {
+					itemObj := r.toObject(item)
+					k := itemObj.self.get(i0)
+					v := itemObj.self.get(i1)
+					wmo.m.set(r.toObject(k), v)
+				})
+			} else {
+				adderFn := toMethod(adder)
+				if adderFn == nil {
+					panic(r.NewTypeError("WeakMap.set in missing"))
+				}
+				r.iterate(iter, func(item Value) {
+					itemObj := r.toObject(item)
+					k := itemObj.self.get(i0)
+					v := itemObj.self.get(i1)
+					adderFn(FunctionCall{This: o, Arguments: []Value{k, v}})
+				})
+			}
+		}
+	}
+	return o
+}
+
+func (r *Runtime) createWeakMapProto(val *Object) objectImpl {
+	o := newBaseObjectObj(val, r.global.ObjectPrototype, classObject)
+
+	o._putProp("constructor", r.global.WeakMap, true, false, true)
+	r.global.weakMapAdder = r.newNativeFunc(r.weakMapProto_set, nil, "set", nil, 2)
+	o._putProp("set", r.global.weakMapAdder, true, false, true)
+	o._putProp("delete", r.newNativeFunc(r.weakMapProto_delete, nil, "delete", nil, 1), true, false, true)
+	o._putProp("has", r.newNativeFunc(r.weakMapProto_has, nil, "has", nil, 1), true, false, true)
+	o._putProp("get", r.newNativeFunc(r.weakMapProto_get, nil, "get", nil, 1), true, false, true)
+
+	o.put(symToStringTag, valueProp(asciiString(classWeakMap), false, false, true), true)
+
+	return o
+}
+
+func (r *Runtime) createWeakMap(val *Object) objectImpl {
+	o := r.newNativeFuncObj(val, r.constructorThrower("WeakMap"), r.builtin_newWeakMap, "WeakMap", r.global.WeakMapPrototype, 0)
+
+	return o
+}
+
+func (r *Runtime) initWeakMap() {
+	r.global.WeakMapPrototype = r.newLazyObject(r.createWeakMapProto)
+	r.global.WeakMap = r.newLazyObject(r.createWeakMap)
+
+	r.addToGlobal("WeakMap", r.global.WeakMap)
+}

+ 33 - 0
builtin_weakmap_test.go

@@ -0,0 +1,33 @@
+package goja
+
+import (
+	"runtime"
+	"testing"
+)
+
+func TestWeakMapExpiry(t *testing.T) {
+	vm := New()
+	_, err := vm.RunString(`
+	var m = new WeakMap();
+	var key = {};
+	m.set(key, true);
+	if (!m.has(key)) {
+		throw new Error("has");
+	}
+	if (m.get(key) !== true) {
+		throw new Error("value does not match");
+	}
+	key = undefined;
+	`)
+	if err != nil {
+		t.Fatal(err)
+	}
+	runtime.GC()
+	wmo := vm.Get("m").ToObject(vm).self.(*weakMapObject)
+	wmo.m.Lock()
+	l := len(wmo.m.data)
+	wmo.m.Unlock()
+	if l > 0 {
+		t.Fatal("Object has not been removed")
+	}
+}

+ 177 - 0
builtin_weakset.go

@@ -0,0 +1,177 @@
+package goja
+
+import "sync"
+
+type weakSet struct {
+	// need to synchronise access to the data map because it may be accessed
+	// from the finalizer goroutine
+	sync.Mutex
+	data map[uintptr]struct{}
+}
+
+type weakSetObject struct {
+	baseObject
+	set *weakSet
+}
+
+func newWeakSet() *weakSet {
+	return &weakSet{
+		data: make(map[uintptr]struct{}),
+	}
+}
+
+func (ws *weakSetObject) init() {
+	ws.baseObject.init()
+	ws.set = newWeakSet()
+}
+
+func (ws *weakSet) removePtr(ptr uintptr) {
+	ws.Lock()
+	delete(ws.data, ptr)
+	ws.Unlock()
+}
+
+func (ws *weakSet) add(o *Object) {
+	refs := o.getWeakCollRefs()
+	ws.Lock()
+	ws.data[refs.id()] = struct{}{}
+	ws.Unlock()
+	refs.add(ws)
+}
+
+func (ws *weakSet) remove(o *Object) bool {
+	if o.weakColls == nil {
+		return false
+	}
+	id := o.weakColls.id()
+	ws.Lock()
+	_, exists := ws.data[id]
+	if exists {
+		delete(ws.data, id)
+	}
+	ws.Unlock()
+	if exists {
+		o.weakColls.remove(ws)
+	}
+	return exists
+}
+
+func (ws *weakSet) has(o *Object) bool {
+	if o.weakColls == nil {
+		return false
+	}
+	ws.Lock()
+	_, exists := ws.data[o.weakColls.id()]
+	ws.Unlock()
+	return exists
+}
+
+func (r *Runtime) weakSetProto_add(call FunctionCall) Value {
+	thisObj := r.toObject(call.This)
+	wso, ok := thisObj.self.(*weakSetObject)
+	if !ok {
+		panic(r.NewTypeError("Method WeakSet.prototype.add called on incompatible receiver %s", thisObj.String()))
+	}
+	wso.set.add(r.toObject(call.Argument(0)))
+	return call.This
+}
+
+func (r *Runtime) weakSetProto_delete(call FunctionCall) Value {
+	thisObj := r.toObject(call.This)
+	wso, ok := thisObj.self.(*weakSetObject)
+	if !ok {
+		panic(r.NewTypeError("Method WeakSet.prototype.delete called on incompatible receiver %s", thisObj.String()))
+	}
+	obj, ok := call.Argument(0).(*Object)
+	if ok && wso.set.remove(obj) {
+		return valueTrue
+	}
+	return valueFalse
+}
+
+func (r *Runtime) weakSetProto_has(call FunctionCall) Value {
+	thisObj := r.toObject(call.This)
+	wso, ok := thisObj.self.(*weakSetObject)
+	if !ok {
+		panic(r.NewTypeError("Method WeakSet.prototype.has called on incompatible receiver %s", thisObj.String()))
+	}
+	obj, ok := call.Argument(0).(*Object)
+	if ok && wso.set.has(obj) {
+		return valueTrue
+	}
+	return valueFalse
+}
+
+func (r *Runtime) populateWeakSetGeneric(s *Object, adderValue Value, iterable Value) {
+	adder := toMethod(adderValue)
+	if adder == nil {
+		panic(r.NewTypeError("WeakSet.add is not set"))
+	}
+	iter := r.getIterator(iterable.ToObject(r), nil)
+	r.iterate(iter, func(val Value) {
+		adder(FunctionCall{This: s, Arguments: []Value{val}})
+	})
+}
+
+func (r *Runtime) builtin_newWeakSet(args []Value) *Object {
+	o := &Object{runtime: r}
+
+	wso := &weakSetObject{}
+	wso.class = classWeakSet
+	wso.val = o
+	wso.extensible = true
+	o.self = wso
+	wso.prototype = r.global.WeakSetPrototype
+	wso.init()
+	if len(args) > 0 {
+		if arg := args[0]; arg != nil && arg != _undefined && arg != _null {
+			adder := wso.getStr("add")
+			if adder == r.global.weakSetAdder {
+				if obj, ok := arg.(*Object); ok {
+					if arr, ok := obj.self.(*arrayObject); ok &&
+						arr.propValueCount == 0 &&
+						arr.length == int64(len(arr.values)) &&
+						arr.getSym(symIterator) == r.global.arrayValues {
+
+						for i, v := range arr.values {
+							if v == nil {
+								v = arr.get(intToValue(int64(i)))
+							}
+							wso.set.add(r.toObject(v))
+						}
+						return o
+					}
+				}
+			}
+			r.populateWeakSetGeneric(o, adder, arg)
+		}
+	}
+	return o
+}
+
+func (r *Runtime) createWeakSetProto(val *Object) objectImpl {
+	o := newBaseObjectObj(val, r.global.ObjectPrototype, classObject)
+
+	o._putProp("constructor", r.global.WeakSet, true, false, true)
+	r.global.weakSetAdder = r.newNativeFunc(r.weakSetProto_add, nil, "add", nil, 1)
+	o._putProp("add", r.global.weakSetAdder, true, false, true)
+	o._putProp("delete", r.newNativeFunc(r.weakSetProto_delete, nil, "delete", nil, 1), true, false, true)
+	o._putProp("has", r.newNativeFunc(r.weakSetProto_has, nil, "has", nil, 1), true, false, true)
+
+	o.put(symToStringTag, valueProp(asciiString(classWeakSet), false, false, true), true)
+
+	return o
+}
+
+func (r *Runtime) createWeakSet(val *Object) objectImpl {
+	o := r.newNativeFuncObj(val, r.constructorThrower("WeakSet"), r.builtin_newWeakSet, "WeakSet", r.global.WeakSetPrototype, 0)
+
+	return o
+}
+
+func (r *Runtime) initWeakSet() {
+	r.global.WeakSetPrototype = r.newLazyObject(r.createWeakSetProto)
+	r.global.WeakSet = r.newLazyObject(r.createWeakSet)
+
+	r.addToGlobal("WeakSet", r.global.WeakSet)
+}

+ 88 - 0
builtin_weakset_test.go

@@ -0,0 +1,88 @@
+package goja
+
+import (
+	"runtime"
+	"testing"
+)
+
+func TestWeakSetBasic(t *testing.T) {
+	const SCRIPT = `
+	var s = new WeakSet();
+	var o = {};
+	s.add(o);
+	if (!s.has(o)) {
+		throw new Error("has");
+	}
+	s.delete(o);
+	if (s.has(o)) {
+		throw new Error("still has");
+	}
+	`
+	testScript1(SCRIPT, _undefined, t)
+}
+
+func TestWeakSetExpiry(t *testing.T) {
+	vm := New()
+	_, err := vm.RunString(`
+	var s = new WeakSet();
+	var o = {};
+	s.add(o);
+	if (!s.has(o)) {
+		throw new Error("has");
+	}
+	o = undefined;
+	`)
+	if err != nil {
+		t.Fatal(err)
+	}
+	runtime.GC()
+	wso := vm.Get("s").ToObject(vm).self.(*weakSetObject)
+	wso.set.Lock()
+	l := len(wso.set.data)
+	wso.set.Unlock()
+	if l > 0 {
+		t.Fatal("Object has not been removed")
+	}
+}
+
+func TestWeakSetArraySimple(t *testing.T) {
+	const SCRIPT = `
+	var o1 = {}, o2 = {}, o3 = {};
+	
+	var s = new WeakSet([o1, o2, o3]);
+	s.has(o1) && s.has(o2) && s.has(o3);
+	`
+	testScript1(SCRIPT, valueTrue, t)
+}
+
+func TestWeakSetArrayGeneric(t *testing.T) {
+	const SCRIPT = `
+	var o1 = {}, o2 = {}, o3 = {};
+	var a = new Array();
+	var s;
+	var thrown = false;
+	a[1] = o2;
+	
+	try {
+		s = new WeakSet(a);
+	} catch (e) {
+		if (e instanceof TypeError) {
+			thrown = true;
+		}
+	}
+	if (!thrown) {
+		throw new Error("Case 1 does not throw");
+	}
+ 
+	Object.defineProperty(a.__proto__, "0", {value: o1, writable: true, enumerable: true, configurable: true});
+	s = new WeakSet(a);
+	if (!(s.has(o1) && s.has(o2) && !s.has(o3))) {
+		throw new Error("Case 2 failed");
+	}
+
+	Object.defineProperty(a, "2", {value: o3, configurable: true});	
+	s = new WeakSet(a);
+	s.has(o1) && s.has(o2) && s.has(o3);
+	`
+	testScript1(SCRIPT, valueTrue, t)
+}

+ 163 - 0
map.go

@@ -0,0 +1,163 @@
+package goja
+
+type mapEntry struct {
+	key, value Value
+
+	iterPrev, iterNext *mapEntry
+	hNext              *mapEntry
+}
+
+type orderedMap struct {
+	hash                map[uint64]*mapEntry
+	iterFirst, iterLast *mapEntry
+	size                int
+}
+
+type orderedMapIter struct {
+	m   *orderedMap
+	cur *mapEntry
+}
+
+func (m *orderedMap) lookup(key Value) (h uint64, entry, hPrev *mapEntry) {
+	if key == _negativeZero {
+		key = intToValue(0)
+	}
+	h = key.hash()
+	for entry = m.hash[h]; entry != nil && !entry.key.SameAs(key); hPrev, entry = entry, entry.hNext {
+	}
+	return
+}
+
+func (m *orderedMap) set(key, value Value) {
+	h, entry, hPrev := m.lookup(key)
+	if entry != nil {
+		entry.value = value
+	} else {
+		if key == _negativeZero {
+			key = intToValue(0)
+		}
+		entry = &mapEntry{key: key, value: value}
+		if hPrev == nil {
+			m.hash[h] = entry
+		} else {
+			hPrev.hNext = entry
+		}
+		if m.iterLast != nil {
+			entry.iterPrev = m.iterLast
+			m.iterLast.iterNext = entry
+		} else {
+			m.iterFirst = entry
+		}
+		m.iterLast = entry
+		m.size++
+	}
+}
+
+func (m *orderedMap) get(key Value) Value {
+	_, entry, _ := m.lookup(key)
+	if entry != nil {
+		return entry.value
+	}
+
+	return nil
+}
+
+func (m *orderedMap) remove(key Value) bool {
+	h, entry, hPrev := m.lookup(key)
+	if entry != nil {
+		entry.key = nil
+		entry.value = nil
+
+		// remove from the doubly-linked list
+		if entry.iterPrev != nil {
+			entry.iterPrev.iterNext = entry.iterNext
+		} else {
+			m.iterFirst = entry.iterNext
+		}
+		if entry.iterNext != nil {
+			entry.iterNext.iterPrev = entry.iterPrev
+		} else {
+			m.iterLast = entry.iterPrev
+		}
+
+		// remove from the hash
+		if hPrev == nil {
+			if entry.hNext == nil {
+				delete(m.hash, h)
+			} else {
+				m.hash[h] = entry.hNext
+			}
+		} else {
+			hPrev.hNext = entry.hNext
+		}
+
+		m.size--
+		return true
+	}
+
+	return false
+}
+
+func (m *orderedMap) has(key Value) bool {
+	_, entry, _ := m.lookup(key)
+	return entry != nil
+}
+
+func (iter *orderedMapIter) next() *mapEntry {
+	if iter.m == nil {
+		// closed iterator
+		return nil
+	}
+
+	cur := iter.cur
+	// if the current item was deleted, track back to find the latest that wasn't
+	for cur != nil && cur.key == nil {
+		cur = cur.iterPrev
+	}
+
+	if cur != nil {
+		cur = cur.iterNext
+	} else {
+		cur = iter.m.iterFirst
+	}
+
+	if cur == nil {
+		iter.close()
+	} else {
+		iter.cur = cur
+	}
+
+	return cur
+}
+
+func (iter *orderedMapIter) close() {
+	iter.m = nil
+	iter.cur = nil
+}
+
+func newOrderedMap() *orderedMap {
+	return &orderedMap{
+		hash: make(map[uint64]*mapEntry),
+	}
+}
+
+func (m *orderedMap) newIter() *orderedMapIter {
+	iter := &orderedMapIter{
+		m: m,
+	}
+	return iter
+}
+
+func (m *orderedMap) clear() {
+	for item := m.iterFirst; item != nil; item = item.iterNext {
+		item.key = nil
+		item.value = nil
+		if item.iterPrev != nil {
+			item.iterPrev.iterNext = nil
+		}
+	}
+	m.iterFirst = nil
+	m.iterLast = nil
+	m.hash = make(map[uint64]*mapEntry)
+	m.size = 0
+}

+ 196 - 0
map_test.go

@@ -0,0 +1,196 @@
+package goja
+
+import (
+	"math"
+	"strconv"
+	"testing"
+)
+
+func testMapHashVal(v1, v2 Value, expected bool, t *testing.T) {
+	actual := v1.hash() == v2.hash()
+	if actual != expected {
+		t.Fatalf("testMapHashVal failed for %v, %v", v1, v2)
+	}
+}
+
+func TestMapHash(t *testing.T) {
+	testMapHashVal(_NaN, _NaN, true, t)
+	testMapHashVal(valueTrue, valueFalse, false, t)
+	testMapHashVal(valueTrue, valueTrue, true, t)
+	testMapHashVal(intToValue(0), _negativeZero, true, t)
+	testMapHashVal(asciiString("Test"), asciiString("Test"), true, t)
+	testMapHashVal(newStringValue("Тест"), newStringValue("Тест"), true, t)
+	testMapHashVal(floatToValue(1.2345), floatToValue(1.2345), true, t)
+	testMapHashVal(symIterator, symToStringTag, false, t)
+	testMapHashVal(symIterator, symIterator, true, t)
+
+	// The following tests introduce indeterministic behaviour
+	//testMapHashVal(asciiString("Test"), asciiString("Test1"), false, t)
+	//testMapHashVal(newStringValue("Тест"), asciiString("Test"), false, t)
+	//testMapHashVal(newStringValue("Тест"), newStringValue("Тест1"), false, t)
+}
+
+func TestOrderedMap(t *testing.T) {
+	m := newOrderedMap()
+	for i := int64(0); i < 50; i++ {
+		m.set(intToValue(i), asciiString(strconv.FormatInt(i, 10)))
+	}
+	if m.size != 50 {
+		t.Fatalf("Unexpected size: %d", m.size)
+	}
+
+	for i := int64(0); i < 50; i++ {
+		expected := asciiString(strconv.FormatInt(i, 10))
+		actual := m.get(intToValue(i))
+		if !expected.SameAs(actual) {
+			t.Fatalf("Wrong value for %d", i)
+		}
+	}
+
+	for i := int64(0); i < 50; i += 2 {
+		if !m.remove(intToValue(i)) {
+			t.Fatalf("remove(%d) return false", i)
+		}
+	}
+	if m.size != 25 {
+		t.Fatalf("Unexpected size: %d", m.size)
+	}
+
+	iter := m.newIter()
+	count := 0
+	for {
+		entry := iter.next()
+		if entry == nil {
+			break
+		}
+		m.remove(entry.key)
+		count++
+	}
+
+	if count != 25 {
+		t.Fatalf("Unexpected iter count: %d", count)
+	}
+
+	if m.size != 0 {
+		t.Fatalf("Unexpected size: %d", m.size)
+	}
+}
+
+func TestOrderedMapCollision(t *testing.T) {
+	m := newOrderedMap()
+	n1 := uint64(123456789)
+	n2 := math.Float64frombits(n1)
+	n1Key := intToValue(int64(n1))
+	n2Key := floatToValue(n2)
+	m.set(n1Key, asciiString("n1"))
+	m.set(n2Key, asciiString("n2"))
+	if m.size == len(m.hash) {
+		t.Fatal("Expected a collision but there wasn't one")
+	}
+	if n2Val := m.get(n2Key); !asciiString("n2").SameAs(n2Val) {
+		t.Fatalf("unexpected n2Val: %v", n2Val)
+	}
+	if n1Val := m.get(n1Key); !asciiString("n1").SameAs(n1Val) {
+		t.Fatalf("unexpected nVal: %v", n1Val)
+	}
+
+	if !m.remove(n1Key) {
+		t.Fatal("removing n1Key returned false")
+	}
+	if n2Val := m.get(n2Key); !asciiString("n2").SameAs(n2Val) {
+		t.Fatalf("2: unexpected n2Val: %v", n2Val)
+	}
+}
+
+func TestOrderedMapIter(t *testing.T) {
+	m := newOrderedMap()
+	iter := m.newIter()
+	ent := iter.next()
+	if ent != nil {
+		t.Fatal("entry should be nil")
+	}
+	iter1 := m.newIter()
+	m.set(intToValue(1), valueTrue)
+	ent = iter.next()
+	if ent != nil {
+		t.Fatal("2: entry should be nil")
+	}
+	ent = iter1.next()
+	if ent == nil {
+		t.Fatal("entry is nil")
+	}
+	if !intToValue(1).SameAs(ent.key) {
+		t.Fatal("unexpected key")
+	}
+	if !valueTrue.SameAs(ent.value) {
+		t.Fatal("unexpected value")
+	}
+}
+
+func TestOrderedMapIterVisitAfterReAdd(t *testing.T) {
+	m := newOrderedMap()
+	one := intToValue(1)
+	two := intToValue(2)
+
+	m.set(one, valueTrue)
+	m.set(two, valueTrue)
+	iter := m.newIter()
+	entry := iter.next()
+	if !one.SameAs(entry.key) {
+		t.Fatalf("1: unexpected key: %v", entry.key)
+	}
+	if !m.remove(one) {
+		t.Fatal("remove returned false")
+	}
+	entry = iter.next()
+	if !two.SameAs(entry.key) {
+		t.Fatalf("2: unexpected key: %v", entry.key)
+	}
+	m.set(one, valueTrue)
+	entry = iter.next()
+	if entry == nil {
+		t.Fatal("entry is nil")
+	}
+	if !one.SameAs(entry.key) {
+		t.Fatalf("3: unexpected key: %v", entry.key)
+	}
+}
+
+func TestOrderedMapIterAddAfterClear(t *testing.T) {
+	m := newOrderedMap()
+	one := intToValue(1)
+	m.set(one, valueTrue)
+	iter := m.newIter()
+	iter.next()
+	m.clear()
+	m.set(one, valueTrue)
+	entry := iter.next()
+	if entry == nil {
+		t.Fatal("entry is nil")
+	}
+	if entry.key != one {
+		t.Fatalf("unexpected key: %v", entry.key)
+	}
+	entry = iter.next()
+	if entry != nil {
+		t.Fatalf("entry is not nil: %v", entry)
+	}
+}
+
+func TestOrderedMapIterDeleteCurrent(t *testing.T) {
+	m := newOrderedMap()
+	one := intToValue(1)
+	two := intToValue(2)
+	iter := m.newIter()
+	m.set(one, valueTrue)
+	m.set(two, valueTrue)
+	entry := iter.next()
+	if entry.key != one {
+		t.Fatalf("unexpected key: %v", entry.key)
+	}
+	m.remove(one)
+	entry = iter.next()
+	if entry.key != two {
+		t.Fatalf("2: unexpected key: %v", entry.key)
+	}
+}

+ 76 - 0
object.go

@@ -3,11 +3,17 @@ package goja
 import (
 	"fmt"
 	"reflect"
+	"runtime"
+	"unsafe"
 )
 
 const (
 	classObject   = "Object"
 	classArray    = "Array"
+	classWeakSet  = "WeakSet"
+	classWeakMap  = "WeakMap"
+	classMap      = "Map"
+	classSet      = "Set"
 	classFunction = "Function"
 	classNumber   = "Number"
 	classString   = "String"
@@ -17,11 +23,72 @@ const (
 	classDate     = "Date"
 
 	classArrayIterator = "Array Iterator"
+	classMapIterator   = "Map Iterator"
+	classSetIterator   = "Set Iterator"
 )
 
+type weakCollection interface {
+	removePtr(uintptr)
+}
+
+type weakCollections struct {
+	colls []weakCollection
+}
+
+func (r *weakCollections) add(c weakCollection) {
+	for _, ec := range r.colls {
+		if ec == c {
+			return
+		}
+	}
+	r.colls = append(r.colls, c)
+}
+
+func (r *weakCollections) id() uintptr {
+	return uintptr(unsafe.Pointer(r))
+}
+
+func (r *weakCollections) remove(c weakCollection) {
+	if cap(r.colls) > 16 && cap(r.colls)>>2 > len(r.colls) {
+		// shrink
+		colls := make([]weakCollection, 0, len(r.colls))
+		for _, coll := range r.colls {
+			if coll != c {
+				colls = append(colls, coll)
+			}
+		}
+		r.colls = colls
+	} else {
+		for i, coll := range r.colls {
+			if coll == c {
+				l := len(r.colls) - 1
+				r.colls[i] = r.colls[l]
+				r.colls[l] = nil
+				r.colls = r.colls[:l]
+				break
+			}
+		}
+	}
+}
+
+func finalizeObjectWeakRefs(r *weakCollections) {
+	id := r.id()
+	for _, c := range r.colls {
+		c.removePtr(id)
+	}
+	r.colls = nil
+}
+
 type Object struct {
 	runtime *Runtime
 	self    objectImpl
+
+	// Contains references to all weak collections that contain this Object.
+	// weakColls has a finalizer that removes the Object's id from all weak collections.
+	// The id is the weakColls pointer value converted to uintptr.
+	// Note, cannot set the finalizer on the *Object itself because it's a part of a
+	// reference cycle.
+	weakColls *weakCollections
 }
 
 type iterNextFunc func() (propIterItem, iterNextFunc)
@@ -790,3 +857,12 @@ func instanceOfOperator(o Value, c *Object) bool {
 
 	return c.self.hasInstance(o)
 }
+
+func (o *Object) getWeakCollRefs() *weakCollections {
+	if o.weakColls == nil {
+		o.weakColls = &weakCollections{}
+		runtime.SetFinalizer(o.weakColls, finalizeObjectWeakRefs)
+	}
+
+	return o.weakColls
+}

+ 75 - 5
runtime.go

@@ -47,6 +47,10 @@ type global struct {
 	Symbol   *Object
 
 	ArrayBuffer *Object
+	WeakSet     *Object
+	WeakMap     *Object
+	Map         *Object
+	Set         *Object
 
 	Error          *Object
 	TypeError      *Object
@@ -70,9 +74,15 @@ type global struct {
 	ArrayIterator     *Object
 
 	ArrayBufferPrototype *Object
+	WeakSetPrototype     *Object
+	WeakMapPrototype     *Object
+	MapPrototype         *Object
+	SetPrototype         *Object
 
 	IteratorPrototype      *Object
 	ArrayIteratorPrototype *Object
+	MapIteratorPrototype   *Object
+	SetIteratorPrototype   *Object
 
 	ErrorPrototype          *Object
 	TypeErrorPrototype      *Object
@@ -90,6 +100,11 @@ type global struct {
 	throwerProperty Value
 
 	regexpProtoExec Value
+	weakSetAdder    *Object
+	weakMapAdder    *Object
+	mapAdder        *Object
+	setAdder        *Object
+	arrayValues     *Object
 }
 
 type Flag int
@@ -297,6 +312,10 @@ func (r *Runtime) init() {
 
 	//r.initTypedArrays()
 	r.initSymbol()
+	r.initWeakSet()
+	r.initWeakMap()
+	r.initMap()
+	r.initSet()
 
 	r.global.thrower = r.newNativeFunc(r.builtin_thrower, nil, "thrower", nil, 0)
 	r.global.throwerProperty = &valueProperty{
@@ -326,12 +345,13 @@ func (r *Runtime) newSyntaxError(msg string, offset int) Value {
 }
 
 func newBaseObjectObj(obj, proto *Object, class string) *baseObject {
-	o := &baseObject{}
-	o.class = class
-	o.val = obj
-	o.extensible = true
+	o := &baseObject{
+		class:      class,
+		val:        obj,
+		extensible: true,
+		prototype:  proto,
+	}
 	obj.self = o
-	o.prototype = proto
 	o.init()
 	return o
 }
@@ -1604,6 +1624,40 @@ func (r *Runtime) returnThis(call FunctionCall) Value {
 	return call.This
 }
 
+func (r *Runtime) getIterator(obj *Object, method func(FunctionCall) Value) *Object {
+	if method == nil {
+		method = toMethod(obj.self.get(symIterator))
+		if method == nil {
+			panic(r.NewTypeError("object is not iterable"))
+		}
+	}
+
+	return r.toObject(method(FunctionCall{
+		This: obj,
+	}))
+}
+
+func (r *Runtime) iterate(iter *Object, step func(Value)) {
+	for {
+		res := r.toObject(toMethod(iter.self.getStr("next"))(FunctionCall{This: iter}))
+		if res.self.getStr("done").ToBoolean() {
+			break
+		}
+		err := tryFunc(func() {
+			step(res.self.getStr("value"))
+		})
+		if err != nil {
+			retMethod := toMethod(iter.self.getStr("return"))
+			if retMethod != nil {
+				_ = tryFunc(func() {
+					retMethod(FunctionCall{This: iter})
+				})
+			}
+			panic(err)
+		}
+	}
+}
+
 func (r *Runtime) createIterResultObject(value Value, done bool) Value {
 	o := r.NewObject()
 	o.self.putStr("value", value, false)
@@ -1611,6 +1665,22 @@ func (r *Runtime) createIterResultObject(value Value, done bool) Value {
 	return o
 }
 
+func (r *Runtime) newLazyObject(create func(*Object) objectImpl) *Object {
+	val := &Object{runtime: r}
+	o := &lazyObject{
+		val:    val,
+		create: create,
+	}
+	val.self = o
+	return val
+}
+
+func (r *Runtime) constructorThrower(name string) func(call FunctionCall) Value {
+	return func(FunctionCall) Value {
+		panic(r.NewTypeError("Constructor %s requires 'new'", name))
+	}
+}
+
 func nilSafe(v Value) Value {
 	if v != nil {
 		return v

+ 7 - 0
string_ascii.go

@@ -225,6 +225,13 @@ func (s asciiString) baseObject(r *Runtime) *Object {
 	return ss.val
 }
 
+func (s asciiString) hash() uint64 {
+	_, _ = mapHasher.WriteString(string(s))
+	h := mapHasher.Sum64()
+	mapHasher.Reset()
+	return h
+}
+
 func (s asciiString) charAt(idx int64) rune {
 	return rune(s[idx])
 }

+ 8 - 0
string_unicode.go

@@ -13,6 +13,7 @@ import (
 	"strings"
 	"unicode/utf16"
 	"unicode/utf8"
+	"unsafe"
 )
 
 type unicodeString []uint16
@@ -317,3 +318,10 @@ func (s unicodeString) Export() interface{} {
 func (s unicodeString) ExportType() reflect.Type {
 	return reflectTypeString
 }
+
+func (s unicodeString) hash() uint64 {
+	_, _ = mapHasher.Write(*(*[]byte)(unsafe.Pointer(&s)))
+	h := mapHasher.Sum64()
+	mapHasher.Reset()
+	return h
+}

+ 42 - 16
tc39_test.go

@@ -32,29 +32,51 @@ var (
 		"test/built-ins/Date/prototype/toISOString/15.9.5.43-0-10.js": true, // timezone
 		"test/annexB/built-ins/escape/escape-above-astral.js":         true, // \u{xxxxx}
 
-		"test/built-ins/Symbol/unscopables/cross-realm.js":                                                true,
-		"test/built-ins/Symbol/toStringTag/cross-realm.js":                                                true,
-		"test/built-ins/Symbol/toPrimitive/cross-realm.js":                                                true,
-		"test/built-ins/Symbol/split/cross-realm.js":                                                      true,
-		"test/built-ins/Symbol/species/cross-realm.js":                                                    true,
-		"test/built-ins/Symbol/search/cross-realm.js":                                                     true,
-		"test/built-ins/Symbol/replace/cross-realm.js":                                                    true,
-		"test/built-ins/Symbol/match/cross-realm.js":                                                      true,
-		"test/built-ins/Symbol/keyFor/cross-realm.js":                                                     true,
-		"test/built-ins/Symbol/iterator/cross-realm.js":                                                   true,
-		"test/built-ins/Symbol/isConcatSpreadable/cross-realm.js":                                         true,
-		"test/built-ins/Symbol/hasInstance/cross-realm.js":                                                true,
-		"test/built-ins/Symbol/for/cross-realm.js":                                                        true,
-		"test/built-ins/Set/symbol-as-entry.js":                                                           true,
-		"test/built-ins/Map/symbol-as-entry-key.js":                                                       true,
+		// cross-realm
+		"test/built-ins/Symbol/unscopables/cross-realm.js":        true,
+		"test/built-ins/Symbol/toStringTag/cross-realm.js":        true,
+		"test/built-ins/Symbol/toPrimitive/cross-realm.js":        true,
+		"test/built-ins/Symbol/split/cross-realm.js":              true,
+		"test/built-ins/Symbol/species/cross-realm.js":            true,
+		"test/built-ins/Symbol/search/cross-realm.js":             true,
+		"test/built-ins/Symbol/replace/cross-realm.js":            true,
+		"test/built-ins/Symbol/match/cross-realm.js":              true,
+		"test/built-ins/Symbol/keyFor/cross-realm.js":             true,
+		"test/built-ins/Symbol/iterator/cross-realm.js":           true,
+		"test/built-ins/Symbol/isConcatSpreadable/cross-realm.js": true,
+		"test/built-ins/Symbol/hasInstance/cross-realm.js":        true,
+		"test/built-ins/Symbol/for/cross-realm.js":                true,
+		"test/built-ins/WeakSet/proto-from-ctor-realm.js":         true,
+		"test/built-ins/WeakMap/proto-from-ctor-realm.js":         true,
+		"test/built-ins/Map/proto-from-ctor-realm.js":             true,
+		"test/built-ins/Set/proto-from-ctor-realm.js":             true,
+
+		// class
 		"test/language/statements/class/subclass/builtin-objects/Symbol/symbol-valid-as-extends-value.js": true,
 		"test/language/statements/class/subclass/builtin-objects/Symbol/new-symbol-with-super-throws.js":  true,
+		"test/language/statements/class/subclass/builtin-objects/WeakSet/super-must-be-called.js":         true,
+		"test/language/statements/class/subclass/builtin-objects/WeakSet/regular-subclassing.js":          true,
+		"test/language/statements/class/subclass/builtin-objects/WeakMap/super-must-be-called.js":         true,
+		"test/language/statements/class/subclass/builtin-objects/WeakMap/regular-subclassing.js":          true,
+		"test/language/statements/class/subclass/builtin-objects/Map/super-must-be-called.js":             true,
+		"test/language/statements/class/subclass/builtin-objects/Map/regular-subclassing.js":              true,
+		"test/language/statements/class/subclass/builtin-objects/Set/super-must-be-called.js":             true,
+		"test/language/statements/class/subclass/builtin-objects/Set/regular-subclassing.js":              true,
 
 		// Proxy
 		"test/built-ins/Object/prototype/toString/proxy-revoked.js":  true,
 		"test/built-ins/Object/prototype/toString/proxy-function.js": true,
 		"test/built-ins/Object/prototype/toString/proxy-array.js":    true,
 		"test/built-ins/JSON/stringify/value-proxy.js":               true,
+
+		// Arrow functions
+		"test/built-ins/Set/prototype/forEach/this-arg-explicit-cannot-override-lexical-this-arrow.js": true,
+
+		// full unicode regexp flag
+		"test/built-ins/RegExp/prototype/Symbol.match/u-advance-after-empty.js":               true,
+		"test/built-ins/RegExp/prototype/Symbol.match/get-unicode-error.js":                   true,
+		"test/built-ins/RegExp/prototype/Symbol.match/builtin-success-u-return-val-groups.js": true,
+		"test/built-ins/RegExp/prototype/Symbol.match/builtin-infer-unicode.js":               true,
 	}
 
 	es6WhiteList = map[string]bool{}
@@ -69,10 +91,14 @@ var (
 		"21.1.3.14",
 		"21.1.3.15",
 		"21.1.3.17",
-		//"21.2.5.6",
+		"21.2.5.6",
 		"22.1.2.5",
 		//"22.1.3.1",
 		"22.1.3.29",
+		"23.1",
+		"23.2",
+		"23.3",
+		"23.4",
 		"25.1.2",
 		"B.2.1",
 		"B.2.2",

+ 51 - 0
value.go

@@ -2,10 +2,12 @@ package goja
 
 import (
 	"fmt"
+	"hash/maphash"
 	"math"
 	"reflect"
 	"regexp"
 	"strconv"
+	"unsafe"
 )
 
 var (
@@ -33,6 +35,10 @@ var (
 
 var intCache [256]Value
 
+var (
+	mapHasher maphash.Hash
+)
+
 type Value interface {
 	ToInteger() int64
 	ToString() valueString
@@ -52,6 +58,8 @@ type Value interface {
 	assertFloat() (float64, bool)
 
 	baseObject(r *Runtime) *Object
+
+	hash() uint64
 }
 
 type typeError string
@@ -199,6 +207,10 @@ func (i valueInt) ExportType() reflect.Type {
 	return reflectTypeInt
 }
 
+func (i valueInt) hash() uint64 {
+	return uint64(i)
+}
+
 func (o valueBool) ToInteger() int64 {
 	if o {
 		return 1
@@ -293,6 +305,13 @@ func (o valueBool) ExportType() reflect.Type {
 	return reflectTypeBool
 }
 
+func (b valueBool) hash() uint64 {
+	if b {
+		return uint64(uintptr(unsafe.Pointer(&valueTrue)))
+	}
+	return uint64(uintptr(unsafe.Pointer(&valueFalse)))
+}
+
 func (n valueNull) ToInteger() int64 {
 	return 0
 }
@@ -331,6 +350,10 @@ func (u valueUndefined) ToFloat() float64 {
 	return math.NaN()
 }
 
+func (u valueUndefined) hash() uint64 {
+	return uint64(uintptr(unsafe.Pointer(&_undefined)))
+}
+
 func (n valueNull) ToFloat() float64 {
 	return 0
 }
@@ -391,6 +414,10 @@ func (n valueNull) ExportType() reflect.Type {
 	return reflectTypeNil
 }
 
+func (n valueNull) hash() uint64 {
+	return uint64(uintptr(unsafe.Pointer(&_null)))
+}
+
 func (p *valueProperty) ToInteger() int64 {
 	return 0
 }
@@ -488,6 +515,10 @@ func (n *valueProperty) ExportType() reflect.Type {
 	panic("Cannot export valueProperty")
 }
 
+func (n *valueProperty) hash() uint64 {
+	panic("valueProperty should never be used in maps or sets")
+}
+
 func (f valueFloat) ToInteger() int64 {
 	switch {
 	case math.IsNaN(float64(f)):
@@ -621,6 +652,13 @@ func (f valueFloat) ExportType() reflect.Type {
 	return reflectTypeFloat
 }
 
+func (f valueFloat) hash() uint64 {
+	if f == _negativeZero {
+		return 0
+	}
+	return math.Float64bits(float64(f))
+}
+
 func (o *Object) ToInteger() int64 {
 	return o.self.toPrimitiveNumber().ToNumber().ToInteger()
 }
@@ -710,6 +748,10 @@ func (o *Object) ExportType() reflect.Type {
 	return o.self.exportType()
 }
 
+func (o *Object) hash() uint64 {
+	return uint64(uintptr(unsafe.Pointer(o)))
+}
+
 func (o *Object) Get(name string) Value {
 	return o.self.getStr(name)
 }
@@ -860,6 +902,11 @@ func (o valueUnresolved) ExportType() reflect.Type {
 	return nil
 }
 
+func (o valueUnresolved) hash() uint64 {
+	o.throw()
+	return 0
+}
+
 func (s *valueSymbol) ToInteger() int64 {
 	panic(typeError("Cannot convert a Symbol value to a number"))
 }
@@ -927,6 +974,10 @@ func (s *valueSymbol) baseObject(r *Runtime) *Object {
 	return r.newPrimitiveObject(s, r.global.SymbolPrototype, "Symbol")
 }
 
+func (s *valueSymbol) hash() uint64 {
+	return uint64(uintptr(unsafe.Pointer(s)))
+}
+
 func (s *valueSymbol) descString() string {
 	return fmt.Sprintf("Symbol(%s)", s.desc)
 }