expr

package module
v0.12.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 25, 2023 License: MIT Imports: 9 Imported by: 0

README

expr

GoDoc

Expression evaluator for Go

Features

  • Operators: + - * / % ! < <= > >= == === != !=== ?: ?? , [] () & | ^
  • Types: String, Number, Boolean, and custom types
  • Supports custom functions, types, associative arrays, and variables.
  • Parenthesized expressions
  • Javascript-like syntax with automatic type conversions
  • Native uint64 and int64 types using the u64 and i64 suffix on number literals
  • Stateless: No variable assignments and no statements.

Using

To start using expr, install Go and run go get:

$ go get github.com/tidwall/expr
Basic expressions

For example:

1 + 1
(10 * 5 <= 50) && (50 > 100 || 8 >= 7)
1e+10 > 0 ? "big" : "small"

In Go, you're code may look like the following.

res, _ := expr.Eval(`1 + 1`, nil)
fmt.Println(res)
res, _ := expr.Eval(`(10 * 5 <= 50) && (50 > 100 || 8 >= 7)`, nil)
fmt.Println(res)
res, _ := expr.Eval(`1e+10 > 0 ? "big" : "small"`, nil)
fmt.Println(res)

// Output: 
// 2
// true
// big
Advanced expressions

Using a custom evaluation extender we can extend the eval function to support arithmetic and comparisons on custom types, such as time.Time that is built into Go. We can also provide some extra user data that exposes extra variables to the evaluator.

Example expressions:

this.timestamp
this.timestamp - dur('1h')
now() + dur('24h')
this.timestamp < now() - dur('24h') ? "old" : "new"
((this.minX + this.maxX) / 2) + "," + ((this.minY + this.maxY) / 2)

In Go, you would provide a custom Extender to the Eval function.

package main

import (
	"fmt"
	"time"

	"github.com/tidwall/expr"
)

func main() {
	// Create a user data map that can be referenced by the Eval function.
	this := make(map[string]expr.Value)

	// Add a bounding box to the user dictionary.
	this["minX"] = expr.Number(112.8192)
	this["minY"] = expr.Number(33.4738)
	this["maxX"] = expr.Number(113.9146)
	this["maxY"] = expr.Number(34.3367)

	// Add a timestamp value to the user dictionary.
	ts, _ := time.Parse(time.RFC3339, "2022-03-31T09:00:00Z")
	this["timestamp"] = expr.Object(ts)

	// Set up an evaluation extender for referencing the user data, and
	// using functions and operators on custom types.
	ext := expr.NewExtender(
		func(info expr.RefInfo, ctx *expr.Context) (expr.Value, error) {
			if info.Chain {
				// The reference is part of a dot chain such as:
				//   this.minX
				if this, ok := ctx.UserData.(map[string]expr.Value); ok {
					return this[info.Ident], nil
				}
				return expr.Undefined, nil
			}
			switch info.Ident {
			case "now":
				// The `now()` function
				return expr.Function("now"), nil
			case "dur":
				// The `dur(str)` function
				return expr.Function("duration"), nil
			case "this":
				// The `this` UserData
				return expr.Object(ctx.UserData), nil
			}
			return expr.Undefined, nil
		},
		func(info expr.CallInfo, ctx *expr.Context) (expr.Value, error) {
			if info.Chain {
				// Only use globals in this example.
				// No chained function like `user.name()`.
				return expr.Undefined, nil
			}
			switch info.Ident {
			case "now":
				// Return the current date/time.
				return expr.Object(time.Now()), nil
			case "dur":
				// Parse the duration using the first argument.
				d, err := time.ParseDuration(info.Args.At(0).String())
				if err != nil {
					return expr.Undefined, err
				}
				// Valid time.Duration, return as an Int64 value
				return expr.Int64(int64(d)), nil
			default:
				return expr.Undefined, nil
			}
		},
		func(info expr.OpInfo, ctx *expr.Context) (expr.Value, error) {
			// Try to convert a and/or b to time.Time
			left, leftOK := info.Left.Value().(time.Time)
			right, rightOK := info.Right.Value().(time.Time)
			if leftOK && rightOK {
				// Both values are time.Time.
				// Perform comparison operation.
				switch info.Op {
				case expr.OpLt:
					return expr.Bool(left.Before(right)), nil
				}
			} else if leftOK || rightOK {
				// Either A or B are time.Time.
				// Perform arithmatic add/sub operation and return a
				// recalcuated time.Time value.
				var x time.Time
				var y int64
				if leftOK {
					x = left
					y = info.Right.Int64()
				} else {
					x = right
					y = info.Left.Int64()
				}
				switch info.Op {
				case expr.OpAdd:
					return expr.Object(x.Add(time.Duration(y))), nil
				case expr.OpSub:
					return expr.Object(x.Add(-time.Duration(y))), nil
				}
			}
			return expr.Undefined, nil
		},
	)

	// Set up a custom expr.context that holds user data and the extender.
	ctx := expr.Context{UserData: this, Extender: ext}

	var res expr.Value

	// Return the timestamp.
	res, _ = expr.Eval(`this.timestamp`, &ctx)
	fmt.Println(res)

	// Subtract an hour from the timestamp.
	res, _ = expr.Eval(`this.timestamp - dur('1h')`, &ctx)
	fmt.Println(res)

	// Add one day to the current time.
	res, _ = expr.Eval(`now() + dur('24h')`, &ctx)
	fmt.Println(res)

	// See if timestamp is older than a day
	res, _ = expr.Eval(`this.timestamp < now() - dur('24h') ? "old" : "new"`, &ctx)
	fmt.Println(res)

	// Get the center of the bounding box as a concatenated string.
	res, _ = expr.Eval(`((this.minX + this.maxX) / 2) + "," + ((this.minY + this.maxY) / 2)`, &ctx)
	fmt.Println(res)

	// Output:
	// 2022-03-31 09:00:00 +0000 UTC
	// 2022-03-31 08:00:00 +0000 UTC
	// 2022-04-02 06:00:40.834656 -0700 MST m=+86400.000714835
	// old
	// 113.36689999999999,33.905249999999995
}

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	Undefined = Value{/* contains filtered or unexported fields */}
	Null      = Value{/* contains filtered or unexported fields */}
)
View Source
var ErrStop = errors.New("stop")

ErrStop is used to stop the EvalForEach and ForEachValue

Functions

func CharPosOfErr

func CharPosOfErr(err error) int

CharPosOfErr returns the character position of where the error occured in the Eval function, or -1 if unknown

Types

type CallInfo added in v0.7.0

type CallInfo struct {
	Chain bool
	Value Value
	Ident string
	Args  Value
}

type Context added in v0.6.0

type Context struct {
	UserData any
	Extender Extender
	NoCase   bool // Disable case insensitive string comparisons
}

Context for Eval

type Extender

type Extender interface {
	// ref allows for custom evaluation of an property or variable.
	Ref(info RefInfo, ctx *Context) (Value, error)
	// Op allows for custom opererators on values.
	Op(info OpInfo, ctx *Context) (Value, error)
	// Call allows for function call for custom values.
	Call(info CallInfo, ctx *Context) (Value, error)
}

func NewExtender

func NewExtender(
	ref func(info RefInfo, ctx *Context) (Value, error),
	call func(info CallInfo, ctx *Context) (Value, error),
	op func(info OpInfo, ctx *Context) (Value, error),
) Extender

NewExtender is a convenience function for creating a simple extender using the provided eval and op functions.

type Op

type Op string

Op is an operator for Custom values used for the Options.Op function.

const (
	OpAdd    Op = "+"
	OpSub    Op = "-"
	OpMul    Op = "*"
	OpDiv    Op = "/"
	OpMod    Op = "%"
	OpLt     Op = "<"
	OpStEq   Op = "==="
	OpAnd    Op = "&&"
	OpOr     Op = "||"
	OpBitOr  Op = "|"
	OpBitXor Op = "^"
	OpBitAnd Op = "&"
	OpCoal   Op = "??"
)

func (Op) String

func (op Op) String() string

type OpInfo added in v0.7.0

type OpInfo struct {
	Left  Value
	Op    Op
	Right Value
}

type RefInfo added in v0.7.0

type RefInfo struct {
	Chain bool
	Value Value
	Ident string
}

type Value

type Value struct {
	// contains filtered or unexported fields
}

Value represents is the return value of Eval.

func Array added in v0.12.0

func Array(values []Value) Value

func Bool

func Bool(t bool) Value

Bool returns a bool value.

func Eval

func Eval(expr string, ctx *Context) (Value, error)

Eval evaluates an expression and returns the Result.

func EvalForEach added in v0.6.0

func EvalForEach(expr string, iter func(value Value) error, ctx *Context,
) (Value, error)

EvalForEach iterates over a series of comma delimited expressions. The last value in the series is returned. Returning ErrStop will stop the iteration early and return the last known value and nil as an error. Returning any other error from iter will stop the iteration and return the same error.

func Float64

func Float64(x float64) Value

Float64 returns an int64 value.

func Function added in v0.7.0

func Function(name string) Value

Function

func Int64

func Int64(x int64) Value

Int64 returns an int64 value.

func Number

func Number(x float64) Value

Number returns a float64 value.

func Object added in v0.7.0

func Object(o interface{}) Value

Object returns a custom user-defined object.

func String

func String(s string) Value

String returns a string value.

func Uint64

func Uint64(x uint64) Value

Uint64 returns a uint64 value.

func (Value) Array added in v0.12.0

func (a Value) Array() []Value

func (Value) At added in v0.12.0

func (a Value) At(i int) Value

At returns a value from an array at index. Returns 'undefined' if the value is not an array or the index is outside of the bounds.

func (Value) Bool

func (a Value) Bool() bool

Bool returns a boolean representation.

func (Value) Float64

func (a Value) Float64() float64

Float64 returns s float64 representation.

func (Value) Int64

func (a Value) Int64() int64

Int64 returns an int64 representation.

func (Value) IsArray added in v0.12.0

func (a Value) IsArray() bool

IsArray returns true if the value is an 'Array'

func (Value) IsNull added in v0.12.0

func (a Value) IsNull() bool

IsNull returns true if the value is 'null'

func (Value) IsUndefined added in v0.12.0

func (a Value) IsUndefined() bool

IsUndefined returns true if the value is 'undefined'

func (Value) Len added in v0.12.0

func (a Value) Len() int

Len return the length of a String or Array. Returns zero other types.

func (Value) Number

func (a Value) Number() float64

Number returns s float64 representation.

func (Value) Object added in v0.11.0

func (a Value) Object() any

Object returns the native Go representation.

func (Value) String

func (a Value) String() string

String returns a string representation.

func (Value) TypeOf added in v0.7.0

func (a Value) TypeOf() string

func (Value) Uint64

func (a Value) Uint64() uint64

Uint64 returns a uint64 representation.

func (Value) Value

func (a Value) Value() any

Object returns the native Go representation. Same as Value.Object()

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL