diff --git a/src/checks.js b/src/checks.js deleted file mode 100644 index 40adb1b..0000000 --- a/src/checks.js +++ /dev/null @@ -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, []); - } -}; diff --git a/src/compiler.js b/src/compiler.js index 1601876..c913e06 100644 --- a/src/compiler.js +++ b/src/compiler.js @@ -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" diff --git a/src/passes.js b/src/passes.js index 427aa03..6c87152 100644 --- a/src/passes.js +++ b/src/passes.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. */ diff --git a/test/checks-test.js b/test/checks-test.js deleted file mode 100644 index d69a63d..0000000 --- a/test/checks-test.js +++ /dev/null @@ -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]); } -}); - -})(); diff --git a/test/index.html b/test/index.html index 89c8bf8..12422ff 100644 --- a/test/index.html +++ b/test/index.html @@ -8,7 +8,6 @@ - diff --git a/test/passes-test.js b/test/passes-test.js index 5ec4f60..0f27def 100644 --- a/test/passes-test.js +++ b/test/passes-test.js @@ -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 { diff --git a/test/run b/test/run index 53f788c..551aa3c 100755 --- a/test/run +++ b/test/run @@ -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) {