mirror of
https://github.com/hanzoai/expr.git
synced 2026-09-24 16:58:58 +00:00
Update docs
This commit is contained in:
+14
-15
@@ -6,15 +6,15 @@ Usually, the return type of expression is anything. But we can instruct type che
|
||||
expression.
|
||||
For example, in filter expressions, we expect the return type to be a boolean.
|
||||
|
||||
```go
|
||||
```go
|
||||
program, err := expr.Compile(code, expr.AsBool())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
panic(err)
|
||||
}
|
||||
|
||||
output, err := expr.Run(program, env)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
panic(err)
|
||||
}
|
||||
|
||||
ok := output.(bool) // It is safe to assert the output to bool, if the expression is type checked as bool.
|
||||
@@ -82,6 +82,9 @@ Function `expr.WithContext()` takes the name of context variable. The context va
|
||||
```go
|
||||
env := map[string]any{
|
||||
"ctx": context.Background(),
|
||||
"customFunc": func(ctx context.Context, a int) int {
|
||||
return a
|
||||
},
|
||||
}
|
||||
|
||||
program, err := expr.Compile(code, expr.Env(env), expr.WithContext("ctx"))
|
||||
@@ -116,20 +119,16 @@ fib(12+12) // will be transformed to 267914296 during the compilation
|
||||
fib(x) // will **not** be transformed and will be evaluated at runtime
|
||||
```
|
||||
|
||||
## Options
|
||||
## Timezone
|
||||
|
||||
Compiler options can be defined as an array:
|
||||
By default, the timezone is set to `time.Local`. We can change the timezone via the [`Timezone`](https://pkg.go.dev/github.com/expr-lang/expr#Timezone) option.
|
||||
|
||||
```go
|
||||
options := []expr.Option{
|
||||
expr.Env(Env{})
|
||||
expr.AsInt(),
|
||||
expr.WarnOnAny(),
|
||||
expr.WithContext("ctx"),
|
||||
expr.ConstExpr("fib"),
|
||||
}
|
||||
|
||||
program, err := expr.Compile(code, options...)
|
||||
program, err := expr.Compile(code, expr.Timezone(time.UTC))
|
||||
```
|
||||
|
||||
Full list of available options can be found in the [pkg.go.dev](https://pkg.go.dev/github.com/expr-lang/expr#Option) documentation.
|
||||
The timezone is used for the following functions:
|
||||
```expr
|
||||
date("2024-11-23 12:00:00") // parses the date in the specified timezone
|
||||
now() // returns the current time in the specified timezone
|
||||
```
|
||||
|
||||
+18
-2
@@ -84,12 +84,28 @@ is the value's type.
|
||||
env := map[string]any{
|
||||
"object": map[string]any{
|
||||
"field": 42,
|
||||
},
|
||||
},
|
||||
"struct": struct {
|
||||
Field int `expr:"field"`
|
||||
}{42},
|
||||
}
|
||||
```
|
||||
|
||||
Expr will infer the type of the `object` variable as `map[string]any`.
|
||||
Accessing fields of the `object` and `struct` variables will return the following results.
|
||||
|
||||
```expr
|
||||
object.field // 42
|
||||
object.unknown // nil (no error)
|
||||
|
||||
struct.field // 42
|
||||
struct.unknown // error (unknown field)
|
||||
|
||||
foobar // error (unknown variable)
|
||||
```
|
||||
|
||||
:::note
|
||||
The `foobar` variable is not defined in the environment.
|
||||
By default, Expr will return an error if unknown variables are used in the expression.
|
||||
|
||||
You can disable this behavior by passing [`AllowUndefinedVariables`](https://pkg.go.dev/github.com/expr-lang/expr#AllowUndefinedVariables) option to the compiler.
|
||||
:::
|
||||
|
||||
@@ -49,6 +49,7 @@ atoi := expr.Function(
|
||||
func(params ...any) (any, error) {
|
||||
return strconv.Atoi(params[0].(string))
|
||||
},
|
||||
// highlight-next-line
|
||||
new(func(string) int),
|
||||
)
|
||||
```
|
||||
@@ -80,7 +81,9 @@ toInt := expr.Function(
|
||||
}
|
||||
return nil, fmt.Errorf("invalid type")
|
||||
},
|
||||
// highlight-start
|
||||
new(func(float64) int),
|
||||
new(func(string) int),
|
||||
// highlight-end
|
||||
)
|
||||
```
|
||||
|
||||
+6
-10
@@ -72,6 +72,7 @@ env := map[string]any{
|
||||
|
||||
program, err := expr.Compile(`name + age`, expr.Env(env))
|
||||
if err != nil {
|
||||
// highlight-next-line
|
||||
panic(err) // Will panic with "invalid operation: string + int"
|
||||
}
|
||||
```
|
||||
@@ -85,9 +86,7 @@ env := map[string]any{
|
||||
"sprintf": fmt.Sprintf,
|
||||
}
|
||||
|
||||
code := `sprintf(greet, names[0])`
|
||||
|
||||
program, err := expr.Compile(code, expr.Env(env))
|
||||
program, err := expr.Compile(`sprintf(greet, names[0])`, expr.Env(env))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -100,17 +99,14 @@ if err != nil {
|
||||
fmt.Print(output) // Hello, world!
|
||||
```
|
||||
|
||||
Also, Expr can use a struct as an environment. Methods defined on the struct become functions.
|
||||
The struct fields can be renamed with the `expr` tag.
|
||||
|
||||
Here is an example:
|
||||
Also, Expr can use a struct as an environment. Here is an example:
|
||||
|
||||
```go
|
||||
type Env struct {
|
||||
Posts []Post `expr:"posts"`
|
||||
}
|
||||
|
||||
func (Env) Format(t time.Time) string {
|
||||
func (Env) Format(t time.Time) string { // Methods defined on the struct become functions.
|
||||
return t.Format(time.RFC822)
|
||||
}
|
||||
|
||||
@@ -122,7 +118,7 @@ type Post struct {
|
||||
func main() {
|
||||
code := `map(posts, Format(.Date) + ": " + .Body)`
|
||||
|
||||
program, err := expr.Compile(code, expr.Env(Env{}))
|
||||
program, err := expr.Compile(code, expr.Env(Env{})) // Pass the struct as an environment.
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -172,7 +168,7 @@ if err != nil {
|
||||
fmt.Print(output) // 7
|
||||
```
|
||||
|
||||
:::tip
|
||||
:::info Eval = Compile + Run
|
||||
For one-off expressions, you can use the `expr.Eval` function. It compiles and runs the expression in one step.
|
||||
```go
|
||||
output, err := expr.Eval(`2 + 2`, env)
|
||||
|
||||
+17
-6
@@ -1,8 +1,8 @@
|
||||
# Patch
|
||||
|
||||
Sometimes it may be necessary to modify an expression before the compilation.
|
||||
Expr provides a powerful mechanism to modify the expression using
|
||||
the [`Patch`](https://pkg.go.dev/github.com/expr-lang/expr#Patch) option.
|
||||
For example, you may want to replace a variable with a constant, transform an expression into a function call,
|
||||
or even modify the expression to use a different operator.
|
||||
|
||||
## Simple example
|
||||
|
||||
@@ -19,12 +19,15 @@ type FooPatcher struct{}
|
||||
|
||||
func (FooPatcher) Visit(node *ast.Node) {
|
||||
if n, ok := (*node).(*ast.IdentifierNode); ok && n.Value == "foo" {
|
||||
// highlight-next-line
|
||||
ast.Patch(node, &ast.IntegerNode{Value: 42})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Now we can use the `FooPatcher` to modify the expression:
|
||||
We used the [ast.Patch](https://pkg.go.dev/github.com/expr-lang/expr/ast#Patch) function to replace the `foo` variable with an integer node.
|
||||
|
||||
Now we can use the `FooPatcher` to modify the expression on compilation via the [expr.Patch](https://pkg.go.dev/github.com/expr-lang/expr#Patch) option:
|
||||
|
||||
```go
|
||||
program, err := expr.Compile(`foo + bar`, expr.Patch(FooPatcher{}))
|
||||
@@ -61,17 +64,24 @@ var decimalType = reflect.TypeOf(Decimal{})
|
||||
|
||||
func (DecimalPatcher) Visit(node *ast.Node) {
|
||||
if n, ok := (*node).(*ast.BinaryNode); ok && n.Operator == "+" {
|
||||
|
||||
if !n.Left.Type().AssignableTo(decimalType) {
|
||||
return // skip, left side is not a Decimal
|
||||
}
|
||||
|
||||
if !n.Right.Type().AssignableTo(decimalType) {
|
||||
return // skip, right side is not a Decimal
|
||||
}
|
||||
ast.Patch(node, &ast.CallNode{
|
||||
|
||||
// highlight-start
|
||||
callNode := &ast.CallNode{
|
||||
Callee: &ast.IdentifierNode{Value: "add"},
|
||||
Arguments: []ast.Node{n.Left, n.Right},
|
||||
})
|
||||
(*node).SetType(decimalType) // set the type, so patcher can be used multiple times
|
||||
}
|
||||
ast.Patch(node, callNode)
|
||||
// highlight-end
|
||||
|
||||
(*node).SetType(decimalType) // set the type, so the patcher can be applied recursively
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -97,6 +107,7 @@ env := map[string]interface{}{
|
||||
|
||||
code := `a + b + c`
|
||||
|
||||
// highlight-next-line
|
||||
program, err := expr.Compile(code, expr.Env(env), expr.Patch(DecimalPatcher{}))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
||||
+11
-2
@@ -1,11 +1,13 @@
|
||||
# Visitor
|
||||
|
||||
Expr provides an interface to traverse the AST of the expression before the compilation.
|
||||
Expr provides an interface to traverse the <span title="Abstract Syntax Tree" style={{borderBottom: "1px dotted currentColor"}}>AST</span> of the expression before the compilation.
|
||||
The `Visitor` interface allows you to collect information about the expression, modify the expression, or even generate
|
||||
a new expression.
|
||||
|
||||
Let's start with an [ast.Visitor](https://pkg.go.dev/github.com/expr-lang/expr/ast#Visitor) implementation which will
|
||||
collect all variables used in the expression:
|
||||
collect all variables used in the expression.
|
||||
|
||||
Visitor must implement a single method `Visit(*ast.Node)`, which will be called for each node in the AST.
|
||||
|
||||
```go
|
||||
type Visitor struct {
|
||||
@@ -30,6 +32,7 @@ if err != nil {
|
||||
}
|
||||
|
||||
v := &Visitor{}
|
||||
// highlight-next-line
|
||||
ast.Walk(&tree.Node, v)
|
||||
|
||||
fmt.Println(v.Identifiers) // [foo, bar]
|
||||
@@ -40,6 +43,12 @@ fmt.Println(v.Identifiers) // [foo, bar]
|
||||
Although it is possible to access the AST of compiled program, it may be already be modified by patchers, optimizers, etc.
|
||||
|
||||
```go
|
||||
program, err := expr.Compile(`foo + bar`)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// highlight-next-line
|
||||
node := program.Node()
|
||||
|
||||
v := &Visitor{}
|
||||
|
||||
Reference in New Issue
Block a user