Consolidate all variable name computations into one compiler pass

Before this change, knowledge about variable names was spread between
the |computeStackDepths| pass and the code emitter code. For example,
the fact that the |&...| expression needs one variable to store a
position was represented in both places.

This changes consolidates that knowledge and introduces a new
|computeVarNames| pass. This pass replaces old |computeStackDepths|
pass, does all computations realted to variable names and stores the
results in the AST. Note that some knowledge about variables
(inevitably) remained in emitter code templates.

Beside DRYing things up, this change simplifies the emitter
significantly. By storing variable names in the AST it also allows
introduction of a pass that will identify parameters passed to actions
using proper symbol tables. Right now, this is done in a hackish way
directly in the emitter, which won't work well with changes planned in
GH-69.
redux
David Majda 13 years ago
parent 9d96e1e303
commit efc38eef9b

@ -7,7 +7,7 @@ PEG.compiler = {
"reportMissingRules", "reportMissingRules",
"reportLeftRecursion", "reportLeftRecursion",
"removeProxyRules", "removeProxyRules",
"computeStackDepths" "computeVarNames"
], ],
/* /*

@ -265,7 +265,7 @@ PEG.compiler.emitter = function(ast) {
' throw new Error("Invalid rule name: " + quote(startRule) + ".");', ' throw new Error("Invalid rule name: " + quote(startRule) + ".");',
' }', ' }',
' } else {', ' } else {',
' startRule = #{string(startRule)};', ' startRule = #{string(node.startRule)};',
' }', ' }',
' ', ' ',
' var pos = 0;', ' var pos = 0;',
@ -390,8 +390,8 @@ PEG.compiler.emitter = function(ast) {
' return { line: line, column: column };', ' return { line: line, column: column };',
' }', ' }',
' ', ' ',
' #if initializerCode !== ""', ' #if node.initializer',
' #block initializerCode', ' #block emit(node.initializer)',
' #end', ' #end',
' ', ' ',
' var result = parseFunctions[startRule]();', ' var result = parseFunctions[startRule]();',
@ -488,126 +488,126 @@ PEG.compiler.emitter = function(ast) {
' return cachedResult.result;', ' return cachedResult.result;',
' }', ' }',
' ', ' ',
' #if resultVars.length > 0', ' #if node.resultVars.length > 0',
' var #{resultVars.join(", ")};', ' var #{node.resultVars.join(", ")};',
' #end', ' #end',
' #if posVars.length > 0', ' #if node.posVars.length > 0',
' var #{posVars.join(", ")};', ' var #{node.posVars.join(", ")};',
' #end', ' #end',
' ', ' ',
' #if node.displayName !== null', ' #if node.displayName !== null',
' reportFailures++;', ' reportFailures++;',
' #end', ' #end',
' #block code', ' #block emit(node.expression)',
' #if node.displayName !== null', ' #if node.displayName !== null',
' reportFailures--;', ' reportFailures--;',
' if (reportFailures === 0 && #{resultVar} === null) {', ' if (reportFailures === 0 && #{node.resultVar} === null) {',
' matchFailed(#{string(node.displayName)});', ' matchFailed(#{string(node.displayName)});',
' }', ' }',
' #end', ' #end',
' ', ' ',
' cache[cacheKey] = {', ' cache[cacheKey] = {',
' nextPos: pos,', ' nextPos: pos,',
' result: #{resultVar}', ' result: #{node.resultVar}',
' };', ' };',
' return #{resultVar};', ' return #{node.resultVar};',
'}' '}'
], ],
choice: [ choice: [
'#block currentAlternativeCode', '#block emit(alternative)',
'#block nextAlternativesCode' '#block nextAlternativesCode'
], ],
"choice.next": [ "choice.next": [
'if (#{resultVar} === null) {', 'if (#{node.resultVar} === null) {',
' #block code', ' #block code',
'}' '}'
], ],
sequence: [ sequence: [
'#{posVar} = pos;', '#{node.posVar} = pos;',
'#block code' '#block code'
], ],
"sequence.iteration": [ "sequence.iteration": [
'#block elementCode', '#block emit(element)',
'if (#{elementResultVar} !== null) {', 'if (#{element.resultVar} !== null) {',
' #block code', ' #block code',
'} else {', '} else {',
' #{resultVar} = null;', ' #{node.resultVar} = null;',
' pos = #{posVar};', ' pos = #{node.posVar};',
'}' '}'
], ],
"sequence.inner": [ "sequence.inner": [
'#{resultVar} = [#{elementResultVars.join(", ")}];' '#{node.resultVar} = [#{pluck(node.elements, "resultVar").join(", ")}];'
], ],
simple_and: [ simple_and: [
'#{posVar} = pos;', '#{node.posVar} = pos;',
'reportFailures++;', 'reportFailures++;',
'#block expressionCode', '#block emit(node.expression)',
'reportFailures--;', 'reportFailures--;',
'if (#{resultVar} !== null) {', 'if (#{node.resultVar} !== null) {',
' #{resultVar} = "";', ' #{node.resultVar} = "";',
' pos = #{posVar};', ' pos = #{node.posVar};',
'} else {', '} else {',
' #{resultVar} = null;', ' #{node.resultVar} = null;',
'}' '}'
], ],
simple_not: [ simple_not: [
'#{posVar} = pos;', '#{node.posVar} = pos;',
'reportFailures++;', 'reportFailures++;',
'#block expressionCode', '#block emit(node.expression)',
'reportFailures--;', 'reportFailures--;',
'if (#{resultVar} === null) {', 'if (#{node.resultVar} === null) {',
' #{resultVar} = "";', ' #{node.resultVar} = "";',
'} else {', '} else {',
' #{resultVar} = null;', ' #{node.resultVar} = null;',
' pos = #{posVar};', ' pos = #{node.posVar};',
'}' '}'
], ],
semantic_and: [ semantic_and: [
'#{resultVar} = (function() {#{node.code}})() ? "" : null;' '#{node.resultVar} = (function() {#{node.code}})() ? "" : null;'
], ],
semantic_not: [ semantic_not: [
'#{resultVar} = (function() {#{node.code}})() ? null : "";' '#{node.resultVar} = (function() {#{node.code}})() ? null : "";'
], ],
optional: [ optional: [
'#block expressionCode', '#block emit(node.expression)',
'#{resultVar} = #{resultVar} !== null ? #{resultVar} : "";' '#{node.resultVar} = #{node.resultVar} !== null ? #{node.resultVar} : "";'
], ],
zero_or_more: [ zero_or_more: [
'#{resultVar} = [];', '#{node.resultVar} = [];',
'#block expressionCode', '#block emit(node.expression)',
'while (#{expressionResultVar} !== null) {', 'while (#{node.expression.resultVar} !== null) {',
' #{resultVar}.push(#{expressionResultVar});', ' #{node.resultVar}.push(#{node.expression.resultVar});',
' #block expressionCode', ' #block emit(node.expression)',
'}' '}'
], ],
one_or_more: [ one_or_more: [
'#block expressionCode', '#block emit(node.expression)',
'if (#{expressionResultVar} !== null) {', 'if (#{node.expression.resultVar} !== null) {',
' #{resultVar} = [];', ' #{node.resultVar} = [];',
' while (#{expressionResultVar} !== null) {', ' while (#{node.expression.resultVar} !== null) {',
' #{resultVar}.push(#{expressionResultVar});', ' #{node.resultVar}.push(#{node.expression.resultVar});',
' #block expressionCode', ' #block emit(node.expression)',
' }', ' }',
'} else {', '} else {',
' #{resultVar} = null;', ' #{node.resultVar} = null;',
'}' '}'
], ],
action: [ action: [
'#{posVar} = pos;', '#{node.posVar} = pos;',
'#block expressionCode', '#block emit(node.expression)',
'if (#{resultVar} !== null) {', 'if (#{node.resultVar} !== null) {',
' #{resultVar} = (function(#{formalParams.join(", ")}) {#{node.code}})(#{actualParams.join(", ")});', ' #{node.resultVar} = (function(#{formalParams.join(", ")}) {#{node.code}})(#{actualParams.join(", ")});',
'}', '}',
'if (#{resultVar} === null) {', 'if (#{node.resultVar} === null) {',
' pos = #{posVar};', ' pos = #{node.posVar};',
'}' '}'
], ],
rule_ref: [ rule_ref: [
'#{resultVar} = parse_#{node.name}();' '#{node.resultVar} = parse_#{node.name}();'
], ],
literal: [ literal: [
'#if node.value.length === 0', '#if node.value.length === 0',
' #{resultVar} = "";', ' #{node.resultVar} = "";',
'#else', '#else',
' #if !node.ignoreCase', ' #if !node.ignoreCase',
' #if node.value.length === 1', ' #if node.value.length === 1',
@ -627,13 +627,13 @@ PEG.compiler.emitter = function(ast) {
' if (input.substr(pos, #{node.value.length}).toLowerCase() === #{string(node.value.toLowerCase())}) {', ' if (input.substr(pos, #{node.value.length}).toLowerCase() === #{string(node.value.toLowerCase())}) {',
' #end', ' #end',
' #if !node.ignoreCase', ' #if !node.ignoreCase',
' #{resultVar} = #{string(node.value)};', ' #{node.resultVar} = #{string(node.value)};',
' #else', ' #else',
' #{resultVar} = input.substr(pos, #{node.value.length});', ' #{node.resultVar} = input.substr(pos, #{node.value.length});',
' #end', ' #end',
' pos += #{node.value.length};', ' pos += #{node.value.length};',
' } else {', ' } else {',
' #{resultVar} = null;', ' #{node.resultVar} = null;',
' if (reportFailures === 0) {', ' if (reportFailures === 0) {',
' matchFailed(#{string(string(node.value))});', ' matchFailed(#{string(string(node.value))});',
' }', ' }',
@ -642,10 +642,10 @@ PEG.compiler.emitter = function(ast) {
], ],
any: [ any: [
'if (input.length > pos) {', 'if (input.length > pos) {',
' #{resultVar} = input.charAt(pos);', ' #{node.resultVar} = input.charAt(pos);',
' pos++;', ' pos++;',
'} else {', '} else {',
' #{resultVar} = null;', ' #{node.resultVar} = null;',
' if (reportFailures === 0) {', ' if (reportFailures === 0) {',
' matchFailed("any character");', ' matchFailed("any character");',
' }', ' }',
@ -653,10 +653,10 @@ PEG.compiler.emitter = function(ast) {
], ],
"class": [ "class": [
'if (#{regexp}.test(input.charAt(pos))) {', 'if (#{regexp}.test(input.charAt(pos))) {',
' #{resultVar} = input.charAt(pos);', ' #{node.resultVar} = input.charAt(pos);',
' pos++;', ' pos++;',
'} else {', '} else {',
' #{resultVar} = null;', ' #{node.resultVar} = null;',
' if (reportFailures === 0) {', ' if (reportFailures === 0) {',
' matchFailed(#{string(node.rawText)});', ' matchFailed(#{string(node.rawText)});',
' }', ' }',
@ -673,18 +673,18 @@ PEG.compiler.emitter = function(ast) {
function fill(name, vars) { function fill(name, vars) {
vars.string = quote; vars.string = quote;
vars.pluck = pluck;
vars.emit = emit;
return templates[name](vars); return templates[name](vars);
} }
function resultVar(index) { return "result" + index; } function emitSimple(name) {
function posVar(index) { return "pos" + index; } return function(node) { return fill(name, { node: node }); };
}
var emit = buildNodeVisitor({ var emit = buildNodeVisitor({
grammar: function(node) { grammar: function(node) {
var initializerCode = node.initializer !== null
? emit(node.initializer)
: "";
var name; var name;
var parseFunctionTableItems = []; var parseFunctionTableItems = [];
@ -699,38 +699,15 @@ PEG.compiler.emitter = function(ast) {
} }
return fill("grammar", { return fill("grammar", {
initializerCode: initializerCode, node: node,
parseFunctionTableItems: parseFunctionTableItems, parseFunctionTableItems: parseFunctionTableItems,
parseFunctionDefinitions: parseFunctionDefinitions, parseFunctionDefinitions: parseFunctionDefinitions
startRule: node.startRule
}); });
}, },
initializer: function(node) { initializer: function(node) { return node.code; },
return node.code;
},
rule: function(node) { rule: emitSimple("rule"),
var context = {
resultIndex: 0,
posIndex: 0,
delta: function(resultIndexDelta, posIndexDelta) {
return {
resultIndex: this.resultIndex + resultIndexDelta,
posIndex: this.posIndex + posIndexDelta,
delta: this.delta
};
}
};
return fill("rule", {
node: node,
resultVars: map(range(node.resultStackDepth), resultVar),
posVars: map(range(node.posStackDepth), posVar),
code: emit(node.expression, context),
resultVar: resultVar(context.resultIndex)
});
},
/* /*
* The contract for all code fragments generated by the following functions * The contract for all code fragments generated by the following functions
@ -742,126 +719,59 @@ PEG.compiler.emitter = function(ast) {
* *
* * If the code fragment matches the input, it advances |pos| to point to * * If the code fragment matches the input, it advances |pos| to point to
* the first chracter following the matched part of the input and sets * the first chracter following the matched part of the input and sets
* variable with a name computed by calling * variable with a name stored in |node.resultVar| to an appropriate
* |resultVar(context.resultIndex)| to an appropriate value. This value is * value. This value is always non-|null|.
* always non-|null|.
* *
* * If the code fragment does not match the input, it returns with |pos| * * If the code fragment does not match the input, it returns with |pos|
* set to the original value and it sets a variable with a name computed * set to the original value and it sets a variable with a name stored in
* by calling |resultVar(context.resultIndex)| to |null|. * |node.resultVar| to |null|.
* *
* The code can use variables with names computed by calling * The code can use variables with names stored in |resultVar| and |posVar|
* * properties of the current node's subnodes. It can't use any other
* |resultVar(context.resultIndex + i)| * variables.
*
* and
*
* |posVar(context.posIndex + i)|
*
* where |i| >= 1 to store necessary data (return values and positions). It
* won't use any other variables.
*/ */
choice: function(node, context) { choice: function(node) {
var code, nextAlternativesCode; var code, nextAlternativesCode;
for (var i = node.alternatives.length - 1; i >= 0; i--) { for (var i = node.alternatives.length - 1; i >= 0; i--) {
nextAlternativesCode = i !== node.alternatives.length - 1 nextAlternativesCode = i !== node.alternatives.length - 1
? fill("choice.next", { ? fill("choice.next", { node: node, code: code })
code: code,
resultVar: resultVar(context.resultIndex)
})
: ''; : '';
code = fill("choice", { code = fill("choice", {
currentAlternativeCode: emit(node.alternatives[i], context), alternative: node.alternatives[i],
nextAlternativesCode: nextAlternativesCode nextAlternativesCode: nextAlternativesCode
}); });
} }
return code; return code;
}, },
sequence: function(node, context) { sequence: function(node) {
var elementResultVars = map(node.elements, function(element, i) { var code = fill("sequence.inner", { node: node });
return resultVar(context.resultIndex + i);
});
var code = fill("sequence.inner", {
resultVar: resultVar(context.resultIndex),
elementResultVars: elementResultVars
});
for (var i = node.elements.length - 1; i >= 0; i--) { for (var i = node.elements.length - 1; i >= 0; i--) {
code = fill("sequence.iteration", { code = fill("sequence.iteration", {
elementCode: emit(node.elements[i], context.delta(i, 1)), node: node,
elementResultVar: elementResultVars[i], element: node.elements[i],
code: code, code: code
posVar: posVar(context.posIndex),
resultVar: resultVar(context.resultIndex)
}); });
} }
return fill("sequence", { code: code, posVar: posVar(context.posIndex) }); return fill("sequence", { node: node, code: code });
},
labeled: function(node, context) {
return emit(node.expression, context);
},
simple_and: function(node, context) {
return fill("simple_and", {
expressionCode: emit(node.expression, context.delta(0, 1)),
posVar: posVar(context.posIndex),
resultVar: resultVar(context.resultIndex)
});
},
simple_not: function(node, context) {
return fill("simple_not", {
expressionCode: emit(node.expression, context.delta(0, 1)),
posVar: posVar(context.posIndex),
resultVar: resultVar(context.resultIndex)
});
},
semantic_and: function(node, context) {
return fill("semantic_and", {
node: node,
resultVar: resultVar(context.resultIndex)
});
}, },
semantic_not: function(node, context) { labeled: function(node) { return emit(node.expression); },
return fill("semantic_not", {
node: node,
resultVar: resultVar(context.resultIndex)
});
},
optional: function(node, context) { simple_and: emitSimple("simple_and"),
return fill("optional", { simple_not: emitSimple("simple_not"),
expressionCode: emit(node.expression, context), semantic_and: emitSimple("semantic_and"),
resultVar: resultVar(context.resultIndex) semantic_not: emitSimple("semantic_not"),
}); optional: emitSimple("optional"),
}, zero_or_more: emitSimple("zero_or_more"),
one_or_more: emitSimple("one_or_more"),
zero_or_more: function(node, context) { action: function(node) {
return fill("zero_or_more", {
expressionCode: emit(node.expression, context.delta(1, 0)),
expressionResultVar: resultVar(context.resultIndex + 1),
resultVar: resultVar(context.resultIndex)
});
},
one_or_more: function(node, context) {
return fill("one_or_more", {
expressionCode: emit(node.expression, context.delta(1, 0)),
expressionResultVar: resultVar(context.resultIndex + 1),
resultVar: resultVar(context.resultIndex)
});
},
action: function(node, context) {
/* /*
* In case of sequences, we splat their elements into function arguments * In case of sequences, we splat their elements into function arguments
* one by one. Example: * one by one. Example:
@ -881,12 +791,12 @@ PEG.compiler.emitter = function(ast) {
each(node.expression.elements, function(element, i) { each(node.expression.elements, function(element, i) {
if (element.type === "labeled") { if (element.type === "labeled") {
formalParams.push(element.label); formalParams.push(element.label);
actualParams.push(resultVar(context.resultIndex) + '[' + i + ']'); actualParams.push(node.resultVar + '[' + i + ']');
} }
}); });
} else if (node.expression.type === "labeled") { } else if (node.expression.type === "labeled") {
formalParams = [node.expression.label]; formalParams = [node.expression.label];
actualParams = [resultVar(context.resultIndex)]; actualParams = [node.resultVar];
} else { } else {
formalParams = []; formalParams = [];
actualParams = []; actualParams = [];
@ -894,33 +804,16 @@ PEG.compiler.emitter = function(ast) {
return fill("action", { return fill("action", {
node: node, node: node,
expressionCode: emit(node.expression, context.delta(0, 1)),
formalParams: formalParams, formalParams: formalParams,
actualParams: actualParams, actualParams: actualParams
posVar: posVar(context.posIndex),
resultVar: resultVar(context.resultIndex)
});
},
rule_ref: function(node, context) {
return fill("rule_ref", {
node: node,
resultVar: resultVar(context.resultIndex)
}); });
}, },
literal: function(node, context) { rule_ref: emitSimple("rule_ref"),
return fill("literal", { literal: emitSimple("literal"),
node: node, any: emitSimple("any"),
resultVar: resultVar(context.resultIndex)
});
},
any: function(node, context) { "class": function(node) {
return fill("any", { resultVar: resultVar(context.resultIndex) });
},
"class": function(node, context) {
var regexp; var regexp;
if (node.parts.length > 0) { if (node.parts.length > 0) {
@ -942,11 +835,7 @@ PEG.compiler.emitter = function(ast) {
regexp = node.inverted ? '/^[\\S\\s]/' : '/^(?!)/'; regexp = node.inverted ? '/^[\\S\\s]/' : '/^(?!)/';
} }
return fill("class", { return fill("class", { node: node, regexp: regexp });
node: node,
regexp: regexp,
resultVar: resultVar(context.resultIndex)
});
} }
}); });

@ -188,80 +188,131 @@ PEG.compiler.passes = {
}, },
/* /*
* Adds |resultStackDepth| and |posStackDepth| properties to each AST node. * Computes names of variables used for storing match results and parse
* These properties specify how many positions on the result or position stack * positions in generated code. These variables are organized as two stacks.
* code generated by the emitter for the node will use. This information is * The following will hold after running this pass:
* used to declare varibles holding the stack data in the generated code. *
* * All nodes except "grammar" and "rule" nodes will have a |resultVar|
* property. It will contain a name of the variable that will store a
* match result of the expression represented by the node in generated
* code.
*
* * Some nodes will have a |posVar| property. It will contain a name of the
* variable that will store a parse position in generated code.
*
* * All "rule" nodes will contain |resultVars| and |posVars| properties.
* They will contain a list of values of |resultVar| and |posVar|
* properties used in rule's subnodes. (This is useful to declare
* variables in generated code.)
*/ */
computeStackDepths: function(ast) { computeVarNames: function(ast) {
function computeZeroes(node) { function resultVar(index) { return "result" + index; }
node.resultStackDepth = 0; function posVar(index) { return "pos" + index; }
node.posStackDepth = 0;
function computeLeaf(node, index) {
node.resultVar = resultVar(index.result);
return { result: 0, pos: 0 };
} }
function computeFromExpression(resultStackDelta, posStackDelta) { function computeFromExpression(delta) {
return function(node) { return function(node, index) {
compute(node.expression); var depth = compute(
node.resultStackDepth = node.expression.resultStackDepth + resultStackDelta; node.expression,
node.posStackDepth = node.expression.posStackDepth + posStackDelta; {
result: index.result + delta.result,
pos: index.pos + delta.pos
}
);
node.resultVar = resultVar(index.result);
if (delta.pos !== 0) {
node.posVar = posVar(index.pos);
}
return {
result: depth.result + delta.result,
pos: depth.pos + delta.pos
};
}; };
} }
var compute = buildNodeVisitor({ var compute = buildNodeVisitor({
grammar: grammar:
function(node) { function(node, index) {
for (var name in node.rules) { var name;
compute(node.rules[name]);
for (name in node.rules) {
compute(node.rules[name], index);
} }
}, },
rule: computeFromExpression(1, 0), rule:
function(node, index) {
var depth = compute(node.expression, index);
node.resultVar = resultVar(index.result);
node.resultVars = map(range(depth.result + 1), resultVar);
node.posVars = map(range(depth.pos), posVar);
},
choice: choice:
function(node) { function(node, index) {
each(node.alternatives, compute); var depths = map(node.alternatives, function(alternative) {
node.resultStackDepth = Math.max.apply( return compute(alternative, index);
null, });
map(node.alternatives, function(e) { return e.resultStackDepth; })
); node.resultVar = resultVar(index.result);
node.posStackDepth = Math.max.apply(
null, return {
map(node.alternatives, function(e) { return e.posStackDepth; }) result: Math.max.apply(null, pluck(depths, "result")),
); pos: Math.max.apply(null, pluck(depths, "pos"))
};
}, },
sequence: sequence:
function(node) { function(node, index) {
each(node.elements, compute); var depths = map(node.elements, function(element, i) {
node.resultStackDepth = node.elements.length > 0 return compute(
? Math.max.apply( element,
null, { result: index.result + i, pos: index.pos + 1 }
map(node.elements, function(e, i) { return i + e.resultStackDepth; }) );
) });
: 0;
node.posStackDepth = node.elements.length > 0 node.resultVar = resultVar(index.result);
? 1 + Math.max.apply( node.posVar = posVar(index.pos);
null,
map(node.elements, function(e) { return e.posStackDepth; }) return {
) result:
: 1; node.elements.length > 0
? Math.max.apply(
null,
map(depths, function(d, i) { return i + d.result; })
)
: 0,
pos:
node.elements.length > 0
? 1 + Math.max.apply(null, pluck(depths, "pos"))
: 1
};
}, },
labeled: computeFromExpression(0, 0), labeled: computeFromExpression({ result: 0, pos: 0 }),
simple_and: computeFromExpression(0, 1), simple_and: computeFromExpression({ result: 0, pos: 1 }),
simple_not: computeFromExpression(0, 1), simple_not: computeFromExpression({ result: 0, pos: 1 }),
semantic_and: computeZeroes, semantic_and: computeLeaf,
semantic_not: computeZeroes, semantic_not: computeLeaf,
optional: computeFromExpression(0, 0), optional: computeFromExpression({ result: 0, pos: 0 }),
zero_or_more: computeFromExpression(1, 0), zero_or_more: computeFromExpression({ result: 1, pos: 0 }),
one_or_more: computeFromExpression(1, 0), one_or_more: computeFromExpression({ result: 1, pos: 0 }),
action: computeFromExpression(0, 1), action: computeFromExpression({ result: 0, pos: 1 }),
rule_ref: computeZeroes, rule_ref: computeLeaf,
literal: computeZeroes, literal: computeLeaf,
any: computeZeroes, any: computeLeaf,
"class": computeZeroes "class": computeLeaf
}); });
compute(ast); compute(ast, { result: 0, pos: 0 });
} }
}; };

@ -42,6 +42,10 @@ function map(array, callback) {
return result; return result;
} }
function pluck(array, key) {
return map(array, function (e) { return e[key]; });
}
/* /*
* Returns a string padded on the left to a desired length with a character. * Returns a string padded on the left to a desired length with a character.
* *

@ -216,94 +216,229 @@ test("removes proxy rules", function() {
} }
}); });
test("computes stack depths", function() { test("computes variable names", function() {
var leafDetails = { resultVar: "result0" },
choiceDetails = {
resultVar: "result0",
alternatives: [
{ resultVar: "result0", posVar: "pos0" },
{ resultVar: "result0", posVar: "pos0" },
{ resultVar: "result0", posVar: "pos0" }
]
},
sequenceDetails = {
resultVar: "result0",
posVar: "pos0",
elements: [
{ resultVar: "result0", posVar: "pos1" },
{ resultVar: "result1", posVar: "pos1" },
{ resultVar: "result2", posVar: "pos1" }
]
};
var cases = [ var cases = [
/* Choice */ /* Choice */
{ {
grammar: 'start = "a" / "b" / "c"', grammar: 'start = &"a" / &"b" / &"c"',
resultStackDepth: 1, resultVars: ["result0"],
posStackDepth: 0 posVars: ["pos0"],
details: choiceDetails
}, },
{ {
grammar: 'start = "a" / "b"* / "c"', grammar: 'start = &"a" / &"b"* / &"c"',
resultStackDepth: 2, resultVars: ["result0", "result1"],
posStackDepth: 0 posVars: ["pos0"],
details: choiceDetails
}, },
{ {
grammar: 'start = "a" / &"b" / "c"', grammar: 'start = &"a" / &(&"b") / &"c"',
resultStackDepth: 1, resultVars: ["result0"],
posStackDepth: 1 posVars: ["pos0", "pos1"],
details: choiceDetails
}, },
/* Sequence */ /* Sequence */
{ {
grammar: 'start = ', grammar: 'start = ',
resultStackDepth: 1, resultVars: ["result0"],
posStackDepth: 1 posVars: ["pos0"],
details: { resultVar: "result0", posVar: "pos0" }
}, },
{ {
grammar: 'start = "a" "b" "c"', grammar: 'start = &"a" &"b" &"c"',
resultStackDepth: 3, resultVars: ["result0", "result1", "result2"],
posStackDepth: 1 posVars: ["pos0", "pos1"],
details: sequenceDetails
}, },
{ {
grammar: 'start = "a" "b" "c"*', grammar: 'start = &"a" &"b" &"c"*',
resultStackDepth: 4, resultVars: ["result0", "result1", "result2", "result3"],
posStackDepth: 1 posVars: ["pos0", "pos1"],
details: sequenceDetails
}, },
{ {
grammar: 'start = "a" "b"* "c"', grammar: 'start = &"a" &"b"* &"c"',
resultStackDepth: 3, resultVars: ["result0", "result1", "result2"],
posStackDepth: 1 posVars: ["pos0", "pos1"],
details: sequenceDetails
}, },
{ {
grammar: 'start = "a" ("b"*)* "c"', grammar: 'start = &"a" &("b"*)* &"c"',
resultStackDepth: 4, resultVars: ["result0", "result1", "result2", "result3"],
posStackDepth: 1 posVars: ["pos0", "pos1"],
details: sequenceDetails
}, },
{ {
grammar: 'start = "a"* "b" "c"', grammar: 'start = &"a"* &"b" &"c"',
resultStackDepth: 3, resultVars: ["result0", "result1", "result2"],
posStackDepth: 1 posVars: ["pos0", "pos1"],
details: sequenceDetails
}, },
{ {
grammar: 'start = ("a"*)* "b" "c"', grammar: 'start = &("a"*)* &"b" &"c"',
resultStackDepth: 3, resultVars: ["result0", "result1", "result2"],
posStackDepth: 1 posVars: ["pos0", "pos1"],
details: sequenceDetails
}, },
{ {
grammar: 'start = (("a"*)*)* "b" "c"', grammar: 'start = &(("a"*)*)* &"b" &"c"',
resultStackDepth: 4, resultVars: ["result0", "result1", "result2", "result3"],
posStackDepth: 1 posVars: ["pos0", "pos1"],
details: sequenceDetails
}, },
{ {
grammar: 'start = "a" &"b" "c"', grammar: 'start = &"a" &(&"b") &"c"',
resultStackDepth: 3, resultVars: ["result0", "result1", "result2"],
posStackDepth: 2 posVars: ["pos0", "pos1", "pos2"],
details: sequenceDetails
}, },
/* Others */ /* Others */
{ grammar: 'start = label:"a"', resultStackDepth: 1, posStackDepth: 0 }, {
{ grammar: 'start = &"a"', resultStackDepth: 1, posStackDepth: 1 }, grammar: 'start = label:&"a"',
{ grammar: 'start = !"a"', resultStackDepth: 1, posStackDepth: 1 }, resultVars: ["result0"],
{ grammar: 'start = &{ code }', resultStackDepth: 1, posStackDepth: 0 }, posVars: ["pos0"],
{ grammar: 'start = !{ code }', resultStackDepth: 1, posStackDepth: 0 }, details: {
{ grammar: 'start = "a"?', resultStackDepth: 1, posStackDepth: 0 }, resultVar: "result0",
{ grammar: 'start = "a"*', resultStackDepth: 2, posStackDepth: 0 }, expression: { resultVar: "result0", posVar: "pos0" }
{ grammar: 'start = "a"+', resultStackDepth: 2, posStackDepth: 0 }, }
{ grammar: 'start = "a" { code }', resultStackDepth: 1, posStackDepth: 1 }, },
{ grammar: 'start = a', resultStackDepth: 1, posStackDepth: 0 }, {
{ grammar: 'start = "a"', resultStackDepth: 1, posStackDepth: 0 }, grammar: 'start = &(&"a")',
{ grammar: 'start = .', resultStackDepth: 1, posStackDepth: 0 }, resultVars: ["result0"],
{ grammar: 'start = [a-z]', resultStackDepth: 1, posStackDepth: 0 } posVars: ["pos0", "pos1"],
details: {
resultVar: "result0",
posVar: "pos0",
expression: { resultVar: "result0", posVar: "pos1" }
}
},
{
grammar: 'start = !(&"a")',
resultVars: ["result0"],
posVars: ["pos0", "pos1"],
details: {
resultVar: "result0",
posVar: "pos0",
expression: { resultVar: "result0", posVar: "pos1" }
}
},
{
grammar: 'start = &{ code }',
resultVars: ["result0"],
posVars: [],
details: leafDetails
},
{
grammar: 'start = !{ code }',
resultVars: ["result0"],
posVars: [],
details: leafDetails
},
{
grammar: 'start = (&"a")?',
resultVars: ["result0"],
posVars: ["pos0"],
details: {
resultVar: "result0",
expression: { resultVar: "result0", posVar: "pos0" }
}
},
{
grammar: 'start = (&"a")*',
resultVars: ["result0", "result1"],
posVars: ["pos0"],
details: {
resultVar: "result0",
expression: { resultVar: "result1", posVar: "pos0" }
}
},
{
grammar: 'start = (&"a")+',
resultVars: ["result0", "result1"],
posVars: ["pos0"],
details: {
resultVar: "result0",
expression: { resultVar: "result1", posVar: "pos0" }
}
},
{
grammar: 'start = &"a" { code }',
resultVars: ["result0"],
posVars: ["pos0", "pos1"],
details: {
resultVar: "result0",
posVar: "pos0",
expression: { resultVar: "result0", posVar: "pos1" }
}
},
{
grammar: 'start = a',
resultVars: ["result0"],
posVars: [],
details: leafDetails
},
{
grammar: 'start = "a"',
resultVars: ["result0"],
posVars: [],
details: leafDetails
},
{
grammar: 'start = .',
resultVars: ["result0"],
posVars: [],
details: leafDetails
},
{
grammar: 'start = [a-z]',
resultVars: ["result0"],
posVars: [],
details: leafDetails
}
]; ];
function checkDetails(node, details) {
for (var key in details) {
if (Object.prototype.toString.call(details[key]) === "[object Array]") {
for (var i = 0; i < details[key].length; i++) {
checkDetails(node[key][i], details[key][i]);
}
} else if (typeof details[key] === "object") {
checkDetails(node[key], details[key]);
} else {
strictEqual(node[key], details[key]);
}
}
}
for (var i = 0; i < cases.length; i++) { for (var i = 0; i < cases.length; i++) {
var ast = PEG.parser.parse(cases[i].grammar); var ast = PEG.parser.parse(cases[i].grammar);
PEG.compiler.passes.computeStackDepths(ast); PEG.compiler.passes.computeVarNames(ast);
deepEqual(ast.rules["start"].resultStackDepth, cases[i].resultStackDepth); deepEqual(ast.rules["start"].resultVars, cases[i].resultVars);
deepEqual(ast.rules["start"].posStackDepth, cases[i].posStackDepth); deepEqual(ast.rules["start"].posVars, cases[i].posVars);
checkDetails(ast.rules["start"].expression, cases[i].details);
} }
}); });

Loading…
Cancel
Save