Start doing earlier work on helpers
This commit is contained in:
@@ -11,12 +11,26 @@ var Handlebars = require('./base');
|
||||
if(inverse) { this.inverse = new Handlebars.AST.ProgramNode(inverse); }
|
||||
};
|
||||
|
||||
Handlebars.AST.MustacheNode = function(params, hash, unescaped) {
|
||||
Handlebars.AST.MustacheNode = function(rawParams, hash, unescaped) {
|
||||
this.type = "mustache";
|
||||
this.id = params[0];
|
||||
this.params = params.slice(1);
|
||||
this.hash = hash;
|
||||
this.escaped = !unescaped;
|
||||
this.hash = hash;
|
||||
|
||||
var id = this.id = rawParams[0];
|
||||
var params = this.params = rawParams.slice(1);
|
||||
|
||||
// a mustache is an eligible helper if:
|
||||
// * its id is simple (a single part, not `this` or `..`)
|
||||
var eligibleHelper = this.eligibleHelper = id.isSimple;
|
||||
|
||||
// a mustache is definitely a helper if:
|
||||
// * it is an eligible helper, and
|
||||
// * it has at least one parameter or hash segment
|
||||
this.isHelper = eligibleHelper && (params.length || hash);
|
||||
|
||||
// if a mustache is an eligible helper but not a definite
|
||||
// helper, it is ambiguous, and will be resolved in a later
|
||||
// pass or at runtime.
|
||||
};
|
||||
|
||||
Handlebars.AST.PartialNode = function(id, context) {
|
||||
@@ -75,7 +89,10 @@ var Handlebars = require('./base');
|
||||
this.parts = dig;
|
||||
this.string = dig.join('.');
|
||||
this.depth = depth;
|
||||
this.isSimple = (dig.length === 1) && (depth === 0);
|
||||
|
||||
// an ID is simple if it only has one part, and that part is not
|
||||
// `..` or `this`.
|
||||
this.isSimple = parts.length === 1 && !this.isScoped && depth === 0;
|
||||
};
|
||||
|
||||
Handlebars.AST.StringNode = function(string) {
|
||||
|
||||
@@ -25,7 +25,10 @@ Handlebars.JavaScriptCompiler = function() {};
|
||||
pushStringParam: 16,
|
||||
knownHelper: 17,
|
||||
pushContext: 18,
|
||||
lookupOnContext: 19
|
||||
lookupOnContext: 19,
|
||||
resolvePossibleLambda: 20,
|
||||
invokeHelper: 21,
|
||||
pushLiteral: 22
|
||||
};
|
||||
|
||||
Compiler.MULTI_PARAM_OPCODES = {
|
||||
@@ -44,7 +47,10 @@ Handlebars.JavaScriptCompiler = function() {};
|
||||
pushStringParam: 1,
|
||||
knownHelper: 1,
|
||||
pushContext: 0,
|
||||
lookupOnContext: 1
|
||||
lookupOnContext: 1,
|
||||
resolvePossibleLambda: 0,
|
||||
invokeHelper: 2,
|
||||
pushLiteral: 1
|
||||
};
|
||||
|
||||
Compiler.DISASSEMBLE_MAP = {};
|
||||
@@ -231,9 +237,18 @@ Handlebars.JavaScriptCompiler = function() {};
|
||||
},
|
||||
|
||||
mustache: function(mustache) {
|
||||
var params = this.setupStackForMustache(mustache);
|
||||
var id = mustache.id;
|
||||
|
||||
this.opcode('invokeMustache', params.length, mustache.id.original);
|
||||
if (!mustache.eligibleHelper) {
|
||||
this.simpleMustache(mustache);
|
||||
} else if (mustache.isHelper) {
|
||||
this.helperMustache(mustache);
|
||||
} else {
|
||||
var params = this.setupStackForMustache(mustache),
|
||||
name = id.parts[0];
|
||||
|
||||
this.opcode('invokeMustache', params.length, mustache.id.original);
|
||||
}
|
||||
|
||||
if(mustache.escaped && !this.options.noEscape) {
|
||||
this.opcode('appendEscaped');
|
||||
@@ -242,9 +257,32 @@ Handlebars.JavaScriptCompiler = function() {};
|
||||
}
|
||||
},
|
||||
|
||||
simpleMustache: function(mustache) {
|
||||
var id = mustache.id;
|
||||
|
||||
this.addDepth(id.depth);
|
||||
this.opcode('getContext', id.depth);
|
||||
|
||||
if (id.parts.length) {
|
||||
this.opcode('lookupOnContext', id.parts[0]);
|
||||
for(var i=1, l=id.parts.length; i<l; i++) {
|
||||
this.opcode('lookup', id.parts[i]);
|
||||
}
|
||||
} else {
|
||||
this.opcode('pushContext');
|
||||
}
|
||||
this.opcode('resolvePossibleLambda');
|
||||
},
|
||||
|
||||
helperMustache: function(mustache) {
|
||||
var params = this.setupMustacheParams(mustache),
|
||||
name = mustache.id.parts[0];
|
||||
|
||||
this.opcode('invokeHelper', params.length, name);
|
||||
},
|
||||
|
||||
ID: function(id) {
|
||||
this.addDepth(id.depth);
|
||||
|
||||
this.opcode('getContext', id.depth);
|
||||
|
||||
var name = id.parts[0];
|
||||
@@ -282,25 +320,6 @@ Handlebars.JavaScriptCompiler = function() {};
|
||||
comment: function() {},
|
||||
|
||||
// HELPERS
|
||||
pushParams: function(params) {
|
||||
var i = params.length, param;
|
||||
|
||||
while(i--) {
|
||||
param = params[i];
|
||||
|
||||
if(this.options.stringParams) {
|
||||
if(param.depth) {
|
||||
this.addDepth(param.depth);
|
||||
}
|
||||
|
||||
this.opcode('getContext', param.depth || 0);
|
||||
this.opcode('pushStringParam', param.string);
|
||||
} else {
|
||||
this[param.type](param);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
opcode: function(name, val1, val2, val3) {
|
||||
this.opcodes.push(Compiler.OPCODE_MAP[name]);
|
||||
if(val1 !== undefined) { this.opcodes.push(val1); }
|
||||
@@ -323,17 +342,41 @@ Handlebars.JavaScriptCompiler = function() {};
|
||||
}
|
||||
},
|
||||
|
||||
setupStackForMustache: function(mustache) {
|
||||
var params = mustache.params;
|
||||
pushParams: function(params) {
|
||||
var i = params.length, param;
|
||||
|
||||
while(i--) {
|
||||
param = params[i];
|
||||
|
||||
if(this.options.stringParams) {
|
||||
if(param.depth) {
|
||||
this.addDepth(param.depth);
|
||||
}
|
||||
|
||||
this.opcode('getContext', param.depth || 0);
|
||||
this.opcode('pushStringParam', param.string);
|
||||
} else {
|
||||
this[param.type](param);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
setupMustacheParams: function(mustache) {
|
||||
var params = mustache.params;
|
||||
this.pushParams(params);
|
||||
|
||||
if(mustache.hash) {
|
||||
this.hash(mustache.hash);
|
||||
} else {
|
||||
this.opcode('push', '{}');
|
||||
this.opcode('pushLiteral', '{}');
|
||||
}
|
||||
|
||||
return params;
|
||||
},
|
||||
|
||||
setupStackForMustache: function(mustache) {
|
||||
var params = this.setupMustacheParams(mustache);
|
||||
|
||||
this.ID(mustache.id);
|
||||
|
||||
return params;
|
||||
@@ -547,13 +590,21 @@ Handlebars.JavaScriptCompiler = function() {};
|
||||
},
|
||||
|
||||
lookupOnContext: function(name) {
|
||||
this.pushStackLiteral(this.nameLookup('depth' + this.lastContext, name, 'context'));
|
||||
this.pushStack(this.nameLookup('depth' + this.lastContext, name, 'context'));
|
||||
},
|
||||
|
||||
pushContext: function() {
|
||||
this.pushStackLiteral('depth' + this.lastContext);
|
||||
},
|
||||
|
||||
resolvePossibleLambda: function() {
|
||||
this.context.aliases.functionType = '"function"';
|
||||
|
||||
this.replaceStack(function(current) {
|
||||
return "typeof " + current + " === functionType ? " + current + "() : " + current;
|
||||
});
|
||||
},
|
||||
|
||||
lookupWithHelpers: function(name) {
|
||||
this.register('foundHelper', this.nameLookup('helpers', name, 'helper'));
|
||||
this.pushStack("foundHelper || " + this.nameLookup('depth' + this.lastContext, name, 'context'));
|
||||
@@ -578,6 +629,10 @@ Handlebars.JavaScriptCompiler = function() {};
|
||||
this.pushStack(name);
|
||||
},
|
||||
|
||||
pushLiteral: function(value) {
|
||||
this.pushStackLiteral(value);
|
||||
},
|
||||
|
||||
// The rules for mustaches are:
|
||||
//
|
||||
// If the first parameter resolves to a function, call the function with the remaining parameters
|
||||
@@ -619,6 +674,50 @@ Handlebars.JavaScriptCompiler = function() {};
|
||||
});
|
||||
},
|
||||
|
||||
invokeHelper: function(paramSize, name) {
|
||||
this.context.aliases.helperMissing = 'helpers.helperMissing';
|
||||
|
||||
var params = [], contexts = [];
|
||||
this.setupParams(paramSize, params, contexts);
|
||||
this.register('foundHelper', this.nameLookup('helpers', name, 'helper'));
|
||||
|
||||
var main = ["depth0"].concat(params).join(", ");
|
||||
var helperMissing = ["depth0", this.quotedString(name)].concat(params).join(", ");
|
||||
this.pushStack("foundHelper ? foundHelper.call(" + main + ") " +
|
||||
": helperMissing.call(" + helperMissing + ")");
|
||||
|
||||
//this.replaceStack(function(current) {
|
||||
//});
|
||||
},
|
||||
|
||||
// the params and contexts arguments are passed in arrays
|
||||
// to fill in
|
||||
setupParams: function(paramSize, params) {
|
||||
var options = [], contexts = [], param;
|
||||
|
||||
options.push("hash:" + this.popStack());
|
||||
|
||||
for(var i=0; i<paramSize; i++) {
|
||||
param = this.popStack();
|
||||
params.push(param);
|
||||
|
||||
if(this.options.stringParams) {
|
||||
contexts.push(this.popStack());
|
||||
}
|
||||
}
|
||||
|
||||
if (this.options.stringParams) {
|
||||
options.push("contexts:[" + contexts.join(",") + "]");
|
||||
}
|
||||
|
||||
if(this.options.data) {
|
||||
options.push("data:data");
|
||||
}
|
||||
|
||||
params.push("{" + options.join(",") + "}");
|
||||
return params.join(", ");
|
||||
},
|
||||
|
||||
populateParams: function(paramSize, helperId, options, resolveLambdas, callback) {
|
||||
var id = this.popStack(), nextStack;
|
||||
var params = [], contexts = [], param, paramString, stringOptions;
|
||||
|
||||
+18
-25
@@ -70,8 +70,8 @@ test("boolean", function() {
|
||||
});
|
||||
|
||||
test("zeros", function() {
|
||||
shouldCompileTo("num1: {{num1}}, num2: {{num2}}", {num1: 42, num2: 0},
|
||||
"num1: 42, num2: 0");
|
||||
shouldCompileTo("num1: {{num1}}, num2: {{num2}}", {num1: 42, num2: 0},
|
||||
"num1: 42, num2: 0");
|
||||
shouldCompileTo("num: {{.}}", 0, "num: 0");
|
||||
shouldCompileTo("num: {{num1/num2}}", {num1: {num2: 0}}, "num: 0");
|
||||
});
|
||||
@@ -112,13 +112,6 @@ test("functions", function() {
|
||||
"functions are called and render their output");
|
||||
});
|
||||
|
||||
test("functions with context argument", function() {
|
||||
shouldCompileTo("{{awesome frank}}",
|
||||
{awesome: function(context) { return context; },
|
||||
frank: "Frank"},
|
||||
"Frank", "functions are called with context arguments");
|
||||
});
|
||||
|
||||
test("paths with hyphens", function() {
|
||||
shouldCompileTo("{{foo-bar}}", {"foo-bar": "baz"}, "baz", "Paths can contain hyphens (-)");
|
||||
});
|
||||
@@ -225,7 +218,7 @@ test("block with complex lookup", function() {
|
||||
"Templates can access variables in contexts up the stack with relative path syntax");
|
||||
});
|
||||
|
||||
test("helper with complex lookup", function() {
|
||||
test("helper with complex lookup$", function() {
|
||||
var string = "{{#goodbyes}}{{{link ../prefix}}}{{/goodbyes}}";
|
||||
var hash = {prefix: "/root", goodbyes: [{text: "Goodbye", url: "goodbye"}]};
|
||||
var helpers = {link: function(prefix) {
|
||||
@@ -779,14 +772,14 @@ test("helpers take precedence over same-named context properties", function() {
|
||||
var helpers = {
|
||||
goodbye: function() {
|
||||
return this.goodbye.toUpperCase();
|
||||
},
|
||||
|
||||
cruel: function(world) {
|
||||
return "cruel " + world.toUpperCase();
|
||||
}
|
||||
};
|
||||
|
||||
var context = {
|
||||
cruel: function(world) {
|
||||
return "cruel " + world.toUpperCase();
|
||||
},
|
||||
|
||||
goodbye: "goodbye",
|
||||
world: "world"
|
||||
};
|
||||
@@ -795,20 +788,20 @@ test("helpers take precedence over same-named context properties", function() {
|
||||
equals(result, "GOODBYE cruel WORLD", "Helper executed");
|
||||
});
|
||||
|
||||
test("helpers take precedence over same-named context properties", function() {
|
||||
test("helpers take precedence over same-named context properties$", function() {
|
||||
var template = CompilerContext.compile("{{#goodbye}} {{cruel world}}{{/goodbye}}");
|
||||
|
||||
var helpers = {
|
||||
goodbye: function(options) {
|
||||
return this.goodbye.toUpperCase() + options.fn(this);
|
||||
},
|
||||
|
||||
cruel: function(world) {
|
||||
return "cruel " + world.toUpperCase();
|
||||
}
|
||||
};
|
||||
|
||||
var context = {
|
||||
cruel: function(world) {
|
||||
return "cruel " + world.toUpperCase();
|
||||
},
|
||||
|
||||
goodbye: "goodbye",
|
||||
world: "world"
|
||||
};
|
||||
@@ -823,14 +816,14 @@ test("Scoped names take precedence over helpers", function() {
|
||||
var helpers = {
|
||||
goodbye: function() {
|
||||
return this.goodbye.toUpperCase();
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
var context = {
|
||||
cruel: function(world) {
|
||||
return "cruel " + world.toUpperCase();
|
||||
},
|
||||
};
|
||||
|
||||
var context = {
|
||||
goodbye: "goodbye",
|
||||
world: "world"
|
||||
};
|
||||
@@ -845,14 +838,14 @@ test("Scoped names take precedence over block helpers", function() {
|
||||
var helpers = {
|
||||
goodbye: function(options) {
|
||||
return this.goodbye.toUpperCase() + options.fn(this);
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
var context = {
|
||||
cruel: function(world) {
|
||||
return "cruel " + world.toUpperCase();
|
||||
},
|
||||
};
|
||||
|
||||
var context = {
|
||||
goodbye: "goodbye",
|
||||
world: "world"
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user