Replace variable name computations by computations of indices

This commit replaces all variable name computations in |computeVarNames|
and |computeParams| passes by computations of indices. The actual names
are computed later in the |generateCode| pass.

This change makes the code generator the only place that deals with the
actual variable names, making them easier to change for example.

The code generator code seems bit more complicated after the change, but
this complexity will pay off (and mostly disappear) later.
redux
David Majda 12 years ago
parent 725927e05f
commit 2c8b323ade

@ -1,9 +1,20 @@
describe("compiler pass |computeParams|", function() {
function pass(ast) {
PEG.compiler.passes.computeVarNames(ast);
PEG.compiler.passes.computeVarIndices(ast);
PEG.compiler.passes.computeParams(ast);
}
var result0 = { resultIndex: 0, subindices: [] },
result0_0 = { resultIndex: 0, subindices: [0] },
result0_1 = { resultIndex: 0, subindices: [1] },
result0_1_0 = { resultIndex: 0, subindices: [1, 0] },
result0_1_1 = { resultIndex: 0, subindices: [1, 1] },
result0_1_2 = { resultIndex: 0, subindices: [1, 2] },
result0_2 = { resultIndex: 0, subindices: [2] },
result1_9 = { resultIndex: 1, subindices: [9] },
result1 = { resultIndex: 1, subindices: [] };
result2 = { resultIndex: 2, subindices: [] };
function ruleDetails(details) { return { rules: [details] }; }
function expressionDetails(details) {
@ -17,7 +28,7 @@ describe("compiler pass |computeParams|", function() {
describe("basic cases", function() {
it("computes params for an action", function() {
expect(pass).toChangeAST('start = a:"a" { }', expressionDetails({
params: { a: "result0" }
params: { a: result0 }
}));
});
@ -25,7 +36,7 @@ describe("compiler pass |computeParams|", function() {
expect(pass).toChangeAST('start = a:"a" &{ }', expressionDetails({
elements: [
{},
{ params: { a: "result0" } }
{ params: { a: result0 } }
]
}));
});
@ -34,7 +45,7 @@ describe("compiler pass |computeParams|", function() {
expect(pass).toChangeAST('start = a:"a" !{ }', expressionDetails({
elements: [
{},
{ params: { a: "result0" } }
{ params: { a: result0 } }
]
}));
});
@ -44,7 +55,7 @@ describe("compiler pass |computeParams|", function() {
it("computes params for a named", function() {
expect(pass).toChangeAST(
'start "start" = a:"a" { }',
innerExpressionDetails({ params: { a: "result0" } })
innerExpressionDetails({ params: { a: result0 } })
);
});
@ -52,20 +63,20 @@ describe("compiler pass |computeParams|", function() {
expect(pass).toChangeAST(
'start = a:"a" { } / "b" / "c"',
expressionDetails({
alternatives: [{ params: { a: "result0" } }, {}, {}]
alternatives: [{ params: { a: result0 } }, {}, {}]
})
);
expect(pass).toChangeAST(
'start = "a" / "b" / c:"c" { }',
expressionDetails({
alternatives: [{}, {}, { params: { c: "result0" } }]
alternatives: [{}, {}, { params: { c: result0 } }]
})
);
});
it("computes params for an action", function() {
expect(pass).toChangeAST('start = (a:"a" { }) { }', innerExpressionDetails({
params: { a: "result0" }
params: { a: result0 }
}));
});
@ -73,50 +84,50 @@ describe("compiler pass |computeParams|", function() {
expect(pass).toChangeAST(
'start = (a:"a" { }) "b" "c"',
expressionDetails({
elements: [{ params: { a: "result0" } }, {}, {}]
elements: [{ params: { a: result0 } }, {}, {}]
})
);
expect(pass).toChangeAST(
'start = "a" "b" (c:"c" { })',
expressionDetails({
elements: [{}, {}, { params: { c: "result2" } }]
elements: [{}, {}, { params: { c: result2 } }]
})
);
});
it("computes params for a labeled", function() {
expect(pass).toChangeAST('start = a:(b:"b" { })', innerExpressionDetails({
params: { b: "result0" }
params: { b: result0 }
}));
});
it("computes params for a simple and", function() {
expect(pass).toChangeAST('start = &(a:"a" { })', innerExpressionDetails({
params: { a: "result0" }
params: { a: result0 }
}));
});
it("computes params for a simple not", function() {
expect(pass).toChangeAST('start = &(a:"a" { })', innerExpressionDetails({
params: { a: "result0" }
params: { a: result0 }
}));
});
it("computes params for an optional", function() {
expect(pass).toChangeAST('start = (a:"a" { })?', innerExpressionDetails({
params: { a: "result0" }
params: { a: result0 }
}));
});
it("computes params for a zero or more", function() {
expect(pass).toChangeAST('start = (a:"a" { })*', innerExpressionDetails({
params: { a: "result1" }
params: { a: result1 }
}));
});
it("computes params for a one or more", function() {
expect(pass).toChangeAST('start = (a:"a" { })+', innerExpressionDetails({
params: { a: "result1" }
params: { a: result1 }
}));
});
});
@ -139,38 +150,26 @@ describe("compiler pass |computeParams|", function() {
expect(pass).toChangeAST(
'start = a:"a" b:"b" c:"c" { }',
expressionDetails({
params: { a: "result0[0]", b: "result0[1]", c: "result0[2]" }
params: { a: result0_0, b: result0_1, c: result0_2 }
})
);
expect(pass).toChangeAST(
'start = a:"a" (b:"b" c:"c" d:"d") e:"e"{ }',
expressionDetails({
params: {
a: "result0[0]",
b: "result0[1][0]",
c: "result0[1][1]",
d: "result0[1][2]",
e: "result0[2]"
a: result0_0,
b: result0_1_0,
c: result0_1_1,
d: result0_1_2,
e: result0_2
}
})
);
/*
* Regression tests for a bug where e.g. resultVar names like |result10|
* were incorrectly treated as names derived from |result1|, leading to
* incorrect substitution.
*/
expect(pass).toChangeAST(
'start = ("a" "b" "c" "d" "e" "f" "g" "h" "i" j:"j" { })*',
innerExpressionDetails({
params: { j: "result1[9]" } // Buggy code put "result1[0]0" here.
})
);
});
it("creates a new scope for a labeled", function() {
expect(pass).toChangeAST('start = a:(b:"b") { }', expressionDetails({
params: { a: "result0"}
params: { a: result0 }
}));
});

@ -0,0 +1,229 @@
describe("compiler pass |computeVarIndices|", function() {
var pass = PEG.compiler.passes.computeVarIndices;
var leafDetails = { resultIndex: 0 },
choiceDetails = {
resultIndex: 0,
alternatives: [
{ resultIndex: 0, posIndex: 0 },
{ resultIndex: 0, posIndex: 0 },
{ resultIndex: 0, posIndex: 0 }
]
},
sequenceDetails = {
resultIndex: 0,
posIndex: 0,
elements: [
{ resultIndex: 0, posIndex: 1 },
{ resultIndex: 1, posIndex: 1 },
{ resultIndex: 2, posIndex: 1 }
]
};
function ruleDetails(details) { return { rules: [details] }; }
it("computes variable indices for a named", function() {
expect(pass).toChangeAST('start "start" = &"a"', ruleDetails({
resultIndices: [0],
posIndices: [0],
expression: {
resultIndex: 0,
expression: { resultIndex: 0, posIndex: 0 }
}
}));
});
it("computes variable indices for a choice", function() {
expect(pass).toChangeAST('start = &"a" / &"b" / &"c"', ruleDetails({
resultIndices: [0],
posIndices: [0],
expression: choiceDetails
}));
expect(pass).toChangeAST('start = &"a" / &"b"* / &"c"', ruleDetails({
resultIndices: [0, 1],
posIndices: [0],
expression: choiceDetails
}));
expect(pass).toChangeAST('start = &"a" / &(&"b") / &"c"', ruleDetails({
resultIndices: [0],
posIndices: [0, 1],
expression: choiceDetails
}));
});
it("computes variable indices for an action", function() {
expect(pass).toChangeAST('start = &"a" { code }', ruleDetails({
resultIndices: [0],
posIndices: [0, 1],
expression: {
resultIndex: 0,
posIndex: 0,
expression: { resultIndex: 0, posIndex: 1 }
}
}));
});
it("computes variable indices for a sequence", function() {
expect(pass).toChangeAST('start = ', ruleDetails({
resultIndices: [0],
posIndices: [0],
expression: { resultIndex: 0, posIndex: 0 }
}));
expect(pass).toChangeAST('start = &"a" &"b" &"c"', ruleDetails({
resultIndices: [0, 1, 2],
posIndices: [0, 1],
expression: sequenceDetails
}));
expect(pass).toChangeAST('start = &"a" &"b" &"c"*', ruleDetails({
resultIndices: [0, 1, 2, 3],
posIndices: [0, 1],
expression: sequenceDetails
}));
expect(pass).toChangeAST('start = &"a" &"b"* &"c"', ruleDetails({
resultIndices: [0, 1, 2],
posIndices: [0, 1],
expression: sequenceDetails
}));
expect(pass).toChangeAST('start = &"a" &("b"*)* &"c"', ruleDetails({
resultIndices: [0, 1, 2, 3],
posIndices: [0, 1],
expression: sequenceDetails
}));
expect(pass).toChangeAST('start = &"a"* &"b" &"c"', ruleDetails({
resultIndices: [0, 1, 2],
posIndices: [0, 1],
expression: sequenceDetails
}));
expect(pass).toChangeAST('start = &("a"*)* &"b" &"c"', ruleDetails({
resultIndices: [0, 1, 2],
posIndices: [0, 1],
expression: sequenceDetails
}));
expect(pass).toChangeAST('start = &(("a"*)*)* &"b" &"c"', ruleDetails({
resultIndices: [0, 1, 2, 3],
posIndices: [0, 1],
expression: sequenceDetails
}));
expect(pass).toChangeAST('start = &"a" &(&"b") &"c"', ruleDetails({
resultIndices: [0, 1, 2],
posIndices: [0, 1, 2],
expression: sequenceDetails
}));
});
it("computes variable indices for a labeled", function() {
expect(pass).toChangeAST('start = label:&"a"', ruleDetails({
resultIndices: [0],
posIndices: [0],
expression: {
resultIndex: 0,
expression: { resultIndex: 0, posIndex: 0 }
}
}));
});
it("computes variable indices for a simple and", function() {
expect(pass).toChangeAST('start = &(&"a")', ruleDetails({
resultIndices: [0],
posIndices: [0, 1],
expression: {
resultIndex: 0,
posIndex: 0,
expression: { resultIndex: 0, posIndex: 1 }
}
}));
});
it("computes variable indices for a simple not", function() {
expect(pass).toChangeAST('start = !(&"a")', ruleDetails({
resultIndices: [0],
posIndices: [0, 1],
expression: {
resultIndex: 0,
posIndex: 0,
expression: { resultIndex: 0, posIndex: 1 }
}
}));
});
it("computes variable indices for a semantic and", function() {
expect(pass).toChangeAST('start = &{ code }', ruleDetails({
resultIndices: [0],
posIndices: [],
expression: leafDetails
}));
});
it("computes variable indices for a semantic not", function() {
expect(pass).toChangeAST('start = !{ code }', ruleDetails({
resultIndices: [0],
posIndices: [],
expression: leafDetails
}));
});
it("computes variable indices for an optional", function() {
expect(pass).toChangeAST('start = (&"a")?', ruleDetails({
resultIndices: [0],
posIndices: [0],
expression: {
resultIndex: 0,
expression: { resultIndex: 0, posIndex: 0 }
}
}));
});
it("computes variable indices for a zero or more", function() {
expect(pass).toChangeAST('start = (&"a")*', ruleDetails({
resultIndices: [0, 1],
posIndices: [0],
expression: {
resultIndex: 0,
expression: { resultIndex: 1, posIndex: 0 }
}
}));
});
it("computes variable indices for a one or more", function() {
expect(pass).toChangeAST('start = (&"a")+', ruleDetails({
resultIndices: [0, 1],
posIndices: [0],
expression: {
resultIndex: 0,
expression: { resultIndex: 1, posIndex: 0 }
}
}));
});
it("computes variable indices for a rule reference", function() {
expect(pass).toChangeAST('start = a', ruleDetails({
resultIndices: [0],
posIndices: [],
expression: leafDetails
}));
});
it("computes variable indices for a literal", function() {
expect(pass).toChangeAST('start = "a"', ruleDetails({
resultIndices: [0],
posIndices: [],
expression: leafDetails
}));
});
it("computes variable indices for a class", function() {
expect(pass).toChangeAST('start = [a-z]', ruleDetails({
resultIndices: [0],
posIndices: [],
expression: leafDetails
}));
});
it("computes variable indices for an any", function() {
expect(pass).toChangeAST('start = .', ruleDetails({
resultIndices: [0],
posIndices: [],
expression: leafDetails
}));
});
});

@ -1,229 +0,0 @@
describe("compiler pass |computeVarNames|", function() {
var pass = PEG.compiler.passes.computeVarNames;
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" }
]
};
function ruleDetails(details) { return { rules: [details] }; }
it("computes variable names for a named", function() {
expect(pass).toChangeAST('start "start" = &"a"', ruleDetails({
resultVars: ["result0"],
posVars: ["pos0"],
expression: {
resultVar: "result0",
expression: { resultVar: "result0", posVar: "pos0" }
}
}));
});
it("computes variable names for a choice", function() {
expect(pass).toChangeAST('start = &"a" / &"b" / &"c"', ruleDetails({
resultVars: ["result0"],
posVars: ["pos0"],
expression: choiceDetails
}));
expect(pass).toChangeAST('start = &"a" / &"b"* / &"c"', ruleDetails({
resultVars: ["result0", "result1"],
posVars: ["pos0"],
expression: choiceDetails
}));
expect(pass).toChangeAST('start = &"a" / &(&"b") / &"c"', ruleDetails({
resultVars: ["result0"],
posVars: ["pos0", "pos1"],
expression: choiceDetails
}));
});
it("computes variable names for an action", function() {
expect(pass).toChangeAST('start = &"a" { code }', ruleDetails({
resultVars: ["result0"],
posVars: ["pos0", "pos1"],
expression: {
resultVar: "result0",
posVar: "pos0",
expression: { resultVar: "result0", posVar: "pos1" }
}
}));
});
it("computes variable names for a sequence", function() {
expect(pass).toChangeAST('start = ', ruleDetails({
resultVars: ["result0"],
posVars: ["pos0"],
expression: { resultVar: "result0", posVar: "pos0" }
}));
expect(pass).toChangeAST('start = &"a" &"b" &"c"', ruleDetails({
resultVars: ["result0", "result1", "result2"],
posVars: ["pos0", "pos1"],
expression: sequenceDetails
}));
expect(pass).toChangeAST('start = &"a" &"b" &"c"*', ruleDetails({
resultVars: ["result0", "result1", "result2", "result3"],
posVars: ["pos0", "pos1"],
expression: sequenceDetails
}));
expect(pass).toChangeAST('start = &"a" &"b"* &"c"', ruleDetails({
resultVars: ["result0", "result1", "result2"],
posVars: ["pos0", "pos1"],
expression: sequenceDetails
}));
expect(pass).toChangeAST('start = &"a" &("b"*)* &"c"', ruleDetails({
resultVars: ["result0", "result1", "result2", "result3"],
posVars: ["pos0", "pos1"],
expression: sequenceDetails
}));
expect(pass).toChangeAST('start = &"a"* &"b" &"c"', ruleDetails({
resultVars: ["result0", "result1", "result2"],
posVars: ["pos0", "pos1"],
expression: sequenceDetails
}));
expect(pass).toChangeAST('start = &("a"*)* &"b" &"c"', ruleDetails({
resultVars: ["result0", "result1", "result2"],
posVars: ["pos0", "pos1"],
expression: sequenceDetails
}));
expect(pass).toChangeAST('start = &(("a"*)*)* &"b" &"c"', ruleDetails({
resultVars: ["result0", "result1", "result2", "result3"],
posVars: ["pos0", "pos1"],
expression: sequenceDetails
}));
expect(pass).toChangeAST('start = &"a" &(&"b") &"c"', ruleDetails({
resultVars: ["result0", "result1", "result2"],
posVars: ["pos0", "pos1", "pos2"],
expression: sequenceDetails
}));
});
it("computes variable names for a labeled", function() {
expect(pass).toChangeAST('start = label:&"a"', ruleDetails({
resultVars: ["result0"],
posVars: ["pos0"],
expression: {
resultVar: "result0",
expression: { resultVar: "result0", posVar: "pos0" }
}
}));
});
it("computes variable names for a simple and", function() {
expect(pass).toChangeAST('start = &(&"a")', ruleDetails({
resultVars: ["result0"],
posVars: ["pos0", "pos1"],
expression: {
resultVar: "result0",
posVar: "pos0",
expression: { resultVar: "result0", posVar: "pos1" }
}
}));
});
it("computes variable names for a simple not", function() {
expect(pass).toChangeAST('start = !(&"a")', ruleDetails({
resultVars: ["result0"],
posVars: ["pos0", "pos1"],
expression: {
resultVar: "result0",
posVar: "pos0",
expression: { resultVar: "result0", posVar: "pos1" }
}
}));
});
it("computes variable names for a semantic and", function() {
expect(pass).toChangeAST('start = &{ code }', ruleDetails({
resultVars: ["result0"],
posVars: [],
expression: leafDetails
}));
});
it("computes variable names for a semantic not", function() {
expect(pass).toChangeAST('start = !{ code }', ruleDetails({
resultVars: ["result0"],
posVars: [],
expression: leafDetails
}));
});
it("computes variable names for an optional", function() {
expect(pass).toChangeAST('start = (&"a")?', ruleDetails({
resultVars: ["result0"],
posVars: ["pos0"],
expression: {
resultVar: "result0",
expression: { resultVar: "result0", posVar: "pos0" }
}
}));
});
it("computes variable names for a zero or more", function() {
expect(pass).toChangeAST('start = (&"a")*', ruleDetails({
resultVars: ["result0", "result1"],
posVars: ["pos0"],
expression: {
resultVar: "result0",
expression: { resultVar: "result1", posVar: "pos0" }
}
}));
});
it("computes variable names for a one or more", function() {
expect(pass).toChangeAST('start = (&"a")+', ruleDetails({
resultVars: ["result0", "result1"],
posVars: ["pos0"],
expression: {
resultVar: "result0",
expression: { resultVar: "result1", posVar: "pos0" }
}
}));
});
it("computes variable names for a rule reference", function() {
expect(pass).toChangeAST('start = a', ruleDetails({
resultVars: ["result0"],
posVars: [],
expression: leafDetails
}));
});
it("computes variable names for a literal", function() {
expect(pass).toChangeAST('start = "a"', ruleDetails({
resultVars: ["result0"],
posVars: [],
expression: leafDetails
}));
});
it("computes variable names for a class", function() {
expect(pass).toChangeAST('start = [a-z]', ruleDetails({
resultVars: ["result0"],
posVars: [],
expression: leafDetails
}));
});
it("computes variable names for an any", function() {
expect(pass).toChangeAST('start = .', ruleDetails({
resultVars: ["result0"],
posVars: [],
expression: leafDetails
}));
});
});

@ -12,7 +12,7 @@
<script src="compiler/passes/report-missing-rules.spec.js"></script>
<script src="compiler/passes/report-left-recursion.spec.js"></script>
<script src="compiler/passes/remove-proxy-rules.spec.js"></script>
<script src="compiler/passes/compute-var-names.spec.js"></script>
<script src="compiler/passes/compute-var-indices.spec.js"></script>
<script src="compiler/passes/compute-params.spec.js"></script>
<script>
(function() {

@ -7,7 +7,7 @@ PEG.compiler = {
"reportMissingRules",
"reportLeftRecursion",
"removeProxyRules",
"computeVarNames",
"computeVarIndices",
"computeParams",
"generateCode"
],

@ -10,6 +10,6 @@ PEG.compiler.passes = {};
// @include "passes/report-missing-rules.js"
// @include "passes/report-left-recursion.js"
// @include "passes/remove-proxy-rules.js"
// @include "passes/compute-var-names.js"
// @include "passes/compute-var-indices.js"
// @include "passes/compute-params.js"
// @include "passes/generate-code.js"

@ -65,10 +65,12 @@ PEG.compiler.passes.computeParams = function(ast) {
var env = envs[envs.length - 1], name;
function fixup(name) {
each(pluck(node.elements, "resultVar"), function(resultVar, i) {
if ((new RegExp("^" + resultVar + "(\\[\\d+\\])*$")).test(env[name])) {
env[name] = node.resultVar + "[" + i + "]"
+ env[name].substr(resultVar.length);
each(pluck(node.elements, "resultIndex"), function(resultIndex, i) {
if (env[name].resultIndex === resultIndex) {
env[name] = {
resultIndex: node.resultIndex,
subindices: [i].concat(env[name].subindices)
};
}
});
}
@ -82,7 +84,10 @@ PEG.compiler.passes.computeParams = function(ast) {
labeled:
function(node) {
envs[envs.length - 1][node.label] = node.resultVar;
envs[envs.length - 1][node.label] = {
resultIndex: node.resultIndex,
subindices: []
};
scoped(function() { compute(node.expression); });
},

@ -1,26 +1,23 @@
/*
* Computes names of variables used for storing match results and parse
* Computes indices 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.
* * All nodes except "grammar" and "rule" nodes will have a |resultIndex|
* property. It will contain an index 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.
* * Some nodes will have a |posIndex| property. It will contain an index 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.)
* * All "rule" nodes will contain |resultIndices| and |posIndices|
* properties. They will contain a list of values of |resultIndex| and
* |posIndex| properties used in rule's subnodes. (This is useful to declare
* variables in generated code.)
*/
PEG.compiler.passes.computeVarNames = function(ast) {
function resultVar(index) { return "result" + index; }
function posVar(index) { return "pos" + index; }
PEG.compiler.passes.computeVarIndices = function(ast) {
function computeLeaf(node, index) {
node.resultVar = resultVar(index.result);
node.resultIndex = index.result;
return { result: 0, pos: 0 };
}
@ -35,9 +32,9 @@ PEG.compiler.passes.computeVarNames = function(ast) {
}
);
node.resultVar = resultVar(index.result);
node.resultIndex = index.result;
if (delta.pos !== 0) {
node.posVar = posVar(index.pos);
node.posIndex = index.pos;
}
return {
@ -59,9 +56,9 @@ PEG.compiler.passes.computeVarNames = function(ast) {
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);
node.resultIndex = index.result;
node.resultIndices = range(depth.result + 1);
node.posIndices = range(depth.pos);
},
named: computeFromExpression({ result: 0, pos: 0 }),
@ -72,7 +69,7 @@ PEG.compiler.passes.computeVarNames = function(ast) {
return compute(alternative, index);
});
node.resultVar = resultVar(index.result);
node.resultIndex = index.result;
return {
result: Math.max.apply(null, pluck(depths, "result")),
@ -91,8 +88,8 @@ PEG.compiler.passes.computeVarNames = function(ast) {
);
});
node.resultVar = resultVar(index.result);
node.posVar = posVar(index.pos);
node.resultIndex = index.result;
node.posIndex = index.pos;
return {
result:

@ -558,11 +558,11 @@ PEG.compiler.passes.generateCode = function(ast, options) {
' }',
' ',
' #end',
' #if node.resultVars.length > 0',
' var #{node.resultVars.join(", ")};',
' #if node.resultIndices.length > 0',
' var #{map(node.resultIndices, resultVar).join(", ")};',
' #end',
' #if node.posVars.length > 0',
' var #{node.posVars.join(", ")};',
' #if node.posIndices.length > 0',
' var #{map(node.posIndices, posVar).join(", ")};',
' #end',
' ',
' #block emit(node.expression)',
@ -570,17 +570,17 @@ PEG.compiler.passes.generateCode = function(ast, options) {
' ',
' cache[cacheKey] = {',
' nextPos: #{posClone("pos")},',
' result: #{node.resultVar}',
' result: #{resultVar(node.resultIndex)}',
' };',
' #end',
' return #{node.resultVar};',
' return #{resultVar(node.resultIndex)};',
'}'
],
named: [
'reportFailures++;',
'#block emit(node.expression)',
'reportFailures--;',
'if (reportFailures === 0 && #{node.resultVar} === null) {',
'if (reportFailures === 0 && #{resultVar(node.resultIndex)} === null) {',
' matchFailed(#{string(node.name)});',
'}'
],
@ -589,17 +589,17 @@ PEG.compiler.passes.generateCode = function(ast, options) {
'#block nextAlternativesCode'
],
"choice.next": [
'if (#{node.resultVar} === null) {',
'if (#{resultVar(node.resultIndex)} === null) {',
' #block code',
'}'
],
action: [
'#{posSave(node)};',
'#block emit(node.expression)',
'if (#{node.resultVar} !== null) {',
' #{node.resultVar} = (function(#{(options.trackLineAndColumn ? ["offset", "line", "column"] : ["offset"]).concat(keys(node.params)).join(", ")}) {#{node.code}})(#{(options.trackLineAndColumn ? [node.posVar + ".offset", node.posVar + ".line", node.posVar + ".column"] : [node.posVar]).concat(values(node.params)).join(", ")});',
'if (#{resultVar(node.resultIndex)} !== null) {',
' #{resultVar(node.resultIndex)} = (function(#{(options.trackLineAndColumn ? ["offset", "line", "column"] : ["offset"]).concat(keys(node.params)).join(", ")}) {#{node.code}})(#{(options.trackLineAndColumn ? [posVar(node.posIndex) + ".offset", posVar(node.posIndex) + ".line", posVar(node.posIndex) + ".column"] : [posVar(node.posIndex)]).concat(map(values(node.params), param)).join(", ")});',
'}',
'if (#{node.resultVar} === null) {',
'if (#{resultVar(node.resultIndex)} === null) {',
' #{posRestore(node)};',
'}'
],
@ -609,26 +609,26 @@ PEG.compiler.passes.generateCode = function(ast, options) {
],
"sequence.iteration": [
'#block emit(element)',
'if (#{element.resultVar} !== null) {',
'if (#{resultVar(element.resultIndex)} !== null) {',
' #block code',
'} else {',
' #{node.resultVar} = null;',
' #{resultVar(node.resultIndex)} = null;',
' #{posRestore(node)};',
'}'
],
"sequence.inner": [
'#{node.resultVar} = [#{pluck(node.elements, "resultVar").join(", ")}];'
'#{resultVar(node.resultIndex)} = [#{map(pluck(node.elements, "resultIndex"), resultVar).join(", ")}];'
],
simple_and: [
'#{posSave(node)};',
'reportFailures++;',
'#block emit(node.expression)',
'reportFailures--;',
'if (#{node.resultVar} !== null) {',
' #{node.resultVar} = "";',
'if (#{resultVar(node.resultIndex)} !== null) {',
' #{resultVar(node.resultIndex)} = "";',
' #{posRestore(node)};',
'} else {',
' #{node.resultVar} = null;',
' #{resultVar(node.resultIndex)} = null;',
'}'
],
simple_not: [
@ -636,49 +636,49 @@ PEG.compiler.passes.generateCode = function(ast, options) {
'reportFailures++;',
'#block emit(node.expression)',
'reportFailures--;',
'if (#{node.resultVar} === null) {',
' #{node.resultVar} = "";',
'if (#{resultVar(node.resultIndex)} === null) {',
' #{resultVar(node.resultIndex)} = "";',
'} else {',
' #{node.resultVar} = null;',
' #{resultVar(node.resultIndex)} = null;',
' #{posRestore(node)};',
'}'
],
semantic_and: [
'#{node.resultVar} = (function(#{(options.trackLineAndColumn ? ["offset", "line", "column"] : ["offset"]).concat(keys(node.params)).join(", ")}) {#{node.code}})(#{(options.trackLineAndColumn ? ["pos.offset", "pos.line", "pos.column"] : ["pos"]).concat(values(node.params)).join(", ")}) ? "" : null;'
'#{resultVar(node.resultIndex)} = (function(#{(options.trackLineAndColumn ? ["offset", "line", "column"] : ["offset"]).concat(keys(node.params)).join(", ")}) {#{node.code}})(#{(options.trackLineAndColumn ? ["pos.offset", "pos.line", "pos.column"] : ["pos"]).concat(map(values(node.params), param)).join(", ")}) ? "" : null;'
],
semantic_not: [
'#{node.resultVar} = (function(#{(options.trackLineAndColumn ? ["offset", "line", "column"] : ["offset"]).concat(keys(node.params)).join(", ")}) {#{node.code}})(#{(options.trackLineAndColumn ? ["pos.offset", "pos.line", "pos.column"] : ["pos"]).concat(values(node.params)).join(", ")}) ? null : "";'
'#{resultVar(node.resultIndex)} = (function(#{(options.trackLineAndColumn ? ["offset", "line", "column"] : ["offset"]).concat(keys(node.params)).join(", ")}) {#{node.code}})(#{(options.trackLineAndColumn ? ["pos.offset", "pos.line", "pos.column"] : ["pos"]).concat(map(values(node.params), param)).join(", ")}) ? null : "";'
],
optional: [
'#block emit(node.expression)',
'#{node.resultVar} = #{node.resultVar} !== null ? #{node.resultVar} : "";'
'#{resultVar(node.resultIndex)} = #{resultVar(node.resultIndex)} !== null ? #{resultVar(node.resultIndex)} : "";'
],
zero_or_more: [
'#{node.resultVar} = [];',
'#{resultVar(node.resultIndex)} = [];',
'#block emit(node.expression)',
'while (#{node.expression.resultVar} !== null) {',
' #{node.resultVar}.push(#{node.expression.resultVar});',
'while (#{resultVar(node.expression.resultIndex)} !== null) {',
' #{resultVar(node.resultIndex)}.push(#{resultVar(node.expression.resultIndex)});',
' #block emit(node.expression)',
'}'
],
one_or_more: [
'#block emit(node.expression)',
'if (#{node.expression.resultVar} !== null) {',
' #{node.resultVar} = [];',
' while (#{node.expression.resultVar} !== null) {',
' #{node.resultVar}.push(#{node.expression.resultVar});',
'if (#{resultVar(node.expression.resultIndex)} !== null) {',
' #{resultVar(node.resultIndex)} = [];',
' while (#{resultVar(node.expression.resultIndex)} !== null) {',
' #{resultVar(node.resultIndex)}.push(#{resultVar(node.expression.resultIndex)});',
' #block emit(node.expression)',
' }',
'} else {',
' #{node.resultVar} = null;',
' #{resultVar(node.resultIndex)} = null;',
'}'
],
rule_ref: [
'#{node.resultVar} = parse_#{node.name}();'
'#{resultVar(node.resultIndex)} = parse_#{node.name}();'
],
literal: [
'#if node.value.length === 0',
' #{node.resultVar} = "";',
' #{resultVar(node.resultIndex)} = "";',
'#else',
' #if !node.ignoreCase',
' #if node.value.length === 1',
@ -698,13 +698,13 @@ PEG.compiler.passes.generateCode = function(ast, options) {
' if (input.substr(#{posOffset("pos")}, #{node.value.length}).toLowerCase() === #{string(node.value.toLowerCase())}) {',
' #end',
' #if !node.ignoreCase',
' #{node.resultVar} = #{string(node.value)};',
' #{resultVar(node.resultIndex)} = #{string(node.value)};',
' #else',
' #{node.resultVar} = input.substr(#{posOffset("pos")}, #{node.value.length});',
' #{resultVar(node.resultIndex)} = input.substr(#{posOffset("pos")}, #{node.value.length});',
' #end',
' #{posAdvance(node.value.length)};',
' } else {',
' #{node.resultVar} = null;',
' #{resultVar(node.resultIndex)} = null;',
' if (reportFailures === 0) {',
' matchFailed(#{string(string(node.value))});',
' }',
@ -713,10 +713,10 @@ PEG.compiler.passes.generateCode = function(ast, options) {
],
"class": [
'if (#{regexp}.test(input.charAt(#{posOffset("pos")}))) {',
' #{node.resultVar} = input.charAt(#{posOffset("pos")});',
' #{resultVar(node.resultIndex)} = input.charAt(#{posOffset("pos")});',
' #{posAdvance(1)};',
'} else {',
' #{node.resultVar} = null;',
' #{resultVar(node.resultIndex)} = null;',
' if (reportFailures === 0) {',
' matchFailed(#{string(node.rawText)});',
' }',
@ -724,10 +724,10 @@ PEG.compiler.passes.generateCode = function(ast, options) {
],
any: [
'if (input.length > #{posOffset("pos")}) {',
' #{node.resultVar} = input.charAt(#{posOffset("pos")});',
' #{resultVar(node.resultIndex)} = input.charAt(#{posOffset("pos")});',
' #{posAdvance(1)};',
'} else {',
' #{node.resultVar} = null;',
' #{resultVar(node.resultIndex)} = null;',
' if (reportFailures === 0) {',
' matchFailed("any character");',
' }',
@ -744,12 +744,21 @@ PEG.compiler.passes.generateCode = function(ast, options) {
function fill(name, vars) {
vars.string = quote;
vars.map = map;
vars.pluck = pluck;
vars.keys = keys;
vars.values = values;
vars.emit = emit;
vars.options = options;
vars.resultVar = function(index) { return "result" + index; };
vars.posVar = function(index) { return "pos" + index; };
vars.param = function(param) {
return vars.resultVar(param.resultIndex)
+ map(param.subindices, function(i) { return "[" + i + "]"; });
};
/* Position-handling macros */
if (options.trackLineAndColumn) {
vars.posInit = function(name) {
@ -772,10 +781,10 @@ PEG.compiler.passes.generateCode = function(ast, options) {
};
}
vars.posSave = function(node) {
return node.posVar + " = " + vars.posClone("pos");
return vars.posVar(node.posIndex) + " = " + vars.posClone("pos");
};
vars.posRestore = function(node) {
return "pos" + " = " + vars.posClone(node.posVar);
return "pos" + " = " + vars.posClone(vars.posVar(node.posIndex));
};
return templates[name](vars);
@ -802,16 +811,16 @@ PEG.compiler.passes.generateCode = function(ast, options) {
*
* * 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 stored in |node.resultVar| to an appropriate
* variable with a name |resultVar(node.resultIndex)| 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 stored in
* |node.resultVar| to |null|.
* set to the original value and it sets a variable with a name
* |resultVar(node.resultIndex)| to |null|.
*
* 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.
* The code can use variables with names |resultVar(node.resultIndex)| and
* |posVar(node.posIndex)| where |node| is any of the current node's
* subnodes. It can't use any other variables.
*/
named: emitSimple("named"),

Loading…
Cancel
Save