exp.js (20973B)
1 // This file is part of AsmJit project <https://asmjit.com> 2 // 3 // See <asmjit/core.h> or LICENSE.md for license and copyright information 4 // SPDX-License-Identifier: Zlib 5 6 (function($scope, $as) { 7 "use strict"; 8 9 // Supported Operators 10 // ------------------- 11 12 const kUnaryOperators = { 13 "-": {prec: 3, rtl : 1, emit: "-@1" }, 14 "~": {prec: 3, rtl : 1, emit: "~@1" }, 15 "!": {prec: 3, rtl : 1, emit: "!@1" } 16 }; 17 18 const kBinaryOperators = { 19 "*" : { prec: 5, rtl : 0, emit: "@1 * @2" }, 20 "/" : { prec: 5, rtl : 0, emit: "@1 / @2" }, 21 "%" : { prec: 5, rtl : 0, emit: "@1 % @2" }, 22 "+" : { prec: 6, rtl : 0, emit: "@1 + @2" }, 23 "-" : { prec: 6, rtl : 0, emit: "@1 - @2" }, 24 ">>": { prec: 7, rtl : 0, emit: "@1 >> @2" }, 25 "<<": { prec: 7, rtl : 0, emit: "@1 << @2" }, 26 "<" : { prec: 9, rtl : 0, emit: "@1 < @2" }, 27 ">" : { prec: 9, rtl : 0, emit: "@1 > @2" }, 28 "<=": { prec: 9, rtl : 0, emit: "@1 <= @2" }, 29 ">=": { prec: 9, rtl : 0, emit: "@1 >= @2" }, 30 "==": { prec:10, rtl : 0, emit: "@1 == @2" }, 31 "!=": { prec:10, rtl : 0, emit: "@1 != @2" }, 32 "&" : { prec:11, rtl : 0, emit: "@1 & @2" }, 33 "^" : { prec:12, rtl : 0, emit: "@1 ^ @2" }, 34 "|" : { prec:13, rtl : 0, emit: "@1 | @2" }, 35 "&&": { prec:14, rtl : 0, emit: "@1 && @2" }, 36 "||": { prec:15, rtl : 0, emit: "@1 || @2" }, 37 "?" : { prec:16, rtl : 0, emit: "@1 ? @2" }, 38 ":" : { prec:16, rtl : 0, emit: "@1 : @2" } 39 }; 40 41 const kMaxOperatorLen = 4; 42 43 function rightAssociate(info, bPrec) { 44 return info.prec > bPrec || (info.prec === bPrec && info.rtl); 45 } 46 47 // Expression Error 48 // ---------------- 49 50 // Contains `message` and `position` members. If the `position` is not `-1` then it is 51 // a zero-based index, which points to a first character of the token near the error. 52 class ExpressionError extends Error { 53 constructor(message, position) { 54 super(message); 55 this.name = "ExpressionError"; 56 this.message = message; 57 this.position = position != null ? position : -1; 58 } 59 } 60 61 function throwTokenizerError(token) { 62 throw new ExpressionError(`Unexpected token '${token.data}'`, token.position); 63 } 64 65 function throwExpressionError(message, position) { 66 throw new ExpressionError(message, position); 67 } 68 69 // Expression Tree 70 // --------------- 71 72 function mustEnclose(node) { 73 return node.isUnary() ? node.child.isOperator() : node.isBinary() ? true : false; 74 } 75 76 class ExpNode { 77 constructor(type) { this.type = type; } 78 79 isImm() { return this.type === "imm"; } 80 isVar() { return this.type === "var"; } 81 isCall() { return this.type === "call"; } 82 isUnary() { return this.type === "unary"; } 83 isBinary() { return this.type === "binary"; } 84 isOperator() { return this.type === "unary" || this.type === "binary"; } 85 86 info() { return null; } 87 clone() { throw new Error("ExpNode.clone() must be overridden"); } 88 evaluate(ctx) { throw new Error("ExpNode.evaluate() must be overridden"); } 89 toString(ctx) { throw new Error("ExpNode.toString() must be overridden"); } 90 } 91 92 class ImmNode extends ExpNode { 93 constructor(imm) { 94 super("imm"); 95 this.imm = imm || 0; 96 } 97 98 clone() { return new ImmNode(this.imm); } 99 evaluate(ctx) { return this.imm; } 100 toString(ctx) { return ctx ? ctx.stringifyImmediate(this.imm) : String(this.imm); } 101 } 102 103 class VarNode extends ExpNode { 104 constructor(name) { 105 super("var"); 106 this.name = name || ""; 107 } 108 109 clone() { return new VarNode(this.name); } 110 evaluate(ctx) { return ctx.variable(this.name); } 111 toString(ctx) { return ctx ? ctx.stringifyVariable(this.name) : String(this.name); } 112 } 113 114 class CallNode extends ExpNode { 115 constructor(name, args) { 116 super("call"); 117 this.name = name || ""; 118 this.args = args || []; 119 } 120 121 clone() { 122 return new CallNode(this.name, this.args.map(function(arg) { return arg.clone(); })); 123 } 124 125 evaluate(ctx) { 126 const evaluatedArgs = this.args.map(function(arg) { return arg.evaluate(ctx); }); 127 return ctx.function(this.name, evaluatedArgs); 128 } 129 130 toString(ctx) { 131 if (this.name === "$bit") { 132 return `((${this.args[0]} >> ${this.args[1]}) & 1)`; 133 } 134 else { 135 let argsCode = this.args.map(function(arg) { return arg.toString(ctx); }).join(", "); 136 if (ctx) 137 return `${ctx.stringifyFunction(this.name)}(${argsCode})`; 138 else 139 return `${this.name}(${argsCode})`; 140 } 141 } 142 } 143 144 class UnaryNode extends ExpNode { 145 constructor(op, child) { 146 if (!Object.hasOwn(kUnaryOperators, op)) 147 throw new Error(`Invalid unary operator '${op}`); 148 149 super("unary"); 150 this.op = op; 151 this.child = child || null; 152 } 153 154 info() { 155 return kUnaryOperators[this.op]; 156 } 157 158 clone() { 159 return new UnaryNode(this.op, this.left ? this.left.clone() : null); 160 } 161 162 evaluate(ctx) { 163 const val = this.child.evaluate(ctx); 164 switch (this.op) { 165 case "-": return (-val); 166 case "~": return (~val); 167 case "!": return (val ? 0 : 1); 168 default : return ctx.unary(this.op, val); 169 } 170 } 171 172 toString(ctx) { 173 return this.info().emit.replace(/@1/g, () => { 174 const node = this.child; 175 const code = node.toString(ctx); 176 return mustEnclose(node) ? `(${code})` : code; 177 }); 178 } 179 } 180 181 class BinaryNode extends ExpNode { 182 constructor(op, left, right) { 183 if (!Object.hasOwn(kBinaryOperators, op)) 184 throw new Error(`Invalid binary operator '${op}`); 185 186 super("binary"); 187 this.op = op || ""; 188 this.left = left || null; 189 this.right = right || null; 190 } 191 192 info() { 193 return kBinaryOperators[this.op]; 194 } 195 196 clone() { 197 return new BinaryNode(this.op, this.left ? this.left.clone() : null, this.right ? this.right.clone() : null); 198 } 199 200 evaluate(ctx) { 201 const left = this.left.evaluate(ctx); 202 const right = this.right.evaluate(ctx); 203 204 switch (this.op) { 205 case "-" : return left - right; 206 case "+" : return left + right; 207 case "*" : return left * right; 208 case "/" : return (left / right)|0; 209 case "%" : return (left % right)|0; 210 case "&" : return left & right; 211 case "|" : return left | right; 212 case "^" : return left ^ right; 213 case "<<": return left << right; 214 case ">>": return left >> right; 215 case "==": return left == right ? 1 : 0; 216 case "!=": return left != right ? 1 : 0; 217 case "<" : return left < right ? 1 : 0; 218 case "<=": return left <= right ? 1 : 0; 219 case ">" : return left > right ? 1 : 0; 220 case ">=": return left >= right ? 1 : 0; 221 case "&&": return left && right ? 1 : 0; 222 case "||": return left || right ? 1 : 0; 223 default : return ctx.binary(this.op, left, right); 224 } 225 } 226 227 toString(ctx) { 228 return this.info().emit.replace(/@[1-2]/g, (p) => { 229 const node = p === "@1" ? this.left : this.right; 230 const code = node.toString(ctx); 231 return mustEnclose(node) ? `(${code})` : code; 232 }); 233 } 234 } 235 236 function Imm(imm) { return new ImmNode(imm); } 237 function Var(name) { return new VarNode(name); } 238 function Call(name, args) { return new CallNode(name, args); } 239 function Unary(op, child) { return new UnaryNode(op, child); } 240 function Binary(op, left, right) { return new BinaryNode(op, left, right); } 241 242 function Negate(child) { return Unary("-", child); } 243 function BitNot(child) { return Unary("~", child); } 244 245 function Add(left, right) { return Binary("+", left, right); } 246 function Sub(left, right) { return Binary("-", left, right); } 247 function Mul(left, right) { return Binary("*", left, right); } 248 function Div(left, right) { return Binary("/", left, right); } 249 function Mod(left, right) { return Binary("%", left, right); } 250 function Shl(left, right) { return Binary("<<", left, right); } 251 function Shr(left, right) { return Binary(">>", left, right); } 252 function BitAnd(left, right) { return Binary("&", left, right); } 253 function BitOr(left, right) { return Binary("|", left, right); } 254 function BitXor(left, right) { return Binary("^", left, right); } 255 function Eq(left, right) { return Binary("==", left, right); } 256 function Ne(left, right) { return Binary("!=", left, right); } 257 function Lt(left, right) { return Binary("<", left, right); } 258 function Le(left, right) { return Binary("<=", left, right); } 259 function Gt(left, right) { return Binary(">", left, right); } 260 function Ge(left, right) { return Binary(">=", left, right); } 261 function And(left, right) { return Binary("&&", left, right); } 262 function Or(left, right) { return Binary("||", left, right); } 263 264 265 266 // Expression Tokenizer 267 // -------------------- 268 269 const kCharNone = 0; // '_' - Character category - Invalid or <end>. 270 const kCharSpace = 1; // 'S' - Character category - Space. 271 const kCharAlpha = 2; // 'A' - Character category - Alpha [A-Za-z_]. 272 const kCharDigit = 3; // 'D' - Character category - Digit [0-9]. 273 const kCharPunct = 4; // '$' - Character category - Punctuation. 274 275 const Category = (function(_, S, A, D, $) { 276 const Table = [ 277 _,_,_,_,_,_,_,_,_,S,S,S,S,S,_,_, // 000-015 |......... ..| 278 _,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_, // 016-031 |................| 279 S,$,$,$,$,$,$,$,$,$,$,$,$,$,$,$, // 032-047 | !"#$%&'()*+,-./| 280 D,D,D,D,D,D,D,D,D,D,$,$,$,$,$,$, // 048-063 |0123456789:;<=>?| 281 $,A,A,A,A,A,A,A,A,A,A,A,A,A,A,A, // 064-079 |@ABCDEFGHIJKLMNO| 282 A,A,A,A,A,A,A,A,A,A,A,$,$,$,$,A, // 080-095 |PQRSTUVWXYZ[\]^_| 283 $,A,A,A,A,A,A,A,A,A,A,A,A,A,A,A, // 096-111 |`abcdefghijklmno| 284 A,A,A,A,A,A,A,A,A,A,A,$,$,$,$,_, // 112-127 |pqrstuvwxyz{|}~ | 285 _,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_, // 128-143 |................| 286 _,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_ // 144-159 |................| 287 ]; 288 const kTableLength = Table.length; 289 290 return function(c) { 291 if (c < kTableLength) 292 return Table[c]; 293 return kCharNone; 294 }; 295 })(kCharNone, kCharSpace, kCharAlpha, kCharDigit, kCharPunct); 296 297 const kTokenNone = 0; 298 const kTokenPunct = 1; 299 const kTokenIdent = 2; 300 const kTokenValue = 3; 301 302 function newToken(type, position, data, value) { 303 return { 304 type : type, // Token type, see `kToken...`. 305 position: position, // Token position in expression's source. 306 data : data, // Token data (content) as string. 307 value : value // Token value (only if the token is a value). 308 }; 309 } 310 const NoToken = newToken(kTokenNone, -1, "<end>", null); 311 312 // Must be reset before it can be used, use `RegExp.lastIndex`. 313 const reNumValue = /(?:(?:\d*\.\d+|\d+)(?:[E|e][+|-]?\d+)?)/g; 314 315 function parseHex(source, from) { 316 let i = from; 317 let number = 0; 318 319 while (i < source.length) { 320 let c = source.charCodeAt(i); 321 let n = 0; 322 323 if (c >= '0'.charCodeAt(0) && c <= '9'.charCodeAt(0)) { 324 n = c - '0'.charCodeAt(0); 325 } 326 else if (c >= 'a'.charCodeAt(0) && c <= 'f'.charCodeAt(0)) { 327 n = c - 'a'.charCodeAt(0) + 10; 328 } 329 else if (c >= 'A'.charCodeAt(0) && c <= 'F'.charCodeAt(0)) { 330 n = c - 'A'.charCodeAt(0) + 10; 331 } 332 else if (c >= 'g'.charCodeAt(0) && c <= 'z'.charCodeAt(0) || c >= 'g'.charCodeAt(0) && c <= 'Z'.charCodeAt(0)) { 333 throwExpressionError(`Invalid hex number 0x${source.substring(from, i + 1)}`); 334 } 335 else { 336 break; 337 } 338 339 number = (number << 4) | n; 340 i++; 341 } 342 343 if (i === from) 344 throwExpressionError(`Invalid number starting with 0x`); 345 346 return { 347 number: number, 348 end: i 349 }; 350 } 351 352 function tokenize(source) { 353 const len = source.length; 354 const tokens = []; 355 356 let i = 0, j = 0; // Current index in `source` and temporary. 357 let start = 0; // Current token start position. 358 let data = ""; // Current token data (content) as string. 359 let c, cat; // Current character code and category. 360 361 while (i < len) { 362 c = source.charCodeAt(i); 363 cat = Category(c); 364 365 if (cat === kCharSpace) { 366 i++; 367 } 368 else if (cat === kCharDigit) { 369 const n = tokens.length - 1; 370 371 // Hex number. 372 if (c === '0'.charCodeAt(0) && i + 1 < len && source.charCodeAt(i + 1) === 'x'.charCodeAt(0)) { 373 const status = parseHex(source, i + 2); 374 tokens.push(newToken(kTokenValue, i, source.substring(i, status.end), status.number)); 375 i = status.end; 376 } 377 else { 378 if (n >= 0 && tokens[n].data === "." && source[i - 1] === ".") { 379 tokens.length = n; 380 i--; 381 } 382 383 reNumValue.lastIndex = i; 384 data = reNumValue.exec(source)[0]; 385 386 tokens.push(newToken(kTokenValue, i, data, parseFloat(data))); 387 i += data.length; 388 } 389 } 390 else if (cat === kCharAlpha) { 391 start = i; 392 while (++i < len && ((cat = Category(source.charCodeAt(i))) === kCharAlpha || cat === kCharDigit)) 393 continue; 394 395 data = source.substring(start, i); 396 tokens.push(newToken(kTokenIdent, start, data, null)); 397 } 398 else if (cat === kCharPunct) { 399 start = i; 400 while (++i < len && Category(source.charCodeAt(i)) === kCharPunct) 401 continue; 402 403 data = source.substring(start, i); 404 do { 405 for (j = Math.min(i - start, kMaxOperatorLen); j > 0; j--) { 406 const part = source.substr(start, j); 407 if (Object.hasOwn(kUnaryOperators, part) || Object.hasOwn(kBinaryOperators, part) || j === 1) { 408 tokens.push(newToken(kTokenPunct, start, part, null)); 409 start += j; 410 break; 411 } 412 } 413 } while (start < i); 414 } 415 else { 416 throwExpressionError(`Unrecognized character '0x${c.toString(16)}'`, i); 417 } 418 } 419 420 return tokens; 421 } 422 423 // Expression Parser 424 // ----------------- 425 426 class Parser { 427 constructor(tokens) { 428 this.tokens = tokens; 429 this.tIndex = 0; 430 } 431 432 peek() { return this.tIndex < this.tokens.length ? this.tokens[this.tIndex ] : NoToken; } 433 next() { return this.tIndex < this.tokens.length ? this.tokens[this.tIndex++] : NoToken; } 434 skip() { this.tIndex++; return this; } 435 back(token) { this.tIndex -= +(token !== NoToken); return this; } 436 437 parse() { 438 // The root expression cannot be empty. 439 let token = this.peek(); 440 if (token === NoToken) 441 throwExpressionError("Expression cannot be empty", 0); 442 443 const exp = this.parseExpression(); 444 445 // The root expression must reach the end of the input. 446 token = this.peek(); 447 if (token !== NoToken) 448 throwTokenizerError(token); 449 450 return exp; 451 } 452 453 parseExpression() { 454 const stack = []; 455 let value = null; 456 let token = null; 457 458 for (;;) { 459 // The only case of value not being `null` is after ternary-if. In that 460 // case the value was already parsed so we want to skip this section. 461 if (value === null) { 462 let unaryFirst = null; 463 let unaryLast = null; 464 465 token = this.next(); 466 467 // Parse a possible unary operator(s). 468 if (token.type === kTokenPunct) { 469 do { 470 const opName = token.data; 471 const opInfo = kUnaryOperators[opName]; 472 473 if (!opInfo) 474 break; 475 476 const node = Unary(opName); 477 if (unaryLast) 478 unaryLast.child = node; 479 else 480 unaryFirst = node; 481 482 unaryLast = node; 483 token = this.next(); 484 } while (token.type === kTokenPunct); 485 } 486 487 // Parse a value, variable, function call, or nested expression. 488 if (token.type === kTokenValue) { 489 value = Imm(token.value); 490 } 491 else if (token.type === kTokenIdent) { 492 const name = token.data; 493 const after = this.peek(); 494 495 if (after.data === "(") 496 value = this.parseCall(token.data); 497 else if (after.data === "[") 498 value = this.parseBitAccess(token.data); 499 else 500 value = Var(name); 501 } 502 else if (token.data === "(") { 503 value = this.parseExpression(); 504 token = this.next(); 505 506 if (token.data !== ")") 507 throwTokenizerError(token); 508 } 509 else { 510 throwTokenizerError(token); 511 } 512 513 // Replace the value with the top-level unary operator, if parsed. 514 if (unaryFirst) { 515 unaryLast.child = value; 516 value = unaryFirst; 517 } 518 } 519 520 // Parse a possible binary operator - the loop must repeat, if present. 521 token = this.peek(); 522 if (token.type === kTokenPunct && Object.hasOwn(kBinaryOperators, token.data)) { 523 const opName = token.data; 524 if (opName === ":") 525 break; 526 527 // Consume the token. 528 this.skip(); 529 530 const bNode = Binary(opName, null, null); 531 532 if (!stack.length) { 533 bNode.left = value; 534 stack.push(bNode); 535 } 536 else { 537 let aNode = stack.pop(); 538 let aPrec = aNode.info().prec; 539 let bPrec = bNode.info().prec; 540 541 if (aPrec > bPrec) { 542 aNode.right = bNode; 543 bNode.left = value; 544 stack.push(aNode, bNode); 545 } 546 else { 547 aNode.right = value; 548 549 // Advance to the top-most op that has less/equal precedence than `bPrec`. 550 while (stack.length) { 551 if (rightAssociate(aNode.info(), bPrec)) 552 break; 553 aNode = stack.pop(); 554 } 555 556 if (!stack.length && !rightAssociate(aNode.info(), bPrec)) { 557 bNode.left = aNode; 558 stack.push(bNode); 559 } 560 else { 561 const tmp = aNode.right; 562 aNode.right = bNode; 563 bNode.left = tmp; 564 stack.push(aNode, bNode); 565 } 566 } 567 } 568 569 // Parse "<cond> {ternary-if} <taken> {ternary-else} <not-taken>". 570 if (opName === "?") { 571 const ternLeft = this.parseExpression(); 572 const ternTok = this.next(); 573 574 if (ternTok.data !== ":") 575 throwExpressionError(`Unterminated ternary if '${token.data}'`, token.position); 576 577 const ternRight = this.parseExpression(); 578 value = Binary(opName, info, ternLeft, ternRight); 579 } 580 else { 581 value = null; 582 } 583 584 continue; 585 } 586 587 break; 588 } 589 590 if (value === null) 591 throwExpressionError("Invalid expression"); 592 593 if (stack.length !== 0) { 594 stack[stack.length - 1].right = value; 595 value = stack[0]; 596 } 597 598 return value; 599 } 600 601 parseCall(name) { 602 const args = []; 603 604 let token = this.next(); 605 if (token.data !== "(") 606 throwTokenizerError(token); 607 608 for (;;) { 609 token = this.peek(); 610 if (token.data === ")") 611 break; 612 613 if (args.length !== 0) { 614 if (token.data !== ",") 615 throwTokenizerError(token); 616 this.skip(); 617 } 618 619 args.push(this.parseExpression()); 620 } 621 622 this.skip(); 623 return Call(name, args); 624 } 625 626 parseBitAccess(name) { 627 let token = this.next(); 628 if (token.data !== "[") 629 throwTokenizerError(token); 630 631 token = this.next(); 632 if (token.type != kTokenValue) 633 throwTokenizerError(token); 634 635 const index = token.value; 636 637 token = this.next(); 638 if (token.data !== "]") 639 throwTokenizerError(token); 640 641 return Call("$bit", [Var(name), index]); 642 } 643 } 644 645 function parse(source) { 646 const tokens = tokenize(source); 647 return new Parser(tokens).parse(); 648 } 649 650 // Expression Visitors 651 // ------------------- 652 653 class Visitor { 654 visit(node) { 655 switch (node.type) { 656 case "imm": 657 case "var": { 658 break; 659 } 660 661 case "call": { 662 for (let arg of node.args) 663 this.visit(arg); 664 break; 665 } 666 667 case "unary": { 668 if (node.child) 669 this.visit(node.child); 670 break; 671 } 672 673 case "binary": { 674 if (node.left) 675 this.visit(node.left); 676 if (node.right) 677 this.visit(node.right); 678 break; 679 } 680 681 default: { 682 throw new Error(`Visitor.visit(): Unknown node type '${node.type}'`); 683 } 684 } 685 } 686 } 687 688 class Collector extends Visitor { 689 constructor(nodeType, dst) { 690 super(); 691 this.dict = dst || Object.create(null); 692 this.nodeType = nodeType; 693 } 694 695 visit(node) { 696 if (node.type === this.nodeType) { 697 if (Object.hasOwn(this.dict, node.name)) 698 this.dict[node.name]++; 699 else 700 this.dict[node.name] = 1; 701 } 702 703 super.visit(node); 704 } 705 } 706 707 function collectVars(node, dst) { 708 const collector = new Collector("var", dst); 709 collector.visit(node) 710 return collector.dict; 711 } 712 713 function collectCalls(node, dst) { 714 const collector = new Collector("call", dst); 715 collector.visit(node) 716 return collector.dict; 717 } 718 719 // Exports 720 // ------- 721 722 $scope[$as] = { 723 Imm: Imm, 724 Var: Var, 725 Call: Call, 726 Unary: Unary, 727 Binary: Binary, 728 729 Negate: Negate, 730 BitNot: BitNot, 731 732 Add: Add, 733 Sub: Sub, 734 Mul: Mul, 735 Div: Div, 736 Mod: Mod, 737 Shl: Shl, 738 Shr: Shr, 739 BitAnd: BitAnd, 740 BitOr: BitOr, 741 BitXor: BitXor, 742 Eq: Eq, 743 Ne: Ne, 744 Lt: Lt, 745 Le: Le, 746 Gt: Gt, 747 Ge: Ge, 748 And: And, 749 Or: Or, 750 751 Visitor: Visitor, 752 ExpressionError: ExpressionError, 753 754 parse: parse, 755 collectVars: collectVars, 756 collectCalls: collectCalls 757 }; 758 759 }).apply(this, typeof module === "object" && module && module.exports 760 ? [module, "exports"] : [this.asmdb || (this.asmdb = {}), "exp"]);