diff --git a/CHANGELOG.md b/CHANGELOG.md index 1bc02ca3..1185a410 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ Unreleased ### Breaking changes ### New features + - [#501](https://github.com/peggyjs/peggy/issues/501) Implement soft-mode for generated parsers with access to partial results on syntax errors. ### Bug fixes diff --git a/lib/compiler/passes/generate-js.js b/lib/compiler/passes/generate-js.js index 5e6f11df..9e4f90e8 100644 --- a/lib/compiler/passes/generate-js.js +++ b/lib/compiler/passes/generate-js.js @@ -1393,9 +1393,8 @@ function generateJS(ast, options) { " peg$maxFailPos", " });", " }", - " if (peg$result !== peg$FAILED && peg$currPos === input.length) {", - " return peg$result;", - " } else {", + " var success = (peg$result !== peg$FAILED && peg$currPos === input.length);", + " function fail() {", " if (peg$result !== peg$FAILED && peg$currPos < input.length) {", " peg$fail(peg$endExpectation());", " }", @@ -1408,6 +1407,14 @@ function generateJS(ast, options) { " : peg$computeLocation(peg$maxFailPos, peg$maxFailPos)", " );", " }", + " if (options.soft) {", + " return {result: peg$result, success, fail};", + " }", + " if (success) {", + " return peg$result;", + " } else {", + " fail();", + " }", "}" ); diff --git a/test/api/generated-parser-api.spec.js b/test/api/generated-parser-api.spec.js index ba106793..c479ed2c 100644 --- a/test/api/generated-parser-api.spec.js +++ b/test/api/generated-parser-api.spec.js @@ -14,12 +14,29 @@ describe("generated parser API", () => { expect(parser.parse("a")).to.equal("a"); }); + it("parses input in soft-mode", () => { + const parser = peg.generate("start = 'a'"); + + const result = parser.parse("a", { soft: true }); + expect(result.result).to.equal("a"); + expect(result.success).to.equal(true); + }); + it("throws an exception on syntax error", () => { const parser = peg.generate("start = 'a'"); expect(() => { parser.parse("b"); }).to.throw(); }); + it("gives partial result on syntax error in soft-mode", () => { + const parser = peg.generate("start = 'a'+"); + + const result = parser.parse("aab", { soft: true }); + expect(result.result).to.deep.equal(["a", "a"]); + expect(result.success).to.equal(false); + expect(result.fail).to.throw('Expected "a" or end of input but "b" found.'); + }); + // Regression: https://github.com/peggyjs/peggy/pull/197 it("correctly describe character class in syntax error", () => { const parser = peg.generate("start = [123-5]"); diff --git a/test/cli/fixtures/imports_peggy.js b/test/cli/fixtures/imports_peggy.js index afb132be..49449774 100644 --- a/test/cli/fixtures/imports_peggy.js +++ b/test/cli/fixtures/imports_peggy.js @@ -501,9 +501,8 @@ function peg$parse(input, options) { peg$maxFailPos }); } - if (peg$result !== peg$FAILED && peg$currPos === input.length) { - return peg$result; - } else { + var success = (peg$result !== peg$FAILED && peg$currPos === input.length); + function fail() { if (peg$result !== peg$FAILED && peg$currPos < input.length) { peg$fail(peg$endExpectation()); } @@ -516,6 +515,14 @@ function peg$parse(input, options) { : peg$computeLocation(peg$maxFailPos, peg$maxFailPos) ); } + if (options.soft) { + return {result: peg$result, success, fail}; + } + if (success) { + return peg$result; + } else { + fail(); + } } module.exports = { diff --git a/test/cli/fixtures/lib.js b/test/cli/fixtures/lib.js index 426d21da..bb31d7d1 100644 --- a/test/cli/fixtures/lib.js +++ b/test/cli/fixtures/lib.js @@ -471,9 +471,8 @@ function peg$parse(input, options) { peg$maxFailPos }); } - if (peg$result !== peg$FAILED && peg$currPos === input.length) { - return peg$result; - } else { + var success = (peg$result !== peg$FAILED && peg$currPos === input.length); + function fail() { if (peg$result !== peg$FAILED && peg$currPos < input.length) { peg$fail(peg$endExpectation()); } @@ -486,6 +485,14 @@ function peg$parse(input, options) { : peg$computeLocation(peg$maxFailPos, peg$maxFailPos) ); } + if (options.soft) { + return {result: peg$result, success, fail}; + } + if (success) { + return peg$result; + } else { + fail(); + } } module.exports = {