From b573991b3af74c6e617dba37bbc13501d1a9f922 Mon Sep 17 00:00:00 2001 From: Ryan Bullock Date: Fri, 17 Nov 2023 17:07:09 -0800 Subject: [PATCH 01/12] A bultin patcher providing interfaces for custom types that can be represented as standard go types for expressions. --- patchers/value/bench_test.go | 71 ++++++++++ patchers/value/value.go | 260 +++++++++++++++++++++++++++++++++++ patchers/value/value_test.go | 170 +++++++++++++++++++++++ 3 files changed, 501 insertions(+) create mode 100644 patchers/value/bench_test.go create mode 100644 patchers/value/value.go create mode 100644 patchers/value/value_test.go diff --git a/patchers/value/bench_test.go b/patchers/value/bench_test.go new file mode 100644 index 00000000..dc5e4c17 --- /dev/null +++ b/patchers/value/bench_test.go @@ -0,0 +1,71 @@ +package value + +import ( + "github.com/antonmedv/expr" + "github.com/antonmedv/expr/vm" + "github.com/stretchr/testify/require" + "testing" +) + +func Benchmark_valueAdd(b *testing.B) { + env := make(map[string]any) + env["ValueOne"] = &customInt{1} + env["ValueTwo"] = &customInt{2} + + program, err := expr.Compile("ValueOne + ValueTwo", expr.Env(env), Patcher) + require.NoError(b, err) + + var out any + v := vm.VM{} + + b.ResetTimer() + for n := 0; n < b.N; n++ { + out, err = v.Run(program, env) + } + b.StopTimer() + + require.NoError(b, err) + require.Equal(b, 3, out.(int)) +} + +func Benchmark_valueUntypedAdd(b *testing.B) { + env := make(map[string]any) + env["ValueOne"] = &customUntypedInt{1} + env["ValueTwo"] = &customUntypedInt{2} + + program, err := expr.Compile("ValueOne + ValueTwo", expr.Env(env), Patcher) + require.NoError(b, err) + + var out any + v := vm.VM{} + + b.ResetTimer() + for n := 0; n < b.N; n++ { + out, err = v.Run(program, env) + } + b.StopTimer() + + require.NoError(b, err) + require.Equal(b, 3, out.(int)) +} + +func Benchmark_valueTypedAdd(b *testing.B) { + env := make(map[string]any) + env["ValueOne"] = &customTypedInt{1} + env["ValueTwo"] = &customTypedInt{2} + + program, err := expr.Compile("ValueOne + ValueTwo", expr.Env(env), Patcher) + require.NoError(b, err) + + var out any + v := vm.VM{} + + b.ResetTimer() + for n := 0; n < b.N; n++ { + out, err = v.Run(program, env) + } + b.StopTimer() + + require.NoError(b, err) + require.Equal(b, 3, out.(int)) +} diff --git a/patchers/value/value.go b/patchers/value/value.go new file mode 100644 index 00000000..5c21b3b5 --- /dev/null +++ b/patchers/value/value.go @@ -0,0 +1,260 @@ +// Package value provides a Patcher that uses interfaces to allow custom types that can be represented as standard go values to be used more easily in expressions. +// +// # Example Usage +// +// import ( +// "fmt" +// "github.com/antonmedv/expr/patchers/value" +// "github.com/antonmedv/expr" +// ) +// +// type customInt struct { +// Int int +// } +// +// // Provides type checking at compile time +// func (v *customInt) IntValue() int { +// return v.Int +// } +// +// // Lets us return nil if we need to +// func (v *customInt) ExprValue() any { +// return v.Int +// } +// +// func main() { +// env := make(map[string]any) +// env["ValueOne"] = &customInt{1} +// env["ValueTwo"] = &customInt{2} +// +// program, err := expr.Compile("ValueOne + ValueTwo", expr.Env(env), value.Patcher) +// +// if err != nil { +// panic(err) +// } +// +// out, err := vm.Run(program, env) +// +// if err != nil { +// panic(err) +// } +// +// fmt.Printf("Got %v", out) +// } +package value + +import ( + "reflect" + "time" + + "github.com/antonmedv/expr" + "github.com/antonmedv/expr/ast" + "github.com/antonmedv/expr/conf" +) + +// Patcher is an expr.Option that both patches the program and adds the `getExprValue` function. +// Use it directly as an Option to expr.Compile() +var Patcher = func() expr.Option { + vPatcher := patcher{} + return func(c *conf.Config) { + c.Visitors = append(c.Visitors, vPatcher) + vPatcher.ApplyOptions(c) + } +}() + +// A ExprValuer provides a generic function for a custom type to return standard go values. +// It allows for returning a `nil` value but does not provide any type checking at expression compile. +// +// A custom type may implement both ExprValuer and a type specific interface to enable both +// compile time checking and the ability to return a `nil` value. +type ExprValuer interface { + ExprValue() any +} + +type IntValuer interface { + IntValue() int +} + +type BoolValuer interface { + BoolValue() bool +} + +type Int8Valuer interface { + Int8Value() int8 +} + +type Int16Valuer interface { + Int16Value() int16 +} + +type Int32Valuer interface { + Int32Value() int32 +} + +type Int64Valuer interface { + Int64Value() int64 +} + +type UintValuer interface { + UintValue() uint +} + +type Uint8Valuer interface { + Uint8Value() uint8 +} + +type Uint16Valuer interface { + Uint16Value() uint16 +} + +type Uint32Valuer interface { + Uint32Value() uint32 +} + +type Uint64Valuer interface { + Uint64Value() uint64 +} + +type Float32Valuer interface { + Float32Value() float32 +} + +type Float64Valuer interface { + Float64Value() float64 +} + +type StringValuer interface { + StringValue() string +} + +type TimeValuer interface { + TimeValue() time.Time +} + +type DurationValuer interface { + DurationValue() time.Duration +} + +type ArrayValuer interface { + ArrayValue() []any +} + +type MapValuer interface { + MapValue() map[string]any +} + +var supportedInterfaces = []reflect.Type{ + reflect.TypeOf((*ExprValuer)(nil)).Elem(), + reflect.TypeOf((*BoolValuer)(nil)).Elem(), + reflect.TypeOf((*IntValuer)(nil)).Elem(), + reflect.TypeOf((*Int8Valuer)(nil)).Elem(), + reflect.TypeOf((*Int16Valuer)(nil)).Elem(), + reflect.TypeOf((*Int32Valuer)(nil)).Elem(), + reflect.TypeOf((*Int64Valuer)(nil)).Elem(), + reflect.TypeOf((*UintValuer)(nil)).Elem(), + reflect.TypeOf((*Uint8Valuer)(nil)).Elem(), + reflect.TypeOf((*Uint16Valuer)(nil)).Elem(), + reflect.TypeOf((*Uint32Valuer)(nil)).Elem(), + reflect.TypeOf((*Uint64Valuer)(nil)).Elem(), + reflect.TypeOf((*Float32Valuer)(nil)).Elem(), + reflect.TypeOf((*Float64Valuer)(nil)).Elem(), + reflect.TypeOf((*StringValuer)(nil)).Elem(), + reflect.TypeOf((*TimeValuer)(nil)).Elem(), + reflect.TypeOf((*DurationValuer)(nil)).Elem(), + reflect.TypeOf((*ArrayValuer)(nil)).Elem(), + reflect.TypeOf((*MapValuer)(nil)).Elem(), +} + +type patcher struct{} + +func (patcher) Visit(node *ast.Node) { + id, ok := (*node).(*ast.IdentifierNode) + if !ok { + return + } + + callnode := &ast.CallNode{ + Callee: &ast.IdentifierNode{Value: "getExprValue"}, + Arguments: []ast.Node{id}, + } + + nodeType := id.Type() + + for _, t := range supportedInterfaces { + if nodeType.Implements(t) { + ast.Patch(node, callnode) + } + } +} + +func (patcher) ApplyOptions(c *conf.Config) { + getExprValueFunc(c) +} + +func getExprValue(params ...any) (any, error) { + + switch v := params[0].(type) { + case ExprValuer: + return v.ExprValue(), nil + case BoolValuer: + return v.BoolValue(), nil + case IntValuer: + return v.IntValue(), nil + case Int8Valuer: + return v.Int8Value(), nil + case Int16Valuer: + return v.Int16Value(), nil + case Int32Valuer: + return v.Int32Value(), nil + case Int64Valuer: + return v.Int64Value(), nil + case UintValuer: + return v.UintValue(), nil + case Uint8Valuer: + return v.Uint8Value(), nil + case Uint16Valuer: + return v.Uint16Value(), nil + case Uint32Valuer: + return v.Uint32Value(), nil + case Uint64Valuer: + return v.Uint64Value(), nil + case Float32Valuer: + return v.Float32Value(), nil + case Float64Valuer: + return v.Float64Value(), nil + case StringValuer: + return v.StringValue(), nil + case TimeValuer: + return v.TimeValue(), nil + case DurationValuer: + return v.DurationValue(), nil + case ArrayValuer: + return v.ArrayValue(), nil + case MapValuer: + return v.MapValue(), nil + } + + return params[0], nil +} + +var getExprValueFunc = expr.Function("getExprValue", getExprValue, + new(func(BoolValuer) bool), + new(func(IntValuer) int), + new(func(Int8Valuer) int8), + new(func(Int16Valuer) int16), + new(func(Int32Valuer) int32), + new(func(Int64Valuer) int64), + new(func(UintValuer) uint), + new(func(Uint8Valuer) uint8), + new(func(Uint16Valuer) uint16), + new(func(Uint32Valuer) uint32), + new(func(Uint64Valuer) uint64), + new(func(Float32Valuer) float32), + new(func(Float64Valuer) float64), + new(func(StringValuer) string), + new(func(TimeValuer) time.Time), + new(func(DurationValuer) time.Duration), + new(func(ArrayValuer) []any), + new(func(MapValuer) map[string]any), + new(func(any) any), +) diff --git a/patchers/value/value_test.go b/patchers/value/value_test.go new file mode 100644 index 00000000..8de3f5ae --- /dev/null +++ b/patchers/value/value_test.go @@ -0,0 +1,170 @@ +package value + +import ( + "github.com/antonmedv/expr" + "github.com/antonmedv/expr/vm" + "github.com/stretchr/testify/require" + "testing" +) + +type customInt struct { + Int int +} + +func (v *customInt) IntValue() int { + return v.Int +} + +func (v *customInt) ExprValue() any { + return v.Int +} + +type customTypedInt struct { + Int int +} + +func (v *customTypedInt) IntValue() int { + return v.Int +} + +type customUntypedInt struct { + Int int +} + +func (v *customUntypedInt) ExprValue() any { + return v.Int +} + +type customString struct { + String string +} + +func (v *customString) StringValue() string { + return v.String +} + +func (v *customString) ExprValue() any { + return v.String +} + +type customTypedString struct { + String string +} + +func (v *customTypedString) StringValue() string { + return v.String +} + +type customUntypedString struct { + String string +} + +func (v *customUntypedString) ExprValue() any { + return v.String +} + +type customTypedArray struct { + Array []any +} + +func (v *customTypedArray) ArrayValue() []any { + return v.Array +} + +type customTypedMap struct { + Map map[string]any +} + +func (v *customTypedMap) MapValue() map[string]any { + return v.Map +} + +func Test_valueAddInt(t *testing.T) { + env := make(map[string]any) + env["ValueOne"] = &customInt{1} + env["ValueTwo"] = &customInt{2} + + program, err := expr.Compile("ValueOne + ValueTwo", expr.Env(env), Patcher) + require.NoError(t, err) + + out, err := vm.Run(program, env) + + require.NoError(t, err) + require.Equal(t, 3, out.(int)) +} + +func Test_valueUntypedAddInt(t *testing.T) { + env := make(map[string]any) + env["ValueOne"] = &customUntypedInt{1} + env["ValueTwo"] = &customUntypedInt{2} + + program, err := expr.Compile("ValueOne + ValueTwo", expr.Env(env), Patcher) + require.NoError(t, err) + + out, err := vm.Run(program, env) + + require.NoError(t, err) + require.Equal(t, 3, out.(int)) +} + +func Test_valueTypedAddInt(t *testing.T) { + env := make(map[string]any) + env["ValueOne"] = &customTypedInt{1} + env["ValueTwo"] = &customTypedInt{2} + + program, err := expr.Compile("ValueOne + ValueTwo", expr.Env(env), Patcher) + require.NoError(t, err) + + out, err := vm.Run(program, env) + + require.NoError(t, err) + require.Equal(t, 3, out.(int)) +} + +func Test_valueTypedAddMismatch(t *testing.T) { + env := make(map[string]any) + env["ValueOne"] = &customTypedInt{1} + env["ValueTwo"] = &customTypedString{"test"} + + _, err := expr.Compile("ValueOne + ValueTwo", expr.Env(env), Patcher) + require.Error(t, err) +} + +func Test_valueUntypedAddMismatch(t *testing.T) { + env := make(map[string]any) + env["ValueOne"] = &customUntypedInt{1} + env["ValueTwo"] = &customUntypedString{"test"} + + program, err := expr.Compile("ValueOne + ValueTwo", expr.Env(env), Patcher) + require.NoError(t, err) + + _, err = vm.Run(program, env) + + require.Error(t, err) +} + +func Test_valueTypedArray(t *testing.T) { + env := make(map[string]any) + env["ValueOne"] = &customTypedArray{[]any{ 1, 2 }} + + program, err := expr.Compile("ValueOne[0] + ValueOne[1]", expr.Env(env), Patcher) + require.NoError(t, err) + + out, err := vm.Run(program, env) + + require.NoError(t, err) + require.Equal(t, 3, out.(int)) +} + +func Test_valueTypedMap(t *testing.T) { + env := make(map[string]any) + env["ValueOne"] = &customTypedMap{map[string]any{ "one": 1, "two": 2 } } + + program, err := expr.Compile("ValueOne.one + ValueOne.two", expr.Env(env), Patcher) + require.NoError(t, err) + + out, err := vm.Run(program, env) + + require.NoError(t, err) + require.Equal(t, 3, out.(int)) +} From 5e3163def9cb467813d69575acb925f90ac03812 Mon Sep 17 00:00:00 2001 From: Ryan Bullock Date: Mon, 4 Dec 2023 10:59:01 -0800 Subject: [PATCH 02/12] Move node creation inside of loop. --- patchers/value/value.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/patchers/value/value.go b/patchers/value/value.go index 5c21b3b5..abafaaa5 100644 --- a/patchers/value/value.go +++ b/patchers/value/value.go @@ -173,15 +173,15 @@ func (patcher) Visit(node *ast.Node) { return } - callnode := &ast.CallNode{ - Callee: &ast.IdentifierNode{Value: "getExprValue"}, - Arguments: []ast.Node{id}, - } - nodeType := id.Type() for _, t := range supportedInterfaces { if nodeType.Implements(t) { + callnode := &ast.CallNode{ + Callee: &ast.IdentifierNode{Value: "getExprValue"}, + Arguments: []ast.Node{id}, + } + ast.Patch(node, callnode) } } From 6c8f684e9a7c0772c7ba9fc7ebadd2137d04b18b Mon Sep 17 00:00:00 2001 From: Ryan Bullock Date: Mon, 4 Dec 2023 11:06:30 -0800 Subject: [PATCH 03/12] Reorder imports. Fix some formatting. --- patchers/value/bench_test.go | 6 ++++-- patchers/value/value.go | 1 - patchers/value/value_test.go | 10 ++++++---- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/patchers/value/bench_test.go b/patchers/value/bench_test.go index dc5e4c17..1a3e1a86 100644 --- a/patchers/value/bench_test.go +++ b/patchers/value/bench_test.go @@ -1,10 +1,12 @@ package value import ( + "testing" + + "github.com/stretchr/testify/require" + "github.com/antonmedv/expr" "github.com/antonmedv/expr/vm" - "github.com/stretchr/testify/require" - "testing" ) func Benchmark_valueAdd(b *testing.B) { diff --git a/patchers/value/value.go b/patchers/value/value.go index abafaaa5..019a2800 100644 --- a/patchers/value/value.go +++ b/patchers/value/value.go @@ -192,7 +192,6 @@ func (patcher) ApplyOptions(c *conf.Config) { } func getExprValue(params ...any) (any, error) { - switch v := params[0].(type) { case ExprValuer: return v.ExprValue(), nil diff --git a/patchers/value/value_test.go b/patchers/value/value_test.go index 8de3f5ae..beafce4e 100644 --- a/patchers/value/value_test.go +++ b/patchers/value/value_test.go @@ -1,10 +1,12 @@ package value import ( + "testing" + + "github.com/stretchr/testify/require" + "github.com/antonmedv/expr" "github.com/antonmedv/expr/vm" - "github.com/stretchr/testify/require" - "testing" ) type customInt struct { @@ -145,7 +147,7 @@ func Test_valueUntypedAddMismatch(t *testing.T) { func Test_valueTypedArray(t *testing.T) { env := make(map[string]any) - env["ValueOne"] = &customTypedArray{[]any{ 1, 2 }} + env["ValueOne"] = &customTypedArray{[]any{1, 2}} program, err := expr.Compile("ValueOne[0] + ValueOne[1]", expr.Env(env), Patcher) require.NoError(t, err) @@ -158,7 +160,7 @@ func Test_valueTypedArray(t *testing.T) { func Test_valueTypedMap(t *testing.T) { env := make(map[string]any) - env["ValueOne"] = &customTypedMap{map[string]any{ "one": 1, "two": 2 } } + env["ValueOne"] = &customTypedMap{map[string]any{"one": 1, "two": 2}} program, err := expr.Compile("ValueOne.one + ValueOne.two", expr.Env(env), Patcher) require.NoError(t, err) From e8ffc6bfbfcfe5a24d6ec58ed39ef4578827314e Mon Sep 17 00:00:00 2001 From: Ryan Bullock Date: Mon, 4 Dec 2023 11:09:15 -0800 Subject: [PATCH 04/12] Rename expr function from 'getExprValue' to '$patcher_value_getter'. --- patchers/value/value.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/patchers/value/value.go b/patchers/value/value.go index 019a2800..286f2524 100644 --- a/patchers/value/value.go +++ b/patchers/value/value.go @@ -52,7 +52,7 @@ import ( "github.com/antonmedv/expr/conf" ) -// Patcher is an expr.Option that both patches the program and adds the `getExprValue` function. +// Patcher is an expr.Option that both patches the program and adds the `$patcher_value_getter` function. // Use it directly as an Option to expr.Compile() var Patcher = func() expr.Option { vPatcher := patcher{} @@ -178,7 +178,7 @@ func (patcher) Visit(node *ast.Node) { for _, t := range supportedInterfaces { if nodeType.Implements(t) { callnode := &ast.CallNode{ - Callee: &ast.IdentifierNode{Value: "getExprValue"}, + Callee: &ast.IdentifierNode{Value: "$patcher_value_getter"}, Arguments: []ast.Node{id}, } @@ -236,7 +236,7 @@ func getExprValue(params ...any) (any, error) { return params[0], nil } -var getExprValueFunc = expr.Function("getExprValue", getExprValue, +var getExprValueFunc = expr.Function("$patcher_value_getter", getExprValue, new(func(BoolValuer) bool), new(func(IntValuer) int), new(func(Int8Valuer) int8), From 3072b8525e3c014d4581fcdac0ac75f3f2e7db10 Mon Sep 17 00:00:00 2001 From: Ryan Bullock Date: Mon, 4 Dec 2023 11:46:20 -0800 Subject: [PATCH 05/12] Move to expr-lang --- patchers/value/bench_test.go | 4 ++-- patchers/value/value.go | 10 +++++----- patchers/value/value_test.go | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/patchers/value/bench_test.go b/patchers/value/bench_test.go index 1a3e1a86..8620afa0 100644 --- a/patchers/value/bench_test.go +++ b/patchers/value/bench_test.go @@ -5,8 +5,8 @@ import ( "github.com/stretchr/testify/require" - "github.com/antonmedv/expr" - "github.com/antonmedv/expr/vm" + "github.com/expr-lang/expr" + "github.com/expr-lang/expr/vm" ) func Benchmark_valueAdd(b *testing.B) { diff --git a/patchers/value/value.go b/patchers/value/value.go index 286f2524..ae27779f 100644 --- a/patchers/value/value.go +++ b/patchers/value/value.go @@ -4,8 +4,8 @@ // // import ( // "fmt" -// "github.com/antonmedv/expr/patchers/value" -// "github.com/antonmedv/expr" +// "github.com/expr-lang/expr/patchers/value" +// "github.com/expr-lang/expr" // ) // // type customInt struct { @@ -47,9 +47,9 @@ import ( "reflect" "time" - "github.com/antonmedv/expr" - "github.com/antonmedv/expr/ast" - "github.com/antonmedv/expr/conf" + "github.com/expr-lang/expr" + "github.com/expr-lang/expr/ast" + "github.com/expr-lang/expr/conf" ) // Patcher is an expr.Option that both patches the program and adds the `$patcher_value_getter` function. diff --git a/patchers/value/value_test.go b/patchers/value/value_test.go index beafce4e..6a2013bd 100644 --- a/patchers/value/value_test.go +++ b/patchers/value/value_test.go @@ -5,8 +5,8 @@ import ( "github.com/stretchr/testify/require" - "github.com/antonmedv/expr" - "github.com/antonmedv/expr/vm" + "github.com/expr-lang/expr" + "github.com/expr-lang/expr/vm" ) type customInt struct { From c76c25f1468b8086510b852cd83added78749f7f Mon Sep 17 00:00:00 2001 From: Ryan Bullock Date: Mon, 4 Dec 2023 11:51:12 -0800 Subject: [PATCH 06/12] Rename Patcher to ValueGetter --- patchers/value/bench_test.go | 6 +++--- patchers/value/value.go | 2 +- patchers/value/value_test.go | 14 +++++++------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/patchers/value/bench_test.go b/patchers/value/bench_test.go index 8620afa0..d6a1aae8 100644 --- a/patchers/value/bench_test.go +++ b/patchers/value/bench_test.go @@ -14,7 +14,7 @@ func Benchmark_valueAdd(b *testing.B) { env["ValueOne"] = &customInt{1} env["ValueTwo"] = &customInt{2} - program, err := expr.Compile("ValueOne + ValueTwo", expr.Env(env), Patcher) + program, err := expr.Compile("ValueOne + ValueTwo", expr.Env(env), ValueGetter) require.NoError(b, err) var out any @@ -35,7 +35,7 @@ func Benchmark_valueUntypedAdd(b *testing.B) { env["ValueOne"] = &customUntypedInt{1} env["ValueTwo"] = &customUntypedInt{2} - program, err := expr.Compile("ValueOne + ValueTwo", expr.Env(env), Patcher) + program, err := expr.Compile("ValueOne + ValueTwo", expr.Env(env), ValueGetter) require.NoError(b, err) var out any @@ -56,7 +56,7 @@ func Benchmark_valueTypedAdd(b *testing.B) { env["ValueOne"] = &customTypedInt{1} env["ValueTwo"] = &customTypedInt{2} - program, err := expr.Compile("ValueOne + ValueTwo", expr.Env(env), Patcher) + program, err := expr.Compile("ValueOne + ValueTwo", expr.Env(env), ValueGetter) require.NoError(b, err) var out any diff --git a/patchers/value/value.go b/patchers/value/value.go index ae27779f..e8302699 100644 --- a/patchers/value/value.go +++ b/patchers/value/value.go @@ -54,7 +54,7 @@ import ( // Patcher is an expr.Option that both patches the program and adds the `$patcher_value_getter` function. // Use it directly as an Option to expr.Compile() -var Patcher = func() expr.Option { +var ValueGetter = func() expr.Option { vPatcher := patcher{} return func(c *conf.Config) { c.Visitors = append(c.Visitors, vPatcher) diff --git a/patchers/value/value_test.go b/patchers/value/value_test.go index 6a2013bd..56f78368 100644 --- a/patchers/value/value_test.go +++ b/patchers/value/value_test.go @@ -86,7 +86,7 @@ func Test_valueAddInt(t *testing.T) { env["ValueOne"] = &customInt{1} env["ValueTwo"] = &customInt{2} - program, err := expr.Compile("ValueOne + ValueTwo", expr.Env(env), Patcher) + program, err := expr.Compile("ValueOne + ValueTwo", expr.Env(env), ValueGetter) require.NoError(t, err) out, err := vm.Run(program, env) @@ -100,7 +100,7 @@ func Test_valueUntypedAddInt(t *testing.T) { env["ValueOne"] = &customUntypedInt{1} env["ValueTwo"] = &customUntypedInt{2} - program, err := expr.Compile("ValueOne + ValueTwo", expr.Env(env), Patcher) + program, err := expr.Compile("ValueOne + ValueTwo", expr.Env(env), ValueGetter) require.NoError(t, err) out, err := vm.Run(program, env) @@ -114,7 +114,7 @@ func Test_valueTypedAddInt(t *testing.T) { env["ValueOne"] = &customTypedInt{1} env["ValueTwo"] = &customTypedInt{2} - program, err := expr.Compile("ValueOne + ValueTwo", expr.Env(env), Patcher) + program, err := expr.Compile("ValueOne + ValueTwo", expr.Env(env), ValueGetter) require.NoError(t, err) out, err := vm.Run(program, env) @@ -128,7 +128,7 @@ func Test_valueTypedAddMismatch(t *testing.T) { env["ValueOne"] = &customTypedInt{1} env["ValueTwo"] = &customTypedString{"test"} - _, err := expr.Compile("ValueOne + ValueTwo", expr.Env(env), Patcher) + _, err := expr.Compile("ValueOne + ValueTwo", expr.Env(env), ValueGetter) require.Error(t, err) } @@ -137,7 +137,7 @@ func Test_valueUntypedAddMismatch(t *testing.T) { env["ValueOne"] = &customUntypedInt{1} env["ValueTwo"] = &customUntypedString{"test"} - program, err := expr.Compile("ValueOne + ValueTwo", expr.Env(env), Patcher) + program, err := expr.Compile("ValueOne + ValueTwo", expr.Env(env), ValueGetter) require.NoError(t, err) _, err = vm.Run(program, env) @@ -149,7 +149,7 @@ func Test_valueTypedArray(t *testing.T) { env := make(map[string]any) env["ValueOne"] = &customTypedArray{[]any{1, 2}} - program, err := expr.Compile("ValueOne[0] + ValueOne[1]", expr.Env(env), Patcher) + program, err := expr.Compile("ValueOne[0] + ValueOne[1]", expr.Env(env), ValueGetter) require.NoError(t, err) out, err := vm.Run(program, env) @@ -162,7 +162,7 @@ func Test_valueTypedMap(t *testing.T) { env := make(map[string]any) env["ValueOne"] = &customTypedMap{map[string]any{"one": 1, "two": 2}} - program, err := expr.Compile("ValueOne.one + ValueOne.two", expr.Env(env), Patcher) + program, err := expr.Compile("ValueOne.one + ValueOne.two", expr.Env(env), ValueGetter) require.NoError(t, err) out, err := vm.Run(program, env) From 80fefcd0b53f2a9aff921b9093b22a5467446fec Mon Sep 17 00:00:00 2001 From: Ryan Bullock Date: Mon, 4 Dec 2023 12:01:26 -0800 Subject: [PATCH 07/12] Remove use of Expr in naming --- patchers/value/value.go | 22 +++++++++++----------- patchers/value/value_test.go | 8 ++++---- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/patchers/value/value.go b/patchers/value/value.go index e8302699..b5eecd8e 100644 --- a/patchers/value/value.go +++ b/patchers/value/value.go @@ -18,7 +18,7 @@ // } // // // Lets us return nil if we need to -// func (v *customInt) ExprValue() any { +// func (v *customInt) AnyValue() any { // return v.Int // } // @@ -62,13 +62,13 @@ var ValueGetter = func() expr.Option { } }() -// A ExprValuer provides a generic function for a custom type to return standard go values. +// A AnyValuer provides a generic function for a custom type to return standard go values. // It allows for returning a `nil` value but does not provide any type checking at expression compile. // -// A custom type may implement both ExprValuer and a type specific interface to enable both +// A custom type may implement both AnyValuer and a type specific interface to enable both // compile time checking and the ability to return a `nil` value. -type ExprValuer interface { - ExprValue() any +type AnyValuer interface { + AnyValue() any } type IntValuer interface { @@ -144,7 +144,7 @@ type MapValuer interface { } var supportedInterfaces = []reflect.Type{ - reflect.TypeOf((*ExprValuer)(nil)).Elem(), + reflect.TypeOf((*AnyValuer)(nil)).Elem(), reflect.TypeOf((*BoolValuer)(nil)).Elem(), reflect.TypeOf((*IntValuer)(nil)).Elem(), reflect.TypeOf((*Int8Valuer)(nil)).Elem(), @@ -188,13 +188,13 @@ func (patcher) Visit(node *ast.Node) { } func (patcher) ApplyOptions(c *conf.Config) { - getExprValueFunc(c) + getValueFunc(c) } -func getExprValue(params ...any) (any, error) { +func getValue(params ...any) (any, error) { switch v := params[0].(type) { - case ExprValuer: - return v.ExprValue(), nil + case AnyValuer: + return v.AnyValue(), nil case BoolValuer: return v.BoolValue(), nil case IntValuer: @@ -236,7 +236,7 @@ func getExprValue(params ...any) (any, error) { return params[0], nil } -var getExprValueFunc = expr.Function("$patcher_value_getter", getExprValue, +var getValueFunc = expr.Function("$patcher_value_getter", getValue, new(func(BoolValuer) bool), new(func(IntValuer) int), new(func(Int8Valuer) int8), diff --git a/patchers/value/value_test.go b/patchers/value/value_test.go index 56f78368..e9601e03 100644 --- a/patchers/value/value_test.go +++ b/patchers/value/value_test.go @@ -17,7 +17,7 @@ func (v *customInt) IntValue() int { return v.Int } -func (v *customInt) ExprValue() any { +func (v *customInt) AnyValue() any { return v.Int } @@ -33,7 +33,7 @@ type customUntypedInt struct { Int int } -func (v *customUntypedInt) ExprValue() any { +func (v *customUntypedInt) AnyValue() any { return v.Int } @@ -45,7 +45,7 @@ func (v *customString) StringValue() string { return v.String } -func (v *customString) ExprValue() any { +func (v *customString) AnyValue() any { return v.String } @@ -61,7 +61,7 @@ type customUntypedString struct { String string } -func (v *customUntypedString) ExprValue() any { +func (v *customUntypedString) AnyValue() any { return v.String } From a7e4934bf2f579623ebe06f49c2139a716e3697e Mon Sep 17 00:00:00 2001 From: Ryan Bullock Date: Tue, 5 Dec 2023 10:59:45 -0800 Subject: [PATCH 08/12] Rename interface functions to use 'AsType()' convention --- patchers/value/value.go | 80 ++++++++++++++++++------------------ patchers/value/value_test.go | 20 ++++----- 2 files changed, 50 insertions(+), 50 deletions(-) diff --git a/patchers/value/value.go b/patchers/value/value.go index b5eecd8e..ff6c0bd3 100644 --- a/patchers/value/value.go +++ b/patchers/value/value.go @@ -13,12 +13,12 @@ // } // // // Provides type checking at compile time -// func (v *customInt) IntValue() int { +// func (v *customInt) AsInt() int { // return v.Int // } // // // Lets us return nil if we need to -// func (v *customInt) AnyValue() any { +// func (v *customInt) AsAny() any { // return v.Int // } // @@ -68,79 +68,79 @@ var ValueGetter = func() expr.Option { // A custom type may implement both AnyValuer and a type specific interface to enable both // compile time checking and the ability to return a `nil` value. type AnyValuer interface { - AnyValue() any + AsAny() any } type IntValuer interface { - IntValue() int + AsInt() int } type BoolValuer interface { - BoolValue() bool + AsBool() bool } type Int8Valuer interface { - Int8Value() int8 + AsInt8() int8 } type Int16Valuer interface { - Int16Value() int16 + AsInt16() int16 } type Int32Valuer interface { - Int32Value() int32 + AsInt32() int32 } type Int64Valuer interface { - Int64Value() int64 + AsInt64() int64 } type UintValuer interface { - UintValue() uint + AsUint() uint } type Uint8Valuer interface { - Uint8Value() uint8 + AsUint8() uint8 } type Uint16Valuer interface { - Uint16Value() uint16 + AsUint16() uint16 } type Uint32Valuer interface { - Uint32Value() uint32 + AsUint32() uint32 } type Uint64Valuer interface { - Uint64Value() uint64 + AsUint64() uint64 } type Float32Valuer interface { - Float32Value() float32 + AsFloat32() float32 } type Float64Valuer interface { - Float64Value() float64 + AsFloat64() float64 } type StringValuer interface { - StringValue() string + AsString() string } type TimeValuer interface { - TimeValue() time.Time + AsTime() time.Time } type DurationValuer interface { - DurationValue() time.Duration + AsDuration() time.Duration } type ArrayValuer interface { - ArrayValue() []any + AsArray() []any } type MapValuer interface { - MapValue() map[string]any + AsMap() map[string]any } var supportedInterfaces = []reflect.Type{ @@ -194,43 +194,43 @@ func (patcher) ApplyOptions(c *conf.Config) { func getValue(params ...any) (any, error) { switch v := params[0].(type) { case AnyValuer: - return v.AnyValue(), nil + return v.AsAny(), nil case BoolValuer: - return v.BoolValue(), nil + return v.AsBool(), nil case IntValuer: - return v.IntValue(), nil + return v.AsInt(), nil case Int8Valuer: - return v.Int8Value(), nil + return v.AsInt8(), nil case Int16Valuer: - return v.Int16Value(), nil + return v.AsInt16(), nil case Int32Valuer: - return v.Int32Value(), nil + return v.AsInt32(), nil case Int64Valuer: - return v.Int64Value(), nil + return v.AsInt64(), nil case UintValuer: - return v.UintValue(), nil + return v.AsUint(), nil case Uint8Valuer: - return v.Uint8Value(), nil + return v.AsUint8(), nil case Uint16Valuer: - return v.Uint16Value(), nil + return v.AsUint16(), nil case Uint32Valuer: - return v.Uint32Value(), nil + return v.AsUint32(), nil case Uint64Valuer: - return v.Uint64Value(), nil + return v.AsUint64(), nil case Float32Valuer: - return v.Float32Value(), nil + return v.AsFloat32(), nil case Float64Valuer: - return v.Float64Value(), nil + return v.AsFloat64(), nil case StringValuer: - return v.StringValue(), nil + return v.AsString(), nil case TimeValuer: - return v.TimeValue(), nil + return v.AsTime(), nil case DurationValuer: - return v.DurationValue(), nil + return v.AsDuration(), nil case ArrayValuer: - return v.ArrayValue(), nil + return v.AsArray(), nil case MapValuer: - return v.MapValue(), nil + return v.AsMap(), nil } return params[0], nil diff --git a/patchers/value/value_test.go b/patchers/value/value_test.go index e9601e03..1b6c5527 100644 --- a/patchers/value/value_test.go +++ b/patchers/value/value_test.go @@ -13,11 +13,11 @@ type customInt struct { Int int } -func (v *customInt) IntValue() int { +func (v *customInt) AsInt() int { return v.Int } -func (v *customInt) AnyValue() any { +func (v *customInt) AsAny() any { return v.Int } @@ -25,7 +25,7 @@ type customTypedInt struct { Int int } -func (v *customTypedInt) IntValue() int { +func (v *customTypedInt) AsInt() int { return v.Int } @@ -33,7 +33,7 @@ type customUntypedInt struct { Int int } -func (v *customUntypedInt) AnyValue() any { +func (v *customUntypedInt) AsAny() any { return v.Int } @@ -41,11 +41,11 @@ type customString struct { String string } -func (v *customString) StringValue() string { +func (v *customString) AsString() string { return v.String } -func (v *customString) AnyValue() any { +func (v *customString) AsAny() any { return v.String } @@ -53,7 +53,7 @@ type customTypedString struct { String string } -func (v *customTypedString) StringValue() string { +func (v *customTypedString) AsString() string { return v.String } @@ -61,7 +61,7 @@ type customUntypedString struct { String string } -func (v *customUntypedString) AnyValue() any { +func (v *customUntypedString) AsAny() any { return v.String } @@ -69,7 +69,7 @@ type customTypedArray struct { Array []any } -func (v *customTypedArray) ArrayValue() []any { +func (v *customTypedArray) AsArray() []any { return v.Array } @@ -77,7 +77,7 @@ type customTypedMap struct { Map map[string]any } -func (v *customTypedMap) MapValue() map[string]any { +func (v *customTypedMap) AsMap() map[string]any { return v.Map } From 6be8f0ee17c6316b67fafaba4a628cdd5853106a Mon Sep 17 00:00:00 2001 From: Ryan Bullock Date: Thu, 7 Dec 2023 09:51:23 -0800 Subject: [PATCH 09/12] Document motivation and an example use case --- patchers/value/value.go | 54 +++++++++-------------------------------- 1 file changed, 11 insertions(+), 43 deletions(-) diff --git a/patchers/value/value.go b/patchers/value/value.go index ff6c0bd3..b7e4a767 100644 --- a/patchers/value/value.go +++ b/patchers/value/value.go @@ -1,46 +1,4 @@ // Package value provides a Patcher that uses interfaces to allow custom types that can be represented as standard go values to be used more easily in expressions. -// -// # Example Usage -// -// import ( -// "fmt" -// "github.com/expr-lang/expr/patchers/value" -// "github.com/expr-lang/expr" -// ) -// -// type customInt struct { -// Int int -// } -// -// // Provides type checking at compile time -// func (v *customInt) AsInt() int { -// return v.Int -// } -// -// // Lets us return nil if we need to -// func (v *customInt) AsAny() any { -// return v.Int -// } -// -// func main() { -// env := make(map[string]any) -// env["ValueOne"] = &customInt{1} -// env["ValueTwo"] = &customInt{2} -// -// program, err := expr.Compile("ValueOne + ValueTwo", expr.Env(env), value.Patcher) -// -// if err != nil { -// panic(err) -// } -// -// out, err := vm.Run(program, env) -// -// if err != nil { -// panic(err) -// } -// -// fmt.Printf("Got %v", out) -// } package value import ( @@ -52,7 +10,17 @@ import ( "github.com/expr-lang/expr/conf" ) -// Patcher is an expr.Option that both patches the program and adds the `$patcher_value_getter` function. +// ValueGetter is a Patcher that allows custom types to be represented as standard go values for use with expr. +// It also adds the `$patcher_value_getter` function to the program for efficiently calling matching interfaces. +// +// The purpose of this Patcher is to make it seemless to use custom types in expressions without the need to +// first convert them to standard go values. It may also facilitate using already existing structs or maps as +// environments when they contain compatabile types. +// +// An example usage may be modeling a database record with columns that have varying data types and constraints. +// In such an example you may have custom types that, beyond storing a simple value, such as an integer, may +// contain metadata such as column type and if a value is specifically a NULL value. +// // Use it directly as an Option to expr.Compile() var ValueGetter = func() expr.Option { vPatcher := patcher{} From 9cbc77faa2b75e520996560b6ff81e7cf460be7d Mon Sep 17 00:00:00 2001 From: Ryan Bullock Date: Thu, 7 Dec 2023 09:52:09 -0800 Subject: [PATCH 10/12] Add example usage to AsAny() --- patchers/value/value_example_test.go | 42 ++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 patchers/value/value_example_test.go diff --git a/patchers/value/value_example_test.go b/patchers/value/value_example_test.go new file mode 100644 index 00000000..a2a2a19a --- /dev/null +++ b/patchers/value/value_example_test.go @@ -0,0 +1,42 @@ +package value_test + +import ( + "fmt" + + "github.com/expr-lang/expr" + "github.com/expr-lang/expr/patchers/value" + "github.com/expr-lang/expr/vm" +) + +type myInt struct { + Int int +} + +func (v *myInt) AsInt() int { + return v.Int +} + +func (v *myInt) AsAny() any { + return v.Int +} + +func ExampleAnyValuer() { + env := make(map[string]any) + env["ValueOne"] = &myInt{1} + env["ValueTwo"] = &myInt{2} + + program, err := expr.Compile("ValueOne + ValueTwo", expr.Env(env), value.ValueGetter) + + if err != nil { + panic(err) + } + + out, err := vm.Run(program, env) + + if err != nil { + panic(err) + } + + fmt.Println(out) + // Output: 3 +} From 7b46d16bf755ac3c92dc76089bc528ad9012e7fa Mon Sep 17 00:00:00 2001 From: Ryan Bullock Date: Wed, 13 Dec 2023 15:53:34 -0800 Subject: [PATCH 11/12] Move under patcher directory --- {patchers => patcher}/value/bench_test.go | 0 {patchers => patcher}/value/value.go | 0 {patchers => patcher}/value/value_example_test.go | 0 {patchers => patcher}/value/value_test.go | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename {patchers => patcher}/value/bench_test.go (100%) rename {patchers => patcher}/value/value.go (100%) rename {patchers => patcher}/value/value_example_test.go (100%) rename {patchers => patcher}/value/value_test.go (100%) diff --git a/patchers/value/bench_test.go b/patcher/value/bench_test.go similarity index 100% rename from patchers/value/bench_test.go rename to patcher/value/bench_test.go diff --git a/patchers/value/value.go b/patcher/value/value.go similarity index 100% rename from patchers/value/value.go rename to patcher/value/value.go diff --git a/patchers/value/value_example_test.go b/patcher/value/value_example_test.go similarity index 100% rename from patchers/value/value_example_test.go rename to patcher/value/value_example_test.go diff --git a/patchers/value/value_test.go b/patcher/value/value_test.go similarity index 100% rename from patchers/value/value_test.go rename to patcher/value/value_test.go From c3babeb996bdefbea6f56bd6ae8b87f8b91ec58d Mon Sep 17 00:00:00 2001 From: Ryan Bullock Date: Wed, 13 Dec 2023 15:55:18 -0800 Subject: [PATCH 12/12] Fix reference to old import path --- patcher/value/value_example_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/patcher/value/value_example_test.go b/patcher/value/value_example_test.go index a2a2a19a..2da1739b 100644 --- a/patcher/value/value_example_test.go +++ b/patcher/value/value_example_test.go @@ -4,7 +4,7 @@ import ( "fmt" "github.com/expr-lang/expr" - "github.com/expr-lang/expr/patchers/value" + "github.com/expr-lang/expr/patcher/value" "github.com/expr-lang/expr/vm" )