Skip to content

Commit

Permalink
Remove duplicate expected values in failure message
Browse files Browse the repository at this point in the history
Previosuly, if multiple parsing branches expected the same token, this token would appear multiple times in the list of expected values. Now, this list is uniquified.
  • Loading branch information
drhagen committed Apr 28, 2018
1 parent 6292682 commit d62e824
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 15 deletions.
18 changes: 5 additions & 13 deletions parsita/options.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import re
from typing import Any, Sequence

from .state import Input, Output, SequenceReader, StringReader, Result, Success, Failure, Continue
from .state import Input, Output, SequenceReader, StringReader, Result

# Global mutable state

Expand All @@ -24,27 +24,19 @@ def wrap_literal(literal: Input):


def default_parse_method(self, source: str) -> Result[Output]:
from .parsers import eof
from .parsers import completely_parse_reader

reader = StringReader(source)
result = (self << eof).consume(reader)

if isinstance(result, Continue):
return Success(result.value)
else:
return Failure(result.farthest.expected_error(' or '.join(map(lambda x: x(), result.expected))))
return completely_parse_reader(self, reader)


def basic_parse(self, source: Sequence[Input]) -> Result[Output]:
from .parsers import eof
from .parsers import completely_parse_reader

reader = SequenceReader(source)
result = (self << eof).consume(reader)

if isinstance(result, Continue):
return Success(result.value)
else:
return Failure(result.farthest.expected_error(' or '.join(map(lambda x: x(), result.expected))))
return completely_parse_reader(self, reader)


parse_method = default_parse_method
Expand Down
34 changes: 32 additions & 2 deletions parsita/parsers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from types import MethodType

from . import options
from .state import Input, Output, Convert, Reader, StringReader, Result, Status, Continue, Backtrack
from .state import Input, Output, Convert, Reader, StringReader, Result, Status, Success, Failure, Continue, Backtrack


class Parser(Generic[Input, Output]):
Expand Down Expand Up @@ -152,6 +152,36 @@ def __gt__(self, other) -> 'ConversionParser':
return ConversionParser(self, other)


def completely_parse_reader(parser: Parser[Input, Output], reader: Reader[Input]) -> Result[Output]:
"""Consume reader and return Success only on complete consumption.
This is a helper function for ``parse`` methods, which return ``Success``
when the input is completely consumed and ``Failure`` with an appropriate
message otherwise.
Args:
parser: The parser doing the consuming
reader: The input being consumed
Returns:
A parsing ``Result``
"""
result = (parser << eof).consume(reader)

if isinstance(result, Continue):
return Success(result.value)
else:
used = set()
unique_expected = []
for expected_lambda in result.expected:
expected = expected_lambda()
if expected not in used:
used.add(expected)
unique_expected.append(expected)

return Failure(result.farthest.expected_error(' or '.join(unique_expected)))


class LiteralParser(Generic[Input], Parser[Input, Input]):
def __init__(self, pattern: Sequence[Input]):
super().__init__()
Expand Down Expand Up @@ -677,4 +707,4 @@ def failure(expected: str = ''):
'AlternativeParser', 'SequentialParser', 'DiscardLeftParser', 'DiscardRightParser', 'RepeatedOnceParser',
'rep1', 'RepeatedParser', 'rep', 'RepeatedOnceSeparatedParser', 'rep1sep', 'RepeatedSeparatedParser',
'repsep', 'ConversionParser', 'EndOfSourceParser', 'eof', 'SuccessParser', 'success', 'FailureParser',
'failure']
'failure', 'completely_parse_reader']
9 changes: 9 additions & 0 deletions tests/test_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,15 @@ class TestParsers(GeneralParsers):

self.assertEqual(TestParsers.ab.parse('a'), Success('a'))

def test_multiple_messages_duplicate(self):
class TestParsers(GeneralParsers):
a = lit('a')
ab = a & 'b'
ac = a & 'c'
either = ab | ac

self.assertEqual(TestParsers.either.parse('cc'), Failure('Expected a but found c at index 0'))


class SequentialTestCase(TestCase):
def test_sequential(self):
Expand Down

0 comments on commit d62e824

Please sign in to comment.