This is an automated email from the git hooks/post-receive script.

git pushed a commit to branch main
in repository ego.

View the commit online.

commit 1629671880e6c0ac2a171710bcb9e22bac4819eb
Author: [email protected] <[email protected]>
AuthorDate: Tue Mar 31 11:50:24 2026 -0600

    feat(efl): add slice and map marshal/unmarshal support
    
    ValueMarshal now handles []T → ARRAY and map[string]T → HASH.
    ValueUnmarshal reverses these conversions. Nested containers
    work via recursive marshaling.
    
    Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
---
 efl/value_marshal.go            |  14 +-
 efl/value_marshal_containers.go | 289 ++++++++++++++++++++++++++++++++++++++++
 efl/value_marshal_test.go       |  66 +++++++++
 3 files changed, 361 insertions(+), 8 deletions(-)

diff --git a/efl/value_marshal.go b/efl/value_marshal.go
index 74c57f8..947df13 100644
--- a/efl/value_marshal.go
+++ b/efl/value_marshal.go
@@ -53,14 +53,12 @@ func ValueMarshal(v any) (*Value, error) {
 	case string:
 		return NewValue[string](u)
 	default:
-		// Use reflection only to produce helpful error messages for composite
-		// types that we plan to support later.
-		kind := reflect.TypeOf(v).Kind()
-		switch kind {
+		rv := reflect.ValueOf(v)
+		switch rv.Kind() {
 		case reflect.Slice:
-			return nil, errors.New("efl: ValueMarshal: slice: not yet implemented")
+			return marshalSlice(rv)
 		case reflect.Map:
-			return nil, errors.New("efl: ValueMarshal: map: not yet implemented")
+			return marshalMap(rv)
 		case reflect.Struct:
 			return nil, errors.New("efl: ValueMarshal: struct: not yet implemented")
 		default:
@@ -190,10 +188,10 @@ func ValueUnmarshal(val *Value, dst any) error {
 		elem.SetString(got)
 
 	case reflect.Slice:
-		return errors.New("efl: ValueUnmarshal: slice: not yet implemented")
+		return unmarshalSlice(val, elem)
 
 	case reflect.Map:
-		return errors.New("efl: ValueUnmarshal: map: not yet implemented")
+		return unmarshalMap(val, elem)
 
 	case reflect.Struct:
 		return errors.New("efl: ValueUnmarshal: struct: not yet implemented")
diff --git a/efl/value_marshal_containers.go b/efl/value_marshal_containers.go
new file mode 100644
index 0000000..4a5c98c
--- /dev/null
+++ b/efl/value_marshal_containers.go
@@ -0,0 +1,289 @@
+package efl
+
+/*
+#cgo pkg-config: eina
+
+#include <Eina.h>
+#include <stdlib.h>
+
+// Forward declarations for helpers defined in value.go and value_container.go.
+// Each cgo file has its own preamble scope; we redeclare (not redefine) here.
+static Eina_Value *_ego_value_new(const Eina_Value_Type *type);
+static void        _ego_value_free(Eina_Value *v);
+
+static Eina_Bool _ego_value_array_setup(Eina_Value *v, const Eina_Value_Type *subtype);
+static unsigned int _ego_value_array_count(const Eina_Value *v);
+static Eina_Bool _ego_value_array_pappend(Eina_Value *v, const void *ptr);
+static Eina_Bool _ego_value_array_pget(const Eina_Value *v, unsigned int pos, void *ptr);
+static Eina_Bool _ego_value_array_append_string(Eina_Value *v, const char *s);
+static const char *_ego_value_array_get_string(const Eina_Value *v, unsigned int pos);
+static Eina_Bool _ego_value_array_append_value(Eina_Value *v, const Eina_Value *sub);
+static Eina_Value *_ego_value_array_new_value_at(const Eina_Value *v, unsigned int pos);
+
+static Eina_Bool _ego_value_hash_setup(Eina_Value *v, const Eina_Value_Type *subtype);
+static Eina_Bool _ego_value_hash_pset(Eina_Value *v, const char *key, const void *ptr);
+static Eina_Bool _ego_value_hash_set_string(Eina_Value *v, const char *key, const char *val);
+static Eina_Bool _ego_value_hash_pget(const Eina_Value *v, const char *key, void *ptr);
+static const char *_ego_value_hash_get_string(const Eina_Value *v, const char *key);
+static unsigned int _ego_value_hash_population(const Eina_Value *v);
+static int _ego_value_hash_keys(const Eina_Value *v, char **keys, int cap);
+static Eina_Value *_ego_value_hash_new_value_at(const Eina_Value *v, const char *key);
+*/
+import "C"
+
+import (
+	"fmt"
+	"reflect"
+	"unsafe"
+)
+
+// marshalSlice converts a Go slice (rv.Kind() == reflect.Slice) into a *Value
+// of kind ValueKindArray. Scalar element types (matching ScalarType) are stored
+// directly with the corresponding Eina subtype. Non-scalar elements (structs,
+// slices, maps) are marshalled recursively and stored with subtype
+// EINA_VALUE_TYPE_VALUE.
+func marshalSlice(rv reflect.Value) (*Value, error) {
+	n := rv.Len()
+	elemType := rv.Type().Elem()
+
+	// Determine the Eina subtype by probing the element type's zero value.
+	zero := reflect.Zero(elemType).Interface()
+	st := scalarEinaTypeAny(zero)
+
+	if st != nil {
+		// Scalar element path: set up a plain ARRAY with the matching subtype.
+		raw := C._ego_value_new(C.EINA_VALUE_TYPE_ARRAY)
+		if raw == nil {
+			return nil, fmt.Errorf("efl: marshalSlice: eina_value_new(ARRAY) returned nil")
+		}
+		if C._ego_value_array_setup(raw, st) == 0 {
+			C._ego_value_free(raw)
+			return nil, fmt.Errorf("efl: marshalSlice: array setup failed")
+		}
+		for i := range n {
+			elem := rv.Index(i).Interface()
+			if err := appendAnyToArray(raw, elem); err != nil {
+				C._ego_value_free(raw)
+				return nil, fmt.Errorf("efl: marshalSlice: element %d: %w", i, err)
+			}
+		}
+		return &Value{ptr: raw, owned: true}, nil
+	}
+
+	// Non-scalar path: use EINA_VALUE_TYPE_VALUE as the subtype and recursively
+	// marshal each element.
+	raw := C._ego_value_new(C.EINA_VALUE_TYPE_ARRAY)
+	if raw == nil {
+		return nil, fmt.Errorf("efl: marshalSlice: eina_value_new(ARRAY) returned nil")
+	}
+	if C._ego_value_array_setup(raw, C.EINA_VALUE_TYPE_VALUE) == 0 {
+		C._ego_value_free(raw)
+		return nil, fmt.Errorf("efl: marshalSlice: array setup (VALUE subtype) failed")
+	}
+	for i := range n {
+		sub, err := ValueMarshal(rv.Index(i).Interface())
+		if err != nil {
+			C._ego_value_free(raw)
+			return nil, fmt.Errorf("efl: marshalSlice: element %d: %w", i, err)
+		}
+		if C._ego_value_array_append_value(raw, sub.ptr) == 0 {
+			sub.Free()
+			C._ego_value_free(raw)
+			return nil, fmt.Errorf("efl: marshalSlice: append_value failed at element %d", i)
+		}
+		// sub was copied into the array; if it is owned by us, free it.
+		if sub.owned {
+			sub.Free()
+		}
+	}
+	return &Value{ptr: raw, owned: true}, nil
+}
+
+// marshalMap converts a Go map (rv.Kind() == reflect.Map) with string keys into
+// a *Value of kind ValueKindHash. Element handling mirrors marshalSlice.
+func marshalMap(rv reflect.Value) (*Value, error) {
+	if rv.Type().Key().Kind() != reflect.String {
+		return nil, fmt.Errorf("efl: marshalMap: map key must be string, got %v", rv.Type().Key())
+	}
+
+	elemType := rv.Type().Elem()
+	zero := reflect.Zero(elemType).Interface()
+	st := scalarEinaTypeAny(zero)
+
+	if st != nil {
+		raw := C._ego_value_new(C.EINA_VALUE_TYPE_HASH)
+		if raw == nil {
+			return nil, fmt.Errorf("efl: marshalMap: eina_value_new(HASH) returned nil")
+		}
+		if C._ego_value_hash_setup(raw, st) == 0 {
+			C._ego_value_free(raw)
+			return nil, fmt.Errorf("efl: marshalMap: hash setup failed")
+		}
+		for _, key := range rv.MapKeys() {
+			k := key.String()
+			elem := rv.MapIndex(key).Interface()
+			if err := setAnyInHash(raw, k, elem); err != nil {
+				C._ego_value_free(raw)
+				return nil, fmt.Errorf("efl: marshalMap: key %q: %w", k, err)
+			}
+		}
+		return &Value{ptr: raw, owned: true}, nil
+	}
+
+	// Non-scalar path.
+	raw := C._ego_value_new(C.EINA_VALUE_TYPE_HASH)
+	if raw == nil {
+		return nil, fmt.Errorf("efl: marshalMap: eina_value_new(HASH) returned nil")
+	}
+	if C._ego_value_hash_setup(raw, C.EINA_VALUE_TYPE_VALUE) == 0 {
+		C._ego_value_free(raw)
+		return nil, fmt.Errorf("efl: marshalMap: hash setup (VALUE subtype) failed")
+	}
+	for _, key := range rv.MapKeys() {
+		k := key.String()
+		sub, err := ValueMarshal(rv.MapIndex(key).Interface())
+		if err != nil {
+			C._ego_value_free(raw)
+			return nil, fmt.Errorf("efl: marshalMap: key %q: %w", k, err)
+		}
+		ck := C.CString(k)
+		ok := C._ego_value_hash_pset(raw, ck, unsafe.Pointer(sub.ptr)) != 0
+		C.free(unsafe.Pointer(ck))
+		if sub.owned {
+			sub.Free()
+		}
+		if !ok {
+			C._ego_value_free(raw)
+			return nil, fmt.Errorf("efl: marshalMap: hash pset failed for key %q", k)
+		}
+	}
+	return &Value{ptr: raw, owned: true}, nil
+}
+
+// unmarshalSlice reads all elements from the array Value val and appends them
+// to the slice pointed to by elem (a reflect.Value of kind reflect.Slice).
+func unmarshalSlice(val *Value, elem reflect.Value) error {
+	if val.Kind() != ValueKindArray {
+		return fmt.Errorf("efl: unmarshalSlice: Value is not an array (Kind=%v)", val.Kind())
+	}
+
+	n := int(C._ego_value_array_count(val.ptr))
+	sliceType := elem.Type()
+	elemType := sliceType.Elem()
+
+	result := reflect.MakeSlice(sliceType, n, n)
+
+	// Probe using a zero value of the element type.
+	zero := reflect.Zero(elemType).Interface()
+	st := scalarEinaTypeAny(zero)
+
+	if st != nil {
+		// Scalar elements.
+		for i := range n {
+			got, err := getAnyFromArray(val.ptr, C.uint(i), zero)
+			if err != nil {
+				return fmt.Errorf("efl: unmarshalSlice: element %d: %w", i, err)
+			}
+			gotRV := reflect.ValueOf(got)
+			// The returned concrete type must be assignable or convertible to elemType.
+			if gotRV.Type().AssignableTo(elemType) {
+				result.Index(i).Set(gotRV)
+			} else if gotRV.Type().ConvertibleTo(elemType) {
+				result.Index(i).Set(gotRV.Convert(elemType))
+			} else {
+				return fmt.Errorf("efl: unmarshalSlice: element %d: cannot assign %T to %v", i, got, elemType)
+			}
+		}
+		elem.Set(result)
+		return nil
+	}
+
+	// Non-scalar: recursively unmarshal each nested *Value.
+	for i := range n {
+		subPtr := C._ego_value_array_new_value_at(val.ptr, C.uint(i))
+		if subPtr == nil {
+			return fmt.Errorf("efl: unmarshalSlice: new_value_at failed at index %d", i)
+		}
+		sub := &Value{ptr: subPtr, owned: true}
+		dst := reflect.New(elemType)
+		if err := ValueUnmarshal(sub, dst.Interface()); err != nil {
+			sub.Free()
+			return fmt.Errorf("efl: unmarshalSlice: element %d: %w", i, err)
+		}
+		sub.Free()
+		result.Index(i).Set(dst.Elem())
+	}
+	elem.Set(result)
+	return nil
+}
+
+// unmarshalMap reads all key-value pairs from the hash Value val and stores
+// them in the map pointed to by elem (a reflect.Value of kind reflect.Map).
+// The map's key type must be string.
+func unmarshalMap(val *Value, elem reflect.Value) error {
+	if val.Kind() != ValueKindHash {
+		return fmt.Errorf("efl: unmarshalMap: Value is not a hash (Kind=%v)", val.Kind())
+	}
+	if elem.Type().Key().Kind() != reflect.String {
+		return fmt.Errorf("efl: unmarshalMap: map key must be string, got %v", elem.Type().Key())
+	}
+
+	n := int(C._ego_value_hash_population(val.ptr))
+	mapType := elem.Type()
+	elemType := mapType.Elem()
+
+	result := reflect.MakeMapWithSize(mapType, n)
+
+	if n == 0 {
+		elem.Set(result)
+		return nil
+	}
+
+	// Collect the internal key pointers (valid while val is alive).
+	ckeys := make([]*C.char, n)
+	got := int(C._ego_value_hash_keys(val.ptr, &ckeys[0], C.int(n)))
+
+	zero := reflect.Zero(elemType).Interface()
+	st := scalarEinaTypeAny(zero)
+
+	if st != nil {
+		for _, ck := range ckeys[:got] {
+			key := C.GoString(ck)
+			raw, err := getAnyFromHash(val.ptr, key, zero)
+			if err != nil {
+				return fmt.Errorf("efl: unmarshalMap: key %q: %w", key, err)
+			}
+			rawRV := reflect.ValueOf(raw)
+			var setVal reflect.Value
+			if rawRV.Type().AssignableTo(elemType) {
+				setVal = rawRV
+			} else if rawRV.Type().ConvertibleTo(elemType) {
+				setVal = rawRV.Convert(elemType)
+			} else {
+				return fmt.Errorf("efl: unmarshalMap: key %q: cannot assign %T to %v", key, raw, elemType)
+			}
+			result.SetMapIndex(reflect.ValueOf(key), setVal)
+		}
+		elem.Set(result)
+		return nil
+	}
+
+	// Non-scalar: recursively unmarshal each nested *Value.
+	for _, ck := range ckeys[:got] {
+		key := C.GoString(ck)
+		subPtr := C._ego_value_hash_new_value_at(val.ptr, ck)
+		if subPtr == nil {
+			return fmt.Errorf("efl: unmarshalMap: new_value_at failed for key %q", key)
+		}
+		sub := &Value{ptr: subPtr, owned: true}
+		dst := reflect.New(elemType)
+		if err := ValueUnmarshal(sub, dst.Interface()); err != nil {
+			sub.Free()
+			return fmt.Errorf("efl: unmarshalMap: key %q: %w", key, err)
+		}
+		sub.Free()
+		result.SetMapIndex(reflect.ValueOf(key), dst.Elem())
+	}
+	elem.Set(result)
+	return nil
+}
diff --git a/efl/value_marshal_test.go b/efl/value_marshal_test.go
index 75c6cc2..03493cc 100644
--- a/efl/value_marshal_test.go
+++ b/efl/value_marshal_test.go
@@ -169,3 +169,69 @@ func TestUnmarshalBool(t *testing.T) {
 		t.Error("want false, got true")
 	}
 }
+
+func TestMarshalSliceInt(t *testing.T) {
+	v, err := ValueMarshal([]int{10, 20, 30})
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer v.Free()
+	if v.Kind() != ValueKindArray {
+		t.Errorf("Kind=%v", v.Kind())
+	}
+	var got []int
+	if err := ValueUnmarshal(v, &got); err != nil {
+		t.Fatal(err)
+	}
+	if len(got) != 3 || got[0] != 10 || got[1] != 20 || got[2] != 30 {
+		t.Errorf("got %v", got)
+	}
+}
+
+func TestMarshalSliceString(t *testing.T) {
+	v, err := ValueMarshal([]string{"a", "b"})
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer v.Free()
+	var got []string
+	if err := ValueUnmarshal(v, &got); err != nil {
+		t.Fatal(err)
+	}
+	if len(got) != 2 || got[0] != "a" || got[1] != "b" {
+		t.Errorf("got %v", got)
+	}
+}
+
+func TestMarshalMapStringString(t *testing.T) {
+	v, err := ValueMarshal(map[string]string{"key": "val"})
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer v.Free()
+	if v.Kind() != ValueKindHash {
+		t.Errorf("Kind=%v", v.Kind())
+	}
+	var got map[string]string
+	if err := ValueUnmarshal(v, &got); err != nil {
+		t.Fatal(err)
+	}
+	if got["key"] != "val" {
+		t.Errorf("got[key]=%q", got["key"])
+	}
+}
+
+func TestMarshalMapStringInt(t *testing.T) {
+	v, err := ValueMarshal(map[string]int{"x": 1, "y": 2})
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer v.Free()
+	var got map[string]int
+	if err := ValueUnmarshal(v, &got); err != nil {
+		t.Fatal(err)
+	}
+	if got["x"] != 1 || got["y"] != 2 {
+		t.Errorf("got %v", got)
+	}
+}

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.

Reply via email to