Error handling: Implement the |error| function

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 #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.)
This commit is contained in:
David Majda 2013-11-30 17:26:06 +01:00
parent dd74ea4144
commit 5460a881af
4 changed files with 120 additions and 8 deletions

View file

@ -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.

View file

@ -745,6 +745,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,',
''
@ -809,6 +810,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;',
@ -848,11 +856,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;',
@ -934,13 +955,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;',
' expected = peg$maxFailMessage === null ? peg$maxFailExpected : null,',
' found = pos < input.length ? input.charAt(pos) : null,',
' message = peg$maxFailMessage !== null',
' ? peg$maxFailMessage',
' : buildMessage(expected, found);',
'',
' cleanupExpected(peg$maxFailExpected);',
' if (expected !== null) {',
' cleanupExpected(expected);',
' }',
'',
' return new SyntaxError(',
' buildMessage(peg$maxFailExpected, found),',
' peg$maxFailExpected,',
' message,',
' expected,',
' found,',
' pos,',
' posDetails.line,',

View file

@ -340,6 +340,7 @@ module.exports = (function() {
peg$cachedPosDetails = { line: 1, column: 1, seenCR: false },
peg$maxFailPos = 0,
peg$maxFailExpected = [],
peg$maxFailMessage = null,
peg$silentFails = 0,
peg$userFail = false,
@ -379,6 +380,13 @@ module.exports = (function() {
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;
@ -418,11 +426,24 @@ module.exports = (function() {
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;
@ -494,13 +515,19 @@ module.exports = (function() {
var pos = Math.max(peg$currPos, peg$maxFailPos),
posDetails = peg$computePosDetails(pos),
found = pos < input.length ? input.charAt(pos) : null;
expected = peg$maxFailMessage === null ? peg$maxFailExpected : null,
found = pos < input.length ? input.charAt(pos) : null,
message = peg$maxFailMessage !== null
? peg$maxFailMessage
: buildMessage(expected, found);
cleanupExpected(peg$maxFailExpected);
if (expected !== null) {
cleanupExpected(expected);
}
return new SyntaxError(
buildMessage(peg$maxFailExpected, found),
peg$maxFailExpected,
message,
expected,
found,
pos,
posDetails.line,

View file

@ -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; } }',