Skip to content

Commit 0cf2376

Browse files
committed
Merge branch 'master' into sum-predicate
2 parents 0ef7dc5 + d66ffcd commit 0cf2376

22 files changed

+941
-93
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ func main() {
162162
* [Visually.io](https://visually.io) employs Expr as a business rule engine for its personalization targeting algorithm.
163163
* [Akvorado](https://github.com/akvorado/akvorado) utilizes Expr to classify exporters and interfaces in network flows.
164164
* [keda.sh](https://keda.sh) uses Expr to allow customization of its Kubernetes-based event-driven autoscaling.
165+
* [Span Digital](https://spandigital.com/) uses Expr in it's Knowledge Management products.
165166

166167
[Add your company too](https://github.com/expr-lang/expr/edit/master/README.md)
167168

ast/print.go

+7-1
Original file line numberDiff line numberDiff line change
@@ -202,5 +202,11 @@ func (n *MapNode) String() string {
202202
}
203203

204204
func (n *PairNode) String() string {
205-
return fmt.Sprintf("%s: %s", n.Key.String(), n.Value.String())
205+
if str, ok := n.Key.(*StringNode); ok {
206+
if utils.IsValidIdentifier(str.Value) {
207+
return fmt.Sprintf("%s: %s", str.Value, n.Value.String())
208+
}
209+
return fmt.Sprintf("%q: %s", str.String(), n.Value.String())
210+
}
211+
return fmt.Sprintf("(%s): %s", n.Key.String(), n.Value.String())
206212
}

ast/print_test.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,8 @@ func TestPrint(t *testing.T) {
5555
{`func(a)`, `func(a)`},
5656
{`func(a, b)`, `func(a, b)`},
5757
{`{}`, `{}`},
58-
{`{a: b}`, `{"a": b}`},
59-
{`{a: b, c: d}`, `{"a": b, "c": d}`},
58+
{`{a: b}`, `{a: b}`},
59+
{`{a: b, c: d}`, `{a: b, c: d}`},
6060
{`[]`, `[]`},
6161
{`[a]`, `[a]`},
6262
{`[a, b]`, `[a, b]`},
@@ -71,6 +71,7 @@ func TestPrint(t *testing.T) {
7171
{`a[1:]`, `a[1:]`},
7272
{`a[:]`, `a[:]`},
7373
{`(nil ?? 1) > 0`, `(nil ?? 1) > 0`},
74+
{`{("a" + "b"): 42}`, `{("a" + "b"): 42}`},
7475
}
7576

7677
for _, tt := range tests {

builtin/builtin_test.go

+14
Original file line numberDiff line numberDiff line change
@@ -608,3 +608,17 @@ func TestBuiltin_bitOpsFunc(t *testing.T) {
608608
})
609609
}
610610
}
611+
612+
type customInt int
613+
614+
func Test_int_unwraps_underlying_value(t *testing.T) {
615+
env := map[string]any{
616+
"customInt": customInt(42),
617+
}
618+
program, err := expr.Compile(`int(customInt) == 42`, expr.Env(env))
619+
require.NoError(t, err)
620+
621+
out, err := expr.Run(program, env)
622+
require.NoError(t, err)
623+
assert.Equal(t, true, out)
624+
}

builtin/lib.go

+4
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,10 @@ func Int(x any) any {
209209
}
210210
return i
211211
default:
212+
val := reflect.ValueOf(x)
213+
if val.CanConvert(integerType) {
214+
return val.Convert(integerType).Interface()
215+
}
212216
panic(fmt.Sprintf("invalid operation: int(%T)", x))
213217
}
214218
}

checker/checker_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -632,7 +632,7 @@ func TestCheck_TaggedFieldName(t *testing.T) {
632632
tree, err := parser.Parse(`foo.bar`)
633633
require.NoError(t, err)
634634

635-
config := &conf.Config{}
635+
config := conf.CreateNew()
636636
expr.Env(struct {
637637
x struct {
638638
y bool `expr:"bar"`

compiler/compiler.go

+24-24
Original file line numberDiff line numberDiff line change
@@ -395,34 +395,12 @@ func (c *compiler) UnaryNode(node *ast.UnaryNode) {
395395
}
396396

397397
func (c *compiler) BinaryNode(node *ast.BinaryNode) {
398-
l := kind(node.Left)
399-
r := kind(node.Right)
400-
401-
leftIsSimple := isSimpleType(node.Left)
402-
rightIsSimple := isSimpleType(node.Right)
403-
leftAndRightAreSimple := leftIsSimple && rightIsSimple
404-
405398
switch node.Operator {
406399
case "==":
407-
c.compile(node.Left)
408-
c.derefInNeeded(node.Left)
409-
c.compile(node.Right)
410-
c.derefInNeeded(node.Right)
411-
412-
if l == r && l == reflect.Int && leftAndRightAreSimple {
413-
c.emit(OpEqualInt)
414-
} else if l == r && l == reflect.String && leftAndRightAreSimple {
415-
c.emit(OpEqualString)
416-
} else {
417-
c.emit(OpEqual)
418-
}
400+
c.equalBinaryNode(node)
419401

420402
case "!=":
421-
c.compile(node.Left)
422-
c.derefInNeeded(node.Left)
423-
c.compile(node.Right)
424-
c.derefInNeeded(node.Right)
425-
c.emit(OpEqual)
403+
c.equalBinaryNode(node)
426404
c.emit(OpNot)
427405

428406
case "or", "||":
@@ -580,6 +558,28 @@ func (c *compiler) BinaryNode(node *ast.BinaryNode) {
580558
}
581559
}
582560

561+
func (c *compiler) equalBinaryNode(node *ast.BinaryNode) {
562+
l := kind(node.Left)
563+
r := kind(node.Right)
564+
565+
leftIsSimple := isSimpleType(node.Left)
566+
rightIsSimple := isSimpleType(node.Right)
567+
leftAndRightAreSimple := leftIsSimple && rightIsSimple
568+
569+
c.compile(node.Left)
570+
c.derefInNeeded(node.Left)
571+
c.compile(node.Right)
572+
c.derefInNeeded(node.Right)
573+
574+
if l == r && l == reflect.Int && leftAndRightAreSimple {
575+
c.emit(OpEqualInt)
576+
} else if l == r && l == reflect.String && leftAndRightAreSimple {
577+
c.emit(OpEqualString)
578+
} else {
579+
c.emit(OpEqual)
580+
}
581+
}
582+
583583
func isSimpleType(node ast.Node) bool {
584584
if node == nil {
585585
return false

compiler/compiler_test.go

+33
Original file line numberDiff line numberDiff line change
@@ -541,6 +541,39 @@ func TestCompile_optimizes_jumps(t *testing.T) {
541541
{vm.OpFetch, 0},
542542
},
543543
},
544+
{
545+
`-1 not in [1, 2, 5]`,
546+
[]op{
547+
{vm.OpPush, 0},
548+
{vm.OpPush, 1},
549+
{vm.OpIn, 0},
550+
{vm.OpNot, 0},
551+
},
552+
},
553+
{
554+
`1 + 8 not in [1, 2, 5]`,
555+
[]op{
556+
{vm.OpPush, 0},
557+
{vm.OpPush, 1},
558+
{vm.OpIn, 0},
559+
{vm.OpNot, 0},
560+
},
561+
},
562+
{
563+
`true ? false : 8 not in [1, 2, 5]`,
564+
[]op{
565+
{vm.OpTrue, 0},
566+
{vm.OpJumpIfFalse, 3},
567+
{vm.OpPop, 0},
568+
{vm.OpFalse, 0},
569+
{vm.OpJump, 5},
570+
{vm.OpPop, 0},
571+
{vm.OpPush, 0},
572+
{vm.OpPush, 1},
573+
{vm.OpIn, 0},
574+
{vm.OpNot, 0},
575+
},
576+
},
544577
}
545578

546579
for _, test := range tests {

conf/config.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ type Config struct {
3232
func CreateNew() *Config {
3333
c := &Config{
3434
Optimize: true,
35+
Types: make(TypesTable),
3536
ConstFns: make(map[string]reflect.Value),
3637
Functions: make(map[string]*builtin.Function),
3738
Builtins: make(map[string]*builtin.Function),
@@ -62,7 +63,10 @@ func (c *Config) WithEnv(env any) {
6263
}
6364

6465
c.Env = env
65-
c.Types = CreateTypesTable(env)
66+
types := CreateTypesTable(env)
67+
for name, t := range types {
68+
c.Types[name] = t
69+
}
6670
c.MapEnv = mapEnv
6771
c.DefaultType = mapValueType
6872
c.Strict = true

0 commit comments

Comments
 (0)