Browse Source

Added support for arbitrary map types

Dmitry Panov 9 years ago
parent
commit
36a24b9863
7 changed files with 360 additions and 28 deletions
  1. 15 5
      object.go
  2. 3 9
      object_gomap.go
  3. 203 0
      object_gomap_reflect.go
  4. 102 0
      object_gomap_reflect_test.go
  5. 7 3
      object_goreflect.go
  6. 9 9
      object_goslice.go
  7. 21 2
      runtime.go

+ 15 - 5
object.go

@@ -132,7 +132,7 @@ func (o *baseObject) className() string {
 }
 
 func (o *baseObject) getPropStr(name string) Value {
-	if val, exists := o.values[name]; exists {
+	if val := o.getOwnProp(name); val != nil {
 		return val
 	}
 	if o.prototype != nil {
@@ -153,15 +153,21 @@ func (o *baseObject) hasPropertyStr(name string) bool {
 	return o.val.self.getPropStr(name) != nil
 }
 
+func (o *baseObject) _getStr(name string) Value {
+	p := o.getOwnProp(name)
+	if p, ok := p.(*valueProperty); ok {
+		return p.get(o.val)
+	}
+
+	return p
+}
+
 func (o *baseObject) getStr(name string) Value {
 	p := o.val.self.getPropStr(name)
 	if p, ok := p.(*valueProperty); ok {
 		return p.get(o.val)
 	}
 
-	if p == nil && name == "__proto__" {
-		return o.prototype
-	}
 	return p
 }
 
@@ -215,7 +221,11 @@ func (o *baseObject) put(n Value, val Value, throw bool) {
 }
 
 func (o *baseObject) getOwnProp(name string) Value {
-	return o.values[name]
+	v := o.values[name]
+	if v == nil && name == "__proto" {
+		return o.prototype
+	}
+	return v
 }
 
 func (o *baseObject) putStr(name string, val Value, throw bool) {

+ 3 - 9
object_gomap.go

@@ -29,17 +29,11 @@ func (o *objectGoMapSimple) _getStr(name string) Value {
 }
 
 func (o *objectGoMapSimple) get(n Value) Value {
-	if v := o._get(n); v != nil {
-		return v
-	}
-	return o.baseObject.get(n)
+	return o.getStr(n.String())
 }
 
 func (o *objectGoMapSimple) getProp(n Value) Value {
-	if v := o._get(n); v != nil {
-		return v
-	}
-	return o.baseObject.getProp(n)
+	return o.getPropStr(n.String())
 }
 
 func (o *objectGoMapSimple) getPropStr(name string) Value {
@@ -53,7 +47,7 @@ func (o *objectGoMapSimple) getStr(name string) Value {
 	if v := o._getStr(name); v != nil {
 		return v
 	}
-	return o.baseObject.getStr(name)
+	return o.baseObject._getStr(name)
 }
 
 func (o *objectGoMapSimple) getOwnProp(name string) Value {

+ 203 - 0
object_gomap_reflect.go

@@ -0,0 +1,203 @@
+package goja
+
+import "reflect"
+
+type objectGoMapReflect struct {
+	objectGoReflect
+
+	keyType, valueType reflect.Type
+}
+
+func (o *objectGoMapReflect) init() {
+	o.objectGoReflect.init()
+	o.keyType = o.value.Type().Key()
+	o.valueType = o.value.Type().Elem()
+}
+
+func (o *objectGoMapReflect) toKey(n Value) reflect.Value {
+	key, err := o.val.runtime.toReflectValue(n, o.keyType)
+	if err != nil {
+		o.val.runtime.typeErrorResult(true, "map key conversion error: %v", err)
+		panic("unreachable")
+	}
+	return key
+}
+
+func (o *objectGoMapReflect) strToKey(name string) reflect.Value {
+	if o.keyType.Kind() == reflect.String {
+		return reflect.ValueOf(name).Convert(o.keyType)
+	}
+	return o.toKey(newStringValue(name))
+}
+
+func (o *objectGoMapReflect) _get(n Value) Value {
+	if v := o.value.MapIndex(o.toKey(n)); v.IsValid() {
+		return o.val.runtime.ToValue(v.Interface())
+	}
+
+	return nil
+}
+
+func (o *objectGoMapReflect) _getStr(name string) Value {
+	if v := o.value.MapIndex(o.strToKey(name)); v.IsValid() {
+		return o.val.runtime.ToValue(v.Interface())
+	}
+
+	return nil
+}
+
+func (o *objectGoMapReflect) get(n Value) Value {
+	if v := o._get(n); v != nil {
+		return v
+	}
+	return o.objectGoReflect.get(n)
+}
+
+func (o *objectGoMapReflect) getStr(name string) Value {
+	if v := o._getStr(name); v != nil {
+		return v
+	}
+	return o.objectGoReflect.getStr(name)
+}
+
+func (o *objectGoMapReflect) getProp(n Value) Value {
+	return o.get(n)
+}
+
+func (o *objectGoMapReflect) getPropStr(name string) Value {
+	return o.getStr(name)
+}
+
+func (o *objectGoMapReflect) getOwnProp(name string) Value {
+	if v := o._getStr(name); v != nil {
+		return &valueProperty{
+			value:      v,
+			writable:   true,
+			enumerable: true,
+		}
+	}
+	return o.objectGoReflect.getOwnProp(name)
+}
+
+func (o *objectGoMapReflect) toValue(val Value, throw bool) (reflect.Value, bool) {
+	v, err := o.val.runtime.toReflectValue(val, o.valueType)
+	if err != nil {
+		o.val.runtime.typeErrorResult(throw, "map value conversion error: %v", err)
+		return reflect.Value{}, false
+	}
+
+	return v, true
+}
+
+func (o *objectGoMapReflect) put(key, val Value, throw bool) {
+	k := o.toKey(key)
+	v, ok := o.toValue(val, throw)
+	if !ok {
+		return
+	}
+	o.value.SetMapIndex(k, v)
+}
+
+func (o *objectGoMapReflect) putStr(name string, val Value, throw bool) {
+	k := o.strToKey(name)
+	v, ok := o.toValue(val, throw)
+	if !ok {
+		return
+	}
+	o.value.SetMapIndex(k, v)
+}
+
+func (o *objectGoMapReflect) _putProp(name string, value Value, writable, enumerable, configurable bool) Value {
+	o.putStr(name, value, true)
+	return value
+}
+
+func (o *objectGoMapReflect) defineOwnProperty(n Value, descr objectImpl, throw bool) bool {
+	name := n.String()
+	if !o.val.runtime.checkHostObjectPropertyDescr(name, descr, throw) {
+		return false
+	}
+
+	o.put(n, descr.getStr("value"), throw)
+	return true
+}
+
+func (o *objectGoMapReflect) hasOwnPropertyStr(name string) bool {
+	return o.value.MapIndex(o.strToKey(name)).IsValid()
+}
+
+func (o *objectGoMapReflect) hasOwnProperty(n Value) bool {
+	return o.value.MapIndex(o.toKey(n)).IsValid()
+}
+
+func (o *objectGoMapReflect) hasProperty(n Value) bool {
+	if o.hasOwnProperty(n) {
+		return true
+	}
+	return o.objectGoReflect.hasProperty(n)
+}
+
+func (o *objectGoMapReflect) hasPropertyStr(name string) bool {
+	if o.hasOwnPropertyStr(name) {
+		return true
+	}
+	return o.objectGoReflect.hasPropertyStr(name)
+}
+
+func (o *objectGoMapReflect) delete(n Value, throw bool) bool {
+	o.value.SetMapIndex(o.toKey(n), reflect.Value{})
+	return true
+}
+
+func (o *objectGoMapReflect) deleteStr(name string, throw bool) bool {
+	o.value.SetMapIndex(o.strToKey(name), reflect.Value{})
+	return true
+}
+
+type gomapReflectPropIter struct {
+	o         *objectGoMapReflect
+	keys      []reflect.Value
+	idx       int
+	recursive bool
+}
+
+func (i *gomapReflectPropIter) next() (propIterItem, iterNextFunc) {
+	for i.idx < len(i.keys) {
+		key := i.keys[i.idx]
+		v := i.o.value.MapIndex(key)
+		i.idx++
+		if v.IsValid() {
+			return propIterItem{name: key.String(), enumerable: _ENUM_TRUE}, i.next
+		}
+	}
+
+	if i.recursive {
+		return i.o.objectGoReflect._enumerate(true)()
+	}
+
+	return propIterItem{}, nil
+}
+
+func (o *objectGoMapReflect) _enumerate(recusrive bool) iterNextFunc {
+	r := &gomapReflectPropIter{
+		o:         o,
+		keys:      o.value.MapKeys(),
+		recursive: recusrive,
+	}
+	return r.next
+}
+
+func (o *objectGoMapReflect) enumerate(all, recursive bool) iterNextFunc {
+	return (&propFilterIter{
+		wrapped: o._enumerate(recursive),
+		all:     all,
+		seen:    make(map[string]bool),
+	}).next
+}
+
+func (o *objectGoMapReflect) equal(other objectImpl) bool {
+	if other, ok := other.(*objectGoMapReflect); ok {
+		return o.value.Interface() == other.value.Interface()
+	}
+	return false
+}

+ 102 - 0
object_gomap_reflect_test.go

@@ -0,0 +1,102 @@
+package goja
+
+import "testing"
+
+func TestGoMapReflectGetSet(t *testing.T) {
+	const SCRIPT = `
+	m.c = m.a + m.b;
+	`
+
+	vm := New()
+	m := map[string]string{
+		"a": "4",
+		"b": "2",
+	}
+	vm.Set("m", m)
+
+	_, err := vm.RunString(SCRIPT)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if c := m["c"]; c != "42" {
+		t.Fatalf("Unexpected value: '%s'", c)
+	}
+}
+
+func TestGoMapReflectIntKey(t *testing.T) {
+	const SCRIPT = `
+	m[2] = m[0] + m[1];
+	`
+
+	vm := New()
+	m := map[int]int{
+		0: 40,
+		1: 2,
+	}
+	vm.Set("m", m)
+
+	_, err := vm.RunString(SCRIPT)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if c := m[2]; c != 42 {
+		t.Fatalf("Unexpected value: '%d'", c)
+	}
+}
+
+func TestGoMapReflectDelete(t *testing.T) {
+	const SCRIPT = `
+	delete m.a;
+	`
+
+	vm := New()
+	m := map[string]string{
+		"a": "4",
+		"b": "2",
+	}
+	vm.Set("m", m)
+
+	_, err := vm.RunString(SCRIPT)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if _, exists := m["a"]; exists {
+		t.Fatal("a still exists")
+	}
+
+	if b := m["b"]; b != "2" {
+		t.Fatalf("Unexpected b: '%s'", b)
+	}
+}
+
+func TestGoMapReflectJSON(t *testing.T) {
+	const SCRIPT = `
+	function f(m) {
+		return JSON.stringify(m);
+	}
+	`
+
+	vm := New()
+	m := map[string]string{
+		"t": "42",
+	}
+	_, err := vm.RunString(SCRIPT)
+	if err != nil {
+		t.Fatal(err)
+	}
+	f := vm.Get("f")
+	if call, ok := AssertFunction(f); ok {
+		v, err := call(nil, ([]Value{vm.ToValue(m)})...)
+		if err != nil {
+			t.Fatal(err)
+		}
+		if !v.StrictEquals(asciiString(`{"t":"42"}`)) {
+			t.Fatalf("Unexpected value: %v", v)
+		}
+	} else {
+		t.Fatalf("Not a function: %v", f)
+	}
+}

+ 7 - 3
object_goreflect.go

@@ -64,7 +64,7 @@ func (o *objectGoReflect) getStr(name string) Value {
 	if v := o._get(name); v != nil {
 		return v
 	}
-	return o.baseObject.getStr(name)
+	return o.baseObject._getStr(name)
 }
 
 func (o *objectGoReflect) getProp(n Value) Value {
@@ -72,7 +72,7 @@ func (o *objectGoReflect) getProp(n Value) Value {
 	if p := o.getOwnProp(name); p != nil {
 		return p
 	}
-	return o.baseObject.getProp(n)
+	return o.baseObject.getOwnProp(name)
 }
 
 func (o *objectGoReflect) getPropStr(name string) Value {
@@ -314,7 +314,11 @@ func (i *goreflectPropIter) nextMethod() (propIterItem, iterNextFunc) {
 		}
 	}
 
-	return i.o.baseObject._enumerate(i.recursive)()
+	if i.recursive {
+		return i.o.baseObject._enumerate(true)()
+	}
+
+	return propIterItem{}, nil
 }
 
 func (o *objectGoReflect) _enumerate(recusrive bool) iterNextFunc {

+ 9 - 9
object_goslice.go

@@ -50,28 +50,28 @@ func (o *objectGoSlice) get(n Value) Value {
 	if v := o._get(n); v != nil {
 		return v
 	}
-	return o.baseObject.get(n)
+	return o.baseObject._getStr(n.String())
 }
 
-func (o *objectGoSlice) getProp(n Value) Value {
-	if v := o._get(n); v != nil {
+func (o *objectGoSlice) getStr(name string) Value {
+	if v := o._getStr(name); v != nil {
 		return v
 	}
-	return o.baseObject.getProp(n)
+	return o.baseObject._getStr(name)
 }
 
-func (o *objectGoSlice) getPropStr(name string) Value {
-	if v := o._getStr(name); v != nil {
+func (o *objectGoSlice) getProp(n Value) Value {
+	if v := o._get(n); v != nil {
 		return v
 	}
-	return o.baseObject.getPropStr(name)
+	return o.baseObject.getPropStr(n.String())
 }
 
-func (o *objectGoSlice) getStr(name string) Value {
+func (o *objectGoSlice) getPropStr(name string) Value {
 	if v := o._getStr(name); v != nil {
 		return v
 	}
-	return o.baseObject.getStr(name)
+	return o.baseObject.getPropStr(name)
 }
 
 func (o *objectGoSlice) getOwnProp(name string) Value {

+ 21 - 2
runtime.go

@@ -873,8 +873,27 @@ func (r *Runtime) ToValue(i interface{}) Value {
 
 	switch value.Kind() {
 	case reflect.Map:
-		if value.Type().Key().Kind() == reflect.String {
-			//TODO: goMapReflect
+		if value.Type().Name() == "" {
+			switch value.Type().Key().Kind() {
+			case reflect.String, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
+				reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
+				reflect.Float64, reflect.Float32:
+
+				obj := &Object{runtime: r}
+				m := &objectGoMapReflect{
+					objectGoReflect: objectGoReflect{
+						baseObject: baseObject{
+							val:        obj,
+							extensible: true,
+						},
+						origValue: origValue,
+						value:     value,
+					},
+				}
+				m.init()
+				obj.self = m
+				return obj
+			}
 		}
 	case reflect.Slice:
 		obj := &Object{runtime: r}