Skip to content

Commit

Permalink
Add support for bitwise operators. & (and), | (or), ^ (xor), an…
Browse files Browse the repository at this point in the history
…d `~` (not) are now supported. Float64 values are truncated to int64 then converted back. The `exponent` operator is now `**`.
  • Loading branch information
wmiller848 committed Jul 19, 2016
1 parent c829088 commit c6a6abe
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 27 deletions.
65 changes: 64 additions & 1 deletion EvaluableExpression.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
26 changes: 19 additions & 7 deletions OperatorToken.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,17 @@ const (

PLUS
MINUS
BITWISE_AND
BITWISE_OR
BITWISE_XOR
MULTIPLY
DIVIDE
MODULUS
EXPONENT

NEGATE
INVERT
BITWISE_NOT

TERNARY_TRUE
TERNARY_FALSE
Expand Down Expand Up @@ -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{
Expand All @@ -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,
}
Expand All @@ -90,7 +102,7 @@ var EXPONENTIAL_MODIFIERS = []OperatorSymbol{
}

var PREFIX_MODIFIERS = []OperatorSymbol{
NEGATE, INVERT,
NEGATE, INVERT, BITWISE_NOT,
}

var NUMERIC_COMPARATORS = []OperatorSymbol{
Expand Down
13 changes: 8 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`)
Expand Down Expand Up @@ -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.

Expand Down
44 changes: 43 additions & 1 deletion evaluation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand All @@ -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",
Expand Down Expand Up @@ -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{
Expand Down
14 changes: 1 addition & 13 deletions parsingFailure_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

/*
Expand Down Expand Up @@ -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",
Expand Down

0 comments on commit c6a6abe

Please sign in to comment.