expr

package module
v0.6.2-number Latest Latest
Warning

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

Go to latest
Published: Apr 9, 2022 License: MIT Imports: 6 Imported by: 0

README

expr

GoDoc

Expression evaluator for Go

This is a fork from tidwall/expr with arbitrary precision achieved with shopspring/decimal.

About this fork

The purpose of this fork is to implement arbitrary precision arithmetic with shopspring/decimal.

Some concepts have been evolved, either as a result of using shopspring/decimal or by personal decision:

  • No arithmetic operations (+, -, etc) on booleans or nulls.
  • No concept of infinity or "NaN" - replaced by "undefined" instead.
  • ival, uval and fval are replaced by nval (number value) which is a decimal.Decimal.
  • number precision (for calculations) defaults to 20. Change it with SetNumberPrecision
  • number truncation (for displaying number values) defaults to 10. Change it with SetNumberTruncation

Features

  • Operators: + - * / % ! < <= == != > >= ?:
  • Types: String, Number, Boolean, and Custom types
  • Parenthesized expressions
  • Javascript-like syntax and automatic type conversions

Using

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

$ go get github.com/seborama/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:

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

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

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

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

// Add a timestamp value to the user data map.
ts, _ := time.Parse(time.RFC3339, "2022-03-31T09:00:00Z")
umap["timestamp"] = Custom(ts)

// Set up an evaluation extender for referencing the user data and
// using operators on custom types.
ext := NewExtender(
	func(expr string, udata any) (Value, error) {
		switch expr {
		case "now":
			// Get the seconds since Epoch.
			return Custom(time.Now()), nil
		default:
			if len(expr) >= 1 && expr[0] == '$' {
				// Try parsing a time.Duration.
				s := expr[1:]
				d, err := time.ParseDuration(s)
				if err != nil {
					return Undefined, err
				}
				// Valid time.Duration, return as an Int64 value
				return Int64(int64(d)), nil
			}
			// Not a time.Duration, check the umap for the data
			umap, ok := udata.(map[string]Value)
			if !ok {
				return Undefined, ErrUndefined
			}
			return umap[expr], nil
		}
	},
	func(op Op, a, b Value, udata any) (Value, error) {
		// Try to convert a and/or b to time.Time
		at, aok := a.Value().(time.Time)
		bt, bok := b.Value().(time.Time)
		if aok && bok {
			// Both values are time.Time.
			// Perform comparison operation.
			switch op {
			case OpLt:
				return Bool(at.Before(bt)), nil
			case OpLte:
				return Bool(!bt.After(at)), nil
			case OpGt:
				return Bool(at.After(bt)), nil
			case OpGte:
				return Bool(!at.Before(bt)), nil
			}
		} else if aok || bok {
			// 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 aok {
				x = at
				y = b.Int64()
			} else {
				x = bt
				y = a.Int64()
			}
			switch op {
			case OpAdd:
				return Custom(x.Add(time.Duration(y))), nil
			case OpSub:
				return Custom(x.Add(-time.Duration(y))), nil
			}
		}
		return Undefined, ErrUndefined
	},
)

// Set up the options
opts := Options{UserData: umap, Extender: ext}

var res Value

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

// Subtract an hour from the timestamp.
res, _ = Eval(`timestamp - $1h`, &opts)
fmt.Println(res)

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

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

// Get the center of the bounding box as a concatenated string.
res, _ = Eval(`((minX + maxX) / 2) + "," + ((minY + maxY) / 2)`, &opts)
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 ErrUndefined = errors.New("undefined")

Functions

func CharPosOfErr

func CharPosOfErr(err error) int

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

func SetNumberPrecision

func SetNumberPrecision(n int32)

func SetNumberTruncation

func SetNumberTruncation(n int32)

Types

type Extender

type Extender interface {
	// Eval allows for custom evaluation of an expression.
	Eval(expr string, udata any) (Value, error)
	// Op allows for custom evaluation of an expression.
	Op(op Op, a, b Value, udata any) (Value, error)
}

func NewExtender

func NewExtender(
	eval func(expr string, udata any) (Value, error),
	op func(op Op, a, b Value, udata any) (Value, error),
) Extender

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

type Op

type Op int

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

const (
	OpAdd Op // +
	OpSub    // -
	OpMul    // *
	OpDiv    // /
	OpMod    // %
	OpLt     // <
	OpLte    // <=
	OpGt     // >
	OpGte    // >=
	OpEq     // ==
	OpNeq    // !=
	OpAnd    // &&
	OpOr     // ||
)

func (Op) String

func (op Op) String() string

type Options

type Options struct {
	UserData any
	Extender Extender
}

Options for Eval

type Value

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

Value represents is the return value of Eval.

func Bool

func Bool(t bool) Value

Bool returns a bool value.

func Custom

func Custom(v interface{}) Value

Custom returns a custom user-defined value.

func Eval

func Eval(expr string, opts *Options) (Value, error)

Eval evaluates an expression and returns the Result.

func Number

func Number(x float64) Value

Number returns a number value.

func String

func String(s string) Value

String returns a string value.

func StringNumber

func StringNumber(s string) Value

StringNumber returns a number value constructed from a string.

func (Value) Bool

func (a Value) Bool() bool

Bool returns a boolean representation.

func (Value) IsCustom

func (a Value) IsCustom() bool

func (Value) Number

func (a Value) Number() float64

Number returns s float64 representation.

func (Value) String

func (a Value) String() string

String returns a string representation.

func (Value) Value

func (a Value) Value() interface{}

Value returns the native Go representation, which is one of the following:

bool, int64, uint64, float64, string, or nil (if undefined)

Jump to

Keyboard shortcuts

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