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() { describe("compiler pass |computeParams|", function() {
function pass(ast) { function pass(ast) {
PEG.compiler.passes.computeVarNames(ast); PEG.compiler.passes.computeVarIndices(ast);
PEG.compiler.passes.computeParams(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 ruleDetails(details) { return { rules: [details] }; }
function expressionDetails(details) { function expressionDetails(details) {
@ -17,7 +28,7 @@ describe("compiler pass |computeParams|", function() {
describe("basic cases", function() { describe("basic cases", function() {
it("computes params for an action", function() { it("computes params for an action", function() {
expect(pass).toChangeAST('start = a:"a" { }', expressionDetails({ 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({ expect(pass).toChangeAST('start = a:"a" &{ }', expressionDetails({
elements: [ elements: [
{}, {},
{ params: { a: "result0" } } { params: { a: result0 } }
] ]
})); }));
}); });
@ -34,7 +45,7 @@ describe("compiler pass |computeParams|", function() {
expect(pass).toChangeAST('start = a:"a" !{ }', expressionDetails({ expect(pass).toChangeAST('start = a:"a" !{ }', expressionDetails({
elements: [ elements: [
{}, {},
{ params: { a: "result0" } } { params: { a: result0 } }
] ]
})); }));
}); });
@ -44,7 +55,7 @@ describe("compiler pass |computeParams|", function() {
it("computes params for a named", function() { it("computes params for a named", function() {
expect(pass).toChangeAST( expect(pass).toChangeAST(
'start "start" = a:"a" { }', 'start "start" = a:"a" { }',
innerExpressionDetails({ params: { a: "result0" } }) innerExpressionDetails({ params: { a: result0 } })
); );
}); });
@ -52,20 +63,20 @@ describe("compiler pass |computeParams|", function() {
expect(pass).toChangeAST( expect(pass).toChangeAST(
'start = a:"a" { } / "b" / "c"', 'start = a:"a" { } / "b" / "c"',
expressionDetails({ expressionDetails({
alternatives: [{ params: { a: "result0" } }, {}, {}] alternatives: [{ params: { a: result0 } }, {}, {}]
}) })
); );
expect(pass).toChangeAST( expect(pass).toChangeAST(
'start = "a" / "b" / c:"c" { }', 'start = "a" / "b" / c:"c" { }',
expressionDetails({ expressionDetails({
alternatives: [{}, {}, { params: { c: "result0" } }] alternatives: [{}, {}, { params: { c: result0 } }]
}) })
); );
}); });
it("computes params for an action", function() { it("computes params for an action", function() {
expect(pass).toChangeAST('start = (a:"a" { }) { }', innerExpressionDetails({ 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( expect(pass).toChangeAST(
'start = (a:"a" { }) "b" "c"', 'start = (a:"a" { }) "b" "c"',
expressionDetails({ expressionDetails({
elements: [{ params: { a: "result0" } }, {}, {}] elements: [{ params: { a: result0 } }, {}, {}]
}) })
); );
expect(pass).toChangeAST( expect(pass).toChangeAST(
'start = "a" "b" (c:"c" { })', 'start = "a" "b" (c:"c" { })',
expressionDetails({ expressionDetails({
elements: [{}, {}, { params: { c: "result2" } }] elements: [{}, {}, { params: { c: result2 } }]
}) })
); );
}); });
it("computes params for a labeled", function() { it("computes params for a labeled", function() {
expect(pass).toChangeAST('start = a:(b:"b" { })', innerExpressionDetails({ expect(pass).toChangeAST('start = a:(b:"b" { })', innerExpressionDetails({
params: { b: "result0" } params: { b: result0 }
})); }));
}); });
it("computes params for a simple and", function() { it("computes params for a simple and", function() {
expect(pass).toChangeAST('start = &(a:"a" { })', innerExpressionDetails({ expect(pass).toChangeAST('start = &(a:"a" { })', innerExpressionDetails({
params: { a: "result0" } params: { a: result0 }
})); }));
}); });
it("computes params for a simple not", function() { it("computes params for a simple not", function() {
expect(pass).toChangeAST('start = &(a:"a" { })', innerExpressionDetails({ expect(pass).toChangeAST('start = &(a:"a" { })', innerExpressionDetails({
params: { a: "result0" } params: { a: result0 }
})); }));
}); });
it("computes params for an optional", function() { it("computes params for an optional", function() {
expect(pass).toChangeAST('start = (a:"a" { })?', innerExpressionDetails({ expect(pass).toChangeAST('start = (a:"a" { })?', innerExpressionDetails({
params: { a: "result0" } params: { a: result0 }
})); }));
}); });
it("computes params for a zero or more", function() { it("computes params for a zero or more", function() {
expect(pass).toChangeAST('start = (a:"a" { })*', innerExpressionDetails({ expect(pass).toChangeAST('start = (a:"a" { })*', innerExpressionDetails({
params: { a: "result1" } params: { a: result1 }
})); }));
}); });
it("computes params for a one or more", function() { it("computes params for a one or more", function() {
expect(pass).toChangeAST('start = (a:"a" { })+', innerExpressionDetails({ 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( expect(pass).toChangeAST(
'start = a:"a" b:"b" c:"c" { }', 'start = a:"a" b:"b" c:"c" { }',
expressionDetails({ expressionDetails({
params: { a: "result0[0]", b: "result0[1]", c: "result0[2]" } params: { a: result0_0, b: result0_1, c: result0_2 }
}) })
); );
expect(pass).toChangeAST( expect(pass).toChangeAST(
'start = a:"a" (b:"b" c:"c" d:"d") e:"e"{ }', 'start = a:"a" (b:"b" c:"c" d:"d") e:"e"{ }',
expressionDetails({ expressionDetails({
params: { params: {
a: "result0[0]", a: result0_0,
b: "result0[1][0]", b: result0_1_0,
c: "result0[1][1]", c: result0_1_1,
d: "result0[1][2]", d: result0_1_2,
e: "result0[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() { it("creates a new scope for a labeled", function() {
expect(pass).toChangeAST('start = a:(b:"b") { }', expressionDetails({ 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-missing-rules.spec.js"></script>
<script src="compiler/passes/report-left-recursion.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/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 src="compiler/passes/compute-params.spec.js"></script>
<script> <script>
(function() { (function() {

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

@ -10,6 +10,6 @@ PEG.compiler.passes = {};
// @include "passes/report-missing-rules.js" // @include "passes/report-missing-rules.js"
// @include "passes/report-left-recursion.js" // @include "passes/report-left-recursion.js"
// @include "passes/remove-proxy-rules.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/compute-params.js"
// @include "passes/generate-code.js" // @include "passes/generate-code.js"

@ -65,10 +65,12 @@ PEG.compiler.passes.computeParams = function(ast) {
var env = envs[envs.length - 1], name; var env = envs[envs.length - 1], name;
function fixup(name) { function fixup(name) {
each(pluck(node.elements, "resultVar"), function(resultVar, i) { each(pluck(node.elements, "resultIndex"), function(resultIndex, i) {
if ((new RegExp("^" + resultVar + "(\\[\\d+\\])*$")).test(env[name])) { if (env[name].resultIndex === resultIndex) {
env[name] = node.resultVar + "[" + i + "]" env[name] = {
+ env[name].substr(resultVar.length); resultIndex: node.resultIndex,
subindices: [i].concat(env[name].subindices)
};
} }
}); });
} }
@ -82,7 +84,10 @@ PEG.compiler.passes.computeParams = function(ast) {
labeled: labeled:
function(node) { 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); }); 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. * positions in generated code. These variables are organized as two stacks.
* The following will hold after running this pass: * The following will hold after running this pass:
* *
* * All nodes except "grammar" and "rule" nodes will have a |resultVar| * * All nodes except "grammar" and "rule" nodes will have a |resultIndex|
* property. It will contain a name of the variable that will store a match * property. It will contain an index of the variable that will store a
* result of the expression represented by the node in generated code. * 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 * * Some nodes will have a |posIndex| property. It will contain an index of
* variable that will store a parse position in generated code. * the variable that will store a parse position in generated code.
* *
* * All "rule" nodes will contain |resultVars| and |posVars| properties. * * All "rule" nodes will contain |resultIndices| and |posIndices|
* They will contain a list of values of |resultVar| and |posVar| properties * properties. They will contain a list of values of |resultIndex| and
* used in rule's subnodes. (This is useful to declare variables in * |posIndex| properties used in rule's subnodes. (This is useful to declare
* generated code.) * variables in generated code.)
*/ */
PEG.compiler.passes.computeVarNames = function(ast) { PEG.compiler.passes.computeVarIndices = function(ast) {
function resultVar(index) { return "result" + index; }
function posVar(index) { return "pos" + index; }
function computeLeaf(node, index) { function computeLeaf(node, index) {
node.resultVar = resultVar(index.result); node.resultIndex = index.result;
return { result: 0, pos: 0 }; 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) { if (delta.pos !== 0) {
node.posVar = posVar(index.pos); node.posIndex = index.pos;
} }
return { return {
@ -59,9 +56,9 @@ PEG.compiler.passes.computeVarNames = function(ast) {
function(node, index) { function(node, index) {
var depth = compute(node.expression, index); var depth = compute(node.expression, index);
node.resultVar = resultVar(index.result); node.resultIndex = index.result;
node.resultVars = map(range(depth.result + 1), resultVar); node.resultIndices = range(depth.result + 1);
node.posVars = map(range(depth.pos), posVar); node.posIndices = range(depth.pos);
}, },
named: computeFromExpression({ result: 0, pos: 0 }), named: computeFromExpression({ result: 0, pos: 0 }),
@ -72,7 +69,7 @@ PEG.compiler.passes.computeVarNames = function(ast) {
return compute(alternative, index); return compute(alternative, index);
}); });
node.resultVar = resultVar(index.result); node.resultIndex = index.result;
return { return {
result: Math.max.apply(null, pluck(depths, "result")), result: Math.max.apply(null, pluck(depths, "result")),
@ -91,8 +88,8 @@ PEG.compiler.passes.computeVarNames = function(ast) {
); );
}); });
node.resultVar = resultVar(index.result); node.resultIndex = index.result;
node.posVar = posVar(index.pos); node.posIndex = index.pos;
return { return {
result: result:

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

Loading…
Cancel
Save