From e114b1596309ad773fc87545ef40b4bbf909b28f Mon Sep 17 00:00:00 2001 From: x1unix <9203548+x1unix@users.noreply.github.com> Date: Thu, 4 Dec 2025 23:59:54 -0500 Subject: [PATCH 1/4] feat: add proxies support for OpFetch --- vm/runtime/runtime.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/vm/runtime/runtime.go b/vm/runtime/runtime.go index 36995d30..e8f984cc 100644 --- a/vm/runtime/runtime.go +++ b/vm/runtime/runtime.go @@ -10,7 +10,27 @@ import ( "github.com/expr-lang/expr/internal/deref" ) +// Proxy is an interface that allows intercepting object property read and write operations. +type Proxy interface { + ProxyGetter +} + +// ProxyGetter is an interface that allows intercepting object property access. +type ProxyGetter interface { + // GetProperty returns the value of the property with the given key. + GetProperty(key string) (any, bool) +} + func Fetch(from, i any) any { + if proxy, ok := from.(ProxyGetter); ok { + r, ok := proxy.GetProperty(i.(string)) + if !ok { + panic(fmt.Sprintf("cannot fetch %v from %T", i, from)) + } + + return r + } + v := reflect.ValueOf(from) if v.Kind() == reflect.Invalid { panic(fmt.Sprintf("cannot fetch %v from %T", i, from)) From 675f10067e7533d24574ff0176128696a55bdaa2 Mon Sep 17 00:00:00 2001 From: x1unix <9203548+x1unix@users.noreply.github.com> Date: Sat, 6 Dec 2025 00:03:52 -0500 Subject: [PATCH 2/4] feat: add proxy read test --- vm/runtime/runtime.go | 16 +++---- vm/vm_test.go | 105 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+), 9 deletions(-) diff --git a/vm/runtime/runtime.go b/vm/runtime/runtime.go index e8f984cc..5de9807e 100644 --- a/vm/runtime/runtime.go +++ b/vm/runtime/runtime.go @@ -12,20 +12,18 @@ import ( // Proxy is an interface that allows intercepting object property read and write operations. type Proxy interface { - ProxyGetter -} - -// ProxyGetter is an interface that allows intercepting object property access. -type ProxyGetter interface { // GetProperty returns the value of the property with the given key. - GetProperty(key string) (any, bool) + GetProperty(key any) (any, bool) + + // SetProperty sets the value of the property with the given key. + SetProperty(key, value any) } func Fetch(from, i any) any { - if proxy, ok := from.(ProxyGetter); ok { - r, ok := proxy.GetProperty(i.(string)) + if proxy, ok := from.(Proxy); ok { + r, ok := proxy.GetProperty(i) if !ok { - panic(fmt.Sprintf("cannot fetch %v from %T", i, from)) + panic(fmt.Sprintf("cannot fetch %v from proxy", i)) } return r diff --git a/vm/vm_test.go b/vm/vm_test.go index 6b613d57..47553b42 100644 --- a/vm/vm_test.go +++ b/vm/vm_test.go @@ -10,6 +10,7 @@ import ( "github.com/expr-lang/expr/file" "github.com/expr-lang/expr/internal/testify/require" + "github.com/expr-lang/expr/vm/runtime" "github.com/expr-lang/expr" "github.com/expr-lang/expr/checker" @@ -267,6 +268,110 @@ func TestRun_InnerMethodWithError_NilSafe(t *testing.T) { require.Equal(t, nil, out) } +var _ runtime.Proxy = (*proxyNode)(nil) + +type proxyNode struct { + parent *proxyNode + values map[any]any +} + +func (n *proxyNode) GetProperty(key any) (any, bool) { + if value, ok := n.values[key]; ok { + return value, true + } + if n.parent != nil { + return n.parent.GetProperty(key) + } + return nil, false +} + +func (n *proxyNode) SetProperty(key any, value any) { + n.values[key] = value +} + +func TestRun_Proxy_Read(t *testing.T) { + cases := []struct { + label string + expr string + env any + expect any + }{ + { + label: "proxy env map member", + expr: `foo.bar`, + env: map[string]any{ + "foo": &proxyNode{ + values: map[any]any{ + "bar": "baz", + }, + }, + }, + expect: "baz", + }, + { + label: "read from root", + expr: `value`, + env: &proxyNode{ + values: map[any]any{ + "value": "hello world", + }, + }, + expect: "hello world", + }, + { + label: "read from child", + expr: `child.value`, + env: &proxyNode{ + values: map[any]any{ + "child": &proxyNode{ + values: map[any]any{ + "value": "hello world", + }, + }, + }, + }, + expect: "hello world", + }, + { + label: "read from grandchild", + expr: `grandchild.child.value`, + env: &proxyNode{ + values: map[any]any{ + "grandchild": &proxyNode{ + values: map[any]any{ + "child": &proxyNode{ + values: map[any]any{ + "value": "hello world", + }, + }, + }, + }, + }, + }, + expect: "hello world", + }, + } + + for _, c := range cases { + t.Run(c.label, func(t *testing.T) { + tree, err := parser.Parse(c.expr) + require.NoError(t, err) + + funcConf := conf.CreateNew() + _, err = checker.Check(tree, funcConf) + require.NoError(t, err) + + program, err := compiler.Compile(tree, funcConf) + require.NoError(t, err) + + out, err := vm.Run(program, c.env) + require.NoError(t, err) + + require.Equal(t, c.expect, out) + }) + } +} + func TestRun_TaggedFieldName(t *testing.T) { input := `value` From a5574904b2c479211702b5ccf3a58eea399173f6 Mon Sep 17 00:00:00 2001 From: x1unix <9203548+x1unix@users.noreply.github.com> Date: Sat, 6 Dec 2025 00:17:51 -0500 Subject: [PATCH 3/4] fix: remove unused write method --- vm/runtime/runtime.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/vm/runtime/runtime.go b/vm/runtime/runtime.go index 5de9807e..7523cba9 100644 --- a/vm/runtime/runtime.go +++ b/vm/runtime/runtime.go @@ -10,13 +10,10 @@ import ( "github.com/expr-lang/expr/internal/deref" ) -// Proxy is an interface that allows intercepting object property read and write operations. +// Proxy is an interface that allows intercepting object property access. type Proxy interface { // GetProperty returns the value of the property with the given key. GetProperty(key any) (any, bool) - - // SetProperty sets the value of the property with the given key. - SetProperty(key, value any) } func Fetch(from, i any) any { From c1e84a15659ca2d785348307463b36a3c1a326fc Mon Sep 17 00:00:00 2001 From: x1unix <9203548+x1unix@users.noreply.github.com> Date: Sat, 6 Dec 2025 00:28:46 -0500 Subject: [PATCH 4/4] fix: simplify proxy interface --- vm/runtime/runtime.go | 9 ++------- vm/vm_test.go | 6 +++--- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/vm/runtime/runtime.go b/vm/runtime/runtime.go index 7523cba9..bb5417fa 100644 --- a/vm/runtime/runtime.go +++ b/vm/runtime/runtime.go @@ -13,17 +13,12 @@ import ( // Proxy is an interface that allows intercepting object property access. type Proxy interface { // GetProperty returns the value of the property with the given key. - GetProperty(key any) (any, bool) + GetProperty(key any) any } func Fetch(from, i any) any { if proxy, ok := from.(Proxy); ok { - r, ok := proxy.GetProperty(i) - if !ok { - panic(fmt.Sprintf("cannot fetch %v from proxy", i)) - } - - return r + return proxy.GetProperty(i) } v := reflect.ValueOf(from) diff --git a/vm/vm_test.go b/vm/vm_test.go index 47553b42..b3017724 100644 --- a/vm/vm_test.go +++ b/vm/vm_test.go @@ -275,14 +275,14 @@ type proxyNode struct { values map[any]any } -func (n *proxyNode) GetProperty(key any) (any, bool) { +func (n *proxyNode) GetProperty(key any) any { if value, ok := n.values[key]; ok { - return value, true + return value } if n.parent != nil { return n.parent.GetProperty(key) } - return nil, false + return nil } func (n *proxyNode) SetProperty(key any, value any) {