Skip to content

Commit

Permalink
Remove context-specific parse (#78)
Browse files Browse the repository at this point in the history
  • Loading branch information
drhagen authored Jun 11, 2023
1 parent dba1b6e commit 82bb315
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 107 deletions.
4 changes: 0 additions & 4 deletions src/parsita/metaclasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,9 @@ class GeneralParsersMeta(type):
def __prepare__(mcs, name, bases, **_): # noqa: N804
old_options = {
"handle_literal": options.handle_literal,
"parse_method": options.parse_method,
}

options.handle_literal = options.wrap_literal
options.parse_method = options.basic_parse

return ParsersDict(old_options)

Expand Down Expand Up @@ -120,7 +118,6 @@ def __prepare__(mcs, name, bases, whitespace: str = options.default_whitespace):
old_options = {
"whitespace": options.whitespace,
"handle_literal": options.handle_literal,
"parse_method": options.parse_method,
}

# Store whitespace in global location so regex parsers can see it
Expand All @@ -133,7 +130,6 @@ def __prepare__(mcs, name, bases, whitespace: str = options.default_whitespace):
options.whitespace = RegexParser(whitespace)

options.handle_literal = options.default_handle_literal
options.parse_method = options.default_parse_method

return ParsersDict(old_options)

Expand Down
24 changes: 1 addition & 23 deletions src/parsita/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,11 @@
"default_handle_literal",
"wrap_literal",
"handle_literal",
"default_parse_method",
"basic_parse",
"parse_method",
]
import re
from typing import Any, Sequence

from .state import Input, Output, Result, SequenceReader, StringReader
from .state import Input

# Global mutable state

Expand All @@ -33,22 +30,3 @@ def wrap_literal(literal: Sequence[Input]):


handle_literal = default_handle_literal


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

reader = StringReader(source)

return completely_parse_reader(self, reader)


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

reader = SequenceReader(source)

return completely_parse_reader(self, reader)


parse_method = default_parse_method
2 changes: 1 addition & 1 deletion src/parsita/parsers/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from ._alternative import FirstAlternativeParser, LongestAlternativeParser, first, longest
from ._any import AnyParser, any1
from ._base import Parser, completely_parse_reader
from ._base import Parser
from ._conversion import ConversionParser, TransformationParser
from ._debug import DebugParser, debug
from ._end_of_source import EndOfSourceParser, eof
Expand Down
92 changes: 44 additions & 48 deletions src/parsita/parsers/_base.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
from __future__ import annotations

__all__ = ["Parser", "completely_parse_reader"]
__all__ = ["Parser"]

from types import MethodType
from typing import Any, Generic, List, Optional, Sequence
from typing import Any, Generic, List, Optional, Sequence, Union

from .. import options
from ..state import Continue, Failure, Input, Output, ParseError, Reader, Result, State, Success
from ..state import (
Continue,
Failure,
Input,
Output,
ParseError,
Reader,
Result,
SequenceReader,
State,
StringReader,
Success,
)

# Singleton indicating that no result is yet in the memo
missing = object()
Expand Down Expand Up @@ -46,9 +57,6 @@ class Parser(Generic[Input, Output]):
name.
"""

def __init__(self):
self.parse = MethodType(options.parse_method, self)

def cached_consume(self, state: State[Input], reader: Reader[Input]) -> Optional[Continue[Input, Output]]:
"""Match this parser at the given location.
Expand Down Expand Up @@ -103,15 +111,9 @@ def consume(self, state: State[Input], reader: Reader[Input]) -> Optional[Contin
"""
raise NotImplementedError()

def parse(self, source: Sequence[Input]) -> Result[Output]:
def parse(self, source: Union[Sequence[Input], Reader]) -> Result[Output]:
"""Abstract method for completely parsing a source.
While ``parse`` is a method on every parser for convenience, it
is really a function of the context. It is the duty of the context
to set the correct ``Reader`` to use and to handle whitespace
not handled by the parsers themselves. This method is pulled from the
context when the parser is initialized.
Args:
source: What will be parsed.
Expand All @@ -122,8 +124,35 @@ def parse(self, source: Sequence[Input]) -> Result[Output]:
``Failure``. If the parser succeeded but the source was not
completely consumed, a ``Failure`` with a message indicating this
is returned.
If a ``Reader`` is passed in, it is used directly. Otherwise, the source
is converted to an appropriate ``Reader``. If the source is ``str`, a
``StringReader`` is used. Otherwise, a ``SequenceReader`` is used.
"""
raise NotImplementedError()
from ._end_of_source import eof

if isinstance(source, Reader):
reader = source
elif isinstance(source, str):
reader = StringReader(source, 0)
else:
reader = SequenceReader(source)

state: State[Input] = State()

status = (self << eof).cached_consume(state, reader)

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

return Failure(ParseError(state.farthest, unique_expected))

name: Optional[str] = None

Expand Down Expand Up @@ -209,36 +238,3 @@ def __ge__(self, other) -> Parser:
from ._conversion import TransformationParser

return TransformationParser(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 Returns ``Result`` containing either the successfully parsed value or
an error from the farthest parsed point in the input.
"""
from ._end_of_source import eof

state: State[Input] = State()
status = (parser << eof).cached_consume(state, reader)

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

return Failure(ParseError(state.farthest, unique_expected))
Loading

0 comments on commit 82bb315

Please sign in to comment.