Skip to content

Commit

Permalink
Update documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
antonmedv committed Jan 30, 2023
1 parent 107f6ef commit 3e9893b
Show file tree
Hide file tree
Showing 6 changed files with 287 additions and 257 deletions.
118 changes: 82 additions & 36 deletions docs/Getting-Started.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# Getting Started

**Expr** provides a package for evaluating arbitrary expressions as well as type
checking of such expression.
**Expr** is a simple, fast and extensible expression language for Go. It is
designed to be easy to use and integrate into your application.


## Evaluate

Expand Down Expand Up @@ -34,22 +35,11 @@ func main() {

## Compile

Usually we want to compile, type check and verify what the expression returns a
boolean (or another type).

For example, if a user saves an expression from a
Usually, we want to compile, type check and verify that the expression returns a
boolean (or another type). For example, if a user saves an expression from a
[web UI](https://antonmedv.github.io/expr/).

```go
package main

import (
"fmt"

"github.com/antonmedv/expr"
)

func main() {
env := map[string]interface{}{
"greet": "Hello, %v!",
"names": []string{"world", "you"},
Expand All @@ -58,8 +48,8 @@ func main() {

code := `sprintf(greet, names[0])`

// Compile the code into a bytecode. This step can be done once
// and program may be reused. Specify an environment for type check.
// Compile the code into a bytecode. This step can be done only once and
// the program may be reused. Specify an environment for the type check.
program, err := expr.Compile(code, expr.Env(env))
if err != nil {
panic(err)
Expand All @@ -71,40 +61,28 @@ func main() {
}

fmt.Print(output)
}
```

You may use existing types.

For example, an environment can be a struct. And structs methods can be used as
functions. Expr supports embedded structs and methods defines on them too.
An environment can be a struct and structs methods can be used as
functions. Expr supports embedded structs and methods defined on them too.

The struct fields can be renamed by adding struct tags such as `expr:"name"`.

```go
package main

import (
"fmt"
"time"

"github.com/antonmedv/expr"
)

type Env struct {
Tweets []Tweet
Messages []Message `expr:"messages"`
}

// Methods defined on the struct become functions.
func (Env) Format(t time.Time) string { return t.Format(time.RFC822) }

type Tweet struct {
type Message struct {
Text string
Date time.Time `expr:"timestamp"`
Date time.Time
}

func main() {
code := `map(filter(Tweets, {len(.Text) > 0}), {.Text + Format(.timestamp)})`
code := `map(filter(messages, len(.Text) > 0), .Text + Format(.timestamp))`

// We can use an empty instance of the struct as an environment.
program, err := expr.Compile(code, expr.Env(Env{}))
Expand All @@ -113,7 +91,7 @@ func main() {
}

env := Env{
Tweets: []Tweet{
Messages: []Message{
{"Oh My God!", time.Now()},
{"How you doin?", time.Now()},
{"Could I be wearing any more clothes?", time.Now()},
Expand All @@ -129,4 +107,72 @@ func main() {
}
```

## Configuration

Expr can be configured to do more things. For example, with [AllowUndefinedVariables](https://pkg.go.dev/github.com/antonmedv/expr#AllowUndefinedVariables) or [AsBool](https://pkg.go.dev/github.com/antonmedv/expr#AsBool) to expect the boolean result from the expression.

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

## Functions

Expr supports any Go functions. For example, you can use `fmt.Sprintf` or methods of your structs.
Also, Expr supports functions configured via [`expr.Function(name, fn[, ...types])`](https://pkg.go.dev/github.com/antonmedv/expr#Function) option.

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

program, err := expr.Compile(`atoi("42")`, atoi)
```

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.

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

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,
)
```

Here is another example with a few function signatures:

```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),
)
```

* Next: [Operator Overloading](Operator-Overloading.md)
42 changes: 19 additions & 23 deletions docs/Internals.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
# Internals

Expr is a stack based virtual machine. It compiles expressions to bytecode and
runs it.

Compilation is done in a few steps:
Expr is a stack-based virtual machine. It compiles expressions to bytecode and
runs it. The compilation is done in a few steps:

- Parse expression to AST
- Type check AST
Expand All @@ -12,72 +10,70 @@ Compilation is done in a few steps:
- Optimize AST
- Compile AST to a bytecode

Compiler has a bunch of optimization which will produce a more optimal program.
The compiler has a bunch of optimization which will produce a more optimal program.

## In array

```js
```
value in ['foo', 'bar', 'baz']
```

If Expr finds an `in` or `not in` expression with an array, it will be
transformed into:

```js
```
value in {"foo": true, "bar": true, "baz": true}
```

## Constant folding

Arithmetic expressions with constants is computed on compile step and replaced
Arithmetic expressions with constants are computed on compile step and replaced
with the result.

```js
```
-(2 - 5) ** 3 - 2 / (+4 - 3) + -2
```

Will be compiled to just single number:
Will be compiled into just a single number:

```js
```
23
```

## In range

```js
user.Age in 18.
.32
```
user.Age in 18..32
```

Will be replaced with a binary operator:

```js
```
18 <= user.Age && user.Age <= 32
```

## Const range

```js
1.
.10_000
```
1..10_000
```

Ranges computed on compile stage, replaced with precreated slices.
Ranges computed on the compile stage, are replaced with pre-created slices.

## Const expr

If some function marked as constant expression with `expr.ConstExpr`. It will be
replaced with result of the call, if all arguments are constants.
If some function is marked as a constant expression with `expr.ConstExpr`. It will be
replaced with the result of the call if all arguments are constants.

```go
expr.ConstExpt("fib")
```

```js
```
fib(42)
```

Will be replaced with result of `fib(42)` on the compile step.
Will be replaced with the result of `fib`(42)` on the compile step.

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

Expand Down
Loading

0 comments on commit 3e9893b

Please sign in to comment.