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