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