eval

package module
v1.1.3 Latest Latest
Warning

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

Go to latest
Published: May 9, 2023 License: Apache-2.0 Imports: 15 Imported by: 0

README

eval

Build Status Coverage Status Go Report Card GoDoc

Package eval implements evaluation of GoLang expression at runtime.

THIS LIB NO MORE MAINTAINED!

For whose who wants to implement golang eval

Suggestions:

  1. Implement 2-steps algorithm: first step - analogue of golang compilation (resolve all types; compute all constants; convert all untyped constant and untyped variables to typed; also simplify AST tree here / convert to your own format), second step - analogue of golang runtime (here will be passed values of external variables and computed final result).
  2. You need to use golang eval package, but keep in mind that it can simplify break your lib on update (golang's backward compatibility does not save you).
  3. Follow language specification (it is hard to implement some additional custom behaviour without breaking compatibility with language accepted expressions).
  4. Language specification may omit some details (so also test with golang compiler).

Requirements for expression:

  1. expression itself and all of subexpression must return exactly one value,
  2. see documentation Bugs section for other requirements/restrictions.

What does supported:

  1. types (by passing predefined/custom types and by defining unnamed types in expression itself),
  2. named arguments (regular variables, typed & untyped constants, untyped boolean variable),
  3. "package.SomeThing" notation,
  4. evaluate expression as if it is evaluates in specified package (even write access to private fields),
  5. evaluate expression like Go evaluates it (following most of specification rules),
  6. position of error in source on evaluation.

Examples:

Simple:

src:="int8(1*(1+2))"
expr,err:=ParseString(src,"")
if err!=nil{
	return err
}
r,err:=expr.EvalToInterface(nil)
if err!=nil{
	return err
}
fmt.Printf("%v %T", r, r)	// "3 int8"

Complicated:

type exampleString string

func (s exampleString) String() exampleString { return "!" + s + "!" }

type exampleStruct struct {
	A, B int
}

func (s exampleStruct) Sum() int { return s.A + s.B }

func main(){
	c := make(chan int64, 10)
	c <- 2

	src := `exampleString(fmt.Sprint(interface{}(math.MaxInt64/exampleStruct(struct{ A, B int }{3, 5}).Sum()+int(<-(<-chan int64)(c))-cap(make([]string, 1, 100))))).String().String() + "."`

	expr, err := ParseString(src, "")
	if err != nil {
		return
	}
	a := Args{
		"exampleString": MakeTypeInterface(exampleString("")),
		"fmt.Sprint":    MakeDataRegularInterface(fmt.Sprint),
		"math.MaxInt64": MakeDataUntypedConst(constanth.MakeUint(math.MaxInt64)),
		"exampleStruct": MakeTypeInterface(exampleStruct{}),
		"c":             MakeDataRegularInterface(c),
	}
	r, err := expr.EvalToInterface(a)
	if err != nil {
		return
	}
	if r != testR {
		return
	}
	fmt.Printf("%v %T\n", r, r)	// "!!1152921504606846877!!. exampleString"
	return
}

With error:

src := `exampleString(fmt.Sprint(interface{}(math.MaxInt64/exampleStruct(struct{ A, B int }{3, 5}).Sum()+int(<-(<-chan int64)(c))-cap(make([]string, 1, 100))))).String().String() + "."`
expr, err := ParseString(src, "")
if err != nil {
	t.Error(err)
}
a := Args{
	"exampleString": MakeTypeInterface(exampleString("")),
	"fmt.Sprint":    MakeDataRegularInterface(fmt.Sprint),
	"math.MaxInt64": MakeDataUntypedConst(constanth.MakeUint(math.MaxInt64)),
	"exampleStruct": MakeTypeInterface(exampleStruct{}),
	// Remove "c" from passed arguments:
	// "c":             MakeDataRegularInterface(c),
}
_, err = expr.EvalToInterface(a)
fmt.Println(err)	// "expression:1:119: undefined: c"

Bugs

Please report bug if you found it.

Documentation

Overview

Package eval implements evaluation of GoLang expression at runtime.

Requirements for expression:

  1. expression itself and all of subexpression must return exactly one value,
  2. see Bugs section for other requirements/restrictions.

What does supported:

  1. types (by passing predefined/custom types and by defining unnamed types in expression itself),
  2. named arguments (regular variables, typed & untyped constants, untyped boolean variable),
  3. "package.SomeThing" notation,
  4. evaluate expression as if it is evaluates in specified package (even write access to private fields),
  5. evaluate expression like Go evaluates it (following most of specification rules),
  6. position of error in source on evaluation.

Supported expression:

  1. identifier ("MyVar")
  2. constant ("123.456")
  3. selector expression ("MyVar.MyField")
  4. call function and method ("a.F(1,a,nil)")
  5. unary expression ("-a")
  6. binary expression ("a+1")
  7. pointer indirection ("*p")
  8. pointer dereferencing ("&a")
  9. parenthesized expression ("2*(1+3)")
  10. channel type ("chan int")
  11. function type ("func(int)string"; only type declaration, not function implementation)
  12. array & slice type ("[...]int{1,2,3}")
  13. interface type ("interface{}"; only empty interface supported)
  14. index expression ("a[i]")
  15. short and full slice expression ("a[1:2:3]")
  16. composite literal ("MyStruct{nil,2,a}")
  17. type conversion ("MyInt(int(1))")
  18. type assertion ("a.(int)"; only single result form)
  19. receiving from channel ("<-c")

Predefined types (no need to pass it via args):

  1. bool
  2. [u]int[8/16/32/64]
  3. float{32/64}
  4. complex{64/128}
  5. byte
  6. rune
  7. uintptr
  8. string

Implemented built-in functions (no need to pass it via args):

  1. len
  2. cap
  3. complex
  4. real
  5. imag
  6. new
  7. make
  8. append

Simple example:

src:="int8(1*(1+2))"
expr,err:=ParseString(src,"")
if err!=nil{
	return err
}
r,err:=expr.EvalToInterface(nil)
if err!=nil{
	return err
}
fmt.Printf("%v %T", r, r)	// "3 int8"

Complicated example:

type exampleString string

func (s exampleString) String() exampleString { return "!" + s + "!" }

type exampleStruct struct {
	A, B int
}

func (s exampleStruct) Sum() int { return s.A + s.B }

func main(){
	c := make(chan int64, 10)
	c <- 2

	src := `exampleString(fmt.Sprint(interface{}(math.MaxInt32/exampleStruct(struct{ A, B int }{3, 5}).Sum()+int(<-(<-chan int64)(c))-cap(make([]string, 1, 100))))).String().String() + "."`

	expr, err := ParseString(src, "")
	if err != nil {
		return
	}
	a := Args{
		"exampleString": MakeTypeInterface(exampleString("")),
		"fmt.Sprint":    MakeDataRegularInterface(fmt.Sprint),
		"math.MaxInt32": MakeDataUntypedConst(constanth.MakeUint(math.MaxInt32)),
		"exampleStruct": MakeTypeInterface(exampleStruct{}),
		"c":             MakeDataRegularInterface(c),
	}
	r, err := expr.EvalToInterface(a)
	if err != nil {
		return
	}
	if r != testR {
		return
	}
	fmt.Printf("%v %T\n", r, r)	// "!!268435357!!. eval.exampleString"
	return
}

Example with error in expression:

src := `exampleString(fmt.Sprint(interface{}(math.MaxInt32/exampleStruct(struct{ A, B int }{3, 5}).Sum()+int(<-(<-chan int64)(c))-cap(make([]string, 1, 100))))).String().String() + "."`
expr, err := ParseString(src, "")
if err != nil {
	t.Error(err)
}
a := Args{
	"exampleString": MakeTypeInterface(exampleString("")),
	"fmt.Sprint":    MakeDataRegularInterface(fmt.Sprint),
	"math.MaxInt32": MakeDataUntypedConst(constanth.MakeUint(math.MaxInt32)),
	"exampleStruct": MakeTypeInterface(exampleStruct{}),
	// Remove "c" from passed arguments:
	// "c":             MakeDataRegularInterface(c),
}
_, err = expr.EvalToInterface(a)
fmt.Println(err)	// "expression:1:119: undefined: c"

Package eval built on top of reflection, go/ast, go/parser, go/token & go/constant. To create Expression it possible to use MakeExpression or some of Parse* functions. All of them use pkgPath to control access rights. pkgPath used when:

1. Accessing struct private field. If type of struct belong to pkgPath then all access to struct fields can modify its value (for example, it is possible to set such fields in composite literal).

2. Creating type in composite literal. All created in expression structures belong to pkgPath.

When Expression is ready it may be evaluated multiple times. Evaluation done via Eval* methods:

  1. EvalRaw - the most flexible, but the hardest to use,
  2. EvalToData,
  3. EvalToRegular,
  4. EvalToInterface - the least flexible, but the easiest to use.

In most cases EvalToInterface should be enough and it is easy to use.

Evaluation performance:

// Parse expression from string
BenchmarkDocParse-8      200000     9118 ns/op    3800 B/op    106 allocs/op
// Eval already parsed expression
BenchmarkDocEval-8       100000    15559 ns/op    6131 B/op    151 allocs/op
// Eval the same expression by Go
BenchmarkDocGoEval-8    5000000      283 ns/op      72 B/op    3 allocs/op

If you found a bug (result of this package evaluation differs from evaluation by Go itself) - please report bug at github.com/apaxa-go/eval.

Index

Constants

View Source
const DefaultFileName = "expression"

DefaultFileName is used as filename if expression constructor does not allow to set custom filename.

Variables

This section is empty.

Functions

This section is empty.

Types

type Args

type Args map[string]Value

Args represents arguments passed into the expression for evaluation. It can be nil if no arguments required for evaluation. Map indexes is the identifiers (possible with short package specification like "strconv.FormatInt" or "mypackage.MySomething", not "github.com/me/mypackage.MySomething"). Map elements is corresponding values. Usually elements is of kind Regular (for typed variables and function), TypedConst (for typed constant) or UntypedConst (for untyped constant). For using function in expression pass it as variable (kind Regular).

func ArgsFromInterfaces

func ArgsFromInterfaces(x map[string]interface{}) Args

ArgsFromInterfaces converts map[string]interface{} to Args (map[string]Value). ArgsI may be useful.

func ArgsFromRegulars

func ArgsFromRegulars(x map[string]reflect.Value) Args

ArgsFromRegulars converts map[string]reflect.Value to Args (map[string]Value). ArgsR may be useful.

type ArgsI

type ArgsI map[string]interface{}

ArgsI is helper class for ArgsFromInterfaces. It can be used in composite literal for this function.

type ArgsR

type ArgsR map[string]reflect.Value

ArgsR is helper class for ArgsFromRegulars. It can be used in composite literal for this function.

type Data

type Data interface {
	// Kind returns current kind of data represented by a Data.
	Kind() DataKind

	// Regular returns regular variable (reflect.Value of it) if data stores regular variable, otherwise it panics.
	Regular() reflect.Value
	// TypedConst returns typed constant if data stores typed constant, otherwise it panics.
	TypedConst() constanth.TypedValue
	// UntypedConst returns untyped constant if data stores untyped constant, otherwise it panics.
	UntypedConst() constant.Value
	// UntypedBool returns value of untyped boolean variable if data stores untyped boolean variable, otherwise it panics.
	UntypedBool() bool

	// IsConst returns true if data stores typed or untyped constant.
	IsConst() bool
	// IsTyped returns true if data stores regular typed variable or typed constant.
	IsTyped() bool

	// AssignableTo reports whether data is assignable to type t.
	AssignableTo(t reflect.Type) bool
	// MustAssign creates new variable of type t and sets it to data.
	// It panics if operation cannot be performed.
	MustAssign(t reflect.Type) reflect.Value
	// Assign creates new variable of type t and tries to set it to data.
	// ok reports whether operation was successful.
	Assign(t reflect.Type) (r reflect.Value, ok bool)
	// ConvertibleTo reports whether data is convertible to type t.
	ConvertibleTo(t reflect.Type) bool
	// MustConvert converts data to type t.
	// Original data remains unchanged.
	// It panics if operation cannot be performed.
	MustConvert(t reflect.Type) Data
	// Convert tries to convert data to type t.
	// Original data remains unchanged.
	// ok reports whether operation was successful.
	Convert(t reflect.Type) (r Data, ok bool)

	// AsInt returns int value for regular variable of [u]int* kinds (if feats in int) and for constants (if it can be represent exactly; type of const does not mean anything).
	// ok reports whether operation was successful.
	AsInt() (r int, ok bool)

	// DeepType returns human readable representation of stored data's type.
	// Example: "untyped constant".
	DeepType() string
	// DeepValue returns human readable representation of stored data's value (without its type).
	// Example: "1".
	DeepValue() string
	// DeepString returns human readable representation of stored data's type and value.
	// Result is in form of "<DeepValue> (type <DeepType>)".
	DeepString() string
	// contains filtered or unexported methods
}

Data used to store all kind of data passed to/returned from expression. It can stores: nil (keyword "nil"), regular typed variable, typed constant, untyped constant and untyped boolean variable (result of comparison). GoLang valid expression cannot return nil (it is just a keyword), but it is presented here for internal purposes.

func MakeNil

func MakeNil() Data

MakeNil makes Data which stores "nil".

func MakeRegular

func MakeRegular(x reflect.Value) Data

MakeRegular makes Data which stores regular typed variable x (x is a reflect.Value of required variable).

func MakeRegularInterface

func MakeRegularInterface(x interface{}) Data

MakeRegularInterface makes Data which stores regular typed variable x (x is a required variable).

func MakeTypedConst

func MakeTypedConst(x constanth.TypedValue) Data

MakeTypedConst makes Data which stores typed constant x.

func MakeUntypedBool

func MakeUntypedBool(x bool) Data

MakeUntypedBool makes Data which stores untyped boolean variable with value x.

func MakeUntypedConst

func MakeUntypedConst(x constant.Value) Data

MakeUntypedConst makes Data which stores untyped constant x.

type DataKind

type DataKind int

DataKind specifies the kind of value represented by a Value.

const (
	Nil          DataKind = iota // just nil (result of parsing keyword "nil")
	Regular                      // regular typed variable
	TypedConst                   // typed constant
	UntypedConst                 // untyped constant
	UntypedBool                  // untyped boolean variable
)

Possible values for value's kind:

func (DataKind) String

func (k DataKind) String() string

String returns human readable representation of data's kind.

type Error

type Error struct {
	Msg string        // description of problem
	Pos asth.Position // position of problem
}

Error is used for all errors related to passed expression. It describes problem (as text) and (in most cases) position of problem.

func (Error) Error

func (err Error) Error() string

Error implements standard error interface.

type Expression

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

Expression store expression for evaluation. It allows evaluate expression multiple times. The zero value for Expression is invalid and causes undefined behaviour.

func MakeExpression

func MakeExpression(e ast.Expr, fset *token.FileSet, pkgPath string) *Expression

MakeExpression make expression with specified arguments. It does not perform any validation of arguments. e is AST of expression. fset is used to describe position of error and must be non nil (use token.NewFileSet instead). pkgPath is fully qualified package name, for more details see package level documentation.

func Parse

func Parse(filename string, src interface{}, pkgPath string) (r *Expression, err error)

Parse parses filename or src for expression using parser.ParseExprFrom. For information about filename and src see parser.ParseExprFrom documentation. pkgPath is fully qualified package name, for more details see package level documentation.

func ParseBytes

func ParseBytes(src []byte, pkgPath string) (r *Expression, err error)

ParseBytes parses expression from []byte src. pkgPath is fully qualified package name, for more details see package level documentation.

func ParseReader

func ParseReader(src io.Reader, pkgPath string) (r *Expression, err error)

ParseReader parses expression from io.Reader src. pkgPath is fully qualified package name, for more details see package level documentation.

func ParseString

func ParseString(src string, pkgPath string) (r *Expression, err error)

ParseString parses expression from string src. pkgPath is fully qualified package name, for more details see package level documentation.

func (*Expression) EvalRaw

func (e *Expression) EvalRaw(args Args) (r Value, err error)

EvalRaw evaluates expression with given arguments args. Result of evaluation is Value.

func (*Expression) EvalToData

func (e *Expression) EvalToData(args Args) (r Data, err error)

EvalToData evaluates expression with given arguments args. It returns error if result of evaluation is not Data.

func (*Expression) EvalToInterface

func (e *Expression) EvalToInterface(args Args) (r interface{}, err error)

EvalToInterface evaluates expression with given arguments args. It returns error if result of evaluation is not Data or if it is impossible to represent it as variable using GoLang assignation rules.

func (*Expression) EvalToRegular

func (e *Expression) EvalToRegular(args Args) (r reflect.Value, err error)

EvalToRegular evaluates expression with given arguments args. It returns error if result of evaluation is not Data or if it is impossible to represent it as variable using GoLang assignation rules.

type Value

type Value interface {
	// Kind returns current kind of value represented by a Value.
	Kind() ValueKind

	// DeepType returns human readable kind of stored value. If value stores Data then result is kind of this Data (deep resolution).
	DeepType() string
	// String returns string human-readable representation of underlying value.
	String() string

	// Data returns data if value stores data, otherwise it panics.
	Data() Data
	// Type returns type if value stores type, otherwise it panics.
	Type() reflect.Type
	// BuiltInFunc returns name of built-in function if value stores built-in function, otherwise it panics.
	BuiltInFunc() string
	// Package returns package as map[<identifier>]<variable/method/...> if value stores package, otherwise it panics. Name of package itself is unknown.
	Package() map[string]Value
	// contains filtered or unexported methods
}

Value used to store arguments passed to/returned from expression. It can stores: data, type, built-in function and package. GoLang valid expression can return only data, all other kind is primary used internally while evaluation.

func MakeBuiltInFunc

func MakeBuiltInFunc(x string) Value

MakeBuiltInFunc makes Value which stores built-in function specified by its name x. MakeBuiltInFunc does not perform any checks for validating name.

func MakeData

func MakeData(x Data) Value

MakeData makes Value which stores data x.

func MakeDataNil

func MakeDataNil() Value

MakeDataNil makes Value which stores data "nil".

func MakeDataRegular

func MakeDataRegular(x reflect.Value) Value

MakeDataRegular makes Value which stores regular data x (x is a reflect.Value of required variable).

func MakeDataRegularInterface

func MakeDataRegularInterface(x interface{}) Value

MakeDataRegularInterface makes Value which stores regular data x (x is a required variable).

func MakeDataTypedConst

func MakeDataTypedConst(x constanth.TypedValue) Value

MakeDataTypedConst makes Value which stores typed constant x.

func MakeDataUntypedBool

func MakeDataUntypedBool(x bool) Value

MakeDataUntypedBool makes Value which stores untyped boolean variable with value x.

func MakeDataUntypedConst

func MakeDataUntypedConst(x constant.Value) Value

MakeDataUntypedConst makes Value which stores untyped constant x.

func MakePackage

func MakePackage(x Args) Value

MakePackage makes Value which stores package x. x must be a map[<identifier>]<variable/method/...>. No name of package itself specified. Keys in args must not have dots in names.

func MakeType

func MakeType(x reflect.Type) Value

MakeType makes Value which stores type x.

func MakeTypeInterface

func MakeTypeInterface(x interface{}) Value

MakeTypeInterface makes Value which stores type of x.

type ValueKind

type ValueKind int

ValueKind specifies the kind of value represented by a Value.

const (
	Datas       ValueKind = iota // value is Data
	Type                         // type
	BuiltInFunc                  // built-in function
	Package                      // package
)

Possible values for value's kind:

Notes

Bugs

  • Eval* currently does not generate wrapper methods for embedded fields in structures (see reflect.StructOf for more details).

  • Only empty interface type can be declared in expression.

  • make(<map>,n) ignore n (but check it type).

  • Builtin function len does not fully following GoLang spec (it always returns typed int constant for arrays & pointers to array).

  • Builtin function cap does not fully following GoLang spec (always returns int instead of untyped for array & pointer to array).

Jump to

Keyboard shortcuts

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