Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for bitwise operators. #20

Merged
merged 1 commit into from
Jul 26, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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