Avoid direct references to sexpr in statements
This allows us to avoid creating unnecessary AST nodes and avoids things like isHelper. Side effect of these changes is that @data functions can now have data parameters passed to them.
This commit is contained in:
@@ -204,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);
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
|
||||
@@ -63,15 +70,6 @@ var AST = {
|
||||
this.hash = hash;
|
||||
},
|
||||
|
||||
PartialExpression: function(name, params, hash, locInfo) {
|
||||
this.loc = locInfo;
|
||||
|
||||
this.type = 'PartialExpression';
|
||||
this.name = name;
|
||||
this.params = params || [];
|
||||
this.hash = hash;
|
||||
},
|
||||
|
||||
PathExpression: function(data, depth, parts, original, locInfo) {
|
||||
this.loc = locInfo;
|
||||
this.type = 'PathExpression';
|
||||
@@ -121,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) {
|
||||
|
||||
@@ -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`
|
||||
@@ -147,20 +146,20 @@ Compiler.prototype = {
|
||||
PartialStatement: function(partial) {
|
||||
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});
|
||||
}
|
||||
|
||||
var partialName = partial.sexpr.name.original,
|
||||
isDynamic = partial.sexpr.name.type === 'SubExpression';
|
||||
var partialName = partial.name.original,
|
||||
isDynamic = partial.name.type === 'SubExpression';
|
||||
if (isDynamic) {
|
||||
this.accept(partial.sexpr.name);
|
||||
this.accept(partial.name);
|
||||
}
|
||||
|
||||
this.setupFullMustacheParams(partial.sexpr, undefined, undefined, true);
|
||||
this.setupFullMustacheParams(partial, undefined, undefined, true);
|
||||
|
||||
var indent = partial.indent || '';
|
||||
if (this.options.preventIndent && indent) {
|
||||
@@ -173,7 +172,7 @@ Compiler.prototype = {
|
||||
},
|
||||
|
||||
MustacheStatement: function(mustache) {
|
||||
this.accept(mustache.sexpr);
|
||||
this.SubExpression(mustache);
|
||||
|
||||
if(mustache.escaped && !this.options.noEscape) {
|
||||
this.opcode('appendEscaped');
|
||||
@@ -340,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
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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.name.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 + ' }}');
|
||||
};
|
||||
|
||||
@@ -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 */) {},
|
||||
|
||||
+5
-5
@@ -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() {
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
+7
-8
@@ -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() {
|
||||
|
||||
+18
-19
@@ -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,23 +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 partial_expr CLOSE -> new yy.PartialStatement($2, yy.stripFlags($1, $3), yy.locInfo(@$))
|
||||
;
|
||||
|
||||
partial_expr
|
||||
: helperName param* hash? -> new yy.PartialExpression($1, $2, $3, yy.locInfo(@$))
|
||||
| dataName -> new yy.PartialExpression($1, null, null, yy.locInfo(@$))
|
||||
| OPEN_SEXPR sexpr CLOSE_SEXPR param* hash? -> new yy.PartialExpression($2, $4, $5, 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
|
||||
@@ -97,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
|
||||
@@ -114,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, @$)
|
||||
;
|
||||
|
||||
Reference in New Issue
Block a user