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