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 12 years ago
parent 9d96e1e303
commit efc38eef9b

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

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

@ -188,80 +188,131 @@ PEG.compiler.passes = {
},
/*
* Adds |resultStackDepth| and |posStackDepth| properties to each AST node.
* These properties specify how many positions on the result or position stack
* code generated by the emitter for the node will use. This information is
* used to declare varibles holding the stack data in the generated code.
* Computes names of variables used for storing match results and parse
* positions in generated code. These variables are organized as two stacks.
* The following will hold after running this pass:
*
* * 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) {
function computeZeroes(node) {
node.resultStackDepth = 0;
node.posStackDepth = 0;
computeVarNames: function(ast) {
function resultVar(index) { return "result" + index; }
function posVar(index) { return "pos" + index; }
function computeLeaf(node, index) {
node.resultVar = resultVar(index.result);
return { result: 0, pos: 0 };
}
function computeFromExpression(resultStackDelta, posStackDelta) {
return function(node) {
compute(node.expression);
node.resultStackDepth = node.expression.resultStackDepth + resultStackDelta;
node.posStackDepth = node.expression.posStackDepth + posStackDelta;
function computeFromExpression(delta) {
return function(node, index) {
var depth = compute(
node.expression,
{
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({
grammar:
function(node) {
for (var name in node.rules) {
compute(node.rules[name]);
function(node, index) {
var 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:
function(node) {
each(node.alternatives, compute);
node.resultStackDepth = Math.max.apply(
null,
map(node.alternatives, function(e) { return e.resultStackDepth; })
);
node.posStackDepth = Math.max.apply(
null,
map(node.alternatives, function(e) { return e.posStackDepth; })
);
function(node, index) {
var depths = map(node.alternatives, function(alternative) {
return compute(alternative, index);
});
node.resultVar = resultVar(index.result);
return {
result: Math.max.apply(null, pluck(depths, "result")),
pos: Math.max.apply(null, pluck(depths, "pos"))
};
},
sequence:
function(node) {
each(node.elements, compute);
node.resultStackDepth = node.elements.length > 0
? Math.max.apply(
null,
map(node.elements, function(e, i) { return i + e.resultStackDepth; })
)
: 0;
node.posStackDepth = node.elements.length > 0
? 1 + Math.max.apply(
null,
map(node.elements, function(e) { return e.posStackDepth; })
)
: 1;
function(node, index) {
var depths = map(node.elements, function(element, i) {
return compute(
element,
{ result: index.result + i, pos: index.pos + 1 }
);
});
node.resultVar = resultVar(index.result);
node.posVar = posVar(index.pos);
return {
result:
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),
simple_and: computeFromExpression(0, 1),
simple_not: computeFromExpression(0, 1),
semantic_and: computeZeroes,
semantic_not: computeZeroes,
optional: computeFromExpression(0, 0),
zero_or_more: computeFromExpression(1, 0),
one_or_more: computeFromExpression(1, 0),
action: computeFromExpression(0, 1),
rule_ref: computeZeroes,
literal: computeZeroes,
any: computeZeroes,
"class": computeZeroes
labeled: computeFromExpression({ result: 0, pos: 0 }),
simple_and: computeFromExpression({ result: 0, pos: 1 }),
simple_not: computeFromExpression({ result: 0, pos: 1 }),
semantic_and: computeLeaf,
semantic_not: computeLeaf,
optional: computeFromExpression({ result: 0, pos: 0 }),
zero_or_more: computeFromExpression({ result: 1, pos: 0 }),
one_or_more: computeFromExpression({ result: 1, pos: 0 }),
action: computeFromExpression({ result: 0, pos: 1 }),
rule_ref: computeLeaf,
literal: computeLeaf,
any: computeLeaf,
"class": computeLeaf
});
compute(ast);
compute(ast, { result: 0, pos: 0 });
}
};

@ -42,6 +42,10 @@ function map(array, callback) {
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.
*

@ -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 = [
/* Choice */
{
grammar: 'start = "a" / "b" / "c"',
resultStackDepth: 1,
posStackDepth: 0
grammar: 'start = &"a" / &"b" / &"c"',
resultVars: ["result0"],
posVars: ["pos0"],
details: choiceDetails
},
{
grammar: 'start = "a" / "b"* / "c"',
resultStackDepth: 2,
posStackDepth: 0
grammar: 'start = &"a" / &"b"* / &"c"',
resultVars: ["result0", "result1"],
posVars: ["pos0"],
details: choiceDetails
},
{
grammar: 'start = "a" / &"b" / "c"',
resultStackDepth: 1,
posStackDepth: 1
grammar: 'start = &"a" / &(&"b") / &"c"',
resultVars: ["result0"],
posVars: ["pos0", "pos1"],
details: choiceDetails
},
/* Sequence */
{
grammar: 'start = ',
resultStackDepth: 1,
posStackDepth: 1
grammar: 'start = ',
resultVars: ["result0"],
posVars: ["pos0"],
details: { resultVar: "result0", posVar: "pos0" }
},
{
grammar: 'start = "a" "b" "c"',
resultStackDepth: 3,
posStackDepth: 1
grammar: 'start = &"a" &"b" &"c"',
resultVars: ["result0", "result1", "result2"],
posVars: ["pos0", "pos1"],
details: sequenceDetails
},
{
grammar: 'start = "a" "b" "c"*',
resultStackDepth: 4,
posStackDepth: 1
grammar: 'start = &"a" &"b" &"c"*',
resultVars: ["result0", "result1", "result2", "result3"],
posVars: ["pos0", "pos1"],
details: sequenceDetails
},
{
grammar: 'start = "a" "b"* "c"',
resultStackDepth: 3,
posStackDepth: 1
grammar: 'start = &"a" &"b"* &"c"',
resultVars: ["result0", "result1", "result2"],
posVars: ["pos0", "pos1"],
details: sequenceDetails
},
{
grammar: 'start = "a" ("b"*)* "c"',
resultStackDepth: 4,
posStackDepth: 1
grammar: 'start = &"a" &("b"*)* &"c"',
resultVars: ["result0", "result1", "result2", "result3"],
posVars: ["pos0", "pos1"],
details: sequenceDetails
},
{
grammar: 'start = "a"* "b" "c"',
resultStackDepth: 3,
posStackDepth: 1
grammar: 'start = &"a"* &"b" &"c"',
resultVars: ["result0", "result1", "result2"],
posVars: ["pos0", "pos1"],
details: sequenceDetails
},
{
grammar: 'start = ("a"*)* "b" "c"',
resultStackDepth: 3,
posStackDepth: 1
grammar: 'start = &("a"*)* &"b" &"c"',
resultVars: ["result0", "result1", "result2"],
posVars: ["pos0", "pos1"],
details: sequenceDetails
},
{
grammar: 'start = (("a"*)*)* "b" "c"',
resultStackDepth: 4,
posStackDepth: 1
grammar: 'start = &(("a"*)*)* &"b" &"c"',
resultVars: ["result0", "result1", "result2", "result3"],
posVars: ["pos0", "pos1"],
details: sequenceDetails
},
{
grammar: 'start = "a" &"b" "c"',
resultStackDepth: 3,
posStackDepth: 2
grammar: 'start = &"a" &(&"b") &"c"',
resultVars: ["result0", "result1", "result2"],
posVars: ["pos0", "pos1", "pos2"],
details: sequenceDetails
},
/* Others */
{ grammar: 'start = label:"a"', resultStackDepth: 1, posStackDepth: 0 },
{ grammar: 'start = &"a"', resultStackDepth: 1, posStackDepth: 1 },
{ grammar: 'start = !"a"', resultStackDepth: 1, posStackDepth: 1 },
{ grammar: 'start = &{ code }', resultStackDepth: 1, posStackDepth: 0 },
{ grammar: 'start = !{ code }', resultStackDepth: 1, posStackDepth: 0 },
{ grammar: 'start = "a"?', resultStackDepth: 1, posStackDepth: 0 },
{ grammar: 'start = "a"*', resultStackDepth: 2, posStackDepth: 0 },
{ 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 = .', resultStackDepth: 1, posStackDepth: 0 },
{ grammar: 'start = [a-z]', resultStackDepth: 1, posStackDepth: 0 }
{
grammar: 'start = label:&"a"',
resultVars: ["result0"],
posVars: ["pos0"],
details: {
resultVar: "result0",
expression: { resultVar: "result0", posVar: "pos0" }
}
},
{
grammar: 'start = &(&"a")',
resultVars: ["result0"],
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++) {
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"].posStackDepth, cases[i].posStackDepth);
deepEqual(ast.rules["start"].resultVars, cases[i].resultVars);
deepEqual(ast.rules["start"].posVars, cases[i].posVars);
checkDetails(ast.rules["start"].expression, cases[i].details);
}
});

Loading…
Cancel
Save