Skip to content

Commit

Permalink
Update docs
Browse files Browse the repository at this point in the history
  • Loading branch information
antonmedv committed May 8, 2024
1 parent 83d67e7 commit 6171d56
Show file tree
Hide file tree
Showing 14 changed files with 495 additions and 402 deletions.
201 changes: 90 additions & 111 deletions docs/configuration.md
Original file line number Diff line number Diff line change
@@ -1,156 +1,135 @@
# Configuration

Expr can be configured with options. For example, you can pass the environment with variables and functions.
## Return type

## AllowUndefinedVariables()
Usually, the return type of expression is anything. But we can instruct type checker to verify the return type of the
expression.
For example, in filter expressions, we expect the return type to be a boolean.

This option allows undefined variables in the expression. By default, Expr will return an error
if the expression contains undefined variables.
```go
program, err := expr.Compile(code, expr.AsBool())
if err != nil {
panic(err)
}

```go
program, err := expr.Compile(`foo + bar`, expr.AllowUndefinedVariables())
```
output, err := expr.Run(program, env)
if err != nil {
panic(err)
}

## AsBool()
ok := output.(bool) // It is safe to assert the output to bool, if the expression is type checked as bool.
```

This option forces the expression to return a boolean value. If the expression returns a non-boolean value,
Expr will return an error.
If `code` variable for example returns a string, the compiler will return an error.

```go
program, err := expr.Compile(`Title contains "Hello"`, expr.AsBool())
```
Expr has a few options to specify the return type:

## AsFloat64()
- [expr.AsBool()](https://pkg.go.dev/github.com/expr-lang/expr#AsBool) - expects the return type to be a bool.
- [expr.AsInt()](https://pkg.go.dev/github.com/expr-lang/expr#AsInt) - expects the return type to be an int (float64,
uint, int32, and other will be cast to int).
- [expr.AsInt64()](https://pkg.go.dev/github.com/expr-lang/expr#AsInt64) - expects the return type to be an int64 (
float64, uint, int32, and other will be cast to int64).
- [expr.AsFloat64()](https://pkg.go.dev/github.com/expr-lang/expr#AsFloat64) - expects the return type to be a float64 (
float32 will be cast to float64).
- [expr.AsAny()](https://pkg.go.dev/github.com/expr-lang/expr#AsAny) - expects the return type to be anything.
- [expr.AsKind(reflect.Kind)](https://pkg.go.dev/github.com/expr-lang/expr#AsKind) - expects the return type to be a
specific kind.

This option forces the expression to return a float64 value. If the expression returns a non-float64 value,
Expr will return an error.
:::tip Warn on any
By default, type checker will accept any type, even if the return type is specified. Consider following examples:

```go
program, err := expr.Compile(`42`, expr.AsFloat64())
```expr
let arr = [1, 2, 3]; arr[0]
```

:::note
If the expression returns integer value, Expr will convert it to float64.
:::

## AsInt()
The return type of the expression is `any`. Arrays created in Expr are of type `[]any`. The type checker will not return
an error if the return type is specified as `expr.AsInt()`. The output of the expression is `1`, which is an int, but the
type checker will not return an error.

This option forces the expression to return an int value. If the expression returns a non-int value,
Expr will return an error.
But we can instruct the type checker to warn us if the return type is `any`. Use [`expr.WarnOnAny()`](https://pkg.go.dev/github.com/expr-lang/expr#WarnOnAny) to enable this behavior.

```go
program, err := expr.Compile(`42`, expr.AsInt())
program, err := expr.Compile(code, expr.AsInt(), expr.WarnOnAny())
```

:::note
If the expression returns a float value, Expr truncates it to int.
:::

## AsInt64()
The type checker will return an error if the return type is `any`. We need to modify the expression to return a specific
type.

Same as `AsInt()` but returns an int64 value.

```go
program, err := expr.Compile(`42`, expr.AsInt64())
```expr
let arr = [1, 2, 3]; int(arr[0])
```
:::

## AsKind()

This option forces the expression to return a value of the specified kind.
If the expression returns a value of a different kind, Expr will return an error.
## WithContext

```go
program, err := expr.Compile(`42`, expr.AsKind(reflect.String))
```
Although the compiled program is guaranteed to be terminated, some user defined functions may not be. For example, if a
user defined function calls a remote service, we may want to pass a context to the function.

## ConstExpr()
This is possible via the [`WithContext`](https://pkg.go.dev/github.com/expr-lang/expr#WithContext) option.

This option tells Expr to treat specified functions as constant expressions.
If all arguments of the function are constants, Expr will replace the function call with the result
during the compile step.
This option will modify function calls to include the context as the first argument (only if the function signature
accepts a context).

```go
program, err := expr.Compile(`fib(42)`, expr.ConstExpr("fib"))
```expr
customFunc(42)
// will be transformed to
customFunc(ctx, 42)
```

[ConstExpr Example](https://pkg.go.dev/github.com/expr-lang/expr?tab=doc#ConstExpr)

## Env()

This option passes the environment with variables and functions to the expression.
Function `expr.WithContext()` takes the name of context variable. The context variable must be defined in the environment.

```go
program, err := expr.Compile(`foo + bar`, expr.Env(Env{}))
env := map[string]any{
"ctx": context.Background(),
}

program, err := expr.Compile(code, expr.Env(env), expr.WithContext("ctx"))
```

## Function()
## ConstExpr

This option adds a function to the expression.
For some user defined functions, we may want to evaluate the expression at compile time. This is possible via the
[`ConstExpr`](https://pkg.go.dev/github.com/expr-lang/expr#ConstExpr) option.

```go
atoi := expr.Function(
"atoi",
func(params ...any) (any, error) {
return strconv.Atoi(params[0].(string))
},
)

program, err := expr.Compile(`atoi("42")`, atoi)
func fib(n int) int {
if n <= 1 {
return n
}
return fib(n-1) + fib(n-2)
}

env := map[string]any{
"fib": fib,
}

program, err := expr.Compile(`fib(10)`, expr.Env(env), expr.ConstExpr("fib"))
```

Expr sees the `atoi` function as a function with a variadic number of arguments of type `any` and returns a value of type `any`. But, we can specify the types of arguments and the return value by adding the correct function
signature or multiple signatures.
If all arguments of the function are constants, the function will be evaluated at compile time. The result of the function
will be used as a constant in the expression.

```go
atoi := expr.Function(
"atoi",
func(params ...any) (any, error) {
return strconv.Atoi(params[0].(string))
},
new(func(string) int),
)
```expr
fib(10) // will be transformed to 55 during the compilation
fib(12+12) // will be transformed to 267914296 during the compilation
fib(x) // will **not** be transformed and will be evaluated at runtime
```

Or we can simply reuse the `strconv.Atoi` function.

```go
atoi := expr.Function(
"atoi",
func(params ...any) (any, error) {
return strconv.Atoi(params[0].(string))
},
strconv.Atoi,
)
```
## Options

Here is another example with a few function signatures:
Compiler options can be defined as an array:

```go
toInt := expr.Function(
"toInt",
func(params ...any) (any, error) {
switch params[0].(type) {
case float64:
return int(params[0].(float64)), nil
case string:
return strconv.Atoi(params[0].(string))
}
return nil, fmt.Errorf("invalid type")
},
new(func(float64) int),
new(func(string) int),
)
options := []expr.Option{
expr.Env(Env{})
expr.AsInt(),
expr.WarnOnAny(),
expr.WithContext("ctx"),
expr.ConstExpr("fib"),
}

program, err := expr.Compile(code, options...)
```


## Operator()

This options defines an [operator overloading](operator-overloading).

## Optimize()

This option enables [optimizations](internals.md). By default, Expr will optimize the expression.

## Patch()

This option allows you to [patch the expression](visitor-and-patch) before compilation.
Full list of available options can be found in the [pkg.go.dev](https://pkg.go.dev/github.com/expr-lang/expr#Option) documentation.
95 changes: 95 additions & 0 deletions docs/environment.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# Environment

The environment is a map or a struct that contains the variables and functions that the expression can access.

## Struct as Environment

Let's consider the following example:

```go
type Env struct {
UpdatedAt time.Time
Posts []Post
Map map[string]string `expr:"tags"`
}
```

The `Env` struct contains 3 variables that the expression can access: `UpdatedAt`, `Posts`, and `tags`.

:::info
The `expr` tag is used to rename the `Map` field to `tags` variable in the expression.
:::

The `Env` struct can also contain methods. The methods defined on the struct become functions that the expression can
call.

```go
func (Env) Format(t time.Time) string {
return t.Format(time.RFC822)
}
```

:::tip
Methods defined on embedded structs are also accessible.
```go
type Env struct {
Helpers
}

type Helpers struct{}

func (Helpers) Format(t time.Time) string {
return t.Format(time.RFC822)
}
```
:::

We can use an empty struct `Env{}` to with [expr.Env](https://pkg.go.dev/github.com/expr-lang/expr#Env) to create an environment. Expr will use reflection to find
the fields and methods of the struct.

```go
program, err := expr.Compile(code, expr.Env(Env{}))
```

Compiler will type check the expression against the environment. After the compilation, we can run the program with the environment.
You should use the same type of environment that you passed to the `expr.Env` function.

```go
output, err := expr.Run(program, Env{
UpdatedAt: time.Now(),
Posts: []Post{{Title: "Hello, World!"}},
Map: map[string]string{"tag1": "value1"},
})
```

## Map as Environment

You can also use a map as an environment.

```go
env := map[string]any{
"UpdatedAt": time.Time{},
"Posts": []Post{},
"tags": map[string]string{},
"sprintf": fmt.Sprintf,
}

program, err := expr.Compile(code, expr.Env(env))
```

A map defines variables and functions that the expression can access. The key is the variable name, and the type
is the value's type.

```go
env := map[string]any{
"object": map[string]any{
"field": 42,
},
}
```

Expr will infer the type of the `object` variable as `map[string]any`.

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.
Loading

0 comments on commit 6171d56

Please sign in to comment.