summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lib/handlebars/compiler/ast.js24
-rw-r--r--lib/handlebars/compiler/compiler.js90
-rw-r--r--lib/handlebars/compiler/javascript-compiler.js42
-rw-r--r--lib/handlebars/compiler/printer.js12
-rw-r--r--spec/ast.js14
-rw-r--r--spec/string-params.js16
-rw-r--r--spec/subexpressions.js166
-rw-r--r--spec/tokenizer.js27
-rw-r--r--src/handlebars.l7
-rw-r--r--src/handlebars.yy16
10 files changed, 329 insertions, 85 deletions
diff --git a/lib/handlebars/compiler/ast.js b/lib/handlebars/compiler/ast.js
index b6c3a03..ce5ee11 100644
--- a/lib/handlebars/compiler/ast.js
+++ b/lib/handlebars/compiler/ast.js
@@ -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);
diff --git a/lib/handlebars/compiler/compiler.js b/lib/handlebars/compiler/compiler.js
index 259c543..00353a8 100644
--- a/lib/handlebars/compiler/compiler.js
+++ b/lib/handlebars/compiler/compiler.js
@@ -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);
-
- if (type === "simple") {
- this.simpleMustache(mustache);
- } else if (type === "helper") {
- this.helperMustache(mustache);
- } else {
- this.ambiguousMustache(mustache);
- }
+ this.sexpr(mustache.sexpr);
- 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');
}
diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js
index 159a38b..d920d52 100644
--- a/lib/handlebars/compiler/javascript-compiler.js
+++ b/lib/handlebars/compiler/javascript-compiler.js
@@ -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(",") + "}");
}
};
diff --git a/lib/handlebars/compiler/printer.js b/lib/handlebars/compiler/printer.js
index f91ff02..ad55c7d 100644
--- a/lib/handlebars/compiler/printer.js
+++ b/lib/handlebars/compiler/printer.js
@@ -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.accept(sexpr.id) + " " + params + hash;
+};
- return this.pad("{{ " + this.accept(mustache.id) + " " + params + hash + " }}");
+PrintVisitor.prototype.mustache = function(mustache) {
+ return this.pad("{{ " + this.accept(mustache.sexpr) + " }}");
};
PrintVisitor.prototype.partial = function(partial) {
diff --git a/spec/ast.js b/spec/ast.js
index 0feb3f5..0cb1103 100644
--- a/spec/ast.js
+++ b/spec/ast.js
@@ -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: {},
diff --git a/spec/string-params.js b/spec/string-params.js
index 1ebb583..920b855 100644
--- a/spec/string-params.js
+++ b/spec/string-params.js
@@ -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");
+ });
});
diff --git a/spec/subexpressions.js b/spec/subexpressions.js
new file mode 100644
index 0000000..9c06f94
--- /dev/null
+++ b/spec/subexpressions.js
@@ -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!");
+ });
+ });
+});
diff --git a/spec/tokenizer.js b/spec/tokenizer.js
index 80f28ab..841a5ab 100644
--- a/spec/tokenizer.js
+++ b/spec/tokenizer.js
@@ -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']);
+ });
});
diff --git a/src/handlebars.l b/src/handlebars.l
index 913121c..996badb 100644
--- a/src/handlebars.l
+++ b/src/handlebars.l
@@ -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';
diff --git a/src/handlebars.yy b/src/handlebars.yy
index 63de17b..319b8ef 100644
--- a/src/handlebars.yy
+++ b/src/handlebars.yy
@@ -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