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
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
the input is invalid. The exception will contain `offset`, `line`, `column`,
`expected`, `found` and `message` properties with more details about the error.
the input is invalid. The exception will contain `location`, `expected`, `found`
and `message` properties with more details about the error.
parser.parse("abba"); // returns ["a", "b", "b", "a"]

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

@ -744,13 +744,11 @@ function generateJavascript(ast, options) {
' child.prototype = new ctor();',
' }',
'',
' function peg$SyntaxError(message, expected, found, offset, line, column) {',
' function peg$SyntaxError(message, expected, found, location) {',
' this.message = message;',
' this.expected = expected;',
' this.found = found;',
' this.offset = offset;',
' this.line = line;',
' this.column = column;',
' this.location = location;',
'',
' this.name = "SyntaxError";',
' }',
@ -929,33 +927,25 @@ function generateJavascript(ast, options) {
' }',
'',
' function location() {',
' var savedPosDetails = peg$computePosDetails(peg$savedPos),',
' 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',
' }',
' };',
' return peg$computeLocation(peg$savedPos, peg$currPos);',
' }',
'',
' function expected(description) {',
' throw peg$buildException(',
' null,',
' [{ type: "other", description: description }],',
' peg$savedPos',
' input.substring(peg$savedPos, peg$currPos),',
' peg$computeLocation(peg$savedPos, peg$currPos)',
' );',
' }',
'',
' 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) {',
@ -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) {',
' if (peg$currPos < peg$maxFailPos) { return; }',
'',
@ -1006,7 +1014,7 @@ function generateJavascript(ast, options) {
' peg$maxFailExpected.push(expected);',
' }',
'',
' function peg$buildException(message, expected, pos) {',
' function peg$buildException(message, expected, found, location) {',
' function cleanupExpected(expected) {',
' var i = 1;',
'',
@ -1080,9 +1088,6 @@ function generateJavascript(ast, options) {
' return "Expected " + expectedDesc + " but " + foundDesc + " found.";',
' }',
'',
' var posDetails = peg$computePosDetails(pos),',
' found = pos < input.length ? input.charAt(pos) : null;',
'',
' if (expected !== null) {',
' cleanupExpected(expected);',
' }',
@ -1091,9 +1096,7 @@ function generateJavascript(ast, options) {
' message !== null ? message : buildMessage(expected, found),',
' expected,',
' found,',
' pos,',
' posDetails.line,',
' posDetails.column',
' location',
' );',
' }',
''
@ -1144,7 +1147,14 @@ function generateJavascript(ast, options) {
' 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();
}
function peg$SyntaxError(message, expected, found, offset, line, column) {
function peg$SyntaxError(message, expected, found, location) {
this.message = message;
this.expected = expected;
this.found = found;
this.offset = offset;
this.line = line;
this.column = column;
this.location = location;
this.name = "SyntaxError";
}
@ -357,33 +355,25 @@ module.exports = (function() {
}
function location() {
var savedPosDetails = peg$computePosDetails(peg$savedPos),
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
}
};
return peg$computeLocation(peg$savedPos, peg$currPos);
}
function expected(description) {
throw peg$buildException(
null,
[{ type: "other", description: description }],
peg$savedPos
input.substring(peg$savedPos, peg$currPos),
peg$computeLocation(peg$savedPos, peg$currPos)
);
}
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) {
@ -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) {
if (peg$currPos < peg$maxFailPos) { return; }
@ -434,7 +442,7 @@ module.exports = (function() {
peg$maxFailExpected.push(expected);
}
function peg$buildException(message, expected, pos) {
function peg$buildException(message, expected, found, location) {
function cleanupExpected(expected) {
var i = 1;
@ -493,9 +501,6 @@ module.exports = (function() {
return "Expected " + expectedDesc + " but " + foundDesc + " found.";
}
var posDetails = peg$computePosDetails(pos),
found = pos < input.length ? input.charAt(pos) : null;
if (expected !== null) {
cleanupExpected(expected);
}
@ -504,9 +509,7 @@ module.exports = (function() {
message !== null ? message : buildMessage(expected, found),
expected,
found,
pos,
posDetails.line,
posDetails.column
location
);
}
@ -4903,7 +4906,14 @@ module.exports = (function() {
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.',
expected: [{ type: "other", description: "a" }],
found: "a",
offset: 0,
line: 1,
column: 1
location: {
start: { offset: 0, line: 1, column: 1 },
end: { offset: 1, line: 1, column: 2 }
}
});
});
@ -1261,9 +1262,10 @@ describe("generated parser behavior", function() {
message: "a",
expected: null,
found: "a",
offset: 0,
line: 1,
column: 1
location: {
start: { offset: 0, line: 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);
expect(parser).toFailToParse("ace", {
offset: 2,
expected: [{ type: "literal", value: "d", description: '"d"' }]
});
});
@ -1433,16 +1434,37 @@ describe("generated parser behavior", 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);
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() {
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() {
@ -1454,38 +1476,44 @@ describe("generated parser behavior", function() {
].join("\n"), options);
expect(parser).toFailToParse("1\n2\n\n3\n\n\n4 5 x", {
offset: 13,
line: 7,
column: 5
location: {
start: { offset: 13, line: 7, column: 5 },
end: { offset: 14, line: 7, column: 6 }
}
});
/* Non-Unix newlines */
expect(parser).toFailToParse("1\rx", { // Old Mac
offset: 2,
line: 2,
column: 1
location: {
start: { offset: 2, line: 2, column: 1 },
end: { offset: 3, line: 2, column: 2 }
}
});
expect(parser).toFailToParse("1\r\nx", { // Windows
offset: 3,
line: 2,
column: 1
location: {
start: { offset: 3, line: 2, column: 1 },
end: { offset: 4, line: 2, column: 2 }
}
});
expect(parser).toFailToParse("1\n\rx", { // mismatched
offset: 3,
line: 3,
column: 1
location: {
start: { offset: 3, line: 3, column: 1 },
end: { offset: 4, line: 3, column: 2 }
}
});
/* Strange newlines */
expect(parser).toFailToParse("1\u2028x", { // line separator
offset: 2,
line: 2,
column: 1
location: {
start: { offset: 2, line: 2, column: 1 },
end: { offset: 3, line: 2, column: 2 }
}
});
expect(parser).toFailToParse("1\u2029x", { // paragraph separator
offset: 2,
line: 2,
column: 1
location: {
start: { offset: 2, line: 2, column: 1 },
end: { offset: 3, line: 2, column: 2 }
}
});
});
});

Loading…
Cancel
Save