How to access pointer value?
I have a usecase where I want to distinguish 0 and nil so I'm trying to pass *int to expr. Below is code I tried and it doesn't work as I had expected. How can get the value of pointer? I want to write expression like *num==1 or num.Value==1 but neither seems to work
package main
import (
"fmt"
"github.com/antonmedv/expr"
)
func main() {
num := 1
env := map[string]interface{}{
"num": &num,
}
p1, _ := expr.Compile(`num!=nil`, expr.Env(env))
o1, _ := expr.Run(p1, env)
fmt.Println(o1) // true
p2, _ := expr.Compile(`num==1`, expr.Env(env))
o2, _ := expr.Run(p2, env)
fmt.Println(o2) // false
p3, _ := expr.Compile(`num.Value==1`, expr.Env(env))
o3, _ := expr.Run(p3, env)
fmt.Println(o3) // nil
}
Compare it to nil. Expr language supports pointer as normal variables.
Hi Anton - I want to know if it's nil, 0 or other value.
Use comparison operations ==. Don’t getting that is your problem.
Maybe my example was not clear. When num is pointer to integer value, I want to first check if num is nil, if not I want to check value of int referenced by the pointer (*num == 1 in golang's code).
My question was how can I get value of referenced variable (*num) in expr.
Just like var: num
Hmm, I'm not really sure if we're on the same page. Can you rewrite below golang program in expr?
func foo(num *int) string {
if num == nil {
return "nil"
} else if *num == 0 {
return "zero"
} else if *num == 1 {
return "one"
}
return "other"
}
Now I got you. Looks like a bug in expr with dereferencing of *int. Will take a look into it. Currently this is not working, but should:
package main
import (
"fmt"
"github.com/antonmedv/expr"
)
type Env struct {
Num *int
}
func main() {
code := `Num == nil ? "is nil" : (Num == 0 ? "zero" : "not zero")`
program, err := expr.Compile(code, expr.Env(Env{}))
if err != nil {
panic(err)
}
num := 0
output, err := expr.Run(program, Env{&num})
if err != nil {
panic(err)
}
fmt.Println(output)
}
On a similar theme, the following is fine:
type s struct {
Stringval string
}
func main() {
sv := s{
Stringval: "my-string-value",
}
prg, err := expr.Compile(`Stringval`)
if err != nil {
panic(err)
}
out, err := expr.Run(prg, s{})
if err != nil {
panic(err)
}
fmt.Println(out)
}
But if we make Stringval a pointer...
type s struct {
Stringval *string
}
func main() {
str := "stringvalue"
sv := s{
Stringval: &str,
}
prg, err := expr.Compile(`Stringval`)
if err != nil {
panic(err)
}
out, err := expr.Run(prg, s{})
if err != nil {
panic(err)
}
fmt.Println(out)
}
...then the output is a memory address (eg. 0xc000030500).
The output can be converted outside the expression language with a type assertion (in this case on the out variable) but this too late to allow it to be combined with other values in the expression. It would be nice to be able to dereference pointers within the expression language itself.
Let me know if you want me to register this as another issue or otherwise. I'd also be happy to help come up with a way to support this if you would like.
Let’s keep pointers issues in this thread.
Having had a little think about this, maybe adding a "deref()" builtin would do the job?
That would allow Daisy's use-case to be satisfied with deref(num) == 1.
I think it best to let the user decide what to dereference as sometimes they may want to return the pointer itself from the output of Run().
I think this will complicate things for end user, which usually managers and people not familiar with programming at all. Better to omit deference problems out of there hands. Better to have transparent deference. Just num == 1, we can understand what is meant here.
Sure, it's your call - but if you are going to dereference by default, could we have a way to switch that off (so we can still have the same functionality we have today)? I have some use-cases where I think I'll want to use expressions on user-defined structures to retrieve pointers.
Definitely. Actually if you don’t do any binary operations type returns unchanged. For example now you can return uint8 if you want as is. Same for pointers.
@antonmedv curious if you had a chance to take a deeper look on this issue? cc @divakarmanoj
No, still getting ton on issues in pr in other projects)
I finally fixed it)
But is a little bit different way: using a separate opcode and using dereference only if needed.
See 8d50db26f99113901f2a23f07f50fe2e12343c9d
wow this is awesome thank you Anton - do you know when you'll release the new version?
I'm planning on release next week. Probably in Tuesday or Wednesday. Trying to finish a few more improvements as well.
Hey @antonmedv, thanks for resolving this!
But interestingly, as a first time user of expr, I hit this issue on v1.9.1-0.20221030193158-2213166cdca2. I was expecting true from the following below snippet, but it returns false somehow for the Num == 5 exp, which actually set to &5:
package main
import (
"fmt"
"github.com/antonmedv/expr"
)
type Env struct {
Num *int
}
func main() {
foo := 5
env := Env {
Num: &foo,
}
code := `Num == 5`
program, err := expr.Compile(code)
if err != nil {
panic(err)
}
output, err := expr.Run(program, env)
if err != nil {
panic(err)
}
fmt.Println(output)
}
It seems like you have already covered this in unit tests and I can see that you asserted it as true and tests are passing. So what did I do wrong? Should I enable a feature flag or something? 🤔
Oh -- I think it's because we have to pass expr.Env() option to expr.Compile func. In my use case, I compile all the expressions (since all predefined and known), but env options are completely unset (unknown) during initial phase. They all set at runtime. That's why I couldn't pass env to Compile function.
Edit: expr.Eval() also returns false.
I think this is an issue. Expr should work without env as well. I'm going to check this use case too.
Fixed and added tests cases: https://github.com/antonmedv/expr/commit/5a4a8a303a1a866aa2908be71c286ed34b62f531