Merge |allocateRegisters| and |computeParams| passes

The purpose of this change is to avoid the need to index register
variables storing match results of sequences whose elements are labeled.
The indexing happened when match results of labeled elements were passed
to action/predicate functions.

In order to avoid indexing, the register allocator needs to ensure that
registers storing match results of any labeled sequence elements are
still "alive" after finishing parsing of the sequence. They should not
be used to store anything else at least until code of all actions and
predicates that can see the labels is executed. This requires that the
|allocateRegisters| pass has the knowledge of scoping. Because that
knowledge was already implicitly embedded in the |coputeParams| pass,
the logical step to prevent duplication was to merge it with the
|allocateRegisters| pass. This is what this commit does.

As a part of the merge the tests of both passes were largely refactored.
This is both to accomodate the merge and to make the tests in sync with
the code again (the tests became a bit out-of-sync during the last few
commits -- they tested more than was needed).

The speed/size impact is slightly positive:

Speed impact
------------
Before:     849.86 kB/s
After:      858.16 kB/s
Difference: 0.97%

Size impact
-----------
Before:     876618 b
After:      875602 b
Difference: -0.12%

(Measured by /tools/impact with Node.js v0.6.18 on x86_64 GNU/Linux.)
redux
David Majda 12 years ago
parent a1fd6acc92
commit 7134b09e50

@ -1,203 +1,288 @@
describe("compiler pass |allocateRegisters|", function() { describe("compiler pass |allocateRegisters|", function() {
var pass = PEG.compiler.passes.allocateRegisters; var pass = PEG.compiler.passes.allocateRegisters;
var leafDetails = { resultIndex: 0 }, function ruleDetails(details) { return { rules: [details] }; }
choiceDetails = {
resultIndex: 0, function expressionDetails(details) {
alternatives: [ return ruleDetails({ expression: details });
{ resultIndex: 0, posIndex: 1 }, }
{ resultIndex: 0, posIndex: 1 },
{ resultIndex: 0, posIndex: 1 } function innerExpressionDetails(details) {
return expressionDetails({ expression: details });
}
var reuseResultDetails = innerExpressionDetails({ resultIndex: 0 }),
allocResultDetails = innerExpressionDetails({ resultIndex: 1 }),
savePosDetails = expressionDetails({ posIndex: 1 }),
scopedDetails = expressionDetails({ params: {} }),
blockedDetails = expressionDetails({
elements: [
{},
{
resultIndex: 3,
posIndex: 5,
elements: [{ resultIndex: 6 }, { resultIndex: 7 }]
}
] ]
}, }),
sequenceDetails = { unblockedDetails = expressionDetails({
resultIndex: 0, elements: [
posIndex: 1, {},
elements: [ {
{ resultIndex: 2, posIndex: 3 }, resultIndex: 3,
{ resultIndex: 3, posIndex: 4 }, posIndex: 4,
{ resultIndex: 4, posIndex: 5 } elements: [{ resultIndex: 5 }, { resultIndex: 6 }]
}
] ]
}; });
function ruleDetails(details) { return { rules: [details] }; } describe("for rule", function() {
it("allocates a new result register for the expression", function() {
expect(pass).toChangeAST('start = "a"', expressionDetails({
resultIndex: 0
}));
});
it("allocates registers for a named", function() { it("counts used registers", function() {
expect(pass).toChangeAST('start "start" = &"a"', ruleDetails({ expect(pass).toChangeAST('start = "a"', ruleDetails({
registerCount: 2, registerCount: 1
expression: { }));
resultIndex: 0, expect(pass).toChangeAST('start = "a"*', ruleDetails({
expression: { resultIndex: 0, posIndex: 1 } registerCount: 2
} }));
})); expect(pass).toChangeAST('start = ("a"*)*', ruleDetails({
}); registerCount: 3
}));
});
it("allocates registers for a choice", function() { it("resets used registers counter", function() {
expect(pass).toChangeAST('start = &"a" / &"b" / &"c"', ruleDetails({ expect(pass).toChangeAST('a = "a"*; b = "b"', {
registerCount: 2, rules: [ { registerCount: 2 }, { registerCount: 1 }]
expression: choiceDetails });
})); });
expect(pass).toChangeAST('start = &"a" / &"b"* / &"c"', ruleDetails({
registerCount: 3,
expression: choiceDetails
}));
expect(pass).toChangeAST('start = &"a" / &(&"b") / &"c"', ruleDetails({
registerCount: 3,
expression: choiceDetails
}));
}); });
it("allocates registers for an action", function() { describe("for named", function() {
expect(pass).toChangeAST('start = &"a" { code }', ruleDetails({ it("reuses its own result register for the expression", function() {
registerCount: 3, expect(pass).toChangeAST('start "start" = "a"', reuseResultDetails);
expression: { });
resultIndex: 0,
posIndex: 1,
expression: { resultIndex: 0, posIndex: 2 }
}
}));
}); });
it("allocates registers for a sequence", function() { describe("for choice", function() {
expect(pass).toChangeAST('start = ', ruleDetails({ it("reuses its own result register for the alternatives", function() {
registerCount: 2, expect(pass).toChangeAST('start = "a" / "b" / "c"', expressionDetails({
expression: { resultIndex: 0, posIndex: 1 } alternatives: [
})); { resultIndex: 0 },
expect(pass).toChangeAST('start = &"a" &"b" &"c"', ruleDetails({ { resultIndex: 0 },
registerCount: 6, { resultIndex: 0 }
expression: sequenceDetails ]
})); }));
expect(pass).toChangeAST('start = &"a" &"b" &"c"*', ruleDetails({ });
registerCount: 7,
expression: sequenceDetails
}));
expect(pass).toChangeAST('start = &"a" &"b"* &"c"', ruleDetails({
registerCount: 6,
expression: sequenceDetails
}));
expect(pass).toChangeAST('start = &"a" &("b"*)* &"c"', ruleDetails({
registerCount: 7,
expression: sequenceDetails
}));
expect(pass).toChangeAST('start = &"a"* &"b" &"c"', ruleDetails({
registerCount: 6,
expression: sequenceDetails
}));
expect(pass).toChangeAST('start = &("a"*)* &"b" &"c"', ruleDetails({
registerCount: 6,
expression: sequenceDetails
}));
expect(pass).toChangeAST('start = &(("a"*)*)* &"b" &"c"', ruleDetails({
registerCount: 7,
expression: sequenceDetails
}));
expect(pass).toChangeAST('start = &"a" &(&"b") &"c"', ruleDetails({
registerCount: 6,
expression: sequenceDetails
}));
});
it("allocates registers for a labeled", function() { it("creates a new scope", function() {
expect(pass).toChangeAST('start = label:&"a"', ruleDetails({ expect(pass).toChangeAST('start = (a:"a" / "b" / "c") { }', scopedDetails);
registerCount: 2, });
expression: {
resultIndex: 0,
expression: { resultIndex: 0, posIndex: 1 }
}
}));
});
it("allocates registers for a simple and", function() { it("unblocks registers blocked by its children", function() {
expect(pass).toChangeAST('start = &(&"a")', ruleDetails({ expect(pass).toChangeAST(
registerCount: 3, 'start = ((a:"a" / "b" / "c") "d") ("e" "f")',
expression: { unblockedDetails
resultIndex: 0, );
posIndex: 1, });
expression: { resultIndex: 0, posIndex: 2 }
}
}));
}); });
it("allocates registers for a simple not", function() { describe("for action", function() {
expect(pass).toChangeAST('start = !(&"a")', ruleDetails({ it("allocates a position register", function() {
registerCount: 3, expect(pass).toChangeAST('start = "a" { }', savePosDetails);
expression: { });
resultIndex: 0,
posIndex: 1, it("reuses its own result register for the expression", function() {
expression: { resultIndex: 0, posIndex: 2 } expect(pass).toChangeAST('start = "a" { }', reuseResultDetails);
} });
}));
it("creates a new scope", function() {
expect(pass).toChangeAST('start = (a:"a" { }) { }', scopedDetails);
});
it("unblocks registers blocked by its children", function() {
expect(pass).toChangeAST(
'start = ((a:"a" { }) "b") ("c" "d")',
unblockedDetails
);
});
it("computes params", function() {
expect(pass).toChangeAST('start = a:"a" b:"b" c:"c" { }', expressionDetails({
params: { a: 3, b: 4, c: 5 }
}));
});
}); });
it("allocates registers for a semantic and", function() { describe("for sequence", function() {
expect(pass).toChangeAST('start = &{ code }', ruleDetails({ it("allocates a position register", function() {
registerCount: 1, expect(pass).toChangeAST('start = ', savePosDetails);
expression: leafDetails expect(pass).toChangeAST('start = "a" "b" "c"', savePosDetails);
})); });
it("allocates new result registers for the elements", function() {
expect(pass).toChangeAST('start = "a" "b" "c"', expressionDetails({
elements: [{ resultIndex: 2 }, { resultIndex: 3 }, { resultIndex: 4 }]
}));
});
it("does not create a new scope", function() {
expect(pass).toChangeAST(
'start = a:"a" b:"b" c:"c" { }',
expressionDetails({ params: { a: 3, b: 4, c: 5 } })
);
});
it("does not unblock blocked result registers from children", function() {
expect(pass).toChangeAST(
'start = (a:"a" "b") ("c" "d")',
blockedDetails
);
});
}); });
it("allocates registers for a semantic not", function() { describe("for labeled", function() {
expect(pass).toChangeAST('start = !{ code }', ruleDetails({ it("reuses its own result register for the expression", function() {
registerCount: 1, expect(pass).toChangeAST('start = a:"a"', reuseResultDetails);
expression: leafDetails });
}));
it("creates a new scope", function() {
expect(pass).toChangeAST('start = a:(b:"b") { }', expressionDetails({
params: { a: 0 }
}));
});
it("unblocks registers blocked by its children", function() {
expect(pass).toChangeAST(
'start = (a:(b:"b") "c") ("d" "e")',
blockedDetails
);
});
it("adds label to the environment", function() {
expect(pass).toChangeAST('start = a:"a" { }', expressionDetails({
params: { a: 0 }
}));
});
it("blocks its own result register", function() {
expect(pass).toChangeAST(
'start = (a:"a" "b") ("c" "d")',
blockedDetails
);
});
}); });
it("allocates registers for an optional", function() { describe("for simple and", function() {
expect(pass).toChangeAST('start = (&"a")?', ruleDetails({ it("allocates a position register", function() {
registerCount: 2, expect(pass).toChangeAST('start = &"a"', savePosDetails);
expression: { });
resultIndex: 0,
expression: { resultIndex: 0, posIndex: 1 } it("reuses its own result register for the expression", function() {
} expect(pass).toChangeAST('start = &"a"', reuseResultDetails);
})); });
it("creates a new scope", function() {
expect(pass).toChangeAST('start = &(a:"a") { }', scopedDetails);
});
it("unblocks registers blocked by its children", function() {
expect(pass).toChangeAST(
'start = (&(a:"a") "b") ("c" "d")',
unblockedDetails
);
});
}); });
it("allocates registers for a zero or more", function() { describe("for simple not", function() {
expect(pass).toChangeAST('start = (&"a")*', ruleDetails({ it("allocates a position register", function() {
registerCount: 3, expect(pass).toChangeAST('start = !"a"', savePosDetails);
expression: { });
resultIndex: 0,
expression: { resultIndex: 1, posIndex: 2 } it("reuses its own result register for the expression", function() {
} expect(pass).toChangeAST('start = !"a"', reuseResultDetails);
})); });
it("creates a new scope", function() {
expect(pass).toChangeAST('start = !(a:"a") { }', scopedDetails);
});
it("unblocks registers blocked by its children", function() {
expect(pass).toChangeAST(
'start = (!(a:"a") "b") ("c" "d")',
unblockedDetails
);
});
}); });
it("allocates registers for a one or more", function() { describe("for semantic and", function() {
expect(pass).toChangeAST('start = (&"a")+', ruleDetails({ it("computes params", function() {
registerCount: 3, expect(pass).toChangeAST('start = a:"a" b:"b" c:"c" &{ }', expressionDetails({
expression: { elements: [{}, {}, {}, { params: { a: 2, b: 3, c: 4 } }]
resultIndex: 0, }));
expression: { resultIndex: 1, posIndex: 2 } });
}
}));
}); });
it("allocates registers for a rule reference", function() { describe("for semantic not", function() {
expect(pass).toChangeAST('start = a', ruleDetails({ it("computes params", function() {
registerCount: 1, expect(pass).toChangeAST('start = a:"a" b:"b" c:"c" !{ }', expressionDetails({
expression: leafDetails elements: [{}, {}, {}, { params: { a: 2, b: 3, c: 4 } }]
})); }));
});
}); });
it("allocates registers for a literal", function() { describe("for optional", function() {
expect(pass).toChangeAST('start = "a"', ruleDetails({ it("reuses its own result register for the expression", function() {
registerCount: 1, expect(pass).toChangeAST('start = "a"?', reuseResultDetails);
expression: leafDetails });
}));
it("creates a new scope", function() {
expect(pass).toChangeAST('start = (a:"a")? { }', scopedDetails);
});
it("unblocks registers blocked by its children", function() {
expect(pass).toChangeAST(
'start = ((a:"a")? "b") ("c" "d")',
unblockedDetails
);
});
}); });
it("allocates registers for a class", function() { describe("for zero or more", function() {
expect(pass).toChangeAST('start = [a-z]', ruleDetails({ it("allocates a new result register for the expression", function() {
registerCount: 1, expect(pass).toChangeAST('start = "a"*', allocResultDetails);
expression: leafDetails });
}));
it("creates a new scope", function() {
expect(pass).toChangeAST('start = (a:"a")* { }', scopedDetails);
});
it("unblocks registers blocked by its children", function() {
expect(pass).toChangeAST(
'start = ((a:"a")* "b") ("c" "d")',
unblockedDetails
);
});
}); });
it("allocates registers for an any", function() { describe("for one or more", function() {
expect(pass).toChangeAST('start = .', ruleDetails({ it("allocates a new result register for the expression", function() {
registerCount: 1, expect(pass).toChangeAST('start = "a"+', allocResultDetails);
expression: leafDetails });
}));
it("creates a new scope", function() {
expect(pass).toChangeAST('start = (a:"a")+ { }', scopedDetails);
});
it("unblocks registers blocked by its children", function() {
expect(pass).toChangeAST(
'start = ((a:"a")+ "b") ("c" "d")',
unblockedDetails
);
});
}); });
}); });

@ -1,207 +0,0 @@
describe("compiler pass |computeParams|", function() {
function pass(ast) {
PEG.compiler.passes.allocateRegisters(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: [] },
result4 = { resultIndex: 4, subindices: [] };
function ruleDetails(details) { return { rules: [details] }; }
function expressionDetails(details) {
return ruleDetails({ expression: details });
}
function innerExpressionDetails(details) {
return expressionDetails({ expression: details });
}
describe("basic cases", function() {
it("computes params for an action", function() {
expect(pass).toChangeAST('start = a:"a" { }', expressionDetails({
params: { a: result0 }
}));
});
it("computes params for a semantic and", function() {
expect(pass).toChangeAST('start = a:"a" &{ }', expressionDetails({
elements: [
{},
{ params: { a: result2 } }
]
}));
});
it("computes params for a semantic not", function() {
expect(pass).toChangeAST('start = a:"a" !{ }', expressionDetails({
elements: [
{},
{ params: { a: result2 } }
]
}));
});
});
describe("recursive walk", function() {
it("computes params for a named", function() {
expect(pass).toChangeAST(
'start "start" = a:"a" { }',
innerExpressionDetails({ params: { a: result0 } })
);
});
it("computes params for a choice", function() {
expect(pass).toChangeAST(
'start = a:"a" { } / "b" / "c"',
expressionDetails({
alternatives: [{ params: { a: result0 } }, {}, {}]
})
);
expect(pass).toChangeAST(
'start = "a" / "b" / c:"c" { }',
expressionDetails({
alternatives: [{}, {}, { params: { c: result0 } }]
})
);
});
it("computes params for an action", function() {
expect(pass).toChangeAST('start = (a:"a" { }) { }', innerExpressionDetails({
params: { a: result0 }
}));
});
it("computes params for a sequence", function() {
expect(pass).toChangeAST(
'start = (a:"a" { }) "b" "c"',
expressionDetails({
elements: [{ params: { a: result2 } }, {}, {}]
})
);
expect(pass).toChangeAST(
'start = "a" "b" (c:"c" { })',
expressionDetails({
elements: [{}, {}, { params: { c: result4 } }]
})
);
});
it("computes params for a labeled", function() {
expect(pass).toChangeAST('start = a:(b:"b" { })', innerExpressionDetails({
params: { b: result0 }
}));
});
it("computes params for a simple and", function() {
expect(pass).toChangeAST('start = &(a:"a" { })', innerExpressionDetails({
params: { a: result0 }
}));
});
it("computes params for a simple not", function() {
expect(pass).toChangeAST('start = &(a:"a" { })', innerExpressionDetails({
params: { a: result0 }
}));
});
it("computes params for an optional", function() {
expect(pass).toChangeAST('start = (a:"a" { })?', innerExpressionDetails({
params: { a: result0 }
}));
});
it("computes params for a zero or more", function() {
expect(pass).toChangeAST('start = (a:"a" { })*', innerExpressionDetails({
params: { a: result1 }
}));
});
it("computes params for a one or more", function() {
expect(pass).toChangeAST('start = (a:"a" { })+', innerExpressionDetails({
params: { a: result1 }
}));
});
});
describe("scoping", function() {
it("creates a new scope for a choice", function() {
expect(pass).toChangeAST(
'start = (a:"a" / b:"b" / c:"c") { }',
expressionDetails({ params: {} })
);
});
it("creates a new scope for an action", function() {
expect(pass).toChangeAST('start = (a:"a" { }) { }', expressionDetails({
params: {}
}));
});
it("does not create a new scope for a sequence", function() {
expect(pass).toChangeAST(
'start = a:"a" b:"b" c:"c" { }',
expressionDetails({
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
}
})
);
});
it("creates a new scope for a labeled", function() {
expect(pass).toChangeAST('start = a:(b:"b") { }', expressionDetails({
params: { a: result0 }
}));
});
it("creates a new scope for a simple and", function() {
expect(pass).toChangeAST('start = &(a:"a") { }', expressionDetails({
params: {}
}));
});
it("creates a new scope for a simple not", function() {
expect(pass).toChangeAST('start = !(a:"a") { }', expressionDetails({
params: {}
}));
});
it("creates a new scope for an optional", function() {
expect(pass).toChangeAST('start = (a:"a")? { }', expressionDetails({
params: {}
}));
});
it("creates a new scope for a zero or more", function() {
expect(pass).toChangeAST('start = (a:"a")* { }', expressionDetails({
params: {}
}));
});
it("creates a new scope for a one or more", function() {
expect(pass).toChangeAST('start = (a:"a")+ { }', expressionDetails({
params: {}
}));
});
});
});

@ -13,7 +13,6 @@
<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/allocate-registers.spec.js"></script> <script src="compiler/passes/allocate-registers.spec.js"></script>
<script src="compiler/passes/compute-params.spec.js"></script>
<script> <script>
(function() { (function() {
var env = jasmine.getEnv(), var env = jasmine.getEnv(),

@ -8,7 +8,6 @@ PEG.compiler = {
"reportLeftRecursion", "reportLeftRecursion",
"removeProxyRules", "removeProxyRules",
"allocateRegisters", "allocateRegisters",
"computeParams",
"generateCode" "generateCode"
], ],

@ -11,5 +11,4 @@ PEG.compiler.passes = {};
// @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/allocate-registers.js" // @include "passes/allocate-registers.js"
// @include "passes/compute-params.js"
// @include "passes/generate-code.js" // @include "passes/generate-code.js"

@ -1,7 +1,11 @@
/* /*
* Allocates registers that the generated code for each node will use to store * Allocates registers that the generated code for each node will use to store
* match results and parse positions. The following will hold after running this * match results and parse positions. For "action", "semantic_and" and
* pass: * "semantic_or" nodes it also computes visibility of labels at the point of
* action/predicate code execution and a mapping from label names to registers
* that will contain the labeled values.
*
* The following will hold after running this pass:
* *
* * All nodes except "grammar" and "rule" nodes will have a |resultIndex| * * All nodes except "grammar" and "rule" nodes will have a |resultIndex|
* property. It will contain an index of a register that will store a match * property. It will contain an index of a register that will store a match
@ -13,97 +17,211 @@
* * All "rule" nodes will contain a |registerCount| property. It will contain * * All "rule" nodes will contain a |registerCount| property. It will contain
* the number of registers that will be used by code generated for the * the number of registers that will be used by code generated for the
* rule's expression. * rule's expression.
*
* * All "action", "semantic_and" and "semantic_or" nodes will have a |params|
* property. It will contain a mapping from names of labels visible at the
* point of action/predicate code execution to registers that will contain
* the labeled values.
*/ */
PEG.compiler.passes.allocateRegisters = function(ast) { PEG.compiler.passes.allocateRegisters = function(ast) {
function computeLeaf(node, index) { return 0; } /*
* Register allocator that allocates registers from an unlimited
* integer-indexed pool. It allows allocating and releaseing registers in any
* order. It also supports reference counting (this simplifies tracking active
* registers when they store values passed to action/predicate code).
* Allocating a register allways uses the first free register (the one with
* the lowest index).
*/
var registers = (function() {
var refCounts = []; // reference count for each register that was
// allocated at least once
return {
alloc: function() {
var i;
for (i = 0; i < refCounts.length; i++) {
if (refCounts[i] === 0) {
refCounts[i] = 1;
return i;
}
}
refCounts.push(1);
return refCounts.length - 1;
},
function computeFromExpression(delta) { use: function(index) {
return function(node, index) { refCounts[index]++;
var depth; },
node.expression.resultIndex = delta.result > 0 release: function(index) {
? index + delta.result + delta.pos refCounts[index]--;
: node.resultIndex; },
depth = compute( maxIndex: function() {
node.expression, return refCounts.length - 1;
index + delta.result + delta.pos },
);
if (delta.pos !== 0) { reset: function() {
node.posIndex = index + delta.pos; refCounts = [];
} }
};
})();
/*
* Manages mapping of label names to indices of registers that will store the
* labeled values as long as they are in scope.
*/
var vars = (function(registers) {
var envs = []; // stack of nested environments
return {
beginScope: function() {
envs.push({});
},
endScope: function() {
var env = envs.pop(), name;
return depth + delta.result + delta.pos; for (name in env) {
registers.release(env[name]);
}
},
add: function(name, index) {
envs[envs.length - 1][name] = index;
registers.use(index);
},
buildParams: function() {
var env = envs[envs.length - 1], params = {}, name;
for (name in env) {
params[name] = env[name];
}
return params;
}
}; };
})(registers);
function savePos(node, f) {
node.posIndex = registers.alloc();
f();
registers.release(node.posIndex);
}
function reuseResult(node, subnode) {
subnode.resultIndex = node.resultIndex;
}
function allocResult(node, f) {
node.resultIndex = registers.alloc();
f();
registers.release(node.resultIndex);
}
function scoped(f) {
vars.beginScope();
f();
vars.endScope();
}
function nop() {}
function computeExpressionScoped(node) {
scoped(function() { compute(node.expression); })
}
function computeExpressionScopedReuseResult(node) {
reuseResult(node, node.expression);
computeExpressionScoped(node);
}
function computeExpressionScopedAllocResult(node) {
allocResult(node.expression, function() { computeExpressionScoped(node); });
}
function computeExpressionScopedReuseResultSavePos(node) {
savePos(node, function() { computeExpressionScopedReuseResult(node); });
}
function computeParams(node) {
node.params = vars.buildParams();
} }
var compute = buildNodeVisitor({ var compute = buildNodeVisitor({
grammar: grammar:
function(node, index) { function(node) {
each(node.rules, function(node) { each(node.rules, compute);
compute(node, index);
});
}, },
rule: rule:
function(node, index) { function(node) {
var depth; registers.reset();
computeExpressionScopedAllocResult(node);
node.expression.resultIndex = index; node.registerCount = registers.maxIndex() + 1;
depth = compute(node.expression, index);
node.registerCount = depth + 1;
}, },
named: computeFromExpression({ result: 0, pos: 0 }), named:
function(node) {
reuseResult(node, node.expression);
compute(node.expression);
},
choice: choice:
function(node, index) { function(node) {
var depths = map(node.alternatives, function(alternative) { each(node.alternatives, function(alternative) {
alternative.resultIndex = node.resultIndex; reuseResult(node, alternative);
scoped(function() {
return compute(alternative, index); compute(alternative);
});
}); });
return Math.max.apply(null, depths);
}, },
action: computeFromExpression({ result: 0, pos: 1 }), action:
function(node) {
savePos(node, function() {
reuseResult(node, node.expression);
scoped(function() {
compute(node.expression);
computeParams(node);
});
});
},
sequence: sequence:
function(node, index) { function(node) {
var depths = map(node.elements, function(element, i) { savePos(node, function() {
element.resultIndex = index + i + 2; each(node.elements, function(element) {
element.resultIndex = registers.alloc();
return compute(element, index + i + 2); compute(element);
});
each(node.elements, function(element) {
registers.release(element.resultIndex);
});
}); });
},
node.posIndex = index + 1; labeled:
function(node) {
return node.elements.length > 0 vars.add(node.label, node.resultIndex);
? Math.max.apply( computeExpressionScopedReuseResult(node);
null,
map(depths, function(d, i) { return i + d; })
)
+ 2
: 1;
}, },
labeled: computeFromExpression({ result: 0, pos: 0 }), simple_and: computeExpressionScopedReuseResultSavePos,
simple_and: computeFromExpression({ result: 0, pos: 1 }), simple_not: computeExpressionScopedReuseResultSavePos,
simple_not: computeFromExpression({ result: 0, pos: 1 }), semantic_and: computeParams,
semantic_and: computeLeaf, semantic_not: computeParams,
semantic_not: computeLeaf, optional: computeExpressionScopedReuseResult,
optional: computeFromExpression({ result: 0, pos: 0 }), zero_or_more: computeExpressionScopedAllocResult,
zero_or_more: computeFromExpression({ result: 1, pos: 0 }), one_or_more: computeExpressionScopedAllocResult,
one_or_more: computeFromExpression({ result: 1, pos: 0 }), rule_ref: nop,
rule_ref: computeLeaf, literal: nop,
literal: computeLeaf, "class": nop,
"class": computeLeaf, any: nop
any: computeLeaf
}); });
compute(ast, 0); compute(ast);
}; };

@ -1,109 +0,0 @@
/*
* This pass walks through the AST and tracks what labels are visible at each
* point. For "action", "semantic_and" and "semantic_or" nodes it computes
* parameter names and values for the function used in generated code. (In the
* emitter, user's code is wrapped into a function that is immediately executed.
* Its parameter names correspond to visible labels and its parameter values to
* their captured values). Implicitly, this pass defines scoping rules for
* labels.
*
* After running this pass, all "action", "semantic_and" and "semantic_or" nodes
* will have a |params| property containing an object mapping parameter names to
* the expressions that will be used as their values.
*/
PEG.compiler.passes.computeParams = function(ast) {
var envs = [];
function scoped(f) {
envs.push({});
f();
envs.pop();
}
function nop() {}
function computeForScopedExpression(node) {
scoped(function() { compute(node.expression); });
}
function computeParams(node) {
var env = envs[envs.length - 1], params = {}, name;
for (name in env) {
params[name] = env[name];
}
node.params = params;
}
var compute = buildNodeVisitor({
grammar:
function(node) {
each(node.rules, compute);
},
rule: computeForScopedExpression,
named:
function(node) {
compute(node.expression);
},
choice:
function(node) {
scoped(function() { each(node.alternatives, compute); });
},
action:
function(node) {
scoped(function() {
compute(node.expression);
computeParams(node);
});
},
sequence:
function(node) {
var env = envs[envs.length - 1], name;
function fixup(name) {
each(pluck(node.elements, "resultIndex"), function(resultIndex, i) {
if (env[name].resultIndex === resultIndex) {
env[name] = {
resultIndex: node.resultIndex,
subindices: [i].concat(env[name].subindices)
};
}
});
}
each(node.elements, compute);
for (name in env) {
fixup(name);
}
},
labeled:
function(node) {
envs[envs.length - 1][node.label] = {
resultIndex: node.resultIndex,
subindices: []
};
scoped(function() { compute(node.expression); });
},
simple_and: computeForScopedExpression,
simple_not: computeForScopedExpression,
semantic_and: computeParams,
semantic_not: computeParams,
optional: computeForScopedExpression,
zero_or_more: computeForScopedExpression,
one_or_more: computeForScopedExpression,
rule_ref: nop,
literal: nop,
"class": nop,
any: nop
});
compute(ast);
};

@ -594,7 +594,7 @@ PEG.compiler.passes.generateCode = function(ast, options) {
'#{posSave(node)};', '#{posSave(node)};',
'#block emit(node.expression)', '#block emit(node.expression)',
'if (#{r(node.resultIndex)} !== null) {', 'if (#{r(node.resultIndex)} !== null) {',
' #{r(node.resultIndex)} = (function(#{(options.trackLineAndColumn ? ["offset", "line", "column"] : ["offset"]).concat(keys(node.params)).join(", ")}) {#{node.code}})(#{(options.trackLineAndColumn ? [r(node.posIndex) + ".offset", r(node.posIndex) + ".line", r(node.posIndex) + ".column"] : [r(node.posIndex)]).concat(map(values(node.params), param)).join(", ")});', ' #{r(node.resultIndex)} = (function(#{(options.trackLineAndColumn ? ["offset", "line", "column"] : ["offset"]).concat(keys(node.params)).join(", ")}) {#{node.code}})(#{(options.trackLineAndColumn ? [r(node.posIndex) + ".offset", r(node.posIndex) + ".line", r(node.posIndex) + ".column"] : [r(node.posIndex)]).concat(map(values(node.params), r)).join(", ")});',
'}', '}',
'if (#{r(node.resultIndex)} === null) {', 'if (#{r(node.resultIndex)} === null) {',
' #{posRestore(node)};', ' #{posRestore(node)};',
@ -641,10 +641,10 @@ PEG.compiler.passes.generateCode = function(ast, options) {
'}' '}'
], ],
semantic_and: [ semantic_and: [
'#{r(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;' '#{r(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), r)).join(", ")}) ? "" : null;'
], ],
semantic_not: [ semantic_not: [
'#{r(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 : "";' '#{r(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), r)).join(", ")}) ? null : "";'
], ],
optional: [ optional: [
'#block emit(node.expression)', '#block emit(node.expression)',
@ -751,11 +751,6 @@ PEG.compiler.passes.generateCode = function(ast, options) {
vars.r = function(index) { return "r" + index; }; vars.r = function(index) { return "r" + index; };
vars.param = function(param) {
return vars.r(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) {

@ -190,7 +190,7 @@ PEG.parser = (function(){
rules: rules, rules: rules,
startRule: rules[0].name startRule: rules[0].name
}; };
})(r1, r0[1], r0[2]); })(r1, r4, r5);
} }
if (r0 === null) { if (r0 === null) {
pos = r1; pos = r1;
@ -223,7 +223,7 @@ PEG.parser = (function(){
type: "initializer", type: "initializer",
code: code code: code
}; };
})(r1, r0[0]); })(r1, r3);
} }
if (r0 === null) { if (r0 === null) {
pos = r1; pos = r1;
@ -282,7 +282,7 @@ PEG.parser = (function(){
} }
: expression : expression
}; };
})(r1, r0[0], r0[1], r0[3]); })(r1, r3, r4, r6);
} }
if (r0 === null) { if (r0 === null) {
pos = r1; pos = r1;
@ -353,7 +353,7 @@ PEG.parser = (function(){
} else { } else {
return head; return head;
} }
})(r1, r0[0], r0[1]); })(r1, r3, r4);
} }
if (r0 === null) { if (r0 === null) {
pos = r1; pos = r1;
@ -397,7 +397,7 @@ PEG.parser = (function(){
expression: expression, expression: expression,
code: code code: code
}; };
})(r1, r0[0], r0[1]); })(r1, r3, r4);
} }
if (r0 === null) { if (r0 === null) {
pos = r1; pos = r1;
@ -458,7 +458,7 @@ PEG.parser = (function(){
label: label, label: label,
expression: expression expression: expression
}; };
})(r1, r0[0], r0[2]); })(r1, r3, r5);
} }
if (r0 === null) { if (r0 === null) {
pos = r1; pos = r1;
@ -493,7 +493,7 @@ PEG.parser = (function(){
type: "semantic_and", type: "semantic_and",
code: code code: code
}; };
})(r1, r0[1]); })(r1, r4);
} }
if (r0 === null) { if (r0 === null) {
pos = r1; pos = r1;
@ -520,7 +520,7 @@ PEG.parser = (function(){
type: "simple_and", type: "simple_and",
expression: expression expression: expression
}; };
})(r1, r0[1]); })(r1, r4);
} }
if (r0 === null) { if (r0 === null) {
pos = r1; pos = r1;
@ -547,7 +547,7 @@ PEG.parser = (function(){
type: "semantic_not", type: "semantic_not",
code: code code: code
}; };
})(r1, r0[1]); })(r1, r4);
} }
if (r0 === null) { if (r0 === null) {
pos = r1; pos = r1;
@ -574,7 +574,7 @@ PEG.parser = (function(){
type: "simple_not", type: "simple_not",
expression: expression expression: expression
}; };
})(r1, r0[1]); })(r1, r4);
} }
if (r0 === null) { if (r0 === null) {
pos = r1; pos = r1;
@ -612,7 +612,7 @@ PEG.parser = (function(){
type: "optional", type: "optional",
expression: expression expression: expression
}; };
})(r1, r0[0]); })(r1, r3);
} }
if (r0 === null) { if (r0 === null) {
pos = r1; pos = r1;
@ -639,7 +639,7 @@ PEG.parser = (function(){
type: "zero_or_more", type: "zero_or_more",
expression: expression expression: expression
}; };
})(r1, r0[0]); })(r1, r3);
} }
if (r0 === null) { if (r0 === null) {
pos = r1; pos = r1;
@ -666,7 +666,7 @@ PEG.parser = (function(){
type: "one_or_more", type: "one_or_more",
expression: expression expression: expression
}; };
})(r1, r0[0]); })(r1, r3);
} }
if (r0 === null) { if (r0 === null) {
pos = r1; pos = r1;
@ -726,7 +726,7 @@ PEG.parser = (function(){
type: "rule_ref", type: "rule_ref",
name: name name: name
}; };
})(r1, r0[0]); })(r1, r3);
} }
if (r0 === null) { if (r0 === null) {
pos = r1; pos = r1;
@ -767,7 +767,7 @@ PEG.parser = (function(){
pos = r2; pos = r2;
} }
if (r0 !== null) { if (r0 !== null) {
r0 = (function(offset, expression) { return expression; })(r1, r0[1]); r0 = (function(offset, expression) { return expression; })(r1, r4);
} }
if (r0 === null) { if (r0 === null) {
pos = r1; pos = r1;
@ -799,7 +799,7 @@ PEG.parser = (function(){
pos = r2; pos = r2;
} }
if (r0 !== null) { if (r0 !== null) {
r0 = (function(offset, braced) { return braced.substr(1, braced.length - 2); })(r1, r0[0]); r0 = (function(offset, braced) { return braced.substr(1, braced.length - 2); })(r1, r3);
} }
if (r0 === null) { if (r0 === null) {
pos = r1; pos = r1;
@ -865,7 +865,7 @@ PEG.parser = (function(){
if (r0 !== null) { if (r0 !== null) {
r0 = (function(offset, parts) { r0 = (function(offset, parts) {
return "{" + parts.join("") + "}"; return "{" + parts.join("") + "}";
})(r1, r0[1]); })(r1, r4);
} }
if (r0 === null) { if (r0 === null) {
pos = r1; pos = r1;
@ -1436,7 +1436,7 @@ PEG.parser = (function(){
if (r0 !== null) { if (r0 !== null) {
r0 = (function(offset, head, tail) { r0 = (function(offset, head, tail) {
return head + tail.join(""); return head + tail.join("");
})(r1, r0[0], r0[1]); })(r1, r3, r4);
} }
if (r0 === null) { if (r0 === null) {
pos = r1; pos = r1;
@ -1492,7 +1492,7 @@ PEG.parser = (function(){
value: value, value: value,
ignoreCase: flags === "i" ignoreCase: flags === "i"
}; };
})(r1, r0[0], r0[1]); })(r1, r3, r4);
} }
if (r0 === null) { if (r0 === null) {
pos = r1; pos = r1;
@ -1527,7 +1527,7 @@ PEG.parser = (function(){
pos = r2; pos = r2;
} }
if (r0 !== null) { if (r0 !== null) {
r0 = (function(offset, string) { return string; })(r1, r0[0]); r0 = (function(offset, string) { return string; })(r1, r3);
} }
if (r0 === null) { if (r0 === null) {
pos = r1; pos = r1;
@ -1585,7 +1585,7 @@ PEG.parser = (function(){
pos = r2; pos = r2;
} }
if (r0 !== null) { if (r0 !== null) {
r0 = (function(offset, chars) { return chars.join(""); })(r1, r0[1]); r0 = (function(offset, chars) { return chars.join(""); })(r1, r4);
} }
if (r0 === null) { if (r0 === null) {
pos = r1; pos = r1;
@ -1673,7 +1673,7 @@ PEG.parser = (function(){
pos = r2; pos = r2;
} }
if (r0 !== null) { if (r0 !== null) {
r0 = (function(offset, char_) { return char_; })(r1, r0[1]); r0 = (function(offset, char_) { return char_; })(r1, r4);
} }
if (r0 === null) { if (r0 === null) {
pos = r1; pos = r1;
@ -1727,7 +1727,7 @@ PEG.parser = (function(){
pos = r2; pos = r2;
} }
if (r0 !== null) { if (r0 !== null) {
r0 = (function(offset, chars) { return chars.join(""); })(r1, r0[1]); r0 = (function(offset, chars) { return chars.join(""); })(r1, r4);
} }
if (r0 === null) { if (r0 === null) {
pos = r1; pos = r1;
@ -1815,7 +1815,7 @@ PEG.parser = (function(){
pos = r2; pos = r2;
} }
if (r0 !== null) { if (r0 !== null) {
r0 = (function(offset, char_) { return char_; })(r1, r0[1]); r0 = (function(offset, char_) { return char_; })(r1, r4);
} }
if (r0 === null) { if (r0 === null) {
pos = r1; pos = r1;
@ -1928,7 +1928,7 @@ PEG.parser = (function(){
inverted: inverted === "^", inverted: inverted === "^",
ignoreCase: flags === "i" ignoreCase: flags === "i"
}; };
})(r1, r0[1], r0[2], r0[4]); })(r1, r4, r5, r7);
} }
if (r0 === null) { if (r0 === null) {
pos = r1; pos = r1;
@ -1985,7 +1985,7 @@ PEG.parser = (function(){
// FIXME: Get the raw text from the input directly. // FIXME: Get the raw text from the input directly.
rawText: begin.rawText + "-" + end.rawText rawText: begin.rawText + "-" + end.rawText
}; };
})(r1, r0[0], r0[2]); })(r1, r3, r5);
} }
if (r0 === null) { if (r0 === null) {
pos = r1; pos = r1;
@ -2093,7 +2093,7 @@ PEG.parser = (function(){
pos = r2; pos = r2;
} }
if (r0 !== null) { if (r0 !== null) {
r0 = (function(offset, char_) { return char_; })(r1, r0[1]); r0 = (function(offset, char_) { return char_; })(r1, r4);
} }
if (r0 === null) { if (r0 === null) {
pos = r1; pos = r1;
@ -2184,7 +2184,7 @@ PEG.parser = (function(){
.replace("r", "\r") .replace("r", "\r")
.replace("t", "\t") .replace("t", "\t")
.replace("v", "\x0B"); // IE does not recognize "\v". .replace("v", "\x0B"); // IE does not recognize "\v".
})(r1, r0[2]); })(r1, r5);
} }
if (r0 === null) { if (r0 === null) {
pos = r1; pos = r1;
@ -2271,7 +2271,7 @@ PEG.parser = (function(){
if (r0 !== null) { if (r0 !== null) {
r0 = (function(offset, h1, h2) { r0 = (function(offset, h1, h2) {
return String.fromCharCode(parseInt(h1 + h2, 16)); return String.fromCharCode(parseInt(h1 + h2, 16));
})(r1, r0[1], r0[2]); })(r1, r4, r5);
} }
if (r0 === null) { if (r0 === null) {
pos = r1; pos = r1;
@ -2326,7 +2326,7 @@ PEG.parser = (function(){
if (r0 !== null) { if (r0 !== null) {
r0 = (function(offset, h1, h2, h3, h4) { r0 = (function(offset, h1, h2, h3, h4) {
return String.fromCharCode(parseInt(h1 + h2 + h3 + h4, 16)); return String.fromCharCode(parseInt(h1 + h2 + h3 + h4, 16));
})(r1, r0[1], r0[2], r0[3], r0[4]); })(r1, r4, r5, r6, r7);
} }
if (r0 === null) { if (r0 === null) {
pos = r1; pos = r1;
@ -2361,7 +2361,7 @@ PEG.parser = (function(){
pos = r2; pos = r2;
} }
if (r0 !== null) { if (r0 !== null) {
r0 = (function(offset, eol) { return eol; })(r1, r0[1]); r0 = (function(offset, eol) { return eol; })(r1, r4);
} }
if (r0 === null) { if (r0 === null) {
pos = r1; pos = r1;

Loading…
Cancel
Save