odin-blend2d

Odin bindings to Blend2D
Log | Files | Refs | README | LICENSE

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"]);