// Pops off the decorator's parameters, invokes the decorator,
// and inserts the decorator into the decorators list.
registerDecorator: function registerDecorator(paramSize, name) {
var foundDecorator = this.nameLookup('decorators', name, 'decorator'),
options = this.setupHelperArgs(name, paramSize);
this.decorators.push(['fn = ', this.decorators.functionCall(foundDecorator, '', ['fn', 'props', 'container', options]), ' || fn;']);
// On stack, before: hash, inverse, program, params..., ...
// On stack, after: result of helper invocation
// Pops off the helper's parameters, invokes the helper,
// and pushes the helper's return value onto the stack.
// If the helper is not found, `helperMissing` is called.
invokeHelper: function invokeHelper(paramSize, name, isSimple) {
var nonHelper = this.popStack(),
helper = this.setupHelper(paramSize, name);
var possibleFunctionCalls = [];
possibleFunctionCalls.push(helper.name);
// call a function from the input object
possibleFunctionCalls.push(nonHelper);
if (!this.options.strict) {
possibleFunctionCalls.push(this.aliasable('container.hooks.helperMissing'));
var functionLookupCode = ['(', this.itemsSeparatedBy(possibleFunctionCalls, '||'), ')'];
var functionCall = this.source.functionCall(functionLookupCode, 'call', helper.callParams);
itemsSeparatedBy: function itemsSeparatedBy(items, separator) {
for (var i = 1; i < items.length; i++) {
result.push(separator, items[i]);
// On stack, before: hash, inverse, program, params..., ...
// On stack, after: result of helper invocation
// This operation is used when the helper is known to exist,
// so a `helperMissing` fallback is not required.
invokeKnownHelper: function invokeKnownHelper(paramSize, name) {
var helper = this.setupHelper(paramSize, name);
this.push(this.source.functionCall(helper.name, 'call', helper.callParams));
// On stack, before: hash, inverse, program, params..., ...
// On stack, after: result of disambiguation
// This operation is used when an expression like `{{foo}}`
// is provided, but we don't know at compile-time whether it
// is a helper or a path.
// This operation emits more code than the other options,
// and can be avoided by passing the `knownHelpers` and
// `knownHelpersOnly` flags at compile-time.
invokeAmbiguous: function invokeAmbiguous(name, helperCall) {
this.useRegister('helper');
var nonHelper = this.popStack();
var helper = this.setupHelper(0, name, helperCall);
var helperName = this.lastHelper = this.nameLookup('helpers', name, 'helper');
var lookup = ['(', '(helper = ', helperName, ' || ', nonHelper, ')'];
if (!this.options.strict) {
lookup[0] = '(helper = ';
lookup.push(' != null ? helper : ', this.aliasable('container.hooks.helperMissing'));
this.push(['(', lookup, helper.paramsInit ? ['),(', helper.paramsInit] : [], '),', '(typeof helper === ', this.aliasable('"function"'), ' ? ', this.source.functionCall('helper', 'call', helper.callParams), ' : helper))']);
// On stack, before: context, ...
// On stack after: result of partial invocation
// This operation pops off a context, invokes a partial with that context,
// and pushes the result of the invocation back.
invokePartial: function invokePartial(isDynamic, name, indent) {
options = this.setupParams(name, 1, params);
options.indent = JSON.stringify(indent);
options.helpers = 'helpers';
options.partials = 'partials';
options.decorators = 'container.decorators';
params.unshift(this.nameLookup('partials', name, 'partial'));
if (this.options.compat) {
options.depths = 'depths';
options = this.objectLiteral(options);
this.push(this.source.functionCall('container.invokePartial', '', params));
// On stack, before: value, ..., hash, ...
// On stack, after: ..., hash, ...
// Pops a value off the stack and assigns it to the current hash
assignToHash: function assignToHash(key) {
var value = this.popStack(),
context = this.popStack();
hash.contexts[key] = context;
hash.values[key] = value;
pushId: function pushId(type, name, child) {
if (type === 'BlockParam') {
this.pushStackLiteral('blockParams[' + name[0] + '].path[' + name[1] + ']' + (child ? ' + ' + JSON.stringify('.' + child) : ''));
} else if (type === 'PathExpression') {
} else if (type === 'SubExpression') {
this.pushStackLiteral('true');
this.pushStackLiteral('null');
compiler: JavaScriptCompiler,
compileChildren: function compileChildren(environment, options) {
var children = environment.children,
for (var i = 0, l = children.length; i < l; i++) {
compiler = new this.compiler(); // eslint-disable-line new-cap
var existing = this.matchExistingProgram(child);
this.context.programs.push(''); // Placeholder to prevent name conflicts for nested children
var index = this.context.programs.length;
child.name = 'program' + index;
this.context.programs[index] = compiler.compile(child, options, this.context, !this.precompile);
this.context.decorators[index] = compiler.decorators;
this.context.environments[index] = child;
this.useDepths = this.useDepths || compiler.useDepths;
this.useBlockParams = this.useBlockParams || compiler.useBlockParams;
child.useDepths = this.useDepths;
child.useBlockParams = this.useBlockParams;
child.index = existing.index;
child.name = 'program' + existing.index;
this.useDepths = this.useDepths || existing.useDepths;
this.useBlockParams = this.useBlockParams || existing.useBlockParams;
matchExistingProgram: function matchExistingProgram(child) {
for (var i = 0, len = this.context.environments.length; i < len; i++) {
var environment = this.context.environments[i];
if (environment && environment.equals(child)) {
programExpression: function programExpression(guid) {
var child = this.environment.children[guid],
programParams = [child.index, 'data', child.blockParams];
if (this.useBlockParams || this.useDepths) {
programParams.push('blockParams');
programParams.push('depths');
return 'container.program(' + programParams.join(', ') + ')';
useRegister: function useRegister(name) {
if (!this.registers[name]) {
this.registers[name] = true;
this.registers.list.push(name);
push: function push(expr) {
if (!(expr instanceof Literal)) {
expr = this.source.wrap(expr);
this.inlineStack.push(expr);
pushStackLiteral: function pushStackLiteral(item) {
this.push(new Literal(item));
pushSource: function pushSource(source) {
if (this.pendingContent) {
this.source.push(this.appendToBuffer(this.source.quotedString(this.pendingContent), this.pendingLocation));
this.pendingContent = undefined;
this.source.push(source);
replaceStack: function replaceStack(callback) {
createdStack = undefined,
/* istanbul ignore next */
throw new _exception2['default']('replaceStack on non-inline');
// We want to merge the inline statement into the replacement statement via ','
var top = this.popStack(true);
if (top instanceof Literal) {
// Literals do not need to be inlined
// Get or create the current stack name for use by the inline
var _name = this.incrStack();
prefix = ['((', this.push(_name), ' = ', top, ')'];
var item = callback.call(this, stack);
this.push(prefix.concat(item, ')'));
incrStack: function incrStack() {
if (this.stackSlot > this.stackVars.length) {
this.stackVars.push('stack' + this.stackSlot);
return this.topStackName();
topStackName: function topStackName() {
return 'stack' + this.stackSlot;
flushInline: function flushInline() {
var inlineStack = this.inlineStack;
for (var i = 0, len = inlineStack.length; i < len; i++) {
var entry = inlineStack[i];
if (entry instanceof Literal) {
this.compileStack.push(entry);
var stack = this.incrStack();
this.pushSource([stack, ' = ', entry, ';']);
this.compileStack.push(stack);
isInline: function isInline() {
return this.inlineStack.length;
popStack: function popStack(wrapped) {
var inline = this.isInline(),
item = (inline ? this.inlineStack : this.compileStack).pop();
if (!wrapped && item instanceof Literal) {
/* istanbul ignore next */
throw new _exception2['default']('Invalid stack pop');
topStack: function topStack() {
var stack = this.isInline() ? this.inlineStack : this.compileStack,
item = stack[stack.length - 1];
if (item instanceof Literal) {
contextName: function contextName(context) {
if (this.useDepths && context) {
return 'depths[' + context + ']';
return 'depth' + context;
quotedString: function quotedString(str) {
return this.source.quotedString(str);
objectLiteral: function objectLiteral(obj) {
return this.source.objectLiteral(obj);
aliasable: function aliasable(name) {
var ret = this.aliases[name];
ret = this.aliases[name] = this.source.wrap(name);
setupHelper: function setupHelper(paramSize, name, blockHelper) {
paramsInit = this.setupHelperArgs(name, paramSize, params, blockHelper);
var foundHelper = this.nameLookup('helpers', name, 'helper'),
callContext = this.aliasable(this.contextName(0) + ' != null ? ' + this.contextName(0) + ' : (container.nullContext || {})');
callParams: [callContext].concat(params)
setupParams: function setupParams(helper, paramSize, params) {
options.name = this.quotedString(helper);
options.hash = this.popStack();
options.hashIds = this.popStack();
options.hashTypes = this.popStack();
options.hashContexts = this.popStack();
var inverse = this.popStack(),
program = this.popStack();
// Avoid setting fn and inverse if neither are set. This allows
// helpers to do a check for `if (options.fn)`
if (program || inverse) {
options.fn = program || 'container.noop';
options.inverse = inverse || 'container.noop';
// The parameters go on to the stack in order (making sure that they are evaluated in order)
// so we need to pop them off the stack in reverse order
ids[i] = this.popStack();
types[i] = this.popStack();
contexts[i] = this.popStack();
options.args = this.source.generateArray(params);
options.ids = this.source.generateArray(ids);
options.types = this.source.generateArray(types);
options.contexts = this.source.generateArray(contexts);
if (this.useBlockParams) {
options.blockParams = 'blockParams';
setupHelperArgs: function setupHelperArgs(helper, paramSize, params, useRegister) {
var options = this.setupParams(helper, paramSize, params);
options.loc = JSON.stringify(this.source.currentLocation);
options = this.objectLiteral(options);
this.useRegister('options');
return ['options=', options];
var reservedWords = ('break else new var' + ' case finally return void' + ' catch for switch while' + ' continue function this with' + ' default if throw' + ' delete in try' + ' do instanceof typeof' + ' abstract enum int short' + ' boolean export interface static' + ' byte extends long super' + ' char final native synchronized' + ' class float package throws' + ' const goto private transient' + ' debugger implements protected volatile' + ' double import public let yield await' + ' null true false').split(' ');
var compilerWords = JavaScriptCompiler.RESERVED_WORDS = {};
for (var i = 0, l = reservedWords.length; i < l; i++) {
compilerWords[reservedWords[i]] = true;