complete Spacebars code gen

This commit is contained in:
David Greenspan
2013-06-17 13:46:08 -07:00
parent efa83a5167
commit 8d92d53bf8
2 changed files with 89 additions and 15 deletions

View File

@@ -427,14 +427,16 @@ Spacebars.parse = function (inputString) {
while (stacheTags[i] !== b.closeTag)
i++;
} else if (t.type === 'BLOCKCLOSE') {
var name = t.path.join('.');
if (isTopLevel)
throw new Error("Unexpected close tag `" +t.name + "` at " +
throw new Error("Unexpected close tag `" + name + "` at " +
prettyOffset(inputString, t.charPos));
if (t.name !== block.openTag.name)
if (name !== block.openTag.path.join('.'))
throw new Error("Close tag at " +
prettyOffset(inputString, t.charPos) +
" doesn't match `" + block.openTag.name +
"`, found `" + t.name + "`");
" doesn't match `" +
block.openTag.path.join('.') +
"`, found `" + name + "`");
block.closeTag = t;
} else if (t.type === 'ELSE') {
if (isTopLevel)
@@ -574,11 +576,19 @@ Spacebars.compile = function (inputString) {
};
// returns: array of source strings, or null if no
// args at all
var codeGenArgs = function (tagArgs, funcInfo, forComponent) {
// args at all.
// if forComponentWithOpts is truthy, perform
// component invocation argument handling.
// forComponentWithOpts is a map from name of keyword
// argument to source code. For example,
// `{ bodyClass: "Component.extend(..." }`.
var codeGenArgs = function (tagArgs, funcInfo,
forComponentWithOpts) {
var options = null; // source -> source
var args = null; // [source]
var forComponent = !! forComponentWithOpts;
_.each(tagArgs, function (arg, i) {
var argType = arg[0];
var argValue = arg[1];
@@ -602,7 +612,10 @@ Spacebars.compile = function (inputString) {
if (arg.length > 2) {
// keyword argument
options = (options || {});
options[toJSLiteral(arg[2])] = argCode;
if (! (forComponentWithOpts &&
(arg[2] in forComponentWithOpts))) {
options[toJSLiteral(arg[2])] = argCode;
}
} else {
// positional argument
if (forComponent) {
@@ -620,6 +633,11 @@ Spacebars.compile = function (inputString) {
});
if (forComponent) {
_.each(forComponentWithOpts, function (v, k) {
options = (options || {});
options[toJSLiteral(k)] = v;
});
// components get one argument, the options dictionary
args = [options ? makeObjectLiteral(options) : '{}'];
} else {
@@ -709,15 +727,33 @@ Spacebars.compile = function (inputString) {
// tag or block
var tag = tagOrStr;
if (tag.isBlock) {
// XXX implement
var block = tag;
var nameCode = codeGenPath(
block.openTag.path, funcInfo);
var extraArgs = {
bodyClass: 'Component.extend({render: ' +
tokensToRenderFunc(block.bodyTokens, indent) +
'})'
};
if (block.elseTokens) {
extraArgs.elseClass =
'Component.extend({render: ' +
tokensToRenderFunc(block.elseTokens, indent) +
'})';
}
var argCode =
codeGenArgs(block.openTag.args, funcInfo,
extraArgs)[0];
bodyLines.push('buf.component(function () { return ((' + nameCode + ') || EmptyComponent).create(' + argCode +
'); });');
} else {
switch (tag.type) {
case 'INCLUSION':
var nameCode = codeGenPath(tag.path, funcInfo);
var argCode =
codeGenArgs(tag.args, funcInfo, true);
bodyLines.push('buf.component(function () { return ((' + nameCode + ') || EmptyComponent).create(' +
(argCode ? argCode.join(', ') : '') + '); });');
codeGenArgs(tag.args, funcInfo, {})[0];
bodyLines.push('buf.component(function () { return ((' + nameCode + ') || EmptyComponent).create(' + argCode +
'); });');
break;
case 'DOUBLE':
case 'TRIPLE':
@@ -802,8 +838,8 @@ Spacebars.compile = function (inputString) {
(bodyLines ?
(funcInfo.usedSelf ?
'\n' + indent + 'var self = this;' : '') +
'\n' + indent + bodyLines.join('\n' + indent) + '\n' :
'') + '}';
'\n' + indent + bodyLines.join('\n' + indent) + '\n' +
oldIndent : '') + '}';
};
return tokensToRenderFunc(tree.bodyTokens);

View File

@@ -326,8 +326,24 @@ Tinytest.add("spacebars - compiler", function (test) {
var run = function (input/*, expectedLines*/) {
var expectedLines = Array.prototype.slice.call(arguments, 1);
var expected = expectedLines.join('\n');
var output = Spacebars.compile(input);
test.equal(output, expected);
if (arguments[1].fail) {
var expectedMessage = arguments[1].fail;
// test for error starting with expectedMessage
var msg = '';
test.throws(function () {
try {
Spacebars.compile(input);
} catch (e) {
msg = e.message;
throw e;
}
});
test.equal(msg.slice(0, expectedMessage.length),
expectedMessage);
} else {
var output = Spacebars.compile(input);
test.equal(output, expected);
}
};
run('abc',
@@ -408,4 +424,26 @@ Tinytest.add("spacebars - compiler", function (test) {
' buf.component(function () { return ((self.lookup("foo")) || EmptyComponent).create({"data": Spacebars.call(self.lookup("bar")), "baz": Spacebars.call(Spacebars.index(self.lookup("x"), "y"))}); });',
'}');
run('{{#foo.bar}}{{/foo.baz}}', {fail: 'Close tag'});
run('{{/foo.bar}}{{#foo.bar}}', {fail: 'Unexpected close tag'});
run('{{#if foo}}bar{{/if}}',
'function (buf) {',
' var self = this;',
' buf.component(function () { return ((self.lookup("if")) || EmptyComponent).create({"data": Spacebars.call(self.lookup("foo")), "bodyClass": Component.extend({render: function (buf) {',
' buf.text("bar");',
' }})}); });',
'}');
run('{{#if foo}}bar{{else}}baz{{/if}}',
'function (buf) {',
' var self = this;',
' buf.component(function () { return ((self.lookup("if")) || EmptyComponent).create({"data": Spacebars.call(self.lookup("foo")), "bodyClass": Component.extend({render: function (buf) {',
' buf.text("bar");',
' }}), "elseClass": Component.extend({render: function (buf) {',
' buf.text("baz");',
' }})}); });',
'}');
});