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