Browse Source

Switch from Jasmine to Mocha & Chai

The switch is mostly mechanical, with assertions translated 1:1. The
biggest non-mechanical part is rewriting Jasmine custom matchers as Chai
helpers. The matchers were streamlined and simplified in the process and
their messages were made more in line with messages produced by built-in
Chai helpers.

Fixes #409.
redux
David Majda 5 years ago
parent
commit
73de2c9384
  1. 6
      gulpfile.js
  2. 4
      package.json
  3. 2
      spec/.eslintrc.json
  4. 68
      spec/api/generated-parser-api.spec.js
  5. 66
      spec/api/pegjs-api.spec.js
  6. 68
      spec/api/plugin-api.spec.js
  7. 501
      spec/behavior/generated-parser-behavior.spec.js
  8. 23
      spec/index.html
  9. 104
      spec/unit/compiler/passes/generate-bytecode.spec.js
  10. 150
      spec/unit/compiler/passes/helpers.js
  11. 10
      spec/unit/compiler/passes/remove-proxy-rules.spec.js
  12. 38
      spec/unit/compiler/passes/report-duplicate-labels.spec.js
  13. 8
      spec/unit/compiler/passes/report-duplicate-rules.spec.js
  14. 92
      spec/unit/compiler/passes/report-infinite-recursion.spec.js
  15. 82
      spec/unit/compiler/passes/report-infinite-repetition.spec.js
  16. 8
      spec/unit/compiler/passes/report-undefined-rules.spec.js
  17. 439
      spec/unit/parser.spec.js
  18. 681
      spec/vendor/jasmine/jasmine-html.js
  19. 82
      spec/vendor/jasmine/jasmine.css
  20. 2600
      spec/vendor/jasmine/jasmine.js
  21. 18
      spec/vendor/mocha/LICENSE
  22. 326
      spec/vendor/mocha/mocha.css
  23. 15559
      spec/vendor/mocha/mocha.js

6
gulpfile.js

@ -9,7 +9,7 @@ let del = require("del");
let eslint = require("gulp-eslint");
let gulp = require("gulp");
let header = require("gulp-header");
let jasmine = require("gulp-jasmine");
let mocha = require("gulp-mocha");
let package_ = require("./package");
let peg = require("./lib/peg");
let rename = require("gulp-rename");
@ -65,8 +65,8 @@ gulp.task("lint", () =>
// Run specs.
gulp.task("spec", () =>
gulp.src(SPEC_FILES)
.pipe(jasmine())
gulp.src(SPEC_FILES, { read: false })
.pipe(mocha())
);
// Run benchmarks.

4
package.json

@ -47,6 +47,7 @@
"babel-preset-es2015": "6.14.0",
"babelify": "7.3.0",
"browserify": "13.1.0",
"chai": "3.5.0",
"del": "2.2.2",
"eslint-config-dmajda": "1.0.0",
"express": "4.14.0",
@ -54,12 +55,13 @@
"gulp": "3.9.1",
"gulp-eslint": "3.0.1",
"gulp-header": "1.8.8",
"gulp-jasmine": "0.2.0",
"gulp-mocha": "3.0.1",
"gulp-rename": "1.2.2",
"gulp-transform": "1.0.8",
"gulp-uglify": "2.0.0",
"morgan": "1.7.0",
"run-sequence": "^1.2.2",
"sinon": "1.17.6",
"vinyl-buffer": "1.0.0",
"vinyl-source-stream": "1.1.0"
},

2
spec/.eslintrc.json

@ -1,6 +1,6 @@
{
"env": {
"commonjs": true,
"jasmine": true
"mocha": true
}
}

68
spec/api/generated-parser-api.spec.js

@ -2,20 +2,24 @@
/* global console */
let chai = require("chai");
let peg = require("../../lib/peg");
let sinon = require("sinon");
let expect = chai.expect;
describe("generated parser API", function() {
describe("parse", function() {
it("parses input", function() {
let parser = peg.generate("start = 'a'");
expect(parser.parse("a")).toBe("a");
expect(parser.parse("a")).to.equal("a");
});
it("throws an exception on syntax error", function() {
let parser = peg.generate("start = 'a'");
expect(() => { parser.parse("b"); }).toThrow();
expect(() => { parser.parse("b"); }).to.throw();
});
describe("start rule", function() {
@ -27,20 +31,20 @@ describe("generated parser API", function() {
describe("when |startRule| is not set", function() {
it("starts parsing from the first allowed rule", function() {
expect(parser.parse("x")).toBe("b");
expect(parser.parse("x")).to.equal("b");
});
});
describe("when |startRule| is set to an allowed rule", function() {
it("starts parsing from specified rule", function() {
expect(parser.parse("x", { startRule: "b" })).toBe("b");
expect(parser.parse("x", { startRule: "c" })).toBe("c");
expect(parser.parse("x", { startRule: "b" })).to.equal("b");
expect(parser.parse("x", { startRule: "c" })).to.equal("c");
});
});
describe("when |startRule| is set to a disallowed start rule", function() {
it("throws an exception", function() {
expect(() => { parser.parse("x", { startRule: "a" }); }).toThrow();
expect(() => { parser.parse("x", { startRule: "a" }); }).to.throw();
});
});
});
@ -55,18 +59,24 @@ describe("generated parser API", function() {
describe("default tracer", function() {
it("traces using console.log (if console is defined)", function() {
if (typeof console === "object") {
spyOn(console, "log");
sinon.stub(console, "log");
}
parser.parse("b");
if (typeof console === "object") {
expect(console.log).toHaveBeenCalledWith("1:1-1:1 rule.enter start");
expect(console.log).toHaveBeenCalledWith("1:1-1:1 rule.enter a");
expect(console.log).toHaveBeenCalledWith("1:1-1:1 rule.fail a");
expect(console.log).toHaveBeenCalledWith("1:1-1:1 rule.enter b");
expect(console.log).toHaveBeenCalledWith("1:1-1:2 rule.match b");
expect(console.log).toHaveBeenCalledWith("1:1-1:2 rule.match start");
try {
parser.parse("b");
if (typeof console === "object") {
expect(console.log.calledWithExactly("1:1-1:1 rule.enter start")).to.equal(true);
expect(console.log.calledWithExactly("1:1-1:1 rule.enter a")).to.equal(true);
expect(console.log.calledWithExactly("1:1-1:1 rule.fail a")).to.equal(true);
expect(console.log.calledWithExactly("1:1-1:1 rule.enter b")).to.equal(true);
expect(console.log.calledWithExactly("1:1-1:2 rule.match b")).to.equal(true);
expect(console.log.calledWithExactly("1:1-1:2 rule.match start")).to.equal(true);
}
} finally {
if (typeof console === "object") {
console.log.restore();
}
}
});
});
@ -74,43 +84,43 @@ describe("generated parser API", function() {
describe("custom tracers", function() {
describe("trace", function() {
it("receives tracing events", function() {
let tracer = jasmine.createSpyObj("tracer", ["trace"]);
let tracer = { trace: sinon.spy() };
parser.parse("b", { tracer: tracer });
expect(tracer.trace).toHaveBeenCalledWith({
expect(tracer.trace.calledWithExactly({
type: "rule.enter",
rule: "start",
location: {
start: { offset: 0, line: 1, column: 1 },
end: { offset: 0, line: 1, column: 1 }
}
});
expect(tracer.trace).toHaveBeenCalledWith({
})).to.equal(true);
expect(tracer.trace.calledWithExactly({
type: "rule.enter",
rule: "a",
location: {
start: { offset: 0, line: 1, column: 1 },
end: { offset: 0, line: 1, column: 1 }
}
});
expect(tracer.trace).toHaveBeenCalledWith({
})).to.equal(true);
expect(tracer.trace.calledWithExactly({
type: "rule.fail",
rule: "a",
location: {
start: { offset: 0, line: 1, column: 1 },
end: { offset: 0, line: 1, column: 1 }
}
});
expect(tracer.trace).toHaveBeenCalledWith({
})).to.equal(true);
expect(tracer.trace.calledWithExactly({
type: "rule.enter",
rule: "b",
location: {
start: { offset: 0, line: 1, column: 1 },
end: { offset: 0, line: 1, column: 1 }
}
});
expect(tracer.trace).toHaveBeenCalledWith({
})).to.equal(true);
expect(tracer.trace.calledWithExactly({
type: "rule.match",
rule: "b",
result: "b",
@ -118,8 +128,8 @@ describe("generated parser API", function() {
start: { offset: 0, line: 1, column: 1 },
end: { offset: 1, line: 1, column: 2 }
}
});
expect(tracer.trace).toHaveBeenCalledWith({
})).to.equal(true);
expect(tracer.trace.calledWithExactly({
type: "rule.match",
rule: "start",
result: "b",
@ -127,7 +137,7 @@ describe("generated parser API", function() {
start: { offset: 0, line: 1, column: 1 },
end: { offset: 1, line: 1, column: 2 }
}
});
})).to.equal(true);
});
});
});

66
spec/api/pegjs-api.spec.js

@ -1,22 +1,26 @@
"use strict";
let chai = require("chai");
let peg = require("../../lib/peg");
let sinon = require("sinon");
let expect = chai.expect;
describe("PEG.js API", function() {
describe("generate", function() {
it("generates a parser", function() {
let parser = peg.generate("start = 'a'");
expect(typeof parser).toBe("object");
expect(parser.parse("a")).toBe("a");
expect(typeof parser).to.equal("object");
expect(parser.parse("a")).to.equal("a");
});
it("throws an exception on syntax error", function() {
expect(() => { peg.generate("start = @"); }).toThrow();
expect(() => { peg.generate("start = @"); }).to.throw();
});
it("throws an exception on semantic error", function() {
expect(() => { peg.generate("start = undefined"); }).toThrow();
expect(() => { peg.generate("start = undefined"); }).to.throw();
});
describe("allowed start rules", function() {
@ -34,9 +38,9 @@ describe("PEG.js API", function() {
it("generated parser can start only from the first rule", function() {
let parser = peg.generate(grammar, { optimize: "speed" });
expect(parser.parse("x", { startRule: "a" })).toBe("x");
expect(() => { parser.parse("x", { startRule: "b" }); }).toThrow();
expect(() => { parser.parse("x", { startRule: "c" }); }).toThrow();
expect(parser.parse("x", { startRule: "a" })).to.equal("x");
expect(() => { parser.parse("x", { startRule: "b" }); }).to.throw();
expect(() => { parser.parse("x", { startRule: "c" }); }).to.throw();
});
});
@ -47,9 +51,9 @@ describe("PEG.js API", function() {
allowedStartRules: ["b", "c"]
});
expect(() => { parser.parse("x", { startRule: "a" }); }).toThrow();
expect(parser.parse("x", { startRule: "b" })).toBe("x");
expect(parser.parse("x", { startRule: "c" })).toBe("x");
expect(() => { parser.parse("x", { startRule: "a" }); }).to.throw();
expect(parser.parse("x", { startRule: "b" })).to.equal("x");
expect(parser.parse("x", { startRule: "c" })).to.equal("x");
});
});
});
@ -59,9 +63,9 @@ describe("PEG.js API", function() {
it("generated parser can start only from the first rule", function() {
let parser = peg.generate(grammar, { optimize: "size" });
expect(parser.parse("x", { startRule: "a" })).toBe("x");
expect(() => { parser.parse("x", { startRule: "b" }); }).toThrow();
expect(() => { parser.parse("x", { startRule: "c" }); }).toThrow();
expect(parser.parse("x", { startRule: "a" })).to.equal("x");
expect(() => { parser.parse("x", { startRule: "b" }); }).to.throw();
expect(() => { parser.parse("x", { startRule: "c" }); }).to.throw();
});
});
@ -72,9 +76,9 @@ describe("PEG.js API", function() {
allowedStartRules: ["b", "c"]
});
expect(() => { parser.parse("x", { startRule: "a" }); }).toThrow();
expect(parser.parse("x", { startRule: "b" })).toBe("x");
expect(parser.parse("x", { startRule: "c" })).toBe("x");
expect(() => { parser.parse("x", { startRule: "a" }); }).to.throw();
expect(parser.parse("x", { startRule: "b" })).to.equal("x");
expect(parser.parse("x", { startRule: "c" })).to.equal("x");
});
});
});
@ -91,7 +95,7 @@ describe("PEG.js API", function() {
it("generated parser doesn't cache intermediate parse results", function() {
let parser = peg.generate(grammar);
expect(parser.parse("ac")).toBe(2);
expect(parser.parse("ac")).to.equal(2);
});
});
@ -99,7 +103,7 @@ describe("PEG.js API", function() {
it("generated parser doesn't cache intermediate parse results", function() {
let parser = peg.generate(grammar, { cache: false });
expect(parser.parse("ac")).toBe(2);
expect(parser.parse("ac")).to.equal(2);
});
});
@ -107,7 +111,7 @@ describe("PEG.js API", function() {
it("generated parser caches intermediate parse results", function() {
let parser = peg.generate(grammar, { cache: true });
expect(parser.parse("ac")).toBe(1);
expect(parser.parse("ac")).to.equal(1);
});
});
});
@ -118,33 +122,33 @@ describe("PEG.js API", function() {
describe("when |trace| is not set", function() {
it("generated parser doesn't trace", function() {
let parser = peg.generate(grammar);
let tracer = jasmine.createSpyObj("tracer", ["trace"]);
let tracer = { trace: sinon.spy() };
parser.parse("a", { tracer: tracer });
expect(tracer.trace).not.toHaveBeenCalled();
expect(tracer.trace.called).to.equal(false);
});
});
describe("when |trace| is set to |false|", function() {
it("generated parser doesn't trace", function() {
let parser = peg.generate(grammar, { trace: false });
let tracer = jasmine.createSpyObj("tracer", ["trace"]);
let tracer = { trace: sinon.spy() };
parser.parse("a", { tracer: tracer });
expect(tracer.trace).not.toHaveBeenCalled();
expect(tracer.trace.called).to.equal(false);
});
});
describe("when |trace| is set to |true|", function() {
it("generated parser traces", function() {
let parser = peg.generate(grammar, { trace: true });
let tracer = jasmine.createSpyObj("tracer", ["trace"]);
let tracer = { trace: sinon.spy() };
parser.parse("a", { tracer: tracer });
expect(tracer.trace).toHaveBeenCalled();
expect(tracer.trace.called).to.equal(true);
});
});
});
@ -159,8 +163,8 @@ describe("PEG.js API", function() {
it("returns generated parser object", function() {
let parser = peg.generate(grammar);
expect(typeof parser).toBe("object");
expect(parser.parse("a")).toBe("a");
expect(typeof parser).to.equal("object");
expect(parser.parse("a")).to.equal("a");
});
});
@ -168,8 +172,8 @@ describe("PEG.js API", function() {
it("returns generated parser object", function() {
let parser = peg.generate(grammar, { output: "parser" });
expect(typeof parser).toBe("object");
expect(parser.parse("a")).toBe("a");
expect(typeof parser).to.equal("object");
expect(parser.parse("a")).to.equal("a");
});
});
@ -177,8 +181,8 @@ describe("PEG.js API", function() {
it("returns generated parser source code", function() {
let source = peg.generate(grammar, { output: "source" });
expect(typeof source).toBe("string");
expect(eval(source).parse("a")).toBe("a");
expect(typeof source).to.equal("string");
expect(eval(source).parse("a")).to.equal("a");
});
});
});

68
spec/api/plugin-api.spec.js

@ -1,39 +1,11 @@
"use strict";
let chai = require("chai");
let peg = require("../../lib/peg");
describe("plugin API", function() {
beforeEach(function() {
this.addMatchers({
toBeObject() {
this.message = () =>
"Expected " + jasmine.pp(this.actual) + " "
+ (this.isNot ? "not " : "")
+ "to be an object.";
return this.actual !== null && typeof this.actual === "object";
},
toBeArray() {
this.message = () =>
"Expected " + jasmine.pp(this.actual) + " "
+ (this.isNot ? "not " : "")
+ "to be an array.";
return Object.prototype.toString.apply(this.actual) === "[object Array]";
},
toBeFunction() {
this.message = () =>
"Expected " + jasmine.pp(this.actual) + " "
+ (this.isNot ? "not " : "")
+ "to be a function.";
return typeof this.actual === "function";
}
});
});
let expect = chai.expect;
describe("plugin API", function() {
describe("use", function() {
let grammar = "start = 'a'";
@ -47,32 +19,32 @@ describe("plugin API", function() {
peg.generate(grammar, { plugins: plugins });
expect(pluginsUsed).toEqual([true, true, true]);
expect(pluginsUsed).to.deep.equal([true, true, true]);
});
it("receives configuration", function() {
let plugin = {
use(config) {
expect(config).toBeObject();
expect(config).to.be.an("object");
expect(config.parser).toBeObject();
expect(config.parser.parse("start = 'a'")).toBeObject();
expect(config.parser).to.be.an("object");
expect(config.parser.parse("start = 'a'")).to.be.an("object");
expect(config.passes).toBeObject();
expect(config.passes).to.be.an("object");
expect(config.passes.check).toBeArray();
expect(config.passes.check).to.be.an("array");
config.passes.check.forEach(pass => {
expect(pass).toBeFunction();
expect(pass).to.be.a("function");
});
expect(config.passes.transform).toBeArray();
expect(config.passes.transform).to.be.an("array");
config.passes.transform.forEach(pass => {
expect(pass).toBeFunction();
expect(pass).to.be.a("function");
});
expect(config.passes.generate).toBeArray();
expect(config.passes.generate).to.be.an("array");
config.passes.generate.forEach(pass => {
expect(pass).toBeFunction();
expect(pass).to.be.a("function");
});
}
};
@ -83,7 +55,7 @@ describe("plugin API", function() {
it("receives options", function() {
let plugin = {
use(config, options) {
expect(options).toEqual(generateOptions);
expect(options).to.equal(generateOptions);
}
};
let generateOptions = { plugins: [plugin], foo: 42 };
@ -114,7 +86,7 @@ describe("plugin API", function() {
};
let parser = peg.generate("a", { plugins: [plugin] });
expect(parser.parse("a")).toBe("a");
expect(parser.parse("a")).to.equal("a");
});
it("can change compiler passes", function() {
@ -129,7 +101,7 @@ describe("plugin API", function() {
};
let parser = peg.generate(grammar, { plugins: [plugin] });
expect(parser.parse("a")).toBe(42);
expect(parser.parse("a")).to.equal(42);
});
it("can change options", function() {
@ -148,9 +120,9 @@ describe("plugin API", function() {
plugins: [plugin]
});
expect(() => { parser.parse("x", { startRule: "a" }); }).toThrow();
expect(parser.parse("x", { startRule: "b" })).toBe("x");
expect(parser.parse("x", { startRule: "c" })).toBe("x");
expect(() => { parser.parse("x", { startRule: "a" }); }).to.throw();
expect(parser.parse("x", { startRule: "b" })).to.equal("x");
expect(parser.parse("x", { startRule: "c" })).to.equal("x");
});
});
});

501
spec/behavior/generated-parser-behavior.spec.js
File diff suppressed because it is too large
View File

23
spec/index.html

@ -3,22 +3,17 @@
<head>
<meta charset="utf-8">
<title>PEG.js Spec Suite</title>
<link rel="stylesheet" href="vendor/jasmine/jasmine.css">
<script src="vendor/jasmine/jasmine.js"></script>
<script src="vendor/jasmine/jasmine-html.js"></script>
<link rel="stylesheet" href="vendor/mocha/mocha.css">
</head>
<body>
<div id="mocha"></div>
<script src="vendor/mocha/mocha.js"></script>
<script>
mocha.setup('bdd');
</script>
<script src="bundle.js"></script>
<script>
(function() {
var env = jasmine.getEnv(),
reporter = new jasmine.HtmlReporter();
env.addReporter(reporter);
env.specFilter = reporter.specFilter;
window.onload = function() { env.execute(); };
})();
mocha.run();
</script>
</head>
<body>
</body>
</html>

104
spec/unit/compiler/passes/generate-bytecode.spec.js

@ -1,7 +1,13 @@
"use strict";
let chai = require("chai");
let helpers = require("./helpers");
let peg = require("../../../../lib/peg");
chai.use(helpers);
let expect = chai.expect;
describe("compiler pass |generateBytecode|", function() {
let pass = peg.compiler.passes.generate.generateBytecode;
@ -15,7 +21,7 @@ describe("compiler pass |generateBytecode|", function() {
describe("for grammar", function() {
it("generates correct bytecode", function() {
expect(pass).toChangeAST([
expect(pass).to.changeAST([
"a = 'a'",
"b = 'b'",
"c = 'c'"
@ -29,7 +35,7 @@ describe("compiler pass |generateBytecode|", function() {
});
it("defines correct constants", function() {
expect(pass).toChangeAST([
expect(pass).to.changeAST([
"a = 'a'",
"b = 'b'",
"c = 'c'"
@ -46,7 +52,7 @@ describe("compiler pass |generateBytecode|", function() {
describe("for rule", function() {
it("generates correct bytecode", function() {
expect(pass).toChangeAST("start = 'a'", bytecodeDetails([
expect(pass).to.changeAST("start = 'a'", bytecodeDetails([
18, 0, 2, 2, 22, 0, 23, 1 // <expression>
]));
});
@ -56,7 +62,7 @@ describe("compiler pass |generateBytecode|", function() {
let grammar = "start 'start' = 'a'";
it("generates correct bytecode", function() {
expect(pass).toChangeAST(grammar, bytecodeDetails([
expect(pass).to.changeAST(grammar, bytecodeDetails([
28, // SILENT_FAILS_ON
18, 1, 2, 2, 22, 1, 23, 2, // <expression>
29, // SILENT_FAILS_OFF
@ -66,7 +72,7 @@ describe("compiler pass |generateBytecode|", function() {
});
it("defines correct constants", function() {
expect(pass).toChangeAST(grammar, constsDetails([
expect(pass).to.changeAST(grammar, constsDetails([
"peg$otherExpectation(\"start\")",
"\"a\"",
"peg$literalExpectation(\"a\", false)"
@ -76,7 +82,7 @@ describe("compiler pass |generateBytecode|", function() {
describe("for choice", function() {
it("generates correct bytecode", function() {
expect(pass).toChangeAST("start = 'a' / 'b' / 'c'", bytecodeDetails([
expect(pass).to.changeAST("start = 'a' / 'b' / 'c'", bytecodeDetails([
18, 0, 2, 2, 22, 0, 23, 1, // <alternatives[0]>
14, 21, 0, // IF_ERROR
6, // * POP
@ -93,7 +99,7 @@ describe("compiler pass |generateBytecode|", function() {
let grammar = "start = 'a' { code }";
it("generates correct bytecode", function() {
expect(pass).toChangeAST(grammar, bytecodeDetails([
expect(pass).to.changeAST(grammar, bytecodeDetails([
5, // PUSH_CURR_POS
18, 0, 2, 2, 22, 0, 23, 1, // <expression>
15, 6, 0, // IF_NOT_ERROR
@ -104,7 +110,7 @@ describe("compiler pass |generateBytecode|", function() {
});
it("defines correct constants", function() {
expect(pass).toChangeAST(grammar, constsDetails([
expect(pass).to.changeAST(grammar, constsDetails([
"\"a\"",
"peg$literalExpectation(\"a\", false)",
"function() { code }"
@ -116,7 +122,7 @@ describe("compiler pass |generateBytecode|", function() {
let grammar = "start = a:'a' { code }";
it("generates correct bytecode", function() {
expect(pass).toChangeAST(grammar, bytecodeDetails([
expect(pass).to.changeAST(grammar, bytecodeDetails([
5, // PUSH_CURR_POS
18, 0, 2, 2, 22, 0, 23, 1, // <expression>
15, 7, 0, // IF_NOT_ERROR
@ -127,7 +133,7 @@ describe("compiler pass |generateBytecode|", function() {
});
it("defines correct constants", function() {
expect(pass).toChangeAST(grammar, constsDetails([
expect(pass).to.changeAST(grammar, constsDetails([
"\"a\"",
"peg$literalExpectation(\"a\", false)",
"function(a) { code }"
@ -139,7 +145,7 @@ describe("compiler pass |generateBytecode|", function() {
let grammar = "start = a:'a' b:'b' c:'c' { code }";
it("generates correct bytecode", function() {
expect(pass).toChangeAST(grammar, bytecodeDetails([
expect(pass).to.changeAST(grammar, bytecodeDetails([
5, // PUSH_CURR_POS
18, 0, 2, 2, 22, 0, 23, 1, // <elements[0]>
15, 40, 3, // IF_NOT_ERROR
@ -163,7 +169,7 @@ describe("compiler pass |generateBytecode|", function() {
});
it("defines correct constants", function() {
expect(pass).toChangeAST(grammar, constsDetails([
expect(pass).to.changeAST(grammar, constsDetails([
"\"a\"",
"peg$literalExpectation(\"a\", false)",
"\"b\"",
@ -180,7 +186,7 @@ describe("compiler pass |generateBytecode|", function() {
let grammar = "start = 'a' 'b' 'c'";
it("generates correct bytecode", function() {
expect(pass).toChangeAST(grammar, bytecodeDetails([
expect(pass).to.changeAST(grammar, bytecodeDetails([
5, // PUSH_CURR_POS
18, 0, 2, 2, 22, 0, 23, 1, // <elements[0]>
15, 33, 3, // IF_NOT_ERROR
@ -203,7 +209,7 @@ describe("compiler pass |generateBytecode|", function() {
});
it("defines correct constants", function() {
expect(pass).toChangeAST(grammar, constsDetails([
expect(pass).to.changeAST(grammar, constsDetails([
"\"a\"",
"peg$literalExpectation(\"a\", false)",
"\"b\"",
@ -216,7 +222,7 @@ describe("compiler pass |generateBytecode|", function() {
describe("for labeled", function() {
it("generates correct bytecode", function() {
expect(pass).toChangeAST("start = a:'a'", bytecodeDetails([
expect(pass).to.changeAST("start = a:'a'", bytecodeDetails([
18, 0, 2, 2, 22, 0, 23, 1 // <expression>
]));
});
@ -224,7 +230,7 @@ describe("compiler pass |generateBytecode|", function() {
describe("for text", function() {
it("generates correct bytecode", function() {
expect(pass).toChangeAST("start = $'a'", bytecodeDetails([
expect(pass).to.changeAST("start = $'a'", bytecodeDetails([
5, // PUSH_CURR_POS
18, 0, 2, 2, 22, 0, 23, 1, // <expression>
15, 2, 1, // IF_NOT_ERROR
@ -239,7 +245,7 @@ describe("compiler pass |generateBytecode|", function() {
let grammar = "start = &'a'";
it("generates correct bytecode", function() {
expect(pass).toChangeAST(grammar, bytecodeDetails([
expect(pass).to.changeAST(grammar, bytecodeDetails([
5, // PUSH_CURR_POS
28, // SILENT_FAILS_ON
18, 0, 2, 2, 22, 0, 23, 1, // <expression>
@ -255,7 +261,7 @@ describe("compiler pass |generateBytecode|", function() {
});
it("defines correct constants", function() {
expect(pass).toChangeAST(grammar, constsDetails([
expect(pass).to.changeAST(grammar, constsDetails([
"\"a\"",
"peg$literalExpectation(\"a\", false)"
]));
@ -266,7 +272,7 @@ describe("compiler pass |generateBytecode|", function() {
let grammar = "start = !'a'";
it("generates correct bytecode", function() {
expect(pass).toChangeAST(grammar, bytecodeDetails([
expect(pass).to.changeAST(grammar, bytecodeDetails([
5, // PUSH_CURR_POS
28, // SILENT_FAILS_ON
18, 0, 2, 2, 22, 0, 23, 1, // <expression>
@ -282,7 +288,7 @@ describe("compiler pass |generateBytecode|", function() {
});
it("defines correct constants", function() {
expect(pass).toChangeAST(grammar, constsDetails([
expect(pass).to.changeAST(grammar, constsDetails([
"\"a\"",
"peg$literalExpectation(\"a\", false)"
]));
@ -293,7 +299,7 @@ describe("compiler pass |generateBytecode|", function() {
let grammar = "start = 'a'?";
it("generates correct bytecode", function() {
expect(pass).toChangeAST(grammar, bytecodeDetails([
expect(pass).to.changeAST(grammar, bytecodeDetails([
18, 0, 2, 2, 22, 0, 23, 1, // <expression>
14, 2, 0, // IF_ERROR
6, // * POP
@ -302,7 +308,7 @@ describe("compiler pass |generateBytecode|", function() {
});
it("defines correct constants", function() {
expect(pass).toChangeAST(grammar, constsDetails([
expect(pass).to.changeAST(grammar, constsDetails([
"\"a\"",
"peg$literalExpectation(\"a\", false)"
]));
@ -313,7 +319,7 @@ describe("compiler pass |generateBytecode|", function() {
let grammar = "start = 'a'*";
it("generates correct bytecode", function() {
expect(pass).toChangeAST(grammar, bytecodeDetails([
expect(pass).to.changeAST(grammar, bytecodeDetails([
4, // PUSH_EMPTY_ARRAY
18, 0, 2, 2, 22, 0, 23, 1, // <expression>
16, 9, // WHILE_NOT_ERROR
@ -324,7 +330,7 @@ describe("compiler pass |generateBytecode|", function() {
});
it("defines correct constants", function() {
expect(pass).toChangeAST(grammar, constsDetails([
expect(pass).to.changeAST(grammar, constsDetails([
"\"a\"",
"peg$literalExpectation(\"a\", false)"
]));
@ -335,7 +341,7 @@ describe("compiler pass |generateBytecode|", function() {
let grammar = "start = 'a'+";
it("generates correct bytecode", function() {
expect(pass).toChangeAST(grammar, bytecodeDetails([
expect(pass).to.changeAST(grammar, bytecodeDetails([
4, // PUSH_EMPTY_ARRAY
18, 0, 2, 2, 22, 0, 23, 1, // <expression>
15, 12, 3, // IF_NOT_ERROR
@ -350,7 +356,7 @@ describe("compiler pass |generateBytecode|", function() {
});
it("defines correct constants", function() {
expect(pass).toChangeAST(grammar, constsDetails([
expect(pass).to.changeAST(grammar, constsDetails([
"\"a\"",
"peg$literalExpectation(\"a\", false)"
]));
@ -359,7 +365,7 @@ describe("compiler pass |generateBytecode|", function() {
describe("for group", function() {
it("generates correct bytecode", function() {
expect(pass).toChangeAST("start = ('a')", bytecodeDetails([
expect(pass).to.changeAST("start = ('a')", bytecodeDetails([
18, 0, 2, 2, 22, 0, 23, 1 // <expression>
]));
});
@ -370,7 +376,7 @@ describe("compiler pass |generateBytecode|", function() {
let grammar = "start = &{ code }";
it("generates correct bytecode", function() {
expect(pass).toChangeAST(grammar, bytecodeDetails([
expect(pass).to.changeAST(grammar, bytecodeDetails([
25, // UPDATE_SAVED_POS
26, 0, 0, 0, // CALL
13, 2, 2, // IF
@ -382,7 +388,7 @@ describe("compiler pass |generateBytecode|", function() {
});
it("defines correct constants", function() {
expect(pass).toChangeAST(
expect(pass).to.changeAST(
grammar,
constsDetails(["function() { code }"])
);
@ -393,7 +399,7 @@ describe("compiler pass |generateBytecode|", function() {
let grammar = "start = a:'a' b:'b' c:'c' &{ code }";
it("generates correct bytecode", function() {
expect(pass).toChangeAST(grammar, bytecodeDetails([
expect(pass).to.changeAST(grammar, bytecodeDetails([
5, // PUSH_CURR_POS
18, 0, 2, 2, 22, 0, 23, 1, // <elements[0]>
15, 55, 3, // IF_NOT_ERROR
@ -427,7 +433,7 @@ describe("compiler pass |generateBytecode|", function() {
});
it("defines correct constants", function() {
expect(pass).toChangeAST(grammar, constsDetails([
expect(pass).to.changeAST(grammar, constsDetails([
"\"a\"",
"peg$literalExpectation(\"a\", false)",
"\"b\"",
@ -445,7 +451,7 @@ describe("compiler pass |generateBytecode|", function() {
let grammar = "start = !{ code }";
it("generates correct bytecode", function() {
expect(pass).toChangeAST(grammar, bytecodeDetails([
expect(pass).to.changeAST(grammar, bytecodeDetails([
25, // UPDATE_SAVED_POS
26, 0, 0, 0, // CALL
13, 2, 2, // IF
@ -457,7 +463,7 @@ describe("compiler pass |generateBytecode|", function() {
});
it("defines correct constants", function() {
expect(pass).toChangeAST(
expect(pass).to.changeAST(
grammar,
constsDetails(["function() { code }"])
);
@ -468,7 +474,7 @@ describe("compiler pass |generateBytecode|", function() {
let grammar = "start = a:'a' b:'b' c:'c' !{ code }";
it("generates correct bytecode", function() {
expect(pass).toChangeAST(grammar, bytecodeDetails([
expect(pass).to.changeAST(grammar, bytecodeDetails([
5, // PUSH_CURR_POS
18, 0, 2, 2, 22, 0, 23, 1, // <elements[0]>
15, 55, 3, // IF_NOT_ERROR
@ -502,7 +508,7 @@ describe("compiler pass |generateBytecode|", function() {
});
it("defines correct constants", function() {
expect(pass).toChangeAST(grammar, constsDetails([
expect(pass).to.changeAST(grammar, constsDetails([
"\"a\"",
"peg$literalExpectation(\"a\", false)",
"\"b\"",
@ -517,7 +523,7 @@ describe("compiler pass |generateBytecode|", function() {
describe("for rule_ref", function() {
it("generates correct bytecode", function() {
expect(pass).toChangeAST([
expect(pass).to.changeAST([
"start = other",
"other = 'other'"
].join("\n"), {
@ -536,13 +542,13 @@ describe("compiler pass |generateBytecode|", function() {
let grammar = "start = ''";
it("generates correct bytecode", function() {
expect(pass).toChangeAST(grammar, bytecodeDetails([
expect(pass).to.changeAST(grammar, bytecodeDetails([
0, 0 // PUSH
]));
});
it("defines correct constants", function() {
expect(pass).toChangeAST(grammar, constsDetails(["\"\""]));
expect(pass).to.changeAST(grammar, constsDetails(["\"\""]));
});
});
@ -550,7 +556,7 @@ describe("compiler pass |generateBytecode|", function() {
let grammar = "start = 'a'";
it("generates correct bytecode", function() {
expect(pass).toChangeAST(grammar, bytecodeDetails([
expect(pass).to.changeAST(grammar, bytecodeDetails([
18, 0, 2, 2, // MATCH_STRING
22, 0, // * ACCEPT_STRING
23, 1 // * FAIL
@ -558,7 +564,7 @@ describe("compiler pass |generateBytecode|", function() {
});
it("defines correct constants", function() {
expect(pass).toChangeAST(grammar, constsDetails([
expect(pass).to.changeAST(grammar, constsDetails([
"\"a\"",
"peg$literalExpectation(\"a\", false)"
]));
@ -569,7 +575,7 @@ describe("compiler pass |generateBytecode|", function() {
let grammar = "start = 'A'i";
it("generates correct bytecode", function() {
expect(pass).toChangeAST(grammar, bytecodeDetails([
expect(pass).to.changeAST(grammar, bytecodeDetails([
19, 0, 2, 2, // MATCH_STRING_IC
21, 1, // * ACCEPT_N
23, 1 // * FAIL
@ -577,7 +583,7 @@ describe("compiler pass |generateBytecode|", function() {
});
it("defines correct constants", function() {
expect(pass).toChangeAST(grammar, constsDetails([
expect(pass).to.changeAST(grammar, constsDetails([
"\"a\"",
"peg$literalExpectation(\"A\", true)"
]));
@ -587,7 +593,7 @@ describe("compiler pass |generateBytecode|", function() {
describe("for class", function() {
it("generates correct bytecode", function() {
expect(pass).toChangeAST("start = [a]", bytecodeDetails([
expect(pass).to.changeAST("start = [a]", bytecodeDetails([
20, 0, 2, 2, // MATCH_REGEXP
21, 1, // * ACCEPT_N
23, 1 // * FAIL
@ -596,7 +602,7 @@ describe("compiler pass |generateBytecode|", function() {
describe("non-inverted case-sensitive", function() {
it("defines correct constants", function() {
expect(pass).toChangeAST("start = [a]", constsDetails([
expect(pass).to.changeAST("start = [a]", constsDetails([
"/^[a]/",
"peg$classExpectation([\"a\"], false, false)"
]));
@ -605,7 +611,7 @@ describe("compiler pass |generateBytecode|", function() {
describe("inverted case-sensitive", function() {
it("defines correct constants", function() {
expect(pass).toChangeAST("start = [^a]", constsDetails([
expect(pass).to.changeAST("start = [^a]", constsDetails([
"/^[^a]/",
"peg$classExpectation([\"a\"], true, false)"
]));
@ -614,7 +620,7 @@ describe("compiler pass |generateBytecode|", function() {
describe("non-inverted case-insensitive", function() {
it("defines correct constants", function() {
expect(pass).toChangeAST("start = [a]i", constsDetails([
expect(pass).to.changeAST("start = [a]i", constsDetails([
"/^[a]/i",
"peg$classExpectation([\"a\"], false, true)"
]));
@ -623,7 +629,7 @@ describe("compiler pass |generateBytecode|", function() {
describe("complex", function() {
it("defines correct constants", function() {
expect(pass).toChangeAST("start = [ab-def-hij-l]", constsDetails([
expect(pass).to.changeAST("start = [ab-def-hij-l]", constsDetails([
"/^[ab-def-hij-l]/",
"peg$classExpectation([\"a\", [\"b\", \"d\"], \"e\", [\"f\", \"h\"], \"i\", [\"j\", \"l\"]], false, false)"
]));
@ -635,7 +641,7 @@ describe("compiler pass |generateBytecode|", function() {
let grammar = "start = .";
it("generates bytecode", function() {
expect(pass).toChangeAST(grammar, bytecodeDetails([
expect(pass).to.changeAST(grammar, bytecodeDetails([
17, 2, 2, // MATCH_ANY
21, 1, // * ACCEPT_N
23, 0 // * FAIL
@ -643,7 +649,7 @@ describe("compiler pass |generateBytecode|", function() {
});
it("defines correct constants", function() {
expect(pass).toChangeAST(
expect(pass).to.changeAST(
grammar,
constsDetails(["peg$anyExpectation()"])
);

150
spec/unit/compiler/passes/helpers.js

@ -2,103 +2,87 @@
let peg = require("../../../../lib/peg");
beforeEach(function() {
this.addMatchers({
toChangeAST(grammar, details, options) {
options = options !== undefined ? options : {};
function matchDetails(value, details) {
function isArray(value) {
return Object.prototype.toString.apply(value) === "[object Array]";
}
module.exports = function(chai, utils) {
let Assertion = chai.Assertion;
function isObject(value) {
return value !== null && typeof value === "object";
}
Assertion.addMethod("changeAST", function(grammar, props, options) {
options = options !== undefined ? options : {};
if (isArray(details)) {
if (!isArray(value)) { return false; }
function matchProps(value, props) {
function isArray(value) {
return Object.prototype.toString.apply(value) === "[object Array]";
}
if (value.length !== details.length) { return false; }
for (let i = 0; i < details.length; i++) {
if (!matchDetails(value[i], details[i])) { return false; }
}
function isObject(value) {
return value !== null && typeof value === "object";
}
return true;
} else if (isObject(details)) {
if (!isObject(value)) { return false; }
if (isArray(props)) {
if (!isArray(value)) { return false; }
let keys = Object.keys(details);
for (let i = 0; i < keys.length; i++) {
let key = keys[i];
if (value.length !== props.length) { return false; }
for (let i = 0; i < props.length; i++) {
if (!matchProps(value[i], props[i])) { return false; }
}
if (!(key in value)) { return false; }
return true;
} else if (isObject(props)) {
if (!isObject(value)) { return false; }
if (!matchDetails(value[key], details[key])) { return false; }
}
let keys = Object.keys(props);
for (let i = 0; i < keys.length; i++) {
let key = keys[i];
return true;
} else {
return value === details;
}
}
if (!(key in value)) { return false; }
let ast = peg.parser.parse(grammar);
this.actual(ast, options);
this.message = () =>
"Expected the pass "
+ "with options " + jasmine.pp(options) + " "
+ (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);
},
toReportError(grammar, details) {
let ast = peg.parser.parse(grammar);
try {
this.actual(ast);
} catch (e) {
if (this.isNot) {
this.message = () =>
"Expected the pass not to report an error "
+ "for grammar " + jasmine.pp(grammar) + ", "
+ "but it did.";
} else {
if (details) {
let keys = Object.keys(details);
for (let i = 0; i < keys.length; i++) {
let key = keys[i];
if (!this.env.equals_(e[key], details[key])) {
this.message = () =>
"Expected the pass to report an error "
+ "with details " + jasmine.pp(details) + " "
+ "for grammar " + jasmine.pp(grammar) + ", "
+ "but " + jasmine.pp(key) + " "
+ "is " + jasmine.pp(e[key]) + ".";
return false;
}
}
}
if (!matchProps(value[key], props[key])) { return false; }
}
return true;
} else {
return value === props;
}
}
let ast = peg.parser.parse(grammar);
utils.flag(this, "object")(ast, options);
this.assert(
matchProps(ast, props),
"expected #{this} to change the AST to match #{exp}",
"expected #{this} to not change the AST to match #{exp}",
props,
ast
);
});
Assertion.addMethod("reportError", function(grammar, props) {
let ast = peg.parser.parse(grammar);
this.message = () =>
"Expected the pass to report an error "
+ (details ? "with details " + jasmine.pp(details) + " " : "")
+ "for grammar " + jasmine.pp(grammar) + ", "
+ "but it didn't.";
let passed, result;
try {
utils.flag(this, "object")(ast);
passed = true;
} catch (e) {
result = e;
passed = false;
}
return false;
this.assert(
!passed,
"expected #{this} to report an error but it didn't",
"expected #{this} to not report an error but #{act} was reported",
null,
result
);
if (!passed && props !== undefined) {
Object.keys(props).forEach(key => {
new Assertion(result).to.have.property(key)
.that.is.deep.equal(props[key]);
});
}
});
});
};

10
spec/unit/compiler/passes/remove-proxy-rules.spec.js

@ -1,13 +1,19 @@
"use strict";
let chai = require("chai");
let helpers = require("./helpers");
let peg = require("../../../../lib/peg");
chai.use(helpers);
let expect = chai.expect;
describe("compiler pass |removeProxyRules|", function() {
let pass = peg.compiler.passes.transform.removeProxyRules;
describe("when a proxy rule isn't listed in |allowedStartRules|", function() {
it("updates references and removes it", function() {
expect(pass).toChangeAST(
expect(pass).to.changeAST(
[
"start = proxy",
"proxy = proxied",
@ -29,7 +35,7 @@ describe("compiler pass |removeProxyRules|", function() {
describe("when a proxy rule is listed in |allowedStartRules|", function() {
it("updates references but doesn't remove it", function() {
expect(pass).toChangeAST(
expect(pass).to.changeAST(
[
"start = proxy",
"proxy = proxied",

38
spec/unit/compiler/passes/report-duplicate-labels.spec.js

@ -1,13 +1,19 @@
"use strict";
let chai = require("chai");
let helpers = require("./helpers");
let peg = require("../../../../lib/peg");
chai.use(helpers);
let expect = chai.expect;
describe("compiler pass |reportDuplicateLabels|", function() {
let 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'", {
expect(pass).to.reportError("start = a:'a' a:'a'", {
message: "Label \"a\" is already defined at line 1, column 9.",
location: {
start: { offset: 14, line: 1, column: 15 },
@ -17,29 +23,29 @@ describe("compiler pass |reportDuplicateLabels|", function() {
});
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'");
expect(pass).to.not.reportError("start = ('a' / a:'a' / 'a') a:'a'");
expect(pass).to.not.reportError("start = (a:'a' { }) a:'a'");
expect(pass).to.not.reportError("start = ('a' a:'a' 'a') a:'a'");
expect(pass).to.not.reportError("start = b:(a:'a') a:'a'");
expect(pass).to.not.reportError("start = $(a:'a') a:'a'");
expect(pass).to.not.reportError("start = &(a:'a') a:'a'");
expect(pass).to.not.reportError("start = !(a:'a') a:'a'");
expect(pass).to.not.reportError("start = (a:'a')? a:'a'");
expect(pass).to.not.reportError("start = (a:'a')* a:'a'");
expect(pass).to.not.reportError("start = (a:'a')+ a:'a'");
expect(pass).to.not.reportError("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'");
expect(pass).to.not.reportError("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')", {
expect(pass).to.reportError("start = a:'a' (a:'a')", {
message: "Label \"a\" is already defined at line 1, column 9.",
location: {
start: { offset: 15, line: 1, column: 16 },
@ -49,11 +55,11 @@ describe("compiler pass |reportDuplicateLabels|", function() {
});
it("doesn't report labels duplicate with the label of the current element", function() {
expect(pass).not.toReportError("start = a:(a:'a')");
expect(pass).to.not.reportError("start = a:(a:'a')");
});