diff --git a/lib/compiler/index.js b/lib/compiler/index.js
index d3edda9..d9888fd 100644
--- a/lib/compiler/index.js
+++ b/lib/compiler/index.js
@@ -19,17 +19,18 @@ var compiler = {
*/
passes: {
check: {
- reportMissingRules: require("./passes/report-missing-rules"),
- reportDuplicateRules: require("./passes/report-duplicate-rules"),
- reportLeftRecursion: require("./passes/report-left-recursion"),
- reportInfiniteLoops: require("./passes/report-infinite-loops")
+ reportMissingRules: require("./passes/report-missing-rules"),
+ reportDuplicateRules: require("./passes/report-duplicate-rules"),
+ reportDuplicateLabels: require("./passes/report-duplicate-labels"),
+ reportLeftRecursion: require("./passes/report-left-recursion"),
+ reportInfiniteLoops: require("./passes/report-infinite-loops")
},
transform: {
- removeProxyRules: require("./passes/remove-proxy-rules")
+ removeProxyRules: require("./passes/remove-proxy-rules")
},
generate: {
- generateBytecode: require("./passes/generate-bytecode"),
- generateJS: require("./passes/generate-js")
+ generateBytecode: require("./passes/generate-bytecode"),
+ generateJS: require("./passes/generate-js")
}
},
diff --git a/lib/compiler/passes/report-duplicate-labels.js b/lib/compiler/passes/report-duplicate-labels.js
new file mode 100644
index 0000000..16a0c11
--- /dev/null
+++ b/lib/compiler/passes/report-duplicate-labels.js
@@ -0,0 +1,54 @@
+"use strict";
+
+var GrammarError = require("../../grammar-error"),
+ arrays = require("../../utils/arrays"),
+ objects = require("../../utils/objects"),
+ visitor = require("../visitor");
+
+/* Checks that each label is defined only once within each scope. */
+function reportDuplicateLabels(ast) {
+ function checkExpressionWithClonedEnv(node, env) {
+ check(node.expression, objects.clone(env));
+ }
+
+ var check = visitor.build({
+ rule: function(node) {
+ check(node.expression, { });
+ },
+
+ choice: function(node, env) {
+ arrays.each(node.alternatives, function(alternative) {
+ check(alternative, objects.clone(env));
+ });
+ },
+
+ action: checkExpressionWithClonedEnv,
+
+ labeled: function(node, env) {
+ if (env.hasOwnProperty(node.label)) {
+ throw new GrammarError(
+ "Label \"" + node.label + "\" is already defined "
+ + "at line " + env[node.label].start.line + ", "
+ + "column " + env[node.label].start.column + ".",
+ node.location
+ );
+ }
+
+ check(node.expression, env);
+
+ env[node.label] = node.location;
+ },
+
+ text: checkExpressionWithClonedEnv,
+ simple_and: checkExpressionWithClonedEnv,
+ simple_not: checkExpressionWithClonedEnv,
+ optional: checkExpressionWithClonedEnv,
+ zero_or_more: checkExpressionWithClonedEnv,
+ one_or_more: checkExpressionWithClonedEnv,
+ group: checkExpressionWithClonedEnv
+ });
+
+ check(ast);
+}
+
+module.exports = reportDuplicateLabels;
diff --git a/package.json b/package.json
index 8240d47..fad938a 100644
--- a/package.json
+++ b/package.json
@@ -28,6 +28,7 @@
"lib/compiler/passes/generate-js.js",
"lib/compiler/passes/remove-proxy-rules.js",
"lib/compiler/passes/report-duplicate-rules.js",
+ "lib/compiler/passes/report-duplicate-labels.js",
"lib/compiler/passes/report-infinite-loops.js",
"lib/compiler/passes/report-left-recursion.js",
"lib/compiler/passes/report-missing-rules.js",
diff --git a/spec/index.html b/spec/index.html
index d59581b..cc4cb88 100644
--- a/spec/index.html
+++ b/spec/index.html
@@ -11,6 +11,7 @@
+
diff --git a/spec/unit/compiler/passes/report-duplicate-labels.spec.js b/spec/unit/compiler/passes/report-duplicate-labels.spec.js
new file mode 100644
index 0000000..cad5a6d
--- /dev/null
+++ b/spec/unit/compiler/passes/report-duplicate-labels.spec.js
@@ -0,0 +1,59 @@
+/* global peg */
+
+"use strict";
+
+describe("compiler pass |reportDuplicateLabels|", function() {
+ var pass = peg.compiler.passes.check.reportDuplicateLabels;
+
+ describe("in a sequence", function() {
+ it("reports labels duplicate with labels of preceding elements", function() {
+ expect(pass).toReportError('start = a:"a" a:"a"', {
+ message: 'Label "a" is already defined at line 1, column 9.',
+ location: {
+ start: { offset: 14, line: 1, column: 15 },
+ end: { offset: 19, line: 1, column: 20 }
+ }
+ });
+ });
+
+ it("doesn't report labels duplicate with labels in subexpressions", function() {
+ expect(pass).not.toReportError('start = ("a" / a:"a" / "a") a:"a"');
+ expect(pass).not.toReportError('start = (a:"a" { }) a:"a"');
+ expect(pass).not.toReportError('start = ("a" a:"a" "a") a:"a"');
+ expect(pass).not.toReportError('start = b:(a:"a") a:"a"');
+ expect(pass).not.toReportError('start = $(a:"a") a:"a"');
+ expect(pass).not.toReportError('start = &(a:"a") a:"a"');
+ expect(pass).not.toReportError('start = !(a:"a") a:"a"');
+ expect(pass).not.toReportError('start = (a:"a")? a:"a"');
+ expect(pass).not.toReportError('start = (a:"a")* a:"a"');
+ expect(pass).not.toReportError('start = (a:"a")+ a:"a"');
+ expect(pass).not.toReportError('start = (a:"a") a:"a"');
+ });
+ });
+
+ describe("in a choice", function() {
+ it("doesn't report labels duplicate with labels of preceding alternatives", function() {
+ expect(pass).not.toReportError('start = a:"a" / a:"a"');
+ });
+ });
+
+ describe("in outer sequence", function() {
+ it("reports labels duplicate with labels of preceding elements", function() {
+ expect(pass).toReportError('start = a:"a" (a:"a")', {
+ message: 'Label "a" is already defined at line 1, column 9.',
+ location: {
+ start: { offset: 15, line: 1, column: 16 },
+ end: { offset: 20, line: 1, column: 21 }
+ }
+ });
+ });
+
+ it("doesn't report labels duplicate with the label of the current element", function() {
+ expect(pass).not.toReportError('start = a:(a:"a")');
+ });
+
+ it("doesn't report labels duplicate with labels of following elements", function() {
+ expect(pass).not.toReportError('start = (a:"a") a:"a"');
+ });
+ });
+});