summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKevin Decker <kpdecker@gmail.com>2015-01-18 20:13:25 -0600
committerKevin Decker <kpdecker@gmail.com>2015-01-18 20:13:25 -0600
commit9f8daf9076410ddbda9d420336767e1e8f1d0d8f (patch)
tree55d078077b9c23779858282dfe42dd3a42bd7c0d
parentb0b522b4f81baf5ba4c190b59abd2b9cfe82bc77 (diff)
parent884bf1553663734f22ffcd9d758c9d71d4373bf9 (diff)
downloadhandlebars.js-9f8daf9076410ddbda9d420336767e1e8f1d0d8f.zip
handlebars.js-9f8daf9076410ddbda9d420336767e1e8f1d0d8f.tar.gz
handlebars.js-9f8daf9076410ddbda9d420336767e1e8f1d0d8f.tar.bz2
Merge pull request #941 from wycats/dynamic-partial
Add support for dynamic partial names
-rw-r--r--docs/compiler-api.md29
-rw-r--r--lib/handlebars/compiler/ast.js27
-rw-r--r--lib/handlebars/compiler/compiler.js34
-rw-r--r--lib/handlebars/compiler/helpers.js22
-rw-r--r--lib/handlebars/compiler/javascript-compiler.js13
-rw-r--r--lib/handlebars/compiler/printer.js15
-rw-r--r--lib/handlebars/compiler/visitor.js18
-rw-r--r--lib/handlebars/runtime.js15
-rw-r--r--spec/ast.js10
-rw-r--r--spec/data.js11
-rw-r--r--spec/partials.js26
-rw-r--r--spec/visitor.js15
-rw-r--r--src/handlebars.yy31
13 files changed, 178 insertions, 88 deletions
diff --git a/docs/compiler-api.md b/docs/compiler-api.md
index 74af672..3593228 100644
--- a/docs/compiler-api.md
+++ b/docs/compiler-api.md
@@ -55,15 +55,21 @@ interface Statement <: Node { }
interface MustacheStatement <: Statement {
type: "MustacheStatement";
- sexpr: SubExpression;
- escaped: boolean;
+ path: PathExpression;
+ params: [ Expression ];
+ hash: Hash;
+
+ escaped: boolean;
strip: StripFlags | null;
}
interface BlockStatement <: Statement {
type: "BlockStatement";
- sexpr: SubExpression;
+ path: PathExpression;
+ params: [ Expression ];
+ hash: Hash;
+
program: Program | null;
inverse: Program | null;
@@ -74,12 +80,19 @@ interface BlockStatement <: Statement {
interface PartialStatement <: Statement {
type: "PartialStatement";
- sexpr: SubExpression;
+ name: PathExpression | SubExpression;
+ params: [ Expression ];
+ hash: Hash;
indent: string;
strip: StripFlags | null;
}
+```
+
+`name` will be a `SubExpression` when tied to a dynamic partial, i.e. `{{> (foo) }}`, otherwise this is a path or literal whose `original` value is used to lookup the desired partial.
+
+```java
interface ContentStatement <: Statement {
type: "ContentStatement";
value: string;
@@ -108,13 +121,9 @@ interface SubExpression <: Expression {
path: PathExpression;
params: [ Expression ];
hash: Hash;
-
- isHelper: true | null;
}
```
-`isHelper` is not required and is used to disambiguate between cases such as `{{foo}}` and `(foo)`, which have slightly different call behaviors.
-
##### Paths
```java
@@ -195,7 +204,7 @@ function ImportScanner() {
ImportScanner.prototype = new Visitor();
ImportScanner.prototype.PartialStatement = function(partial) {
- this.partials.push({request: partial.sexpr.original});
+ this.partials.push({request: partial.name.original});
Visitor.prototype.PartialStatement.call(this, partial);
};
@@ -221,6 +230,8 @@ The `Handlebars.JavaScriptCompiler` object has a number of methods that may be c
- `name` is the current path component
- `type` is the type of name being evaluated. May be one of `context`, `data`, `helper`, or `partial`.
+ Note that this does not impact dynamic partials, which implementors need to be aware of. Overriding `VM.resolvePartial` may be required to support dynamic cases.
+
- `depthedLookup(name)`
Used to generate code that resolves parameters within any context in the stack. Is only used in `compat` mode.
diff --git a/lib/handlebars/compiler/ast.js b/lib/handlebars/compiler/ast.js
index 113c03e..45212b3 100644
--- a/lib/handlebars/compiler/ast.js
+++ b/lib/handlebars/compiler/ast.js
@@ -8,21 +8,25 @@ var AST = {
this.strip = strip;
},
- MustacheStatement: function(sexpr, escaped, strip, locInfo) {
+ MustacheStatement: function(path, params, hash, escaped, strip, locInfo) {
this.loc = locInfo;
this.type = 'MustacheStatement';
- this.sexpr = sexpr;
+ this.path = path;
+ this.params = params || [];
+ this.hash = hash;
this.escaped = escaped;
this.strip = strip;
},
- BlockStatement: function(sexpr, program, inverse, openStrip, inverseStrip, closeStrip, locInfo) {
+ BlockStatement: function(path, params, hash, program, inverse, openStrip, inverseStrip, closeStrip, locInfo) {
this.loc = locInfo;
-
this.type = 'BlockStatement';
- this.sexpr = sexpr;
+
+ this.path = path;
+ this.params = params || [];
+ this.hash = hash;
this.program = program;
this.inverse = inverse;
@@ -31,12 +35,15 @@ var AST = {
this.closeStrip = closeStrip;
},
- PartialStatement: function(sexpr, strip, locInfo) {
+ PartialStatement: function(name, params, hash, strip, locInfo) {
this.loc = locInfo;
this.type = 'PartialStatement';
- this.sexpr = sexpr;
- this.indent = '';
+ this.name = name;
+ this.params = params || [];
+ this.hash = hash;
+
+ this.indent = '';
this.strip = strip;
},
@@ -112,8 +119,8 @@ var AST = {
// * it is an eligible helper, and
// * it has at least one parameter or hash segment
// TODO: Make these public utility methods
- helperExpression: function(sexpr) {
- return !!(sexpr.isHelper || sexpr.params.length || sexpr.hash);
+ helperExpression: function(node) {
+ return !!(node.type === 'SubExpression' || node.params.length || node.hash);
},
scopedId: function(path) {
diff --git a/lib/handlebars/compiler/compiler.js b/lib/handlebars/compiler/compiler.js
index 95fe101..03bf8f4 100644
--- a/lib/handlebars/compiler/compiler.js
+++ b/lib/handlebars/compiler/compiler.js
@@ -110,28 +110,27 @@ Compiler.prototype = {
},
BlockStatement: function(block) {
- var sexpr = block.sexpr,
- program = block.program,
+ var program = block.program,
inverse = block.inverse;
program = program && this.compileProgram(program);
inverse = inverse && this.compileProgram(inverse);
- var type = this.classifySexpr(sexpr);
+ var type = this.classifySexpr(block);
if (type === 'helper') {
- this.helperSexpr(sexpr, program, inverse);
+ this.helperSexpr(block, program, inverse);
} else if (type === 'simple') {
- this.simpleSexpr(sexpr);
+ this.simpleSexpr(block);
// now that the simple mustache is resolved, we need to
// evaluate it by executing `blockHelperMissing`
this.opcode('pushProgram', program);
this.opcode('pushProgram', inverse);
this.opcode('emptyHash');
- this.opcode('blockValue', sexpr.path.original);
+ this.opcode('blockValue', block.path.original);
} else {
- this.ambiguousSexpr(sexpr, program, inverse);
+ this.ambiguousSexpr(block, program, inverse);
// now that the simple mustache is resolved, we need to
// evaluate it by executing `blockHelperMissing`
@@ -145,29 +144,35 @@ Compiler.prototype = {
},
PartialStatement: function(partial) {
- var partialName = partial.sexpr.path.original;
this.usePartial = true;
- var params = partial.sexpr.params;
+ var params = partial.params;
if (params.length > 1) {
throw new Exception('Unsupported number of partial arguments: ' + params.length, partial);
} else if (!params.length) {
params.push({type: 'PathExpression', parts: [], depth: 0});
}
- this.setupFullMustacheParams(partial.sexpr, undefined, undefined, true);
+ var partialName = partial.name.original,
+ isDynamic = partial.name.type === 'SubExpression';
+ if (isDynamic) {
+ this.accept(partial.name);
+ }
+
+ this.setupFullMustacheParams(partial, undefined, undefined, true);
var indent = partial.indent || '';
if (this.options.preventIndent && indent) {
this.opcode('appendContent', indent);
indent = '';
}
- this.opcode('invokePartial', partialName, indent);
+
+ this.opcode('invokePartial', isDynamic, partialName, indent);
this.opcode('append');
},
MustacheStatement: function(mustache) {
- this.accept(mustache.sexpr);
+ this.SubExpression(mustache);
if(mustache.escaped && !this.options.noEscape) {
this.opcode('appendEscaped');
@@ -334,11 +339,6 @@ Compiler.prototype = {
pushParam: function(val) {
var value = val.value != null ? val.value : val.original || '';
- // Force helper evaluation
- if (val.type === 'SubExpression') {
- val.isHelper = true;
- }
-
if (this.stringParams) {
if (value.replace) {
value = value
diff --git a/lib/handlebars/compiler/helpers.js b/lib/handlebars/compiler/helpers.js
index 6cfc0e0..beaf988 100644
--- a/lib/handlebars/compiler/helpers.js
+++ b/lib/handlebars/compiler/helpers.js
@@ -52,28 +52,29 @@ export function preparePath(data, parts, locInfo) {
return new this.PathExpression(data, depth, dig, original, locInfo);
}
-export function prepareMustache(sexpr, open, strip, locInfo) {
+export function prepareMustache(path, params, hash, open, strip, locInfo) {
/*jshint -W040 */
// Must use charAt to support IE pre-10
var escapeFlag = open.charAt(3) || open.charAt(2),
escaped = escapeFlag !== '{' && escapeFlag !== '&';
- return new this.MustacheStatement(sexpr, escaped, strip, this.locInfo(locInfo));
+ return new this.MustacheStatement(path, params, hash, escaped, strip, this.locInfo(locInfo));
}
export function prepareRawBlock(openRawBlock, content, close, locInfo) {
/*jshint -W040 */
- if (openRawBlock.sexpr.path.original !== close) {
- var errorNode = {loc: openRawBlock.sexpr.loc};
+ if (openRawBlock.path.original !== close) {
+ var errorNode = {loc: openRawBlock.path.loc};
- throw new Exception(openRawBlock.sexpr.path.original + " doesn't match " + close, errorNode);
+ throw new Exception(openRawBlock.path.original + " doesn't match " + close, errorNode);
}
locInfo = this.locInfo(locInfo);
var program = new this.Program([content], null, {}, locInfo);
return new this.BlockStatement(
- openRawBlock.sexpr, program, undefined,
+ openRawBlock.path, openRawBlock.params, openRawBlock.hash,
+ program, undefined,
{}, {}, {},
locInfo);
}
@@ -81,10 +82,10 @@ export function prepareRawBlock(openRawBlock, content, close, locInfo) {
export function prepareBlock(openBlock, program, inverseAndProgram, close, inverted, locInfo) {
/*jshint -W040 */
// When we are chaining inverse calls, we will not have a close path
- if (close && close.path && openBlock.sexpr.path.original !== close.path.original) {
- var errorNode = {loc: openBlock.sexpr.loc};
+ if (close && close.path && openBlock.path.original !== close.path.original) {
+ var errorNode = {loc: openBlock.path.loc};
- throw new Exception(openBlock.sexpr.path.original + ' doesn\'t match ' + close.path.original, errorNode);
+ throw new Exception(openBlock.path.original + ' doesn\'t match ' + close.path.original, errorNode);
}
program.blockParams = openBlock.blockParams;
@@ -108,7 +109,8 @@ export function prepareBlock(openBlock, program, inverseAndProgram, close, inver
}
return new this.BlockStatement(
- openBlock.sexpr, program, inverse,
+ openBlock.path, openBlock.params, openBlock.hash,
+ program, inverse,
openBlock.strip, inverseStrip, close && close.strip,
this.locInfo(locInfo));
}
diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js
index 75f9960..a027edb 100644
--- a/lib/handlebars/compiler/javascript-compiler.js
+++ b/lib/handlebars/compiler/javascript-compiler.js
@@ -644,17 +644,26 @@ JavaScriptCompiler.prototype = {
//
// This operation pops off a context, invokes a partial with that context,
// and pushes the result of the invocation back.
- invokePartial: function(name, indent) {
+ invokePartial: function(isDynamic, name, indent) {
var params = [],
options = this.setupParams(name, 1, params, false);
+ if (isDynamic) {
+ name = this.popStack();
+ delete options.name;
+ }
+
if (indent) {
options.indent = JSON.stringify(indent);
}
options.helpers = 'helpers';
options.partials = 'partials';
- params.unshift(this.nameLookup('partials', name, 'partial'));
+ if (!isDynamic) {
+ params.unshift(this.nameLookup('partials', name, 'partial'));
+ } else {
+ params.unshift(name);
+ }
if (this.options.compat) {
options.depths = 'depths';
diff --git a/lib/handlebars/compiler/printer.js b/lib/handlebars/compiler/printer.js
index ad276f1..55232cc 100644
--- a/lib/handlebars/compiler/printer.js
+++ b/lib/handlebars/compiler/printer.js
@@ -45,7 +45,7 @@ PrintVisitor.prototype.Program = function(program) {
};
PrintVisitor.prototype.MustacheStatement = function(mustache) {
- return this.pad('{{ ' + this.accept(mustache.sexpr) + ' }}');
+ return this.pad('{{ ' + this.SubExpression(mustache) + ' }}');
};
PrintVisitor.prototype.BlockStatement = function(block) {
@@ -53,7 +53,7 @@ PrintVisitor.prototype.BlockStatement = function(block) {
out = out + this.pad('BLOCK:');
this.padding++;
- out = out + this.pad(this.accept(block.sexpr));
+ out = out + this.pad(this.SubExpression(block));
if (block.program) {
out = out + this.pad('PROGRAM:');
this.padding++;
@@ -74,13 +74,12 @@ PrintVisitor.prototype.BlockStatement = function(block) {
};
PrintVisitor.prototype.PartialStatement = function(partial) {
- var sexpr = partial.sexpr,
- content = 'PARTIAL:' + sexpr.path.original;
- if(sexpr.params[0]) {
- content += ' ' + this.accept(sexpr.params[0]);
+ var content = 'PARTIAL:' + partial.name.original;
+ if(partial.params[0]) {
+ content += ' ' + this.accept(partial.params[0]);
}
- if (sexpr.hash) {
- content += ' ' + this.accept(sexpr.hash);
+ if (partial.hash) {
+ content += ' ' + this.accept(partial.hash);
}
return this.pad('{{> ' + content + ' }}');
};
diff --git a/lib/handlebars/compiler/visitor.js b/lib/handlebars/compiler/visitor.js
index 3fb37fb..4101a4f 100644
--- a/lib/handlebars/compiler/visitor.js
+++ b/lib/handlebars/compiler/visitor.js
@@ -71,17 +71,24 @@ Visitor.prototype = {
},
MustacheStatement: function(mustache) {
- this.acceptRequired(mustache, 'sexpr');
+ this.acceptRequired(mustache, 'path');
+ this.acceptArray(mustache.params);
+ this.acceptKey(mustache, 'hash');
},
BlockStatement: function(block) {
- this.acceptRequired(block, 'sexpr');
+ this.acceptRequired(block, 'path');
+ this.acceptArray(block.params);
+ this.acceptKey(block, 'hash');
+
this.acceptKey(block, 'program');
this.acceptKey(block, 'inverse');
},
PartialStatement: function(partial) {
- this.acceptRequired(partial, 'sexpr');
+ this.acceptRequired(partial, 'name');
+ this.acceptArray(partial.params);
+ this.acceptKey(partial, 'hash');
},
ContentStatement: function(/* content */) {},
@@ -92,6 +99,11 @@ Visitor.prototype = {
this.acceptArray(sexpr.params);
this.acceptKey(sexpr, 'hash');
},
+ PartialExpression: function(partial) {
+ this.acceptRequired(partial, 'name');
+ this.acceptArray(partial.params);
+ this.acceptKey(partial, 'hash');
+ },
PathExpression: function(/* path */) {},
diff --git a/lib/handlebars/runtime.js b/lib/handlebars/runtime.js
index 2b9f474..4e8c33a 100644
--- a/lib/handlebars/runtime.js
+++ b/lib/handlebars/runtime.js
@@ -39,10 +39,8 @@ export function template(templateSpec, env) {
if (options.hash) {
context = Utils.extend({}, context, options.hash);
}
- if (!partial) {
- partial = options.partials[options.name];
- }
+ partial = env.VM.resolvePartial.call(this, partial, context, options);
var result = env.VM.invokePartial.call(this, partial, context, options);
if (result == null && env.compile) {
@@ -187,6 +185,17 @@ export function program(container, i, fn, data, declaredBlockParams, blockParams
return prog;
}
+export function resolvePartial(partial, context, options) {
+ if (!partial) {
+ partial = options.partials[options.name];
+ } else if (!partial.call && !options.name) {
+ // This is a dynamic partial that returned a string
+ options.name = partial;
+ partial = options.partials[partial];
+ }
+ return partial;
+}
+
export function invokePartial(partial, context, options) {
options.partial = true;
diff --git a/spec/ast.js b/spec/ast.js
index d464cf1..3b2a4ae 100644
--- a/spec/ast.js
+++ b/spec/ast.js
@@ -26,7 +26,7 @@ describe('ast', function() {
it('should store args', function() {
var id = {isSimple: true},
hash = {},
- mustache = new handlebarsEnv.AST.MustacheStatement({}, true, {}, LOCATION_INFO);
+ mustache = new handlebarsEnv.AST.MustacheStatement({}, null, null, true, {}, LOCATION_INFO);
equals(mustache.type, 'MustacheStatement');
equals(mustache.escaped, true);
testLocationInfoStorage(mustache);
@@ -40,10 +40,10 @@ describe('ast', function() {
});
it('stores location info', function(){
- var sexprNode = new handlebarsEnv.AST.SubExpression([{ original: 'foo'}], null);
- var mustacheNode = new handlebarsEnv.AST.MustacheStatement(sexprNode, false, {});
+ var mustacheNode = new handlebarsEnv.AST.MustacheStatement([{ original: 'foo'}], null, null, false, {});
var block = new handlebarsEnv.AST.BlockStatement(
mustacheNode,
+ null, null,
{body: []},
{body: []},
{},
@@ -104,7 +104,7 @@ describe('ast', function() {
describe('PartialStatement', function(){
it('stores location info', function(){
- var pn = new handlebarsEnv.AST.PartialStatement('so_partial', {}, LOCATION_INFO);
+ var pn = new handlebarsEnv.AST.PartialStatement('so_partial', [], {}, {}, LOCATION_INFO);
testLocationInfoStorage(pn);
});
});
@@ -200,7 +200,7 @@ describe('ast', function() {
block = ast.body[0];
equals(block.program.body[0].value, '');
- equals(block.program.body[1].sexpr.path.original, 'foo');
+ equals(block.program.body[1].path.original, 'foo');
equals(block.program.body[2].value, '\n');
});
it('marks nested block mustaches as standalone', function() {
diff --git a/spec/data.js b/spec/data.js
index bb90df5..1678eea 100644
--- a/spec/data.js
+++ b/spec/data.js
@@ -93,6 +93,17 @@ describe('data', function() {
}, Error);
});
+ it('data can be functions', function() {
+ var template = CompilerContext.compile('{{@hello}}');
+ var result = template({}, { data: { hello: function() { return 'hello'; } } });
+ equals('hello', result);
+ });
+ it('data can be functions with params', function() {
+ var template = CompilerContext.compile('{{@hello "hello"}}');
+ var result = template({}, { data: { hello: function(arg) { return arg; } } });
+ equals('hello', result);
+ });
+
it("data is inherited downstream", function() {
var template = CompilerContext.compile("{{#let foo=1 bar=2}}{{#let foo=bar.baz}}{{@bar}}{{@foo}}{{/let}}{{@foo}}{{/let}}", { data: true });
var helpers = {
diff --git a/spec/partials.js b/spec/partials.js
index b150942..0c9e0f6 100644
--- a/spec/partials.js
+++ b/spec/partials.js
@@ -8,6 +8,32 @@ describe('partials', function() {
shouldCompileToWithPartials(string, [hash, {}, {dude: partial},,false], true, 'Dudes: Yehuda (http://yehuda) Alan (http://alan) ');
});
+ it('dynamic partials', function() {
+ var string = 'Dudes: {{#dudes}}{{> (partial)}}{{/dudes}}';
+ var partial = '{{name}} ({{url}}) ';
+ var hash = {dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]};
+ var helpers = {
+ partial: function() {
+ return 'dude';
+ }
+ };
+ shouldCompileToWithPartials(string, [hash, helpers, {dude: partial}], true, 'Dudes: Yehuda (http://yehuda) Alan (http://alan) ');
+ shouldCompileToWithPartials(string, [hash, helpers, {dude: partial},,false], true, 'Dudes: Yehuda (http://yehuda) Alan (http://alan) ');
+ });
+ it('failing dynamic partials', function() {
+ var string = 'Dudes: {{#dudes}}{{> (partial)}}{{/dudes}}';
+ var partial = '{{name}} ({{url}}) ';
+ var hash = {dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]};
+ var helpers = {
+ partial: function() {
+ return 'missing';
+ }
+ };
+ shouldThrow(function() {
+ shouldCompileToWithPartials(string, [hash, helpers, {dude: partial}], true, 'Dudes: Yehuda (http://yehuda) Alan (http://alan) ');
+ }, Handlebars.Exception, 'The partial missing could not be found');
+ });
+
it("partials with context", function() {
var string = "Dudes: {{>dude dudes}}";
var partial = "{{#this}}{{name}} ({{url}}) {{/this}}";
diff --git a/spec/visitor.js b/spec/visitor.js
index 0c23c0d..3217230 100644
--- a/spec/visitor.js
+++ b/spec/visitor.js
@@ -24,11 +24,10 @@ describe('Visitor', function() {
visitor.BooleanLiteral = function(bool) {
equal(bool.value, true);
- equal(this.parents.length, 4);
+ equal(this.parents.length, 3);
equal(this.parents[0].type, 'SubExpression');
- equal(this.parents[1].type, 'SubExpression');
- equal(this.parents[2].type, 'BlockStatement');
- equal(this.parents[3].type, 'Program');
+ equal(this.parents[1].type, 'BlockStatement');
+ equal(this.parents[2].type, 'Program');
};
visitor.PathExpression = function(id) {
equal(/(foo\.)?bar$/.test(id.original), true);
@@ -84,26 +83,26 @@ describe('Visitor', function() {
var visitor = new Handlebars.Visitor();
visitor.mutating = true;
- visitor.SubExpression = function() {
+ visitor.PathExpression = function() {
return false;
};
var ast = Handlebars.parse('{{foo 42}}');
visitor.accept(ast);
- }, Handlebars.Exception, 'MustacheStatement requires sexpr');
+ }, Handlebars.Exception, 'MustacheStatement requires path');
});
it('should throw when returning non-node responses', function() {
shouldThrow(function() {
var visitor = new Handlebars.Visitor();
visitor.mutating = true;
- visitor.SubExpression = function() {
+ visitor.PathExpression = function() {
return {};
};
var ast = Handlebars.parse('{{foo 42}}');
visitor.accept(ast);
- }, Handlebars.Exception, 'Unexpected node type "undefined" found when accepting sexpr on MustacheStatement');
+ }, Handlebars.Exception, 'Unexpected node type "undefined" found when accepting path on MustacheStatement');
});
});
describe('arrays', function() {
diff --git a/src/handlebars.yy b/src/handlebars.yy
index 27985b8..39f8027 100644
--- a/src/handlebars.yy
+++ b/src/handlebars.yy
@@ -30,7 +30,7 @@ rawBlock
;
openRawBlock
- : OPEN_RAW_BLOCK sexpr CLOSE_RAW_BLOCK -> { sexpr: $2 }
+ : OPEN_RAW_BLOCK helperName param* hash? CLOSE_RAW_BLOCK -> { path: $2, params: $3, hash: $4 }
;
block
@@ -39,15 +39,15 @@ block
;
openBlock
- : OPEN_BLOCK sexpr blockParams? CLOSE -> { sexpr: $2, blockParams: $3, strip: yy.stripFlags($1, $4) }
+ : OPEN_BLOCK helperName param* hash? blockParams? CLOSE -> { path: $2, params: $3, hash: $4, blockParams: $5, strip: yy.stripFlags($1, $6) }
;
openInverse
- : OPEN_INVERSE sexpr blockParams? CLOSE -> { sexpr: $2, blockParams: $3, strip: yy.stripFlags($1, $4) }
+ : OPEN_INVERSE helperName param* hash? blockParams? CLOSE -> { path: $2, params: $3, hash: $4, blockParams: $5, strip: yy.stripFlags($1, $6) }
;
openInverseChain
- : OPEN_INVERSE_CHAIN sexpr blockParams? CLOSE -> { sexpr: $2, blockParams: $3, strip: yy.stripFlags($1, $4) }
+ : OPEN_INVERSE_CHAIN helperName param* hash? blockParams? CLOSE -> { path: $2, params: $3, hash: $4, blockParams: $5, strip: yy.stripFlags($1, $6) }
;
inverseAndProgram
@@ -72,17 +72,12 @@ 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 sexpr CLOSE -> yy.prepareMustache($2, $1, yy.stripFlags($1, $3), @$)
- | OPEN_UNESCAPED sexpr CLOSE_UNESCAPED -> yy.prepareMustache($2, $1, yy.stripFlags($1, $3), @$)
+ : OPEN helperName param* hash? CLOSE -> yy.prepareMustache($2, $3, $4, $1, yy.stripFlags($1, $5), @$)
+ | OPEN_UNESCAPED helperName param* hash? CLOSE_UNESCAPED -> yy.prepareMustache($2, $3, $4, $1, yy.stripFlags($1, $5), @$)
;
partial
- : OPEN_PARTIAL sexpr CLOSE -> new yy.PartialStatement($2, yy.stripFlags($1, $3), yy.locInfo(@$))
- ;
-
-sexpr
- : helperName param* hash? -> new yy.SubExpression($1, $2, $3, yy.locInfo(@$))
- | dataName -> new yy.SubExpression($1, null, null, yy.locInfo(@$))
+ : OPEN_PARTIAL partialName param* hash? CLOSE -> new yy.PartialStatement($2, $3, $4, yy.stripFlags($1, $5), yy.locInfo(@$))
;
param
@@ -91,7 +86,11 @@ param
| NUMBER -> new yy.NumberLiteral($1, yy.locInfo(@$))
| BOOLEAN -> new yy.BooleanLiteral($1, yy.locInfo(@$))
| dataName -> $1
- | OPEN_SEXPR sexpr CLOSE_SEXPR -> $2
+ | sexpr -> $1
+ ;
+
+sexpr
+ : OPEN_SEXPR helperName param* hash? CLOSE_SEXPR -> new yy.SubExpression($2, $3, $4, yy.locInfo(@$))
;
hash
@@ -108,10 +107,16 @@ blockParams
helperName
: path -> $1
+ | dataName -> $1
| STRING -> new yy.StringLiteral($1, yy.locInfo(@$)), yy.locInfo(@$)
| NUMBER -> new yy.NumberLiteral($1, yy.locInfo(@$))
;
+partialName
+ : helperName -> $1
+ | sexpr -> $1
+ ;
+
dataName
: DATA pathSegments -> yy.preparePath(true, $2, @$)
;