GithubHelp home page GithubHelp logo

govaluate's Introduction

govaluate

Build Status Godoc Go Report Card Gocover

Provides support for evaluating arbitrary C-like artithmetic/string expressions.

Why can't you just write these expressions in code?

Sometimes, you can't know ahead-of-time what an expression will look like, or you want those expressions to be configurable. Perhaps you've got a set of data running through your application, and you want to allow your users to specify some validations to run on it before committing it to a database. Or maybe you've written a monitoring framework which is capable of gathering a bunch of metrics, then evaluating a few expressions to see if any metrics should be alerted upon, but the conditions for alerting are different for each monitor.

A lot of people wind up writing their own half-baked style of evaluation language that fits their needs, but isn't complete. Or they wind up baking the expression into the actual executable, even if they know it's subject to change. These strategies may work, but they take time to implement, time for users to learn, and induce technical debt as requirements change. This library is meant to cover all the normal C-like expressions, so that you don't have to reinvent one of the oldest wheels on a computer.

How do I use it?

You create a new EvaluableExpression, then call "Evaluate" on it.

	expression, err := govaluate.NewEvaluableExpression("10 > 0");
	result, err := expression.Evaluate(nil);
	// result is now set to "true", the bool value.

Cool, but how about with parameters?

	expression, err := govaluate.NewEvaluableExpression("foo > 0");

	parameters := make(map[string]interface{}, 8)
	parameters["foo"] = -1;

	result, err := expression.Evaluate(parameters);
	// result is now set to "false", the bool value.

That's cool, but we can almost certainly have done all that in code. What about a complex use case that involves some math?

	expression, err := govaluate.NewEvaluableExpression("(requests_made * requests_succeeded / 100) >= 90");

	parameters := make(map[string]interface{}, 8)
	parameters["requests_made"] = 100;
	parameters["requests_succeeded"] = 80;

	result, err := expression.Evaluate(parameters);
	// result is now set to "false", the bool value.

Or maybe you want to check the status of an alive check ("smoketest") page, which will be a string?

	expression, err := govaluate.NewEvaluableExpression("http_response_body == 'service is ok'");

	parameters := make(map[string]interface{}, 8)
	parameters["http_response_body"] = "service is ok";

	result, err := expression.Evaluate(parameters);
	// result is now set to "true", the bool value.

These examples have all returned boolean values, but it's equally possible to return numeric ones.

	expression, err := govaluate.NewEvaluableExpression("(mem_used / total_mem) * 100");

	parameters := make(map[string]interface{}, 8)
	parameters["total_mem"] = 1024;
	parameters["mem_used"] = 512;

	result, err := expression.Evaluate(parameters);
	// result is now set to "50.0", the float64 value.

You can also do date parsing, though the formats are somewhat limited. Stick to RF3339, ISO8061, unix date, or ruby date formats. If you're having trouble getting a date string to parse, check the list of formats actually used: parsing.go:248.

	expression, err := govaluate.NewEvaluableExpression("'2014-01-02' > '2014-01-01 23:59:59'");
	result, err := expression.Evaluate(nil);

	// result is now set to true

Expressions are parsed once, and can be re-used 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. Like so;

	expression, err := govaluate.NewEvaluableExpression("response_time <= 100");
	parameters := make(map[string]interface{}, 8)

	for {
		parameters["response_time"] = pingSomething();
		result, err := expression.Evaluate(parameters)
	}

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

Escaping characters

Sometimes you'll have parameters that have spaces, slashes, pluses, ampersands or some other character that this library interprets as something special. For example, the following expression will not act as one might expect:

"response-time < 100"

As written, the library will parse it as "[response] minus [time] is less than 100". In reality, "response-time" is meant to be one variable that just happens to have a dash in it.

There are two ways to work around this. First, you can escape the entire parameter name:

"[response-time] < 100"

Or you can use backslashes to escape only the minus sign.

"response\\-time < 100"

Backslashes can be used anywhere in an expression to escape the very next character. Square bracketed parameter names can be used instead of plain parameter names at any time.

Functions

You may have cases where you want to call a function on a parameter during execution of the expression. Perhaps you want to aggregate some set of data, but don't know the exact aggregation you want to use until you're writing the expression itself. Or maybe you have a mathematical operation you want to perform, for which there is no operator; like log or tan or sqrt. For cases like this, you can provide a map of functions to NewEvaluableExpressionWithFunctions, which will then be able to use them during execution. For instance;

	functions := map[string]govaluate.ExpressionFunction {
		"strlen": func(args ...interface{}) (interface{}, error) {
			length := len(args[0].(string))
			return (float64)(length), nil
		},
	}

	expString := "strlen('someReallyLongInputString') <= 16"
	expression, _ := govaluate.NewEvaluableExpressionWithFunctions(expString, functions)

	result, _ := expression.Evaluate(nil)
	// result is now "false", the boolean value

Functions can accept any number of arguments, correctly handles nested functions, and arguments can be of any type (even if none of this library's operators support evaluation of that type). For instance, each of these usages of functions in an expression are valid (assuming that the appropriate functions and parameters are given):

"sqrt(x1 ** y1, x2 ** y2)"
"max(someValue, abs(anotherValue), 10 * lastValue)"

Functions cannot be passed as parameters, they must be known at the time when the expression is parsed, and are unchangeable after parsing.

Accessors

If you have structs in your parameters, you can access their fields and methods in the usual way. For instance, given a struct that has a method "Echo", present in the parameters as foo, the following is valid:

"foo.Echo('hello world')"

Fields are accessed in a similar way. Assuming foo has a field called "Length":

"foo.Length > 9000"

Accessors can be nested to any depth, like the following

"foo.Bar.Baz.SomeFunction()"

However it is not currently supported to access values in maps. So the following will not work

"foo.SomeMap['key']"

This may be convenient, but note that using accessors involves a lot of reflection. This makes the expression about four times slower than just using a parameter (consult the benchmarks for more precise measurements on your system). If at all reasonable, the author recommends extracting the values you care about into a parameter map beforehand, or defining a struct that implements the Parameters interface, and which grabs fields as required. If there are functions you want to use, it's better to pass them as expression functions (see the above section). These approaches use no reflection, and are designed to be fast and clean.

What operators and types does this support?

  • Modifiers: + - / * & | ^ ** % >> <<
  • Comparators: > >= < <= == != =~ !~
  • Logical ops: || &&
  • Numeric constants, as 64-bit floating point (12345.678)
  • String constants (single quotes: 'foobar')
  • Date constants (single quotes, using any permutation of RFC3339, ISO8601, ruby date, or unix date; date parsing is automatically tried with any string constant)
  • Boolean constants: true false
  • Parenthesis to control order of evaluation ( )
  • Arrays (anything separated by , within parenthesis: (1, 2, 'foo'))
  • Prefixes: ! - ~
  • Ternary conditional: ? :
  • Null coalescence: ??

See MANUAL.md for exacting details on what types each operator supports.

Types

Some operators don't make sense when used with some types. For instance, what does it mean to get the modulo of a string? What happens if you check to see if two numbers are logically AND'ed together?

Everyone has a different intuition about the answers to these questions. To prevent confusion, this library will refuse to operate upon types for which there is not an unambiguous meaning for the operation. See MANUAL.md for details about what operators are valid for which types.

Benchmarks

If you're concerned about the overhead of this library, a good range of benchmarks are built into this repo. You can run them with go test -bench=.. The library is built with an eye towards being quick, but has not been aggressively profiled and optimized. For most applications, though, it is completely fine.

For a very rough idea of performance, here are the results output from a benchmark run on a 3rd-gen Macbook Pro (Linux Mint 17.1).

BenchmarkSingleParse-12                          1000000              1382 ns/op
BenchmarkSimpleParse-12                           200000             10771 ns/op
BenchmarkFullParse-12                              30000             49383 ns/op
BenchmarkEvaluationSingle-12                    50000000                30.1 ns/op
BenchmarkEvaluationNumericLiteral-12            10000000               119 ns/op
BenchmarkEvaluationLiteralModifiers-12          10000000               236 ns/op
BenchmarkEvaluationParameters-12                 5000000               260 ns/op
BenchmarkEvaluationParametersModifiers-12        3000000               547 ns/op
BenchmarkComplexExpression-12                    2000000               963 ns/op
BenchmarkRegexExpression-12                       100000             20357 ns/op
BenchmarkConstantRegexExpression-12              1000000              1392 ns/op
ok

API Breaks

While this library has very few cases which will ever result in an API break, it can (and has) happened. If you are using this in production, vendor the commit you've tested against, or use gopkg.in to redirect your import (e.g., import "gopkg.in/Knetic/govaluate.v2"). Master branch (while infrequent) may at some point contain API breaking changes, and the author will have no way to communicate these to downstreams, other than creating a new major release.

Releases will explicitly state when an API break happens, and if they do not specify an API break it should be safe to upgrade.

License

This project is licensed under the MIT general use license. You're free to integrate, fork, and play with this code as you feel fit without consulting the author, as long as you provide proper credit to the author in your works.

govaluate's People

Contributors

abrander avatar benpaxton-hf avatar bgaifullin avatar dpaolella avatar felixonmars avatar knetic avatar oxtoacart avatar prashantv avatar sambonfire avatar wmiller848 avatar xfennec 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  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

govaluate's Issues

Null Coalescence operator

A coalescence operator ?? should be added. It ought to share precedence with ternaries. It should return the left-hand value if it is not nil, otherwise the right-hand value (regardless of nil). This will mirror the C# / ECMA standard of null coalescence, without making users resort to ugly ternary conditionals for the same effect.

NaN / +Inf detection in govaluate?

Hi @Knetic .

I'm working with this wonderful toot to allow on the fly evaluation from a network gatherer tool.

https://github.com/toni-moreno/snmpcollector/blob/master/pkg/snmpmetric.go#L286

When evaluating an expression like that C=A/B , I got

NaN when A=0 and B=0
+Inf when B = 0.

but the in the below expression, the err = nil.

result, err := s.expr.Evaluate(parameters)
if err != nil {
	log.Errorf("Error in metric %s On EVAL string: %s : ERROR : %s", ID, evalString, err)
	return
}

There is any easy way to check if NaN or Inf are returned? ( is result a string) What other valid results could I get with err == nil ?

String Compare

"abc" < "ad"
Evaluates to True in GoLang.
Please add string compare as I need it for a project I'm open-sourcing soon.

Ternary else

The ternary operator currently only supports the true case (?), but doesn't support an "else" case (:), so it's not really ternary. An operator should be implemented which returns the left-hand value unless it is nil, in which case it returns the right-hand value.

Support for arrays

Hi,
Would it be possible to add array support (i.e. IN and NOT IN), please?

I know that you closed PR #9, but I would like to have a follow-up with that :)

This is needed to be able to parse logstash expressions, and for that, we need to be able to check if a value is present (or not) in an array.

The most common use-case is that filters are adding tags on failure and send these failed message to a specific output.

For example:

if "_dateparsefailure" in [tags] {
   stdout { codec => rubydebug }
}

Better godocs

The godocs are in a fairly sad state, representing half-finished ideas that were written down years ago. All the major public feature docs should be rewritten to be, well, useful.

issues with functions?

I wrote this function:

    functions := map[string]govaluate.ExpressionFunction{
        "now": func(args ...interface{}) (interface{}, error) {
            return float64(time.Now().UnixNano() / 1e6), nil
        },
    }

Executing 1+now() works but now()+1 yields "Unable to plan token kind: MODIFIER".

More complex arithmetic expressions including now() trigger other errors.

Parse dates in current Location?

Hi,
First, big thanks for this library, it's awesome, really.

I use custom functions in my expression returning (float64)xxx.Unix() values (I hope it's correct to do so?). But when comparing values with literal dates like '2017-03-11' (without an explicit zone) , I have timezone offset.

Currently, dates are parsed using time.Parse() and the documentation states "In the absence of a time zone indicator, Parse returns a time in UTC." It makes me think that time.ParseInLocation() with the current Location should be used instead.

What do you think? It sounds OK to you? If so, I can create a PR, of course.

Thanks again for the lib !

Implement parser failure tests

Currently, all tests are happy-path. And while that effectively covers the cases where the the library works, tests need to be written for the cases where it needs to fail.

Implement time evaluation

There is no date handling yet, dates need to be manually parsed and turned into unix times in order to be evaluated. This should change.

A time.Time object should be allowable as a parameter, and comparable against ISO 8601-formatted strings within an expression.

Odd Behavior with order dependent operators (`-` and `/`)

Take these two test for example

2 / 6 / 3 and 2 - 6 - 10 - 2, simple enough right?

Running those same expressions through nodejs, ruby, and go gives different results then this evaluator.

Node:

> 2 / 6 / 3
0.1111111111111111
> 2 - 6 - 10 - 2
-16

Ruby:

2.0.0-p645 :002 > 2.0 / 6.0 / 3.0
 => 0.1111111111111111
2.0.0-p645 :003 > 2 - 6 - 10 - 2
 => -16

Go:

/////////////////////////////
// main.go
/////////////////////////////
package main

import (
  "fmt"
)

func main() {
  t1 := 2.0 / 6.0 / 3.0
  fmt.Println(t1)
  t2 := 2 - 6 - 10 - 2
  fmt.Println(t2)
}
/////////////////////////////
/////////////////////////////

$ go run main.go
0.1111111111111111
-16
EvaluationTest{
  Name:     "Incorrect divide behavior",
  Input:    "2 / 6 / 3",
  Expected: 0.1111111111111111,
},
EvaluationTest{
  Name:     "Incorrect subtract behavior",
  Input:    "2 - 6 - 10 - 2",
  Expected: -16.0,
}

evaluation_test.go:877: Test 'Incorrect divide behavior' failed
evaluation_test.go:878: Evaluation result '1' does not match expected: '0.1111111111111111'
evaluation_test.go:877: Test 'Incorrect subtract behavior' failed
evaluation_test.go:878: Evaluation result '4' does not match expected: '-16'

The issue is the parser is moving right -> left NOT left -> right . I tried playing around with swapping the value vs rightValue orders but it gets messy. I'm sure this is a simple fix but I'm still trying to fully understand how the evaluate chain and the token stream all play together in respect to order of evaluation.

Cheers,
W

Support internationalized strings and variables

Right now any rune that uses >1 bytes to represent a character is not correctly parsed. Parsing an international string literal and comparing it to itself will return false, and naturally variable names suffer the same way.

Unable to parse numeric value '.' to float64 when using a struct parameter.

When passing in an expression that contains a struct to NewEvaluableExpression, I'm getting "Unable to parse numeric value '.' to float64" error.

// element.ShowCondition.Condition = "Survey.Element('q1').Row('r2').Selected()"
expression, err := govaluate.NewEvaluableExpression(element.ShowCondition.Condition)
if err != nil {
        return 0, e.SMSInvalidConditionError
}

Any ideas?

Bug with nested ternaries(?)

This expression " (2 == 2) ? 5 : (true ? 0 : 6) " fails with:
"Value '5' cannot be used with the ternary operator '?', it is not a bool"

I can only guess that it incorrectly handles the nested ternary and tries to parse the 5 as part of the Left of the second ternary.

Cool library by the way. Thanks for publishing it!

Can I use a struct method (or have an object pointer as the parameter) in functions?

Hi, I have a struct like testUser as below.

type testUser struct {
	name string
	domain string
}

func newTestUser(name string, domain string) *testUser {
	u := testUser{}
	u.name = name
	u.domain = domain
	return &u
}

func (u *testUser) getAttribute(attributeName string) string {
	ru := reflect.ValueOf(u)
	f := reflect.Indirect(ru).FieldByName(attributeName)
	return f.String()
}

func getAttribute2(u *testUser, attributeName string) string {
	ru := reflect.ValueOf(u)
	f := reflect.Indirect(ru).FieldByName(attributeName)
	return f.String()
}

func getAttribute3(userName string, attributeName string) string {
	u := getUserObjectByName(userName) // for example using a map
	ru := reflect.ValueOf(u)
	f := reflect.Indirect(ru).FieldByName(attributeName)
	return f.String()
}

alice := newTestUser("alice", "domain1")

I'd like to make the properties of testUser object dynamically parsed in govaluate, the object is an input parameter. I don't know if this is possible.

  1. The best expectation is: expression = alice.name, and use alice object as a parameter. If this is not possible:
  2. I define a func (u *testUser) getAttribute(attributeName string) string function as above. expression = alice.getAttribute("name"), and use alice object as a parameter. If this is either not possible:
  3. I define a global func getAttribute2(u *testUser, attributeName string) string function as above. expression = getAttribute2(alice, "name"), and use alice object as a parameter.
  4. The last way to to define func getAttribute3(userName string, attributeName string) string. expression = getAttribute2("alice", "name"), and use alice name as a string parameter. I know this way works. But it is inconvenient because I need to implement a get-object-by-name function via a map. So I don't want this way.

So my priority is: 1. > 2. > 3. > 4. Do any of the above ideas 1, 2, 3 work in govaluate? Thanks!

Create SQL/Mongo queries from expressions

Allow the creation of arbitrary Mongo and SQL query construction based on expressions. For instance, let's say I want to grab all documents in a Mongo collection where "response_time_ms" >= 100 and "response_code" == 200. I'd normally write a Mongo query that looks like:

{
    response_time_ms:
    {
        $gte: 1000
    },
    response_code: 200
}

And perhaps Mongo is just my most-useful cache, and I represent the historical copy of the same data in SQL as well. If I don't come up with any results in Mongo, I want to run the same check against SQL, which looks like:

response_time_ms >= 1000 AND
response_code = 200

I've spent a great deal of time writing or constructing queries like these. Migrating between database types, having archive databases, and using cache databases leads to a variety of query languages being used to represent the same expression. For a long time, I simply rewrote the query in multiple ways - but that led to some real problems with maintainability. Maybe I updated the archive query, but not the cache query. Suddenly my users see out-of-sync data. But if i represented all of my queries (to SQL, Mongo, Redis, TFS, etc) with a govaluate expression, that could look like:

response_time_ms >= 1000 && response_code == 200

And if this feature were implemented (it isn't, yet), I could create queries as easily as:

expression, err = NewEvaluableExpression("response_time_ms >= 1000 && response_code == 200");
sql_query_string := expression.GetSQLQuery();
mongo_query_string := expression.GetMongoQuery();

One expression to represent the same logic across all databases, as well as in-memory!

Parameter Evaluation in Ternaries Not Short Circuited

I would expect the following code to print 42 instead it fails for not finding bar.

ee, _ := govaluate.NewEvaluableExpression(`true ? [foo] : [bar]`)
fmt.Println(ee.Evaluate(
    map[string]interface{}{"foo": "42"},
))

There's arguments for that behavior but on the basis of issue #40 saying

Some users also specify a late-binding Parameters structure so that they don't need to pre-compute expensive variables every time

I had assumed that the Get method of the parameter interface was called only where needed (though I understand short circuiting logical AND/OR is an open issue). I'm in a place where I have some very expensive variables and would appreciate clarity on whether I'm missing something, found an issue, or looking at forking/going with another solution for evaluation.

Thanks for the awesome package.

Stage reordering sucks

Because of how the evaluation tree is built, operators of like precedence will be evaluated "backwards" from what you'd expect. The fix for that was to invent a rather complicated B-tree mirroring technique dubbed "stage reodering". After the expression b-tree is built, a post-processor runs through and reorders the tree when it finds multiple consecutive operators of the same precedence.

This works exactly correctly, but has had bugs in the past. #30 and #32 have both been directly caused by stage reordering. It's especially difficult to write tests that can expose stage reordering bugs, and harder still to debug them. It's possible that nobody except the author would really be able to even diagnose bugs in this process.

It seems doubtful that anyone else who has written an expression evaluator has felt the need to do this kind of post-processing on the expression tree. There must be a more orthodox way to built the tree in the first place.

Write actual doc explaining behavior of each operator

The readme has a nice overview of how operators, errors, functions, etc work. But it sadly lacks in individual detail. Important notes about things like the behavior of the ternary operators when there is no else-case are footnotes in random places.

It'd be beneficial to actually write out a page that explicitly, redundantly, boringly explains the behavior and acceptable types for each operator.

Undefined function

Re,

When creating a new expression with NewEvaluableExpressionWithFunctions, I get a "strange" error message if it contains a call to an undefined function: Cannot transition token types from VARIABLE [foobar] to CLAUSE [40] (the expression here was simply foobar(), which is not defined).

I'm afraid my end users won't understand this message properly. Is it possible to return something like "undefined function foobar"? Or build the expression anyway (like for undefined parameters) so it let me check FUNCTION tokens before calling Evaluate?

Thanks!

Privatize most constants

A lot of parser-specific constants are public, and they have no reason to be - they clutter up the docs and shouldn't be used by users anyway.

The next time an API break happens, it's probably worth making these private.

Expression evaluation errors

Hello and thanks for the useful library, but i've got an issue with it.

I have a quite simple expression:
([X] >= 2887057408 && [X] <= 2887122943) || ([X] >= 168100864 && [X] <= 168118271)

If i pass X = 2887057409, which satisfies first expression in parenthesis, the expression is for some reason evaluated to FALSE.

If i pass X = 168100865, which satisfies second part of an expression in parenthesis, i get TRUE.

If i swap the sub-expressions, then i get the inverted behaviour.

If i use smaller numbers, like:
([X] >= 0 && [X] <= 10) || ([X] >= 20 && [X] <= 30)

then the expression is evaluated right in all cases.

I think the problem is with numbers being converted to float64, however looking at tokens after expression compilation the numbers seem fine:
{Kind:13 Value:40} {Kind:7 Value:X} {Kind:10 Value:>=} {Kind:2 Value:2.887057408e+09} {Kind:11 Value:&&} {Kind:7 Value:X} {Kind:10 Value:<=} {Kind:2 Value:2.887122943e+09} {Kind:14 Value:41} {Kind:11 Value:||} {Kind:13 Value:40} {Kind:7 Value:X} {Kind:10 Value:>=} {Kind:2 Value:1.68100864e+08} {Kind:11 Value:&&} {Kind:7 Value:X} {Kind:10 Value:<=} {Kind:2 Value:1.68118271e+08} {Kind:14 Value:41}

And this does not explain why the behaviour changes if i just reverse the parts before and after || operator...

Another boolean expression problem

Hi!

Just hit another problematic case:

true && true || false && false should return true, but returns false, while true && true || false as well as true && true || (false && false) both return true, as expected.

unable to compare string to any operator symbol (as string)

For example, following code fail with runtime error: index out of range :

expression, err := govaluate.NewEvaluableExpression("'foo' == '-'")
result, err := expression.Evaluate(nil)

Same thing with any of the operator symbols (-, +, /, * and %)

Support for bitwise shifts (<< and >>)

In the same spirit as #18 , bitwise shift operators (<< and >>) would be beneficial. They should operate the same as other bitwise operators - truncating number values to int64, perform the operation, and re-cast back to float64.

Modulus operator

The modulus operator is not implemented, but would be useful.

Name: %
Token kind: MODIFIER

unexpected logical expression result

Hello
|| and && operators does not seems to work as expected

    expression, _ := govaluate.NewEvaluableExpression("foo == true || bar == true")
    parameters := make(map[string]interface{}, 8)
    parameters["foo"] = true
    parameters["bar"] = false
    result, _ := expression.Evaluate(parameters)
    log.Printf("actual %t, expected : true \n", result)

    expression, _ = govaluate.NewEvaluableExpression("foo > 10  && bar > 10")
    parameters = make(map[string]interface{}, 8)
    parameters["foo"] = 1
    parameters["bar"] = 11
    result, _ = expression.Evaluate(parameters)
    log.Printf("actual %t, expected : false \n", result)

this code will evaluate false for the first result, and true for the second one.

string Concat panic

Hi,govaluate is superising , but when I concat string , it panic.

package main
import (
    "log"
    "github.com/Knetic/govaluate"
)
func main() {
    expression, err := govaluate.NewEvaluableExpression("'http_response_body' + 'service is ok'")
    if err != nil {
        log.Printf("err1: ", err)
    }
    parameters := make(map[string]interface{}, 8)
    parameters["http_response_body"] = "service is ok"
    result, err := expression.Evaluate(parameters)
    if err != nil {
        log.Printf("err2: ", err)
    }
    log.Printf("rs: ", result)
}

Short circuit logical AND/OR

Most similar languages short-circuit logical operators such that if the left side of the expression would determine the result of the expression, then the right side is simply not evaluated. For instance; true || somethingComplicated(), this library currently evaluates somethingComplicated() even though it has no reason to - the left side will cause the whole expression to be true.

Add a "strict mode" for parameters

Following the line of thought in #39 , it is possible to check that the Parameters given to Evaluate contain all the variables required for the expression.

Such a thing might conflict with #36 , since short-circuiting might entirely avoid some parameters. Some users also specify a late-binding Parameters structure so that they don't need to pre-compute expensive variables every time; so if the "strict mode" became default behavior, it would force them to pre-compute variables that never get used, just to satisfy the library.

So - it would be best if this was an optional thing, such as a bool in EvaluableExpression that defaulted to false. Otherwise this could break quite a lot of people who might rely on the existing (or overridden) "parameter not found" behavior.

How to Get parameters from expression?

There is any way to get parameters contained on an arbitrary expression?.

Supose this:

govaluate.NewEvaluableExpression("(requests_made * requests_succeeded / 100) >= 90");

I need a way to get an parameter array with "request_made","request_succeded"

I need it to query a database for values to replace them.

Thanks you very much!!

Optimize regex

Right now, regex are compiled upon every evaluation. This can lead to some punishing execution times when dealing with the regex operators.

There are three ways this can be helped:

  • When a regex operator's right-hand value is built entirely out of constants, that regex can be compiled at expression parsing time, rather than evaluation-time.
  • Users can give a precompiled regex directly to parameters. Instead of type-checking for only Strings, we can also look for *regexp.Regexp, preferring it if present.
  • When a regex operator compiles a regex for a given parameter, an internal cache can be made which maps the parameter's name and value to the compiled regex. This means that if a parameter doesn't change, its regex will (in effect) be statically compiled, similar to the above.

No guarantees about which of these will be implemented. All are subject to testing and benchmarking.

Investigate "unchecked" mode

As it stands, the library strictly type-checks values before operating on them. It does this so that it never panics, and neatly returns an error in cases where mismatched types are used.

However, this leads to "double-checking" where the library checks for types, and so does golang itself (in order to determine if it should panic or not). It may be desirable, in some edge cases where per-nanosecond performance matters, to have an "unchecked" mode which panics instead of returning errors. This should be implemented and investigated.

Pre-compute literal operations

As an optimization, modifiers where both sides are made up entirely of literals can be condensed at stage-planning-time. For instance; "foo > 2 ** 16", as written, would actually calculate 2 ** 16 upon every call to Evaluate(). Instead, at stage planning time, it ought to be possible to eliminate the 2 ** 16 step and condense the resultant "expression" into the equivalent of foo > 65536.

Passing date values in parameters

First of all, thank you so much for your library. It is awesome.

I am trying to evaluate an expression with a date value set in parameters. I have posted a question on StackOverflow about this: https://stackoverflow.com/questions/44893202/compare-date-values-in-expression-using-govaluate

Is there a way i can achieve the following:

package main

import (
    "github.com/Knetic/govaluate"
    "fmt"
)

func main()  {
    expression, err := govaluate.NewEvaluableExpression("first_name == 'Julian' && emp_id == 302 && birth_date >= '2016-12-12' && birth_date <= '2016-12-25'")

    parameters := make(map[string]interface{}, 8)
    parameters["first_name"] = "Julian"
    parameters["emp_id"] = 302
    parameters["birth_date"] = "2016-12-15"
    result, err := expression.Evaluate(parameters)

    if err != nil {
        fmt.Println("Error while evaluating the expression")
    }

    if result.(bool) {
        fmt.Println("Condition is evaluated to true")
    } else {
        fmt.Println("Condition is evaluated to false")
    }
}

I a getting this error:

Error while evaluating the expression
panic: interface conversion: interface is nil, not bool

I tried doing the following: convert the date literal into float and pass as a parameter.

dateFloat, _ := strconv.ParseFloat("2016-12-15", 64)
parameters["birth_date"] = dateFloat

Although, the error disappeared, the result value is false which is logically wrong since "2016-12-15" falls between "2016-12-12" and "2016-12-25".

Can you suggest me a way to achieve it?

Only the last object is valid when using class methods as functions.

I want to use a class method like below in the functions.

func (c *MyClass) method(arg1 int, arg2 int) bool {

}

functions["func1"] = func(args ...interface{}) (interface{}, error) {
	arg1:= args[0].(string)
	arg2:= args[1].(string)
	return (bool)(obj1.method(arg1, arg2)), nil
}
functions["func2"] = func(args ...interface{}) (interface{}, error) {
	arg1:= args[0].(string)
	arg2:= args[1].(string)
	return (bool)(obj2.method(arg1, arg2)), nil
}

obj1 and obj2 are two different instances of MyClass.

After some debugging, I found that func1 is not working, because it's actually calling obj2.method instead of obj1.method. So it seems that the object itself for a class method is not regarded as a parameter.

Maybe a workaround is that defining a global function and explicitly regarding the class object a parameter of this function. But it requires to modify the expression to add the class object into parameters, which is kind of ugly.

So I want to ask is there any better way to let class methods work well during the expression evaluation? Thanks.

REQUEST - support user defined functions

hi, this library is great. User defined parameters/variables is very welcome. Is there any chance you can add user defined functions as well (ideally nested user defined functions).

example:

"(requests_made("5m") * requests_succeeded("5m") / 100) >= 90"

Add doc comments

All the exported functions / fields are embarassingly undocumented.

Parameters are not type-checked before being operated upon

While there is good support for making sure that literals in an expression make sense lexically, there is no type safety or rules about which operators work on which values.

For instance, there is no intuitive or unambiguous behavior to checking if a string is ">=" another string, nor is there well-defined behavior for what negating a bool (-(1 > 2)) would do.

Currently if any operation isn't evaluable in golang itself, this library will panic. Panics are bad. Instead, an error should be returned in cases where operations against a type don't make sense.

Support bitwise XOR (^), AND (&), OR (|)

It would be great if this could support bitwise operations.

https://en.wikipedia.org/wiki/Bitwise_operation
https://golang.org/ref/spec#Arithmetic_operators

I'm working on a project that generates expressions and I've looked at a few options, built in AST support from go/token, go/constant, go/types is good but getting it do this auto conversion is a pain. I started writing my own AST then I found your project and thought it would be a great fit.

Here you state

All numeric constants and variables are converted to float64 for evaluation

This would require something like float64(int64(variable) & int64(otherVariable)) when evaluating these bitwise expressions.

For 10.5 | 20.2 to just work in your expression evaluator would be super amazing

I'm not sure what to say about ^ (power) vs ^ (xor) but I'm sure there is a good solution!

Cheers!

Hex literals cause panic

The following code leads to a panic:

expression, err := govaluate.NewEvaluableExpression("0x10 > 0");
result, err := expression.Evaluate(nil);

Relavant stack trace
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x401753]

goroutine 1 [running]:
panic(0x5ef040, 0xc42000c0e0)
/home/blah/blah/go-1.7.4/src/runtime/panic.go:500 +0x1a1

Implement parameter name quoting

Currently, variable names are limited to non-symbol characters. Trying to use a minus sign ("-") in a variable name will cause the expression to fail. E.g.,

response-time > 10

Would be parsed as "response subtract time is greater than ten", when in reality the parameter name is "response-time".

To fix this, a system of "quoting" should be implemented, so that the user can specify that they want an entire run of characters to be a variable name. Such as;

[response-time] > 10

This approach will also need to escape whatever quoting character is used. For instance;

[response\[time\]] > 10

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.