diff --git a/tatsu/bootstrap.py b/tatsu/bootstrap.py index 84e5205..ed5aff8 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 fb92401..3c12190 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') @@ -32,7 +31,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): @@ -81,25 +80,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) @@ -112,7 +111,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 ccf0b49..0956af0 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 4ac6c12..7c3fa4e 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 876a96c..909a682 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, MutableMapping from itertools import starmap from typing import Any, NamedTuple @@ -12,6 +13,12 @@ from .util.misc import cached_re_compile 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 @@ -261,7 +268,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 e1026a7..c982d5e 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 f67e527..6fa0c1a 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 7658337..791c691 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 7c8fad2..7e4a786 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 0e25fe5..c598b29 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 9784100..3890f53 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 48c1503..14f5149 100644 --- a/tatsu/util/_common.py +++ b/tatsu/util/_common.py @@ -183,7 +183,7 @@ def decode_match(match): def isiter(value): return isinstance(value, Iterable) and not isinstance( - value, str | bytes | bytearray, + value, (str, bytes, bytearray), ) @@ -238,7 +238,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: @@ -246,11 +246,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) @@ -301,7 +301,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/util/misc.py b/tatsu/util/misc.py index 8fc2b42..17fd4e2 100644 --- a/tatsu/util/misc.py +++ b/tatsu/util/misc.py @@ -1,10 +1,16 @@ from __future__ import annotations import re +import sys from collections.abc import Iterable -from functools import cache from typing import TypeVar +if sys.version_info > (3, 9): + from functools import cache +else: + from functools import lru_cache + cache = lru_cache(None) + _T = TypeVar('_T') _undefined = object() # unique object for when None is not a good default @@ -114,4 +120,4 @@ def with_incoming() -> set[_T]: def cached_re_compile(regex: re.Pattern | str | bytes) -> re.Pattern | None: if isinstance(regex, re.Pattern): return regex - return re.compile(regex) if isinstance(regex, (str | bytes)) else None + return re.compile(regex) if isinstance(regex, (str, bytes)) else None diff --git a/tatsu/walkers.py b/tatsu/walkers.py index 7762a4d..ed78d23 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 51deebd..4de537a 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"""