expr icon indicating copy to clipboard operation
expr copied to clipboard

How to access pointer value?

Open daisy1754 opened this issue 5 years ago • 15 comments

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
}

daisy1754 avatar Dec 15 '20 21:12 daisy1754

Compare it to nil. Expr language supports pointer as normal variables.

antonmedv avatar Dec 15 '20 22:12 antonmedv

Hi Anton - I want to know if it's nil, 0 or other value.

daisy1754 avatar Dec 15 '20 22:12 daisy1754

Use comparison operations ==. Don’t getting that is your problem.

antonmedv avatar Dec 16 '20 08:12 antonmedv

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.

daisy1754 avatar Dec 16 '20 09:12 daisy1754

Just like var: num

antonmedv avatar Dec 16 '20 09:12 antonmedv

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"
}

daisy1754 avatar Dec 17 '20 05:12 daisy1754

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)
}

antonmedv avatar Dec 17 '20 09:12 antonmedv

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.

rbrumby avatar Dec 27 '20 12:12 rbrumby

Let’s keep pointers issues in this thread.

antonmedv avatar Dec 27 '20 12:12 antonmedv

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().

rbrumby avatar Dec 27 '20 17:12 rbrumby

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.

antonmedv avatar Dec 27 '20 19:12 antonmedv

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.

rbrumby avatar Dec 28 '20 09:12 rbrumby

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 avatar Dec 28 '20 11:12 antonmedv

@antonmedv curious if you had a chance to take a deeper look on this issue? cc @divakarmanoj

daisy1754 avatar Mar 08 '21 20:03 daisy1754

No, still getting ton on issues in pr in other projects)

antonmedv avatar Mar 09 '21 07:03 antonmedv

I finally fixed it)

But is a little bit different way: using a separate opcode and using dereference only if needed.

See 8d50db26f99113901f2a23f07f50fe2e12343c9d

antonmedv avatar Oct 17 '22 22:10 antonmedv

wow this is awesome thank you Anton - do you know when you'll release the new version?

daisy1754 avatar Oct 22 '22 13:10 daisy1754

I'm planning on release next week. Probably in Tuesday or Wednesday. Trying to finish a few more improvements as well.

antonmedv avatar Oct 22 '22 14:10 antonmedv

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? 🤔

Dentrax avatar Oct 31 '22 19:10 Dentrax

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.

Dentrax avatar Oct 31 '22 19:10 Dentrax

I think this is an issue. Expr should work without env as well. I'm going to check this use case too.

antonmedv avatar Oct 31 '22 20:10 antonmedv

Fixed and added tests cases: https://github.com/antonmedv/expr/commit/5a4a8a303a1a866aa2908be71c286ed34b62f531

antonmedv avatar Nov 02 '22 19:11 antonmedv