Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
80760 views
1
/***********************************************************************
2
3
A JavaScript tokenizer / parser / beautifier / compressor.
4
https://github.com/mishoo/UglifyJS2
5
6
-------------------------------- (C) ---------------------------------
7
8
Author: Mihai Bazon
9
<[email protected]>
10
http://mihai.bazon.net/blog
11
12
Distributed under the BSD license:
13
14
Copyright 2012 (c) Mihai Bazon <[email protected]>
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_before 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_Statement)"
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_DWLoop = DEFNODE("DWLoop", "condition", {
201
$documentation: "Base class for do/while statements",
202
$propdoc: {
203
condition: "[AST_Node] the loop condition. Should not be instanceof AST_Statement"
204
},
205
_walk: function(visitor) {
206
return visitor._visit(this, function(){
207
this.condition._walk(visitor);
208
this.body._walk(visitor);
209
});
210
}
211
}, AST_StatementWithBody);
212
213
var AST_Do = DEFNODE("Do", null, {
214
$documentation: "A `do` statement",
215
}, AST_DWLoop);
216
217
var AST_While = DEFNODE("While", null, {
218
$documentation: "A `while` statement",
219
}, AST_DWLoop);
220
221
var AST_For = DEFNODE("For", "init condition step", {
222
$documentation: "A `for` statement",
223
$propdoc: {
224
init: "[AST_Node?] the `for` initialization code, or null if empty",
225
condition: "[AST_Node?] the `for` termination clause, or null if empty",
226
step: "[AST_Node?] the `for` update clause, or null if empty"
227
},
228
_walk: function(visitor) {
229
return visitor._visit(this, function(){
230
if (this.init) this.init._walk(visitor);
231
if (this.condition) this.condition._walk(visitor);
232
if (this.step) this.step._walk(visitor);
233
this.body._walk(visitor);
234
});
235
}
236
}, AST_StatementWithBody);
237
238
var AST_ForIn = DEFNODE("ForIn", "init name object", {
239
$documentation: "A `for ... in` statement",
240
$propdoc: {
241
init: "[AST_Node] the `for/in` initialization code",
242
name: "[AST_SymbolRef?] the loop variable, only if `init` is AST_Var",
243
object: "[AST_Node] the object that we're looping through"
244
},
245
_walk: function(visitor) {
246
return visitor._visit(this, function(){
247
this.init._walk(visitor);
248
this.object._walk(visitor);
249
this.body._walk(visitor);
250
});
251
}
252
}, AST_StatementWithBody);
253
254
var AST_With = DEFNODE("With", "expression", {
255
$documentation: "A `with` statement",
256
$propdoc: {
257
expression: "[AST_Node] the `with` expression"
258
},
259
_walk: function(visitor) {
260
return visitor._visit(this, function(){
261
this.expression._walk(visitor);
262
this.body._walk(visitor);
263
});
264
}
265
}, AST_StatementWithBody);
266
267
/* -----[ scope and functions ]----- */
268
269
var AST_Scope = DEFNODE("Scope", "directives variables functions uses_with uses_eval parent_scope enclosed cname", {
270
$documentation: "Base class for all statements introducing a lexical scope",
271
$propdoc: {
272
directives: "[string*/S] an array of directives declared in this scope",
273
variables: "[Object/S] a map of name -> SymbolDef for all variables/functions defined in this scope",
274
functions: "[Object/S] like `variables`, but only lists function declarations",
275
uses_with: "[boolean/S] tells whether this scope uses the `with` statement",
276
uses_eval: "[boolean/S] tells whether this scope contains a direct call to the global `eval`",
277
parent_scope: "[AST_Scope?/S] link to the parent scope",
278
enclosed: "[SymbolDef*/S] a list of all symbol definitions that are accessed from this scope or any subscopes",
279
cname: "[integer/S] current index for mangling variables (used internally by the mangler)",
280
},
281
}, AST_Block);
282
283
var AST_Toplevel = DEFNODE("Toplevel", "globals", {
284
$documentation: "The toplevel scope",
285
$propdoc: {
286
globals: "[Object/S] a map of name -> SymbolDef for all undeclared names",
287
},
288
wrap_commonjs: function(name, export_all) {
289
var self = this;
290
var to_export = [];
291
if (export_all) {
292
self.figure_out_scope();
293
self.walk(new TreeWalker(function(node){
294
if (node instanceof AST_SymbolDeclaration && node.definition().global) {
295
if (!find_if(function(n){ return n.name == node.name }, to_export))
296
to_export.push(node);
297
}
298
}));
299
}
300
var wrapped_tl = "(function(exports, global){ global['" + name + "'] = exports; '$ORIG'; '$EXPORTS'; }({}, (function(){return this}())))";
301
wrapped_tl = parse(wrapped_tl);
302
wrapped_tl = wrapped_tl.transform(new TreeTransformer(function before(node){
303
if (node instanceof AST_SimpleStatement) {
304
node = node.body;
305
if (node instanceof AST_String) switch (node.getValue()) {
306
case "$ORIG":
307
return MAP.splice(self.body);
308
case "$EXPORTS":
309
var body = [];
310
to_export.forEach(function(sym){
311
body.push(new AST_SimpleStatement({
312
body: new AST_Assign({
313
left: new AST_Sub({
314
expression: new AST_SymbolRef({ name: "exports" }),
315
property: new AST_String({ value: sym.name }),
316
}),
317
operator: "=",
318
right: new AST_SymbolRef(sym),
319
}),
320
}));
321
});
322
return MAP.splice(body);
323
}
324
}
325
}));
326
return wrapped_tl;
327
}
328
}, AST_Scope);
329
330
var AST_Lambda = DEFNODE("Lambda", "name argnames uses_arguments", {
331
$documentation: "Base class for functions",
332
$propdoc: {
333
name: "[AST_SymbolDeclaration?] the name of this function",
334
argnames: "[AST_SymbolFunarg*] array of function arguments",
335
uses_arguments: "[boolean/S] tells whether this function accesses the arguments array"
336
},
337
_walk: function(visitor) {
338
return visitor._visit(this, function(){
339
if (this.name) this.name._walk(visitor);
340
this.argnames.forEach(function(arg){
341
arg._walk(visitor);
342
});
343
walk_body(this, visitor);
344
});
345
}
346
}, AST_Scope);
347
348
var AST_Accessor = DEFNODE("Accessor", null, {
349
$documentation: "A setter/getter function"
350
}, AST_Lambda);
351
352
var AST_Function = DEFNODE("Function", null, {
353
$documentation: "A function expression"
354
}, AST_Lambda);
355
356
var AST_Defun = DEFNODE("Defun", null, {
357
$documentation: "A function definition"
358
}, AST_Lambda);
359
360
/* -----[ JUMPS ]----- */
361
362
var AST_Jump = DEFNODE("Jump", null, {
363
$documentation: "Base class for “jumps” (for now that's `return`, `throw`, `break` and `continue`)"
364
}, AST_Statement);
365
366
var AST_Exit = DEFNODE("Exit", "value", {
367
$documentation: "Base class for “exits” (`return` and `throw`)",
368
$propdoc: {
369
value: "[AST_Node?] the value returned or thrown by this statement; could be null for AST_Return"
370
},
371
_walk: function(visitor) {
372
return visitor._visit(this, this.value && function(){
373
this.value._walk(visitor);
374
});
375
}
376
}, AST_Jump);
377
378
var AST_Return = DEFNODE("Return", null, {
379
$documentation: "A `return` statement"
380
}, AST_Exit);
381
382
var AST_Throw = DEFNODE("Throw", null, {
383
$documentation: "A `throw` statement"
384
}, AST_Exit);
385
386
var AST_LoopControl = DEFNODE("LoopControl", "label", {
387
$documentation: "Base class for loop control statements (`break` and `continue`)",
388
$propdoc: {
389
label: "[AST_LabelRef?] the label, or null if none",
390
},
391
_walk: function(visitor) {
392
return visitor._visit(this, this.label && function(){
393
this.label._walk(visitor);
394
});
395
}
396
}, AST_Jump);
397
398
var AST_Break = DEFNODE("Break", null, {
399
$documentation: "A `break` statement"
400
}, AST_LoopControl);
401
402
var AST_Continue = DEFNODE("Continue", null, {
403
$documentation: "A `continue` statement"
404
}, AST_LoopControl);
405
406
/* -----[ IF ]----- */
407
408
var AST_If = DEFNODE("If", "condition alternative", {
409
$documentation: "A `if` statement",
410
$propdoc: {
411
condition: "[AST_Node] the `if` condition",
412
alternative: "[AST_Statement?] the `else` part, or null if not present"
413
},
414
_walk: function(visitor) {
415
return visitor._visit(this, function(){
416
this.condition._walk(visitor);
417
this.body._walk(visitor);
418
if (this.alternative) this.alternative._walk(visitor);
419
});
420
}
421
}, AST_StatementWithBody);
422
423
/* -----[ SWITCH ]----- */
424
425
var AST_Switch = DEFNODE("Switch", "expression", {
426
$documentation: "A `switch` statement",
427
$propdoc: {
428
expression: "[AST_Node] the `switch` “discriminant”"
429
},
430
_walk: function(visitor) {
431
return visitor._visit(this, function(){
432
this.expression._walk(visitor);
433
walk_body(this, visitor);
434
});
435
}
436
}, AST_Block);
437
438
var AST_SwitchBranch = DEFNODE("SwitchBranch", null, {
439
$documentation: "Base class for `switch` branches",
440
}, AST_Block);
441
442
var AST_Default = DEFNODE("Default", null, {
443
$documentation: "A `default` switch branch",
444
}, AST_SwitchBranch);
445
446
var AST_Case = DEFNODE("Case", "expression", {
447
$documentation: "A `case` switch branch",
448
$propdoc: {
449
expression: "[AST_Node] the `case` expression"
450
},
451
_walk: function(visitor) {
452
return visitor._visit(this, function(){
453
this.expression._walk(visitor);
454
walk_body(this, visitor);
455
});
456
}
457
}, AST_SwitchBranch);
458
459
/* -----[ EXCEPTIONS ]----- */
460
461
var AST_Try = DEFNODE("Try", "bcatch bfinally", {
462
$documentation: "A `try` statement",
463
$propdoc: {
464
bcatch: "[AST_Catch?] the catch block, or null if not present",
465
bfinally: "[AST_Finally?] the finally block, or null if not present"
466
},
467
_walk: function(visitor) {
468
return visitor._visit(this, function(){
469
walk_body(this, visitor);
470
if (this.bcatch) this.bcatch._walk(visitor);
471
if (this.bfinally) this.bfinally._walk(visitor);
472
});
473
}
474
}, AST_Block);
475
476
// XXX: this is wrong according to ECMA-262 (12.4). the catch block
477
// should introduce another scope, as the argname should be visible
478
// only inside the catch block. However, doing it this way because of
479
// IE which simply introduces the name in the surrounding scope. If
480
// we ever want to fix this then AST_Catch should inherit from
481
// AST_Scope.
482
var AST_Catch = DEFNODE("Catch", "argname", {
483
$documentation: "A `catch` node; only makes sense as part of a `try` statement",
484
$propdoc: {
485
argname: "[AST_SymbolCatch] symbol for the exception"
486
},
487
_walk: function(visitor) {
488
return visitor._visit(this, function(){
489
this.argname._walk(visitor);
490
walk_body(this, visitor);
491
});
492
}
493
}, AST_Block);
494
495
var AST_Finally = DEFNODE("Finally", null, {
496
$documentation: "A `finally` node; only makes sense as part of a `try` statement"
497
}, AST_Block);
498
499
/* -----[ VAR/CONST ]----- */
500
501
var AST_Definitions = DEFNODE("Definitions", "definitions", {
502
$documentation: "Base class for `var` or `const` nodes (variable declarations/initializations)",
503
$propdoc: {
504
definitions: "[AST_VarDef*] array of variable definitions"
505
},
506
_walk: function(visitor) {
507
return visitor._visit(this, function(){
508
this.definitions.forEach(function(def){
509
def._walk(visitor);
510
});
511
});
512
}
513
}, AST_Statement);
514
515
var AST_Var = DEFNODE("Var", null, {
516
$documentation: "A `var` statement"
517
}, AST_Definitions);
518
519
var AST_Const = DEFNODE("Const", null, {
520
$documentation: "A `const` statement"
521
}, AST_Definitions);
522
523
var AST_VarDef = DEFNODE("VarDef", "name value", {
524
$documentation: "A variable declaration; only appears in a AST_Definitions node",
525
$propdoc: {
526
name: "[AST_SymbolVar|AST_SymbolConst] name of the variable",
527
value: "[AST_Node?] initializer, or null of there's no initializer"
528
},
529
_walk: function(visitor) {
530
return visitor._visit(this, function(){
531
this.name._walk(visitor);
532
if (this.value) this.value._walk(visitor);
533
});
534
}
535
});
536
537
/* -----[ OTHER ]----- */
538
539
var AST_Call = DEFNODE("Call", "expression args", {
540
$documentation: "A function call expression",
541
$propdoc: {
542
expression: "[AST_Node] expression to invoke as function",
543
args: "[AST_Node*] array of arguments"
544
},
545
_walk: function(visitor) {
546
return visitor._visit(this, function(){
547
this.expression._walk(visitor);
548
this.args.forEach(function(arg){
549
arg._walk(visitor);
550
});
551
});
552
}
553
});
554
555
var AST_New = DEFNODE("New", null, {
556
$documentation: "An object instantiation. Derives from a function call since it has exactly the same properties"
557
}, AST_Call);
558
559
var AST_Seq = DEFNODE("Seq", "car cdr", {
560
$documentation: "A sequence expression (two comma-separated expressions)",
561
$propdoc: {
562
car: "[AST_Node] first element in sequence",
563
cdr: "[AST_Node] second element in sequence"
564
},
565
$cons: function(x, y) {
566
var seq = new AST_Seq(x);
567
seq.car = x;
568
seq.cdr = y;
569
return seq;
570
},
571
$from_array: function(array) {
572
if (array.length == 0) return null;
573
if (array.length == 1) return array[0].clone();
574
var list = null;
575
for (var i = array.length; --i >= 0;) {
576
list = AST_Seq.cons(array[i], list);
577
}
578
var p = list;
579
while (p) {
580
if (p.cdr && !p.cdr.cdr) {
581
p.cdr = p.cdr.car;
582
break;
583
}
584
p = p.cdr;
585
}
586
return list;
587
},
588
to_array: function() {
589
var p = this, a = [];
590
while (p) {
591
a.push(p.car);
592
if (p.cdr && !(p.cdr instanceof AST_Seq)) {
593
a.push(p.cdr);
594
break;
595
}
596
p = p.cdr;
597
}
598
return a;
599
},
600
add: function(node) {
601
var p = this;
602
while (p) {
603
if (!(p.cdr instanceof AST_Seq)) {
604
var cell = AST_Seq.cons(p.cdr, node);
605
return p.cdr = cell;
606
}
607
p = p.cdr;
608
}
609
},
610
_walk: function(visitor) {
611
return visitor._visit(this, function(){
612
this.car._walk(visitor);
613
if (this.cdr) this.cdr._walk(visitor);
614
});
615
}
616
});
617
618
var AST_PropAccess = DEFNODE("PropAccess", "expression property", {
619
$documentation: "Base class for property access expressions, i.e. `a.foo` or `a[\"foo\"]`",
620
$propdoc: {
621
expression: "[AST_Node] the “container” expression",
622
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"
623
}
624
});
625
626
var AST_Dot = DEFNODE("Dot", null, {
627
$documentation: "A dotted property access expression",
628
_walk: function(visitor) {
629
return visitor._visit(this, function(){
630
this.expression._walk(visitor);
631
});
632
}
633
}, AST_PropAccess);
634
635
var AST_Sub = DEFNODE("Sub", null, {
636
$documentation: "Index-style property access, i.e. `a[\"foo\"]`",
637
_walk: function(visitor) {
638
return visitor._visit(this, function(){
639
this.expression._walk(visitor);
640
this.property._walk(visitor);
641
});
642
}
643
}, AST_PropAccess);
644
645
var AST_Unary = DEFNODE("Unary", "operator expression", {
646
$documentation: "Base class for unary expressions",
647
$propdoc: {
648
operator: "[string] the operator",
649
expression: "[AST_Node] expression that this unary operator applies to"
650
},
651
_walk: function(visitor) {
652
return visitor._visit(this, function(){
653
this.expression._walk(visitor);
654
});
655
}
656
});
657
658
var AST_UnaryPrefix = DEFNODE("UnaryPrefix", null, {
659
$documentation: "Unary prefix expression, i.e. `typeof i` or `++i`"
660
}, AST_Unary);
661
662
var AST_UnaryPostfix = DEFNODE("UnaryPostfix", null, {
663
$documentation: "Unary postfix expression, i.e. `i++`"
664
}, AST_Unary);
665
666
var AST_Binary = DEFNODE("Binary", "left operator right", {
667
$documentation: "Binary expression, i.e. `a + b`",
668
$propdoc: {
669
left: "[AST_Node] left-hand side expression",
670
operator: "[string] the operator",
671
right: "[AST_Node] right-hand side expression"
672
},
673
_walk: function(visitor) {
674
return visitor._visit(this, function(){
675
this.left._walk(visitor);
676
this.right._walk(visitor);
677
});
678
}
679
});
680
681
var AST_Conditional = DEFNODE("Conditional", "condition consequent alternative", {
682
$documentation: "Conditional expression using the ternary operator, i.e. `a ? b : c`",
683
$propdoc: {
684
condition: "[AST_Node]",
685
consequent: "[AST_Node]",
686
alternative: "[AST_Node]"
687
},
688
_walk: function(visitor) {
689
return visitor._visit(this, function(){
690
this.condition._walk(visitor);
691
this.consequent._walk(visitor);
692
this.alternative._walk(visitor);
693
});
694
}
695
});
696
697
var AST_Assign = DEFNODE("Assign", null, {
698
$documentation: "An assignment expression — `a = b + 5`",
699
}, AST_Binary);
700
701
/* -----[ LITERALS ]----- */
702
703
var AST_Array = DEFNODE("Array", "elements", {
704
$documentation: "An array literal",
705
$propdoc: {
706
elements: "[AST_Node*] array of elements"
707
},
708
_walk: function(visitor) {
709
return visitor._visit(this, function(){
710
this.elements.forEach(function(el){
711
el._walk(visitor);
712
});
713
});
714
}
715
});
716
717
var AST_Object = DEFNODE("Object", "properties", {
718
$documentation: "An object literal",
719
$propdoc: {
720
properties: "[AST_ObjectProperty*] array of properties"
721
},
722
_walk: function(visitor) {
723
return visitor._visit(this, function(){
724
this.properties.forEach(function(prop){
725
prop._walk(visitor);
726
});
727
});
728
}
729
});
730
731
var AST_ObjectProperty = DEFNODE("ObjectProperty", "key value", {
732
$documentation: "Base class for literal object properties",
733
$propdoc: {
734
key: "[string] the property name; it's always a plain string in our AST, no matter if it was a string, number or identifier in original code",
735
value: "[AST_Node] property value. For setters and getters this is an AST_Function."
736
},
737
_walk: function(visitor) {
738
return visitor._visit(this, function(){
739
this.value._walk(visitor);
740
});
741
}
742
});
743
744
var AST_ObjectKeyVal = DEFNODE("ObjectKeyVal", null, {
745
$documentation: "A key: value object property",
746
}, AST_ObjectProperty);
747
748
var AST_ObjectSetter = DEFNODE("ObjectSetter", null, {
749
$documentation: "An object setter property",
750
}, AST_ObjectProperty);
751
752
var AST_ObjectGetter = DEFNODE("ObjectGetter", null, {
753
$documentation: "An object getter property",
754
}, AST_ObjectProperty);
755
756
var AST_Symbol = DEFNODE("Symbol", "scope name thedef", {
757
$propdoc: {
758
name: "[string] name of this symbol",
759
scope: "[AST_Scope/S] the current scope (not necessarily the definition scope)",
760
thedef: "[SymbolDef/S] the definition of this symbol"
761
},
762
$documentation: "Base class for all symbols",
763
});
764
765
var AST_SymbolAccessor = DEFNODE("SymbolAccessor", null, {
766
$documentation: "The name of a property accessor (setter/getter function)"
767
}, AST_Symbol);
768
769
var AST_SymbolDeclaration = DEFNODE("SymbolDeclaration", "init", {
770
$documentation: "A declaration symbol (symbol in var/const, function name or argument, symbol in catch)",
771
$propdoc: {
772
init: "[AST_Node*/S] array of initializers for this declaration."
773
}
774
}, AST_Symbol);
775
776
var AST_SymbolVar = DEFNODE("SymbolVar", null, {
777
$documentation: "Symbol defining a variable",
778
}, AST_SymbolDeclaration);
779
780
var AST_SymbolConst = DEFNODE("SymbolConst", null, {
781
$documentation: "A constant declaration"
782
}, AST_SymbolDeclaration);
783
784
var AST_SymbolFunarg = DEFNODE("SymbolFunarg", null, {
785
$documentation: "Symbol naming a function argument",
786
}, AST_SymbolVar);
787
788
var AST_SymbolDefun = DEFNODE("SymbolDefun", null, {
789
$documentation: "Symbol defining a function",
790
}, AST_SymbolDeclaration);
791
792
var AST_SymbolLambda = DEFNODE("SymbolLambda", null, {
793
$documentation: "Symbol naming a function expression",
794
}, AST_SymbolDeclaration);
795
796
var AST_SymbolCatch = DEFNODE("SymbolCatch", null, {
797
$documentation: "Symbol naming the exception in catch",
798
}, AST_SymbolDeclaration);
799
800
var AST_Label = DEFNODE("Label", "references", {
801
$documentation: "Symbol naming a label (declaration)",
802
$propdoc: {
803
references: "[AST_LabelRef*] a list of nodes referring to this label"
804
}
805
}, AST_Symbol);
806
807
var AST_SymbolRef = DEFNODE("SymbolRef", null, {
808
$documentation: "Reference to some symbol (not definition/declaration)",
809
}, AST_Symbol);
810
811
var AST_LabelRef = DEFNODE("LabelRef", null, {
812
$documentation: "Reference to a label symbol",
813
}, AST_Symbol);
814
815
var AST_This = DEFNODE("This", null, {
816
$documentation: "The `this` symbol",
817
}, AST_Symbol);
818
819
var AST_Constant = DEFNODE("Constant", null, {
820
$documentation: "Base class for all constants",
821
getValue: function() {
822
return this.value;
823
}
824
});
825
826
var AST_String = DEFNODE("String", "value", {
827
$documentation: "A string literal",
828
$propdoc: {
829
value: "[string] the contents of this string"
830
}
831
}, AST_Constant);
832
833
var AST_Number = DEFNODE("Number", "value", {
834
$documentation: "A number literal",
835
$propdoc: {
836
value: "[number] the numeric value"
837
}
838
}, AST_Constant);
839
840
var AST_RegExp = DEFNODE("RegExp", "value", {
841
$documentation: "A regexp literal",
842
$propdoc: {
843
value: "[RegExp] the actual regexp"
844
}
845
}, AST_Constant);
846
847
var AST_Atom = DEFNODE("Atom", null, {
848
$documentation: "Base class for atoms",
849
}, AST_Constant);
850
851
var AST_Null = DEFNODE("Null", null, {
852
$documentation: "The `null` atom",
853
value: null
854
}, AST_Atom);
855
856
var AST_NaN = DEFNODE("NaN", null, {
857
$documentation: "The impossible value",
858
value: 0/0
859
}, AST_Atom);
860
861
var AST_Undefined = DEFNODE("Undefined", null, {
862
$documentation: "The `undefined` value",
863
value: (function(){}())
864
}, AST_Atom);
865
866
var AST_Hole = DEFNODE("Hole", null, {
867
$documentation: "A hole in an array",
868
value: (function(){}())
869
}, AST_Atom);
870
871
var AST_Infinity = DEFNODE("Infinity", null, {
872
$documentation: "The `Infinity` value",
873
value: 1/0
874
}, AST_Atom);
875
876
var AST_Boolean = DEFNODE("Boolean", null, {
877
$documentation: "Base class for booleans",
878
}, AST_Atom);
879
880
var AST_False = DEFNODE("False", null, {
881
$documentation: "The `false` atom",
882
value: false
883
}, AST_Boolean);
884
885
var AST_True = DEFNODE("True", null, {
886
$documentation: "The `true` atom",
887
value: true
888
}, AST_Boolean);
889
890
/* -----[ TreeWalker ]----- */
891
892
function TreeWalker(callback) {
893
this.visit = callback;
894
this.stack = [];
895
};
896
TreeWalker.prototype = {
897
_visit: function(node, descend) {
898
this.stack.push(node);
899
var ret = this.visit(node, descend ? function(){
900
descend.call(node);
901
} : noop);
902
if (!ret && descend) {
903
descend.call(node);
904
}
905
this.stack.pop();
906
return ret;
907
},
908
parent: function(n) {
909
return this.stack[this.stack.length - 2 - (n || 0)];
910
},
911
push: function (node) {
912
this.stack.push(node);
913
},
914
pop: function() {
915
return this.stack.pop();
916
},
917
self: function() {
918
return this.stack[this.stack.length - 1];
919
},
920
find_parent: function(type) {
921
var stack = this.stack;
922
for (var i = stack.length; --i >= 0;) {
923
var x = stack[i];
924
if (x instanceof type) return x;
925
}
926
},
927
in_boolean_context: function() {
928
var stack = this.stack;
929
var i = stack.length, self = stack[--i];
930
while (i > 0) {
931
var p = stack[--i];
932
if ((p instanceof AST_If && p.condition === self) ||
933
(p instanceof AST_Conditional && p.condition === self) ||
934
(p instanceof AST_DWLoop && p.condition === self) ||
935
(p instanceof AST_For && p.condition === self) ||
936
(p instanceof AST_UnaryPrefix && p.operator == "!" && p.expression === self))
937
{
938
return true;
939
}
940
if (!(p instanceof AST_Binary && (p.operator == "&&" || p.operator == "||")))
941
return false;
942
self = p;
943
}
944
},
945
loopcontrol_target: function(label) {
946
var stack = this.stack;
947
if (label) {
948
for (var i = stack.length; --i >= 0;) {
949
var x = stack[i];
950
if (x instanceof AST_LabeledStatement && x.label.name == label.name) {
951
return x.body;
952
}
953
}
954
} else {
955
for (var i = stack.length; --i >= 0;) {
956
var x = stack[i];
957
if (x instanceof AST_Switch
958
|| x instanceof AST_For
959
|| x instanceof AST_ForIn
960
|| x instanceof AST_DWLoop) return x;
961
}
962
}
963
}
964
};
965
966