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.)
redux
David Majda 11 years ago
parent dd74ea4144
commit 5460a881af

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

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

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

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

Loading…
Cancel
Save