Implement |cache| option for |PEG.buildParser|

This option enables/disables the results cache in generated parsers.
Until now, it was always enabled, but after this commit it needs to be
enabled explicitly (i.e. the |cache| option default value is |false|).
The reason is that parsing without it is *much* faster according to the
benchmark.

Note that disabling the cache breaks the linear parsing time guarantee,
meaning that with some grammars you can get exponential parsing time
with respect to the input length. This, together with the possibility of
improving the cache performance in the future, is the reason to keep it
as an option.

Speed impact
------------
Before:     214.08 kB/s
After:      827.52 kB/s
Difference: 286.54%

Size impact
-----------
Before:     1045396 b
After:      949783 b
Difference: -9.15%

(Measured by /tools/impact with Node.js v0.6.6 on x86_64 GNU/Linux.)
redux
David Majda 12 years ago
parent 1ee6731b51
commit 5b3321d302

@ -1,6 +1,9 @@
/* Emits the generated code for the AST. */ /* Emits the generated code for the AST. */
PEG.compiler.emitter = function(ast, options) { PEG.compiler.emitter = function(ast, options) {
options = options || {}; options = options || {};
if (options.cache === undefined) {
options.cache = false;
}
if (options.trackLineAndColumn === undefined) { if (options.trackLineAndColumn === undefined) {
options.trackLineAndColumn = false; options.trackLineAndColumn = false;
} }
@ -321,7 +324,9 @@ PEG.compiler.emitter = function(ast, options) {
' var reportFailures = 0;', // 0 = report, anything > 0 = do not report ' var reportFailures = 0;', // 0 = report, anything > 0 = do not report
' #{posInit("rightmostFailuresPos")};', ' #{posInit("rightmostFailuresPos")};',
' var rightmostFailuresExpected = [];', ' var rightmostFailuresExpected = [];',
' var cache = {};', ' #if options.cache',
' var cache = {};',
' #end',
' ', ' ',
/* This needs to be in sync with |padLeft| in utils.js. */ /* This needs to be in sync with |padLeft| in utils.js. */
' function padLeft(input, padding, length) {', ' function padLeft(input, padding, length) {',
@ -544,13 +549,15 @@ PEG.compiler.emitter = function(ast, options) {
], ],
rule: [ rule: [
'function parse_#{node.name}() {', 'function parse_#{node.name}() {',
' var cacheKey = "#{node.name}@" + #{posOffset("pos")};', ' #if options.cache',
' var cachedResult = cache[cacheKey];', ' var cacheKey = "#{node.name}@" + #{posOffset("pos")};',
' if (cachedResult) {', ' var cachedResult = cache[cacheKey];',
' pos = #{posClone("cachedResult.nextPos")};', ' if (cachedResult) {',
' return cachedResult.result;', ' pos = #{posClone("cachedResult.nextPos")};',
' }', ' return cachedResult.result;',
' ', ' }',
' ',
' #end',
' #if node.resultVars.length > 0', ' #if node.resultVars.length > 0',
' var #{node.resultVars.join(", ")};', ' var #{node.resultVars.join(", ")};',
' #end', ' #end',
@ -568,11 +575,13 @@ PEG.compiler.emitter = function(ast, options) {
' matchFailed(#{string(node.displayName)});', ' matchFailed(#{string(node.displayName)});',
' }', ' }',
' #end', ' #end',
' ', ' #if options.cache',
' cache[cacheKey] = {', ' ',
' nextPos: #{posClone("pos")},', ' cache[cacheKey] = {',
' result: #{node.resultVar}', ' nextPos: #{posClone("pos")},',
' };', ' result: #{node.resultVar}',
' };',
' #end',
' return #{node.resultVar};', ' return #{node.resultVar};',
'}' '}'
], ],

@ -785,16 +785,23 @@ testWithVaryingTrackLineAndColumn("classes", function(options) {
}); });
testWithVaryingTrackLineAndColumn("cache", function(options) { testWithVaryingTrackLineAndColumn("cache", function(options) {
/* var grammar = [
* Should trigger a codepath where the cache is used (for the "a" rule). '{ var n = 0; }',
*/ 'start = (a "b") / (a "c") { return n; }',
var parser = PEG.buildParser([ 'a = "a" { n++; }',
'start = (a b) / (a c)', ].join("\n");
'a = "a"',
'b = "b"', /* Without cache */
'c = "c"'
].join("\n"), options); parses(PEG.buildParser(grammar, options), "ac", 2);
parses(parser, "ac", ["a", "c"]);
options.cache = false;
parses(PEG.buildParser(grammar, options), "ac", 2);
/* With cache */
options.cache = true;
parses(PEG.buildParser(grammar, options), "ac", 1);
}); });
testWithVaryingTrackLineAndColumn("indempotence", function(options) { testWithVaryingTrackLineAndColumn("indempotence", function(options) {

Loading…
Cancel
Save