mirror of
https://github.com/hanzoai/expr.git
synced 2026-09-24 16:58:58 +00:00
* export the checker and ensure no methods or fields are exported * make Checker reusable * add PatchAndCheck method to Checker, and reuse it in visitors * avoid unnecessary map and consolidate structs * make all Nature methods use pointer-receiver * added cache for type and nature information * optimize type and nature checks * more aggressively cache some partial results and add cheap checks * cache reused values * reduce copying in Nature by breaking down optional fields * enforce use of cache * improve ergonomics of FromType and NatureOf * improve struct method fetching * make cache private to Nature * first iteration improving struct reflect caching * cleaned up struct fields data * improve caching of functions data * simplify checker code * simplify and remove dead code * add checker benchmarks * address PR comments and minor improvements * rename from SetCache to Bind * make the benchmarks more fair * fix benchmark option not being applied * move Cache to be argument instead of field * merge FuncData into Optional * rename Optional to TypeData * simplify struct logic * cache builtin numeric types --------- Co-authored-by: Anton Medvedev <[email protected]>
115 lines
3.2 KiB
Go
115 lines
3.2 KiB
Go
package checker_test
|
|
|
|
import (
|
|
"runtime"
|
|
"testing"
|
|
|
|
"github.com/expr-lang/expr"
|
|
"github.com/expr-lang/expr/ast"
|
|
"github.com/expr-lang/expr/checker"
|
|
"github.com/expr-lang/expr/checker/nature"
|
|
"github.com/expr-lang/expr/conf"
|
|
"github.com/expr-lang/expr/parser"
|
|
)
|
|
|
|
func BenchmarkChecker(b *testing.B) {
|
|
cases := []struct {
|
|
name, input string
|
|
}{
|
|
{"function calls", `
|
|
func(
|
|
func(
|
|
func(func(a, 'a', 1, nil), func(a, 'a', 1, nil), func(a, 'a', 1, nil)),
|
|
func(func(a, 'a', 1, nil), func(a, 'a', 1, nil), func(a, 'a', 1, nil)),
|
|
func(func(a, 'a', 1, nil), func(a, 'a', 1, nil), func(a, 'a', 1, nil)),
|
|
),
|
|
func(
|
|
func(func(a, 'a', 1, nil), func(a, 'a', 1, nil), func(a, 'a', 1, nil)),
|
|
func(func(a, 'a', 1, nil), func(a, 'a', 1, nil), func(a, 'a', 1, nil)),
|
|
func(func(a, 'a', 1, nil), func(a, 'a', 1, nil), func(a, 'a', 1, nil)),
|
|
),
|
|
func(
|
|
func(func(a, 'a', 1, nil), func(a, 'a', 1, nil), func(a, 'a', 1, nil)),
|
|
func(func(a, 'a', 1, nil), func(a, 'a', 1, nil), func(a, 'a', 1, nil)),
|
|
func(func(a, 'a', 1, nil), func(a, 'a', 1, nil), func(a, 'a', 1, nil)),
|
|
)
|
|
)
|
|
`},
|
|
{"unary and binary operations", `
|
|
!b && !b || !b == !b && !b != !b || 1 < 1.0 && 0.1 > 1 || 0 <= 1.0 && 0.1 >= 1 &&
|
|
!b && !b || !b == !b && !b != !b || 1 < 1.0 && 0.1 > 1 || 0 <= 1.0 && 0.1 >= 1 &&
|
|
!b && !b || !b == !b && !b != !b || 1 < 1.0 && 0.1 > 1 || 0 <= 1.0 && 0.1 >= 1 &&
|
|
!b && !b || !b == !b && !b != !b || 1 < 1.0 && 0.1 > 1 || 0 <= 1.0 && 0.1 >= 1 &&
|
|
!b && !b || !b == !b && !b != !b || 1 < 1.0 && 0.1 > 1 || 0 <= 1.0 && 0.1 >= 1 &&
|
|
!b && !b || !b == !b && !b != !b || 1 < 1.0 && 0.1 > 1 || 0 <= 1.0 && 0.1 >= 1
|
|
`},
|
|
{"deep struct access", `
|
|
a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.
|
|
a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.
|
|
a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.
|
|
a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a
|
|
`},
|
|
}
|
|
|
|
f := func(params ...any) (any, error) { return nil, nil }
|
|
env := map[string]any{
|
|
"a": new(recursive),
|
|
"b": true,
|
|
"func": f,
|
|
}
|
|
config := conf.New(env)
|
|
expr.Function("func", f, f)(config)
|
|
expr.ConstExpr("func")(config)
|
|
|
|
for _, c := range cases {
|
|
batchSize := 100_000
|
|
if batchSize > b.N {
|
|
batchSize = b.N
|
|
}
|
|
trees := make([]*parser.Tree, 0, batchSize)
|
|
for i := 0; i < batchSize; i++ {
|
|
tree, err := parser.ParseWithConfig(c.input, config)
|
|
if err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
trees = append(trees, tree)
|
|
}
|
|
runtime.GC() // try to cleanup the mess from the initialization
|
|
|
|
b.Run("name="+c.name, func(b *testing.B) {
|
|
var err error
|
|
for i := 0; i < b.N; i++ {
|
|
j := i
|
|
if j < 0 || j >= len(trees) {
|
|
b.StopTimer()
|
|
invalidateTrees(trees...)
|
|
j = 0
|
|
b.StartTimer()
|
|
}
|
|
|
|
_, err = checker.Check(trees[j], config)
|
|
}
|
|
b.StopTimer()
|
|
if err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
type visitorFunc func(*ast.Node)
|
|
|
|
func (f visitorFunc) Visit(node *ast.Node) { f(node) }
|
|
|
|
func invalidateTrees(trees ...*parser.Tree) {
|
|
for _, tree := range trees {
|
|
ast.Walk(&tree.Node, visitorFunc(func(node *ast.Node) {
|
|
(*node).SetNature(nature.Nature{})
|
|
}))
|
|
}
|
|
}
|
|
|
|
type recursive struct {
|
|
Inner *recursive `expr:"a"`
|
|
}
|