From 46b2eaf3e3f8b77659e951e9cdb19a521aeb79b9 Mon Sep 17 00:00:00 2001 From: David Majda Date: Sun, 12 Feb 2012 12:20:15 +0100 Subject: [PATCH] Add |expected| and |found| properties to exceptions thrown by parsers Based on a patch by Marcin Stefaniuk (marcin@stefaniuk.info). --- README.md | 2 +- src/emitter.js | 2 ++ src/parser.js | 2 ++ test/compiler-test.js | 66 ++++++++++++++++++++++++++++++++----------- test/helpers.js | 19 +++++++++++++ 5 files changed, 73 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 916963b..bec292a 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ To get parser’s source code, call the `toSource` method on 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"] diff --git a/src/emitter.js b/src/emitter.js index 5e20589..12541a5 100644 --- a/src/emitter.js +++ b/src/emitter.js @@ -466,6 +466,8 @@ PEG.compiler.emitter = function(ast) { ' }', ' ', ' this.name = "SyntaxError";', + ' this.expected = expected;', + ' this.found = found;', ' this.message = buildMessage(expected, found);', ' this.offset = offset;', ' this.line = line;', diff --git a/src/parser.js b/src/parser.js index bc562cc..8e9bc81 100644 --- a/src/parser.js +++ b/src/parser.js @@ -3621,6 +3621,8 @@ PEG.parser = (function(){ } this.name = "SyntaxError"; + this.expected = expected; + this.found = found; this.message = buildMessage(expected, found); this.offset = offset; this.line = line; diff --git a/test/compiler-test.js b/test/compiler-test.js index e9e5a41..9e9ca86 100644 --- a/test/compiler-test.js +++ b/test/compiler-test.js @@ -331,55 +331,71 @@ test("indempotence", function() { strictEqual(parser1.toSource(), parser2.toSource()); }); -test("error messages", function() { +test("error details", function() { var literalParser = PEG.buildParser('start = "abcd"'); - doesNotParseWithMessage( + doesNotParseWithDetails( literalParser, "", + ["\"abcd\""], + null, 'Expected "abcd" but end of input found.' ); - doesNotParseWithMessage( + doesNotParseWithDetails( literalParser, "efgh", + ["\"abcd\""], + "e", 'Expected "abcd" but "e" found.' ); - doesNotParseWithMessage( + doesNotParseWithDetails( literalParser, "abcde", + [], + "e", 'Expected end of input but "e" found.' ); var classParser = PEG.buildParser('start = [a-d]'); - doesNotParseWithMessage( + doesNotParseWithDetails( classParser, "", + ["[a-d]"], + null, 'Expected [a-d] but end of input found.' ); var negativeClassParser = PEG.buildParser('start = [^a-d]'); - doesNotParseWithMessage( + doesNotParseWithDetails( negativeClassParser, "", + ["[^a-d]"], + null, 'Expected [^a-d] but end of input found.' ); var anyParser = PEG.buildParser('start = .'); - doesNotParseWithMessage( + doesNotParseWithDetails( anyParser, "", + ["any character"], + null, 'Expected any character but end of input found.' ); var namedRuleWithLiteralParser = PEG.buildParser('start "digit" = [0-9]'); - doesNotParseWithMessage( + doesNotParseWithDetails( namedRuleWithLiteralParser, "a", + ["digit"], + "a", 'Expected digit but "a" found.' ); var namedRuleWithAnyParser = PEG.buildParser('start "whatever" = .'); - doesNotParseWithMessage( + doesNotParseWithDetails( namedRuleWithAnyParser, "", + ["whatever"], + null, 'Expected whatever but end of input found.' ); @@ -387,58 +403,74 @@ test("error messages", function() { 'start "digits" = digit+', 'digit "digit" = [0-9]' ].join("\n")); - doesNotParseWithMessage( + doesNotParseWithDetails( namedRuleWithNamedRuleParser, "", + ["digits"], + null, 'Expected digits but end of input found.' ); var choiceParser1 = PEG.buildParser('start = "a" / "b" / "c"'); - doesNotParseWithMessage( + doesNotParseWithDetails( choiceParser1, "def", + ["\"a\"", "\"b\"", "\"c\""], + "d", 'Expected "a", "b" or "c" but "d" found.' ); var choiceParser2 = PEG.buildParser('start = "a" "b" "c" / "a"'); - doesNotParseWithMessage( + doesNotParseWithDetails( choiceParser2, "abd", + ["\"c\""], + "d", 'Expected "c" but "d" found.' ); var simpleNotParser = PEG.buildParser('start = !"a" "b"'); - doesNotParseWithMessage( + doesNotParseWithDetails( simpleNotParser, "c", + ["\"b\""], + "c", 'Expected "b" but "c" found.' ); var simpleAndParser = PEG.buildParser('start = &"a" [a-b]'); - doesNotParseWithMessage( + doesNotParseWithDetails( simpleAndParser, "c", + [], + "c", 'Expected end of input but "c" found.' ); var emptyParser = PEG.buildParser('start = '); - doesNotParseWithMessage( + doesNotParseWithDetails( emptyParser, "something", + [], + "s", 'Expected end of input but "s" found.' ); var duplicateErrorParser = PEG.buildParser('start = "a" / "a"'); - doesNotParseWithMessage( + doesNotParseWithDetails( duplicateErrorParser, "", + ["\"a\""], + null, 'Expected "a" but end of input found.' ); var unsortedErrorsParser = PEG.buildParser('start = "b" / "a"'); - doesNotParseWithMessage( + doesNotParseWithDetails( unsortedErrorsParser, "", + ["\"a\"", "\"b\""], + null, 'Expected "a" or "b" but end of input found.' ); }); diff --git a/test/helpers.js b/test/helpers.js index 9b0a734..6674004 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -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) { raises( function() { parser.parse(input); },