├── go.mod ├── README.md ├── LICENSE ├── expr_test.go └── expr.go /go.mod: -------------------------------------------------------------------------------- 1 | module robpike.io/expr 2 | 3 | go 1.17 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | The expr package provides a simple evaluator for arithmetic integer 2 | expressions. The syntax and operations are the same as in Go. Operands are 3 | the native "int" type, except that unlike in Go, boolean values, which are 4 | created by comparisons, are integer 1 (true) and 0 (false). Create a parsed 5 | expression using Parse, and then evaluate it with Eval. 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021 Rob Pike. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * The names of its contributors may not be used to endorse or 14 | promote products derived from this software without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 20 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 21 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 22 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | -------------------------------------------------------------------------------- /expr_test.go: -------------------------------------------------------------------------------- 1 | package expr 2 | 3 | import "testing" 4 | 5 | func TestParse(t *testing.T) { 6 | // The String method adds parens everywhere, so it is an easy 7 | // check of the parse tree. 8 | var tests = []struct { 9 | in, out string 10 | }{ 11 | // Singletons. 12 | {"3", "3"}, 13 | {"x", "x"}, 14 | {"x_9", "x_9"}, 15 | 16 | // Unary operators. 17 | {"-3", "(-3)"}, 18 | {"+3", "(+3)"}, 19 | {"!1", "(!1)"}, 20 | {"^1", "(^1)"}, 21 | 22 | // Binary arithmetic operators. 23 | {"x * y", "(x * y)"}, 24 | {"x / y", "(x / y)"}, 25 | {"x % y", "(x % y)"}, 26 | {"x << y", "(x << y)"}, 27 | {"x >> y", "(x >> y)"}, 28 | {"x & y", "(x & y)"}, 29 | {"x &^ y", "(x &^ y)"}, 30 | {"x + y", "(x + y)"}, 31 | {"x - y", "(x - y)"}, 32 | {"x | y", "(x | y)"}, 33 | {"x ^ y", "(x ^ y)"}, 34 | 35 | // Binary comparison operators. 36 | {"x == y", "(x == y)"}, 37 | {"x != y", "(x != y)"}, 38 | {"x < y", "(x < y)"}, 39 | {"x <= y", "(x <= y)"}, 40 | {"x > y", "(x > y)"}, 41 | {"x >= y", "(x >= y)"}, 42 | 43 | // Binary logical operators. 44 | {"x && y", "(x && y)"}, 45 | {"x || y", "(x || y)"}, 46 | 47 | // Precedence checks. Left-associative with precedence. 48 | 49 | // && and || 50 | {"x || y || z", "((x || y) || z)"}, 51 | {"x && y && z", "((x && y) && z)"}, 52 | {"x && y || z", "((x && y) || z)"}, 53 | {"x || y && z", "(x || (y && z))"}, 54 | 55 | // Comparison operators. 56 | {"x == y == z", "((x == y) == z)"}, 57 | {"x != y != z", "((x != y) != z)"}, 58 | {"x > y > z", "((x > y) > z)"}, 59 | {"x >= y >= z", "((x >= y) >= z)"}, 60 | {"x < y < z", "((x < y) < z)"}, 61 | {"x <= y <= z", "((x <= y) <= z)"}, 62 | 63 | // Unaries and binaries at two precedence levels. 64 | {"+x + y", "((+x) + y)"}, 65 | {"x + +y", "(x + (+y))"}, 66 | {"-x - y", "((-x) - y)"}, 67 | {"x - -y", "(x - (-y))"}, 68 | {"^x + y", "((^x) + y)"}, 69 | {"x + ^y", "(x + (^y))"}, 70 | {"!x + y", "((!x) + y)"}, 71 | {"x + !y", "(x + (!y))"}, 72 | {"+x * y", "((+x) * y)"}, 73 | {"x * +y", "(x * (+y))"}, 74 | {"-x - y", "((-x) - y)"}, 75 | {"x - -y", "(x - (-y))"}, 76 | {"^x * y", "((^x) * y)"}, 77 | {"x * ^y", "(x * (^y))"}, 78 | {"!x * y", "((!x) * y)"}, 79 | {"x * !y", "(x * (!y))"}, 80 | 81 | // Grouping of operators at same precedence. 82 | // Multiplies. 83 | {"x * y * z", "((x * y) * z)"}, 84 | {"x * y / z", "((x * y) / z)"}, 85 | {"x / y * z", "((x / y) * z)"}, 86 | {"x % y / z", "((x % y) / z)"}, 87 | {"x / y % z", "((x / y) % z)"}, 88 | {"x >> y / z", "((x >> y) / z)"}, 89 | {"x / y >> z", "((x / y) >> z)"}, 90 | {"x << y / z", "((x << y) / z)"}, 91 | {"x / y << z", "((x / y) << z)"}, 92 | {"x & y / z", "((x & y) / z)"}, 93 | {"x / y & z", "((x / y) & z)"}, 94 | {"x &^ y / z", "((x &^ y) / z)"}, 95 | {"x / y &^ z", "((x / y) &^ z)"}, 96 | 97 | // Adds. 98 | {"x + y + z", "((x + y) + z)"}, 99 | {"x + y - z", "((x + y) - z)"}, 100 | {"x - y + z", "((x - y) + z)"}, 101 | {"x + y | z", "((x + y) | z)"}, 102 | {"x | y + z", "((x | y) + z)"}, 103 | {"x + y + z", "((x + y) + z)"}, 104 | {"x + y ^ z", "((x + y) ^ z)"}, 105 | 106 | // Multiplies and adds. 107 | {"x * y + z", "((x * y) + z)"}, 108 | {"x + y * z", "(x + (y * z))"}, // Gotcha! 109 | 110 | // Grouping of comparisons and other operators. 111 | {"(x < y && z < 3)", "((x < y) && (z < 3))"}, 112 | {"(x < y && z || 1)", "(((x < y) && z) || 1)"}, 113 | {"(u == v && x == y || w == z)", "(((u == v) && (x == y)) || (w == z))"}, 114 | {"(u == v*3 && x == y-2 || w == !z)", "(((u == (v * 3)) && (x == (y - 2))) || (w == (!z)))"}, 115 | } 116 | for _, test := range tests { 117 | e, err := Parse(test.in) 118 | if err != nil { 119 | t.Errorf("Parsing %s: %v", test.in, err) 120 | continue 121 | } 122 | got := e.String() 123 | if got != test.out { 124 | t.Errorf("String for %q: %q, want %q", test.in, got, test.out) 125 | continue 126 | } 127 | // Round-trip. This also tests parentheses. 128 | e, err = Parse(got) 129 | if err != nil { 130 | t.Errorf("Parsing round-trip %s: %v", test.in, err) 131 | continue 132 | } 133 | got2 := e.String() 134 | if got2 != test.out { 135 | t.Errorf("String for roundtrip %q: %q, want %q", test.in, got, test.out) 136 | continue 137 | } 138 | } 139 | } 140 | 141 | func TestParseError(t *testing.T) { 142 | var tests = []struct { 143 | expr string 144 | err string 145 | }{ 146 | {"x x", `syntax error at "x"`}, 147 | {"(x + ", `unexpected eof`}, 148 | {"(x + 1", `unclosed paren at eof`}, 149 | {"(x + 1))", `syntax error at ")"`}, 150 | {"x + >4", `bad expression at ">4"`}, 151 | {"x @ 4", `syntax error at "@ 4"`}, 152 | } 153 | for _, test := range tests { 154 | _, err := Parse(test.expr) 155 | if err == nil { 156 | t.Errorf("Parsing %s: no error", test.expr) 157 | continue 158 | } 159 | got := err.Error() 160 | if got != test.err { 161 | t.Errorf("Wrong error for %s: got %q, want %q", test.expr, got, test.err) 162 | continue 163 | } 164 | } 165 | } 166 | 167 | func TestEval(t *testing.T) { 168 | var tests = []struct { 169 | x, y int 170 | expr string 171 | result int 172 | }{ 173 | // Singletons. 174 | {0, 0, "3", 3}, 175 | {0, 0, "3+4", 7}, 176 | {10, 0, "x", 10}, 177 | {10, 20, "y", 20}, 178 | 179 | // Unary operators. 180 | {0, 0, "+3", 3}, 181 | {0, 0, "-3", -3}, 182 | {0, 0, "!0", 1}, // No booleans here, just 0 and 1. 183 | {0, 0, "!3", 0}, 184 | {0, 0, "^0", -1}, 185 | {0, 0, "^1", -2}, 186 | {7, 0, "+x", 7}, 187 | {7, 0, "-x", -7}, 188 | 189 | // Binary operators. 190 | 191 | // Arithmetic. 192 | // To be sure it's working, aim for a different answer for each when feasible. 193 | {7, 3, "x * y", 21}, 194 | {7, 3, "x / y", 2}, 195 | {7, 0, "x / y", 0}, // Special case. 196 | {7, 3, "x % y", 1}, 197 | {7, 0, "x % y", 0}, // Special case. 198 | {7, 3, "x << y", 56}, 199 | {7, 1, "x >> y", 3}, 200 | {7, 4, "x & y", 4}, 201 | {17, 5, "x &^ y", 16}, 202 | {7, 3, "x + y", 10}, 203 | {7, 2, "x - y", 5}, 204 | 205 | // Bits. 206 | {4, 2, "x | y", 6}, 207 | {7, 7, "x ^ y", 0}, 208 | {14, 7, "x ^ y", 9}, 209 | {14, 7, "x &^ y", 8}, 210 | {144, 3, "x >> y", 18}, 211 | {14, 1, "x << y", 28}, 212 | 213 | // Comparison. 214 | {14, 7, "x == y", 0}, 215 | {14, 7, "x != y", 1}, 216 | {14, 7, "x > y", 1}, 217 | {7, 14, "x > y", 0}, 218 | {14, 7, "x < y", 0}, 219 | {7, 14, "x < y", 1}, 220 | {14, 7, "x >= y", 1}, 221 | {7, 14, "x >= y", 0}, 222 | {14, 14, "x >= y", 1}, 223 | {14, 7, "x <= y", 0}, 224 | {14, 14, "x <= y", 1}, 225 | {7, 14, "x <= y", 1}, 226 | 227 | // Mix it up. 228 | {4, 4, "x > y || y == x", 1}, 229 | {4, 4, "x > y && y == x", 0}, 230 | {5, 4, "x > y && y == 4", 1}, 231 | {5, 4, "x*x + y*y", 41}, 232 | 233 | // No errors. 234 | {4, 0, "x / 0", 0}, 235 | {4, 0, "x % 0", 0}, 236 | {4, 0, "x << -1", 0}, 237 | {4, 0, "x >> -1", 0}, 238 | {4, 0, "v >> -1", 0}, 239 | 240 | // Check that ReturnZero works mid-expression. 241 | {0, 0, "zz", 0}, 242 | {0, 0, "4 + zz", 4}, 243 | {1, 0, "3 + x / y", 3}, 244 | {1, 0, "3 + x % y", 3}, 245 | {1, -3, "3*x + x << y", 3}, 246 | {1, -3, "3*y + x >> y", -9}, 247 | } 248 | vars := make(map[string]int) 249 | for _, test := range tests { 250 | e, err := Parse(test.expr) 251 | if err != nil { 252 | t.Errorf("Parsing %s: %v", test.expr, err) 253 | continue 254 | } 255 | vars["x"] = test.x 256 | vars["y"] = test.y 257 | got, err := e.Eval(vars, ReturnZero) 258 | if err != nil { 259 | t.Errorf("Evaluating %s: %v", test.expr, err) 260 | continue 261 | } 262 | if got != test.result { 263 | t.Errorf("Evaluating %s: got %d, expected %d", test.expr, got, test.result) 264 | } 265 | } 266 | } 267 | 268 | func TestEvalError(t *testing.T) { 269 | var tests = []struct { 270 | expr string 271 | err string 272 | }{ 273 | {"y", `undefined variable y`}, 274 | {"x / 0", `division by zero`}, 275 | {"x % 0", `modulo by zero`}, 276 | {"x << -1", `negative left shift amount`}, 277 | {"x >> -1", `negative right shift amount`}, 278 | } 279 | vars := map[string]int{"x": 1} 280 | for _, test := range tests { 281 | e, err := Parse(test.expr) 282 | if err != nil { 283 | t.Errorf("Parsing %s: %v", test.expr, err) 284 | continue 285 | } 286 | _, err = e.Eval(vars, ReturnError) 287 | if err == nil { 288 | t.Errorf("Evaluating %s: no error", test.expr) 289 | continue 290 | } 291 | got := err.Error() 292 | if got != test.err { 293 | t.Errorf("Wrong error for %s: got %q, want %q", test.expr, got, test.err) 294 | continue 295 | } 296 | } 297 | } 298 | -------------------------------------------------------------------------------- /expr.go: -------------------------------------------------------------------------------- 1 | // The expr package provides a simple evaluator for arithmetic integer expressions. 2 | // The syntax and operations are the same as in Go. Operands are the native "int" 3 | // type, except that unlike in Go, boolean values, which are created by 4 | // comparisons, are integer 1 (true) and 0 (false). 5 | // Create a parsed expression using Parse, and then evaluate it with Eval. 6 | package expr // import "robpike.io/expr" 7 | 8 | import ( 9 | "errors" 10 | "fmt" 11 | ) 12 | 13 | // Expr holds a parsed expression. 14 | type Expr struct { 15 | op string 16 | left *Expr 17 | right *Expr 18 | ident string 19 | num int 20 | } 21 | 22 | func (e *Expr) String() string { 23 | if e == nil { 24 | return "" 25 | } 26 | if e.op == "" { 27 | if e.left != nil { 28 | return e.left.String() 29 | } 30 | if e.ident != "" { 31 | return e.ident 32 | } 33 | return fmt.Sprint(e.num) 34 | } 35 | left := e.left.String() 36 | right := e.right.String() 37 | if left == "" { 38 | return fmt.Sprintf("(%s%s)", e.op, right) 39 | } 40 | return fmt.Sprintf("(%s %s %s)", left, e.op, right) 41 | } 42 | 43 | const eof = 0 44 | 45 | type parser struct { 46 | s string 47 | } 48 | 49 | func (p *parser) next(doSkip bool) byte { 50 | if doSkip { 51 | p.skip() 52 | } 53 | if p.s == "" { 54 | return eof 55 | } 56 | c := p.s[0] // ASCII only, doesn't matter. 57 | p.s = p.s[1:] 58 | return c 59 | } 60 | 61 | func (p *parser) peek(doSkip bool) byte { 62 | if doSkip { 63 | p.skip() 64 | } 65 | if p.s == "" { 66 | return eof 67 | } 68 | return p.s[0] 69 | } 70 | 71 | func (p *parser) skip() { 72 | for p.s != "" && p.starts(" \t\n\r") { 73 | p.s = p.s[1:] 74 | } 75 | } 76 | 77 | func (p *parser) starts(set string) bool { 78 | if len(p.s) < 1 { 79 | return false 80 | } 81 | c := p.s[0] 82 | for i := 0; i < len(set); i++ { 83 | if c == set[i] { 84 | return true 85 | } 86 | } 87 | return false 88 | } 89 | 90 | func (p *parser) nextOpLen() int { 91 | if p.s == "" { 92 | return 0 93 | } 94 | switch p.s[0] { 95 | case '+', '-', '*', '/', '%', '^': 96 | return 1 97 | case '<': 98 | return p.maybe("=<") 99 | case '>': 100 | return p.maybe("=>") 101 | case '&': 102 | return p.maybe("&^") 103 | case '|': 104 | return p.maybe("|") 105 | case '!': 106 | return p.maybe("=") 107 | case '=': // = is not an operator but == is. 108 | if len(p.s) >= 2 || p.s[1] == '=' { 109 | return 2 110 | } 111 | } 112 | return 0 113 | } 114 | 115 | // We are at an operator. Does it extend to the second character? 116 | // Return the length of operator corresponding to the present 117 | // character (assumed), plus possibly a character from extra. 118 | func (p *parser) maybe(extra string) int { 119 | if len(p.s) >= 2 { 120 | for i := 0; i < len(extra); i++ { 121 | if p.s[1] == extra[i] { 122 | return 2 123 | } 124 | } 125 | } 126 | return 1 127 | } 128 | 129 | // op returns the next operator. Singles and doubles 130 | // are the legal one- and two-character operators, 131 | // as concatenated strings. See call sites for examples. 132 | func (p *parser) op(singles, doubles string) string { 133 | n := p.nextOpLen() 134 | op := p.s[:n] 135 | switch n { 136 | case 1: 137 | for i := 0; i < len(singles); i++ { 138 | if op[0] == singles[i] { 139 | p.s = p.s[n:] 140 | return op 141 | } 142 | } 143 | case 2: 144 | for i := 0; i < len(doubles); i += 2 { 145 | if op == doubles[i:i+2] { 146 | p.s = p.s[n:] 147 | return op 148 | } 149 | } 150 | } 151 | return "" 152 | } 153 | 154 | func isDigit(c byte) bool { 155 | return '0' <= c && c <= '9' 156 | } 157 | 158 | // isAlpha reports whether byte is an alphabetic or underscore or, 159 | // if digitOK is true, a digit. 160 | func isAlpha(c byte, digitOK bool) bool { 161 | if 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || c == '_' { 162 | return true 163 | } 164 | return digitOK && isDigit(c) 165 | } 166 | 167 | func recoverer(errp *error) { 168 | r := recover() 169 | if r != nil { 170 | var ok bool 171 | *errp, ok = r.(error) 172 | if ok { 173 | return 174 | } 175 | panic(r) 176 | } 177 | } 178 | 179 | // Parse parses a single expression. 180 | func Parse(s string) (expr *Expr, err error) { 181 | p := &parser{s} 182 | defer recoverer(&err) 183 | expr = orList(p) 184 | if p.peek(true) != eof { 185 | throw("syntax error at ", p.remaining()) 186 | } 187 | return 188 | } 189 | 190 | func throw(s ...interface{}) { 191 | panic(errors.New(fmt.Sprint(s...))) 192 | } 193 | 194 | // remaining returns the quoted contents of the remaining input after a failed parse, or eof at EOF. 195 | func (p *parser) remaining() string { 196 | if p.s != "" { 197 | return fmt.Sprintf("%q", p.s) 198 | } 199 | return "eof" 200 | } 201 | 202 | // parse implements a production in the expression parse hierarchy. Singles and 203 | // doubles are strings holding the operators that are available at this precedence 204 | // level, while nextLevel implements the next higher precedence level. 205 | func (p *parser) parse(singles, doubles string, nextLevel func(*parser) *Expr) *Expr { 206 | e := nextLevel(p) 207 | for { 208 | if p.peek(true) == eof { 209 | return e 210 | } 211 | op := p.op(singles, doubles) 212 | if op == "" { 213 | return e 214 | } 215 | e = &Expr{ 216 | op: op, 217 | left: e, 218 | right: nextLevel(p), 219 | } 220 | } 221 | } 222 | 223 | // orList = andList | andList '||' orList. 224 | func orList(p *parser) *Expr { 225 | return p.parse("", "||", andList) 226 | } 227 | 228 | // andList = cmpList | cmpList '&&' andList. 229 | func andList(p *parser) *Expr { 230 | return p.parse("", "&&", cmpList) 231 | } 232 | 233 | // cmpList = expr | expr ('>' | '<' | '==' | '!=' | '>=' | '<=') expr. 234 | func cmpList(p *parser) *Expr { 235 | return p.parse("+-|^!><", "==!=>=<=", expr) 236 | } 237 | 238 | // expr = term | term ('+' | '-' | '|' | '^') term. 239 | func expr(p *parser) *Expr { 240 | return p.parse("+-|^!", "", term) 241 | } 242 | 243 | // term = factor | factor ('*' | '/' | '%' | '>>' | '<<' | '&' | '&^') factor 244 | func term(p *parser) *Expr { 245 | return p.parse("*/%&", ">><<&^", factor) 246 | } 247 | 248 | // factor = constant | identifier | '+' factor | '-' factor | '^' factor | '!' factor | '(' orList ')' 249 | func factor(p *parser) *Expr { 250 | c := p.peek(true) 251 | switch { 252 | case c == eof: 253 | throw("unexpected eof") 254 | case isDigit(c): 255 | return &Expr{ 256 | num: p.number(), 257 | } 258 | case isAlpha(c, false): 259 | return &Expr{ 260 | ident: p.identifier(), 261 | } 262 | case p.starts("+-^!"): 263 | op := p.s[:1] 264 | p.next(false) 265 | return &Expr{ 266 | op: op, 267 | right: factor(p), 268 | } 269 | case c == '(': 270 | p.next(false) 271 | e := orList(p) 272 | if p.next(true) != ')' { 273 | throw("unclosed paren at ", p.remaining()) 274 | } 275 | return e 276 | } 277 | throw("bad expression at ", p.remaining()) 278 | return nil 279 | } 280 | 281 | // number returns the next number in the input. We know there is at 282 | // least one digit. 283 | func (p *parser) number() int { 284 | n := 0 285 | for { 286 | c := p.peek(false) 287 | if !isDigit(c) { 288 | break 289 | } 290 | p.next(false) 291 | n = 10*n + int(c) - '0' 292 | } 293 | return n 294 | } 295 | 296 | // identifier returns the next identifier in the input. We know there 297 | // is at least one identifier character. 298 | func (p *parser) identifier() string { 299 | s := "" 300 | for { 301 | c := p.peek(false) 302 | if !isAlpha(c, s != "") { 303 | break 304 | } 305 | p.next(false) 306 | s = s + string(c) 307 | } 308 | return s 309 | } 310 | 311 | // ErrorMode specifies how to handle arithmetic errors such as division by zero or 312 | // undefined variable: Either return an error (ReturnError) or replace the 313 | // erroneous calculation with zero and press on (ReturnZero). 314 | type ErrorMode int 315 | 316 | const ( 317 | ReturnError ErrorMode = iota 318 | ReturnZero 319 | ) 320 | 321 | func (e ErrorMode) error(s ...interface{}) int { 322 | switch e { 323 | case ReturnZero: 324 | return 0 325 | case ReturnError: 326 | throw(s...) 327 | } 328 | panic("bad error mode") 329 | } 330 | 331 | // Eval evaluates the expression. 332 | // The symbol table is provided as a map from identifier to value. The error mode 333 | // sets the behavior if an error occurs (zero division, undefined variable, illegal 334 | // shift). Normally the error is returned to the caller, but if errMode is 335 | // ReturnZero the erroneous expression or subexpression is just set to zero and 336 | // evaluation continues. 337 | func (e *Expr) Eval(vars map[string]int, errMode ErrorMode) (result int, err error) { 338 | defer recoverer(&err) 339 | result = e.eval(vars, errMode) 340 | return 341 | } 342 | 343 | func (e *Expr) eval(vars map[string]int, errMode ErrorMode) int { 344 | if e == nil { 345 | return 0 346 | } 347 | if e.op == "" { 348 | if e.ident != "" { 349 | n, ok := vars[e.ident] 350 | if !ok { 351 | return errMode.error("undefined variable ", e.ident) 352 | } 353 | return n 354 | } 355 | return e.num 356 | } 357 | // Binary operators. 358 | if e.left != nil && e.right != nil { 359 | left := e.left.eval(vars, errMode) 360 | right := e.right.eval(vars, errMode) 361 | switch e.op { 362 | case "+": 363 | return left + right 364 | case "-": 365 | return left - right 366 | case "*": 367 | return left * right 368 | case "/": 369 | if right == 0 { 370 | return errMode.error("division by zero") 371 | } 372 | return left / right 373 | case "%": 374 | if right == 0 { 375 | return errMode.error("modulo by zero") 376 | } 377 | return left % right 378 | case "&": 379 | return left & right 380 | case "|": 381 | return left | right 382 | case "^": 383 | return left ^ right 384 | case "&^": 385 | return left &^ right 386 | case ">>": 387 | if right < 0 { 388 | return errMode.error("negative right shift amount") 389 | } 390 | return left >> right 391 | case "<<": 392 | if right < 0 { 393 | return errMode.error("negative left shift amount") 394 | } 395 | return left << right 396 | case "==": 397 | return toInt(left == right) 398 | case "!=": 399 | return toInt(left != right) 400 | case ">=": 401 | return toInt(left >= right) 402 | case "<=": 403 | return toInt(left <= right) 404 | case "<": 405 | return toInt(left < right) 406 | case ">": 407 | return toInt(left > right) 408 | case "||": 409 | return toInt(left != 0 || right != 0) 410 | case "&&": 411 | return toInt(left != 0 && right != 0) 412 | default: 413 | throw("unknown binary operator (can't happen) ", e.op) 414 | } 415 | } 416 | if e.right != nil { 417 | right := e.right.eval(vars, errMode) 418 | switch e.op { 419 | case "+": 420 | return right 421 | case "-": 422 | return -right 423 | case "^": 424 | return ^right 425 | case "!": 426 | return toInt(right == 0) 427 | default: 428 | throw("unknown unary operator (can't happen) ", e.op) 429 | } 430 | } 431 | throw("unrecognized expression: can't happen") 432 | panic("not reached") 433 | } 434 | 435 | func toInt(t bool) int { 436 | if t { 437 | return 1 438 | } 439 | return 0 440 | } 441 | --------------------------------------------------------------------------------