Unify checks and passes

There is no real reason to have them separated.
redux
David Majda 13 years ago
parent 6cd5bdc5e6
commit 8a0276ffb7

@ -1,120 +0,0 @@
/*
* Checks made on the grammar AST before compilation. Each check is a function
* that is passed the AST and does not return anything. If the check passes, the
* function does not do anything special, otherwise it throws
* |PEG.GrammarError|. The order in which the checks are run is specified in
* |PEG.compiler.compile| and should be the same as the order of definitions
* here.
*/
PEG.compiler.checks = {
/* Checks that all referenced rules exist. */
reportMissingRules: function(ast) {
function nop() {}
function checkExpression(node) { check(node.expression); }
function checkSubnodes(propertyName) {
return function(node) { each(node[propertyName], check); };
}
var check = buildNodeVisitor({
grammar:
function(node) {
for (var name in node.rules) {
check(node.rules[name]);
}
},
rule: checkExpression,
choice: checkSubnodes("alternatives"),
sequence: checkSubnodes("elements"),
labeled: checkExpression,
simple_and: checkExpression,
simple_not: checkExpression,
semantic_and: nop,
semantic_not: nop,
optional: checkExpression,
zero_or_more: checkExpression,
one_or_more: checkExpression,
action: checkExpression,
rule_ref:
function(node) {
if (ast.rules[node.name] === undefined) {
throw new PEG.GrammarError(
"Referenced rule \"" + node.name + "\" does not exist."
);
}
},
literal: nop,
any: nop,
"class": nop
});
check(ast);
},
/* Checks that no left recursion is present. */
reportLeftRecursion: function(ast) {
function nop() {}
function checkExpression(node, appliedRules) {
check(node.expression, appliedRules);
}
var check = buildNodeVisitor({
grammar:
function(node, appliedRules) {
for (var name in node.rules) {
check(node.rules[name], appliedRules);
}
},
rule:
function(node, appliedRules) {
check(node.expression, appliedRules.concat(node.name));
},
choice:
function(node, appliedRules) {
each(node.alternatives, function(alternative) {
check(alternative, appliedRules);
});
},
sequence:
function(node, appliedRules) {
if (node.elements.length > 0) {
check(node.elements[0], appliedRules);
}
},
labeled: checkExpression,
simple_and: checkExpression,
simple_not: checkExpression,
semantic_and: nop,
semantic_not: nop,
optional: checkExpression,
zero_or_more: checkExpression,
one_or_more: checkExpression,
action: checkExpression,
rule_ref:
function(node, appliedRules) {
if (contains(appliedRules, node.name)) {
throw new PEG.GrammarError(
"Left recursion detected for rule \"" + node.name + "\"."
);
}
check(ast.rules[node.name], appliedRules);
},
literal: nop,
any: nop,
"class": nop
});
check(ast, []);
}
};

@ -6,23 +6,14 @@ PEG.compiler = {
* cause its malfunction.
*/
compile: function(ast) {
var CHECK_NAMES = [
"reportMissingRules",
"reportLeftRecursion"
];
var PASS_NAMES = [
"reportMissingRules",
"reportLeftRecursion",
"removeProxyRules",
"computeStackDepths"
];
var i;
for (i = 0; i < CHECK_NAMES.length; i++) {
this.checks[CHECK_NAMES[i]](ast);
}
for (i = 0; i < PASS_NAMES.length; i++) {
for (var i = 0; i < PASS_NAMES.length; i++) {
this.passes[PASS_NAMES[i]](ast);
}
@ -34,6 +25,5 @@ PEG.compiler = {
}
};
// @include "checks.js"
// @include "passes.js"
// @include "emitter.js"

@ -1,11 +1,122 @@
/*
* Optimalization passes made on the grammar AST before compilation. Each pass
* is a function that is passed the AST and does not return anything. The AST
* can be modified in-place by the pass. The order in which the passes are run
* is specified in |PEG.compiler.compile| and should be the same as the order of
* definitions here.
* Compiler passes.
*
* Each pass is a function that is passed the AST. It can perform checks on it
* or modify it as needed. If the pass encounters a semantic error, it throws
* |PEG.GrammarError|.
*/
PEG.compiler.passes = {
/* Checks that all referenced rules exist. */
reportMissingRules: function(ast) {
function nop() {}
function checkExpression(node) { check(node.expression); }
function checkSubnodes(propertyName) {
return function(node) { each(node[propertyName], check); };
}
var check = buildNodeVisitor({
grammar:
function(node) {
for (var name in node.rules) {
check(node.rules[name]);
}
},
rule: checkExpression,
choice: checkSubnodes("alternatives"),
sequence: checkSubnodes("elements"),
labeled: checkExpression,
simple_and: checkExpression,
simple_not: checkExpression,
semantic_and: nop,
semantic_not: nop,
optional: checkExpression,
zero_or_more: checkExpression,
one_or_more: checkExpression,
action: checkExpression,
rule_ref:
function(node) {
if (ast.rules[node.name] === undefined) {
throw new PEG.GrammarError(
"Referenced rule \"" + node.name + "\" does not exist."
);
}
},
literal: nop,
any: nop,
"class": nop
});
check(ast);
},
/* Checks that no left recursion is present. */
reportLeftRecursion: function(ast) {
function nop() {}
function checkExpression(node, appliedRules) {
check(node.expression, appliedRules);
}
var check = buildNodeVisitor({
grammar:
function(node, appliedRules) {
for (var name in node.rules) {
check(node.rules[name], appliedRules);
}
},
rule:
function(node, appliedRules) {
check(node.expression, appliedRules.concat(node.name));
},
choice:
function(node, appliedRules) {
each(node.alternatives, function(alternative) {
check(alternative, appliedRules);
});
},
sequence:
function(node, appliedRules) {
if (node.elements.length > 0) {
check(node.elements[0], appliedRules);
}
},
labeled: checkExpression,
simple_and: checkExpression,
simple_not: checkExpression,
semantic_and: nop,
semantic_not: nop,
optional: checkExpression,
zero_or_more: checkExpression,
one_or_more: checkExpression,
action: checkExpression,
rule_ref:
function(node, appliedRules) {
if (contains(appliedRules, node.name)) {
throw new PEG.GrammarError(
"Left recursion detected for rule \"" + node.name + "\"."
);
}
check(ast.rules[node.name], appliedRules);
},
literal: nop,
any: nop,
"class": nop
});
check(ast, []);
},
/*
* Removes proxy rules -- that is, rules that only delegate to other rule.
*/

@ -1,72 +0,0 @@
(function() {
module("PEG.compiler.checks");
test("reports missing referenced rules", function() {
function testGrammar(grammar) {
raises(
function() {
var ast = PEG.parser.parse(grammar);
PEG.compiler.checks.reportMissingRules(ast);
},
function(e) {
return e instanceof PEG.GrammarError
&& e.message === "Referenced rule \"missing\" does not exist.";
}
);
}
var grammars = [
'start = missing',
'start = missing / "a" / "b"',
'start = "a" / "b" / missing',
'start = missing "a" "b"',
'start = "a" "b" missing',
'start = label:missing',
'start = &missing',
'start = !missing',
'start = missing?',
'start = missing*',
'start = missing+',
'start = missing { }'
];
for (var i = 0; i < grammars.length; i++) { testGrammar(grammars[i]); }
});
test("reports left recursion", function() {
function testGrammar(grammar) {
raises(
function() {
var ast = PEG.parser.parse(grammar);
PEG.compiler.checks.reportLeftRecursion(ast);
},
function(e) {
return e instanceof PEG.GrammarError
&& e.message === "Left recursion detected for rule \"start\".";
}
);
}
var grammars = [
/* Direct */
'start = start',
'start = start / "a" / "b"',
'start = "a" / "b" / start',
'start = start "a" "b"',
'start = label:start',
'start = &start',
'start = !start',
'start = start?',
'start = start*',
'start = start+',
'start = start { }',
/* Indirect */
'start = stop; stop = start'
];
for (var i = 0; i < grammars.length; i++) { testGrammar(grammars[i]); }
});
})();

@ -8,7 +8,6 @@
<script src="vendor/qunit/qunit.js"></script>
<script src="helpers.js"></script>
<script src="parser-test.js"></script>
<script src="checks-test.js"></script>
<script src="passes-test.js"></script>
<script src="compiler-test.js"></script>
</head>

@ -2,6 +2,73 @@
module("PEG.compiler.passes");
test("reports missing referenced rules", function() {
function testGrammar(grammar) {
raises(
function() {
var ast = PEG.parser.parse(grammar);
PEG.compiler.passes.reportMissingRules(ast);
},
function(e) {
return e instanceof PEG.GrammarError
&& e.message === "Referenced rule \"missing\" does not exist.";
}
);
}
var grammars = [
'start = missing',
'start = missing / "a" / "b"',
'start = "a" / "b" / missing',
'start = missing "a" "b"',
'start = "a" "b" missing',
'start = label:missing',
'start = &missing',
'start = !missing',
'start = missing?',
'start = missing*',
'start = missing+',
'start = missing { }'
];
for (var i = 0; i < grammars.length; i++) { testGrammar(grammars[i]); }
});
test("reports left recursion", function() {
function testGrammar(grammar) {
raises(
function() {
var ast = PEG.parser.parse(grammar);
PEG.compiler.passes.reportLeftRecursion(ast);
},
function(e) {
return e instanceof PEG.GrammarError
&& e.message === "Left recursion detected for rule \"start\".";
}
);
}
var grammars = [
/* Direct */
'start = start',
'start = start / "a" / "b"',
'start = "a" / "b" / start',
'start = start "a" "b"',
'start = label:start',
'start = &start',
'start = !start',
'start = start?',
'start = start*',
'start = start+',
'start = start { }',
/* Indirect */
'start = stop; stop = start'
];
for (var i = 0; i < grammars.length; i++) { testGrammar(grammars[i]); }
});
test("removes proxy rules", function() {
function simpleGrammar(rules, startRule) {
return {

@ -77,7 +77,6 @@ QUnit.done = function(details) {
[
"helpers.js",
"parser-test.js",
"checks-test.js",
"passes-test.js",
"compiler-test.js",
].forEach(function(file) {

Loading…
Cancel
Save