Skip to content

Commit

Permalink
Backport recent fixes to 5.7.5 release with Python 3.8 and later comp…
Browse files Browse the repository at this point in the history
…atibility (#340)

* Add CI jobs for Python 3.8, 3.9, and 3.10

* Adjust ruff configuration for Python 3.8

* Tweaks for Python 3.8 compatibility

* [dist] Update classifiers and requires-python for Python 3.8 support

* [dist] prepare 5.7.5 release
  • Loading branch information
dnicolodi authored Jun 5, 2024
1 parent fa2df38 commit 527397e
Show file tree
Hide file tree
Showing 18 changed files with 54 additions and 29 deletions.
6 changes: 5 additions & 1 deletion .github/workflows/default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ jobs:
python-version: [
'3.12',
'3.11',
'3.10',
'3.9',
'3.8',
]

steps:
Expand All @@ -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
Expand All @@ -43,6 +46,7 @@ jobs:
run: |
make examples
- name: documentation
if: ${{ matrix.python-version == '3.12' }}
run: |
make documentation
- name: distributions
Expand Down
5 changes: 4 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand All @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion ruff.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ lint.ignore = [
]
exclude = []

target-version = "py312"
target-version = "py38"

[lint.per-file-ignores]

Expand Down
2 changes: 1 addition & 1 deletion tatsu/_version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = '5.12.2b1'
__version__ = '5.7.5'
2 changes: 2 additions & 0 deletions tatsu/bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@

# ruff: noqa: C405, COM812, I001, F401, PLR1702, PLC2801, SIM117

from __future__ import annotations

import sys
from pathlib import Path

Expand Down
21 changes: 10 additions & 11 deletions tatsu/collections/orderedset.py
Original file line number Diff line number Diff line change
@@ -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')

Expand All @@ -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):
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions tatsu/g2e/semantics.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
2 changes: 1 addition & 1 deletion tatsu/grammars.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
9 changes: 8 additions & 1 deletion tatsu/infos.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions tatsu/mixins/indent.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

import io
from contextlib import contextmanager

Expand Down
2 changes: 2 additions & 0 deletions tatsu/ngcodegen/objectmodel.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

import builtins
from collections import namedtuple

Expand Down
4 changes: 3 additions & 1 deletion tatsu/ngcodegen/python.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
# ruff: noqa: C405, COM812, I001, F401, PLR1702, PLC2801, SIM117
from __future__ import annotations
import sys
from pathlib import Path
Expand Down Expand Up @@ -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])
Expand Down
4 changes: 2 additions & 2 deletions tatsu/objectmodel.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
)
Expand Down
2 changes: 2 additions & 0 deletions tatsu/parser.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

import re
from typing import Any

Expand Down
2 changes: 2 additions & 0 deletions tatsu/synth.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

from collections.abc import Mapping
from typing import Any

Expand Down
10 changes: 5 additions & 5 deletions tatsu/util/_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -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),
)


Expand Down Expand Up @@ -241,19 +241,19 @@ 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:
seen = set()
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)
Expand Down Expand Up @@ -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'
Expand Down
2 changes: 1 addition & 1 deletion tatsu/walkers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
2 changes: 1 addition & 1 deletion test/grammar/semantics_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"""
Expand Down

0 comments on commit 527397e

Please sign in to comment.