Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
80713 views
1
import { COMPILER_REVISION, REVISION_CHANGES } from "../base";
2
import Exception from "../exception";
3
import {isArray} from "../utils";
4
import CodeGen from "./code-gen";
5
6
function Literal(value) {
7
this.value = value;
8
}
9
10
function JavaScriptCompiler() {}
11
12
JavaScriptCompiler.prototype = {
13
// PUBLIC API: You can override these methods in a subclass to provide
14
// alternative compiled forms for name lookup and buffering semantics
15
nameLookup: function(parent, name /* , type*/) {
16
if (JavaScriptCompiler.isValidJavaScriptVariableName(name)) {
17
return [parent, ".", name];
18
} else {
19
return [parent, "['", name, "']"];
20
}
21
},
22
depthedLookup: function(name) {
23
return [this.aliasable('this.lookup'), '(depths, "', name, '")'];
24
},
25
26
compilerInfo: function() {
27
var revision = COMPILER_REVISION,
28
versions = REVISION_CHANGES[revision];
29
return [revision, versions];
30
},
31
32
appendToBuffer: function(source, location, explicit) {
33
// Force a source as this simplifies the merge logic.
34
if (!isArray(source)) {
35
source = [source];
36
}
37
source = this.source.wrap(source, location);
38
39
if (this.environment.isSimple) {
40
return ['return ', source, ';'];
41
} else if (explicit) {
42
// This is a case where the buffer operation occurs as a child of another
43
// construct, generally braces. We have to explicitly output these buffer
44
// operations to ensure that the emitted code goes in the correct location.
45
return ['buffer += ', source, ';'];
46
} else {
47
source.appendToBuffer = true;
48
return source;
49
}
50
},
51
52
initializeBuffer: function() {
53
return this.quotedString("");
54
},
55
// END PUBLIC API
56
57
compile: function(environment, options, context, asObject) {
58
this.environment = environment;
59
this.options = options;
60
this.stringParams = this.options.stringParams;
61
this.trackIds = this.options.trackIds;
62
this.precompile = !asObject;
63
64
this.name = this.environment.name;
65
this.isChild = !!context;
66
this.context = context || {
67
programs: [],
68
environments: []
69
};
70
71
this.preamble();
72
73
this.stackSlot = 0;
74
this.stackVars = [];
75
this.aliases = {};
76
this.registers = { list: [] };
77
this.hashes = [];
78
this.compileStack = [];
79
this.inlineStack = [];
80
this.blockParams = [];
81
82
this.compileChildren(environment, options);
83
84
this.useDepths = this.useDepths || environment.useDepths || this.options.compat;
85
this.useBlockParams = this.useBlockParams || environment.useBlockParams;
86
87
var opcodes = environment.opcodes,
88
opcode,
89
firstLoc,
90
i,
91
l;
92
93
for (i = 0, l = opcodes.length; i < l; i++) {
94
opcode = opcodes[i];
95
96
this.source.currentLocation = opcode.loc;
97
firstLoc = firstLoc || opcode.loc;
98
this[opcode.opcode].apply(this, opcode.args);
99
}
100
101
// Flush any trailing content that might be pending.
102
this.source.currentLocation = firstLoc;
103
this.pushSource('');
104
105
/* istanbul ignore next */
106
if (this.stackSlot || this.inlineStack.length || this.compileStack.length) {
107
throw new Exception('Compile completed with content left on stack');
108
}
109
110
var fn = this.createFunctionContext(asObject);
111
if (!this.isChild) {
112
var ret = {
113
compiler: this.compilerInfo(),
114
main: fn
115
};
116
var programs = this.context.programs;
117
for (i = 0, l = programs.length; i < l; i++) {
118
if (programs[i]) {
119
ret[i] = programs[i];
120
}
121
}
122
123
if (this.environment.usePartial) {
124
ret.usePartial = true;
125
}
126
if (this.options.data) {
127
ret.useData = true;
128
}
129
if (this.useDepths) {
130
ret.useDepths = true;
131
}
132
if (this.useBlockParams) {
133
ret.useBlockParams = true;
134
}
135
if (this.options.compat) {
136
ret.compat = true;
137
}
138
139
if (!asObject) {
140
ret.compiler = JSON.stringify(ret.compiler);
141
142
this.source.currentLocation = {start: {line: 1, column: 0}};
143
ret = this.objectLiteral(ret);
144
145
if (options.srcName) {
146
ret = ret.toStringWithSourceMap({file: options.destName});
147
ret.map = ret.map && ret.map.toString();
148
} else {
149
ret = ret.toString();
150
}
151
} else {
152
ret.compilerOptions = this.options;
153
}
154
155
return ret;
156
} else {
157
return fn;
158
}
159
},
160
161
preamble: function() {
162
// track the last context pushed into place to allow skipping the
163
// getContext opcode when it would be a noop
164
this.lastContext = 0;
165
this.source = new CodeGen(this.options.srcName);
166
},
167
168
createFunctionContext: function(asObject) {
169
var varDeclarations = '';
170
171
var locals = this.stackVars.concat(this.registers.list);
172
if(locals.length > 0) {
173
varDeclarations += ", " + locals.join(", ");
174
}
175
176
// Generate minimizer alias mappings
177
//
178
// When using true SourceNodes, this will update all references to the given alias
179
// as the source nodes are reused in situ. For the non-source node compilation mode,
180
// aliases will not be used, but this case is already being run on the client and
181
// we aren't concern about minimizing the template size.
182
var aliasCount = 0;
183
for (var alias in this.aliases) {
184
var node = this.aliases[alias];
185
186
if (this.aliases.hasOwnProperty(alias) && node.children && node.referenceCount > 1) {
187
varDeclarations += ', alias' + (++aliasCount) + '=' + alias;
188
node.children[0] = 'alias' + aliasCount;
189
}
190
}
191
192
var params = ["depth0", "helpers", "partials", "data"];
193
194
if (this.useBlockParams || this.useDepths) {
195
params.push('blockParams');
196
}
197
if (this.useDepths) {
198
params.push('depths');
199
}
200
201
// Perform a second pass over the output to merge content when possible
202
var source = this.mergeSource(varDeclarations);
203
204
if (asObject) {
205
params.push(source);
206
207
return Function.apply(this, params);
208
} else {
209
return this.source.wrap(['function(', params.join(','), ') {\n ', source, '}']);
210
}
211
},
212
mergeSource: function(varDeclarations) {
213
var isSimple = this.environment.isSimple,
214
appendOnly = !this.forceBuffer,
215
appendFirst,
216
217
sourceSeen,
218
bufferStart,
219
bufferEnd;
220
this.source.each(function(line) {
221
if (line.appendToBuffer) {
222
if (bufferStart) {
223
line.prepend(' + ');
224
} else {
225
bufferStart = line;
226
}
227
bufferEnd = line;
228
} else {
229
if (bufferStart) {
230
if (!sourceSeen) {
231
appendFirst = true;
232
} else {
233
bufferStart.prepend('buffer += ');
234
}
235
bufferEnd.add(';');
236
bufferStart = bufferEnd = undefined;
237
}
238
239
sourceSeen = true;
240
if (!isSimple) {
241
appendOnly = false;
242
}
243
}
244
});
245
246
247
if (appendOnly) {
248
if (bufferStart) {
249
bufferStart.prepend('return ');
250
bufferEnd.add(';');
251
} else if (!sourceSeen) {
252
this.source.push('return "";');
253
}
254
} else {
255
varDeclarations += ", buffer = " + (appendFirst ? '' : this.initializeBuffer());
256
257
if (bufferStart) {
258
bufferStart.prepend('return buffer + ');
259
bufferEnd.add(';');
260
} else {
261
this.source.push('return buffer;');
262
}
263
}
264
265
if (varDeclarations) {
266
this.source.prepend('var ' + varDeclarations.substring(2) + (appendFirst ? '' : ';\n'));
267
}
268
269
return this.source.merge();
270
},
271
272
// [blockValue]
273
//
274
// On stack, before: hash, inverse, program, value
275
// On stack, after: return value of blockHelperMissing
276
//
277
// The purpose of this opcode is to take a block of the form
278
// `{{#this.foo}}...{{/this.foo}}`, resolve the value of `foo`, and
279
// replace it on the stack with the result of properly
280
// invoking blockHelperMissing.
281
blockValue: function(name) {
282
var blockHelperMissing = this.aliasable('helpers.blockHelperMissing'),
283
params = [this.contextName(0)];
284
this.setupHelperArgs(name, 0, params);
285
286
var blockName = this.popStack();
287
params.splice(1, 0, blockName);
288
289
this.push(this.source.functionCall(blockHelperMissing, 'call', params));
290
},
291
292
// [ambiguousBlockValue]
293
//
294
// On stack, before: hash, inverse, program, value
295
// Compiler value, before: lastHelper=value of last found helper, if any
296
// On stack, after, if no lastHelper: same as [blockValue]
297
// On stack, after, if lastHelper: value
298
ambiguousBlockValue: function() {
299
// We're being a bit cheeky and reusing the options value from the prior exec
300
var blockHelperMissing = this.aliasable('helpers.blockHelperMissing'),
301
params = [this.contextName(0)];
302
this.setupHelperArgs('', 0, params, true);
303
304
this.flushInline();
305
306
var current = this.topStack();
307
params.splice(1, 0, current);
308
309
this.pushSource([
310
'if (!', this.lastHelper, ') { ',
311
current, ' = ', this.source.functionCall(blockHelperMissing, 'call', params),
312
'}']);
313
},
314
315
// [appendContent]
316
//
317
// On stack, before: ...
318
// On stack, after: ...
319
//
320
// Appends the string value of `content` to the current buffer
321
appendContent: function(content) {
322
if (this.pendingContent) {
323
content = this.pendingContent + content;
324
} else {
325
this.pendingLocation = this.source.currentLocation;
326
}
327
328
this.pendingContent = content;
329
},
330
331
// [append]
332
//
333
// On stack, before: value, ...
334
// On stack, after: ...
335
//
336
// Coerces `value` to a String and appends it to the current buffer.
337
//
338
// If `value` is truthy, or 0, it is coerced into a string and appended
339
// Otherwise, the empty string is appended
340
append: function() {
341
if (this.isInline()) {
342
this.replaceStack(function(current) {
343
return [' != null ? ', current, ' : ""'];
344
});
345
346
this.pushSource(this.appendToBuffer(this.popStack()));
347
} else {
348
var local = this.popStack();
349
this.pushSource(['if (', local, ' != null) { ', this.appendToBuffer(local, undefined, true), ' }']);
350
if (this.environment.isSimple) {
351
this.pushSource(['else { ', this.appendToBuffer("''", undefined, true), ' }']);
352
}
353
}
354
},
355
356
// [appendEscaped]
357
//
358
// On stack, before: value, ...
359
// On stack, after: ...
360
//
361
// Escape `value` and append it to the buffer
362
appendEscaped: function() {
363
this.pushSource(this.appendToBuffer(
364
[this.aliasable('this.escapeExpression'), '(', this.popStack(), ')']));
365
},
366
367
// [getContext]
368
//
369
// On stack, before: ...
370
// On stack, after: ...
371
// Compiler value, after: lastContext=depth
372
//
373
// Set the value of the `lastContext` compiler value to the depth
374
getContext: function(depth) {
375
this.lastContext = depth;
376
},
377
378
// [pushContext]
379
//
380
// On stack, before: ...
381
// On stack, after: currentContext, ...
382
//
383
// Pushes the value of the current context onto the stack.
384
pushContext: function() {
385
this.pushStackLiteral(this.contextName(this.lastContext));
386
},
387
388
// [lookupOnContext]
389
//
390
// On stack, before: ...
391
// On stack, after: currentContext[name], ...
392
//
393
// Looks up the value of `name` on the current context and pushes
394
// it onto the stack.
395
lookupOnContext: function(parts, falsy, scoped) {
396
var i = 0;
397
398
if (!scoped && this.options.compat && !this.lastContext) {
399
// The depthed query is expected to handle the undefined logic for the root level that
400
// is implemented below, so we evaluate that directly in compat mode
401
this.push(this.depthedLookup(parts[i++]));
402
} else {
403
this.pushContext();
404
}
405
406
this.resolvePath('context', parts, i, falsy);
407
},
408
409
// [lookupBlockParam]
410
//
411
// On stack, before: ...
412
// On stack, after: blockParam[name], ...
413
//
414
// Looks up the value of `parts` on the given block param and pushes
415
// it onto the stack.
416
lookupBlockParam: function(blockParamId, parts) {
417
this.useBlockParams = true;
418
419
this.push(['blockParams[', blockParamId[0], '][', blockParamId[1], ']']);
420
this.resolvePath('context', parts, 1);
421
},
422
423
// [lookupData]
424
//
425
// On stack, before: ...
426
// On stack, after: data, ...
427
//
428
// Push the data lookup operator
429
lookupData: function(depth, parts) {
430
/*jshint -W083 */
431
if (!depth) {
432
this.pushStackLiteral('data');
433
} else {
434
this.pushStackLiteral('this.data(data, ' + depth + ')');
435
}
436
437
this.resolvePath('data', parts, 0, true);
438
},
439
440
resolvePath: function(type, parts, i, falsy) {
441
/*jshint -W083 */
442
if (this.options.strict || this.options.assumeObjects) {
443
this.push(strictLookup(this.options.strict, this, parts, type));
444
return;
445
}
446
447
var len = parts.length;
448
for (; i < len; i++) {
449
this.replaceStack(function(current) {
450
var lookup = this.nameLookup(current, parts[i], type);
451
// We want to ensure that zero and false are handled properly if the context (falsy flag)
452
// needs to have the special handling for these values.
453
if (!falsy) {
454
return [' != null ? ', lookup, ' : ', current];
455
} else {
456
// Otherwise we can use generic falsy handling
457
return [' && ', lookup];
458
}
459
});
460
}
461
},
462
463
// [resolvePossibleLambda]
464
//
465
// On stack, before: value, ...
466
// On stack, after: resolved value, ...
467
//
468
// If the `value` is a lambda, replace it on the stack by
469
// the return value of the lambda
470
resolvePossibleLambda: function() {
471
this.push([this.aliasable('this.lambda'), '(', this.popStack(), ', ', this.contextName(0), ')']);
472
},
473
474
// [pushStringParam]
475
//
476
// On stack, before: ...
477
// On stack, after: string, currentContext, ...
478
//
479
// This opcode is designed for use in string mode, which
480
// provides the string value of a parameter along with its
481
// depth rather than resolving it immediately.
482
pushStringParam: function(string, type) {
483
this.pushContext();
484
this.pushString(type);
485
486
// If it's a subexpression, the string result
487
// will be pushed after this opcode.
488
if (type !== 'SubExpression') {
489
if (typeof string === 'string') {
490
this.pushString(string);
491
} else {
492
this.pushStackLiteral(string);
493
}
494
}
495
},
496
497
emptyHash: function(omitEmpty) {
498
if (this.trackIds) {
499
this.push('{}'); // hashIds
500
}
501
if (this.stringParams) {
502
this.push('{}'); // hashContexts
503
this.push('{}'); // hashTypes
504
}
505
this.pushStackLiteral(omitEmpty ? 'undefined' : '{}');
506
},
507
pushHash: function() {
508
if (this.hash) {
509
this.hashes.push(this.hash);
510
}
511
this.hash = {values: [], types: [], contexts: [], ids: []};
512
},
513
popHash: function() {
514
var hash = this.hash;
515
this.hash = this.hashes.pop();
516
517
if (this.trackIds) {
518
this.push(this.objectLiteral(hash.ids));
519
}
520
if (this.stringParams) {
521
this.push(this.objectLiteral(hash.contexts));
522
this.push(this.objectLiteral(hash.types));
523
}
524
525
this.push(this.objectLiteral(hash.values));
526
},
527
528
// [pushString]
529
//
530
// On stack, before: ...
531
// On stack, after: quotedString(string), ...
532
//
533
// Push a quoted version of `string` onto the stack
534
pushString: function(string) {
535
this.pushStackLiteral(this.quotedString(string));
536
},
537
538
// [pushLiteral]
539
//
540
// On stack, before: ...
541
// On stack, after: value, ...
542
//
543
// Pushes a value onto the stack. This operation prevents
544
// the compiler from creating a temporary variable to hold
545
// it.
546
pushLiteral: function(value) {
547
this.pushStackLiteral(value);
548
},
549
550
// [pushProgram]
551
//
552
// On stack, before: ...
553
// On stack, after: program(guid), ...
554
//
555
// Push a program expression onto the stack. This takes
556
// a compile-time guid and converts it into a runtime-accessible
557
// expression.
558
pushProgram: function(guid) {
559
if (guid != null) {
560
this.pushStackLiteral(this.programExpression(guid));
561
} else {
562
this.pushStackLiteral(null);
563
}
564
},
565
566
// [invokeHelper]
567
//
568
// On stack, before: hash, inverse, program, params..., ...
569
// On stack, after: result of helper invocation
570
//
571
// Pops off the helper's parameters, invokes the helper,
572
// and pushes the helper's return value onto the stack.
573
//
574
// If the helper is not found, `helperMissing` is called.
575
invokeHelper: function(paramSize, name, isSimple) {
576
var nonHelper = this.popStack();
577
var helper = this.setupHelper(paramSize, name);
578
var simple = isSimple ? [helper.name, ' || '] : '';
579
580
var lookup = ['('].concat(simple, nonHelper);
581
if (!this.options.strict) {
582
lookup.push(' || ', this.aliasable('helpers.helperMissing'));
583
}
584
lookup.push(')');
585
586
this.push(this.source.functionCall(lookup, 'call', helper.callParams));
587
},
588
589
// [invokeKnownHelper]
590
//
591
// On stack, before: hash, inverse, program, params..., ...
592
// On stack, after: result of helper invocation
593
//
594
// This operation is used when the helper is known to exist,
595
// so a `helperMissing` fallback is not required.
596
invokeKnownHelper: function(paramSize, name) {
597
var helper = this.setupHelper(paramSize, name);
598
this.push(this.source.functionCall(helper.name, 'call', helper.callParams));
599
},
600
601
// [invokeAmbiguous]
602
//
603
// On stack, before: hash, inverse, program, params..., ...
604
// On stack, after: result of disambiguation
605
//
606
// This operation is used when an expression like `{{foo}}`
607
// is provided, but we don't know at compile-time whether it
608
// is a helper or a path.
609
//
610
// This operation emits more code than the other options,
611
// and can be avoided by passing the `knownHelpers` and
612
// `knownHelpersOnly` flags at compile-time.
613
invokeAmbiguous: function(name, helperCall) {
614
this.useRegister('helper');
615
616
var nonHelper = this.popStack();
617
618
this.emptyHash();
619
var helper = this.setupHelper(0, name, helperCall);
620
621
var helperName = this.lastHelper = this.nameLookup('helpers', name, 'helper');
622
623
var lookup = ['(', '(helper = ', helperName, ' || ', nonHelper, ')'];
624
if (!this.options.strict) {
625
lookup[0] = '(helper = ';
626
lookup.push(
627
' != null ? helper : ',
628
this.aliasable('helpers.helperMissing')
629
);
630
}
631
632
this.push([
633
'(', lookup,
634
(helper.paramsInit ? ['),(', helper.paramsInit] : []), '),',
635
'(typeof helper === ', this.aliasable('"function"'), ' ? ',
636
this.source.functionCall('helper','call', helper.callParams), ' : helper))'
637
]);
638
},
639
640
// [invokePartial]
641
//
642
// On stack, before: context, ...
643
// On stack after: result of partial invocation
644
//
645
// This operation pops off a context, invokes a partial with that context,
646
// and pushes the result of the invocation back.
647
invokePartial: function(isDynamic, name, indent) {
648
var params = [],
649
options = this.setupParams(name, 1, params, false);
650
651
if (isDynamic) {
652
name = this.popStack();
653
delete options.name;
654
}
655
656
if (indent) {
657
options.indent = JSON.stringify(indent);
658
}
659
options.helpers = 'helpers';
660
options.partials = 'partials';
661
662
if (!isDynamic) {
663
params.unshift(this.nameLookup('partials', name, 'partial'));
664
} else {
665
params.unshift(name);
666
}
667
668
if (this.options.compat) {
669
options.depths = 'depths';
670
}
671
options = this.objectLiteral(options);
672
params.push(options);
673
674
this.push(this.source.functionCall('this.invokePartial', '', params));
675
},
676
677
// [assignToHash]
678
//
679
// On stack, before: value, ..., hash, ...
680
// On stack, after: ..., hash, ...
681
//
682
// Pops a value off the stack and assigns it to the current hash
683
assignToHash: function(key) {
684
var value = this.popStack(),
685
context,
686
type,
687
id;
688
689
if (this.trackIds) {
690
id = this.popStack();
691
}
692
if (this.stringParams) {
693
type = this.popStack();
694
context = this.popStack();
695
}
696
697
var hash = this.hash;
698
if (context) {
699
hash.contexts[key] = context;
700
}
701
if (type) {
702
hash.types[key] = type;
703
}
704
if (id) {
705
hash.ids[key] = id;
706
}
707
hash.values[key] = value;
708
},
709
710
pushId: function(type, name, child) {
711
if (type === 'BlockParam') {
712
this.pushStackLiteral(
713
'blockParams[' + name[0] + '].path[' + name[1] + ']'
714
+ (child ? ' + ' + JSON.stringify('.' + child) : ''));
715
} else if (type === 'PathExpression') {
716
this.pushString(name);
717
} else if (type === 'SubExpression') {
718
this.pushStackLiteral('true');
719
} else {
720
this.pushStackLiteral('null');
721
}
722
},
723
724
// HELPERS
725
726
compiler: JavaScriptCompiler,
727
728
compileChildren: function(environment, options) {
729
var children = environment.children, child, compiler;
730
731
for(var i=0, l=children.length; i<l; i++) {
732
child = children[i];
733
compiler = new this.compiler();
734
735
var index = this.matchExistingProgram(child);
736
737
if (index == null) {
738
this.context.programs.push(''); // Placeholder to prevent name conflicts for nested children
739
index = this.context.programs.length;
740
child.index = index;
741
child.name = 'program' + index;
742
this.context.programs[index] = compiler.compile(child, options, this.context, !this.precompile);
743
this.context.environments[index] = child;
744
745
this.useDepths = this.useDepths || compiler.useDepths;
746
this.useBlockParams = this.useBlockParams || compiler.useBlockParams;
747
} else {
748
child.index = index;
749
child.name = 'program' + index;
750
751
this.useDepths = this.useDepths || child.useDepths;
752
this.useBlockParams = this.useBlockParams || child.useBlockParams;
753
}
754
}
755
},
756
matchExistingProgram: function(child) {
757
for (var i = 0, len = this.context.environments.length; i < len; i++) {
758
var environment = this.context.environments[i];
759
if (environment && environment.equals(child)) {
760
return i;
761
}
762
}
763
},
764
765
programExpression: function(guid) {
766
var child = this.environment.children[guid],
767
programParams = [child.index, 'data', child.blockParams];
768
769
if (this.useBlockParams || this.useDepths) {
770
programParams.push('blockParams');
771
}
772
if (this.useDepths) {
773
programParams.push('depths');
774
}
775
776
return 'this.program(' + programParams.join(', ') + ')';
777
},
778
779
useRegister: function(name) {
780
if(!this.registers[name]) {
781
this.registers[name] = true;
782
this.registers.list.push(name);
783
}
784
},
785
786
push: function(expr) {
787
if (!(expr instanceof Literal)) {
788
expr = this.source.wrap(expr);
789
}
790
791
this.inlineStack.push(expr);
792
return expr;
793
},
794
795
pushStackLiteral: function(item) {
796
this.push(new Literal(item));
797
},
798
799
pushSource: function(source) {
800
if (this.pendingContent) {
801
this.source.push(
802
this.appendToBuffer(this.source.quotedString(this.pendingContent), this.pendingLocation));
803
this.pendingContent = undefined;
804
}
805
806
if (source) {
807
this.source.push(source);
808
}
809
},
810
811
replaceStack: function(callback) {
812
var prefix = ['('],
813
stack,
814
createdStack,
815
usedLiteral;
816
817
/* istanbul ignore next */
818
if (!this.isInline()) {
819
throw new Exception('replaceStack on non-inline');
820
}
821
822
// We want to merge the inline statement into the replacement statement via ','
823
var top = this.popStack(true);
824
825
if (top instanceof Literal) {
826
// Literals do not need to be inlined
827
stack = [top.value];
828
prefix = ['(', stack];
829
usedLiteral = true;
830
} else {
831
// Get or create the current stack name for use by the inline
832
createdStack = true;
833
var name = this.incrStack();
834
835
prefix = ['((', this.push(name), ' = ', top, ')'];
836
stack = this.topStack();
837
}
838
839
var item = callback.call(this, stack);
840
841
if (!usedLiteral) {
842
this.popStack();
843
}
844
if (createdStack) {
845
this.stackSlot--;
846
}
847
this.push(prefix.concat(item, ')'));
848
},
849
850
incrStack: function() {
851
this.stackSlot++;
852
if(this.stackSlot > this.stackVars.length) { this.stackVars.push("stack" + this.stackSlot); }
853
return this.topStackName();
854
},
855
topStackName: function() {
856
return "stack" + this.stackSlot;
857
},
858
flushInline: function() {
859
var inlineStack = this.inlineStack;
860
this.inlineStack = [];
861
for (var i = 0, len = inlineStack.length; i < len; i++) {
862
var entry = inlineStack[i];
863
/* istanbul ignore if */
864
if (entry instanceof Literal) {
865
this.compileStack.push(entry);
866
} else {
867
var stack = this.incrStack();
868
this.pushSource([stack, ' = ', entry, ';']);
869
this.compileStack.push(stack);
870
}
871
}
872
},
873
isInline: function() {
874
return this.inlineStack.length;
875
},
876
877
popStack: function(wrapped) {
878
var inline = this.isInline(),
879
item = (inline ? this.inlineStack : this.compileStack).pop();
880
881
if (!wrapped && (item instanceof Literal)) {
882
return item.value;
883
} else {
884
if (!inline) {
885
/* istanbul ignore next */
886
if (!this.stackSlot) {
887
throw new Exception('Invalid stack pop');
888
}
889
this.stackSlot--;
890
}
891
return item;
892
}
893
},
894
895
topStack: function() {
896
var stack = (this.isInline() ? this.inlineStack : this.compileStack),
897
item = stack[stack.length - 1];
898
899
/* istanbul ignore if */
900
if (item instanceof Literal) {
901
return item.value;
902
} else {
903
return item;
904
}
905
},
906
907
contextName: function(context) {
908
if (this.useDepths && context) {
909
return 'depths[' + context + ']';
910
} else {
911
return 'depth' + context;
912
}
913
},
914
915
quotedString: function(str) {
916
return this.source.quotedString(str);
917
},
918
919
objectLiteral: function(obj) {
920
return this.source.objectLiteral(obj);
921
},
922
923
aliasable: function(name) {
924
var ret = this.aliases[name];
925
if (ret) {
926
ret.referenceCount++;
927
return ret;
928
}
929
930
ret = this.aliases[name] = this.source.wrap(name);
931
ret.aliasable = true;
932
ret.referenceCount = 1;
933
934
return ret;
935
},
936
937
setupHelper: function(paramSize, name, blockHelper) {
938
var params = [],
939
paramsInit = this.setupHelperArgs(name, paramSize, params, blockHelper);
940
var foundHelper = this.nameLookup('helpers', name, 'helper');
941
942
return {
943
params: params,
944
paramsInit: paramsInit,
945
name: foundHelper,
946
callParams: [this.contextName(0)].concat(params)
947
};
948
},
949
950
setupParams: function(helper, paramSize, params) {
951
var options = {}, contexts = [], types = [], ids = [], param;
952
953
options.name = this.quotedString(helper);
954
options.hash = this.popStack();
955
956
if (this.trackIds) {
957
options.hashIds = this.popStack();
958
}
959
if (this.stringParams) {
960
options.hashTypes = this.popStack();
961
options.hashContexts = this.popStack();
962
}
963
964
var inverse = this.popStack(),
965
program = this.popStack();
966
967
// Avoid setting fn and inverse if neither are set. This allows
968
// helpers to do a check for `if (options.fn)`
969
if (program || inverse) {
970
options.fn = program || 'this.noop';
971
options.inverse = inverse || 'this.noop';
972
}
973
974
// The parameters go on to the stack in order (making sure that they are evaluated in order)
975
// so we need to pop them off the stack in reverse order
976
var i = paramSize;
977
while (i--) {
978
param = this.popStack();
979
params[i] = param;
980
981
if (this.trackIds) {
982
ids[i] = this.popStack();
983
}
984
if (this.stringParams) {
985
types[i] = this.popStack();
986
contexts[i] = this.popStack();
987
}
988
}
989
990
if (this.trackIds) {
991
options.ids = this.source.generateArray(ids);
992
}
993
if (this.stringParams) {
994
options.types = this.source.generateArray(types);
995
options.contexts = this.source.generateArray(contexts);
996
}
997
998
if (this.options.data) {
999
options.data = 'data';
1000
}
1001
if (this.useBlockParams) {
1002
options.blockParams = 'blockParams';
1003
}
1004
return options;
1005
},
1006
1007
setupHelperArgs: function(helper, paramSize, params, useRegister) {
1008
var options = this.setupParams(helper, paramSize, params, true);
1009
options = this.objectLiteral(options);
1010
if (useRegister) {
1011
this.useRegister('options');
1012
params.push('options');
1013
return ['options=', options];
1014
} else {
1015
params.push(options);
1016
return '';
1017
}
1018
}
1019
};
1020
1021
1022
var reservedWords = (
1023
"break else new var" +
1024
" case finally return void" +
1025
" catch for switch while" +
1026
" continue function this with" +
1027
" default if throw" +
1028
" delete in try" +
1029
" do instanceof typeof" +
1030
" abstract enum int short" +
1031
" boolean export interface static" +
1032
" byte extends long super" +
1033
" char final native synchronized" +
1034
" class float package throws" +
1035
" const goto private transient" +
1036
" debugger implements protected volatile" +
1037
" double import public let yield await" +
1038
" null true false"
1039
).split(" ");
1040
1041
var compilerWords = JavaScriptCompiler.RESERVED_WORDS = {};
1042
1043
for(var i=0, l=reservedWords.length; i<l; i++) {
1044
compilerWords[reservedWords[i]] = true;
1045
}
1046
1047
JavaScriptCompiler.isValidJavaScriptVariableName = function(name) {
1048
return !JavaScriptCompiler.RESERVED_WORDS[name] && /^[a-zA-Z_$][0-9a-zA-Z_$]*$/.test(name);
1049
};
1050
1051
function strictLookup(requireTerminal, compiler, parts, type) {
1052
var stack = compiler.popStack();
1053
1054
var i = 0,
1055
len = parts.length;
1056
if (requireTerminal) {
1057
len--;
1058
}
1059
1060
for (; i < len; i++) {
1061
stack = compiler.nameLookup(stack, parts[i], type);
1062
}
1063
1064
if (requireTerminal) {
1065
return [compiler.aliasable('this.strict'), '(', stack, ', ', compiler.quotedString(parts[i]), ')'];
1066
} else {
1067
return stack;
1068
}
1069
}
1070
1071
export default JavaScriptCompiler;
1072
1073