diff --git a/spec/unit/compiler/passes/helpers.js b/spec/unit/compiler/passes/helpers.js index d6fa50d..064b249 100644 --- a/spec/unit/compiler/passes/helpers.js +++ b/spec/unit/compiler/passes/helpers.js @@ -54,6 +54,57 @@ beforeEach(function() { }; return matchDetails(ast, details); + }, + + toReportError: function(grammar, details) { + var ast = PEG.parser.parse(grammar); + + try { + this.actual(ast); + + this.message = function() { + return "Expected the pass to report an error" + + (details ? " with details " + jasmine.pp(details) : "") + ", " + + "for grammar " + jasmine.pp(grammar) + ", " + + "but it didn't."; + }; + + return false; + } catch (e) { + /* + * Should be at the top level but then JSHint complains about bad for + * in variable. + */ + var key; + + if (this.isNot) { + this.message = function() { + return "Expected the pass not to report an error" + + "for grammar " + jasmine.pp(grammar) + ", " + + "but it did."; + }; + } else { + if (details) { + for (key in details) { + if (details.hasOwnProperty(key)) { + if (!this.env.equals_(e[key], details[key])) { + this.message = function() { + return "Expected the pass to report an error" + + (details ? " with details " + jasmine.pp(details) : "") + ", " + + "for grammar " + jasmine.pp(grammar) + ", " + + "but " + jasmine.pp(key) + " " + + "is " + jasmine.pp(e[key]) + "."; + }; + + return false; + } + } + } + } + } + + return true; + } } }); }); diff --git a/spec/unit/compiler/passes/remove-proxy-rules.spec.js b/spec/unit/compiler/passes/remove-proxy-rules.spec.js index fa18369..862033d 100644 --- a/spec/unit/compiler/passes/remove-proxy-rules.spec.js +++ b/spec/unit/compiler/passes/remove-proxy-rules.spec.js @@ -1,140 +1,51 @@ describe("compiler pass |removeProxyRules|", function() { var pass = PEG.compiler.passes.transform.removeProxyRules; - function proxyGrammar(rule) { - return [rule, 'proxy = proxied', 'proxied = "a"'].join("\n"); - } - - function expressionDetails(details) { - return { - rules: [ - { type: "rule", name: "start", expression: details }, - { type: "rule", name: "proxied" } - ] - }; - } - - var defaultOptions = { allowedStartRules: ["start"] }, - proxiedRefDetails = { type: "rule_ref", name: "proxied" }, - simpleDetails = expressionDetails({ expression: proxiedRefDetails }); - - it("removes proxy rule from a rule", function() { - expect(pass).toChangeAST(proxyGrammar('start = proxy'), defaultOptions, { - rules: [ - { type: "rule", name: "start", expression: proxiedRefDetails }, - { type: "rule", name: "proxied" } - ] + describe("when a proxy rule isn't listed in |allowedStartRules|", function() { + it("updates references and removes it", function() { + expect(pass).toChangeAST( + [ + 'start = proxy', + 'proxy = proxied', + 'proxied = "a"' + ].join("\n"), + { allowedStartRules: ["start"] }, + { + rules: [ + { + name: "start", + expression: { type: "rule_ref", name: "proxied" } + }, + { name: "proxied" } + ] + } + ); }); }); - it("removes proxy rule from a named", function() { - expect(pass).toChangeAST( - proxyGrammar('start "start" = proxy'), - defaultOptions, - simpleDetails - ); - }); - - it("removes proxy rule from a choice", function() { - expect(pass).toChangeAST( - proxyGrammar('start = proxy / "a" / "b"'), - defaultOptions, - expressionDetails({ alternatives: [proxiedRefDetails, {}, {}] }) - ); - expect(pass).toChangeAST( - proxyGrammar('start = "a" / "b" / proxy'), - defaultOptions, - expressionDetails({ alternatives: [{}, {}, proxiedRefDetails] }) - ); - }); - - it("removes proxy rule from an action", function() { - expect(pass).toChangeAST( - proxyGrammar('start = proxy { }'), - defaultOptions, - simpleDetails - ); - }); - - it("removes proxy rule from a sequence", function() { - expect(pass).toChangeAST( - proxyGrammar('start = proxy "a" "b"'), - defaultOptions, - expressionDetails({ elements: [proxiedRefDetails, {}, {}] }) - ); - expect(pass).toChangeAST( - proxyGrammar('start = "a" "b" proxy'), - defaultOptions, - expressionDetails({ elements: [{}, {}, proxiedRefDetails] }) - ); - }); - - it("removes proxy rule from a labeled", function() { - expect(pass).toChangeAST( - proxyGrammar('start = label:proxy'), - defaultOptions, - simpleDetails - ); - }); - - it("removes proxy rule from a text", function() { - expect(pass).toChangeAST( - proxyGrammar('start = $proxy'), - defaultOptions, - simpleDetails - ); - }); - - it("removes proxy rule from a simple and", function() { - expect(pass).toChangeAST( - proxyGrammar('start = &proxy'), - defaultOptions, - simpleDetails - ); - }); - - it("removes proxy rule from a simple not", function() { - expect(pass).toChangeAST( - proxyGrammar('start = &proxy'), - defaultOptions, - simpleDetails - ); - }); - - it("removes proxy rule from an optional", function() { - expect(pass).toChangeAST( - proxyGrammar('start = proxy?'), - defaultOptions, - simpleDetails - ); - }); - - it("removes proxy rule from a zero or more", function() { - expect(pass).toChangeAST( - proxyGrammar('start = proxy*'), - defaultOptions, - simpleDetails - ); - }); - - it("removes proxy rule from a one or more", function() { - expect(pass).toChangeAST( - proxyGrammar('start = proxy+'), - defaultOptions, - simpleDetails - ); - }); - - it("doesn't remove a proxy rule listed in |allowedStartRules|", function() { - expect(pass).toChangeAST( - proxyGrammar('start = proxy'), - { allowedStartRules: ["proxy"] }, - { - rules: [ - { name: "proxy" }, - { name: "proxied" } - ] - } - ); + describe("when a proxy rule is listed in |allowedStartRules|", function() { + it("updates references but doesn't remove it", function() { + expect(pass).toChangeAST( + [ + 'start = proxy', + 'proxy = proxied', + 'proxied = "a"' + ].join("\n"), + { allowedStartRules: ["start", "proxy"] }, + { + rules: [ + { + name: "start", + expression: { type: "rule_ref", name: "proxied" } + }, + { + name: "proxy", + expression: { type: "rule_ref", name: "proxied" } + }, + { name: "proxied" } + ] + } + ); + }); }); }); diff --git a/spec/unit/compiler/passes/report-left-recursion.spec.js b/spec/unit/compiler/passes/report-left-recursion.spec.js index 937e4fa..0b57cd8 100644 --- a/spec/unit/compiler/passes/report-left-recursion.spec.js +++ b/spec/unit/compiler/passes/report-left-recursion.spec.js @@ -1,96 +1,29 @@ describe("compiler pass |reportLeftRecursion|", function() { var pass = PEG.compiler.passes.check.reportLeftRecursion; - beforeEach(function() { - this.addMatchers({ - toReportLeftRecursionIn: function(grammar) { - var ast = PEG.parser.parse(grammar); - - try { - this.actual(ast); - - this.message = function() { - return "Expected the pass to report left recursion for grammar " - + jasmine.pp(grammar) + ", " - + "but it didn't."; - }; - - return false; - } catch (e) { - if (this.isNot) { - this.message = function() { - return "Expected the pass not to report left recursion for grammar " - + jasmine.pp(grammar) + ", " - + "but it did."; - }; - } else { - this.message = function() { - return "Expected the pass to report left recursion for grammar " - + jasmine.pp(grammar) + ", " - + "but it reported an error with message " - + jasmine.pp(e.message) + "."; - }; - } - - return e.message === 'Left recursion detected for rule \"start\".'; - } - } + it("reports direct left recursion", function() { + expect(pass).toReportError('start = start', { + message: 'Left recursion detected for rule \"start\".' }); }); - it("reports left recursion inside a rule", function() { - expect(pass).toReportLeftRecursionIn('start = start'); - }); - - it("reports left recursion inside a named", function() { - expect(pass).toReportLeftRecursionIn('start "start" = start'); - }); - - it("reports left recursion inside a choice", function() { - expect(pass).toReportLeftRecursionIn('start = start / "a" / "b"'); - expect(pass).toReportLeftRecursionIn('start = "a" / "b" / start'); - }); - - it("reports left recursion inside an action", function() { - expect(pass).toReportLeftRecursionIn('start = start { }'); - }); - - it("reports left recursion inside a sequence", function() { - expect(pass).toReportLeftRecursionIn('start = start "a" "b"'); - }); - - it("reports left recursion inside a labeled", function() { - expect(pass).toReportLeftRecursionIn('start = label:start'); - }); - - it("reports left recursion inside a text", function() { - expect(pass).toReportLeftRecursionIn('start = $start'); - }); - - it("reports left recursion inside a simple and", function() { - expect(pass).toReportLeftRecursionIn('start = &start'); - }); - - it("reports left recursion inside a simple not", function() { - expect(pass).toReportLeftRecursionIn('start = &start'); - }); - - it("reports left recursion inside an optional", function() { - expect(pass).toReportLeftRecursionIn('start = start?'); - }); - - it("reports left recursion inside a zero or more", function() { - expect(pass).toReportLeftRecursionIn('start = start*'); - }); - - it("reports left recursion inside a one or more", function() { - expect(pass).toReportLeftRecursionIn('start = start+'); - }); - it("reports indirect left recursion", function() { - expect(pass).toReportLeftRecursionIn([ + expect(pass).toReportError([ 'start = stop', 'stop = start' - ].join("\n")); + ].join("\n"), { + message: 'Left recursion detected for rule \"start\".' + }); + }); + + describe("in sequences", function() { + it("reports left recursion only for the first element", function() { + expect(pass).toReportError('start = start "a" "b"', { + message: 'Left recursion detected for rule \"start\".' + }); + + expect(pass).not.toReportError('start = "a" start "b"'); + expect(pass).not.toReportError('start = "a" "b" start'); + }); }); }); diff --git a/spec/unit/compiler/passes/report-missing-rules.spec.js b/spec/unit/compiler/passes/report-missing-rules.spec.js index 11c94e6..f24b660 100644 --- a/spec/unit/compiler/passes/report-missing-rules.spec.js +++ b/spec/unit/compiler/passes/report-missing-rules.spec.js @@ -1,90 +1,9 @@ describe("compiler pass |reportMissingRules|", function() { var pass = PEG.compiler.passes.check.reportMissingRules; - beforeEach(function() { - this.addMatchers({ - toReportMissingRuleIn: function(grammar) { - var ast = PEG.parser.parse(grammar); - - try { - this.actual(ast); - - this.message = function() { - return "Expected the pass to report a missing rule for grammar " - + jasmine.pp(grammar) + ", " - + "but it didn't."; - }; - - return false; - } catch (e) { - if (this.isNot) { - this.message = function() { - return "Expected the pass not to report a missing rule for grammar " - + jasmine.pp(grammar) + ", " - + "but it did."; - }; - } else { - this.message = function() { - return "Expected the pass to report a missing rule for grammar " - + jasmine.pp(grammar) + ", " - + "but it reported an error with message " - + jasmine.pp(e.message) + "."; - }; - } - - return e.message === 'Referenced rule "missing" does not exist.'; - } - } + it("reports missing rules", function() { + expect(pass).toReportError('start = missing', { + message: 'Referenced rule "missing" does not exist.' }); }); - - it("reports missing rule referenced from a rule", function() { - expect(pass).toReportMissingRuleIn('start = missing'); - }); - - it("reports missing rule referenced from a named", function() { - expect(pass).toReportMissingRuleIn('start "start" = missing'); - }); - - it("reports missing rule referenced from a choice", function() { - expect(pass).toReportMissingRuleIn('start = missing / "a" / "b"'); - expect(pass).toReportMissingRuleIn('start = "a" / "b" / missing'); - }); - - it("reports missing rule referenced from an action", function() { - expect(pass).toReportMissingRuleIn('start = missing { }'); - }); - - it("reports missing rule referenced from a sequence", function() { - expect(pass).toReportMissingRuleIn('start = missing "a" "b"'); - expect(pass).toReportMissingRuleIn('start = "a" "b" missing'); - }); - - it("reports missing rule referenced from a labeled", function() { - expect(pass).toReportMissingRuleIn('start = label:missing'); - }); - - it("reports missing rule referenced from a text", function() { - expect(pass).toReportMissingRuleIn('start = $missing'); - }); - - it("reports missing rule referenced from a simple and", function() { - expect(pass).toReportMissingRuleIn('start = &missing'); - }); - - it("reports missing rule referenced from a simple not", function() { - expect(pass).toReportMissingRuleIn('start = &missing'); - }); - - it("reports missing rule referenced from an optional", function() { - expect(pass).toReportMissingRuleIn('start = missing?'); - }); - - it("reports missing rule referenced from a zero or more", function() { - expect(pass).toReportMissingRuleIn('start = missing*'); - }); - - it("reports missing rule referenced from a one or more", function() { - expect(pass).toReportMissingRuleIn('start = missing+'); - }); });