From 066db25218b2225d701fa93cd4daa13409cc980f Mon Sep 17 00:00:00 2001 From: Joe Hendrix Date: Thu, 29 Jan 2026 15:25:28 -0800 Subject: [PATCH 1/4] Add Python 3.11 to tests --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f1dbc9a8..66c53c84 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -142,7 +142,7 @@ jobs: contents: read strategy: matrix: - python_version: [3.12, 3.13, 3.14] + python_version: [3.11, 3.12, 3.13, 3.14] steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 From 4d1e5b0bf24530a880d90ca8eebc71c54f76891c Mon Sep 17 00:00:00 2001 From: Joe Hendrix Date: Thu, 29 Jan 2026 22:40:53 -0800 Subject: [PATCH 2/4] WIP. Python 3.11 support --- Tools/Python/pyproject.toml | 2 +- Tools/Python/strata/base.py | 28 ++++++++++++++++------------ Tools/Python/strata/pythonast.py | 5 +++-- 3 files changed, 20 insertions(+), 15 deletions(-) diff --git a/Tools/Python/pyproject.toml b/Tools/Python/pyproject.toml index cbf41efd..942b083f 100644 --- a/Tools/Python/pyproject.toml +++ b/Tools/Python/pyproject.toml @@ -2,7 +2,7 @@ name = "strata" version = "0.0.1" description = 'Python support for Strata.' -requires-python = ">= 3.12" +requires-python = ">= 3.11" dependencies = [ "amazon.ion" ] diff --git a/Tools/Python/strata/base.py b/Tools/Python/strata/base.py index 78944a44..4d85d881 100644 --- a/Tools/Python/strata/base.py +++ b/Tools/Python/strata/base.py @@ -10,7 +10,7 @@ from decimal import Decimal import sys import typing -from typing import Any, Callable, Iterable, cast +from typing import Any, Callable, Iterable, TypeVar, Generic, Union import amazon.ion.simpleion as ion @@ -113,7 +113,8 @@ def __init__(self, name: QualifiedIdent, args: tuple['SyntaxCat', ...] | None = self.args = () if args is None else args def strPrec(self, prec: int) -> str: - s = f'{str(self.name)}{"".join(' ' + a.strPrec(10) for a in self.args)}' + args = ''.join(' ' + a.strPrec(10) for a in self.args) + s = f'{str(self.name)}{args}' return f'({s})' if prec > 0 else s def __str__(self) -> str: @@ -353,12 +354,14 @@ def __init__(self, value: str, *, ann = None): def __str__(self): return f'StrLit({repr(self.value)})' +T = TypeVar('T', bound='Arg') + @dataclass -class OptionArg[T : 'Arg']: - value: T|None +class OptionArg(Generic[T]): + value: Union[T, None] ann : Any - def __init__(self, value: T|None, *, ann = None): + def __init__(self, value: Union[T, None], *, ann = None): self.value = value self.ann = ann @@ -372,7 +375,7 @@ def __str__(self): return f'Some({self.value})' @dataclass -class Seq[T : 'Arg']: +class Seq(Generic[T]): values: tuple[T, ...] ann : Any @@ -400,8 +403,7 @@ def __init__(self, values: list['Arg'], *, ann = None): self.values = values self.ann = ann -type Arg = SyntaxCat | Operation | TypeExpr | Expr | Ident \ - | BytesLit | NumLit | DecimalLit | StrLit | OptionArg['Arg'] | Seq['Arg'] | CommaSepBy +Arg = Union[SyntaxCat, Operation, TypeExpr, Expr, Ident, BytesLit, NumLit, DecimalLit, StrLit, OptionArg['Arg'], Seq['Arg'], CommaSepBy] strlitSym = ion_symbol("strlit") numSym = ion_symbol("num") @@ -493,7 +495,7 @@ class MetadataAttr: def to_ion(self): return ion_sexp(self.ident.to_ion(), *(metadata_arg_to_ion(a) for a in self.args)) -type Metadata = list[MetadataAttr] +Metadata = list[MetadataAttr] def metadata_to_ion(values): return [ v.to_ion() for v in values ] @@ -531,7 +533,7 @@ def __init__(self, indent: int, args: tuple['SyntaxDefAtom', ...]): def to_ion(self): return ion_sexp(ion_symbol("indent"), self.indent, *(syntaxdef_atom_to_ion(a) for a in self.args)) -type SyntaxDefAtom = SyntaxDefAtomBase | str +SyntaxDefAtom = Union[SyntaxDefAtomBase, str] def syntaxdef_atom_to_ion(atom : SyntaxDefAtom) -> object: if isinstance(atom, str): @@ -846,7 +848,9 @@ def read_string(reader) -> str: assert isinstance(scalar, str) return scalar -def read_list[X](reader : object, f : Callable[[object, ion.IonEvent], X] ) -> tuple[X, ...]: +X = TypeVar('X') + +def read_list(reader : object, f : Callable[[object, ion.IonEvent], X] ) -> tuple[X, ...]: res = [] while True: event = read_event(reader) @@ -855,7 +859,7 @@ def read_list[X](reader : object, f : Callable[[object, ion.IonEvent], X] ) -> t v = f(reader, event) res.append(v) -def read_sexpr[X](reader : object, f : Callable[[object, ion.IonEvent], X] ) -> tuple[X, ...]: +def read_sexpr(reader : object, f : Callable[[object, ion.IonEvent], X] ) -> tuple[X, ...]: res = [] while True: event = read_event(reader) diff --git a/Tools/Python/strata/pythonast.py b/Tools/Python/strata/pythonast.py index 45fdf1dc..351d94aa 100644 --- a/Tools/Python/strata/pythonast.py +++ b/Tools/Python/strata/pythonast.py @@ -214,13 +214,14 @@ def populate_op(name : str, op : type) -> Op: new_args : list[tuple[type, str]] if sys.version_info >= (3, 13): new_args = [] -else: - assert sys.version_info >= (3, 12) +elif sys.version_info >= (3, 12): new_args = [ (ast.TypeVar, "default_value"), (ast.ParamSpec, "default_value"), (ast.TypeVarTuple, "default_value"), ] +else: + new_args = [] def check_op(d : strata.Dialect, name, op : type): opd = getattr(d, name) From 88b639305fc3359ef362ea57840a8071165ffbb6 Mon Sep 17 00:00:00 2001 From: Joe Hendrix Date: Fri, 30 Jan 2026 15:13:19 -0800 Subject: [PATCH 3/4] Add support for empty sequences --- Tools/Python/strata/pythonast.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/Tools/Python/strata/pythonast.py b/Tools/Python/strata/pythonast.py index 351d94aa..a4090f88 100644 --- a/Tools/Python/strata/pythonast.py +++ b/Tools/Python/strata/pythonast.py @@ -191,7 +191,13 @@ def populate_op(name : str, op : type) -> Op: arg = decl.args[idx] cat = arg.kind assert isinstance(cat, SyntaxCat) - missing = strata.OptionArg(None) + # Default value depends on the category type + if cat.name == Init.Option.ident: + missing = strata.OptionArg(None) + elif cat.name == Init.Seq.ident: + missing = strata.Seq(()) + else: + raise ValueError(f"Unexpected category type for missing field: {cat.name}") op_args.append(OpArg(None, cat, missing=missing)) return Op(decl, op_args) @@ -221,7 +227,14 @@ def populate_op(name : str, op : type) -> Op: (ast.TypeVarTuple, "default_value"), ] else: - new_args = [] + # Python 3.11 - missing type_params added in 3.12 and type parameter nodes + new_args = [ + (ast.FunctionDef, "type_params"), + (ast.AsyncFunctionDef, "type_params"), + (ast.ClassDef, "type_params"), + ] + # TypeVar, ParamSpec, TypeVarTuple nodes don't exist in 3.11 but are in the 3.14 dialect + # We need to handle them separately since we can't reference non-existent AST nodes def check_op(d : strata.Dialect, name, op : type): opd = getattr(d, name) @@ -239,7 +252,8 @@ def check_op(d : strata.Dialect, name, op : type): assert (op, arg.name) in new_args, f"Extra field name {arg.name} in {opd.name}" k = arg.kind assert isinstance(k, SyntaxCat) - assert k.name == Init.Option.ident, f"Bad type for {op} {arg.name}" + # Extra fields can be Option (defaults to None) or Seq (defaults to empty list) + assert k.name == Init.Option.ident or k.name == Init.Seq.ident, f"Bad type for {op} {arg.name}: {k.name}" l += 1 def check_ast(d : strata.Dialect): From a03f106c2de414cd3665f13e8df4ce8406f5b4ea Mon Sep 17 00:00:00 2001 From: Joe Hendrix Date: Fri, 30 Jan 2026 15:19:10 -0800 Subject: [PATCH 4/4] Add expected failures --- Tools/Python/scripts/run_cpython_tests.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Tools/Python/scripts/run_cpython_tests.sh b/Tools/Python/scripts/run_cpython_tests.sh index 99ceacca..409ce57c 100755 --- a/Tools/Python/scripts/run_cpython_tests.sh +++ b/Tools/Python/scripts/run_cpython_tests.sh @@ -52,6 +52,11 @@ elif [ "$VER" == "3.12" ]; then expected_failures="$expected_failures;/test_lib2to3/data/bom.py" expected_failures="$expected_failures;/test_lib2to3/data/py2_test_grammar.py" expected_failures="$expected_failures;/test_lib2to3/data/crlf.py" +elif [ "$VER" == "3.11" ]; then + expected_failures="/tokenizedata/bad_coding.py" + expected_failures="$expected_failures;/tokenizedata/bad_coding2.py" + expected_failures="$expected_failures;/tokenizedata/badsyntax_3131.py" + expected_failures="$expected_failures;/tokenizedata/badsyntax_pep3120.py" else expected_failures="" fi