1/*********************************************************************** 2 3 A JavaScript tokenizer / parser / beautifier / compressor. 4 https://github.com/mishoo/UglifyJS2 5 6 -------------------------------- (C) --------------------------------- 7 8 Author: Mihai Bazon 9 <mihai.bazon@gmail.com> 10 http://mihai.bazon.net/blog 11 12 Distributed under the BSD license: 13 14 Copyright 2012 (c) Mihai Bazon <mihai.bazon@gmail.com> 15 16 Redistribution and use in source and binary forms, with or without 17 modification, are permitted provided that the following conditions 18 are met: 19 20 * Redistributions of source code must retain the above 21 copyright notice, this list of conditions and the following 22 disclaimer. 23 24 * Redistributions in binary form must reproduce the above 25 copyright notice, this list of conditions and the following 26 disclaimer in the documentation and/or other materials 27 provided with the distribution. 28 29 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY 30 EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 31 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 32 PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE 33 LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, 34 OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 35 PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 36 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 37 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 38 TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 39 THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 40 SUCH DAMAGE. 41 42 ***********************************************************************/ 43 44"use strict"; 45 46function DEFNODE(type, props, methods, base) { 47 if (arguments.length < 4) base = AST_Node; 48 if (!props) props = []; 49 else props = props.split(/\s+/); 50 var self_props = props; 51 if (base && base.PROPS) 52 props = props.concat(base.PROPS); 53 var code = "return function AST_" + type + "(props){ if (props) { "; 54 for (var i = props.length; --i >= 0;) { 55 code += "this." + props[i] + " = props." + props[i] + ";"; 56 } 57 var proto = base && new base; 58 if (proto && proto.initialize || (methods && methods.initialize)) 59 code += "this.initialize();"; 60 code += "}}"; 61 var ctor = new Function(code)(); 62 if (proto) { 63 ctor.prototype = proto; 64 ctor.BASE = base; 65 } 66 if (base) base.SUBCLASSES.push(ctor); 67 ctor.prototype.CTOR = ctor; 68 ctor.PROPS = props || null; 69 ctor.SELF_PROPS = self_props; 70 ctor.SUBCLASSES = []; 71 if (type) { 72 ctor.prototype.TYPE = ctor.TYPE = type; 73 } 74 if (methods) for (i in methods) if (HOP(methods, i)) { 75 if (/^\$/.test(i)) { 76 ctor[i.substr(1)] = methods[i]; 77 } else { 78 ctor.prototype[i] = methods[i]; 79 } 80 } 81 ctor.DEFMETHOD = function(name, method) { 82 this.prototype[name] = method; 83 }; 84 if (typeof exports !== "undefined") { 85 exports["AST_" + type] = ctor; 86 } 87 return ctor; 88}; 89 90var AST_Token = DEFNODE("Token", "type value line col pos endline endcol endpos nlb comments_before file raw", { 91}, null); 92 93var AST_Node = DEFNODE("Node", "start end", { 94 _clone: function(deep) { 95 if (deep) { 96 var self = this.clone(); 97 return self.transform(new TreeTransformer(function(node) { 98 if (node !== self) { 99 return node.clone(true); 100 } 101 })); 102 } 103 return new this.CTOR(this); 104 }, 105 clone: function(deep) { 106 return this._clone(deep); 107 }, 108 $documentation: "Base class of all AST nodes", 109 $propdoc: { 110 start: "[AST_Token] The first token of this node", 111 end: "[AST_Token] The last token of this node" 112 }, 113 _walk: function(visitor) { 114 return visitor._visit(this); 115 }, 116 walk: function(visitor) { 117 return this._walk(visitor); // not sure the indirection will be any help 118 } 119}, null); 120 121AST_Node.warn_function = null; 122AST_Node.warn = function(txt, props) { 123 if (AST_Node.warn_function) 124 AST_Node.warn_function(string_template(txt, props)); 125}; 126 127/* -----[ statements ]----- */ 128 129var AST_Statement = DEFNODE("Statement", null, { 130 $documentation: "Base class of all statements", 131}); 132 133var AST_Debugger = DEFNODE("Debugger", null, { 134 $documentation: "Represents a debugger statement", 135}, AST_Statement); 136 137var AST_Directive = DEFNODE("Directive", "value scope quote", { 138 $documentation: "Represents a directive, like \"use strict\";", 139 $propdoc: { 140 value: "[string] The value of this directive as a plain string (it's not an AST_String!)", 141 scope: "[AST_Scope/S] The scope that this directive affects", 142 quote: "[string] the original quote character" 143 }, 144}, AST_Statement); 145 146var AST_SimpleStatement = DEFNODE("SimpleStatement", "body", { 147 $documentation: "A statement consisting of an expression, i.e. a = 1 + 2", 148 $propdoc: { 149 body: "[AST_Node] an expression node (should not be instanceof AST_Statement)" 150 }, 151 _walk: function(visitor) { 152 return visitor._visit(this, function(){ 153 this.body._walk(visitor); 154 }); 155 } 156}, AST_Statement); 157 158function walk_body(node, visitor) { 159 var body = node.body; 160 if (body instanceof AST_Statement) { 161 body._walk(visitor); 162 } 163 else for (var i = 0, len = body.length; i < len; i++) { 164 body[i]._walk(visitor); 165 } 166}; 167 168var AST_Block = DEFNODE("Block", "body", { 169 $documentation: "A body of statements (usually bracketed)", 170 $propdoc: { 171 body: "[AST_Statement*] an array of statements" 172 }, 173 _walk: function(visitor) { 174 return visitor._visit(this, function(){ 175 walk_body(this, visitor); 176 }); 177 } 178}, AST_Statement); 179 180var AST_BlockStatement = DEFNODE("BlockStatement", null, { 181 $documentation: "A block statement", 182}, AST_Block); 183 184var AST_EmptyStatement = DEFNODE("EmptyStatement", null, { 185 $documentation: "The empty statement (empty block or simply a semicolon)", 186 _walk: function(visitor) { 187 return visitor._visit(this); 188 } 189}, AST_Statement); 190 191var AST_StatementWithBody = DEFNODE("StatementWithBody", "body", { 192 $documentation: "Base class for all statements that contain one nested body: `For`, `ForIn`, `Do`, `While`, `With`", 193 $propdoc: { 194 body: "[AST_Statement] the body; this should always be present, even if it's an AST_EmptyStatement" 195 }, 196 _walk: function(visitor) { 197 return visitor._visit(this, function(){ 198 this.body._walk(visitor); 199 }); 200 } 201}, AST_Statement); 202 203var AST_LabeledStatement = DEFNODE("LabeledStatement", "label", { 204 $documentation: "Statement with a label", 205 $propdoc: { 206 label: "[AST_Label] a label definition" 207 }, 208 _walk: function(visitor) { 209 return visitor._visit(this, function(){ 210 this.label._walk(visitor); 211 this.body._walk(visitor); 212 }); 213 }, 214 clone: function(deep) { 215 var node = this._clone(deep); 216 if (deep) { 217 var label = node.label; 218 var def = this.label; 219 node.walk(new TreeWalker(function(node) { 220 if (node instanceof AST_LoopControl 221 && node.label && node.label.thedef === def) { 222 node.label.thedef = label; 223 label.references.push(node); 224 } 225 })); 226 } 227 return node; 228 } 229}, AST_StatementWithBody); 230 231var AST_IterationStatement = DEFNODE("IterationStatement", null, { 232 $documentation: "Internal class. All loops inherit from it." 233}, AST_StatementWithBody); 234 235var AST_DWLoop = DEFNODE("DWLoop", "condition", { 236 $documentation: "Base class for do/while statements", 237 $propdoc: { 238 condition: "[AST_Node] the loop condition. Should not be instanceof AST_Statement" 239 } 240}, AST_IterationStatement); 241 242var AST_Do = DEFNODE("Do", null, { 243 $documentation: "A `do` statement", 244 _walk: function(visitor) { 245 return visitor._visit(this, function(){ 246 this.body._walk(visitor); 247 this.condition._walk(visitor); 248 }); 249 } 250}, AST_DWLoop); 251 252var AST_While = DEFNODE("While", null, { 253 $documentation: "A `while` statement", 254 _walk: function(visitor) { 255 return visitor._visit(this, function(){ 256 this.condition._walk(visitor); 257 this.body._walk(visitor); 258 }); 259 } 260}, AST_DWLoop); 261 262var AST_For = DEFNODE("For", "init condition step", { 263 $documentation: "A `for` statement", 264 $propdoc: { 265 init: "[AST_Node?] the `for` initialization code, or null if empty", 266 condition: "[AST_Node?] the `for` termination clause, or null if empty", 267 step: "[AST_Node?] the `for` update clause, or null if empty" 268 }, 269 _walk: function(visitor) { 270 return visitor._visit(this, function(){ 271 if (this.init) this.init._walk(visitor); 272 if (this.condition) this.condition._walk(visitor); 273 if (this.step) this.step._walk(visitor); 274 this.body._walk(visitor); 275 }); 276 } 277}, AST_IterationStatement); 278 279var AST_ForIn = DEFNODE("ForIn", "init name object", { 280 $documentation: "A `for ... in` statement", 281 $propdoc: { 282 init: "[AST_Node] the `for/in` initialization code", 283 name: "[AST_SymbolRef?] the loop variable, only if `init` is AST_Var", 284 object: "[AST_Node] the object that we're looping through" 285 }, 286 _walk: function(visitor) { 287 return visitor._visit(this, function(){ 288 this.init._walk(visitor); 289 this.object._walk(visitor); 290 this.body._walk(visitor); 291 }); 292 } 293}, AST_IterationStatement); 294 295var AST_With = DEFNODE("With", "expression", { 296 $documentation: "A `with` statement", 297 $propdoc: { 298 expression: "[AST_Node] the `with` expression" 299 }, 300 _walk: function(visitor) { 301 return visitor._visit(this, function(){ 302 this.expression._walk(visitor); 303 this.body._walk(visitor); 304 }); 305 } 306}, AST_StatementWithBody); 307 308/* -----[ scope and functions ]----- */ 309 310var AST_Scope = DEFNODE("Scope", "directives variables functions uses_with uses_eval parent_scope enclosed cname", { 311 $documentation: "Base class for all statements introducing a lexical scope", 312 $propdoc: { 313 directives: "[string*/S] an array of directives declared in this scope", 314 variables: "[Object/S] a map of name -> SymbolDef for all variables/functions defined in this scope", 315 functions: "[Object/S] like `variables`, but only lists function declarations", 316 uses_with: "[boolean/S] tells whether this scope uses the `with` statement", 317 uses_eval: "[boolean/S] tells whether this scope contains a direct call to the global `eval`", 318 parent_scope: "[AST_Scope?/S] link to the parent scope", 319 enclosed: "[SymbolDef*/S] a list of all symbol definitions that are accessed from this scope or any subscopes", 320 cname: "[integer/S] current index for mangling variables (used internally by the mangler)", 321 }, 322}, AST_Block); 323 324var AST_Toplevel = DEFNODE("Toplevel", "globals", { 325 $documentation: "The toplevel scope", 326 $propdoc: { 327 globals: "[Object/S] a map of name -> SymbolDef for all undeclared names", 328 }, 329 wrap_enclose: function(arg_parameter_pairs) { 330 var self = this; 331 var args = []; 332 var parameters = []; 333 334 arg_parameter_pairs.forEach(function(pair) { 335 var splitAt = pair.lastIndexOf(":"); 336 337 args.push(pair.substr(0, splitAt)); 338 parameters.push(pair.substr(splitAt + 1)); 339 }); 340 341 var wrapped_tl = "(function(" + parameters.join(",") + "){ '$ORIG'; })(" + args.join(",") + ")"; 342 wrapped_tl = parse(wrapped_tl); 343 wrapped_tl = wrapped_tl.transform(new TreeTransformer(function before(node){ 344 if (node instanceof AST_Directive && node.value == "$ORIG") { 345 return MAP.splice(self.body); 346 } 347 })); 348 return wrapped_tl; 349 }, 350 wrap_commonjs: function(name, export_all) { 351 var self = this; 352 var to_export = []; 353 if (export_all) { 354 self.figure_out_scope(); 355 self.walk(new TreeWalker(function(node){ 356 if (node instanceof AST_SymbolDeclaration && node.definition().global) { 357 if (!find_if(function(n){ return n.name == node.name }, to_export)) 358 to_export.push(node); 359 } 360 })); 361 } 362 var wrapped_tl = "(function(exports, global){ '$ORIG'; '$EXPORTS'; global['" + name + "'] = exports; }({}, (function(){return this}())))"; 363 wrapped_tl = parse(wrapped_tl); 364 wrapped_tl = wrapped_tl.transform(new TreeTransformer(function before(node){ 365 if (node instanceof AST_Directive) { 366 switch (node.value) { 367 case "$ORIG": 368 return MAP.splice(self.body); 369 case "$EXPORTS": 370 var body = []; 371 to_export.forEach(function(sym){ 372 body.push(new AST_SimpleStatement({ 373 body: new AST_Assign({ 374 left: new AST_Sub({ 375 expression: new AST_SymbolRef({ name: "exports" }), 376 property: new AST_String({ value: sym.name }), 377 }), 378 operator: "=", 379 right: new AST_SymbolRef(sym), 380 }), 381 })); 382 }); 383 return MAP.splice(body); 384 } 385 } 386 })); 387 return wrapped_tl; 388 } 389}, AST_Scope); 390 391var AST_Lambda = DEFNODE("Lambda", "name argnames uses_arguments", { 392 $documentation: "Base class for functions", 393 $propdoc: { 394 name: "[AST_SymbolDeclaration?] the name of this function", 395 argnames: "[AST_SymbolFunarg*] array of function arguments", 396 uses_arguments: "[boolean/S] tells whether this function accesses the arguments array" 397 }, 398 _walk: function(visitor) { 399 return visitor._visit(this, function(){ 400 if (this.name) this.name._walk(visitor); 401 var argnames = this.argnames; 402 for (var i = 0, len = argnames.length; i < len; i++) { 403 argnames[i]._walk(visitor); 404 } 405 walk_body(this, visitor); 406 }); 407 } 408}, AST_Scope); 409 410var AST_Accessor = DEFNODE("Accessor", null, { 411 $documentation: "A setter/getter function. The `name` property is always null." 412}, AST_Lambda); 413 414var AST_Function = DEFNODE("Function", null, { 415 $documentation: "A function expression" 416}, AST_Lambda); 417 418var AST_Defun = DEFNODE("Defun", null, { 419 $documentation: "A function definition" 420}, AST_Lambda); 421 422/* -----[ JUMPS ]----- */ 423 424var AST_Jump = DEFNODE("Jump", null, { 425 $documentation: "Base class for “jumps” (for now that's `return`, `throw`, `break` and `continue`)" 426}, AST_Statement); 427 428var AST_Exit = DEFNODE("Exit", "value", { 429 $documentation: "Base class for “exits” (`return` and `throw`)", 430 $propdoc: { 431 value: "[AST_Node?] the value returned or thrown by this statement; could be null for AST_Return" 432 }, 433 _walk: function(visitor) { 434 return visitor._visit(this, this.value && function(){ 435 this.value._walk(visitor); 436 }); 437 } 438}, AST_Jump); 439 440var AST_Return = DEFNODE("Return", null, { 441 $documentation: "A `return` statement" 442}, AST_Exit); 443 444var AST_Throw = DEFNODE("Throw", null, { 445 $documentation: "A `throw` statement" 446}, AST_Exit); 447 448var AST_LoopControl = DEFNODE("LoopControl", "label", { 449 $documentation: "Base class for loop control statements (`break` and `continue`)", 450 $propdoc: { 451 label: "[AST_LabelRef?] the label, or null if none", 452 }, 453 _walk: function(visitor) { 454 return visitor._visit(this, this.label && function(){ 455 this.label._walk(visitor); 456 }); 457 } 458}, AST_Jump); 459 460var AST_Break = DEFNODE("Break", null, { 461 $documentation: "A `break` statement" 462}, AST_LoopControl); 463 464var AST_Continue = DEFNODE("Continue", null, { 465 $documentation: "A `continue` statement" 466}, AST_LoopControl); 467 468/* -----[ IF ]----- */ 469 470var AST_If = DEFNODE("If", "condition alternative", { 471 $documentation: "A `if` statement", 472 $propdoc: { 473 condition: "[AST_Node] the `if` condition", 474 alternative: "[AST_Statement?] the `else` part, or null if not present" 475 }, 476 _walk: function(visitor) { 477 return visitor._visit(this, function(){ 478 this.condition._walk(visitor); 479 this.body._walk(visitor); 480 if (this.alternative) this.alternative._walk(visitor); 481 }); 482 } 483}, AST_StatementWithBody); 484 485/* -----[ SWITCH ]----- */ 486 487var AST_Switch = DEFNODE("Switch", "expression", { 488 $documentation: "A `switch` statement", 489 $propdoc: { 490 expression: "[AST_Node] the `switch` “discriminant”" 491 }, 492 _walk: function(visitor) { 493 return visitor._visit(this, function(){ 494 this.expression._walk(visitor); 495 walk_body(this, visitor); 496 }); 497 } 498}, AST_Block); 499 500var AST_SwitchBranch = DEFNODE("SwitchBranch", null, { 501 $documentation: "Base class for `switch` branches", 502}, AST_Block); 503 504var AST_Default = DEFNODE("Default", null, { 505 $documentation: "A `default` switch branch", 506}, AST_SwitchBranch); 507 508var AST_Case = DEFNODE("Case", "expression", { 509 $documentation: "A `case` switch branch", 510 $propdoc: { 511 expression: "[AST_Node] the `case` expression" 512 }, 513 _walk: function(visitor) { 514 return visitor._visit(this, function(){ 515 this.expression._walk(visitor); 516 walk_body(this, visitor); 517 }); 518 } 519}, AST_SwitchBranch); 520 521/* -----[ EXCEPTIONS ]----- */ 522 523var AST_Try = DEFNODE("Try", "bcatch bfinally", { 524 $documentation: "A `try` statement", 525 $propdoc: { 526 bcatch: "[AST_Catch?] the catch block, or null if not present", 527 bfinally: "[AST_Finally?] the finally block, or null if not present" 528 }, 529 _walk: function(visitor) { 530 return visitor._visit(this, function(){ 531 walk_body(this, visitor); 532 if (this.bcatch) this.bcatch._walk(visitor); 533 if (this.bfinally) this.bfinally._walk(visitor); 534 }); 535 } 536}, AST_Block); 537 538var AST_Catch = DEFNODE("Catch", "argname", { 539 $documentation: "A `catch` node; only makes sense as part of a `try` statement", 540 $propdoc: { 541 argname: "[AST_SymbolCatch] symbol for the exception" 542 }, 543 _walk: function(visitor) { 544 return visitor._visit(this, function(){ 545 this.argname._walk(visitor); 546 walk_body(this, visitor); 547 }); 548 } 549}, AST_Block); 550 551var AST_Finally = DEFNODE("Finally", null, { 552 $documentation: "A `finally` node; only makes sense as part of a `try` statement" 553}, AST_Block); 554 555/* -----[ VAR/CONST ]----- */ 556 557var AST_Definitions = DEFNODE("Definitions", "definitions", { 558 $documentation: "Base class for `var` or `const` nodes (variable declarations/initializations)", 559 $propdoc: { 560 definitions: "[AST_VarDef*] array of variable definitions" 561 }, 562 _walk: function(visitor) { 563 return visitor._visit(this, function(){ 564 var definitions = this.definitions; 565 for (var i = 0, len = definitions.length; i < len; i++) { 566 definitions[i]._walk(visitor); 567 } 568 }); 569 } 570}, AST_Statement); 571 572var AST_Var = DEFNODE("Var", null, { 573 $documentation: "A `var` statement" 574}, AST_Definitions); 575 576var AST_Const = DEFNODE("Const", null, { 577 $documentation: "A `const` statement" 578}, AST_Definitions); 579 580var AST_VarDef = DEFNODE("VarDef", "name value", { 581 $documentation: "A variable declaration; only appears in a AST_Definitions node", 582 $propdoc: { 583 name: "[AST_SymbolVar|AST_SymbolConst] name of the variable", 584 value: "[AST_Node?] initializer, or null of there's no initializer" 585 }, 586 _walk: function(visitor) { 587 return visitor._visit(this, function(){ 588 this.name._walk(visitor); 589 if (this.value) this.value._walk(visitor); 590 }); 591 } 592}); 593 594/* -----[ OTHER ]----- */ 595 596var AST_Call = DEFNODE("Call", "expression args", { 597 $documentation: "A function call expression", 598 $propdoc: { 599 expression: "[AST_Node] expression to invoke as function", 600 args: "[AST_Node*] array of arguments" 601 }, 602 _walk: function(visitor) { 603 return visitor._visit(this, function(){ 604 this.expression._walk(visitor); 605 var args = this.args; 606 for (var i = 0, len = args.length; i < len; i++) { 607 args[i]._walk(visitor); 608 } 609 }); 610 } 611}); 612 613var AST_New = DEFNODE("New", null, { 614 $documentation: "An object instantiation. Derives from a function call since it has exactly the same properties" 615}, AST_Call); 616 617var AST_Seq = DEFNODE("Seq", "car cdr", { 618 $documentation: "A sequence expression (two comma-separated expressions)", 619 $propdoc: { 620 car: "[AST_Node] first element in sequence", 621 cdr: "[AST_Node] second element in sequence" 622 }, 623 $cons: function(x, y) { 624 var seq = new AST_Seq(x); 625 seq.car = x; 626 seq.cdr = y; 627 return seq; 628 }, 629 $from_array: function(array) { 630 if (array.length == 0) return null; 631 if (array.length == 1) return array[0].clone(); 632 var list = null; 633 for (var i = array.length; --i >= 0;) { 634 list = AST_Seq.cons(array[i], list); 635 } 636 var p = list; 637 while (p) { 638 if (p.cdr && !p.cdr.cdr) { 639 p.cdr = p.cdr.car; 640 break; 641 } 642 p = p.cdr; 643 } 644 return list; 645 }, 646 to_array: function() { 647 var p = this, a = []; 648 while (p) { 649 a.push(p.car); 650 if (p.cdr && !(p.cdr instanceof AST_Seq)) { 651 a.push(p.cdr); 652 break; 653 } 654 p = p.cdr; 655 } 656 return a; 657 }, 658 add: function(node) { 659 var p = this; 660 while (p) { 661 if (!(p.cdr instanceof AST_Seq)) { 662 var cell = AST_Seq.cons(p.cdr, node); 663 return p.cdr = cell; 664 } 665 p = p.cdr; 666 } 667 }, 668 len: function() { 669 if (this.cdr instanceof AST_Seq) { 670 return this.cdr.len() + 1; 671 } else { 672 return 2; 673 } 674 }, 675 _walk: function(visitor) { 676 return visitor._visit(this, function(){ 677 this.car._walk(visitor); 678 if (this.cdr) this.cdr._walk(visitor); 679 }); 680 } 681}); 682 683var AST_PropAccess = DEFNODE("PropAccess", "expression property", { 684 $documentation: "Base class for property access expressions, i.e. `a.foo` or `a[\"foo\"]`", 685 $propdoc: { 686 expression: "[AST_Node] the “container” expression", 687 property: "[AST_Node|string] the property to access. For AST_Dot this is always a plain string, while for AST_Sub it's an arbitrary AST_Node" 688 } 689}); 690 691var AST_Dot = DEFNODE("Dot", null, { 692 $documentation: "A dotted property access expression", 693 _walk: function(visitor) { 694 return visitor._visit(this, function(){ 695 this.expression._walk(visitor); 696 }); 697 } 698}, AST_PropAccess); 699 700var AST_Sub = DEFNODE("Sub", null, { 701 $documentation: "Index-style property access, i.e. `a[\"foo\"]`", 702 _walk: function(visitor) { 703 return visitor._visit(this, function(){ 704 this.expression._walk(visitor); 705 this.property._walk(visitor); 706 }); 707 } 708}, AST_PropAccess); 709 710var AST_Unary = DEFNODE("Unary", "operator expression", { 711 $documentation: "Base class for unary expressions", 712 $propdoc: { 713 operator: "[string] the operator", 714 expression: "[AST_Node] expression that this unary operator applies to" 715 }, 716 _walk: function(visitor) { 717 return visitor._visit(this, function(){ 718 this.expression._walk(visitor); 719 }); 720 } 721}); 722 723var AST_UnaryPrefix = DEFNODE("UnaryPrefix", null, { 724 $documentation: "Unary prefix expression, i.e. `typeof i` or `++i`" 725}, AST_Unary); 726 727var AST_UnaryPostfix = DEFNODE("UnaryPostfix", null, { 728 $documentation: "Unary postfix expression, i.e. `i++`" 729}, AST_Unary); 730 731var AST_Binary = DEFNODE("Binary", "left operator right", { 732 $documentation: "Binary expression, i.e. `a + b`", 733 $propdoc: { 734 left: "[AST_Node] left-hand side expression", 735 operator: "[string] the operator", 736 right: "[AST_Node] right-hand side expression" 737 }, 738 _walk: function(visitor) { 739 return visitor._visit(this, function(){ 740 this.left._walk(visitor); 741 this.right._walk(visitor); 742 }); 743 } 744}); 745 746var AST_Conditional = DEFNODE("Conditional", "condition consequent alternative", { 747 $documentation: "Conditional expression using the ternary operator, i.e. `a ? b : c`", 748 $propdoc: { 749 condition: "[AST_Node]", 750 consequent: "[AST_Node]", 751 alternative: "[AST_Node]" 752 }, 753 _walk: function(visitor) { 754 return visitor._visit(this, function(){ 755 this.condition._walk(visitor); 756 this.consequent._walk(visitor); 757 this.alternative._walk(visitor); 758 }); 759 } 760}); 761 762var AST_Assign = DEFNODE("Assign", null, { 763 $documentation: "An assignment expression — `a = b + 5`", 764}, AST_Binary); 765 766/* -----[ LITERALS ]----- */ 767 768var AST_Array = DEFNODE("Array", "elements", { 769 $documentation: "An array literal", 770 $propdoc: { 771 elements: "[AST_Node*] array of elements" 772 }, 773 _walk: function(visitor) { 774 return visitor._visit(this, function(){ 775 var elements = this.elements; 776 for (var i = 0, len = elements.length; i < len; i++) { 777 elements[i]._walk(visitor); 778 } 779 }); 780 } 781}); 782 783var AST_Object = DEFNODE("Object", "properties", { 784 $documentation: "An object literal", 785 $propdoc: { 786 properties: "[AST_ObjectProperty*] array of properties" 787 }, 788 _walk: function(visitor) { 789 return visitor._visit(this, function(){ 790 var properties = this.properties; 791 for (var i = 0, len = properties.length; i < len; i++) { 792 properties[i]._walk(visitor); 793 } 794 }); 795 } 796}); 797 798var AST_ObjectProperty = DEFNODE("ObjectProperty", "key value", { 799 $documentation: "Base class for literal object properties", 800 $propdoc: { 801 key: "[string] the property name converted to a string for ObjectKeyVal. For setters and getters this is an AST_SymbolAccessor.", 802 value: "[AST_Node] property value. For setters and getters this is an AST_Accessor." 803 }, 804 _walk: function(visitor) { 805 return visitor._visit(this, function(){ 806 this.value._walk(visitor); 807 }); 808 } 809}); 810 811var AST_ObjectKeyVal = DEFNODE("ObjectKeyVal", "quote", { 812 $documentation: "A key: value object property", 813 $propdoc: { 814 quote: "[string] the original quote character" 815 } 816}, AST_ObjectProperty); 817 818var AST_ObjectSetter = DEFNODE("ObjectSetter", null, { 819 $documentation: "An object setter property", 820}, AST_ObjectProperty); 821 822var AST_ObjectGetter = DEFNODE("ObjectGetter", null, { 823 $documentation: "An object getter property", 824}, AST_ObjectProperty); 825 826var AST_Symbol = DEFNODE("Symbol", "scope name thedef", { 827 $propdoc: { 828 name: "[string] name of this symbol", 829 scope: "[AST_Scope/S] the current scope (not necessarily the definition scope)", 830 thedef: "[SymbolDef/S] the definition of this symbol" 831 }, 832 $documentation: "Base class for all symbols", 833}); 834 835var AST_SymbolAccessor = DEFNODE("SymbolAccessor", null, { 836 $documentation: "The name of a property accessor (setter/getter function)" 837}, AST_Symbol); 838 839var AST_SymbolDeclaration = DEFNODE("SymbolDeclaration", "init", { 840 $documentation: "A declaration symbol (symbol in var/const, function name or argument, symbol in catch)", 841}, AST_Symbol); 842 843var AST_SymbolVar = DEFNODE("SymbolVar", null, { 844 $documentation: "Symbol defining a variable", 845}, AST_SymbolDeclaration); 846 847var AST_SymbolConst = DEFNODE("SymbolConst", null, { 848 $documentation: "A constant declaration" 849}, AST_SymbolDeclaration); 850 851var AST_SymbolFunarg = DEFNODE("SymbolFunarg", null, { 852 $documentation: "Symbol naming a function argument", 853}, AST_SymbolVar); 854 855var AST_SymbolDefun = DEFNODE("SymbolDefun", null, { 856 $documentation: "Symbol defining a function", 857}, AST_SymbolDeclaration); 858 859var AST_SymbolLambda = DEFNODE("SymbolLambda", null, { 860 $documentation: "Symbol naming a function expression", 861}, AST_SymbolDeclaration); 862 863var AST_SymbolCatch = DEFNODE("SymbolCatch", null, { 864 $documentation: "Symbol naming the exception in catch", 865}, AST_SymbolDeclaration); 866 867var AST_Label = DEFNODE("Label", "references", { 868 $documentation: "Symbol naming a label (declaration)", 869 $propdoc: { 870 references: "[AST_LoopControl*] a list of nodes referring to this label" 871 }, 872 initialize: function() { 873 this.references = []; 874 this.thedef = this; 875 } 876}, AST_Symbol); 877 878var AST_SymbolRef = DEFNODE("SymbolRef", null, { 879 $documentation: "Reference to some symbol (not definition/declaration)", 880}, AST_Symbol); 881 882var AST_LabelRef = DEFNODE("LabelRef", null, { 883 $documentation: "Reference to a label symbol", 884}, AST_Symbol); 885 886var AST_This = DEFNODE("This", null, { 887 $documentation: "The `this` symbol", 888}, AST_Symbol); 889 890var AST_Constant = DEFNODE("Constant", null, { 891 $documentation: "Base class for all constants", 892 getValue: function() { 893 return this.value; 894 } 895}); 896 897var AST_String = DEFNODE("String", "value quote", { 898 $documentation: "A string literal", 899 $propdoc: { 900 value: "[string] the contents of this string", 901 quote: "[string] the original quote character" 902 } 903}, AST_Constant); 904 905var AST_Number = DEFNODE("Number", "value literal", { 906 $documentation: "A number literal", 907 $propdoc: { 908 value: "[number] the numeric value", 909 literal: "[string] numeric value as string (optional)" 910 } 911}, AST_Constant); 912 913var AST_RegExp = DEFNODE("RegExp", "value", { 914 $documentation: "A regexp literal", 915 $propdoc: { 916 value: "[RegExp] the actual regexp" 917 } 918}, AST_Constant); 919 920var AST_Atom = DEFNODE("Atom", null, { 921 $documentation: "Base class for atoms", 922}, AST_Constant); 923 924var AST_Null = DEFNODE("Null", null, { 925 $documentation: "The `null` atom", 926 value: null 927}, AST_Atom); 928 929var AST_NaN = DEFNODE("NaN", null, { 930 $documentation: "The impossible value", 931 value: 0/0 932}, AST_Atom); 933 934var AST_Undefined = DEFNODE("Undefined", null, { 935 $documentation: "The `undefined` value", 936 value: (function(){}()) 937}, AST_Atom); 938 939var AST_Hole = DEFNODE("Hole", null, { 940 $documentation: "A hole in an array", 941 value: (function(){}()) 942}, AST_Atom); 943 944var AST_Infinity = DEFNODE("Infinity", null, { 945 $documentation: "The `Infinity` value", 946 value: 1/0 947}, AST_Atom); 948 949var AST_Boolean = DEFNODE("Boolean", null, { 950 $documentation: "Base class for booleans", 951}, AST_Atom); 952 953var AST_False = DEFNODE("False", null, { 954 $documentation: "The `false` atom", 955 value: false 956}, AST_Boolean); 957 958var AST_True = DEFNODE("True", null, { 959 $documentation: "The `true` atom", 960 value: true 961}, AST_Boolean); 962 963/* -----[ TreeWalker ]----- */ 964 965function TreeWalker(callback) { 966 this.visit = callback; 967 this.stack = []; 968 this.directives = Object.create(null); 969}; 970TreeWalker.prototype = { 971 _visit: function(node, descend) { 972 this.push(node); 973 var ret = this.visit(node, descend ? function(){ 974 descend.call(node); 975 } : noop); 976 if (!ret && descend) { 977 descend.call(node); 978 } 979 this.pop(node); 980 return ret; 981 }, 982 parent: function(n) { 983 return this.stack[this.stack.length - 2 - (n || 0)]; 984 }, 985 push: function (node) { 986 if (node instanceof AST_Lambda) { 987 this.directives = Object.create(this.directives); 988 } else if (node instanceof AST_Directive && !this.directives[node.value]) { 989 this.directives[node.value] = node; 990 } 991 this.stack.push(node); 992 }, 993 pop: function(node) { 994 this.stack.pop(); 995 if (node instanceof AST_Lambda) { 996 this.directives = Object.getPrototypeOf(this.directives); 997 } 998 }, 999 self: function() { 1000 return this.stack[this.stack.length - 1]; 1001 }, 1002 find_parent: function(type) { 1003 var stack = this.stack; 1004 for (var i = stack.length; --i >= 0;) { 1005 var x = stack[i]; 1006 if (x instanceof type) return x; 1007 } 1008 }, 1009 has_directive: function(type) { 1010 var dir = this.directives[type]; 1011 if (dir) return dir; 1012 var node = this.stack[this.stack.length - 1]; 1013 if (node instanceof AST_Scope) { 1014 for (var i = 0; i < node.body.length; ++i) { 1015 var st = node.body[i]; 1016 if (!(st instanceof AST_Directive)) break; 1017 if (st.value == type) return st; 1018 } 1019 } 1020 }, 1021 in_boolean_context: function() { 1022 var stack = this.stack; 1023 var i = stack.length, self = stack[--i]; 1024 while (i > 0) { 1025 var p = stack[--i]; 1026 if ((p instanceof AST_If && p.condition === self) || 1027 (p instanceof AST_Conditional && p.condition === self) || 1028 (p instanceof AST_DWLoop && p.condition === self) || 1029 (p instanceof AST_For && p.condition === self) || 1030 (p instanceof AST_UnaryPrefix && p.operator == "!" && p.expression === self)) 1031 { 1032 return true; 1033 } 1034 if (!(p instanceof AST_Binary && (p.operator == "&&" || p.operator == "||"))) 1035 return false; 1036 self = p; 1037 } 1038 }, 1039 loopcontrol_target: function(node) { 1040 var stack = this.stack; 1041 if (node.label) for (var i = stack.length; --i >= 0;) { 1042 var x = stack[i]; 1043 if (x instanceof AST_LabeledStatement && x.label.name == node.label.name) 1044 return x.body; 1045 } else for (var i = stack.length; --i >= 0;) { 1046 var x = stack[i]; 1047 if (x instanceof AST_IterationStatement 1048 || node instanceof AST_Break && x instanceof AST_Switch) 1049 return x; 1050 } 1051 } 1052}; 1053