From fb148c305a9133fff5c493e6bd5409705734972c Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Thu, 17 Mar 2022 14:08:08 +1100 Subject: [PATCH] Added comparison ops (>,>=...) --- pkg/yqlib/compare_operators.go | 133 +++++++ pkg/yqlib/compare_operators_test.go | 371 ++++++++++++++++++ pkg/yqlib/doc/operators/boolean-operators.md | 6 + pkg/yqlib/doc/operators/compare.md | 106 +++++ pkg/yqlib/doc/operators/equals.md | 7 + .../operators/headers/boolean-operators.md | 6 + pkg/yqlib/doc/operators/headers/compare.md | 15 + pkg/yqlib/doc/operators/headers/equals.md | 7 + pkg/yqlib/doc/operators/headers/select.md | 6 + pkg/yqlib/doc/operators/select.md | 6 + pkg/yqlib/expression_tokeniser.go | 7 + pkg/yqlib/lib.go | 4 +- 12 files changed, 673 insertions(+), 1 deletion(-) create mode 100644 pkg/yqlib/compare_operators.go create mode 100644 pkg/yqlib/compare_operators_test.go create mode 100644 pkg/yqlib/doc/operators/compare.md create mode 100644 pkg/yqlib/doc/operators/headers/compare.md diff --git a/pkg/yqlib/compare_operators.go b/pkg/yqlib/compare_operators.go new file mode 100644 index 00000000..663a7a8a --- /dev/null +++ b/pkg/yqlib/compare_operators.go @@ -0,0 +1,133 @@ +package yqlib + +import ( + "fmt" + "strconv" + "time" + + yaml "gopkg.in/yaml.v3" +) + +type compareTypePref struct { + OrEqual bool + Greater bool +} + +func compareOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { + log.Debugf("-- compareOperator") + prefs := expressionNode.Operation.Preferences.(compareTypePref) + return crossFunction(d, context.ReadOnlyClone(), expressionNode, compare(prefs), true) +} + +func compare(prefs compareTypePref) func(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) { + return func(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) { + log.Debugf("-- compare cross function") + if lhs == nil && rhs == nil { + owner := &CandidateNode{} + return createBooleanCandidate(owner, prefs.OrEqual), nil + } else if lhs == nil { + log.Debugf("lhs nil, but rhs is not") + return createBooleanCandidate(rhs, false), nil + } else if rhs == nil { + log.Debugf("rhs nil, but rhs is not") + return createBooleanCandidate(lhs, false), nil + } + + lhs.Node = unwrapDoc(lhs.Node) + rhs.Node = unwrapDoc(rhs.Node) + + switch lhs.Node.Kind { + case yaml.MappingNode: + return nil, fmt.Errorf("maps not yet supported for comparison") + case yaml.SequenceNode: + return nil, fmt.Errorf("arrays not yet supported for comparison") + default: + if rhs.Node.Kind != yaml.ScalarNode { + return nil, fmt.Errorf("%v (%v) cannot be subtracted from %v", rhs.Node.Tag, rhs.Path, lhs.Node.Tag) + } + target := lhs.CreateReplacement(&yaml.Node{}) + boolV, err := compareScalars(context, prefs, lhs.Node, rhs.Node) + + return createBooleanCandidate(target, boolV), err + } + } +} + +func compareDateTime(layout string, prefs compareTypePref, lhs *yaml.Node, rhs *yaml.Node) (bool, error) { + lhsTime, err := time.Parse(layout, lhs.Value) + if err != nil { + return false, err + } + + rhsTime, err := time.Parse(layout, rhs.Value) + if err != nil { + return false, err + } + + if prefs.OrEqual && lhsTime.Equal(rhsTime) { + return true, nil + } + if prefs.Greater { + return lhsTime.After(rhsTime), nil + } + return lhsTime.Before(rhsTime), nil + +} + +func compareScalars(context Context, prefs compareTypePref, lhs *yaml.Node, rhs *yaml.Node) (bool, error) { + lhsTag := guessTagFromCustomType(lhs) + rhsTag := guessTagFromCustomType(rhs) + + isDateTime := lhs.Tag == "!!timestamp" + // if the lhs is a string, it might be a timestamp in a custom format. + if lhsTag == "!!str" && context.GetDateTimeLayout() != time.RFC3339 { + _, err := time.Parse(context.GetDateTimeLayout(), lhs.Value) + isDateTime = err == nil + } + if isDateTime { + return compareDateTime(context.GetDateTimeLayout(), prefs, lhs, rhs) + } else if lhsTag == "!!int" && rhsTag == "!!int" { + _, lhsNum, err := parseInt(lhs.Value) + if err != nil { + return false, err + } + _, rhsNum, err := parseInt(rhs.Value) + if err != nil { + return false, err + } + + if prefs.OrEqual && lhsNum == rhsNum { + return true, nil + } + if prefs.Greater { + return lhsNum > rhsNum, nil + } + return lhsNum < rhsNum, nil + } else if (lhsTag == "!!int" || lhsTag == "!!float") && (rhsTag == "!!int" || rhsTag == "!!float") { + lhsNum, err := strconv.ParseFloat(lhs.Value, 64) + if err != nil { + return false, err + } + rhsNum, err := strconv.ParseFloat(rhs.Value, 64) + if err != nil { + return false, err + } + if prefs.OrEqual && lhsNum == rhsNum { + return true, nil + } + if prefs.Greater { + return lhsNum > rhsNum, nil + } + return lhsNum < rhsNum, nil + } else if lhsTag == "!!str" && rhsTag == "!!str" { + if prefs.OrEqual && lhs.Value == rhs.Value { + return true, nil + } + if prefs.Greater { + return lhs.Value > rhs.Value, nil + } + return lhs.Value < rhs.Value, nil + } + + return false, fmt.Errorf("%v not yet supported for comparison", lhs.Tag) +} diff --git a/pkg/yqlib/compare_operators_test.go b/pkg/yqlib/compare_operators_test.go new file mode 100644 index 00000000..bf07247f --- /dev/null +++ b/pkg/yqlib/compare_operators_test.go @@ -0,0 +1,371 @@ +package yqlib + +import ( + "testing" +) + +var compareOperatorScenarios = []expressionScenario{ + // ints, not equal + { + description: "Compare numbers (>)", + document: "a: 5\nb: 4", + expression: ".a > .b", + expected: []string{ + "D0, P[a], (!!bool)::true\n", + }, + }, + { + skipDoc: true, + document: "a: 5\nb: 4", + expression: ".a < .b", + expected: []string{ + "D0, P[a], (!!bool)::false\n", + }, + }, + { + skipDoc: true, + description: "Compare integers (>=)", + document: "a: 5\nb: 4", + expression: ".a >= .b", + expected: []string{ + "D0, P[a], (!!bool)::true\n", + }, + }, + { + skipDoc: true, + document: "a: 5\nb: 4", + expression: ".a <= .b", + expected: []string{ + "D0, P[a], (!!bool)::false\n", + }, + }, + + // ints, equal + { + skipDoc: true, + description: "Compare equal numbers (>)", + document: "a: 5\nb: 5", + expression: ".a > .b", + expected: []string{ + "D0, P[a], (!!bool)::false\n", + }, + }, + { + skipDoc: true, + document: "a: 5\nb: 5", + expression: ".a < .b", + expected: []string{ + "D0, P[a], (!!bool)::false\n", + }, + }, + { + description: "Compare equal numbers (>=)", + document: "a: 5\nb: 5", + expression: ".a >= .b", + expected: []string{ + "D0, P[a], (!!bool)::true\n", + }, + }, + { + skipDoc: true, + document: "a: 5\nb: 5", + expression: ".a <= .b", + expected: []string{ + "D0, P[a], (!!bool)::true\n", + }, + }, + + // floats, not equal + { + skipDoc: true, + document: "a: 5.2\nb: 4.1", + expression: ".a > .b", + expected: []string{ + "D0, P[a], (!!bool)::true\n", + }, + }, + { + skipDoc: true, + document: "a: 5.2\nb: 4.1", + expression: ".a < .b", + expected: []string{ + "D0, P[a], (!!bool)::false\n", + }, + }, + { + skipDoc: true, + document: "a: 5.2\nb: 4.1", + expression: ".a >= .b", + expected: []string{ + "D0, P[a], (!!bool)::true\n", + }, + }, + { + skipDoc: true, + document: "a: 5.5\nb: 4.1", + expression: ".a <= .b", + expected: []string{ + "D0, P[a], (!!bool)::false\n", + }, + }, + + // floats, equal + { + skipDoc: true, + document: "a: 5.5\nb: 5.5", + expression: ".a > .b", + expected: []string{ + "D0, P[a], (!!bool)::false\n", + }, + }, + { + skipDoc: true, + document: "a: 5.5\nb: 5.5", + expression: ".a < .b", + expected: []string{ + "D0, P[a], (!!bool)::false\n", + }, + }, + { + skipDoc: true, + document: "a: 5.1\nb: 5.1", + expression: ".a >= .b", + expected: []string{ + "D0, P[a], (!!bool)::true\n", + }, + }, + { + skipDoc: true, + document: "a: 5.1\nb: 5.1", + expression: ".a <= .b", + expected: []string{ + "D0, P[a], (!!bool)::true\n", + }, + }, + + // strings, not equal + { + description: "Compare strings", + subdescription: "Compares strings by their bytecode.", + document: "a: zoo\nb: apple", + expression: ".a > .b", + expected: []string{ + "D0, P[a], (!!bool)::true\n", + }, + }, + { + skipDoc: true, + document: "a: zoo\nb: apple", + expression: ".a < .b", + expected: []string{ + "D0, P[a], (!!bool)::false\n", + }, + }, + { + skipDoc: true, + document: "a: zoo\nb: apple", + expression: ".a >= .b", + expected: []string{ + "D0, P[a], (!!bool)::true\n", + }, + }, + { + skipDoc: true, + document: "a: zoo\nb: apple", + expression: ".a <= .b", + expected: []string{ + "D0, P[a], (!!bool)::false\n", + }, + }, + + // strings, equal + { + skipDoc: true, + document: "a: cat\nb: cat", + expression: ".a > .b", + expected: []string{ + "D0, P[a], (!!bool)::false\n", + }, + }, + { + skipDoc: true, + document: "a: cat\nb: cat", + expression: ".a < .b", + expected: []string{ + "D0, P[a], (!!bool)::false\n", + }, + }, + { + skipDoc: true, + document: "a: cat\nb: cat", + expression: ".a >= .b", + expected: []string{ + "D0, P[a], (!!bool)::true\n", + }, + }, + { + skipDoc: true, + document: "a: cat\nb: cat", + expression: ".a <= .b", + expected: []string{ + "D0, P[a], (!!bool)::true\n", + }, + }, + + // datetime, not equal + { + description: "Compare date times", + subdescription: "You can compare date times. Assumes RFC3339 date time format, see [date-time operators](https://mikefarah.gitbook.io/yq/operators/date-time-operators) for more information.", + document: "a: 2021-01-01T03:10:00Z\nb: 2020-01-01T03:10:00Z", + expression: ".a > .b", + expected: []string{ + "D0, P[a], (!!bool)::true\n", + }, + }, + { + skipDoc: true, + document: "a: 2021-01-01T03:10:00Z\nb: 2020-01-01T03:10:00Z", + expression: ".a < .b", + expected: []string{ + "D0, P[a], (!!bool)::false\n", + }, + }, + { + skipDoc: true, + document: "a: 2021-01-01T03:10:00Z\nb: 2020-01-01T03:10:00Z", + expression: ".a >= .b", + expected: []string{ + "D0, P[a], (!!bool)::true\n", + }, + }, + { + skipDoc: true, + document: "a: 2021-01-01T03:10:00Z\nb: 2020-01-01T03:10:00Z", + expression: ".a <= .b", + expected: []string{ + "D0, P[a], (!!bool)::false\n", + }, + }, + + // datetime, equal + { + skipDoc: true, + document: "a: 2021-01-01T03:10:00Z\nb: 2021-01-01T03:10:00Z", + expression: ".a > .b", + expected: []string{ + "D0, P[a], (!!bool)::false\n", + }, + }, + { + skipDoc: true, + document: "a: 2021-01-01T03:10:00Z\nb: 2021-01-01T03:10:00Z", + expression: ".a < .b", + expected: []string{ + "D0, P[a], (!!bool)::false\n", + }, + }, + { + skipDoc: true, + document: "a: 2021-01-01T03:10:00Z\nb: 2021-01-01T03:10:00Z", + expression: ".a >= .b", + expected: []string{ + "D0, P[a], (!!bool)::true\n", + }, + }, + { + skipDoc: true, + document: "a: 2021-01-01T03:10:00Z\nb: 2021-01-01T03:10:00Z", + expression: ".a <= .b", + expected: []string{ + "D0, P[a], (!!bool)::true\n", + }, + }, + // both null + { + description: "Both sides are null: > is false", + expression: ".a > .b", + expected: []string{ + "D0, P[], (!!bool)::false\n", + }, + }, + { + skipDoc: true, + expression: ".a < .b", + expected: []string{ + "D0, P[], (!!bool)::false\n", + }, + }, + { + description: "Both sides are null: >= is true", + expression: ".a >= .b", + expected: []string{ + "D0, P[], (!!bool)::true\n", + }, + }, + { + skipDoc: true, + expression: ".a <= .b", + expected: []string{ + "D0, P[], (!!bool)::true\n", + }, + }, + + // one null + { + skipDoc: true, + description: "One side is null: > is false", + document: `a: 5`, + expression: ".a > .b", + expected: []string{ + "D0, P[a], (!!bool)::false\n", + }, + }, + { + skipDoc: true, + document: `a: 5`, + expression: ".a < .b", + expected: []string{ + "D0, P[a], (!!bool)::false\n", + }, + }, + { + skipDoc: true, + description: "One side is null: >= is false", + document: `a: 5`, + expression: ".a >= .b", + expected: []string{ + "D0, P[a], (!!bool)::false\n", + }, + }, + { + skipDoc: true, + document: `a: 5`, + expression: ".a <= .b", + expected: []string{ + "D0, P[a], (!!bool)::false\n", + }, + }, + { + skipDoc: true, + document: `a: 5`, + expression: ".b <= .a", + expected: []string{ + "D0, P[a], (!!bool)::false\n", + }, + }, + { + skipDoc: true, + document: `a: 5`, + expression: ".b < .a", + expected: []string{ + "D0, P[a], (!!bool)::false\n", + }, + }, +} + +func TestCompareOperatorScenarios(t *testing.T) { + for _, tt := range compareOperatorScenarios { + testScenario(t, &tt) + } + documentOperatorScenarios(t, "compare", compareOperatorScenarios) +} diff --git a/pkg/yqlib/doc/operators/boolean-operators.md b/pkg/yqlib/doc/operators/boolean-operators.md index 70c19bd2..fb6ae394 100644 --- a/pkg/yqlib/doc/operators/boolean-operators.md +++ b/pkg/yqlib/doc/operators/boolean-operators.md @@ -10,6 +10,12 @@ The `or` and `and` operators take two parameters and return a boolean result. These are most commonly used with the `select` operator to filter particular nodes. +## Related Operators + +- equals / not equals (`==`, `!=`) operators (here)[https://mikefarah.gitbook.io/yq/operators/equals] +- comparison (`>=`, `<` etc) operators (here)[https://mikefarah.gitbook.io/yq/operators/compare] +- select operator (here)[https://mikefarah.gitbook.io/yq/operators/select] + {% hint style="warning" %} Note that versions prior to 4.18 require the 'eval/e' command to be specified. diff --git a/pkg/yqlib/doc/operators/compare.md b/pkg/yqlib/doc/operators/compare.md new file mode 100644 index 00000000..577e6692 --- /dev/null +++ b/pkg/yqlib/doc/operators/compare.md @@ -0,0 +1,106 @@ +# Compare Operators + +Comparison operators (`>`, `>=`, `<`, `<=`) can be used for comparing scalar values of the same time. + +The following types are currently supported: + +- numbers +- strings +- datetimes + +## Related Operators + +- equals / not equals (`==`, `!=`) operators (here)[https://mikefarah.gitbook.io/yq/operators/equals] +- boolean operators (`and`, `or`, `any` etc) (here)[https://mikefarah.gitbook.io/yq/operators/boolean-operators] +- select operator (here)[https://mikefarah.gitbook.io/yq/operators/select] + +{% hint style="warning" %} +Note that versions prior to 4.18 require the 'eval/e' command to be specified. + +`yq e ` +{% endhint %} + +## Compare numbers (>) +Given a sample.yml file of: +```yaml +a: 5 +b: 4 +``` +then +```bash +yq '.a > .b' sample.yml +``` +will output +```yaml +true +``` + +## Compare equal numbers (>=) +Given a sample.yml file of: +```yaml +a: 5 +b: 5 +``` +then +```bash +yq '.a >= .b' sample.yml +``` +will output +```yaml +true +``` + +## Compare strings +Compares strings by their bytecode. + +Given a sample.yml file of: +```yaml +a: zoo +b: apple +``` +then +```bash +yq '.a > .b' sample.yml +``` +will output +```yaml +true +``` + +## Compare date times +You can compare date times. Assumes RFC3339 date time format, see [date-time operators](https://mikefarah.gitbook.io/yq/operators/date-time-operators) for more information. + +Given a sample.yml file of: +```yaml +a: 2021-01-01T03:10:00Z +b: 2020-01-01T03:10:00Z +``` +then +```bash +yq '.a > .b' sample.yml +``` +will output +```yaml +true +``` + +## Both sides are null: > is false +Running +```bash +yq --null-input '.a > .b' +``` +will output +```yaml +false +``` + +## Both sides are null: >= is true +Running +```bash +yq --null-input '.a >= .b' +``` +will output +```yaml +true +``` + diff --git a/pkg/yqlib/doc/operators/equals.md b/pkg/yqlib/doc/operators/equals.md index d108c9f0..21e0f496 100644 --- a/pkg/yqlib/doc/operators/equals.md +++ b/pkg/yqlib/doc/operators/equals.md @@ -14,6 +14,13 @@ select(.a == .b) The not equals `!=` operator returns `false` if the LHS is equal to the RHS. +## Related Operators + +- comparison (`>=`, `<` etc) operators (here)[https://mikefarah.gitbook.io/yq/operators/compare] +- boolean operators (`and`, `or`, `any` etc) (here)[https://mikefarah.gitbook.io/yq/operators/boolean-operators] +- select operator (here)[https://mikefarah.gitbook.io/yq/operators/select] + + {% hint style="warning" %} Note that versions prior to 4.18 require the 'eval/e' command to be specified. diff --git a/pkg/yqlib/doc/operators/headers/boolean-operators.md b/pkg/yqlib/doc/operators/headers/boolean-operators.md index ddf571a4..ac3072e1 100644 --- a/pkg/yqlib/doc/operators/headers/boolean-operators.md +++ b/pkg/yqlib/doc/operators/headers/boolean-operators.md @@ -9,3 +9,9 @@ The `or` and `and` operators take two parameters and return a boolean result. `any_c(condition)` and `all_c(condition)` are like `any` and `all` but they take a condition expression that is used against each element to determine if it's `true`. Note: in `jq` you can simply pass a condition to `any` or `all` and it simply works - `yq` isn't that clever..yet These are most commonly used with the `select` operator to filter particular nodes. + +## Related Operators + +- equals / not equals (`==`, `!=`) operators (here)[https://mikefarah.gitbook.io/yq/operators/equals] +- comparison (`>=`, `<` etc) operators (here)[https://mikefarah.gitbook.io/yq/operators/compare] +- select operator (here)[https://mikefarah.gitbook.io/yq/operators/select] diff --git a/pkg/yqlib/doc/operators/headers/compare.md b/pkg/yqlib/doc/operators/headers/compare.md new file mode 100644 index 00000000..a51b5b33 --- /dev/null +++ b/pkg/yqlib/doc/operators/headers/compare.md @@ -0,0 +1,15 @@ +# Compare Operators + +Comparison operators (`>`, `>=`, `<`, `<=`) can be used for comparing scalar values of the same time. + +The following types are currently supported: + +- numbers +- strings +- datetimes + +## Related Operators + +- equals / not equals (`==`, `!=`) operators (here)[https://mikefarah.gitbook.io/yq/operators/equals] +- boolean operators (`and`, `or`, `any` etc) (here)[https://mikefarah.gitbook.io/yq/operators/boolean-operators] +- select operator (here)[https://mikefarah.gitbook.io/yq/operators/select] diff --git a/pkg/yqlib/doc/operators/headers/equals.md b/pkg/yqlib/doc/operators/headers/equals.md index de32f5f0..ee47f47d 100644 --- a/pkg/yqlib/doc/operators/headers/equals.md +++ b/pkg/yqlib/doc/operators/headers/equals.md @@ -13,3 +13,10 @@ select(.a == .b) ``` The not equals `!=` operator returns `false` if the LHS is equal to the RHS. + +## Related Operators + +- comparison (`>=`, `<` etc) operators (here)[https://mikefarah.gitbook.io/yq/operators/compare] +- boolean operators (`and`, `or`, `any` etc) (here)[https://mikefarah.gitbook.io/yq/operators/boolean-operators] +- select operator (here)[https://mikefarah.gitbook.io/yq/operators/select] + diff --git a/pkg/yqlib/doc/operators/headers/select.md b/pkg/yqlib/doc/operators/headers/select.md index b1851e7e..dba72f2f 100644 --- a/pkg/yqlib/doc/operators/headers/select.md +++ b/pkg/yqlib/doc/operators/headers/select.md @@ -1,3 +1,9 @@ # Select Select is used to filter arrays and maps by a boolean expression. + +## Related Operators + +- equals / not equals (`==`, `!=`) operators (here)[https://mikefarah.gitbook.io/yq/operators/equals] +- comparison (`>=`, `<` etc) operators (here)[https://mikefarah.gitbook.io/yq/operators/compare] +- boolean operators (`and`, `or`, `any` etc) (here)[https://mikefarah.gitbook.io/yq/operators/boolean-operators] diff --git a/pkg/yqlib/doc/operators/select.md b/pkg/yqlib/doc/operators/select.md index 43a35f12..7912fb11 100644 --- a/pkg/yqlib/doc/operators/select.md +++ b/pkg/yqlib/doc/operators/select.md @@ -2,6 +2,12 @@ Select is used to filter arrays and maps by a boolean expression. +## Related Operators + +- equals / not equals (`==`, `!=`) operators (here)[https://mikefarah.gitbook.io/yq/operators/equals] +- comparison (`>=`, `<` etc) operators (here)[https://mikefarah.gitbook.io/yq/operators/compare] +- boolean operators (`and`, `or`, `any` etc) (here)[https://mikefarah.gitbook.io/yq/operators/boolean-operators] + {% hint style="warning" %} Note that versions prior to 4.18 require the 'eval/e' command to be specified. diff --git a/pkg/yqlib/expression_tokeniser.go b/pkg/yqlib/expression_tokeniser.go index ded0bd7e..8c62c1c7 100644 --- a/pkg/yqlib/expression_tokeniser.go +++ b/pkg/yqlib/expression_tokeniser.go @@ -509,6 +509,13 @@ func initLexer() (*lex.Lexer, error) { lexer.Add([]byte(`\s*==\s*`), opToken(equalsOpType)) lexer.Add([]byte(`\s*!=\s*`), opToken(notEqualsOpType)) + + lexer.Add([]byte(`\s*>=\s*`), opTokenWithPrefs(compareOpType, nil, compareTypePref{OrEqual: true, Greater: true})) + lexer.Add([]byte(`\s*>\s*`), opTokenWithPrefs(compareOpType, nil, compareTypePref{OrEqual: false, Greater: true})) + + lexer.Add([]byte(`\s*<=\s*`), opTokenWithPrefs(compareOpType, nil, compareTypePref{OrEqual: true, Greater: false})) + lexer.Add([]byte(`\s*<\s*`), opTokenWithPrefs(compareOpType, nil, compareTypePref{OrEqual: false, Greater: false})) + lexer.Add([]byte(`\s*=\s*`), assignOpToken(false)) lexer.Add([]byte(`del`), opToken(deleteChildOpType)) diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go index 3c25aafd..097a58c2 100644 --- a/pkg/yqlib/lib.go +++ b/pkg/yqlib/lib.go @@ -72,7 +72,9 @@ var subtractOpType = &operationType{Type: "SUBTRACT", NumArgs: 2, Precedence: 42 var alternativeOpType = &operationType{Type: "ALTERNATIVE", NumArgs: 2, Precedence: 42, Handler: alternativeOperator} var equalsOpType = &operationType{Type: "EQUALS", NumArgs: 2, Precedence: 40, Handler: equalsOperator} -var notEqualsOpType = &operationType{Type: "EQUALS", NumArgs: 2, Precedence: 40, Handler: notEqualsOperator} +var notEqualsOpType = &operationType{Type: "NOT_EQUALS", NumArgs: 2, Precedence: 40, Handler: notEqualsOperator} + +var compareOpType = &operationType{Type: "COMPARE", NumArgs: 2, Precedence: 40, Handler: compareOperator} //createmap needs to be above union, as we use union to build the components of the objects var createMapOpType = &operationType{Type: "CREATE_MAP", NumArgs: 2, Precedence: 15, Handler: createMapOperator}