Mental model change: Variables do not form a stack, they are registers

This commit changes the model underlying parser variables used to store
match results and parse positions. Until now they were treated as a
stack, now they are thought of as registers. The actual behavior does
not change (yet), only the terminology.

More specifically, this commit:

  * Changes parser variable names from |result0|, |result1|, etc. to
    |r0|, |r1|, etc.

  * Changes various internal names and comments to match the new model.

  * Renames the |computeVarIndices| pass to |allocateRegisters|.
redux
David Majda 12 years ago
parent 2f3dd951e9
commit 2d36ebeb59

@ -1,5 +1,5 @@
describe("compiler pass |computeVarIndices|", function() {
var pass = PEG.compiler.passes.computeVarIndices;
describe("compiler pass |allocateRegisters|", function() {
var pass = PEG.compiler.passes.allocateRegisters;
var leafDetails = { resultIndex: 0 },
choiceDetails = {
@ -22,35 +22,35 @@ describe("compiler pass |computeVarIndices|", function() {
function ruleDetails(details) { return { rules: [details] }; }
it("computes variable indices for a named", function() {
it("allocates registers for a named", function() {
expect(pass).toChangeAST('start "start" = &"a"', ruleDetails({
resultCount: 2,
expression: {
registerCount: 2,
expression: {
resultIndex: 0,
expression: { resultIndex: 0, posIndex: 1 }
}
}));
});
it("computes variable indices for a choice", function() {
it("allocates registers for a choice", function() {
expect(pass).toChangeAST('start = &"a" / &"b" / &"c"', ruleDetails({
resultCount: 2,
expression: choiceDetails
registerCount: 2,
expression: choiceDetails
}));
expect(pass).toChangeAST('start = &"a" / &"b"* / &"c"', ruleDetails({
resultCount: 3,
expression: choiceDetails
registerCount: 3,
expression: choiceDetails
}));
expect(pass).toChangeAST('start = &"a" / &(&"b") / &"c"', ruleDetails({
resultCount: 3,
expression: choiceDetails
registerCount: 3,
expression: choiceDetails
}));
});
it("computes variable indices for an action", function() {
it("allocates registers for an action", function() {
expect(pass).toChangeAST('start = &"a" { code }', ruleDetails({
resultCount: 3,
expression: {
registerCount: 3,
expression: {
resultIndex: 0,
posIndex: 1,
expression: { resultIndex: 0, posIndex: 2 }
@ -58,59 +58,59 @@ describe("compiler pass |computeVarIndices|", function() {
}));
});
it("computes variable indices for a sequence", function() {
it("allocates registers for a sequence", function() {
expect(pass).toChangeAST('start = ', ruleDetails({
resultCount: 2,
expression: { resultIndex: 0, posIndex: 1 }
registerCount: 2,
expression: { resultIndex: 0, posIndex: 1 }
}));
expect(pass).toChangeAST('start = &"a" &"b" &"c"', ruleDetails({
resultCount: 6,
expression: sequenceDetails
registerCount: 6,
expression: sequenceDetails
}));
expect(pass).toChangeAST('start = &"a" &"b" &"c"*', ruleDetails({
resultCount: 7,
expression: sequenceDetails
registerCount: 7,
expression: sequenceDetails
}));
expect(pass).toChangeAST('start = &"a" &"b"* &"c"', ruleDetails({
resultCount: 6,
expression: sequenceDetails
registerCount: 6,
expression: sequenceDetails
}));
expect(pass).toChangeAST('start = &"a" &("b"*)* &"c"', ruleDetails({
resultCount: 7,
expression: sequenceDetails
registerCount: 7,
expression: sequenceDetails
}));
expect(pass).toChangeAST('start = &"a"* &"b" &"c"', ruleDetails({
resultCount: 6,
expression: sequenceDetails
registerCount: 6,
expression: sequenceDetails
}));
expect(pass).toChangeAST('start = &("a"*)* &"b" &"c"', ruleDetails({
resultCount: 6,
expression: sequenceDetails
registerCount: 6,
expression: sequenceDetails
}));
expect(pass).toChangeAST('start = &(("a"*)*)* &"b" &"c"', ruleDetails({
resultCount: 7,
expression: sequenceDetails
registerCount: 7,
expression: sequenceDetails
}));
expect(pass).toChangeAST('start = &"a" &(&"b") &"c"', ruleDetails({
resultCount: 6,
expression: sequenceDetails
registerCount: 6,
expression: sequenceDetails
}));
});
it("computes variable indices for a labeled", function() {
it("allocates registers for a labeled", function() {
expect(pass).toChangeAST('start = label:&"a"', ruleDetails({
resultCount: 2,
expression: {
registerCount: 2,
expression: {
resultIndex: 0,
expression: { resultIndex: 0, posIndex: 1 }
}
}));
});
it("computes variable indices for a simple and", function() {
it("allocates registers for a simple and", function() {
expect(pass).toChangeAST('start = &(&"a")', ruleDetails({
resultCount: 3,
expression: {
registerCount: 3,
expression: {
resultIndex: 0,
posIndex: 1,
expression: { resultIndex: 0, posIndex: 2 }
@ -118,10 +118,10 @@ describe("compiler pass |computeVarIndices|", function() {
}));
});
it("computes variable indices for a simple not", function() {
it("allocates registers for a simple not", function() {
expect(pass).toChangeAST('start = !(&"a")', ruleDetails({
resultCount: 3,
expression: {
registerCount: 3,
expression: {
resultIndex: 0,
posIndex: 1,
expression: { resultIndex: 0, posIndex: 2 }
@ -129,75 +129,75 @@ describe("compiler pass |computeVarIndices|", function() {
}));
});
it("computes variable indices for a semantic and", function() {
it("allocates registers for a semantic and", function() {
expect(pass).toChangeAST('start = &{ code }', ruleDetails({
resultCount: 1,
expression: leafDetails
registerCount: 1,
expression: leafDetails
}));
});
it("computes variable indices for a semantic not", function() {
it("allocates registers for a semantic not", function() {
expect(pass).toChangeAST('start = !{ code }', ruleDetails({
resultCount: 1,
expression: leafDetails
registerCount: 1,
expression: leafDetails
}));
});
it("computes variable indices for an optional", function() {
it("allocates registers for an optional", function() {
expect(pass).toChangeAST('start = (&"a")?', ruleDetails({
resultCount: 2,
expression: {
registerCount: 2,
expression: {
resultIndex: 0,
expression: { resultIndex: 0, posIndex: 1 }
}
}));
});
it("computes variable indices for a zero or more", function() {
it("allocates registers for a zero or more", function() {
expect(pass).toChangeAST('start = (&"a")*', ruleDetails({
resultCount: 3,
expression: {
registerCount: 3,
expression: {
resultIndex: 0,
expression: { resultIndex: 1, posIndex: 2 }
}
}));
});
it("computes variable indices for a one or more", function() {
it("allocates registers for a one or more", function() {
expect(pass).toChangeAST('start = (&"a")+', ruleDetails({
resultCount: 3,
expression: {
registerCount: 3,
expression: {
resultIndex: 0,
expression: { resultIndex: 1, posIndex: 2 }
}
}));
});
it("computes variable indices for a rule reference", function() {
it("allocates registers for a rule reference", function() {
expect(pass).toChangeAST('start = a', ruleDetails({
resultCount: 1,
expression: leafDetails
registerCount: 1,
expression: leafDetails
}));
});
it("computes variable indices for a literal", function() {
it("allocates registers for a literal", function() {
expect(pass).toChangeAST('start = "a"', ruleDetails({
resultCount: 1,
expression: leafDetails
registerCount: 1,
expression: leafDetails
}));
});
it("computes variable indices for a class", function() {
it("allocates registers for a class", function() {
expect(pass).toChangeAST('start = [a-z]', ruleDetails({
resultCount: 1,
expression: leafDetails
registerCount: 1,
expression: leafDetails
}));
});
it("computes variable indices for an any", function() {
it("allocates registers for an any", function() {
expect(pass).toChangeAST('start = .', ruleDetails({
resultCount: 1,
expression: leafDetails
registerCount: 1,
expression: leafDetails
}));
});
});

@ -1,6 +1,6 @@
describe("compiler pass |computeParams|", function() {
function pass(ast) {
PEG.compiler.passes.computeVarIndices(ast);
PEG.compiler.passes.allocateRegisters(ast);
PEG.compiler.passes.computeParams(ast);
}

@ -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-indices.spec.js"></script>
<script src="compiler/passes/allocate-registers.spec.js"></script>
<script src="compiler/passes/compute-params.spec.js"></script>
<script>
(function() {

@ -7,7 +7,7 @@ PEG.compiler = {
"reportMissingRules",
"reportLeftRecursion",
"removeProxyRules",
"computeVarIndices",
"allocateRegisters",
"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-indices.js"
// @include "passes/allocate-registers.js"
// @include "passes/compute-params.js"
// @include "passes/generate-code.js"

@ -1,21 +1,20 @@
/*
* Computes indices of variables used for storing match results and parse
* positions in generated code. These variables are organized as one stack. The
* following will hold after running this pass:
* 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
* pass:
*
* * 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.
* property. It will contain an index of a register that will store a match
* result of the expression represented by the node 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.
* * Some nodes will have a |posIndex| property. It will contain an index of a
* register that will store a saved parse position in generated code.
*
* * All "rule" nodes will contain |resultCount| property. It will contain a
* count of distinct values of |resultIndex| and |posIndex| properties used
* in rule's subnodes. (This is useful to declare variables in generated
* code.)
* * All "rule" nodes will contain a |registerCount| property. It will contain
* the number of registers that will be used by code generated for the
* rule's expression.
*/
PEG.compiler.passes.computeVarIndices = function(ast) {
PEG.compiler.passes.allocateRegisters = function(ast) {
function computeLeaf(node, index) { return 0; }
function computeFromExpression(delta) {
@ -56,7 +55,7 @@ PEG.compiler.passes.computeVarIndices = function(ast) {
depth = compute(node.expression, index);
node.resultCount = depth + 1;
node.registerCount = depth + 1;
},
named: computeFromExpression({ result: 0, pos: 0 }),

@ -558,8 +558,8 @@ PEG.compiler.passes.generateCode = function(ast, options) {
' }',
' ',
' #end',
' #if node.resultCount > 0',
' var #{map(range(node.resultCount), resultVar).join(", ")};',
' #if node.registerCount > 0',
' var #{map(range(node.registerCount), r).join(", ")};',
' #end',
' ',
' #block emit(node.expression)',
@ -567,17 +567,17 @@ PEG.compiler.passes.generateCode = function(ast, options) {
' ',
' cache[cacheKey] = {',
' nextPos: #{posClone("pos")},',
' result: #{resultVar(node.resultIndex)}',
' result: #{r(node.resultIndex)}',
' };',
' #end',
' return #{resultVar(node.resultIndex)};',
' return #{r(node.resultIndex)};',
'}'
],
named: [
'reportFailures++;',
'#block emit(node.expression)',
'reportFailures--;',
'if (reportFailures === 0 && #{resultVar(node.resultIndex)} === null) {',
'if (reportFailures === 0 && #{r(node.resultIndex)} === null) {',
' matchFailed(#{string(node.name)});',
'}'
],
@ -586,17 +586,17 @@ PEG.compiler.passes.generateCode = function(ast, options) {
'#block nextAlternativesCode'
],
"choice.next": [
'if (#{resultVar(node.resultIndex)} === null) {',
'if (#{r(node.resultIndex)} === null) {',
' #block code',
'}'
],
action: [
'#{posSave(node)};',
'#block emit(node.expression)',
'if (#{resultVar(node.resultIndex)} !== null) {',
' #{resultVar(node.resultIndex)} = (function(#{(options.trackLineAndColumn ? ["offset", "line", "column"] : ["offset"]).concat(keys(node.params)).join(", ")}) {#{node.code}})(#{(options.trackLineAndColumn ? [resultVar(node.posIndex) + ".offset", resultVar(node.posIndex) + ".line", resultVar(node.posIndex) + ".column"] : [resultVar(node.posIndex)]).concat(map(values(node.params), param)).join(", ")});',
'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(", ")});',
'}',
'if (#{resultVar(node.resultIndex)} === null) {',
'if (#{r(node.resultIndex)} === null) {',
' #{posRestore(node)};',
'}'
],
@ -606,26 +606,26 @@ PEG.compiler.passes.generateCode = function(ast, options) {
],
"sequence.iteration": [
'#block emit(element)',
'if (#{resultVar(element.resultIndex)} !== null) {',
'if (#{r(element.resultIndex)} !== null) {',
' #block code',
'} else {',
' #{resultVar(node.resultIndex)} = null;',
' #{r(node.resultIndex)} = null;',
' #{posRestore(node)};',
'}'
],
"sequence.inner": [
'#{resultVar(node.resultIndex)} = [#{map(pluck(node.elements, "resultIndex"), resultVar).join(", ")}];'
'#{r(node.resultIndex)} = [#{map(pluck(node.elements, "resultIndex"), r).join(", ")}];'
],
simple_and: [
'#{posSave(node)};',
'reportFailures++;',
'#block emit(node.expression)',
'reportFailures--;',
'if (#{resultVar(node.resultIndex)} !== null) {',
' #{resultVar(node.resultIndex)} = "";',
'if (#{r(node.resultIndex)} !== null) {',
' #{r(node.resultIndex)} = "";',
' #{posRestore(node)};',
'} else {',
' #{resultVar(node.resultIndex)} = null;',
' #{r(node.resultIndex)} = null;',
'}'
],
simple_not: [
@ -633,49 +633,49 @@ PEG.compiler.passes.generateCode = function(ast, options) {
'reportFailures++;',
'#block emit(node.expression)',
'reportFailures--;',
'if (#{resultVar(node.resultIndex)} === null) {',
' #{resultVar(node.resultIndex)} = "";',
'if (#{r(node.resultIndex)} === null) {',
' #{r(node.resultIndex)} = "";',
'} else {',
' #{resultVar(node.resultIndex)} = null;',
' #{r(node.resultIndex)} = null;',
' #{posRestore(node)};',
'}'
],
semantic_and: [
'#{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;'
'#{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;'
],
semantic_not: [
'#{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 : "";'
'#{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 : "";'
],
optional: [
'#block emit(node.expression)',
'#{resultVar(node.resultIndex)} = #{resultVar(node.resultIndex)} !== null ? #{resultVar(node.resultIndex)} : "";'
'#{r(node.resultIndex)} = #{r(node.resultIndex)} !== null ? #{r(node.resultIndex)} : "";'
],
zero_or_more: [
'#{resultVar(node.resultIndex)} = [];',
'#{r(node.resultIndex)} = [];',
'#block emit(node.expression)',
'while (#{resultVar(node.expression.resultIndex)} !== null) {',
' #{resultVar(node.resultIndex)}.push(#{resultVar(node.expression.resultIndex)});',
'while (#{r(node.expression.resultIndex)} !== null) {',
' #{r(node.resultIndex)}.push(#{r(node.expression.resultIndex)});',
' #block emit(node.expression)',
'}'
],
one_or_more: [
'#block emit(node.expression)',
'if (#{resultVar(node.expression.resultIndex)} !== null) {',
' #{resultVar(node.resultIndex)} = [];',
' while (#{resultVar(node.expression.resultIndex)} !== null) {',
' #{resultVar(node.resultIndex)}.push(#{resultVar(node.expression.resultIndex)});',
'if (#{r(node.expression.resultIndex)} !== null) {',
' #{r(node.resultIndex)} = [];',
' while (#{r(node.expression.resultIndex)} !== null) {',
' #{r(node.resultIndex)}.push(#{r(node.expression.resultIndex)});',
' #block emit(node.expression)',
' }',
'} else {',
' #{resultVar(node.resultIndex)} = null;',
' #{r(node.resultIndex)} = null;',
'}'
],
rule_ref: [
'#{resultVar(node.resultIndex)} = parse_#{node.name}();'
'#{r(node.resultIndex)} = parse_#{node.name}();'
],
literal: [
'#if node.value.length === 0',
' #{resultVar(node.resultIndex)} = "";',
' #{r(node.resultIndex)} = "";',
'#else',
' #if !node.ignoreCase',
' #if node.value.length === 1',
@ -695,13 +695,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',
' #{resultVar(node.resultIndex)} = #{string(node.value)};',
' #{r(node.resultIndex)} = #{string(node.value)};',
' #else',
' #{resultVar(node.resultIndex)} = input.substr(#{posOffset("pos")}, #{node.value.length});',
' #{r(node.resultIndex)} = input.substr(#{posOffset("pos")}, #{node.value.length});',
' #end',
' #{posAdvance(node.value.length)};',
' } else {',
' #{resultVar(node.resultIndex)} = null;',
' #{r(node.resultIndex)} = null;',
' if (reportFailures === 0) {',
' matchFailed(#{string(string(node.value))});',
' }',
@ -710,10 +710,10 @@ PEG.compiler.passes.generateCode = function(ast, options) {
],
"class": [
'if (#{regexp}.test(input.charAt(#{posOffset("pos")}))) {',
' #{resultVar(node.resultIndex)} = input.charAt(#{posOffset("pos")});',
' #{r(node.resultIndex)} = input.charAt(#{posOffset("pos")});',
' #{posAdvance(1)};',
'} else {',
' #{resultVar(node.resultIndex)} = null;',
' #{r(node.resultIndex)} = null;',
' if (reportFailures === 0) {',
' matchFailed(#{string(node.rawText)});',
' }',
@ -721,10 +721,10 @@ PEG.compiler.passes.generateCode = function(ast, options) {
],
any: [
'if (input.length > #{posOffset("pos")}) {',
' #{resultVar(node.resultIndex)} = input.charAt(#{posOffset("pos")});',
' #{r(node.resultIndex)} = input.charAt(#{posOffset("pos")});',
' #{posAdvance(1)};',
'} else {',
' #{resultVar(node.resultIndex)} = null;',
' #{r(node.resultIndex)} = null;',
' if (reportFailures === 0) {',
' matchFailed("any character");',
' }',
@ -749,10 +749,10 @@ PEG.compiler.passes.generateCode = function(ast, options) {
vars.emit = emit;
vars.options = options;
vars.resultVar = function(index) { return "result" + index; };
vars.r = function(index) { return "r" + index; };
vars.param = function(param) {
return vars.resultVar(param.resultIndex)
return vars.r(param.resultIndex)
+ map(param.subindices, function(i) { return "[" + i + "]"; });
};
@ -778,10 +778,10 @@ PEG.compiler.passes.generateCode = function(ast, options) {
};
}
vars.posSave = function(node) {
return vars.resultVar(node.posIndex) + " = " + vars.posClone("pos");
return vars.r(node.posIndex) + " = " + vars.posClone("pos");
};
vars.posRestore = function(node) {
return "pos" + " = " + vars.posClone(vars.resultVar(node.posIndex));
return "pos" + " = " + vars.posClone(vars.r(node.posIndex));
};
return templates[name](vars);
@ -807,17 +807,17 @@ PEG.compiler.passes.generateCode = function(ast, options) {
* input.
*
* * 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 |resultVar(node.resultIndex)| to an appropriate
* the first chracter following the matched part of the input and sets a
* register with index specified by |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
* |resultVar(node.resultIndex)| to |null|.
* set to the original value and it sets a register with index specified
* by |node.posIndex| to |null|.
*
* The code can use variables with names |resultVar(node.resultIndex)|
* where |node| is any of the current node's subnodes. It can't use any
* other variables.
* The code uses only registers with indices specified by |node.resultIndex|
* and |node.posIndex| where |node| is the processed node or some of its
* subnodes. It does not use any other registers.
*/
named: emitSimple("named"),

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save