From 99b19cffc583ffdfd304818c9dbf96f8ab8eae73 Mon Sep 17 00:00:00 2001 From: Peter Birch Date: Tue, 2 Sep 2025 22:12:27 +0100 Subject: [PATCH 1/9] Adding syntax support for constants using type references --- examples/constants/spec.pt | 52 ++++++++++++++++++++++++++++++++ examples/constants/test.sh | 5 ++- packtype/common/expression.py | 4 +++ packtype/grammar/declarations.py | 49 ++++++++++++++++++++++++++++++ packtype/grammar/grammar.py | 35 +++++++++++++++------ packtype/grammar/packtype.lark | 6 +++- packtype/grammar/transformer.py | 17 ++++++++++- packtype/types/instance.py | 18 +++++++++++ packtype/types/package.py | 12 ++++++++ 9 files changed, 186 insertions(+), 12 deletions(-) create mode 100644 examples/constants/spec.pt create mode 100644 packtype/types/instance.py diff --git a/examples/constants/spec.pt b/examples/constants/spec.pt new file mode 100644 index 0000000..2aee189 --- /dev/null +++ b/examples/constants/spec.pt @@ -0,0 +1,52 @@ +package date_consts { + + // Basic definitions + DAYS_PER_YEAR : constant = 365 + DAYS_PER_WEEK : constant = 7 + HOURS_PER_DAY : constant[8] = 24 + MINS_PER_HOUR : constant = 60 + + // Weekdays + enum weekday_e { + MON : constant + TUE : constant + WED : constant + THU : constant + FRI : constant + SAT : constant + SUN : constant + } + + START_OF_WEEK : weekday_e = weekday_e::MON + END_OF_WEEK : weekday_e = weekday_e::SUN + + // Months + enum month_e { + JAN : constant + FEB : constant + MAR : constant + APR : constant + MAY : constant + JUN : constant + JUL : constant + AUG : constant + SEP : constant + OCT : constant + NOV : constant + DEC : constant + } + + // Date + struct date_t { + year : scalar[12] + month : month_e + day : scalar[5] + } + + CHRISTMAS : date_t = { + year = 2025 + month = month_e::DEC + day = 25 + } + +} diff --git a/examples/constants/test.sh b/examples/constants/test.sh index 3d994ba..067bb7b 100755 --- a/examples/constants/test.sh +++ b/examples/constants/test.sh @@ -11,4 +11,7 @@ this_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" export PYTHONPATH=${this_dir}/../..:$PYTHONPATH # Invoke packtype -python3 -m packtype --debug code package sv ${this_dir}/out spec.py +python3 -m packtype --debug code package sv ${this_dir}/out_py spec.py + +# Invoke packtype on Packtype syntax +python3 -m packtype --debug code package sv --type-filter none ${this_dir}/out_pt ${this_dir}/spec.pt diff --git a/packtype/common/expression.py b/packtype/common/expression.py index 8604cb2..fef7bd4 100644 --- a/packtype/common/expression.py +++ b/packtype/common/expression.py @@ -82,6 +82,10 @@ def evaluate(self, cb_lookup: Callable[[str], int]) -> int: # Check if the LHS is a _PT_BASE attribute elif hasattr(lhs, "_PT_BASE") and type(lhs).__name__ != "Constant": return lhs + # Check if the LHS is a foreign-reference (supports enum references) + elif type(lhs).__name__ == "ForeignRef": + f_type = cb_lookup(lhs.package) + return int(getattr(f_type, lhs.name)) # Otherwise, cast LHS to an integer else: return int(lhs) diff --git a/packtype/grammar/declarations.py b/packtype/grammar/declarations.py index e3da28e..693ab13 100644 --- a/packtype/grammar/declarations.py +++ b/packtype/grammar/declarations.py @@ -17,6 +17,7 @@ from ..types.struct import Struct from ..types.union import Union from ..types.wrap import build_from_fields +from .. import utils class Signed: @@ -143,6 +144,54 @@ def to_instance( return const +@dataclass +class FieldAssignment: + field: str + value: Expression + + +@dataclass() +class FieldAssignments: + assignments: list[FieldAssignment] + + +@dataclass() +class DeclInstance: + position: Position + name: str + ref: str + assignment: Expression | FieldAssignments + description: Description | None = None + + def to_instance( + self, + cb_resolve: Callable[ + [ + str, + ], + int | type[Base], + ], + ) -> Base: + # Get the referenced type + ref = cb_resolve(self.ref) + # If the assignment is a simple expression, referenced type needs to be + # either a scalar or an enum + if isinstance(self.assignment, Expression): + if not issubclass(ref, Scalar | Enum): + raise Expression( + f"{ref} must be a scalar or enum for simple expression assignment" + ) + return utils.unpack(ref, self.assignment.evaluate(cb_resolve)) + # If instead we get a field assigment, referenced type needs to be a + # struct or union + elif isinstance(self.assignment, FieldAssignments): + if not issubclass(ref, Struct | Union): + raise Expression( + f"{ref} must be a struct or union for field assignment" + ) + return ref(**{x.field: x.value.evaluate(cb_resolve) for x in self.assignment.assignments}) + + @dataclass() class DeclScalar: position: Position diff --git a/packtype/grammar/grammar.py b/packtype/grammar/grammar.py index 6d99409..a12c098 100644 --- a/packtype/grammar/grammar.py +++ b/packtype/grammar/grammar.py @@ -13,6 +13,7 @@ from ..common.logging import get_log from ..types.base import Base from ..types.constant import Constant +from ..types.instance import Instance from ..types.package import Package from ..types.wrap import build_from_fields from .declarations import ( @@ -20,6 +21,7 @@ DeclConstant, DeclEnum, DeclImport, + DeclInstance, DeclPackage, DeclScalar, DeclStruct, @@ -135,6 +137,8 @@ def _resolve(ref: str | ForeignRef) -> int: match decl: # Imports case DeclImport(): + # Check for name collisions + _check_collision(decl.foreign.name) # Resolve the package if (foreign_pkg := namespaces.get(decl.foreign.package, None)) is None: raise ImportError(f"Unknown package '{decl.foreign.package}'") @@ -144,8 +148,6 @@ def _resolve(ref: str | ForeignRef) -> int: f"'{decl.foreign.name}' not declared in package " f"'{decl.foreign.package}'" ) - # Check for name collisions - _check_collision(decl.foreign.name) # Remember this type if isinstance(foreign_type, Constant): known_entities[decl.foreign.name] = (foreign_type, decl.position) @@ -153,22 +155,24 @@ def _resolve(ref: str | ForeignRef) -> int: known_entities[decl.foreign.name] = (foreign_type, decl.position) # Aliases case DeclAlias(): + # Check for name collisions + _check_collision(decl.name) + # Attach to the package package._pt_attach( alias := decl.to_class(_resolve), name=decl.name, ) - # Check for name collisions - _check_collision(decl.name) # Remember this type known_entities[decl.name] = (alias, decl.position) # Build constants case DeclConstant(): + # Check for name collisions + _check_collision(decl.name) + # Attach to the package constant = decl.to_instance(_resolve) if keep_expression: constant._PT_EXPRESSION = decl.expr package._pt_attach_constant(decl.name, constant) - # Check for name collisions - _check_collision(decl.name) # Check for a constant override if decl.name in constant_overrides: get_log().debug( @@ -178,21 +182,34 @@ def _resolve(ref: str | ForeignRef) -> int: constant._pt_set(int(constant_overrides[decl.name])) # Remember this constant known_entities[decl.name] = (constant, decl.position) + # Build instances (constants that reference other types) + case DeclInstance(): + # Check for name collisions + _check_collision(decl.name) + # Attach to the package + package._pt_attach_instance( + decl.name, + inst := Instance(decl.name, decl.to_instance(_resolve)), + ) + # Remember this type + known_entities[decl.name] = (inst, decl.position) # Build aliases and scalars case DeclScalar() | DeclAlias(): + # Check for name collisions + _check_collision(decl.name) + # Attach to the package package._pt_attach( obj := decl.to_class(_resolve), name=decl.name, ) - # Check for name collisions - _check_collision(decl.name) # Remember this type known_entities[decl.name] = (obj, decl.position) # Build enums, structs, and unions case DeclEnum() | DeclStruct() | DeclUnion(): - package._pt_attach(obj := decl.to_class(source, _resolve)) # Check for name collisions _check_collision(decl.name) + # Attach to the package + package._pt_attach(obj := decl.to_class(source, _resolve)) # Remember this type known_entities[decl.name] = (obj, decl.position) case _: diff --git a/packtype/grammar/packtype.lark b/packtype/grammar/packtype.lark index 64a80bb..86a5ebf 100644 --- a/packtype/grammar/packtype.lark +++ b/packtype/grammar/packtype.lark @@ -82,7 +82,7 @@ decl_import: "import" foreign_ref // ============================================================================= // Example: local_type_t : foreign_type_t -decl_alias: name ":" (name | foreign_ref) dimensions? descr? +decl_alias: name ":" (name | foreign_ref) dimensions? ("=" (expr | field_assignments))? descr? // Example: MY_CONSTANT : constant[8] = 123 decl_constant: name ":" "constant"i dimension? "=" expr descr? @@ -90,6 +90,9 @@ decl_constant: name ":" "constant"i dimension? "=" expr descr? // Example: simple_type_t : scalar[8] decl_scalar: name ":" (signed|unsigned)? "scalar"i dimensions? descr? +field_assignment: name "=" expr descr? +?field_assignments: "{" field_assignment* "}" + // ============================================================================= // Enumerations // @@ -166,6 +169,7 @@ decl_union: "union"i name "{" descr? modifier* field* "}" expr: expr_term (OPERATOR expr_term)* ?expr_term: name + | foreign_ref | HEX | BINARY | DECIMAL diff --git a/packtype/grammar/transformer.py b/packtype/grammar/transformer.py index f3a89ba..5b6ef65 100644 --- a/packtype/grammar/transformer.py +++ b/packtype/grammar/transformer.py @@ -17,11 +17,14 @@ DeclEnum, DeclField, DeclImport, + DeclInstance, DeclPackage, DeclScalar, DeclStruct, DeclUnion, Description, + FieldAssignment, + FieldAssignments, ForeignRef, Modifier, Position, @@ -96,13 +99,25 @@ def dimensions(self, body): def foreign_ref(self, body): return ForeignRef(*body) + def field_assignment(self, body): + return FieldAssignment(*body) + + def field_assignments(self, body): + return FieldAssignments(assignments=body) + @v_args(meta=True) def decl_import(self, meta, body): return DeclImport(Position(meta.line, meta.column), *body) @v_args(meta=True) def decl_alias(self, meta, body): - return DeclAlias(Position(meta.line, meta.column), *body) + name = body.pop(0) + ref = body.pop(0) + dimensions = body.pop(0) if body and isinstance(body[0], DeclDimensions) else None + if isinstance(body[0], Expression | FieldAssignments): + return DeclInstance(Position(meta.line, meta.column), name, ref, body[0], body[1] if len(body) > 1 else None) + else: + return DeclAlias(Position(meta.line, meta.column), name, ref, dimensions, body[0] if body else None) @v_args(meta=True) def decl_constant(self, meta, body): diff --git a/packtype/types/instance.py b/packtype/types/instance.py new file mode 100644 index 0000000..5ff68d3 --- /dev/null +++ b/packtype/types/instance.py @@ -0,0 +1,18 @@ +# Copyright 2023-2025, Peter Birch, mailto:peter@intuity.io +# SPDX-License-Identifier: Apache-2.0 +# + +from .base import Base + + +class Instance: + + def __init__(self, name: str, instance: Base): + self.name = name + self.instance = instance + + def __str__(self): + return f"" + + def __repr__(self): + return str(self) diff --git a/packtype/types/package.py b/packtype/types/package.py index ca7c72b..2ba852c 100644 --- a/packtype/types/package.py +++ b/packtype/types/package.py @@ -13,6 +13,7 @@ from .base import Base from .constant import Constant from .enum import Enum +from .instance import Instance from .primitive import NumericType from .scalar import ScalarType from .struct import Struct @@ -41,6 +42,13 @@ def _pt_attach_constant(cls, fname: str, finst: Constant) -> Constant: cls._PT_FIELDS[finst] = fname return finst + @classmethod + def _pt_attach_instance(cls, fname: str, finst: Instance) -> Constant: + setattr(cls, fname, finst) + finst._PT_ATTACHED_TO = cls + cls._PT_FIELDS[finst] = fname + return finst + @classmethod def _pt_attach(cls, field: type[Base], name: str | None = None) -> Base: cls._PT_ATTACH.append(field) @@ -98,6 +106,10 @@ def _pt_fields(self) -> dict: def _pt_constants(self) -> Iterable[Constant]: return ((y, x) for x, y in self._pt_fields.items() if isinstance(x, Constant)) + @property + def _pt_instances(self) -> Iterable[Instance]: + return ((y, x) for x, y in self._pt_fields.items() if isinstance(x, Instance)) + def _pt_filter_for_class(self, ctype: type[Base]) -> Iterable[tuple[str, type[Base]]]: return ( (y, x) From 1c35da3ccd49b9a2914cc0c9434902c9e734c120 Mon Sep 17 00:00:00 2001 From: Peter Birch Date: Wed, 3 Sep 2025 10:16:21 +0100 Subject: [PATCH 2/9] Getting instances to render --- packtype/grammar/grammar.py | 3 +-- packtype/grammar/transformer.py | 2 +- packtype/templates/package.sv.mako | 26 ++++++++++++++++++++++++++ packtype/types/instance.py | 18 ------------------ packtype/types/package.py | 9 ++++----- packtype/utils/basic.py | 5 ++++- 6 files changed, 36 insertions(+), 27 deletions(-) delete mode 100644 packtype/types/instance.py diff --git a/packtype/grammar/grammar.py b/packtype/grammar/grammar.py index a12c098..12ba463 100644 --- a/packtype/grammar/grammar.py +++ b/packtype/grammar/grammar.py @@ -13,7 +13,6 @@ from ..common.logging import get_log from ..types.base import Base from ..types.constant import Constant -from ..types.instance import Instance from ..types.package import Package from ..types.wrap import build_from_fields from .declarations import ( @@ -189,7 +188,7 @@ def _resolve(ref: str | ForeignRef) -> int: # Attach to the package package._pt_attach_instance( decl.name, - inst := Instance(decl.name, decl.to_instance(_resolve)), + inst := decl.to_instance(_resolve), ) # Remember this type known_entities[decl.name] = (inst, decl.position) diff --git a/packtype/grammar/transformer.py b/packtype/grammar/transformer.py index 5b6ef65..c13e2c3 100644 --- a/packtype/grammar/transformer.py +++ b/packtype/grammar/transformer.py @@ -114,7 +114,7 @@ def decl_alias(self, meta, body): name = body.pop(0) ref = body.pop(0) dimensions = body.pop(0) if body and isinstance(body[0], DeclDimensions) else None - if isinstance(body[0], Expression | FieldAssignments): + if body and isinstance(body[0], Expression | FieldAssignments): return DeclInstance(Position(meta.line, meta.column), name, ref, body[0], body[1] if len(body) > 1 else None) else: return DeclAlias(Position(meta.line, meta.column), name, ref, dimensions, body[0] if body else None) diff --git a/packtype/templates/package.sv.mako b/packtype/templates/package.sv.mako index a5a2a80..95ecfaf 100644 --- a/packtype/templates/package.sv.mako +++ b/packtype/templates/package.sv.mako @@ -166,7 +166,33 @@ typedef union packed { %endif %endfor +// ============================================================================= +// Instances +// ============================================================================= + +%for name, inst in baseline._pt_instances: +// ${name | filters.constant} +localparam ${utils.get_name(inst) | filters.type} ${name | filters.constant} = \ + %if isinstance(inst, Struct): +'{ +<% first = True %>\ + %for _msb, _lsb, (name, field) in utils.struct.get_fields_msb_desc(inst): +<% value = int(field) %>\ + ${" " if first else ","} ${name}: \ + %if isinstance(field, ScalarType): +${utils.get_width(field)}'h${f"{value:X}"} + %else: +${utils.get_name(field) | filters.type}'(${value}) + %endif +<% first = False %>\ + %endfor +}\ + %else: +${name | filters.constant}'(${f"{int(inst):X}"})\ + %endif +; +%endfor endpackage : ${baseline._pt_name() | filters.package} /* verilator lint_on UNUSEDPARAM */ diff --git a/packtype/types/instance.py b/packtype/types/instance.py deleted file mode 100644 index 5ff68d3..0000000 --- a/packtype/types/instance.py +++ /dev/null @@ -1,18 +0,0 @@ -# Copyright 2023-2025, Peter Birch, mailto:peter@intuity.io -# SPDX-License-Identifier: Apache-2.0 -# - -from .base import Base - - -class Instance: - - def __init__(self, name: str, instance: Base): - self.name = name - self.instance = instance - - def __str__(self): - return f"" - - def __repr__(self): - return str(self) diff --git a/packtype/types/package.py b/packtype/types/package.py index 2ba852c..bcae10e 100644 --- a/packtype/types/package.py +++ b/packtype/types/package.py @@ -13,7 +13,6 @@ from .base import Base from .constant import Constant from .enum import Enum -from .instance import Instance from .primitive import NumericType from .scalar import ScalarType from .struct import Struct @@ -43,7 +42,7 @@ def _pt_attach_constant(cls, fname: str, finst: Constant) -> Constant: return finst @classmethod - def _pt_attach_instance(cls, fname: str, finst: Instance) -> Constant: + def _pt_attach_instance(cls, fname: str, finst: Base) -> Constant: setattr(cls, fname, finst) finst._PT_ATTACHED_TO = cls cls._PT_FIELDS[finst] = fname @@ -103,12 +102,12 @@ def _pt_fields(self) -> dict: return self._PT_FIELDS @property - def _pt_constants(self) -> Iterable[Constant]: + def _pt_constants(self) -> Iterable[tuple[str, Constant]]: return ((y, x) for x, y in self._pt_fields.items() if isinstance(x, Constant)) @property - def _pt_instances(self) -> Iterable[Instance]: - return ((y, x) for x, y in self._pt_fields.items() if isinstance(x, Instance)) + def _pt_instances(self) -> Iterable[tuple[str, Base]]: + return ((y, x) for x, y in self._pt_fields.items() if isinstance(x, Base) and not isinstance(x, Constant)) def _pt_filter_for_class(self, ctype: type[Base]) -> Iterable[tuple[str, type[Base]]]: return ( diff --git a/packtype/utils/basic.py b/packtype/utils/basic.py index 89c6e3f..e98615b 100644 --- a/packtype/utils/basic.py +++ b/packtype/utils/basic.py @@ -48,7 +48,10 @@ def get_name(ptype: type[Base] | Base) -> str: :param ptype: The Packtype definition to inspect :return: The name of the Packtype definition """ - if isinstance(ptype, Base) or issubclass(ptype, Base): + if isinstance(ptype, ScalarType) or (inspect.isclass(ptype) and issubclass(ptype, ScalarType)): + ptype = ptype if inspect.isclass(ptype) else type(ptype) + return ptype._PT_ATTACHED_TO._PT_FIELDS[ptype] + elif isinstance(ptype, Base) or (inspect.isclass(ptype) and issubclass(ptype, Base)): return ptype._pt_name() elif issubclass(ptype, Alias): return get_name(ptype._PT_ALIAS) From d79d95973430f77ba9a009c862a2a43b280fa16c Mon Sep 17 00:00:00 2001 From: Peter Birch Date: Wed, 3 Sep 2025 10:29:48 +0100 Subject: [PATCH 3/9] Adding unit tests for instances --- tests/grammar/test_instances.py | 92 +++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 tests/grammar/test_instances.py diff --git a/tests/grammar/test_instances.py b/tests/grammar/test_instances.py new file mode 100644 index 0000000..1a1c7ae --- /dev/null +++ b/tests/grammar/test_instances.py @@ -0,0 +1,92 @@ +# Copyright 2023-2025, Peter Birch, mailto:peter@intuity.io +# SPDX-License-Identifier: Apache-2.0 +# + +import pytest + +from packtype import Alias +from packtype.grammar import UnknownEntityError, parse_string +from packtype.types.scalar import ScalarType +from packtype.utils import get_width + +from ..fixtures import reset_registry + +assert reset_registry + + +def test_parse_enum_instance(): + """Parse an instance of an enum within a package""" + pkg = next( + parse_string( + """ + package the_package { + enum fruit_e { + APPLE : constant + ORANGE : constant + PEAR : constant + BANANA : constant + } + + BEST_FRUIT : fruit_e = fruit_e::APPLE + WORST_FRUIT : fruit_e = fruit_e::BANANA + } + """ + ) + ) + + assert isinstance(pkg.BEST_FRUIT, pkg.fruit_e) + assert pkg.BEST_FRUIT is pkg.fruit_e.APPLE + assert isinstance(pkg.WORST_FRUIT, pkg.fruit_e) + assert pkg.WORST_FRUIT is pkg.fruit_e.BANANA + + +def test_parse_struct_instance(): + """Parse an instance of a struct within a package""" + pkg = next( + parse_string( + """ + package the_package { + enum month_e { + JAN : constant + FEB : constant + MAR : constant + APR : constant + MAY : constant + JUN : constant + JUL : constant + AUG : constant + SEP : constant + OCT : constant + NOV : constant + DEC : constant + } + + struct date_t { + year : scalar[16] + month : month_e + day : scalar[5] + } + + CHRISTMAS : date_t = { + year = 2025 + month = month_e::DEC + day = 25 + } + + NEW_YEAR : date_t = { + year = 2026 + month = month_e::JAN + day = 1 + } + } + """ + ) + ) + assert isinstance(pkg.CHRISTMAS, pkg.date_t) + assert pkg.CHRISTMAS.year == 2025 + assert pkg.CHRISTMAS.month == pkg.month_e.DEC + assert pkg.CHRISTMAS.day == 25 + assert isinstance(pkg.NEW_YEAR, pkg.date_t) + assert pkg.NEW_YEAR.year == 2026 + assert pkg.NEW_YEAR.month == pkg.month_e.JAN + assert pkg.NEW_YEAR.day == 1 From 7fd82e6984cba03b8adf58096b3c7aa8a4194d5b Mon Sep 17 00:00:00 2001 From: Peter Birch Date: Wed, 3 Sep 2025 10:45:57 +0100 Subject: [PATCH 4/9] Adding docs --- docs/syntax/instance.md | 81 ++++++++++++++++++++++++++++++ examples/constants/spec.py | 41 ++++++++++++++- mkdocs.yml | 1 + packtype/templates/package.sv.mako | 2 +- packtype/utils/basic.py | 12 +++++ 5 files changed, 135 insertions(+), 2 deletions(-) create mode 100644 docs/syntax/instance.md diff --git a/docs/syntax/instance.md b/docs/syntax/instance.md new file mode 100644 index 0000000..367db5d --- /dev/null +++ b/docs/syntax/instance.md @@ -0,0 +1,81 @@ +Instances of types can be associated to a package, allowing structured constant +values to be declared. + +## Example + +The Packtype definition can either use a Python dataclass style or the Packtype +custom grammar: + +=== "Python (.py)" + + ```python linenums="1" + import packtype + from packtype import Constant, Scalar + + @packtype.package() + class MyPackage: + pass + + @MyPackage.enum() + class Month: + JAN : Constant + FEB : Constant + ... + DEC : Constant + + @MyPackage.struct() + class Date: + year : Scalar[12] + month : Month + day : Scalar[5] + + MyPackage._pt_attach_instance("SUMMER_START", Month.JUN) + MyPackage._pt_attach_instance("SUMMER_END", Month.AUG) + MyPackage._pt_attach_instance("CHRISTMAS", Date(year=2025, month=Month.DEC, day=25)) + ``` + +=== "Packtype (.pt)" + + ```sv linenums="1" + package my_package { + enum month_e { + JAN : constant + FEB : constant + ... + DEC : constant + } + + struct date_t { + year : scalar[12] + month : month_e + day : scalar[5] + } + + SUMMER_START : month_e = month_e::JUN + SUMMER_END : month_e = month_e::AUG + CHRISTMAS : date_t = { + year = 2025 + month = month_e::DEC + day = 25 + } + } + ``` + +As rendered to SystemVerilog: + +```sv linenums="1" +package my_package; + +localparam month_e SUMMER_START = month_e'(7); + +localparam month_e SUMMER_END = month_e'(9); + +// CHRISTMAS +localparam date_t CHRISTMAS = '{ + day: 5'h19 + , month: month_e'(11) + , year: 12'h7E9 +}; + +endpackage : my_package +``` diff --git a/examples/constants/spec.py b/examples/constants/spec.py index 08d1e8b..f8ba78d 100644 --- a/examples/constants/spec.py +++ b/examples/constants/spec.py @@ -3,7 +3,7 @@ # import packtype -from packtype import Constant +from packtype import Constant, Scalar @packtype.package() @@ -14,3 +14,42 @@ class DateConsts: DAYS_PER_WEEK: Constant = 7 HOURS_PER_DAY: Constant[8] = 24 MINS_PER_HOUR: Constant = 60 + + +@DateConsts.enum() +class Weekday: + MON : Constant + TUE : Constant + WED : Constant + THU : Constant + FRI : Constant + SAT : Constant + SUN : Constant + + +@DateConsts.enum() +class Month: + JAN : Constant + FEB : Constant + MAR : Constant + APR : Constant + MAY : Constant + JUN : Constant + JUL : Constant + AUG : Constant + SEP : Constant + OCT : Constant + NOV : Constant + DEC : Constant + + +@DateConsts.struct() +class Date: + year: Scalar[12] + month: Month + day: Scalar[5] + + +DateConsts._pt_attach_instance("START_OF_WEEK", Weekday.MON) +DateConsts._pt_attach_instance("END_OF_WEEK", Weekday.SUN) +DateConsts._pt_attach_instance("CHRISTMAS", Date(year=2025, month=Month.DEC, day=25)) diff --git a/mkdocs.yml b/mkdocs.yml index d2dfdae..a69c1e1 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -38,6 +38,7 @@ nav: - Arrays: syntax/arrays.md - Constants: syntax/constant.md - Enumerations: syntax/enum.md + - Instances: syntax/instance.md - Packages: syntax/package.md - Scalars: syntax/scalar.md - Structs: syntax/struct.md diff --git a/packtype/templates/package.sv.mako b/packtype/templates/package.sv.mako index 95ecfaf..5de888d 100644 --- a/packtype/templates/package.sv.mako +++ b/packtype/templates/package.sv.mako @@ -188,7 +188,7 @@ ${utils.get_name(field) | filters.type}'(${value}) %endfor }\ %else: -${name | filters.constant}'(${f"{int(inst):X}"})\ +${utils.get_name(inst) | filters.type}'(${f"{int(inst):X}"})\ %endif ; diff --git a/packtype/utils/basic.py b/packtype/utils/basic.py index e98615b..1437ec8 100644 --- a/packtype/utils/basic.py +++ b/packtype/utils/basic.py @@ -59,6 +59,18 @@ def get_name(ptype: type[Base] | Base) -> str: raise TypeError(f"{ptype} is not a Packtype definition") +def get_package(ptype: type[Base] | Base) -> type[Base] | None: + """ + Get the package a Packtype definition is attached to, if the type is not + associated to a package then None will be returned + :param ptype: The Packtype definition to inspect + :return: The Package to which this type is attached + """ + if not isinstance(ptype, Base) and not (inspect.isclass(ptype) and issubclass(ptype, Base)): + raise TypeError(f"{ptype} is not a Packtype definition") + return ptype._PT_ATTACHED_TO + + def get_doc(ptype: type[Base] | Base) -> str: """ Get the docstring of a Packtype definition From 998ecb85580f07870c168a9fb6f2f2b4832eec53 Mon Sep 17 00:00:00 2001 From: Peter Birch Date: Wed, 3 Sep 2025 10:46:12 +0100 Subject: [PATCH 5/9] Lint tidy --- examples/constants/spec.py | 38 ++++++++++++++++---------------- packtype/grammar/declarations.py | 14 +++++------- packtype/grammar/transformer.py | 12 ++++++++-- packtype/types/package.py | 6 ++++- tests/grammar/test_instances.py | 6 +---- 5 files changed, 41 insertions(+), 35 deletions(-) diff --git a/examples/constants/spec.py b/examples/constants/spec.py index f8ba78d..bff91fa 100644 --- a/examples/constants/spec.py +++ b/examples/constants/spec.py @@ -18,29 +18,29 @@ class DateConsts: @DateConsts.enum() class Weekday: - MON : Constant - TUE : Constant - WED : Constant - THU : Constant - FRI : Constant - SAT : Constant - SUN : Constant + MON: Constant + TUE: Constant + WED: Constant + THU: Constant + FRI: Constant + SAT: Constant + SUN: Constant @DateConsts.enum() class Month: - JAN : Constant - FEB : Constant - MAR : Constant - APR : Constant - MAY : Constant - JUN : Constant - JUL : Constant - AUG : Constant - SEP : Constant - OCT : Constant - NOV : Constant - DEC : Constant + JAN: Constant + FEB: Constant + MAR: Constant + APR: Constant + MAY: Constant + JUN: Constant + JUL: Constant + AUG: Constant + SEP: Constant + OCT: Constant + NOV: Constant + DEC: Constant @DateConsts.struct() diff --git a/packtype/grammar/declarations.py b/packtype/grammar/declarations.py index 693ab13..5d02fdb 100644 --- a/packtype/grammar/declarations.py +++ b/packtype/grammar/declarations.py @@ -6,6 +6,7 @@ from dataclasses import dataclass from pathlib import Path +from .. import utils from ..common.expression import Expression from ..types.alias import Alias from ..types.array import ArraySpec @@ -17,7 +18,6 @@ from ..types.struct import Struct from ..types.union import Union from ..types.wrap import build_from_fields -from .. import utils class Signed: @@ -178,18 +178,16 @@ def to_instance( # either a scalar or an enum if isinstance(self.assignment, Expression): if not issubclass(ref, Scalar | Enum): - raise Expression( - f"{ref} must be a scalar or enum for simple expression assignment" - ) + raise Expression(f"{ref} must be a scalar or enum for simple expression assignment") return utils.unpack(ref, self.assignment.evaluate(cb_resolve)) # If instead we get a field assigment, referenced type needs to be a # struct or union elif isinstance(self.assignment, FieldAssignments): if not issubclass(ref, Struct | Union): - raise Expression( - f"{ref} must be a struct or union for field assignment" - ) - return ref(**{x.field: x.value.evaluate(cb_resolve) for x in self.assignment.assignments}) + raise Expression(f"{ref} must be a struct or union for field assignment") + return ref( + **{x.field: x.value.evaluate(cb_resolve) for x in self.assignment.assignments} + ) @dataclass() diff --git a/packtype/grammar/transformer.py b/packtype/grammar/transformer.py index c13e2c3..8a46515 100644 --- a/packtype/grammar/transformer.py +++ b/packtype/grammar/transformer.py @@ -115,9 +115,17 @@ def decl_alias(self, meta, body): ref = body.pop(0) dimensions = body.pop(0) if body and isinstance(body[0], DeclDimensions) else None if body and isinstance(body[0], Expression | FieldAssignments): - return DeclInstance(Position(meta.line, meta.column), name, ref, body[0], body[1] if len(body) > 1 else None) + return DeclInstance( + Position(meta.line, meta.column), + name, + ref, + body[0], + body[1] if len(body) > 1 else None, + ) else: - return DeclAlias(Position(meta.line, meta.column), name, ref, dimensions, body[0] if body else None) + return DeclAlias( + Position(meta.line, meta.column), name, ref, dimensions, body[0] if body else None + ) @v_args(meta=True) def decl_constant(self, meta, body): diff --git a/packtype/types/package.py b/packtype/types/package.py index bcae10e..567ac0f 100644 --- a/packtype/types/package.py +++ b/packtype/types/package.py @@ -107,7 +107,11 @@ def _pt_constants(self) -> Iterable[tuple[str, Constant]]: @property def _pt_instances(self) -> Iterable[tuple[str, Base]]: - return ((y, x) for x, y in self._pt_fields.items() if isinstance(x, Base) and not isinstance(x, Constant)) + return ( + (y, x) + for x, y in self._pt_fields.items() + if isinstance(x, Base) and not isinstance(x, Constant) + ) def _pt_filter_for_class(self, ctype: type[Base]) -> Iterable[tuple[str, type[Base]]]: return ( diff --git a/tests/grammar/test_instances.py b/tests/grammar/test_instances.py index 1a1c7ae..3d424ba 100644 --- a/tests/grammar/test_instances.py +++ b/tests/grammar/test_instances.py @@ -2,12 +2,8 @@ # SPDX-License-Identifier: Apache-2.0 # -import pytest -from packtype import Alias -from packtype.grammar import UnknownEntityError, parse_string -from packtype.types.scalar import ScalarType -from packtype.utils import get_width +from packtype.grammar import parse_string from ..fixtures import reset_registry From 933843a0860db4c35a7a21277f12b7341938f181 Mon Sep 17 00:00:00 2001 From: Peter Birch Date: Wed, 3 Sep 2025 10:47:01 +0100 Subject: [PATCH 6/9] Bumping to 3.0.3 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index b5d7d9f..771ac70 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "poetry.masonry.api" [tool.poetry] name = "packtype" -version = "3.0.2" +version = "3.0.3" description = "Packed data structure specifications for multi-language hardware projects" authors = ["Peter Birch "] license = "Apache-2.0" From 9a4714c4b9ea670bcbfdfe30cdb3570d18dbd93e Mon Sep 17 00:00:00 2001 From: Peter Birch Date: Wed, 3 Sep 2025 11:00:53 +0100 Subject: [PATCH 7/9] Correcting 'raise Expression' --- packtype/grammar/declarations.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packtype/grammar/declarations.py b/packtype/grammar/declarations.py index 5d02fdb..40d795a 100644 --- a/packtype/grammar/declarations.py +++ b/packtype/grammar/declarations.py @@ -81,7 +81,7 @@ def resolve( if isinstance(raw_dim, Expression): eval_dims.append(raw_dim.evaluate(cb_resolve)) else: - raise Exception("Unexpected width type in DeclScalar") + raise ValueError("Unexpected width type in DeclScalar") return eval_dims @@ -178,13 +178,13 @@ def to_instance( # either a scalar or an enum if isinstance(self.assignment, Expression): if not issubclass(ref, Scalar | Enum): - raise Expression(f"{ref} must be a scalar or enum for simple expression assignment") + raise ValueError(f"{ref} must be a scalar or enum for simple expression assignment") return utils.unpack(ref, self.assignment.evaluate(cb_resolve)) # If instead we get a field assigment, referenced type needs to be a # struct or union elif isinstance(self.assignment, FieldAssignments): if not issubclass(ref, Struct | Union): - raise Expression(f"{ref} must be a struct or union for field assignment") + raise ValueError(f"{ref} must be a struct or union for field assignment") return ref( **{x.field: x.value.evaluate(cb_resolve) for x in self.assignment.assignments} ) From 76728e5073b19e93f0b16b9921504062703d8564 Mon Sep 17 00:00:00 2001 From: Peter Birch Date: Wed, 3 Sep 2025 11:05:18 +0100 Subject: [PATCH 8/9] Swap to TypeError --- packtype/grammar/declarations.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packtype/grammar/declarations.py b/packtype/grammar/declarations.py index 40d795a..d34124a 100644 --- a/packtype/grammar/declarations.py +++ b/packtype/grammar/declarations.py @@ -178,13 +178,13 @@ def to_instance( # either a scalar or an enum if isinstance(self.assignment, Expression): if not issubclass(ref, Scalar | Enum): - raise ValueError(f"{ref} must be a scalar or enum for simple expression assignment") + raise TypeError(f"{ref} must be a scalar or enum for simple expression assignment") return utils.unpack(ref, self.assignment.evaluate(cb_resolve)) # If instead we get a field assigment, referenced type needs to be a # struct or union elif isinstance(self.assignment, FieldAssignments): if not issubclass(ref, Struct | Union): - raise ValueError(f"{ref} must be a struct or union for field assignment") + raise TypeError(f"{ref} must be a struct or union for field assignment") return ref( **{x.field: x.value.evaluate(cb_resolve) for x in self.assignment.assignments} ) From 4020a7cf94fff93a96ceec7c32fabbab11465b4b Mon Sep 17 00:00:00 2001 From: Peter Birch Date: Wed, 3 Sep 2025 11:05:48 +0100 Subject: [PATCH 9/9] Correcting type annotation --- packtype/types/package.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packtype/types/package.py b/packtype/types/package.py index 567ac0f..27c5f28 100644 --- a/packtype/types/package.py +++ b/packtype/types/package.py @@ -42,7 +42,7 @@ def _pt_attach_constant(cls, fname: str, finst: Constant) -> Constant: return finst @classmethod - def _pt_attach_instance(cls, fname: str, finst: Base) -> Constant: + def _pt_attach_instance(cls, fname: str, finst: Base) -> Base: setattr(cls, fname, finst) finst._PT_ATTACHED_TO = cls cls._PT_FIELDS[finst] = fname