diff --git a/docs/Getting-Started.md b/docs/Getting-Started.md index 513da3fa6..1ea874713 100644 --- a/docs/Getting-Started.md +++ b/docs/Getting-Started.md @@ -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 @@ -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"}, @@ -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) @@ -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{})) @@ -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()}, @@ -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) diff --git a/docs/Internals.md b/docs/Internals.md index 93f5e7e0c..d3aa8e3f6 100644 --- a/docs/Internals.md +++ b/docs/Internals.md @@ -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 @@ -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) diff --git a/docs/Language-Definition.md b/docs/Language-Definition.md index d2b2912e7..90c0fc90c 100644 --- a/docs/Language-Definition.md +++ b/docs/Language-Definition.md @@ -1,186 +1,186 @@ # Language Definition -
Built-in Functions | -Operators | -||
---|---|---|---|
- all() - any() - one() - none() - |
-
- len() - map() - filter() - count() - |
-
- matches - contains - startsWith - endsWith - |
-
- in - not in - x..y - [x:y] - |
-
Boolean | +
+ true , false
+ |
+
Integer | +
+ 42 , 0x2A
+ |
+
Float | +
+ 0.5 , .5
+ |
+
String | +
+ "foo" , 'bar'
+ |
+
Array | +
+ [1, 2, 3]
+ |
+
Map | +
+ {a: 1, b: 2, c: 3}
+ |
+
Nil + |
+ nil
+ |
+
Arithmetic | +
+ + , - , * , / , % (modulus), ^ or ** (exponent)
+ |
+
Comparison | +
+ == , != , < , > , <= , >=
+ |
+
Logical | +
+ not or ! , and or && , or or ||
+ |
+
Conditional | +
+ ?: (ternary)
+ |
+
Membership | +
+ [] , . , ?. , in
+ |
+
String | +
+ + (concatenation), contains , startsWith , endsWith
+ |
+
Regex | +
+ matches
+ |
+
Range | +
+ ..
+ |
+
Slice | +
+ [:]
+ |
+
+ all() + any() + one() + none() + |
+
+ map() + filter() + count() + |
+
+ len() + abs() + int() + float() + |
+