Update docs

This commit is contained in:
Anton Medvedev
2025-03-17 14:28:04 +01:00
parent 3c678df33f
commit 99e8ef04a5
6 changed files with 69 additions and 35 deletions
+13 -14
View File
@@ -9,12 +9,12 @@ For example, in filter expressions, we expect the return type to be a boolean.
```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
```
+17 -1
View File
@@ -85,11 +85,27 @@ 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.
:::
+3
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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{}