Skip to content

[Request/Question] Support proxies for expr.Env #777

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
x1unix opened this issue Mar 30, 2025 · 8 comments
Open

[Request/Question] Support proxies for expr.Env #777

x1unix opened this issue Mar 30, 2025 · 8 comments
Labels

Comments

@x1unix
Copy link

x1unix commented Mar 30, 2025

Greetings.

I've a question - does expr support having proxies as expr.Env (something similar to Proxy feature in JS)?

I have a situation where I've to deal with a chain of objects as env that have prototype inheritance (also like in JS).

As env itself is dynamic (might depend on context, environment vars, etc), in order to build expr.Env each time I need to run an expression - it's necessary to traverse a whole prototype chain and create a new map.

Feature Benefits

This feature will allow to handle complex cases such as:

  • Cases when value have to be pulled dynamically in runtime.
  • To abstract away env when it has a complex structure, such as prototype chain.
  • Avoid allocating additional maps or structs for env.

Proposed solutions

Global & Nested Proxy

Ideally, to handle such cases, expr.Env could consume a ProxyEnv interface that will be called to get a variable:

// feel free to provide a better name
type ProxyEnv interface {
   GetValue(path string) (bool, any)
}

If returned value implements ProxyEnv, it also should be treated as proxy:

func example() {
    // Let's assume expression is `foo.bar[keyName]`
    keyName, _ := getValue(env, "keyName")

    foo, _ := getValue(env, "foo")
    bar, _ := getValue(bar, "bar")

    result, _ := getValue(bar, keyName)
}

func getValue(env any, key string) (any, bool) {
  switch t := env.(type) {
    case ProxyEnv:
        return t.GetValue(key)
    case map[string]any:
        v, ok := t[key]
        return v, ok
    default:
        // use reflection
        return getUsingReflect(env, key)
  }
}

Pros

  • Easier to implement

Cons

  • Additional type casting

Alternative Solutions

Please feel free to provide a better more efficient solution.

@antonmedv
Copy link
Member

I think we can add those features.

What about this case:

 foo.bar[42 + x].baz

@x1unix
Copy link
Author

x1unix commented Mar 30, 2025

@antonmedv good question!

Please check updated proposal.

@antonmedv
Copy link
Member

What about something like this:

type Proxy struct {
    Get(path []string) (any, error)
}

And on an expression:

let x = 100; 
foo.bar[42 + x].baz

Proxy will be called with

path := []string{
    "foo",
    "bar",
    "142",
    "baz",
}

@x1unix
Copy link
Author

x1unix commented Mar 31, 2025

@antonmedv as far as I know, expr supports variables.

It might be complex to handle such cases:

let y = foo.bar
y[42 + x].baz // Path chain has to be preserved when passed to Proxy

imho it might be easier to support nested ProxyEnv instead.

Also user might want to have a proxy only for specific fields (e.g. only for foo.bar but not for foo.bar.baz)

@x1unix
Copy link
Author

x1unix commented Mar 31, 2025

@antonmedv if you're interested, I can try to make a PR after the proposal will be finalized.

@antonmedv
Copy link
Member

I think implementation should be done via patcher which will replace property access with function cause.

@x1unix
Copy link
Author

x1unix commented Apr 7, 2025

@antonmedv if patched code will throw an error, would error position point to an original (pre-patched) code?

Let's imagine a code like this:

foo.bar.baz

and patched version would look like this:

foo.bar().baz() // bar returns nil

Ideally returned EvalError should be transparently mapped to original code:

foo.bar.baz
        ^^^ Cannot read properties of undefined (reading "baz")

Imho native object proxy support will make implementation much more simple.

@antonmedv
Copy link
Member

Expr patcher designed to point to original code location! Actually a lot of optimizations is done via patcher. But errors always return original code allocation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants