Files
expr/docs/getting-started.md
T
2025-03-17 14:28:04 +01:00

177 lines
3.8 KiB
Markdown

# Getting Started
**Expr** is a simple, fast and extensible expression language for Go. It is
designed to be easy to use and integrate into your Go application. Let's delve
deeper into its core features:
- **Memory safe** - Designed to prevent vulnerabilities like buffer overflows and memory leaks.
- **Type safe** - Enforces strict type rules, aligning with Go's type system.
- **Terminating** - Ensures every expression evaluation cannot loop indefinitely.
- **Side effects free** - Evaluations won't modify global states or variables.
Let's start with a simple example:
```go
program, err := expr.Compile(`2 + 2`)
if err != nil {
panic(err)
}
output, err := expr.Run(program, nil)
if err != nil {
panic(err)
}
fmt.Print(output) // 4
```
Expr compiles the expression `2 + 2` into a bytecode program. Then we run
the program and get the output.
:::tip
In performance-critical applications, you can reuse the compiled program. Compiled programs are safe for concurrent use.
**Compile once** and run **multiple** times.
:::
The `expr.Compile` function returns a `*vm.Program` and an error. The `expr.Run` function takes a program and an
environment. The environment is a map of variables that can be used in the expression. In this example, we use `nil` as
an environment because we don't need any variables.
Now let's pass some variables to the expression:
```go
env := map[string]any{
"foo": 100,
"bar": 200,
}
program, err := expr.Compile(`foo + bar`, expr.Env(env))
if err != nil {
panic(err)
}
output, err := expr.Run(program, env)
if err != nil {
panic(err)
}
fmt.Print(output) // 300
```
Why do we need to pass the environment to the `expr.Compile` function? Expr can be used as a type-safe language.
Expr can infer the type of the expression and check it against the environment.
Here is an example:
```go
env := map[string]any{
"name": "Anton",
"age": 35,
}
program, err := expr.Compile(`name + age`, expr.Env(env))
if err != nil {
// highlight-next-line
panic(err) // Will panic with "invalid operation: string + int"
}
```
Expr can work with any Go types:
```go
env := map[string]any{
"greet": "Hello, %v!",
"names": []string{"world", "you"},
"sprintf": fmt.Sprintf,
}
program, err := expr.Compile(`sprintf(greet, names[0])`, expr.Env(env))
if err != nil {
panic(err)
}
output, err := expr.Run(program, env)
if err != nil {
panic(err)
}
fmt.Print(output) // Hello, world!
```
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 { // Methods defined on the struct become functions.
return t.Format(time.RFC822)
}
type Post struct {
Body string
Date time.Time
}
func main() {
code := `map(posts, Format(.Date) + ": " + .Body)`
program, err := expr.Compile(code, expr.Env(Env{})) // Pass the struct as an environment.
if err != nil {
panic(err)
}
env := Env{
Posts: []Post{
{"Oh My God!", time.Now()},
{"How you doin?", time.Now()},
{"Could I be wearing any more clothes?", time.Now()},
},
}
output, err := expr.Run(program, env)
if err != nil {
panic(err)
}
fmt.Print(output)
}
```
The compiled program can be reused between runs.
```go
type Env struct {
X int
Y int
}
program, err := expr.Compile(`X + Y`, expr.Env(Env{}))
if err != nil {
panic(err)
}
output, err := expr.Run(program, Env{1, 2})
if err != nil {
panic(err)
}
fmt.Print(output) // 3
output, err = expr.Run(program, Env{3, 4})
if err != nil {
panic(err)
}
fmt.Print(output) // 7
```
:::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)
```
:::