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