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.