From eb67cbedb46267e1dd4b8c2bd4af4498b7cfa0fd Mon Sep 17 00:00:00 2001 From: David Majda Date: Mon, 27 Jun 2016 13:42:28 +0200 Subject: [PATCH] Report duplicate labels as errors Resolves #270. --- lib/compiler/index.js | 15 ++--- .../passes/report-duplicate-labels.js | 54 +++++++++++++++++ package.json | 1 + spec/index.html | 1 + .../passes/report-duplicate-labels.spec.js | 59 +++++++++++++++++++ 5 files changed, 123 insertions(+), 7 deletions(-) create mode 100644 lib/compiler/passes/report-duplicate-labels.js create mode 100644 spec/unit/compiler/passes/report-duplicate-labels.spec.js 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"'); + }); + }); +});