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