Skip to content

Commit 2a97919

Browse files
authored
[compile] add stateless operators config (#22)
1 parent 19f42ba commit 2a97919

File tree

8 files changed

+197
-105
lines changed

8 files changed

+197
-105
lines changed

compile.go

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ func CopyCompileConfig(origin *CompileConfig) *CompileConfig {
5353
for k, v := range origin.CostsMap {
5454
conf.CostsMap[k] = v
5555
}
56+
for _, op := range origin.StatelessOperators {
57+
conf.StatelessOperators = append(conf.StatelessOperators, op)
58+
}
5659
return conf
5760
}
5861

@@ -103,11 +106,12 @@ var (
103106

104107
func NewCompileConfig(opts ...CompileOption) *CompileConfig {
105108
conf := &CompileConfig{
106-
ConstantMap: make(map[string]Value),
107-
SelectorMap: make(map[string]SelectorKey),
108-
OperatorMap: make(map[string]Operator),
109-
CompileOptions: make(map[Option]bool),
110-
CostsMap: make(map[string]int),
109+
ConstantMap: make(map[string]Value),
110+
SelectorMap: make(map[string]SelectorKey),
111+
OperatorMap: make(map[string]Operator),
112+
CompileOptions: make(map[Option]bool),
113+
CostsMap: make(map[string]int),
114+
StatelessOperators: []string{},
111115
}
112116
for _, opt := range opts {
113117
opt(conf)
@@ -125,6 +129,8 @@ type CompileConfig struct {
125129

126130
// compile options
127131
CompileOptions map[Option]bool
132+
133+
StatelessOperators []string
128134
}
129135

130136
func (cc *CompileConfig) getCosts(nodeType uint8, nodeName string) int {
@@ -383,21 +389,27 @@ func isStatelessOp(c *CompileConfig, n *node) (bool, Operator) {
383389
return false, nil
384390
}
385391

386-
s, ok := n.value.(string)
392+
op, ok := n.value.(string)
387393
if !ok {
388394
return false, nil
389395
}
390396

391-
// by default, we only do constant folding on builtin operators
392-
if _, exist := c.OperatorMap[s]; exist {
393-
return false, nil
397+
// builtinOperators are all stateless functions
398+
fn, exist := builtinOperators[op]
399+
if exist {
400+
return true, fn
394401
}
395402

396-
fn, exist := builtinOperators[s] // should be stateless function
397-
if !exist {
398-
return false, nil
403+
for _, so := range c.StatelessOperators {
404+
if so == op {
405+
if fn = c.OperatorMap[op]; fn != nil {
406+
return true, fn
407+
}
408+
break
409+
}
399410
}
400-
return true, fn
411+
412+
return false, fn
401413
}
402414

403415
func optimizeFastEvaluation(cc *CompileConfig, root *astNode) {

compile_test.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,15 @@ func TestCopyCompileConfig(t *testing.T) {
1717
assertNotNil(t, res.ConstantMap)
1818
assertNotNil(t, res.SelectorMap)
1919
assertNotNil(t, res.CompileOptions)
20+
assertNotNil(t, res.StatelessOperators)
2021

2122
res = CopyCompileConfig(&CompileConfig{})
2223
assertNotNil(t, res)
2324
assertNotNil(t, res.OperatorMap)
2425
assertNotNil(t, res.ConstantMap)
2526
assertNotNil(t, res.SelectorMap)
2627
assertNotNil(t, res.CompileOptions)
28+
assertNotNil(t, res.StatelessOperators)
2729

2830
cc := &CompileConfig{
2931
ConstantMap: map[string]Value{
@@ -60,6 +62,49 @@ func TestCopyCompileConfig(t *testing.T) {
6062
age := int64(time.Now().Sub(birthTime) / timeYear)
6163
return age < 18, nil
6264
},
65+
"max": func(_ *Ctx, param []Value) (Value, error) {
66+
const op = "max"
67+
if len(param) < 2 {
68+
return nil, ParamsCountError(op, 2, len(param))
69+
}
70+
71+
var m int64
72+
for i, p := range param {
73+
v, ok := p.(int64)
74+
if !ok {
75+
return nil, ParamTypeError(op, typeInt, p)
76+
}
77+
if i == 0 {
78+
m = v
79+
} else {
80+
if v > m {
81+
m = v
82+
}
83+
}
84+
}
85+
return m, nil
86+
},
87+
"to_set": func(_ *Ctx, params []Value) (Value, error) {
88+
if len(params) != 1 {
89+
return nil, ParamsCountError("to_set", 1, len(params))
90+
}
91+
switch list := params[0].(type) {
92+
case []int64:
93+
set := make(map[int64]struct{}, len(list))
94+
for _, i := range list {
95+
set[i] = empty
96+
}
97+
return set, nil
98+
case []string:
99+
set := make(map[string]struct{}, len(list))
100+
for _, s := range list {
101+
set[s] = empty
102+
}
103+
return set, nil
104+
default:
105+
return nil, ParamTypeError("to_set", "slice", list)
106+
}
107+
},
63108
},
64109
CostsMap: map[string]int{
65110
"selector": 10,
@@ -69,13 +114,17 @@ func TestCopyCompileConfig(t *testing.T) {
69114
Reordering: true,
70115
ConstantFolding: false,
71116
},
117+
// max & to_set are both stateless operators
118+
// but is_child is not, because it varies with time
119+
StatelessOperators: []string{"max", "to_set"},
72120
}
73121

74122
res = CopyCompileConfig(cc)
75123
assertEquals(t, res.ConstantMap, cc.ConstantMap)
76124
assertEquals(t, res.SelectorMap, cc.SelectorMap)
77125
assertEquals(t, res.CompileOptions, cc.CompileOptions)
78126
assertEquals(t, res.CostsMap, cc.CostsMap)
127+
assertEquals(t, res.StatelessOperators, cc.StatelessOperators)
79128

80129
assertEquals(t, len(res.OperatorMap), len(cc.OperatorMap))
81130
for s := range cc.OperatorMap {

engine.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ const (
6161
type Event struct {
6262
CurtIdx int16
6363
EventType EventType
64-
NodeValue interface{}
64+
NodeValue Value
6565
Stack []Value
6666
Data interface{}
6767
}

engine_test.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -780,6 +780,18 @@ func TestExpr_TryEval(t *testing.T) {
780780
"F": false,
781781
},
782782
},
783+
{
784+
want: false,
785+
optimizeLevel: disable,
786+
s: `
787+
(and F
788+
(= 0 0)
789+
(!= 0 0))`,
790+
valMap: map[string]interface{}{
791+
"F": false,
792+
"T": true,
793+
},
794+
},
783795
{
784796
want: true,
785797
optimizeLevel: disable,
@@ -1292,6 +1304,72 @@ func TestReportEvent(t *testing.T) {
12921304
assertEquals(t, events, []Value{int64(1), int64(2), "+"})
12931305
}
12941306

1307+
func TestStatelessOperators(t *testing.T) {
1308+
cc := &CompileConfig{
1309+
OperatorMap: map[string]Operator{
1310+
"to_set": func(_ *Ctx, params []Value) (Value, error) {
1311+
if len(params) != 1 {
1312+
return nil, ParamsCountError("to_set", 1, len(params))
1313+
}
1314+
switch list := params[0].(type) {
1315+
case []int64:
1316+
set := make(map[int64]struct{}, len(list))
1317+
for _, i := range list {
1318+
set[i] = empty
1319+
}
1320+
return set, nil
1321+
case []string:
1322+
set := make(map[string]struct{}, len(list))
1323+
for _, s := range list {
1324+
set[s] = empty
1325+
}
1326+
return set, nil
1327+
default:
1328+
return nil, ParamTypeError("to_set", "list", list)
1329+
}
1330+
},
1331+
},
1332+
SelectorMap: map[string]SelectorKey{
1333+
"num": SelectorKey(1),
1334+
},
1335+
StatelessOperators: []string{"to_set"},
1336+
}
1337+
1338+
s := `
1339+
(in
1340+
num
1341+
(to_set
1342+
(2 3 5 7 11 13 17 19 23 29 31 37 41
1343+
43 47 53 59 61 67 71 73 79 83 89 97
1344+
101 103 107 109 113 127 131 137 139
1345+
149 151 157 163 167 173 179 181 191
1346+
193 197 199 211 223 227 229 233 239
1347+
241 251 257 263 269 271 277 281 283
1348+
293 307 311 313 317 331 337 347 349
1349+
353 359 367 373 379 383 389 397 401
1350+
409 419 421 431 433 439 443 449 457
1351+
461 463 467 479 487 491 499 503 509
1352+
521 523 541 547 557 563 569 571 577
1353+
587 593 599 601 607 613 617 619 631
1354+
641 643 647 653 659 661 673 677 683
1355+
691 701 709 719 727 733 739 743 751
1356+
757 761 769 773 787 797 809 811 821
1357+
823 827 829 839 853 857 859 863 877
1358+
881 883 887 907 911 919 929 937 941
1359+
947 953 967 971 977 983 991 997)))
1360+
`
1361+
1362+
expr, err := Compile(cc, s)
1363+
assertNil(t, err)
1364+
res, err := expr.EvalBool(NewCtxWithMap(cc, map[string]interface{}{
1365+
"num": 499,
1366+
}))
1367+
1368+
assertNil(t, err)
1369+
assertEquals(t, res, true)
1370+
assertEquals(t, len(expr.nodes), 3)
1371+
}
1372+
12951373
func assertEquals(t *testing.T, got, want any, msg ...any) {
12961374
if !reflect.DeepEqual(got, want) {
12971375
t.Fatalf("assertEquals failed, got: %+v, want: %+v, msg: %+v", got, want, msg)

example_test.go

Lines changed: 0 additions & 75 deletions
This file was deleted.

operator.go

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -348,29 +348,36 @@ func listIn(_ *Ctx, params []Value) (Value, error) {
348348
}
349349
switch v := params[0].(type) {
350350
case string:
351-
list, ok := params[1].([]string)
352-
if !ok {
353-
return nil, ParamTypeError(op, typeStrList, params[1])
354-
}
355-
for _, s := range list {
356-
if s == v {
357-
return true, nil
351+
switch coll := params[1].(type) {
352+
case []string:
353+
for _, i := range coll {
354+
if i == v {
355+
return true, nil
356+
}
358357
}
358+
return false, nil
359+
case map[string]struct{}:
360+
_, exist := coll[v]
361+
return exist, nil
362+
default:
363+
return nil, ParamTypeError(op, typeStrList, params[1])
359364
}
360-
return false, nil
361365
case int64:
362-
switch list := params[1].(type) {
366+
switch coll := params[1].(type) {
363367
case []int64:
364-
for _, i := range list {
368+
for _, i := range coll {
365369
if i == v {
366370
return true, nil
367371
}
368372
}
369373
return false, nil
370374
case []string: // the empty list is parsed to a string list
371-
if len(list) == 0 {
375+
if len(coll) == 0 {
372376
return false, nil
373377
}
378+
case map[int64]struct{}:
379+
_, exist := coll[v]
380+
return exist, nil
374381
}
375382
return nil, ParamTypeError(op, typeIntList, params[1])
376383
}

0 commit comments

Comments
 (0)