Merge pull request #690 from machty/subsexpr
Added support for subexpressions
This commit is contained in:
@@ -46,7 +46,6 @@ var AST = {
|
||||
MustacheNode: function(rawParams, hash, open, strip, locInfo) {
|
||||
LocationInfo.call(this, locInfo);
|
||||
this.type = "mustache";
|
||||
this.hash = hash;
|
||||
this.strip = strip;
|
||||
|
||||
// Open may be a string parsed from the parser or a passed boolean flag
|
||||
@@ -58,6 +57,25 @@ var AST = {
|
||||
this.escaped = !!open;
|
||||
}
|
||||
|
||||
if (rawParams instanceof AST.SexprNode) {
|
||||
this.sexpr = rawParams;
|
||||
} else {
|
||||
// Support old AST API
|
||||
this.sexpr = new AST.SexprNode(rawParams, hash);
|
||||
}
|
||||
|
||||
// Support old AST API that stored this info in MustacheNode
|
||||
this.id = this.sexpr.id;
|
||||
this.params = this.sexpr.params;
|
||||
this.hash = this.sexpr.hash;
|
||||
this.eligibleHelper = this.sexpr.eligibleHelper;
|
||||
this.isHelper = this.sexpr.isHelper;
|
||||
},
|
||||
|
||||
SexprNode: function(rawParams, hash) {
|
||||
this.type = "sexpr";
|
||||
this.hash = hash;
|
||||
|
||||
var id = this.id = rawParams[0];
|
||||
var params = this.params = rawParams.slice(1);
|
||||
|
||||
@@ -84,8 +102,8 @@ var AST = {
|
||||
},
|
||||
|
||||
BlockNode: function(mustache, program, inverse, close, locInfo) {
|
||||
if(mustache.id.original !== close.path.original) {
|
||||
throw new Exception(mustache.id.original + " doesn't match " + close.path.original);
|
||||
if(mustache.sexpr.id.original !== close.path.original) {
|
||||
throw new Exception(mustache.sexpr.id.original + " doesn't match " + close.path.original);
|
||||
}
|
||||
|
||||
LocationInfo.call(this, locInfo);
|
||||
|
||||
@@ -156,12 +156,13 @@ Compiler.prototype = {
|
||||
inverse = this.compileProgram(inverse);
|
||||
}
|
||||
|
||||
var type = this.classifyMustache(mustache);
|
||||
var sexpr = mustache.sexpr;
|
||||
var type = this.classifySexpr(sexpr);
|
||||
|
||||
if (type === "helper") {
|
||||
this.helperMustache(mustache, program, inverse);
|
||||
this.helperSexpr(sexpr, program, inverse);
|
||||
} else if (type === "simple") {
|
||||
this.simpleMustache(mustache);
|
||||
this.simpleSexpr(sexpr);
|
||||
|
||||
// now that the simple mustache is resolved, we need to
|
||||
// evaluate it by executing `blockHelperMissing`
|
||||
@@ -170,7 +171,7 @@ Compiler.prototype = {
|
||||
this.opcode('emptyHash');
|
||||
this.opcode('blockValue');
|
||||
} else {
|
||||
this.ambiguousMustache(mustache, program, inverse);
|
||||
this.ambiguousSexpr(sexpr, program, inverse);
|
||||
|
||||
// now that the simple mustache is resolved, we need to
|
||||
// evaluate it by executing `blockHelperMissing`
|
||||
@@ -198,6 +199,12 @@ Compiler.prototype = {
|
||||
}
|
||||
this.opcode('getContext', val.depth || 0);
|
||||
this.opcode('pushStringParam', val.stringModeValue, val.type);
|
||||
|
||||
if (val.type === 'sexpr') {
|
||||
// Subexpressions get evaluated and passed in
|
||||
// in string params mode.
|
||||
this.sexpr(val);
|
||||
}
|
||||
} else {
|
||||
this.accept(val);
|
||||
}
|
||||
@@ -226,26 +233,17 @@ Compiler.prototype = {
|
||||
},
|
||||
|
||||
mustache: function(mustache) {
|
||||
var options = this.options;
|
||||
var type = this.classifyMustache(mustache);
|
||||
this.sexpr(mustache.sexpr);
|
||||
|
||||
if (type === "simple") {
|
||||
this.simpleMustache(mustache);
|
||||
} else if (type === "helper") {
|
||||
this.helperMustache(mustache);
|
||||
} else {
|
||||
this.ambiguousMustache(mustache);
|
||||
}
|
||||
|
||||
if(mustache.escaped && !options.noEscape) {
|
||||
if(mustache.escaped && !this.options.noEscape) {
|
||||
this.opcode('appendEscaped');
|
||||
} else {
|
||||
this.opcode('append');
|
||||
}
|
||||
},
|
||||
|
||||
ambiguousMustache: function(mustache, program, inverse) {
|
||||
var id = mustache.id,
|
||||
ambiguousSexpr: function(sexpr, program, inverse) {
|
||||
var id = sexpr.id,
|
||||
name = id.parts[0],
|
||||
isBlock = program != null || inverse != null;
|
||||
|
||||
@@ -257,8 +255,8 @@ Compiler.prototype = {
|
||||
this.opcode('invokeAmbiguous', name, isBlock);
|
||||
},
|
||||
|
||||
simpleMustache: function(mustache) {
|
||||
var id = mustache.id;
|
||||
simpleSexpr: function(sexpr) {
|
||||
var id = sexpr.id;
|
||||
|
||||
if (id.type === 'DATA') {
|
||||
this.DATA(id);
|
||||
@@ -274,9 +272,9 @@ Compiler.prototype = {
|
||||
this.opcode('resolvePossibleLambda');
|
||||
},
|
||||
|
||||
helperMustache: function(mustache, program, inverse) {
|
||||
var params = this.setupFullMustacheParams(mustache, program, inverse),
|
||||
name = mustache.id.parts[0];
|
||||
helperSexpr: function(sexpr, program, inverse) {
|
||||
var params = this.setupFullMustacheParams(sexpr, program, inverse),
|
||||
name = sexpr.id.parts[0];
|
||||
|
||||
if (this.options.knownHelpers[name]) {
|
||||
this.opcode('invokeKnownHelper', params.length, name);
|
||||
@@ -287,6 +285,18 @@ Compiler.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
sexpr: function(sexpr) {
|
||||
var type = this.classifySexpr(sexpr);
|
||||
|
||||
if (type === "simple") {
|
||||
this.simpleSexpr(sexpr);
|
||||
} else if (type === "helper") {
|
||||
this.helperSexpr(sexpr);
|
||||
} else {
|
||||
this.ambiguousSexpr(sexpr);
|
||||
}
|
||||
},
|
||||
|
||||
ID: function(id) {
|
||||
this.addDepth(id.depth);
|
||||
this.opcode('getContext', id.depth);
|
||||
@@ -349,14 +359,14 @@ Compiler.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
classifyMustache: function(mustache) {
|
||||
var isHelper = mustache.isHelper;
|
||||
var isEligible = mustache.eligibleHelper;
|
||||
classifySexpr: function(sexpr) {
|
||||
var isHelper = sexpr.isHelper;
|
||||
var isEligible = sexpr.eligibleHelper;
|
||||
var options = this.options;
|
||||
|
||||
// if ambiguous, we can possibly resolve the ambiguity now
|
||||
if (isEligible && !isHelper) {
|
||||
var name = mustache.id.parts[0];
|
||||
var name = sexpr.id.parts[0];
|
||||
|
||||
if (options.knownHelpers[name]) {
|
||||
isHelper = true;
|
||||
@@ -383,35 +393,27 @@ Compiler.prototype = {
|
||||
|
||||
this.opcode('getContext', param.depth || 0);
|
||||
this.opcode('pushStringParam', param.stringModeValue, param.type);
|
||||
|
||||
if (param.type === 'sexpr') {
|
||||
// Subexpressions get evaluated and passed in
|
||||
// in string params mode.
|
||||
this.sexpr(param);
|
||||
}
|
||||
} 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('emptyHash');
|
||||
}
|
||||
|
||||
return params;
|
||||
},
|
||||
|
||||
// this will replace setupMustacheParams when we're done
|
||||
setupFullMustacheParams: function(mustache, program, inverse) {
|
||||
var params = mustache.params;
|
||||
setupFullMustacheParams: function(sexpr, program, inverse) {
|
||||
var params = sexpr.params;
|
||||
this.pushParams(params);
|
||||
|
||||
this.opcode('pushProgram', program);
|
||||
this.opcode('pushProgram', inverse);
|
||||
|
||||
if(mustache.hash) {
|
||||
this.hash(mustache.hash);
|
||||
if (sexpr.hash) {
|
||||
this.hash(sexpr.hash);
|
||||
} else {
|
||||
this.opcode('emptyHash');
|
||||
}
|
||||
|
||||
@@ -244,9 +244,6 @@ JavaScriptCompiler.prototype = {
|
||||
var current = this.topStack();
|
||||
params.splice(1, 0, current);
|
||||
|
||||
// Use the options value generated from the invocation
|
||||
params[params.length-1] = 'options';
|
||||
|
||||
this.pushSource("if (!" + this.lastHelper + ") { " + current + " = blockHelperMissing.call(" + params.join(", ") + "); }");
|
||||
},
|
||||
|
||||
@@ -398,10 +395,14 @@ JavaScriptCompiler.prototype = {
|
||||
|
||||
this.pushString(type);
|
||||
|
||||
if (typeof string === 'string') {
|
||||
this.pushString(string);
|
||||
} else {
|
||||
this.pushStackLiteral(string);
|
||||
// If it's a subexpression, the string result
|
||||
// will be pushed after this opcode.
|
||||
if (type !== 'sexpr') {
|
||||
if (typeof string === 'string') {
|
||||
this.pushString(string);
|
||||
} else {
|
||||
this.pushStackLiteral(string);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -409,8 +410,8 @@ JavaScriptCompiler.prototype = {
|
||||
this.pushStackLiteral('{}');
|
||||
|
||||
if (this.options.stringParams) {
|
||||
this.register('hashTypes', '{}');
|
||||
this.register('hashContexts', '{}');
|
||||
this.push('{}'); // hashContexts
|
||||
this.push('{}'); // hashTypes
|
||||
}
|
||||
},
|
||||
pushHash: function() {
|
||||
@@ -421,9 +422,10 @@ JavaScriptCompiler.prototype = {
|
||||
this.hash = undefined;
|
||||
|
||||
if (this.options.stringParams) {
|
||||
this.register('hashContexts', '{' + hash.contexts.join(',') + '}');
|
||||
this.register('hashTypes', '{' + hash.types.join(',') + '}');
|
||||
this.push('{' + hash.contexts.join(',') + '}');
|
||||
this.push('{' + hash.types.join(',') + '}');
|
||||
}
|
||||
|
||||
this.push('{\n ' + hash.values.join(',\n ') + '\n }');
|
||||
},
|
||||
|
||||
@@ -526,7 +528,7 @@ JavaScriptCompiler.prototype = {
|
||||
invokeAmbiguous: function(name, helperCall) {
|
||||
this.context.aliases.functionType = '"function"';
|
||||
|
||||
this.pushStackLiteral('{}'); // Hash value
|
||||
this.emptyHash();
|
||||
var helper = this.setupHelper(0, name, helperCall);
|
||||
|
||||
var helperName = this.lastHelper = this.nameLookup('helpers', name, 'helper');
|
||||
@@ -805,6 +807,11 @@ JavaScriptCompiler.prototype = {
|
||||
|
||||
options.push("hash:" + this.popStack());
|
||||
|
||||
if (this.options.stringParams) {
|
||||
options.push("hashTypes:" + this.popStack());
|
||||
options.push("hashContexts:" + this.popStack());
|
||||
}
|
||||
|
||||
inverse = this.popStack();
|
||||
program = this.popStack();
|
||||
|
||||
@@ -838,22 +845,13 @@ JavaScriptCompiler.prototype = {
|
||||
if (this.options.stringParams) {
|
||||
options.push("contexts:[" + contexts.join(",") + "]");
|
||||
options.push("types:[" + types.join(",") + "]");
|
||||
options.push("hashContexts:hashContexts");
|
||||
options.push("hashTypes:hashTypes");
|
||||
}
|
||||
|
||||
if(this.options.data) {
|
||||
options.push("data:data");
|
||||
}
|
||||
|
||||
options = "{" + options.join(",") + "}";
|
||||
if (useRegister) {
|
||||
this.register('options', options);
|
||||
params.push('options');
|
||||
} else {
|
||||
params.push(options);
|
||||
}
|
||||
return params.join(", ");
|
||||
params.push("{" + options.join(",") + "}");
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -62,8 +62,8 @@ PrintVisitor.prototype.block = function(block) {
|
||||
return out;
|
||||
};
|
||||
|
||||
PrintVisitor.prototype.mustache = function(mustache) {
|
||||
var params = mustache.params, paramStrings = [], hash;
|
||||
PrintVisitor.prototype.sexpr = function(sexpr) {
|
||||
var params = sexpr.params, paramStrings = [], hash;
|
||||
|
||||
for(var i=0, l=params.length; i<l; i++) {
|
||||
paramStrings.push(this.accept(params[i]));
|
||||
@@ -71,9 +71,13 @@ PrintVisitor.prototype.mustache = function(mustache) {
|
||||
|
||||
params = "[" + paramStrings.join(", ") + "]";
|
||||
|
||||
hash = mustache.hash ? " " + this.accept(mustache.hash) : "";
|
||||
hash = sexpr.hash ? " " + this.accept(sexpr.hash) : "";
|
||||
|
||||
return this.pad("{{ " + this.accept(mustache.id) + " " + params + hash + " }}");
|
||||
return this.accept(sexpr.id) + " " + params + hash;
|
||||
};
|
||||
|
||||
PrintVisitor.prototype.mustache = function(mustache) {
|
||||
return this.pad("{{ " + this.accept(mustache.sexpr) + " }}");
|
||||
};
|
||||
|
||||
PrintVisitor.prototype.partial = function(partial) {
|
||||
|
||||
+12
-2
@@ -68,14 +68,24 @@ describe('ast', function() {
|
||||
});
|
||||
});
|
||||
describe('BlockNode', function() {
|
||||
it('should throw on mustache mismatch (old sexpr-less version)', function() {
|
||||
shouldThrow(function() {
|
||||
var mustacheNode = new handlebarsEnv.AST.MustacheNode([{ original: 'foo'}], null, '{{', {});
|
||||
new handlebarsEnv.AST.BlockNode(mustacheNode, {}, {}, {path: {original: 'bar'}});
|
||||
}, Handlebars.Exception, "foo doesn't match bar");
|
||||
});
|
||||
it('should throw on mustache mismatch', function() {
|
||||
shouldThrow(function() {
|
||||
new handlebarsEnv.AST.BlockNode({id: {original: 'foo'}}, {}, {}, {path: {original: 'bar'}});
|
||||
var sexprNode = new handlebarsEnv.AST.SexprNode([{ original: 'foo'}], null);
|
||||
var mustacheNode = new handlebarsEnv.AST.MustacheNode(sexprNode, null, '{{', {});
|
||||
new handlebarsEnv.AST.BlockNode(mustacheNode, {}, {}, {path: {original: 'bar'}});
|
||||
}, Handlebars.Exception, "foo doesn't match bar");
|
||||
});
|
||||
|
||||
it('stores location info', function(){
|
||||
var block = new handlebarsEnv.AST.BlockNode({strip: {}, id: {original: 'foo'}},
|
||||
var sexprNode = new handlebarsEnv.AST.SexprNode([{ original: 'foo'}], null);
|
||||
var mustacheNode = new handlebarsEnv.AST.MustacheNode(sexprNode, null, '{{', {});
|
||||
var block = new handlebarsEnv.AST.BlockNode(mustacheNode,
|
||||
{strip: {}}, {strip: {}},
|
||||
{
|
||||
strip: {},
|
||||
|
||||
@@ -142,4 +142,20 @@ describe('string params mode', function() {
|
||||
|
||||
equals(result, "STOP ME FROM READING HACKER NEWS I need-a dad.joke wot", "Proper context variable output");
|
||||
});
|
||||
|
||||
it("with nested block ambiguous", function() {
|
||||
var template = CompilerContext.compile('{{#with content}}{{#view}}{{firstName}} {{lastName}}{{/view}}{{/with}}', {stringParams: true});
|
||||
|
||||
var helpers = {
|
||||
with: function(options) {
|
||||
return "WITH";
|
||||
},
|
||||
view: function() {
|
||||
return "VIEW";
|
||||
}
|
||||
};
|
||||
|
||||
var result = template({}, {helpers: helpers});
|
||||
equals(result, "WITH");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
/*global CompilerContext, shouldCompileTo */
|
||||
describe('subexpressions', function() {
|
||||
it("arg-less helper", function() {
|
||||
var string = "{{foo (bar)}}!";
|
||||
var context = {};
|
||||
var helpers = {
|
||||
foo: function(val) {
|
||||
return val+val;
|
||||
},
|
||||
bar: function() {
|
||||
return "LOL";
|
||||
}
|
||||
};
|
||||
shouldCompileTo(string, [context, helpers], "LOLLOL!");
|
||||
});
|
||||
|
||||
it("helper w args", function() {
|
||||
var string = '{{blog (equal a b)}}';
|
||||
|
||||
var context = { bar: "LOL" };
|
||||
var helpers = {
|
||||
blog: function(val) {
|
||||
return "val is " + val;
|
||||
},
|
||||
equal: function(x, y) {
|
||||
return x === y;
|
||||
}
|
||||
};
|
||||
shouldCompileTo(string, [context, helpers], "val is true");
|
||||
});
|
||||
|
||||
it("supports much nesting", function() {
|
||||
var string = '{{blog (equal (equal true true) true)}}';
|
||||
|
||||
var context = { bar: "LOL" };
|
||||
var helpers = {
|
||||
blog: function(val) {
|
||||
return "val is " + val;
|
||||
},
|
||||
equal: function(x, y) {
|
||||
return x === y;
|
||||
}
|
||||
};
|
||||
shouldCompileTo(string, [context, helpers], "val is true");
|
||||
});
|
||||
|
||||
it("provides each nested helper invocation its own options hash", function() {
|
||||
var string = '{{equal (equal true true) true}}';
|
||||
|
||||
var lastOptions = null;
|
||||
var helpers = {
|
||||
equal: function(x, y, options) {
|
||||
if (!options || options === lastOptions) {
|
||||
throw new Error("options hash was reused");
|
||||
}
|
||||
lastOptions = options;
|
||||
return x === y;
|
||||
}
|
||||
};
|
||||
shouldCompileTo(string, [{}, helpers], "true");
|
||||
});
|
||||
|
||||
it("with hashes", function() {
|
||||
var string = '{{blog (equal (equal true true) true fun="yes")}}';
|
||||
|
||||
var context = { bar: "LOL" };
|
||||
var helpers = {
|
||||
blog: function(val) {
|
||||
return "val is " + val;
|
||||
},
|
||||
equal: function(x, y) {
|
||||
return x === y;
|
||||
}
|
||||
};
|
||||
shouldCompileTo(string, [context, helpers], "val is true");
|
||||
});
|
||||
|
||||
it("as hashes", function() {
|
||||
var string = '{{blog fun=(equal true true)}}';
|
||||
|
||||
var helpers = {
|
||||
blog: function(options) {
|
||||
return "val is " + options.hash.fun;
|
||||
},
|
||||
equal: function(x, y) {
|
||||
return x === y;
|
||||
}
|
||||
};
|
||||
shouldCompileTo(string, [{}, helpers], "val is true");
|
||||
});
|
||||
|
||||
it("in string params mode,", function() {
|
||||
var template = CompilerContext.compile('{{snog (blorg foo x=y) yeah a=b}}', {stringParams: true});
|
||||
|
||||
var helpers = {
|
||||
snog: function(a, b, options) {
|
||||
equals(a, 'foo');
|
||||
equals(options.types.length, 2, "string params for outer helper processed correctly");
|
||||
equals(options.types[0], 'sexpr', "string params for outer helper processed correctly");
|
||||
equals(options.types[1], 'ID', "string params for outer helper processed correctly");
|
||||
return a + b;
|
||||
},
|
||||
|
||||
blorg: function(a, options) {
|
||||
equals(options.types.length, 1, "string params for inner helper processed correctly");
|
||||
equals(options.types[0], 'ID', "string params for inner helper processed correctly");
|
||||
return a;
|
||||
}
|
||||
};
|
||||
|
||||
var result = template({
|
||||
foo: {},
|
||||
yeah: {}
|
||||
}, {helpers: helpers});
|
||||
|
||||
equals(result, "fooyeah");
|
||||
});
|
||||
|
||||
it("as hashes in string params mode", function() {
|
||||
|
||||
var template = CompilerContext.compile('{{blog fun=(bork)}}', {stringParams: true});
|
||||
|
||||
var helpers = {
|
||||
blog: function(options) {
|
||||
equals(options.hashTypes.fun, 'sexpr');
|
||||
return "val is " + options.hash.fun;
|
||||
},
|
||||
bork: function() {
|
||||
return "BORK";
|
||||
}
|
||||
};
|
||||
|
||||
var result = template({}, {helpers: helpers});
|
||||
equals(result, "val is BORK");
|
||||
});
|
||||
|
||||
it("subexpression functions on the context", function() {
|
||||
var string = "{{foo (bar)}}!";
|
||||
var context = {
|
||||
bar: function() {
|
||||
return "LOL";
|
||||
}
|
||||
};
|
||||
var helpers = {
|
||||
foo: function(val) {
|
||||
return val+val;
|
||||
}
|
||||
};
|
||||
shouldCompileTo(string, [context, helpers], "LOLLOL!");
|
||||
});
|
||||
|
||||
it("subexpressions can't just be property lookups", function() {
|
||||
var string = "{{foo (bar)}}!";
|
||||
var context = {
|
||||
bar: "LOL"
|
||||
};
|
||||
var helpers = {
|
||||
foo: function(val) {
|
||||
return val+val;
|
||||
}
|
||||
};
|
||||
shouldThrow(function() {
|
||||
shouldCompileTo(string, [context, helpers], "LOLLOL!");
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -364,4 +364,31 @@ describe('Tokenizer', function() {
|
||||
it('does not time out in a mustache when invalid ID characters are used', function() {
|
||||
shouldMatchTokens(tokenize("{{foo & }}"), ['OPEN', 'ID']);
|
||||
});
|
||||
|
||||
it('tokenizes subexpressions', function() {
|
||||
var result = tokenize("{{foo (bar)}}");
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'OPEN_SEXPR', 'ID', 'CLOSE_SEXPR', 'CLOSE']);
|
||||
shouldBeToken(result[1], "ID", "foo");
|
||||
shouldBeToken(result[3], "ID", "bar");
|
||||
|
||||
result = tokenize("{{foo (a-x b-y)}}");
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'OPEN_SEXPR', 'ID', 'ID', 'CLOSE_SEXPR', 'CLOSE']);
|
||||
shouldBeToken(result[1], "ID", "foo");
|
||||
shouldBeToken(result[3], "ID", "a-x");
|
||||
shouldBeToken(result[4], "ID", "b-y");
|
||||
});
|
||||
|
||||
it('tokenizes nested subexpressions', function() {
|
||||
var result = tokenize("{{foo (bar (lol rofl)) (baz)}}");
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'OPEN_SEXPR', 'ID', 'OPEN_SEXPR', 'ID', 'ID', 'CLOSE_SEXPR', 'CLOSE_SEXPR', 'OPEN_SEXPR', 'ID', 'CLOSE_SEXPR', 'CLOSE']);
|
||||
shouldBeToken(result[3], "ID", "bar");
|
||||
shouldBeToken(result[5], "ID", "lol");
|
||||
shouldBeToken(result[6], "ID", "rofl");
|
||||
shouldBeToken(result[10], "ID", "baz");
|
||||
});
|
||||
|
||||
it('tokenizes nested subexpressions: literals', function() {
|
||||
var result = tokenize("{{foo (bar (lol true) false) (baz 1) (blah 'b') (blorg \"c\")}}");
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'OPEN_SEXPR', 'ID', 'OPEN_SEXPR', 'ID', 'BOOLEAN', 'CLOSE_SEXPR', 'BOOLEAN', 'CLOSE_SEXPR', 'OPEN_SEXPR', 'ID', 'INTEGER', 'CLOSE_SEXPR', 'OPEN_SEXPR', 'ID', 'STRING', 'CLOSE_SEXPR', 'OPEN_SEXPR', 'ID', 'STRING', 'CLOSE_SEXPR', 'CLOSE']);
|
||||
});
|
||||
});
|
||||
|
||||
+5
-2
@@ -12,8 +12,8 @@ function strip(start, end) {
|
||||
LEFT_STRIP "~"
|
||||
RIGHT_STRIP "~"
|
||||
|
||||
LOOKAHEAD [=~}\s\/.]
|
||||
LITERAL_LOOKAHEAD [~}\s]
|
||||
LOOKAHEAD [=~}\s\/.)]
|
||||
LITERAL_LOOKAHEAD [~}\s)]
|
||||
|
||||
/*
|
||||
ID is the inverse of control characters.
|
||||
@@ -51,6 +51,9 @@ ID [^\s!"#%-,\.\/;->@\[-\^`\{-~]+/{LOOKAHEAD}
|
||||
|
||||
<com>[\s\S]*?"--}}" strip(0,4); this.popState(); return 'COMMENT';
|
||||
|
||||
<mu>"(" return 'OPEN_SEXPR';
|
||||
<mu>")" return 'CLOSE_SEXPR';
|
||||
|
||||
<mu>"{{"{LEFT_STRIP}?">" return 'OPEN_PARTIAL';
|
||||
<mu>"{{"{LEFT_STRIP}?"#" return 'OPEN_BLOCK';
|
||||
<mu>"{{"{LEFT_STRIP}?"/" return 'OPEN_ENDBLOCK';
|
||||
|
||||
+8
-8
@@ -44,11 +44,11 @@ statement
|
||||
;
|
||||
|
||||
openBlock
|
||||
: OPEN_BLOCK inMustache CLOSE -> new yy.MustacheNode($2[0], $2[1], $1, stripFlags($1, $3), @$)
|
||||
: OPEN_BLOCK sexpr CLOSE -> new yy.MustacheNode($2, null, $1, stripFlags($1, $3), @$)
|
||||
;
|
||||
|
||||
openInverse
|
||||
: OPEN_INVERSE inMustache CLOSE -> new yy.MustacheNode($2[0], $2[1], $1, stripFlags($1, $3), @$)
|
||||
: OPEN_INVERSE sexpr CLOSE -> new yy.MustacheNode($2, null, $1, stripFlags($1, $3), @$)
|
||||
;
|
||||
|
||||
closeBlock
|
||||
@@ -58,11 +58,10 @@ closeBlock
|
||||
mustache
|
||||
// Parsing out the '&' escape token at AST level saves ~500 bytes after min due to the removal of one parser node.
|
||||
// This also allows for handler unification as all mustache node instances can utilize the same handler
|
||||
: OPEN inMustache CLOSE -> new yy.MustacheNode($2[0], $2[1], $1, stripFlags($1, $3), @$)
|
||||
| OPEN_UNESCAPED inMustache CLOSE_UNESCAPED -> new yy.MustacheNode($2[0], $2[1], $1, stripFlags($1, $3), @$)
|
||||
: OPEN sexpr CLOSE -> new yy.MustacheNode($2, null, $1, stripFlags($1, $3), @$)
|
||||
| OPEN_UNESCAPED sexpr CLOSE_UNESCAPED -> new yy.MustacheNode($2, null, $1, stripFlags($1, $3), @$)
|
||||
;
|
||||
|
||||
|
||||
partial
|
||||
: OPEN_PARTIAL partialName path? CLOSE -> new yy.PartialNode($2, $3, stripFlags($1, $4), @$)
|
||||
;
|
||||
@@ -71,9 +70,9 @@ simpleInverse
|
||||
: OPEN_INVERSE CLOSE -> stripFlags($1, $2)
|
||||
;
|
||||
|
||||
inMustache
|
||||
: path param* hash? -> [[$1].concat($2), $3]
|
||||
| dataName -> [[$1], null]
|
||||
sexpr
|
||||
: path param* hash? -> new yy.SexprNode([$1].concat($2), $3)
|
||||
| dataName -> new yy.SexprNode([$1], null)
|
||||
;
|
||||
|
||||
param
|
||||
@@ -82,6 +81,7 @@ param
|
||||
| INTEGER -> new yy.IntegerNode($1, @$)
|
||||
| BOOLEAN -> new yy.BooleanNode($1, @$)
|
||||
| dataName -> $1
|
||||
| OPEN_SEXPR sexpr CLOSE_SEXPR {$2.isHelper = true; $$ = $2;}
|
||||
;
|
||||
|
||||
hash
|
||||
|
||||
Reference in New Issue
Block a user