From c6a6abe70486918ac89ef93e642bd9f9076d08bc Mon Sep 17 00:00:00 2001 From: wmiller848 Date: Mon, 18 Jul 2016 02:37:46 -0700 Subject: [PATCH] Add support for bitwise operators. `&` (and), `|` (or), `^` (xor), and `~` (not) are now supported. Float64 values are truncated to int64 then converted back. The `exponent` operator is now `**`. --- EvaluableExpression.go | 65 +++++++++++++++++++++++++++++++++++++++++- OperatorToken.go | 26 ++++++++++++----- README.md | 13 +++++---- evaluation_test.go | 44 +++++++++++++++++++++++++++- parsingFailure_test.go | 14 +-------- 5 files changed, 135 insertions(+), 27 deletions(-) diff --git a/EvaluableExpression.go b/EvaluableExpression.go index 7401f66..afcae20 100644 --- a/EvaluableExpression.go +++ b/EvaluableExpression.go @@ -256,7 +256,7 @@ func evaluateComparator(stream *tokenStream, parameters Parameters) (interface{} var err error var keyFound bool - value, err = evaluateAdditiveModifier(stream, parameters) + value, err = evaluateBitwiseModifier(stream, parameters) if err != nil { return nil, err @@ -346,6 +346,66 @@ func evaluateComparator(stream *tokenStream, parameters Parameters) (interface{} return value, nil } +func evaluateBitwiseModifier(stream *tokenStream, parameters Parameters) (interface{}, error) { + + var token ExpressionToken + var value, rightValue interface{} + var symbol OperatorSymbol + var err error + var keyFound bool + + value, err = evaluateAdditiveModifier(stream, parameters) + + if err != nil { + return nil, err + } + + for stream.hasNext() { + + token = stream.next() + + if !isString(token.Value) { + break + } + + symbol, keyFound = MODIFIER_SYMBOLS[token.Value.(string)] + if !keyFound { + break + } + + // short circuit if this is, in fact, not bitwise. + if !symbol.IsModifierType(BITWISE_MODIFIERS) { + stream.rewind() + return value, nil + } + + rightValue, err = evaluateBitwiseModifier(stream, parameters) + if err != nil { + return nil, err + } + + // make sure that we're only operating on the appropriate types + if !isFloat64(value) { + return nil, errors.New(fmt.Sprintf("Value '%v' cannot be used with the modifier '%v', it is not a number", value, token.Value)) + } + if !isFloat64(rightValue) { + return nil, errors.New(fmt.Sprintf("Value '%v' cannot be used with the modifier '%v', it is not a number", rightValue, token.Value)) + } + + switch symbol { + case BITWISE_AND: + return float64(int64(value.(float64)) & int64(rightValue.(float64))), nil + case BITWISE_OR: + return float64(int64(value.(float64)) | int64(rightValue.(float64))), nil + case BITWISE_XOR: + return float64(int64(value.(float64)) ^ int64(rightValue.(float64))), nil + } + } + + stream.rewind() + return value, nil +} + func evaluateAdditiveModifier(stream *tokenStream, parameters Parameters) (interface{}, error) { var token ExpressionToken @@ -565,6 +625,9 @@ func evaluatePrefix(stream *tokenStream, parameters Parameters) (interface{}, er case NEGATE: return -value.(float64), nil + case BITWISE_NOT: + return float64(^int64(value.(float64))), nil + default: stream.rewind() return value, nil diff --git a/OperatorToken.go b/OperatorToken.go index a970acb..38b7b16 100644 --- a/OperatorToken.go +++ b/OperatorToken.go @@ -21,6 +21,9 @@ const ( PLUS MINUS + BITWISE_AND + BITWISE_OR + BITWISE_XOR MULTIPLY DIVIDE MODULUS @@ -28,6 +31,7 @@ const ( NEGATE INVERT + BITWISE_NOT TERNARY_TRUE TERNARY_FALSE @@ -58,18 +62,22 @@ var LOGICAL_SYMBOLS = map[string]OperatorSymbol{ var MODIFIER_SYMBOLS = map[string]OperatorSymbol{ - "+": PLUS, - "-": MINUS, - "*": MULTIPLY, - "/": DIVIDE, - "%": MODULUS, - "^": EXPONENT, + "+": PLUS, + "-": MINUS, + "&": BITWISE_AND, + "|": BITWISE_OR, + "^": BITWISE_XOR, + "*": MULTIPLY, + "/": DIVIDE, + "%": MODULUS, + "**": EXPONENT, } var PREFIX_SYMBOLS = map[string]OperatorSymbol{ "-": NEGATE, "!": INVERT, + "~": BITWISE_NOT, } var TERNARY_SYMBOLS = map[string]OperatorSymbol{ @@ -81,6 +89,10 @@ var ADDITIVE_MODIFIERS = []OperatorSymbol{ PLUS, MINUS, } +var BITWISE_MODIFIERS = []OperatorSymbol{ + BITWISE_AND, BITWISE_OR, BITWISE_XOR, +} + var MULTIPLICATIVE_MODIFIERS = []OperatorSymbol{ MULTIPLY, DIVIDE, MODULUS, } @@ -90,7 +102,7 @@ var EXPONENTIAL_MODIFIERS = []OperatorSymbol{ } var PREFIX_MODIFIERS = []OperatorSymbol{ - NEGATE, INVERT, + NEGATE, INVERT, BITWISE_NOT, } var NUMERIC_COMPARATORS = []OperatorSymbol{ diff --git a/README.md b/README.md index 43733a6..fb4a0c4 100644 --- a/README.md +++ b/README.md @@ -125,7 +125,7 @@ Backslashes can be used anywhere in an expression to escape the very next charac What operators and types does this support? -- -* Modifiers: `+` `-` `/` `*` `^` `%` +* Modifiers: `+` `-` `/` `*` `&` `|` `^` `**` `%` * Comparators: `>` `>=` `<` `<=` `==` `!=` `=~` `!~` * Logical ops: `||` `&&` * Numeric constants, as 64-bit floating point (`12345.678`) @@ -157,14 +157,17 @@ Note that this table shows what each type supports - if you use an operator then | - | Subtracts | **X** | **X** | | / | Divides | **X** | **X** | | * | Multiplies | **X** | **X** | -| ^ | Takes to the power of | **X** | **X** | +| & | Bitwise and | **X** | **X** | +|\| | Bitwise or | **X** | **X** | +| ^ | Bitwise xor | **X** | **X** | +| ** | Takes to the power of | **X** | **X** | | % | Modulo | **X** | **X** | | Greater/Lesser (> >= < <=) | Valid | **X** | **X** | | Equality (== !=) | Checks by value | Checks by value | Checks by value | -| Ternary (? :) | **X** | **X** | Checks by value | -| Regex (=~ !~) | **X** | Regex | **X** | +| Ternary (? :) | **X** | **X** | Checks by value | +| Regex (=~ !~) | **X** | Regex | **X** | | ! | **X** | **X** | Inverts | -| Negate (-) | Multiplies by -1 | **X** | **X** | +| Negate (-) | Multiplies by -1 | **X** | **X** | It may, at first, not make sense why a Date supports all the same things as a number. In this library, dates are treated as the unix time. That is, the number of seconds since epoch. In practice this means that sub-second precision with this library is impossible (drop an issue in Github if this is a deal-breaker for you). It also, by association, means that you can do operations that you may not expect, like taking a date to the power of two. The author sees no harm in this. Your date probably appreciates it. diff --git a/evaluation_test.go b/evaluation_test.go index d98208b..96b4151 100644 --- a/evaluation_test.go +++ b/evaluation_test.go @@ -37,6 +37,30 @@ func TestNoParameterEvaluation(test *testing.T) { Input: "100 - 51", Expected: 49.0, }, + EvaluationTest{ + + Name: "Single BITWISE AND", + Input: "100 & 50", + Expected: 32.0, + }, + EvaluationTest{ + + Name: "Single BITWISE OR", + Input: "100 | 50", + Expected: 118.0, + }, + EvaluationTest{ + + Name: "Single BITWISE XOR", + Input: "100 ^ 50", + Expected: 86.0, + }, + EvaluationTest{ + + Name: "Single BITWISE NOT", + Input: "~10", + Expected: -11.0, + }, EvaluationTest{ Name: "Single MULTIPLY", @@ -61,12 +85,24 @@ func TestNoParameterEvaluation(test *testing.T) { Input: "101 % 2", Expected: 1.0, }, + EvaluationTest{ + + Name: "Single EXPONENT", + Input: "10 ** 2", + Expected: 100.0, + }, EvaluationTest{ Name: "Compound PLUS", Input: "20 + 30 + 50", Expected: 100.0, }, + EvaluationTest{ + + Name: "Compound BITWISE AND", + Input: "20 & 30 & 50", + Expected: 16.0, + }, EvaluationTest{ Name: "Mutiple operators", @@ -85,6 +121,12 @@ func TestNoParameterEvaluation(test *testing.T) { Input: "50 + (5 * (15 - 5))", Expected: 100.0, }, + EvaluationTest{ + + Name: "Nested parentheses with bitwise", + Input: "100 ^ (23 * (2 | 5))", + Expected: 197.0, + }, EvaluationTest{ Name: "Logical OR operation of two clauses", @@ -178,7 +220,7 @@ func TestNoParameterEvaluation(test *testing.T) { EvaluationTest{ Name: "Exponent precedence", - Input: "1 + 5 ^ 3 % 2 * 5", + Input: "1 + 5 ** 3 % 2 * 5", Expected: 6.0, }, EvaluationTest{ diff --git a/parsingFailure_test.go b/parsingFailure_test.go index 41aac07..5627249 100644 --- a/parsingFailure_test.go +++ b/parsingFailure_test.go @@ -13,7 +13,7 @@ const ( INVALID_TOKEN_KIND = "Invalid token" UNCLOSED_QUOTES = "Unclosed string literal" UNCLOSED_BRACKETS = "Unclosed parameter bracket" - UNBALANCED_PARENTHESIS = "Unbalanced parenthesis" + UNBALANCED_PARENTHESIS = "Unbalanced parenthesis" ) /* @@ -41,18 +41,6 @@ func TestParsingFailure(test *testing.T) { Input: "1 === 1", Expected: INVALID_TOKEN_KIND, }, - ParsingFailureTest{ - - Name: "Half of a logical operator", - Input: "true & false", - Expected: INVALID_TOKEN_KIND, - }, - ParsingFailureTest{ - - Name: "Half of a logical operator", - Input: "true | false", - Expected: INVALID_TOKEN_KIND, - }, ParsingFailureTest{ Name: "Too many characters for logical operator",