Giter Club home page Giter Club logo

gval's Introduction

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.

gval's People

Contributors

bhavpreet avatar buglloc avatar darh avatar edigaryev avatar fkorotkov avatar generikvault avatar haxwagon avatar hengwei-test avatar impl avatar jacalz avatar lafriks avatar machship-mm avatar mariotoffia avatar miroslav-debug avatar tw1nk avatar vicpatel avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

gval's Issues

Extending variable() to support custom selectors on value

I'm struggling to get a bit more control over how path selection is done.

I would like to expose one of my structs with prop that's internally slice of structs ({ name, value, position }) to be accessed in more "fluent" way (record.values.foo or record.values.bar[0]). More info here: https://github.com/cortezaproject/corteza-server/blob/develop/compose/types/record.go#L38

Currently, I'm doing this by casting into map[string]interface{} but I would like to avoid this because it requires keeping copies of the original data and constantly syncing and casting.

I've tried with adding an operator but I have to admit I'm lacking know-how of the internal workings of gval to make even a proof of concept. A custom function (getValue(record, name, position)) would work but that ruins the fluentness :)

After digging more into gval, I'm thniking that a) variable fn could be modified to get additional case that would cover a special interface interface { GvalSelect(key string) (interface{}, error) } (I'm not 100% happy with the fn name here... ).

If you think this would be a good approach I can open a PR in next couple of days.

how to parse complex json with jsonpath?

value, err := gval.Evaluate($["response-time"]+" "+$["request.method"],
map[string]interface{}{
"response-time": 100,
"response-time1": 110,
"request": {
"method": "GET",
"path": "/hello/2",
} //here causes syntax error: unexpected newline, expecting comma or }, how to fix it?
},
jsonpath.Language(),
)

Please check the above code when want to include nested json content, which always leads to the above specified syntax error

Keywords

Hello,
I was wondering what would be the way to handle keywords like: Now, Pi.
For now I add them as parameters in the Evaluate() function, but I was wondering if there was a more appropriate way to handle them.
Kind regards,
JClaude

Guidance; abusing gval to set keys

Hey folks; first up thanks for gval... it's a seriously awesome little library. Hard to believe it doesn't have more adoption!

To the point... I'm using gval in a little tool I wrote to smash up kubernetes manifests. https://github.com/mikesimons/kpatch. Please excuse the nasty code; it's prototype grade at the moment.

I've implemented an = infix operator and it works pretty well to overwrite values that already exist.

The problem is that because I can't get the verbatim key from the left expression I'm having to set values based on a walk of the input structure and matching on reflection values. It works but it means that if the key doesn't exist no value matches and the value is not set.

To get around it I implemented a set function but from a user experience perspective I'd really like to be able to do some.key = "value" even if some.key wasn't previously set.

I had a go with an InfixEvalExpression but due to the input being wrapped in an Evaluable func I couldn't get the value out.

Any advice on a way to do this (even if it means patching gval)?

functions inside expression are evaluated in coroutine and on panic are not recovered thus crashing whole application

functions inside expressions are evaluated in coroutine and on panic are not recovered thus crashing whole application. I think there should be somekind of recover as when coroutine ends with not recovered panic the whole application gets shutdown by go runtime.

go func() {

and
go func() {

note: why it is done in coroutine? from timeout or context cancellation perspective this makes little sense because if there is endless loop returning from expression that "timeouted" makes little difference because that endless coroutine still works on background.

Export parseIf

The functions used by the full language are not exported, which makes it hard to use them in a custom language. I got around this with //go:linkname for now, but was wondering if it might be possible to export them similar to Parentheses() and other language components.

Unable to accept an array as input to custom function

`varsForPassing := map[string]interface{}{
	"AGG311": 1,
	"AGG312": 2,
	"AGG313": 3,
}

function := gval.Function("max", func(args ...interface{}) (interface{}, error) {
	arr := args[0].([]float64)
	largest := arr[0]
	for _, val := range arr {
		if val > largest {
			largest = val
		}
	}
	return largest, nil
})
value, err := gval.Evaluate("max([AGG311, AGG312, AGG313]) > 1000", varsForPassing, function)
fmt.Println(value, err)`

I get the error "panic: interface conversion: interface {} is []interface {}, not []float64"

Is it not possible to define an array with variables and use a function on it?

Error callback instead of terminating error in JSON

Hi and thanks for this greate piece of software! :)

We're using it to do sensor mapping:

  • i.e fetch sensor data as a single HTTP request
  • map those sensors into our own sensor model.

This works great, except when for some reason we do not get one or more needed sensor data points. In this case it will error out.

Could it be possible to do a callback to an error function instead, and if that error function do return an error, it will fail, otherwise it will remove the json key or set the value to nil (or a value returned by the error function?

func OnError(c context.Context, key, expr string, e error) (val interface{], err error) {}

Example mapping document that we use (partial)

{ "tz": TZ,
	"sensors": { 
			"IDT_O3": float64(datapoint["1!6WHH6DKH3CCVW-"].Value),
			"IDT_O5": float64(datapoint["1!6WHH6DKHADPZ1N"].Value), 
                         "AEUPS": float64(datapoint["1!6WHH6DKHICVDSS"].Value * 1000) }
}

The error function may act as a OnErrorResumeNext or a circuit-breaker (the latter is how it currently works).

What do you think about this? Is is hard to implement in your library, would it fit?

To maybe do other than current error handling when this or this returns an error?

p.Camouflage("object", ',', '}')
key, err := p.ParseExpression(c)
if err != nil {
  return nil, err
}

Cheers,
Mario :)

Handle decimal/money arithmetic

This library is fantastic!

The one issue I've found is that the Arithmetic is done as floating point numbers (which is fine for that use case) but this falls short when doing math involving decimals.

An example:

func TestDecimalArithmetic(t *testing.T) {
	input := ArithmeticVariables{
		NumberMap: map[string]float64{
			"x": 12.5,
			"y": -5,
		},
		Expression: "(x * 12.146) - y",
	}

	result, _ := gval.Arithmetic().Evaluate(input.Expression, input.NumberMap)

	want := 156.825
	if result != want {
		t.Errorf("wanted %f; got %f", want, result)
		t.FailNow()
	}

	t.Log(result)
}

This results in this output:

=== RUN   TestDecimalArithmetic
    arithmetic_test.go:46: wanted 156.825000; got 156.825000
--- FAIL: TestDecimalArithmetic (0.00s)

When inspecting the variable values, I can see that result has the actual value of 156.82500000000002.

Do you have any suggestions for what I should do, or is this even a solvable problem?

I was thinking that there could be an EvaluateDecimal method, which instead of treating numbers as floats, would treat them as a decimal type? For decimals, I use github.com/shopspring/decimal.

Feature Request: high order functions (collection)

What is it?

Some special functions like filter, map, exists, all.

For example:

["project1/text.txt", "project2/text.txt"].exists(t => t.startsWith("project1"))
; => true

Why?

This would simplify a lot of some operations that would require very specific custom functions.


Let me know if that's something you have thought about it.

How to define a language which allows to create varaiables?

I intend to create a new language to accept following expressions:

a = 1 + 2
b = a * 0.1
a + b

The program will eval it line-by-line, so the ideal result will be:

  1. Found a new variable named 'a' and it's value is Evaluate("1+2"). Register "a" and it's value 3 to the parameter map which will be used to eval next line.
  2. Found a new variable named 'b' and it's value is Evaluate("a * 0.1", map[string]interface{}{"a":3}).
  3. Finally Evaluate("a + b", map[string]interface{}{"a": 3, "b":0.3}).

I'm trying to create a calculator notebook app which allows user to create new avarialbe and new functions.

I found gval.InfixEvalOperator maybe the right spot to look, but I'm stucked by getting the text value of a.

Here is the demo code

		gval.InfixEvalOperator("=", func(a, b gval.Evaluable) (gval.Evaluable, error) {
			return func(c context.Context, v interface{}) (interface{}, error) {
				// I should get a's text value as the variable name here.
				// and register global parameter map with a's text value to b's eval result.
				return b.EvalString(c, v) // This is is just let me know the value of b.
			}, nil
		}),

Any hint?

Without space after && leading error

package main

import (
	"fmt"
	"github.com/PaesslerAG/gval"
)

func main() {
	vars := map[string]interface{}{"name": true}

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

	value, err = gval.Evaluate("true&& name", vars)
	if err != nil {
		fmt.Println(err)
	}

	fmt.Println(value)
}

runing result:

parsing error: true&&name	 - 1:8 unknown operator &&n
<nil>
true

My expected result is the first expression also working fine.

Return the error when error is returned by a function

When the error is returned by the function as the last value, the error is treated as it was thrown by the eval function. Code:

package main

import (
	"errors"
	"fmt"

	"github.com/PaesslerAG/gval"
)

func main() {
	exports := map[string]interface{}{
		"call": func(s string) (string, error) {
			if s == "yes" {
				return s + "_returned", errors.New("some error")
			}
			return s + "_returned", nil
		},
	}

	res, err := gval.Evaluate(`call("yes")`, exports)
	fmt.Println(res, err)
}

Will return:

<nil> can not evaluate call("yes"): some error

When you call the the evaluate with

gval.Evaluate(`call("no")`, exports)

Will return:

no_returned <nil>

When you change the error to be returned as not the last element:

package main

import (
	"errors"
	"fmt"

	"github.com/PaesslerAG/gval"
)

func main() {
	exports := map[string]interface{}{
		"call": func(s string) (error, string) {
			if s == "yes" {
				return errors.New("some error"), s + "_returned"
			}
			return nil, s + "_returned"
		},
	}

	res, err := gval.Evaluate(`call("yes")`, exports)
	fmt.Println(res, err)
}

Both "yes" and "no" will be treated as the return values of the function.

[some error yes_returned] <nil>
[<nil> no_returned] <nil>

Any way to make the error returned by the functions treated as return values?

No error even for invalid expression

In the following code:

	result, err := gval.Evaluate("undefined_value == 10", map[string]interface{}{
		"whatever_var": "whatever_value",
	})
	fmt.Println(result, err)

I expect an error to be printed, instead it is printing: false, <nil>

Unable to define and look up array

According to the readme, you can define an array in gval:

Json Arrays : [1, 2, "foo"]

And you can look up an element from an array:

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

Then I should be able to define an array and look up an element:

[10,11,12,13][2]

I expected this to return 12, but instead I got:

&errors.errorString{s:"parsing error: [10,11,12,13][2]\t:1:14 - 1:15 unexpected \"[\" while scanning operator"}

What I'm trying to do is let a customer define an array in a gval string, and input from their user will be used to look up an element:

["foo", "bar", ...][x]

Where "foo", "bar", ... is written by an admin, and x comes from an end user.

getting list of parsed variables before expression is evaluated for validation

I have expression vtg_ground_speed_unit > 0 ? vtg_ground_speed_knots : vtg_ground_speed_kph.

These variables (vtg_ground_speed_unit, vtg_ground_speed_knots, vtg_ground_speed_kph) can only be from fixed list (coming from configuration file). So it would be nice if would be a way to get all variables from parsed expression so they could be validated against current configuration - to see if they exist or not.

So far I have tried to create selector that captures all seen variables in "test evaluation".

Something like that:

type variableCaptor struct {
	variables []string
}

func (c *variableCaptor) SelectGVal(_ context.Context, variable string) (interface{}, error) {
	c.captureVariable(variable)
	return 1.0, nil
}

func (c *variableCaptor) captureVariable(variable string) {
	for _, v := range c.variables {
		if variable == v {
			return
		}
	}
	c.variables = append(c.variables, variable)
}

func TestCaptureVariables(t *testing.T) {
	var testCases = []struct {
		name       string
		expression string
		expect     []string
	}{
		{
			name:       "capture 3 variables from IF",
			expression: "vtg_ground_speed_unit > 0 ? vtg_ground_speed_knots : vtg_ground_speed_kph",
			expect:     []string{"vtg_ground_speed_unit", "vtg_ground_speed_knots", "vtg_ground_speed_kph"},
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			eval, err := compile(tc.expression, gval.Full())
			if !assert.NoError(t, err) {
				return
			}
			captor := &variableCaptor{variables: make([]string, 0)}

			_, err = eval.EvalBool(context.Background(), captor)
			if !assert.NoError(t, err) {
				return
			}
			assert.Equal(t, tc.expect, captor.variables)
		})
	}
}

but this has limitation as vtg_ground_speed_kph will not get captured as executing all paths from expression involves knowing how to provides values for getting true/false sides.

Is there a way to traverse parsed tree without evaluation and pick all parts that are used as "variable"?

Issues with uint64 when using in expression

Hi,

Looks like gval is corrupting and returning float64 instead of uint64 when evaluating simple expression.
I'm probably missing something obvious here :)

func TestGval(t *testing.T) {
	tUint64 := uint64(197830758113476796)
	fmt.Printf("%d %T \n", tUint64, tUint64)

	{
		eval, _ := gval.Full().NewEvaluable(`a`)
		rval, _ := eval(context.Background(), map[string]interface{}{"a": tUint64})
		fmt.Printf("%d %T (as expected, got uint64) \n", rval, rval)
	}
	{
		eval, _ := gval.Full().NewEvaluable(`a+0`)
		rval, _ := eval(context.Background(), map[string]interface{}{"a": tUint64})
		fmt.Printf("%f %T (got float, not expected...) \n", rval, rval)
	}
	{
		eval, _ := gval.Full().NewEvaluable(`A+0`)
		rval, _ := eval(context.Background(), struct{ A uint64 }{A: tUint64})
		fmt.Printf("%f %T (got float, not expected...) \n", rval, rval)
	}
}

Results:

=== RUN   TestGval
197830758113476796 uint64
197830758113476796 uint64 (as expected, got uint64)
197830758113476800.000000 float64 (got float, not expected...)
197830758113476800.000000 float64 (got float, not expected...)

Parsing errors are not user-friendly

Our app allows end users (not developers) to write mathematical expressions. We would like to present errors in a way that the users will understand. Unfortunately, the parser errors are very low-level and not related to the mathematical syntax.

Examples of actual and desired output:

Input:
Actual error:  unexpected EOF while scanning extensions
Desired error: input is empty

Input:         (
Actual error:  unexpected EOF while scanning extensions
Desired error: missing right parenthesis

Input:         )
Actual error:  unexpected ")" while scanning extensions
Desired error: missing left parenthesis

Input:         ()
Actual error:  unexpected ")" while scanning extensions
Desired error: no expression inside parentheses

Input:         1+
Actual error:  unexpected EOF while scanning extensions
Desired error: missing expression to the right of "+"

Input:         +1
Actual error:  unexpected "+" while scanning extensions
Desired error: missing expression to the left of "+"

Move `parseIf` operator in `base`

Greetings,
Kudos for the awesome package!

I do think that parseIf operator should be in base, because it is useful in arithmetic, but is not arithmetic operator itself. Currently I am using .Full(), instead of just .Arithmetic() just because of it.

Best

Add option to override functions to parse string/float/bool

Currently when defining language there is no way to override how float/string/bool are handled and to override behavior for example to be more strict for operations not to allow autocasting for example false || 1 will result in 1 being treated as boolean true or 1+'1' resulting into 2

Case Mismatch: Able to create Evaluable but actual evaluate fails

Hi,

I am using gval to build an excel formula parser. Repo name is efp. Check efp_test.go. You can change case in expression to see behavior.

In excel, all function names are capitalized so, I have created Functions with Capital names. Language allows creation of non-null Evaluable even when case does not match. But, when Eval* function is called on Evaluable the results are incorrect.

The function mapped to keyword is called correctly and the value returned from the function is accurate but, it seems this value is lost after evaluation. Any thoughts?

Ideally Parse should fail in this scenario so that users get feedback when expression is built improperly.

Evaluating strings with backslashes raises parsing errors

Env

This is on github.com/PaesslerAG/gval v1.2.1

Issue

We have a custom handler for regular expressions (match(value, regex)) where the regex can be configured from the front-end (web application).

For example, inputting match(variable, "[a-z]\d") would be presented as (when dumping the value) "match(variable, \"[a-z]\\d\")" (which seems correct to me), but when passed to gval, it raises parsing error: match(variable, "[a-z]\d") :1:11 - 1:20 could not parse string: invalid syntax.

If I bypass all of the surrounding code and run it directly as so x, err := gval.Evaluate("match(variable, \"[a-z]\\d\")", map[string]any{"a": "b"}) the error is the same.

Another example would be running this x, err := gval.Evaluate("\"\\\"", map[string]any{"a": "b"}) -- same error in regards to the invalid string (parsing error: "\" :1:1 - 1:4 could not parse string: invalid syntax).

But then, if I do this x, err := gval.Evaluate("\"apple\\banana\"", map[string]any{"a": "b"}), it passes and outputs (when running spew.Dump(x, err))

(string) (len=11) "apple\banana"
(interface {}) <nil>

Conclusion

Am I doing something wrong with my strings, or is this a (un)intentional edge case?

If this needs more investigation I can take a look through the code and propose a fix as well, but I'd appreciate some notes on where would be a good place to start/your suspicions about what's wrong

Allow variables to overload functoins

Is it be possible to configure a language so that variable names takes precedence over functions?

Use case: for a system that stores expressions, allow adding new functions to the language without breaking backwards-compatibility.

Exmaple: for a language with function sin, in the expresison sin + b -- treat sin as variable instead of a functoin if present.

unable to get value

i have run the following with jsonPath

package main

func main() {
	v := interface{}(nil)
	json.Unmarshal([]byte(`{
		"foo": {
			"bar": {
				"val": 3
			}
		}
	}`), &v)

	value, err := jsonpath.Get("$.*.*.val", v)
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	for _, value := range value.([]interface{}) {
		fmt.Println(value)
	}
}

and this is my output
$.*.*.valmap[foo:map[bar:map[val:3]]]3

however if i run

v := interface{}(nil)
	json.Unmarshal([]byte(`{
		"foo": {
			"bar": {
				"val": 3
			}
		}
	}`), &v)
value, err := gval.Evaluate("$.*.*.val", v, jsonpath.Language())

it returns

[]
am i using this wrong?

i would expect it to return

3
and if i run

v := interface{}(nil)
	json.Unmarshal([]byte(`{
		"foo": {
			"bar": {
				"val": 3
			}
		}
	}`), &v)
value, err := gval.Evaluate("$.*.*.val == 3", v, jsonpath.Language())

i would expect

true 

Date evaluation

Hello,

First your library looks great. Thank you for sharing.
I would like to evaluate date + duration:
date(myObject.date) + 3d
I was wondering what was the best approach: adding a new language?
Cheers,
JC

Struct methods do not accept func arguments

type Client struct{}
func (c *Client) Foo(s string) string {
	return s
}

gval.Evaluate(`client.Foo("bar")`, map[string]interface{}{"client": c}) // works: does return "bar"

But

type Client struct{}
func (c *Client) Foo(s string, f func()) string {
        f()
	return s
}

gval.Evaluate(`client.Foo("bar", func(){})`, map[string]interface{}{"client": c}) // does not work

returns&errors.errorString{s:"parsing error: client.Foo(\"bar\", func(){})\t:1:25 - 1:26 unexpected \"{\" while scanning arguments expected \")\" or \",\""} a <nil>

Force fail when a parameter is missing.

Scenario:
I am evaluating " a && b " with map[string]interface{}{"a":true} and i'm getting false.

If I pass to the function an alias for map[string]interface{}, like

type Values map[string]interface{}

with "a":true and without B, I get an error saying:

can not evaluate a && b: unknown parameter b

And this is exactly what we want instead of a nil in the error because we prefer an error instead of a partial evaluation; In other words, we want all the parameters before doing the evaluation.

Is there a way to force the library to fail if some parameter is missing, using a map[string]interface{}. Otherwise, is safety use an alias to map[string]interface{} to check this errors?

Better language granularity

Hi!

We would like to integrate gval into our platform, so that our users can write their own arithmetic expressions. However, in order to keep the language well defined, we would like to select exactly which operators to allow, in our case it would be - ( ) + - * / and nothing more.

We could duplicate the definitions of base and arithmetic and strip them down, but it feels wrong to duplicate the inline code (which is library internals) outside the library, as there might be compatibility issues in the future.

Therefore we would welcome improved granularity, so that operators can be chosen on an individual basis. If you don't have time to implement it, would you accept a PR with such a change?

Best Regards,
Tom Weber
Precisely AB

Allow dashes in identfiers

I have the case that one of my identifiers contains a -, like type-id. The parser fails because - is not an allowed rune in an identifier.

Example:

func main() {
    vars := map[string]interface{}{"type-id": "4"}
    expr := `type-id == "4"`
    e, err := gval.Full().NewEvaluable(expr)
    if err != nil {
        return
    }
    value, err := e.EvalBool(context.Background(), vars)
    if err != nil {
        return
    }
    fmt.Println(value)
}

I could open a PR, but the fix is so simple: Add the condition (pos > 0 && r == '-') to https://github.com/PaesslerAG/gval/blob/master/parser.go#L33 . The example works, I tested it even for multiple dashes. But I'm new to gval, so I might miss some cases.

Variables beginning with a number

Hi, thanks for your work.

I found one annoying error in the execution process. When you define parameters that start with a number, then a parsing error occurs.

For example, this code will generate an error parsing error: 2_GH :1:2 - 1:5 unexpected Ident while scanning operator

resultEval, err := gval.Evaluate("2_GH", map[string]interface{}{
		"2_GH": 10,
	})

Can you please tell me how to fix this? Is this a bug or a feature?

Panic when provided using struct object with null pointer field

Error:
panic: reflect: call of reflect.Value.Interface on zero Value [recovered] panic: reflect: call of reflect.Value.Interface on zero Value

after quick reading, I think it has something to do with the usage of reflection, it does not check whether its valid reflect Value or not

feature request: add metadata in evaluable

I use this project in my job and solve a lot problems. Really appreciate for it.

But i found there are some scences that i need to get the metadata of parsed evaluable.
For example:
in our system, we allow users to configure their expression with functions and operators our language supported like a rule engine.
Apperently we want to check if user's input is valid (not syntax but business logic) but i found it is hard to get metadata by gval now, like which function does this expr use and the params pass by user.

From a legitimate point of view, this is an ability that parse module should support instead of user use init language and context to hack it.

I have read the other issues and found this problem also made other guys confused like #75

I have tried to solve it by adding wrap gval.Evaluable with a metadata, it looks fine to solve this problem. if you guys agree this request, i can work on it.

// EvaluableFunc evaluates given parameter
type EvaluableFunc func(c context.Context, parameter interface{}) (interface{}, error)

// when parse the specific token, we can pour metadata into Evaluable
type Evaluable struct {
	EvaluableFunc
	metadata *EvaluableMetadata
}

expect your replies. @generikvault @darh @impl

evaluate with operator in value not expected

that is code:

package main

import (
	"fmt"

	"github.com/PaesslerAG/gval"
)

func main() {

	value, err := gval.Evaluate(`foo in [100, 101]`, map[string]interface{}{
		"foo": 100,
	})
	if err != nil {
		fmt.Println(err)
	}

	fmt.Print(value)

}

it expect be true, but get false

Undefined attributes key returns error

Hi,
I'm trying to create a conditional expression if a variable is nil, but it throws an Error instead.

parameters := make(map[string]interface)
parameters["foo"] = "bar"

expression := "fooz ?? foo"
result, err := gval.Evaluate(expression, parameters)
if err != nil {
  panic(err)
}

Throw error unknown parameter fooz when it should be bar. I've tried to solve this problem by change return error value to a nil value from private function func variable(path ...Evaluable)

How to get rendered expressions

Example:
expression: a + b > 6
args: {"a":3,"b":4}
result: 3 + 4 > 6

Because i need to show the user the calculation process of the expression rather than the direct result.
Thanks.

Passing a context to the custom functions

Hello guys,

at the moment if we want to define a custom function, we just extend the language

gval.Evaluate(
    expression,
    data,
    gval.Function("foo", func(args ...interface{}) (interface{}, error) {
	    return "bar", nil
    }),
   ...
)

Yet we can EvaluateWithContext, say context with value.

ctx := context.WithValue(context.Background(), "input", data)
gval.EvaluateWithContext(
    ctx,
    expression,
    data,
    gval.Function("foo", func(args ...interface{}) (interface{}, error) {
	    return "bar", nil
    }),
   ...
)

Unfortunately, the context is not passed into a function itself. So maybe, we could do something like

ctx := context.WithValue(context.Background(), "input", data)
gval.EvaluateWithContext(
    ctx,
    expression,
    data,
    gval.FunctionWithContext("foo", func(ctx context.Context, args ...interface{}) (interface{}, error) {
	    return count(ctx.Value("input")), nil
    }),
   ...
)

Why would one need it? E.g.:

  • To get an entire parameters, not only arguments selected for the function (say, count all elements in []interface{});
  • If the string is the argument, check in context, that this type should be time, parse as time and do something.

Wdyt? Or maybe there's already the way to pass some extra information into the gval function? I've tried with Init, but I apparently don't understand its concept that much.

Best regards,
sk

Error when Not Adding Space for New Operator

Update - I got things working by adding custom regex to add spaces to my evaluation:

e.g. 4d2 would roll 4 "d2" dice. By adding custom logic to turn 4d2 into "4 d 2" I was able to get things working. Thanks for the support!

Result line/column information

Is it possible to get source location information for the returned results? I'm primarily interested in a JSON Path use-case where I can tell where in the original document the results of the query came from, e.g. a query like $..parameters[[email protected] == "query"] gives me a bunch of parameter objects and I'd like to know the line/col of the start of each parameter in the input file bytes. Is this something that is currently possible or something you would support in this library?

eval `nil > 1` returns true

I am accessing a map or struct fields in expression string, and in case field is not present in map or struct and if compared like Person.Age > 30 then return true.

Reduced issue is "nil > somevalue" return true.

600+k png file is checked in

The file prtg-batmin-gopher.png is over 600k and seemingly only referenced in the README. This needly increases the size of repos that end up vendoring this library. Can this file be reduced in size (it's presented as much smaller in the image tag that references it) or be remove from the repo all together?

lazy evaluation

I am evaluating " a && b " with map[string]interface{}{"a":true} and i'm getting false in result instead of err result.

I remember that in some cases the function return the following err:

can not evaluate a && b: unknown parameter b

How to create a language with nested parameter support?

I need to create a language with some concrete set of rules (to obtain an ES query). I already have something like:

func getLanguage() gval.Language {
	return gval.NewLanguage(
		gval.InfixTextOperator("==", eqStringOp),
		gval.InfixOperator("&&", andOp),
		gval.InfixOperator("||", orOp),
		gval.Base(),
	)
}

and I evaluate it with:

var paramMap = map[string]string{
	"device":          "device_id",
	"type":            "type",
	"label.id":        "label.id",
	"label.value":     "label.value",
}

result, err := lang.Evaluate(`label.id = "my-id"`, paramMap)

How can I add nested parameter support (as it is support when calling gval.Evaluate())?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.