diff --git a/spec/compiler/passes/compute-params.spec.js b/spec/compiler/passes/compute-params.spec.js new file mode 100644 index 0000000..5903702 --- /dev/null +++ b/spec/compiler/passes/compute-params.spec.js @@ -0,0 +1,200 @@ +describe("compiler pass |computeParams|", function() { + function pass(ast) { + PEG.compiler.passes.computeVarNames(ast); + PEG.compiler.passes.computeParams(ast); + } + + function ruleDetails(details) { return { rules: [details] }; } + + function expressionDetails(details) { + return ruleDetails({ expression: details }); + } + + function innerExpressionDetails(details) { + return expressionDetails({ expression: details }); + } + + describe("basic cases", function() { + it("computes params for an action", function() { + expect(pass).toChangeAST('start = a:"a" { }', expressionDetails({ + params: { a: "result0" } + })); + }); + + it("computes params for a semantic and", function() { + expect(pass).toChangeAST('start = a:"a" &{ }', expressionDetails({ + elements: [ + {}, + { params: { a: "result0" } } + ] + })); + }); + + it("computes params for a semantic not", function() { + expect(pass).toChangeAST('start = a:"a" !{ }', expressionDetails({ + elements: [ + {}, + { params: { a: "result0" } } + ] + })); + }); + }); + + describe("recursive walk", function() { + it("computes params for a choice", function() { + expect(pass).toChangeAST( + 'start = a:"a" { } / "b" / "c"', + expressionDetails({ + alternatives: [{ params: { a: "result0" } }, {}, {}] + }) + ); + expect(pass).toChangeAST( + 'start = "a" / "b" / c:"c" { }', + expressionDetails({ + alternatives: [{}, {}, { params: { c: "result0" } }] + }) + ); + }); + + it("computes params for a sequence", function() { + expect(pass).toChangeAST( + 'start = (a:"a" { }) "b" "c"', + expressionDetails({ + elements: [{ params: { a: "result0" } }, {}, {}] + }) + ); + expect(pass).toChangeAST( + 'start = "a" "b" (c:"c" { })', + expressionDetails({ + elements: [{}, {}, { params: { c: "result2" } }] + }) + ); + }); + + it("computes params for a labeled", function() { + expect(pass).toChangeAST('start = a:(b:"b" { })', innerExpressionDetails({ + params: { b: "result0" } + })); + }); + + it("computes params for a simple and", function() { + expect(pass).toChangeAST('start = &(a:"a" { })', innerExpressionDetails({ + params: { a: "result0" } + })); + }); + + it("computes params for a simple not", function() { + expect(pass).toChangeAST('start = &(a:"a" { })', innerExpressionDetails({ + params: { a: "result0" } + })); + }); + + it("computes params for an optional", function() { + expect(pass).toChangeAST('start = (a:"a" { })?', innerExpressionDetails({ + params: { a: "result0" } + })); + }); + + it("computes params for a zero or more", function() { + expect(pass).toChangeAST('start = (a:"a" { })*', innerExpressionDetails({ + params: { a: "result1" } + })); + }); + + it("computes params for a one or more", function() { + expect(pass).toChangeAST('start = (a:"a" { })+', innerExpressionDetails({ + params: { a: "result1" } + })); + }); + + it("computes params for an action", function() { + expect(pass).toChangeAST('start = (a:"a" { }) { }', innerExpressionDetails({ + params: { a: "result0" } + })); + }); + }); + + describe("scoping", function() { + it("creates a new scope for a choice", function() { + expect(pass).toChangeAST( + 'start = (a:"a" / b:"b" / c:"c") { }', + expressionDetails({ params: {} }) + ); + }); + + it("does not create a new scope for a sequence", function() { + expect(pass).toChangeAST( + 'start = a:"a" b:"b" c:"c" { }', + expressionDetails({ + params: { a: "result0[0]", b: "result0[1]", c: "result0[2]" } + }) + ); + expect(pass).toChangeAST( + 'start = a:"a" (b:"b" c:"c" d:"d") e:"e"{ }', + expressionDetails({ + params: { + a: "result0[0]", + b: "result0[1][0]", + c: "result0[1][1]", + d: "result0[1][2]", + e: "result0[2]" + } + }) + ); + + /* + * Regression tests for a bug where e.g. resultVar names like |result10| + * were incorrectly treated as names derived from |result1|, leading to + * incorrect substitution. + */ + expect(pass).toChangeAST( + 'start = ("a" "b" "c" "d" "e" "f" "g" "h" "i" j:"j" { })*', + innerExpressionDetails({ + params: { j: "result1[9]" } // Buggy code put "result1[0]0" here. + }) + ); + }); + + it("creates a new scope for a labeled", function() { + expect(pass).toChangeAST('start = a:(b:"b") { }', expressionDetails({ + params: { a: "result0"} + })); + }); + + it("creates a new scope for a simple and", function() { + expect(pass).toChangeAST('start = &(a:"a") { }', expressionDetails({ + params: {} + })); + }); + + it("creates a new scope for a simple not", function() { + expect(pass).toChangeAST('start = !(a:"a") { }', expressionDetails({ + params: {} + })); + }); + + it("creates a new scope for an optional", function() { + expect(pass).toChangeAST('start = (a:"a")? { }', expressionDetails({ + params: {} + })); + }); + + it("creates a new scope for a zero or more", function() { + expect(pass).toChangeAST('start = (a:"a")* { }', expressionDetails({ + params: {} + })); + }); + + it("creates a new scope for a one or more", function() { + expect(pass).toChangeAST('start = (a:"a")+ { }', expressionDetails({ + params: {} + })); + }); + + it("creates a new scope for an action", function() { + expect(pass).toChangeAST('start = (a:"a" { }) { }', expressionDetails({ + params: {} + })); + }); + }); +}); diff --git a/spec/compiler/passes/compute-var-names.spec.js b/spec/compiler/passes/compute-var-names.spec.js index e16c8d4..57eff42 100644 --- a/spec/compiler/passes/compute-var-names.spec.js +++ b/spec/compiler/passes/compute-var-names.spec.js @@ -22,61 +22,6 @@ describe("compiler pass |computeVarNames|", function() { function ruleDetails(details) { return { rules: [details] }; } - beforeEach(function() { - this.addMatchers({ - toChangeAST: function(grammar, details) { - function matchDetails(value, details) { - function isArray(value) { - return Object.prototype.toString.apply(value) === "[object Array]"; - } - - function isObject(value) { - return value !== null && typeof value === "object"; - } - - var i, key; - - if (isArray(details)) { - if (!isArray(value)) { return false; } - - if (value.length !== details.length) { return false; } - for (i = 0; i < details.length; i++) { - if (!matchDetails(value[i], details[i])) { return false; } - } - - return true; - } else if (isObject(details)) { - if (!isObject(value)) { return false; } - - for (key in details) { - if (!(key in value)) { return false; } - - if (!matchDetails(value[key], details[key])) { return false; } - } - - return true; - } else { - return value === details; - } - } - - var ast = PEG.parser.parse(grammar); - - this.actual(ast); - - this.message = function() { - return "Expected the pass " - + (this.isNot ? "not " : "") - + "to change the AST " + jasmine.pp(ast) + " " - + "to match " + jasmine.pp(details) + ", " - + "but it " + (this.isNot ? "did" : "didn't") + "."; - }; - - return matchDetails(ast, details); - } - }); - }); - it("computes variable names for a choice", function() { expect(pass).toChangeAST('start = &"a" / &"b" / &"c"', ruleDetails({ resultVars: ["result0"], diff --git a/spec/helpers.js b/spec/helpers.js index b866ac8..6b65989 100644 --- a/spec/helpers.js +++ b/spec/helpers.js @@ -1 +1,58 @@ -PEG = require("../lib/peg.js"); +if (typeof module !== "undefined") { + PEG = require("../lib/peg.js"); +} + +beforeEach(function() { + this.addMatchers({ + toChangeAST: function(grammar, details) { + function matchDetails(value, details) { + function isArray(value) { + return Object.prototype.toString.apply(value) === "[object Array]"; + } + + function isObject(value) { + return value !== null && typeof value === "object"; + } + + var i, key; + + if (isArray(details)) { + if (!isArray(value)) { return false; } + + if (value.length !== details.length) { return false; } + for (i = 0; i < details.length; i++) { + if (!matchDetails(value[i], details[i])) { return false; } + } + + return true; + } else if (isObject(details)) { + if (!isObject(value)) { return false; } + + for (key in details) { + if (!(key in value)) { return false; } + + if (!matchDetails(value[key], details[key])) { return false; } + } + + return true; + } else { + return value === details; + } + } + + var ast = PEG.parser.parse(grammar); + + this.actual(ast); + + this.message = function() { + return "Expected the pass " + + (this.isNot ? "not " : "") + + "to change the AST " + jasmine.pp(ast) + " " + + "to match " + jasmine.pp(details) + ", " + + "but it " + (this.isNot ? "did" : "didn't") + "."; + }; + + return matchDetails(ast, details); + } + }); +}); diff --git a/spec/index.html b/spec/index.html index 7622420..8f864a6 100644 --- a/spec/index.html +++ b/spec/index.html @@ -6,11 +6,13 @@ + + -

PEG.js Test Suite

diff --git a/test/run b/test/run index 33685a6..e1732c7 100755 --- a/test/run +++ b/test/run @@ -75,8 +75,7 @@ QUnit.done(function(details) { [ "helpers.js", - "compiler/passes/remove-proxy-rules-test.js", - "compiler/passes/compute-params-test.js" + "compiler/passes/remove-proxy-rules-test.js" ].forEach(function(file) { eval("with (QUnit) {" + fs.readFileSync(__dirname + "/" + file, "utf8") + "}"); });