Expression evaluation engine for Go: fast, non-Turing complete, dynamic typing, static typing

Overview

Expr

Build Status Go Report Card GoDoc

expr logo

Expr package provides an engine that can compile and evaluate expressions. An expression is a one-liner that returns a value (mostly, but not limited to, booleans). It is designed for simplicity, speed and safety.

The purpose of the package is to allow users to use expressions inside configuration for more complex logic. It is a perfect candidate for the foundation of a business rule engine. The idea is to let configure things in a dynamic way without recompile of a program:

# Get the special price if
user.Group in ["good_customers", "collaborator"]

# Promote article to the homepage when
len(article.Comments) > 100 and article.Category not in ["misc"]

# Send an alert when
product.Stock < 15

Features

  • Seamless integration with Go (no need to redefine types)
  • Static typing (example).
    out, err := expr.Compile(`name + age`)
    // err: invalid operation + (mismatched types string and int)
    // | name + age
    // | .....^
  • User-friendly error messages.
  • Reasonable set of basic operators.
  • Builtins all, none, any, one, filter, map.
    all(Tweets, {.Size <= 280})
  • Fast (benchmarks): uses bytecode virtual machine and optimizing compiler.

Install

go get github.com/antonmedv/expr

Documentation

Expr Code Editor

Expr Code Editor

Also, I have an embeddable code editor written in JavaScript which allows editing expressions with syntax highlighting and autocomplete based on your types declaration.

Learn more →

Examples

Play Online

package main

import (
	"fmt"
	"github.com/antonmedv/expr"
)

func main() {
	env := map[string]interface{}{
		"greet":   "Hello, %v!",
		"names":   []string{"world", "you"},
		"sprintf": fmt.Sprintf,
	}

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

	program, err := expr.Compile(code, expr.Env(env))
	if err != nil {
		panic(err)
	}

	output, err := expr.Run(program, env)
	if err != nil {
		panic(err)
	}

	fmt.Println(output)
}

Play Online

package main

import (
	"fmt"
	"github.com/antonmedv/expr"
)

type Tweet struct {
	Len int
}

type Env struct {
	Tweets []Tweet
}

func main() {
	code := `all(Tweets, {.Len <= 240})`

	program, err := expr.Compile(code, expr.Env(Env{}))
	if err != nil {
		panic(err)
	}

	env := Env{
		Tweets: []Tweet{{42}, {98}, {69}},
	}
	output, err := expr.Run(program, env)
	if err != nil {
		panic(err)
	}

	fmt.Println(output)
}

Contributing

Expr consist of a few packages for parsing source code to AST, type checking AST, compiling to bytecode and VM for running bytecode program.

Also expr provides powerful tool exe for debugging. It has interactive terminal debugger for our bytecode virtual machine.

debugger

Who is using Expr?

  • Aviasales Aviasales are actively using Expr for different parts of the search engine.
  • Argo Argo Rollouts - Progressive Delivery for Kubernetes.
  • Argo Argo Workflows - The workflow engine for KubernetesOverview.
  • CrowdSec Crowdsec - A security automation tool.
  • Mystery Minds uses Expr to allow easy yet powerful customization of its matching algorithm.

Add your company too

License

MIT

Comments
  • A new type named set(contain no repeated value) different from the built-in type map

    A new type named set(contain no repeated value) different from the built-in type map

    Thanks for your contribution, this library help me a lot. Now, I receive a requirement from colleagues, he needs a type "set". The built-in type "map" all contains repeated value, but I want a type that like "map", but without repeating elements, just like type “set” in java, how do i add a new type “set” in this “expr”. @antonmedv

    the new type "set" equal to "map[string]struct{}" in golang

    opened by xiaoyang-chen 13
  • Parser error on 386 arch by int overflows

    Parser error on 386 arch by int overflows

    Windows 7 32-bit go1.11.2 windows/386 Trying to do:

    package main
    
    import (
    	"github.com/antonmedv/expr"
    )
    
    func main() {
    	p, err := expr.Compile("1+2") 
    }
    
    

    Build fails with multiple errors:

    Installation failed: # github.com/antonmedv/expr/parser/gen
    ..\..\..\..\github.com\antonmedv\expr\parser\gen\expr_parser.go:2231: constant 4228579072 overflows int
    ..\..\..\..\github.com\antonmedv\expr\parser\gen\expr_parser.go:2249: constant 4228579072 overflows int
    ..\..\..\..\github.com\antonmedv\expr\parser\gen\expr_parser.go:2300: constant 4228579072 overflows int
    
    
    bug 
    opened by r3code 13
  • JSON marshaling of Program is incomplete

    JSON marshaling of Program is incomplete

    The documentation states that marshaling and unmarshaling of a program is supported. However, the following test fails:

    func Test_marshalRegexp(t *testing.T) {
    	prog, err := expr.Compile(`"hello" matches "h.*"`)
    	if !assert.NoError(t, err) {
    		t.FailNow()
    	}
    
    	marshaled, err := json.Marshal(prog)
    	if !assert.NoError(t, err) {
    		t.FailNow()
    	}
    
    	unmarshaledProgram := &vm.Program{}
    	err = json.Unmarshal(marshaled, unmarshaledProgram)
    	if !assert.NoError(t, err) {
    		t.FailNow()
    	}
    
    	output, err := expr.Run(unmarshaledProgram, nil)
    	if !assert.NoError(t, err) {
    		t.FailNow()
    	}
    	assert.Equal(t, true, output)
    }
    
    bug good first issue 
    opened by bontibon 11
  • fast function

    fast function

    I utilized the fast function capability. It still uses reflection. I am trying to avoid the load of reflection. Is there a Fetcher type facility for function in an env struct, to avoid reflection? Is there an alternate way? Is it possible to pre-register a function with its signature to avoid reflection?

    Thank you.

    opened by gitperson1980 10
  • Another go.mod v2 issue?

    Another go.mod v2 issue?

    We have been using v1.1.4 of the 'expr' package, which is great, by the way. We finally updated to GO 1.13 and at the same time started migrating to GO modules.

    However, when upgrading, our process is failing because of a missing "/v2" in your go.mod module path.

    $ go get github.com/antonmedv/[email protected]
    go: finding github.com v2.1.1
    go: finding github.com/antonmedv v2.1.1
    go: finding github.com/antonmedv/expr v2.1.1
    go: finding github.com/antonmedv/expr v2.1.1
    go get github.com/antonmedv/[email protected]: github.com/antonmedv/[email protected]: invalid version: module contains a go.mod file, so major version must be compatible: should be v0 or v1, not v2
    

    I was able to download the source and modify the "go.mod" file to be:

    $ cat go.mod
    module github.com/antonmedv/expr/v2
    
    go 1.12
    
    require (
    	github.com/antlr/antlr4 v0.0.0-20190518164840-edae2a1c9b4b
    	github.com/gdamore/tcell v1.1.2
    	github.com/rivo/tview v0.0.0-20190515161233-bd836ef13b4b
    	github.com/sanity-io/litter v1.1.0
    	github.com/stretchr/testify v1.3.0
    	golang.org/x/text v0.3.2
    )
    

    This helped me get past the above error.

    Is there something I am doing wrong? Or is this an issue with the "expr" module. I am new to "go modules" and dealing with the learning curve.

    opened by starbuck-ms 9
  • Performance of function calls

    Performance of function calls

    Hi @antonmedv,

    Just wondering whether there is something to optimise calling functions? I am testing out vs GoEvaluate and Expr is fastest except for calling functions. I am not sure the reason but I am guessing it may be due to GoEvaluate only defining functions on the compile step. In the examples below I am just doing a simple addition (float64 + float64) in a function.

    I got some performance improvement by using interface{} for the arguments and return value (BenchmarkFuncExprFast), but it still takes twice the time.

    BenchmarkFuncExpr-12                   	 1428219	       881 ns/op	     208 B/op	       8 allocs/op
    BenchmarkFuncExprFast-12               	 1476798	       680 ns/op	     136 B/op	       7 allocs/op
    BenchmarkFuncGoEvaluate-12             	 3650031	       332 ns/op	      88 B/op	       4 allocs/op
    
    func BenchmarkFuncExprFast(b *testing.B) {
    
    	env := Env{
    		"val1": 1.0,
    		"val2": 2.0,
    		"add": func(args ...interface{}) interface{} {
    			return args[0].(float64) + args[1].(float64)
    		},
    	}
    
    	program, err := expr.Compile(`add(val1, val2)`, expr.Env(env))
    	if err != nil {
    		b.Error(err)
    		b.FailNow()
    	}
    
    	b.ResetTimer()
    
    	for i := 0; i < b.N; i++ {
    		val, err := expr.Run(program, env)
    		if err != nil {
    			b.Error(err)
    		}
    		if val != 3.0 {
    			b.Errorf("Val %v not 3", val)
    		}
    	}
    
    }
    
    opened by cubewise-tryan 8
  • Reference names with special characters and spaces

    Reference names with special characters and spaces

    Hi @antonmedv,

    Awesome library! Is there any way to escape variable names that have special characters in them? For example GoEvaluate uses square brackets:

    var (
    	codeSpecial   = `[!val 1] + [!val 2]`
    	paramsSpecial = map[string]interface{}{
    		"!val 1": 1.0,
    		"!val 2": 2.0,
    	}
    )
    
    func BenchmarkBangExpr(b *testing.B) {
    
    	program, err := expr.Compile(codeSpecial, expr.Env(paramsSpecial))
    	if err != nil {
    		b.Error(err)
    		b.FailNow()
    	}
    
    	b.ResetTimer()
    
    	for i := 0; i < b.N; i++ {
    		val, err := expr.Run(program, paramsSpecial)
    		if err != nil {
    			b.Error(err)
    		}
    		if val != 3.0 {
    			b.Errorf("Val %v not 3", val)
    		}
    	}
    
    }
    
    opened by cubewise-tryan 8
  • Question

    Question

    Hello guys!

    Would like to do simple assignation évaluation, something like:

    Var lib string Code = ‘lib="ok"’ ... expr code to parse this thing... Font.Println(lib) // should print ok

    Is it something doable? If yes what code should i use to achieve that?

    opened by jscoys 8
  • vm: Use a preallocated fixed call frame for typical argument sizes

    vm: Use a preallocated fixed call frame for typical argument sizes

    For calls with 20 or less arguments use a preallocated fixed array for the calling frame, otherwise fall back to heap allocation. This avoids what was previously always a heap allocation at callsites.

    Before: Benchmark_Calls-4 50000 27618 ns/op

    After: Benchmark_Calls-4 50000 25948 ns/op

    opened by deanm 8
  • [Bug?] Handle extra whitespace in

    [Bug?] Handle extra whitespace in "not in" syntax

    i.e. case 1:

    "A"  not in ["a", "b", "c"]
    

    works and returns true

    case 2:

    "A" not  in ["a", "b", "c"]
    

    got panic:

    panic: unexpected token Operator("not") (1:5)
     | "A" not  in ["a", "b", "c"]
     | ....^
    

    do you guys think it's a bug and needs fix? thanks!

    bug 
    opened by qu-way 7
  • Proposal: extending Regexp support

    Proposal: extending Regexp support

    @antonmedv: First of all, thank you for a great library! I played with it a bit and it is very nicely done.

    One thing that looks a bit limited is Regexp support. From what I see the only supported operation is currently string match which takes a regexp string on the right-hand side.

    Would you be interested in enhancing Regexp support? I will be happy to contribute PRs that implement new features. Here is what I would love to see added:

    • Add Regexp as native supported type with a set of simple functions, e.g. FindMatches, FindSubmatches, etc. that implement more complex functionality related to regexes.

    • Regexp literal support (e.g. JavaScript-like forward slash-based). Yes, one can define a custom Regexp() function which accepts a regex string and creates a Regex type, but that will only work dynamically. With literal support it would be possible to pre-compile regexes.

    Is this something you would be interested in adding to the language? I am currently considering using Expr in a product I work on and these capabilities are what I am missing to move forward. I can implement the features myself but wanted to check first if this is inline with how you want to evolve the library.

    Thanks again :)

    opened by tigrannajaryan 7
  • Adds support for common operators for time.Time

    Adds support for common operators for time.Time

    This should resolve the following issues.

    #73 #203 #227

    Before starting this PR, I attempted to add support for time operators, but ended up with an impossible state b/c I cannot support interface{} -OP- interface{} without losing the built-in functionality of the operators. The only option I could see was to copy/paste/extend /vm/helpers.go.

    I decided to search the issues and found that others have run into this too.


    With this PR, the operators ==, >=, <=, <, > are included by default allowing users to compare time.Time instances, returning bool.

    The + operator adds a time.Duration to time.Time, returning time.Time The - operator returns a duration representing the diff between two time.Time instances.


    Please lmk if there are any concerns with the implementation. Please lmk if you see any gaps in the tests. I'll be happy to fix it up.

    :)

    opened by panthershark 2
  • how to new a string slice in expr

    how to new a string slice in expr

    ---------------code -------------------------- exprStr := "join([]string{'foo','bar'})" m := make[string]interface{} m['join'] = func(input []string) { // do something } program, err := expr.Compile(exprStr, expr.Env(m)) if err != nil { return } res, err := expr.Run(program, m) ---------------errorMsg----------------------------- unexpected token Identifier("string") ---------------question----------------------------- how to new a string slice in expr

    help wanted 
    opened by KoalaInTree 0
  • [Feature] Allow to use function defined in a Fetcher

    [Feature] Allow to use function defined in a Fetcher

    Today the fetcher.Fetch is ignored for function calls, making it impossible to create functions at runtime.

    This PR adds this feature, allowing the build below to take place.

    type FetcherMap struct {
    	data map[string]interface{}
    }
    
    func (m *FetcherMap) Set(key string, value interface{}) {
    	if m.data == nil {
    		m.data = map[string]interface{}{}
    	}
    	m.data[key] = value
    }
    
    func (m *FetcherMap) Fetch(key interface{}) interface{} {
    	if keyStr, ok := key.(string); ok {
    		if value, exists := m.data[keyStr]; exists {
    			return value
    		}
    	}
    	return nil
    }
    
    func TestRun_fetch_functions(t *testing.T) {
    	input := `hello() + world() + suffix()`
    
    	tree, err := parser.Parse(input)
    	require.NoError(t, err)
    
    	program, err := compiler.Compile(tree, nil)
    	require.NoError(t, err)
    
    	fetcher := &FetcherMap{}
    	fetcher.Set("hello", func() string { return "hello " })
    	fetcher.Set("world", func() string { return "world" })
    	suffixFnPtr := func() string { return "!" }
    	fetcher.Set("suffix", &suffixFnPtr)
    
    	out, err := vm.Run(program, fetcher)
    	require.NoError(t, err)
    
    	require.Equal(t, "hello world!", out)
    }
    

    fix https://github.com/antonmedv/expr/issues/247

    image

    opened by nidorx 1
  • Allow renaming struct fields using struct tags

    Allow renaming struct fields using struct tags

    This PR allows renaming struct fields by adding struct tags. For example, the expession lowercaseField + OtherField can be checked and evaluated with the following struct as env:

    type Env struct {
    	UppercaseField int `expr:"lowercaseField"`
    	X              int `expr:"OtherField"`
    }
    

    Closes #13. I drive-by fixed two minor errors in file/source_test.go and gofmt reformatted a few unrelated lines, I hope that's okay for the PR, otherwise I'll revert the unrelated changes.

    opened by rtpt-erikgeiser 5
  • Operator override in case when env is passed as map[string]interface{}

    Operator override in case when env is passed as map[string]interface{}

    Hi @antonmedv ,

    We have a use case where we want to support decimal in expr library. I was thinking to use ConstantNode for representing decimal values and overriding operators like +, -, *..etc. When I am adding my function for add in environment (which is a map of string to interface{}), it gives me error function add for + operator does not have a correct signature. I have a requirement to pass environment as map[string]interface{}, so I wanted to know if there is any workaround for this? Please help!

    Add Function Signature in environment

    "add": func(a decimal.Decimal, b decimal.Decimal) (decimal.Decimal, error) {
    			z := a.Add(b)
    			return z, nil
    		},
    

    We are using github.com/shopspring/decimal library here.

    Thanks

    opened by shivam-940 4
  • Support long jumps inside an expression

    Support long jumps inside an expression

    We are generating really long expressions (>1MB) and using conditionals there that can cause more than 64KB jumps. Today, the args of the opcodes are 2 bytes and long jumps are truncated to this size. When running the expression, the VM jumps (actual_offset % 64Kb) and runs invalid opcodes.

    opened by danysirota 1
Releases(v1.9.0)
Owner
Anton Medvedev
curl medv.io
Anton Medvedev
Expression evaluation in golang

Gval Gval (Go eVALuate) provides support for evaluating arbitrary expressions, in particular Go-like expressions. Evaluate Gval can evaluate expressio

null 527 Sep 9, 2022
Arbitrary expression evaluation for golang

govaluate Provides support for evaluating arbitrary C-like artithmetic/string expressions. Why can't you just write these expressions in code? Sometim

George Lester 2.7k Sep 26, 2022
Mathematical expression parsing and calculation engine library. 数学表达式解析计算引擎库

Math-Engine 使用 Go 实现的数学表达式解析计算引擎库,它小巧,无任何依赖,具有扩展性(比如可以注册自己的函数到引擎中),比较完整的完成了数学表达式解析执行,包括词法分析、语法分析、构建AST、运行。 go get -u github.com/dengsgo/math-engine 能够

Deng.Liu 240 Sep 19, 2022
Logexp - Logical expression compiler for golang

Logical Expression Compiler Functions: - Compile(exp string) - Match(text string

Jinglever 1 Jan 24, 2022
Suan - Mathematical expression calculation tool

suan Suan( 算 ) is a CLI tool to calculate given mathematical expression. Current

null 1 Feb 14, 2022
ECMAScript/JavaScript engine in pure Go

goja ECMAScript 5.1(+) implementation in Go. Goja is an implementation of ECMAScript 5.1 in pure Go with emphasis on standard compliance and performan

Dmitry Panov 3.2k Sep 21, 2022
A fast script language for Go

The Tengo Language Tengo is a small, dynamic, fast, secure script language for Go. Tengo is fast and secure because it's compiled/executed as bytecode

daniel 2.9k Sep 21, 2022
Simple, fast, safe, compiled language for developing maintainable software. Compiles itself in <1s with zero library dependencies. https://vlang.io

The V Programming Language vlang.io | Docs | Changelog | Speed | Contributing & compiler design Key Features of V Simplicity: the language can be lear

The V Programming Language 30.5k Sep 20, 2022
Expression evaluation engine for Go: fast, non-Turing complete, dynamic typing, static typing

Expr Expr package provides an engine that can compile and evaluate expressions. An expression is a one-liner that returns a value (mostly, but not lim

Anton Medvedev 2.8k Sep 15, 2022
Expression evaluation engine for Go: fast, non-Turing complete, dynamic typing, static typing

Expr Expr package provides an engine that can compile and evaluate expressions. An expression is a one-liner that returns a value (mostly, but not lim

Anton Medvedev 2.8k Sep 21, 2022
Fast, portable, non-Turing complete expression evaluation with gradual typing (Go)

Common Expression Language The Common Expression Language (CEL) is a non-Turing complete language designed for simplicity, speed, safety, and portabil

Google 1.3k Sep 14, 2022
Fast, portable, non-Turing complete expression evaluation with gradual typing (Go)

Common Expression Language The Common Expression Language (CEL) is a non-Turing complete language designed for simplicity, speed, safety, and portabil

Google 1.3k Sep 23, 2022
Go-turing-i2c-cmdline - Controlling the i2c management bus of the turing pi with i2c works fine

go-turing-i2c-cmdline What is it? Controlling the i2c management bus of the turi

null 2 Jan 24, 2022
Simple expression evaluation engine for Go

??️ chili Currently in development, Unstable (API may change in future) Simple expression evaluation engine. Expression is one liner that evalutes int

Santhosh Kumar 71 Jun 21, 2022
Expression evaluation in golang

Gval Gval (Go eVALuate) provides support for evaluating arbitrary expressions, in particular Go-like expressions. Evaluate Gval can evaluate expressio

null 530 Sep 21, 2022
Expression evaluation in golang

Gval Gval (Go eVALuate) provides support for evaluating arbitrary expressions, in particular Go-like expressions. Evaluate Gval can evaluate expressio

null 527 Sep 9, 2022
Arbitrary expression evaluation for golang

govaluate Provides support for evaluating arbitrary C-like artithmetic/string expressions. Why can't you just write these expressions in code? Sometim

George Lester 2.7k Sep 26, 2022
An implementation of Neural Turing Machines

Neural Turing Machines Package ntm implements the Neural Turing Machine architecture as described in A.Graves, G. Wayne, and I. Danihelka. arXiv prepr

Fumin 398 Sep 13, 2022
The Dual-Stack Dynamic DNS client, the world's first dynamic DNS client built for IPv6.

dsddns DsDDNS is the Dual-Stack Dynamic DNS client. A dynamic DNS client keeps your DNS records in sync with the IP addresses associated with your hom

Ryan Young 14 Sep 7, 2022
A complete Liquid template engine in Go

Liquid Template Parser liquid is a pure Go implementation of Shopify Liquid templates. It was developed for use in the Gojekyll port of the Jekyll sta

Oliver Steele 172 Sep 16, 2022