Browse Source

[fea] add push support for goslice_reflect object

Oleg Gerasimov 5 years ago
parent
commit
794ceddc90
3 changed files with 78 additions and 6 deletions
  1. 1 1
      object_goreflect.go
  2. 39 5
      object_goslice_reflect.go
  3. 38 0
      object_goslice_reflect_test.go

+ 1 - 1
object_goreflect.go

@@ -143,7 +143,7 @@ func (o *objectGoReflect) getOwnProp(name string) Value {
 	if o.value.Kind() == reflect.Struct {
 		if v := o._getField(name); v.IsValid() {
 			canSet := v.CanSet()
-			if v.Kind() == reflect.Struct && v.CanAddr() {
+			if (v.Kind() == reflect.Struct || v.Kind() == reflect.Slice) && v.CanAddr() {
 				v = v.Addr()
 			}
 			return &valueProperty{

+ 39 - 5
object_goslice_reflect.go

@@ -7,14 +7,16 @@ import (
 
 type objectGoSliceReflect struct {
 	objectGoReflect
-	lengthProp valueProperty
+	lengthProp      valueProperty
+	sliceExtensible bool
 }
 
 func (o *objectGoSliceReflect) init() {
 	o.objectGoReflect.init()
 	o.class = classArray
 	o.prototype = o.val.runtime.global.ArrayPrototype
-	o.lengthProp.writable = false
+	o.sliceExtensible = o.value.CanSet()
+	o.lengthProp.writable = o.sliceExtensible
 	o._setLen()
 	o.baseObject._put("length", &o.lengthProp)
 }
@@ -95,8 +97,11 @@ func (o *objectGoSliceReflect) getOwnProp(name string) Value {
 
 func (o *objectGoSliceReflect) putIdx(idx int64, v Value, throw bool) {
 	if idx >= int64(o.value.Len()) {
-		o.val.runtime.typeErrorResult(throw, "Cannot extend a Go reflect slice")
-		return
+		if !o.sliceExtensible {
+			o.val.runtime.typeErrorResult(throw, "Cannot extend a Go unaddressable reflect slice")
+			return
+		}
+		o.grow(int(idx + 1))
 	}
 	val, err := o.val.runtime.toReflectValue(v, o.value.Type().Elem())
 	if err != nil {
@@ -106,6 +111,32 @@ func (o *objectGoSliceReflect) putIdx(idx int64, v Value, throw bool) {
 	o.value.Index(int(idx)).Set(val)
 }
 
+func (o *objectGoSliceReflect) grow(size int) {
+	newcap := o.value.Cap()
+	if newcap < size {
+		// Use the same algorithm as in runtime.growSlice
+		doublecap := newcap + newcap
+		if size > doublecap {
+			newcap = size
+		} else {
+			if o.value.Len() < 1024 {
+				newcap = doublecap
+			} else {
+				for newcap < size {
+					newcap += newcap / 4
+				}
+			}
+		}
+
+		n := reflect.MakeSlice(o.value.Type(), size, newcap)
+		reflect.Copy(n, o.value)
+		o.value.Set(n)
+	} else {
+		o.value.SetLen(size)
+	}
+	o._setLen()
+}
+
 func (o *objectGoSliceReflect) put(n Value, val Value, throw bool) {
 	if idx := toIdx(n); idx >= 0 {
 		o.putIdx(idx, val, throw)
@@ -120,7 +151,10 @@ func (o *objectGoSliceReflect) putStr(name string, val Value, throw bool) {
 		o.putIdx(idx, val, throw)
 		return
 	}
-	// TODO: length
+	if name == "length" {
+		o.baseObject.putStr(name, val, throw)
+		return
+	}
 	o.objectGoReflect.putStr(name, val, throw)
 }
 

+ 38 - 0
object_goslice_reflect_test.go

@@ -71,6 +71,44 @@ func TestGoSliceReflectSet(t *testing.T) {
 	}
 }
 
+func TestGoSliceReflectPush(t *testing.T) {
+
+	r := New()
+
+	t.Run("Can push to array by array ptr", func(t *testing.T) {
+		a := []int8{1}
+		r.Set("a", &a)
+		_, err := r.RunString(`a.push (10)`)
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		if a[1] != 10 {
+			t.Fatalf("a[1] = %d, expected 10", a[1])
+		}
+	})
+
+	t.Run("Can push to array by struct ptr", func(t *testing.T) {
+		type testStr struct {
+			A []int
+		}
+		a := testStr{
+			A: []int{2},
+		}
+
+		r.Set("a", &a)
+		_, err := r.RunString(`a.A.push (10)`)
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		if a.A[1] != 10 {
+			t.Fatalf("a[1] = %v, expected 10", a)
+		}
+	})
+
+}
+
 func TestGoSliceReflectProto(t *testing.T) {
 	const SCRIPT = `
 	a.join(",")