gval

package module
v1.1.3 Latest Latest
Warning

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

Go to latest
Published: Oct 31, 2021 License: BSD-3-Clause Imports: 12 Imported by: 1

README

Gval

Go Reference Build Status Coverage Status Go Report Card

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

gopher

Evaluate

Gval can evaluate expressions with parameters, arimethetic, logical, and string operations:

It can easily be extended with custom functions or operators:

You can parse gval.Expressions once and re-use them multiple times. Parsing is the compute-intensive phase of the process, so if you intend to use the same expression with different parameters, just parse it once:

The normal Go-standard order of operators is respected. When writing an expression, be sure that you either order the operators correctly, or use parentheses to clarify which portions of an expression should be run first.

Strings, numbers, and booleans can be used like in Go:

Parameter

Variables can be accessed via string literals. They can be used for values with string keys if the parameter is a map[string]interface{} or map[interface{}]interface{} and for fields or methods if the parameter is a struct.

Bracket Selector

Map and array elements and Struct Field can be accessed via [].

Dot Selector

A nested variable with a name containing only letters and underscores can be accessed via a dot selector.

Custom Selector

Parameter names like response-time will be interpreted as response minus time. While gval doesn't support these parameter names directly, you can easily access them via a custom extension like JSON Path:

Jsonpath is also suitable for accessing array elements.

Fields and Methods

If you have structs in your parameters, you can access their fields and methods in the usual way:

It also works if the parameter is a struct directly Hello + World() or if the fields are nested foo.Hello + foo.World()

This may be convenient but note that using accessors on strucs makes the expression about four times slower than just using a parameter (consult the benchmarks for more precise measurements on your system). If there are functions you want to use, it's faster (and probably cleaner) to define them as functions (see the Evaluate section). These approaches use no reflection, and are designed to be fast and clean.

Default Language

The default language is in serveral sub languages like text, arithmetic or propositional logic defined. See Godoc for details. All sub languages are merged into gval.Full which contains the following elements:

  • Modifiers: + - / * & | ^ ** % >> <<
  • Comparators: > >= < <= == != =~ !~
  • Logical ops: || &&
  • Numeric constants, as 64-bit floating point (12345.678)
  • String constants (double quotes: "foobar")
  • Date function 'Date(x)', using any permutation of RFC3339, ISO8601, ruby date, or unix date
  • Boolean constants: true false
  • Parentheses to control order of evaluation ( )
  • Json Arrays : [1, 2, "foo"]
  • Json Objects : {"a":1, "b":2, "c":"foo"}
  • Prefixes: ! - ~
  • Ternary conditional: ? :
  • Null coalescence: ??

Customize

Gval is completly customizable. Every constant, function or operator can be defined separately and existing expression languages can be reused:

For details see Godoc.

Implementing custom selector

In a case you want to provide custom logic for selectors you can implement SelectGVal(ctx context.Context, k string) (interface{}, error) on your struct. Function receives next part of the path and can return any type of var that is again evaluated through standard gval procedures.

Example Custom Selector

External gval Languages

A list of external libraries for gval. Feel free to add your own library.

Performance

The library is built with the intention of being quick but has not been aggressively profiled and optimized. For most applications, though, it is completely fine. If performance is an issue, make sure to create your expression language with all functions, constants and operators only once. Evaluating an expression like gval.Evaluate("expression, const1, func1, func2, ...) creates a new gval.Language everytime it is called and slows execution.

The library comes with a bunch of benchmarks to measure the performance of parsing and evaluating expressions. You can run them with go test -bench=..

For a very rough idea of performance, here are the results from a benchmark run on a Dell Latitude E7470 Win 10 i5-6300U.

BenchmarkGval/const_evaluation-4                               500000000                 3.57 ns/op
BenchmarkGval/const_parsing-4                                    1000000              1144 ns/op
BenchmarkGval/single_parameter_evaluation-4                     10000000               165 ns/op
BenchmarkGval/single_parameter_parsing-4                         1000000              1648 ns/op
BenchmarkGval/parameter_evaluation-4                             5000000               352 ns/op
BenchmarkGval/parameter_parsing-4                                 500000              2773 ns/op
BenchmarkGval/common_evaluation-4                                3000000               434 ns/op
BenchmarkGval/common_parsing-4                                    300000              4419 ns/op
BenchmarkGval/complex_evaluation-4                             100000000                11.6 ns/op
BenchmarkGval/complex_parsing-4                                   100000             17936 ns/op
BenchmarkGval/literal_evaluation-4                             300000000                 3.84 ns/op
BenchmarkGval/literal_parsing-4                                   500000              2559 ns/op
BenchmarkGval/modifier_evaluation-4                            500000000                 3.54 ns/op
BenchmarkGval/modifier_parsing-4                                  500000              3755 ns/op
BenchmarkGval/regex_evaluation-4                                   50000             21347 ns/op
BenchmarkGval/regex_parsing-4                                     200000              6480 ns/op
BenchmarkGval/constant_regex_evaluation-4                        1000000              1000 ns/op
BenchmarkGval/constant_regex_parsing-4                            200000              9417 ns/op
BenchmarkGval/accessors_evaluation-4                             3000000               417 ns/op
BenchmarkGval/accessors_parsing-4                                1000000              1778 ns/op
BenchmarkGval/accessors_method_evaluation-4                      1000000              1931 ns/op
BenchmarkGval/accessors_method_parsing-4                         1000000              1729 ns/op
BenchmarkGval/accessors_method_parameter_evaluation-4            1000000              2162 ns/op
BenchmarkGval/accessors_method_parameter_parsing-4                500000              2618 ns/op
BenchmarkGval/nested_accessors_evaluation-4                      2000000               681 ns/op
BenchmarkGval/nested_accessors_parsing-4                         1000000              2115 ns/op
BenchmarkRandom-4                                                 500000              3631 ns/op
ok

API Breaks

Gval is designed with easy expandability in mind and API breaks will be avoided if possible. If API breaks are unavoidable they wil be explicitly stated via an increased major version number.


Credits to Reene French for the gophers.

Documentation

Overview

Package gval provides a generic expression language. All functions, infix and prefix operators can be replaced by composing languages into a new one.

The package contains concrete expression languages for common application in text, arithmetic, decimal arithmetic, propositional logic and so on. They can be used as basis for a custom expression language or to evaluate expressions directly.

Example
package main

import (
	"fmt"

	"github.com/PaesslerAG/gval"
)

func main() {

	vars := map[string]interface{}{"name": "World"}

	value, err := gval.Evaluate(`"Hello " + name + "!"`, vars)
	if err != nil {
		fmt.Println(err)
	}

	fmt.Print(value)

}
Output:

Hello World!

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Evaluate

func Evaluate(expression string, parameter interface{}, opts ...Language) (interface{}, error)

Evaluate given parameter with given expression in gval full language

Example
package main

import (
	"fmt"

	"github.com/PaesslerAG/gval"
)

func main() {

	value, err := gval.Evaluate("foo > 0", map[string]interface{}{
		"foo": -1.,
	})
	if err != nil {
		fmt.Println(err)
	}

	fmt.Print(value)

}
Output:

false
Example (Accessor)
package main

import (
	"fmt"

	"github.com/PaesslerAG/gval"
)

type exampleType struct {
	Hello string
}

func (e exampleType) World() string {
	return "world"
}

func main() {

	value, err := gval.Evaluate(`foo.Hello + foo.World()`,
		map[string]interface{}{
			"foo": exampleType{Hello: "hello "},
		})
	if err != nil {
		fmt.Println(err)
	}

	fmt.Print(value)

}
Output:

hello world
Example (Arithmetic)
package main

import (
	"fmt"

	"github.com/PaesslerAG/gval"
)

func main() {

	value, err := gval.Evaluate("(requests_made * requests_succeeded / 100) >= 90",
		map[string]interface{}{
			"requests_made":      100,
			"requests_succeeded": 80,
		})
	if err != nil {
		fmt.Println(err)
	}

	fmt.Print(value)

}
Output:

false
Example (Array)
package main

import (
	"fmt"

	"github.com/PaesslerAG/gval"
)

func main() {

	value, err := gval.Evaluate("foo[0]", map[string]interface{}{
		"foo": []interface{}{-1.},
	})
	if err != nil {
		fmt.Println(err)
	}

	fmt.Print(value)

}
Output:

-1
Example (ComplexAccessor)
package main

import (
	"fmt"

	"github.com/PaesslerAG/gval"
)

func main() {

	value, err := gval.Evaluate(`foo["b" + "a" + "r"]`, map[string]interface{}{
		"foo": map[string]interface{}{"bar": -1.},
	})
	if err != nil {
		fmt.Println(err)
	}

	fmt.Print(value)

}
Output:

-1
Example (DateComparison)
package main

import (
	"fmt"
	"time"

	"github.com/PaesslerAG/gval"
)

func main() {

	value, err := gval.Evaluate("date(`2014-01-02`) > date(`2014-01-01 23:59:59`)",
		nil,
		// define Date comparison because it is not part expression language gval
		gval.InfixOperator(">", func(a, b interface{}) (interface{}, error) {
			date1, ok1 := a.(time.Time)
			date2, ok2 := b.(time.Time)

			if ok1 && ok2 {
				return date1.After(date2), nil
			}
			return nil, fmt.Errorf("unexpected operands types (%T) > (%T)", a, b)
		}),
	)
	if err != nil {
		fmt.Println(err)
	}

	fmt.Print(value)

}
Output:

true
Example (Encoding)
package main

import (
	"fmt"

	"github.com/PaesslerAG/gval"
)

func main() {

	value, err := gval.Evaluate(`(7 < "47" == true ? "hello world!\n\u263a" : "good bye\n")`+" + ` more text`",
		nil,
		gval.Function("strlen", func(args ...interface{}) (interface{}, error) {
			length := len(args[0].(string))
			return (float64)(length), nil
		}))
	if err != nil {
		fmt.Println(err)
	}

	fmt.Print(value)

}
Output:

hello world!
☺ more text
Example (FlatAccessor)
package main

import (
	"fmt"

	"github.com/PaesslerAG/gval"
)

type exampleType struct {
	Hello string
}

func (e exampleType) World() string {
	return "world"
}

func main() {

	value, err := gval.Evaluate(`Hello + World()`,
		exampleType{Hello: "hello "},
	)
	if err != nil {
		fmt.Println(err)
	}

	fmt.Print(value)

}
Output:

hello world
Example (Float64)
package main

import (
	"fmt"

	"github.com/PaesslerAG/gval"
)

func main() {

	value, err := gval.Evaluate("(mem_used / total_mem) * 100",
		map[string]interface{}{
			"total_mem": 1024,
			"mem_used":  512,
		})
	if err != nil {
		fmt.Println(err)
	}

	fmt.Print(value)

}
Output:

50
Example (Jsonpath)
package main

import (
	"fmt"

	"github.com/PaesslerAG/gval"
	"github.com/PaesslerAG/jsonpath"
)

func main() {

	value, err := gval.Evaluate(`$["response-time"]`,
		map[string]interface{}{
			"response-time": 100,
		},
		jsonpath.Language(),
	)
	if err != nil {
		fmt.Println(err)
	}

	fmt.Print(value)

}
Output:

100
Example (NestedAccessor)
package main

import (
	"fmt"

	"github.com/PaesslerAG/gval"
)

type exampleType struct {
	Hello string
}

func (e exampleType) World() string {
	return "world"
}

func main() {

	value, err := gval.Evaluate(`foo.Bar.Hello + foo.Bar.World()`,
		map[string]interface{}{
			"foo": struct{ Bar exampleType }{
				Bar: exampleType{Hello: "hello "},
			},
		})
	if err != nil {
		fmt.Println(err)
	}

	fmt.Print(value)

}
Output:

hello world
Example (NestedParameter)
package main

import (
	"fmt"

	"github.com/PaesslerAG/gval"
)

func main() {

	value, err := gval.Evaluate("foo.bar > 0", map[string]interface{}{
		"foo": map[string]interface{}{"bar": -1.},
	})
	if err != nil {
		fmt.Println(err)
	}

	fmt.Print(value)

}
Output:

false
Example (String)
package main

import (
	"fmt"

	"github.com/PaesslerAG/gval"
)

func main() {

	value, err := gval.Evaluate(`http_response_body == "service is ok"`,
		map[string]interface{}{
			"http_response_body": "service is ok",
		})
	if err != nil {
		fmt.Println(err)
	}

	fmt.Print(value)

}
Output:

true
Example (Strlen)
package main

import (
	"fmt"

	"github.com/PaesslerAG/gval"
)

func main() {

	value, err := gval.Evaluate(`strlen("someReallyLongInputString") <= 16`,
		nil,
		gval.Function("strlen", func(args ...interface{}) (interface{}, error) {
			length := len(args[0].(string))
			return (float64)(length), nil
		}))
	if err != nil {
		fmt.Println(err)
	}

	fmt.Print(value)

}
Output:

false

func EvaluateWithContext added in v1.1.3

func EvaluateWithContext(c context.Context, expression string, parameter interface{}, opts ...Language) (interface{}, error)

Evaluate given parameter with given expression in gval full language using a context

Types

type Evaluable

type Evaluable func(c context.Context, parameter interface{}) (interface{}, error)

Evaluable evaluates given parameter

Example
package main

import (
	"context"
	"fmt"

	"github.com/PaesslerAG/gval"
)

func main() {
	eval, err := gval.Full(gval.Constant("maximum_time", 52)).
		NewEvaluable("response_time <= maximum_time")
	if err != nil {
		fmt.Println(err)
	}

	for i := 50; i < 55; i++ {
		value, err := eval(context.Background(), map[string]interface{}{
			"response_time": i,
		})
		if err != nil {
			fmt.Println(err)

		}

		fmt.Println(value)
	}

}
Output:

true
true
true
false
false

func (Evaluable) EvalBool

func (e Evaluable) EvalBool(c context.Context, parameter interface{}) (bool, error)

EvalBool evaluates given parameter to a bool

Example
package main

import (
	"context"
	"fmt"

	"github.com/PaesslerAG/gval"
)

func main() {
	eval, err := gval.Full().NewEvaluable("1 == x")
	if err != nil {
		fmt.Println(err)
		return
	}

	value, err := eval.EvalBool(context.Background(), map[string]interface{}{"x": 1})
	if err != nil {
		fmt.Println(err)
	}

	if value {
		fmt.Print("yeah")
	}

}
Output:

yeah

func (Evaluable) EvalFloat64

func (e Evaluable) EvalFloat64(c context.Context, parameter interface{}) (float64, error)

EvalFloat64 evaluates given parameter to a float64

func (Evaluable) EvalInt

func (e Evaluable) EvalInt(c context.Context, parameter interface{}) (int, error)

EvalInt evaluates given parameter to an int

Example
package main

import (
	"context"
	"fmt"

	"github.com/PaesslerAG/gval"
)

func main() {
	eval, err := gval.Full().NewEvaluable("1 + x")
	if err != nil {
		fmt.Println(err)
		return
	}

	value, err := eval.EvalInt(context.Background(), map[string]interface{}{"x": 5})
	if err != nil {
		fmt.Println(err)
	}

	fmt.Print(value)

}
Output:

6

func (Evaluable) EvalString

func (e Evaluable) EvalString(c context.Context, parameter interface{}) (string, error)

EvalString evaluates given parameter to a string

func (Evaluable) IsConst

func (e Evaluable) IsConst() bool

IsConst returns if the Evaluable is a Parser.Const() value

type Evaluables added in v1.1.3

type Evaluables []Evaluable

Evaluables is a slice of Evaluable.

func (Evaluables) EvalStrings added in v1.1.3

func (evs Evaluables) EvalStrings(c context.Context, parameter interface{}) ([]string, error)

EvalStrings evaluates given parameter to a string slice

type Language

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

Language is an expression language

Example
package main

import (
	"context"
	"fmt"

	"github.com/PaesslerAG/gval"
)

func main() {
	lang := gval.NewLanguage(gval.JSON(), gval.Arithmetic(),
		//pipe operator
		gval.PostfixOperator("|", func(c context.Context, p *gval.Parser, pre gval.Evaluable) (gval.Evaluable, error) {
			post, err := p.ParseExpression(c)
			if err != nil {
				return nil, err
			}
			return func(c context.Context, v interface{}) (interface{}, error) {
				v, err := pre(c, v)
				if err != nil {
					return nil, err
				}
				return post(c, v)
			}, nil
		}))

	eval, err := lang.NewEvaluable(`{"foobar": 50} | foobar + 100`)
	if err != nil {
		fmt.Println(err)
	}

	value, err := eval(context.Background(), nil)

	if err != nil {
		fmt.Println(err)
	}

	fmt.Println(value)

}
Output:

150

func Arithmetic

func Arithmetic() Language

Arithmetic contains base, plus(+), minus(-), divide(/), power(**), negative(-) and numerical order (<=,<,>,>=)

Arithmetic operators expect float64 operands. Called with unfitting input, they try to convert the input to float64. They can parse strings and convert any type of int or float.

func Base

func Base() Language

Base contains equal (==) and not equal (!=), perentheses and general support for variables, constants and functions It contains true, false, (floating point) number, string ("" or “) and char (”) constants

func Bitmask

func Bitmask() Language

Bitmask contains base, bitwise and(&), bitwise or(|) and bitwise not(^).

Bitmask operators expect float64 operands. Called with unfitting input they try to convert the input to float64. They can parse strings and convert any type of int or float.

func Constant

func Constant(name string, value interface{}) Language

Constant returns a Language with given constant

func DecimalArithmetic added in v1.1.3

func DecimalArithmetic() Language

DecimalArithmetic contains base, plus(+), minus(-), divide(/), power(**), negative(-) and numerical order (<=,<,>,>=)

DecimalArithmetic operators expect decimal.Decimal operands (github.com/shopspring/decimal) and are used to calculate money/decimal rather than floating point calculations. Called with unfitting input, they try to convert the input to decimal.Decimal. They can parse strings and convert any type of int or float.

func DefaultExtension added in v1.1.3

func DefaultExtension(ext func(context.Context, *Parser) (Evaluable, error)) Language

DefaultExtension is a language that runs the given function if no other prefix matches.

func Full

func Full(extensions ...Language) Language

Full is the union of Arithmetic, Bitmask, Text, PropositionalLogic, and Json

Operator in: a in b is true iff value a is an element of array b
Operator ??: a ?? b returns a if a is not false or nil, otherwise n
Operator ?: a ? b : c returns b if bool a is true, otherwise b

Function Date: Date(a) parses string a. a must match RFC3339, ISO8601, ruby date, or unix date

func Function

func Function(name string, function interface{}) Language

Function returns a Language with given function. Function has no conversion for input types.

If the function returns an error it must be the last return parameter.

If the function has (without the error) more then one return parameter, it returns them as []interface{}.

func Ident added in v1.1.3

func Ident() Language

Ident contains support for variables and functions.

func InfixBoolOperator

func InfixBoolOperator(name string, f func(a, b bool) (interface{}, error)) Language

InfixBoolOperator for two bool values.

func InfixDecimalOperator added in v1.1.3

func InfixDecimalOperator(name string, f func(a, b decimal.Decimal) (interface{}, error)) Language

InfixDecimalOperator for two decimal values.

func InfixEvalOperator

func InfixEvalOperator(name string, f func(a, b Evaluable) (Evaluable, error)) Language

InfixEvalOperator operates on the raw operands. Therefore it cannot be combined with operators for other operand types.

func InfixNumberOperator

func InfixNumberOperator(name string, f func(a, b float64) (interface{}, error)) Language

InfixNumberOperator for two number values.

func InfixOperator

func InfixOperator(name string, f func(a, b interface{}) (interface{}, error)) Language

InfixOperator for two arbitrary values.

func InfixShortCircuit

func InfixShortCircuit(name string, f func(a interface{}) (interface{}, bool)) Language

InfixShortCircuit operator is called after the left operand is evaluated.

func InfixTextOperator

func InfixTextOperator(name string, f func(a, b string) (interface{}, error)) Language

InfixTextOperator for two text values.

func Init added in v1.1.3

func Init(ext func(context.Context, *Parser) (Evaluable, error)) Language

Init is a language that does no parsing, but invokes the given function when parsing starts. It is incumbent upon the function to call ParseExpression to continue parsing.

This function can be used to customize the parser settings, such as whitespace or ident behavior.

func JSON

func JSON() Language

JSON contains json objects ({string:expression,...}) and json arrays ([expression, ...])

func NewLanguage

func NewLanguage(bases ...Language) Language

NewLanguage returns the union of given Languages as new Language.

func Parentheses added in v1.1.3

func Parentheses() Language

Parentheses contains support for parentheses.

func PostfixOperator

func PostfixOperator(name string, ext func(context.Context, *Parser, Evaluable) (Evaluable, error)) Language

PostfixOperator extends a Language.

func Precedence

func Precedence(name string, operatorPrecendence uint8) Language

Precedence of operator. The Operator with higher operatorPrecedence is evaluated first.

func PrefixExtension

func PrefixExtension(r rune, ext func(context.Context, *Parser) (Evaluable, error)) Language

PrefixExtension extends a Language

func PrefixMetaPrefix

func PrefixMetaPrefix(r rune, ext func(context.Context, *Parser) (call string, alternative func() (Evaluable, error), err error)) Language

PrefixMetaPrefix chooses a Prefix to be executed

func PrefixOperator

func PrefixOperator(name string, e Evaluable) Language

PrefixOperator returns a Language with given prefix

func PropositionalLogic

func PropositionalLogic() Language

PropositionalLogic contains base, not(!), and (&&), or (||) and Base.

Propositional operator expect bool operands. Called with unfitting input they try to convert the input to bool. Numbers other than 0 and the strings "TRUE" and "true" are interpreted as true. 0 and the strings "FALSE" and "false" are interpreted as false.

func Text

func Text() Language

Text contains base, lexical order on strings (<=,<,>,>=), regex match (=~) and regex not match (!~)

func VariableSelector added in v1.1.3

func VariableSelector(selector func(path Evaluables) Evaluable) Language

VariableSelector returns a Language which uses given variable selector. It must be combined with a Language that uses the vatiable selector. E.g. gval.Base().

Example
package main

import (
	"context"
	"fmt"
	"strings"

	"github.com/PaesslerAG/gval"
)

func main() {
	value, err := gval.Evaluate(`hello.world`,
		"!",
		gval.VariableSelector(func(path gval.Evaluables) gval.Evaluable {
			return func(c context.Context, v interface{}) (interface{}, error) {
				keys, err := path.EvalStrings(c, v)
				if err != nil {
					return nil, err
				}
				return fmt.Sprintf("%s%s", strings.Join(keys, " "), v), nil
			}
		}),
	)
	if err != nil {
		fmt.Println(err)
	}

	fmt.Print(value)

}
Output:

hello world!

func (Language) Evaluate

func (l Language) Evaluate(expression string, parameter interface{}) (interface{}, error)

Evaluate given parameter with given expression

func (Language) EvaluateWithContext added in v1.1.3

func (l Language) EvaluateWithContext(c context.Context, expression string, parameter interface{}) (interface{}, error)

Evaluate given parameter with given expression using context

func (Language) NewEvaluable

func (l Language) NewEvaluable(expression string) (Evaluable, error)

NewEvaluable returns an Evaluable for given expression in the specified language

type Parser

type Parser struct {
	Language
	// contains filtered or unexported fields
}

Parser parses expressions in a Language into an Evaluable

func (*Parser) Camouflage

func (p *Parser) Camouflage(unit string, expected ...rune)

Camouflage rewind the last Scan(). The Parser holds the camouflage error until the next Scan() Do not call Rewind() on a camouflaged Parser

func (*Parser) Const

func (*Parser) Const(value interface{}) Evaluable

Const Evaluable represents given constant

func (*Parser) Expected

func (p *Parser) Expected(unit string, expected ...rune) error

Expected returns an error signaling an unexpected Scan() result

func (*Parser) Next

func (p *Parser) Next() rune

Next reads and returns the next Unicode character. It returns EOF at the end of the source. Do not call Next() on a camouflaged Parser

func (*Parser) ParseExpression

func (p *Parser) ParseExpression(c context.Context) (eval Evaluable, err error)

ParseExpression scans an expression into an Evaluable.

func (*Parser) ParseNextExpression

func (p *Parser) ParseNextExpression(c context.Context) (eval Evaluable, err error)

ParseNextExpression scans the expression ignoring following operators

func (*Parser) ParseSublanguage added in v1.1.3

func (p *Parser) ParseSublanguage(c context.Context, l Language) (Evaluable, error)

ParseSublanguage sets the next language for this parser to parse and calls its initialization function, usually ParseExpression.

Example
package main

import (
	"context"
	"fmt"

	"github.com/PaesslerAG/gval"
)

func parseSub(ctx context.Context, p *gval.Parser) (gval.Evaluable, error) {
	return p.ParseSublanguage(ctx, subLang)
}

var (
	superLang = gval.NewLanguage(
		gval.PrefixExtension('$', parseSub),
	)
	subLang = gval.NewLanguage(
		gval.Init(func(ctx context.Context, p *gval.Parser) (gval.Evaluable, error) { return p.Const("hello world"), nil }),
	)
)

func main() {
	value, err := superLang.Evaluate("$", nil)

	if err != nil {
		fmt.Println(err)
	}

	fmt.Println(value)

}
Output:

hello world

func (*Parser) Peek

func (p *Parser) Peek() rune

Peek returns the next Unicode character in the source without advancing the scanner. It returns EOF if the scanner's position is at the last character of the source. Do not call Peek() on a camouflaged Parser

func (*Parser) Scan

func (p *Parser) Scan() rune

Scan reads the next token or Unicode character from source and returns it. It only recognizes tokens t for which the respective Mode bit (1<<-t) is set. It returns scanner.EOF at the end of the source.

func (*Parser) SetIsIdentRuneFunc added in v1.1.3

func (p *Parser) SetIsIdentRuneFunc(fn func(ch rune, i int) bool)

SetIsIdentRuneFunc sets the function that matches ident characters in the underlying scanner.

func (*Parser) SetMode added in v1.1.3

func (p *Parser) SetMode(mode uint)

SetMode sets the tokens that the underlying scanner will match.

func (*Parser) SetWhitespace added in v1.1.3

func (p *Parser) SetWhitespace(chars ...rune)

SetWhitespace sets the behavior of the whitespace matcher. The given characters must be less than or equal to 0x20 (' ').

func (*Parser) TokenText

func (p *Parser) TokenText() string

TokenText returns the string corresponding to the most recently scanned token. Valid after calling Scan().

func (*Parser) Var

func (p *Parser) Var(path ...Evaluable) Evaluable

Var Evaluable represents value at given path. It supports with default language VariableSelector:

	map[interface{}]interface{},
	map[string]interface{} and
	[]interface{} and via reflect
	struct fields,
	struct methods,
	slices and
 map with int or string key.

type Selector added in v1.1.3

type Selector interface {
	SelectGVal(c context.Context, key string) (interface{}, error)
}

Selector allows for custom variable selection from structs

Return value is again handled with variable() until end of the given path

Example
package main

import (
	"context"
	"fmt"

	"github.com/PaesslerAG/gval"
)

type exampleCustomSelector struct{ hidden string }

func (s *exampleCustomSelector) SelectGVal(ctx context.Context, k string) (interface{}, error) {
	if k == "hidden" {
		return s.hidden, nil
	}

	return nil, nil
}

func main() {
	lang := gval.Base()
	value, err := lang.Evaluate(
		"myStruct.hidden",
		map[string]interface{}{"myStruct": &exampleCustomSelector{hidden: "hello world"}},
	)

	if err != nil {
		fmt.Println(err)
	}

	fmt.Println(value)

}
Output:

hello world

Jump to

Keyboard shortcuts

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