diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..af21d39 --- /dev/null +++ b/.flake8 @@ -0,0 +1,2 @@ +[flake8] +max-line-length = 88 # black formatting \ No newline at end of file diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..7091ac4 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,27 @@ +name: "Publish" + +on: + push: + tags: + # Publish on any tag starting with a `v`, e.g., v0.1.0 + - v* + +jobs: + run: + runs-on: ubuntu-latest + environment: + name: pypi + permissions: + id-token: write + contents: read + steps: + - name: Checkout + uses: actions/checkout@v6 + - name: Install uv + uses: astral-sh/setup-uv@v7 + - name: Install Python 3.13 + run: uv python install 3.13 + - name: Build + run: uv build + - name: Publish + run: uv publish \ No newline at end of file diff --git a/.github/workflows/test_publish.yml b/.github/workflows/test_publish.yml index 525503f..40f17bb 100644 --- a/.github/workflows/test_publish.yml +++ b/.github/workflows/test_publish.yml @@ -22,8 +22,11 @@ jobs: - name: Install Python 3.13 run: uv python install 3.13 - name: Build - run: uv build + run: | + uv version --bump patch --bump dev=$RANDOM # random version id + uv build - name: Publish env: TEST_PYPI_SECRET_KEY: ${{ secrets.TEST_PYPI_SECRET_KEY }} - run: uv publish --index testpypi --token $TEST_PYPI_SECRET_KEY \ No newline at end of file + run: | + uv publish --index testpypi --token $TEST_PYPI_SECRET_KEY \ No newline at end of file diff --git a/compilertoolkit/__pycache__/tokens.cpython-312.pyc b/compilertoolkit/__pycache__/tokens.cpython-312.pyc deleted file mode 100644 index f268f93..0000000 Binary files a/compilertoolkit/__pycache__/tokens.cpython-312.pyc and /dev/null differ diff --git a/compilertoolkit/ast.py b/compilertoolkit/ast.py index 045de25..dcdbfbd 100644 --- a/compilertoolkit/ast.py +++ b/compilertoolkit/ast.py @@ -101,7 +101,7 @@ def __init__(self, tokens: T): def set_parent(self, parent: Self): self.parent = parent - def walk[S](self, func: Callable[[Self], S]) -> list[S]: + def walk[S](self, func: Callable[["AbstractAstNode"], S]) -> list[S]: """Walk the syntax tree and run a function""" return [ func(self), diff --git a/compilertoolkit/exceptions.py b/compilertoolkit/exceptions.py index b7dacf2..64bdcf5 100644 --- a/compilertoolkit/exceptions.py +++ b/compilertoolkit/exceptions.py @@ -1,7 +1,7 @@ """Compilation Exceptions. You pass a source position to these kind of exceptions""" -from .tokens import SourcePosition +from compilertoolkit.tokens import SourcePosition class CompilerError(Exception): @@ -10,4 +10,18 @@ def __init__(self, positions: list[SourcePosition] | SourcePosition, msg: str): super().__init__(msg) +class ParserError(Exception): + """Errors in parser construction or execution \ + related to internal failure""" + + pass + + +class ParserNotFound(ParserError): + """Parser not found""" + + def __init__(self): + super().__init__("Parser Not Found") + + __all__ = ["CompilerError"] diff --git a/compilertoolkit/parsing.py b/compilertoolkit/parsing.py index da2baa3..121d91e 100644 --- a/compilertoolkit/parsing.py +++ b/compilertoolkit/parsing.py @@ -1,9 +1,10 @@ from abc import ABC, abstractmethod -from typing import TYPE_CHECKING, Any, Final, Generator, Self, Sequence, Type +from collections.abc import MutableSequence +from typing import TYPE_CHECKING, Any, Final, Generator, Self, Sequence, Type, overload +from compilertoolkit.exceptions import ParserNotFound from compilertoolkit.tokens import TokenEnum, TokenType - if TYPE_CHECKING: from compilertoolkit.ast import AbstractAstNode @@ -21,7 +22,12 @@ def __init__(self): @abstractmethod def eval( - self, token: TokenEnum, index: int, precedence, pattern_precedence=None, parser=None + self, + token: TokenEnum, + index: int, + precedence, + pattern_precedence=None, + parser=None, ) -> bool: pass @@ -29,9 +35,15 @@ def __set_name__(self, owner, name): self.idx = owner._IDX owner._IDX += 1 - def __get__(self, inst, owner) -> TokenEnum[T]: + @overload + def __get__(self, inst: "ParsingPattern", owner: Any) -> TokenEnum[T]: ... + + @overload + def __get__(self, inst: Any, owner: Any) -> Self: ... + + def __get__(self, inst: "ParsingPattern | Any", owner: Any) -> TokenEnum[T] | Self: if inst is None: - return self # type: ignore + return self return inst._children[self.idx] def __set__(self, value, inst): @@ -45,7 +57,12 @@ def __init__(self, typ: TokenType): self.typ = typ def eval( - self, token: TokenEnum, index: int, precedence, pattern_precedence=None, parser=None + self, + token: TokenEnum, + index: int, + precedence, + pattern_precedence=None, + parser=None, ) -> bool: return token.typ == self.typ @@ -57,7 +74,12 @@ def __init__(self, typ: Type | tuple[Type]): self.typ = typ def eval( - self, token: TokenEnum, index: int, precedence, pattern_precedence=None, parser=None + self, + token: TokenEnum, + index: int, + precedence, + pattern_precedence=None, + parser=None, ) -> bool: return isinstance(token.typ, self.typ) @@ -69,7 +91,12 @@ def __init__(self, val: Any): self.val = val def eval( - self, token: TokenEnum, index: int, precedence, pattern_precedence=None, parser=None + self, + token: TokenEnum, + index: int, + precedence, + pattern_precedence=None, + parser=None, ) -> bool: return token.value == self.val @@ -77,14 +104,28 @@ def eval( class ParseThenCheck(TokenPattern): __slots__ = "node" + node: TokenPattern + def __init__(self, node: TokenPattern): self.node = node def eval( - self, token: TokenEnum, index: int, precedence, pattern_precedence=None, parser=None + self, + token: TokenEnum, + index: int, + precedence, + pattern_precedence=None, + parser=None, ) -> bool: + if parser is None: + raise ParserNotFound() # TODO: verify node we are checking is fully parsed - return self.node.eval(parser(self.idx, pattern_precedence)[index], precedence, pattern_precedence, parser) + return self.node.eval( + parser(self.idx, pattern_precedence)[index], + precedence, + pattern_precedence, + parser, + ) class ParsingPatternMeta(type): @@ -120,11 +161,13 @@ def __init_subclass__( if isinstance(value, TokenPattern) } - def __init__(self, items: list[TokenEnum]): - self._children = items + def __init__(self, items: MutableSequence[TokenEnum]): + self._children = list(items) @classmethod - def eval(cls, tokens: list[TokenEnum], index: int, precedence, parser) -> bool: + def eval( + cls, tokens: MutableSequence[TokenEnum], index: int, precedence, parser + ) -> bool: if cls._PATTERN_PRECEDENCE is not None and precedence > cls._PATTERN_PRECEDENCE: return False return all( @@ -137,18 +180,19 @@ def __iter__(self) -> Generator[TokenEnum, None, None]: def set_parents(self, parent: "AbstractAstNode"): from compilertoolkit.ast import AbstractAstNode + for child in self._children: if child.value is not None and isinstance(child.value, AbstractAstNode): child.value.set_parent(parent) -class Parser[T]: +class Parser: __slots__ = ("rules", "eof") rules: list[Type[ParsingPattern]] - eof: Final[T] + eof: Final[TokenEnum[None]] - def __init__(self, EOF_token: T): + def __init__(self, EOF_token: TokenEnum[None]): self.rules = [] self.eof = EOF_token @@ -160,17 +204,23 @@ def add_rules(self, rules: list[Type[ParsingPattern]]) -> Self: self.rules += rules return self - def get_tokens(self, tokens: list[TokenEnum[Any]], start: int, end: int): + def get_tokens(self, tokens: MutableSequence[TokenEnum[Any]], start: int, end: int): if start > len(tokens): return [self.eof] * (end - start) - return tokens[start: min(end, len(tokens))] + ( + return list(tokens[start : min(end, len(tokens))]) + ( [self.eof] * (max(end, len(tokens)) - min(end, len(tokens))) ) def parse( - self, tokens: Sequence[TokenEnum[Any]], offset: int, precedence: int + self, _tokens: Sequence[TokenEnum[Any]], offset: int, precedence: int ) -> list[TokenEnum[Any]]: """Notice: the tokens list will be edited""" + # relies on editing a singular list instance- + # we must not reinstantiate the list + if not isinstance(_tokens, list): + tokens = list(_tokens) + else: + tokens = _tokens def parser(n_offset, n_precedence): return self.parse(tokens, offset + n_offset, n_precedence) @@ -178,7 +228,9 @@ def parser(n_offset, n_precedence): for rule in self.rules: rule_tokens = self.get_tokens(tokens, offset, offset + len(rule._PATTERNS)) if rule.eval(rule_tokens, offset, precedence, parser): - tok = rule._OWNER(rule(self.get_tokens(tokens, offset, offset + len(rule._PATTERNS)))) + tok = rule._OWNER( + rule(self.get_tokens(tokens, offset, offset + len(rule._PATTERNS))) + ) for _ in range(len(rule._PATTERNS) - 1): del tokens[offset] tokens[offset] = rule._TOKEN_TYPE(tok.position, tok) diff --git a/compilertoolkit/tokens.py b/compilertoolkit/tokens.py index 73550cd..f1a4318 100644 --- a/compilertoolkit/tokens.py +++ b/compilertoolkit/tokens.py @@ -28,7 +28,8 @@ class SourcePosition(NamedTuple): end_line: int """The line the token ends on""" end_column: int - """The ending index of the last character of the token on the last line of the position""" + """The ending index of the last character of the token \ + on the last line of the position""" source: Source @@ -110,17 +111,19 @@ def __repr__(self) -> str: return f"{self.typ.name}({self.position}, {repr(self.value)})" -class Lexer[T: TokenEnum](rplyLexer): +class Lexer[T: TokenEnum](): """A wrapper around the rply lexer. Returns your custom Token types when lexing""" - __slots__ = "_TokenType" + __slots__ = "_TokenType", "inner_lexer" _TokenType: Type[T] + inner_lexer: rplyLexer + '''Internal rply lexer used''' def __init__(self, rules, ignore_rules, token_type): self._TokenType = token_type - super().__init__(rules, ignore_rules) + self.inner_lexer = rplyLexer(rules, ignore_rules) def _fix_position( self, source: Source, position: rplySourcePosition, value @@ -130,6 +133,8 @@ def _fix_position( ) def _fix_token(self, source: Source, token: rplyToken) -> T: + if token.source_pos is None: + raise ValueError(token.source_pos) return self._TokenType( self._fix_position(source, token.source_pos, token.value), token.name, @@ -137,7 +142,7 @@ def _fix_token(self, source: Source, token: rplyToken) -> T: ) def lex(self, source: Source) -> list[T]: - output = super().lex(source.contents) + output = self.inner_lexer.lex(source.contents) return [self._fix_token(source, token) for token in output] diff --git a/mockup.py b/mockup.py index cab208a..a54379e 100644 --- a/mockup.py +++ b/mockup.py @@ -40,7 +40,7 @@ class AstNode(AbstractAstNode): __slots__ = () @abstractcompilationstep(0) - def analyze_types(self, ctx) -> type: + def analyze_types(self, ctx): ... @abstractcompilationstep(1) @@ -124,10 +124,13 @@ def compile(self, ctx): add_rule(NumberLiteral.ParserPattern).\ add_rule(SumNode.ParserPattern) +print(lexed_data) + parsed = parser.parse(lexed_data, 0, 0) +parsed = parser.parse(parsed, 0, 0) if len(parsed) != 1: - raise Exception() + raise Exception(parsed) ast = parsed[0].value diff --git a/pyproject.toml b/pyproject.toml index 7f7eeab..c03cebf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -50,10 +50,20 @@ Issues = "https://github.com/summersweet-software/compilertoolkit/issues" [dependency-groups] -dev = ["mypy>=1.20.1", "sphinx>=9.1.0", "sphinx-autobuild>=2025.8.25"] +dev = [ + "black>=26.3.1", + "mypy>=1.20.1", + "sphinx>=9.1.0", + "sphinx-autobuild>=2025.8.25", +] [[tool.uv.index]] name = "testpypi" url = "https://test.pypi.org/simple/" publish-url = "https://test.pypi.org/legacy/" explicit = true + +[tool.setuptools] +packages = ["compilertoolkit"] + +[tool.black] \ No newline at end of file diff --git a/testing.py b/testing.py index bde01f4..8dc00e6 100644 --- a/testing.py +++ b/testing.py @@ -1,7 +1,18 @@ from typing import Any -from compilertoolkit.ast import AbstractAstNode, abstractcompilationstep, compilationstep +from compilertoolkit.ast import ( + AbstractAstNode, + abstractcompilationstep, + compilationstep, +) from compilertoolkit.parsing import ParseThenCheck, Parser, ParsingPattern, TokenHasType -from compilertoolkit.tokens import Ignore, Source, SourcePosition, TokenEnum, TokenType, create_lexer +from compilertoolkit.tokens import ( + Ignore, + Source, + SourcePosition, + TokenEnum, + TokenType, + create_lexer, +) class Token[T](TokenEnum[T]): @@ -39,7 +50,7 @@ class ExpressionNode(AstNode): __slots__ = "return_type" # instance variables - return_type: None # Your own type class + return_type: None | type # Your own type class class NumberLiteral(ExpressionNode): @@ -100,12 +111,12 @@ def compile(self, ctx): source = Source("8 + 12") lexer = create_lexer(Token) -tokens: list[Token[Any]] = lexer.lex(source) -parser = Parser(Token.EOF(SourcePosition(-1, -1, -1, -1, Source("")), None)) -parser.add_rule(NumberLiteral.ParserPattern)\ - .add_rule(SumNode.ParserPattern) -tokens = parser.parse(tokens, 0, 0) -print(tokens[0].value) - -tokens[0].value.analyze_types({}) -print(tokens[0].value.compile({})) +tokens = lexer.lex(source) +EOF = Token.EOF(SourcePosition(-1, -1, -1, -1, source), None) +parser = Parser(EOF) +parser.add_rule(NumberLiteral.ParserPattern).add_rule(SumNode.ParserPattern) +parsed_tokens = parser.parse(tokens, 0, 0) +print(parsed_tokens[0].value) + +parsed_tokens[0].value.analyze_types({}) +print(parsed_tokens[0].value.compile({})) diff --git a/typings/rply-stubs/__init__.pyi b/typings/rply-stubs/__init__.pyi new file mode 100644 index 0000000..b4e07f8 --- /dev/null +++ b/typings/rply-stubs/__init__.pyi @@ -0,0 +1,6 @@ +from . import token +from . import lexer +from . import lexergenerator +from .lexergenerator import LexerGenerator + +__all__ = ["token", "lexer", "lexergenerator", "LexerGenerator"] diff --git a/typings/rply-stubs/lexer.pyi b/typings/rply-stubs/lexer.pyi new file mode 100644 index 0000000..4f92276 --- /dev/null +++ b/typings/rply-stubs/lexer.pyi @@ -0,0 +1,38 @@ +from typing import Self +from .token import Token +from .lexergenerator import Rule, Match + + +class Lexer(object): + rules: list[Rule] + ignore_rules: list[Rule] + + def __init__(self, rules: list[Rule], ignore_rules: list[Rule]): + ... + + def lex(self, s: str) -> "LexerStream": + ... + + +class LexerStream(object): + lexer: Lexer + s: str + idx: int + + _lineno: int + _colno: int + + def __init__(self, lexer: Lexer, s: str): + ... + + def __iter__(self) -> Self: + ... + + def _update_pos(self, match: Match) -> int: + ... + + def next(self) -> Token: + ... + + def __next__(self) -> Token: + ... diff --git a/typings/rply-stubs/lexergenerator.pyi b/typings/rply-stubs/lexergenerator.pyi new file mode 100644 index 0000000..170658f --- /dev/null +++ b/typings/rply-stubs/lexergenerator.pyi @@ -0,0 +1,44 @@ +from typing import Any +from .lexer import Lexer +from .token import SourcePosition + + +class Rule[N: str](object): + _attrs_ = ['name', 'flags', '_pattern'] + name: N + pattern: str + flags: int + + def __init__(self, name: N, pattern: str, flags=0): + ... + + def _freeze_(self) -> bool: + ... + + def matches(self, s: str, pos: SourcePosition): + ... + + +class Match(object): + start: int + end: int + + def __init__(self, start: int, end: int): + ... + + +class LexerGenerator(object): + rules: list[Rule] + ignore_rules: list[Rule] + + def __init__(self): + ... + + def add(self, name: str | Any, pattern: str, flags: int = 0): + ... + + def ignore(self, pattern: str, flags: int = 0): + ... + + def build(self) -> Lexer: + ... diff --git a/typings/rply-stubs/token.pyi b/typings/rply-stubs/token.pyi new file mode 100644 index 0000000..4d127ee --- /dev/null +++ b/typings/rply-stubs/token.pyi @@ -0,0 +1,46 @@ +from typing import Any, Never, Self, overload + + +class BaseBox(object): + _attrs_: list[str] = [] + + +class SourcePosition(object): + idx: int + lineno: int + colno: int + + def __init__(self, idx: int, lineno: int, colno: int): + ... + + def __repr__(self) -> str: + ... + + +class Token[N: str, V](BaseBox): + name: N + value: V + source_pos: SourcePosition | None + + def __init__(self, name: N, value: V, source_pos=None): + ... + + def __repr__(self) -> str: + ... + + @overload + def __eq__(self, other: Self) -> bool: + ... + + @overload + def __eq__(self, other: Any) -> Never: + ... + + def gettokentype(self) -> N: + ... + + def getsourcepos(self) -> SourcePosition: + ... + + def getstr(self) -> N: + ... diff --git a/uv.lock b/uv.lock index e253414..219d7e2 100644 --- a/uv.lock +++ b/uv.lock @@ -42,6 +42,38 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl", hash = "sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35", size = 10196845, upload-time = "2026-02-01T12:30:53.445Z" }, ] +[[package]] +name = "black" +version = "26.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "mypy-extensions" }, + { name = "packaging" }, + { name = "pathspec" }, + { name = "platformdirs" }, + { name = "pytokens" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e1/c5/61175d618685d42b005847464b8fb4743a67b1b8fdb75e50e5a96c31a27a/black-26.3.1.tar.gz", hash = "sha256:2c50f5063a9641c7eed7795014ba37b0f5fa227f3d408b968936e24bc0566b07", size = 666155, upload-time = "2026-03-12T03:36:03.593Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/f8/da5eae4fc75e78e6dceb60624e1b9662ab00d6b452996046dfa9b8a6025b/black-26.3.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b5e6f89631eb88a7302d416594a32faeee9fb8fb848290da9d0a5f2903519fc1", size = 1895920, upload-time = "2026-03-12T03:40:13.921Z" }, + { url = "https://files.pythonhosted.org/packages/2c/9f/04e6f26534da2e1629b2b48255c264cabf5eedc5141d04516d9d68a24111/black-26.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:41cd2012d35b47d589cb8a16faf8a32ef7a336f56356babd9fcf70939ad1897f", size = 1718499, upload-time = "2026-03-12T03:40:15.239Z" }, + { url = "https://files.pythonhosted.org/packages/04/91/a5935b2a63e31b331060c4a9fdb5a6c725840858c599032a6f3aac94055f/black-26.3.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f76ff19ec5297dd8e66eb64deda23631e642c9393ab592826fd4bdc97a4bce7", size = 1794994, upload-time = "2026-03-12T03:40:17.124Z" }, + { url = "https://files.pythonhosted.org/packages/e7/0a/86e462cdd311a3c2a8ece708d22aba17d0b2a0d5348ca34b40cdcbea512e/black-26.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:ddb113db38838eb9f043623ba274cfaf7d51d5b0c22ecb30afe58b1bb8322983", size = 1420867, upload-time = "2026-03-12T03:40:18.83Z" }, + { url = "https://files.pythonhosted.org/packages/5b/e5/22515a19cb7eaee3440325a6b0d95d2c0e88dd180cb011b12ae488e031d1/black-26.3.1-cp312-cp312-win_arm64.whl", hash = "sha256:dfdd51fc3e64ea4f35873d1b3fb25326773d55d2329ff8449139ebaad7357efb", size = 1230124, upload-time = "2026-03-12T03:40:20.425Z" }, + { url = "https://files.pythonhosted.org/packages/f5/77/5728052a3c0450c53d9bb3945c4c46b91baa62b2cafab6801411b6271e45/black-26.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:855822d90f884905362f602880ed8b5df1b7e3ee7d0db2502d4388a954cc8c54", size = 1895034, upload-time = "2026-03-12T03:40:21.813Z" }, + { url = "https://files.pythonhosted.org/packages/52/73/7cae55fdfdfbe9d19e9a8d25d145018965fe2079fa908101c3733b0c55a0/black-26.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8a33d657f3276328ce00e4d37fe70361e1ec7614da5d7b6e78de5426cb56332f", size = 1718503, upload-time = "2026-03-12T03:40:23.666Z" }, + { url = "https://files.pythonhosted.org/packages/e1/87/af89ad449e8254fdbc74654e6467e3c9381b61472cc532ee350d28cfdafb/black-26.3.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f1cd08e99d2f9317292a311dfe578fd2a24b15dbce97792f9c4d752275c1fa56", size = 1793557, upload-time = "2026-03-12T03:40:25.497Z" }, + { url = "https://files.pythonhosted.org/packages/43/10/d6c06a791d8124b843bf325ab4ac7d2f5b98731dff84d6064eafd687ded1/black-26.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:c7e72339f841b5a237ff14f7d3880ddd0fc7f98a1199e8c4327f9a4f478c1839", size = 1422766, upload-time = "2026-03-12T03:40:27.14Z" }, + { url = "https://files.pythonhosted.org/packages/59/4f/40a582c015f2d841ac24fed6390bd68f0fc896069ff3a886317959c9daf8/black-26.3.1-cp313-cp313-win_arm64.whl", hash = "sha256:afc622538b430aa4c8c853f7f63bc582b3b8030fd8c80b70fb5fa5b834e575c2", size = 1232140, upload-time = "2026-03-12T03:40:28.882Z" }, + { url = "https://files.pythonhosted.org/packages/d5/da/e36e27c9cebc1311b7579210df6f1c86e50f2d7143ae4fcf8a5017dc8809/black-26.3.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2d6bfaf7fd0993b420bed691f20f9492d53ce9a2bcccea4b797d34e947318a78", size = 1889234, upload-time = "2026-03-12T03:40:30.964Z" }, + { url = "https://files.pythonhosted.org/packages/0e/7b/9871acf393f64a5fa33668c19350ca87177b181f44bb3d0c33b2d534f22c/black-26.3.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f89f2ab047c76a9c03f78d0d66ca519e389519902fa27e7a91117ef7611c0568", size = 1720522, upload-time = "2026-03-12T03:40:32.346Z" }, + { url = "https://files.pythonhosted.org/packages/03/87/e766c7f2e90c07fb7586cc787c9ae6462b1eedab390191f2b7fc7f6170a9/black-26.3.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b07fc0dab849d24a80a29cfab8d8a19187d1c4685d8a5e6385a5ce323c1f015f", size = 1787824, upload-time = "2026-03-12T03:40:33.636Z" }, + { url = "https://files.pythonhosted.org/packages/ac/94/2424338fb2d1875e9e83eed4c8e9c67f6905ec25afd826a911aea2b02535/black-26.3.1-cp314-cp314-win_amd64.whl", hash = "sha256:0126ae5b7c09957da2bdbd91a9ba1207453feada9e9fe51992848658c6c8e01c", size = 1445855, upload-time = "2026-03-12T03:40:35.442Z" }, + { url = "https://files.pythonhosted.org/packages/86/43/0c3338bd928afb8ee7471f1a4eec3bdbe2245ccb4a646092a222e8669840/black-26.3.1-cp314-cp314-win_arm64.whl", hash = "sha256:92c0ec1f2cc149551a2b7b47efc32c866406b6891b0ee4625e95967c8f4acfb1", size = 1258109, upload-time = "2026-03-12T03:40:36.832Z" }, + { url = "https://files.pythonhosted.org/packages/8e/0d/52d98722666d6fc6c3dd4c76df339501d6efd40e0ff95e6186a7b7f0befd/black-26.3.1-py3-none-any.whl", hash = "sha256:2bd5aa94fc267d38bb21a70d7410a89f1a1d318841855f698746f8e7f51acd1b", size = 207542, upload-time = "2026-03-12T03:36:01.668Z" }, +] + [[package]] name = "certifi" version = "2026.2.25" @@ -155,6 +187,7 @@ dependencies = [ [package.dev-dependencies] dev = [ + { name = "black" }, { name = "mypy" }, { name = "sphinx" }, { name = "sphinx-autobuild" }, @@ -165,6 +198,7 @@ requires-dist = [{ name = "rply", specifier = ">=0.7.8" }] [package.metadata.requires-dev] dev = [ + { name = "black", specifier = ">=26.3.1" }, { name = "mypy", specifier = ">=1.20.1" }, { name = "sphinx", specifier = ">=9.1.0" }, { name = "sphinx-autobuild", specifier = ">=2025.8.25" }, @@ -411,6 +445,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723", size = 55206, upload-time = "2026-01-27T03:59:45.137Z" }, ] +[[package]] +name = "platformdirs" +version = "4.9.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9f/4a/0883b8e3802965322523f0b200ecf33d31f10991d0401162f4b23c698b42/platformdirs-4.9.6.tar.gz", hash = "sha256:3bfa75b0ad0db84096ae777218481852c0ebc6c727b3168c1b9e0118e458cf0a", size = 29400, upload-time = "2026-04-09T00:04:10.812Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/75/a6/a0a304dc33b49145b21f4808d763822111e67d1c3a32b524a1baf947b6e1/platformdirs-4.9.6-py3-none-any.whl", hash = "sha256:e61adb1d5e5cb3441b4b7710bea7e4c12250ca49439228cc1021c00dcfac0917", size = 21348, upload-time = "2026-04-09T00:04:09.463Z" }, +] + [[package]] name = "pygments" version = "2.20.0" @@ -420,6 +463,35 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, ] +[[package]] +name = "pytokens" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b6/34/b4e015b99031667a7b960f888889c5bd34ef585c85e1cb56a594b92836ac/pytokens-0.4.1.tar.gz", hash = "sha256:292052fe80923aae2260c073f822ceba21f3872ced9a68bb7953b348e561179a", size = 23015, upload-time = "2026-01-30T01:03:45.924Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/5d/e44573011401fb82e9d51e97f1290ceb377800fb4eed650b96f4753b499c/pytokens-0.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:140709331e846b728475786df8aeb27d24f48cbcf7bcd449f8de75cae7a45083", size = 160663, upload-time = "2026-01-30T01:03:06.473Z" }, + { url = "https://files.pythonhosted.org/packages/f0/e6/5bbc3019f8e6f21d09c41f8b8654536117e5e211a85d89212d59cbdab381/pytokens-0.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d6c4268598f762bc8e91f5dbf2ab2f61f7b95bdc07953b602db879b3c8c18e1", size = 255626, upload-time = "2026-01-30T01:03:08.177Z" }, + { url = "https://files.pythonhosted.org/packages/bf/3c/2d5297d82286f6f3d92770289fd439956b201c0a4fc7e72efb9b2293758e/pytokens-0.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:24afde1f53d95348b5a0eb19488661147285ca4dd7ed752bbc3e1c6242a304d1", size = 269779, upload-time = "2026-01-30T01:03:09.756Z" }, + { url = "https://files.pythonhosted.org/packages/20/01/7436e9ad693cebda0551203e0bf28f7669976c60ad07d6402098208476de/pytokens-0.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5ad948d085ed6c16413eb5fec6b3e02fa00dc29a2534f088d3302c47eb59adf9", size = 268076, upload-time = "2026-01-30T01:03:10.957Z" }, + { url = "https://files.pythonhosted.org/packages/2e/df/533c82a3c752ba13ae7ef238b7f8cdd272cf1475f03c63ac6cf3fcfb00b6/pytokens-0.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:3f901fe783e06e48e8cbdc82d631fca8f118333798193e026a50ce1b3757ea68", size = 103552, upload-time = "2026-01-30T01:03:12.066Z" }, + { url = "https://files.pythonhosted.org/packages/cb/dc/08b1a080372afda3cceb4f3c0a7ba2bde9d6a5241f1edb02a22a019ee147/pytokens-0.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8bdb9d0ce90cbf99c525e75a2fa415144fd570a1ba987380190e8b786bc6ef9b", size = 160720, upload-time = "2026-01-30T01:03:13.843Z" }, + { url = "https://files.pythonhosted.org/packages/64/0c/41ea22205da480837a700e395507e6a24425151dfb7ead73343d6e2d7ffe/pytokens-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5502408cab1cb18e128570f8d598981c68a50d0cbd7c61312a90507cd3a1276f", size = 254204, upload-time = "2026-01-30T01:03:14.886Z" }, + { url = "https://files.pythonhosted.org/packages/e0/d2/afe5c7f8607018beb99971489dbb846508f1b8f351fcefc225fcf4b2adc0/pytokens-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:29d1d8fb1030af4d231789959f21821ab6325e463f0503a61d204343c9b355d1", size = 268423, upload-time = "2026-01-30T01:03:15.936Z" }, + { url = "https://files.pythonhosted.org/packages/68/d4/00ffdbd370410c04e9591da9220a68dc1693ef7499173eb3e30d06e05ed1/pytokens-0.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:970b08dd6b86058b6dc07efe9e98414f5102974716232d10f32ff39701e841c4", size = 266859, upload-time = "2026-01-30T01:03:17.458Z" }, + { url = "https://files.pythonhosted.org/packages/a7/c9/c3161313b4ca0c601eeefabd3d3b576edaa9afdefd32da97210700e47652/pytokens-0.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:9bd7d7f544d362576be74f9d5901a22f317efc20046efe2034dced238cbbfe78", size = 103520, upload-time = "2026-01-30T01:03:18.652Z" }, + { url = "https://files.pythonhosted.org/packages/8f/a7/b470f672e6fc5fee0a01d9e75005a0e617e162381974213a945fcd274843/pytokens-0.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4a14d5f5fc78ce85e426aa159489e2d5961acf0e47575e08f35584009178e321", size = 160821, upload-time = "2026-01-30T01:03:19.684Z" }, + { url = "https://files.pythonhosted.org/packages/80/98/e83a36fe8d170c911f864bfded690d2542bfcfacb9c649d11a9e6eb9dc41/pytokens-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f50fd18543be72da51dd505e2ed20d2228c74e0464e4262e4899797803d7fa", size = 254263, upload-time = "2026-01-30T01:03:20.834Z" }, + { url = "https://files.pythonhosted.org/packages/0f/95/70d7041273890f9f97a24234c00b746e8da86df462620194cef1d411ddeb/pytokens-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dc74c035f9bfca0255c1af77ddd2d6ae8419012805453e4b0e7513e17904545d", size = 268071, upload-time = "2026-01-30T01:03:21.888Z" }, + { url = "https://files.pythonhosted.org/packages/da/79/76e6d09ae19c99404656d7db9c35dfd20f2086f3eb6ecb496b5b31163bad/pytokens-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f66a6bbe741bd431f6d741e617e0f39ec7257ca1f89089593479347cc4d13324", size = 271716, upload-time = "2026-01-30T01:03:23.633Z" }, + { url = "https://files.pythonhosted.org/packages/79/37/482e55fa1602e0a7ff012661d8c946bafdc05e480ea5a32f4f7e336d4aa9/pytokens-0.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:b35d7e5ad269804f6697727702da3c517bb8a5228afa450ab0fa787732055fc9", size = 104539, upload-time = "2026-01-30T01:03:24.788Z" }, + { url = "https://files.pythonhosted.org/packages/30/e8/20e7db907c23f3d63b0be3b8a4fd1927f6da2395f5bcc7f72242bb963dfe/pytokens-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:8fcb9ba3709ff77e77f1c7022ff11d13553f3c30299a9fe246a166903e9091eb", size = 168474, upload-time = "2026-01-30T01:03:26.428Z" }, + { url = "https://files.pythonhosted.org/packages/d6/81/88a95ee9fafdd8f5f3452107748fd04c24930d500b9aba9738f3ade642cc/pytokens-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:79fc6b8699564e1f9b521582c35435f1bd32dd06822322ec44afdeba666d8cb3", size = 290473, upload-time = "2026-01-30T01:03:27.415Z" }, + { url = "https://files.pythonhosted.org/packages/cf/35/3aa899645e29b6375b4aed9f8d21df219e7c958c4c186b465e42ee0a06bf/pytokens-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d31b97b3de0f61571a124a00ffe9a81fb9939146c122c11060725bd5aea79975", size = 303485, upload-time = "2026-01-30T01:03:28.558Z" }, + { url = "https://files.pythonhosted.org/packages/52/a0/07907b6ff512674d9b201859f7d212298c44933633c946703a20c25e9d81/pytokens-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:967cf6e3fd4adf7de8fc73cd3043754ae79c36475c1c11d514fc72cf5490094a", size = 306698, upload-time = "2026-01-30T01:03:29.653Z" }, + { url = "https://files.pythonhosted.org/packages/39/2a/cbbf9250020a4a8dd53ba83a46c097b69e5eb49dd14e708f496f548c6612/pytokens-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:584c80c24b078eec1e227079d56dc22ff755e0ba8654d8383b2c549107528918", size = 116287, upload-time = "2026-01-30T01:03:30.912Z" }, + { url = "https://files.pythonhosted.org/packages/c6/78/397db326746f0a342855b81216ae1f0a32965deccfd7c830a2dbc66d2483/pytokens-0.4.1-py3-none-any.whl", hash = "sha256:26cef14744a8385f35d0e095dc8b3a7583f6c953c2e3d269c7f82484bf5ad2de", size = 13729, upload-time = "2026-01-30T01:03:45.029Z" }, +] + [[package]] name = "requests" version = "2.33.1"