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 3588ac7
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 22 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
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 3588ac7

Please sign in to comment.