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 99b586177f934d7390c70d6cdbda57071b762f7d
Author: [email protected] <[email protected]>
AuthorDate: Tue Mar 31 11:44:41 2026 -0600

    feat(efl): add ValueMarshal/ValueUnmarshal for scalar types
    
    Implement ValueMarshal(any) and ValueUnmarshal(*Value, any) with
    scalar type dispatch via reflection. Supports int, string, float64,
    bool, and all sized integer variants. Container and struct support
    follows in subsequent commits.
    
    Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
---
 efl/value_marshal.go      | 206 ++++++++++++++++++++++++++++++++++++++++++++++
 efl/value_marshal_test.go | 171 ++++++++++++++++++++++++++++++++++++++
 2 files changed, 377 insertions(+)

diff --git a/efl/value_marshal.go b/efl/value_marshal.go
new file mode 100644
index 0000000..74c57f8
--- /dev/null
+++ b/efl/value_marshal.go
@@ -0,0 +1,206 @@
+package efl
+
+import (
+	"errors"
+	"fmt"
+	"reflect"
+)
+
+// ValueMarshal converts any Go scalar value to a *Value.
+//
+// Supported input types: bool, string, all integer and float variants, and
+// *Value (pass-through). Slice, map, and struct inputs return a "not yet
+// implemented" error. nil returns an error.
+func ValueMarshal(v any) (*Value, error) {
+	if v == nil {
+		return nil, errors.New("efl: ValueMarshal: nil input")
+	}
+
+	// Pass-through: if the caller already has a *Value, return it directly.
+	if val, ok := v.(*Value); ok {
+		return val, nil
+	}
+
+	switch u := v.(type) {
+	case int:
+		return NewValue[int](u)
+	case int8:
+		// int8 is not in ScalarType; promote to int.
+		return NewValue[int](int(u))
+	case int16:
+		return NewValue[int16](u)
+	case int32:
+		return NewValue[int32](u)
+	case int64:
+		return NewValue[int64](u)
+	case uint:
+		return NewValue[uint](u)
+	case uint8:
+		// uint8 == byte; route through the byte branch so Kind() == ValueKindByte.
+		return NewValue[byte](u)
+	case uint16:
+		return NewValue[uint16](u)
+	case uint32:
+		return NewValue[uint32](u)
+	case uint64:
+		return NewValue[uint64](u)
+	case float32:
+		return NewValue[float32](u)
+	case float64:
+		return NewValue[float64](u)
+	case bool:
+		return NewValue[bool](u)
+	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 {
+		case reflect.Slice:
+			return nil, errors.New("efl: ValueMarshal: slice: not yet implemented")
+		case reflect.Map:
+			return nil, errors.New("efl: ValueMarshal: map: not yet implemented")
+		case reflect.Struct:
+			return nil, errors.New("efl: ValueMarshal: struct: not yet implemented")
+		default:
+			return nil, fmt.Errorf("efl: ValueMarshal: unsupported type %T", v)
+		}
+	}
+}
+
+// ValueUnmarshal reads the scalar stored in val and writes it into the value
+// pointed to by dst. dst must be a non-nil pointer to one of the supported
+// scalar types (bool, string, int family, uint family, float family).
+//
+// The unmarshal uses the destination type to determine which ValueGet variant
+// to call; if the stored type cannot be coerced by Eina, an error is returned.
+func ValueUnmarshal(val *Value, dst any) error {
+	if dst == nil {
+		return errors.New("efl: ValueUnmarshal: dst is nil")
+	}
+
+	rv := reflect.ValueOf(dst)
+	if rv.Kind() != reflect.Pointer {
+		return fmt.Errorf("efl: ValueUnmarshal: dst must be a pointer, got %T", dst)
+	}
+	if rv.IsNil() {
+		return errors.New("efl: ValueUnmarshal: dst pointer is nil")
+	}
+
+	elem := rv.Elem()
+
+	switch elem.Kind() {
+	case reflect.Int:
+		got, err := ValueGet[int](val)
+		if err != nil {
+			return fmt.Errorf("efl: ValueUnmarshal into int: %w", err)
+		}
+		elem.SetInt(int64(got))
+
+	case reflect.Int8:
+		got, err := ValueGet[int](val)
+		if err != nil {
+			return fmt.Errorf("efl: ValueUnmarshal into int8: %w", err)
+		}
+		elem.SetInt(int64(got))
+
+	case reflect.Int16:
+		got, err := ValueGet[int16](val)
+		if err != nil {
+			return fmt.Errorf("efl: ValueUnmarshal into int16: %w", err)
+		}
+		elem.SetInt(int64(got))
+
+	case reflect.Int32:
+		got, err := ValueGet[int32](val)
+		if err != nil {
+			return fmt.Errorf("efl: ValueUnmarshal into int32: %w", err)
+		}
+		elem.SetInt(int64(got))
+
+	case reflect.Int64:
+		got, err := ValueGet[int64](val)
+		if err != nil {
+			return fmt.Errorf("efl: ValueUnmarshal into int64: %w", err)
+		}
+		elem.SetInt(got)
+
+	case reflect.Uint:
+		got, err := ValueGet[uint](val)
+		if err != nil {
+			return fmt.Errorf("efl: ValueUnmarshal into uint: %w", err)
+		}
+		elem.SetUint(uint64(got))
+
+	case reflect.Uint8:
+		got, err := ValueGet[byte](val)
+		if err != nil {
+			return fmt.Errorf("efl: ValueUnmarshal into uint8: %w", err)
+		}
+		elem.SetUint(uint64(got))
+
+	case reflect.Uint16:
+		got, err := ValueGet[uint16](val)
+		if err != nil {
+			return fmt.Errorf("efl: ValueUnmarshal into uint16: %w", err)
+		}
+		elem.SetUint(uint64(got))
+
+	case reflect.Uint32:
+		got, err := ValueGet[uint32](val)
+		if err != nil {
+			return fmt.Errorf("efl: ValueUnmarshal into uint32: %w", err)
+		}
+		elem.SetUint(uint64(got))
+
+	case reflect.Uint64:
+		got, err := ValueGet[uint64](val)
+		if err != nil {
+			return fmt.Errorf("efl: ValueUnmarshal into uint64: %w", err)
+		}
+		elem.SetUint(got)
+
+	case reflect.Float32:
+		got, err := ValueGet[float32](val)
+		if err != nil {
+			return fmt.Errorf("efl: ValueUnmarshal into float32: %w", err)
+		}
+		elem.SetFloat(float64(got))
+
+	case reflect.Float64:
+		got, err := ValueGet[float64](val)
+		if err != nil {
+			return fmt.Errorf("efl: ValueUnmarshal into float64: %w", err)
+		}
+		elem.SetFloat(got)
+
+	case reflect.Bool:
+		got, err := ValueGet[bool](val)
+		if err != nil {
+			return fmt.Errorf("efl: ValueUnmarshal into bool: %w", err)
+		}
+		elem.SetBool(got)
+
+	case reflect.String:
+		got, err := ValueGet[string](val)
+		if err != nil {
+			return fmt.Errorf("efl: ValueUnmarshal into string: %w", err)
+		}
+		elem.SetString(got)
+
+	case reflect.Slice:
+		return errors.New("efl: ValueUnmarshal: slice: not yet implemented")
+
+	case reflect.Map:
+		return errors.New("efl: ValueUnmarshal: map: not yet implemented")
+
+	case reflect.Struct:
+		return errors.New("efl: ValueUnmarshal: struct: not yet implemented")
+
+	default:
+		return fmt.Errorf("efl: ValueUnmarshal: unsupported destination type %T", dst)
+	}
+
+	return nil
+}
diff --git a/efl/value_marshal_test.go b/efl/value_marshal_test.go
new file mode 100644
index 0000000..75c6cc2
--- /dev/null
+++ b/efl/value_marshal_test.go
@@ -0,0 +1,171 @@
+package efl
+
+import (
+	"math"
+	"testing"
+)
+
+// TestMarshalInt verifies that an int is marshalled to a Value with
+// ValueKindInt and that the value survives an unmarshal round-trip.
+func TestMarshalInt(t *testing.T) {
+	val, err := ValueMarshal(42)
+	if err != nil {
+		t.Fatalf("ValueMarshal(42): %v", err)
+	}
+	defer val.Free()
+
+	if got := val.Kind(); got != ValueKindInt {
+		t.Errorf("Kind: want ValueKindInt, got %v", got)
+	}
+
+	var out int
+	if err := ValueUnmarshal(val, &out); err != nil {
+		t.Fatalf("ValueUnmarshal into *int: %v", err)
+	}
+	if out != 42 {
+		t.Errorf("round-trip: want 42, got %d", out)
+	}
+}
+
+// TestMarshalString verifies string marshalling and round-trip.
+func TestMarshalString(t *testing.T) {
+	const input = "hello"
+	val, err := ValueMarshal(input)
+	if err != nil {
+		t.Fatalf("ValueMarshal(%q): %v", input, err)
+	}
+	defer val.Free()
+
+	if got := val.Kind(); got != ValueKindString {
+		t.Errorf("Kind: want ValueKindString, got %v", got)
+	}
+
+	var out string
+	if err := ValueUnmarshal(val, &out); err != nil {
+		t.Fatalf("ValueUnmarshal into *string: %v", err)
+	}
+	if out != input {
+		t.Errorf("round-trip: want %q, got %q", input, out)
+	}
+}
+
+// TestMarshalFloat64 verifies float64 marshalling and round-trip.
+func TestMarshalFloat64(t *testing.T) {
+	const input = 3.14
+	val, err := ValueMarshal(input)
+	if err != nil {
+		t.Fatalf("ValueMarshal(%v): %v", input, err)
+	}
+	defer val.Free()
+
+	if got := val.Kind(); got != ValueKindFloat64 {
+		t.Errorf("Kind: want ValueKindFloat64, got %v", got)
+	}
+
+	var out float64
+	if err := ValueUnmarshal(val, &out); err != nil {
+		t.Fatalf("ValueUnmarshal into *float64: %v", err)
+	}
+	if math.Abs(out-input) > 1e-10 {
+		t.Errorf("round-trip: want ~%v, got %v", input, out)
+	}
+}
+
+// TestMarshalBool verifies bool marshalling and round-trip.
+func TestMarshalBool(t *testing.T) {
+	val, err := ValueMarshal(true)
+	if err != nil {
+		t.Fatalf("ValueMarshal(true): %v", err)
+	}
+	defer val.Free()
+
+	if got := val.Kind(); got != ValueKindBool {
+		t.Errorf("Kind: want ValueKindBool, got %v", got)
+	}
+
+	var out bool
+	if err := ValueUnmarshal(val, &out); err != nil {
+		t.Fatalf("ValueUnmarshal into *bool: %v", err)
+	}
+	if !out {
+		t.Error("round-trip: want true, got false")
+	}
+}
+
+// TestMarshalValuePassthrough verifies that marshalling an existing *Value
+// returns the same pointer without wrapping or copying.
+func TestMarshalValuePassthrough(t *testing.T) {
+	original, err := NewValue[int](7)
+	if err != nil {
+		t.Fatalf("NewValue[int]: %v", err)
+	}
+	defer original.Free()
+
+	result, err := ValueMarshal(original)
+	if err != nil {
+		t.Fatalf("ValueMarshal(*Value): %v", err)
+	}
+	if result != original {
+		t.Error("passthrough: expected same pointer, got a different one")
+	}
+}
+
+// TestMarshalNil verifies that passing nil to ValueMarshal returns an error.
+func TestMarshalNil(t *testing.T) {
+	_, err := ValueMarshal(nil)
+	if err == nil {
+		t.Fatal("expected error for nil input, got nil")
+	}
+}
+
+// TestUnmarshalInt marshals an int and unmarshals it back into *int.
+func TestUnmarshalInt(t *testing.T) {
+	val, err := ValueMarshal(99)
+	if err != nil {
+		t.Fatalf("ValueMarshal(99): %v", err)
+	}
+	defer val.Free()
+
+	var out int
+	if err := ValueUnmarshal(val, &out); err != nil {
+		t.Fatalf("ValueUnmarshal: %v", err)
+	}
+	if out != 99 {
+		t.Errorf("want 99, got %d", out)
+	}
+}
+
+// TestUnmarshalString marshals a string and unmarshals it back into *string.
+func TestUnmarshalString(t *testing.T) {
+	const want = "unmarshal me"
+	val, err := ValueMarshal(want)
+	if err != nil {
+		t.Fatalf("ValueMarshal: %v", err)
+	}
+	defer val.Free()
+
+	var out string
+	if err := ValueUnmarshal(val, &out); err != nil {
+		t.Fatalf("ValueUnmarshal: %v", err)
+	}
+	if out != want {
+		t.Errorf("want %q, got %q", want, out)
+	}
+}
+
+// TestUnmarshalBool marshals a bool and unmarshals it back into *bool.
+func TestUnmarshalBool(t *testing.T) {
+	val, err := ValueMarshal(false)
+	if err != nil {
+		t.Fatalf("ValueMarshal(false): %v", err)
+	}
+	defer val.Free()
+
+	out := true // set to true so we can confirm it was overwritten with false
+	if err := ValueUnmarshal(val, &out); err != nil {
+		t.Fatalf("ValueUnmarshal: %v", err)
+	}
+	if out {
+		t.Error("want false, got true")
+	}
+}

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

Reply via email to