Improve location info in syntax errors

Replace |line|, |column|, and |offset| properties of |SyntaxError| with
the |location| property. It contains an object similar to the one
returned by the |location| function available in action code:

  {
    start: { offset: 23, line: 5, column: 6 },
    end:   { offset: 25, line: 5, column: 8 }
  }

For syntax errors produced in the middle of the input, |start| refers to
the first unparsed character and |end| refers to the character behind it
(meaning the span is 1 character). This corresponds to the portion of
the input in the |found| property.

For syntax errors produced the end of the input, both |start| and |end|
refer to a character past the end of the input (meaning the span is 0
characters).

For syntax errors produced by calling |expected| or |error| functions in
action code the location info is the same as the |location| function
would return.
redux
David Majda 9 years ago
parent b1ad2a1f61
commit 065f4e1b75

@ -127,8 +127,8 @@ Using the Parser
Using the generated parser is simple — just call its `parse` method and pass an Using the generated parser is simple — just call its `parse` method and pass an
input string as a parameter. The method will return a parse result (the exact input string as a parameter. The method will return a parse result (the exact
value depends on the grammar used to build the parser) or throw an exception if value depends on the grammar used to build the parser) or throw an exception if
the input is invalid. The exception will contain `offset`, `line`, `column`, the input is invalid. The exception will contain `location`, `expected`, `found`
`expected`, `found` and `message` properties with more details about the error. and `message` properties with more details about the error.
parser.parse("abba"); // returns ["a", "b", "b", "a"] parser.parse("abba"); // returns ["a", "b", "b", "a"]

@ -253,8 +253,8 @@ readStream(inputStream, function(input) {
try { try {
source = PEG.buildParser(input, options); source = PEG.buildParser(input, options);
} catch (e) { } catch (e) {
if (e.line !== undefined && e.column !== undefined) { if (e.location !== undefined) {
abort(e.line + ":" + e.column + ": " + e.message); abort(e.location.start.line + ":" + e.location.start.column + ": " + e.message);
} else { } else {
abort(e.message); abort(e.message);
} }

@ -744,13 +744,11 @@ function generateJavascript(ast, options) {
' child.prototype = new ctor();', ' child.prototype = new ctor();',
' }', ' }',
'', '',
' function peg$SyntaxError(message, expected, found, offset, line, column) {', ' function peg$SyntaxError(message, expected, found, location) {',
' this.message = message;', ' this.message = message;',
' this.expected = expected;', ' this.expected = expected;',
' this.found = found;', ' this.found = found;',
' this.offset = offset;', ' this.location = location;',
' this.line = line;',
' this.column = column;',
'', '',
' this.name = "SyntaxError";', ' this.name = "SyntaxError";',
' }', ' }',
@ -929,33 +927,25 @@ function generateJavascript(ast, options) {
' }', ' }',
'', '',
' function location() {', ' function location() {',
' var savedPosDetails = peg$computePosDetails(peg$savedPos),', ' return peg$computeLocation(peg$savedPos, peg$currPos);',
' currPosDetails = peg$computePosDetails(peg$currPos);',
'',
' return {',
' start: {',
' offset: peg$savedPos,',
' line: savedPosDetails.line,',
' column: savedPosDetails.column',
' },',
' end: {',
' offset: peg$currPos,',
' line: currPosDetails.line,',
' column: currPosDetails.column',
' }',
' };',
' }', ' }',
'', '',
' function expected(description) {', ' function expected(description) {',
' throw peg$buildException(', ' throw peg$buildException(',
' null,', ' null,',
' [{ type: "other", description: description }],', ' [{ type: "other", description: description }],',
' peg$savedPos', ' input.substring(peg$savedPos, peg$currPos),',
' peg$computeLocation(peg$savedPos, peg$currPos)',
' );', ' );',
' }', ' }',
'', '',
' function error(message) {', ' function error(message) {',
' throw peg$buildException(message, null, peg$savedPos);', ' throw peg$buildException(',
' message,',
' null,',
' input.substring(peg$savedPos, peg$currPos),',
' peg$computeLocation(peg$savedPos, peg$currPos)',
' );',
' }', ' }',
'', '',
' function peg$computePosDetails(pos) {', ' function peg$computePosDetails(pos) {',
@ -995,6 +985,24 @@ function generateJavascript(ast, options) {
' };', ' };',
' }', ' }',
'', '',
' function peg$computeLocation(startPos, endPos) {',
' var startPosDetails = peg$computePosDetails(startPos),',
' endPosDetails = peg$computePosDetails(endPos);',
'',
' return {',
' start: {',
' offset: startPos,',
' line: startPosDetails.line,',
' column: startPosDetails.column',
' },',
' end: {',
' offset: endPos,',
' line: endPosDetails.line,',
' column: endPosDetails.column',
' }',
' };',
' }',
'',
' function peg$fail(expected) {', ' function peg$fail(expected) {',
' if (peg$currPos < peg$maxFailPos) { return; }', ' if (peg$currPos < peg$maxFailPos) { return; }',
'', '',
@ -1006,7 +1014,7 @@ function generateJavascript(ast, options) {
' peg$maxFailExpected.push(expected);', ' peg$maxFailExpected.push(expected);',
' }', ' }',
'', '',
' function peg$buildException(message, expected, pos) {', ' function peg$buildException(message, expected, found, location) {',
' function cleanupExpected(expected) {', ' function cleanupExpected(expected) {',
' var i = 1;', ' var i = 1;',
'', '',
@ -1080,9 +1088,6 @@ function generateJavascript(ast, options) {
' return "Expected " + expectedDesc + " but " + foundDesc + " found.";', ' return "Expected " + expectedDesc + " but " + foundDesc + " found.";',
' }', ' }',
'', '',
' var posDetails = peg$computePosDetails(pos),',
' found = pos < input.length ? input.charAt(pos) : null;',
'',
' if (expected !== null) {', ' if (expected !== null) {',
' cleanupExpected(expected);', ' cleanupExpected(expected);',
' }', ' }',
@ -1091,9 +1096,7 @@ function generateJavascript(ast, options) {
' message !== null ? message : buildMessage(expected, found),', ' message !== null ? message : buildMessage(expected, found),',
' expected,', ' expected,',
' found,', ' found,',
' pos,', ' location',
' posDetails.line,',
' posDetails.column',
' );', ' );',
' }', ' }',
'' ''
@ -1144,7 +1147,14 @@ function generateJavascript(ast, options) {
' peg$fail({ type: "end", description: "end of input" });', ' peg$fail({ type: "end", description: "end of input" });',
' }', ' }',
'', '',
' throw peg$buildException(null, peg$maxFailExpected, peg$maxFailPos);', ' throw peg$buildException(',
' null,',
' peg$maxFailExpected,',
' peg$maxFailPos < input.length ? input.charAt(peg$maxFailPos) : null,',
' peg$maxFailPos < input.length',
' ? peg$computeLocation(peg$maxFailPos, peg$maxFailPos + 1)',
' : peg$computeLocation(peg$maxFailPos, peg$maxFailPos)',
' );',
' }', ' }',
' }', ' }',
'', '',

@ -11,13 +11,11 @@ module.exports = (function() {
child.prototype = new ctor(); child.prototype = new ctor();
} }
function peg$SyntaxError(message, expected, found, offset, line, column) { function peg$SyntaxError(message, expected, found, location) {
this.message = message; this.message = message;
this.expected = expected; this.expected = expected;
this.found = found; this.found = found;
this.offset = offset; this.location = location;
this.line = line;
this.column = column;
this.name = "SyntaxError"; this.name = "SyntaxError";
} }
@ -357,33 +355,25 @@ module.exports = (function() {
} }
function location() { function location() {
var savedPosDetails = peg$computePosDetails(peg$savedPos), return peg$computeLocation(peg$savedPos, peg$currPos);
currPosDetails = peg$computePosDetails(peg$currPos);
return {
start: {
offset: peg$savedPos,
line: savedPosDetails.line,
column: savedPosDetails.column
},
end: {
offset: peg$currPos,
line: currPosDetails.line,
column: currPosDetails.column
}
};
} }
function expected(description) { function expected(description) {
throw peg$buildException( throw peg$buildException(
null, null,
[{ type: "other", description: description }], [{ type: "other", description: description }],
peg$savedPos input.substring(peg$savedPos, peg$currPos),
peg$computeLocation(peg$savedPos, peg$currPos)
); );
} }
function error(message) { function error(message) {
throw peg$buildException(message, null, peg$savedPos); throw peg$buildException(
message,
null,
input.substring(peg$savedPos, peg$currPos),
peg$computeLocation(peg$savedPos, peg$currPos)
);
} }
function peg$computePosDetails(pos) { function peg$computePosDetails(pos) {
@ -423,6 +413,24 @@ module.exports = (function() {
}; };
} }
function peg$computeLocation(startPos, endPos) {
var startPosDetails = peg$computePosDetails(startPos),
endPosDetails = peg$computePosDetails(endPos);
return {
start: {
offset: startPos,
line: startPosDetails.line,
column: startPosDetails.column
},
end: {
offset: endPos,
line: endPosDetails.line,
column: endPosDetails.column
}
};
}
function peg$fail(expected) { function peg$fail(expected) {
if (peg$currPos < peg$maxFailPos) { return; } if (peg$currPos < peg$maxFailPos) { return; }
@ -434,7 +442,7 @@ module.exports = (function() {
peg$maxFailExpected.push(expected); peg$maxFailExpected.push(expected);
} }
function peg$buildException(message, expected, pos) { function peg$buildException(message, expected, found, location) {
function cleanupExpected(expected) { function cleanupExpected(expected) {
var i = 1; var i = 1;
@ -493,9 +501,6 @@ module.exports = (function() {
return "Expected " + expectedDesc + " but " + foundDesc + " found."; return "Expected " + expectedDesc + " but " + foundDesc + " found.";
} }
var posDetails = peg$computePosDetails(pos),
found = pos < input.length ? input.charAt(pos) : null;
if (expected !== null) { if (expected !== null) {
cleanupExpected(expected); cleanupExpected(expected);
} }
@ -504,9 +509,7 @@ module.exports = (function() {
message !== null ? message : buildMessage(expected, found), message !== null ? message : buildMessage(expected, found),
expected, expected,
found, found,
pos, location
posDetails.line,
posDetails.column
); );
} }
@ -4903,7 +4906,14 @@ module.exports = (function() {
peg$fail({ type: "end", description: "end of input" }); peg$fail({ type: "end", description: "end of input" });
} }
throw peg$buildException(null, peg$maxFailExpected, peg$maxFailPos); throw peg$buildException(
null,
peg$maxFailExpected,
peg$maxFailPos < input.length ? input.charAt(peg$maxFailPos) : null,
peg$maxFailPos < input.length
? peg$computeLocation(peg$maxFailPos, peg$maxFailPos + 1)
: peg$computeLocation(peg$maxFailPos, peg$maxFailPos)
);
} }
} }

@ -1245,9 +1245,10 @@ describe("generated parser behavior", function() {
message: 'Expected a but "a" found.', message: 'Expected a but "a" found.',
expected: [{ type: "other", description: "a" }], expected: [{ type: "other", description: "a" }],
found: "a", found: "a",
offset: 0, location: {
line: 1, start: { offset: 0, line: 1, column: 1 },
column: 1 end: { offset: 1, line: 1, column: 2 }
}
}); });
}); });
@ -1261,9 +1262,10 @@ describe("generated parser behavior", function() {
message: "a", message: "a",
expected: null, expected: null,
found: "a", found: "a",
offset: 0, location: {
line: 1, start: { offset: 0, line: 1, column: 1 },
column: 1 end: { offset: 1, line: 1, column: 2 }
}
}); });
}); });
}); });
@ -1313,7 +1315,6 @@ describe("generated parser behavior", function() {
var parser = PEG.buildParser('start = "a" "b" / "a" "c" "d"', options); var parser = PEG.buildParser('start = "a" "b" / "a" "c" "d"', options);
expect(parser).toFailToParse("ace", { expect(parser).toFailToParse("ace", {
offset: 2,
expected: [{ type: "literal", value: "d", description: '"d"' }] expected: [{ type: "literal", value: "d", description: '"d"' }]
}); });
}); });
@ -1433,16 +1434,37 @@ describe("generated parser behavior", function() {
}); });
describe("position reporting", function() { describe("position reporting", function() {
it("reports position correctly with invalid input", function() { it("reports position correctly at the end of input", function() {
var parser = PEG.buildParser('start = "a"', options); var parser = PEG.buildParser('start = "a"', options);
expect(parser).toFailToParse("b", { offset: 0, line: 1, column: 1 }); expect(parser).toFailToParse("", {
location: {
start: { offset: 0, line: 1, column: 1 },
end: { offset: 0, line: 1, column: 1 }
}
});
});
it("reports position correctly in the middle of input", function() {
var parser = PEG.buildParser('start = "a"', options);
expect(parser).toFailToParse("b", {
location: {
start: { offset: 0, line: 1, column: 1 },
end: { offset: 1, line: 1, column: 2 }
}
});
}); });
it("reports position correctly with trailing input", function() { it("reports position correctly with trailing input", function() {
var parser = PEG.buildParser('start = "a"', options); var parser = PEG.buildParser('start = "a"', options);
expect(parser).toFailToParse("aa", { offset: 1, line: 1, column: 2}); expect(parser).toFailToParse("aa", {
location: {
start: { offset: 1, line: 1, column: 2 },
end: { offset: 2, line: 1, column: 3 }
}
});
}); });
it("reports position correctly in complex cases", function() { it("reports position correctly in complex cases", function() {
@ -1454,38 +1476,44 @@ describe("generated parser behavior", function() {
].join("\n"), options); ].join("\n"), options);
expect(parser).toFailToParse("1\n2\n\n3\n\n\n4 5 x", { expect(parser).toFailToParse("1\n2\n\n3\n\n\n4 5 x", {
offset: 13, location: {
line: 7, start: { offset: 13, line: 7, column: 5 },
column: 5 end: { offset: 14, line: 7, column: 6 }
}
}); });
/* Non-Unix newlines */ /* Non-Unix newlines */
expect(parser).toFailToParse("1\rx", { // Old Mac expect(parser).toFailToParse("1\rx", { // Old Mac
offset: 2, location: {
line: 2, start: { offset: 2, line: 2, column: 1 },
column: 1 end: { offset: 3, line: 2, column: 2 }
}
}); });
expect(parser).toFailToParse("1\r\nx", { // Windows expect(parser).toFailToParse("1\r\nx", { // Windows
offset: 3, location: {
line: 2, start: { offset: 3, line: 2, column: 1 },
column: 1 end: { offset: 4, line: 2, column: 2 }
}
}); });
expect(parser).toFailToParse("1\n\rx", { // mismatched expect(parser).toFailToParse("1\n\rx", { // mismatched
offset: 3, location: {
line: 3, start: { offset: 3, line: 3, column: 1 },
column: 1 end: { offset: 4, line: 3, column: 2 }
}
}); });
/* Strange newlines */ /* Strange newlines */
expect(parser).toFailToParse("1\u2028x", { // line separator expect(parser).toFailToParse("1\u2028x", { // line separator
offset: 2, location: {
line: 2, start: { offset: 2, line: 2, column: 1 },
column: 1 end: { offset: 3, line: 2, column: 2 }
}
}); });
expect(parser).toFailToParse("1\u2029x", { // paragraph separator expect(parser).toFailToParse("1\u2029x", { // paragraph separator
offset: 2, location: {
line: 2, start: { offset: 2, line: 2, column: 1 },
column: 1 end: { offset: 3, line: 2, column: 2 }
}
}); });
}); });
}); });

Loading…
Cancel
Save