Skip to content

techerfan/expression

Repository files navigation

Expression

Expression is a basic expression parser and compiler, implemented in Golang for handling simple sytanxes such as mathematical operations (+, -, *, /, %), logical operations (==, <, >, <=, >=, !=, &&, ||), unary operations (-, !), and bitwise operations (>>, <<, ^, |, &).

Installation

To use the package inside your golang project, simply run the following command:

go get github.com/techerfan/expression

How to use?

To parse a string expression, first you need a to make a sytanx tree. During this process, lexer will tokenize the expression and makes tree for you:

tree := syntax.Parse("1 + 2")

The result of the parsing, contains an array named Diagnostics that helps you to diagnose errors that happens during the process. So basically when the length of the Diagnostics array is greater than zero, the provided expression is not valid and has syntax issues. Otherwise, we are good to continue:

if len(tree.Diagnostics) > 0 {
  for _, d := range tree.Diagnostics {
    fmt.Println(d.Message, d.Span, d.Span.Length, d.Span.Start)
  }
  panic(1)
}

If any variable is used in the expression, the parser will return them to us:

// Imagine the expression was "variable1 + variable2":
for _, v := range tree.Variables() {
  fmt.Println(v)
}
// The result would be:
// variable1
// variable2

After parsing the expression, it needs to be compiled:

compilationResult := expression.NewCompilation(tree)

Lastly, the compilation result must be evaluated. If variables are used inside the express, we must tell the evaluator. Otherwise, pass an empty map:

// No variables used 
result := compilationResult.Evaluate(map[*contracts.VariableSymbol]interface{}{})

// Variables used:
var variables = map[*contracts.VariableSymbol]interface{}{
  // for each variable, you must specify the type of it:
  contracts.NewVariableSymbol("variable1", reflect.Float64): 10.0,
  contracts.NewVariableSymbol("variable2", reflect.Float64): 11.0,
}

result := compilationResult.Evaluate(variables)

It is possible to face errors in the evaluation process. Just like the syntax tree, the result of the evaluation contains a Diagnostics array (if it is empty, we are good to go). To get the result value, there are mainly two ways, get it as aninterface{} or casted as a float64:

if len(result.Diagnostics) > 0 {
  for _, d := range tree.Diagnostics {
    fmt.Println(d.Message, d.Span, d.Span.Length, d.Span.Start)
  }
  panic(1)
}

// Printing the result as an interface{}
fmt.Println(result.Value)

// Printing the result as a float64
fmt.Println(result.FloatCastedValue)

Sample Code

The whole code with the using of variables:

tree := syntax.Parse("variable1 + variable2")

// Check if parsing is done without any error
if len(tree.Diagnostics) > 0 {
  for _, d := range tree.Diagnostics {
    fmt.Println(d.Message, d.Span, d.Span.Length, d.Span.Start)
  }
  panic(1)
}

variables := make(map[*contracts.VariableSymbol]interface{})
for _, v := range tree.Variables() {
  variables[contracts.NewVariableSymbol(v, reflect.Float64)] = 10.0  
}

result := compilationResult.Evaluate(variables)

// Check if evaluation is done without any error
if len(result.Diagnostics) > 0 {
  for _, d := range tree.Diagnostics {
    fmt.Println(d.Message, d.Span, d.Span.Length, d.Span.Start)
  }
  panic(1)
}

// Printing the result as an interface{}
fmt.Println(result.Value)

// Printing the result as a float64
fmt.Println(result.FloatCastedValue)

Operators Support

Mathematical operations

Name Operator Sample
Addition + var1 + var2
Substraction - var1 - var2
Multiplication * var1 * var2
Division / var1 / var2

Bitwise operations

Name Operator Sample
Shift right >> var1 >> var2
Shift Left << var1 << var2
AND & var1 & var2
OR | var1 | var2
XOR ^ var1 ^ var2

Logical operations

Name Operator Sample
Equal == var1 == var2
Not Equal != var1 != var2
Logical And && var1 && var2
Logical Or || var1 || var2
Greater Than or Equal >= var1 >= var2
Lesser Than or Equal <= var1 <= var2
Greater Than > var1 > var2
Lesser Than < var1 < var2

Unary

Name Operator Sample
Logical Negation ! !bool_var1
Negation - -var1

Parenthesis

Name Operator Sample
Parenthesis () ((var1 + var2) * (var1 - var2)) / 2

Assignment

Name Operator Sample
Assignment = var3 = var1 + var2

Acknowledgements

This package is built based on the Immo Landwerth's tutorial on youtube. Many thanks to him for making such a great content.

Contributing

Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.

Please make sure to update tests as appropriate.

License

MIT