Skip to content

Commit

Permalink
Run mypy on src (#115)
Browse files Browse the repository at this point in the history
Many fixes to the types
  • Loading branch information
drhagen authored Nov 21, 2024
1 parent 6bb1ce8 commit 00fffdc
Show file tree
Hide file tree
Showing 35 changed files with 709 additions and 330 deletions.
4 changes: 2 additions & 2 deletions docs/utility_functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ assert BooleanParsers.boolean.parse('false') == Success(False)

## `splat(function)`: convert a function of many arguments to take only one list argument

The function `splat(function: Callable[Tuple[*B], A]) -> Callable[Tuple[Tuple[*B]], A]` has a complicated type signature, but does a simple thing. It takes a single function that takes multiple arguments and converts it to a function that takes only one argument, which is a list of all original arguments. It is particularly useful for passing a list of results from a sequential parser `&` to a function that takes each element as an separate argument. By applying `splat` to the function, it now takes the single list that is returned by the sequential parser.
The function `splat(function: Callable[tuple[*B], A]) -> Callable[tuple[tuple[*B]], A]` has a complicated type signature, but does a simple thing. It takes a single function that takes multiple arguments and converts it to a function that takes only one argument, which is a list of all original arguments. It is particularly useful for passing a list of results from a sequential parser `&` to a function that takes each element as an separate argument. By applying `splat` to the function, it now takes the single list that is returned by the sequential parser.

```python
from collections import namedtuple
Expand All @@ -44,7 +44,7 @@ assert UrlParsers.url.parse('https://drhagen.com:443/blog/') == \

## `unsplat(function)`: convert a function of one list argument to take many arguments

The function `unsplat(function: Callable[Tuple[Tuple[*B]], A]) -> Callable[Tuple[*B], A]` does the opposite of `splat`. It takes a single function that takes a single argument that is a list and converts it to a function that takes multiple arguments, each of which was an element of the original list. It is not very useful for writing parsers because the conversion parser always calls its converter function with a single argument, but is included here to complement `splat`.
The function `unsplat(function: Callable[tuple[tuple[*B]], A]) -> Callable[tuple[*B], A]` does the opposite of `splat`. It takes a single function that takes a single argument that is a list and converts it to a function that takes multiple arguments, each of which was an element of the original list. It is not very useful for writing parsers because the conversion parser always calls its converter function with a single argument, but is included here to complement `splat`.

```python
from parsita.util import splat, unsplat
Expand Down
44 changes: 24 additions & 20 deletions examples/expressions.py
Original file line number Diff line number Diff line change
@@ -1,35 +1,39 @@
from typing import Literal, Sequence

from parsita import ParserContext, lit, opt, reg, rep


def make_term(args: tuple[float, Sequence[tuple[Literal["*", "/"], float]]]) -> float:
factor1, factors = args
result = factor1
for op, factor in factors:
if op == "*":
result = result * factor
else:
result = result / factor
return result


def make_expr(args: tuple[float, Sequence[tuple[Literal["+", "-"], float]]]) -> float:
term1, terms = args
result = term1
for op, term2 in terms:
if op == "+":
result = result + term2
else:
result = result - term2
return result


class ExpressionParsers(ParserContext, whitespace=r"[ ]*"):
number = reg(r"[+-]?\d+(\.\d+)?(e[+-]?\d+)?") > float

base = "(" >> expr << ")" | number

factor = base & opt("^" >> base) > (lambda x: x[0] ** x[1][0] if x[1] else x[0])

def make_term(args):
factor1, factors = args
result = factor1
for op, factor in factors:
if op == "*":
result = result * factor
else:
result = result / factor
return result

term = factor & rep(lit("*", "/") & factor) > make_term

def make_expr(args):
term1, terms = args
result = term1
for op, term2 in terms:
if op == "+":
result = result + term2
else:
result = result - term2
return result

expr = term & rep(lit("+", "-") & term) > make_expr


Expand Down
3 changes: 2 additions & 1 deletion examples/json.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class JsonStringParsers(ParserContext):
line_feed = lit(r"\n") > constant("\n")
carriage_return = lit(r"\r") > constant("\r")
tab = lit(r"\t") > constant("\t")
uni = reg(r"\\u([0-9a-fA-F]{4})") > (lambda x: chr(int(x.group(1), 16)))
uni = reg(r"\\u[0-9a-fA-F]{4}") > (lambda x: chr(int(x[2:], 16)))

escaped = (
quote
Expand Down Expand Up @@ -61,6 +61,7 @@ class JsonParsers(ParserContext, whitespace=r"[ \t\n\r]*"):
"width" : 4.0
}""",
'{"text" : ""}',
r'"\u2260"',
]

for string in strings:
Expand Down
22 changes: 11 additions & 11 deletions examples/positioned.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

from abc import abstractmethod
from dataclasses import dataclass
from typing import Generic
from typing import Generic, Optional

from parsita import Parser, ParserContext, Reader, reg
from parsita.state import Continue, Input, Output, State
Expand Down Expand Up @@ -45,7 +45,7 @@ def __init__(self, parser: Parser[Input, PositionAware[Output]]):
super().__init__()
self.parser = parser

def _consume(self, state: State, reader: Reader[Input]):
def _consume(self, state: State, reader: Reader[Input]) -> Optional[Continue[Input, Output]]:
start = reader.position
status = self.parser.consume(state, reader)

Expand All @@ -55,11 +55,11 @@ def _consume(self, state: State, reader: Reader[Input]):
else:
return status

def __repr__(self):
def __repr__(self) -> str:
return self.name_or_nothing() + f"positioned({self.parser.name_or_repr()})"


def positioned(parser: Parser[Input, PositionAware[Output]]):
def positioned(parser: Parser[Input, PositionAware[Output]]) -> PositionedParser[Input, Output]:
"""Set the position on a PositionAware value.
This parser matches ``parser`` and, if successful, calls ``set_position``
Expand All @@ -75,18 +75,18 @@ def positioned(parser: Parser[Input, PositionAware[Output]]):

# Everything below here is an example use case
@dataclass
class UnfinishedVariable(PositionAware):
class Variable:
name: str

def set_position(self, start: int, length: int):
return Variable(self.name, start, length)
start: int
length: int


@dataclass
class Variable:
class UnfinishedVariable(PositionAware[Variable]):
name: str
start: int
length: int

def set_position(self, start: int, length: int) -> Variable:
return Variable(self.name, start, length)


@dataclass
Expand Down
9 changes: 7 additions & 2 deletions noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from nox import options, parametrize
from nox_poetry import Session, session

options.sessions = ["test", "coverage", "lint"]
options.sessions = ["test", "coverage", "lint", "type_check"]


@session(python=["3.9", "3.10", "3.11", "3.12", "3.13"])
Expand All @@ -27,6 +27,11 @@ def lint(s: Session, command: list[str]):


@session(venv_backend="none")
def format(s: Session) -> None:
def type_check(s: Session):
s.run("mypy", "src")


@session(venv_backend="none")
def format(s: Session):
s.run("ruff", "check", ".", "--select", "I", "--fix")
s.run("ruff", "format", ".")
60 changes: 59 additions & 1 deletion poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ pytest-timeout = "*"
# Lint
ruff = "^0.6"

# Type checking
mypy = "^1"

# Docs
mkdocs-material = "^9"

Expand All @@ -49,6 +52,7 @@ exclude_lines = [
"pragma: no cover",
"raise NotImplementedError",
"if TYPE_CHECKING",
"@overload",
]

[tool.coverage.paths]
Expand Down Expand Up @@ -85,6 +89,11 @@ extend-ignore = ["F821", "N805"]
"__init__.py" = ["F401"]


[tool.mypy]
strict = true
implicit_reexport = true


[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
Loading

0 comments on commit 00fffdc

Please sign in to comment.