UNPKG

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