diff --git a/.github/workflows/default.yml b/.github/workflows/default.yml index fc0f0fc5..0c6b4d7c 100644 --- a/.github/workflows/default.yml +++ b/.github/workflows/default.yml @@ -15,6 +15,9 @@ jobs: python-version: [ '3.12', '3.11', + '3.10', + '3.9', + '3.8', ] steps: @@ -33,7 +36,7 @@ jobs: python -m pip install --upgrade pip pip install --upgrade --requirement requirements-test.txt - name: lint - # if: ${{ matrix.python-version == '3.12' }} + if: ${{ matrix.python-version == '3.12' }} run: | make lint - name: pytest @@ -43,6 +46,7 @@ jobs: run: | make examples - name: documentation + if: ${{ matrix.python-version == '3.12' }} run: | make documentation - name: distributions diff --git a/pyproject.toml b/pyproject.toml index df45d2cc..38f6e908 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,7 @@ authors = [ ] description = "TatSu takes a grammar in a variation of EBNF as input, and outputs a memoizing PEG/Packrat parser in Python." readme = "README.rst" -requires-python = ">=3.11" +requires-python = ">=3.8" keywords = [] license = {file = "LICENSE.TXT"} classifiers = [ @@ -21,6 +21,9 @@ classifiers = [ "Intended Audience :: Science/Research", "Environment :: Console", "Operating System :: OS Independent", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Topic :: Software Development :: Code Generators", diff --git a/ruff.toml b/ruff.toml index abf3684a..48fd70d5 100644 --- a/ruff.toml +++ b/ruff.toml @@ -44,7 +44,7 @@ lint.ignore = [ ] exclude = [] -target-version = "py312" +target-version = "py38" [lint.per-file-ignores] diff --git a/tatsu/_version.py b/tatsu/_version.py index 0218cf33..83e4df9f 100644 --- a/tatsu/_version.py +++ b/tatsu/_version.py @@ -1 +1 @@ -__version__ = '5.12.2b1' +__version__ = '5.7.5' diff --git a/tatsu/bootstrap.py b/tatsu/bootstrap.py index 4f656b2a..2c4abe03 100644 --- a/tatsu/bootstrap.py +++ b/tatsu/bootstrap.py @@ -11,6 +11,8 @@ # ruff: noqa: C405, COM812, I001, F401, PLR1702, PLC2801, SIM117 +from __future__ import annotations + import sys from pathlib import Path diff --git a/tatsu/collections/orderedset.py b/tatsu/collections/orderedset.py index 5efc2137..17fc97fb 100644 --- a/tatsu/collections/orderedset.py +++ b/tatsu/collections/orderedset.py @@ -1,14 +1,13 @@ # NOTE: from https://github.com/LuminosoInsight/ordered-set/blob/master/ordered_set.py +from __future__ import annotations + import itertools from collections.abc import ( - Iterable, Iterator, Mapping, MutableSequence, - MutableSet, - Sequence, ) -from typing import Any, TypeVar +from typing import Any, Iterable, MutableSet, Sequence, TypeVar T = TypeVar('T') @@ -29,7 +28,7 @@ def __getitem__(self, i): self._list_cache = list(self._map.keys()) return self._list_cache[i] - def copy(self) -> 'OrderedSet[T]': + def copy(self) -> OrderedSet[T]: return self.__class__(self) def __getstate__(self): @@ -78,25 +77,25 @@ def __repr__(self) -> str: def __eq__(self, other: Any) -> bool: return all(item in other for item in self) - def union(self, *other: Iterable[T]) -> 'OrderedSet[T]': + def union(self, *other: Iterable[T]) -> OrderedSet[T]: # do not split `str` outer = tuple( - [o] if not isinstance(o, set | Mapping | MutableSequence) else o + [o] if not isinstance(o, (set, Mapping, MutableSequence)) else o for o in other ) inner = itertools.chain([self], *outer) items = itertools.chain.from_iterable(inner) return type(self)(itertools.chain(items)) - def __and__(self, other: Iterable[Iterable[T]]) -> 'OrderedSet[T]': + def __and__(self, other: Iterable[Iterable[T]]) -> OrderedSet[T]: return self.intersection(other) - def intersection(self, *other: Iterable[Iterable[T]]) -> 'OrderedSet[T]': + def intersection(self, *other: Iterable[Iterable[T]]) -> OrderedSet[T]: common = set.intersection(*other) # type: ignore[var-annotated, arg-type] items = (item for item in self if item in common) return type(self)(items) - def difference(self, *other: Iterable[T]) -> 'OrderedSet[T]': + def difference(self, *other: Iterable[T]) -> OrderedSet[T]: other = set.union(*other) # type: ignore[assignment, arg-type] items = (item for item in self if item not in other) return type(self)(items) @@ -109,7 +108,7 @@ def issuperset(self, other: set[T]) -> bool: return False return all(item in self for item in other) - def symmetric_difference(self, other: set[T]) -> 'OrderedSet[T]': + def symmetric_difference(self, other: set[T]) -> OrderedSet[T]: cls = type(self) diff1 = cls(self).difference(other) diff2 = cls(other).difference(self) diff --git a/tatsu/g2e/semantics.py b/tatsu/g2e/semantics.py index 982ed777..c811e7ae 100644 --- a/tatsu/g2e/semantics.py +++ b/tatsu/g2e/semantics.py @@ -85,12 +85,12 @@ def syntactic_predicate(self, ast): return None def optional(self, ast): - if isinstance(ast, model.Group | model.Optional | model.Closure): + if isinstance(ast, (model.Group, model.Optional, model.Closure)): ast = ast.exp return model.Optional(ast) def closure(self, ast): - if isinstance(ast, model.Group | model.Optional): + if isinstance(ast, (model.Group, model.Optional)): ast = ast.exp return model.Closure(ast) diff --git a/tatsu/grammars.py b/tatsu/grammars.py index 726225f1..f9d4908c 100644 --- a/tatsu/grammars.py +++ b/tatsu/grammars.py @@ -865,7 +865,7 @@ def _nullable(self): @staticmethod def param_repr(p): - if isinstance(p, int | float) or (isinstance(p, str) and p.isalnum()): + if isinstance(p, (int, float)) or (isinstance(p, str) and p.isalnum()): return str(p) else: return repr(p) diff --git a/tatsu/infos.py b/tatsu/infos.py index 0efb982f..97c980a0 100644 --- a/tatsu/infos.py +++ b/tatsu/infos.py @@ -3,6 +3,7 @@ import copy import dataclasses import re +import sys from collections.abc import Callable, Mapping from itertools import starmap from typing import Any, NamedTuple @@ -11,6 +12,12 @@ from .tokenizing import Tokenizer from .util.unicode_characters import C_DERIVE +if sys.version_info < (3, 10): + import builtins + + def zip(*iterables, strict=False): + return builtins.zip(*iterables) + class UndefinedStr(str): pass @@ -234,7 +241,7 @@ class RuleResult(NamedTuple): newstate: Any -@dataclasses.dataclass(slots=True) +@dataclasses.dataclass(**({'slots': True} if sys.version_info >= (3, 10) else {})) class ParseState: pos: int = 0 ast: AST = dataclasses.field(default_factory=AST) diff --git a/tatsu/mixins/indent.py b/tatsu/mixins/indent.py index e1026a7f..c982d5e6 100644 --- a/tatsu/mixins/indent.py +++ b/tatsu/mixins/indent.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import io from contextlib import contextmanager diff --git a/tatsu/ngcodegen/objectmodel.py b/tatsu/ngcodegen/objectmodel.py index f67e5272..6fa0c1ab 100644 --- a/tatsu/ngcodegen/objectmodel.py +++ b/tatsu/ngcodegen/objectmodel.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import builtins from collections import namedtuple diff --git a/tatsu/ngcodegen/python.py b/tatsu/ngcodegen/python.py index 30725801..b5a51e50 100644 --- a/tatsu/ngcodegen/python.py +++ b/tatsu/ngcodegen/python.py @@ -27,6 +27,8 @@ # ruff: noqa: C405, COM812, I001, F401, PLR1702, PLC2801, SIM117 + from __future__ import annotations + import sys from pathlib import Path @@ -94,7 +96,7 @@ def walk_Grammar(self, grammar: grammars.Grammar): def walk_Rule(self, rule: grammars.Rule): def param_repr(p): - if isinstance(p, int | float): + if isinstance(p, (int, float)): return str(p) else: return repr(p.split('::')[0]) diff --git a/tatsu/objectmodel.py b/tatsu/objectmodel.py index 7c8fad2e..7e4a786d 100644 --- a/tatsu/objectmodel.py +++ b/tatsu/objectmodel.py @@ -112,7 +112,7 @@ def with_parent(node): return node def children_of(child): - if isinstance(child, weakref.ReferenceType | weakref.ProxyType): + if isinstance(child, (weakref.ReferenceType, weakref.ProxyType)): return elif isinstance(child, Node): yield with_parent(child) @@ -121,7 +121,7 @@ def children_of(child): if name.startswith('_'): continue yield from children_of(value) - elif isinstance(child, list | tuple): + elif isinstance(child, (list, tuple)): yield from ( with_parent(c) for c in child if isinstance(c, Node) ) diff --git a/tatsu/parser.py b/tatsu/parser.py index 0e25fe5f..c598b293 100644 --- a/tatsu/parser.py +++ b/tatsu/parser.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import re from typing import Any diff --git a/tatsu/synth.py b/tatsu/synth.py index 97841007..3890f530 100644 --- a/tatsu/synth.py +++ b/tatsu/synth.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from collections.abc import Mapping from typing import Any diff --git a/tatsu/util/_common.py b/tatsu/util/_common.py index c0819064..daee7416 100644 --- a/tatsu/util/_common.py +++ b/tatsu/util/_common.py @@ -186,7 +186,7 @@ def decode_match(match): def isiter(value): return isinstance(value, Iterable) and not isinstance( - value, str | bytes | bytearray, + value, (str, bytes, bytearray), ) @@ -241,7 +241,7 @@ def timestamp(): def asjson(obj, seen=None): # noqa: PLR0911, PLR0912 - if obj is None or isinstance(obj, int | float | str | bool): + if obj is None or isinstance(obj, (int, float, str, bool)): return obj if seen is None: @@ -249,11 +249,11 @@ def asjson(obj, seen=None): # noqa: PLR0911, PLR0912 elif id(obj) in seen: return f'{type(obj).__name__}@{id(obj)}' - if isinstance(obj, Mapping | AsJSONMixin) or isiter(obj): + if isinstance(obj, (Mapping, AsJSONMixin)) or isiter(obj): seen.add(id(obj)) try: - if isinstance(obj, weakref.ReferenceType | weakref.ProxyType): + if isinstance(obj, (weakref.ReferenceType, weakref.ProxyType)): return f'{obj.__class__.__name__}@0x{hex(id(obj)).upper()[2:]}' elif hasattr(obj, '__json__'): return obj.__json__(seen=seen) @@ -304,7 +304,7 @@ def plainjson(obj): for name, value in obj.items() if name not in {'__class__', 'parseinfo'} } - elif isinstance(obj, weakref.ReferenceType | weakref.ProxyType): + elif isinstance(obj, (weakref.ReferenceType, weakref.ProxyType)): return '@ref' elif isinstance(obj, str) and obj.startswith('@'): return '@ref' diff --git a/tatsu/walkers.py b/tatsu/walkers.py index 3de070ea..cf102b9f 100644 --- a/tatsu/walkers.py +++ b/tatsu/walkers.py @@ -25,7 +25,7 @@ def __init__(self): )._walker_cache # pylint: disable=no-member def walk(self, node: Node | list[Node], *args, **kwargs) -> Any: - if isinstance(node, list | tuple): + if isinstance(node, (list, tuple)): return [self.walk(n, *args, **kwargs) for n in node] if isinstance(node, Mapping): diff --git a/test/grammar/semantics_test.py b/test/grammar/semantics_test.py index 51deebd7..4de537a4 100644 --- a/test/grammar/semantics_test.py +++ b/test/grammar/semantics_test.py @@ -100,7 +100,7 @@ def test_builder_basetype_codegen(self): self.assertTrue(hasattr(ast, 'a')) self.assertTrue(hasattr(ast, 'b')) - self.assertTrue(issubclass(D, A | B | C)) + self.assertTrue(issubclass(D, (A, B, C))) def test_optional_attributes(self): grammar = r"""