├── .gitignore ├── .travis.yml ├── LICENSE.txt ├── README.md ├── evaler.go ├── evaler_test.go └── stack ├── stack.go └── stack_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/vim 2 | 3 | ### Vim ### 4 | # Swap 5 | [._]*.s[a-v][a-z] 6 | [._]*.sw[a-p] 7 | [._]s[a-rt-v][a-z] 8 | [._]ss[a-gi-z] 9 | [._]sw[a-p] 10 | 11 | # Session 12 | Session.vim 13 | 14 | # Temporary 15 | .netrwhist 16 | *~ 17 | # Auto-generated tag files 18 | tags 19 | # Persistent undo 20 | [._]*.un~ 21 | 22 | # End of https://www.gitignore.io/api/vim 23 | 24 | # Golang 25 | .idea/ 26 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.8 5 | - 1.9 6 | - "1.10" 7 | 8 | install: 9 | - go get github.com/stretchr/testify/assert 10 | - go get github.com/soniah/evaler 11 | 12 | script: 13 | - go test 14 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (C) 2012 Sonia Hamilton sonia@snowfrog.net 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | * Neither the name of the Sonia Hamilton nor the 12 | names of its contributors may be used to endorse or promote products 13 | derived from this software without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | evaler 2 | ====== 3 | 4 | [![Build Status](https://travis-ci.org/soniah/evaler.svg?branch=master)](https://travis-ci.org/soniah/evaler) 5 | [![Coverage](http://gocover.io/_badge/github.com/soniah/evaler)](http://gocover.io/github.com/soniah/evaler) 6 | [![GoDoc](https://godoc.org/github.com/soniah/evaler?status.png)](http://godoc.org/github.com/soniah/evaler) 7 | https://github.com/soniah/evaler 8 | 9 | Package evaler implements a simple floating point arithmetic expression evaluator. 10 | 11 | Evaler uses Dijkstra's [Shunting Yard algorithm](http://en.wikipedia.org/wiki/Shunting-yard_algorithm) to convert an 12 | infix expression to [postfix/RPN format](http://en.wikipedia.org/wiki/Reverse_Polish_notation), then evaluates 13 | the RPN expression. The implementation is adapted from a [Java implementation](http://willcode4beer.com/design.jsp?set=evalInfix). The results 14 | are returned as a `*big.Rat`. 15 | 16 | Usage 17 | ----- 18 | 19 | ```go 20 | result, err := evaler.Eval("1+2") 21 | ``` 22 | 23 | Operators 24 | --------- 25 | 26 | The operators supported are: 27 | 28 | ```+ - * / ^ ** () < > <= >= == !=``` 29 | 30 | (`^` and `**` are both exponent operators) 31 | 32 | Logical operators like `<` (less than) or `>` (greater than) get lowest precedence, 33 | all other precedence is as expected - 34 | [BODMAS](http://www.mathsisfun.com/operation-order-bodmas.html). 35 | 36 | Logical tests like `<` and `>` tests will evaluate to 0.0 for false and 1.0 37 | for true, allowing expressions like: 38 | 39 | ``` 40 | 3 * (1 < 2) # returns 3.0 41 | 3 * (1 > 2) # returns 0.0 42 | ``` 43 | 44 | Minus implements both binary and unary operations. 45 | 46 | See `evaler_test.go` for more examples of using operators. 47 | 48 | Trigonometric Operators 49 | ----------------------- 50 | 51 | The trigonometric operators supported are: 52 | 53 | ```sin, cos, tan, ln, arcsin, arccos, arctan``` 54 | 55 | For example: 56 | 57 | ``` 58 | cos(1) 59 | sin(2-1) 60 | sin(1)+2**2 61 | ``` 62 | 63 | See `evaler_test.go` for more examples of using trigonometric operators. 64 | 65 | Variables 66 | --------- 67 | 68 | `EvalWithVariables()` allows variables to be passed into expressions, 69 | for example evaluate `"x + 1"`, where `x=5`. 70 | 71 | See `evaler_test.go` for more examples of using variables. 72 | 73 | Issues 74 | ------ 75 | 76 | The `math/big` library doesn't have an exponent function `**` and implenting one 77 | for `big.Rat` numbers is non-trivial. As a work around, arguments are converted 78 | to float64's, the calculation is done using the `math.Pow()` function, the 79 | result is converted to a `big.Rat` and placed back on the stack. 80 | 81 | * floating point numbers missing leading digits (like `".5 * 2"`) are failing - PR's welcome 82 | 83 | Documentation 84 | ------------- 85 | 86 | http://godoc.org/github.com/soniah/evaler 87 | 88 | There are also a number of utility functions e.g. `BigratToFloat()`, 89 | `BigratToInt()` that may be useful when working with evaler. 90 | 91 | Contributions 92 | ------------- 93 | 94 | Contributions are welcome. 95 | 96 | If you've never contributed to a Go project before here is an example workflow. 97 | 98 | 1. [fork this repo on the GitHub webpage](https://github.com/soniah/evaler/fork) 99 | 1. `go get github.com/soniah/evaler` 100 | 1. `cd $GOPATH/src/github.com/soniah/evaler` 101 | 1. `git remote rename origin upstream` 102 | 1. `git remote add origin git@github.com:/evaler.git` 103 | 1. `git checkout -b development` 104 | 1. `git push -u origin development` (setup where you push to, check it works) 105 | 106 | Author 107 | ------ 108 | 109 | Sonia Hamilton sonia@snowfrog.net 110 | 111 | Dem Waffles dem-waffles@server.fake - trigonometric operators 112 | 113 | License 114 | ------- 115 | 116 | Modified BSD License (BSD-3) 117 | 118 | Links 119 | ----- 120 | 121 | [1] http://en.wikipedia.org/wiki/Shunting-yard_algorithm 122 | 123 | [2] http://en.wikipedia.org/wiki/Reverse_Polish_notation 124 | 125 | [3] http://willcode4beer.com/design.jsp?set=evalInfix 126 | 127 | [4] http://www.mathsisfun.com/operation-order-bodmas.html 128 | 129 | -------------------------------------------------------------------------------- /evaler.go: -------------------------------------------------------------------------------- 1 | // Package evaler implements a simple fp arithmetic expression evaluator. 2 | // 3 | // See README.md for documentation. 4 | 5 | package evaler 6 | 7 | import ( 8 | "fmt" 9 | "math" 10 | "math/big" 11 | "regexp" 12 | "strconv" 13 | "strings" 14 | 15 | "github.com/soniah/evaler/stack" 16 | ) 17 | 18 | // all regex's here 19 | var fp_rx = regexp.MustCompile(`(\d+(?:\.\d+)?)`) // simple fp number 20 | var functions_rx = regexp.MustCompile(`(sin|cos|tan|ln|arcsin|arccos|arctan|sqrt)`) 21 | var symbols_rx *regexp.Regexp // TODO used as a mutable global variable!! 22 | var unary_minus_rx = regexp.MustCompile(`((?:^|[-+^%*/<>!=(])\s*)-`) 23 | var whitespace_rx = regexp.MustCompile(`\s+`) 24 | 25 | var symbolTable map[string]string // TODO used as a mutable global variable!! 26 | 27 | // Operator '@' means unary minus 28 | var operators = []string{"-", "+", "*", "/", "<", ">", "@", "^", "**", "%", "!=", "==", ">=", "<="} 29 | 30 | // prec returns the operator's precedence 31 | func prec(op string) (result int) { 32 | if op == "-" || op == "+" { 33 | result = 1 34 | } else if op == "*" || op == "/" { 35 | result = 2 36 | } else if op == "^" || op == "%" || op == "**" { 37 | result = 3 38 | } else if op == "@" { 39 | result = 4 40 | } else if functions_rx.MatchString(op) { 41 | result = 5 42 | } else { 43 | result = 0 44 | } 45 | return 46 | } 47 | 48 | // opGTE returns true if op1's precedence is >= op2 49 | func opGTE(op1, op2 string) bool { 50 | return prec(op1) >= prec(op2) 51 | } 52 | 53 | func isFunction(token string) bool { 54 | return functions_rx.MatchString(token) 55 | } 56 | 57 | // isOperator returns true if token is an operator 58 | func isOperator(token string) bool { 59 | for _, v := range operators { 60 | if v == token { 61 | return true 62 | } 63 | } 64 | return false 65 | } 66 | 67 | // isOperand returns true if token is an operand 68 | func isOperand(token string) bool { 69 | return fp_rx.MatchString(token) 70 | } 71 | 72 | func isSymbol(token string) bool { 73 | for k := range symbolTable { 74 | if k == token { 75 | return true 76 | } 77 | } 78 | return false 79 | } 80 | 81 | // convert2postfix converts an infix expression to postfix 82 | func convert2postfix(tokens []string) []string { 83 | var stack stack.Stack 84 | var result []string 85 | for _, token := range tokens { 86 | 87 | stackString := fmt.Sprint(stack) // HACK - debugging 88 | stackString += "" // HACK - debugging 89 | 90 | if isOperator(token) { 91 | OPERATOR: 92 | for { 93 | top, err := stack.Top() 94 | if err == nil && top != "(" { 95 | if opGTE(top.(string), token) { 96 | pop, _ := stack.Pop() 97 | result = append(result, pop.(string)) 98 | continue 99 | } else { 100 | break OPERATOR 101 | } 102 | } 103 | break OPERATOR 104 | } 105 | stack.Push(token) 106 | 107 | } else if isFunction(token) { 108 | FUNCTION: 109 | for { 110 | top, err := stack.Top() 111 | if err == nil && top != "(" { 112 | if opGTE(top.(string), token) { 113 | pop, _ := stack.Pop() 114 | result = append(result, pop.(string)) 115 | } 116 | } else { 117 | break FUNCTION 118 | } 119 | break FUNCTION 120 | } 121 | stack.Push(token) 122 | 123 | } else if token == "(" { 124 | stack.Push(token) 125 | 126 | } else if token == ")" { 127 | PAREN: 128 | for { 129 | top, err := stack.Top() 130 | if err == nil && top != "(" { 131 | pop, _ := stack.Pop() 132 | result = append(result, pop.(string)) 133 | } else { 134 | stack.Pop() // pop off "(" 135 | break PAREN 136 | } 137 | } 138 | 139 | } else if isOperand(token) { 140 | result = append(result, token) 141 | 142 | } else if isSymbol(token) { 143 | result = append(result, symbolTable[token]) 144 | 145 | } else { 146 | result = append(result, token) 147 | } 148 | 149 | } 150 | 151 | for !stack.IsEmpty() { 152 | pop, _ := stack.Pop() 153 | result = append(result, pop.(string)) 154 | } 155 | 156 | return result 157 | } 158 | 159 | // evaluatePostfix takes a postfix expression and evaluates it 160 | func evaluatePostfix(postfix []string) (*big.Rat, error) { 161 | var stack stack.Stack 162 | result := new(big.Rat) // note: a new(big.Rat) has value "0/1" ie zero 163 | for _, token := range postfix { 164 | 165 | stackString := fmt.Sprint(stack) // HACK - debugging 166 | stackString += "" // HACK - debugging 167 | 168 | if isOperand(token) { 169 | bigrat := new(big.Rat) 170 | if _, err := fmt.Sscan(token, bigrat); err != nil { 171 | return nil, fmt.Errorf("unable to scan %s", token) 172 | } 173 | stack.Push(bigrat) 174 | 175 | } else if isOperator(token) { 176 | 177 | op2, err2 := stack.Pop() 178 | if err2 != nil { 179 | return nil, err2 180 | } 181 | 182 | var op1 interface{} 183 | if token != "@" { 184 | var err1 error 185 | if op1, err1 = stack.Pop(); err1 != nil { 186 | return nil, err1 187 | } 188 | } 189 | 190 | dummy := new(big.Rat) 191 | switch token { 192 | case "**", "^": 193 | float1 := BigratToFloat(op1.(*big.Rat)) 194 | float2 := BigratToFloat(op2.(*big.Rat)) 195 | float_result := math.Pow(float1, float2) 196 | stack.Push(FloatToBigrat(float_result)) 197 | case "%": 198 | float1 := BigratToFloat(op1.(*big.Rat)) 199 | float2 := BigratToFloat(op2.(*big.Rat)) 200 | float_result := math.Mod(float1, float2) 201 | stack.Push(FloatToBigrat(float_result)) 202 | case "*": 203 | result := dummy.Mul(op1.(*big.Rat), op2.(*big.Rat)) 204 | stack.Push(result) 205 | case "/": 206 | result := dummy.Quo(op1.(*big.Rat), op2.(*big.Rat)) 207 | stack.Push(result) 208 | case "+": 209 | result = dummy.Add(op1.(*big.Rat), op2.(*big.Rat)) 210 | stack.Push(result) 211 | case "-": 212 | result = dummy.Sub(op1.(*big.Rat), op2.(*big.Rat)) 213 | stack.Push(result) 214 | case "<": 215 | if op1.(*big.Rat).Cmp(op2.(*big.Rat)) <= -1 { 216 | stack.Push(big.NewRat(1, 1)) 217 | } else { 218 | stack.Push(new(big.Rat)) 219 | } 220 | case "<=": 221 | if op1.(*big.Rat).Cmp(op2.(*big.Rat)) <= 0 { 222 | stack.Push(big.NewRat(1, 1)) 223 | } else { 224 | stack.Push(new(big.Rat)) 225 | } 226 | case ">": 227 | if op1.(*big.Rat).Cmp(op2.(*big.Rat)) >= 1 { 228 | stack.Push(big.NewRat(1, 1)) 229 | } else { 230 | stack.Push(new(big.Rat)) 231 | } 232 | case ">=": 233 | if op1.(*big.Rat).Cmp(op2.(*big.Rat)) >= 0 { 234 | stack.Push(big.NewRat(1, 1)) 235 | } else { 236 | stack.Push(new(big.Rat)) 237 | } 238 | case "==": 239 | if op1.(*big.Rat).Cmp(op2.(*big.Rat)) == 0 { 240 | stack.Push(big.NewRat(1, 1)) 241 | } else { 242 | stack.Push(new(big.Rat)) 243 | } 244 | case "!=": 245 | if op1.(*big.Rat).Cmp(op2.(*big.Rat)) == 0 { 246 | stack.Push(new(big.Rat)) 247 | } else { 248 | stack.Push(big.NewRat(1, 1)) 249 | } 250 | case "@": 251 | result := dummy.Mul(big.NewRat(-1, 1), op2.(*big.Rat)) 252 | stack.Push(result) 253 | } 254 | } else if isFunction(token) { 255 | op2, err := stack.Pop() 256 | if err != nil { 257 | return nil, err 258 | } 259 | switch token { 260 | case "sin": 261 | float_result := BigratToFloat(op2.(*big.Rat)) 262 | stack.Push(FloatToBigrat(math.Sin(float_result))) 263 | case "cos": 264 | float_result := BigratToFloat(op2.(*big.Rat)) 265 | stack.Push(FloatToBigrat(math.Cos(float_result))) 266 | case "tan": 267 | float_result := BigratToFloat(op2.(*big.Rat)) 268 | stack.Push(FloatToBigrat(math.Tan(float_result))) 269 | case "arcsin": 270 | float_result := BigratToFloat(op2.(*big.Rat)) 271 | stack.Push(FloatToBigrat(math.Asin(float_result))) 272 | case "arccos": 273 | float_result := BigratToFloat(op2.(*big.Rat)) 274 | stack.Push(FloatToBigrat(math.Acos(float_result))) 275 | case "arctan": 276 | float_result := BigratToFloat(op2.(*big.Rat)) 277 | stack.Push(FloatToBigrat(math.Atan(float_result))) 278 | case "ln": 279 | float_result := BigratToFloat(op2.(*big.Rat)) 280 | stack.Push(FloatToBigrat(math.Log(float_result))) 281 | case "sqrt": 282 | float_result := BigratToFloat(op2.(*big.Rat)) 283 | stack.Push(FloatToBigrat(math.Sqrt(float_result))) 284 | } 285 | } else { 286 | return nil, fmt.Errorf("unknown token %v", token) 287 | } 288 | } 289 | 290 | retval, err := stack.Pop() 291 | if err != nil { 292 | return nil, err 293 | } 294 | return retval.(*big.Rat), nil 295 | } 296 | 297 | // Tokenise takes an expr string and converts it to a slice of tokens 298 | // 299 | // Tokenise puts spaces around all non-numbers, removes leading and 300 | // trailing spaces, then splits on spaces 301 | // 302 | func Tokenise(expr string) []string { 303 | 304 | spaced := unary_minus_rx.ReplaceAllString(expr, "$1 @") 305 | spaced = fp_rx.ReplaceAllString(spaced, " ${1} ") 306 | spaced = functions_rx.ReplaceAllString(spaced, " ${1} ") 307 | 308 | if symbols_rx != nil { 309 | spaced = symbols_rx.ReplaceAllString(spaced, " ${1} ") 310 | } 311 | 312 | symbols := []string{"(", ")"} 313 | for _, symbol := range symbols { 314 | spaced = strings.Replace(spaced, symbol, fmt.Sprintf(" %s ", symbol), -1) 315 | } 316 | 317 | stripped := whitespace_rx.ReplaceAllString(strings.TrimSpace(spaced), "|") 318 | result := strings.Split(stripped, "|") 319 | return result 320 | } 321 | 322 | // Eval takes an infix string arithmetic expression, and evaluates it 323 | // 324 | // Usage: 325 | // result, err := evaler.Eval("1+2") 326 | // Returns: the result of the evaluation, and any errors 327 | // 328 | func Eval(expr string) (result *big.Rat, err error) { 329 | defer func() { 330 | if e := recover(); e != nil { 331 | result = nil 332 | err = fmt.Errorf("Invalid Expression: %s", expr) 333 | } 334 | }() 335 | 336 | tokens := Tokenise(expr) 337 | postfix := convert2postfix(tokens) 338 | return evaluatePostfix(postfix) 339 | } 340 | 341 | // EvalWithVariables allows variables to be passed into expressions, for 342 | // example evaluate "x + 1" where x=5 343 | func EvalWithVariables(expr string, variables map[string]string) (result *big.Rat, err error) { 344 | symbolTable = variables 345 | s := "" 346 | for k := range symbolTable { 347 | s += k 348 | } 349 | symbols_rx = regexp.MustCompile(fmt.Sprintf("(%s)", s)) 350 | return Eval(expr) 351 | } 352 | 353 | // BigratToInt converts a *big.Rat to an int64 (with truncation); it 354 | // returns an error for integer overflows. 355 | func BigratToInt(bigrat *big.Rat) (int64, error) { 356 | float_string := bigrat.FloatString(0) 357 | return strconv.ParseInt(float_string, 10, 64) 358 | } 359 | 360 | // BigratToInt converts a *big.Rat to a *big.Int (with truncation) 361 | func BigratToBigint(bigrat *big.Rat) *big.Int { 362 | int_string := bigrat.FloatString(0) 363 | bigint := new(big.Int) 364 | // no error scenario could be imagined in testing, so discard err 365 | fmt.Sscan(int_string, bigint) 366 | return bigint 367 | } 368 | 369 | // BigratToFloat converts a *big.Rat to a float64 (with loss of 370 | // precision). 371 | func BigratToFloat(bigrat *big.Rat) float64 { 372 | float_string := bigrat.FloatString(10) // arbitrary largish precision 373 | // no error scenario could be imagined in testing, so discard err 374 | float, _ := strconv.ParseFloat(float_string, 64) 375 | return float 376 | } 377 | 378 | // FloatToBigrat converts a float64 to a *big.Rat. 379 | func FloatToBigrat(float float64) *big.Rat { 380 | float_string := fmt.Sprintf("%g", float) 381 | bigrat := new(big.Rat) 382 | // no error scenario could be imagined in testing, so discard err 383 | fmt.Sscan(float_string, bigrat) 384 | return bigrat 385 | } 386 | -------------------------------------------------------------------------------- /evaler_test.go: -------------------------------------------------------------------------------- 1 | package evaler_test 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | "math/big" 7 | "testing" 8 | 9 | "github.com/soniah/evaler" 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | // ----------------------------------------------------------------------------- 14 | 15 | var testsEval = []struct { 16 | in string // input expression 17 | tokens []string // expected tokenisation 18 | out *big.Rat // expected result 19 | ok bool // or false if expected to fail 20 | }{ 21 | {"5 + 2", 22 | []string{"5", "+", "2"}, 23 | big.NewRat(7, 1), 24 | true}, // simple plus 25 | 26 | {"5 - 2", 27 | []string{"5", "-", "2"}, 28 | big.NewRat(3, 1), 29 | true}, // simple minus 30 | 31 | {"5 * 2", 32 | []string{"5", "*", "2"}, 33 | big.NewRat(10, 1), 34 | true}, // simple multiply 35 | 36 | {"5 / 2", 37 | []string{"5", "/", "2"}, 38 | big.NewRat(5, 2), 39 | true}, // simple divide 40 | 41 | {"U + U", 42 | []string{"U", "+", "U"}, 43 | nil, 44 | false}, // letters 1 45 | 46 | {"2 + U", 47 | []string{"2", "+", "U"}, 48 | 49 | nil, 50 | false}, // broken 1 51 | 52 | {"2 + ", 53 | []string{"2", "+"}, 54 | nil, 55 | false}, // broken 2 56 | 57 | {"+ 2 - + * ", 58 | []string{"+", "2", "-", "+", "*"}, 59 | nil, 60 | false}, // broken 3 61 | 62 | {"5.5+2*(3+1)", 63 | []string{"5.5", "+", "2", "*", "(", "3", "+", "1", ")"}, 64 | big.NewRat(27, 2), 65 | true}, // complex 1 66 | 67 | {"(((1+2.3)))", 68 | []string{"(", "(", "(", "1", "+", "2.3", ")", ")", ")"}, 69 | big.NewRat(33, 10), 70 | true}, // complex 2 71 | 72 | {"(1+(2))*(5-2.5)", 73 | []string{"(", "1", "+", "(", "2", ")", ")", "*", "(", "5", "-", "2.5", ")"}, 74 | big.NewRat(15, 2), 75 | true}, // complex 3 76 | 77 | {"3*(2<4)", 78 | []string{"3", "*", "(", "2", "<", "4", ")"}, 79 | big.NewRat(3, 1), 80 | true}, // less than 81 | 82 | {"3*(2>4)", 83 | []string{"3", "*", "(", "2", ">", "4", ")"}, 84 | new(big.Rat), 85 | true}, // greater than 86 | 87 | {"3+5 == 8", 88 | []string{"3", "+", "5", "==", "8"}, 89 | big.NewRat(1, 1), 90 | true}, // equals match 91 | 92 | {"4+5 == 8", 93 | []string{"4", "+", "5", "==", "8"}, 94 | new(big.Rat), 95 | true}, // equals no-match 96 | 97 | {"3+5 != 8", 98 | []string{"3", "+", "5", "!=", "8"}, 99 | new(big.Rat), 100 | true}, // not-equals match 101 | 102 | {"4+5 != 8", 103 | []string{"4", "+", "5", "!=", "8"}, 104 | big.NewRat(1, 1), 105 | true}, // not-equals no-match 106 | 107 | {"5 / 0", 108 | []string{"5", "/", "0"}, 109 | nil, 110 | false}, // divide by zero 111 | 112 | {"2 ^ 3", 113 | []string{"2", "^", "3"}, 114 | big.NewRat(8, 1), 115 | true}, // exponent 1 116 | 117 | {"2 ** 3", 118 | []string{"2", "**", "3"}, 119 | big.NewRat(8, 1), 120 | true}, // exponent 1 121 | 122 | {"9.0^0.5", 123 | []string{"9.0", "^", "0.5"}, 124 | big.NewRat(3, 1), 125 | true}, // exponent 2 126 | 127 | {"4^-1", 128 | []string{"4", "^", "@", "1"}, 129 | big.NewRat(1, 4), 130 | true}, // exponent 3 131 | 132 | {"10%3", 133 | []string{"10", "%", "3"}, 134 | big.NewRat(1, 1), 135 | true}, // mod 1 136 | 137 | {"10%3 + 5", 138 | []string{"10", "%", "3", "+", "5"}, 139 | big.NewRat(6, 1), 140 | true}, // mod 2 141 | 142 | {"5 + 10%3", 143 | []string{"5", "+", "10", "%", "3"}, 144 | big.NewRat(6, 1), 145 | true}, // mod 3 146 | 147 | {"9.0**0.5", 148 | []string{"9.0", "**", "0.5"}, 149 | big.NewRat(3, 1), 150 | true}, // exponent 2 151 | 152 | {"4**-1", 153 | []string{"4", "**", "@", "1"}, 154 | big.NewRat(1, 4), 155 | true}, // exponent 3 156 | 157 | {"2*6**3+4**6", 158 | []string{"2", "*", "6", "**", "3", "+", "4", "**", "6"}, 159 | big.NewRat(4528, 1), 160 | true}, // fruit salad 161 | 162 | {"1.23", 163 | []string{"1.23"}, 164 | big.NewRat(123, 100), 165 | true}, 166 | 167 | {"-1+2", 168 | []string{"@", "1", "+", "2"}, 169 | big.NewRat(1, 1), 170 | true}, // unary minus (the beginning of a expression) 171 | 172 | {"3*-4", 173 | []string{"3", "*", "@", "4"}, 174 | big.NewRat(-12, 1), 175 | true}, // unary minus (after an operator) 176 | 177 | {"4/(-1+3)", 178 | []string{"4", "/", "(", "@", "1", "+", "3", ")"}, 179 | big.NewRat(2, 1), 180 | true}, // unary minus (after '(' ) 181 | 182 | {"-(-1+2)--2**3", 183 | []string{"@", "(", "@", "1", "+", "2", ")", "-", "@", "2", "**", "3"}, 184 | big.NewRat(7, 1), 185 | true}, // unary minus (complex) 186 | 187 | {"-(-1+2)--2^3", 188 | []string{"@", "(", "@", "1", "+", "2", ")", "-", "@", "2", "^", "3"}, 189 | big.NewRat(7, 1), 190 | true}, // unary minus (complex) 191 | 192 | {"sin(1)", 193 | []string{"sin", "(", "1", ")"}, 194 | big.NewRat(1682941969615793, 2000000000000000), 195 | true}, // simple sin 196 | 197 | {"sin(1)+1", 198 | []string{"sin", "(", "1", ")", "+", "1"}, 199 | big.NewRat(3682941969615793, 2000000000000000), 200 | true}, // sin in an expression 201 | 202 | {"sin(1)+2^2", 203 | []string{"sin", "(", "1", ")", "+", "2", "^", "2"}, 204 | big.NewRat(9682941969615793, 2000000000000000), 205 | true}, // sin in more complex expression 206 | 207 | {"sin(1)+2**2", 208 | []string{"sin", "(", "1", ")", "+", "2", "**", "2"}, 209 | big.NewRat(9682941969615793, 2000000000000000), 210 | true}, // sin in more complex expression 211 | 212 | {"sin(2-1)", 213 | []string{"sin", "(", "2", "-", "1", ")"}, 214 | big.NewRat(1682941969615793, 2000000000000000), 215 | true}, // sin of expression 216 | 217 | {"sin(2^2)", 218 | []string{"sin", "(", "2", "^", "2", ")"}, 219 | big.NewRat(-3784012476539641, 5000000000000000), 220 | true}, // sin of expression 221 | 222 | {"sin(2**2)", 223 | []string{"sin", "(", "2", "**", "2", ")"}, 224 | big.NewRat(-3784012476539641, 5000000000000000), 225 | true}, // sin of expression 226 | 227 | {"1+sin(1)", 228 | []string{"1", "+", "sin", "(", "1", ")"}, 229 | big.NewRat(3682941969615793, 2000000000000000), 230 | true}, 231 | 232 | {"cos(1)", 233 | []string{"cos", "(", "1", ")"}, 234 | big.NewRat(2701511529340699, 5000000000000000), 235 | true}, // simple sin 236 | 237 | {"tan(1)", 238 | []string{"tan", "(", "1", ")"}, 239 | big.NewRat(778703862327451, 500000000000000), 240 | true}, // simple tan 241 | 242 | {"arcsin(1)", 243 | []string{"arcsin", "(", "1", ")"}, 244 | big.NewRat(7853981633974483, 5000000000000000), 245 | true}, // simple arcsin 246 | 247 | {"arccos(1)", 248 | []string{"arccos", "(", "1", ")"}, 249 | big.NewRat(0, 1), 250 | true}, // simple arcsin 251 | 252 | {"arctan(1)", 253 | []string{"arctan", "(", "1", ")"}, 254 | big.NewRat(7853981633974483, 10000000000000000), 255 | true}, // simple arcsin 256 | 257 | {"sqrt(9)", 258 | []string{"sqrt", "(", "9", ")"}, 259 | big.NewRat(3, 1), 260 | true}, // simple sqrt 261 | 262 | {"ln(1)", 263 | []string{"ln", "(", "1", ")"}, 264 | big.NewRat(0, 1), 265 | true}, // simple ln 266 | 267 | {"1 = 1", 268 | []string{"1", "=", "1"}, 269 | nil, 270 | false}, // check for invalid operator 271 | 272 | {"1 == 1", 273 | []string{"1", "==", "1"}, 274 | big.NewRat(1, 1), 275 | true}, // check for valid operator 276 | 277 | /* TODO this is breaking, probably because of regex for floating point number 278 | {".5 * 2", 279 | []string{".", "5", "*", "2"}, 280 | big.NewRat(1, 1), 281 | true}, // no leading zero 282 | */ 283 | 284 | {"1. * 2", 285 | []string{"1", ".", "*", "2"}, 286 | nil, 287 | false}, // no trailing numbers 288 | 289 | {". * 2", 290 | []string{".", "*", "2"}, 291 | nil, 292 | false}, // decimal, but no numbers at all 293 | } 294 | 295 | func TestTokenise(t *testing.T) { 296 | for i, test := range testsEval { 297 | assert.EqualValues(t, test.tokens, evaler.Tokenise(test.in), fmt.Sprintf("#%d failed: %s", i, test.in)) 298 | } 299 | } 300 | 301 | func TestEval(t *testing.T) { 302 | for i, test := range testsEval { 303 | 304 | ret, err := evaler.Eval(test.in) 305 | if ret == nil && test.out == nil { 306 | // ok, do nothing 307 | } else if ret == nil || test.out == nil { 308 | t.Errorf("#%d: %s: unexpected nil result: %v vs %v", i, test.in, ret, test.out) 309 | } else if ret.Cmp(test.out) != 0 { 310 | t.Errorf("#%d: %s: bad result: got %v expected %v", i, test.in, ret, test.out) 311 | } 312 | if (err == nil) != test.ok { 313 | t.Errorf("#%d: %s: unexpected err result: %t vs %t", i, test.in, (err == nil), test.ok) 314 | } 315 | } 316 | } 317 | 318 | var testsEvalSymbols = []struct { 319 | in string 320 | variables map[string]string 321 | out *big.Rat 322 | ok bool 323 | }{ 324 | {"x", map[string]string{"x": "5"}, big.NewRat(5, 1), true}, // simple substitution 325 | {"x + 1", map[string]string{"x": "5"}, big.NewRat(6, 1), true}, // basic addition 326 | {"2*x", map[string]string{"x": "2"}, big.NewRat(4, 1), true}, // moderate 327 | {"x^x", map[string]string{"x": "2"}, big.NewRat(4, 1), true}, // more complex 328 | {"1^x", map[string]string{"x": "100"}, big.NewRat(1, 1), true}, // sanity 329 | {"9^x", map[string]string{"x": "-.5"}, big.NewRat(3333333333333333, 10000000000000000), true}, // basic negative value passed in for variable 330 | {"9^-x", map[string]string{"x": ".5"}, big.NewRat(3333333333333333, 10000000000000000), true}, // negative of variable 331 | {"t", map[string]string{"t": "5"}, big.NewRat(5, 1), true}, // test variables that could be misinterpreted as operators 332 | {"x", map[string]string{"t": "5"}, nil, false}, // unassigned variable 333 | {"sin(x)", map[string]string{"x": "1"}, big.NewRat(1682941969615793, 2000000000000000), true}, // negative of variable 334 | {"sin(x)*(x+1)", map[string]string{"x": "1"}, big.NewRat(1682941969615793, 1000000000000000), true}, // negative of variable 335 | {"sin(x)^-1", map[string]string{"x": "1"}, big.NewRat(2970987764473183, 2500000000000000), true}, // switcharoo 336 | {"(x)*(x+1)", map[string]string{"x": "1"}, big.NewRat(2, 1), true}, // negative of variable 337 | } 338 | 339 | func TestEvalWithVariables(t *testing.T) { 340 | for i, test := range testsEvalSymbols { 341 | ret, err := evaler.EvalWithVariables(test.in, test.variables) 342 | if ret == nil && test.out == nil { 343 | // ok, do nothing 344 | } else if ret == nil || test.out == nil { 345 | t.Errorf("#%d: %s: unexpected nil result: %v vs %v", i, test.in, ret, test.out) 346 | } else if ret.Cmp(test.out) != 0 { 347 | t.Errorf("#%d: %s: bad result: got %v expected %v", i, test.in, ret, test.out) 348 | } 349 | if (err == nil) != test.ok { 350 | t.Errorf("#%d: %s: unexpected err result: %t vs %t", i, test.in, (err == nil), test.ok) 351 | } 352 | } 353 | } 354 | 355 | // ----------------------------------------------------------------------------- 356 | 357 | var testsBigratToInt = []struct { 358 | in *big.Rat 359 | out int64 360 | ok bool 361 | }{ 362 | {big.NewRat(4, 2), int64(2), true}, 363 | {big.NewRat(5, 2), int64(3), true}, 364 | {big.NewRat(-4, 2), int64(-2), true}, 365 | {new(big.Rat).Mul(big.NewRat(math.MaxInt64, 1), big.NewRat(math.MaxInt64, 1)), int64(0), false}, 366 | } 367 | 368 | func TestBigratToInt(t *testing.T) { 369 | for i, test := range testsBigratToInt { 370 | ret, err := evaler.BigratToInt(test.in) 371 | if test.ok && (ret != test.out) { 372 | t.Errorf("#%d: got %d expected %d", i, ret, test.out) 373 | } 374 | if (err == nil) != test.ok { 375 | t.Errorf("#%d: %s: unexpected err result: %t vs %t", i, test.in, (err == nil), test.ok) 376 | } 377 | } 378 | } 379 | 380 | // ----------------------------------------------------------------------------- 381 | 382 | var testsBigratToBigint = []struct { 383 | in *big.Rat 384 | out *big.Int 385 | }{ 386 | {big.NewRat(4, 2), big.NewInt(2)}, 387 | {big.NewRat(5, 2), big.NewInt(3)}, 388 | {big.NewRat(-4, 2), big.NewInt(-2)}, 389 | } 390 | 391 | func TestBigratToBigint(t *testing.T) { 392 | for i, test := range testsBigratToBigint { 393 | ret := evaler.BigratToBigint(test.in) 394 | if ret.Cmp(test.out) != 0 { 395 | t.Errorf("#%d: got %d expected %d", i, ret, test.out) 396 | } 397 | } 398 | } 399 | 400 | // ----------------------------------------------------------------------------- 401 | 402 | var testsBigratToFloat = []struct { 403 | in *big.Rat 404 | out float64 405 | }{ 406 | {big.NewRat(4, 2), float64(2.0)}, 407 | {big.NewRat(5, 2), float64(2.5)}, 408 | {big.NewRat(-4, 2), float64(-2.0)}, 409 | } 410 | 411 | func TestBigratToFloat(t *testing.T) { 412 | for i, test := range testsBigratToFloat { 413 | ret := evaler.BigratToFloat(test.in) 414 | if ret != test.out { 415 | t.Errorf("#%d: got %f expected %f", i, ret, test.out) 416 | } 417 | } 418 | } 419 | 420 | // ----------------------------------------------------------------------------- 421 | 422 | var testsFloatToBigrat = []struct { 423 | in float64 424 | out *big.Rat 425 | }{ 426 | {float64(2.0), big.NewRat(4, 2)}, 427 | {float64(2.5), big.NewRat(5, 2)}, 428 | {float64(-2.0), big.NewRat(-4, 2)}, 429 | } 430 | 431 | func TestFloatToBigrat(t *testing.T) { 432 | for i, test := range testsFloatToBigrat { 433 | ret := evaler.FloatToBigrat(test.in) 434 | if ret.Cmp(test.out) != 0 { 435 | t.Errorf("#%d: got %s expected %s", i, ret, test.out) 436 | } 437 | } 438 | } 439 | -------------------------------------------------------------------------------- /stack/stack.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2010-12 Qtrac Ltd. 2 | // 3 | // This program or package and any associated files are licensed under the 4 | // Apache License, Version 2.0 (the "License"); you may not use these files 5 | // except in compliance with the License. You can get a copy of the License 6 | // at: http://www.apache.org/licenses/LICENSE-2.0. 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | // 14 | // Copied from samples for "Programming in Go", Mark Summerfield. 15 | 16 | package stack 17 | 18 | import ( 19 | "errors" 20 | "fmt" 21 | ) 22 | 23 | type Stack []interface{} 24 | 25 | // Stringer is useful for debugging 26 | func (s Stack) String() string { 27 | var result string 28 | for _, value := range s { 29 | result += fmt.Sprintf("%s | ", value) 30 | } 31 | return result 32 | } 33 | 34 | func (stack *Stack) Pop() (interface{}, error) { 35 | theStack := *stack 36 | if len(theStack) == 0 { 37 | return nil, errors.New("can't Pop() an empty stack") 38 | } 39 | x := theStack[len(theStack)-1] 40 | *stack = theStack[:len(theStack)-1] 41 | return x, nil 42 | } 43 | 44 | func (stack *Stack) Push(x interface{}) { 45 | *stack = append(*stack, x) 46 | } 47 | 48 | func (stack Stack) Top() (interface{}, error) { 49 | if len(stack) == 0 { 50 | return nil, errors.New("can't Top() an empty stack") 51 | } 52 | return stack[len(stack)-1], nil 53 | } 54 | 55 | func (stack Stack) Cap() int { 56 | return cap(stack) 57 | } 58 | 59 | func (stack Stack) Len() int { 60 | return len(stack) 61 | } 62 | 63 | func (stack Stack) IsEmpty() bool { 64 | return len(stack) == 0 65 | } 66 | -------------------------------------------------------------------------------- /stack/stack_test.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2010-12 Qtrac Ltd. 2 | // 3 | // This program or package and any associated files are licensed under the 4 | // Apache License, Version 2.0 (the "License"); you may not use these files 5 | // except in compliance with the License. You can get a copy of the License 6 | // at: http://www.apache.org/licenses/LICENSE-2.0. 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package stack_test 15 | 16 | import ( 17 | "testing" 18 | 19 | "github.com/soniah/evaler/stack" 20 | ) 21 | 22 | func TestStack(t *testing.T) { 23 | count := 1 24 | var aStack stack.Stack 25 | assertTrue(t, aStack.Len() == 0, "expected empty Stack", count) // 1 26 | count++ 27 | assertTrue(t, aStack.Cap() == 0, "expected empty Stack", count) // 2 28 | count++ 29 | assertTrue(t, aStack.IsEmpty(), "expected empty Stack", count) // 3 30 | count++ 31 | value, err := aStack.Pop() 32 | assertTrue(t, value == nil, "expected nil value", count) // 4 33 | count++ 34 | assertTrue(t, err != nil, "expected error", count) // 5 35 | count++ 36 | value1, err := aStack.Top() 37 | assertTrue(t, value1 == nil, "expected nil value", count) // 6 38 | count++ 39 | assertTrue(t, err != nil, "expected error", count) // 7 40 | count++ 41 | aStack.Push(1) 42 | aStack.Push(2) 43 | aStack.Push("three") 44 | assertTrue(t, aStack.Len() == 3, "expected nonempty Stack", count) // 8 45 | count++ 46 | assertTrue(t, aStack.IsEmpty() == false, "expected nonempty Stack", 47 | count) // 9 48 | count++ 49 | value2, err := aStack.Pop() 50 | assertEqualString(t, value2.(string), "three", "unexpected text", 51 | count) // 10 52 | count++ 53 | assertTrue(t, err == nil, "no error expected", count) // 11 54 | count++ 55 | value3, err := aStack.Top() 56 | assertTrue(t, value3 == 2, "unexpected number", count) // 12 57 | count++ 58 | assertTrue(t, err == nil, "no error expected", count) // 13 59 | count++ 60 | aStack.Pop() 61 | assertTrue(t, aStack.Len() == 1, "expected nonempty Stack", count) //14 62 | count++ 63 | assertTrue(t, aStack.IsEmpty() == false, "expected nonempty Stack", 64 | count) // 15 65 | count++ 66 | value4, err := aStack.Pop() 67 | assertTrue(t, value4 == 1, "unexpected number", count) // 16 68 | count++ 69 | assertTrue(t, err == nil, "no error expected", count) // 17 70 | count++ 71 | assertTrue(t, aStack.Len() == 0, "expected empty Stack", count) // 18 72 | count++ 73 | assertTrue(t, aStack.IsEmpty(), "expected empty Stack", count) // 19 74 | count++ 75 | } 76 | 77 | // assertTrue() calls testing.T.Error() with the given message if the 78 | // condition is false. 79 | func assertTrue(t *testing.T, condition bool, message string, id int) { 80 | if !condition { 81 | t.Errorf("#%d: %s", id, message) 82 | } 83 | } 84 | 85 | // assertEqualString() calls testing.T.Error() with the given message if 86 | // the given strings are not equal. 87 | func assertEqualString(t *testing.T, a, b string, message string, id int) { 88 | if a != b { 89 | t.Errorf("#%d: %s \"%s\" !=\n\"%s\"", id, message, a, b) 90 | } 91 | } 92 | --------------------------------------------------------------------------------