Add |expected| and |found| properties to exceptions thrown by parsers

Based on a patch by Marcin Stefaniuk (marcin@stefaniuk.info).
redux
David Majda 12 years ago
parent e9d8dc8eba
commit 46b2eaf3e3

@ -70,7 +70,7 @@ To get parsers source code, call the `toSource` method on the parser.
Using the Parser 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` and `message` properties with more details about the error. 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.
parser.parse("abba"); // returns ["a", "b", "b", "a"] parser.parse("abba"); // returns ["a", "b", "b", "a"]

@ -466,6 +466,8 @@ PEG.compiler.emitter = function(ast) {
' }', ' }',
' ', ' ',
' this.name = "SyntaxError";', ' this.name = "SyntaxError";',
' this.expected = expected;',
' this.found = found;',
' this.message = buildMessage(expected, found);', ' this.message = buildMessage(expected, found);',
' this.offset = offset;', ' this.offset = offset;',
' this.line = line;', ' this.line = line;',

@ -3621,6 +3621,8 @@ PEG.parser = (function(){
} }
this.name = "SyntaxError"; this.name = "SyntaxError";
this.expected = expected;
this.found = found;
this.message = buildMessage(expected, found); this.message = buildMessage(expected, found);
this.offset = offset; this.offset = offset;
this.line = line; this.line = line;

@ -331,55 +331,71 @@ test("indempotence", function() {
strictEqual(parser1.toSource(), parser2.toSource()); strictEqual(parser1.toSource(), parser2.toSource());
}); });
test("error messages", function() { test("error details", function() {
var literalParser = PEG.buildParser('start = "abcd"'); var literalParser = PEG.buildParser('start = "abcd"');
doesNotParseWithMessage( doesNotParseWithDetails(
literalParser, literalParser,
"", "",
["\"abcd\""],
null,
'Expected "abcd" but end of input found.' 'Expected "abcd" but end of input found.'
); );
doesNotParseWithMessage( doesNotParseWithDetails(
literalParser, literalParser,
"efgh", "efgh",
["\"abcd\""],
"e",
'Expected "abcd" but "e" found.' 'Expected "abcd" but "e" found.'
); );
doesNotParseWithMessage( doesNotParseWithDetails(
literalParser, literalParser,
"abcde", "abcde",
[],
"e",
'Expected end of input but "e" found.' 'Expected end of input but "e" found.'
); );
var classParser = PEG.buildParser('start = [a-d]'); var classParser = PEG.buildParser('start = [a-d]');
doesNotParseWithMessage( doesNotParseWithDetails(
classParser, classParser,
"", "",
["[a-d]"],
null,
'Expected [a-d] but end of input found.' 'Expected [a-d] but end of input found.'
); );
var negativeClassParser = PEG.buildParser('start = [^a-d]'); var negativeClassParser = PEG.buildParser('start = [^a-d]');
doesNotParseWithMessage( doesNotParseWithDetails(
negativeClassParser, negativeClassParser,
"", "",
["[^a-d]"],
null,
'Expected [^a-d] but end of input found.' 'Expected [^a-d] but end of input found.'
); );
var anyParser = PEG.buildParser('start = .'); var anyParser = PEG.buildParser('start = .');
doesNotParseWithMessage( doesNotParseWithDetails(
anyParser, anyParser,
"", "",
["any character"],
null,
'Expected any character but end of input found.' 'Expected any character but end of input found.'
); );
var namedRuleWithLiteralParser = PEG.buildParser('start "digit" = [0-9]'); var namedRuleWithLiteralParser = PEG.buildParser('start "digit" = [0-9]');
doesNotParseWithMessage( doesNotParseWithDetails(
namedRuleWithLiteralParser, namedRuleWithLiteralParser,
"a", "a",
["digit"],
"a",
'Expected digit but "a" found.' 'Expected digit but "a" found.'
); );
var namedRuleWithAnyParser = PEG.buildParser('start "whatever" = .'); var namedRuleWithAnyParser = PEG.buildParser('start "whatever" = .');
doesNotParseWithMessage( doesNotParseWithDetails(
namedRuleWithAnyParser, namedRuleWithAnyParser,
"", "",
["whatever"],
null,
'Expected whatever but end of input found.' 'Expected whatever but end of input found.'
); );
@ -387,58 +403,74 @@ test("error messages", function() {
'start "digits" = digit+', 'start "digits" = digit+',
'digit "digit" = [0-9]' 'digit "digit" = [0-9]'
].join("\n")); ].join("\n"));
doesNotParseWithMessage( doesNotParseWithDetails(
namedRuleWithNamedRuleParser, namedRuleWithNamedRuleParser,
"", "",
["digits"],
null,
'Expected digits but end of input found.' 'Expected digits but end of input found.'
); );
var choiceParser1 = PEG.buildParser('start = "a" / "b" / "c"'); var choiceParser1 = PEG.buildParser('start = "a" / "b" / "c"');
doesNotParseWithMessage( doesNotParseWithDetails(
choiceParser1, choiceParser1,
"def", "def",
["\"a\"", "\"b\"", "\"c\""],
"d",
'Expected "a", "b" or "c" but "d" found.' 'Expected "a", "b" or "c" but "d" found.'
); );
var choiceParser2 = PEG.buildParser('start = "a" "b" "c" / "a"'); var choiceParser2 = PEG.buildParser('start = "a" "b" "c" / "a"');
doesNotParseWithMessage( doesNotParseWithDetails(
choiceParser2, choiceParser2,
"abd", "abd",
["\"c\""],
"d",
'Expected "c" but "d" found.' 'Expected "c" but "d" found.'
); );
var simpleNotParser = PEG.buildParser('start = !"a" "b"'); var simpleNotParser = PEG.buildParser('start = !"a" "b"');
doesNotParseWithMessage( doesNotParseWithDetails(
simpleNotParser, simpleNotParser,
"c", "c",
["\"b\""],
"c",
'Expected "b" but "c" found.' 'Expected "b" but "c" found.'
); );
var simpleAndParser = PEG.buildParser('start = &"a" [a-b]'); var simpleAndParser = PEG.buildParser('start = &"a" [a-b]');
doesNotParseWithMessage( doesNotParseWithDetails(
simpleAndParser, simpleAndParser,
"c", "c",
[],
"c",
'Expected end of input but "c" found.' 'Expected end of input but "c" found.'
); );
var emptyParser = PEG.buildParser('start = '); var emptyParser = PEG.buildParser('start = ');
doesNotParseWithMessage( doesNotParseWithDetails(
emptyParser, emptyParser,
"something", "something",
[],
"s",
'Expected end of input but "s" found.' 'Expected end of input but "s" found.'
); );
var duplicateErrorParser = PEG.buildParser('start = "a" / "a"'); var duplicateErrorParser = PEG.buildParser('start = "a" / "a"');
doesNotParseWithMessage( doesNotParseWithDetails(
duplicateErrorParser, duplicateErrorParser,
"", "",
["\"a\""],
null,
'Expected "a" but end of input found.' 'Expected "a" but end of input found.'
); );
var unsortedErrorsParser = PEG.buildParser('start = "b" / "a"'); var unsortedErrorsParser = PEG.buildParser('start = "b" / "a"');
doesNotParseWithMessage( doesNotParseWithDetails(
unsortedErrorsParser, unsortedErrorsParser,
"", "",
["\"a\"", "\"b\""],
null,
'Expected "a" or "b" but end of input found.' 'Expected "a" or "b" but end of input found.'
); );
}); });

@ -19,6 +19,25 @@ doesNotParseWithMessage = function(parser, input, message) {
); );
}; };
doesNotParseWithDetails = function(parser, input, expected, found, message) {
raises(
function() { parser.parse(input); },
function(e) {
var i;
if (!(e instanceof parser.SyntaxError)) { return false; }
if (e.expected.length !== expected.length) { return false; }
for (i = 0; i < e.expected.length; i++) {
if (e.expected[i] !== expected[i]) { return false; }
}
if (e.found !== found) { return false; }
if (e.message !== message) { return false; }
return true;
}
);
};
doesNotParseWithPos = function(parser, input, offset, line, column) { doesNotParseWithPos = function(parser, input, offset, line, column) {
raises( raises(
function() { parser.parse(input); }, function() { parser.parse(input); },

Loading…
Cancel
Save