Skip to content

Commit

Permalink
Error handling: Implement the |error| function
Browse files Browse the repository at this point in the history
The |error| function allows users to report custom match failures inside
actions.

If the |error| function is called, and the reported match failure turns
out to be the cause of a parse error, the error message reported by the
parser will be exactly the one specified in the |error| call.

Implements part of pegjs#198.

Speed impact
------------
Before:     999.83 kB/s
After:      1000.84 kB/s
Difference: 0.10%

Size impact
-----------
Before:     1017212 b
After:      1019968 b
Difference: 0.27%

(Measured by /tools/impact with Node.js v0.6.18 on x86_64 GNU/Linux.)
  • Loading branch information
dmajda authored and andreineculau committed Apr 17, 2014
1 parent 00001fc commit 843a569
Show file tree
Hide file tree
Showing 4 changed files with 121 additions and 9 deletions.
4 changes: 4 additions & 0 deletions README.md
Expand Up @@ -381,6 +381,10 @@ To indicate a match failure, the code inside the action can invoke the
at the current position. This description will be used as part of a message of
the exception thrown if the match failure leads to an parse error.

The code inside an action can also invoke the `error` function. It takes one
parameter — an error message. This message will be used by the exception thrown
if the match failure leads to an parse error.

The code inside the action can access all variables and functions defined in the
initializer at the beginning of the grammar. Curly braces in the action code
must be balanced.
Expand Down
37 changes: 32 additions & 5 deletions lib/compiler/passes/generate-javascript.js
Expand Up @@ -744,6 +744,7 @@ module.exports = function(ast, options) {
' peg$cachedPosDetails = { line: 1, column: 1, seenCR: false },',
' peg$maxFailPos = 0,',
' peg$maxFailExpected = [],',
' peg$maxFailMessage = null,',
' peg$silentFails = 0,', // 0 = report failures, > 0 = silence failures
' peg$userFail = false,',
''
Expand Down Expand Up @@ -808,6 +809,13 @@ module.exports = function(ast, options) {
' peg$userFail = true;',
' }',
'',
' function error(message) {',
' if (peg$silentFails === 0) {',
' peg$error(message, peg$reportedPos);',
' }',
' peg$userFail = true;',
' }',
'',
' function peg$computePosDetails(pos) {',
' function advance(details, startPos, endPos) {',
' var p, ch;',
Expand Down Expand Up @@ -847,11 +855,24 @@ module.exports = function(ast, options) {
' if (pos > peg$maxFailPos) {',
' peg$maxFailPos = pos;',
' peg$maxFailExpected = [];',
' peg$maxFailMessage = null;',
' }',
'',
' peg$maxFailExpected.push(expected);',
' }',
'',
' function peg$error(message, pos) {',
' if (pos < peg$maxFailPos) { return; }',
'',
' if (pos > peg$maxFailPos) {',
' peg$maxFailPos = pos;',
' peg$maxFailExpected = [];',
' peg$maxFailMessage = null;',
' }',
'',
' peg$maxFailMessage = message;',
' }',
'',
' function peg$buildException() {',
' function cleanupExpected(expected) {',
' var i = 1;',
Expand Down Expand Up @@ -933,13 +954,19 @@ module.exports = function(ast, options) {
'',
' var pos = Math.max(peg$currPos, peg$maxFailPos),',
' posDetails = peg$computePosDetails(pos),',
' found = pos < input.length ? input.charAt(pos) : null;',
'',
' cleanupExpected(peg$maxFailExpected);',
' expected = peg$maxFailMessage === null ? peg$maxFailExpected : null,',
' found = pos < input.length ? input.charAt(pos) : null,',
' message = peg$maxFailMessage !== null',
' ? peg$maxFailMessage',
' : buildMessage(expected, found);',
'',
' if (expected !== null) {',
' cleanupExpected(expected);',
' }',
'',
' return new SyntaxError(',
' buildMessage(peg$maxFailExpected, found),',
' peg$maxFailExpected,',
' message,',
' expected,',
' found,',
' pos,',
' posDetails.line,',
Expand Down
35 changes: 31 additions & 4 deletions lib/parser.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

54 changes: 54 additions & 0 deletions spec/generated-parser.spec.js
Expand Up @@ -397,6 +397,60 @@ describe("generated parser", function() {
});
});

describe("|error| function", function() {
it("generates a custom match failure", function() {
var parser = PEG.buildParser(
'start = "a" { error("a"); }',
options
);

expect(parser).toFailToParse("a", {
offset: 0,
line: 1,
column: 1,
expected: null,
found: "a",
message: "a"
});
});

it("generated failures overrides failures generated before", function() {
var parser = PEG.buildParser(
'start = "a" / ("b" { error("b"); })',
options
);

expect(parser).toFailToParse("b", {
message: "b",
expected: null
});
});

it("generated failures override failures generated after", function() {
var parser = PEG.buildParser(
'start = ("a" { error("a"); }) / "b"',
options
);

expect(parser).toFailToParse("a", {
message: "a",
expected: null
});
});

it("the last invocation wins", function() {
var parser = PEG.buildParser(
'start = "a" { error("a1"); error("a2"); }',
options
);

expect(parser).toFailToParse("a", {
message: "a2",
expected: null
});
});
});

it("can use functions defined in the initializer", function() {
var parser = PEG.buildParser([
'{ function f() { return 42; } }',
Expand Down

0 comments on commit 843a569

Please sign in to comment.