github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/examples/gno.land/p/demo/math_eval/int32/int32.gno (about) 1 // eval/int32 is a evaluator for int32 expressions. 2 // This code is heavily forked from https://github.com/dengsgo/math-engine 3 // which is licensed under Apache 2.0: 4 // https://raw.githubusercontent.com/dengsgo/math-engine/298e2b57b7e7350d0f67bd036916efd5709abe25/LICENSE 5 package int32 6 7 import ( 8 "errors" 9 "strconv" 10 "strings" 11 12 "gno.land/p/demo/ufmt" 13 ) 14 15 const ( 16 Identifier = iota 17 Number // numbers 18 Operator // +, -, *, /, etc. 19 Variable // x, y, z, etc. (one-letter only) 20 ) 21 22 type expression interface { 23 String() string 24 } 25 26 type expressionRaw struct { 27 expression string 28 Type int 29 Flag int 30 Offset int 31 } 32 33 type parser struct { 34 Input string 35 ch byte 36 offset int 37 err error 38 } 39 40 type expressionNumber struct { 41 Val int 42 Str string 43 } 44 45 type expressionVariable struct { 46 Val int 47 Str string 48 } 49 50 type expressionOperation struct { 51 Op string 52 Lhs, 53 Rhs expression 54 } 55 56 type ast struct { 57 rawexpressions []*expressionRaw 58 source string 59 currentexpression *expressionRaw 60 currentIndex int 61 depth int 62 err error 63 } 64 65 // Parse takes an expression string, e.g. "1+2" and returns 66 // a parsed expression. If there is an error it will return. 67 func Parse(s string) (ar expression, err error) { 68 toks, err := lexer(s) 69 if err != nil { 70 return 71 } 72 ast, err := newAST(toks, s) 73 if err != nil { 74 return 75 } 76 ar, err = ast.parseExpression() 77 return 78 } 79 80 // Eval takes a parsed expression and a map of variables (or nil). The parsed 81 // expression is evaluated using any variables and returns the 82 // resulting int and/or error. 83 func Eval(expr expression, variables map[string]int) (res int, err error) { 84 if err != nil { 85 return 86 } 87 var l, r int 88 switch expr.(type) { 89 case expressionVariable: 90 ast := expr.(expressionVariable) 91 ok := false 92 if variables != nil { 93 res, ok = variables[ast.Str] 94 } 95 if !ok { 96 err = ufmt.Errorf("variable '%s' not found", ast.Str) 97 } 98 return 99 case expressionOperation: 100 ast := expr.(expressionOperation) 101 l, err = Eval(ast.Lhs, variables) 102 if err != nil { 103 return 104 } 105 r, err = Eval(ast.Rhs, variables) 106 if err != nil { 107 return 108 } 109 switch ast.Op { 110 case "+": 111 res = l + r 112 case "-": 113 res = l - r 114 case "*": 115 res = l * r 116 case "/": 117 if r == 0 { 118 err = ufmt.Errorf("violation of arithmetic specification: a division by zero in Eval: [%d/%d]", l, r) 119 return 120 } 121 res = l / r 122 case "%": 123 if r == 0 { 124 res = 0 125 } else { 126 res = l % r 127 } 128 case "^": 129 res = l ^ r 130 case ">>": 131 res = l >> r 132 case "<<": 133 res = l << r 134 case ">": 135 if l > r { 136 res = 1 137 } else { 138 res = 0 139 } 140 case "<": 141 if l < r { 142 res = 1 143 } else { 144 res = 0 145 } 146 case "&": 147 res = l & r 148 case "|": 149 res = l | r 150 default: 151 152 } 153 case expressionNumber: 154 res = expr.(expressionNumber).Val 155 } 156 157 return 158 } 159 160 func expressionError(s string, pos int) string { 161 r := strings.Repeat("-", len(s)) + "\n" 162 s += "\n" 163 for i := 0; i < pos; i++ { 164 s += " " 165 } 166 s += "^\n" 167 return r + s + r 168 } 169 170 func (n expressionVariable) String() string { 171 return ufmt.Sprintf( 172 "expressionVariable: %s", 173 n.Str, 174 ) 175 } 176 177 func (n expressionNumber) String() string { 178 return ufmt.Sprintf( 179 "expressionNumber: %s", 180 n.Str, 181 ) 182 } 183 184 func (b expressionOperation) String() string { 185 return ufmt.Sprintf( 186 "expressionOperation: (%s %s %s)", 187 b.Op, 188 b.Lhs.String(), 189 b.Rhs.String(), 190 ) 191 } 192 193 func newAST(toks []*expressionRaw, s string) (*ast, error) { 194 a := &ast{ 195 rawexpressions: toks, 196 source: s, 197 } 198 if a.rawexpressions == nil || len(a.rawexpressions) == 0 { 199 return a, errors.New("empty token") 200 } else { 201 a.currentIndex = 0 202 a.currentexpression = a.rawexpressions[0] 203 } 204 return a, nil 205 } 206 207 func (a *ast) parseExpression() (expression, error) { 208 a.depth++ // called depth 209 lhs := a.parsePrimary() 210 r := a.parseBinOpRHS(0, lhs) 211 a.depth-- 212 if a.depth == 0 && a.currentIndex != len(a.rawexpressions) && a.err == nil { 213 return r, ufmt.Errorf("bad expression, reaching the end or missing the operator\n%s", 214 expressionError(a.source, a.currentexpression.Offset)) 215 } 216 return r, nil 217 } 218 219 func (a *ast) getNextexpressionRaw() *expressionRaw { 220 a.currentIndex++ 221 if a.currentIndex < len(a.rawexpressions) { 222 a.currentexpression = a.rawexpressions[a.currentIndex] 223 return a.currentexpression 224 } 225 return nil 226 } 227 228 func (a *ast) getTokPrecedence() int { 229 switch a.currentexpression.expression { 230 case "/", "%", "*": 231 return 100 232 case "<<", ">>": 233 return 80 234 case "+", "-": 235 return 75 236 case "<", ">": 237 return 70 238 case "&": 239 return 60 240 case "^": 241 return 50 242 case "|": 243 return 40 244 } 245 return -1 246 } 247 248 func (a *ast) parseNumber() expressionNumber { 249 f64, err := strconv.Atoi(a.currentexpression.expression) 250 if err != nil { 251 a.err = ufmt.Errorf("%v\nwant '(' or '0-9' but get '%s'\n%s", 252 err.Error(), 253 a.currentexpression.expression, 254 expressionError(a.source, a.currentexpression.Offset)) 255 return expressionNumber{} 256 } 257 n := expressionNumber{ 258 Val: f64, 259 Str: a.currentexpression.expression, 260 } 261 a.getNextexpressionRaw() 262 return n 263 } 264 265 func (a *ast) parseVariable() expressionVariable { 266 n := expressionVariable{ 267 Val: 0, 268 Str: a.currentexpression.expression, 269 } 270 a.getNextexpressionRaw() 271 return n 272 } 273 274 func (a *ast) parsePrimary() expression { 275 switch a.currentexpression.Type { 276 case Variable: 277 return a.parseVariable() 278 case Number: 279 return a.parseNumber() 280 case Operator: 281 if a.currentexpression.expression == "(" { 282 t := a.getNextexpressionRaw() 283 if t == nil { 284 a.err = ufmt.Errorf("want '(' or '0-9' but get EOF\n%s", 285 expressionError(a.source, a.currentexpression.Offset)) 286 return nil 287 } 288 e, _ := a.parseExpression() 289 if e == nil { 290 return nil 291 } 292 if a.currentexpression.expression != ")" { 293 a.err = ufmt.Errorf("want ')' but get %s\n%s", 294 a.currentexpression.expression, 295 expressionError(a.source, a.currentexpression.Offset)) 296 return nil 297 } 298 a.getNextexpressionRaw() 299 return e 300 } else if a.currentexpression.expression == "-" { 301 if a.getNextexpressionRaw() == nil { 302 a.err = ufmt.Errorf("want '0-9' but get '-'\n%s", 303 expressionError(a.source, a.currentexpression.Offset)) 304 return nil 305 } 306 bin := expressionOperation{ 307 Op: "-", 308 Lhs: expressionNumber{}, 309 Rhs: a.parsePrimary(), 310 } 311 return bin 312 } else { 313 return a.parseNumber() 314 } 315 default: 316 return nil 317 } 318 } 319 320 func (a *ast) parseBinOpRHS(execPrec int, lhs expression) expression { 321 for { 322 tokPrec := a.getTokPrecedence() 323 if tokPrec < execPrec { 324 return lhs 325 } 326 binOp := a.currentexpression.expression 327 if a.getNextexpressionRaw() == nil { 328 a.err = ufmt.Errorf("want '(' or '0-9' but get EOF\n%s", 329 expressionError(a.source, a.currentexpression.Offset)) 330 return nil 331 } 332 rhs := a.parsePrimary() 333 if rhs == nil { 334 return nil 335 } 336 nextPrec := a.getTokPrecedence() 337 if tokPrec < nextPrec { 338 rhs = a.parseBinOpRHS(tokPrec+1, rhs) 339 if rhs == nil { 340 return nil 341 } 342 } 343 lhs = expressionOperation{ 344 Op: binOp, 345 Lhs: lhs, 346 Rhs: rhs, 347 } 348 } 349 } 350 351 func lexer(s string) ([]*expressionRaw, error) { 352 p := &parser{ 353 Input: s, 354 err: nil, 355 ch: s[0], 356 } 357 toks := p.parse() 358 if p.err != nil { 359 return nil, p.err 360 } 361 return toks, nil 362 } 363 364 func (p *parser) parse() []*expressionRaw { 365 toks := make([]*expressionRaw, 0) 366 for { 367 tok := p.nextTok() 368 if tok == nil { 369 break 370 } 371 toks = append(toks, tok) 372 } 373 return toks 374 } 375 376 func (p *parser) nextTok() *expressionRaw { 377 if p.offset >= len(p.Input) || p.err != nil { 378 return nil 379 } 380 var err error 381 for p.isWhitespace(p.ch) && err == nil { 382 err = p.nextCh() 383 } 384 start := p.offset 385 var tok *expressionRaw 386 switch p.ch { 387 case 388 '(', 389 ')', 390 '+', 391 '-', 392 '*', 393 '/', 394 '^', 395 '&', 396 '|', 397 '%': 398 tok = &expressionRaw{ 399 expression: string(p.ch), 400 Type: Operator, 401 } 402 tok.Offset = start 403 err = p.nextCh() 404 case '>', '<': 405 tokS := string(p.ch) 406 bb, be := p.nextChPeek() 407 if be == nil && string(bb) == tokS { 408 tokS += string(p.ch) 409 } 410 tok = &expressionRaw{ 411 expression: tokS, 412 Type: Operator, 413 } 414 tok.Offset = start 415 if len(tokS) > 1 { 416 p.nextCh() 417 } 418 err = p.nextCh() 419 case 420 '0', 421 '1', 422 '2', 423 '3', 424 '4', 425 '5', 426 '6', 427 '7', 428 '8', 429 '9': 430 for p.isDigitNum(p.ch) && p.nextCh() == nil { 431 if (p.ch == '-' || p.ch == '+') && p.Input[p.offset-1] != 'e' { 432 break 433 } 434 } 435 tok = &expressionRaw{ 436 expression: strings.ReplaceAll(p.Input[start:p.offset], "_", ""), 437 Type: Number, 438 } 439 tok.Offset = start 440 default: 441 if p.isChar(p.ch) { 442 tok = &expressionRaw{ 443 expression: string(p.ch), 444 Type: Variable, 445 } 446 tok.Offset = start 447 err = p.nextCh() 448 } else if p.ch != ' ' { 449 p.err = ufmt.Errorf("symbol error: unknown '%v', pos [%v:]\n%s", 450 string(p.ch), 451 start, 452 expressionError(p.Input, start)) 453 } 454 } 455 return tok 456 } 457 458 func (p *parser) nextChPeek() (byte, error) { 459 offset := p.offset + 1 460 if offset < len(p.Input) { 461 return p.Input[offset], nil 462 } 463 return byte(0), errors.New("no byte") 464 } 465 466 func (p *parser) nextCh() error { 467 p.offset++ 468 if p.offset < len(p.Input) { 469 p.ch = p.Input[p.offset] 470 return nil 471 } 472 return errors.New("EOF") 473 } 474 475 func (p *parser) isWhitespace(c byte) bool { 476 return c == ' ' || 477 c == '\t' || 478 c == '\n' || 479 c == '\v' || 480 c == '\f' || 481 c == '\r' 482 } 483 484 func (p *parser) isDigitNum(c byte) bool { 485 return '0' <= c && c <= '9' || c == '.' || c == '_' || c == 'e' || c == '-' || c == '+' 486 } 487 488 func (p *parser) isChar(c byte) bool { 489 return 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' 490 } 491 492 func (p *parser) isWordChar(c byte) bool { 493 return p.isChar(c) || '0' <= c && c <= '9' 494 }