diff --git a/dist/requirements.txt b/dist/requirements.txt index e0dde0365..29952f9c3 100644 --- a/dist/requirements.txt +++ b/dist/requirements.txt @@ -1,2 +1,2 @@ wheel ~= 0.47.0 -twine ~= 6.2 +twine ~= 7.0 diff --git a/doc/Dependency.rst b/doc/Dependency.rst index a033fe4fa..eb0279d19 100644 --- a/doc/Dependency.rst +++ b/doc/Dependency.rst @@ -23,7 +23,7 @@ pyVHDLModel Package +--------------------------------------------------------+-------------+------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------+ | **Package** | **Version** | **License** | **Dependencies** | +========================================================+=============+==========================================================================================+=================================================================================================================================+ -| `pyTooling `__ | ≥8.15 | `Apache License, 2.0 `__ | *None* | +| `pyTooling `__ | ≥8.17 | `Apache License, 2.0 `__ | *None* | +--------------------------------------------------------+-------------+------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------+ @@ -57,7 +57,7 @@ the mandatory dependencies too. +---------------------------------------------------------------------+-------------+----------------------------------------------------------------------------------------+----------------------+ | `Coverage `__ | ≥7.15 | `Apache License, 2.0 `__ | *Not yet evaluated.* | +---------------------------------------------------------------------+-------------+----------------------------------------------------------------------------------------+----------------------+ -| `mypy `__ | ≥2.1 | `MIT `__ | *Not yet evaluated.* | +| `mypy `__ | ≥2.3 | `MIT `__ | *Not yet evaluated.* | +---------------------------------------------------------------------+-------------+----------------------------------------------------------------------------------------+----------------------+ | `typing-extensions `__ | ≥4.16 | `PSF-2.0 `__ | *Not yet evaluated.* | +---------------------------------------------------------------------+-------------+----------------------------------------------------------------------------------------+----------------------+ @@ -89,19 +89,19 @@ the mandatory dependencies too. +-------------------------------------------------------------------------------------------------+--------------+----------------------------------------------------------------------------------------------------------+----------------------+ | **Package** | **Version** | **License** | **Dependencies** | +=================================================================================================+==============+==========================================================================================================+======================+ -| `pyTooling `__ | ≥8.15 | `Apache License, 2.0 `__ | *None* | +| `pyTooling `__ | ≥8.17 | `Apache License, 2.0 `__ | *None* | +-------------------------------------------------------------------------------------------------+--------------+----------------------------------------------------------------------------------------------------------+----------------------+ | `Sphinx `__ | ≥9.1 | `BSD 3-Clause `__ | *Not yet evaluated.* | +-------------------------------------------------------------------------------------------------+--------------+----------------------------------------------------------------------------------------------------------+----------------------+ | `sphinx_rtd_theme `__ | ≥3.1 | `MIT `__ | *Not yet evaluated.* | +-------------------------------------------------------------------------------------------------+--------------+----------------------------------------------------------------------------------------------------------+----------------------+ -| `sphinxcontrib-mermaid `__ | ≥2.0 | `BSD `__ | *Not yet evaluated.* | +| `sphinxcontrib-mermaid `__ | ≥2.1 | `BSD `__ | *Not yet evaluated.* | +-------------------------------------------------------------------------------------------------+--------------+----------------------------------------------------------------------------------------------------------+----------------------+ | `autoapi `__ | ≥2.0.1 | `Apache License, 2.0 `__ | *Not yet evaluated.* | +-------------------------------------------------------------------------------------------------+--------------+----------------------------------------------------------------------------------------------------------+----------------------+ | !! `sphinx_fontawesome `__ | ≥0.0.6 | `GPL 2.0 `__ | *Not yet evaluated.* | +-------------------------------------------------------------------------------------------------+--------------+----------------------------------------------------------------------------------------------------------+----------------------+ -| `sphinx_autodoc_typehints `__ | ≥3.12 | `MIT `__ | *Not yet evaluated.* | +| `sphinx_autodoc_typehints `__ | ≥3.13 | `MIT `__ | *Not yet evaluated.* | +-------------------------------------------------------------------------------------------------+--------------+----------------------------------------------------------------------------------------------------------+----------------------+ @@ -129,7 +129,7 @@ install the mandatory dependencies too. +----------------------------------------------------------------------------+--------------+----------------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------+ | **Package** | **Version** | **License** | **Dependencies** | +============================================================================+==============+==========================================================================================================+======================================================================================================================================================+ -| `pyTooling `__ | ≥8.15 | `Apache License, 2.0 `__ | *None* | +| `pyTooling `__ | ≥8.17 | `Apache License, 2.0 `__ | *None* | +----------------------------------------------------------------------------+--------------+----------------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------+ | `wheel `__ | ≥0.47 | `MIT `__ | *Not yet evaluated.* | +----------------------------------------------------------------------------+--------------+----------------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------+ diff --git a/doc/LanguageModel/InterfaceItems.rst b/doc/LanguageModel/InterfaceItems.rst index 34b2a2019..cff8d2851 100644 --- a/doc/LanguageModel/InterfaceItems.rst +++ b/doc/LanguageModel/InterfaceItems.rst @@ -10,7 +10,7 @@ Interface items are used in generic, port and parameter declarations. .. rubric:: Class Hierarchy -.. inheritance-diagram:: pyVHDLModel.SyntaxModel.GenericConstantInterfaceItem pyVHDLModel.SyntaxModel.GenericTypeInterfaceItem pyVHDLModel.SyntaxModel.GenericProcedureInterfaceItem pyVHDLModel.SyntaxModel.GenericFunctionInterfaceItem pyVHDLModel.SyntaxModel.PortSignalInterfaceItem pyVHDLModel.SyntaxModel.ParameterConstantInterfaceItem pyVHDLModel.SyntaxModel.ParameterVariableInterfaceItem pyVHDLModel.SyntaxModel.ParameterSignalInterfaceItem pyVHDLModel.SyntaxModel.ParameterFileInterfaceItem +.. inheritance-diagram:: pyVHDLModel.SyntaxModel.GenericConstantInterfaceItem pyVHDLModel.SyntaxModel.GenericTypeInterfaceItem pyVHDLModel.SyntaxModel.GenericProcedureInterfaceItem pyVHDLModel.SyntaxModel.GenericFunctionInterfaceItem pyVHDLModel.SyntaxModel.PortSimpleSignalInterfaceItem pyVHDLModel.SyntaxModel.PortViewSignalInterfaceItem pyVHDLModel.SyntaxModel.ParameterConstantInterfaceItem pyVHDLModel.SyntaxModel.ParameterVariableInterfaceItem pyVHDLModel.SyntaxModel.ParameterSimpleSignalInterfaceItem pyVHDLModel.SyntaxModel.ParameterViewSignalInterfaceItem pyVHDLModel.SyntaxModel.ParameterFileInterfaceItem :parts: 1 @@ -136,16 +136,25 @@ Port Interface Item PortSignalInterfaceItem ----------------------- +``PortSignalInterfaceItem`` is now an abstract base-class for :class:`PortSimpleSignalInterfaceItem` +(``port (p : in bit);``) and :class:`PortViewSignalInterfaceItem` (``port (p : view MyView);``, +VHDL-2019 mode views) - see below. It is not meant to be instantiated directly. + +.. _vhdlmodel-portsimplesignal: + +PortSimpleSignalInterfaceItem +------------------------------ + .. todo:: Write documentation. -**Condensed definition of class** :class:`~pyVHDLModel.SyntaxModel.PortSignalInterfaceItem`: +**Condensed definition of class** :class:`~pyVHDLModel.SyntaxModel.PortSimpleSignalInterfaceItem`: .. code-block:: Python @export - class PortSignalInterfaceItem(Signal, PortInterfaceItem): + class PortSimpleSignalInterfaceItem(PortSignalInterfaceItem): # inherited from ModelEntity @property def Parent(self) -> ModelEntity: @@ -162,11 +171,43 @@ PortSignalInterfaceItem @property def DefaultExpression(self) -> BaseExpression: - # inherited from InterfaceItem + # inherited from InterfaceItemWithModeMixin @property def Mode(self) -> Mode: +.. _vhdlmodel-portviewsignal: + +PortViewSignalInterfaceItem +----------------------------- + +.. todo:: + + Write documentation. + +**Condensed definition of class** :class:`~pyVHDLModel.SyntaxModel.PortViewSignalInterfaceItem`: + +.. code-block:: Python + + @export + class PortViewSignalInterfaceItem(PortSignalInterfaceItem): + # inherited from ModelEntity + @property + def Parent(self) -> ModelEntity: + + # inherited from NamedEntity + @property + def Name(self) -> str: + + # inherited from Object; aliased as ModeViewIndication (a mode view reference occupies the + # same structural position as an ordinary subtype indication) + @property + def Subtype(self) -> Symbol: + + @property + def ModeViewIndication(self) -> ModeViewSymbol: + + .. _vhdlmodel-parameters: @@ -252,16 +293,26 @@ ParameterVariableInterfaceItem ParameterSignalInterfaceItem ---------------------------- +``ParameterSignalInterfaceItem`` is now an abstract base-class for +:class:`ParameterSimpleSignalInterfaceItem` (``procedure p(signal s : in bit);``) and +:class:`ParameterViewSignalInterfaceItem` (``procedure p(signal s : view MyView);``, VHDL-2019 mode +views) - see below. It is not meant to be instantiated directly. + +.. _vhdlmodel-parametersimplesignal: + +ParameterSimpleSignalInterfaceItem +----------------------------------- + .. todo:: Write documentation. -**Condensed definition of class** :class:`~pyVHDLModel.SyntaxModel.ParameterSignalInterfaceItem`: +**Condensed definition of class** :class:`~pyVHDLModel.SyntaxModel.ParameterSimpleSignalInterfaceItem`: .. code-block:: Python @export - class ParameterSignalInterfaceItem(Signal, ParameterInterfaceItem): + class ParameterSimpleSignalInterfaceItem(ParameterSignalInterfaceItem): # inherited from ModelEntity @property def Parent(self) -> ModelEntity: @@ -278,11 +329,43 @@ ParameterSignalInterfaceItem @property def DefaultExpression(self) -> BaseExpression: - # inherited from InterfaceItem + # inherited from InterfaceItemWithModeMixin @property def Mode(self) -> Mode: +.. _vhdlmodel-parameterviewsignal: + +ParameterViewSignalInterfaceItem +----------------------------------- + +.. todo:: + + Write documentation. + +**Condensed definition of class** :class:`~pyVHDLModel.SyntaxModel.ParameterViewSignalInterfaceItem`: + +.. code-block:: Python + + @export + class ParameterViewSignalInterfaceItem(ParameterSignalInterfaceItem): + # inherited from ModelEntity + @property + def Parent(self) -> ModelEntity: + + # inherited from NamedEntity + @property + def Name(self) -> str: + + # inherited from Object; aliased as ModeViewIndication (a mode view reference occupies the + # same structural position as an ordinary subtype indication) + @property + def Subtype(self) -> Symbol: + + @property + def ModeViewIndication(self) -> ModeViewSymbol: + + .. _vhdlmodel-parameterfile: diff --git a/doc/requirements.txt b/doc/requirements.txt index b53e0c358..2f0363e6e 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -9,9 +9,9 @@ docutils_stubs ~= 0.0.22 sphinx_rtd_theme ~= 3.1 # Sphinx Extenstions -sphinxcontrib-mermaid ~= 2.0 +sphinxcontrib-mermaid ~= 2.1 autoapi >= 2.0.1 sphinx_design ~= 0.7.0 sphinx-copybutton >= 0.5.2 -sphinx_autodoc_typehints ~= 3.12 -sphinx_reports ~= 0.11.0 +sphinx_autodoc_typehints ~= 3.13 +sphinx_reports ~= 0.11.1 diff --git a/pyVHDLModel/Association.py b/pyVHDLModel/Association.py index 1b3100a77..6699539d0 100644 --- a/pyVHDLModel/Association.py +++ b/pyVHDLModel/Association.py @@ -57,12 +57,24 @@ class AssociationItem(ModelEntity): """ A base-class for all association items. + + .. seealso:: + + * :class:`Generic association item ` + * :class:`Port association item ` + * :class:`Parameter association item ` """ - _formal: Nullable[Symbol] - _actual: ExpressionUnion + _formal: Nullable[Symbol] #: Reference to the formal part, or ``None`` for a positional association. + _actual: ExpressionUnion #: The actual part of this association. - def __init__(self, actual: ExpressionUnion, formal: Nullable[Symbol] = None) -> None: + def __init__(self, formal: Nullable[Symbol], actual: ExpressionUnion) -> None: + """ + Initializes an association item. + + :param formal: Reference to the formal part, or ``None`` for a positional association. + :param actual: The actual part of this association. + """ super().__init__() self._formal = formal @@ -74,13 +86,30 @@ def __init__(self, actual: ExpressionUnion, formal: Nullable[Symbol] = None) -> @readonly def Formal(self) -> Nullable[Symbol]: # TODO: can also be a conversion function !! + """ + Read-only property to access the formal (:attr:`_formal`). + + :returns: The formal, or ``None`` if not set. + """ return self._formal @readonly def Actual(self) -> ExpressionUnion: + """ + Read-only property to access the actual (:attr:`_actual`). + + :returns: The actual. + """ return self._actual def __str__(self) -> str: + """ + Formats the association item. + + **Format:** ``formal => actual``, or ``actual`` alone when positional + + :returns: Formatted association item. + """ if self._formal is None: return str(self._actual) else: diff --git a/pyVHDLModel/Base.py b/pyVHDLModel/Base.py index bb2668cc9..588bee341 100644 --- a/pyVHDLModel/Base.py +++ b/pyVHDLModel/Base.py @@ -35,8 +35,9 @@ Base-classes for the VHDL language model. """ from enum import unique, Enum -from typing import Type, Tuple, Iterable, Optional as Nullable, Union, cast +from typing import Type, Tuple, List, Iterable, Optional as Nullable, Union, cast +from pyTooling.Common import getFullyQualifiedName from pyTooling.Decorators import export, readonly from pyTooling.MetaClasses import ExtendedType @@ -90,9 +91,9 @@ class Mode(Enum): def __str__(self) -> str: """ - Formats the direction. + Formats the mode. - :returns: Formatted direction. + :returns: Formatted mode. """ return ("", "in", "out", "inout", "buffer", "linkage")[cast(int, self.value)] # TODO: check performance @@ -134,9 +135,28 @@ def Parent(self, parent: 'ModelEntity') -> None: self._parent = parent def GetAncestor(self, type: Type) -> 'ModelEntity': + """ + Return the closest ancestor of the given ``type`` found by walking the parent chain upwards. + + Iterates the parent chain - starting at this model entity - upwards (toward the root of the model) until an + ancestor of the requested type is found. + + :param type: Class (type) of the ancestor to find. + :returns: The closest ancestor of the requested type. + :raises VHDLModelException: If the root of the model is reached without finding an ancestor of the requested + type. + """ + # Deferred import to avoid a circular import: Base -> Exception -> Symbol -> Base. + from pyVHDLModel.Exception import VHDLModelException + parent = self._parent - while not isinstance(parent, type): + while parent is not None: + if isinstance(parent, type): + break + parent = parent._parent + else: + raise VHDLModelException(f"No ancestor of type '{type.__name__}' found for {self!r}.") return parent @@ -148,6 +168,19 @@ class NamedEntityMixin(metaclass=ExtendedType, mixin=True): Protected variables :attr:`_identifier` and :attr:`_normalizedIdentifier` are available to derived classes as well as two readonly properties :attr:`Identifier` and :attr:`NormalizedIdentifier` for public access. + + .. seealso:: + + * :class:`Attribute ` + * :class:`Alias ` + * :class:`Design unit ` + * :class:`Component ` + * :class:`Mode view declaration ` + * :class:`Interface package ` + * :class:`Default clock ` + * :class:`Subprogram ` + * :class:`Base type ` + * :class:`Library ` """ _identifier: str #: The identifier of a model entity. @@ -165,7 +198,7 @@ def __init__(self, identifier: str) -> None: @readonly def Identifier(self) -> str: """ - Returns a model entity's identifier (name). + Read-only property to access the model entity's identifier (:attr:`_identifier`). :returns: Name of a model entity. """ @@ -174,7 +207,7 @@ def Identifier(self) -> str: @readonly def NormalizedIdentifier(self) -> str: """ - Returns a model entity's normalized identifier (lower case name). + Read-only property to access the model entity's normalized identifier (:attr:`_normalizedIdentifier`). :returns: Normalized name of a model entity. """ @@ -188,6 +221,10 @@ class OptionallyNamedEntityMixin(metaclass=ExtendedType, mixin=True): Protected variables :attr:`_identifier` and :attr:`_normalizedIdentifier` are available to derived classes as well as two readonly properties :attr:`Identifier` and :attr:`NormalizedIdentifier` for public access. + + .. seealso:: + + * :class:`Interface group ` """ _identifier: Nullable[str] #: The identifier of a model entity. @@ -205,18 +242,18 @@ def __init__(self, identifier: Nullable[str]) -> None: @readonly def Identifier(self) -> Nullable[str]: """ - Returns a model entity's identifier (name). + Read-only property to access the model entity's optional identifier (:attr:`_identifier`). - :returns: Name of a model entity. + :returns: Name of a model entity, or ``None`` if unnamed. """ return self._identifier @readonly def NormalizedIdentifier(self) -> Nullable[str]: """ - Returns a model entity's normalized identifier (lower case name). + Read-only property to access the model entity's optional normalized identifier (:attr:`_normalizedIdentifier`). - :returns: Normalized name of a model entity. + :returns: Normalized name of a model entity, or ``None`` if unnamed. """ return self._normalizedIdentifier @@ -229,6 +266,12 @@ class MultipleNamedEntityMixin(metaclass=ExtendedType, mixin=True): Protected variables :attr:`_identifiers` and :attr:`_normalizedIdentifiers` are available to derived classes as well as two readonly properties :attr:`Identifiers` and :attr:`NormalizedIdentifiers` for public access. + + .. seealso:: + + * :class:`Mode view element ` + * :class:`Obj ` + * :class:`Record type element ` """ _identifiers: Tuple[str] #: A list of identifiers. @@ -246,7 +289,7 @@ def __init__(self, identifiers: Iterable[str]) -> None: @readonly def Identifiers(self) -> Tuple[str]: """ - Returns a model entity's tuple of identifiers (names). + Read-only property to access the model entity's identifiers (:attr:`_identifiers`). :returns: Tuple of identifiers. """ @@ -255,13 +298,69 @@ def Identifiers(self) -> Tuple[str]: @readonly def NormalizedIdentifiers(self) -> Tuple[str]: """ - Returns a model entity's tuple of normalized identifiers (lower case names). + Read-only property to access the model entity's normalized identifiers (:attr:`_normalizedIdentifiers`). :returns: Tuple of normalized identifiers. """ return self._normalizedIdentifiers +@export +def identifiersOf(item) -> Tuple[str, ...]: + """ + Return an item's identifier(s), regardless of how many names its declaration carries. + + VHDL entities come in two shapes: singularly named ones deriving from :class:`NamedEntityMixin` + (``generic (type T)``, ``GenericProcedureInterfaceItem``, ...) and plurally named ones deriving from + :class:`MultipleNamedEntityMixin`, where one declaration names several items at once + (``port (p1, p2 : in bit)``, and every ``Constant``/``Signal``/``Variable``/``File``-derived item). + + :param item: A singularly or plurally named entity. + :returns: The item's identifiers. + :raises TypeError: If the item is neither singularly nor plurally named. + + .. seealso:: + + :func:`normalizedIdentifiersOf` + The same, but normalized (lower case) - use that for dictionary keys and name resolution. + """ + if isinstance(item, MultipleNamedEntityMixin): + return item._identifiers + elif isinstance(item, NamedEntityMixin): + return (item._identifier, ) + + ex = TypeError(f"Item '{item}' is neither a NamedEntityMixin nor a MultipleNamedEntityMixin.") + ex.add_note(f"Got type '{getFullyQualifiedName(item)}'.") + raise ex + + +@export +def normalizedIdentifiersOf(item) -> Tuple[str, ...]: + """ + Return an item's normalized (lower case) identifier(s). + + This is the form used as dictionary keys and for name resolution, because VHDL identifiers are + case-insensitive. + + :param item: A singularly or plurally named entity. + :returns: The item's normalized identifiers. + :raises TypeError: If the item is neither singularly nor plurally named. + + .. seealso:: + + :func:`identifiersOf` + The same, but as written in the source - use that for rendering. + """ + if isinstance(item, MultipleNamedEntityMixin): + return item._normalizedIdentifiers + elif isinstance(item, NamedEntityMixin): + return (item._normalizedIdentifier, ) + + ex = TypeError(f"Item '{item}' is neither a NamedEntityMixin nor a MultipleNamedEntityMixin.") + ex.add_note(f"Got type '{getFullyQualifiedName(item)}'.") + raise ex + + @export class LabeledEntityMixin(metaclass=ExtendedType, mixin=True): """ @@ -269,6 +368,12 @@ class LabeledEntityMixin(metaclass=ExtendedType, mixin=True): protected variables :attr:`_label` and :attr:`_normalizedLabel` are available to derived classes as well as two readonly properties :attr:`Label` and :attr:`NormalizedLabel` for public access. + + .. seealso:: + + * :class:`Statement ` + * :class:`Concurrent block statement ` + * :class:`Concurrent case ` """ _label: Nullable[str] #: The label of a model entity. _normalizedLabel: Nullable[str] #: The normalized (lower case) label of a model entity. @@ -285,7 +390,7 @@ def __init__(self, label: Nullable[str]) -> None: @readonly def Label(self) -> Nullable[str]: """ - Returns a model entity's label. + Read-only property to access the model entity's label (:attr:`_label`). :returns: Label of a model entity. """ @@ -294,7 +399,7 @@ def Label(self) -> Nullable[str]: @readonly def NormalizedLabel(self) -> Nullable[str]: """ - Returns a model entity's normalized (lower case) label. + Read-only property to access the model entity's normalized label (:attr:`_normalizedLabel`). :returns: Normalized label of a model entity. """ @@ -323,7 +428,7 @@ def __init__(self, documentation: Nullable[str]) -> None: @readonly def Documentation(self) -> Nullable[str]: """ - Returns a model entity's associated documentation. + Read-only property to access the model entity's documentation (:attr:`_documentation`). :returns: Associated documentation of a model entity. """ @@ -332,9 +437,21 @@ def Documentation(self) -> Nullable[str]: @export class ConditionalMixin(metaclass=ExtendedType, mixin=True): - """A ``ConditionalMixin`` is a mixin-class for all statements with a condition.""" + """ + A ``ConditionalMixin`` is a mixin-class for all statements with a condition. - _condition: ExpressionUnion + .. seealso:: + + * :class:`Conditional branch mixin ` + * :class:`Assert statement mixin ` + * :class:`Conditional waveform ` + * :class:`Conditional expression ` + * :class:`While loop statement ` + * :class:`Loop control statement ` + * :class:`Wait statement ` + """ + + _condition: ExpressionUnion #: The condition guarding this statement. def __init__(self, condition: Nullable[ExpressionUnion] = None) -> None: """ @@ -360,43 +477,99 @@ def Condition(self) -> ExpressionUnion: @export class BranchMixin(metaclass=ExtendedType, mixin=True): - """A ``BranchMixin`` is a mixin-class for all statements with branches.""" + """ + A ``BranchMixin`` is a mixin-class for all statements with branches. + + .. seealso:: + + * :class:`Conditional branch mixin ` + * :class:`Else branch mixin ` + """ def __init__(self) -> None: + """ + Initializes a branch. + """ pass @export class ConditionalBranchMixin(BranchMixin, ConditionalMixin, mixin=True): - """A ``BaseBranch`` is a mixin-class for all branch statements with a condition.""" + """ + A ``BaseBranch`` is a mixin-class for all branch statements with a condition. + + .. seealso:: + + * :class:`If branch mixin ` + * :class:`Elsif branch mixin ` + """ def __init__(self, condition: ExpressionUnion) -> None: + """ + Initializes a conditional branch. + + :param condition: The condition guarding this statement. + """ super().__init__() ConditionalMixin.__init__(self, condition) @export class IfBranchMixin(ConditionalBranchMixin, mixin=True): - """A ``BaseIfBranch`` is a mixin-class for all if-branches.""" + """ + A ``BaseIfBranch`` is a mixin-class for all if-branches. + + .. seealso:: + + * :class:`If generate branch ` + * :class:`If branch ` + """ @export class ElsifBranchMixin(ConditionalBranchMixin, mixin=True): - """A ``BaseElsifBranch`` is a mixin-class for all elsif-branches.""" + """ + A ``BaseElsifBranch`` is a mixin-class for all elsif-branches. + + .. seealso:: + + * :class:`Elsif generate branch ` + * :class:`Elsif branch ` + """ @export class ElseBranchMixin(BranchMixin, mixin=True): - """A ``BaseElseBranch`` is a mixin-class for all else-branches.""" + """ + A ``BaseElseBranch`` is a mixin-class for all else-branches. + + .. seealso:: + + * :class:`Else generate branch ` + * :class:`Else branch ` + """ @export class ReportStatementMixin(metaclass=ExtendedType, mixin=True): - """A ``MixinReportStatement`` is a mixin-class for all report and assert statements.""" + """ + A ``MixinReportStatement`` is a mixin-class for all report and assert statements. + + .. seealso:: + + * :class:`Assert statement mixin ` + * :class:`Sequential report statement ` + """ - _message: Nullable[ExpressionUnion] - _severity: Nullable[ExpressionUnion] + _message: Nullable[ExpressionUnion] #: The reported message, or ``None`` if none was given. + _severity: Nullable[ExpressionUnion] #: The reported severity level, or ``None`` if none was given. def __init__(self, message: Nullable[ExpressionUnion] = None, severity: Nullable[ExpressionUnion] = None) -> None: + """ + Initializes a report statement. + + :param message: The reported message, or ``None`` if none was given. + :param severity: The reported severity level, or ``None`` if none was given. + """ self._message = message if message is not None: message.Parent = self @@ -405,50 +578,163 @@ def __init__(self, message: Nullable[ExpressionUnion] = None, severity: Nullable if severity is not None: severity.Parent = self - @property + @readonly def Message(self) -> Nullable[ExpressionUnion]: + """ + Read-only property to access the message (:attr:`_message`). + + :returns: The message, or ``None`` if not set. + """ return self._message - @property + @readonly def Severity(self) -> Nullable[ExpressionUnion]: + """ + Read-only property to access the severity (:attr:`_severity`). + + :returns: The severity, or ``None`` if not set. + """ return self._severity @export class AssertStatementMixin(ReportStatementMixin, ConditionalMixin, mixin=True): - """A ``MixinAssertStatement`` is a mixin-class for all assert statements.""" + """ + A ``MixinAssertStatement`` is a mixin-class for all assert statements. + + .. seealso:: + + * :class:`Concurrent assert statement ` + * :class:`Sequential assert statement ` + """ def __init__(self, condition: ExpressionUnion, message: Nullable[ExpressionUnion] = None, severity: Nullable[ExpressionUnion] = None) -> None: + """ + Initializes an assert statement. + + :param condition: The condition guarding this statement. + :param message: The reported message, or ``None`` if none was given. + :param severity: The reported severity level, or ``None`` if none was given. + """ super().__init__(message, severity) ConditionalMixin.__init__(self, condition) class BlockStatementMixin(metaclass=ExtendedType, mixin=True): - """A ``BlockStatement`` is a mixin-class for all block statements.""" + """ + A ``BlockStatement`` is a mixin-class for all block statements. + + .. seealso:: + + * :class:`Concurrent block statement ` + """ def __init__(self) -> None: + """ + Initializes a block statement. + """ pass @export class BaseChoice(ModelEntity): - """A ``Choice`` is a base-class for all choices.""" + """ + A ``Choice`` is a base-class for all choices. + + .. seealso:: + + * :class:`Concurrent choice ` + * :class:`Sequential choice ` + """ @export class BaseCase(ModelEntity): """ A ``Case`` is a base-class for all cases. + + .. seealso:: + + * :class:`Selected waveform ` + * :class:`Others selected waveform ` + * :class:`Selected expression ` + * :class:`Others selected expression ` + * :class:`Concurrent case ` + * :class:`Sequential case ` """ +@export +class ChoicesMixin(metaclass=ExtendedType, mixin=True): + """ + A mixin-class for all statements/entities holding a list of :class:`BaseChoice`. + + .. seealso:: + + * :class:`Selected waveform ` + * :class:`Selected expression ` + * :class:`Concurrent case ` + * :class:`Sequential case ` + """ + + _choices: List[BaseChoice] #: List of all choices selecting this alternative. + + def __init__(self, choices: Nullable[Iterable[BaseChoice]] = None) -> None: + """ + Initializes choices. + + :param choices: List of all choices selecting this alternative. + """ + self._choices = [] + if choices is not None: + for choice in choices: + self._choices.append(choice) + choice.Parent = self + + @readonly + def Choices(self) -> List[BaseChoice]: + """ + Read-only property to access the choices (:attr:`_choices`). + + :returns: List of choices. + """ + return self._choices + + @export class Range(ModelEntity): - _leftBound: ExpressionUnion - _rightBound: ExpressionUnion - _direction: Direction + """ + Base-class for all ranges. + + VHDL's ``range`` rule offers a range denoted by a name (:class:`RangeFromName`) as well as a range + given by explicit bounds (:class:`SimpleRange`). + + .. seealso:: + + * :class:`Simple range ` + * :class:`Range from name ` + """ + + +@export +class SimpleRange(Range): + """ + A range with both bounds given as expressions, e.g. ``0 to 7``. + """ + + _leftBound: ExpressionUnion #: The range's left bound. + _rightBound: ExpressionUnion #: The range's right bound. + _direction: Direction #: The range's direction, either ascending (``to``) or descending (``downto``). def __init__(self, leftBound: ExpressionUnion, rightBound: ExpressionUnion, direction: Direction, parent: Nullable[ModelEntity] = None) -> None: + """ + Initialize a simple range. + + :param leftBound: The range's left bound. + :param rightBound: The range's right bound. + :param direction: The range's direction (``to`` or ``downto``). + :param parent: The parent model entity. + """ super().__init__(parent) self._leftBound = leftBound @@ -459,28 +745,131 @@ def __init__(self, leftBound: ExpressionUnion, rightBound: ExpressionUnion, dire self._direction = direction - @property + @readonly def LeftBound(self) -> ExpressionUnion: + """ + Read-only property to access the range's left bound (:attr:`_leftBound`). + + :returns: The left bound. + """ return self._leftBound - @property + @readonly def RightBound(self) -> ExpressionUnion: + """ + Read-only property to access the range's right bound (:attr:`_rightBound`). + + :returns: The right bound. + """ return self._rightBound - @property + @readonly def Direction(self) -> Direction: + """ + Read-only property to access the range's direction (:attr:`_direction`). + + :returns: The direction. + """ return self._direction def __str__(self) -> str: + """ + Formats the simple range. + + **Format:** ``0 to 7`` + + :returns: Formatted simple range. + """ return f"{self._leftBound!s} {self._direction!s} {self._rightBound!s}" +@export +class RangeFromName(Range): + """ + A range denoted by a name, so its bounds are inferred from whatever that name references. + + The name is represented by a :class:`~pyVHDLModel.Symbol.Symbol`, so the bounds become available once + that symbol is resolved. A constrained subtype indication keeps its type mark *and* its range + constraint, because it's carried by a :class:`~pyVHDLModel.Symbol.ConstrainedScalarSubtypeSymbol`. + + .. note:: + + Two forms reach this class, because a parser can't tell them apart beyond "a name, optionally with + a range constraint": + + * a range attribute like ``vector'range``, and + * a discrete subtype indication like ``bit`` or ``integer range 0 to 7``. + + VHDL's grammar puts the latter one level up (``discrete_range ::= discrete_subtype_indication | + range``), so representing both as a range deviates from the rule split deliberately. + """ + + _symbol: 'Symbol' #: Reference to the name the range's bounds are inferred from. + + def __init__(self, symbol: 'Symbol', parent: Nullable[ModelEntity] = None) -> None: + """ + Initialize a range denoted by a name. + + :param symbol: The symbol referencing the range attribute or discrete subtype. + :param parent: The parent model entity. + """ + super().__init__(parent) + + self._symbol = symbol + symbol.Parent = self + + @readonly + def Symbol(self) -> 'Symbol': + """ + Read-only property to access the referenced symbol (:attr:`_symbol`). + + :returns: The symbol. + """ + return self._symbol + + def __str__(self) -> str: + """ + Formats the range denoted by a name. + + **Format:** ``v'range`` + + :returns: Formatted range denoted by a name. + """ + return f"{self._symbol!s}" + + @export class WaveformElement(ModelEntity): - _expression: ExpressionUnion - _after: ExpressionUnion + """ + Represents one element of a waveform in a signal assignment. + + A waveform element assigns a value (:data:`Expression`) after an optional delay (:data:`After`). + + .. admonition:: Example + + .. code-block:: VHDL + + s <= '1' after 5 ns; + -- ^^^ <- Expression + -- ^^^^ <- After + + .. seealso:: + + * :class:`Waveform of a simple assignment ` + * :class:`Waveform of one conditional branch ` + * :class:`Waveform of one selected alternative ` + """ + _expression: ExpressionUnion #: The value this waveform element assigns. + _after: ExpressionUnion #: The delay after which the value is assigned, or ``None`` if none was given. def __init__(self, expression: ExpressionUnion, after: Nullable[ExpressionUnion] = None, parent: Nullable[ModelEntity] = None) -> None: + """ + Initializes a waveform element. + + :param expression: The value this waveform element assigns. + :param after: The delay after which the value is assigned, or ``None`` if none was given. + :param parent: The parent model entity of this entity. + """ super().__init__(parent) self._expression = expression @@ -490,10 +879,20 @@ def __init__(self, expression: ExpressionUnion, after: Nullable[ExpressionUnion] if after is not None: after.Parent = self - @property + @readonly def Expression(self) -> ExpressionUnion: + """ + Read-only property to access the expression (:attr:`_expression`). + + :returns: The expression. + """ return self._expression - @property + @readonly def After(self) -> Expression: + """ + Read-only property to access the waveform element's delay (:attr:`_after`). + + :returns: The after. + """ return self._after diff --git a/pyVHDLModel/Common.py b/pyVHDLModel/Common.py index f717462a0..b978d5cea 100644 --- a/pyVHDLModel/Common.py +++ b/pyVHDLModel/Common.py @@ -39,9 +39,9 @@ from pyTooling.Decorators import export, readonly from pyTooling.MetaClasses import ExtendedType -from pyVHDLModel.Base import ModelEntity, LabeledEntityMixin +from pyVHDLModel.Base import ModelEntity, LabeledEntityMixin, BaseCase, BaseChoice, WaveformElement, ConditionalMixin, ChoicesMixin from pyVHDLModel.Expression import BaseExpression, QualifiedExpression, FunctionCall, TypeConversion, Literal -from pyVHDLModel.Symbol import Symbol +from pyVHDLModel.Symbol import Symbol, SignalSymbol, VariableSymbol from pyVHDLModel.Association import ParameterAssociationItem @@ -57,27 +57,57 @@ @export class AllowBlackboxMixin(metaclass=ExtendedType, mixin=True): + """ + A mixin-class for language entities that may permit blackboxes. + + The setting is inherited from the parent when not set locally (:data:`AllowBlackbox`). + + .. seealso:: + + * :class:`Concurrent block statement ` + * :class:`Generate branch ` + * :class:`Generate statement ` + * :class:`Concurrent case ` + * :class:`Package ` + * :class:`Entity ` + * :class:`Architecture ` + * :class:`Component ` + * :class:`Design ` + * :class:`Library ` + """ _allowBlackbox: Nullable[bool] #: Allow blackboxes for components in language entity. def __init__(self, allowBlackbox: Nullable[bool] = None) -> None: + """ + Initializes a hierarchical model entity allow for blackboxes. + + :param allowBlackbox: Allow blackboxes for components in language entity. + """ self._allowBlackbox = allowBlackbox @property def AllowBlackbox(self) -> bool: """ - Read-only property to check if a design supports blackboxes (:attr:`_allowBlackbox`). + Property to return whether a design supports blackboxes, inherited from the parent if not set locally + (:attr:`_allowBlackbox`). .. rubric:: Algorithm 1. If allow blackbox property is locally set, return the local value, 2. Otherwise, return allow blackbox value from parent object. - :returns: If blackboxes are allowed. + :returns: ``True``, if blackboxes are allowed. + :raises VHDLModelException: If neither a local value is set nor a parent object is available to inherit the + value from. """ - if self._allowBlackbox is None: - return self._parent.AllowBlackbox - else: + if self._allowBlackbox is not None: return self._allowBlackbox + elif self._parent is None: + from pyVHDLModel.Exception import VHDLModelException + + raise VHDLModelException(f"AllowBlackbox is not set on {self!r} and no parent is available to inherit it from.") + else: + return self._parent.AllowBlackbox @AllowBlackbox.setter def AllowBlackbox(self, value: Nullable[bool]) -> None: @@ -88,70 +118,561 @@ def AllowBlackbox(self, value: Nullable[bool]) -> None: class Statement(ModelEntity, LabeledEntityMixin): """ A ``Statement`` is a base-class for all statements. + + .. seealso:: + + * :class:`Concurrent statement ` + * :class:`Sequential statement ` """ def __init__(self, label: Nullable[str] = None, parent=None) -> None: + """ + Initializes a statement. + + :param label: The label of a model entity. + :param parent: The parent model entity of this entity. + """ super().__init__(parent) LabeledEntityMixin.__init__(self, label) @export class ProcedureCallMixin(metaclass=ExtendedType, mixin=True): - _procedure: Symbol # TODO: implement a ProcedureSymbol - _parameterMappings: List[ParameterAssociationItem] + """ + A mixin-class for statements calling a procedure. - def __init__(self, procedureName: Symbol, parameterMappings: Nullable[Iterable[ParameterAssociationItem]] = None) -> None: + The called procedure is available as :data:`Procedure`, its actual parameters as + :data:`ParameterAssociationItems`. + + .. seealso:: + + * :class:`Concurrent procedure call ` + * :class:`Sequential procedure call ` + """ + # TODO: implement a ProcedureSymbol + _procedure: Symbol #: Reference to the called procedure. + _parameterAssociationItems: List[ParameterAssociationItem] #: List of all parameter associations of the call. + + def __init__(self, procedureName: Symbol, parameterAssociationItems: Nullable[Iterable[ParameterAssociationItem]] = None) -> None: + """ + Initializes a procedure call. + + :param procedureName: Reference to the called procedure. + :param parameterAssociationItems: List of all parameter associations of the call. + """ self._procedure = procedureName procedureName.Parent = self # TODO: extract to mixin - self._parameterMappings = [] - if parameterMappings is not None: - for parameterMapping in parameterMappings: - self._parameterMappings.append(parameterMapping) + self._parameterAssociationItems = [] + if parameterAssociationItems is not None: + for parameterMapping in parameterAssociationItems: + self._parameterAssociationItems.append(parameterMapping) parameterMapping.Parent = self @readonly def Procedure(self) -> Symbol: + """ + Read-only property to access the procedure (:attr:`_procedure`). + + :returns: The procedure. + """ return self._procedure - @property - def ParameterMappings(self) -> List[ParameterAssociationItem]: - return self._parameterMappings + @readonly + def ParameterAssociationItems(self) -> List[ParameterAssociationItem]: + """ + Read-only property to access the parameter association items (:attr:`_parameterAssociationItems`). + + :returns: List of parameter association items. + """ + return self._parameterAssociationItems @export class AssignmentMixin(metaclass=ExtendedType, mixin=True): - """A mixin-class for all assignment statements.""" + """ + A mixin-class for all assignment statements. - _target: Symbol + .. seealso:: + + * :class:`Signal assignment mixin ` + * :class:`Variable assignment mixin ` + * :class:`Conditional variable assignment ` + * :class:`Sequential selected variable assignment ` + """ + + _target: Symbol #: Reference to the assignment's destination. def __init__(self, target: Symbol) -> None: + """ + Initializes an assignment. + + :param target: Reference to the assignment's destination. + """ self._target = target target.Parent = self - @property + @readonly def Target(self) -> Symbol: + """ + Read-only property to access the target (:attr:`_target`). + + :returns: The target. + """ return self._target @export class SignalAssignmentMixin(AssignmentMixin, mixin=True): - """A mixin-class for all signal assignment statements.""" + """ + A mixin-class for all signal assignment statements. + + .. seealso:: + + * :class:`Concurrent signal assignment ` + * :class:`Sequential signal assignment ` + * :class:`Conditional signal assignment ` + * :class:`Sequential selected signal assignment ` + * :class:`Signal force assignment ` + * :class:`Signal release assignment ` + """ + + @readonly + def Target(self) -> SignalSymbol: + """ + Read-only property to access the target (:attr:`_target`). + + :returns: The target. + """ + return self._target @export class VariableAssignmentMixin(AssignmentMixin, mixin=True): - """A mixin-class for all variable assignment statements.""" + """ + A mixin-class for all variable assignment statements. + + .. seealso:: + + * :class:`Sequential variable assignment ` + """ # FIXME: move to sequential? - _expression: ExpressionUnion + _expression: ExpressionUnion #: The assigned expression. + + def __init__(self, target: VariableSymbol, expression: ExpressionUnion) -> None: + """ + Initializes a variable assignment. - def __init__(self, target: Symbol, expression: ExpressionUnion) -> None: + :param target: Reference to the assignment's destination. + :param expression: The assigned expression. + """ super().__init__(target) self._expression = expression expression.Parent = self - @property + @readonly + def Target(self) -> VariableSymbol: + """ + Read-only property to access the target (:attr:`_target`). + + :returns: The target. + """ + return self._target + + @readonly def Expression(self) -> ExpressionUnion: + """ + Read-only property to access the expression (:attr:`_expression`). + + :returns: The expression. + """ return self._expression + + +@export +class WaveformMixin(metaclass=ExtendedType, mixin=True): + """ + A mixin-class for all statements/entities holding a waveform (a list of :class:`WaveformElement`). + + .. seealso:: + + * :class:`Conditional waveform ` + * :class:`Selected waveform ` + * :class:`Others selected waveform ` + * :class:`Concurrent simple signal assignment ` + * :class:`Sequential simple signal assignment ` + * :class:`Waveform element ` + """ + + _waveform: List[WaveformElement] #: List of all waveform elements, in the order they were written. + + def __init__(self, waveform: Iterable[WaveformElement]) -> None: + """ + Initializes a waveform. + + :param waveform: List of all waveform elements, in the order they were written. + """ + self._waveform = [] + for waveformElement in waveform: + self._waveform.append(waveformElement) + waveformElement.Parent = self + + @readonly + def Waveform(self) -> List[WaveformElement]: + """ + Read-only property to access the waveform (:attr:`_waveform`). + + :returns: List of waveform. + """ + return self._waveform + + +@export +class ExpressionMixin(metaclass=ExtendedType, mixin=True): + """ + A mixin-class for all statements/entities holding a single expression. + + .. seealso:: + + * :class:`Conditional expression ` + * :class:`Selected expression ` + * :class:`Others selected expression ` + * :class:`Concurrent selected signal assignment ` + * :class:`Sequential selected variable assignment ` + * :class:`Sequential selected signal assignment ` + * :class:`Signal force assignment ` + """ + + _expression: ExpressionUnion #: The expression held by this construct. + + def __init__(self, expression: ExpressionUnion) -> None: + """ + Initializes an expression. + + :param expression: The expression held by this construct. + """ + self._expression = expression + expression.Parent = self + + @readonly + def Expression(self) -> ExpressionUnion: + """ + Read-only property to access the expression (:attr:`_expression`). + + :returns: The expression. + """ + return self._expression + + +@export +class ConditionalWaveform(ModelEntity, WaveformMixin, ConditionalMixin): + """ + Represents one branch of a conditional signal assignment. + + Each branch pairs a waveform (:data:`Waveform`) with a condition (:data:`Condition`). The final + branch has no ``when``, so its condition is ``None``. + + .. admonition:: Example + + .. code-block:: VHDL + + s <= '1' when cond else '0'; + -- ^^^^^^^^^^^^^ <- this branch: Waveform=['1'], Condition=cond + -- ^^^ <- final branch (no ``when``): Waveform=['0'], Condition=None + + .. seealso:: + + * :class:`Waveform element ` + * :class:`Conditional expression ` + """ + + def __init__( + self, + waveform: Iterable[WaveformElement], + condition: Nullable[ExpressionUnion] = None, + parent: Nullable[ModelEntity] = None + ) -> None: + """ + Initializes a conditional waveform. + + :param waveform: List of all waveform elements, in the order they were written. + :param condition: The condition selecting this alternative. + :param parent: The parent model entity of this entity. + """ + super().__init__(parent) + WaveformMixin.__init__(self, waveform) + ConditionalMixin.__init__(self, condition) + + +@export +class ConditionalExpression(ModelEntity, ExpressionMixin, ConditionalMixin): + """ + Represents one branch of a conditional variable assignment. + + Each branch pairs an expression (:data:`Expression`) with a condition (:data:`Condition`). The + final branch has no ``when``, so its condition is ``None``. + + .. admonition:: Example + + .. code-block:: VHDL + + v := '1' when cond else '0'; + -- ^^^^^^^^^^^^^ <- this branch: Expression='1', Condition=cond + -- ^^^ <- final branch (no ``when``): Expression='0', Condition=None + + .. seealso:: + + * :class:`Conditional waveform ` + """ + + def __init__( + self, + expression: ExpressionUnion, + condition: Nullable[ExpressionUnion] = None, + parent: Nullable[ModelEntity] = None + ) -> None: + """ + Initializes a conditional expression. + + :param expression: The value assigned when the condition holds. + :param condition: The condition selecting this alternative. + :param parent: The parent model entity of this entity. + """ + super().__init__(parent) + ExpressionMixin.__init__(self, expression) + ConditionalMixin.__init__(self, condition) + + +@export +class ConditionalWaveformsMixin(metaclass=ExtendedType, mixin=True): + """ + A mixin-class for all statements holding a list of :class:`ConditionalWaveform` (both the + concurrent and sequential forms of a conditional signal assignment). + + .. seealso:: + + * :class:`Conditional signal assignment ` + * :class:`Conditional signal assignment ` """ + + _conditionalWaveforms: List[ConditionalWaveform] #: All alternatives, in order. + + def __init__(self, conditionalWaveforms: Iterable[ConditionalWaveform]) -> None: + """ + Initializes conditional waveforms. + + :param conditionalWaveforms: All alternatives, in order. + """ + self._conditionalWaveforms = [] + for conditionalWaveform in conditionalWaveforms: + self._conditionalWaveforms.append(conditionalWaveform) + conditionalWaveform.Parent = self + + @readonly + def ConditionalWaveforms(self) -> List[ConditionalWaveform]: + """ + Read-only property to access the conditional waveforms (:attr:`_conditionalWaveforms`). + + :returns: List of conditional waveforms. + """ + return self._conditionalWaveforms + + +@export +class SelectedWaveform(BaseCase, WaveformMixin, ChoicesMixin): + """ + Represents one alternative of a selected signal assignment. + + Each alternative pairs a waveform (:data:`Waveform`) with the choices selecting it + (:data:`Choices`). + + .. admonition:: Example + + .. code-block:: VHDL + + with sel select s <= '1' when '0', '0' when others; + -- ^^^^^^^^^^^^ <- this alternative: Choices=['0'], Waveform=['1'] + + .. seealso:: + + * :class:`Waveform element ` + * :class:`Selected expression ` + """ + + def __init__( + self, + choices: Iterable[BaseChoice], + waveform: Iterable[WaveformElement], + parent: Nullable[ModelEntity] = None + ) -> None: + """ + Initializes a selected waveform. + + :param choices: List of all choices selecting this alternative. + :param waveform: List of all waveform elements, in the order they were written. + :param parent: The parent model entity of this entity. + """ + super().__init__(parent) + WaveformMixin.__init__(self, waveform) + ChoicesMixin.__init__(self, choices) + + +@export +class OthersSelectedWaveform(BaseCase, WaveformMixin): + """ + Represents the ``others`` alternative of a selected signal assignment. + + It supplies the waveform (:data:`Waveform`) for every choice not named explicitly. + + .. admonition:: Example + + .. code-block:: VHDL + + with sel select s <= '1' when '0', '0' when others; + -- ^^^^^^^^^^^^^^^ <- the others alternative + """ + + def __init__(self, waveform: Iterable[WaveformElement], parent: Nullable[ModelEntity] = None) -> None: + """ + Initializes an others selected waveform. + + :param waveform: List of all waveform elements, in the order they were written. + :param parent: The parent model entity of this entity. + """ + super().__init__(parent) + WaveformMixin.__init__(self, waveform) + + +@export +class SelectedExpression(BaseCase, ExpressionMixin, ChoicesMixin): + """ + Represents one alternative of a selected variable assignment. + + Each alternative pairs an expression (:data:`Expression`) with the choices selecting it + (:data:`Choices`). + + .. admonition:: Example + + .. code-block:: VHDL + + with sel select v := '1' when '0', '0' when others; + -- ^^^^^^^^^^^^ <- this alternative: Choices=['0'], Expression='1' + + .. seealso:: + + * :class:`Selected waveform ` + """ + + def __init__( + self, + choices: Iterable[BaseChoice], + expression: ExpressionUnion, + parent: Nullable[ModelEntity] = None + ) -> None: + """ + Initializes a selected expression. + + :param choices: List of all choices selecting this alternative. + :param expression: The value assigned for the matching choices. + :param parent: The parent model entity of this entity. + """ + super().__init__(parent) + ExpressionMixin.__init__(self, expression) + ChoicesMixin.__init__(self, choices) + + +@export +class OthersSelectedExpression(BaseCase, ExpressionMixin): + """ + Represents the ``others`` alternative of a selected variable assignment. + + It supplies the expression (:data:`Expression`) for every choice not named explicitly. + + .. admonition:: Example + + .. code-block:: VHDL + + with sel select v := '1' when '0', '0' when others; + -- ^^^^^^^^^^^^^^^ <- the others alternative + """ + + def __init__(self, expression: ExpressionUnion, parent: Nullable[ModelEntity] = None) -> None: + """ + Initializes an others selected expression. + + :param expression: The value assigned for every unnamed choice. + :param parent: The parent model entity of this entity. + """ + super().__init__(parent) + ExpressionMixin.__init__(self, expression) + + +@export +class SelectedWaveformsMixin(metaclass=ExtendedType, mixin=True): + """ + A mixin-class for all statements holding a list of :class:`SelectedWaveform`/ + :class:`OthersSelectedWaveform` (both the concurrent and sequential forms of a selected signal + assignment). + + .. seealso:: + + * :class:`Concurrent selected signal assignment ` + * :class:`Sequential selected signal assignment ` + """ + + _selectedWaveforms: List[Union[SelectedWaveform, OthersSelectedWaveform]] #: All alternatives, in order. + + def __init__(self, selectedWaveforms: Iterable[Union[SelectedWaveform, OthersSelectedWaveform]]) -> None: + """ + Initializes selected waveforms. + + :param selectedWaveforms: All alternatives, in order. + """ + self._selectedWaveforms = [] + for selectedWaveform in selectedWaveforms: + self._selectedWaveforms.append(selectedWaveform) + selectedWaveform.Parent = self + + @readonly + def SelectedWaveforms(self) -> List[Union[SelectedWaveform, OthersSelectedWaveform]]: + """ + Read-only property to access the selected waveforms (:attr:`_selectedWaveforms`). + + :returns: List of selected waveforms. + """ + return self._selectedWaveforms + + +@export +class SelectedExpressionsMixin(metaclass=ExtendedType, mixin=True): + """ + A mixin-class for all statements holding a list of :class:`SelectedExpression`/ + :class:`OthersSelectedExpression`. + + .. seealso:: + + * :class:`Sequential selected variable assignment ` + """ + + _selectedExpressions: List[Union[SelectedExpression, OthersSelectedExpression]] #: All alternatives, in order. + + def __init__(self, selectedExpressions: Iterable[Union[SelectedExpression, OthersSelectedExpression]]) -> None: + """ + Initializes selected expressions. + + :param selectedExpressions: All alternatives, in order. + """ + self._selectedExpressions = [] + for selectedExpression in selectedExpressions: + self._selectedExpressions.append(selectedExpression) + selectedExpression.Parent = self + + @readonly + def SelectedExpressions(self) -> List[Union[SelectedExpression, OthersSelectedExpression]]: + """ + Read-only property to access the selected expressions (:attr:`_selectedExpressions`). + + :returns: List of selected expressions. + """ + return self._selectedExpressions diff --git a/pyVHDLModel/Concurrent.py b/pyVHDLModel/Concurrent.py index fd39e617b..dfce04d0a 100644 --- a/pyVHDLModel/Concurrent.py +++ b/pyVHDLModel/Concurrent.py @@ -40,16 +40,20 @@ from pyTooling.MetaClasses import ExtendedType from pyVHDLModel.Base import ModelEntity, LabeledEntityMixin, DocumentedEntityMixin, Range, BaseChoice, BaseCase, IfBranchMixin -from pyVHDLModel.Base import ElsifBranchMixin, ElseBranchMixin, AssertStatementMixin, BlockStatementMixin, WaveformElement -from pyVHDLModel.Regions import ConcurrentDeclarationRegionMixin +from pyVHDLModel.Base import ElsifBranchMixin, ElseBranchMixin, AssertStatementMixin, BlockStatementMixin, WaveformElement, ChoicesMixin +from pyVHDLModel.Regions import ConcurrentDeclarationRegionMixin, SequentialDeclarationRegionMixin from pyVHDLModel.Namespace import Namespace from pyVHDLModel.Name import Name from pyVHDLModel.Symbol import ComponentInstantiationSymbol, EntityInstantiationSymbol, ArchitectureSymbol, ConfigurationInstantiationSymbol +from pyVHDLModel.Symbol import SignalSymbol from pyVHDLModel.Expression import BaseExpression, QualifiedExpression, FunctionCall, TypeConversion, Literal from pyVHDLModel.Association import AssociationItem, ParameterAssociationItem -from pyVHDLModel.Interface import PortInterfaceItemMixin +from pyVHDLModel.Interface import PortInterfaceItemMixin, WithPortsMixin from pyVHDLModel.Common import Statement, ProcedureCallMixin, SignalAssignmentMixin, AllowBlackboxMixin -from pyVHDLModel.Sequential import SequentialStatement, SequentialStatementsMixin, SequentialDeclarationsMixin +from pyVHDLModel.Common import ConditionalWaveform, SelectedWaveform, OthersSelectedWaveform +from pyVHDLModel.Common import ConditionalWaveformsMixin, WaveformMixin +from pyVHDLModel.Common import ExpressionMixin, SelectedWaveformsMixin +from pyVHDLModel.Sequential import SequentialStatement, SequentialStatementsMixin ExpressionUnion = Union[ @@ -64,7 +68,19 @@ @export class ConcurrentStatement(Statement): - """A base-class for all concurrent statements.""" + """ + A base-class for all concurrent statements. + + .. seealso:: + + * :class:`Instantiation ` + * :class:`Process statement ` + * :class:`Concurrent procedure call ` + * :class:`Concurrent block statement ` + * :class:`Generate statement ` + * :class:`Concurrent signal assignment ` + * :class:`Concurrent assert statement ` + """ @export @@ -74,22 +90,31 @@ class ConcurrentStatementsMixin(metaclass=ExtendedType, mixin=True): .. seealso:: + * :class:`Concurrent block statement ` + * :class:`Generate branch ` + * :class:`Concurrent case ` + * :class:`For generate statement ` + * :class:`Entity ` + * :class:`Architecture ` + .. todo:: concurrent declaration region """ - _statements: List[ConcurrentStatement] + _statements: List[ConcurrentStatement] #: List of all concurrent statements in this construct. - _instantiations: Dict[str, 'Instantiation'] # TODO: add another instantiation class level for entity/configuration/component inst. - _blocks: Dict[str, 'ConcurrentBlockStatement'] - _generates: Dict[str, 'GenerateStatement'] - _hierarchy: Dict[str, Union['ConcurrentBlockStatement', 'GenerateStatement']] + # TODO: add another instantiation class level for entity/configuration/component inst. + _instantiations: Dict[str, 'Instantiation'] #: All instantiations, indexed by label. + _hierarchy: Dict[str, Union['ConcurrentBlockStatement', 'GenerateStatement']] #: All elements creating a hierarchy level (blocks and generates), in declaration order. def __init__(self, statements: Nullable[Iterable[ConcurrentStatement]] = None) -> None: + """ + Initializes concurrent statements. + + :param statements: List of all concurrent statements in this construct. + """ self._statements = [] self._instantiations = {} - self._blocks = {} - self._generates = {} self._hierarchy = {} if statements is not None: @@ -99,17 +124,19 @@ def __init__(self, statements: Nullable[Iterable[ConcurrentStatement]] = None) - @readonly def Statements(self) -> List[ConcurrentStatement]: + """ + Read-only property to access the statements (:attr:`_statements`). + + :returns: List of statements. + """ return self._statements def IterateInstantiations(self) -> Generator['Instantiation', None, None]: for instance in self._instantiations.values(): yield instance - for block in self._blocks.values(): - yield from block.IterateInstantiations() - - for generate in self._generates.values(): - yield from generate.IterateInstantiations() + for element in self._hierarchy.values(): + yield from element.IterateInstantiations() # TODO: move into _init__ def IndexStatements(self) -> None: @@ -117,7 +144,7 @@ def IndexStatements(self) -> None: if isinstance(statement, (EntityInstantiation, ComponentInstantiation, ConfigurationInstantiation)): self._instantiations[statement.NormalizedLabel] = statement elif isinstance(statement, (ForGenerateStatement, IfGenerateStatement, CaseGenerateStatement)): - self._generates[statement.NormalizedLabel] = statement + self._hierarchy[statement.NormalizedLabel] = statement statement.IndexStatement() elif isinstance(statement, ConcurrentBlockStatement): self._hierarchy[statement.NormalizedLabel] = statement @@ -128,100 +155,159 @@ def IndexStatements(self) -> None: class Instantiation(ConcurrentStatement): """ A base-class for all (component) instantiations. + + .. seealso:: + + * :class:`Component instantiation ` + * :class:`Entity instantiation ` + * :class:`Configuration instantiation ` """ - _genericAssociations: List[AssociationItem] - _portAssociations: List[AssociationItem] + _genericAssociationItems: List[AssociationItem] #: List of all generic associations in the generic map aspect. + _portAssociationItems: List[AssociationItem] #: List of all port associations in the port map aspect. def __init__( self, label: str, - genericAssociations: Nullable[Iterable[AssociationItem]] = None, - portAssociations: Nullable[Iterable[AssociationItem]] = None, + genericAssociationItems: Nullable[Iterable[AssociationItem]] = None, + portAssociationItems: Nullable[Iterable[AssociationItem]] = None, parent: Nullable[ModelEntity] = None ) -> None: + """ + Initializes an instantiation. + + :param label: The label of a model entity. + :param genericAssociationItems: List of all generic associations in the generic map aspect. + :param portAssociationItems: List of all port associations in the port map aspect. + :param parent: The parent model entity of this entity. + """ super().__init__(label, parent) # TODO: extract to mixin - self._genericAssociations = [] - if genericAssociations is not None: - for association in genericAssociations: - self._genericAssociations.append(association) + self._genericAssociationItems = [] + if genericAssociationItems is not None: + for association in genericAssociationItems: + self._genericAssociationItems.append(association) association.Parent = self # TODO: extract to mixin - self._portAssociations = [] - if portAssociations is not None: - for association in portAssociations: - self._portAssociations.append(association) + self._portAssociationItems = [] + if portAssociationItems is not None: + for association in portAssociationItems: + self._portAssociationItems.append(association) association.Parent = self @readonly - def GenericAssociations(self) -> List[AssociationItem]: - return self._genericAssociations + def GenericAssociationItems(self) -> List[AssociationItem]: + """ + Read-only property to access the generic association items (:attr:`_genericAssociationItems`). + + :returns: List of generic association items. + """ + return self._genericAssociationItems + + @readonly + def PortAssociationItems(self) -> List[AssociationItem]: + """ + Read-only property to access the port association items (:attr:`_portAssociationItems`). - @property - def PortAssociations(self) -> List[AssociationItem]: - return self._portAssociations + :returns: List of port association items. + """ + return self._portAssociationItems @export class ComponentInstantiation(Instantiation): """ - Represents a component instantiation by referring to a component name. + Represents a component instantiation. + + The instantiated component is available as :data:`Component`, the associations as + :data:`GenericAssociationItems` and :data:`PortAssociationItems`. The label is mandatory. .. admonition:: Example .. code-block:: VHDL - inst : component Counter; + inst : component Counter; + --^^^^ <- Label + -- ^^^^^^^ <- Component """ - _component: ComponentInstantiationSymbol + _component: ComponentInstantiationSymbol #: Reference to the instantiated component. def __init__( self, label: str, componentSymbol: ComponentInstantiationSymbol, - genericAssociations: Nullable[Iterable[AssociationItem]] = None, - portAssociations: Nullable[Iterable[AssociationItem]] = None, + genericAssociationItems: Nullable[Iterable[AssociationItem]] = None, + portAssociationItems: Nullable[Iterable[AssociationItem]] = None, parent: Nullable[ModelEntity] = None ) -> None: - super().__init__(label, genericAssociations, portAssociations, parent) + """ + Initializes a component instantiation. + + :param label: The label of a model entity. + :param componentSymbol: Reference to the instantiated component. + :param genericAssociationItems: List of all generic associations in the generic map aspect. + :param portAssociationItems: List of all port associations in the port map aspect. + :param parent: The parent model entity of this entity. + """ + super().__init__(label, genericAssociationItems, portAssociationItems, parent) self._component = componentSymbol componentSymbol.Parent = self - @property + @readonly def Component(self) -> ComponentInstantiationSymbol: + """ + Read-only property to access the component (:attr:`_component`). + + :returns: The component. + """ return self._component @export class EntityInstantiation(Instantiation): """ - Represents an entity instantiation by referring to an entity name with optional architecture name. + Represents a direct entity instantiation. + + The instantiated entity is available as :data:`Entity` and the optionally selected architecture + as :data:`Architecture`. The label is mandatory. .. admonition:: Example .. code-block:: VHDL - inst : entity work. Counter; + inst : entity work.Counter(rtl); + --^^^^ <- Label + -- ^^^^^^^^^^^^ <- Entity + -- ^^^ <- optional Architecture """ - _entity: EntityInstantiationSymbol - _architecture: ArchitectureSymbol + _entity: EntityInstantiationSymbol #: Reference to the directly instantiated entity. + _architecture: ArchitectureSymbol #: Reference to the selected architecture, if one was given. def __init__( self, label: str, entitySymbol: EntityInstantiationSymbol, architectureSymbol: Nullable[ArchitectureSymbol] = None, - genericAssociations: Nullable[Iterable[AssociationItem]] = None, - portAssociations: Nullable[Iterable[AssociationItem]] = None, + genericAssociationItems: Nullable[Iterable[AssociationItem]] = None, + portAssociationItems: Nullable[Iterable[AssociationItem]] = None, parent: Nullable[ModelEntity] = None ) -> None: - super().__init__(label, genericAssociations, portAssociations, parent) + """ + Initializes a direct entity instantiation. + + :param label: The label of a model entity. + :param entitySymbol: Reference to the directly instantiated entity. + :param architectureSymbol: Reference to the selected architecture, if one was given. + :param genericAssociationItems: List of all generic associations in the generic map aspect. + :param portAssociationItems: List of all port associations in the port map aspect. + :param parent: The parent model entity of this entity. + """ + super().__init__(label, genericAssociationItems, portAssociationItems, parent) self._entity = entitySymbol entitySymbol.Parent = self @@ -230,64 +316,100 @@ def __init__( if architectureSymbol is not None: architectureSymbol.Parent = self - @property + @readonly def Entity(self) -> EntityInstantiationSymbol: + """ + Read-only property to access the entity (:attr:`_entity`). + + :returns: The entity. + """ return self._entity - @property + @readonly def Architecture(self) -> ArchitectureSymbol: + """ + Read-only property to access the architecture (:attr:`_architecture`). + + :returns: The architecture. + """ return self._architecture @export class ConfigurationInstantiation(Instantiation): """ - Represents a configuration instantiation by referring to a configuration name. + Represents a configuration instantiation. + + The instantiated configuration is available as :data:`Configuration`. The label is mandatory. .. admonition:: Example .. code-block:: VHDL - inst : configuration Counter; + inst : configuration Counter; + --^^^^ <- Label + -- ^^^^^^^ <- Configuration """ - _configuration: ConfigurationInstantiationSymbol + _configuration: ConfigurationInstantiationSymbol #: Reference to the instantiated configuration. def __init__( self, label: str, configurationSymbol: ConfigurationInstantiationSymbol, - genericAssociations: Nullable[Iterable[AssociationItem]] = None, - portAssociations: Nullable[Iterable[AssociationItem]] = None, + genericAssociationItems: Nullable[Iterable[AssociationItem]] = None, + portAssociationItems: Nullable[Iterable[AssociationItem]] = None, parent: Nullable[ModelEntity] = None ) -> None: - super().__init__(label, genericAssociations, portAssociations, parent) + """ + Initializes a configuration instantiation. + + :param label: The label of a model entity. + :param configurationSymbol: Reference to the instantiated configuration. + :param genericAssociationItems: List of all generic associations in the generic map aspect. + :param portAssociationItems: List of all port associations in the port map aspect. + :param parent: The parent model entity of this entity. + """ + super().__init__(label, genericAssociationItems, portAssociationItems, parent) self._configuration = configurationSymbol configurationSymbol.Parent = self - @property + @readonly def Configuration(self) -> ConfigurationInstantiationSymbol: + """ + Read-only property to access the configuration (:attr:`_configuration`). + + :returns: The configuration. + """ return self._configuration @export -class ProcessStatement(ConcurrentStatement, SequentialDeclarationsMixin, SequentialStatementsMixin, DocumentedEntityMixin): +class ProcessStatement(ConcurrentStatement, SequentialDeclarationRegionMixin, SequentialStatementsMixin, DocumentedEntityMixin): """ - Represents a process statement with sensitivity list, sequential declaration region and sequential statements. + Represents a process statement. + + A process declares its own items (:data:`DeclaredItems`) and groups sequential statements + (:data:`Statements`). It may name a sensitivity list (:data:`SensitivityList`). .. admonition:: Example .. code-block:: VHDL - proc: process(Clock) - -- sequential declarations - begin - -- sequential statements - end process; + proc : process (clock) + --^^^^ <- optional Label + -- ^^^^^ <- optional SensitivityList + variable v : bit; + -- ^^^^^^^^^^^^^^^^^ <- DeclaredItems + begin + v := '1'; + -- ^^^^^^^^^ <- Statements + end process; """ - _sensitivityList: List[Name] # TODO: implement a SignalSymbol + # TODO: implement a SignalSymbol + _sensitivityList: List[Name] #: List of all signal names in the sensitivity list, or ``None`` if none was given. def __init__( self, @@ -298,8 +420,18 @@ def __init__( documentation: Nullable[str] = None, parent: Nullable[ModelEntity] = None ) -> None: + """ + Initializes a process statement. + + :param label: The label of a model entity. + :param declaredItems: List of all declared items in this sequential declaration region. + :param statements: List of all sequential statements in this construct. + :param sensitivityList: List of all signal names in the sensitivity list, or ``None`` if none was given. + :param documentation: The documentation comment associated with this declaration. + :param parent: The parent model entity of this entity. + """ super().__init__(label, parent) - SequentialDeclarationsMixin.__init__(self, declaredItems) + SequentialDeclarationRegionMixin.__init__(self, self._normalizedLabel, declaredItems) SequentialStatementsMixin.__init__(self, statements) DocumentedEntityMixin.__init__(self, documentation) @@ -311,29 +443,97 @@ def __init__( self._sensitivityList.append(signalSymbol) # signalSymbol._parent = self # FIXME: currently str are provided - @property + @ConcurrentStatement.Parent.setter + def Parent(self, parent: ModelEntity) -> None: + ConcurrentStatement.Parent.fset(self, parent) + + # Connect the process' namespace to the enclosing declaration region's namespace, so a declaration + # inside the process hides a same-named one from the architecture, block or generate around it. + self._namespace.ParentNamespace = parent._namespace + + @readonly def SensitivityList(self) -> List[Name]: + """ + Read-only property to access the sensitivity list (:attr:`_sensitivityList`). + + :returns: List of sensitivity list. + """ return self._sensitivityList @export class ConcurrentProcedureCall(ConcurrentStatement, ProcedureCallMixin): + """ + Represents a concurrent procedure call. + + Like every concurrent statement, it can carry an optional label (:data:`Label`). + + .. admonition:: Example + + .. code-block:: VHDL + + proc_lbl : proc(clock, open); + --^^^^^^^^ <- optional Label + -- ^^^^^^^^^^^^^^^^^ <- the call + + .. seealso:: + + * :class:`Sequential counterpart ` + """ def __init__( self, label: str, procedureName: Name, - parameterMappings: Nullable[Iterable[ParameterAssociationItem]] = None, + parameterAssociationItems: Nullable[Iterable[ParameterAssociationItem]] = None, parent: Nullable[ModelEntity] = None ) -> None: + """ + Initializes a concurrent procedure call. + + :param label: The label of a model entity. + :param procedureName: Reference to the called procedure. + :param parameterAssociationItems: List of all parameter associations of the call. + :param parent: The parent model entity of this entity. + """ super().__init__(label, parent) - ProcedureCallMixin.__init__(self, procedureName, parameterMappings) + ProcedureCallMixin.__init__(self, procedureName, parameterAssociationItems) @export -class ConcurrentBlockStatement(ConcurrentStatement, BlockStatementMixin, LabeledEntityMixin, ConcurrentDeclarationRegionMixin, ConcurrentStatementsMixin, DocumentedEntityMixin, AllowBlackboxMixin): - _portItems: List[PortInterfaceItemMixin] +class ConcurrentBlockStatement(ConcurrentStatement, BlockStatementMixin, LabeledEntityMixin, WithPortsMixin, ConcurrentDeclarationRegionMixin, ConcurrentStatementsMixin, DocumentedEntityMixin, AllowBlackboxMixin): + """ + Represents a block statement. + + A block groups concurrent statements (:data:`Statements`) and may declare its own items + (:data:`DeclaredItems`). It always forms a hierarchy level; independently of that, it may also + have a port clause (:data:`PortItems`). + + .. admonition:: Example + + .. code-block:: VHDL + + blk : block + --^^^ <- Label + port (bp : in bit); + -- ^^^^^^^^^^^ <- PortItems + port map (bp => clock); + signal inner : bit := '0'; + -- ^^^^^^^^^^^^^^^^^^^^^^^^^^ <- DeclaredItems + begin + inner <= bp; + -- ^^^^^^^^^^^^ <- Statements + end block; + + .. note:: + + The block's *port map aspect* (``port map (bp => clock);`` above) is not represented by the + model yet - there is no field for the association items, so it has no marker. - _namespace: Namespace + .. seealso:: + + * :class:`Generate statement ` + """ + _namespace: Namespace #: The namespace of this block's declarative region. def __init__( self, @@ -345,6 +545,17 @@ def __init__( allowBlackbox: Nullable[bool] = None, parent: Nullable[ModelEntity] = None ) -> None: + """ + Initializes a block statement. + + :param label: The label of a model entity. + :param portItems: List of all ports, in declaration order. + :param declaredItems: List of all declared items in this concurrent declaration region. + :param statements: List of all concurrent statements in this construct. + :param documentation: The documentation comment associated with this declaration. + :param allowBlackbox: Allow blackboxes for components in language entity. + :param parent: The parent model entity of this entity. + """ super().__init__(label, parent) self._namespace = Namespace(self._normalizedLabel) @@ -353,27 +564,24 @@ def __init__( BlockStatementMixin.__init__(self) LabeledEntityMixin.__init__(self, label) + WithPortsMixin.__init__(self, portItems) ConcurrentDeclarationRegionMixin.__init__(self, declaredItems) ConcurrentStatementsMixin.__init__(self, statements) DocumentedEntityMixin.__init__(self, documentation) AllowBlackboxMixin.__init__(self, allowBlackbox) - # TODO: extract to mixin - self._portItems = [] - if portItems is not None: - for item in portItems: - self._portItems.append(item) - item.Parent = self - @ConcurrentStatement.Parent.setter def Parent(self, parent: ModelEntity) -> None: ConcurrentStatement.Parent.fset(self, parent) self._namespace.ParentNamespace = parent._namespace - @property - def PortItems(self) -> List[PortInterfaceItemMixin]: - return self._portItems + + def IndexDeclaredItems(self) -> None: + """A block's ports share the declarative region of its declarative part.""" + self._IndexPortItems() + + super().IndexDeclaredItems() @export @@ -383,15 +591,15 @@ class GenerateBranch(ModelEntity, ConcurrentDeclarationRegionMixin, ConcurrentSt .. seealso:: - * :class:`If-generate branch ` - * :class:`Elsif-generate branch ` - * :class:`Else-generate branch ` + * :class:`If generate branch ` + * :class:`Elsif generate branch ` + * :class:`Else generate branch ` """ - _alternativeLabel: Nullable[str] - _normalizedAlternativeLabel: Nullable[str] + _alternativeLabel: Nullable[str] #: The branch's alternative label, if one was given. + _normalizedAlternativeLabel: Nullable[str] #: The normalized (lower case) alternative label. - _namespace: Namespace + _namespace: Namespace #: The namespace of this branch's declarative region. def __init__( self, @@ -401,6 +609,15 @@ def __init__( allowBlackbox: Nullable[bool] = None, parent: Nullable[ModelEntity] = None ) -> None: + """ + Initializes a generate branch. + + :param declaredItems: List of all declared items in this concurrent declaration region. + :param statements: List of all concurrent statements in this construct. + :param alternativeLabel: The branch's alternative label, if one was given. + :param allowBlackbox: Allow blackboxes for components in language entity. + :param parent: The parent model entity of this entity. + """ super().__init__(parent) self._alternativeLabel = alternativeLabel @@ -414,12 +631,22 @@ def __init__( ConcurrentStatementsMixin.__init__(self, statements) AllowBlackboxMixin.__init__(self, allowBlackbox) - @property + @readonly def AlternativeLabel(self) -> Nullable[str]: + """ + Read-only property to access the alternative label (:attr:`_alternativeLabel`). + + :returns: The alternative label, or ``None`` if not set. + """ return self._alternativeLabel - @property + @readonly def NormalizedAlternativeLabel(self) -> Nullable[str]: + """ + Read-only property to access the normalized alternative label (:attr:`_normalizedAlternativeLabel`). + + :returns: The normalized alternative label, or ``None`` if not set. + """ return self._normalizedAlternativeLabel @@ -452,6 +679,16 @@ def __init__( allowBlackbox: Nullable[bool] = None, parent: Nullable[ModelEntity] = None ) -> None: + """ + Initializes an if generate branch. + + :param condition: The condition guarding this statement. + :param declaredItems: List of all declared items in this concurrent declaration region. + :param statements: List of all concurrent statements in this construct. + :param alternativeLabel: The branch's alternative label, if one was given. + :param allowBlackbox: Allow blackboxes for components in language entity. + :param parent: The parent model entity of this entity. + """ super().__init__(declaredItems, statements, alternativeLabel, allowBlackbox, parent) IfBranchMixin.__init__(self, condition) @@ -485,6 +722,16 @@ def __init__( allowBlackbox: Nullable[bool] = None, parent: Nullable[ModelEntity] = None ) -> None: + """ + Initializes an elsif generate branch. + + :param condition: The condition guarding this statement. + :param declaredItems: List of all declared items in this concurrent declaration region. + :param statements: List of all concurrent statements in this construct. + :param alternativeLabel: The branch's alternative label, if one was given. + :param allowBlackbox: Allow blackboxes for components in language entity. + :param parent: The parent model entity of this entity. + """ super().__init__(declaredItems, statements, alternativeLabel, allowBlackbox, parent) ElsifBranchMixin.__init__(self, condition) @@ -517,6 +764,15 @@ def __init__( allowBlackbox: Nullable[bool] = None, parent: Nullable[ModelEntity] = None ) -> None: + """ + Initializes an else generate branch. + + :param declaredItems: List of all declared items in this concurrent declaration region. + :param statements: List of all concurrent statements in this construct. + :param alternativeLabel: The branch's alternative label, if one was given. + :param allowBlackbox: Allow blackboxes for components in language entity. + :param parent: The parent model entity of this entity. + """ super().__init__(declaredItems, statements, alternativeLabel, allowBlackbox, parent) ElseBranchMixin.__init__(self) @@ -524,13 +780,15 @@ def __init__( @export class GenerateStatement(ConcurrentStatement, AllowBlackboxMixin): """ - A base-class for all generate statements. + Represents the base-class of all generate statements. + + A generate statement replicates or conditionally elaborates concurrent statements. .. seealso:: - * :class:`If...generate statement ` - * :class:`Case...generate statement ` - * :class:`For...generate statement ` + * :class:`If generate statement ` + * :class:`Case generate statement ` + * :class:`For generate statement ` """ def __init__( @@ -539,6 +797,13 @@ def __init__( allowBlackbox: Nullable[bool] = None, parent: Nullable[ModelEntity] = None ) -> None: + """ + Initializes a generate statement. + + :param label: The label of a model entity. + :param allowBlackbox: Allow blackboxes for components in language entity. + :param parent: The parent model entity of this entity. + """ super().__init__(label, parent) AllowBlackboxMixin.__init__(self, allowBlackbox) @@ -554,19 +819,27 @@ def IndexStatement(self) -> None: @export class IfGenerateStatement(GenerateStatement): """ - Represents an if...generate statement. + Represents an if-generate statement. + + It has one ``if`` branch (:data:`IfBranch`), any number of ``elsif`` branches + (:data:`ElsifBranches`) and an optional ``else`` branch (:data:`ElseBranch`). The label is + mandatory and the branch conditions must be static expressions. .. admonition:: Example .. code-block:: VHDL - gen: if condition generate - -- ... - elsif condition generate - -- ... - else generate - -- ... - end generate; + gen : if WIDTH > 8 generate + --^^^ <- Label + -- ^^^^^^^^^^^^^^^^^^^^^ <- IfBranch + q <= '0'; + elsif WIDTH > 4 generate + --^^^^^^^^^^^^^^^^^^^^^^^^ <- ElsifBranches[0] + q <= '1'; + else generate + --^^^^^^^^^^^^^ <- ElseBranch + q <= 'Z'; + end generate; .. seealso:: @@ -574,11 +847,13 @@ class IfGenerateStatement(GenerateStatement): * :class:`If-generate branch ` * :class:`Elsif-generate branch ` * :class:`Else-generate branch ` + * :class:`Case-generate statement ` + * :class:`For-generate statement ` """ - _ifBranch: IfGenerateBranch - _elsifBranches: List[ElsifGenerateBranch] - _elseBranch: Nullable[ElseGenerateBranch] + _ifBranch: IfGenerateBranch #: The mandatory ``if`` branch. + _elsifBranches: List[ElsifGenerateBranch] #: List of all ``elsif`` branches, in the order they were written. + _elseBranch: Nullable[ElseGenerateBranch] #: The optional ``else`` branch, or ``None`` if none was given. def __init__( self, @@ -589,6 +864,16 @@ def __init__( allowBlackbox: Nullable[bool] = None, parent: Nullable[ModelEntity] = None ) -> None: + """ + Initializes an if-generate statement. + + :param label: The label of a model entity. + :param ifBranch: The mandatory ``if`` branch. + :param elsifBranches: List of all ``elsif`` branches, in the order they were written. + :param elseBranch: The optional ``else`` branch, or ``None`` if none was given. + :param allowBlackbox: Allow blackboxes for components in language entity. + :param parent: The parent model entity of this entity. + """ super().__init__(label, allowBlackbox, parent) self._ifBranch = ifBranch @@ -615,7 +900,7 @@ def Parent(self, parent: ModelEntity) -> None: # Connect namespaces namespace = self._ifBranch._namespace namespace.ParentNamespace = parent._namespace - if namespace._name == "": + if namespace._name is None: namespace._name = self._normalizedLabel for elseBranch in self._elsifBranches: @@ -624,16 +909,31 @@ def Parent(self, parent: ModelEntity) -> None: if self._elseBranch is not None: self._elseBranch._namespace.ParentNamespace = parent._namespace - @property + @readonly def IfBranch(self) -> IfGenerateBranch: + """ + Read-only property to access the if branch (:attr:`_ifBranch`). + + :returns: The if branch. + """ return self._ifBranch - @property + @readonly def ElsifBranches(self) -> List[ElsifGenerateBranch]: + """ + Read-only property to access the elsif branches (:attr:`_elsifBranches`). + + :returns: List of elsif branches. + """ return self._elsifBranches - @property + @readonly def ElseBranch(self) -> Nullable[ElseGenerateBranch]: + """ + Read-only property to access the else branch (:attr:`_elseBranch`). + + :returns: The else branch, or ``None`` if not set. + """ return self._elseBranch def IterateInstantiations(self) -> Generator[Instantiation, None, None]: @@ -653,57 +953,143 @@ def IndexStatement(self) -> None: @export class ConcurrentChoice(BaseChoice): - """A base-class for all concurrent choices (in case...generate statements).""" + """ + A base-class for all concurrent choices (in case...generate statements). + + .. seealso:: + + * :class:`Indexed generate choice ` + * :class:`Ranged generate choice ` + """ @export class IndexedGenerateChoice(ConcurrentChoice): - _expression: ExpressionUnion + """ + Represents a case-generate choice given by a single value. + + The value is available as :data:`Expression`. + + .. admonition:: Example + + .. code-block:: VHDL + + when 8 => + -- ^ <- Expression + """ + _expression: ExpressionUnion #: The expression this choice selects on. def __init__(self, expression: ExpressionUnion, parent: Nullable[ModelEntity] = None) -> None: + """ + Initializes a case-generate choice given by a single value. + + :param expression: The expression this choice selects on. + :param parent: The parent model entity of this entity. + """ super().__init__(parent) self._expression = expression expression.Parent = self - @property + @readonly def Expression(self) -> ExpressionUnion: + """ + Read-only property to access the expression (:attr:`_expression`). + + :returns: The expression. + """ return self._expression def __str__(self) -> str: + """ + Formats the indexed case-generate choice. + + **Format:** ``0`` + + :returns: Formatted indexed case-generate choice. + """ return str(self._expression) @export class RangedGenerateChoice(ConcurrentChoice): - _range: 'Range' + """ + Represents a case-generate choice given by a range. + + The range is available as :data:`Range`. + + .. admonition:: Example + + .. code-block:: VHDL + + when 0 to 3 => + -- ^^^^^^ <- Range + """ + _range: 'Range' #: The range this choice selects on. def __init__(self, rng: 'Range', parent: Nullable[ModelEntity] = None) -> None: + """ + Initializes a case-generate choice given by a range. + + :param rng: The range this choice selects on. + :param parent: The parent model entity of this entity. + """ super().__init__(parent) self._range = rng rng.Parent = self - @property + @readonly def Range(self) -> 'Range': + """ + Read-only property to access the range (:attr:`_range`). + + :returns: The range. + """ return self._range def __str__(self) -> str: + """ + Formats the ranged case-generate choice. + + **Format:** ``0 to 3`` + + :returns: Formatted ranged case-generate choice. + """ return str(self._range) @export -class ConcurrentCase(BaseCase, LabeledEntityMixin, ConcurrentDeclarationRegionMixin, ConcurrentStatementsMixin, AllowBlackboxMixin): - _namespace: Namespace +class ConcurrentCase(BaseCase, LabeledEntityMixin, ConcurrentDeclarationRegionMixin, ConcurrentStatementsMixin, AllowBlackboxMixin, ChoicesMixin): + """ + Represents the base-class of all alternatives of a case-generate statement. + + .. seealso:: + + * :class:`Generate case ` + * :class:`Others generate case ` + """ + _namespace: Namespace #: The namespace of this alternative's declarative region. def __init__( self, declaredItems: Nullable[Iterable] = None, statements: Nullable[Iterable[ConcurrentStatement]] = None, alternativeLabel: Nullable[str] = None, + choices: Nullable[Iterable[BaseChoice]] = None, allowBlackbox: Nullable[bool] = None, parent: Nullable[ModelEntity] = None ) -> None: + """ + Initializes a concurrent case. + + :param declaredItems: List of all declared items in this concurrent declaration region. + :param statements: List of all concurrent statements in this construct. + :param alternativeLabel: The alternative's label. + :param choices: List of all choices selecting this alternative. + :param allowBlackbox: Allow blackboxes for components in language entity. + :param parent: The parent model entity of this entity. + """ super().__init__(parent) LabeledEntityMixin.__init__(self, alternativeLabel) @@ -717,12 +1103,21 @@ def __init__( ConcurrentDeclarationRegionMixin.__init__(self, declaredItems) ConcurrentStatementsMixin.__init__(self, statements) AllowBlackboxMixin.__init__(self, allowBlackbox) + ChoicesMixin.__init__(self, choices) @export class GenerateCase(ConcurrentCase): - _choices: List[ConcurrentChoice] + """ + Represents one alternative of a case-generate statement, selected by its choices. + + .. admonition:: Example + .. code-block:: VHDL + + when 8 => + -- ^ <- Choices + """ def __init__( self, choices: Iterable[ConcurrentChoice], @@ -732,51 +1127,83 @@ def __init__( allowBlackbox: Nullable[bool] = None, parent: Nullable[ModelEntity] = None ) -> None: - super().__init__(declaredItems, statements, alternativeLabel, allowBlackbox, parent) + """ + Initializes a generate case. + + :param choices: List of all choices selecting this alternative. + :param declaredItems: List of all declared items in this concurrent declaration region. + :param statements: List of all concurrent statements in this construct. + :param alternativeLabel: The alternative's label. + :param allowBlackbox: Allow blackboxes for components in language entity. + :param parent: The parent model entity of this entity. + """ + super().__init__(declaredItems, statements, alternativeLabel, choices, allowBlackbox, parent) - # TODO: move to parent or grandparent - self._choices = [] - if choices is not None: - for choice in choices: - self._choices.append(choice) - choice.Parent = self + def __str__(self) -> str: + """ + Formats the case-generate alternative. - # TODO: move to parent or grandparent - @property - def Choices(self) -> List[ConcurrentChoice]: - return self._choices + **Format:** ``when 0 | 1 =>`` - def __str__(self) -> str: + :returns: Formatted case-generate alternative. + """ return "when {choices} =>".format(choices=" | ".join(str(c) for c in self._choices)) @export class OthersGenerateCase(ConcurrentCase): + """ + Represents the ``others`` alternative of a case-generate statement. + + It covers every choice not named explicitly. + + .. admonition:: Example + + .. code-block:: VHDL + + when others => + -- ^^^^^^ <- the choice + """ def __str__(self) -> str: + """ + Formats the ``others`` case-generate alternative. + + **Format:** ``when others =>`` + + :returns: Formatted ``others`` case-generate alternative. + """ return "when others =>" @export class CaseGenerateStatement(GenerateStatement): """ - Represents a case...generate statement. + Represents a case-generate statement. + + The expression being tested is available as :data:`SelectExpression`, the alternatives as + :data:`Cases`. The label is mandatory and the selector must be a static expression. .. admonition:: Example .. code-block:: VHDL - gen: case selector generate - case choice1 => - -- ... - case choice2 => - -- ... - case others => - -- ... - end generate; + gen : case MODE generate + --^^^ <- Label + -- ^^^^ <- SelectExpression + when 0 => q <= '0'; + -- ^^^^^^^^^^^^^^^^^^^ <- Cases[0] + when others => q <= '1'; + -- ^^^^^^^^^^^^^^^^^^^^^^^^ <- Cases[1] + end generate; + + .. seealso:: + + * :class:`If-generate statement ` + * :class:`For-generate statement ` """ - _expression: ExpressionUnion - _cases: List[GenerateCase] + _expression: ExpressionUnion #: The expression being tested; it must be static. + _cases: List[GenerateCase] #: List of all alternatives, in the order they were written. def __init__( self, @@ -786,6 +1213,15 @@ def __init__( allowBlackbox: Nullable[bool] = None, parent: Nullable[ModelEntity] = None ) -> None: + """ + Initializes a case-generate statement. + + :param label: The label of a model entity. + :param expression: The expression being tested; it must be static. + :param cases: List of all alternatives, in the order they were written. + :param allowBlackbox: Allow blackboxes for components in language entity. + :param parent: The parent model entity of this entity. + """ super().__init__(label, allowBlackbox, parent) self._expression = expression @@ -806,12 +1242,22 @@ def Parent(self, parent: ModelEntity) -> None: for case in self._cases: case._namespace.ParentNamespace = parent._namespace - @property + @readonly def SelectExpression(self) -> ExpressionUnion: + """ + Read-only property to access the select expression (:attr:`_expression`). + + :returns: The select expression. + """ return self._expression - @property + @readonly def Cases(self) -> List[GenerateCase]: + """ + Read-only property to access the cases (:attr:`_cases`). + + :returns: List of cases. + """ return self._cases def IterateInstantiations(self) -> Generator[Instantiation, None, None]: @@ -826,21 +1272,33 @@ def IndexStatement(self) -> None: @export class ForGenerateStatement(GenerateStatement, ConcurrentDeclarationRegionMixin, ConcurrentStatementsMixin): """ - Represents a for...generate statement. + Represents a for-generate statement. + + The loop index is available as :data:`LoopIndex`, the iteration range as :data:`Range` and the + generated statements as :data:`Statements`. The label is mandatory. .. admonition:: Example .. code-block:: VHDL - gen: for i in 0 to 3 generate - -- ... - end generate; + gen : for i in 0 to 3 generate + --^^^ <- Label + -- ^ <- LoopIndex + -- ^^^^^^ <- Range + q(i) <= '0'; + -- ^^^^^^^^^^^^ <- Statements + end generate; + + .. seealso:: + + * :class:`If-generate statement ` + * :class:`Case-generate statement ` """ - _loopIndex: str - _range: Range + _loopIndex: str #: The name of the generate loop's index. + _range: Range #: The range the generate loop iterates over. - _namespace: Namespace + _namespace: Namespace #: The namespace of the generate loop's declarative region. def __init__( self, @@ -852,6 +1310,17 @@ def __init__( allowBlackbox: Nullable[bool] = None, parent: Nullable[ModelEntity] = None ) -> None: + """ + Initializes a for-generate statement. + + :param label: The label of a model entity. + :param loopIndex: The name of the generate loop's index. + :param rng: The range the generate loop iterates over. + :param declaredItems: List of all declared items in this concurrent declaration region. + :param statements: List of all concurrent statements in this construct. + :param allowBlackbox: Allow blackboxes for components in language entity. + :param parent: The parent model entity of this entity. + """ super().__init__(label, allowBlackbox, parent) self._namespace = Namespace(self._normalizedLabel) @@ -872,12 +1341,22 @@ def Parent(self, parent: ModelEntity) -> None: self._namespace.ParentNamespace = parent._namespace - @property + @readonly def LoopIndex(self) -> str: + """ + Read-only property to access the loop index (:attr:`_loopIndex`). + + :returns: The loop index. + """ return self._loopIndex - @property + @readonly def Range(self) -> Range: + """ + Read-only property to access the range (:attr:`_range`). + + :returns: The range. + """ return self._range # IndexDeclaredItems = ConcurrentStatements.IndexDeclaredItems @@ -895,52 +1374,172 @@ def IterateInstantiations(self) -> Generator[Instantiation, None, None]: @export class ConcurrentSignalAssignment(ConcurrentStatement, SignalAssignmentMixin): """ - A base-class for concurrent signal assignments. + Represents the base-class of all concurrent signal assignments. .. seealso:: - * :class:`~pyVHDLModel.Concurrent.ConcurrentSimpleSignalAssignment` - * :class:`~pyVHDLModel.Concurrent.ConcurrentSelectedSignalAssignment` - * :class:`~pyVHDLModel.Concurrent.ConcurrentConditionalSignalAssignment` - """ - def __init__(self, label: str, target: Name, parent: Nullable[ModelEntity] = None) -> None: + * :class:`Concurrent simple signal assignment ` + * :class:`Concurrent selected signal assignment ` + * :class:`Conditional signal assignment ` """ + def __init__(self, label: str, target: SignalSymbol, parent: Nullable[ModelEntity] = None) -> None: + """ + Initializes a concurrent signal assignment. + + :param label: The label of a model entity. + :param target: Reference to the assignment's destination. + :param parent: The parent model entity of this entity. + """ super().__init__(label, parent) SignalAssignmentMixin.__init__(self, target) @export -class ConcurrentSimpleSignalAssignment(ConcurrentSignalAssignment): - _waveform: List[WaveformElement] +class ConcurrentSimpleSignalAssignment(ConcurrentSignalAssignment, WaveformMixin): + """ + Represents a simple concurrent signal assignment. - def __init__(self, label: str, target: Name, waveform: Iterable[WaveformElement], parent: Nullable[ModelEntity] = None) -> None: - super().__init__(label, target, parent) + The assignment's destination is available as :data:`Target`, its value as :data:`Waveform`. - # TODO: extract to mixin - self._waveform = [] - if waveform is not None: - for waveformElement in waveform: - self._waveform.append(waveformElement) - waveformElement.Parent = self + .. admonition:: Example + + .. code-block:: VHDL + + lbl : q <= '1'; + --^^^ <- optional Label + -- ^ <- Target + -- ^^^ <- Waveform + + .. seealso:: - @property - def Waveform(self) -> List[WaveformElement]: - return self._waveform + * :class:`Sequential counterpart ` + """ + def __init__(self, label: str, target: SignalSymbol, waveform: Iterable[WaveformElement], parent: Nullable[ModelEntity] = None) -> None: + """ + Initializes a simple concurrent signal assignment. + + :param label: The label of a model entity. + :param target: Reference to the assignment's destination. + :param waveform: List of all waveform elements, in the order they were written. + :param parent: The parent model entity of this entity. + """ + super().__init__(label, target, parent) + WaveformMixin.__init__(self, waveform) @export -class ConcurrentSelectedSignalAssignment(ConcurrentSignalAssignment): - def __init__(self, label: str, target: Name, expression: ExpressionUnion, parent: Nullable[ModelEntity] = None) -> None: +class ConcurrentSelectedSignalAssignment(ConcurrentSignalAssignment, ExpressionMixin, SelectedWaveformsMixin): + """ + Represents a selected concurrent signal assignment. + + The selector is available as :data:`Expression`, the alternatives as :data:`SelectedWaveforms`, + a list of :class:`~pyVHDLModel.Common.SelectedWaveform`. The model holds them in a list and has + no distinct field per alternative, so the markers below name list elements. + + .. admonition:: Example + + .. code-block:: VHDL + + lbl : with sel select q <= '1' when '0', '0' when others; + --^^^ <- optional Label + -- ^^^ <- Expression + -- ^ <- Target + -- ^^^^^^^^^^^^ <- SelectedWaveforms[0] + -- ^^^^^^^^^^^^^^^ <- SelectedWaveforms[1] + + .. seealso:: + + * :class:`Sequential counterpart ` + * :class:`Selected waveform ` + """ + + def __init__( + self, + label: str, + target: SignalSymbol, + expression: ExpressionUnion, + selectedWaveforms: Iterable[SelectedWaveform], + parent: Nullable[ModelEntity] = None + ) -> None: + """ + Initializes a selected concurrent signal assignment. + + :param label: The label of a model entity. + :param target: Reference to the assignment's destination. + :param expression: The selector expression. + :param selectedWaveforms: All alternatives, in order. + :param parent: The parent model entity of this entity. + """ super().__init__(label, target, parent) + ExpressionMixin.__init__(self, expression) + SelectedWaveformsMixin.__init__(self, selectedWaveforms) @export -class ConcurrentConditionalSignalAssignment(ConcurrentSignalAssignment): - def __init__(self, label: str, target: Name, expression: ExpressionUnion, parent: Nullable[ModelEntity] = None) -> None: +class ConcurrentConditionalSignalAssignment(ConcurrentSignalAssignment, ConditionalWaveformsMixin): + """ + Represents a conditional concurrent signal assignment. + + The alternatives are available as :data:`ConditionalWaveforms`, a list of + :class:`~pyVHDLModel.Common.ConditionalWaveform`. The model holds them in a list and has no + distinct field per alternative, so the markers below name list elements. + + .. admonition:: Example + + .. code-block:: VHDL + + lbl : q <= '1' when cond else '0'; + --^^^ <- optional Label + -- ^ <- Target + -- ^^^^^^^^^^^^^ <- ConditionalWaveforms[0] + -- ^^^ <- ConditionalWaveforms[1] + + .. seealso:: + + * :class:`Sequential counterpart ` + * :class:`Conditional waveform ` + """ + + def __init__( + self, + label: str, + target: SignalSymbol, + conditionalWaveforms: Iterable[ConditionalWaveform], + parent: Nullable[ModelEntity] = None + ) -> None: + """ + Initializes a conditional concurrent signal assignment. + + :param label: The label of a model entity. + :param target: Reference to the assignment's destination. + :param conditionalWaveforms: All alternatives, in order. + :param parent: The parent model entity of this entity. + """ super().__init__(label, target, parent) + ConditionalWaveformsMixin.__init__(self, conditionalWaveforms) @export class ConcurrentAssertStatement(ConcurrentStatement, AssertStatementMixin): + """ + Represents a concurrent assertion statement. + + The checked condition is available as :data:`Condition`, the optional report string as + :data:`Message` and the optional severity as :data:`Severity`. + + .. admonition:: Example + + .. code-block:: VHDL + + lbl : assert cond report "bad" severity note; + --^^^ <- optional Label + -- ^^^^ <- Condition + -- ^^^^^ <- optional Message + -- ^^^^ <- optional Severity + + .. seealso:: + + * :class:`Sequential counterpart ` + """ def __init__( self, condition: ExpressionUnion, @@ -949,5 +1548,14 @@ def __init__( label: Nullable[str] = None, parent: Nullable[ModelEntity] = None ) -> None: + """ + Initializes a concurrent assertion statement. + + :param condition: The condition guarding this statement. + :param message: The reported message, or ``None`` if none was given. + :param severity: The reported severity level, or ``None`` if none was given. + :param label: The label of a model entity. + :param parent: The parent model entity of this entity. + """ super().__init__(label, parent) AssertStatementMixin.__init__(self, condition, message, severity) diff --git a/pyVHDLModel/Configuration.py b/pyVHDLModel/Configuration.py new file mode 100644 index 000000000..4a7c2ed3a --- /dev/null +++ b/pyVHDLModel/Configuration.py @@ -0,0 +1,423 @@ +# ==================================================================================================================== # +# __ ___ _ ____ _ __ __ _ _ # +# _ __ _ \ \ / / | | | _ \| | | \/ | ___ __| | ___| | # +# | '_ \| | | \ \ / /| |_| | | | | | | |\/| |/ _ \ / _` |/ _ \ | # +# | |_) | |_| |\ V / | _ | |_| | |___| | | | (_) | (_| | __/ | # +# | .__/ \__, | \_/ |_| |_|____/|_____|_| |_|\___/ \__,_|\___|_| # +# |_| |___/ # +# ==================================================================================================================== # +# Authors: # +# Patrick Lehmann # +# # +# License: # +# ==================================================================================================================== # +# Copyright 2026-2026 Patrick Lehmann - Boetzingen, Germany # +# # +# Licensed under the Apache License, Version 2.0 (the "License"); # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at # +# # +# http://www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +# # +# SPDX-License-Identifier: Apache-2.0 # +# ==================================================================================================================== # +# +""" +This module contains parts of an abstract document language model for VHDL. + +Configurations: entity aspects, binding indications, component configurations (and the structurally +identical configuration specifications), and block configurations. +""" +from typing import List, Iterable, Union, Optional as Nullable + +from pyTooling.Decorators import export, readonly +from pyTooling.MetaClasses import ExtendedType + +from pyVHDLModel.Base import ModelEntity +from pyVHDLModel.Name import Name +from pyVHDLModel.Symbol import Symbol, EntitySymbol, ArchitectureSymbol, ConfigurationSymbol +from pyVHDLModel.Symbol import ComponentInstantiationSymbol +from pyVHDLModel.Association import GenericAssociationItem, PortAssociationItem + + +@export +class EntityAspect(ModelEntity): + """ + Base-class for the three forms an entity aspect can take in a binding indication: an entity + (optionally with an architecture), a configuration, or ``open``. + + .. admonition:: Example + + .. code-block:: VHDL + + for U1 : comp use entity work.sub(behav); + -- ^^^^^^^^^^^^^^^^^^^^^^ + + .. seealso:: + + * :class:`Entity aspect entity ` + * :class:`Entity aspect configuration ` + * :class:`Entity aspect open ` + """ + + +@export +class EntityAspectEntity(EntityAspect): + """ + Represents an entity aspect naming an entity, optionally with an architecture. + + .. admonition:: Example + + .. code-block:: VHDL + + use entity work.e_rest(rtl); + -- ^^^^^^^^^^^ <- Entity + -- ^^^ <- Architecture + """ + + _entity: EntitySymbol #: Reference to the named entity. + _architecture: Nullable[ArchitectureSymbol] #: Reference to the selected architecture, or ``None`` if none was given. + + def __init__( + self, + entity: EntitySymbol, + architecture: Nullable[ArchitectureSymbol] = None, + parent: Nullable[ModelEntity] = None + ) -> None: + """ + Initializes an entity aspect naming an entity, optionally with an architecture. + + :param entity: Reference to the named entity. + :param architecture: Reference to the selected architecture, or ``None`` if none was given. + :param parent: The parent model entity of this entity. + """ + super().__init__(parent) + + self._entity = entity + entity.Parent = self + + self._architecture = architecture + if architecture is not None: + architecture.Parent = self + + @readonly + def Entity(self) -> EntitySymbol: + """ + Read-only property to access the entity (:attr:`_entity`). + + :returns: The entity. + """ + return self._entity + + @readonly + def Architecture(self) -> Nullable[ArchitectureSymbol]: + """ + Read-only property to access the architecture (:attr:`_architecture`). + + :returns: The architecture, or ``None`` if not set. + """ + return self._architecture + + +@export +class EntityAspectConfiguration(EntityAspect): + """ + Represents an entity aspect naming a configuration. + + .. admonition:: Example + + .. code-block:: VHDL + + use configuration work.cfg; + -- ^^^^^^^^ <- Configuration + """ + + _configuration: ConfigurationSymbol #: Reference to the named configuration. + + def __init__(self, configuration: ConfigurationSymbol, parent: Nullable[ModelEntity] = None) -> None: + """ + Initializes an entity aspect naming a configuration. + + :param configuration: Reference to the named configuration. + :param parent: The parent model entity of this entity. + """ + super().__init__(parent) + + self._configuration = configuration + configuration.Parent = self + + @readonly + def Configuration(self) -> ConfigurationSymbol: + """ + Read-only property to access the configuration (:attr:`_configuration`). + + :returns: The configuration. + """ + return self._configuration + + +@export +class EntityAspectOpen(EntityAspect): + """ + Represents an open entity aspect, leaving the binding unspecified. + + .. admonition:: Example + + .. code-block:: VHDL + + use open; + -- ^^^^ <- the aspect + """ + + +@export +class BindingIndication(ModelEntity): + """ + Represents a binding indication: which design entity a component is bound to. + + The entity aspect is available as :data:`EntityAspect`, together with the generic and port maps + (:data:`GenericAssociations`, :data:`PortAssociations`). + """ + + _entityAspect: Nullable[EntityAspect] #: The bound design entity, or ``None`` if not given. + _genericAssociationItems: List[GenericAssociationItem] #: List of all generic associations in the generic map aspect. + _portAssociationItems: List[PortAssociationItem] #: List of all port associations in the port map aspect. + + def __init__( + self, + entityAspect: Nullable[EntityAspect] = None, + genericAssociationItems: Nullable[Iterable[GenericAssociationItem]] = None, + portAssociationItems: Nullable[Iterable[PortAssociationItem]] = None, + parent: Nullable[ModelEntity] = None + ) -> None: + """ + Initializes a binding indication. + + :param entityAspect: The bound design entity, or ``None`` if not given. + :param genericAssociationItems: List of all generic associations in the generic map aspect. + :param portAssociationItems: List of all port associations in the port map aspect. + :param parent: The parent model entity of this entity. + """ + super().__init__(parent) + + self._entityAspect = entityAspect + if entityAspect is not None: + entityAspect.Parent = self + + self._genericAssociationItems = [] + if genericAssociationItems is not None: + for association in genericAssociationItems: + self._genericAssociationItems.append(association) + association.Parent = self + + self._portAssociationItems = [] + if portAssociationItems is not None: + for association in portAssociationItems: + self._portAssociationItems.append(association) + association.Parent = self + + @readonly + def EntityAspect(self) -> Nullable[EntityAspect]: + """ + Read-only property to access the entity aspect (:attr:`_entityAspect`). + + :returns: The entity aspect, or ``None`` if not set. + """ + return self._entityAspect + + @readonly + def GenericAssociationItems(self) -> List[GenericAssociationItem]: + """ + Read-only property to access the generic association items (:attr:`_genericAssociationItems`). + + :returns: List of generic association items. + """ + return self._genericAssociationItems + + @readonly + def PortAssociationItems(self) -> List[PortAssociationItem]: + """ + Read-only property to access the port association items (:attr:`_portAssociationItems`). + + :returns: List of port association items. + """ + return self._portAssociationItems + + +@export +class AllInstantiationList(ModelEntity): + """ + Represents an instantiation list naming ``all`` instances of a component. + + .. admonition:: Example + + .. code-block:: VHDL + + for all : comp use entity work.sub(behav); + -- ^^^ <- the instantiation list + """ + + +@export +class OthersInstantiationList(ModelEntity): + """ + Represents an instantiation list naming all instances not configured elsewhere. + + .. admonition:: Example + + .. code-block:: VHDL + + for others : comp use entity work.sub(behav); + -- ^^^^^^ <- the instantiation list + """ + + +InstantiationListUnion = Union[List[Name], AllInstantiationList, OthersInstantiationList] + + +@export +class ComponentConfiguration(ModelEntity): + """ + Represents a component configuration (inside a block configuration), or - structurally identical + - a configuration specification (declared directly in an architecture's declarative part). + + .. admonition:: Example + + .. code-block:: VHDL + + for U1 : comp use entity work.sub(behav); + -- ^^ ^^^^ + -- | Component name + -- Instantiation list + """ + + _instantiationList: InstantiationListUnion #: The instances this configuration applies to. + _componentName: ComponentInstantiationSymbol #: Reference to the component being configured. + _bindingIndication: Nullable[BindingIndication] #: The binding indication, or ``None`` if none was given. + + def __init__( + self, + instantiationList: InstantiationListUnion, + componentName: ComponentInstantiationSymbol, + bindingIndication: Nullable[BindingIndication] = None, + parent: Nullable[ModelEntity] = None + ) -> None: + """ + Initializes a component configuration. + + :param instantiationList: The instances this configuration applies to. + :param componentName: Reference to the component being configured. + :param bindingIndication: The binding indication, or ``None`` if none was given. + :param parent: The parent model entity of this entity. + """ + super().__init__(parent) + + if isinstance(instantiationList, (AllInstantiationList, OthersInstantiationList)): + self._instantiationList = instantiationList + instantiationList.Parent = self + else: + self._instantiationList = [label for label in instantiationList] + for label in self._instantiationList: + label.Parent = self + + self._componentName = componentName + componentName.Parent = self + + self._bindingIndication = bindingIndication + if bindingIndication is not None: + bindingIndication.Parent = self + + @readonly + def InstantiationList(self) -> InstantiationListUnion: + """ + Read-only property to access the instantiation list (:attr:`_instantiationList`). + + :returns: The instantiation list. + """ + return self._instantiationList + + @readonly + def ComponentName(self) -> ComponentInstantiationSymbol: + """ + Read-only property to access the component name (:attr:`_componentName`). + + :returns: The component name. + """ + return self._componentName + + @readonly + def BindingIndication(self) -> Nullable[BindingIndication]: + """ + Read-only property to access the binding indication (:attr:`_bindingIndication`). + + :returns: The binding indication, or ``None`` if not set. + """ + return self._bindingIndication + + +@export +class BlockConfiguration(ModelEntity): + """ + Represents the configuration of one block: an architecture, a block statement or a generate body. + + Nested configurations are available as :data:`ConfigurationItems`. + + .. admonition:: Example + + .. code-block:: VHDL + + for rtl + -- ^^^ <- Block + end for; + """ + + _blockSpecification: Symbol #: The configured block. + _items: List[Union["BlockConfiguration", ComponentConfiguration]] #: Nested configurations. + + def __init__( + self, + blockSpecification: Symbol, + items: Nullable[Iterable[Union["BlockConfiguration", ComponentConfiguration]]] = None, + parent: Nullable[ModelEntity] = None + ) -> None: + """ + Initializes a block configuration. + + :param blockSpecification: The configured block. + :param items: Nested configurations. + :param parent: The parent model entity of this entity. + """ + super().__init__(parent) + + self._blockSpecification = blockSpecification + blockSpecification.Parent = self + + self._items = [] + if items is not None: + for item in items: + self._items.append(item) + item.Parent = self + + @readonly + def BlockSpecification(self) -> Symbol: + """ + Read-only property to access the block specification (:attr:`_blockSpecification`). + + :returns: The block specification. + """ + return self._blockSpecification + + @readonly + def Items(self) -> List[Union["BlockConfiguration", ComponentConfiguration]]: + """ + Read-only property to access the items (:attr:`_items`). + + :returns: List of items. + """ + return self._items diff --git a/pyVHDLModel/Declaration.py b/pyVHDLModel/Declaration.py index 5e3f66a13..f59915014 100644 --- a/pyVHDLModel/Declaration.py +++ b/pyVHDLModel/Declaration.py @@ -42,7 +42,7 @@ from pyVHDLModel.Base import ModelEntity, NamedEntityMixin, DocumentedEntityMixin from pyVHDLModel.Expression import BaseExpression, QualifiedExpression, FunctionCall, TypeConversion, Literal from pyVHDLModel.Name import Name -from pyVHDLModel.Symbol import Symbol +from pyVHDLModel.Symbol import Symbol, SubtypeSymbol @@ -96,7 +96,7 @@ class Attribute(ModelEntity, NamedEntityMixin, DocumentedEntityMixin): attribute TotalBits : natural; """ - _subtype: Symbol + _subtype: Symbol #: Reference to the attribute's subtype. def __init__( self, @@ -105,6 +105,14 @@ def __init__( documentation: Nullable[str] = None, parent: Nullable[ModelEntity] = None ) -> None: + """ + Initializes an attribute declaration. + + :param identifier: The identifier of a model entity. + :param subtype: Reference to the attribute's subtype. + :param documentation: The documentation comment associated with this declaration. + :param parent: The parent model entity of this entity. + """ super().__init__(parent) NamedEntityMixin.__init__(self, identifier) DocumentedEntityMixin.__init__(self, documentation) @@ -114,6 +122,11 @@ def __init__( @readonly def Subtype(self) -> None: + """ + Read-only property to access the subtype (:attr:`_subtype`). + + :returns: The subtype. + """ return self._subtype @@ -129,10 +142,10 @@ class AttributeSpecification(ModelEntity, DocumentedEntityMixin): attribute TotalBits of BusType : subtype is 32; """ - _identifiers: List[Name] - _attribute: Name - _entityClass: EntityClass - _expression: ExpressionUnion + _identifiers: List[Name] #: List of all names the attribute is specified for. + _attribute: Name #: Reference to the specified attribute. + _entityClass: EntityClass #: The entity class the named items belong to. + _expression: ExpressionUnion #: The value assigned to the attribute. def __init__( self, @@ -143,6 +156,16 @@ def __init__( documentation: Nullable[str] = None, parent: Nullable[ModelEntity] = None ) -> None: + """ + Initializes an attribute specification. + + :param identifiers: List of all names the attribute is specified for. + :param attribute: Reference to the specified attribute. + :param entityClass: The entity class the named items belong to. + :param expression: The value assigned to the attribute. + :param documentation: The documentation comment associated with this declaration. + :param parent: The parent model entity of this entity. + """ super().__init__(parent) DocumentedEntityMixin.__init__(self, documentation) @@ -161,30 +184,115 @@ def __init__( @readonly def Identifiers(self) -> List[Name]: + """ + Read-only property to access the identifiers (:attr:`_identifiers`). + + :returns: List of identifiers. + """ return self._identifiers @readonly def Attribute(self) -> Name: + """ + Read-only property to access the attribute (:attr:`_attribute`). + + :returns: The attribute. + """ return self._attribute @readonly def EntityClass(self) -> EntityClass: + """ + Read-only property to access the entity class (:attr:`_entityClass`). + + :returns: The entity class. + """ return self._entityClass @readonly def Expression(self) -> ExpressionUnion: + """ + Read-only property to access the expression (:attr:`_expression`). + + :returns: The expression. + """ return self._expression # TODO: move somewhere else @export class Alias(ModelEntity, NamedEntityMixin, DocumentedEntityMixin): - def __init__(self, identifier: str, documentation: Nullable[str] = None, parent: Nullable[ModelEntity] = None) -> None: + """ + Represents an alias declaration. + + :attr:`Name` is a :class:`~pyVHDLModel.Symbol.Symbol` - like every other cross-reference in this + model - rather than a bare :class:`~pyVHDLModel.Name.Name`, so it participates in the usual + resolve-later mechanism (:attr:`~pyVHDLModel.Symbol.Symbol.Reference` / + :attr:`~pyVHDLModel.Symbol.Symbol.IsResolved`). Unlike ``PackageReferenceSymbol`` and similar, + there is no single fixed :class:`~pyVHDLModel.Symbol.PossibleReference` value that always fits: an + alias without a subtype indication can refer to almost anything nameable (an object, a type, a + subprogram, a literal, ...), while an alias *with* a subtype indication can - per the LRM - only + ever refer to an object (a constant, variable, signal, or file); the ``possibleReferences`` passed + to the ``Symbol`` should reflect whichever case applies. + + .. admonition:: Example + + .. code-block:: VHDL + + alias a : bit_vector(3 downto 0) is s(3 downto 0); + -- ^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^ + -- optional Subtype Name + + alias b is s; + -- ^ + -- Name + """ + + _name: Symbol #: Reference to the name being aliased. + _subtype: Nullable[SubtypeSymbol] #: Reference to the alias' subtype, or ``None`` if none was given. + + def __init__( + self, + identifier: str, + name: Symbol, + subtype: Nullable[SubtypeSymbol] = None, + documentation: Nullable[str] = None, + parent: Nullable[ModelEntity] = None + ) -> None: """ - Initializes underlying ``BaseType``. + Initializes an alias declaration. - :param identifier: Name of the type. + :param identifier: The identifier of a model entity. + :param name: Reference to the name being aliased. + :param subtype: Reference to the alias' subtype, or ``None`` if none was given. + :param documentation: The documentation comment associated with this declaration. + :param parent: The parent model entity of this entity. """ super().__init__(parent) NamedEntityMixin.__init__(self, identifier) DocumentedEntityMixin.__init__(self, documentation) + + self._name = name + name.Parent = self + + self._subtype = subtype + if subtype is not None: + subtype.Parent = self + + @readonly + def Name(self) -> Symbol: + """ + Read-only property to access the name (:attr:`_name`). + + :returns: The name. + """ + return self._name + + @readonly + def Subtype(self) -> Nullable[SubtypeSymbol]: + """ + Read-only property to access the subtype (:attr:`_subtype`). + + :returns: The subtype, or ``None`` if not set. + """ + return self._subtype diff --git a/pyVHDLModel/DesignUnit.py b/pyVHDLModel/DesignUnit.py index 8f535faa0..a0be0ca25 100644 --- a/pyVHDLModel/DesignUnit.py +++ b/pyVHDLModel/DesignUnit.py @@ -49,6 +49,7 @@ from pyVHDLModel.Interface import GenericInterfaceItemMixin, PortInterfaceItemMixin, WithGenericsMixin, WithPortsMixin from pyVHDLModel.Object import DeferredConstant from pyVHDLModel.Concurrent import ConcurrentStatement, ConcurrentStatementsMixin +from pyVHDLModel.Configuration import BlockConfiguration @export @@ -58,12 +59,12 @@ class Reference(ModelEntity): .. seealso:: - * :class:`~pyVHDLModel.DesignUnit.LibraryClause` - * :class:`~pyVHDLModel.DesignUnit.UseClause` - * :class:`~pyVHDLModel.DesignUnit.ContextReference` + * :class:`Library clause ` + * :class:`Use clause ` + * :class:`Context reference ` """ - _symbols: List[Symbol] + _symbols: List[Symbol] #: List of all symbols referenced by this clause. def __init__(self, symbols: Iterable[Symbol], parent: Nullable[ModelEntity] = None) -> None: """ @@ -147,6 +148,14 @@ class ContextReference(Reference): class DesignUnitWithContextMixin(metaclass=ExtendedType, mixin=True): """ A mixin-class for all design units with a context. + + .. seealso:: + + * :class:`Package ` + * :class:`Package body ` + * :class:`Entity ` + * :class:`Architecture ` + * :class:`Configuration ` """ @@ -155,19 +164,22 @@ class DesignUnit(ModelEntity, NamedEntityMixin, DocumentedEntityMixin): """ A base-class for all design units. + When a design unit is formatted, an unknown part - a library that is not set, or an entity with no + known architecture - is rendered as ``?``. + .. seealso:: * :class:`Primary design units ` - * :class:`~pyVHDLModel.DesignUnit.Context` - * :class:`~pyVHDLModel.DesignUnit.Entity` - * :class:`~pyVHDLModel.DesignUnit.Package` - * :class:`~pyVHDLModel.DesignUnit.Configuration` + * :class:`Context ` + * :class:`Entity ` + * :class:`Package ` + * :class:`Configuration ` * :class:`Secondary design units ` - * :class:`~pyVHDLModel.DesignUnit.Architecture` - * :class:`~pyVHDLModel.DesignUnit.PackageBody` + * :class:`Architecture ` + * :class:`Package body ` """ _document: 'Document' #: The VHDL library, the design unit was analyzed into. @@ -185,7 +197,7 @@ class DesignUnit(ModelEntity, NamedEntityMixin, DocumentedEntityMixin): _dependencyVertex: Vertex[None, None, str, 'DesignUnit', None, None, None, None, None, None, None, None, None, None, None, None, None] #: Reference to the vertex in the dependency graph representing the design unit. |br| This reference is set by :meth:`~pyVHDLModel.Design.CreateDependencyGraph`. _hierarchyVertex: Vertex[None, None, str, 'DesignUnit', None, None, None, None, None, None, None, None, None, None, None, None, None] #: The vertex in the hierarchy graph - _namespace: 'Namespace' + _namespace: 'Namespace' #: The namespace of this design unit's declarative region. def __init__(self, identifier: str, contextItems: Nullable[Iterable[ContextUnion]] = None, documentation: Nullable[str] = None, parent: Nullable[ModelEntity] = None) -> None: """ @@ -226,8 +238,13 @@ def __init__(self, identifier: str, contextItems: Nullable[Iterable[ContextUnion self._namespace = Namespace(self._normalizedIdentifier) - @readonly + @property def Document(self) -> 'Document': + """ + Property to access the document (:attr:`_document`). + + :returns: The document. + """ return self._document @Document.setter @@ -236,13 +253,18 @@ def Document(self, document: 'Document') -> None: @property def Library(self) -> 'Library': + """ + Property to access the library (:attr:`_parent`). + + :returns: The library. + """ return self._parent @Library.setter def Library(self, library: 'Library') -> None: self._parent = library - @property + @readonly def ContextItems(self) -> List['ContextUnion']: """ Read-only property to access the sequence of all context items comprising library, use and context clauses @@ -252,7 +274,7 @@ def ContextItems(self) -> List['ContextUnion']: """ return self._contextItems - @property + @readonly def ContextReferences(self) -> List['ContextReference']: """ Read-only property to access the sequence of context clauses (:attr:`_contextReferences`). @@ -261,7 +283,7 @@ def ContextReferences(self) -> List['ContextReference']: """ return self._contextReferences - @property + @readonly def LibraryReferences(self) -> List['LibraryClause']: """ Read-only property to access the sequence of library clauses (:attr:`_libraryReferences`). @@ -270,7 +292,7 @@ def LibraryReferences(self) -> List['LibraryClause']: """ return self._libraryReferences - @property + @readonly def PackageReferences(self) -> List['UseClause']: """ Read-only property to access the sequence of use clauses (:attr:`_packageReferences`). @@ -279,19 +301,34 @@ def PackageReferences(self) -> List['UseClause']: """ return self._packageReferences - @property + @readonly def ReferencedLibraries(self) -> Dict[str, 'Library']: + """ + Read-only property to access the referenced libraries (:attr:`_referencedLibraries`). + + :returns: Dictionary of referenced libraries, indexed by normalized identifier. + """ return self._referencedLibraries - @property + @readonly def ReferencedPackages(self) -> Dict[str, 'Package']: + """ + Read-only property to access the referenced packages (:attr:`_referencedPackages`). + + :returns: Dictionary of referenced packages, indexed by normalized identifier. + """ return self._referencedPackages - @property + @readonly def ReferencedContexts(self) -> Dict[str, 'Context']: + """ + Read-only property to access the referenced contexts (:attr:`_referencedContexts`). + + :returns: Dictionary of referenced contexts, indexed by normalized identifier. + """ return self._referencedContexts - @property + @readonly def DependencyVertex(self) -> Vertex: """ Read-only property to access the corresponding dependency vertex (:attr:`_dependencyVertex`). @@ -302,7 +339,7 @@ def DependencyVertex(self) -> Vertex: """ return self._dependencyVertex - @property + @readonly def HierarchyVertex(self) -> Vertex: """ Read-only property to access the corresponding hierarchy vertex (:attr:`_hierarchyVertex`). @@ -321,10 +358,11 @@ class PrimaryUnit(DesignUnit): .. seealso:: - * :class:`~pyVHDLModel.DesignUnit.Context` - * :class:`~pyVHDLModel.DesignUnit.Entity` - * :class:`~pyVHDLModel.DesignUnit.Package` - * :class:`~pyVHDLModel.DesignUnit.Configuration` + * :class:`Context ` + * :class:`Package ` + * :class:`Entity ` + * :class:`Configuration ` + * :class:`PSL primary unit ` (PSL is not supported) """ @@ -335,8 +373,8 @@ class SecondaryUnit(DesignUnit): .. seealso:: - * :class:`~pyVHDLModel.DesignUnit.Architecture` - * :class:`~pyVHDLModel.DesignUnit.PackageBody` + * :class:`Package body ` + * :class:`Architecture ` """ @@ -369,11 +407,25 @@ class Context(PrimaryUnit): context ctx is -- ... end context; + + .. seealso:: + + * :class:`Library clause ` + * :class:`Use clause ` """ - _references: List[ContextUnion] + _references: List[ContextUnion] #: All context items, in declaration order. def __init__(self, identifier: str, references: Nullable[Iterable[ContextUnion]] = None, documentation: Nullable[str] = None, parent: Nullable[ModelEntity] = None) -> None: + """ + Initializes a context declaration. + + :param identifier: The identifier of a model entity. + :param references: All context items, in declaration order. + :param documentation: The documentation comment associated with this declaration. + :param parent: The parent model entity of this entity. + :raises VHDLModelException: If a context item is neither a library clause, use clause, nor context reference. + """ super().__init__(identifier, None, documentation, parent) self._references = [] @@ -393,22 +445,44 @@ def __init__(self, identifier: str, references: Nullable[Iterable[ContextUnion]] elif isinstance(reference, ContextReference): self._contextReferences.append(reference) else: - raise VHDLModelException() # FIXME: needs exception message + raise VHDLModelException(f"Reference '{reference!r}' is neither a library clause, use clause, nor context reference.") - @property + @readonly def LibraryReferences(self) -> List[LibraryClause]: + """ + Read-only property to access the library references (:attr:`_libraryReferences`). + + :returns: List of library references. + """ return self._libraryReferences - @property + @readonly def PackageReferences(self) -> List[UseClause]: + """ + Read-only property to access the package references (:attr:`_packageReferences`). + + :returns: List of package references. + """ return self._packageReferences - @property + @readonly def ContextReferences(self) -> List[ContextReference]: + """ + Read-only property to access the context references (:attr:`_contextReferences`). + + :returns: List of context references. + """ return self._contextReferences def __str__(self) -> str: - lib = self._parent._identifier + "?" if self._parent is not None else "" + """ + Formats the context declaration. + + **Format:** ``Context: mylib.myContext`` + + :returns: Formatted context declaration. + """ + lib = self._parent._identifier if self._parent is not None else "?" return f"Context: {lib}.{self._identifier}" @@ -425,12 +499,18 @@ class Package(PrimaryUnit, DesignUnitWithContextMixin, WithGenericsMixin, Concur package pkg is -- ... end package; + + .. seealso:: + + * :class:`Package instantiation ` + * :class:`Predefined package ` + * :class:`Package body implementing it ` """ - _packageBody: Nullable["PackageBody"] + _packageBody: Nullable["PackageBody"] #: The corresponding package body, or ``None`` if none was analyzed. - _deferredConstants: Dict[str, DeferredConstant] - _components: Dict[str, 'Component'] + _deferredConstants: Dict[str, DeferredConstant] #: Deferred constants, indexed by name. + _components: Dict[str, 'Component'] #: Components, indexed by name. def __init__( self, @@ -464,23 +544,43 @@ def __init__( self._deferredConstants = {} self._components = {} - @property + @readonly def PackageBody(self) -> Nullable["PackageBody"]: + """ + Read-only property to access the package body (:attr:`_packageBody`). + + :returns: The package body, or ``None`` if not set. + """ return self._packageBody - @property + @readonly def DeclaredItems(self) -> List: + """ + Read-only property to access the declared items (:attr:`_declaredItems`). + + :returns: List of declared items. + """ return self._declaredItems - @property - def DeferredConstants(self): + @readonly + def DeferredConstants(self) -> Dict[str, DeferredConstant]: + """ + Read-only property to access the deferred constants (:attr:`_deferredConstants`). + + :returns: Dictionary of deferred constants, indexed by normalized identifier. + """ return self._deferredConstants - @property - def Components(self): + @readonly + def Components(self) -> Dict[str, 'Component']: + """ + Read-only property to access the components (:attr:`_components`). + + :returns: Dictionary of components, indexed by normalized identifier. + """ return self._components - def _IndexOtherDeclaredItem(self, item): + def _IndexOtherDeclaredItem(self, item) -> None: if isinstance(item, DeferredConstant): for normalizedIdentifier in item.NormalizedIdentifiers: self._deferredConstants[normalizedIdentifier] = item @@ -490,16 +590,37 @@ def _IndexOtherDeclaredItem(self, item): super()._IndexOtherDeclaredItem(item) def __str__(self) -> str: - lib = self._parent._identifier if self._parent is not None else "%" + """ + Formats the package declaration. + + **Format:** ``Package: 'mylib.myPackage'`` + + :returns: Formatted package declaration. + """ + lib = self._parent._identifier if self._parent is not None else "?" return f"Package: '{lib}.{self._identifier}'" def __repr__(self) -> str: - lib = self._parent._identifier if self._parent is not None else "%" + """ + Formats a representation of the package declaration. + + **Format:** ``mylib.myPackage`` + + :returns: String representation of the package declaration. + """ + lib = self._parent._identifier if self._parent is not None else "?" return f"{lib}.{self._identifier}" + def IndexDeclaredItems(self) -> None: + """A generic package's generics share the declarative region of its declarative part.""" + self._IndexGenericItems() + + super().IndexDeclaredItems() + + @export class PackageBody(SecondaryUnit, DesignUnitWithContextMixin, ConcurrentDeclarationRegionMixin): """ @@ -512,9 +633,14 @@ class PackageBody(SecondaryUnit, DesignUnitWithContextMixin, ConcurrentDeclarati package body pkg is -- ... end package body; + + .. seealso:: + + * :class:`Predefined package body ` + * :class:`Package it implements ` """ - _package: PackageSymbol + _package: PackageSymbol #: Reference to the package this body implements. def __init__( self, @@ -524,6 +650,15 @@ def __init__( documentation: Nullable[str] = None, parent: Nullable[ModelEntity] = None ) -> None: + """ + Initializes a package body declaration. + + :param packageSymbol: Reference to the package this body implements. + :param contextItems: List of all context items (library, use and context clauses). + :param declaredItems: List of all declared items in this concurrent declaration region. + :param documentation: The documentation comment associated with this declaration. + :param parent: The parent model entity of this entity. + """ super().__init__(packageSymbol.Name.Identifier, contextItems, documentation, parent) DesignUnitWithContextMixin.__init__(self) ConcurrentDeclarationRegionMixin.__init__(self, declaredItems) @@ -531,24 +666,48 @@ def __init__( self._package = packageSymbol packageSymbol.Parent = self - @property + @readonly def Package(self) -> PackageSymbol: + """ + Read-only property to access the package (:attr:`_package`). + + :returns: The package. + """ return self._package - @property + @readonly def DeclaredItems(self) -> List: + """ + Read-only property to access the declared items (:attr:`_declaredItems`). + + :returns: List of declared items. + """ return self._declaredItems def LinkDeclaredItemsToPackage(self) -> None: pass def __str__(self) -> str: - lib = self._parent._identifier + "?" if self._parent is not None else "" + """ + Formats the package body declaration. + + **Format:** ``Package Body: mylib.myPackage(body)`` + + :returns: Formatted package body declaration. + """ + lib = self._parent._identifier if self._parent is not None else "?" return f"Package Body: {lib}.{self._identifier}(body)" def __repr__(self) -> str: - lib = self._parent._identifier + "?" if self._parent is not None else "" + """ + Formats a representation of the package body declaration. + + **Format:** ``mylib.myPackage(body)`` + + :returns: String representation of the package body declaration. + """ + lib = self._parent._identifier if self._parent is not None else "?" return f"{lib}.{self._identifier}(body)" @@ -565,9 +724,15 @@ class Entity(PrimaryUnit, DesignUnitWithContextMixin, WithGenericsMixin, WithPor entity ent is -- ... end entity; + + .. seealso:: + + * :class:`Architecture implementing it ` + * :class:`Component declaring the same interface ` + * :class:`Configuration binding it ` """ - _architectures: Dict[str, 'Architecture'] + _architectures: Dict[str, 'Architecture'] #: Dictionary of all architectures of this entity, indexed by name. def __init__( self, @@ -581,6 +746,19 @@ def __init__( allowBlackbox: Nullable[bool] = None, parent: Nullable[ModelEntity] = None ) -> None: + """ + Initializes an entity declaration. + + :param identifier: The identifier of a model entity. + :param contextItems: List of all context items (library, use and context clauses). + :param genericItems: List of all generics, in declaration order. + :param portItems: List of all ports, in declaration order. + :param declaredItems: List of all declared items in this concurrent declaration region. + :param statements: List of all concurrent statements in this construct. + :param documentation: The documentation comment associated with this declaration. + :param allowBlackbox: Allow blackboxes for components in language entity. + :param parent: The parent model entity of this entity. + """ super().__init__(identifier, contextItems, documentation, parent) DesignUnitWithContextMixin.__init__(self) WithGenericsMixin.__init__(self, genericItems) @@ -591,23 +769,52 @@ def __init__( self._architectures = {} - @property + @readonly def Architectures(self) -> Dict[str, 'Architecture']: + """ + Read-only property to access the architectures (:attr:`_architectures`). + + :returns: Dictionary of architectures, indexed by normalized identifier. + """ return self._architectures def __str__(self) -> str: - lib = self._parent._identifier if self._parent is not None else "%" - archs = ', '.join(self._architectures.keys()) if self._architectures else "%" + """ + Formats the entity declaration. + + **Format:** ``Entity: 'mylib.myEntity(rtl, sim)'`` + + The parenthesis lists the known architectures, or ``?`` if there are none. + + :returns: Formatted entity declaration. + """ + lib = self._parent._identifier if self._parent is not None else "?" + archs = ', '.join(self._architectures.keys()) if self._architectures else "?" return f"Entity: '{lib}.{self._identifier}({archs})'" def __repr__(self) -> str: - lib = self._parent._identifier if self._parent is not None else "%" - archs = ', '.join(self._architectures.keys()) if self._architectures else "%" + """ + Formats a representation of the entity declaration. + + **Format:** ``mylib.myEntity(rtl, sim)`` + + :returns: String representation of the entity declaration. + """ + lib = self._parent._identifier if self._parent is not None else "?" + archs = ', '.join(self._architectures.keys()) if self._architectures else "?" return f"{lib}.{self._identifier}({archs})" + def IndexDeclaredItems(self) -> None: + """An entity's generics and ports share the declarative region of its declarative part.""" + self._IndexGenericItems() + self._IndexPortItems() + + super().IndexDeclaredItems() + + @export class Architecture(SecondaryUnit, DesignUnitWithContextMixin, ConcurrentDeclarationRegionMixin, ConcurrentStatementsMixin, AllowBlackboxMixin): """ @@ -622,9 +829,13 @@ class Architecture(SecondaryUnit, DesignUnitWithContextMixin, ConcurrentDeclarat begin -- ... end architecture; + + .. seealso:: + + * :class:`Entity it implements ` """ - _entity: EntitySymbol + _entity: EntitySymbol #: Reference to the entity this architecture implements. def __init__( self, @@ -637,6 +848,18 @@ def __init__( allowBlackbox: Nullable[bool] = None, parent: Nullable[ModelEntity] = None ) -> None: + """ + Initializes an architecture declaration. + + :param identifier: The identifier of a model entity. + :param entity: Reference to the entity this architecture implements. + :param contextItems: List of all context items (library, use and context clauses). + :param declaredItems: List of all declared items in this concurrent declaration region. + :param statements: List of all concurrent statements in this construct. + :param documentation: The documentation comment associated with this declaration. + :param allowBlackbox: Allow blackboxes for components in language entity. + :param parent: The parent model entity of this entity. + """ super().__init__(identifier, contextItems, documentation, parent) DesignUnitWithContextMixin.__init__(self) ConcurrentDeclarationRegionMixin.__init__(self, declaredItems) @@ -646,19 +869,38 @@ def __init__( self._entity = entity entity.Parent = self - @property + @readonly def Entity(self) -> EntitySymbol: # FIXME: change to entitySymbol, offer entity directly, but raise exception if not resolved. + """ + Read-only property to access the entity (:attr:`_entity`). + + :returns: The entity. + """ return self._entity def __str__(self) -> str: - lib = self._parent._identifier if self._parent is not None else "%" - ent = self._entity._name._identifier if self._entity is not None else "%" + """ + Formats the architecture declaration. + + **Format:** ``Architecture: mylib.myEntity(rtl)`` + + :returns: Formatted architecture declaration. + """ + lib = self._parent._identifier if self._parent is not None else "?" + ent = self._entity._name._identifier if self._entity is not None else "?" return f"Architecture: {lib}.{ent}({self._identifier})" def __repr__(self) -> str: - lib = self._parent._identifier if self._parent is not None else "%" - ent = self._entity._name._identifier if self._entity is not None else "%" + """ + Formats a representation of the architecture declaration. + + **Format:** ``mylib.myEntity(rtl)`` + + :returns: String representation of the architecture declaration. + """ + lib = self._parent._identifier if self._parent is not None else "?" + ent = self._entity._name._identifier if self._entity is not None else "?" return f"{lib}.{ent}({self._identifier})" @@ -666,7 +908,7 @@ def __repr__(self) -> str: @export class Component(ModelEntity, NamedEntityMixin, DocumentedEntityMixin, AllowBlackboxMixin): """ - Represents a configuration declaration. + Represents a component declaration. .. admonition:: Example @@ -675,14 +917,19 @@ class Component(ModelEntity, NamedEntityMixin, DocumentedEntityMixin, AllowBlack component ent is -- ... end component; + + .. seealso:: + + * :class:`Entity it may be bound to ` + * :class:`Component configuration ` """ - _isBlackBox: Nullable[bool] #: Component is a blackbox. + _isBlackbox: Nullable[bool] #: Component is a blackbox. - _genericItems: List[GenericInterfaceItemMixin] - _portItems: List[PortInterfaceItemMixin] + _genericItems: List[GenericInterfaceItemMixin] #: List of all generics of this component, in declaration order. + _portItems: List[PortInterfaceItemMixin] #: List of all ports of this component, in declaration order. - _entity: Nullable[Entity] + _entity: Nullable[Entity] #: Linked entity, or ``None`` if unresolved. def __init__( self, @@ -693,12 +940,22 @@ def __init__( allowBlackbox: Nullable[bool] = None, parent: Nullable[ModelEntity] = None ) -> None: + """ + Initializes a component declaration. + + :param identifier: The identifier of a model entity. + :param genericItems: List of all generics of this component, in declaration order. + :param portItems: List of all ports of this component, in declaration order. + :param documentation: The documentation comment associated with this declaration. + :param allowBlackbox: Allow blackboxes for components in language entity. + :param parent: The parent model entity of this entity. + """ super().__init__(parent) NamedEntityMixin.__init__(self, identifier) DocumentedEntityMixin.__init__(self, documentation) AllowBlackboxMixin.__init__(self, allowBlackbox) - self._isBlackBox = None + self._isBlackbox = None self._entity = None # TODO: extract to mixin @@ -715,42 +972,69 @@ def __init__( self._portItems.append(item) item.Parent = self - @property + @readonly def IsBlackbox(self) -> Nullable[bool]: """ - Read-only property returning true, if this component is a blackbox (:attr:`_isBlackbox`). + Check if the component is a blackbox (:attr:`_isBlackbox`). - If components were not linked to matching entities, this property returns None. + If components were not linked to matching entities, this property returns ``None``. - :returns: If this component is a blackbox. + :returns: ``True``, if the component is a blackbox; ``False``, if it is not; ``None``, if components + were not linked to entities yet. """ - return self._isBlackBox + return self._isBlackbox - @property + @readonly def GenericItems(self) -> List[GenericInterfaceItemMixin]: + """ + Read-only property to access the generic items (:attr:`_genericItems`). + + :returns: List of generic items. + """ return self._genericItems - @property + @readonly def PortItems(self) -> List[PortInterfaceItemMixin]: + """ + Read-only property to access the port items (:attr:`_portItems`). + + :returns: List of port items. + """ return self._portItems @property def Entity(self) -> Nullable[Entity]: + """ + Property to access the entity (:attr:`_entity`). + + :returns: The entity, or ``None`` if not set. + """ return self._entity @Entity.setter def Entity(self, value: Entity) -> None: self._entity = value - self._isBlackBox = False + self._isBlackbox = False def __str__(self) -> str: + """ + Formats the component declaration. + + **Format:** ``Component: myComponent`` + + :returns: Formatted component declaration. + """ return f"Component: {self._identifier}" def __repr__(self) -> str: - if isinstance(self._parent, Package): - return f"{self._parent!r}:{self._identifier}" - elif isinstance(self._parent, Architecture): - return f"{self._parent!r}:{self._identifier}" + """ + Formats a representation of the component declaration. + + **Format:** ``mylib.myPackage:myComponent`` + + :returns: String representation of the component declaration. + """ + return f"{self._parent!r}:{self._identifier}" @export @@ -767,24 +1051,82 @@ class Configuration(PrimaryUnit, DesignUnitWithContextMixin): -- ... end for; end configuration; + + .. seealso:: + + * :class:`Entity it configures ` + * :class:`Block configuration ` """ + _entity: EntitySymbol #: Reference to the entity this configuration configures. + _blockConfiguration: BlockConfiguration #: The configuration of the entity's architecture. + def __init__( self, identifier: str, + entity: EntitySymbol, + blockConfiguration: BlockConfiguration, contextItems: Nullable[Iterable[Context]] = None, documentation: Nullable[str] = None, parent: Nullable[ModelEntity] = None ) -> None: + """ + Initializes a configuration declaration. + + :param identifier: The identifier of a model entity. + :param entity: Reference to the entity this configuration configures. + :param blockConfiguration: The configuration of the entity's architecture. + :param contextItems: List of all context items (library, use and context clauses). + :param documentation: The documentation comment associated with this declaration. + :param parent: The parent model entity of this entity. + """ super().__init__(identifier, contextItems, documentation, parent) DesignUnitWithContextMixin.__init__(self) + self._entity = entity + entity.Parent = self + + self._blockConfiguration = blockConfiguration + blockConfiguration.Parent = self + + @readonly + def Entity(self) -> EntitySymbol: + """ + Read-only property to access the entity (:attr:`_entity`). + + :returns: The entity. + """ + return self._entity + + @readonly + def BlockConfiguration(self) -> BlockConfiguration: + """ + Read-only property to access the block configuration (:attr:`_blockConfiguration`). + + :returns: The block configuration. + """ + return self._blockConfiguration + def __str__(self) -> str: - lib = self._parent._identifier if self._parent is not None else "%" + """ + Formats the configuration declaration. + + **Format:** ``Configuration: mylib.myConfiguration`` + + :returns: Formatted configuration declaration. + """ + lib = self._parent._identifier if self._parent is not None else "?" return f"Configuration: {lib}.{self._identifier}" def __repr__(self) -> str: - lib = self._parent._identifier if self._parent is not None else "%" + """ + Formats a representation of the configuration declaration. + + **Format:** ``mylib.myConfiguration`` + + :returns: String representation of the configuration declaration. + """ + lib = self._parent._identifier if self._parent is not None else "?" return f"{lib}.{self._identifier}" diff --git a/pyVHDLModel/Exception.py b/pyVHDLModel/Exception.py index 542d37139..34a2bb60c 100644 --- a/pyVHDLModel/Exception.py +++ b/pyVHDLModel/Exception.py @@ -43,27 +43,62 @@ @export class VHDLModelWarning(Warning): + """ + Base-class for all warnings raised by pyVHDLModel. + + .. seealso:: + + * :class:`Not implemented warning ` + """ pass @export class NotImplementedWarning(VHDLModelWarning): + """ + Raised when a language construct is recognised but not yet modelled. + """ pass @export class VHDLModelCriticalWarning(Warning): + """ + Base-class for critical warnings that likely indicate a defect in the model or its input. + + .. seealso:: + + * :class:`Blackbox warning ` + """ pass @export class BlackboxWarning(VHDLModelCriticalWarning): + """ + Raised when a component could not be bound and is treated as a blackbox. + """ pass @export class VHDLModelException(Exception): - """Base-class for all exceptions (errors) raised by pyVHDLModel.""" + """ + Base-class for all exceptions (errors) raised by pyVHDLModel. + + .. seealso:: + + * :class:`Library exists in design error ` + * :class:`Registered to foreign design error ` + * :class:`Library not registered error ` + * :class:`Entity exists in library error ` + * :class:`Architecture exists in library error ` + * :class:`Package exists in library error ` + * :class:`Package body exists error ` + * :class:`Configuration exists in library error ` + * :class:`Context exists in library error ` + * :class:`Referenced library not existing error ` + """ @export @@ -74,7 +109,7 @@ class LibraryExistsInDesignError(VHDLModelException): Message: :pycode:`f"Library '{library._identifier}' already exists in design."` """ - _library: 'Library' + _library: 'Library' #: The library involved in this error. def __init__(self, library: 'Library') -> None: """ @@ -103,7 +138,7 @@ class LibraryRegisteredToForeignDesignError(VHDLModelException): Message: :pycode:`f"Library '{library._identifier}' already registered in design '{library.Parent}'."` """ - _library: 'Library' + _library: 'Library' #: The library involved in this error. def __init__(self, library: 'Library') -> None: """ @@ -116,6 +151,11 @@ def __init__(self, library: 'Library') -> None: @readonly def Library(self) -> 'Library': + """ + Read-only property to access the library (:attr:`_library`). + + :returns: The library. + """ return self._library @@ -127,7 +167,7 @@ class LibraryNotRegisteredError(VHDLModelException): Message: :pycode:`f"Library '{library._identifier}' is not registered in the design."` """ - _library: 'Library' + _library: 'Library' #: The library involved in this error. def __init__(self, library: 'Library') -> None: """ @@ -140,6 +180,11 @@ def __init__(self, library: 'Library') -> None: @readonly def Library(self) -> 'Library': + """ + Read-only property to access the library (:attr:`_library`). + + :returns: The library. + """ return self._library @@ -151,8 +196,8 @@ class EntityExistsInLibraryError(VHDLModelException): Message: :pycode:`f"Entity '{entity._identifier}' already exists in library '{library._identifier}'."` """ - _library: 'Library' - _entity: 'Entity' + _library: 'Library' #: The library involved in this error. + _entity: 'Entity' #: The entity involved in this error. def __init__(self, entity: 'Entity', library: 'Library') -> None: """ @@ -167,10 +212,20 @@ def __init__(self, entity: 'Entity', library: 'Library') -> None: @readonly def Library(self) -> 'Library': + """ + Read-only property to access the library (:attr:`_library`). + + :returns: The library. + """ return self._library @readonly def Entity(self) -> 'Entity': + """ + Read-only property to access the entity (:attr:`_entity`). + + :returns: The entity. + """ return self._entity @@ -182,9 +237,9 @@ class ArchitectureExistsInLibraryError(VHDLModelException): Message: :pycode:`f"Architecture '{architecture._identifier}' for entity '{entity._identifier}' already exists in library '{library._identifier}'."` """ - _library: 'Library' - _entity: 'Entity' - _architecture: 'Architecture' + _library: 'Library' #: The library involved in this error. + _entity: 'Entity' #: The entity involved in this error. + _architecture: 'Architecture' #: The architecture involved in this error. def __init__(self, architecture: 'Architecture', entity: 'Entity', library: 'Library') -> None: """ @@ -201,14 +256,29 @@ def __init__(self, architecture: 'Architecture', entity: 'Entity', library: 'Lib @readonly def Library(self) -> 'Library': + """ + Read-only property to access the library (:attr:`_library`). + + :returns: The library. + """ return self._library @readonly def Entity(self) -> 'Entity': + """ + Read-only property to access the entity (:attr:`_entity`). + + :returns: The entity. + """ return self._entity @readonly def Architecture(self) -> 'Architecture': + """ + Read-only property to access the architecture (:attr:`_architecture`). + + :returns: The architecture. + """ return self._architecture @@ -220,8 +290,8 @@ class PackageExistsInLibraryError(VHDLModelException): Message: :pycode:`f"Package '{package._identifier}' already exists in library '{library._identifier}'."` """ - _library: 'Library' - _package: 'Package' + _library: 'Library' #: The library involved in this error. + _package: 'Package' #: The package involved in this error. def __init__(self, package: 'Package', library: 'Library') -> None: """ @@ -236,10 +306,20 @@ def __init__(self, package: 'Package', library: 'Library') -> None: @readonly def Library(self) -> 'Library': + """ + Read-only property to access the library (:attr:`_library`). + + :returns: The library. + """ return self._library @readonly def Package(self) -> 'Package': + """ + Read-only property to access the package (:attr:`_package`). + + :returns: The package. + """ return self._package @@ -251,8 +331,8 @@ class PackageBodyExistsError(VHDLModelException): Message: :pycode:`f"Package body '{packageBody._identifier}' already exists in library '{library._identifier}'."` """ - _library: 'Library' - _packageBody: 'PackageBody' + _library: 'Library' #: The library involved in this error. + _packageBody: 'PackageBody' #: The package body involved in this error. def __init__(self, packageBody: 'PackageBody', library: 'Library') -> None: """ @@ -267,10 +347,20 @@ def __init__(self, packageBody: 'PackageBody', library: 'Library') -> None: @readonly def Library(self) -> 'Library': + """ + Read-only property to access the library (:attr:`_library`). + + :returns: The library. + """ return self._library - @property + @readonly def PackageBody(self) -> 'PackageBody': + """ + Read-only property to access the package body (:attr:`_packageBody`). + + :returns: The package body. + """ return self._packageBody @@ -282,8 +372,8 @@ class ConfigurationExistsInLibraryError(VHDLModelException): Message: :pycode:`f"Configuration '{configuration._identifier}' already exists in library '{library._identifier}'."` """ - _library: 'Library' - _configuration: 'Configuration' + _library: 'Library' #: The library involved in this error. + _configuration: 'Configuration' #: The configuration involved in this error. def __init__(self, configuration: 'Configuration', library: 'Library') -> None: """ @@ -296,12 +386,22 @@ def __init__(self, configuration: 'Configuration', library: 'Library') -> None: self._library = library self._configuration = configuration - @property + @readonly def Library(self) -> 'Library': + """ + Read-only property to access the library (:attr:`_library`). + + :returns: The library. + """ return self._library - @property + @readonly def Configuration(self) -> 'Configuration': + """ + Read-only property to access the configuration (:attr:`_configuration`). + + :returns: The configuration. + """ return self._configuration @@ -313,8 +413,8 @@ class ContextExistsInLibraryError(VHDLModelException): Message: :pycode:`f"Context '{context._identifier}' already exists in library '{library._identifier}'."` """ - _library: 'Library' - _context: 'Context' + _library: 'Library' #: The library involved in this error. + _context: 'Context' #: The context involved in this error. def __init__(self, context: 'Context', library: 'Library') -> None: """ @@ -327,12 +427,22 @@ def __init__(self, context: 'Context', library: 'Library') -> None: self._library = library self._context = context - @property + @readonly def Library(self) -> 'Library': + """ + Read-only property to access the library (:attr:`_library`). + + :returns: The library. + """ return self._library - @property + @readonly def Context(self) -> 'Context': + """ + Read-only property to access the context (:attr:`_context`). + + :returns: The context. + """ return self._context @@ -344,8 +454,8 @@ class ReferencedLibraryNotExistingError(VHDLModelException): Message: :pycode:`f"Library '{librarySymbol.Name._identifier}' referenced by library clause of context '{context._identifier}' doesn't exist in design."` """ - _librarySymbol: Symbol - _context: 'Context' + _librarySymbol: Symbol #: The library symbol that could not be resolved. + _context: 'Context' #: The context involved in this error. def __init__(self, context: 'Context', librarySymbol: Symbol) -> None: """ @@ -358,10 +468,20 @@ def __init__(self, context: 'Context', librarySymbol: Symbol) -> None: self._librarySymbol = librarySymbol self._context = context - @property + @readonly def LibrarySymbol(self) -> Symbol: + """ + Read-only property to access the library symbol (:attr:`_librarySymbol`). + + :returns: The library symbol. + """ return self._librarySymbol - @property + @readonly def Context(self) -> 'Context': + """ + Read-only property to access the context (:attr:`_context`). + + :returns: The context. + """ return self._context diff --git a/pyVHDLModel/Expression.py b/pyVHDLModel/Expression.py index a3fd3f6e0..a11d9dc78 100644 --- a/pyVHDLModel/Expression.py +++ b/pyVHDLModel/Expression.py @@ -40,7 +40,7 @@ from pyTooling.Decorators import export, readonly from pyVHDLModel.Base import ModelEntity, Direction, Range -from pyVHDLModel.Symbol import Symbol +from pyVHDLModel.Symbol import Symbol, SubtypeSymbol ExpressionUnion = Union[ @@ -55,150 +55,437 @@ @export class BaseExpression(ModelEntity): - """A ``BaseExpression`` is a base-class for all expressions.""" + """ + Represents the base-class of all expressions. + + .. seealso:: + + * :class:`Literal ` + * :class:`Unary expression ` + * :class:`Binary expression ` + * :class:`Qualified expression ` + * :class:`Ternary expression ` + * :class:`Function call ` + * :class:`Allocation ` + * :class:`Aggregate ` + """ @export class Literal(BaseExpression): - """A ``Literal`` is a base-class for all literals.""" + """ + Represents the base-class of all literals. + + A literal is an expression denoting a value written directly in the source. + + .. seealso:: + + * :class:`Null literal ` + * :class:`Enumeration literal ` + * :class:`Numeric literal ` + * :class:`Character literal ` + * :class:`String literal ` + * :class:`Bit string literal ` + """ @export class NullLiteral(Literal): + """ + Represents a ``null`` literal. + + A null literal denotes the null value of an access type. + + .. admonition:: Example + + .. code-block:: VHDL + + p := null; + -- ^^^^ <- the literal + """ def __str__(self) -> str: + """ + Formats the null literal. + + **Format:** ``null`` + + :returns: Formatted null literal. + """ return "null" @export class EnumerationLiteral(Literal): - _value: str + """ + Represents an enumeration literal. + + The literal's name is available as :data:`Value`. + + .. admonition:: Example + + .. code-block:: VHDL + + st <= Idle; + -- ^^^^ <- Value + """ + _value: str #: The enumeration literal's name. def __init__(self, value: str, parent: Nullable[ModelEntity] = None) -> None: + """ + Initializes an enumeration literal. + + :param value: The enumeration literal's name. + :param parent: The parent model entity of this entity. + """ super().__init__(parent) self._value = value @readonly def Value(self) -> str: + """ + Read-only property to access the value (:attr:`_value`). + + :returns: The value. + """ return self._value def __str__(self) -> str: + """ + Formats the enumeration literal. + + **Format:** ``idle`` + + :returns: Formatted enumeration literal. + """ return self._value @export class NumericLiteral(Literal): - """A ``NumericLiteral`` is a base-class for all numeric literals.""" + """ + Represents the base-class of all numeric literals. + + Integer, floating-point and physical literals are numeric. + + .. seealso:: + + * :class:`Integer literal ` + * :class:`Floating point literal ` + * :class:`Physical literal ` + """ @export class IntegerLiteral(NumericLiteral): - _value: int + """ + Represents an integer literal. + + The literal's value is available as :data:`Value`. + + .. admonition:: Example + + .. code-block:: VHDL + + res := a + 42; + -- ^^ <- Value + """ + _value: int #: The literal's integer value. def __init__(self, value: int) -> None: + """ + Initializes an integer literal. + + :param value: The literal's integer value. + """ super().__init__() self._value = value @readonly def Value(self) -> int: + """ + Read-only property to access the value (:attr:`_value`). + + :returns: The value. + """ return self._value def __str__(self) -> str: + """ + Formats the integer literal. + + **Format:** ``42`` + + :returns: Formatted integer literal. + """ return str(self._value) @export class FloatingPointLiteral(NumericLiteral): - _value: float + """ + Represents a floating-point literal. + + The literal's value is available as :data:`Value`. + + .. admonition:: Example + + .. code-block:: VHDL + + r <= 3.14; + -- ^^^^ <- Value + """ + _value: float #: The literal's floating-point value. def __init__(self, value: float) -> None: + """ + Initializes a floating-point literal. + + :param value: The literal's floating-point value. + """ super().__init__() self._value = value @readonly def Value(self) -> float: + """ + Read-only property to access the value (:attr:`_value`). + + :returns: The value. + """ return self._value def __str__(self) -> str: + """ + Formats the floating-point literal. + + **Format:** ``3.5`` + + :returns: Formatted floating-point literal. + """ return str(self._value) @export class PhysicalLiteral(NumericLiteral): - _unitName: str + """ + Represents the base-class of all physical literals. + + A physical literal combines a numeric value with a unit name (:data:`UnitName`). + + .. admonition:: Example + + .. code-block:: VHDL + + t <= 10 ns; + -- ^^ <- the value + -- ^^ <- UnitName + + .. seealso:: + + * :class:`Physical integer literal ` + * :class:`Physical floating literal ` + """ + _unitName: str #: The name of the physical unit the value is given in. def __init__(self, unitName: str) -> None: + """ + Initializes a physical literal. + + :param unitName: The name of the physical unit the value is given in. + """ super().__init__() self._unitName = unitName @readonly def UnitName(self) -> str: + """ + Read-only property to access the unit name (:attr:`_unitName`). + + :returns: The unit name. + """ return self._unitName def __str__(self) -> str: + """ + Formats the physical literal. + + **Format:** ``10 ns`` + + :returns: Formatted physical literal. + """ return f"{self._value} {self._unitName}" @export class PhysicalIntegerLiteral(PhysicalLiteral): - _value: int + """ + Represents a physical literal with an integer value. + + Value (:data:`Value`) and unit name (:data:`UnitName`) are available separately. + + .. admonition:: Example + + .. code-block:: VHDL + + t <= 10 ns; + -- ^^ <- Value + -- ^^ <- UnitName + """ + _value: int #: The literal's integer value, in units of :attr:`_unitName`. def __init__(self, value: int, unitName: str) -> None: + """ + Initializes a physical literal with an integer value. + + :param value: The literal's integer value, in units of :attr:`_unitName`. + :param unitName: The name of the physical unit the value is given in. + """ super().__init__(unitName) self._value = value @readonly def Value(self) -> int: + """ + Read-only property to access the value (:attr:`_value`). + + :returns: The value. + """ return self._value @export class PhysicalFloatingLiteral(PhysicalLiteral): - _value: float + """ + Represents a physical literal with a floating-point value. + + Value (:data:`Value`) and unit name (:data:`UnitName`) are available separately. + + .. admonition:: Example + + .. code-block:: VHDL + + t <= 1.5 ns; + -- ^^^ <- Value + -- ^^ <- UnitName + """ + _value: float #: The literal's floating-point value, in units of :attr:`_unitName`. def __init__(self, value: float, unitName: str) -> None: + """ + Initializes a physical literal with a floating-point value. + + :param value: The literal's floating-point value, in units of :attr:`_unitName`. + :param unitName: The name of the physical unit the value is given in. + """ super().__init__(unitName) self._value = value @readonly def Value(self) -> float: + """ + Read-only property to access the value (:attr:`_value`). + + :returns: The value. + """ return self._value @export class CharacterLiteral(Literal): - _value: str + """ + Represents a character literal. + + The literal's character is available as :data:`Value`. + + .. admonition:: Example + + .. code-block:: VHDL + + ch <= 'a'; + -- ^^^ <- Value + """ + _value: str #: The literal's character value. def __init__(self, value: str) -> None: + """ + Initializes a character literal. + + :param value: The literal's character value. + """ super().__init__() self._value = value @readonly def Value(self) -> str: + """ + Read-only property to access the value (:attr:`_value`). + + :returns: The value. + """ return self._value def __str__(self) -> str: + """ + Formats the character literal. + + **Format:** ``a`` + + :returns: Formatted character literal. + """ return str(self._value) @export class StringLiteral(Literal): - _value: str + """ + Represents a string literal. + + The literal's text is available as :data:`Value`. + + .. admonition:: Example + + .. code-block:: VHDL + + txt <= "text"; + -- ^^^^^^ <- Value + """ + _value: str #: The literal's string value, without the enclosing double quotes. def __init__(self, value: str) -> None: + """ + Initializes a string literal. + + :param value: The literal's string value, without the enclosing double quotes. + """ super().__init__() self._value = value @readonly def Value(self) -> str: + """ + Read-only property to access the value (:attr:`_value`). + + :returns: The value. + """ return self._value def __str__(self) -> str: + """ + Formats the string literal. + + **Format:** ``"hello"`` + + :returns: Formatted string literal. + """ return "\"" + self._value + "\"" @export class BitStringBase(Flag): + """ + Represents the base of a bit string literal: binary, octal, decimal or hexadecimal. + """ NoBase = 0 Binary = 2 Octal = 8 @@ -210,44 +497,107 @@ class BitStringBase(Flag): @export class BitStringLiteral(Literal): - # _base: ClassVar[BitStringBase] - _value: str - _binaryValue: str - _bits: int - _length: Nullable[int] - _signed: Nullable[bool] - - def __init__(self, value: str, length: Nullable[int] = None, signed: Nullable[bool] = None) -> None: + """ + Represents the base-class of all bit string literals. + + Besides the literal as written (:data:`Value`), the bits are available in binary form + (:data:`BinaryValue`, :data:`Bits`), together with the literal's length (:data:`Length`) and + whether it is signed (:data:`IsSigned`). + + .. admonition:: Example + + .. code-block:: VHDL + + res := b"10100000"; + -- ^^^^^^^^^^^ <- Value + + .. seealso:: + + * :class:`Binary bit string literal ` + * :class:`Octal bit string literal ` + * :class:`Decimal bit string literal ` + * :class:`Hexadecimal bit string literal ` + """ + _base: ClassVar[BitStringBase] = BitStringBase.NoBase #: The base this literal is written in. + + _value: str #: The literal as written in the source, without the enclosing double quotes. + _binaryValue: str #: The literal's value expanded to base 2, one character per bit. + _bits: int #: The number of bits the literal represents. + _length: Nullable[int] #: The explicitly given length, or ``None`` if the literal has no length specification. + _isSigned: Nullable[bool] #: ``True`` if signed, ``False`` if unsigned, ``None`` if unspecified. + + def __init__(self, value: str, length: Nullable[int] = None, isSigned: Nullable[bool] = None) -> None: + """ + Initializes a bit string literal. + + :param value: The literal as written in the source, without the enclosing double quotes. + :param length: The explicitly given length, or ``None`` if the literal has no length specification. + :param isSigned: ``True`` if signed, ``False`` if unsigned, ``None`` if unspecified. + """ super().__init__() self._value = value self._length = length - self._signed = signed + self._isSigned = isSigned self._binaryValue = None self._bits = None @readonly def Value(self) -> str: + """ + Read-only property to access the value (:attr:`_value`). + + :returns: The value. + """ return self._value @readonly def BinaryValue(self) -> str: + """ + Read-only property to access the binary value (:attr:`_binaryValue`). + + :returns: The binary value. + """ return self._binaryValue @readonly def Bits(self) -> Nullable[int]: + """ + Read-only property to access the bits (:attr:`_bits`). + + :returns: The bits, or ``None`` if not set. + """ return self._bits @readonly def Length(self) -> Nullable[int]: + """ + Read-only property to access the length (:attr:`_length`). + + :returns: The length, or ``None`` if not set. + """ return self._length @readonly - def Signed(self) -> Nullable[bool]: - return self._signed + def IsSigned(self) -> Nullable[bool]: + """ + Check if the bit string literal is signed (:attr:`_isSigned`). + + :returns: ``True``, if the literal is signed; ``None``, if unspecified. + """ + return self._isSigned def __str__(self) -> str: - signed = "" if self._signed is None else "s" if self._signed is True else "u" + """ + Formats the bit string literal. + + **Format:** ``8ub"10100000"`` + + The length and the signedness marker (``s``/``u``) are omitted when unspecified. + + :returns: Formatted bit string literal. + """ + signed = "" if self._isSigned is None else "s" if self._isSigned is True else "u" if self._base is BitStringBase.NoBase: base = "" elif self._base is BitStringBase.Binary: @@ -264,123 +614,421 @@ def __str__(self) -> str: @export class BinaryBitStringLiteral(BitStringLiteral): - _base: ClassVar[BitStringBase] = BitStringBase.Binary + """ + Represents a bit string literal written in base 2. + + .. admonition:: Example + + .. code-block:: VHDL + + res := b"10100000"; + -- ^^^^^^^^^^^ <- Value + """ + _base: ClassVar[BitStringBase] = BitStringBase.Binary #: The base this literal is written in. @export class OctalBitStringLiteral(BitStringLiteral): - _base: ClassVar[BitStringBase] = BitStringBase.Octal + """ + Represents a bit string literal written in base 8. + + Each digit contributes three bits. + + .. admonition:: Example + + .. code-block:: VHDL + + nine := o"240"; + -- ^^^^^^ <- Value + """ + _base: ClassVar[BitStringBase] = BitStringBase.Octal #: The base this literal is written in. @export class DecimalBitStringLiteral(BitStringLiteral): - _base: ClassVar[BitStringBase] = BitStringBase.Decimal + """ + Represents a bit string literal written in base 10. + + .. admonition:: Example + + .. code-block:: VHDL + + res := d"160"; + -- ^^^^^^ <- Value + """ + _base: ClassVar[BitStringBase] = BitStringBase.Decimal #: The base this literal is written in. @export class HexadecimalBitStringLiteral(BitStringLiteral): - _base: ClassVar[BitStringBase] = BitStringBase.Hexadecimal + """ + Represents a bit string literal written in base 16. + + Each digit contributes four bits. + + .. admonition:: Example + + .. code-block:: VHDL + + res := x"A0"; + -- ^^^^^ <- Value + """ + _base: ClassVar[BitStringBase] = BitStringBase.Hexadecimal #: The base this literal is written in. @export class ParenthesisExpression: #(Protocol): + """ + Represents the base-class of expressions wrapped in parentheses. + + The operand is available as :data:`Operand`. + + .. seealso:: + + * :class:`Sub expression ` + * :class:`Qualified expression ` + """ __slots__ = () # FIXME: use ExtendedType? @readonly def Operand(self) -> ExpressionUnion: + """ + Read-only property to return the operand. A parenthesis expression has none of its own. + + :returns: The operand. + """ return None @export class UnaryExpression(BaseExpression): - """A ``UnaryExpression`` is a base-class for all unary expressions.""" + """ + Represents the base-class of all unary expressions. - _FORMAT: Tuple[str, str] - _operand: ExpressionUnion + The operand is available as :data:`Operand`. + """ + + _FORMAT: Tuple[str, str] #: The operator's string representation as (prefix, suffix) around the operand. + _operand: ExpressionUnion #: The expression the operator is applied to. def __init__(self, operand: ExpressionUnion, parent: Nullable[ModelEntity] = None) -> None: + """ + Initializes a unary expression. + + :param operand: The expression the operator is applied to. + :param parent: The parent model entity of this entity. + """ super().__init__(parent) self._operand = operand - # operand.Parent = self # FIXME: operand is provided as None + operand.Parent = self @readonly - def Operand(self): + def Operand(self) -> ExpressionUnion: + """ + Read-only property to access the operand (:attr:`_operand`). + + :returns: The operand. + """ return self._operand def __str__(self) -> str: + """ + Formats the unary expression. + + **Format:** ``not operand`` + + :returns: Formatted unary expression. + """ return f"{self._FORMAT[0]}{self._operand!s}{self._FORMAT[1]}" @export class NegationExpression(UnaryExpression): + """ + Represents a negation (unary minus) expression. + + The operand is available as :data:`Operand`. + + .. admonition:: Example + + .. code-block:: VHDL + + res := - operand; + -- ^^^^^^^^^ <- the expression + -- ^^^^^^^ <- Operand + """ _FORMAT = ("-", "") @export class IdentityExpression(UnaryExpression): + """ + Represents an identity (unary plus) expression. + + The operand is available as :data:`Operand`. + + .. admonition:: Example + + .. code-block:: VHDL + + res := + operand; + -- ^^^^^^^^^ <- the expression + -- ^^^^^^^ <- Operand + """ _FORMAT = ("+", "") @export class InverseExpression(UnaryExpression): + """ + Represents a logical inversion expression (``not``). + + The operand is available as :data:`Operand`. + + .. admonition:: Example + + .. code-block:: VHDL + + res := not operand; + -- ^^^^^^^^^^^ <- the expression + -- ^^^^^^^ <- Operand + """ _FORMAT = ("not ", "") @export class UnaryAndExpression(UnaryExpression): + """ + Represents a ``and`` reduction expression. + + A reduction operator folds all elements of an array into a single value. + The operand is available as :data:`Operand`. + + .. admonition:: Example + + .. code-block:: VHDL + + res := and operand; + -- ^^^^^^^^^^^ <- the expression + -- ^^^^^^^ <- Operand + """ _FORMAT = ("and ", "") @export class UnaryNandExpression(UnaryExpression): + """ + Represents a ``nand`` reduction expression. + + A reduction operator folds all elements of an array into a single value. + The operand is available as :data:`Operand`. + + .. admonition:: Example + + .. code-block:: VHDL + + res := nand operand; + -- ^^^^^^^^^^^^ <- the expression + -- ^^^^^^^ <- Operand + """ _FORMAT = ("nand ", "") @export class UnaryOrExpression(UnaryExpression): + """ + Represents a ``or`` reduction expression. + + A reduction operator folds all elements of an array into a single value. + The operand is available as :data:`Operand`. + + .. admonition:: Example + + .. code-block:: VHDL + + res := or operand; + -- ^^^^^^^^^^ <- the expression + -- ^^^^^^^ <- Operand + """ _FORMAT = ("or ", "") @export class UnaryNorExpression(UnaryExpression): + """ + Represents a ``nor`` reduction expression. + + A reduction operator folds all elements of an array into a single value. + The operand is available as :data:`Operand`. + + .. admonition:: Example + + .. code-block:: VHDL + + res := nor operand; + -- ^^^^^^^^^^^ <- the expression + -- ^^^^^^^ <- Operand + """ _FORMAT = ("nor ", "") @export class UnaryXorExpression(UnaryExpression): + """ + Represents a ``xor`` reduction expression. + + A reduction operator folds all elements of an array into a single value. + The operand is available as :data:`Operand`. + + .. admonition:: Example + + .. code-block:: VHDL + + res := xor operand; + -- ^^^^^^^^^^^ <- the expression + -- ^^^^^^^ <- Operand + """ _FORMAT = ("xor ", "") @export class UnaryXnorExpression(UnaryExpression): + """ + Represents a ``xnor`` reduction expression. + + A reduction operator folds all elements of an array into a single value. + The operand is available as :data:`Operand`. + + .. admonition:: Example + + .. code-block:: VHDL + + res := xnor operand; + -- ^^^^^^^^^^^^ <- the expression + -- ^^^^^^^ <- Operand + """ _FORMAT = ("xnor ", "") @export class AbsoluteExpression(UnaryExpression): + """ + Represents an absolute value expression (``abs``). + + The operand is available as :data:`Operand`. + + .. admonition:: Example + + .. code-block:: VHDL + + res := abs operand; + -- ^^^^^^^^^^^ <- the expression + -- ^^^^^^^ <- Operand + """ _FORMAT = ("abs ", "") @export class TypeConversion(UnaryExpression): - pass + """ + Represents a type conversion. + + A type conversion converts its operand (:data:`Operand`) to the target subtype + (:data:`TargetSubtype`). Unlike every other :class:`UnaryExpression`, its "operator" is the target + type name itself rather than a fixed string, so it carries its own subtype and renders itself. + + .. admonition:: Example + + .. code-block:: VHDL + + res := integer(val); + -- ^^^^^^^ <- TargetSubtype + -- ^^^ <- Operand + """ + + _targetSubtype: SubtypeSymbol #: Reference to the subtype the expression is converted to. + + def __init__(self, targetSubtype: SubtypeSymbol, operand: ExpressionUnion, parent: Nullable[ModelEntity] = None) -> None: + """ + Initializes a type conversion. + + :param targetSubtype: Reference to the subtype the expression is converted to. + :param operand: The expression the operator is applied to. + :param parent: The parent model entity of this entity. + """ + super().__init__(operand, parent) + + self._targetSubtype = targetSubtype + targetSubtype.Parent = self + + @readonly + def TargetSubtype(self) -> SubtypeSymbol: + """ + Read-only property to access the target subtype (:attr:`_targetSubtype`). + + :returns: The target subtype. + """ + return self._targetSubtype + + def __str__(self) -> str: + """ + Formats the type conversion. + + **Format:** ``integer(val)`` + + :returns: Formatted type conversion. + """ + return f"{self._targetSubtype!s}({self._operand!s})" @export class SubExpression(UnaryExpression, ParenthesisExpression): + """ + Represents a parenthesized sub-expression. + + The operand is available as :data:`Operand`. + + .. admonition:: Example + + .. code-block:: VHDL + + res := (lhs + rhs); + -- ^^^^^^^^^^^ <- the sub-expression + -- ^^^^^^^^^ <- Operand + """ _FORMAT = ("(", ")") @export class BinaryExpression(BaseExpression): - """A ``BinaryExpression`` is a base-class for all binary expressions.""" + """ + Represents the base-class of all binary expressions. + + Both operands are available as :data:`LeftOperand` and :data:`RightOperand`. + + .. seealso:: + + * :class:`Range expression ` + * :class:`Adding expression ` + * :class:`Multiplying expression ` + * :class:`Logical expression ` + * :class:`Relational expression ` + * :class:`Shift expression ` + """ - _FORMAT: Tuple[str, str, str] - _leftOperand: ExpressionUnion - _rightOperand: ExpressionUnion + _FORMAT: Tuple[str, str, str] #: The operator's string representation as (prefix, infix, suffix). + _leftOperand: ExpressionUnion #: The expression left of the operator. + _rightOperand: ExpressionUnion #: The expression right of the operator. def __init__(self, leftOperand: ExpressionUnion, rightOperand: ExpressionUnion, parent: Nullable[ModelEntity] = None) -> None: + """ + Initializes a binary expression. + + :param leftOperand: The expression left of the operator. + :param rightOperand: The expression right of the operator. + :param parent: The parent model entity of this entity. + """ super().__init__(parent) self._leftOperand = leftOperand @@ -389,15 +1037,32 @@ def __init__(self, leftOperand: ExpressionUnion, rightOperand: ExpressionUnion, self._rightOperand = rightOperand rightOperand.Parent = self - @property - def LeftOperand(self): + @readonly + def LeftOperand(self) -> ExpressionUnion: + """ + Read-only property to access the left operand (:attr:`_leftOperand`). + + :returns: The left operand. + """ return self._leftOperand - @property - def RightOperand(self): + @readonly + def RightOperand(self) -> ExpressionUnion: + """ + Read-only property to access the right operand (:attr:`_rightOperand`). + + :returns: The right operand. + """ return self._rightOperand def __str__(self) -> str: + """ + Formats the binary expression. + + **Format:** ``lhs + rhs`` + + :returns: Formatted binary expression. + """ return "{leftOperator}{leftOperand!s}{middleOperator}{rightOperand!s}{rightOperator}".format( leftOperator=self._FORMAT[0], leftOperand=self._leftOperand, @@ -409,236 +1074,859 @@ def __str__(self) -> str: @export class RangeExpression(BinaryExpression): - _direction: Direction + """ + Represents the base-class of range expressions. + + A range has a direction (:data:`Direction`) and two bounds. Both operands are available as :data:`LeftOperand` and + :data:`RightOperand`. - @property + .. seealso:: + + * :class:`Ascending range expression ` + * :class:`Descending range expression ` + """ + _direction: Direction #: The range's direction, either ascending (``to``) or descending (``downto``). + + @readonly def Direction(self) -> Direction: + """ + Read-only property to access the direction (:attr:`_direction`). + + :returns: The direction. + """ return self._direction @export class AscendingRangeExpression(RangeExpression): + """ + Represents an ascending range expression (``to``). + + Both operands are available as :data:`LeftOperand` and :data:`RightOperand`. + + .. admonition:: Example + + .. code-block:: VHDL + + res := v(0 to 3); + -- ^^^^^^ <- the range + -- ^ <- LeftOperand + -- ^ <- RightOperand + """ _direction = Direction.To _FORMAT = ("", " to ", "") @export class DescendingRangeExpression(RangeExpression): + """ + Represents a descending range expression (``downto``). + + Both operands are available as :data:`LeftOperand` and :data:`RightOperand`. + + .. admonition:: Example + + .. code-block:: VHDL + + res := v(7 downto 4); + -- ^^^^^^^^^^ <- the range + -- ^ <- LeftOperand + -- ^ <- RightOperand + """ _direction = Direction.DownTo _FORMAT = ("", " downto ", "") @export class AddingExpression(BinaryExpression): - """A ``AddingExpression`` is a base-class for all adding expressions.""" + """ + Represents the base-class of all adding expressions: ``+``, ``-`` and ``&``. + + Both operands are available as :data:`LeftOperand` and :data:`RightOperand`. + + .. seealso:: + + * :class:`Addition expression ` + * :class:`Subtraction expression ` + * :class:`Concatenation expression ` + """ @export class AdditionExpression(AddingExpression): + """ + Represents an addition expression (``+``). + + Both operands are available as :data:`LeftOperand` and :data:`RightOperand`. + + .. admonition:: Example + + .. code-block:: VHDL + + res := lhs + rhs; + -- ^^^^^^^^^ <- the expression + -- ^^^ <- LeftOperand + -- ^^^ <- RightOperand + """ _FORMAT = ("", " + ", "") @export class SubtractionExpression(AddingExpression): + """ + Represents a subtraction expression (``-``). + + Both operands are available as :data:`LeftOperand` and :data:`RightOperand`. + + .. admonition:: Example + + .. code-block:: VHDL + + res := lhs - rhs; + -- ^^^^^^^^^ <- the expression + -- ^^^ <- LeftOperand + -- ^^^ <- RightOperand + """ _FORMAT = ("", " - ", "") @export class ConcatenationExpression(AddingExpression): + """ + Represents a concatenation expression (``&``). + + Both operands are available as :data:`LeftOperand` and :data:`RightOperand`. + + .. admonition:: Example + + .. code-block:: VHDL + + res := lhs & rhs; + -- ^^^^^^^^^ <- the expression + -- ^^^ <- LeftOperand + -- ^^^ <- RightOperand + """ _FORMAT = ("", " & ", "") @export class MultiplyingExpression(BinaryExpression): - """A ``MultiplyingExpression`` is a base-class for all multiplying expressions.""" + """ + Represents the base-class of all multiplying expressions: ``*``, ``/``, ``rem``, ``mod`` and ``**``. + + Both operands are available as :data:`LeftOperand` and :data:`RightOperand`. + + .. seealso:: + + * :class:`Multiply expression ` + * :class:`Division expression ` + * :class:`Remainder expression ` + * :class:`Modulo expression ` + * :class:`Exponentiation expression ` + """ @export class MultiplyExpression(MultiplyingExpression): + """ + Represents a multiplication expression (``*``). + + Both operands are available as :data:`LeftOperand` and :data:`RightOperand`. + + .. admonition:: Example + + .. code-block:: VHDL + + res := lhs * rhs; + -- ^^^^^^^^^ <- the expression + -- ^^^ <- LeftOperand + -- ^^^ <- RightOperand + """ _FORMAT = ("", " * ", "") @export class DivisionExpression(MultiplyingExpression): + """ + Represents a division expression (``/``). + + Both operands are available as :data:`LeftOperand` and :data:`RightOperand`. + + .. admonition:: Example + + .. code-block:: VHDL + + res := lhs / rhs; + -- ^^^^^^^^^ <- the expression + -- ^^^ <- LeftOperand + -- ^^^ <- RightOperand + """ _FORMAT = ("", " / ", "") @export class RemainderExpression(MultiplyingExpression): + """ + Represents a remainder expression (``rem``). + + Both operands are available as :data:`LeftOperand` and :data:`RightOperand`. + + .. admonition:: Example + + .. code-block:: VHDL + + res := lhs rem rhs; + -- ^^^^^^^^^^^ <- the expression + -- ^^^ <- LeftOperand + -- ^^^ <- RightOperand + """ _FORMAT = ("", " rem ", "") @export class ModuloExpression(MultiplyingExpression): + """ + Represents a modulo expression (``mod``). + + Both operands are available as :data:`LeftOperand` and :data:`RightOperand`. + + .. admonition:: Example + + .. code-block:: VHDL + + res := lhs mod rhs; + -- ^^^^^^^^^^^ <- the expression + -- ^^^ <- LeftOperand + -- ^^^ <- RightOperand + """ _FORMAT = ("", " mod ", "") @export class ExponentiationExpression(MultiplyingExpression): + """ + Represents an exponentiation expression (``**``). + + Both operands are available as :data:`LeftOperand` and :data:`RightOperand`. + + .. admonition:: Example + + .. code-block:: VHDL + + res := lhs ** rhs; + -- ^^^^^^^^^^ <- the expression + -- ^^^ <- LeftOperand + -- ^^^ <- RightOperand + """ _FORMAT = ("", "**", "") @export class LogicalExpression(BinaryExpression): - """A ``LogicalExpression`` is a base-class for all logical expressions.""" + """ + Represents the base-class of all binary logical expressions. + + Both operands are available as :data:`LeftOperand` and :data:`RightOperand`. + + .. seealso:: + + * :class:`And expression ` + * :class:`Nand expression ` + * :class:`Or expression ` + * :class:`Nor expression ` + * :class:`Xor expression ` + * :class:`Xnor expression ` + """ @export class AndExpression(LogicalExpression): + """ + Represents a logical ``and`` expression. + + Both operands are available as :data:`LeftOperand` and :data:`RightOperand`. + + .. admonition:: Example + + .. code-block:: VHDL + + res := lhs and rhs; + -- ^^^^^^^^^^^ <- the expression + -- ^^^ <- LeftOperand + -- ^^^ <- RightOperand + """ _FORMAT = ("", " and ", "") @export class NandExpression(LogicalExpression): + """ + Represents a logical ``nand`` expression. + + Both operands are available as :data:`LeftOperand` and :data:`RightOperand`. + + .. admonition:: Example + + .. code-block:: VHDL + + res := lhs nand rhs; + -- ^^^^^^^^^^^^ <- the expression + -- ^^^ <- LeftOperand + -- ^^^ <- RightOperand + """ _FORMAT = ("", " nand ", "") @export class OrExpression(LogicalExpression): + """ + Represents a logical ``or`` expression. + + Both operands are available as :data:`LeftOperand` and :data:`RightOperand`. + + .. admonition:: Example + + .. code-block:: VHDL + + res := lhs or rhs; + -- ^^^^^^^^^^ <- the expression + -- ^^^ <- LeftOperand + -- ^^^ <- RightOperand + """ _FORMAT = ("", " or ", "") @export class NorExpression(LogicalExpression): + """ + Represents a logical ``nor`` expression. + + Both operands are available as :data:`LeftOperand` and :data:`RightOperand`. + + .. admonition:: Example + + .. code-block:: VHDL + + res := lhs nor rhs; + -- ^^^^^^^^^^^ <- the expression + -- ^^^ <- LeftOperand + -- ^^^ <- RightOperand + """ _FORMAT = ("", " nor ", "") @export class XorExpression(LogicalExpression): + """ + Represents a logical ``xor`` expression. + + Both operands are available as :data:`LeftOperand` and :data:`RightOperand`. + + .. admonition:: Example + + .. code-block:: VHDL + + res := lhs xor rhs; + -- ^^^^^^^^^^^ <- the expression + -- ^^^ <- LeftOperand + -- ^^^ <- RightOperand + """ _FORMAT = ("", " xor ", "") @export class XnorExpression(LogicalExpression): + """ + Represents a logical ``xnor`` expression. + + Both operands are available as :data:`LeftOperand` and :data:`RightOperand`. + + .. admonition:: Example + + .. code-block:: VHDL + + res := lhs xnor rhs; + -- ^^^^^^^^^^^^ <- the expression + -- ^^^ <- LeftOperand + -- ^^^ <- RightOperand + """ _FORMAT = ("", " xnor ", "") @export class RelationalExpression(BinaryExpression): - """A ``RelationalExpression`` is a base-class for all shifting expressions.""" + """ + Represents the base-class of all relational expressions. + + Both operands are available as :data:`LeftOperand` and :data:`RightOperand`. + + .. seealso:: + + * :class:`Equal expression ` + * :class:`Unequal expression ` + * :class:`Greater than expression ` + * :class:`Greater equal expression ` + * :class:`Less than expression ` + * :class:`Less equal expression ` + * :class:`Matching relational expression ` + """ @export class EqualExpression(RelationalExpression): + """ + Represents an equality expression (``=``). + + Both operands are available as :data:`LeftOperand` and :data:`RightOperand`. + + .. admonition:: Example + + .. code-block:: VHDL + + res := lhs = rhs; + -- ^^^^^^^^^ <- the expression + -- ^^^ <- LeftOperand + -- ^^^ <- RightOperand + """ _FORMAT = ("", " = ", "") @export class UnequalExpression(RelationalExpression): + """ + Represents an inequality expression (``/=``). + + Both operands are available as :data:`LeftOperand` and :data:`RightOperand`. + + .. admonition:: Example + + .. code-block:: VHDL + + res := lhs /= rhs; + -- ^^^^^^^^^^ <- the expression + -- ^^^ <- LeftOperand + -- ^^^ <- RightOperand + """ _FORMAT = ("", " /= ", "") @export class GreaterThanExpression(RelationalExpression): + """ + Represents a greater-than expression (``>``). + + Both operands are available as :data:`LeftOperand` and :data:`RightOperand`. + + .. admonition:: Example + + .. code-block:: VHDL + + res := lhs > rhs; + -- ^^^^^^^^^ <- the expression + -- ^^^ <- LeftOperand + -- ^^^ <- RightOperand + """ _FORMAT = ("", " > ", "") @export class GreaterEqualExpression(RelationalExpression): + """ + Represents a greater-or-equal expression (``>=``). + + Both operands are available as :data:`LeftOperand` and :data:`RightOperand`. + + .. admonition:: Example + + .. code-block:: VHDL + + res := lhs >= rhs; + -- ^^^^^^^^^^ <- the expression + -- ^^^ <- LeftOperand + -- ^^^ <- RightOperand + """ _FORMAT = ("", " >= ", "") @export class LessThanExpression(RelationalExpression): + """ + Represents a less-than expression (``<``). + + Both operands are available as :data:`LeftOperand` and :data:`RightOperand`. + + .. admonition:: Example + + .. code-block:: VHDL + + res := lhs < rhs; + -- ^^^^^^^^^ <- the expression + -- ^^^ <- LeftOperand + -- ^^^ <- RightOperand + """ _FORMAT = ("", " < ", "") @export class LessEqualExpression(RelationalExpression): + """ + Represents a less-or-equal expression (``<=``). + + Both operands are available as :data:`LeftOperand` and :data:`RightOperand`. + + .. admonition:: Example + + .. code-block:: VHDL + + res := lhs <= rhs; + -- ^^^^^^^^^^ <- the expression + -- ^^^ <- LeftOperand + -- ^^^ <- RightOperand + """ _FORMAT = ("", " <= ", "") @export class MatchingRelationalExpression(RelationalExpression): + """ + Represents the base-class of all matching relational expressions. + + Matching operators return a ``bit``/``std_ulogic`` rather than a ``boolean``. Both operands are available as + :data:`LeftOperand` and :data:`RightOperand`. + + .. seealso:: + + * :class:`Matching equal expression ` + * :class:`Matching unequal expression ` + * :class:`Matching greater than expression ` + * :class:`Matching greater equal expression ` + * :class:`Matching less than expression ` + * :class:`Matching less equal expression ` + """ pass @export class MatchingEqualExpression(MatchingRelationalExpression): + """ + Represents a matching equality expression (``?=``). + + Unlike ``=``, a matching operator returns a ``bit``/``std_ulogic``. + Both operands are available as :data:`LeftOperand` and :data:`RightOperand`. + + .. admonition:: Example + + .. code-block:: VHDL + + res := lhs ?= rhs; + -- ^^^^^^^^^^ <- the expression + -- ^^^ <- LeftOperand + -- ^^^ <- RightOperand + """ _FORMAT = ("", " ?= ", "") @export class MatchingUnequalExpression(MatchingRelationalExpression): + """ + Represents a matching inequality expression (``?/=``). + + Unlike ``/=``, a matching operator returns a ``bit``/``std_ulogic``. + Both operands are available as :data:`LeftOperand` and :data:`RightOperand`. + + .. admonition:: Example + + .. code-block:: VHDL + + res := lhs ?/= rhs; + -- ^^^^^^^^^^^ <- the expression + -- ^^^ <- LeftOperand + -- ^^^ <- RightOperand + """ _FORMAT = ("", " ?/= ", "") @export class MatchingGreaterThanExpression(MatchingRelationalExpression): + """ + Represents a matching greater-than expression (``?>``). + + Unlike ``>``, a matching operator returns a ``bit``/``std_ulogic``. + Both operands are available as :data:`LeftOperand` and :data:`RightOperand`. + + .. admonition:: Example + + .. code-block:: VHDL + + res := lhs ?> rhs; + -- ^^^^^^^^^^ <- the expression + -- ^^^ <- LeftOperand + -- ^^^ <- RightOperand + """ _FORMAT = ("", " ?> ", "") @export class MatchingGreaterEqualExpression(MatchingRelationalExpression): + """ + Represents a matching greater-or-equal expression (``?>=``). + + Unlike ``>=``, a matching operator returns a ``bit``/``std_ulogic``. + Both operands are available as :data:`LeftOperand` and :data:`RightOperand`. + + .. admonition:: Example + + .. code-block:: VHDL + + res := lhs ?>= rhs; + -- ^^^^^^^^^^^ <- the expression + -- ^^^ <- LeftOperand + -- ^^^ <- RightOperand + """ _FORMAT = ("", " ?>= ", "") @export class MatchingLessThanExpression(MatchingRelationalExpression): + """ + Represents a matching less-than expression (``?<``). + + Unlike ``<``, a matching operator returns a ``bit``/``std_ulogic``. + Both operands are available as :data:`LeftOperand` and :data:`RightOperand`. + + .. admonition:: Example + + .. code-block:: VHDL + + res := lhs ?< rhs; + -- ^^^^^^^^^^ <- the expression + -- ^^^ <- LeftOperand + -- ^^^ <- RightOperand + """ _FORMAT = ("", " ?< ", "") @export class MatchingLessEqualExpression(MatchingRelationalExpression): + """ + Represents a matching less-or-equal expression (``?<=``). + + Unlike ``<=``, a matching operator returns a ``bit``/``std_ulogic``. + Both operands are available as :data:`LeftOperand` and :data:`RightOperand`. + + .. admonition:: Example + + .. code-block:: VHDL + + res := lhs ?<= rhs; + -- ^^^^^^^^^^^ <- the expression + -- ^^^ <- LeftOperand + -- ^^^ <- RightOperand + """ _FORMAT = ("", " ?<= ", "") @export class ShiftExpression(BinaryExpression): - """A ``ShiftExpression`` is a base-class for all shifting expressions.""" + """ + Represents the base-class of all shift and rotate expressions. + + Both operands are available as :data:`LeftOperand` and :data:`RightOperand`. + + .. seealso:: + + * :class:`Shift logic expression ` + * :class:`Shift arithmetic expression ` + * :class:`Rotate expression ` + """ @export class ShiftLogicExpression(ShiftExpression): + """ + Represents the base-class of the logical shift expressions ``srl`` and ``sll``. + + Both operands are available as :data:`LeftOperand` and :data:`RightOperand`. + + .. seealso:: + + * :class:`Shift right logic expression ` + * :class:`Shift left logic expression ` + """ pass @export class ShiftArithmeticExpression(ShiftExpression): + """ + Represents the base-class of the arithmetic shift expressions ``sra`` and ``sla``. + + Both operands are available as :data:`LeftOperand` and :data:`RightOperand`. + + .. seealso:: + + * :class:`Shift right arithmetic expression ` + * :class:`Shift left arithmetic expression ` + """ pass @export class RotateExpression(ShiftExpression): + """ + Represents the base-class of the rotate expressions ``ror`` and ``rol``. + + Both operands are available as :data:`LeftOperand` and :data:`RightOperand`. + + .. seealso:: + + * :class:`Rotate right expression ` + * :class:`Rotate left expression ` + """ pass @export class ShiftRightLogicExpression(ShiftLogicExpression): + """ + Represents a logical right shift expression (``srl``). + + Both operands are available as :data:`LeftOperand` and :data:`RightOperand`. + + .. admonition:: Example + + .. code-block:: VHDL + + res := lhs srl rhs; + -- ^^^^^^^^^^^ <- the expression + -- ^^^ <- LeftOperand + -- ^^^ <- RightOperand + """ _FORMAT = ("", " srl ", "") @export class ShiftLeftLogicExpression(ShiftLogicExpression): + """ + Represents a logical left shift expression (``sll``). + + Both operands are available as :data:`LeftOperand` and :data:`RightOperand`. + + .. admonition:: Example + + .. code-block:: VHDL + + res := lhs sll rhs; + -- ^^^^^^^^^^^ <- the expression + -- ^^^ <- LeftOperand + -- ^^^ <- RightOperand + """ _FORMAT = ("", " sll ", "") @export class ShiftRightArithmeticExpression(ShiftArithmeticExpression): + """ + Represents an arithmetic right shift expression (``sra``). + + Both operands are available as :data:`LeftOperand` and :data:`RightOperand`. + + .. admonition:: Example + + .. code-block:: VHDL + + res := lhs sra rhs; + -- ^^^^^^^^^^^ <- the expression + -- ^^^ <- LeftOperand + -- ^^^ <- RightOperand + """ _FORMAT = ("", " sra ", "") @export class ShiftLeftArithmeticExpression(ShiftArithmeticExpression): + """ + Represents an arithmetic left shift expression (``sla``). + + Both operands are available as :data:`LeftOperand` and :data:`RightOperand`. + + .. admonition:: Example + + .. code-block:: VHDL + + res := lhs sla rhs; + -- ^^^^^^^^^^^ <- the expression + -- ^^^ <- LeftOperand + -- ^^^ <- RightOperand + """ _FORMAT = ("", " sla ", "") @export class RotateRightExpression(RotateExpression): + """ + Represents a right rotate expression (``ror``). + + Both operands are available as :data:`LeftOperand` and :data:`RightOperand`. + + .. admonition:: Example + + .. code-block:: VHDL + + res := lhs ror rhs; + -- ^^^^^^^^^^^ <- the expression + -- ^^^ <- LeftOperand + -- ^^^ <- RightOperand + """ _FORMAT = ("", " ror ", "") @export class RotateLeftExpression(RotateExpression): + """ + Represents a left rotate expression (``rol``). + + Both operands are available as :data:`LeftOperand` and :data:`RightOperand`. + + .. admonition:: Example + + .. code-block:: VHDL + + res := lhs rol rhs; + -- ^^^^^^^^^^^ <- the expression + -- ^^^ <- LeftOperand + -- ^^^ <- RightOperand + """ _FORMAT = ("", " rol ", "") @export class QualifiedExpression(BaseExpression, ParenthesisExpression): - _operand: ExpressionUnion - _subtype: Symbol + """ + Represents a qualified expression. + + A qualified expression states the subtype (:data:`Subtype`) of its operand (:data:`Operand`), + resolving which of several overloaded meanings is intended. + + .. admonition:: Example + + .. code-block:: VHDL + + res := byte'(others => '0'); + -- ^^^^ <- Subtype + -- ^^^^^^^^^^^^^^^ <- Operand + """ + _operand: ExpressionUnion #: The expression being qualified. + _subtype: Symbol #: Reference to the subtype qualifying the expression. def __init__(self, subtype: Symbol, operand: ExpressionUnion, parent: Nullable[ModelEntity] = None) -> None: + """ + Initializes a qualified expression. + + :param subtype: Reference to the subtype qualifying the expression. + :param operand: The expression being qualified. + :param parent: The parent model entity of this entity. + """ super().__init__(parent) self._operand = operand @@ -647,45 +1935,85 @@ def __init__(self, subtype: Symbol, operand: ExpressionUnion, parent: Nullable[M self._subtype = subtype subtype.Parent = self - @property - def Operand(self): + @readonly + def Operand(self) -> ExpressionUnion: + """ + Read-only property to access the operand (:attr:`_operand`). + + :returns: The operand. + """ return self._operand - @property - def Subtyped(self): + @readonly + def Subtype(self) -> Symbol: + """ + Read-only property to access the subtype (:attr:`_subtype`). + + :returns: The subtype. + """ return self._subtype def __str__(self) -> str: + """ + Formats the qualified expression. + + **Format:** ``byte'(val)`` + + :returns: Formatted qualified expression. + """ return f"{self._subtype}'({self._operand!s})" @export class TernaryExpression(BaseExpression): - """A ``TernaryExpression`` is a base-class for all ternary expressions.""" - - _FORMAT: Tuple[str, str, str, str] - _firstOperand: ExpressionUnion - _secondOperand: ExpressionUnion - _thirdOperand: ExpressionUnion - - def __init__(self, parent: Nullable[ModelEntity] = None) -> None: + """ + Represents the base-class of all ternary expressions. + + .. seealso:: + + * :class:`When else expression ` + """ + + # FIXME: needs ClassVar[...] when pyTooling gets fixed. + _FORMAT: Tuple[str, str, str, str] #: The operator's string representation as four fragments. + _firstOperand: ExpressionUnion #: The operator's first operand. + _secondOperand: ExpressionUnion #: The operator's second operand. + _thirdOperand: ExpressionUnion #: The operator's third operand. + + def __init__( + self, + firstOperand: ExpressionUnion, + secondOperand: ExpressionUnion, + thirdOperand: ExpressionUnion, + parent: Nullable[ModelEntity] = None + ) -> None: + """ + Initializes a ternary expression. + + :param firstOperand: The operator's first operand. + :param secondOperand: The operator's second operand. + :param thirdOperand: The operator's third operand. + :param parent: The parent model entity of this entity. + """ super().__init__(parent) - # FIXME: parameters and initializers are missing !! - - @property - def FirstOperand(self): - return self._firstOperand + self._firstOperand = firstOperand + firstOperand.Parent = self - @property - def SecondOperand(self): - return self._secondOperand + self._secondOperand = secondOperand + secondOperand.Parent = self - @property - def ThirdOperand(self): - return self._thirdOperand + self._thirdOperand = thirdOperand + thirdOperand.Parent = self def __str__(self) -> str: + """ + Formats the ternary expression. + + **Format:** ``val when cond else other`` + + :returns: Formatted ternary expression. + """ return "{beforeFirstOperator}{firstOperand!s}{beforeSecondOperator}{secondOperand!s}{beforeThirdOperator}{thirdOperand!s}{lastOperator}".format( beforeFirstOperator=self._FORMAT[0], firstOperand=self._firstOperand, @@ -693,134 +2021,412 @@ def __str__(self) -> str: secondOperand=self._secondOperand, beforeThirdOperator=self._FORMAT[2], thirdOperand=self._thirdOperand, - lastOperator=self._FORMAT[4], + lastOperator=self._FORMAT[3], ) @export class WhenElseExpression(TernaryExpression): + """ + Represents a conditional expression. + + A conditional expression selects between two values (:data:`ThenValue`, :data:`ElseValue`) based on + a condition (:data:`Condition`). It is usable anywhere an expression is expected - distinct from + :class:`~pyVHDLModel.Common.ConditionalExpression`, which models the cascading ``when``/``else`` + list of a conditional *assignment*. + + .. admonition:: Example + + .. code-block:: VHDL + + res := a when f else b; + -- ^ <- ThenValue + -- ^ <- Condition + -- ^ <- ElseValue + """ + _FORMAT = ("", " when ", " else ", "") + def __init__( + self, + thenValue: ExpressionUnion, + condition: ExpressionUnion, + elseValue: ExpressionUnion, + parent: Nullable[ModelEntity] = None + ) -> None: + """ + Initializes a conditional expression. + + :param thenValue: The value if the condition holds. + :param condition: The condition selecting between both values. + :param elseValue: The value if the condition does not hold. + :param parent: The parent model entity of this entity. + """ + super().__init__(thenValue, condition, elseValue, parent) + + @readonly + def ThenValue(self) -> ExpressionUnion: + """ + Read-only property to access the then value (:attr:`_firstOperand`). + + :returns: The then value. + """ + return self._firstOperand + + @readonly + def Condition(self) -> ExpressionUnion: + """ + Read-only property to access the condition (:attr:`_secondOperand`). + + :returns: The condition. + """ + return self._secondOperand + + @readonly + def ElseValue(self) -> ExpressionUnion: + """ + Read-only property to access the else value (:attr:`_thirdOperand`). + + :returns: The else value. + """ + return self._thirdOperand + @export class FunctionCall(BaseExpression): + """ + Represents a call to a function. + + .. admonition:: Example + + .. code-block:: VHDL + + res := maximum(a, b); + -- ^^^^^^^^^^^^^ <- the call + """ pass @export class Allocation(BaseExpression): + """ + Represents the base-class of all allocations via ``new``. + + .. seealso:: + + * :class:`Subtype allocation ` + * :class:`Qualified expression allocation ` + """ pass @export class SubtypeAllocation(Allocation): - _subtype: Symbol + """ + Represents an allocation of a subtype via ``new``. + + The allocated subtype is available as :data:`Subtype`. The allocated object is default-initialized. + + .. admonition:: Example + + .. code-block:: VHDL + + p := new integer; + -- ^^^^^^^ <- Subtype + """ + _subtype: Symbol #: Reference to the subtype being allocated. def __init__(self, subtype: Symbol, parent: Nullable[ModelEntity] = None) -> None: + """ + Initializes an allocation of a subtype via ``new``. + + :param subtype: Reference to the subtype being allocated. + :param parent: The parent model entity of this entity. + """ super().__init__(parent) self._subtype = subtype subtype.Parent = self - @property + @readonly def Subtype(self) -> Symbol: + """ + Read-only property to access the subtype (:attr:`_subtype`). + + :returns: The subtype. + """ return self._subtype def __str__(self) -> str: + """ + Formats the subtype allocation. + + **Format:** ``new node`` + + :returns: Formatted subtype allocation. + """ return f"new {self._subtype!s}" @export class QualifiedExpressionAllocation(Allocation): - _qualifiedExpression: QualifiedExpression + """ + Represents an allocation initialized by a qualified expression. + + The qualified expression providing the initial value is available as :data:`QualifiedExpression`. + + .. admonition:: Example + + .. code-block:: VHDL + + p := new integer'(5); + -- ^^^^^^^^^^^ <- QualifiedExpression + """ + _qualifiedExpression: QualifiedExpression #: The qualified expression the allocated object is initialized with. def __init__(self, qualifiedExpression: QualifiedExpression, parent: Nullable[ModelEntity] = None) -> None: + """ + Initializes an allocation initialized by a qualified expression. + + :param qualifiedExpression: The qualified expression the allocated object is initialized with. + :param parent: The parent model entity of this entity. + """ super().__init__(parent) self._qualifiedExpression = qualifiedExpression qualifiedExpression.Parent = self - @property + @readonly def QualifiedExpression(self) -> QualifiedExpression: + """ + Read-only property to access the qualified expression (:attr:`_qualifiedExpression`). + + :returns: The qualified expression. + """ return self._qualifiedExpression def __str__(self) -> str: + """ + Formats the qualified expression allocation. + + **Format:** ``new byte'(val)`` + + :returns: Formatted qualified expression allocation. + """ return f"new {self._qualifiedExpression!s}" @export class AggregateElement(ModelEntity): - """A ``AggregateElement`` is a base-class for all aggregate elements.""" + """ + Represents the base-class of all aggregate elements. + + Every element carries the value assigned to it (:data:`Expression`). + + .. seealso:: + + * :class:`Simple aggregate element ` + * :class:`Indexed aggregate element ` + * :class:`Ranged aggregate element ` + * :class:`Named aggregate element ` + * :class:`Others aggregate element ` + """ - _expression: ExpressionUnion + _expression: ExpressionUnion #: The expression this aggregate element supplies. def __init__(self, expression: ExpressionUnion, parent: Nullable[ModelEntity] = None) -> None: + """ + Initializes an aggregate element. + + :param expression: The expression this aggregate element supplies. + :param parent: The parent model entity of this entity. + """ super().__init__(parent) self._expression = expression expression.Parent = self - @property - def Expression(self): + @readonly + def Expression(self) -> ExpressionUnion: + """ + Read-only property to access the expression (:attr:`_expression`). + + :returns: The expression. + """ return self._expression @export class SimpleAggregateElement(AggregateElement): + """ + Represents an aggregate element given by position. + + A positional element has no choice of its own; only its value (:data:`Expression`). + + .. admonition:: Example + + .. code-block:: VHDL + + res := ('1', '0', '1', '0', '1', '0', '1', '0'); + -- ^^^ <- Expression + """ def __str__(self) -> str: + """ + Formats the simple aggregate element. + + **Format:** ``val`` + + :returns: Formatted simple aggregate element. + """ return str(self._expression) @export class IndexedAggregateElement(AggregateElement): - _index: int + """ + Represents an aggregate element chosen by an index. + + The index is available as :data:`Index`, the assigned value as :data:`Expression`. + + .. admonition:: Example + + .. code-block:: VHDL + + res := (0 => '1', others => '0'); + -- ^ <- Index + -- ^^^ <- Expression + """ + _index: int #: The index selecting the element this value is assigned to. def __init__(self, index: ExpressionUnion, expression: ExpressionUnion, parent: Nullable[ModelEntity] = None) -> None: + """ + Initializes an aggregate element chosen by an index. + + :param index: The index selecting the element this value is assigned to. + :param expression: The expression this aggregate element supplies. + :param parent: The parent model entity of this entity. + """ super().__init__(expression, parent) self._index = index - @property + @readonly def Index(self) -> int: + """ + Read-only property to access the index (:attr:`_index`). + + :returns: The index. + """ return self._index def __str__(self) -> str: + """ + Formats the indexed aggregate element. + + **Format:** ``0 => val`` + + :returns: Formatted indexed aggregate element. + """ return f"{self._index!s} => {self._expression!s}" @export class RangedAggregateElement(AggregateElement): - _range: Range + """ + Represents an aggregate element chosen by a range. + + The range is available as :data:`Range`, the assigned value as :data:`Expression`. + + .. admonition:: Example + + .. code-block:: VHDL + + res := (1 to 3 => '0', others => '1'); + -- ^^^^^^ <- Range + -- ^^^ <- Expression + """ + _range: Range #: The range selecting the elements this value is assigned to. def __init__(self, rng: Range, expression: ExpressionUnion, parent: Nullable[ModelEntity] = None) -> None: + """ + Initializes an aggregate element chosen by a range. + + :param rng: The range selecting the elements this value is assigned to. + :param expression: The expression this aggregate element supplies. + :param parent: The parent model entity of this entity. + """ super().__init__(expression, parent) self._range = rng rng.Parent = self - @property + @readonly def Range(self) -> Range: + """ + Read-only property to access the range (:attr:`_range`). + + :returns: The range. + """ return self._range def __str__(self) -> str: + """ + Formats the ranged aggregate element. + + **Format:** ``0 to 3 => val`` + + :returns: Formatted ranged aggregate element. + """ return f"{self._range!s} => {self._expression!s}" @export class NamedAggregateElement(AggregateElement): - _name: Symbol + """ + Represents an aggregate element chosen by a name. + + Used for record aggregates, where the choice names a record element (:data:`Name`). + + .. admonition:: Example + + .. code-block:: VHDL + + r := (a => '1', b => '0'); + -- ^ <- Name + -- ^^^ <- Expression + """ + _name: Symbol #: Reference to the name selecting the element this value is assigned to. def __init__(self, name: Symbol, expression: ExpressionUnion, parent: Nullable[ModelEntity] = None) -> None: + """ + Initializes an aggregate element chosen by a name. + + :param name: Reference to the name selecting the element this value is assigned to. + :param expression: The expression this aggregate element supplies. + :param parent: The parent model entity of this entity. + """ super().__init__(expression, parent) self._name = name name.Parent = self - @property + @readonly def Name(self) -> Symbol: + """ + Read-only property to access the name (:attr:`_name`). + + :returns: The name. + """ return self._name def __str__(self) -> str: + """ + Formats the named aggregate element. + + **Format:** ``elem => val`` + + :returns: Formatted named aggregate element. + """ return "{name!s} => {value!s}".format( name=self._name, value=self._expression, @@ -829,7 +2435,27 @@ def __str__(self) -> str: @export class OthersAggregateElement(AggregateElement): + """ + Represents the ``others`` element of an aggregate. + + It supplies the value (:data:`Expression`) for every choice not named explicitly. + + .. admonition:: Example + + .. code-block:: VHDL + + res := (0 => '1', others => '0'); + -- ^^^^^^ <- the choice + -- ^^^ <- Expression + """ def __str__(self) -> str: + """ + Formats the ``others`` aggregate element. + + **Format:** ``others => val`` + + :returns: Formatted ``others`` aggregate element. + """ return "others => {value!s}".format( value=self._expression, ) @@ -837,9 +2463,28 @@ def __str__(self) -> str: @export class Aggregate(BaseExpression): - _elements: List[AggregateElement] + """ + Represents an aggregate. + + An aggregate composes a value from its elements (:data:`Elements`), each of which associates a + choice with a value. + + .. admonition:: Example + + .. code-block:: VHDL + + res := (0 => '1', 1 to 3 => '0', others => '1'); + -- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ <- Elements + """ + _elements: List[AggregateElement] #: List of all elements of this aggregate, in the order they were written. def __init__(self, elements: Iterable[AggregateElement], parent: Nullable[ModelEntity] = None) -> None: + """ + Initializes an aggregate. + + :param elements: List of all elements of this aggregate, in the order they were written. + :param parent: The parent model entity of this entity. + """ super().__init__(parent) self._elements = [] @@ -847,11 +2492,23 @@ def __init__(self, elements: Iterable[AggregateElement], parent: Nullable[ModelE self._elements.append(element) element.Parent = self - @property + @readonly def Elements(self) -> List[AggregateElement]: + """ + Read-only property to access the elements (:attr:`_elements`). + + :returns: List of elements. + """ return self._elements def __str__(self) -> str: + """ + Formats the aggregate. + + **Format:** ``(1, others => 0)`` + + :returns: Formatted aggregate. + """ choices = [str(element) for element in self._elements] return "({choices})".format( choices=", ".join(choices) diff --git a/pyVHDLModel/IEEE.py b/pyVHDLModel/IEEE.py index a6666e516..bffb31b0b 100644 --- a/pyVHDLModel/IEEE.py +++ b/pyVHDLModel/IEEE.py @@ -78,15 +78,15 @@ class Ieee(PredefinedLibrary): * Synopsys packages - * :class:`~pyVHDLModel.IEEE.Std_logic_arith` - * :class:`~pyVHDLModel.IEEE.Std_logic_misc` - * :class:`~pyVHDLModel.IEEE.Std_logic_signed` - * :class:`~pyVHDLModel.IEEE.Std_logic_textio` - * :class:`~pyVHDLModel.IEEE.Std_logic_unsigned` + * :class:`~pyVHDLModel.IEEE.Std_Logic_Arith` + * :class:`~pyVHDLModel.IEEE.Std_Logic_Misc` + * :class:`~pyVHDLModel.IEEE.Std_Logic_Signed` + * :class:`~pyVHDLModel.IEEE.Std_Logic_TextIO` + * :class:`~pyVHDLModel.IEEE.Std_Logic_Unsigned` * Mentor Graphics packages - * :class:`~pyVHDLModel.IEEE.Std_logic_arith` + * :class:`~pyVHDLModel.IEEE.Std_Logic_Arith` * VITAL packages @@ -100,9 +100,15 @@ class Ieee(PredefinedLibrary): * Library :class:`~pyVHDLModel.STD.Std` """ - _flavor: IEEEFlavor + _flavor: IEEEFlavor #: The flavor of the ``ieee`` library this instance provides. def __init__(self, flavor: Nullable[IEEEFlavor] = None) -> None: + """ + Initializes the ``ieee`` library. + + :param flavor: The flavor of the ``ieee`` library this instance provides. + :raises VHDLModelException: If the given IEEE library flavor is unknown. + """ super().__init__(PACKAGES) self._flavor = IEEEFlavor.IEEE @@ -122,6 +128,11 @@ def __init__(self, flavor: Nullable[IEEEFlavor] = None) -> None: @readonly def Flavor(self) -> IEEEFlavor: + """ + Read-only property to access the flavor (:attr:`_flavor`). + + :returns: The flavor. + """ return self._flavor def LoadSynopsysPackages(self) -> None: @@ -164,6 +175,9 @@ class Math_Complex(PredefinedPackage): """ def __init__(self) -> None: + """ + Initializes the ``math_complex`` package. + """ super().__init__() self._AddPackageClause(("work.math_real.all",)) @@ -176,6 +190,9 @@ class Math_Complex_Body(PredefinedPackageBody): """ def __init__(self) -> None: + """ + Initializes the ``math_complex`` package body. + """ super().__init__() self._AddPackageClause(("work.math_real.all",)) @@ -193,6 +210,9 @@ class Std_Logic_1164(PredefinedPackage): """ def __init__(self) -> None: + """ + Initializes the ``std_logic_1164`` package. + """ super().__init__() self._AddPackageClause(("STD.TEXTIO.all", )) @@ -240,6 +260,9 @@ class Std_Logic_TextIO(PredefinedPackage): """ def __init__(self) -> None: + """ + Initializes the ``std_logic_textio`` package. + """ super().__init__() self._AddPackageClause(("STD.TEXTIO.all", )) @@ -254,6 +277,9 @@ class Numeric_Bit(PredefinedPackage): """ def __init__(self) -> None: + """ + Initializes the ``numeric_bit`` package. + """ super().__init__() self._AddPackageClause(("STD.TEXTIO.all", )) @@ -280,6 +306,9 @@ class Numeric_Bit_Unsigned_Body(PredefinedPackageBody): """ def __init__(self) -> None: + """ + Initializes the ``numeric_bit_unsigned`` package body. + """ super().__init__() self._AddLibraryClause(("IEEE", )) @@ -298,6 +327,9 @@ class Numeric_Std(PredefinedPackage): """ def __init__(self) -> None: + """ + Initializes the ``numeric_std`` package. + """ super().__init__() self._AddPackageClause(("STD.TEXTIO.all", )) @@ -337,6 +369,9 @@ class Numeric_Std_Unsigned(PredefinedPackage): """ def __init__(self) -> None: + """ + Initializes the ``numeric_std_unsigned`` package. + """ super().__init__() self._AddLibraryClause(("IEEE", )) @@ -350,6 +385,9 @@ class Numeric_Std_Unsigned_Body(PredefinedPackageBody): """ def __init__(self) -> None: + """ + Initializes the ``numeric_std_unsigned`` package body. + """ super().__init__() self._AddLibraryClause(("IEEE", )) @@ -370,6 +408,9 @@ class Fixed_Generic_Pkg(PredefinedPackage): """ def __init__(self) -> None: + """ + Initializes the ``fixed_generic_pkg`` package. + """ super().__init__() self._AddPackageClause(("STD.TEXTIO.all", )) @@ -386,6 +427,9 @@ class Fixed_Generic_Pkg_Body(PredefinedPackageBody): """ def __init__(self) -> None: + """ + Initializes the ``fixed_generic_pkg`` package body. + """ super().__init__() self._AddLibraryClause(("IEEE", )) @@ -398,6 +442,9 @@ class Fixed_Pkg(PredefinedPackage): Predefined package ``ieee.fixed_pkg``. """ def __init__(self) -> None: + """ + Initializes the ``fixed_pkg`` package. + """ super().__init__() self._AddLibraryClause(("IEEE", )) @@ -410,6 +457,9 @@ class Float_Generic_Pkg(PredefinedPackage): """ def __init__(self) -> None: + """ + Initializes the ``float_generic_pkg`` package. + """ super().__init__() self._AddPackageClause(("STD.TEXTIO.all", )) @@ -433,6 +483,9 @@ class Float_Pkg(PredefinedPackage): """ def __init__(self) -> None: + """ + Initializes the ``float_pkg`` package. + """ super().__init__() self._AddLibraryClause(("IEEE", )) @@ -456,13 +509,16 @@ def __init__(self) -> None: @export -class Std_Logic_Arith(PredefinedPackage): +class Std_Logic_Arith_MentorGraphics(PredefinedPackage): """ Predefined Mentor Graphics package ``ieee.std_logic_arith``. """ def __init__(self) -> None: - super().__init__() + """ + Initializes the ``std_logic_arith`` package. + """ + super().__init__("Std_Logic_Arith") self._AddLibraryClause(("IEEE", )) @@ -471,14 +527,20 @@ def __init__(self) -> None: @export -class Std_Logic_Arith_Body(PredefinedPackageBody): +class Std_Logic_Arith_Body_MentorGraphics(PredefinedPackageBody): """ Predefined package body of Mentor Graphics package ``ieee.std_logic_arith``. """ + def __init__(self) -> None: + """ + Initializes the ``std_logic_arith`` package body. + """ + super().__init__("Std_Logic_Arith") + MENTOR_GRAPHICS_PACKAGES = ( - (Std_Logic_Arith, Std_Logic_Arith_Body), + (Std_Logic_Arith_MentorGraphics, Std_Logic_Arith_Body_MentorGraphics), ) @@ -489,6 +551,9 @@ class VITAL_Timing(PredefinedPackage): """ def __init__(self) -> None: + """ + Initializes the ``VITAL_Timing`` package. + """ super().__init__() self._AddLibraryClause(("IEEE", )) @@ -502,6 +567,9 @@ class VITAL_Timing_Body(PredefinedPackageBody): """ def __init__(self) -> None: + """ + Initializes the ``VITAL_Timing`` package body. + """ super().__init__() self._AddLibraryClause(("STD", )) @@ -515,6 +583,9 @@ class VITAL_Primitives(PredefinedPackage): """ def __init__(self) -> None: + """ + Initializes the ``VITAL_Primitives`` package. + """ super().__init__() self._AddLibraryClause(("IEEE", )) @@ -529,6 +600,9 @@ class VITAL_Primitives_Body(PredefinedPackageBody): """ def __init__(self) -> None: + """ + Initializes the ``VITAL_Primitives`` package body. + """ super().__init__() self._AddLibraryClause(("STD", )) @@ -542,6 +616,9 @@ class VITAL_Memory(PredefinedPackage): """ def __init__(self) -> None: + """ + Initializes the ``VITAL_Memory`` package. + """ super().__init__() self._AddLibraryClause(("IEEE", )) @@ -560,6 +637,9 @@ class VITAL_Memory_Body(PredefinedPackageBody): """ def __init__(self) -> None: + """ + Initializes the ``VITAL_Memory`` package body. + """ super().__init__() self._AddLibraryClause(("IEEE", )) @@ -579,13 +659,16 @@ def __init__(self) -> None: @export -class Std_Logic_Arith(PredefinedPackage): +class Std_Logic_Arith_Synopsys(PredefinedPackage): """ Predefined Synopsys package ``ieee.std_logic_arith``. """ def __init__(self) -> None: - super().__init__() + """ + Initializes the ``std_logic_arith`` package. + """ + super().__init__("Std_Logic_Arith") self._AddLibraryClause(("IEEE", )) self._AddPackageClause(("IEEE.std_logic_1164.all", )) @@ -598,6 +681,9 @@ class Std_Logic_Misc(PredefinedPackage): """ def __init__(self) -> None: + """ + Initializes the ``std_logic_misc`` package. + """ super().__init__() self._AddLibraryClause(("IEEE", )) @@ -618,6 +704,9 @@ class Std_Logic_Signed(PredefinedPackage): """ def __init__(self) -> None: + """ + Initializes the ``std_logic_signed`` package. + """ super().__init__() self._AddLibraryClause(("IEEE", )) @@ -626,13 +715,16 @@ def __init__(self) -> None: @export -class Std_Logic_TextIO(PredefinedPackage): +class Std_Logic_TextIO_Synopsys(PredefinedPackage): """ Predefined Synopsys package ``ieee.std_logic_textio``. """ def __init__(self) -> None: - super().__init__() + """ + Initializes the ``std_logic_textio`` package. + """ + super().__init__("Std_Logic_TextIO") self._AddPackageClause(("STD.textio.all", )) @@ -647,6 +739,9 @@ class Std_Logic_Unsigned(PredefinedPackage): """ def __init__(self) -> None: + """ + Initializes the ``std_logic_unsigned`` package. + """ super().__init__() self._AddLibraryClause(("IEEE", )) @@ -655,9 +750,9 @@ def __init__(self) -> None: SYNOPSYS_PACKAGES = ( - (Std_Logic_Arith, None), + (Std_Logic_Arith_Synopsys, None), (Std_Logic_Misc, Std_Logic_Misc_Body), (Std_Logic_Signed, None), - (Std_Logic_TextIO, None), + (Std_Logic_TextIO_Synopsys, None), (Std_Logic_Unsigned, None), ) diff --git a/pyVHDLModel/Instantiation.py b/pyVHDLModel/Instantiation.py index 23343ee78..49fb34be6 100644 --- a/pyVHDLModel/Instantiation.py +++ b/pyVHDLModel/Instantiation.py @@ -34,74 +34,272 @@ Instantiations of packages, procedures, functions and protected types. """ -from typing import List, Optional as Nullable +from typing import List, Iterable, Optional as Nullable from pyTooling.Decorators import export, readonly from pyTooling.MetaClasses import ExtendedType from pyVHDLModel import VHDLModelException from pyVHDLModel.Base import ModelEntity -from pyVHDLModel.DesignUnit import Package +from pyVHDLModel.DesignUnit import Package, ContextUnion from pyVHDLModel.Association import GenericAssociationItem from pyVHDLModel.Subprogram import Procedure, Function, Subprogram -from pyVHDLModel.Symbol import PackageReferenceSymbol +from pyVHDLModel.Symbol import PackageReferenceSymbol, SubprogramReferenceSymbol, SubtypeSymbol @export class GenericInstantiationMixin(metaclass=ExtendedType, mixin=True): + """ + A mixin-class for instantiations passing generic actuals. + + .. seealso:: + + * :class:`Generic entity instantiation mixin ` + * :class:`Subprogram instantiation mixin ` + * :class:`Package instantiation ` + """ def __init__(self) -> None: + """ + Initializes a generic instantiation. + """ pass @export class GenericEntityInstantiationMixin(GenericInstantiationMixin, mixin=True): + """ + A mixin-class for instantiations of a design entity. + """ def __init__(self) -> None: + """ + Initializes a generic entity instantiation. + """ pass @export class SubprogramInstantiationMixin(GenericInstantiationMixin, mixin=True): - _subprogramReference: Subprogram # FIXME: is this a subprogram symbol? + """ + A mixin-class for instantiations of a generic subprogram. - def __init__(self) -> None: + .. seealso:: + + * :class:`Procedure instantiation ` + * :class:`Function instantiation ` + """ + _subprogramReference: SubprogramReferenceSymbol #: Reference to the instantiated generic subprogram. + _genericAssociationItems: List[GenericAssociationItem] #: List of all generic associations in the generic map aspect. + + def __init__( + self, + subprogramReference: SubprogramReferenceSymbol, + genericAssociationItems: Nullable[Iterable[GenericAssociationItem]] = None + ) -> None: + """ + Initializes a subprogram instantiation. + + :param subprogramReference: Reference to the instantiated generic subprogram. + :param genericAssociationItems: List of all generic associations in the generic map aspect. + """ super().__init__() - self._subprogramReference = None + + self._subprogramReference = subprogramReference + subprogramReference.Parent = self + + self._genericAssociationItems = [] + if genericAssociationItems is not None: + for association in genericAssociationItems: + self._genericAssociationItems.append(association) + association.Parent = self + + @readonly + def SubprogramReference(self) -> SubprogramReferenceSymbol: + """ + Read-only property to access the subprogram reference (:attr:`_subprogramReference`). + + :returns: The subprogram reference. + """ + return self._subprogramReference + + @readonly + def GenericAssociationItems(self) -> List[GenericAssociationItem]: + """ + Read-only property to access the generic association items (:attr:`_genericAssociationItems`). + + :returns: List of generic association items. + """ + return self._genericAssociationItems @export class ProcedureInstantiation(Procedure, SubprogramInstantiationMixin): - pass + """ + Represents the instantiation of a generic procedure. + + .. admonition:: Example + + .. code-block:: VHDL + + procedure p is new gp generic map (N => 1); + -- ^ <- Identifier + -- ^^ <- GenericProcedure + """ + + def __init__( + self, + identifier: str, + subprogramReference: SubprogramReferenceSymbol, + genericAssociationItems: Nullable[Iterable[GenericAssociationItem]] = None, + genericItems: Nullable[Iterable] = None, + parameterItems: Nullable[Iterable] = None, + declaredItems: Nullable[Iterable] = None, + statements: Nullable[Iterable] = None, + documentation: Nullable[str] = None, + parent: Nullable[ModelEntity] = None + ) -> None: + """ + Initializes a procedure instantiation. + + :param identifier: The identifier of a model entity. + :param subprogramReference: Reference to the instantiated generic subprogram. + :param genericAssociationItems: List of all generic associations in the generic map aspect. + :param genericItems: List of all generics, in declaration order. + :param parameterItems: List of all parameters, in declaration order. + :param declaredItems: List of all declared items in this sequential declaration region. + :param statements: List of all sequential statements in the subprogram's body. + :param documentation: The documentation comment associated with this declaration. + :param parent: The parent model entity of this entity. + """ + super().__init__(identifier, genericItems, parameterItems, declaredItems, statements, documentation, parent) + SubprogramInstantiationMixin.__init__(self, subprogramReference, genericAssociationItems) @export class FunctionInstantiation(Function, SubprogramInstantiationMixin): - pass + """ + Represents the instantiation of a generic function. + + .. admonition:: Example + + .. code-block:: VHDL + + function f is new gf generic map (N => 1); + -- ^ <- Identifier + -- ^^ <- GenericFunction + """ + + def __init__( + self, + identifier: str, + subprogramReference: SubprogramReferenceSymbol, + isPure: bool = True, + genericAssociationItems: Nullable[Iterable[GenericAssociationItem]] = None, + genericItems: Nullable[Iterable] = None, + parameterItems: Nullable[Iterable] = None, + declaredItems: Nullable[Iterable] = None, + statements: Nullable[Iterable] = None, + documentation: Nullable[str] = None, + parent: Nullable[ModelEntity] = None + ) -> None: + # NOTE: deliberately calls Subprogram.__init__ directly, not super().__init__() (which would + # resolve to Function.__init__ and require a returnType that can never be known here - see + # the class docstring above). + """ + Initializes a function instantiation. + + :param identifier: The identifier of a model entity. + :param subprogramReference: Reference to the instantiated generic subprogram. + :param isPure: ``True`` if the subprogram was declared pure. + :param genericAssociationItems: List of all generic associations in the generic map aspect. + :param genericItems: List of all generics, in declaration order. + :param parameterItems: List of all parameters, in declaration order. + :param declaredItems: List of all declared items in this sequential declaration region. + :param statements: List of all sequential statements in the subprogram's body. + :param documentation: The documentation comment associated with this declaration. + :param parent: The parent model entity of this entity. + """ + Subprogram.__init__(self, identifier, isPure, genericItems, parameterItems, declaredItems, statements, documentation, parent) + SubprogramInstantiationMixin.__init__(self, subprogramReference, genericAssociationItems) + + self._returnType = None + + @readonly + def ReturnType(self) -> Nullable[SubtypeSymbol]: + """ + Read-only property to access the return type (:attr:`_returnType`). + + :returns: The return type, or ``None`` if not set. + """ + return self._returnType @export class PackageInstantiation(Package, GenericInstantiationMixin): # TODO: maybe a PackageBase class is needed to share members. - _packageReference: PackageReferenceSymbol - _genericAssociations: List[GenericAssociationItem] + """ + Represents the instantiation of a generic package. + + .. admonition:: Example + + .. code-block:: VHDL - def __init__(self, identifier: str, genericPackage: PackageReferenceSymbol, documentation: Nullable[str] = None, parent: Nullable[ModelEntity] = None) -> None: - super().__init__(identifier, documentation=documentation, parent=parent) + package p is new gp generic map (N => 1); + -- ^ <- Identifier + -- ^^ <- PackageReference + """ + _packageReference: PackageReferenceSymbol #: Reference to the instantiated generic package. + _genericAssociationItems: List[GenericAssociationItem] #: List of all generic associations in the generic map aspect. + + def __init__( + self, + identifier: str, + genericPackage: PackageReferenceSymbol, + contextItems: Nullable[Iterable[ContextUnion]] = None, + genericAssociationItems: Nullable[Iterable[GenericAssociationItem]] = None, + documentation: Nullable[str] = None, + parent: Nullable[ModelEntity] = None + ) -> None: + """ + Initializes a package instantiation. + + :param identifier: The identifier of a model entity. + :param genericPackage: Reference to the instantiated generic package. + :param contextItems: List of all context items (library, use and context clauses). + :param genericAssociationItems: List of all generic associations in the generic map aspect. + :param documentation: The documentation comment associated with this declaration. + :param parent: The parent model entity of this entity. + """ + super().__init__(identifier, contextItems, documentation=documentation, parent=parent) GenericEntityInstantiationMixin.__init__(self) self._packageReference = genericPackage self._packageReference.Parent = self # TODO: extract to mixin - self._genericAssociations = [] + self._genericAssociationItems = [] + if genericAssociationItems is not None: + for association in genericAssociationItems: + self._genericAssociationItems.append(association) + association.Parent = self @readonly def PackageReference(self) -> PackageReferenceSymbol: + """ + Read-only property to access the package reference (:attr:`_packageReference`). + + :returns: The package reference. + """ return self._packageReference @readonly - def GenericAssociations(self) -> List[GenericAssociationItem]: - return self._genericAssociations + def GenericAssociationItems(self) -> List[GenericAssociationItem]: + """ + Read-only property to access the generic association items (:attr:`_genericAssociationItems`). + + :returns: List of generic association items. + """ + return self._genericAssociationItems - def Instantiate(self): + def Instantiate(self) -> None: genericPackage: Package = self._packageReference.Package if genericPackage is None: raise VHDLModelException(f"PackageInstantiation '{self.Identifier}' isn't linked to the generic package '{self._packageReference.Name}'.") diff --git a/pyVHDLModel/Interface.py b/pyVHDLModel/Interface.py index 06a0e1820..a0a419e6e 100644 --- a/pyVHDLModel/Interface.py +++ b/pyVHDLModel/Interface.py @@ -34,13 +34,14 @@ Interface items are used in generic, port and parameter declarations. """ -from typing import Iterable, Optional as Nullable, List, Iterator +from typing import Iterable, Optional as Nullable, List, Iterator, Tuple from pyTooling.Decorators import export, readonly from pyTooling.MetaClasses import ExtendedType -from pyVHDLModel.Symbol import Symbol +from pyVHDLModel.Symbol import Symbol, SubtypeSymbol, ModeViewSymbol from pyVHDLModel.Base import ModelEntity, DocumentedEntityMixin, NamedEntityMixin, OptionallyNamedEntityMixin +from pyVHDLModel.Base import MultipleNamedEntityMixin, identifiersOf from pyVHDLModel.Base import ExpressionUnion, Mode from pyVHDLModel.Object import Constant, Signal, Variable, File from pyVHDLModel.Subprogram import Procedure, Function @@ -48,48 +49,292 @@ @export -class InterfaceItemMixin(DocumentedEntityMixin, mixin=True): - """An ``InterfaceItem`` is a base-class for all mixin-classes for all interface items.""" +class ModeViewElement(ModelEntity, MultipleNamedEntityMixin): + """ + Base-class for one element definition inside a mode view declaration (VHDL-2019). An element may name + several fields sharing the same specification (e.g. ``a, b : out;``), hence + :class:`~pyVHDLModel.Base.MultipleNamedEntityMixin` is inherited. - def __init__(self, documentation: Nullable[str] = None) -> None: - super().__init__(documentation) + .. seealso:: + + * :class:`Simple mode view element ` + * :class:`Composite mode view element ` + """ + + def __init__(self, identifiers: Iterable[str], parent: Nullable[ModelEntity] = None) -> None: + """ + Initializes a mode view element. + + :param identifiers: A list of identifiers. + :param parent: The parent model entity of this entity. + """ + super().__init__(parent) + MultipleNamedEntityMixin.__init__(self, identifiers) + + +@export +class SimpleModeViewElement(ModeViewElement): + """ + A mode view element with a plain (simple) mode. + + .. admonition:: Example + + .. code-block:: VHDL + + view MyView of RecordType is + a, b : out; + -- ^^^ + end view; + """ + + _mode: Mode #: The element's mode. + + def __init__(self, identifiers: Iterable[str], mode: Mode, parent: Nullable[ModelEntity] = None) -> None: + """ + Initializes a simple mode view element. + + :param identifiers: A list of identifiers. + :param mode: The element's mode. + :param parent: The parent model entity of this entity. + """ + super().__init__(identifiers, parent) + self._mode = mode + + @readonly + def Mode(self) -> Mode: + """ + Read-only property to access the mode (:attr:`_mode`). + + :returns: The mode. + """ + return self._mode + + +@export +class CompositeModeViewElement(ModeViewElement): + """ + A mode view element that refers to another (named) mode view for an array or record sub-element. + .. admonition:: Example + + .. code-block:: VHDL + + view OuterView of OuterRecord is + b : view InnerView; + -- ^^^^^^^^^ + end view; + """ + + _modeViewName: ModeViewSymbol #: Reference to the mode view applied to this element. + + def __init__(self, identifiers: Iterable[str], modeViewName: ModeViewSymbol, parent: Nullable[ModelEntity] = None) -> None: + """ + Initializes a composite mode view element. + + :param identifiers: A list of identifiers. + :param modeViewName: Reference to the mode view applied to this element. + :param parent: The parent model entity of this entity. + """ + super().__init__(identifiers, parent) + + self._modeViewName = modeViewName + modeViewName.Parent = self + + @readonly + def ModeViewName(self) -> ModeViewSymbol: + """ + Read-only property to access the mode view name (:attr:`_modeViewName`). + + :returns: The mode view name. + """ + return self._modeViewName + + +@export +class ModeViewDeclaration(ModelEntity, NamedEntityMixin, DocumentedEntityMixin): + """ + Represents a mode view declaration (VHDL-2019). + + .. admonition:: Example + + .. code-block:: VHDL + + view MyView of RecordType is + a : out; + b : in; + end view; + + .. seealso:: + + * :class:`Port declared with a mode view ` + * :class:`Parameter declared with a mode view ` + * :class:`Reference to a mode view ` + """ + + _subtype: SubtypeSymbol #: Reference to the subtype this mode view applies to. + _elements: List[ModeViewElement] #: List of all mode view elements, in declaration order. + + def __init__( + self, + identifier: str, + subtype: SubtypeSymbol, + elements: Nullable[Iterable[ModeViewElement]] = None, + documentation: Nullable[str] = None, + parent: Nullable[ModelEntity] = None + ) -> None: + """ + Initializes a mode view declaration (VHDL-2019). + + :param identifier: The identifier of a model entity. + :param subtype: Reference to the subtype this mode view applies to. + :param elements: List of all mode view elements, in declaration order. + :param documentation: The documentation comment associated with this declaration. + :param parent: The parent model entity of this entity. + """ + super().__init__(parent) + NamedEntityMixin.__init__(self, identifier) + DocumentedEntityMixin.__init__(self, documentation) + + self._subtype = subtype + subtype.Parent = self + + self._elements = [] + if elements is not None: + for element in elements: + self._elements.append(element) + element.Parent = self + + @readonly + def Subtype(self) -> SubtypeSymbol: + """ + Read-only property to access the subtype (:attr:`_subtype`). + + :returns: The subtype. + """ + return self._subtype + + @readonly + def Elements(self) -> List[ModeViewElement]: + """ + Read-only property to access the elements (:attr:`_elements`). + + :returns: List of elements. + """ + return self._elements + + +@export +class InterfaceItemMixin(metaclass=ExtendedType, mixin=True): + """ + A mixin-class marking a declaration as an interface item. + + Interface items appear in generic clauses, port clauses and parameter lists. + + .. seealso:: + + * :class:`Generic interface item mixin ` + * :class:`Port interface item mixin ` + * :class:`Parameter interface item mixin ` + * :class:`Port signal interface item ` + """ @export class InterfaceItemWithModeMixin(metaclass=ExtendedType, mixin=True): - """An ``InterfaceItemWithMode`` is a mixin-class to provide a ``Mode`` to interface items.""" + """ + A mixin-class for interface items declared with a mode. + + The mode is available as :data:`Mode`. + + .. seealso:: - _mode: Mode + * :class:`Port interface item mixin ` + * :class:`Generic constant interface item ` + * :class:`Port simple signal interface item ` + * :class:`Parameter constant interface item ` + * :class:`Parameter variable interface item ` + * :class:`Parameter simple signal interface item ` + """ + + _mode: Mode #: The interface item's mode. def __init__(self, mode: Mode) -> None: + """ + Initializes an interface item with mode. + + :param mode: The interface item's mode. + """ self._mode = mode @readonly def Mode(self) -> Mode: + """ + Read-only property to access the mode (:attr:`_mode`). + + :returns: The mode. + """ return self._mode @export class GenericInterfaceItemMixin(InterfaceItemMixin, mixin=True): - """A ``GenericInterfaceItem`` is a mixin class for all generic interface items.""" + """ + A mixin-class for all items in a generic clause. + + .. seealso:: + + * :class:`Generic constant interface item ` + * :class:`Generic type interface item ` + * :class:`Generic subprogram interface item ` + * :class:`Generic procedure interface item ` + * :class:`Generic function interface item ` + * :class:`Generic package interface item ` + """ @export class PortInterfaceItemMixin(InterfaceItemMixin, InterfaceItemWithModeMixin, mixin=True): - """A ``PortInterfaceItem`` is a mixin class for all port interface items.""" + """ + A mixin-class for all items in a port clause. + """ def __init__(self, mode: Mode) -> None: + """ + Initializes a port interface item. + + :param mode: The interface item's mode. + """ super().__init__() InterfaceItemWithModeMixin.__init__(self, mode) @export class ParameterInterfaceItemMixin(InterfaceItemMixin, mixin=True): - """A ``ParameterInterfaceItem`` is a mixin class for all parameter interface items.""" + """ + A mixin-class for all items in a subprogram's parameter list. + + .. seealso:: + + * :class:`Parameter constant interface item ` + * :class:`Parameter variable interface item ` + * :class:`Parameter signal interface item ` + * :class:`Parameter file interface item ` + """ @export class GenericConstantInterfaceItem(Constant, GenericInterfaceItemMixin, InterfaceItemWithModeMixin): + """ + Represents a constant in a generic clause. + + .. admonition:: Example + + .. code-block:: VHDL + + generic (W : positive := 8); + -- ^ <- Identifiers + -- ^^^^^^^^ <- Subtype + -- ^ <- DefaultExpression + """ def __init__( self, identifiers: Iterable[str], @@ -99,6 +344,16 @@ def __init__( documentation: Nullable[str] = None, parent: Nullable[ModelEntity] = None ) -> None: + """ + Initializes a constant in a generic clause. + + :param identifiers: A list of identifiers. + :param mode: The interface item's mode. + :param subtype: Reference to the object's subtype. + :param defaultExpression: The default value, or ``None`` if none was given. + :param documentation: The documentation comment associated with this declaration. + :param parent: The parent model entity of this entity. + """ super().__init__(identifiers, subtype, defaultExpression, documentation, parent) GenericInterfaceItemMixin.__init__(self) InterfaceItemWithModeMixin.__init__(self, mode) @@ -106,33 +361,115 @@ def __init__( @export class GenericTypeInterfaceItem(Type, GenericInterfaceItemMixin): + """ + Represents a type in a generic clause. + + A generic type introduces a type name without defining the type. + + .. admonition:: Example + + .. code-block:: VHDL + + generic (type T); + -- ^ <- Identifier + """ def __init__(self, identifier: str, documentation: Nullable[str] = None, parent: Nullable[ModelEntity] = None) -> None: + """ + Initializes a type in a generic clause. + + :param identifier: The identifier of a model entity. + :param documentation: The documentation comment associated with this declaration. + :param parent: The parent model entity of this entity. + """ super().__init__(identifier, documentation, parent) GenericInterfaceItemMixin.__init__(self) @export class GenericSubprogramInterfaceItem(GenericInterfaceItemMixin): + """ + Represents the base-class of subprograms in a generic clause. + """ pass @export class GenericProcedureInterfaceItem(Procedure, GenericInterfaceItemMixin): + """ + Represents a procedure in a generic clause. + + .. admonition:: Example + + .. code-block:: VHDL + + generic (procedure log(msg : string)); + -- ^^^ <- Identifier + -- ^^^^^^^^^^^^ <- ParameterItems + """ def __init__(self, identifier: str, documentation: Nullable[str] = None, parent: Nullable[ModelEntity] = None) -> None: - super().__init__(identifier, documentation, parent) + """ + Initializes a procedure in a generic clause. + + :param identifier: The identifier of a model entity. + :param documentation: The documentation comment associated with this declaration. + :param parent: The parent model entity of this entity. + """ + super().__init__(identifier, documentation=documentation, parent=parent) GenericInterfaceItemMixin.__init__(self) @export class GenericFunctionInterfaceItem(Function, GenericInterfaceItemMixin): - def __init__(self, identifier: str, documentation: Nullable[str] = None, parent: Nullable[ModelEntity] = None) -> None: - super().__init__(identifier, documentation, parent) + """ + Represents a function in a generic clause. + + .. admonition:: Example + + .. code-block:: VHDL + + generic (function cmp(a, b : integer) return boolean); + -- ^^^ <- Identifier + -- ^^^^^^^^^^^^^^ <- ParameterItems + -- ^^^^^^^ <- ReturnType + """ + def __init__( + self, + identifier: str, + returnType: SubtypeSymbol, + documentation: Nullable[str] = None, + parent: Nullable[ModelEntity] = None + ) -> None: + """ + Initializes a function in a generic clause. + + :param identifier: The identifier of a model entity. + :param returnType: Reference to the subtype of the function's return value. + :param documentation: The documentation comment associated with this declaration. + :param parent: The parent model entity of this entity. + """ + super().__init__(identifier, returnType, documentation=documentation, parent=parent) GenericInterfaceItemMixin.__init__(self) @export class InterfacePackage(ModelEntity, NamedEntityMixin, DocumentedEntityMixin): + """ + Represents a package as a generic of a design unit. + + An interface package parameterises a design unit with an instantiated package. + + .. seealso:: + + * :class:`Generic package interface item ` + """ def __init__(self, identifier: str, documentation: Nullable[str] = None, parent: Nullable[ModelEntity] = None) -> None: + """ + Initializes a package as a generic of a design unit. + + :param identifier: The identifier of a model entity. + :param documentation: The documentation comment associated with this declaration. + :param parent: The parent model entity of this entity. + """ super().__init__(parent) NamedEntityMixin.__init__(self, identifier) DocumentedEntityMixin.__init__(self, documentation) @@ -140,13 +477,59 @@ def __init__(self, identifier: str, documentation: Nullable[str] = None, parent: @export class GenericPackageInterfaceItem(InterfacePackage, GenericInterfaceItemMixin): + """ + Represents a package in a generic clause. + + A generic package parameterises a design unit with an instantiated package. + """ def __init__(self, identifier: str, documentation: Nullable[str] = None, parent: Nullable[ModelEntity] = None) -> None: + """ + Initializes a package in a generic clause. + + :param identifier: The identifier of a model entity. + :param documentation: The documentation comment associated with this declaration. + :param parent: The parent model entity of this entity. + """ super().__init__(identifier, documentation, parent) GenericInterfaceItemMixin.__init__(self) @export -class PortSignalInterfaceItem(Signal, PortInterfaceItemMixin): +class PortSignalInterfaceItem(Signal, InterfaceItemMixin): + """ + Represents the base-class of all signals in a port clause. + + A port is declared either with a simple mode (:class:`PortSimpleSignalInterfaceItem`) or with a + mode view (:class:`PortViewSignalInterfaceItem`). + + .. seealso:: + + * :class:`Port simple signal interface item ` + * :class:`Port view signal interface item ` + """ + + +@export +class PortSimpleSignalInterfaceItem(PortSignalInterfaceItem, InterfaceItemWithModeMixin): + """ + Represents a port declared with a simple mode. + + The port's mode is available as :data:`Mode`, its subtype as :data:`Subtype`. + + .. admonition:: Example + + .. code-block:: VHDL + + port (p : in bit); + -- ^ <- Identifiers + -- ^^ <- Mode + -- ^^^ <- Subtype + + .. seealso:: + + * :class:`Port declared with a mode view ` + """ + def __init__( self, identifiers: Iterable[str], @@ -156,12 +539,83 @@ def __init__( documentation: Nullable[str] = None, parent: Nullable[ModelEntity] = None ) -> None: + """ + Initializes a port declared with a simple mode. + + :param identifiers: A list of identifiers. + :param mode: The interface item's mode. + :param subtype: Reference to the object's subtype. + :param defaultExpression: The default value, or ``None`` if none was given. + :param documentation: The documentation comment associated with this declaration. + :param parent: The parent model entity of this entity. + """ super().__init__(identifiers, subtype, defaultExpression, documentation, parent) - PortInterfaceItemMixin.__init__(self, mode) + InterfaceItemWithModeMixin.__init__(self, mode) + + +@export +class PortViewSignalInterfaceItem(PortSignalInterfaceItem): + """ + Represents a port declared with a mode view (VHDL-2019). + + Instead of a mode, the port names a mode view (:data:`ModeViewIndication`) that assigns a mode to + each element of its record type. + + .. admonition:: Example + + .. code-block:: VHDL + + port (p : view MyView); + -- ^ <- Identifiers + -- ^^^^^^ <- ModeViewIndication + + .. seealso:: + + * :class:`Mode view declaration ` + * :class:`Port declared with a simple mode ` + """ + + def __init__( + self, + identifiers: Iterable[str], + modeViewIndication: ModeViewSymbol, + documentation: Nullable[str] = None, + parent: Nullable[ModelEntity] = None + ) -> None: + """ + Initializes a port declared with a mode view (VHDL-2019). + + :param identifiers: A list of identifiers. + :param modeViewIndication: Reference to the mode view applied to this port. + :param documentation: The documentation comment associated with this declaration. + :param parent: The parent model entity of this entity. + """ + super().__init__(identifiers, modeViewIndication, None, documentation, parent) + + @readonly + def ModeViewIndication(self) -> ModeViewSymbol: + """ + Read-only property to access the mode view indication (:attr:`_subtype`). + + :returns: The mode view indication. + """ + return self._subtype @export class ParameterConstantInterfaceItem(Constant, ParameterInterfaceItemMixin, InterfaceItemWithModeMixin): + """ + Represents a constant parameter of a subprogram. + + .. admonition:: Example + + .. code-block:: VHDL + + function fun(constant cst : in integer) return integer; + -- ^^^ <- Identifiers + -- ^^ <- Mode + -- ^^^^^^^ <- Subtype + """ def __init__( self, identifiers: Iterable[str], @@ -171,6 +625,16 @@ def __init__( documentation: Nullable[str] = None, parent: Nullable[ModelEntity] = None ) -> None: + """ + Initializes a constant parameter of a subprogram. + + :param identifiers: A list of identifiers. + :param mode: The interface item's mode. + :param subtype: Reference to the object's subtype. + :param defaultExpression: The default value, or ``None`` if none was given. + :param documentation: The documentation comment associated with this declaration. + :param parent: The parent model entity of this entity. + """ super().__init__(identifiers, subtype, defaultExpression, documentation, parent) ParameterInterfaceItemMixin.__init__(self) InterfaceItemWithModeMixin.__init__(self, mode) @@ -178,6 +642,18 @@ def __init__( @export class ParameterVariableInterfaceItem(Variable, ParameterInterfaceItemMixin, InterfaceItemWithModeMixin): + """ + Represents a variable parameter of a subprogram. + + .. admonition:: Example + + .. code-block:: VHDL + + procedure proc(variable var : out bit); + -- ^^^ <- Identifiers + -- ^^^ <- Mode + -- ^^^ <- Subtype + """ def __init__( self, identifiers: Iterable[str], @@ -187,13 +663,59 @@ def __init__( documentation: Nullable[str] = None, parent: Nullable[ModelEntity] = None ) -> None: + """ + Initializes a variable parameter of a subprogram. + + :param identifiers: A list of identifiers. + :param mode: The interface item's mode. + :param subtype: Reference to the object's subtype. + :param defaultExpression: The default value, or ``None`` if none was given. + :param documentation: The documentation comment associated with this declaration. + :param parent: The parent model entity of this entity. + """ super().__init__(identifiers, subtype, defaultExpression, documentation, parent) ParameterInterfaceItemMixin.__init__(self) InterfaceItemWithModeMixin.__init__(self, mode) @export -class ParameterSignalInterfaceItem(Signal, ParameterInterfaceItemMixin, InterfaceItemWithModeMixin): +class ParameterSignalInterfaceItem(Signal, ParameterInterfaceItemMixin): + """ + Represents a signal parameter of a subprogram. + + .. admonition:: Example + + .. code-block:: VHDL + + procedure proc(signal sig : in bit); + -- ^^^ <- Identifiers + -- ^^ <- Mode + -- ^^^ <- Subtype + + .. seealso:: + + * :class:`Parameter simple signal interface item ` + * :class:`Parameter view signal interface item ` + """ + + +@export +class ParameterSimpleSignalInterfaceItem(ParameterSignalInterfaceItem, InterfaceItemWithModeMixin): + """ + Represents a signal parameter declared with a simple mode. + + The parameter's mode is available as :data:`Mode`, its subtype as :data:`Subtype`. + + .. admonition:: Example + + .. code-block:: VHDL + + procedure proc(signal sig : in bit); + -- ^^^ <- Identifiers + -- ^^ <- Mode + -- ^^^ <- Subtype + """ + def __init__( self, identifiers: Iterable[str], @@ -203,13 +725,84 @@ def __init__( documentation: Nullable[str] = None, parent: Nullable[ModelEntity] = None ) -> None: + """ + Initializes a signal parameter declared with a simple mode. + + :param identifiers: A list of identifiers. + :param mode: The interface item's mode. + :param subtype: Reference to the object's subtype. + :param defaultExpression: The default value, or ``None`` if none was given. + :param documentation: The documentation comment associated with this declaration. + :param parent: The parent model entity of this entity. + """ super().__init__(identifiers, subtype, defaultExpression, documentation, parent) ParameterInterfaceItemMixin.__init__(self) InterfaceItemWithModeMixin.__init__(self, mode) +@export +class ParameterViewSignalInterfaceItem(ParameterSignalInterfaceItem): + """ + Represents a signal parameter declared with a mode view (VHDL-2019). + + Instead of a mode, the parameter names a mode view (:data:`ModeViewIndication`) that assigns a mode + to each element of its record type. + + .. admonition:: Example + + .. code-block:: VHDL + + procedure proc(signal sig : view MasterView); + -- ^^^ <- Identifiers + -- ^^^^^^^^^^ <- ModeViewIndication + + .. seealso:: + + * :class:`Mode view declaration ` + * :class:`Parameter declared with a simple mode ` + """ + + def __init__( + self, + identifiers: Iterable[str], + modeViewIndication: ModeViewSymbol, + documentation: Nullable[str] = None, + parent: Nullable[ModelEntity] = None + ) -> None: + """ + Initializes a signal parameter declared with a mode view (VHDL-2019). + + :param identifiers: A list of identifiers. + :param modeViewIndication: Reference to the mode view applied to this parameter. + :param documentation: The documentation comment associated with this declaration. + :param parent: The parent model entity of this entity. + """ + super().__init__(identifiers, modeViewIndication, None, documentation, parent) + ParameterInterfaceItemMixin.__init__(self) + + @readonly + def ModeViewIndication(self) -> ModeViewSymbol: + """ + Read-only property to access the mode view indication (:attr:`_subtype`). + + :returns: The mode view indication. + """ + return self._subtype + + @export class ParameterFileInterfaceItem(File, ParameterInterfaceItemMixin): + """ + Represents a file parameter of a subprogram. + + .. admonition:: Example + + .. code-block:: VHDL + + procedure proc(file fil : text_file); + -- ^^^ <- Identifiers + -- ^^^^^^^^^ <- Subtype + """ def __init__( self, identifiers: Iterable[str], @@ -217,95 +810,200 @@ def __init__( documentation: Nullable[str] = None, parent: Nullable[ModelEntity] = None ) -> None: + """ + Initializes a file parameter of a subprogram. + + :param identifiers: A list of identifiers. + :param subtype: Reference to the object's subtype. + :param documentation: The documentation comment associated with this declaration. + :param parent: The parent model entity of this entity. + """ super().__init__(identifiers, subtype, documentation, parent) ParameterInterfaceItemMixin.__init__(self) @export class WithGenericsMixin(metaclass=ExtendedType, mixin=True): - _genericItems: List[GenericInterfaceItemMixin] + """ + A mixin-class for language constructs with a generic clause. + + .. seealso:: + + * :class:`Package ` + * :class:`Entity ` + * :class:`Generic group ` + """ + _genericItems: List[GenericInterfaceItemMixin] #: List of all generics, in declaration order. def __init__( self, genericItems: Nullable[Iterable[GenericInterfaceItemMixin]] = None, ) -> None: + """ + Initializes a language construct with a generic clause. + + :param genericItems: List of all generics, in declaration order. + """ self._genericItems = [] if genericItems is not None: for item in genericItems: self._genericItems.append(item) item.Parent = self - @property + @readonly def GenericItems(self) -> List[GenericInterfaceItemMixin]: + """ + Read-only property to access the generic items (:attr:`_genericItems`). + + :returns: List of generic items. + """ return self._genericItems - @property + @readonly def GenericCount(self) -> int: + """ + Read-only property to return the number of generics in :attr:`_genericItems`. + + :returns: The generic count. + """ return len(self._genericItems) @export class WithPortsMixin(metaclass=ExtendedType, mixin=True): - _portItems: List[PortInterfaceItemMixin] + """ + A mixin-class for language constructs with a port clause. + + .. seealso:: + + * :class:`Concurrent block statement ` + * :class:`Entity ` + * :class:`Port group ` + """ + _portItems: List[PortInterfaceItemMixin] #: List of all ports, in declaration order. def __init__( self, portItems: Nullable[Iterable[PortInterfaceItemMixin]] = None, ) -> None: + """ + Initializes a language construct with a port clause. + + :param portItems: List of all ports, in declaration order. + """ self._portItems = [] if portItems is not None: for item in portItems: self._portItems.append(item) item.Parent = self - @property + @readonly def PortItems(self) -> List[PortInterfaceItemMixin]: + """ + Read-only property to access the port items (:attr:`_portItems`). + + :returns: List of port items. + """ return self._portItems - @property + @readonly def PortCount(self) -> int: + """ + Read-only property to return the number of ports in :attr:`_portItems`. + + :returns: The port count. + """ return len(self._portItems) @export class WithParametersMixin(metaclass=ExtendedType, mixin=True): - _parameterItems: List[ParameterInterfaceItemMixin] + """ + A mixin-class for language constructs with a parameter list. + + .. seealso:: + + * :class:`Parameter group ` + """ + _parameterItems: List[ParameterInterfaceItemMixin] #: List of all parameters, in declaration order. def __init__( self, parameterItems: Nullable[Iterable[ParameterInterfaceItemMixin]] = None, ) -> None: + """ + Initializes a language construct with a parameter list. + + :param parameterItems: List of all parameters, in declaration order. + """ self._parameterItems = [] if parameterItems is not None: for item in parameterItems: self._parameterItems.append(item) item.Parent = self - @property + @readonly def ParameterItems(self) -> List[ParameterInterfaceItemMixin]: + """ + Read-only property to access the parameter items (:attr:`_parameterItems`). + + :returns: List of parameter items. + """ return self._parameterItems - @property + @readonly def ParameterCount(self) -> int: + """ + Read-only property to return the number of parameters in :attr:`_parameterItems`. + + :returns: The parameter count. + """ return len(self._parameterItems) @export class InterfaceGroup(ModelEntity, OptionallyNamedEntityMixin, DocumentedEntityMixin): + """ + Represents a group of interface items sharing one clause. + + The group may be named (:data:`Identifier`), which is optional. + + .. seealso:: + + * :class:`Generic group ` + * :class:`Port group ` + * :class:`Parameter group ` + """ def __init__( self, name: Nullable[str] = None, documentation: Nullable[str] = None, parent: Nullable[ModelEntity] = None ) -> None: - """Initialize a PortGroup with a list of ports and optional name.""" + """ + Initializes a group of interface items sharing one clause. + + :param name: The group's name. + :param documentation: The documentation comment associated with this declaration. + :param parent: The parent model entity of this entity. + """ super().__init__(parent) OptionallyNamedEntityMixin.__init__(self, name) DocumentedEntityMixin.__init__(self, documentation) @export -class GenericGroup(InterfaceGroup): +class GenericGroup(InterfaceGroup, WithGenericsMixin): + """ + Represents the generic clause of a design unit. + + The generics are available as :data:`GenericItems`. + + .. seealso:: + + * :class:`Port clause ` + * :class:`Parameter list ` + """ def __init__( self, genericItems: Iterable[GenericInterfaceItemMixin], @@ -313,21 +1011,57 @@ def __init__( documentation: Nullable[str] = None, parent: Nullable[ModelEntity] = None ) -> None: + """ + Initializes a generic group. + + :param genericItems: List of all generics, in declaration order. + :param name: The group's name. + :param documentation: The documentation comment associated with this declaration. + :param parent: The parent model entity of this entity. + """ super().__init__(name, documentation, parent) WithGenericsMixin.__init__(self, genericItems) def __len__(self) -> int: + """ + Returns the number of generics in this group. + + :returns: Number of generics. + """ return len(self._genericItems) def __iter__(self) -> Iterator[GenericInterfaceItemMixin]: + """ + Iterates the generics in this group. + + :returns: An iterator over the group's generics. + """ return iter(self._genericItems) def __str__(self) -> str: - return f"GenericGroup {self._identifier} ({len(self._genericItems)}) - generics: {', '.join(p._identifier for p in self._genericItems)})" + """ + Formats the generic group. + + **Format:** ``GenericGroup: myGroup (2): WIDTH, DEPTH`` + + :returns: Formatted generic group. + """ + names = ", ".join(name for item in self._genericItems for name in identifiersOf(item)) + return f"GenericGroup: {self._identifier} ({len(self._genericItems)}): {names}" @export class PortGroup(InterfaceGroup, WithPortsMixin): + """ + Represents the port clause of a design unit. + + The ports are available as :data:`PortItems`. + + .. seealso:: + + * :class:`Generic clause ` + * :class:`Parameter list ` + """ def __init__( self, portItems: Iterable[PortInterfaceItemMixin], @@ -335,21 +1069,57 @@ def __init__( documentation: Nullable[str] = None, parent: Nullable[ModelEntity] = None ) -> None: + """ + Initializes a port group. + + :param portItems: List of all ports, in declaration order. + :param name: The group's name. + :param documentation: The documentation comment associated with this declaration. + :param parent: The parent model entity of this entity. + """ super().__init__(name, documentation, parent) WithPortsMixin.__init__(self, portItems) def __len__(self) -> int: + """ + Returns the number of ports in this group. + + :returns: Number of ports. + """ return len(self._portItems) def __iter__(self) -> Iterator[PortInterfaceItemMixin]: + """ + Iterates the ports in this group. + + :returns: An iterator over the group's ports. + """ return iter(self._portItems) def __str__(self) -> str: - return f"PortGroup: {self._identifier} ({len(self._portItems)}) - ports: {', '.join(p._identifier for p in self._portItems)})" + """ + Formats the port group. + + **Format:** ``PortGroup: myGroup (2): clock, reset`` + + :returns: Formatted port group. + """ + names = ", ".join(name for item in self._portItems for name in identifiersOf(item)) + return f"PortGroup: {self._identifier} ({len(self._portItems)}): {names}" @export -class ParameterGroup(InterfaceGroup): +class ParameterGroup(InterfaceGroup, WithParametersMixin): + """ + Represents the parameter list of a subprogram. + + The parameters are available as :data:`ParameterItems`. + + .. seealso:: + + * :class:`Generic clause ` + * :class:`Port clause ` + """ def __init__( self, parameterItems: Iterable[ParameterInterfaceItemMixin], @@ -357,14 +1127,40 @@ def __init__( documentation: Nullable[str] = None, parent: Nullable[ModelEntity] = None ) -> None: + """ + Initializes a parameter group. + + :param parameterItems: List of all parameters, in declaration order. + :param name: The group's name. + :param documentation: The documentation comment associated with this declaration. + :param parent: The parent model entity of this entity. + """ super().__init__(name, documentation, parent) WithParametersMixin.__init__(self, parameterItems) def __len__(self) -> int: + """ + Returns the number of parameters in this group. + + :returns: Number of parameters. + """ return len(self._parameterItems) def __iter__(self) -> Iterator[ParameterInterfaceItemMixin]: + """ + Iterates the parameters in this group. + + :returns: An iterator over the group's parameters. + """ return iter(self._parameterItems) def __str__(self) -> str: - return f"ParameterGroup {self._identifier} ({len(self._parameterItems)}) - parameters: {', '.join(p._identifier for p in self._parameterItems)})" + """ + Formats the parameter group. + + **Format:** ``ParameterGroup: myGroup (2): a, b`` + + :returns: Formatted parameter group. + """ + names = ", ".join(name for item in self._parameterItems for name in identifiersOf(item)) + return f"ParameterGroup: {self._identifier} ({len(self._parameterItems)}): {names}" diff --git a/pyVHDLModel/Name.py b/pyVHDLModel/Name.py index 1ae9af0a2..d828f303e 100644 --- a/pyVHDLModel/Name.py +++ b/pyVHDLModel/Name.py @@ -45,14 +45,34 @@ @export class Name(ModelEntity): - """``Name`` is the base-class for all *names* in the VHDL language model.""" + """ + ``Name`` is the base-class for all *names* in the VHDL language model. + + .. seealso:: - _identifier: str - _normalizedIdentifier: str - _root: Nullable['Name'] # TODO: seams to be unused. There is no reverse linking, or? - _prefix: Nullable['Name'] + * :class:`Simple name ` + * :class:`Parenthesis name ` + * :class:`Indexed name ` + * :class:`Sliced name ` + * :class:`Selected name ` + * :class:`Attribute name ` + * :class:`Open name ` + """ + + _identifier: str #: The name's identifier. + _normalizedIdentifier: str #: The normalized (lower case) identifier. + # TODO: seams to be unused. There is no reverse linking, or? + _root: Nullable['Name'] #: Reference to the root of the name chain. + _prefix: Nullable['Name'] #: Reference to the name's prefix, or ``None`` for a simple name. def __init__(self, identifier: str, prefix: Nullable["Name"] = None, parent: Nullable[ModelEntity] = None) -> None: + """ + Initializes a name. + + :param identifier: The name's identifier. + :param prefix: Reference to the name's prefix, or ``None`` for a simple name. + :param parent: The parent model entity of this entity. + """ super().__init__(parent) self._identifier = identifier @@ -68,7 +88,7 @@ def __init__(self, identifier: str, prefix: Nullable["Name"] = None, parent: Nul @readonly def Identifier(self) -> str: """ - The identifier the name is referencing. + Read-only property to access the identifier this name references (:attr:`_identifier`). :returns: The referenced identifier. """ @@ -77,7 +97,7 @@ def Identifier(self) -> str: @readonly def NormalizedIdentifier(self) -> str: """ - The normalized identifier the name is referencing. + Read-only property to access the normalized identifier this name references (:attr:`_normalizedIdentifier`). :returns: The referenced identifier (normalized). """ @@ -86,7 +106,7 @@ def NormalizedIdentifier(self) -> str: @readonly def Root(self) -> 'Name': """ - The root (left-most) element in a chain of names. + Read-only property to access the root (left-most) element in a chain of names (:attr:`_root`). In case the name is a :class:`simple name `, the root points to the name itself. @@ -97,7 +117,7 @@ def Root(self) -> 'Name': @readonly def Prefix(self) -> Nullable['Name']: """ - The name's prefix in a chain of names. + Read-only property to access the name's prefix in a chain of names (:attr:`_prefix`). :returns: The name left from current name, if not a simple name, otherwise ``None``. """ @@ -106,18 +126,32 @@ def Prefix(self) -> Nullable['Name']: @readonly def HasPrefix(self) -> bool: """ - Returns true, if the name has a prefix. + Check if the name has a prefix, i.e. :attr:`_prefix` is set. This is true for all names except :class:`simple names `. - :returns: ``True``, if the name as a prefix. + :returns: ``True``, if the name has a prefix. """ return self._prefix is not None def __repr__(self) -> str: + """ + Formats a representation of the name. + + **Format:** ``Name: 'sig'`` + + :returns: String representation of the name. + """ return f"Name: '{self.__str__()}'" def __str__(self) -> str: + """ + Formats the name. + + **Format:** ``sig`` + + :returns: Formatted name. + """ return self._identifier @@ -134,9 +168,21 @@ class SimpleName(Name): @export class ParenthesisName(Name): - _associations: List + """ + Represents a name followed by a parenthesized association list. + + Used where indexing and a function call are indistinguishable before resolution. + """ + _associations: List #: List of all associations in the parenthesis. def __init__(self, prefix: Name, associations: Iterable, parent: Nullable[ModelEntity] = None) -> None: + """ + Initializes a name followed by a parenthesized association list. + + :param prefix: Reference to the name's prefix, or ``None`` for a simple name. + :param associations: List of all associations in the parenthesis. + :param parent: The parent model entity of this entity. + """ super().__init__("", prefix, parent) self._associations = [] @@ -146,17 +192,46 @@ def __init__(self, prefix: Name, associations: Iterable, parent: Nullable[ModelE @readonly def Associations(self) -> List: + """ + Read-only property to access the associations (:attr:`_associations`). + + :returns: List of associations. + """ return self._associations def __str__(self) -> str: + """ + Formats the parenthesis name. + + **Format:** ``func(a, b)`` + + :returns: Formatted parenthesis name. + """ return f"{self._prefix!s}({', '.join(str(a) for a in self._associations)})" @export class IndexedName(Name): - _indices: List[ExpressionUnion] + """ + Represents a name indexing an array by one or more values. + + .. admonition:: Example + + .. code-block:: VHDL + + s <= v(0); + -- ^^^^ <- the indexed name + """ + _indices: List[ExpressionUnion] #: List of all index expressions, one per dimension. def __init__(self, prefix: Name, indices: Iterable[ExpressionUnion], parent: Nullable[ModelEntity] = None) -> None: + """ + Initializes a name indexing an array by one or more values. + + :param prefix: Reference to the name's prefix, or ``None`` for a simple name. + :param indices: List of all index expressions, one per dimension. + :param parent: The parent model entity of this entity. + """ super().__init__("", prefix, parent) self._indices = [] @@ -166,14 +241,36 @@ def __init__(self, prefix: Name, indices: Iterable[ExpressionUnion], parent: Nul @readonly def Indices(self) -> List[ExpressionUnion]: + """ + Read-only property to access the indices (:attr:`_indices`). + + :returns: List of indices. + """ return self._indices def __str__(self) -> str: + """ + Formats the indexed name. + + **Format:** ``arr(0)`` + + :returns: Formatted indexed name. + """ return f"{self._prefix!s}({', '.join(str(i) for i in self._indices)})" @export class SlicedName(Name): + """ + Represents a name selecting a slice of an array. + + .. admonition:: Example + + .. code-block:: VHDL + + vres := v(3 downto 0); + -- ^^^^^^^^^^^^^ <- the sliced name + """ pass @@ -185,21 +282,63 @@ class SelectedName(Name): For example, the library and entity name in a direct entity instantiation is a selected name. Here the entity identifier is a selected name. The library identifier is a :class:`simple name `, which is referenced by the selected name via the :attr:`~pyVHDLModel.Name.Prefix` property. + + .. seealso:: + + * :class:`All name ` """ def __init__(self, identifier: str, prefix: Name, parent: Nullable[ModelEntity] = None) -> None: + """ + Initializes a selected name. + + :param identifier: The name's identifier. + :param prefix: Reference to the name's prefix, or ``None`` for a simple name. + :param parent: The parent model entity of this entity. + """ super().__init__(identifier, prefix, parent) def __str__(self) -> str: + """ + Formats the selected name. + + **Format:** ``rec.elem`` + + :returns: Formatted selected name. + """ return f"{self._prefix!s}.{self._identifier}" @export class AttributeName(Name): + """ + Represents a name selecting an attribute of its prefix. + + .. admonition:: Example + + .. code-block:: VHDL + + for i in v'range loop + -- ^^^^^^^ <- the attribute name + """ def __init__(self, identifier: str, prefix: Name, parent: Nullable[ModelEntity] = None) -> None: + """ + Initializes a name selecting an attribute of its prefix. + + :param identifier: The name's identifier. + :param prefix: Reference to the name's prefix, or ``None`` for a simple name. + :param parent: The parent model entity of this entity. + """ super().__init__(identifier, prefix, parent) def __str__(self) -> str: + """ + Formats the attribute name. + + **Format:** ``v'range`` + + :returns: Formatted attribute name. + """ return f"{self._prefix!s}'{self._identifier}" @@ -211,6 +350,12 @@ class AllName(SelectedName): Most likely this name is used in use-statements. """ def __init__(self, prefix: Name, parent: Nullable[ModelEntity] = None) -> None: + """ + Initializes an ``all`` name. + + :param prefix: Reference to the name's prefix, or ``None`` for a simple name. + :param parent: The parent model entity of this entity. + """ super().__init__("all", prefix, parent) # TODO: the case of 'ALL' is not preserved @@ -222,7 +367,19 @@ class OpenName(Name): Most likely this name is used in port associations. """ def __init__(self, parent: Nullable[ModelEntity] = None) -> None: - super().__init__("open", parent) # TODO: the case of 'OPEN' is not preserved + """ + Initializes an ``open`` name. + + :param parent: The parent model entity of this entity. + """ + super().__init__("open", parent=parent) # TODO: the case of 'OPEN' is not preserved def __str__(self) -> str: + """ + Formats the open name. + + **Format:** ``open`` + + :returns: Formatted open name. + """ return "open" diff --git a/pyVHDLModel/Namespace.py b/pyVHDLModel/Namespace.py index 8ed96381f..27a7924ca 100644 --- a/pyVHDLModel/Namespace.py +++ b/pyVHDLModel/Namespace.py @@ -36,6 +36,7 @@ """ from typing import TypeVar, Generic, Dict, Optional as Nullable, Any, Tuple +from pyTooling.Common import getFullyQualifiedName from pyTooling.Decorators import readonly from pyVHDLModel.Object import Obj, Signal, Constant, Variable @@ -47,10 +48,23 @@ class ExtendedKeyError(KeyError): - key: str - searchedNamespaces: Tuple["Namespace", ...] + """ + A :exc:`KeyError` reporting which namespaces were searched. + + Raised when a name cannot be resolved. Besides the key (:data:`key`), it carries every namespace + visited while walking outwards (:data:`searchedNamespaces`). + """ + key: str #: The key that was not found. + searchedNamespaces: Tuple["Namespace", ...] #: The namespaces that were searched for the key. def __init__(self, key: str, searchedNamespaces: Tuple["Namespace", ...], message: str) -> None: + """ + Initializes an extended key error. + + :param key: The key that was not found. + :param searchedNamespaces: The namespaces that were searched for the key. + :param message: The error message. + """ super().__init__(message) self.key = key @@ -58,12 +72,30 @@ def __init__(self, key: str, searchedNamespaces: Tuple["Namespace", ...], messag class Namespace(Generic[K, O]): - _name: str - _parentNamespace: "Namespace" - _subNamespaces: Dict[str, "Namespace"] - _elements: Dict[K, O] + """ + Represents a namespace: the declared items visible in one declarative region. + + Namespaces nest, so a lookup that misses locally continues in the parent namespace + (:data:`ParentNamespace`). That is what makes an entity's ports visible inside its architecture, + and lets a process variable hide an outer signal. + + .. seealso:: + + * :class:`Concurrent declaration region ` + * :class:`Sequential declaration region ` + """ + _name: str #: The namespace's name. + _parentNamespace: "Namespace" #: Reference to the enclosing namespace, or ``None`` for the outermost one. + _subNamespaces: Dict[str, "Namespace"] #: Dictionary of all nested namespaces, indexed by name. + _elements: Dict[K, O] #: Dictionary of all elements declared in this namespace, indexed by name. def __init__(self, name: str, parentNamespace: Nullable["Namespace"] = None) -> None: + """ + Initializes a namespace. + + :param name: The namespace's name. + :param parentNamespace: Reference to the enclosing namespace, or ``None`` for the outermost one. + """ self._name = name self._parentNamespace = parentNamespace self._subNamespaces = {} @@ -71,19 +103,34 @@ def __init__(self, name: str, parentNamespace: Nullable["Namespace"] = None) -> @readonly def Name(self) -> str: + """ + Read-only property to access the name (:attr:`_name`). + + :returns: The name. + """ return self._name - @readonly + @property def ParentNamespace(self) -> 'Namespace': + """ + Property to access the parent namespace (:attr:`_parentNamespace`). + + :returns: The parent namespace. + """ return self._parentNamespace @ParentNamespace.setter - def ParentNamespace(self, value: 'Namespace'): + def ParentNamespace(self, value: 'Namespace') -> None: self._parentNamespace = value value._subNamespaces[self._name] = self @readonly def SubNamespaces(self) -> Dict[str, 'Namespace']: + """ + Read-only property to access the sub namespaces (:attr:`_subNamespaces`). + + :returns: Dictionary of sub namespaces. + """ return self._subNamespaces def Elements(self) -> Dict[K, O]: @@ -97,7 +144,9 @@ def FindComponent(self, componentSymbol: ComponentInstantiationSymbol) -> 'Compo if isinstance(element, Component): return element else: - raise TypeError(f"Found element '{componentSymbol._name._identifier}', but it is not a component.") + ex = TypeError(f"Found element '{componentSymbol._name._identifier}', but it is not a component.") + ex.add_note(f"Got type '{getFullyQualifiedName(element)}'.") + raise ex except KeyError: key = componentSymbol._name._identifier @@ -117,19 +166,33 @@ def FindSubtype(self, subtypeSymbol: Symbol) -> BaseType: if PossibleReference.Subtype in subtypeSymbol._possibleReferences: return element else: - raise TypeError(f"Found subtype '{subtypeSymbol._name._identifier}', but it was not expected.") + ex = TypeError(f"Found subtype '{subtypeSymbol._name._identifier}', but it was not expected.") + ex.add_note(f"Got type '{getFullyQualifiedName(element)}'.") + ex.add_note(f"Expected one of: {subtypeSymbol._possibleReferences}.") + raise ex elif isinstance(element, FullType): if PossibleReference.Type in subtypeSymbol._possibleReferences: return element else: - raise TypeError(f"Found type '{subtypeSymbol._name._identifier}', but it was not expected.") + ex = TypeError(f"Found type '{subtypeSymbol._name._identifier}', but it was not expected.") + ex.add_note(f"Got type '{getFullyQualifiedName(element)}'.") + ex.add_note(f"Expected one of: {subtypeSymbol._possibleReferences}.") + raise ex else: - raise TypeError(f"Found element '{subtypeSymbol._name._identifier}', but it is not a type or subtype.") + ex = TypeError(f"Found element '{subtypeSymbol._name._identifier}', but it is not a type or subtype.") + ex.add_note(f"Got type '{getFullyQualifiedName(element)}'.") + raise ex except KeyError: + key = subtypeSymbol._name._identifier + if (parentNamespace := self._parentNamespace) is None: - raise KeyError(f"Subtype '{subtypeSymbol._name._identifier}' not found in '{self._name}'.") + raise ExtendedKeyError(key, (self, ), f"Subtype '{key}' not found in '{self._name}'.") - return parentNamespace.FindSubtype(subtypeSymbol) + try: + return parentNamespace.FindSubtype(subtypeSymbol) + except ExtendedKeyError as ex: + searchedNamespaces = (self, *ex.searchedNamespaces) + raise ExtendedKeyError(key, searchedNamespaces, f"Subtype '{key}' not found in: {', '.join(ns._name for ns in searchedNamespaces)}.") from ex def FindObject(self, objectSymbol: Symbol) -> Obj: try: @@ -140,21 +203,38 @@ def FindObject(self, objectSymbol: Symbol) -> Obj: elif PossibleReference.SignalAttribute in objectSymbol._possibleReferences: return element else: - raise TypeError(f"Found signal '{objectSymbol._name._identifier}', but it was not expected.") + ex = TypeError(f"Found signal '{objectSymbol._name._identifier}', but it was not expected.") + ex.add_note(f"Got type '{getFullyQualifiedName(element)}'.") + ex.add_note(f"Expected one of: {objectSymbol._possibleReferences}.") + raise ex elif isinstance(element, Constant): if PossibleReference.Constant in objectSymbol._possibleReferences: return element else: - raise TypeError(f"Found constant '{objectSymbol._name._identifier}', but it was not expected.") + ex = TypeError(f"Found constant '{objectSymbol._name._identifier}', but it was not expected.") + ex.add_note(f"Got type '{getFullyQualifiedName(element)}'.") + ex.add_note(f"Expected one of: {objectSymbol._possibleReferences}.") + raise ex elif isinstance(element, Variable): if PossibleReference.Variable in objectSymbol._possibleReferences: return element else: - raise TypeError(f"Found variable '{objectSymbol._name._identifier}', but it was not expected.") + ex = TypeError(f"Found variable '{objectSymbol._name._identifier}', but it was not expected.") + ex.add_note(f"Got type '{getFullyQualifiedName(element)}'.") + ex.add_note(f"Expected one of: {objectSymbol._possibleReferences}.") + raise ex else: - raise TypeError(f"Found element '{objectSymbol._name._identifier}', but it is not a type or subtype.") + ex = TypeError(f"Found element '{objectSymbol._name._identifier}', but it is not an object.") + ex.add_note(f"Got type '{getFullyQualifiedName(element)}'.") + raise ex except KeyError: + key = objectSymbol._name._identifier + if (parentNamespace := self._parentNamespace) is None: - raise KeyError(f"Subtype '{objectSymbol._name._identifier}' not found in '{self._name}'.") + raise ExtendedKeyError(key, (self, ), f"Object '{key}' not found in '{self._name}'.") - return parentNamespace.FindObject(objectSymbol) + try: + return parentNamespace.FindObject(objectSymbol) + except ExtendedKeyError as ex: + searchedNamespaces = (self, *ex.searchedNamespaces) + raise ExtendedKeyError(key, searchedNamespaces, f"Object '{key}' not found in: {', '.join(ns._name for ns in searchedNamespaces)}.") from ex diff --git a/pyVHDLModel/Object.py b/pyVHDLModel/Object.py index f138b09d4..dde6e5035 100644 --- a/pyVHDLModel/Object.py +++ b/pyVHDLModel/Object.py @@ -57,12 +57,28 @@ class Obj(ModelEntity, MultipleNamedEntityMixin, DocumentedEntityMixin): Objects are elements in the type and object graph, thus a reference to a vertex in that graph is stored in :data:`__objectVertex`. + + .. seealso:: + + * :class:`Base constant ` + * :class:`Variable ` + * :class:`Shared variable ` + * :class:`Signal ` + * :class:`File ` """ - _subtype: Symbol - _objectVertex: Nullable[Vertex] + _subtype: Symbol #: Reference to the object's subtype. + _objectVertex: Nullable[Vertex] #: The vertex representing this object in the design's object graph. def __init__(self, identifiers: Iterable[str], subtype: Symbol, documentation: Nullable[str] = None, parent: Nullable[ModelEntity] = None) -> None: + """ + Initializes an object. + + :param identifiers: A list of identifiers. + :param subtype: Reference to the object's subtype. + :param documentation: The documentation comment associated with this declaration. + :param parent: The parent model entity of this entity. + """ super().__init__(parent) MultipleNamedEntityMixin.__init__(self, identifiers) DocumentedEntityMixin.__init__(self, documentation) @@ -74,6 +90,11 @@ def __init__(self, identifiers: Iterable[str], subtype: Symbol, documentation: N @readonly def Subtype(self) -> Symbol: + """ + Read-only property to access the subtype (:attr:`_subtype`). + + :returns: The subtype. + """ return self._subtype @readonly @@ -95,17 +116,33 @@ class WithDefaultExpressionMixin(metaclass=ExtendedType, mixin=True): The default expression is referenced by :data:`__defaultExpression`. If no default expression is present, this field is ``None``. + + .. seealso:: + + * :class:`Constant ` + * :class:`Variable ` + * :class:`Signal ` """ - _defaultExpression: Nullable[ExpressionUnion] + _defaultExpression: Nullable[ExpressionUnion] #: The default value, or ``None`` if none was given. def __init__(self, defaultExpression: Nullable[ExpressionUnion] = None) -> None: + """ + Initializes an object with a default expression. + + :param defaultExpression: The default value, or ``None`` if none was given. + """ self._defaultExpression = defaultExpression if defaultExpression is not None: defaultExpression.Parent = self @readonly def DefaultExpression(self) -> Nullable[ExpressionUnion]: + """ + Read-only property to access the default expression (:attr:`_defaultExpression`). + + :returns: The default expression, or ``None`` if not set. + """ return self._defaultExpression @@ -113,6 +150,11 @@ def DefaultExpression(self) -> Nullable[ExpressionUnion]: class BaseConstant(Obj): """ Base-class for all constants (normal and deferred constants) in VHDL. + + .. seealso:: + + * :class:`Constant ` + * :class:`Deferred constant ` """ @@ -128,6 +170,11 @@ class Constant(BaseConstant, WithDefaultExpressionMixin): .. code-block:: VHDL constant BITS : positive := 8; + + .. seealso:: + + * :class:`Generic constant interface item ` + * :class:`Parameter constant interface item ` """ def __init__( @@ -138,6 +185,15 @@ def __init__( documentation: Nullable[str] = None, parent: Nullable[ModelEntity] = None ) -> None: + """ + Initializes a constant. + + :param identifiers: A list of identifiers. + :param subtype: Reference to the object's subtype. + :param defaultExpression: The default value, or ``None`` if none was given. + :param documentation: The documentation comment associated with this declaration. + :param parent: The parent model entity of this entity. + """ super().__init__(identifiers, subtype, documentation, parent) WithDefaultExpressionMixin.__init__(self, defaultExpression) @@ -156,7 +212,7 @@ class DeferredConstant(BaseConstant): constant BITS : positive; """ - _constantReference: Nullable[Constant] + _constantReference: Nullable[Constant] #: The full declaration, or ``None`` if unlinked. def __init__( self, @@ -165,13 +221,35 @@ def __init__( documentation: Nullable[str] = None, parent: Nullable[ModelEntity] = None ) -> None: + """ + Initializes a deferred constant. + + :param identifiers: A list of identifiers. + :param subtype: Reference to the object's subtype. + :param documentation: The documentation comment associated with this declaration. + :param parent: The parent model entity of this entity. + """ super().__init__(identifiers, subtype, documentation, parent) + self._constantReference = None + @readonly def ConstantReference(self) -> Nullable[Constant]: + """ + Read-only property to access the constant reference (:attr:`_constantReference`). + + :returns: The constant reference, or ``None`` if not set. + """ return self._constantReference def __str__(self) -> str: + """ + Formats the deferred constant declaration. + + **Format:** ``constant c : integer`` + + :returns: Formatted deferred constant declaration. + """ return f"constant {', '.join(self._identifiers)} : {self._subtype}" @@ -187,6 +265,10 @@ class Variable(Obj, WithDefaultExpressionMixin): .. code-block:: VHDL variable result : natural := 0; + + .. seealso:: + + * :class:`Parameter variable interface item ` """ def __init__( @@ -197,6 +279,15 @@ def __init__( documentation: Nullable[str] = None, parent: Nullable[ModelEntity] = None ) -> None: + """ + Initializes a variable. + + :param identifiers: A list of identifiers. + :param subtype: Reference to the object's subtype. + :param defaultExpression: The default value, or ``None`` if none was given. + :param documentation: The documentation comment associated with this declaration. + :param parent: The parent model entity of this entity. + """ super().__init__(identifiers, subtype, documentation, parent) WithDefaultExpressionMixin.__init__(self, defaultExpression) @@ -223,6 +314,11 @@ class Signal(Obj, WithDefaultExpressionMixin): .. code-block:: VHDL signal counter : unsigned(7 downto 0) := '0'; + + .. seealso:: + + * :class:`Port signal interface item ` + * :class:`Parameter signal interface item ` """ def __init__( @@ -233,6 +329,15 @@ def __init__( documentation: Nullable[str] = None, parent: Nullable[ModelEntity] = None ) -> None: + """ + Initializes a signal. + + :param identifiers: A list of identifiers. + :param subtype: Reference to the object's subtype. + :param defaultExpression: The default value, or ``None`` if none was given. + :param documentation: The documentation comment associated with this declaration. + :param parent: The parent model entity of this entity. + """ super().__init__(identifiers, subtype, documentation, parent) WithDefaultExpressionMixin.__init__(self, defaultExpression) @@ -243,4 +348,8 @@ class File(Obj): Represents a file. .. todo:: File object not implemented. + + .. seealso:: + + * :class:`Parameter file interface item ` """ diff --git a/pyVHDLModel/PSLModel.py b/pyVHDLModel/PSLModel.py index 2559f5e7e..7ef8376e9 100644 --- a/pyVHDLModel/PSLModel.py +++ b/pyVHDLModel/PSLModel.py @@ -40,34 +40,87 @@ @export class PSLEntity(ModelEntity): + """ + Represents the base-class of all PSL entities. + + PSL (Property Specification Language) support is rudimentary: verification units are recognised + and named, but their contents are not modelled. + + .. seealso:: + + * :class:`Default clock ` + """ pass @export class PSLPrimaryUnit(PrimaryUnit): + """ + Represents the base-class of all PSL primary units. + + .. seealso:: + + * :class:`Verification unit ` + * :class:`Verification property ` + * :class:`Verification mode ` + """ pass @export class VerificationUnit(PSLPrimaryUnit): + """ + Represents a PSL verification unit (``vunit``). + """ def __init__(self, identifier: str) -> None: + """ + Initializes a PSL verification unit (``vunit``). + + :param identifier: The identifier of a model entity. + """ super().__init__(identifier, parent=None) @export class VerificationProperty(PSLPrimaryUnit): + """ + Represents a PSL verification property (``vprop``). + """ def __init__(self, identifier: str) -> None: + """ + Initializes a PSL verification property (``vprop``). + + :param identifier: The identifier of a model entity. + """ super().__init__(identifier, parent=None) @export class VerificationMode(PSLPrimaryUnit): + """ + Represents a PSL verification mode (``vmode``). + """ def __init__(self, identifier: str) -> None: + """ + Initializes a PSL verification mode (``vmode``). + + :param identifier: The identifier of a model entity. + """ super().__init__(identifier, parent=None) @export class DefaultClock(PSLEntity, NamedEntityMixin): + """ + Represents a PSL default clock declaration. + + It names the clock expression used by PSL directives that do not state one themselves. + """ def __init__(self, identifier: str) -> None: + """ + Initializes a PSL default clock declaration. + + :param identifier: The identifier of a model entity. + """ super().__init__() NamedEntityMixin.__init__(self, identifier) diff --git a/pyVHDLModel/Predefined.py b/pyVHDLModel/Predefined.py index eff4924c1..43d2c93ce 100644 --- a/pyVHDLModel/Predefined.py +++ b/pyVHDLModel/Predefined.py @@ -30,7 +30,7 @@ # ==================================================================================================================== # # """This module contains base-classes for predefined library and package declarations.""" -from typing import Iterable +from typing import Iterable, Optional as Nullable from pyTooling.Decorators import export from pyTooling.MetaClasses import ExtendedType @@ -50,9 +50,19 @@ class PredefinedLibrary(Library): * :class:`~pyVHDLModel.STD.Std` * :class:`~pyVHDLModel.IEEE.Ieee` + + .. seealso:: + + * :class:`Ieee ` + * :class:`Std ` """ def __init__(self, packages) -> None: + """ + Initializes a predefined library. + + :param packages: Dictionary of all packages defined in a library. + """ super().__init__(self.__class__.__name__, None) self.AddPackages(packages) @@ -73,16 +83,21 @@ def AddPackages(self, packages) -> None: class PredefinedPackageMixin(metaclass=ExtendedType, mixin=True): """ A mixin-class for predefined VHDL packages and package bodies. + + .. seealso:: + + * :class:`Predefined package ` + * :class:`Predefined package body ` """ - def _AddLibraryClause(self, libraries: Iterable[str]): + def _AddLibraryClause(self, libraries: Iterable[str]) -> None: symbols = [LibraryReferenceSymbol(SimpleName(libName)) for libName in libraries] libraryClause = LibraryClause(symbols) self._contextItems.append(libraryClause) self._libraryReferences.append(libraryClause) - def _AddPackageClause(self, packages: Iterable[str]): + def _AddPackageClause(self, packages: Iterable[str]) -> None: symbols = [] for qualifiedPackageName in packages: libName, packName, members = qualifiedPackageName.split(".") @@ -104,8 +119,16 @@ class PredefinedPackage(Package, PredefinedPackageMixin): A base-class for predefined VHDL packages. """ - def __init__(self) -> None: - super().__init__(self.__class__.__name__, parent=None) + def __init__(self, identifier: Nullable[str] = None) -> None: + """ + Initializes a predefined package. + + By default the VHDL package name is the Python class name. Pass ``identifier`` when the two must + differ - e.g. when two vendor flavors of the same VHDL package need distinct Python classes. + + :param identifier: The VHDL package name, or ``None`` to use the class name. + """ + super().__init__(self.__class__.__name__ if identifier is None else identifier) @export @@ -114,6 +137,16 @@ class PredefinedPackageBody(PackageBody, PredefinedPackageMixin): A base-class for predefined VHDL package bodies. """ - def __init__(self) -> None: - packageSymbol = PackageSymbol(SimpleName(self.__class__.__name__[:-5])) - super().__init__(packageSymbol, parent=None) + def __init__(self, packageIdentifier: Nullable[str] = None) -> None: + """ + Initializes a predefined package body. + + By default the VHDL package name is the Python class name with the trailing ``_Body`` removed. + Pass ``packageIdentifier`` when the two must differ. + + :param packageIdentifier: The VHDL name of the package this body implements, or ``None`` to derive + it from the class name. + """ + identifier = self.__class__.__name__[:-5] if packageIdentifier is None else packageIdentifier + packageSymbol = PackageSymbol(SimpleName(identifier)) + super().__init__(packageSymbol) diff --git a/pyVHDLModel/Regions.py b/pyVHDLModel/Regions.py index 4b711d1ab..6ccc0a893 100644 --- a/pyVHDLModel/Regions.py +++ b/pyVHDLModel/Regions.py @@ -38,15 +38,89 @@ from pyTooling.Decorators import export, readonly from pyTooling.MetaClasses import ExtendedType +from pyTooling.Warning import WarningCollector +from pyVHDLModel.Base import normalizedIdentifiersOf +from pyVHDLModel.Exception import NotImplementedWarning +from pyVHDLModel.Namespace import Namespace from pyVHDLModel.Object import Constant, SharedVariable, File, Variable, Signal -from pyVHDLModel.Subprogram import Subprogram, Function, Procedure from pyVHDLModel.Type import Subtype, FullType +# `pyVHDLModel.Subprogram` imports this module (Subprogram is a sequential declaration region), so +# `Function`/`Procedure` are quoted in annotations and imported lazily where they're needed at runtime. + + @export -class ConcurrentDeclarationRegionMixin(metaclass=ExtendedType, mixin=True): +class DeclarationRegionMixin(metaclass=ExtendedType, mixin=True): + """ + A base-class for the concurrent and sequential declaration region mixins. + + It carries what both regions share: adding interface items to the region's namespace, and the hook for + declared items neither region handles itself. + + An interface item shares the declarative region of the declarative part beside it - VHDL rejects + ``port (g : in bit)`` beside ``generic (g : integer)``, ``signal x`` beside ``port (x : in bit)``, and a + subprogram variable named like one of its parameters, all as "identifier already used for a + declaration". So they belong in the region's *own* namespace, not a separate one. + + Which of the three a region has is known statically by the class that declares them, so each derived + class calls the ones it needs from its own :meth:`IndexDeclaredItems` before delegating upwards. + Interface items are added to the namespace only - ``GenericItems``/``PortItems``/``ParameterItems`` + already expose them as ordered lists, so no extra lookup table is needed. + + .. seealso:: + + * :class:`Concurrent declaration region mixin ` + * :class:`Sequential declaration region mixin ` + """ + + def _IndexGenericItems(self) -> None: + """Add this region's generics to its namespace.""" + for item in self._genericItems: + for normalizedIdentifier in normalizedIdentifiersOf(item): + self._namespace._elements[normalizedIdentifier] = item + + def _IndexPortItems(self) -> None: + """Add this region's ports to its namespace.""" + for item in self._portItems: + for normalizedIdentifier in normalizedIdentifiersOf(item): + self._namespace._elements[normalizedIdentifier] = item + + def _IndexParameterItems(self) -> None: + """Add this region's parameters to its namespace.""" + for item in self._parameterItems: + for normalizedIdentifier in normalizedIdentifiersOf(item): + self._namespace._elements[normalizedIdentifier] = item + + def _IndexOtherDeclaredItem(self, item) -> None: + """Hook for declared items the region doesn't handle itself. Derived classes may override it.""" + pass + + +@export +class ConcurrentDeclarationRegionMixin(DeclarationRegionMixin, mixin=True): # FIXME: define list prefix type e.g. via Union + """ + A mixin-class for concurrent declaration regions. + + Entities, architectures, packages, blocks and generate bodies declare items concurrently. Beside + the namespace, the region keeps a lookup table per kind of declared item (:data:`Types`, + :data:`Signals`, :data:`Constants`, ...). + + .. seealso:: + + * :class:`Concurrent block statement ` + * :class:`Generate branch ` + * :class:`Concurrent case ` + * :class:`For generate statement ` + * :class:`Package ` + * :class:`Package body ` + * :class:`Entity ` + * :class:`Architecture ` + * :class:`Sequential declaration region ` + * :class:`Namespace ` + """ _declaredItems: List #: List of all declared items in this concurrent declaration region. # _attributes: Dict[str, Attribute] @@ -58,13 +132,19 @@ class ConcurrentDeclarationRegionMixin(metaclass=ExtendedType, mixin=True): _signals: Dict[str, Signal] #: Dictionary of all signals declared in this concurrent declaration region. _sharedVariables: Dict[str, SharedVariable] #: Dictionary of all shared variables declared in this concurrent declaration region. _files: Dict[str, File] #: Dictionary of all files declared in this concurrent declaration region. - # _subprograms: Dict[str, Dict[str, Subprogram]] #: Dictionary of all subprograms declared in this concurrent declaration region. - _functions: Dict[str, Dict[str, Function]] #: Dictionary of all functions declared in this concurrent declaration region. - _procedures: Dict[str, Dict[str, Procedure]] #: Dictionary of all procedures declared in this concurrent declaration region. + # _subprograms: Dict[str, List[Subprogram]] #: Dictionary of all subprograms declared in this concurrent declaration region. + # FIXME: overloads are only collected into a list, not matched/resolved by signature. + _functions: Dict[str, List['Function']] #: Dictionary of all functions declared in this concurrent declaration region, indexed by name; each entry is a list of overloads. + _procedures: Dict[str, List['Procedure']] #: Dictionary of all procedures declared in this concurrent declaration region, indexed by name; each entry is a list of overloads. _components: Dict[str, Any] #: Dictionary of all components declared in this concurrent declaration region. def __init__(self, declaredItems: Nullable[Iterable] = None) -> None: # TODO: extract to mixin + """ + Initializes a concurrent declaration region. + + :param declaredItems: List of all declared items in this concurrent declaration region. + """ self._declaredItems = [] # TODO: convert to dict if declaredItems is not None: for item in declaredItems: @@ -85,14 +165,29 @@ def __init__(self, declaredItems: Nullable[Iterable] = None) -> None: @readonly def DeclaredItems(self) -> List: + """ + Read-only property to access the declared items (:attr:`_declaredItems`). + + :returns: List of declared items. + """ return self._declaredItems @readonly def Types(self) -> Dict[str, FullType]: + """ + Read-only property to access the types (:attr:`_types`). + + :returns: Dictionary of types, indexed by normalized identifier. + """ return self._types @readonly def Subtypes(self) -> Dict[str, Subtype]: + """ + Read-only property to access the subtypes (:attr:`_subtypes`). + + :returns: Dictionary of subtypes, indexed by normalized identifier. + """ return self._subtypes # @readonly @@ -101,18 +196,38 @@ def Subtypes(self) -> Dict[str, Subtype]: @readonly def Constants(self) -> Dict[str, Constant]: + """ + Read-only property to access the constants (:attr:`_constants`). + + :returns: Dictionary of constants, indexed by normalized identifier. + """ return self._constants @readonly def Signals(self) -> Dict[str, Signal]: + """ + Read-only property to access the signals (:attr:`_signals`). + + :returns: Dictionary of signals, indexed by normalized identifier. + """ return self._signals @readonly def SharedVariables(self) -> Dict[str, SharedVariable]: + """ + Read-only property to access the shared variables (:attr:`_sharedVariables`). + + :returns: Dictionary of shared variables, indexed by normalized identifier. + """ return self._sharedVariables @readonly def Files(self) -> Dict[str, File]: + """ + Read-only property to access the files (:attr:`_files`). + + :returns: Dictionary of files, indexed by normalized identifier. + """ return self._files # @readonly @@ -120,15 +235,30 @@ def Files(self) -> Dict[str, File]: # return self._subprograms @readonly - def Functions(self) -> Dict[str, Dict[str, Function]]: + def Functions(self) -> Dict[str, List['Function']]: + """ + Read-only property to access the functions (:attr:`_functions`). + + :returns: Dictionary of functions, indexed by normalized identifier; each entry is a list of overloads. + """ return self._functions @readonly - def Procedures(self) -> Dict[str, Dict[str, Procedure]]: + def Procedures(self) -> Dict[str, List['Procedure']]: + """ + Read-only property to access the procedures (:attr:`_procedures`). + + :returns: Dictionary of procedures, indexed by normalized identifier; each entry is a list of overloads. + """ return self._procedures @readonly def Components(self) -> Dict[str, Any]: + """ + Read-only property to access the components (:attr:`_components`). + + :returns: Dictionary of components, indexed by normalized identifier. + """ return self._components def IndexDeclaredItems(self) -> None: @@ -141,7 +271,7 @@ def IndexDeclaredItems(self) -> None: * Every declared item is added to :attr:`_namespace`. * If the declared item is a :class:`~pyVHDLModel.Type.FullType`, then add an entry to :attr:`_types`. - * If the declared item is a :class:`~pyVHDLModel.Type.SubType`, then add an entry to :attr:`_subtypes`. + * If the declared item is a :class:`~pyVHDLModel.Type.Subtype`, then add an entry to :attr:`_subtypes`. * If the declared item is a :class:`~pyVHDLModel.Subprogram.Function`, then add an entry to :attr:`_functions`. * If the declared item is a :class:`~pyVHDLModel.Subprogram.Procedure`, then add an entry to :attr:`_procedures`. * If the declared item is a :class:`~pyVHDLModel.Object.Constant`, then add an entry to :attr:`_constants`. @@ -162,6 +292,7 @@ def IndexDeclaredItems(self) -> None: Iterate all packages in the library and index declared items. """ from pyVHDLModel.DesignUnit import Component + from pyVHDLModel.Subprogram import Function, Procedure for item in self._declaredItems: if isinstance(item, FullType): @@ -171,10 +302,14 @@ def IndexDeclaredItems(self) -> None: self._subtypes[item._normalizedIdentifier] = item self._namespace._elements[item._normalizedIdentifier] = item elif isinstance(item, Function): - self._functions[item._normalizedIdentifier] = item + # FIXME: overloads are only appended to a list, not matched/resolved by signature (no + # real overload resolution yet). + self._functions.setdefault(item._normalizedIdentifier, []).append(item) self._namespace._elements[item._normalizedIdentifier] = item elif isinstance(item, Procedure): - self._procedures[item._normalizedIdentifier] = item + # FIXME: overloads are only appended to a list, not matched/resolved by signature (no + # real overload resolution yet). + self._procedures.setdefault(item._normalizedIdentifier, []).append(item) self._namespace._elements[item._normalizedIdentifier] = item elif isinstance(item, Constant): for normalizedIdentifier in item._normalizedIdentifiers: @@ -186,8 +321,10 @@ def IndexDeclaredItems(self) -> None: self._signals[normalizedIdentifier] = item self._namespace._elements[normalizedIdentifier] = item elif isinstance(item, Variable): - # FIXME: raise a warning - print(f"IndexDeclaredItems - {item._identifiers}") + # TODO: variables declared in a concurrent declaration region (e.g. shared variables outside a + # protected type) are not yet indexed into a dedicated namespace/lookup table. + identifiers = ", ".join(f"'{i}'" for i in item._identifiers) + WarningCollector.Raise(NotImplementedWarning(f"IndexDeclaredItems: variable(s) {identifiers} are not yet indexed.")) elif isinstance(item, SharedVariable): for normalizedIdentifier in item._normalizedIdentifiers: self._sharedVariables[normalizedIdentifier] = item @@ -202,6 +339,186 @@ def IndexDeclaredItems(self) -> None: else: self._IndexOtherDeclaredItem(item) - def _IndexOtherDeclaredItem(self, item) -> None: - pass - # print(f"_IndexOtherDeclaredItem - {item}\n ({' -> '.join(t.__name__ for t in type(item).mro())})") + +@export +class SequentialDeclarationRegionMixin(DeclarationRegionMixin, mixin=True): + """ + A mixin-class for sequential declaration regions: process statements and subprogram bodies. + + .. note:: + + VHDL's ``process_declarative_item`` and ``subprogram_declarative_item`` rules are identical, so both + regions share this implementation. Compared to a concurrent region + (:class:`ConcurrentDeclarationRegionMixin`, ``block_declarative_item``), a sequential region can + declare a **variable**, but no signal, shared variable, component or mode view, and none of the + specifications. + + .. seealso:: + + * :class:`Process statement ` + * :class:`Subprogram ` + * :class:`Concurrent declaration region ` + * :class:`Namespace ` + """ + + _declaredItems: List #: List of all declared items in this sequential declaration region. + _namespace: Namespace #: The namespace of this sequential declaration region. + + _types: Dict[str, FullType] #: Dictionary of all types declared in this sequential declaration region. + _subtypes: Dict[str, Subtype] #: Dictionary of all subtypes declared in this sequential declaration region. + _constants: Dict[str, Constant] #: Dictionary of all constants declared in this sequential declaration region. + _variables: Dict[str, Variable] #: Dictionary of all variables declared in this sequential declaration region. + _files: Dict[str, File] #: Dictionary of all files declared in this sequential declaration region. + # FIXME: overloads are only collected into a list, not matched/resolved by signature. + _functions: Dict[str, List['Function']] #: Dictionary of all functions declared in this sequential declaration region, indexed by name; each entry is a list of overloads. + _procedures: Dict[str, List['Procedure']] #: Dictionary of all procedures declared in this sequential declaration region, indexed by name; each entry is a list of overloads. + + def __init__(self, namespaceName: Nullable[str] = None, declaredItems: Nullable[Iterable] = None) -> None: + """ + Initialize a sequential declaration region. + + :param namespaceName: Name of this region's namespace, usually the host's label or identifier. + :param declaredItems: The items declared in this region. + """ + self._namespace = Namespace(namespaceName) + + self._declaredItems = [] # TODO: convert to dict + if declaredItems is not None: + for item in declaredItems: + self._declaredItems.append(item) + item.Parent = self + + self._types = {} + self._subtypes = {} + self._constants = {} + self._variables = {} + self._files = {} + self._functions = {} + self._procedures = {} + + @readonly + def DeclaredItems(self) -> List: + """ + Read-only property to access the declared items (:attr:`_declaredItems`). + + :returns: List of declared items. + """ + return self._declaredItems + + @readonly + def Namespace(self) -> Namespace: + """ + Read-only property to access this region's namespace (:attr:`_namespace`). + + :returns: The namespace. + """ + return self._namespace + + @readonly + def Types(self) -> Dict[str, FullType]: + """ + Read-only property to access the declared types (:attr:`_types`). + + :returns: Dictionary of types, indexed by normalized identifier. + """ + return self._types + + @readonly + def Subtypes(self) -> Dict[str, Subtype]: + """ + Read-only property to access the declared subtypes (:attr:`_subtypes`). + + :returns: Dictionary of subtypes, indexed by normalized identifier. + """ + return self._subtypes + + @readonly + def Constants(self) -> Dict[str, Constant]: + """ + Read-only property to access the declared constants (:attr:`_constants`). + + :returns: Dictionary of constants, indexed by normalized identifier. + """ + return self._constants + + @readonly + def Variables(self) -> Dict[str, Variable]: + """ + Read-only property to access the declared variables (:attr:`_variables`). + + :returns: Dictionary of variables, indexed by normalized identifier. + """ + return self._variables + + @readonly + def Files(self) -> Dict[str, File]: + """ + Read-only property to access the declared files (:attr:`_files`). + + :returns: Dictionary of files, indexed by normalized identifier. + """ + return self._files + + @readonly + def Functions(self) -> Dict[str, List['Function']]: + """ + Read-only property to access the declared functions (:attr:`_functions`). + + :returns: Dictionary of functions, indexed by normalized identifier; each entry is a list of overloads. + """ + return self._functions + + @readonly + def Procedures(self) -> Dict[str, List['Procedure']]: + """ + Read-only property to access the declared procedures (:attr:`_procedures`). + + :returns: Dictionary of procedures, indexed by normalized identifier; each entry is a list of overloads. + """ + return self._procedures + + def IndexDeclaredItems(self) -> None: + """ + Index declared items listed in the sequential declaration region. + + Every declared item is added to :attr:`_namespace`, and additionally to the lookup table matching its + kind. Items of an unhandled kind are passed to :meth:`_IndexOtherDeclaredItem`. + + .. seealso:: + + :meth:`ConcurrentDeclarationRegionMixin.IndexDeclaredItems` + The same algorithm for a concurrent declaration region. + """ + from pyVHDLModel.Subprogram import Function, Procedure + + for item in self._declaredItems: + if isinstance(item, FullType): + self._types[item._normalizedIdentifier] = item + self._namespace._elements[item._normalizedIdentifier] = item + elif isinstance(item, Subtype): + self._subtypes[item._normalizedIdentifier] = item + self._namespace._elements[item._normalizedIdentifier] = item + elif isinstance(item, Function): + # FIXME: overloads are only appended to a list, not matched/resolved by signature (no + # real overload resolution yet). + self._functions.setdefault(item._normalizedIdentifier, []).append(item) + self._namespace._elements[item._normalizedIdentifier] = item + elif isinstance(item, Procedure): + # FIXME: overloads are only appended to a list, not matched/resolved by signature (no + # real overload resolution yet). + self._procedures.setdefault(item._normalizedIdentifier, []).append(item) + self._namespace._elements[item._normalizedIdentifier] = item + elif isinstance(item, Constant): + for normalizedIdentifier in item._normalizedIdentifiers: + self._constants[normalizedIdentifier] = item + self._namespace._elements[normalizedIdentifier] = item + elif isinstance(item, Variable): + for normalizedIdentifier in item._normalizedIdentifiers: + self._variables[normalizedIdentifier] = item + self._namespace._elements[normalizedIdentifier] = item + elif isinstance(item, File): + for normalizedIdentifier in item._normalizedIdentifiers: + self._files[normalizedIdentifier] = item + self._namespace._elements[normalizedIdentifier] = item + else: + self._IndexOtherDeclaredItem(item) diff --git a/pyVHDLModel/STD.py b/pyVHDLModel/STD.py index 4a178d60d..e650de76c 100644 --- a/pyVHDLModel/STD.py +++ b/pyVHDLModel/STD.py @@ -33,7 +33,7 @@ from pyTooling.Decorators import export -from pyVHDLModel.Base import Range, Direction +from pyVHDLModel.Base import SimpleRange, Direction from pyVHDLModel.Name import SimpleName from pyVHDLModel.Symbol import SimpleSubtypeSymbol from pyVHDLModel.Expression import EnumerationLiteral, IntegerLiteral, FloatingPointLiteral, PhysicalIntegerLiteral @@ -59,6 +59,9 @@ class Std(PredefinedLibrary): """ def __init__(self) -> None: + """ + Initializes the ``std`` library. + """ super().__init__(PACKAGES) @@ -84,6 +87,9 @@ class Standard(PredefinedPackage): """ def __init__(self) -> None: + """ + Initializes the ``standard`` package. + """ super().__init__() boolean = EnumeratedType("boolean", (EnumerationLiteral("false"), EnumerationLiteral("true")), None) @@ -117,16 +123,16 @@ def __init__(self) -> None: self._types[severityLevel._normalizedIdentifier] = severityLevel self._declaredItems.append(severityLevel) - integer = IntegerType("integer", Range(IntegerLiteral(-2**31), IntegerLiteral(2**31 - 1), Direction.To), None) + integer = IntegerType("integer", SimpleRange(IntegerLiteral(-2**31), IntegerLiteral(2**31 - 1), Direction.To), None) self._types[integer._normalizedIdentifier] = integer self._declaredItems.append(integer) # real - real = RealType("real", Range(FloatingPointLiteral(-5.0), FloatingPointLiteral(5.0), Direction.To), None) + real = RealType("real", SimpleRange(FloatingPointLiteral(-5.0), FloatingPointLiteral(5.0), Direction.To), None) self._types[real._normalizedIdentifier] = real self._declaredItems.append(real) - time = PhysicalType("time", Range(IntegerLiteral(-2**63), IntegerLiteral(2**63 - 1), Direction.To), primaryUnit="fs", units=( + time = PhysicalType("time", SimpleRange(IntegerLiteral(-2**63), IntegerLiteral(2**63 - 1), Direction.To), primaryUnit="fs", units=( ("ps", PhysicalIntegerLiteral(1000, "fs")), ("ns", PhysicalIntegerLiteral(1000, "ps")), ("us", PhysicalIntegerLiteral(1000, "ns")), @@ -144,13 +150,13 @@ def __init__(self) -> None: natural = Subtype("natural", SimpleSubtypeSymbol(SimpleName("integer")), None) natural._baseType = integer - natural._range = Range(IntegerLiteral(0), IntegerLiteral(2**31 - 1), Direction.To) + natural._range = SimpleRange(IntegerLiteral(0), IntegerLiteral(2**31 - 1), Direction.To) self._subtypes[natural._normalizedIdentifier] = natural self._declaredItems.append(natural) positive = Subtype("positive", SimpleSubtypeSymbol(SimpleName("integer")), None) positive._baseType = integer - positive._range = Range(IntegerLiteral(1), IntegerLiteral(2**31 - 1), Direction.To) + positive._range = SimpleRange(IntegerLiteral(1), IntegerLiteral(2**31 - 1), Direction.To) self._subtypes[positive._normalizedIdentifier] = positive self._declaredItems.append(positive) @@ -237,6 +243,9 @@ class Env(PredefinedPackage): """ def __init__(self) -> None: + """ + Initializes the ``env`` package. + """ super().__init__() self._AddPackageClause(("work.textio.all",)) diff --git a/pyVHDLModel/Sequential.py b/pyVHDLModel/Sequential.py index abfea3cca..45d7da5cf 100644 --- a/pyVHDLModel/Sequential.py +++ b/pyVHDLModel/Sequential.py @@ -40,24 +40,50 @@ from pyTooling.MetaClasses import ExtendedType from pyVHDLModel.Base import ModelEntity, ExpressionUnion, Range, BaseChoice, BaseCase, ConditionalMixin, IfBranchMixin, ElsifBranchMixin -from pyVHDLModel.Base import ElseBranchMixin, ReportStatementMixin, AssertStatementMixin, WaveformElement -from pyVHDLModel.Symbol import Symbol +from pyVHDLModel.Base import ElseBranchMixin, ReportStatementMixin, AssertStatementMixin, WaveformElement, ChoicesMixin +from pyVHDLModel.Symbol import Symbol, SignalSymbol, VariableSymbol from pyVHDLModel.Common import Statement, ProcedureCallMixin -from pyVHDLModel.Common import SignalAssignmentMixin, VariableAssignmentMixin +from pyVHDLModel.Common import AssignmentMixin, SignalAssignmentMixin, VariableAssignmentMixin +from pyVHDLModel.Common import ConditionalWaveform, ConditionalExpression +from pyVHDLModel.Common import ConditionalWaveformsMixin, WaveformMixin +from pyVHDLModel.Common import ExpressionMixin, SelectedWaveformsMixin, SelectedExpressionsMixin +from pyVHDLModel.Common import SelectedWaveform, OthersSelectedWaveform +from pyVHDLModel.Common import SelectedExpression, OthersSelectedExpression from pyVHDLModel.Association import ParameterAssociationItem @export class SequentialStatement(Statement): - """A ``SequentialStatement`` is a base-class for all sequential statements.""" + """ + Represents the base-class of all sequential statements. + + Sequential statements appear in a process or a subprogram body. + """ @export class SequentialStatementsMixin(metaclass=ExtendedType, mixin=True): - _statements: List[SequentialStatement] + """ + A mixin-class for language constructs containing sequential statements. + + The statements are available in declaration order as :data:`Statements`. + + .. seealso:: + + * :class:`Process statement ` + * :class:`Branch ` + * :class:`Sequential case ` + * :class:`Loop statement ` + """ + _statements: List[SequentialStatement] #: List of all sequential statements in this construct. def __init__(self, statements: Nullable[Iterable[SequentialStatement]] = None) -> None: # TODO: extract to mixin + """ + Initializes sequential statements. + + :param statements: List of all sequential statements in this construct. + """ self._statements = [] if statements is not None: for item in statements: @@ -76,64 +102,442 @@ def Statements(self) -> List[SequentialStatement]: @export class SequentialProcedureCall(SequentialStatement, ProcedureCallMixin): + """ + Represents a procedure call as a sequential statement. + + Like every sequential statement, it can carry an optional label (:data:`Label`). + + .. admonition:: Example + + .. code-block:: VHDL + + lbl : log("hello"); + --^^^ <- optional Label + -- ^^^^^^^^^^^^ <- the call + + .. seealso:: + + * :class:`Concurrent counterpart ` + """ def __init__( self, procedureName: Symbol, - parameterMappings: Nullable[Iterable[ParameterAssociationItem]] = None, + parameterAssociationItems: Nullable[Iterable[ParameterAssociationItem]] = None, label: Nullable[str] = None, parent: Nullable[ModelEntity] = None ) -> None: + """ + Initializes a procedure call as a sequential statement. + + :param procedureName: Reference to the called procedure. + :param parameterAssociationItems: List of all parameter associations of the call. + :param label: The label of a model entity. + :param parent: The parent model entity of this entity. + """ super().__init__(label, parent) - ProcedureCallMixin.__init__(self, procedureName, parameterMappings) + ProcedureCallMixin.__init__(self, procedureName, parameterAssociationItems) @export class SequentialSignalAssignment(SequentialStatement, SignalAssignmentMixin): - def __init__(self, target: Symbol, label: Nullable[str] = None, parent: Nullable[ModelEntity] = None) -> None: + """ + Represents the base-class of all sequential signal assignments. + + .. seealso:: + + * :class:`Sequential simple signal assignment ` + """ + def __init__(self, target: SignalSymbol, label: Nullable[str] = None, parent: Nullable[ModelEntity] = None) -> None: + """ + Initializes a sequential signal assignment. + + :param target: Reference to the assignment's destination. + :param label: The label of a model entity. + :param parent: The parent model entity of this entity. + """ super().__init__(label, parent) SignalAssignmentMixin.__init__(self, target) @export -class SequentialSimpleSignalAssignment(SequentialSignalAssignment): - _waveform: List[WaveformElement] +class SequentialSimpleSignalAssignment(SequentialSignalAssignment, WaveformMixin): + """ + Represents a simple sequential signal assignment. - def __init__(self, target: Symbol, waveform: Iterable[WaveformElement], label: Nullable[str] = None, parent: Nullable[ModelEntity] = None) -> None: - super().__init__(target, label, parent) + The assignment's destination is available as :data:`Target`, its value as :data:`Waveform`. - # TODO: extract to mixin - self._waveform = [] - if waveform is not None: - for waveformElement in waveform: - self._waveform.append(waveformElement) - waveformElement.Parent = self + .. admonition:: Example - @readonly - def Waveform(self) -> List[WaveformElement]: + .. code-block:: VHDL + + lbl : s <= '1'; + --^^^ <- optional Label + -- ^ <- Target + -- ^^^ <- Waveform + + .. seealso:: + + * :class:`Concurrent counterpart ` + """ + def __init__(self, target: SignalSymbol, waveform: Iterable[WaveformElement], label: Nullable[str] = None, parent: Nullable[ModelEntity] = None) -> None: """ - Read-only property to access the list waveform elements (:attr:`_waveform`). + Initializes a simple sequential signal assignment. - :returns: A list of waveform elements. + :param target: Reference to the assignment's destination. + :param waveform: List of all waveform elements, in the order they were written. + :param label: The label of a model entity. + :param parent: The parent model entity of this entity. """ - return self._waveform + super().__init__(target, label, parent) + WaveformMixin.__init__(self, waveform) @export class SequentialVariableAssignment(SequentialStatement, VariableAssignmentMixin): - def __init__(self, target: Symbol, expression: ExpressionUnion, label: Nullable[str] = None, parent: Nullable[ModelEntity] = None) -> None: + """ + Represents a simple sequential variable assignment. + + The assignment's destination is available as :data:`Target`, its value as :data:`Expression`. + + .. admonition:: Example + + .. code-block:: VHDL + + lbl : v := '1'; + --^^^ <- optional Label + -- ^ <- Target + -- ^^^ <- Expression + """ + def __init__(self, target: VariableSymbol, expression: ExpressionUnion, label: Nullable[str] = None, parent: Nullable[ModelEntity] = None) -> None: + """ + Initializes a simple sequential variable assignment. + + :param target: Reference to the assignment's destination. + :param expression: The assigned expression. + :param label: The label of a model entity. + :param parent: The parent model entity of this entity. + """ super().__init__(label, parent) VariableAssignmentMixin.__init__(self, target, expression) +@export +class SequentialConditionalVariableAssignment(SequentialStatement, AssignmentMixin): + """ + Represents a conditional sequential variable assignment. + + The alternatives are available as :data:`ConditionalExpressions`, a list of + :class:`~pyVHDLModel.Common.ConditionalExpression`. The model holds them in a list and has no + distinct field per alternative, so the markers below name list elements. + + .. admonition:: Example + + .. code-block:: VHDL + + lbl : v := '1' when sel = '0' else '0'; + --^^^ <- optional Label + -- ^ <- Target + -- ^^^^^^^^^^^^^^^^^^ <- ConditionalExpressions[0] + -- ^^^ <- ConditionalExpressions[1] + + .. seealso:: + + * :class:`Conditional expression ` + """ + + _conditionalExpressions: List[ConditionalExpression] #: List of all alternatives, in the order they were written. + + def __init__( + self, + target: VariableSymbol, + conditionalExpressions: Iterable[ConditionalExpression], + label: Nullable[str] = None, + parent: Nullable[ModelEntity] = None + ) -> None: + """ + Initializes a conditional sequential variable assignment. + + :param target: Reference to the assignment's destination. + :param conditionalExpressions: List of all alternatives, in the order they were written. + :param label: The label of a model entity. + :param parent: The parent model entity of this entity. + """ + super().__init__(label, parent) + AssignmentMixin.__init__(self, target) + + self._conditionalExpressions = [] + for conditionalExpression in conditionalExpressions: + self._conditionalExpressions.append(conditionalExpression) + conditionalExpression.Parent = self + + @readonly + def ConditionalExpressions(self) -> List[ConditionalExpression]: + """ + Read-only property to access the conditional expressions (:attr:`_conditionalExpressions`). + + :returns: List of conditional expressions. + """ + return self._conditionalExpressions + + +@export +class SequentialConditionalSignalAssignment(SequentialStatement, SignalAssignmentMixin, ConditionalWaveformsMixin): + """ + Represents a conditional sequential signal assignment. + + The alternatives are available as :data:`ConditionalWaveforms`, a list of + :class:`~pyVHDLModel.Common.ConditionalWaveform`. The model holds them in a list and has no + distinct field per alternative, so the markers below name list elements. + + .. admonition:: Example + + .. code-block:: VHDL + + lbl : s <= '1' when sel = '0' else '0'; + --^^^ <- optional Label + -- ^ <- Target + -- ^^^^^^^^^^^^^^^^^^ <- ConditionalWaveforms[0] + -- ^^^ <- ConditionalWaveforms[1] + + .. seealso:: + + * :class:`Concurrent counterpart ` + * :class:`Conditional waveform ` + """ + + def __init__( + self, + target: SignalSymbol, + conditionalWaveforms: Iterable[ConditionalWaveform], + label: Nullable[str] = None, + parent: Nullable[ModelEntity] = None + ) -> None: + """ + Initializes a conditional sequential signal assignment. + + :param target: Reference to the assignment's destination. + :param conditionalWaveforms: All alternatives, in order. + :param label: The label of a model entity. + :param parent: The parent model entity of this entity. + """ + super().__init__(label, parent) + SignalAssignmentMixin.__init__(self, target) + ConditionalWaveformsMixin.__init__(self, conditionalWaveforms) + + +@export +class SequentialSelectedVariableAssignment(SequentialStatement, AssignmentMixin, ExpressionMixin, SelectedExpressionsMixin): + """ + Represents a selected sequential variable assignment. + + The selector is available as :data:`Expression`, the alternatives as :data:`SelectedExpressions`, + a list of :class:`~pyVHDLModel.Common.SelectedExpression`. The model holds them in a list and has + no distinct field per alternative, so the markers below name list elements. + + .. admonition:: Example + + .. code-block:: VHDL + + lbl : with sel select v := '1' when '0', '0' when others; + --^^^ <- optional Label + -- ^^^ <- Expression + -- ^ <- Target + -- ^^^^^^^^^^^^ <- SelectedExpressions[0] + -- ^^^^^^^^^^^^^^^ <- SelectedExpressions[1] + + .. seealso:: + + * :class:`Selected expression ` + """ + + def __init__( + self, + target: VariableSymbol, + expression: ExpressionUnion, + selectedExpressions: Iterable[SelectedExpression], + label: Nullable[str] = None, + parent: Nullable[ModelEntity] = None + ) -> None: + """ + Initializes a selected sequential variable assignment. + + :param target: Reference to the assignment's destination. + :param expression: The selector expression. + :param selectedExpressions: All alternatives, in order. + :param label: The label of a model entity. + :param parent: The parent model entity of this entity. + """ + super().__init__(label, parent) + AssignmentMixin.__init__(self, target) + ExpressionMixin.__init__(self, expression) + SelectedExpressionsMixin.__init__(self, selectedExpressions) + + +@export +class SequentialSelectedSignalAssignment(SequentialStatement, SignalAssignmentMixin, ExpressionMixin, SelectedWaveformsMixin): + """ + Represents a selected sequential signal assignment. + + The selector is available as :data:`Expression`, the alternatives as :data:`SelectedWaveforms`, + a list of :class:`~pyVHDLModel.Common.SelectedWaveform`. The model holds them in a list and has + no distinct field per alternative, so the markers below name list elements. + + .. admonition:: Example + + .. code-block:: VHDL + + lbl : with sel select s <= '1' when '0', '0' when others; + --^^^ <- optional Label + -- ^^^ <- Expression + -- ^ <- Target + -- ^^^^^^^^^^^^ <- SelectedWaveforms[0] + -- ^^^^^^^^^^^^^^^ <- SelectedWaveforms[1] + + .. seealso:: + + * :class:`Concurrent counterpart ` + * :class:`Selected waveform ` + """ + + def __init__( + self, + target: SignalSymbol, + expression: ExpressionUnion, + selectedWaveforms: Iterable[SelectedWaveform], + label: Nullable[str] = None, + parent: Nullable[ModelEntity] = None + ) -> None: + """ + Initializes a selected sequential signal assignment. + + :param target: Reference to the assignment's destination. + :param expression: The selector expression. + :param selectedWaveforms: All alternatives, in order. + :param label: The label of a model entity. + :param parent: The parent model entity of this entity. + """ + super().__init__(label, parent) + SignalAssignmentMixin.__init__(self, target) + ExpressionMixin.__init__(self, expression) + SelectedWaveformsMixin.__init__(self, selectedWaveforms) + + +@export +class SignalForceAssignment(SequentialStatement, SignalAssignmentMixin, ExpressionMixin): + """ + Represents a signal force assignment. + + A force assignment overrides a signal's driver until it is released. + + .. admonition:: Example + + .. code-block:: VHDL + + lbl : s <= force '1'; + --^^^ <- optional Label + -- ^ <- Target + -- ^^^ <- Expression + """ + + def __init__( + self, + target: SignalSymbol, + expression: ExpressionUnion, + label: Nullable[str] = None, + parent: Nullable[ModelEntity] = None + ) -> None: + """ + Initializes a signal force assignment. + + :param target: Reference to the assignment's destination. + :param expression: The value forced onto the signal. + :param label: The label of a model entity. + :param parent: The parent model entity of this entity. + """ + super().__init__(label, parent) + SignalAssignmentMixin.__init__(self, target) + ExpressionMixin.__init__(self, expression) + + +@export +class SignalReleaseAssignment(SequentialStatement, SignalAssignmentMixin): + """ + Represents a signal release assignment. + + A release assignment ends a previously applied force. + + .. admonition:: Example + + .. code-block:: VHDL + + lbl : s <= release; + --^^^ <- optional Label + -- ^ <- Target + """ + + def __init__(self, target: SignalSymbol, label: Nullable[str] = None, parent: Nullable[ModelEntity] = None) -> None: + """ + Initializes a signal release assignment. + + :param target: Reference to the assignment's destination. + :param label: The label of a model entity. + :param parent: The parent model entity of this entity. + """ + super().__init__(label, parent) + SignalAssignmentMixin.__init__(self, target) + + @export class SequentialReportStatement(SequentialStatement, ReportStatementMixin): + """ + Represents a sequential report statement. + + The report string is available as :data:`Message`, the optional severity as :data:`Severity`. + + .. admonition:: Example + + .. code-block:: VHDL + + lbl : report "message" severity note; + --^^^ <- optional Label + -- ^^^^^^^^^ <- Message + -- ^^^^ <- optional Severity + """ def __init__(self, message: ExpressionUnion, severity: Nullable[ExpressionUnion] = None, label: Nullable[str] = None, parent: Nullable[ModelEntity] = None) -> None: + """ + Initializes a sequential report statement. + + :param message: The reported message, or ``None`` if none was given. + :param severity: The reported severity level, or ``None`` if none was given. + :param label: The label of a model entity. + :param parent: The parent model entity of this entity. + """ super().__init__(label, parent) ReportStatementMixin.__init__(self, message, severity) @export class SequentialAssertStatement(SequentialStatement, AssertStatementMixin): + """ + Represents a sequential assertion statement. + + The checked condition is available as :data:`Condition`, the optional report string as + :data:`Message` and the optional severity as :data:`Severity`. + + .. admonition:: Example + + .. code-block:: VHDL + + lbl : assert sel = '0' report "bad" severity error; + --^^^ <- optional Label + -- ^^^^^^^^^ <- Condition + -- ^^^^^ <- optional Message + -- ^^^^^ <- optional Severity + + .. seealso:: + + * :class:`Concurrent counterpart ` + """ def __init__( self, condition: ExpressionUnion, @@ -142,50 +546,204 @@ def __init__( label: Nullable[str] = None, parent: Nullable[ModelEntity] = None ) -> None: + """ + Initializes a sequential assertion statement. + + :param condition: The condition guarding this statement. + :param message: The reported message, or ``None`` if none was given. + :param severity: The reported severity level, or ``None`` if none was given. + :param label: The label of a model entity. + :param parent: The parent model entity of this entity. + """ super().__init__(label, parent) AssertStatementMixin.__init__(self, condition, message, severity) @export class CompoundStatement(SequentialStatement): - """A ``CompoundStatement`` is a base-class for all compound statements.""" + """ + Represents the base-class of all compound statements. + + A compound statement contains further sequential statements: if, case and loop statements. + + .. seealso:: + + * :class:`If statement ` + * :class:`Case statement ` + * :class:`Loop statement ` + """ @export class Branch(ModelEntity, SequentialStatementsMixin): - """A ``Branch`` is a base-class for all branches in a if statement.""" + """ + Represents the base-class of all branches of an if statement. + + .. seealso:: + + * :class:`If branch ` + * :class:`Elsif branch ` + * :class:`Else branch ` + """ def __init__(self, statements: Nullable[Iterable[SequentialStatement]] = None, parent: Nullable[ModelEntity] = None) -> None: + """ + Initializes a branch. + + :param statements: List of all sequential statements in this construct. + :param parent: The parent model entity of this entity. + """ super().__init__(parent) SequentialStatementsMixin.__init__(self, statements) @export class IfBranch(Branch, IfBranchMixin): + """ + Represents the ``if`` branch of an if statement. + + The branch's condition is available as :data:`Condition`, its body as :data:`Statements`. + + .. admonition:: Example + + The whole if statement is shown; the bracket marks the part this class represents. + + .. code-block:: VHDL + + if sel = '0' then -- ┐ IfBranch + -- ^^^^^^^^^ -- │ <- Condition + s <= '0'; -- │ + --^^^^^^^^^ -- ┘ <- Statements + elsif sel = '1' then + s <= '1'; + else + s <= '0'; + end if; + """ def __init__(self, condition: ExpressionUnion, statements: Nullable[Iterable[SequentialStatement]] = None, parent: Nullable[ModelEntity] = None) -> None: + """ + Initializes an if branch. + + :param condition: The condition guarding this statement. + :param statements: List of all sequential statements in this construct. + :param parent: The parent model entity of this entity. + """ super().__init__(statements, parent) IfBranchMixin.__init__(self, condition) @export class ElsifBranch(Branch, ElsifBranchMixin): + """ + Represents an ``elsif`` branch of an if statement. + + The branch's condition is available as :data:`Condition`, its body as :data:`Statements`. + An if statement may have any number of them. + + .. admonition:: Example + + The whole if statement is shown; the bracket marks the part this class represents. + + .. code-block:: VHDL + + if sel = '0' then + s <= '0'; + elsif sel = '1' then -- ┐ ElsifBranch + -- ^^^^^^^^^ -- │ <- Condition + s <= '1'; -- │ + --^^^^^^^^^ -- ┘ <- Statements + else + s <= '0'; + end if; + """ def __init__(self, condition: ExpressionUnion, statements: Nullable[Iterable[SequentialStatement]] = None, parent: Nullable[ModelEntity] = None) -> None: + """ + Initializes an ``elsif`` branch of an if statement. + + :param condition: The condition guarding this statement. + :param statements: List of all sequential statements in this construct. + :param parent: The parent model entity of this entity. + """ super().__init__(statements, parent) ElsifBranchMixin.__init__(self, condition) @export class ElseBranch(Branch, ElseBranchMixin): + """ + Represents the ``else`` branch of an if statement. + + Unlike the other branches, an else branch has no condition; it only has a body + (:data:`Statements`). An if statement has at most one. + + .. admonition:: Example + + The whole if statement is shown; the bracket marks the part this class represents. + + .. code-block:: VHDL + + if sel = '0' then + s <= '0'; + elsif sel = '1' then + s <= '1'; + else -- ┐ ElseBranch + s <= '0'; -- │ + --^^^^^^^^^ -- ┘ <- Statements + end if; + """ def __init__(self, statements: Nullable[Iterable[SequentialStatement]] = None, parent: Nullable[ModelEntity] = None) -> None: + """ + Initializes an else branch. + + :param statements: List of all sequential statements in this construct. + :param parent: The parent model entity of this entity. + """ super().__init__(statements, parent) ElseBranchMixin.__init__(self) @export class IfStatement(CompoundStatement): - _ifBranch: IfBranch - _elsifBranches: List['ElsifBranch'] - _elseBranch: Nullable[ElseBranch] + """ + Represents an if statement. + + An if statement has one ``if`` branch (:data:`IfBranch`), any number of ``elsif`` branches + (:data:`ElsIfBranches`) and an optional ``else`` branch (:data:`ElseBranch`). + + .. admonition:: Example + + Only an ``if`` branch: + + .. code-block:: VHDL + + lbl : if sel = '0' then + --^^^ <- optional Label + s <= '0'; + end if; + + With ``elsif`` and ``else`` branches: + + .. code-block:: VHDL + + lbl : if sel = '0' then + --^^^ <- optional Label + -- ^^^^^^^^^^^^^^^^^ <- IfBranch + s <= '0'; + elsif sel = '1' then + --^^^^^^^^^^^^^^^^^^^^ <- ElsIfBranches[0] + s <= '1'; + else + --^^^^ <- ElseBranch + s <= '0'; + end if; + + .. seealso:: + + * :class:`If-generate statement ` + """ + _ifBranch: IfBranch #: The mandatory ``if`` branch. + _elsifBranches: List['ElsifBranch'] #: List of all ``elsif`` branches, in the order they were written. + _elseBranch: Nullable[ElseBranch] #: The optional ``else`` branch, or ``None`` if none was given. def __init__( self, @@ -195,6 +753,15 @@ def __init__( label: Nullable[str] = None, parent: Nullable[ModelEntity] = None ) -> None: + """ + Initializes an if statement. + + :param ifBranch: The mandatory ``if`` branch. + :param elsifBranches: List of all ``elsif`` branches, in the order they were written. + :param elseBranch: The optional ``else`` branch, or ``None`` if none was given. + :param label: The label of a model entity. + :param parent: The parent model entity of this entity. + """ super().__init__(label, parent) self._ifBranch = ifBranch @@ -221,7 +788,7 @@ def IfBranch(self) -> IfBranch: """ return self._ifBranch - @property + @readonly def ElsIfBranches(self) -> List['ElsifBranch']: """ Read-only property to access the elsif-branch of the if-statement (:attr:`_elsifBranch`). @@ -230,7 +797,7 @@ def ElsIfBranches(self) -> List['ElsifBranch']: """ return self._elsifBranches - @property + @readonly def ElseBranch(self) -> Nullable[ElseBranch]: """ Read-only property to access the else-branch of the if-statement (:attr:`_elseBranch`). @@ -242,91 +809,236 @@ def ElseBranch(self) -> Nullable[ElseBranch]: @export class SequentialChoice(BaseChoice): - """A ``SequentialChoice`` is a base-class for all sequential choices (in case statements).""" + """ + Represents the base-class of all choices in a sequential case statement. + + .. seealso:: + + * :class:`Indexed choice ` + * :class:`Ranged choice ` + """ @export class IndexedChoice(SequentialChoice): - _expression: ExpressionUnion + """ + Represents a case choice given by a single value. + + The value is available as :data:`Expression`. + + .. admonition:: Example + + .. code-block:: VHDL + + when 0 => v := '1'; + -- ^ <- Expression + """ + _expression: ExpressionUnion #: The expression this choice selects on. def __init__(self, expression: ExpressionUnion, parent: Nullable[ModelEntity] = None) -> None: + """ + Initializes a case choice given by a single value. + + :param expression: The expression this choice selects on. + :param parent: The parent model entity of this entity. + """ super().__init__(parent) self._expression = expression - # expression.Parent = self # FIXME: received None + expression.Parent = self - @property + @readonly def Expression(self) -> ExpressionUnion: + """ + Read-only property to access the expression (:attr:`_expression`). + + :returns: The expression. + """ return self._expression def __str__(self) -> str: + """ + Formats the indexed case choice. + + **Format:** ``0`` + + :returns: Formatted indexed case choice. + """ return str(self._expression) @export class RangedChoice(SequentialChoice): - _range: 'Range' + """ + Represents a case choice given by a range. + + The range is available as :data:`Range`. + + .. admonition:: Example + + .. code-block:: VHDL + + when 1 to 2 => v := '0'; + -- ^^^^^^ <- Range + """ + _range: 'Range' #: The range this choice selects on. def __init__(self, rng: 'Range', parent: Nullable[ModelEntity] = None) -> None: + """ + Initializes a case choice given by a range. + + :param rng: The range this choice selects on. + :param parent: The parent model entity of this entity. + """ super().__init__(parent) self._range = rng rng.Parent = self - @property + @readonly def Range(self) -> 'Range': + """ + Read-only property to access the range (:attr:`_range`). + + :returns: The range. + """ return self._range def __str__(self) -> str: + """ + Formats the ranged case choice. + + **Format:** ``0 to 3`` + + :returns: Formatted ranged case choice. + """ return str(self._range) @export -class SequentialCase(BaseCase, SequentialStatementsMixin): - _choices: List +class SequentialCase(BaseCase, SequentialStatementsMixin, ChoicesMixin): + """ + Represents the base-class of all alternatives of a sequential case statement. - def __init__(self, statements: Nullable[Iterable[SequentialStatement]] = None, parent: Nullable[ModelEntity] = None) -> None: - super().__init__(parent) - SequentialStatementsMixin.__init__(self, statements) + .. seealso:: - # TODO: what about choices? + * :class:`Case ` + * :class:`Others case ` + """ + def __init__( + self, + statements: Nullable[Iterable[SequentialStatement]] = None, + choices: Nullable[Iterable[BaseChoice]] = None, + parent: Nullable[ModelEntity] = None + ) -> None: + """ + Initializes a sequential case. - @property - def Choices(self) -> List[BaseChoice]: - return self._choices + :param statements: List of all sequential statements in this construct. + :param choices: List of all choices selecting this alternative. + :param parent: The parent model entity of this entity. + """ + super().__init__(parent) + SequentialStatementsMixin.__init__(self, statements) + ChoicesMixin.__init__(self, choices) @export class Case(SequentialCase): - def __init__(self, choices: Iterable[SequentialChoice], statements: Nullable[Iterable[SequentialStatement]] = None, parent: Nullable[ModelEntity] = None) -> None: - super().__init__(statements, parent) + """ + Represents one alternative of a case statement, selected by its choices. - self._choices = [] - if choices is not None: - for choice in choices: - self._choices.append(choice) - choice.Parent = self + .. admonition:: Example + + .. code-block:: VHDL + + when 1 to 2 => v := '0'; + -- ^^^^^^ <- Choices + -- ^^^^^^^^^ <- the statements + """ + def __init__(self, choices: Iterable[SequentialChoice], statements: Nullable[Iterable[SequentialStatement]] = None, parent: Nullable[ModelEntity] = None) -> None: + """ + Initializes a case. - @property - def Choices(self) -> List[SequentialChoice]: - return self._choices + :param choices: List of all choices selecting this alternative. + :param statements: List of all sequential statements in this construct. + :param parent: The parent model entity of this entity. + """ + super().__init__(statements, choices, parent) def __str__(self) -> str: + """ + Formats the case alternative. + + **Format:** ``when 0 | 1 =>`` + + :returns: Formatted case alternative. + """ return "when {choices} =>".format(choices=" | ".join(str(c) for c in self._choices)) @export class OthersCase(SequentialCase): + """ + Represents the ``others`` alternative of a case statement. + + It covers every choice not named explicitly. + + .. admonition:: Example + + .. code-block:: VHDL + + when others => null; + -- ^^^^^^ <- the choice + """ def __str__(self) -> str: + """ + Formats the ``others`` case alternative. + + **Format:** ``when others =>`` + + :returns: Formatted ``others`` case alternative. + """ return "when others =>" @export class CaseStatement(CompoundStatement): - _expression: ExpressionUnion - _cases: List[SequentialCase] + """ + Represents a case statement. + + The expression being tested is available as :data:`SelectExpression`, the alternatives as + :data:`Cases`. + + .. admonition:: Example + + .. code-block:: VHDL + + lbl : case sel is + --^^^ <- optional Label + -- ^^^ <- SelectExpression + when '0' => s <= '1'; + -- ^^^^^^^^^^^^^^^^^^^^^^^^ <- Cases[0] + when others => null; + -- ^^^^^^^^^^^^^^^^^^^^ <- Cases[1] + end case; + + .. seealso:: + + * :class:`Case-generate statement ` + """ + _expression: ExpressionUnion #: The expression being tested. + _cases: List[SequentialCase] #: List of all alternatives, in the order they were written. def __init__(self, expression: ExpressionUnion, cases: Iterable[SequentialCase], label: Nullable[str] = None, parent: Nullable[ModelEntity] = None) -> None: + """ + Initializes a case statement. + + :param expression: The expression being tested. + :param cases: List of all alternatives, in the order they were written. + :param label: The label of a model entity. + :param parent: The parent model entity of this entity. + """ super().__init__(label, parent) self._expression = expression @@ -338,35 +1050,114 @@ def __init__(self, expression: ExpressionUnion, cases: Iterable[SequentialCase], self._cases.append(case) case.Parent = self - @property + @readonly def SelectExpression(self) -> ExpressionUnion: + """ + Read-only property to access the select expression (:attr:`_expression`). + + :returns: The select expression. + """ return self._expression - @property + @readonly def Cases(self) -> List[SequentialCase]: + """ + Read-only property to access the cases (:attr:`_cases`). + + :returns: List of cases. + """ return self._cases @export class LoopStatement(CompoundStatement, SequentialStatementsMixin): - """A ``LoopStatement`` is a base-class for all loop statements.""" + """ + Represents the base-class of all loop statements. + + .. seealso:: + + * :class:`Endless loop statement ` + * :class:`For loop statement ` + * :class:`While loop statement ` + """ def __init__(self, statements: Nullable[Iterable[SequentialStatement]] = None, label: Nullable[str] = None, parent: Nullable[ModelEntity] = None) -> None: + """ + Initializes a loop statement. + + :param statements: List of all sequential statements in this construct. + :param label: The label of a model entity. + :param parent: The parent model entity of this entity. + """ super().__init__(label, parent) SequentialStatementsMixin.__init__(self, statements) @export class EndlessLoopStatement(LoopStatement): + """ + Represents an endless loop statement. + + The loop body is available as :data:`Statements`. The loop has no iteration scheme, so it is + left with an exit or return statement. + + .. admonition:: Example + + .. code-block:: VHDL + + lbl : loop + --^^^ <- optional Label + exit; + -- ^^^^^ <- Statements + end loop; + + .. seealso:: + + * :class:`For loop statement ` + * :class:`While loop statement ` + """ pass @export class ForLoopStatement(LoopStatement): - _loopIndex: str - _range: Range + """ + Represents a for-loop statement. + + The loop index is available as :data:`LoopIndex`, the iteration range as :data:`Range` and the + loop body as :data:`Statements`. + + .. admonition:: Example + + .. code-block:: VHDL + + lbl : for k in 0 to 3 loop + --^^^ <- optional Label + -- ^ <- LoopIndex + -- ^^^^^^ <- Range + null; + -- ^^^^^ <- Statements + end loop; + + .. seealso:: + + * :class:`Endless loop statement ` + * :class:`While loop statement ` + * :class:`For-generate statement ` + """ + _loopIndex: str #: The name of the loop's index. + _range: Range #: The range the loop iterates over. def __init__(self, loopIndex: str, rng: Range, statements: Nullable[Iterable[SequentialStatement]] = None, label: Nullable[str] = None, parent: Nullable[ModelEntity] = None) -> None: + """ + Initializes a for-loop statement. + + :param loopIndex: The name of the loop's index. + :param rng: The range the loop iterates over. + :param statements: List of all sequential statements in this construct. + :param label: The label of a model entity. + :param parent: The parent model entity of this entity. + """ super().__init__(statements, label, parent) self._loopIndex = loopIndex @@ -374,17 +1165,48 @@ def __init__(self, loopIndex: str, rng: Range, statements: Nullable[Iterable[Seq self._range = rng rng.Parent = self - @property + @readonly def LoopIndex(self) -> str: + """ + Read-only property to access the loop index (:attr:`_loopIndex`). + + :returns: The loop index. + """ return self._loopIndex - @property + @readonly def Range(self) -> Range: + """ + Read-only property to access the range (:attr:`_range`). + + :returns: The range. + """ return self._range @export class WhileLoopStatement(LoopStatement, ConditionalMixin): + """ + Represents a while-loop statement. + + The loop condition is available as :data:`Condition`, the loop body as :data:`Statements`. + + .. admonition:: Example + + .. code-block:: VHDL + + lbl : while i < 4 loop + --^^^ <- optional Label + -- ^^^^^ <- Condition + null; + -- ^^^^^ <- Statements + end loop; + + .. seealso:: + + * :class:`Endless loop statement ` + * :class:`For loop statement ` + """ def __init__( self, condition: ExpressionUnion, @@ -392,62 +1214,183 @@ def __init__( label: Nullable[str] = None, parent: Nullable[ModelEntity] = None ) -> None: + """ + Initializes a while-loop statement. + + :param condition: The condition guarding this statement. + :param statements: List of all sequential statements in this construct. + :param label: The label of a model entity. + :param parent: The parent model entity of this entity. + """ super().__init__(statements, label, parent) ConditionalMixin.__init__(self, condition) @export class LoopControlStatement(SequentialStatement, ConditionalMixin): - """A ``LoopControlStatement`` is a base-class for all loop controlling statements.""" + """ + Represents the base-class of the loop control statements ``next`` and ``exit``. + + An optional loop label (:data:`LoopReference`) selects which enclosing loop is affected. + + .. seealso:: - _loopReference: LoopStatement + * :class:`Next statement ` + * :class:`Exit statement ` + """ + + _loopReference: LoopStatement #: Reference to the loop this statement controls. def __init__(self, condition: Nullable[ExpressionUnion] = None, loopLabel: Nullable[str] = None, parent: Nullable[ModelEntity] = None) -> None: # TODO: is this label (currently str) a Name or a Label class? + """ + Initializes a loop control statement. + + :param condition: The condition guarding this statement. + :param loopLabel: The label of the controlled loop, or ``None`` for the innermost loop. + :param parent: The parent model entity of this entity. + """ super().__init__(parent) ConditionalMixin.__init__(self, condition) + self._loopReference = None + # TODO: loopLabel # TODO: loop reference -> is it a symbol? - @property + @readonly def LoopReference(self) -> LoopStatement: + """ + Read-only property to access the loop reference (:attr:`_loopReference`). + + :returns: The loop reference. + """ return self._loopReference @export class NextStatement(LoopControlStatement): + """ + Represents a next statement. + + A next statement skips to the next iteration of the named loop (:data:`LoopReference`), + optionally only when a condition (:data:`Condition`) holds. + + .. admonition:: Example + + .. code-block:: VHDL + + lbl : next outer when k = 1; + --^^^ <- optional Label + -- ^^^^^ <- optional LoopReference + -- ^^^^^ <- optional Condition + """ pass @export class ExitStatement(LoopControlStatement): + """ + Represents an exit statement. + + An exit statement leaves the named loop (:data:`LoopReference`), optionally only when a + condition (:data:`Condition`) holds. + + .. admonition:: Example + + .. code-block:: VHDL + + lbl : exit outer when k = 1; + --^^^ <- optional Label + -- ^^^^^ <- optional LoopReference + -- ^^^^^ <- optional Condition + """ pass @export class NullStatement(SequentialStatement): + """ + Represents a null statement. + + A null statement does nothing. Like every sequential statement, it can carry an optional label + (:data:`Label`). + + .. admonition:: Example + + .. code-block:: VHDL + + lbl : null; + --^^^ <- optional Label + -- ^^^^ <- the statement + """ pass @export -class ReturnStatement(SequentialStatement, ConditionalMixin): - _returnValue: ExpressionUnion +class ReturnStatement(SequentialStatement): + """ + Represents a return statement. - def __init__(self, returnValue: Nullable[ExpressionUnion] = None, parent: Nullable[ModelEntity] = None) -> None: - super().__init__(parent) - ConditionalMixin.__init__(self, returnValue) + The optionally returned value is available as :data:`ReturnValue`; a procedure returns nothing. + + .. admonition:: Example + + .. code-block:: VHDL + + lbl : return x; + --^^^ <- optional Label + -- ^ <- optional ReturnValue + """ + _returnValue: Nullable[ExpressionUnion] #: The returned expression, or ``None`` for a procedure. + + def __init__( + self, + returnValue: Nullable[ExpressionUnion] = None, + label: Nullable[str] = None, + parent: Nullable[ModelEntity] = None + ) -> None: + """ + Initializes a return statement. + + :param returnValue: The returned expression, or ``None`` for a procedure. + :param label: The label of a model entity. + :param parent: The parent model entity of this entity. + """ + super().__init__(label, parent) - # TODO: return value? + self._returnValue = returnValue + if returnValue is not None: + returnValue.Parent = self - @property - def ReturnValue(self) -> ExpressionUnion: + @readonly + def ReturnValue(self) -> Nullable[ExpressionUnion]: + """ + Read-only property to access the return value (:attr:`_returnValue`). + + :returns: The return value, or ``None`` if not set. + """ return self._returnValue @export class WaitStatement(SequentialStatement, ConditionalMixin): - _sensitivityList: Nullable[List[Symbol]] - _timeout: ExpressionUnion + """ + Represents a wait statement. + + A wait statement may name a sensitivity list (:data:`SensitivityList`), a condition + (:data:`Condition`) and a timeout (:data:`Timeout`); all three are optional. + + .. admonition:: Example + + .. code-block:: VHDL + + lbl : wait until clock = '1' for 10 ns; + --^^^ <- optional Label + -- ^^^^^^^^^^^ <- optional Condition + -- ^^^^^ <- optional Timeout + """ + _sensitivityList: Nullable[List[Symbol]] #: List of all signal names to wait on, or ``None`` if none was given. + _timeout: ExpressionUnion #: The timeout expression, or ``None`` if none was given. def __init__( self, @@ -457,6 +1400,15 @@ def __init__( label: Nullable[str] = None, parent: Nullable[ModelEntity] = None ) -> None: + """ + Initializes a wait statement. + + :param sensitivityList: List of all signal names to wait on, or ``None`` if none was given. + :param condition: The condition guarding this statement. + :param timeout: The timeout expression, or ``None`` if none was given. + :param label: The label of a model entity. + :param parent: The parent model entity of this entity. + """ super().__init__(label, parent) ConditionalMixin.__init__(self, condition) @@ -472,27 +1424,22 @@ def __init__( if timeout is not None: timeout.Parent = self - @property + @readonly def SensitivityList(self) -> List[Symbol]: + """ + Read-only property to access the sensitivity list (:attr:`_sensitivityList`). + + :returns: List of sensitivity list. + """ return self._sensitivityList - @property + @readonly def Timeout(self) -> ExpressionUnion: - return self._timeout - + """ + Read-only property to access the timeout (:attr:`_timeout`). -@export -class SequentialDeclarationsMixin(metaclass=ExtendedType, mixin=True): - _declaredItems: List + :returns: The timeout. + """ + return self._timeout - def __init__(self, declaredItems: Iterable) -> None: - # TODO: extract to mixin - self._declaredItems = [] # TODO: convert to dict - if declaredItems is not None: - for item in declaredItems: - self._declaredItems.append(item) - item.Parent = self - @property - def DeclaredItems(self) -> List: - return self._declaredItems diff --git a/pyVHDLModel/Subprogram.py b/pyVHDLModel/Subprogram.py index 377f3b360..3236faf86 100644 --- a/pyVHDLModel/Subprogram.py +++ b/pyVHDLModel/Subprogram.py @@ -34,100 +34,377 @@ Subprograms are procedures, functions and methods. """ -from typing import List, Optional as Nullable +from typing import List, Iterable, Optional as Nullable from pyTooling.Decorators import export, readonly from pyTooling.MetaClasses import ExtendedType from pyVHDLModel.Base import ModelEntity, NamedEntityMixin, DocumentedEntityMixin -from pyVHDLModel.Type import Subtype, ProtectedType +from pyVHDLModel.Symbol import SubtypeSymbol +from pyVHDLModel.Type import ProtectedType +from pyVHDLModel.Regions import ConcurrentDeclarationRegionMixin, SequentialDeclarationRegionMixin from pyVHDLModel.Sequential import SequentialStatement @export -class Subprogram(ModelEntity, NamedEntityMixin, DocumentedEntityMixin): - _genericItems: List['GenericInterfaceItem'] - _parameterItems: List['ParameterInterfaceItem'] - _declaredItems: List - _statements: List['SequentialStatement'] - _isPure: bool - - def __init__(self, identifier: str, isPure: bool, documentation: Nullable[str] = None, parent: Nullable[ModelEntity] = None) -> None: +class Subprogram(ModelEntity, NamedEntityMixin, DocumentedEntityMixin, SequentialDeclarationRegionMixin): + """ + Represents the base-class of all subprograms: procedures and functions. + + A subprogram is a named entity (:data:`Identifier`) with an optional generic clause + (:data:`GenericItems`), a parameter list (:data:`ParameterItems`), its own declarative part + (:data:`DeclaredItems`) and a sequence of statements (:data:`Statements`). + + .. seealso:: + + * :class:`Procedure ` + * :class:`Function ` + """ + _genericItems: List['GenericInterfaceItemMixin'] #: List of all generics, in declaration order. + _parameterItems: List['ParameterInterfaceItemMixin'] #: List of all parameters, in declaration order. + _statements: List[SequentialStatement] #: List of all sequential statements in the subprogram's body. + _isPure: bool #: ``True`` if the subprogram was declared pure. + + def __init__( + self, + identifier: str, + isPure: bool, + genericItems: Nullable[Iterable['GenericInterfaceItemMixin']] = None, + parameterItems: Nullable[Iterable['ParameterInterfaceItemMixin']] = None, + declaredItems: Nullable[Iterable] = None, + statements: Nullable[Iterable[SequentialStatement]] = None, + documentation: Nullable[str] = None, + parent: Nullable[ModelEntity] = None + ) -> None: + """ + Initializes a subprogram. + + :param identifier: The identifier of a model entity. + :param isPure: ``True`` if the subprogram was declared pure. + :param genericItems: List of all generics, in declaration order. + :param parameterItems: List of all parameters, in declaration order. + :param declaredItems: List of all declared items in this sequential declaration region. + :param statements: List of all sequential statements in the subprogram's body. + :param documentation: The documentation comment associated with this declaration. + :param parent: The parent model entity of this entity. + """ super().__init__(parent) NamedEntityMixin.__init__(self, identifier) DocumentedEntityMixin.__init__(self, documentation) + SequentialDeclarationRegionMixin.__init__(self, self._normalizedIdentifier, declaredItems) + + self._genericItems = [] # TODO: convert to dict + if genericItems is not None: + for item in genericItems: + self._genericItems.append(item) + item.Parent = self - self._genericItems = [] # TODO: convert to dict - self._parameterItems = [] # TODO: convert to dict - self._declaredItems = [] # TODO: use mixin class - self._statements = [] # TODO: use mixin class - self._isPure = isPure + self._parameterItems = [] # TODO: convert to dict + if parameterItems is not None: + for item in parameterItems: + self._parameterItems.append(item) + item.Parent = self + + self._statements = [] # TODO: use mixin class + if statements is not None: + for item in statements: + self._statements.append(item) + item.Parent = self + + self._isPure = isPure + + @ModelEntity.Parent.setter + def Parent(self, parent: ModelEntity) -> None: + ModelEntity.Parent.fset(self, parent) + + # Connect the subprogram's namespace to the enclosing declaration region's namespace, so a + # declaration inside the subprogram hides a same-named one from the scope around it. A subprogram + # can also be a protected type's method, and a protected type is no declaration region, hence the + # check. + if isinstance(parent, (ConcurrentDeclarationRegionMixin, SequentialDeclarationRegionMixin)): + self._namespace.ParentNamespace = parent._namespace @readonly - def GenericItems(self) -> List['GenericInterfaceItem']: + def GenericItems(self) -> List['GenericInterfaceItemMixin']: + """ + Read-only property to access the generic items (:attr:`_genericItems`). + + :returns: List of generic items. + """ return self._genericItems @readonly - def ParameterItems(self) -> List['ParameterInterfaceItem']: + def ParameterItems(self) -> List['ParameterInterfaceItemMixin']: + """ + Read-only property to access the parameter items (:attr:`_parameterItems`). + + :returns: List of parameter items. + """ return self._parameterItems @readonly - def DeclaredItems(self) -> List: - return self._declaredItems + def Statements(self) -> List[SequentialStatement]: + """ + Read-only property to access the statements (:attr:`_statements`). - @readonly - def Statements(self) -> List['SequentialStatement']: + :returns: List of statements. + """ return self._statements @readonly def IsPure(self) -> bool: + """ + Check if the subprogram is pure (:attr:`_isPure`). + + :returns: ``True``, if the subprogram is pure. + """ return self._isPure + def IndexDeclaredItems(self) -> None: + """A subprogram's generics and parameters share the declarative region of its declarative part.""" + self._IndexGenericItems() + self._IndexParameterItems() + + super().IndexDeclaredItems() + + @export class Procedure(Subprogram): - def __init__(self, identifier: str, documentation: Nullable[str] = None, parent: Nullable[ModelEntity] = None) -> None: - super().__init__(identifier, False, documentation, parent) + """ + Represents a procedure. + + Unlike a function, a procedure returns no value. Besides its parameters, it has its own + declarative part (:data:`DeclaredItems`) and statements (:data:`Statements`). + + .. admonition:: Example + + .. code-block:: VHDL + + procedure proc(signal s : in bit; variable v : out bit) is + -- ^^^^ <- Identifier + -- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ <- ParameterItems + variable tmp : bit; + --^^^^^^^^^^^^^^^^^^^ <- DeclaredItems + begin + tmp := s; + --^^^^^^^^^ <- Statements + v := tmp; + end procedure; + + .. seealso:: + + * :class:`Procedure instantiation ` + * :class:`Generic procedure interface item ` + * :class:`Procedure method ` + * :class:`Function ` + """ + def __init__( + self, + identifier: str, + genericItems: Nullable[Iterable['GenericInterfaceItemMixin']] = None, + parameterItems: Nullable[Iterable['ParameterInterfaceItemMixin']] = None, + declaredItems: Nullable[Iterable] = None, + statements: Nullable[Iterable[SequentialStatement]] = None, + documentation: Nullable[str] = None, + parent: Nullable[ModelEntity] = None + ) -> None: + """ + Initializes a procedure. + + :param identifier: The identifier of a model entity. + :param genericItems: List of all generics, in declaration order. + :param parameterItems: List of all parameters, in declaration order. + :param declaredItems: List of all declared items in this sequential declaration region. + :param statements: List of all sequential statements in the subprogram's body. + :param documentation: The documentation comment associated with this declaration. + :param parent: The parent model entity of this entity. + """ + super().__init__(identifier, False, genericItems, parameterItems, declaredItems, statements, documentation, parent) @export class Function(Subprogram): - _returnType: Subtype + """ + Represents a function. + + A function returns a value of its return type (:data:`ReturnType`) and is either pure or impure + (:data:`IsPure`). Besides its parameters, it has its own declarative part (:data:`DeclaredItems`) + and statements (:data:`Statements`). + + .. admonition:: Example + + .. code-block:: VHDL + + function fun(constant c : in positive) return integer is + -- ^^^ <- Identifier + -- ^^^^^^^^^^^^^^^^^^^^^^^^ <- ParameterItems + -- ^^^^^^^ <- ReturnType + variable tmp : integer; + --^^^^^^^^^^^^^^^^^^^^^^^ <- DeclaredItems + begin + tmp := c; + --^^^^^^^^^ <- Statements + return tmp; + end function; - def __init__(self, identifier: str, isPure: bool = True, documentation: Nullable[str] = None, parent: Nullable[ModelEntity] = None) -> None: - super().__init__(identifier, isPure, documentation, parent) + .. seealso:: - # FIXME: return type is missing + * :class:`Function instantiation ` + * :class:`Generic function interface item ` + * :class:`Function method ` + * :class:`Procedure ` + """ + _returnType: SubtypeSymbol #: Reference to the subtype of the function's return value. + + def __init__( + self, + identifier: str, + returnType: SubtypeSymbol, + isPure: bool = True, + genericItems: Nullable[Iterable['GenericInterfaceItemMixin']] = None, + parameterItems: Nullable[Iterable['ParameterInterfaceItemMixin']] = None, + declaredItems: Nullable[Iterable] = None, + statements: Nullable[Iterable[SequentialStatement]] = None, + documentation: Nullable[str] = None, + parent: Nullable[ModelEntity] = None + ) -> None: + """ + Initializes a function. + + :param identifier: The identifier of a model entity. + :param returnType: Reference to the subtype of the function's return value. + :param isPure: ``True`` if the subprogram was declared pure. + :param genericItems: List of all generics, in declaration order. + :param parameterItems: List of all parameters, in declaration order. + :param declaredItems: List of all declared items in this sequential declaration region. + :param statements: List of all sequential statements in the subprogram's body. + :param documentation: The documentation comment associated with this declaration. + :param parent: The parent model entity of this entity. + """ + super().__init__(identifier, isPure, genericItems, parameterItems, declaredItems, statements, documentation, parent) + + self._returnType = returnType + returnType.Parent = self @readonly - def ReturnType(self) -> Subtype: + def ReturnType(self) -> SubtypeSymbol: + """ + Read-only property to access the return type (:attr:`_returnType`). + + :returns: The return type. + """ return self._returnType @export class MethodMixin(metaclass=ExtendedType, mixin=True): - """A ``Method`` is a mixin class for all subprograms in a protected type.""" + """ + A ``Method`` is a mixin class for all subprograms in a protected type. + + .. seealso:: - _protectedType: ProtectedType + * :class:`Procedure method ` + * :class:`Function method ` + """ - def __init__(self, protectedType: ProtectedType) -> None: + _protectedType: ProtectedType #: Reference to the protected type this method belongs to. + + def __init__(self, protectedType: Nullable[ProtectedType] = None) -> None: + """ + Initializes a method. + + :param protectedType: Reference to the protected type this method belongs to. + """ self._protectedType = protectedType - protectedType.Parent = self + if protectedType is not None: + protectedType.Parent = self @readonly def ProtectedType(self) -> ProtectedType: + """ + Read-only property to access the protected type (:attr:`_protectedType`). + + :returns: The protected type. + """ return self._protectedType @export class ProcedureMethod(Procedure, MethodMixin): - def __init__(self, identifier: str, documentation: Nullable[str] = None, protectedType: Nullable[ProtectedType] = None, parent: Nullable[ModelEntity] = None) -> None: - super().__init__(identifier, documentation, parent) + """ + Represents a procedure declared as a method of a protected type. + + The protected type is available as :data:`ProtectedType`. + + .. seealso:: + + * :class:`Protected type ` + """ + def __init__( + self, + identifier: str, + genericItems: Nullable[Iterable['GenericInterfaceItemMixin']] = None, + parameterItems: Nullable[Iterable['ParameterInterfaceItemMixin']] = None, + declaredItems: Nullable[Iterable] = None, + statements: Nullable[Iterable[SequentialStatement]] = None, + documentation: Nullable[str] = None, + protectedType: Nullable[ProtectedType] = None, + parent: Nullable[ModelEntity] = None + ) -> None: + """ + Initializes a procedure declared as a method of a protected type. + + :param identifier: The identifier of a model entity. + :param genericItems: List of all generics, in declaration order. + :param parameterItems: List of all parameters, in declaration order. + :param declaredItems: List of all declared items in this sequential declaration region. + :param statements: List of all sequential statements in the subprogram's body. + :param documentation: The documentation comment associated with this declaration. + :param protectedType: Reference to the protected type this method belongs to. + :param parent: The parent model entity of this entity. + """ + super().__init__(identifier, genericItems, parameterItems, declaredItems, statements, documentation, parent) MethodMixin.__init__(self, protectedType) @export class FunctionMethod(Function, MethodMixin): - def __init__(self, identifier: str, isPure: bool = True, documentation: Nullable[str] = None, protectedType: Nullable[ProtectedType] = None, parent: Nullable[ModelEntity] = None) -> None: - super().__init__(identifier, isPure, documentation, parent) + """ + Represents a function declared as a method of a protected type. + + The protected type is available as :data:`ProtectedType`. + + .. seealso:: + + * :class:`Protected type ` + """ + def __init__( + self, + identifier: str, + returnType: SubtypeSymbol, + isPure: bool = True, + genericItems: Nullable[Iterable['GenericInterfaceItemMixin']] = None, + parameterItems: Nullable[Iterable['ParameterInterfaceItemMixin']] = None, + declaredItems: Nullable[Iterable] = None, + statements: Nullable[Iterable[SequentialStatement]] = None, + documentation: Nullable[str] = None, + protectedType: Nullable[ProtectedType] = None, + parent: Nullable[ModelEntity] = None + ) -> None: + """ + Initializes a function declared as a method of a protected type. + + :param identifier: The identifier of a model entity. + :param returnType: Reference to the subtype of the function's return value. + :param isPure: ``True`` if the subprogram was declared pure. + :param genericItems: List of all generics, in declaration order. + :param parameterItems: List of all parameters, in declaration order. + :param declaredItems: List of all declared items in this sequential declaration region. + :param statements: List of all sequential statements in the subprogram's body. + :param documentation: The documentation comment associated with this declaration. + :param protectedType: Reference to the protected type this method belongs to. + :param parent: The parent model entity of this entity. + """ + super().__init__(identifier, returnType, isPure, genericItems, parameterItems, declaredItems, statements, documentation, parent) MethodMixin.__init__(self, protectedType) diff --git a/pyVHDLModel/Symbol.py b/pyVHDLModel/Symbol.py index f3932b4a6..a43edefe1 100644 --- a/pyVHDLModel/Symbol.py +++ b/pyVHDLModel/Symbol.py @@ -47,7 +47,7 @@ @export class PossibleReference(Flag): """ - Is an enumeration, representing possible targets for a reference in a :class:`~pyVHDLModel.Symbol`. + Is an enumeration, representing possible targets for a reference in a :class:`~pyVHDLModel.Symbol.Symbol`. """ Unknown = 0 @@ -99,37 +99,77 @@ class Symbol(metaclass=ExtendedType): Base-class for all symbol classes. """ - _name: Name #: The name to reference the langauge entity. + _name: Name #: The name to reference the language entity. _possibleReferences: PossibleReference #: An enumeration to filter possible references. _reference: Nullable[Any] #: The resolved language entity, otherwise ``None``. def __init__(self, name: Name, possibleReferences: PossibleReference) -> None: + """ + Initializes a symbol. + + :param name: The name to reference the language entity. + :param possibleReferences: An enumeration to filter possible references. + """ self._name = name self._possibleReferences = possibleReferences self._reference = None @readonly def Name(self) -> Name: + """ + Read-only property to access the name (:attr:`_name`). + + :returns: The name. + """ return self._name @readonly def Reference(self) -> Nullable[Any]: + """ + Read-only property to access the reference (:attr:`_reference`). + + :returns: The reference, or ``None`` if not set. + """ return self._reference @readonly def IsResolved(self) -> bool: + """ + Check if the symbol is resolved, i.e. :attr:`_reference` is set. + + :returns: ``True``, if the symbol is resolved. + """ return self._reference is not None def __bool__(self) -> bool: + """ + Reports whether this symbol has been resolved. + + :returns: ``True`` if the symbol references a model entity. + """ return self._reference is not None def __repr__(self) -> str: + """ + Formats a representation of the symbol. + + **Format:** ``SignalSymbol: 'clk' -> ``, or ``... -> ?`` while unresolved + + :returns: String representation of the symbol. + """ if self._reference is not None: return f"{self.__class__.__name__}: '{self._name!s}' -> {self._reference!s}" return f"{self.__class__.__name__}: '{self._name!s}' -> unresolved" def __str__(self) -> str: + """ + Formats the symbol. + + **Format:** the referenced model entity once resolved, else the name plus ``?`` + + :returns: Formatted symbol. + """ if self._reference is not None: return str(self._reference) @@ -152,10 +192,20 @@ class LibraryReferenceSymbol(Symbol): """ def __init__(self, name: Name) -> None: + """ + Initializes a reference (name) to a library. + + :param name: The name to reference the language entity. + """ super().__init__(name, PossibleReference.Library) - @readonly + @property def Library(self) -> Nullable['Library']: + """ + Property to access the library (:attr:`_reference`). + + :returns: The library, or ``None`` if not set. + """ return self._reference @Library.setter @@ -179,10 +229,20 @@ class PackageReferenceSymbol(Symbol): """ def __init__(self, name: Name) -> None: + """ + Initializes a reference (name) to a package. + + :param name: The name to reference the language entity. + """ super().__init__(name, PossibleReference.Package) @property def Package(self) -> Nullable['Package']: + """ + Property to access the package (:attr:`_reference`). + + :returns: The package, or ``None`` if not set. + """ return self._reference @Package.setter @@ -190,6 +250,197 @@ def Package(self, value: 'Package') -> None: self._reference = value +@export +class ModeViewSymbol(Symbol): + """ + Represents a reference to a mode view (VHDL-2019). + + The referenced mode view is available as :data:`Reference` once resolved. A reference may also + select the converse view. + + .. admonition:: Example + + Referencing a mode view: + + .. code-block:: VHDL + + port (p : view MasterView); + -- ^^^^^^^^^^ <- Name + + Referencing its converse: + + .. code-block:: VHDL + + port (p : view MasterView'converse); + -- ^^^^^^^^^^^^^^^^^^^ <- Name + """ + + def __init__(self, name: Name) -> None: + """ + Initializes a reference to a mode view (VHDL-2019). + + :param name: The name to reference the language entity. + """ + super().__init__(name, PossibleReference.View) + + @property + def ModeView(self) -> Nullable['ModeViewDeclaration']: + """ + Property to access the mode view (:attr:`_reference`). + + :returns: The mode view, or ``None`` if not set. + """ + return self._reference + + @ModeView.setter + def ModeView(self, value: 'ModeViewDeclaration') -> None: + self._reference = value + + +@export +class SubprogramReferenceSymbol(Symbol): + """ + Represents a reference to a subprogram. + + The referenced subprogram is available as :data:`Reference` once resolved. + + .. admonition:: Example + + .. code-block:: VHDL + + function f is new gen_fun generic map (N => 1); + -- ^^^^^^^ <- Name + """ + + def __init__(self, name: Name) -> None: + """ + Initializes a reference to a subprogram. + + :param name: The name to reference the language entity. + """ + super().__init__(name, PossibleReference.SubProgram) + + @property + def Subprogram(self) -> Nullable['Subprogram']: + """ + Property to access the subprogram (:attr:`_reference`). + + :returns: The subprogram, or ``None`` if not set. + """ + return self._reference + + @Subprogram.setter + def Subprogram(self, value: 'Subprogram') -> None: + self._reference = value + + +@export +class ConfigurationSymbol(Symbol): + """ + Represents a reference to a configuration. + + The referenced configuration is available as :data:`Reference` once resolved. + + .. admonition:: Example + + .. code-block:: VHDL + + for U1 : comp use configuration work.cfg; + -- ^^^^^^^^ <- Name + """ + + def __init__(self, name: Name) -> None: + """ + Initializes a reference to a configuration. + + :param name: The name to reference the language entity. + """ + super().__init__(name, PossibleReference.Configuration) + + @property + def Configuration(self) -> Nullable['Configuration']: + """ + Property to access the configuration (:attr:`_reference`). + + :returns: The configuration, or ``None`` if not set. + """ + return self._reference + + @Configuration.setter + def Configuration(self, value: 'Configuration') -> None: + self._reference = value + + +@export +class VariableSymbol(Symbol): + """ + Represents a reference (name) to a variable, e.g. the target of a variable assignment. + + .. admonition:: Example + + .. code-block:: VHDL + + v := '1'; + --^ + """ + + def __init__(self, name: Name) -> None: + """ + Initializes a variable symbol. + + :param name: The name to reference the language entity. + """ + super().__init__(name, PossibleReference.Variable) + + @property + def Variable(self) -> Nullable['Variable']: + """ + Property to access the variable (:attr:`_reference`). + + :returns: The variable, or ``None`` if not set. + """ + return self._reference + + @Variable.setter + def Variable(self, value: 'Variable') -> None: + self._reference = value + + +@export +class SignalSymbol(Symbol): + """ + Represents a reference (name) to a signal, e.g. the target of a signal assignment. + + .. admonition:: Example + + .. code-block:: VHDL + + s <= '1'; + --^ + """ + + def __init__(self, name: Name) -> None: + """ + Initializes a signal symbol. + + :param name: The name to reference the language entity. + """ + super().__init__(name, PossibleReference.Signal) + + @property + def Signal(self) -> Nullable['Signal']: + """ + Property to access the signal (:attr:`_reference`). + + :returns: The signal, or ``None`` if not set. + """ + return self._reference + + @Signal.setter + def Signal(self, value: 'Signal') -> None: + self._reference = value + + @export class ContextReferenceSymbol(Symbol): """ @@ -206,10 +457,20 @@ class ContextReferenceSymbol(Symbol): """ def __init__(self, name: Name) -> None: + """ + Initializes a reference (name) to a context. + + :param name: The name to reference the language entity. + """ super().__init__(name, PossibleReference.Context) @property def Context(self) -> 'Context': + """ + Property to access the context (:attr:`_reference`). + + :returns: The context. + """ return self._reference @Context.setter @@ -233,10 +494,20 @@ class PackageMemberReferenceSymbol(Symbol): """ def __init__(self, name: Name) -> None: + """ + Initializes a reference (name) to a package member. + + :param name: The name to reference the language entity. + """ super().__init__(name, PossibleReference.PackageMember) @property def Member(self) -> Nullable['Package']: # TODO: typehint + """ + Property to access the member (:attr:`_reference`). + + :returns: The member, or ``None`` if not set. + """ return self._reference @Member.setter @@ -260,10 +531,20 @@ class AllPackageMembersReferenceSymbol(Symbol): """ def __init__(self, name: AllName) -> None: + """ + Initializes a reference (name) to all package members. + + :param name: The name to reference the language entity. + """ super().__init__(name, PossibleReference.PackageMember) @property def Members(self) -> 'Package': # TODO: typehint + """ + Property to access the members (:attr:`_reference`). + + :returns: The members. + """ return self._reference @Members.setter @@ -287,10 +568,20 @@ class EntityInstantiationSymbol(Symbol): """ def __init__(self, name: Name) -> None: + """ + Initializes a reference (name) to an entity in a direct entity instantiation. + + :param name: The name to reference the language entity. + """ super().__init__(name, PossibleReference.Entity) @property def Entity(self) -> 'Entity': + """ + Property to access the entity (:attr:`_reference`). + + :returns: The entity. + """ return self._reference @Entity.setter @@ -314,10 +605,20 @@ class ComponentInstantiationSymbol(Symbol): """ def __init__(self, name: Name) -> None: + """ + Initializes a reference (name) to an entity in a component instantiation. + + :param name: The name to reference the language entity. + """ super().__init__(name, PossibleReference.Component) @property def Component(self) -> 'Component': + """ + Property to access the component (:attr:`_reference`). + + :returns: The component. + """ return self._reference @Component.setter @@ -341,10 +642,20 @@ class ConfigurationInstantiationSymbol(Symbol): """ def __init__(self, name: Name) -> None: + """ + Initializes a reference (name) to an entity in a configuration instantiation. + + :param name: The name to reference the language entity. + """ super().__init__(name, PossibleReference.Configuration) @property def Configuration(self) -> 'Configuration': + """ + Property to access the configuration (:attr:`_reference`). + + :returns: The configuration. + """ return self._reference @Configuration.setter @@ -370,10 +681,20 @@ class EntitySymbol(Symbol): """ def __init__(self, name: Name) -> None: + """ + Initializes a reference (name) to an entity in an architecture declaration. + + :param name: The name to reference the language entity. + """ super().__init__(name, PossibleReference.Entity) @property def Entity(self) -> 'Entity': + """ + Property to access the entity (:attr:`_reference`). + + :returns: The entity. + """ return self._reference @Entity.setter @@ -386,10 +707,20 @@ class ArchitectureSymbol(Symbol): """An entity reference in an entity instantiation with architecture name.""" def __init__(self, name: Name) -> None: + """ + Initializes an architecture symbol. + + :param name: The name to reference the language entity. + """ super().__init__(name, PossibleReference.Architecture) @property def Architecture(self) -> 'Architecture': + """ + Property to access the architecture (:attr:`_reference`). + + :returns: The architecture. + """ return self._reference @Architecture.setter @@ -414,10 +745,20 @@ class PackageSymbol(Symbol): """ def __init__(self, name: Name) -> None: + """ + Initializes a reference (name) to a package in a package body declaration. + + :param name: The name to reference the language entity. + """ super().__init__(name, PossibleReference.Package) @property def Package(self) -> 'Package': + """ + Property to access the package (:attr:`_reference`). + + :returns: The package. + """ return self._reference @Package.setter @@ -427,17 +768,68 @@ def Package(self, value: 'Package') -> None: @export class RecordElementSymbol(Symbol): + """ + Represents a reference to a record element. + + The referenced language entity is available as :data:`Reference` once resolved. + + .. admonition:: Example + + .. code-block:: VHDL + + r := (a => '1', b => '0'); + -- ^ <- Name + """ def __init__(self, name: Name) -> None: + """ + Initializes a reference to a record element. + + :param name: The name to reference the language entity. + """ super().__init__(name, PossibleReference.RecordElement) +@export +class RangeAttributeSymbol(Symbol): + """A symbol referencing a range attribute, e.g. ``vector'range``.""" + + def __init__(self, name: Name) -> None: + """ + Initialize a range attribute symbol. + + :param name: The attribute name referencing the range. + """ + super().__init__(name, PossibleReference.RangeAttribute) + + @export class SubtypeSymbol(Symbol): + """ + Represents the base-class of all references to a type or subtype. + + The referenced language entity is available as :data:`Reference` once resolved. + + .. seealso:: + + * :class:`Simple subtype symbol ` + * :class:`Constrained scalar subtype symbol ` + * :class:`Constrained composite subtype symbol ` + """ def __init__(self, name: Name) -> None: + """ + Initializes a subtype symbol. + + :param name: The name to reference the language entity. + """ super().__init__(name, PossibleReference.Type | PossibleReference.Subtype) @property def Subtype(self) -> 'Subtype': + """ + Property to access the subtype (:attr:`_reference`). + + :returns: The subtype. + """ return self._reference @Subtype.setter @@ -447,73 +839,247 @@ def Subtype(self, value: 'Subtype') -> None: @export class SimpleSubtypeSymbol(SubtypeSymbol): + """ + Represents a reference to a type or subtype by its type mark. + + The referenced language entity is available as :data:`Reference` once resolved. + + .. admonition:: Example + + .. code-block:: VHDL + + signal s : bit := '0'; + -- ^^^ <- Name + """ pass @export -class ConstrainedScalarSubtypeSymbol(SubtypeSymbol): +class Constraint(metaclass=ExtendedType, mixin=True): + """ + A mixin-class for symbols carrying a constraint. + + .. seealso:: + + * :class:`Scalar constraint ` + * :class:`Array constraint ` + * :class:`Record constraint ` + """ pass @export -class Constraint: - pass +class ScalarConstraint(Constraint, mixin=True): + """ + A mixin-class for a scalar constraint: a range. + + The range is available as :data:`Constraint`. + + .. seealso:: + + * :class:`Constrained scalar subtype symbol ` + """ + _constraint: Nullable[Range] #: The range constraining the scalar subtype, or ``None`` if unconstrained. + + def __init__(self, constraint: Nullable[Range]) -> None: + """ + Initializes a scalar constraint. + + :param constraint: The range constraining the scalar subtype, or ``None`` if unconstrained. + """ + self._constraint = constraint + + @readonly + def Constraint(self) -> Nullable[Range]: + """ + Read-only property to access the scalar type's range constraint (:attr:`_constraint`). + + :returns: The constraint, or ``None`` if not set. + """ + return self._constraint + + +@export +class ConstrainedScalarSubtypeSymbol(SubtypeSymbol, ScalarConstraint): + """ + Represents a reference to a scalar subtype narrowed by a range. + + The referenced language entity is available as :data:`Reference` once resolved. + + .. admonition:: Example + + .. code-block:: VHDL + + for i in integer range 0 to 3 loop + -- ^^^^^^^ <- Name + -- ^^^^^^ <- Constraint + """ + + def __init__(self, name: Name, constraint: Nullable[Range] = None) -> None: + """ + Initializes a reference to a scalar subtype narrowed by a range. + + :param name: The name to reference the language entity. + :param constraint: The range constraining the scalar subtype, or ``None`` if unconstrained. + """ + super().__init__(name) + ScalarConstraint.__init__(self, constraint) @export -class ArrayConstraint(Constraint): - _constraints: List[Range] +class ArrayConstraint(Constraint, mixin=True): + """ + A mixin-class for an array constraint: one range per dimension. + + The ranges are available as :data:`Constraints`. + + .. seealso:: + + * :class:`Constrained array subtype symbol ` + """ + _constraints: List[Range] #: List of all index ranges, one per dimension. def __init__(self, constraints: Iterable[Range]) -> None: + """ + Initializes an array constraint. + + :param constraints: List of all index ranges, one per dimension. + """ self._constraints = [constraint for constraint in constraints] @readonly def Constraints(self) -> List[Range]: + """ + Read-only property to access the constraints (:attr:`_constraints`). + + :returns: List of constraints. + """ return self._constraints @export -class RecordConstraint(Constraint): - _constraints: Dict[RecordElementSymbol, Range] +class RecordConstraint(Constraint, mixin=True): + """ + A mixin-class for a record constraint: one constraint per element. + + The constraints are available as :data:`Constraints`. + + .. seealso:: + + * :class:`Constrained record subtype symbol ` + """ + _constraints: Dict[RecordElementSymbol, Range] #: Dictionary of the constraint per constrained record element. def __init__(self, constraints: Mapping[RecordElementSymbol, Range]) -> None: + """ + Initializes a record constraint. + + :param constraints: Dictionary of the constraint per constrained record element. + """ self._constraints = {key: value for key, value in constraints.items()} @readonly def Constraints(self) -> Dict[RecordElementSymbol, Range]: + """ + Read-only property to access the constraints (:attr:`_constraints`). + + :returns: Dictionary of constraints. + """ return self._constraints @export class ConstrainedCompositeSubtypeSymbol(SubtypeSymbol): + """ + Represents the base-class of references to constrained composite subtypes. + + The referenced language entity is available as :data:`Reference` once resolved. + + .. seealso:: + + * :class:`Constrained array subtype symbol ` + * :class:`Constrained record subtype symbol ` + """ pass @export class ConstrainedArraySubtypeSymbol(ConstrainedCompositeSubtypeSymbol, ArrayConstraint): - _constraints: List + """ + Represents a reference to an array subtype narrowed by index ranges. + + The referenced language entity is available as :data:`Reference` once resolved. + + .. admonition:: Example + + .. code-block:: VHDL + + signal v : bit_vector(7 downto 0); + -- ^^^^^^^^^^ <- Name + -- ^^^^^^^^^^ <- Constraints + """ + _constraints: List #: List of all index ranges, one per dimension. def __init__(self, name: Name, constraints: Iterable) -> None: + """ + Initializes a reference to an array subtype narrowed by index ranges. + + :param name: The name to reference the language entity. + :param constraints: List of all index ranges, one per dimension. + """ super().__init__(name) ArrayConstraint.__init__(self, constraints) @export class ConstrainedRecordSubtypeSymbol(ConstrainedCompositeSubtypeSymbol, RecordConstraint): - _constraints: Dict[RecordElementSymbol, Any] + """ + Represents a reference to a record subtype with constrained elements. + + The referenced language entity is available as :data:`Reference` once resolved. + """ + _constraints: Dict[RecordElementSymbol, Any] #: Dictionary of the constraint per constrained record element. def __init__(self, name: Name, constraints: Mapping) -> None: + """ + Initializes a reference to a record subtype with constrained elements. + + :param name: The name to reference the language entity. + :param constraints: Dictionary of the constraint per constrained record element. + """ super().__init__(name) RecordConstraint.__init__(self, constraints) @export class SimpleObjectOrFunctionCallSymbol(Symbol): + """ + Represents a reference that is either an object or a parameterless function call. + + Which of the two it is cannot be decided before the name is resolved. The referenced language + entity is available as :data:`Reference` once resolved. + """ def __init__(self, name: Name) -> None: + """ + Initializes a reference that is either an object or a parameterless function call. + + :param name: The name to reference the language entity. + """ super().__init__(name, PossibleReference.SimpleNameInExpression) @export class IndexedObjectOrFunctionCallSymbol(Symbol): + """ + Represents a reference that is either an indexed object or a function call. + + An expression like ``f(0)`` may index an array or call a function; both look identical until the + name is resolved. The referenced language entity is available as :data:`Reference` once resolved. + """ def __init__(self, name: Name) -> None: + """ + Initializes a reference that is either an indexed object or a function call. + + :param name: The name to reference the language entity. + """ super().__init__(name, PossibleReference.Object | PossibleReference.Function) diff --git a/pyVHDLModel/Type.py b/pyVHDLModel/Type.py index 6ffca477c..8dc176ead 100644 --- a/pyVHDLModel/Type.py +++ b/pyVHDLModel/Type.py @@ -42,54 +42,152 @@ from pyVHDLModel.Base import ModelEntity, NamedEntityMixin, MultipleNamedEntityMixin, DocumentedEntityMixin, ExpressionUnion, Range from pyVHDLModel.Symbol import Symbol -from pyVHDLModel.Name import Name from pyVHDLModel.Expression import EnumerationLiteral, PhysicalIntegerLiteral @export class BaseType(ModelEntity, NamedEntityMixin, DocumentedEntityMixin): - """``BaseType`` is the base-class of all type entities in this model.""" + """ + Represents the base-class of all type entities: full types, subtypes and anonymous types. - _objectVertex: Vertex + Every type is a named entity (:data:`Identifier`, :data:`NormalizedIdentifier`) and can carry + documentation (:data:`Documentation`). + + .. seealso:: + + * :class:`Type ` + * :class:`Full type ` + * :class:`Subtype ` + """ + + _objectVertex: Vertex #: The vertex representing this type in the design's object graph. def __init__(self, identifier: str, documentation: Nullable[str] = None, parent: Nullable[ModelEntity] = None) -> None: """ Initializes underlying ``BaseType``. - :param identifier: Name of the type. - :param parent: Reference to the logical parent in the model hierarchy. + :param identifier: Name of the type. + :param documentation: The documentation comment associated with this declaration. + :param parent: Reference to the logical parent in the model hierarchy. """ super().__init__(parent) NamedEntityMixin.__init__(self, identifier) DocumentedEntityMixin.__init__(self, documentation) - _objectVertex = None + self._objectVertex = None @export class Type(BaseType): + """ + Represents a base-class for types introduced by a type declaration. + + Besides real type declarations, this is also the base-class of a generic type interface item, which + introduces a type name without defining the type itself. + + .. seealso:: + + * :class:`Generic type interface item ` + * :class:`Anonymous type ` + """ pass @export class AnonymousType(Type): + """ + Represents a base-class for types without a type definition of their own. + + An incomplete type is the typical case: it names a type (:data:`Identifier`) whose full + definition follows later in the same declarative part. + + .. admonition:: Example + + .. code-block:: VHDL + + type node; + -- ^^^^ <- Identifier + type ptr is access node; + type node is record + value : integer; + nextNode : ptr; + end record; + """ pass @export class FullType(BaseType): + """ + Represents a base-class for all full type definitions, as opposed to a :class:`Subtype`. + + This is the distinction the declaration regions index on: a full type is registered in ``Types``, a + subtype in ``Subtypes``. + + .. seealso:: + + * :class:`Scalar type ` + * :class:`Composite type ` + * :class:`Protected type ` + * :class:`Protected type body ` + * :class:`Access type ` + * :class:`File type ` + """ pass @export class Subtype(BaseType): - _type: Symbol - _baseType: BaseType - _range: Range - _resolutionFunction: 'Function' + """ + Represents a subtype declaration. + + A subtype is a named entity (:data:`Identifier`, :data:`NormalizedIdentifier`) referencing a type + (:data:`Type`). Optionally, the subtype can be narrowed by a constraint (:data:`Range`) and/or + resolved by a resolution function (:data:`ResolutionFunction`). + + .. admonition:: Example - def __init__(self, identifier: str, symbol: Symbol, parent: Nullable[ModelEntity] = None) -> None: - super().__init__(identifier, parent) + Without a constraint: + + .. code-block:: VHDL + + subtype byte is bit_vector; + -- ^^^^ <- Identifier + -- ^^^^^^^^^^ <- Type + + With a constraint: + + .. code-block:: VHDL + + subtype nibble is bit_vector(3 downto 0); + -- ^^^^^^^^^^^^ <- Range + + With a resolution function: + + .. code-block:: VHDL + + subtype wired is resolved std_ulogic; + -- ^^^^^^^^ <- ResolutionFunction + + .. seealso:: + + * :class:`Reference to a type or subtype ` + """ + _type: Symbol #: Reference to the type or subtype this subtype is derived from. + _baseType: BaseType #: The resolved base type of this subtype. + _range: Range #: The constraint narrowing the base type, or ``None`` if unconstrained. + _resolutionFunction: 'Function' #: The resolution function, or ``None`` if the subtype is unresolved. + + def __init__(self, identifier: str, symbol: Symbol, documentation: Nullable[str] = None, parent: Nullable[ModelEntity] = None) -> None: + """ + Initializes a subtype declaration. + + :param identifier: The identifier of a model entity. + :param symbol: Reference to the type or subtype this subtype is derived from. + :param documentation: The documentation comment associated with this declaration. + :param parent: The parent model entity of this entity. + """ + super().__init__(identifier, documentation, parent) self._type = symbol self._baseType = None @@ -98,68 +196,169 @@ def __init__(self, identifier: str, symbol: Symbol, parent: Nullable[ModelEntity @readonly def Type(self) -> Symbol: + """ + Read-only property to access the type (:attr:`_type`). + + :returns: The type. + """ return self._type @readonly def BaseType(self) -> BaseType: + """ + Read-only property to access the base type (:attr:`_baseType`). + + :returns: The base type. + """ return self._baseType @readonly def Range(self) -> Range: + """ + Read-only property to access the range (:attr:`_range`). + + :returns: The range. + """ return self._range @readonly def ResolutionFunction(self) -> 'Function': + """ + Read-only property to access the resolution function (:attr:`_resolutionFunction`). + + :returns: The resolution function. + """ return self._resolutionFunction def __str__(self) -> str: + """ + Formats the subtype declaration. + + **Format:** ``subtype byte is bit_vector`` + + The *base type* is rendered, so an unlinked subtype shows ``None``. + + :returns: Formatted subtype declaration. + """ return f"subtype {self._identifier} is {self._baseType}" @export class ScalarType(FullType): - """A ``ScalarType`` is a base-class for all scalar types.""" + """ + Represents a base-class for all scalar types: enumerated, integer, real and physical types. + + .. seealso:: + + * :class:`Ranged scalar type ` + * :class:`Enumerated type ` + """ @export class RangedScalarType(ScalarType): - """A ``RangedScalarType`` is a base-class for all scalar types with a range.""" + """ + Represents a base-class for all scalar types constrained by a range (:data:`Range`). + + Integer, real and physical types are ranged. An enumerated type is scalar but not ranged, so it + derives from :class:`ScalarType` directly. + + .. seealso:: + + * :class:`Integer type ` + * :class:`Real type ` + * :class:`Physical type ` + """ + + _range: Range #: The range constraining this scalar type. - _range: Union[Range, Name] - _leftBound: ExpressionUnion - _rightBound: ExpressionUnion + def __init__(self, identifier: str, rng: Range, documentation: Nullable[str] = None, parent: Nullable[ModelEntity] = None) -> None: + """ + Initialize a scalar type with a range. - def __init__(self, identifier: str, rng: Union[Range, Name], parent: Nullable[ModelEntity] = None) -> None: - super().__init__(identifier, parent) + :param identifier: The type's identifier. + :param rng: The type's range. + :param documentation: The type's documentation. + :param parent: The parent model entity. + """ + super().__init__(identifier, documentation, parent) self._range = rng @readonly - def Range(self) -> Union[Range, Name]: + def Range(self) -> Range: + """ + Read-only property to access the type's range (:attr:`_range`). + + :returns: The range. + """ return self._range @export class NumericTypeMixin(metaclass=ExtendedType, mixin=True): - """A ``NumericType`` is a mixin class for all numeric types.""" + """ + A mixin-class for all numeric types: integer, real and physical types. + + .. seealso:: + + * :class:`Integer type ` + * :class:`Real type ` + * :class:`Physical type ` + """ def __init__(self) -> None: + """ + Initializes a numeric type. + """ pass @export class DiscreteTypeMixin(metaclass=ExtendedType, mixin=True): - """A ``DiscreteType`` is a mixin class for all discrete types.""" + """ + A mixin-class for all discrete types: enumerated and integer types. + + .. seealso:: + + * :class:`Enumerated type ` + * :class:`Integer type ` + """ def __init__(self) -> None: + """ + Initializes a discrete type. + """ pass @export class EnumeratedType(ScalarType, DiscreteTypeMixin): - _literals: List[EnumerationLiteral] + """ + Represents an enumerated type definition. + + An enumerated type is a named entity (:data:`Identifier`) listing its enumeration literals + (:data:`Literals`) in declaration order. - def __init__(self, identifier: str, literals: Iterable[EnumerationLiteral], parent: Nullable[ModelEntity] = None) -> None: - super().__init__(identifier, parent) + .. admonition:: Example + + .. code-block:: VHDL + + type state is (Idle, Running, Done); + -- ^^^^^ <- Identifier + -- ^^^^^^^^^^^^^^^^^^^ <- Literals + """ + _literals: List[EnumerationLiteral] #: List of all enumeration literals, in declaration order. + + def __init__(self, identifier: str, literals: Iterable[EnumerationLiteral], documentation: Nullable[str] = None, parent: Nullable[ModelEntity] = None) -> None: + """ + Initializes an enumerated type definition. + + :param identifier: The identifier of a model entity. + :param literals: List of all enumeration literals, in declaration order. + :param documentation: The documentation comment associated with this declaration. + :param parent: The parent model entity of this entity. + """ + super().__init__(identifier, documentation, parent) self._literals = [] if literals is not None: @@ -169,44 +368,147 @@ def __init__(self, identifier: str, literals: Iterable[EnumerationLiteral], pare @readonly def Literals(self) -> List[EnumerationLiteral]: + """ + Read-only property to access the literals (:attr:`_literals`). + + :returns: List of literals. + """ return self._literals def __str__(self) -> str: + """ + Formats the enumerated type definition. + + **Format:** ``state is (idle, run)`` + + :returns: Formatted enumerated type definition. + """ return f"{self._identifier} is ({', '.join(str(l) for l in self._literals)})" @export class IntegerType(RangedScalarType, NumericTypeMixin, DiscreteTypeMixin): - def __init__(self, identifier: str, rng: Union[Range, Name], parent: Nullable[ModelEntity] = None) -> None: - super().__init__(identifier, rng, parent) + """ + Represents an integer type definition. + + An integer type is a named entity (:data:`Identifier`) constrained by a range (:data:`Range`). + + .. admonition:: Example + + .. code-block:: VHDL + + type nibble is range 0 to 15; + -- ^^^^^^ <- Identifier + -- ^^^^^^^ <- Range + """ + def __init__(self, identifier: str, rng: Range, documentation: Nullable[str] = None, parent: Nullable[ModelEntity] = None) -> None: + """ + Initializes an integer type definition. + + :param identifier: The identifier of a model entity. + :param rng: The range constraining this scalar type. + :param documentation: The documentation comment associated with this declaration. + :param parent: The parent model entity of this entity. + """ + super().__init__(identifier, rng, documentation, parent) def __str__(self) -> str: + """ + Formats the integer type definition. + + **Format:** ``byte_count is range 0 to 7`` + + :returns: Formatted integer type definition. + """ return f"{self._identifier} is range {self._range}" @export class RealType(RangedScalarType, NumericTypeMixin): - def __init__(self, identifier: str, rng: Union[Range, Name], parent: Nullable[ModelEntity] = None) -> None: - super().__init__(identifier, rng, parent) + """ + Represents a floating-point type definition. + + A floating-point type is a named entity (:data:`Identifier`) constrained by a range + (:data:`Range`). + + .. admonition:: Example + + .. code-block:: VHDL + + type fraction is range 0.0 to 1.0; + -- ^^^^^^^^ <- Identifier + -- ^^^^^^^^^^ <- Range + """ + def __init__(self, identifier: str, rng: Range, documentation: Nullable[str] = None, parent: Nullable[ModelEntity] = None) -> None: + """ + Initializes a floating-point type definition. + + :param identifier: The identifier of a model entity. + :param rng: The range constraining this scalar type. + :param documentation: The documentation comment associated with this declaration. + :param parent: The parent model entity of this entity. + """ + super().__init__(identifier, rng, documentation, parent) def __str__(self) -> str: + """ + Formats the floating-point type definition. + + **Format:** ``gain is range 0.0 to 1.0`` + + :returns: Formatted floating-point type definition. + """ return f"{self._identifier} is range {self._range}" @export class PhysicalType(RangedScalarType, NumericTypeMixin): - _primaryUnit: str - _secondaryUnits: List[Tuple[str, PhysicalIntegerLiteral]] + """ + Represents a physical type definition. + + A physical type is a named entity (:data:`Identifier`) constrained by a range (:data:`Range`), and + defines a primary unit (:data:`PrimaryUnit`) plus any number of secondary units + (:data:`SecondaryUnits`). The model holds the secondary units in a list and has no distinct field + per unit, so the markers below name list elements. + + .. admonition:: Example + + .. code-block:: VHDL + + type distance is range 0 to 1000000 units + -- ^^^^^^^^ <- Identifier + -- ^^^^^^^^^^^^ <- Range + um; + --^^^ <- PrimaryUnit + mm = 1000 um; + --^^^^^^^^^^^^^ <- SecondaryUnits[0] + m = 1000 mm; + --^^^^^^^^^^^^^ <- SecondaryUnits[1] + end units; + """ + _primaryUnit: str #: The name of the type's primary unit. + _secondaryUnits: List[Tuple[str, PhysicalIntegerLiteral]] #: Secondary units as (name, value) pairs. def __init__( self, identifier: str, - rng: Union[Range, Name], + rng: Range, primaryUnit: str, units: Iterable[Tuple[str, PhysicalIntegerLiteral]], + documentation: Nullable[str] = None, parent: Nullable[ModelEntity] = None ) -> None: - super().__init__(identifier, rng, parent) + """ + Initializes a physical type definition. + + :param identifier: The identifier of a model entity. + :param rng: The range constraining this scalar type. + :param primaryUnit: The name of the type's primary unit. + :param units: Iterable of the secondary units as (name, value) pairs. + :param documentation: The documentation comment associated with this declaration. + :param parent: The parent model entity of this entity. + """ + super().__init__(identifier, rng, documentation, parent) self._primaryUnit = primaryUnit @@ -217,34 +519,96 @@ def __init__( @readonly def PrimaryUnit(self) -> str: + """ + Read-only property to access the primary unit (:attr:`_primaryUnit`). + + :returns: The primary unit. + """ return self._primaryUnit - @property + @readonly def SecondaryUnits(self) -> List[Tuple[str, PhysicalIntegerLiteral]]: + """ + Read-only property to access the secondary units (:attr:`_secondaryUnits`). + + :returns: List of secondary units. + """ return self._secondaryUnits def __str__(self) -> str: + """ + Formats the physical type definition. + + **Format:** ``distance is range 0 to 1000 units um; mm = 1000 um;`` + + :returns: Formatted physical type definition. + """ return f"{self._identifier} is range {self._range} units {self._primaryUnit}; {'; '.join(su + ' = ' + str(pu) for su, pu in self._secondaryUnits)};" @export class CompositeType(FullType): - """A ``CompositeType`` is a base-class for all composite types.""" + """ + Represents a base-class for all composite types: array and record types. + + .. seealso:: + + * :class:`Array type ` + * :class:`Record type ` + """ @export class ArrayType(CompositeType): - _dimensions: List[Range] - _elementType: Symbol + """ + Represents an array type definition. + + An array type is a named entity (:data:`Identifier`) defining one or more index ranges + (:data:`Dimensions`) and the subtype of its elements (:data:`ElementType`). + + .. admonition:: Example + + One dimension: + + .. code-block:: VHDL + + type memory is array (0 to 255) of bit_vector(7 downto 0); + -- ^^^^^^ <- Identifier + -- ^^^^^^^^ <- Dimensions + -- ^^^^^^^^^^^^^^^^^^^^^^ <- ElementType + + Two dimensions, both unconstrained: + + .. code-block:: VHDL + + type matrix is array (natural range <>, natural range <>) of bit; + -- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ <- Dimensions + + .. seealso:: + + * :class:`Reference to a constrained array subtype ` + """ + _dimensions: List[Range] #: List of all index ranges, one per dimension. + _elementType: Symbol #: Reference to the subtype of the array's elements. def __init__( self, identifier: str, indices: Iterable, elementSubtype: Symbol, + documentation: Nullable[str] = None, parent: Nullable[ModelEntity] = None ) -> None: - super().__init__(identifier, parent) + """ + Initializes an array type definition. + + :param identifier: The identifier of a model entity. + :param indices: List of all index ranges, one per dimension. + :param elementSubtype: Reference to the subtype of the array's elements. + :param documentation: The documentation comment associated with this declaration. + :param parent: The parent model entity of this entity. + """ + super().__init__(identifier, documentation, parent) self._dimensions = [] for index in indices: @@ -254,43 +618,131 @@ def __init__( self._elementType = elementSubtype # elementSubtype.Parent = self # FIXME: subtype is provided as None - @property + @readonly def Dimensions(self) -> List[Range]: + """ + Read-only property to access the dimensions (:attr:`_dimensions`). + + :returns: List of dimensions. + """ return self._dimensions - @property + @readonly def ElementType(self) -> Symbol: + """ + Read-only property to access the element type (:attr:`_elementType`). + + :returns: The element type. + """ return self._elementType def __str__(self) -> str: + """ + Formats the array type definition. + + **Format:** ``memory is array(0 to 7) of bit`` + + :returns: Formatted array type definition. + """ return f"{self._identifier} is array({'; '.join(str(r) for r in self._dimensions)}) of {self._elementType}" @export class RecordTypeElement(ModelEntity, MultipleNamedEntityMixin): - _subtype: Symbol + """ + Represents one element declaration inside a record type definition. + + A single declaration may name several elements at once, hence :data:`Identifiers` rather than one + identifier. All of them share the same subtype (:data:`Subtype`). + + .. admonition:: Example + + .. code-block:: VHDL + + type frame is record + a, b : bit; + --^^^^ <- Identifiers + -- ^^^ <- Subtype + end record; + + .. seealso:: + + * :class:`Record type ` + """ + _subtype: Symbol #: Reference to the subtype shared by all identifiers of this element declaration. def __init__(self, identifiers: Iterable[str], subtype: Symbol, parent: Nullable[ModelEntity] = None) -> None: + """ + Initializes a record type element. + + :param identifiers: A list of identifiers. + :param subtype: Reference to the subtype shared by all identifiers of this element declaration. + :param parent: The parent model entity of this entity. + """ super().__init__(parent) MultipleNamedEntityMixin.__init__(self, identifiers) self._subtype = subtype subtype.Parent = self - @property + @readonly def Subtype(self) -> Symbol: + """ + Read-only property to access the subtype (:attr:`_subtype`). + + :returns: The subtype. + """ return self._subtype def __str__(self) -> str: + """ + Formats the record element declaration. + + **Format:** ``a, b : bit`` + + :returns: Formatted record element declaration. + """ return f"{', '.join(self._identifiers)} : {self._subtype}" @export class RecordType(CompositeType): - _elements: List[RecordTypeElement] + """ + Represents a record type definition. + + A record type is a named entity (:data:`Identifier`) holding its element declarations + (:data:`Elements`) in declaration order. The model holds them in a list and has no distinct + field per element, so the markers below name list elements. + + .. admonition:: Example + + .. code-block:: VHDL + + type frame is record + -- ^^^^^ <- Identifier + a, b : bit; + --^^^^^^^^^^^^^^ <- Elements[0] + payload : bit_vector(31 downto 0); + --^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ <- Elements[1] + end record; - def __init__(self, identifier: str, elements: Nullable[Iterable[RecordTypeElement]] = None, parent: Nullable[ModelEntity] = None) -> None: - super().__init__(identifier, parent) + .. seealso:: + + * :class:`Record element ` + * :class:`Reference to a record element ` + """ + _elements: List[RecordTypeElement] #: List of all element declarations, in declaration order. + + def __init__(self, identifier: str, elements: Nullable[Iterable[RecordTypeElement]] = None, documentation: Nullable[str] = None, parent: Nullable[ModelEntity] = None) -> None: + """ + Initializes a record type definition. + + :param identifier: The identifier of a model entity. + :param elements: List of all element declarations, in declaration order. + :param documentation: The documentation comment associated with this declaration. + :param parent: The parent model entity of this entity. + """ + super().__init__(identifier, documentation, parent) self._elements = [] # TODO: convert to dict if elements is not None: @@ -298,20 +750,65 @@ def __init__(self, identifier: str, elements: Nullable[Iterable[RecordTypeElemen self._elements.append(element) element.Parent = self - @property + @readonly def Elements(self) -> List[RecordTypeElement]: + """ + Read-only property to access the elements (:attr:`_elements`). + + :returns: List of elements. + """ return self._elements def __str__(self) -> str: + """ + Formats the record type definition. + + **Format:** ``frame is record a : bit;`` + + :returns: Formatted record type definition. + """ return f"{self._identifier} is record {'; '.join(str(re) for re in self._elements)};" @export class ProtectedType(FullType): - _methods: List[Union['Procedure', 'Function']] + """ + Represents a protected type declaration. + + A protected type is a named entity (:data:`Identifier`) exposing only its methods + (:data:`Methods`). The implementation lives in a separate :class:`ProtectedTypeBody`. + The model holds the methods in a list and has no distinct field per method, so the markers + below name list elements. - def __init__(self, identifier: str, methods: Union[List, Iterator] = None, parent: Nullable[ModelEntity] = None) -> None: - super().__init__(identifier, parent) + .. admonition:: Example + + .. code-block:: VHDL + + type counter is protected + -- ^^^^^^^ <- Identifier + procedure increment; + --^^^^^^^^^^^^^^^^^^^^ <- Methods[0] + impure function value return natural; + --^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ <- Methods[1] + end protected; + + .. seealso:: + + * :class:`Protected type body ` + * :class:`Method of a protected type ` + """ + _methods: List[Union['Procedure', 'Function']] #: All methods, in declaration order. + + def __init__(self, identifier: str, methods: Union[List, Iterator] = None, documentation: Nullable[str] = None, parent: Nullable[ModelEntity] = None) -> None: + """ + Initializes a protected type declaration. + + :param identifier: The identifier of a model entity. + :param methods: All methods, in declaration order. + :param documentation: The documentation comment associated with this declaration. + :param parent: The parent model entity of this entity. + """ + super().__init__(identifier, documentation, parent) self._methods = [] if methods is not None: @@ -319,17 +816,60 @@ def __init__(self, identifier: str, methods: Union[List, Iterator] = None, paren self._methods.append(method) method.Parent = self - @property + @readonly def Methods(self) -> List[Union['Procedure', 'Function']]: + """ + Read-only property to access the methods (:attr:`_methods`). + + :returns: List of methods. + """ return self._methods @export class ProtectedTypeBody(FullType): - _methods: List[Union['Procedure', 'Function']] + """ + Represents a protected type body. + + A protected type body implements the methods (:data:`Methods`) declared by the + :class:`ProtectedType` of the same identifier (:data:`Identifier`). + + .. admonition:: Example + + .. code-block:: VHDL - def __init__(self, identifier: str, declaredItems: Union[List, Iterator] = None, parent: Nullable[ModelEntity] = None) -> None: - super().__init__(identifier, parent) + type counter is protected body + -- ^^^^^^^ <- Identifier + variable count : natural := 0; + procedure increment is + --^^^^^^^^^^^^^^^^^^^^^^ <- Methods[0] + begin + count := count + 1; + end procedure; + end protected body; + + .. note:: + + A protected type body may also declare non-subprogram items - ``variable count`` above. The + model currently stores every declared item in :data:`Methods`, so such declarations are + conflated with the methods and have no marker of their own. + + .. seealso:: + + * :class:`Protected type declaration ` + """ + _methods: List[Union['Procedure', 'Function']] #: All declared items; see the class docs on the conflation. + + def __init__(self, identifier: str, declaredItems: Union[List, Iterator] = None, documentation: Nullable[str] = None, parent: Nullable[ModelEntity] = None) -> None: + """ + Initializes a protected type body. + + :param identifier: The identifier of a model entity. + :param declaredItems: Iterable of all items declared in this body. + :param documentation: The documentation comment associated with this declaration. + :param parent: The parent model entity of this entity. + """ + super().__init__(identifier, documentation, parent) self._methods = [] if declaredItems is not None: @@ -338,42 +878,115 @@ def __init__(self, identifier: str, declaredItems: Union[List, Iterator] = None, method.Parent = self # FIXME: needs to be declared items or so - @property + @readonly def Methods(self) -> List[Union['Procedure', 'Function']]: + """ + Read-only property to access the methods (:attr:`_methods`). + + :returns: List of methods. + """ return self._methods @export class AccessType(FullType): - _designatedSubtype: Symbol + """ + Represents an access type definition. + + An access type is a named entity (:data:`Identifier`) pointing at values of its designated subtype + (:data:`DesignatedSubtype`). + + .. admonition:: Example - def __init__(self, identifier: str, designatedSubtype: Symbol, parent: Nullable[ModelEntity] = None) -> None: - super().__init__(identifier, parent) + .. code-block:: VHDL + + type ptr is access integer; + -- ^^^ <- Identifier + -- ^^^^^^^ <- DesignatedSubtype + """ + _designatedSubtype: Symbol #: Reference to the subtype the access values designate. + + def __init__(self, identifier: str, designatedSubtype: Symbol, documentation: Nullable[str] = None, parent: Nullable[ModelEntity] = None) -> None: + """ + Initializes an access type definition. + + :param identifier: The identifier of a model entity. + :param designatedSubtype: Reference to the subtype the access values designate. + :param documentation: The documentation comment associated with this declaration. + :param parent: The parent model entity of this entity. + """ + super().__init__(identifier, documentation, parent) self._designatedSubtype = designatedSubtype designatedSubtype.Parent = self - @property - def DesignatedSubtype(self): + @readonly + def DesignatedSubtype(self) -> Symbol: + """ + Read-only property to access the designated subtype (:attr:`_designatedSubtype`). + + :returns: The designated subtype. + """ return self._designatedSubtype def __str__(self) -> str: + """ + Formats the access type definition. + + **Format:** ``ptr is access node`` + + :returns: Formatted access type definition. + """ return f"{self._identifier} is access {self._designatedSubtype}" @export class FileType(FullType): - _designatedSubtype: Symbol + """ + Represents a file type definition. + + A file type is a named entity (:data:`Identifier`) holding values of its designated subtype + (:data:`DesignatedSubtype`). + + .. admonition:: Example - def __init__(self, identifier: str, designatedSubtype: Symbol, parent: Nullable[ModelEntity] = None) -> None: - super().__init__(identifier, parent) + .. code-block:: VHDL + + type text_file is file of string; + -- ^^^^^^^^^ <- Identifier + -- ^^^^^^ <- DesignatedSubtype + """ + _designatedSubtype: Symbol #: Reference to the subtype of the values stored in the file. + + def __init__(self, identifier: str, designatedSubtype: Symbol, documentation: Nullable[str] = None, parent: Nullable[ModelEntity] = None) -> None: + """ + Initializes a file type definition. + + :param identifier: The identifier of a model entity. + :param designatedSubtype: Reference to the subtype of the values stored in the file. + :param documentation: The documentation comment associated with this declaration. + :param parent: The parent model entity of this entity. + """ + super().__init__(identifier, documentation, parent) self._designatedSubtype = designatedSubtype designatedSubtype.Parent = self - @property - def DesignatedSubtype(self): + @readonly + def DesignatedSubtype(self) -> Symbol: + """ + Read-only property to access the designated subtype (:attr:`_designatedSubtype`). + + :returns: The designated subtype. + """ return self._designatedSubtype def __str__(self) -> str: - return f"{self._identifier} is access {self._designatedSubtype}" + """ + Formats the file type definition. + + **Format:** ``ft is file of character`` + + :returns: Formatted file type definition. + """ + return f"{self._identifier} is file of {self._designatedSubtype}" diff --git a/pyVHDLModel/__init__.py b/pyVHDLModel/__init__.py index e1c20a9ae..b9dd59eba 100644 --- a/pyVHDLModel/__init__.py +++ b/pyVHDLModel/__init__.py @@ -48,7 +48,7 @@ __email__ = "Paebbels@gmail.com" __copyright__ = "2016-2026, Patrick Lehmann" __license__ = "Apache License, Version 2.0" -__version__ = "0.37.0" +__version__ = "0.38.0" # __keywords__ = [] __project_url__ = "https://github.com/VHDL/pyVHDLModel" __documentation_url__ = "https://vhdl.github.io/pyVHDLModel" @@ -57,7 +57,6 @@ from enum import unique, Enum, Flag, auto from pathlib import Path -from sys import version_info from typing import Union, Dict, cast, List, Generator, Optional as Nullable @@ -184,7 +183,9 @@ def __lt__(self, other: Any) -> bool: if isinstance(other, VHDLVersion): return self.value < other.value else: - raise TypeError("Second operand is not of type 'VHDLVersion'.") + ex = TypeError("Second operand is not of type 'VHDLVersion'.") + ex.add_note(f"Got type '{getFullyQualifiedName(other)}'.") + raise ex def __le__(self, other: Any) -> bool: """ @@ -197,7 +198,9 @@ def __le__(self, other: Any) -> bool: if isinstance(other, VHDLVersion): return self.value <= other.value else: - raise TypeError("Second operand is not of type 'VHDLVersion'.") + ex = TypeError("Second operand is not of type 'VHDLVersion'.") + ex.add_note(f"Got type '{getFullyQualifiedName(other)}'.") + raise ex def __gt__(self, other: Any) -> bool: """ @@ -210,7 +213,9 @@ def __gt__(self, other: Any) -> bool: if isinstance(other, VHDLVersion): return self.value > other.value else: - raise TypeError("Second operand is not of type 'VHDLVersion'.") + ex = TypeError("Second operand is not of type 'VHDLVersion'.") + ex.add_note(f"Got type '{getFullyQualifiedName(other)}'.") + raise ex def __ge__(self, other: Any) -> bool: """ @@ -223,7 +228,9 @@ def __ge__(self, other: Any) -> bool: if isinstance(other, VHDLVersion): return self.value >= other.value else: - raise TypeError("Second operand is not of type 'VHDLVersion'.") + ex = TypeError("Second operand is not of type 'VHDLVersion'.") + ex.add_note(f"Got type '{getFullyQualifiedName(other)}'.") + raise ex def __ne__(self, other: Any) -> bool: """ @@ -236,7 +243,9 @@ def __ne__(self, other: Any) -> bool: if isinstance(other, VHDLVersion): return self.value != other.value else: - raise TypeError("Second operand is not of type 'VHDLVersion'.") + ex = TypeError("Second operand is not of type 'VHDLVersion'.") + ex.add_note(f"Got type '{getFullyQualifiedName(other)}'.") + raise ex def __eq__(self, other: Any) -> bool: """ @@ -252,23 +261,40 @@ def __eq__(self, other: Any) -> bool: else: return self.value == other.value else: - raise TypeError("Second operand is not of type 'VHDLVersion'.") + ex = TypeError("Second operand is not of type 'VHDLVersion'.") + ex.add_note(f"Got type '{getFullyQualifiedName(other)}'.") + raise ex + + def __hash__(self) -> int: + """ + Return the hash of the VHDL version using the underlying version number. + + .. note:: + + ``Any`` compares equal to every other member (see :meth:`__eq__`), which no hash value can satisfy + simultaneously for all members without collapsing every member to the same hash. This implementation + hashes by ``self.value``, which is internally consistent for all comparisons *except* those + involving ``Any`` - avoid using ``Any`` as a dict key or set member. + + :returns: Hash value of the underlying VHDL version number. + """ + return hash(self.value) @readonly def IsVHDL(self) -> bool: """ - Checks if the version is a VHDL (not VHDL-AMS) version. + Check if the version is a VHDL (not VHDL-AMS) version. - :returns: True if version is a VHDL version. + :returns: ``True``, if the version is a VHDL version. """ return self in (self.VHDL87, self.VHDL93, self.VHDL2002, self.VHDL2008, self.VHDL2019) @readonly def IsAMS(self) -> bool: """ - Checks if the version is a VHDL-AMS (not VHDL) version. + Check if the version is a VHDL-AMS (not VHDL) version. - :returns: True if version is a VHDL-AMS version. + :returns: ``True``, if the version is a VHDL-AMS version. """ return self in (self.AMS93, self.AMS99, self.AMS2017) @@ -497,8 +523,8 @@ def __init__( """ Initialize a VHDL design. - :param allowBlackbox: Specify if blackboxes are allowed in this design. :param name: Name of the design. + :param allowBlackbox: Specify if blackboxes are allowed in this design. """ super().__init__() AllowBlackboxMixin.__init__(self, allowBlackbox) @@ -593,7 +619,7 @@ def TopLevel(self) -> Union[Entity, Configuration]: if self._toplevel is not None: return self._toplevel - if self._hierarchyGraph.EdgeCount == 0: + if self._hierarchyGraph.VertexCount == 0: raise VHDLModelException(f"Hierarchy is not yet computed from dependency graph.") roots = tuple(self._hierarchyGraph.IterateRoots()) @@ -1171,7 +1197,7 @@ def _HandlePackage(package) -> None: signalVertex["kind"] = ObjectGraphVertexKind.Signal signal._objectVertex = signalVertex - def _LinkSymbolsInExpression(expression, namespace: Namespace, typeVertex: Vertex): + def _LinkSymbolsInExpression(expression, namespace: Namespace, typeVertex: Vertex) -> None: if isinstance(expression, UnaryExpression): _LinkSymbolsInExpression(expression.Operand, namespace, typeVertex) elif isinstance(expression, BinaryExpression): @@ -1188,7 +1214,7 @@ def _LinkSymbolsInExpression(expression, namespace: Namespace, typeVertex: Verte else: WarningCollector.Raise(NotImplementedWarning(f"Unhandled else-branch")) - def _LinkItems(package: Package): + def _LinkItems(package: Package) -> None: for item in package._declaredItems: if isinstance(item, Constant): print(f"constant: {item}") @@ -1874,7 +1900,7 @@ def searchEntityAndLinkComponent(library: Library, component: Component) -> None entity = library._entities[component.NormalizedIdentifier] except KeyError: if component.AllowBlackbox: - component._isBlackBox = True + component._isBlackbox = True return else: raise VHDLModelException( @@ -2173,7 +2199,13 @@ def __repr__(self) -> str: @export class Library(ModelEntity, NamedEntityMixin, AllowBlackboxMixin): - """A ``Library`` represents a VHDL library. It contains all *primary* and *secondary* design units.""" + """ + A ``Library`` represents a VHDL library. It contains all *primary* and *secondary* design units. + + .. seealso:: + + * :class:`Predefined library ` + """ _allowBlackbox: Nullable[bool] #: Allow blackboxes for components in this library. _contexts: Dict[str, Context] #: Dictionary of all contexts defined in a library. @@ -2213,32 +2245,57 @@ def __init__( @readonly def Contexts(self) -> Dict[str, Context]: - """Returns a list of all context declarations declared in this library.""" + """ + Read-only property to access the dictionary of all context declarations in this library (:attr:`_contexts`). + + :returns: Dictionary of all contexts, indexed by normalized identifier. + """ return self._contexts @readonly def Configurations(self) -> Dict[str, Configuration]: - """Returns a list of all configuration declarations declared in this library.""" + """ + Read-only property to access the dictionary of all configuration declarations in this library (:attr:`_configurations`). + + :returns: Dictionary of all configurations, indexed by normalized identifier. + """ return self._configurations @readonly def Entities(self) -> Dict[str, Entity]: - """Returns a list of all entity declarations declared in this library.""" + """ + Read-only property to access the dictionary of all entity declarations in this library (:attr:`_entities`). + + :returns: Dictionary of all entities, indexed by normalized identifier. + """ return self._entities @readonly def Architectures(self) -> Dict[str, Dict[str, Architecture]]: - """Returns a list of all architectures declarations declared in this library.""" + """ + Read-only property to access the dictionary of all architecture declarations in this library (:attr:`_architectures`). + + :returns: Dictionary of all architectures, indexed by normalized entity identifier, then by normalized + architecture identifier. + """ return self._architectures @readonly def Packages(self) -> Dict[str, Package]: - """Returns a list of all package declarations declared in this library.""" + """ + Read-only property to access the dictionary of all package declarations in this library (:attr:`_packages`). + + :returns: Dictionary of all packages, indexed by normalized identifier. + """ return self._packages @readonly def PackageBodies(self) -> Dict[str, PackageBody]: - """Returns a list of all package body declarations declared in this library.""" + """ + Read-only property to access the dictionary of all package body declarations in this library (:attr:`_packageBodies`). + + :returns: Dictionary of all package bodies, indexed by normalized identifier. + """ return self._packageBodies @readonly @@ -2568,7 +2625,7 @@ def __repr__(self) -> str: class Document(ModelEntity, DocumentedEntityMixin): """A ``Document`` represents a sourcefile. It contains *primary* and *secondary* design units.""" - _path: Path #: path to the document. ``None`` if virtual document. + _path: Path #: Path to the document. ``None`` if in-memory document. _vhdlVersion: VHDLVersion #: VHDL version used for analyzing this source file. _library: Library #: VHDL library used for analyzing the source file's content into. _designUnits: List[DesignUnit] #: List of all design units defined in a document. @@ -2593,6 +2650,15 @@ def __init__( library: Nullable[Library] = None, parent: Nullable[ModelEntity] = None ) -> None: + """ + Initializes a VHDL document. + + :param path: Path to the document. ``None`` if in-memory document. + :param documentation: The documentation comment associated with this declaration. + :param vhdlVersion: VHDL version used for analyzing this source file. + :param library: VHDL library used for analyzing the source file's content into. + :param parent: The parent model entity of this entity. + """ super().__init__(parent) DocumentedEntityMixin.__init__(self, documentation) @@ -2618,13 +2684,12 @@ def _AddEntity(self, item: Entity) -> None: Add an entity to the document's lists of design units. :param item: Entity object to be added to the document. - :raises TypeError: If parameter 'item' is not of type :class:`~pyVHDLModel.DesignUnits.Entity`. + :raises TypeError: If parameter 'item' is not of type :class:`~pyVHDLModel.DesignUnit.Entity`. :raises VHDLModelException: If entity name already exists in document. """ if not isinstance(item, Entity): ex = TypeError(f"Parameter 'item' is not of type 'Entity'.") - if version_info >= (3, 11): # pragma: no cover - ex.add_note(f"Got type '{getFullyQualifiedName(item)}'.") + ex.add_note(f"Got type '{getFullyQualifiedName(item)}'.") raise ex identifier = item._normalizedIdentifier @@ -2643,13 +2708,12 @@ def _AddArchitecture(self, item: Architecture) -> None: Add an architecture to the document's lists of design units. :param item: Architecture object to be added to the document. - :raises TypeError: If parameter 'item' is not of type :class:`~pyVHDLModel.DesignUnits.Architecture`. + :raises TypeError: If parameter 'item' is not of type :class:`~pyVHDLModel.DesignUnit.Architecture`. :raises VHDLModelException: If architecture name already exists for the referenced entity name in document. """ if not isinstance(item, Architecture): ex = TypeError(f"Parameter 'item' is not of type 'Architecture'.") - if version_info >= (3, 11): # pragma: no cover - ex.add_note(f"Got type '{getFullyQualifiedName(item)}'.") + ex.add_note(f"Got type '{getFullyQualifiedName(item)}'.") raise ex entity = item._entity.Name @@ -2675,13 +2739,12 @@ def _AddPackage(self, item: Package) -> None: Add a package to the document's lists of design units. :param item: Package object to be added to the document. - :raises TypeError: If parameter 'item' is not of type :class:`~pyVHDLModel.DesignUnits.Package`. + :raises TypeError: If parameter 'item' is not of type :class:`~pyVHDLModel.DesignUnit.Package`. :raises VHDLModelException: If package name already exists in document. """ if not isinstance(item, (Package, PackageInstantiation)): ex = TypeError(f"Parameter 'item' is not of type 'Package' or 'PackageInstantiation'.") - if version_info >= (3, 11): # pragma: no cover - ex.add_note(f"Got type '{getFullyQualifiedName(item)}'.") + ex.add_note(f"Got type '{getFullyQualifiedName(item)}'.") raise ex identifier = item._normalizedIdentifier @@ -2700,13 +2763,12 @@ def _AddPackageBody(self, item: PackageBody) -> None: Add a package body to the document's lists of design units. :param item: Package body object to be added to the document. - :raises TypeError: If parameter 'item' is not of type :class:`~pyVHDLModel.DesignUnits.PackageBody`. + :raises TypeError: If parameter 'item' is not of type :class:`~pyVHDLModel.DesignUnit.PackageBody`. :raises VHDLModelException: If package body name already exists in document. """ if not isinstance(item, PackageBody): ex = TypeError(f"Parameter 'item' is not of type 'PackageBody'.") - if version_info >= (3, 11): # pragma: no cover - ex.add_note(f"Got type '{getFullyQualifiedName(item)}'.") + ex.add_note(f"Got type '{getFullyQualifiedName(item)}'.") raise ex identifier = item._normalizedIdentifier @@ -2725,13 +2787,12 @@ def _AddContext(self, item: Context) -> None: Add a context to the document's lists of design units. :param item: Context object to be added to the document. - :raises TypeError: If parameter 'item' is not of type :class:`~pyVHDLModel.DesignUnits.Context`. + :raises TypeError: If parameter 'item' is not of type :class:`~pyVHDLModel.DesignUnit.Context`. :raises VHDLModelException: If context name already exists in document. """ if not isinstance(item, Context): ex = TypeError(f"Parameter 'item' is not of type 'Context'.") - if version_info >= (3, 11): # pragma: no cover - ex.add_note(f"Got type '{getFullyQualifiedName(item)}'.") + ex.add_note(f"Got type '{getFullyQualifiedName(item)}'.") raise ex identifier = item._normalizedIdentifier @@ -2750,13 +2811,12 @@ def _AddConfiguration(self, item: Configuration) -> None: Add a configuration to the document's lists of design units. :param item: Configuration object to be added to the document. - :raises TypeError: If parameter 'item' is not of type :class:`~pyVHDLModel.DesignUnits.Configuration`. + :raises TypeError: If parameter 'item' is not of type :class:`~pyVHDLModel.DesignUnit.Configuration`. :raises VHDLModelException: If configuration name already exists in document. """ if not isinstance(item, Configuration): ex = TypeError(f"Parameter 'item' is not of type 'Configuration'.") - if version_info >= (3, 11): # pragma: no cover - ex.add_note(f"Got type '{getFullyQualifiedName(item)}'.") + ex.add_note(f"Got type '{getFullyQualifiedName(item)}'.") raise ex identifier = item._normalizedIdentifier @@ -2773,8 +2833,7 @@ def _AddConfiguration(self, item: Configuration) -> None: def _AddVerificationUnit(self, item: VerificationUnit) -> None: if not isinstance(item, VerificationUnit): ex = TypeError(f"Parameter 'item' is not of type 'VerificationUnit'.") - if version_info >= (3, 11): # pragma: no cover - ex.add_note(f"Got type '{getFullyQualifiedName(item)}'.") + ex.add_note(f"Got type '{getFullyQualifiedName(item)}'.") raise ex identifier = item._normalizedIdentifier @@ -2790,8 +2849,7 @@ def _AddVerificationUnit(self, item: VerificationUnit) -> None: def _AddVerificationProperty(self, item: VerificationProperty) -> None: if not isinstance(item, VerificationProperty): ex = TypeError(f"Parameter 'item' is not of type 'VerificationProperty'.") - if version_info >= (3, 11): # pragma: no cover - ex.add_note(f"Got type '{getFullyQualifiedName(item)}'.") + ex.add_note(f"Got type '{getFullyQualifiedName(item)}'.") raise ex identifier = item.NormalizedIdentifier @@ -2807,8 +2865,7 @@ def _AddVerificationProperty(self, item: VerificationProperty) -> None: def _AddVerificationMode(self, item: VerificationMode) -> None: if not isinstance(item, VerificationMode): ex = TypeError(f"Parameter 'item' is not of type 'VerificationMode'.") - if version_info >= (3, 11): # pragma: no cover - ex.add_note(f"Got type '{getFullyQualifiedName(item)}'.") + ex.add_note(f"Got type '{getFullyQualifiedName(item)}'.") raise ex identifier = item.NormalizedIdentifier @@ -2826,14 +2883,13 @@ def _AddDesignUnit(self, item: DesignUnit) -> None: Add a design unit to the document's lists of design units. :param item: Configuration object to be added to the document. - :raises TypeError: If parameter 'item' is not of type :class:`~pyVHDLModel.DesignUnits.DesignUnit`. - :raises ValueError: If parameter 'item' is an unknown :class:`~pyVHDLModel.DesignUnits.DesignUnit`. + :raises TypeError: If parameter 'item' is not of type :class:`~pyVHDLModel.DesignUnit.DesignUnit`. + :raises ValueError: If parameter 'item' is an unknown :class:`~pyVHDLModel.DesignUnit.DesignUnit`. :raises VHDLModelException: If configuration name already exists in document. """ if not isinstance(item, DesignUnit): ex = TypeError(f"Parameter 'item' is not of type 'DesignUnit'.") - if version_info >= (3, 11): # pragma: no cover - ex.add_note(f"Got type '{getFullyQualifiedName(item)}'.") + ex.add_note(f"Got type '{getFullyQualifiedName(item)}'.") raise ex if isinstance(item, Entity): @@ -2856,8 +2912,7 @@ def _AddDesignUnit(self, item: DesignUnit) -> None: self._AddVerificationMode(item) else: ex = ValueError(f"Parameter 'item' is an unknown 'DesignUnit'.") - if version_info >= (3, 11): # pragma: no cover - ex.add_note(f"Got type '{getFullyQualifiedName(item)}'.") + ex.add_note(f"Got type '{getFullyQualifiedName(item)}'.") raise ex @readonly @@ -2906,81 +2961,82 @@ def DesignUnits(self) -> List[DesignUnit]: @readonly def Contexts(self) -> Dict[str, Context]: """ - Read-only property to access a list of all context declarations found in this document (:attr:`_contexts`). + Read-only property to access the dictionary of all context declarations in this document (:attr:`_contexts`). - :returns: List of all contexts. + :returns: Dictionary of all contexts, indexed by normalized identifier. """ return self._contexts @readonly def Configurations(self) -> Dict[str, Configuration]: """ - Read-only property to access a list of all configuration declarations found in this document (:attr:`_configurations`). + Read-only property to access the dictionary of all configuration declarations in this document (:attr:`_configurations`). - :returns: List of all configurations. + :returns: Dictionary of all configurations, indexed by normalized identifier. """ return self._configurations @readonly def Entities(self) -> Dict[str, Entity]: """ - Read-only property to access a list of all entity declarations found in this document (:attr:`_entities`). + Read-only property to access the dictionary of all entity declarations in this document (:attr:`_entities`). - :returns: List of all entities. + :returns: Dictionary of all entities, indexed by normalized identifier. """ return self._entities @readonly def Architectures(self) -> Dict[str, Dict[str, Architecture]]: """ - Read-only property to access a list of all architecture declarations found in this document (:attr:`_architectures`). + Read-only property to access the dictionary of all architecture declarations in this document (:attr:`_architectures`). - :returns: List of all architectures. + :returns: Dictionary of all architectures, indexed by normalized entity identifier, then by normalized + architecture identifier. """ return self._architectures @readonly def Packages(self) -> Dict[str, Package]: """ - Read-only property to access a list of all package declarations found in this document (:attr:`_packages`). + Read-only property to access the dictionary of all package declarations in this document (:attr:`_packages`). - :returns: List of all packages. + :returns: Dictionary of all packages, indexed by normalized identifier. """ return self._packages @readonly def PackageBodies(self) -> Dict[str, PackageBody]: """ - Read-only property to access a list of all package body declarations found in this document (:attr:`_packageBodies`). + Read-only property to access the dictionary of all package body declarations in this document (:attr:`_packageBodies`). - :returns: List of all package bodies. + :returns: Dictionary of all package bodies, indexed by normalized identifier. """ return self._packageBodies @readonly def VerificationUnits(self) -> Dict[str, VerificationUnit]: """ - Read-only property to access a list of all verification unit declarations found in this document (:attr:`_verificationUnits`). + Read-only property to access the dictionary of all verification unit declarations in this document (:attr:`_verificationUnits`). - :returns: List of all verification units. + :returns: Dictionary of all verification units, indexed by normalized identifier. """ return self._verificationUnits @readonly def VerificationProperties(self) -> Dict[str, VerificationProperty]: """ - Read-only property to access a list of all verification properties declarations found in this document (:attr:`_verificationProperties`). + Read-only property to access the dictionary of all verification property declarations in this document (:attr:`_verificationProperties`). - :returns: List of all verification properties. + :returns: Dictionary of all verification properties, indexed by normalized identifier. """ return self._verificationProperties @readonly def VerificationModes(self) -> Dict[str, VerificationMode]: """ - Read-only property to access a list of all verification modes declarations found in this document (:attr:`_verificationModes`). + Read-only property to access the dictionary of all verification mode declarations in this document (:attr:`_verificationModes`). - :returns: List of all verification modes. + :returns: Dictionary of all verification mode declarations, indexed by normalized identifier. """ return self._verificationModes diff --git a/pyproject.toml b/pyproject.toml index 03196172f..b0ea9d27c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [build-system] requires = [ "setuptools >= 83.0", - "pyTooling ~= 8.15" + "pyTooling ~= 8.17" ] build-backend = "setuptools.build_meta" @@ -53,7 +53,7 @@ junit_xml = "report/unit/unittest.xml" [tool.interrogate] color = true verbose = 1 # possible values: 0 (minimal output), 1 (-v), 2 (-vv) -fail-under = 59 +fail-under = 90 exclude = [ "build", "dist", @@ -62,6 +62,7 @@ exclude = [ "setup.py" ] ignore-setters = true +ignore-nested-functions = true # Nested helper functions are implementation-internal and never rendered by Sphinx. [tool.coverage.run] branch = true diff --git a/requirements.txt b/requirements.txt index 82b41f73f..fccb24b51 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1 @@ -pyTooling ~= 8.15 +pyTooling ~= 8.17 diff --git a/run.ps1 b/run.ps1 index 3ed08fa8d..e23f2fdd1 100644 --- a/run.ps1 +++ b/run.ps1 @@ -33,7 +33,7 @@ Param( ) $PackageName = "pyVHDLModel" -$PackageVersion = "0.37.0" +$PackageVersion = "0.38.0" # set default values $EnableDebug = [bool]$PSCmdlet.MyInvocation.BoundParameters["Debug"] diff --git a/tests/typing/requirements.txt b/tests/typing/requirements.txt index cde824b99..6fe31217f 100644 --- a/tests/typing/requirements.txt +++ b/tests/typing/requirements.txt @@ -1,6 +1,6 @@ -r ../../requirements.txt # Static Type Checking -mypy[reports] ~= 2.1 +mypy[reports] ~= 2.3 typing_extensions ~= 4.16 lxml >= 6.1 , <7.0 diff --git a/tests/unit/Analyze.py b/tests/unit/Analyze.py index 8f086c0ea..5318199c2 100644 --- a/tests/unit/Analyze.py +++ b/tests/unit/Analyze.py @@ -33,13 +33,19 @@ from pathlib import Path from unittest import TestCase -from pyVHDLModel import Design, Document +from pyVHDLModel import Design, Document, VHDLModelException from pyVHDLModel.Name import SimpleName, SelectedName, AllName from pyVHDLModel.Symbol import LibraryReferenceSymbol, PackageReferenceSymbol, AllPackageMembersReferenceSymbol from pyVHDLModel.Symbol import ContextReferenceSymbol, EntitySymbol, PackageSymbol, EntityInstantiationSymbol +from pyVHDLModel.Symbol import SimpleSubtypeSymbol, Symbol, PossibleReference from pyVHDLModel.DesignUnit import Package, PackageBody, Context, Entity, Architecture, Configuration from pyVHDLModel.DesignUnit import LibraryClause, UseClause, ContextReference +from pyVHDLModel.Configuration import BlockConfiguration from pyVHDLModel.Concurrent import EntityInstantiation +from pyVHDLModel.Object import Variable +from pyVHDLModel.Exception import NotImplementedWarning + +from pyTooling.Warning import WarningCollector if __name__ == "__main__": # pragma: no cover @@ -120,7 +126,12 @@ def CreateDesign(self) -> Design: packageBody = PackageBody(PackageSymbol(SimpleName("pack_1")), parent=None) document._AddDesignUnit(packageBody) - configuration = Configuration("cfg_1", parent=None) + configuration = Configuration( + "cfg_1", + EntitySymbol(SimpleName("entity_A")), + BlockConfiguration(Symbol(SimpleName("rtl"), PossibleReference.Architecture | PossibleReference.Label)), + parent=None + ) document._AddDesignUnit(configuration) design.AddDocument(document, library) @@ -206,3 +217,74 @@ def test_Analyze(self) -> None: design = self.CreateDesign() design.Analyze() + + +class TopLevel(TestCase): + def test_SingleEntityWithoutArchitecture_IsNotMistakenForUncomputedHierarchy(self) -> None: + """ + A design consisting of a single entity without any (yet analyzed) architecture produces a hierarchy graph with + exactly one vertex and zero edges. This must not be mistaken for "hierarchy not yet computed". + """ + design = Design("example") + library = design.GetLibrary("lib_1") + + document = Document(Path("tests.vhdl"), parent=None) + document._AddDesignUnit(Entity("entity_1", parent=None)) + design.AddDocument(document, library) + + design.CreateDependencyGraph() + design.CreateHierarchyGraph() + + self.assertEqual(0, design.HierarchyGraph.EdgeCount) + self.assertEqual(1, design.HierarchyGraph.VertexCount) + self.assertIs(library.Entities["entity_1"], design.TopLevel) + + def test_HierarchyNotYetComputed_RaisesVHDLModelException(self) -> None: + design = Design("example") + + with self.assertRaises(VHDLModelException): + design.TopLevel + + +class IndexDeclaredItems(TestCase): + """ + Regression tests for replacing a stray ``print()`` with ``WarningCollector.Raise(NotImplementedWarning(...))`` in + ``IndexDeclaredItems``, consistent with the same warning idiom used elsewhere in this codebase and mirrored by + pyGHDL.dom's own ``WarningCollector`` usage (both build on ``pyTooling.Warning``). + """ + + @staticmethod + def _designWithVariableInArchitecture() -> Design: + design = Design("example") + library = design.GetLibrary("lib_1") + + document = Document(Path("tests.vhdl"), parent=None) + document._AddDesignUnit(Entity("entity_1", parent=None)) + + variable = Variable(["v"], SimpleSubtypeSymbol(SimpleName("natural"))) + architecture = Architecture("arch_1", EntitySymbol(SimpleName("entity_1")), declaredItems=[variable], parent=None) + document._AddDesignUnit(architecture) + + design.AddDocument(document, library) + + return design + + def test_WarningIsCollected_WhenCollectorIsInScope(self) -> None: + design = self._designWithVariableInArchitecture() + + with WarningCollector() as collector: + design.IndexArchitectures() + + self.assertEqual(1, len(collector)) + self.assertIsInstance(collector[0], NotImplementedWarning) + + def test_DoesNotCrash_WhenNoCollectorIsInScope(self) -> None: + """ + Previously raised ``TypeError: category must be a Warning subclass, not 'type'`` due to a broken + ``warnings.warn(...)`` call. Must not raise here at all, since ``NotImplementedWarning`` is a *non-critical* + ``pyTooling.Warning.Warning`` and is silently dropped when unhandled - exactly like every other + ``NotImplementedWarning`` call site in this codebase. + """ + design = self._designWithVariableInArchitecture() + + design.IndexArchitectures() diff --git a/tests/unit/Hierarchy.py b/tests/unit/Hierarchy.py new file mode 100644 index 000000000..f02c3884a --- /dev/null +++ b/tests/unit/Hierarchy.py @@ -0,0 +1,94 @@ +# ==================================================================================================================== # +# __ ___ _ ____ _ __ __ _ _ # +# _ __ _ \ \ / / | | | _ \| | | \/ | ___ __| | ___| | # +# | '_ \| | | \ \ / /| |_| | | | | | | |\/| |/ _ \ / _` |/ _ \ | # +# | |_) | |_| |\ V / | _ | |_| | |___| | | | (_) | (_| | __/ | # +# | .__/ \__, | \_/ |_| |_|____/|_____|_| |_|\___/ \__,_|\___|_| # +# |_| |___/ # +# ==================================================================================================================== # +# Authors: # +# Patrick Lehmann # +# # +# License: # +# ==================================================================================================================== # +# Copyright 2026-2026 Patrick Lehmann - Boetzingen, Germany # +# # +# Licensed under the Apache License, Version 2.0 (the "License"); # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at # +# # +# http://www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +# # +# SPDX-License-Identifier: Apache-2.0 # +# ==================================================================================================================== # +# +"""Tests for traversing the model's parent-chain hierarchy, spanning multiple classes/levels of the model.""" +from unittest import TestCase + +from pyVHDLModel import Design, Library, VHDLModelException +from pyVHDLModel.DesignUnit import Entity, Architecture, Package +from pyVHDLModel.Symbol import EntitySymbol +from pyVHDLModel.Name import SimpleName + + +if __name__ == "__main__": # pragma: no cover + print("ERROR: you called a testcase declaration file as an executable module.") + print("Use: 'python -m unitest '") + exit(1) + + +class GetAncestor(TestCase): + def test_AncestorExists(self) -> None: + entity = Entity("entity_1") + architecture = Architecture("arch_1", EntitySymbol(SimpleName("entity_1")), parent=entity) + + self.assertIs(entity, architecture.GetAncestor(Entity)) + + def test_AncestorIsSelfsType(self) -> None: + design = Design() + library = Library("lib_1") + design.AddLibrary(library) + entity = Entity("entity_1", parent=library) + + self.assertIs(library, entity.GetAncestor(Library)) + self.assertIs(design, entity.GetAncestor(Design)) + + def test_AncestorDoesNotExist_RaisesVHDLModelException(self) -> None: + """Previously raised an unguarded ``AttributeError`` once the root of the model was reached without a match.""" + entity = Entity("entity_1") + + with self.assertRaises(VHDLModelException): + entity.GetAncestor(Package) + + +class AllowBlackBox(TestCase): + def test_LocalValueIsUsed(self) -> None: + entity = Entity("entity_1", allowBlackbox=True) + + self.assertTrue(entity.AllowBlackbox) + + def test_InheritsFromParent(self) -> None: + library = Library("lib_1", allowBlackbox=False) + entity = Entity("entity_1", parent=library) + + self.assertFalse(entity.AllowBlackbox) + + def test_LocalValueOverridesParent(self) -> None: + library = Library("lib_1", allowBlackbox=False) + entity = Entity("entity_1", allowBlackbox=True, parent=library) + + self.assertTrue(entity.AllowBlackbox) + self.assertFalse(library.AllowBlackbox) + + def test_NoLocalValueAndNoParent_RaisesVHDLModelException(self) -> None: + """Previously raised an unguarded ``AttributeError`` when no parent was available to inherit from.""" + entity = Entity("entity_1") + + with self.assertRaises(VHDLModelException): + entity.AllowBlackbox diff --git a/tests/unit/Instantiation/Assignment.py b/tests/unit/Instantiation/Assignment.py new file mode 100644 index 000000000..c1e569407 --- /dev/null +++ b/tests/unit/Instantiation/Assignment.py @@ -0,0 +1,196 @@ +# ==================================================================================================================== # +# __ ___ _ ____ _ __ __ _ _ # +# _ __ _ \ \ / / | | | _ \| | | \/ | ___ __| | ___| | # +# | '_ \| | | \ \ / /| |_| | | | | | | |\/| |/ _ \ / _` |/ _ \ | # +# | |_) | |_| |\ V / | _ | |_| | |___| | | | (_) | (_| | __/ | # +# | .__/ \__, | \_/ |_| |_|____/|_____|_| |_|\___/ \__,_|\___|_| # +# |_| |___/ # +# ==================================================================================================================== # +# Authors: # +# Patrick Lehmann # +# # +# License: # +# ==================================================================================================================== # +# Copyright 2026-2026 Patrick Lehmann - Boetzingen, Germany # +# # +# Licensed under the Apache License, Version 2.0 (the "License"); # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at # +# # +# http://www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +# # +# SPDX-License-Identifier: Apache-2.0 # +# ==================================================================================================================== # +# +"""Tests for conditional/selected/force/release assignment statements (Concurrent.py, Sequential.py, Common.py).""" +from unittest import TestCase + +from pyVHDLModel.Name import SimpleName +from pyVHDLModel.Symbol import SignalSymbol, VariableSymbol +from pyVHDLModel.Base import WaveformElement +from pyVHDLModel.Expression import IntegerLiteral, CharacterLiteral +from pyVHDLModel.Sequential import IndexedChoice +from pyVHDLModel.Common import ( + ConditionalWaveform, ConditionalExpression, + SelectedWaveform, OthersSelectedWaveform, + SelectedExpression, OthersSelectedExpression, +) +from pyVHDLModel.Concurrent import ConcurrentConditionalSignalAssignment, ConcurrentSelectedSignalAssignment +from pyVHDLModel.Sequential import ( + SequentialVariableAssignment, + SequentialConditionalVariableAssignment, SequentialConditionalSignalAssignment, + SequentialSelectedVariableAssignment, SequentialSelectedSignalAssignment, + SignalForceAssignment, SignalReleaseAssignment, +) + + +if __name__ == "__main__": # pragma: no cover + print("ERROR: you called a testcase declaration file as an executable module.") + print("Use: 'python -m unitest '") + exit(1) + + +def _signalTarget() -> SignalSymbol: + return SignalSymbol(SimpleName("s")) + + +def _variableTarget() -> VariableSymbol: + return VariableSymbol(SimpleName("v")) + + +class ConditionalAndSelectedBuildingBlocks(TestCase): + def test_ConditionalWaveform(self) -> None: + condition = IntegerLiteral(1) + waveform = [WaveformElement(CharacterLiteral("'1'"))] + cw = ConditionalWaveform(waveform, condition) + + self.assertIs(condition, cw.Condition) + self.assertEqual(1, len(cw.Waveform)) + + def test_ConditionalWaveform_FinalBranch(self) -> None: + """The final ('else') branch has no condition.""" + cw = ConditionalWaveform([WaveformElement(CharacterLiteral("'0'"))]) + + self.assertIsNone(cw.Condition) + + def test_ConditionalExpression(self) -> None: + condition = IntegerLiteral(1) + expression = CharacterLiteral("'1'") + ce = ConditionalExpression(expression, condition) + + self.assertIs(expression, ce.Expression) + self.assertIs(condition, ce.Condition) + + def test_SelectedWaveform(self) -> None: + choice = IndexedChoice(IntegerLiteral(0)) + waveform = [WaveformElement(CharacterLiteral("'1'"))] + sw = SelectedWaveform([choice], waveform) + + self.assertEqual(1, len(sw.Choices)) + self.assertEqual(1, len(sw.Waveform)) + + def test_OthersSelectedWaveform(self) -> None: + waveform = [WaveformElement(CharacterLiteral("'0'"))] + osw = OthersSelectedWaveform(waveform) + + self.assertEqual(1, len(osw.Waveform)) + + def test_SelectedExpression(self) -> None: + choice = IndexedChoice(IntegerLiteral(0)) + expression = CharacterLiteral("'1'") + se = SelectedExpression([choice], expression) + + self.assertEqual(1, len(se.Choices)) + self.assertIs(expression, se.Expression) + + def test_OthersSelectedExpression(self) -> None: + expression = CharacterLiteral("'0'") + ose = OthersSelectedExpression(expression) + + self.assertIs(expression, ose.Expression) + + +class ConcurrentAssignments(TestCase): + def test_ConditionalSignalAssignment(self) -> None: + """``s <= '1' when cond else '0';`` - regression test: this class was previously a bare stub + accepting a single 'expression' parameter that didn't fit what a conditional signal + assignment actually needs to store at all.""" + cw1 = ConditionalWaveform([WaveformElement(CharacterLiteral("'1'"))], IntegerLiteral(1)) + cw2 = ConditionalWaveform([WaveformElement(CharacterLiteral("'0'"))]) + + assignment = ConcurrentConditionalSignalAssignment("lbl", _signalTarget(), [cw1, cw2]) + + self.assertEqual(2, len(assignment.ConditionalWaveforms)) + self.assertIsNone(assignment.ConditionalWaveforms[-1].Condition) + + def test_SelectedSignalAssignment(self) -> None: + """``with sel select s <= '1' when 0, '0' when others;`` - same kind of stub as above.""" + sw = SelectedWaveform([IndexedChoice(IntegerLiteral(0))], [WaveformElement(CharacterLiteral("'1'"))]) + osw = OthersSelectedWaveform([WaveformElement(CharacterLiteral("'0'"))]) + + assignment = ConcurrentSelectedSignalAssignment("lbl", _signalTarget(), IntegerLiteral(0), [sw, osw]) + + self.assertEqual(2, len(assignment.SelectedWaveforms)) + + +class SequentialAssignments(TestCase): + def test_SimpleVariableAssignment(self) -> None: + """``v := '1';``""" + assignment = SequentialVariableAssignment(_variableTarget(), CharacterLiteral("'1'")) + + self.assertEqual("'1'", str(assignment.Expression)) + + def test_ConditionalVariableAssignment(self) -> None: + """``v := '1' when cond else '0';`` (VHDL-2008)""" + ce1 = ConditionalExpression(CharacterLiteral("'1'"), IntegerLiteral(1)) + ce2 = ConditionalExpression(CharacterLiteral("'0'")) + + assignment = SequentialConditionalVariableAssignment(_variableTarget(), [ce1, ce2]) + + self.assertEqual(2, len(assignment.ConditionalExpressions)) + self.assertIsNotNone(assignment.Target) + + def test_ConditionalSignalAssignment(self) -> None: + """``s <= '1' when cond else '0';`` (sequential form, VHDL-2008)""" + cw1 = ConditionalWaveform([WaveformElement(CharacterLiteral("'1'"))], IntegerLiteral(1)) + cw2 = ConditionalWaveform([WaveformElement(CharacterLiteral("'0'"))]) + + assignment = SequentialConditionalSignalAssignment(_signalTarget(), [cw1, cw2]) + + self.assertEqual(2, len(assignment.ConditionalWaveforms)) + + def test_SelectedVariableAssignment(self) -> None: + """``with sel select v := '1' when 0, '0' when others;``""" + se = SelectedExpression([IndexedChoice(IntegerLiteral(0))], CharacterLiteral("'1'")) + ose = OthersSelectedExpression(CharacterLiteral("'0'")) + + assignment = SequentialSelectedVariableAssignment(_variableTarget(), IntegerLiteral(0), [se, ose]) + + self.assertEqual(2, len(assignment.SelectedExpressions)) + + def test_SelectedSignalAssignment(self) -> None: + """``with sel select s <= '1' when 0, '0' when others;`` (sequential form)""" + sw = SelectedWaveform([IndexedChoice(IntegerLiteral(0))], [WaveformElement(CharacterLiteral("'1'"))]) + osw = OthersSelectedWaveform([WaveformElement(CharacterLiteral("'0'"))]) + + assignment = SequentialSelectedSignalAssignment(_signalTarget(), IntegerLiteral(0), [sw, osw]) + + self.assertEqual(2, len(assignment.SelectedWaveforms)) + + def test_SignalForceAssignment(self) -> None: + """``s <= force '1';`` (VHDL-2008)""" + assignment = SignalForceAssignment(_signalTarget(), CharacterLiteral("'1'")) + + self.assertEqual("'1'", str(assignment.Expression)) + + def test_SignalReleaseAssignment(self) -> None: + """``s <= release;`` (VHDL-2008) - no expression at all.""" + assignment = SignalReleaseAssignment(_signalTarget()) + + self.assertIsNotNone(assignment.Target) diff --git a/tests/unit/Instantiation/Base.py b/tests/unit/Instantiation/Base.py new file mode 100644 index 000000000..6371552c0 --- /dev/null +++ b/tests/unit/Instantiation/Base.py @@ -0,0 +1,283 @@ +# ==================================================================================================================== # +# __ ___ _ ____ _ __ __ _ _ # +# _ __ _ \ \ / / | | | _ \| | | \/ | ___ __| | ___| | # +# | '_ \| | | \ \ / /| |_| | | | | | | |\/| |/ _ \ / _` |/ _ \ | # +# | |_) | |_| |\ V / | _ | |_| | |___| | | | (_) | (_| | __/ | # +# | .__/ \__, | \_/ |_| |_|____/|_____|_| |_|\___/ \__,_|\___|_| # +# |_| |___/ # +# ==================================================================================================================== # +# Authors: # +# Patrick Lehmann # +# # +# License: # +# ==================================================================================================================== # +# Copyright 2026-2026 Patrick Lehmann - Boetzingen, Germany # +# # +# Licensed under the Apache License, Version 2.0 (the "License"); # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at # +# # +# http://www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +# # +# SPDX-License-Identifier: Apache-2.0 # +# ==================================================================================================================== # +# +""" +Tests for pyVHDLModel.Base. + +Level-1 tests: instantiate each mixin's simplest real consumer and check construction, property +access and parent-wiring. A mixin has no independent existence (mixin classes here can't be +instantiated standalone - they rely on the composed class's slots), so each is tested through its +narrowest real consumer rather than a synthetic stand-in class. + +Mixins already fully exercised elsewhere are intentionally not repeated here: + +- ``NamedEntityMixin``, ``ConditionalMixin`` - covered via tests/unit/Hierarchy.py and + tests/unit/Assignment.py (``ConditionalWaveform``) respectively. +- ``ChoicesMixin`` with a non-empty choice list - covered via tests/unit/Assignment.py + (``SelectedWaveform`` et al.). Only the ``choices=None`` default path is added here. +""" +from unittest import TestCase + +from pyVHDLModel.Base import Direction, Mode, ModelEntity, Range, RangeFromName, SimpleRange, WaveformElement +from pyVHDLModel.Expression import IntegerLiteral, CharacterLiteral +from pyVHDLModel.Interface import InterfaceGroup +from pyVHDLModel.Sequential import IfBranch, ElsifBranch, ElseBranch, SequentialReportStatement, SequentialAssertStatement, SequentialCase +from pyVHDLModel.Concurrent import ConcurrentBlockStatement +from pyVHDLModel.Name import SimpleName +from pyVHDLModel.Symbol import ConstrainedScalarSubtypeSymbol, SimpleSubtypeSymbol + + +if __name__ == "__main__": # pragma: no cover + print("ERROR: you called a testcase declaration file as an executable module.") + print("Use: 'python -m unitest '") + exit(1) + + +class DirectionEnum(TestCase): + def test_To(self) -> None: + self.assertEqual("to", str(Direction.To)) + + def test_DownTo(self) -> None: + self.assertEqual("downto", str(Direction.DownTo)) + + +class ModeEnum(TestCase): + def test_AllValues(self) -> None: + """Every ``Mode`` value has a dedicated formatting slot in ``Mode.__str__``'s lookup tuple.""" + expected = { + Mode.Default: "", + Mode.In: "in", + Mode.Out: "out", + Mode.InOut: "inout", + Mode.Buffer: "buffer", + Mode.Linkage: "linkage", + } + + for mode, text in expected.items(): + self.assertEqual(text, str(mode)) + + +class ModelEntities(TestCase): + def test_NoParent(self) -> None: + entity = ModelEntity() + + self.assertIsNone(entity.Parent) + + def test_ConstructedWithParent(self) -> None: + parent = ModelEntity() + entity = ModelEntity(parent=parent) + + self.assertIs(parent, entity.Parent) + + def test_ParentSetter(self) -> None: + parent = ModelEntity() + entity = ModelEntity() + entity.Parent = parent + + self.assertIs(parent, entity.Parent) + + def test_ParentSetter_RejectsNone(self) -> None: + """Unlike the constructor (where omitting ``parent`` is normal for a not-yet-attached root), the + ``Parent`` setter rejects ``None`` outright - it's only ever used to attach an entity to a real + parent after the fact.""" + entity = ModelEntity(parent=ModelEntity()) + + with self.assertRaises(ValueError): + entity.Parent = None + + +class OptionallyNamedEntity(TestCase): + """``InterfaceGroup`` is the only current consumer of ``OptionallyNamedEntityMixin``.""" + + def test_WithName(self) -> None: + group = InterfaceGroup("generics") + + self.assertEqual("generics", group.Identifier) + self.assertEqual("generics", group.NormalizedIdentifier) + + def test_WithoutName(self) -> None: + group = InterfaceGroup() + + self.assertIsNone(group.Identifier) + self.assertIsNone(group.NormalizedIdentifier) + + def test_NameIsNormalized(self) -> None: + group = InterfaceGroup("Generics") + + self.assertEqual("generics", group.NormalizedIdentifier) + + +class BranchMixins(TestCase): + """``IfBranch``/``ElsifBranch``/``ElseBranch`` are the only consumers of ``BranchMixin``, + ``ConditionalBranchMixin`` and the ``If-``/``Elsif-``/``ElseBranchMixin`` variants.""" + + def test_IfBranch(self) -> None: + condition = IntegerLiteral(1) + branch = IfBranch(condition) + + self.assertIs(condition, branch.Condition) + self.assertEqual(0, len(branch.Statements)) + + def test_ElsifBranch(self) -> None: + condition = IntegerLiteral(1) + branch = ElsifBranch(condition) + + self.assertIs(condition, branch.Condition) + + def test_ElseBranch(self) -> None: + """The ``else`` branch has no condition at all - ``ElseBranchMixin`` doesn't inherit + ``ConditionalMixin``.""" + branch = ElseBranch() + + self.assertEqual(0, len(branch.Statements)) + + +class ReportAndAssertStatementMixins(TestCase): + def test_ReportStatement_MessageAndSeverity(self) -> None: + message = CharacterLiteral("'a'") + severity = IntegerLiteral(1) + statement = SequentialReportStatement(message, severity) + + self.assertIs(message, statement.Message) + self.assertIs(severity, statement.Severity) + + def test_ReportStatement_MessageOnly(self) -> None: + """``severity`` genuinely defaults to omitted in the grammar (``report "msg";`` without a + ``severity`` clause).""" + message = CharacterLiteral("'a'") + statement = SequentialReportStatement(message) + + self.assertIs(message, statement.Message) + self.assertIsNone(statement.Severity) + + def test_AssertStatement(self) -> None: + """``AssertStatementMixin`` adds a required ``Condition`` on top of ``ReportStatementMixin``'s + optional message/severity.""" + condition = IntegerLiteral(1) + message = CharacterLiteral("'a'") + statement = SequentialAssertStatement(condition, message) + + self.assertIs(condition, statement.Condition) + self.assertIs(message, statement.Message) + self.assertIsNone(statement.Severity) + + +class BlockStatementMixinHost(TestCase): + """``ConcurrentBlockStatement`` is the only consumer of ``BlockStatementMixin``. Also covers + ``LabeledEntityMixin.NormalizedLabel``, which no other current test reads (only ``.Label``).""" + + def test_MinimalBlock(self) -> None: + block = ConcurrentBlockStatement("BLK") + + self.assertEqual("BLK", block.Label) + self.assertEqual("blk", block.NormalizedLabel) + + +class ChoicesMixinHost(TestCase): + def test_NoChoices(self) -> None: + """``choices=None`` (the default) - the non-empty-list path is already covered via + tests/unit/Assignment.py's ``SelectedWaveform``/``SelectedExpression`` tests.""" + case = SequentialCase() + + self.assertEqual(0, len(case.Choices)) + + +class Ranges(TestCase): + """``SimpleRange`` (``3 downto 0``) - a concrete class defined directly in Base.py, not a mixin.""" + + def test_Construction(self) -> None: + left = IntegerLiteral(3) + right = IntegerLiteral(0) + rng = SimpleRange(left, right, Direction.DownTo) + + self.assertIs(left, rng.LeftBound) + self.assertIs(right, rng.RightBound) + self.assertIs(Direction.DownTo, rng.Direction) + self.assertIs(rng, left.Parent) + self.assertIs(rng, right.Parent) + + def test_ToString(self) -> None: + rng = SimpleRange(IntegerLiteral(0), IntegerLiteral(7), Direction.To) + + self.assertEqual("0 to 7", str(rng)) + + def test_IsARange(self) -> None: + rng = SimpleRange(IntegerLiteral(0), IntegerLiteral(7), Direction.To) + + self.assertIsInstance(rng, Range) + + +class RangesFromName(TestCase): + """``RangeFromName`` (``vector'range``, ``bit``) - a range whose bounds come from a referenced symbol.""" + + def test_Construction(self) -> None: + symbol = SimpleSubtypeSymbol(SimpleName("bit")) + rng = RangeFromName(symbol) + + self.assertIs(symbol, rng.Symbol) + self.assertIs(rng, symbol.Parent) + self.assertIsInstance(rng, Range) + + def test_ToStringUnresolved(self) -> None: + rng = RangeFromName(SimpleSubtypeSymbol(SimpleName("bit"))) + + # An unresolved symbol renders with a trailing question mark. + self.assertEqual("bit?", str(rng)) + + def test_ConstrainedSubtypeKeepsTypeMarkAndConstraint(self) -> None: + # `integer range 0 to 7` - the type mark must survive alongside the range constraint. + constraint = SimpleRange(IntegerLiteral(0), IntegerLiteral(7), Direction.To) + symbol = ConstrainedScalarSubtypeSymbol(SimpleName("integer"), constraint) + rng = RangeFromName(symbol) + + self.assertIs(symbol, rng.Symbol) + self.assertIs(constraint, rng.Symbol.Constraint) + self.assertEqual("integer", rng.Symbol.Name.Identifier) + + +class WaveformElements(TestCase): + """``WaveformElement`` (``'1' after 5 ns``) - a concrete class defined directly in Base.py, not a + mixin. tests/unit/Assignment.py already constructs these but never reads ``.Expression``/ + ``.After``.""" + + def test_WithoutAfter(self) -> None: + expression = CharacterLiteral("'1'") + element = WaveformElement(expression) + + self.assertIs(expression, element.Expression) + self.assertIsNone(element.After) + + def test_WithAfter(self) -> None: + expression = CharacterLiteral("'1'") + after = IntegerLiteral(5) + element = WaveformElement(expression, after) + + self.assertIs(after, element.After) + self.assertIs(element, after.Parent) diff --git a/tests/unit/Instantiation/Concurrent.py b/tests/unit/Instantiation/Concurrent.py new file mode 100644 index 000000000..a091f8278 --- /dev/null +++ b/tests/unit/Instantiation/Concurrent.py @@ -0,0 +1,350 @@ +# ==================================================================================================================== # +# __ ___ _ ____ _ __ __ _ _ # +# _ __ _ \ \ / / | | | _ \| | | \/ | ___ __| | ___| | # +# | '_ \| | | \ \ / /| |_| | | | | | | |\/| |/ _ \ / _` |/ _ \ | # +# | |_) | |_| |\ V / | _ | |_| | |___| | | | (_) | (_| | __/ | # +# | .__/ \__, | \_/ |_| |_|____/|_____|_| |_|\___/ \__,_|\___|_| # +# |_| |___/ # +# ==================================================================================================================== # +# Authors: # +# Patrick Lehmann # +# # +# License: # +# ==================================================================================================================== # +# Copyright 2026-2026 Patrick Lehmann - Boetzingen, Germany # +# # +# Licensed under the Apache License, Version 2.0 (the "License"); # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at # +# # +# http://www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +# # +# SPDX-License-Identifier: Apache-2.0 # +# ==================================================================================================================== # +# +""" +Tests for pyVHDLModel.Concurrent - the concurrent statement kinds not already covered by +tests/unit/Assignment.py (conditional/selected signal assignments) or tests/unit/Base.py +(ConcurrentBlockStatement's BlockStatementMixin/LabeledEntityMixin wiring). +""" +from unittest import TestCase + +from pyVHDLModel.Base import Direction, SimpleRange +from pyVHDLModel.Name import SimpleName +from pyVHDLModel.Symbol import ( + ComponentInstantiationSymbol, EntityInstantiationSymbol, ArchitectureSymbol, + ConfigurationInstantiationSymbol, SignalSymbol, Symbol, PossibleReference, EntitySymbol, +) +from pyVHDLModel.Expression import IntegerLiteral, CharacterLiteral +from pyVHDLModel.Association import GenericAssociationItem, PortAssociationItem +from pyVHDLModel.Base import WaveformElement +from pyVHDLModel.DesignUnit import Architecture +from pyVHDLModel.Concurrent import ( + ComponentInstantiation, EntityInstantiation, ConfigurationInstantiation, + ProcessStatement, ConcurrentProcedureCall, ConcurrentBlockStatement, + IfGenerateBranch, ElsifGenerateBranch, ElseGenerateBranch, + GenerateStatement, IfGenerateStatement, + IndexedGenerateChoice, RangedGenerateChoice, GenerateCase, OthersGenerateCase, CaseGenerateStatement, + ForGenerateStatement, ConcurrentSimpleSignalAssignment, ConcurrentAssertStatement, +) + + +if __name__ == "__main__": # pragma: no cover + print("ERROR: you called a testcase declaration file as an executable module.") + print("Use: 'python -m unitest '") + exit(1) + + +def _entitySymbol(name: str = "e") -> EntitySymbol: + return EntitySymbol(SimpleName(name)) + + +class Instantiations(TestCase): + def test_ComponentInstantiation(self) -> None: + componentSymbol = ComponentInstantiationSymbol(SimpleName("comp")) + generic = GenericAssociationItem(SimpleName("G"), IntegerLiteral(1)) + port = PortAssociationItem(SimpleName("p"), SimpleName("s")) + instance = ComponentInstantiation("inst", componentSymbol, [generic], [port]) + + self.assertIs(componentSymbol, instance.Component) + self.assertIs(instance, componentSymbol.Parent) + self.assertEqual(1, len(instance.GenericAssociationItems)) + self.assertIs(instance, generic.Parent) + self.assertEqual(1, len(instance.PortAssociationItems)) + self.assertIs(instance, port.Parent) + + def test_EntityInstantiation_WithArchitecture(self) -> None: + entitySymbol = EntityInstantiationSymbol(SimpleName("ent")) + architectureSymbol = ArchitectureSymbol(SimpleName("rtl")) + instance = EntityInstantiation("inst", entitySymbol, architectureSymbol) + + self.assertIs(entitySymbol, instance.Entity) + self.assertIs(architectureSymbol, instance.Architecture) + self.assertIs(instance, architectureSymbol.Parent) + + def test_EntityInstantiation_WithoutArchitecture(self) -> None: + instance = EntityInstantiation("inst", EntityInstantiationSymbol(SimpleName("ent"))) + + self.assertIsNone(instance.Architecture) + + def test_ConfigurationInstantiation(self) -> None: + configurationSymbol = ConfigurationInstantiationSymbol(SimpleName("cfg")) + instance = ConfigurationInstantiation("inst", configurationSymbol) + + self.assertIs(configurationSymbol, instance.Configuration) + self.assertIs(instance, configurationSymbol.Parent) + + +class ProcessStatements(TestCase): + def test_Empty(self) -> None: + """``proc: process begin end process;``""" + process = ProcessStatement("proc") + + self.assertEqual("proc", process.Label) + self.assertIsNone(process.SensitivityList) + self.assertEqual(0, len(process.DeclaredItems)) + self.assertEqual(0, len(process.Statements)) + + def test_WithSensitivityList(self) -> None: + """``proc: process(clk) begin end process;``""" + clk = SimpleName("clk") + process = ProcessStatement("proc", sensitivityList=[clk]) + + self.assertEqual(1, len(process.SensitivityList)) + self.assertIs(clk, process.SensitivityList[0]) + + +class ConcurrentProcedureCalls(TestCase): + def test_Construction(self) -> None: + procedureName = Symbol(SimpleName("proc"), PossibleReference.Procedure) + call = ConcurrentProcedureCall("lbl", procedureName) + + self.assertIs(procedureName, call.Procedure) + self.assertEqual("lbl", call.Label) + + +class ConcurrentBlockStatements(TestCase): + """``BlockStatementMixin``/``LabeledEntityMixin`` wiring is already covered in tests/unit/Base.py; + this covers the block-specific state (port items, declared items/statements via + ``ConcurrentDeclarationRegionMixin``/``ConcurrentStatementsMixin``) instead.""" + + def test_WithPortItems(self) -> None: + portItem = PortAssociationItem(SimpleName("p"), SimpleName("s")) + block = ConcurrentBlockStatement("blk", portItems=[portItem]) + + self.assertEqual(1, len(block.PortItems)) + self.assertIs(portItem, block.PortItems[0]) + self.assertIs(block, portItem.Parent) + +class GenerateBranches(TestCase): + def test_IfGenerateBranch(self) -> None: + condition = IntegerLiteral(1) + branch = IfGenerateBranch(condition, alternativeLabel="LBL") + + self.assertIs(condition, branch.Condition) + self.assertEqual("LBL", branch.AlternativeLabel) + self.assertEqual("lbl", branch.NormalizedAlternativeLabel) + + def test_IfGenerateBranch_NoLabel(self) -> None: + branch = IfGenerateBranch(IntegerLiteral(1)) + + self.assertIsNone(branch.AlternativeLabel) + self.assertIsNone(branch.NormalizedAlternativeLabel) + + def test_ElsifGenerateBranch(self) -> None: + condition = IntegerLiteral(1) + branch = ElsifGenerateBranch(condition) + + self.assertIs(condition, branch.Condition) + + def test_ElseGenerateBranch(self) -> None: + branch = ElseGenerateBranch() + + self.assertEqual(0, len(branch.Statements)) + + +class GenerateStatements(TestCase): + def test_AbstractMethodsRaise(self) -> None: + """``GenerateStatement`` itself is meant to always be used through a concrete subclass - + ``IterateInstantiations``/``IndexStatement`` are placeholders raising ``NotImplementedError`` + if a subclass doesn't override them (none of the three real subclasses omit them, so this only + matters if constructed directly, as done here).""" + statement = GenerateStatement("gen") + + with self.assertRaises(NotImplementedError): + next(statement.IterateInstantiations()) + + with self.assertRaises(NotImplementedError): + statement.IndexStatement() + + +class IfGenerateStatements(TestCase): + def test_IfOnly(self) -> None: + ifBranch = IfGenerateBranch(IntegerLiteral(1)) + statement = IfGenerateStatement("gen", ifBranch) + + self.assertIs(ifBranch, statement.IfBranch) + self.assertIs(statement, ifBranch.Parent) + self.assertEqual(0, len(statement.ElsifBranches)) + self.assertIsNone(statement.ElseBranch) + + def test_IterateInstantiations_And_IndexStatement(self) -> None: + componentSymbol = ComponentInstantiationSymbol(SimpleName("comp")) + instance = ComponentInstantiation("inst", componentSymbol) + ifBranch = IfGenerateBranch(IntegerLiteral(1), statements=[instance]) + statement = IfGenerateStatement("gen", ifBranch) + + statement.IndexStatement() + instantiations = list(statement.IterateInstantiations()) + + self.assertEqual(1, len(instantiations)) + self.assertIs(instance, instantiations[0]) + + +class GenerateChoices(TestCase): + def test_IndexedGenerateChoice(self) -> None: + expression = IntegerLiteral(0) + choice = IndexedGenerateChoice(expression) + + self.assertIs(expression, choice.Expression) + self.assertIs(choice, expression.Parent) + self.assertEqual("0", str(choice)) + + def test_RangedGenerateChoice(self) -> None: + rng = SimpleRange(IntegerLiteral(0), IntegerLiteral(3), Direction.To) + choice = RangedGenerateChoice(rng) + + self.assertIs(rng, choice.Range) + self.assertEqual("0 to 3", str(choice)) + + +class GenerateCases(TestCase): + def test_GenerateCase(self) -> None: + choice = IndexedGenerateChoice(IntegerLiteral(0)) + case = GenerateCase([choice]) + + self.assertEqual(1, len(case.Choices)) + self.assertEqual("when 0 =>", str(case)) + + def test_OthersGenerateCase(self) -> None: + case = OthersGenerateCase() + + self.assertEqual("when others =>", str(case)) + + +class CaseGenerateStatements(TestCase): + def test_Construction(self) -> None: + expression = IntegerLiteral(0) + case = GenerateCase([IndexedGenerateChoice(IntegerLiteral(0))]) + statement = CaseGenerateStatement("gen", expression, [case]) + + self.assertIs(expression, statement.SelectExpression) + self.assertIs(statement, expression.Parent) + self.assertEqual(1, len(statement.Cases)) + self.assertIs(case, statement.Cases[0]) + self.assertIs(statement, case.Parent) + + def test_IterateInstantiations_And_IndexStatement(self) -> None: + componentSymbol = ComponentInstantiationSymbol(SimpleName("comp")) + instance = ComponentInstantiation("inst", componentSymbol) + case = GenerateCase([IndexedGenerateChoice(IntegerLiteral(0))], statements=[instance]) + statement = CaseGenerateStatement("gen", IntegerLiteral(0), [case]) + + statement.IndexStatement() + instantiations = list(statement.IterateInstantiations()) + + self.assertEqual(1, len(instantiations)) + self.assertIs(instance, instantiations[0]) + + +class ForGenerateStatements(TestCase): + def test_Construction(self) -> None: + rng = SimpleRange(IntegerLiteral(0), IntegerLiteral(3), Direction.To) + statement = ForGenerateStatement("gen", "i", rng) + + self.assertEqual("i", statement.LoopIndex) + self.assertIs(rng, statement.Range) + self.assertIs(statement, rng.Parent) + + def test_IterateInstantiations_And_IndexStatement(self) -> None: + componentSymbol = ComponentInstantiationSymbol(SimpleName("comp")) + instance = ComponentInstantiation("inst", componentSymbol) + rng = SimpleRange(IntegerLiteral(0), IntegerLiteral(3), Direction.To) + statement = ForGenerateStatement("gen", "i", rng, statements=[instance]) + + statement.IndexStatement() + instantiations = list(statement.IterateInstantiations()) + + self.assertEqual(1, len(instantiations)) + self.assertIs(instance, instantiations[0]) + + +class ConcurrentSimpleSignalAssignments(TestCase): + def test_Construction(self) -> None: + """``s <= '1';``""" + target = SignalSymbol(SimpleName("s")) + waveformElement = WaveformElement(CharacterLiteral("'1'")) + assignment = ConcurrentSimpleSignalAssignment("lbl", target, [waveformElement]) + + self.assertIs(target, assignment.Target) + self.assertEqual(1, len(assignment.Waveform)) + self.assertIs(waveformElement, assignment.Waveform[0]) + + +class ConcurrentAssertStatements(TestCase): + def test_Full(self) -> None: + condition = IntegerLiteral(1) + message = CharacterLiteral("'a'") + severity = IntegerLiteral(2) + statement = ConcurrentAssertStatement(condition, message, severity, label="lbl") + + self.assertIs(condition, statement.Condition) + self.assertIs(message, statement.Message) + self.assertIs(severity, statement.Severity) + self.assertEqual("lbl", statement.Label) + + def test_NoSeverityNoLabel(self) -> None: + statement = ConcurrentAssertStatement(IntegerLiteral(1), CharacterLiteral("'a'")) + + self.assertIsNone(statement.Severity) + self.assertIsNone(statement.Label) + + +class ConcurrentStatementsMixinIndexing(TestCase): + """Tested via ``Architecture`` (the canonical ``ConcurrentDeclarationRegionMixin``/ + ``ConcurrentStatementsMixin`` host - see tests/unit/DesignUnit.py).""" + + def test_IndexStatements_BucketsByKind(self) -> None: + """Blocks and generates both land in the single, unified ``_hierarchy`` dict (in source/ + declaration order), not separate per-kind dicts.""" + entitySymbol = _entitySymbol() + instance = ComponentInstantiation("inst", ComponentInstantiationSymbol(SimpleName("comp"))) + block = ConcurrentBlockStatement("blk") + rng = SimpleRange(IntegerLiteral(0), IntegerLiteral(3), Direction.To) + generate = ForGenerateStatement("gen", "i", rng) + + architecture = Architecture("rtl", entitySymbol, statements=[instance, block, generate]) + architecture.IndexStatements() + + self.assertIs(instance, architecture._instantiations["inst"]) + self.assertIs(block, architecture._hierarchy["blk"]) + self.assertIs(generate, architecture._hierarchy["gen"]) + self.assertEqual(["blk", "gen"], list(architecture._hierarchy.keys())) + + def test_IterateInstantiations_RecursesIntoBlocksAndGenerates(self) -> None: + entitySymbol = _entitySymbol() + nestedInstance = ComponentInstantiation("nested_inst", ComponentInstantiationSymbol(SimpleName("comp"))) + block = ConcurrentBlockStatement("blk", statements=[nestedInstance]) + + architecture = Architecture("rtl", entitySymbol, statements=[block]) + architecture.IndexStatements() + instantiations = list(architecture.IterateInstantiations()) + + self.assertEqual(1, len(instantiations)) + self.assertIs(nestedInstance, instantiations[0]) diff --git a/tests/unit/Instantiation/Configuration.py b/tests/unit/Instantiation/Configuration.py new file mode 100644 index 000000000..87c1e2f4d --- /dev/null +++ b/tests/unit/Instantiation/Configuration.py @@ -0,0 +1,171 @@ +# ==================================================================================================================== # +# __ ___ _ ____ _ __ __ _ _ # +# _ __ _ \ \ / / | | | _ \| | | \/ | ___ __| | ___| | # +# | '_ \| | | \ \ / /| |_| | | | | | | |\/| |/ _ \ / _` |/ _ \ | # +# | |_) | |_| |\ V / | _ | |_| | |___| | | | (_) | (_| | __/ | # +# | .__/ \__, | \_/ |_| |_|____/|_____|_| |_|\___/ \__,_|\___|_| # +# |_| |___/ # +# ==================================================================================================================== # +# Authors: # +# Patrick Lehmann # +# # +# License: # +# ==================================================================================================================== # +# Copyright 2026-2026 Patrick Lehmann - Boetzingen, Germany # +# # +# Licensed under the Apache License, Version 2.0 (the "License"); # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at # +# # +# http://www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +# # +# SPDX-License-Identifier: Apache-2.0 # +# ==================================================================================================================== # +# +"""Tests for pyVHDLModel.Configuration.""" +from unittest import TestCase + +from pyVHDLModel.Name import SimpleName, SelectedName +from pyVHDLModel.Symbol import EntitySymbol, ArchitectureSymbol, ConfigurationSymbol +from pyVHDLModel.Symbol import ComponentInstantiationSymbol, Symbol, PossibleReference +from pyVHDLModel.Association import GenericAssociationItem, PortAssociationItem +from pyVHDLModel.Expression import IntegerLiteral +from pyVHDLModel.Configuration import ( + EntityAspect, EntityAspectEntity, EntityAspectConfiguration, EntityAspectOpen, + BindingIndication, AllInstantiationList, OthersInstantiationList, ComponentConfiguration, + BlockConfiguration, +) + + +if __name__ == "__main__": # pragma: no cover + print("ERROR: you called a testcase declaration file as an executable module.") + print("Use: 'python -m unitest '") + exit(1) + + +class EntityAspects(TestCase): + def test_Entity(self) -> None: + """``use entity work.sub(behav);``""" + entity = EntitySymbol(SelectedName("sub", SimpleName("work"))) + architecture = ArchitectureSymbol(SimpleName("behav")) + aspect = EntityAspectEntity(entity, architecture) + + self.assertIsInstance(aspect, EntityAspect) + self.assertIs(entity, aspect.Entity) + self.assertIs(architecture, aspect.Architecture) + + def test_EntityWithoutArchitecture(self) -> None: + """``use entity work.sub;``""" + entity = EntitySymbol(SelectedName("sub", SimpleName("work"))) + aspect = EntityAspectEntity(entity) + + self.assertIs(entity, aspect.Entity) + self.assertIsNone(aspect.Architecture) + + def test_Configuration(self) -> None: + """``use configuration work.cfg;``""" + configuration = ConfigurationSymbol(SelectedName("cfg", SimpleName("work"))) + aspect = EntityAspectConfiguration(configuration) + + self.assertIsInstance(aspect, EntityAspect) + self.assertIs(configuration, aspect.Configuration) + + def test_Open(self) -> None: + """``use open;``""" + aspect = EntityAspectOpen() + + self.assertIsInstance(aspect, EntityAspect) + + +class BindingIndications(TestCase): + def test_Full(self) -> None: + """``use entity work.sub(behav) generic map (G => 1) port map (p => s);``""" + entity = EntitySymbol(SelectedName("sub", SimpleName("work"))) + aspect = EntityAspectEntity(entity, ArchitectureSymbol(SimpleName("behav"))) + generics = [GenericAssociationItem(SimpleName("G"), IntegerLiteral(1))] + ports = [PortAssociationItem(SimpleName("p"), SimpleName("s"))] + + binding = BindingIndication(aspect, generics, ports) + + self.assertIs(aspect, binding.EntityAspect) + self.assertEqual(1, len(binding.GenericAssociationItems)) + self.assertEqual(1, len(binding.PortAssociationItems)) + + def test_Empty(self) -> None: + binding = BindingIndication() + + self.assertIsNone(binding.EntityAspect) + self.assertEqual(0, len(binding.GenericAssociationItems)) + self.assertEqual(0, len(binding.PortAssociationItems)) + + +class ComponentConfigurations(TestCase): + """Also covers configuration specifications, which are structurally identical.""" + + def test_LabeledInstantiationList(self) -> None: + """``for U1 : comp use entity work.sub(behav);``""" + componentName = ComponentInstantiationSymbol(SimpleName("comp")) + binding = BindingIndication(EntityAspectEntity(EntitySymbol(SimpleName("sub")))) + + config = ComponentConfiguration([SimpleName("U1")], componentName, binding) + + self.assertEqual(1, len(config.InstantiationList)) + self.assertIs(componentName, config.ComponentName) + self.assertIs(binding, config.BindingIndication) + + def test_All(self) -> None: + """``for all : comp use entity work.sub(behav);``""" + allMarker = AllInstantiationList() + config = ComponentConfiguration(allMarker, ComponentInstantiationSymbol(SimpleName("comp"))) + + self.assertIs(allMarker, config.InstantiationList) + + def test_Others(self) -> None: + """``for others : comp use entity work.sub(behav);``""" + othersMarker = OthersInstantiationList() + config = ComponentConfiguration(othersMarker, ComponentInstantiationSymbol(SimpleName("comp"))) + + self.assertIs(othersMarker, config.InstantiationList) + + def test_WithoutBindingIndication(self) -> None: + config = ComponentConfiguration([SimpleName("U1")], ComponentInstantiationSymbol(SimpleName("comp"))) + + self.assertIsNone(config.BindingIndication) + + +class BlockConfigurations(TestCase): + def test_Empty(self) -> None: + """``for rtl end for;``""" + blockSpec = Symbol(SimpleName("rtl"), PossibleReference.Architecture | PossibleReference.Label) + block = BlockConfiguration(blockSpec) + + self.assertIs(blockSpec, block.BlockSpecification) + self.assertEqual(0, len(block.Items)) + + def test_WithComponentConfiguration(self) -> None: + """``for rtl for U1 : comp use entity work.sub(behav); end for; end for;``""" + blockSpec = Symbol(SimpleName("rtl"), PossibleReference.Architecture | PossibleReference.Label) + componentConfig = ComponentConfiguration( + [SimpleName("U1")], ComponentInstantiationSymbol(SimpleName("comp")) + ) + block = BlockConfiguration(blockSpec, [componentConfig]) + + self.assertEqual(1, len(block.Items)) + self.assertIs(componentConfig, block.Items[0]) + + def test_NestedBlockConfiguration(self) -> None: + """A block configuration may itself contain nested block configurations (e.g. for a generate + statement's alternative).""" + outerSpec = Symbol(SimpleName("rtl"), PossibleReference.Architecture | PossibleReference.Label) + innerSpec = Symbol(SimpleName("gen_label"), PossibleReference.Architecture | PossibleReference.Label) + innerBlock = BlockConfiguration(innerSpec) + outerBlock = BlockConfiguration(outerSpec, [innerBlock]) + + self.assertEqual(1, len(outerBlock.Items)) + self.assertIs(innerBlock, outerBlock.Items[0]) diff --git a/tests/unit/Instantiation/Declaration.py b/tests/unit/Instantiation/Declaration.py new file mode 100644 index 000000000..17b91754e --- /dev/null +++ b/tests/unit/Instantiation/Declaration.py @@ -0,0 +1,72 @@ +# ==================================================================================================================== # +# __ ___ _ ____ _ __ __ _ _ # +# _ __ _ \ \ / / | | | _ \| | | \/ | ___ __| | ___| | # +# | '_ \| | | \ \ / /| |_| | | | | | | |\/| |/ _ \ / _` |/ _ \ | # +# | |_) | |_| |\ V / | _ | |_| | |___| | | | (_) | (_| | __/ | # +# | .__/ \__, | \_/ |_| |_|____/|_____|_| |_|\___/ \__,_|\___|_| # +# |_| |___/ # +# ==================================================================================================================== # +# Authors: # +# Patrick Lehmann # +# # +# License: # +# ==================================================================================================================== # +# Copyright 2026-2026 Patrick Lehmann - Boetzingen, Germany # +# # +# Licensed under the Apache License, Version 2.0 (the "License"); # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at # +# # +# http://www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +# # +# SPDX-License-Identifier: Apache-2.0 # +# ==================================================================================================================== # +# +"""Tests for pyVHDLModel.Declaration.""" +from unittest import TestCase + +from pyVHDLModel.Name import SimpleName +from pyVHDLModel.Symbol import ConstrainedArraySubtypeSymbol, Symbol, PossibleReference +from pyVHDLModel.Declaration import Alias + + +if __name__ == "__main__": # pragma: no cover + print("ERROR: you called a testcase declaration file as an executable module.") + print("Use: 'python -m unitest '") + exit(1) + + +class Aliases(TestCase): + """ + Regression tests: Alias previously had no field at all for what's being aliased - only its own + identifier and documentation. ``alias b is s;`` lost the fact that ``b`` aliases ``s`` entirely. + + Name is a Symbol (like every other cross-reference in the model), not a bare Name - see the + class docstring for why there is no single fixed PossibleReference value for it. + """ + + def test_WithoutSubtype(self) -> None: + """``alias b is s;``""" + name = Symbol(SimpleName("s"), PossibleReference.PackageMember | PossibleReference.EnumLiteral) + alias = Alias("b", name) + + self.assertEqual("b", alias.Identifier) + self.assertIs(name, alias.Name) + self.assertIsNone(alias.Subtype) + + def test_WithSubtype(self) -> None: + """``alias a : bit_vector(3 downto 0) is s(3 downto 0);`` - with an explicit subtype, the LRM + restricts this to referencing an object.""" + name = Symbol(SimpleName("s"), PossibleReference.Object) + subtype = ConstrainedArraySubtypeSymbol(SimpleName("bit_vector"), []) + alias = Alias("a", name, subtype) + + self.assertEqual("a", alias.Identifier) + self.assertIs(name, alias.Name) + self.assertIs(subtype, alias.Subtype) diff --git a/tests/unit/Instantiation/DesignUnit.py b/tests/unit/Instantiation/DesignUnit.py new file mode 100644 index 000000000..472d2ed45 --- /dev/null +++ b/tests/unit/Instantiation/DesignUnit.py @@ -0,0 +1,366 @@ +# ==================================================================================================================== # +# __ ___ _ ____ _ __ __ _ _ # +# _ __ _ \ \ / / | | | _ \| | | \/ | ___ __| | ___| | # +# | '_ \| | | \ \ / /| |_| | | | | | | |\/| |/ _ \ / _` |/ _ \ | # +# | |_) | |_| |\ V / | _ | |_| | |___| | | | (_) | (_| | __/ | # +# | .__/ \__, | \_/ |_| |_|____/|_____|_| |_|\___/ \__,_|\___|_| # +# |_| |___/ # +# ==================================================================================================================== # +# Authors: # +# Patrick Lehmann # +# # +# License: # +# ==================================================================================================================== # +# Copyright 2026-2026 Patrick Lehmann - Boetzingen, Germany # +# # +# Licensed under the Apache License, Version 2.0 (the "License"); # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at # +# # +# http://www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +# # +# SPDX-License-Identifier: Apache-2.0 # +# ==================================================================================================================== # +# +""" +Tests for pyVHDLModel.DesignUnit and pyVHDLModel.Regions. + +``ConcurrentDeclarationRegionMixin`` (Regions.py) is shared by ``Package``, ``PackageBody``, +``Entity``, ``Architecture`` and (via Common.py/Concurrent.py, tested in their own slice) +``ConcurrentBlockStatement`` and the generate-statement branches. Its actual indexing behaviour is +tested once here via ``Architecture`` as the canonical host; every other consumer below gets only a +one-line smoke test confirming the mixin is wired up. +""" +from unittest import TestCase + +from pyVHDLModel import VHDLModelException +from pyVHDLModel.Base import ModelEntity +from pyVHDLModel.Name import SimpleName, SelectedName +from pyVHDLModel.Symbol import EntitySymbol, PackageSymbol, LibraryReferenceSymbol, SimpleSubtypeSymbol +from pyVHDLModel.Object import Constant, Signal, Variable, SharedVariable, File, DeferredConstant +from pyVHDLModel.Type import FullType, Subtype +from pyVHDLModel.Subprogram import Function, Procedure +from pyVHDLModel.DesignUnit import ( + Reference, LibraryClause, UseClause, ContextReference, + DesignUnit, PrimaryUnit, SecondaryUnit, Context, + Package, PackageBody, Entity, Architecture, Component, Configuration, +) + + +if __name__ == "__main__": # pragma: no cover + print("ERROR: you called a testcase declaration file as an executable module.") + print("Use: 'python -m unitest '") + exit(1) + + +def _entitySymbol(name: str = "ent") -> EntitySymbol: + return EntitySymbol(SimpleName(name)) + + +class References(TestCase): + def test_LibraryClause(self) -> None: + symbol = LibraryReferenceSymbol(SimpleName("ieee")) + clause = LibraryClause([symbol]) + + self.assertEqual(1, len(clause.Symbols)) + self.assertIs(symbol, clause.Symbols[0]) + + def test_UseClause(self) -> None: + """``UseClause``/``ContextReference`` add no behaviour of their own over the generic + ``Reference`` base - only ``LibraryClause`` overrides ``Symbols``' type hint.""" + clause = UseClause([]) + + self.assertIsInstance(clause, Reference) + + def test_ContextReference(self) -> None: + reference = ContextReference([]) + + self.assertIsInstance(reference, Reference) + + +class DesignUnits(TestCase): + """``DesignUnit`` itself has no public non-abstract-in-spirit subclass without extra state, so + it's tested directly via its own class rather than through e.g. ``Context``.""" + + def test_NoContextItems(self) -> None: + unit = DesignUnit("u") + + self.assertEqual("u", unit.Identifier) + self.assertEqual(0, len(unit.ContextItems)) + self.assertEqual(0, len(unit.LibraryReferences)) + self.assertEqual(0, len(unit.PackageReferences)) + self.assertEqual(0, len(unit.ContextReferences)) + self.assertIsNone(unit.Document) + self.assertIsNone(unit.DependencyVertex) + self.assertIsNone(unit.HierarchyVertex) + + def test_ContextItemsAreSeparatedByKind(self) -> None: + library = LibraryClause([LibraryReferenceSymbol(SimpleName("ieee"))]) + use = UseClause([]) + context = ContextReference([]) + unit = DesignUnit("u", [library, use, context]) + + self.assertEqual(3, len(unit.ContextItems)) + self.assertEqual([library], unit.LibraryReferences) + self.assertEqual([use], unit.PackageReferences) + self.assertEqual([context], unit.ContextReferences) + + def test_DocumentSetter(self) -> None: + unit = DesignUnit("u") + document = object() + unit.Document = document + + self.assertIs(document, unit.Document) + + def test_LibrarySetter(self) -> None: + """``Library`` is just a renamed view onto the same ``_parent`` field every ``ModelEntity`` + has.""" + unit = DesignUnit("u") + library = ModelEntity() + unit.Library = library + + self.assertIs(library, unit.Library) + self.assertIs(library, unit.Parent) + + +class MarkerSubclasses(TestCase): + def test_PrimaryUnit(self) -> None: + unit = PrimaryUnit("u") + + self.assertEqual("u", unit.Identifier) + + def test_SecondaryUnit(self) -> None: + unit = SecondaryUnit("u") + + self.assertEqual("u", unit.Identifier) + + +class Contexts(TestCase): + def test_NoReferences(self) -> None: + context = Context("ctx") + + self.assertEqual(0, len(context.LibraryReferences)) + self.assertEqual(0, len(context.PackageReferences)) + self.assertEqual(0, len(context.ContextReferences)) + + def test_ReferencesAreSeparatedByKind(self) -> None: + library = LibraryClause([LibraryReferenceSymbol(SimpleName("ieee"))]) + use = UseClause([]) + contextReference = ContextReference([]) + context = Context("ctx", [library, use, contextReference]) + + self.assertEqual([library], context.LibraryReferences) + self.assertEqual([use], context.PackageReferences) + self.assertEqual([contextReference], context.ContextReferences) + self.assertIs(context, library.Parent) + + def test_UnknownReferenceKind_Raises(self) -> None: + """Regression test: ``VHDLModelException()`` previously carried no message at all (``# FIXME: + needs exception message`` in the source) - now includes the offending reference. Uses a bare + ``ModelEntity`` (not ``object()``) since the ``.Parent = self`` assignment a few lines above + the kind-check runs first and needs a real settable ``Parent``.""" + with self.assertRaises(VHDLModelException) as context: + Context("ctx", [ModelEntity()]) + + self.assertIn("neither a library clause, use clause, nor context reference", str(context.exception)) + + +class Packages(TestCase): + def test_Minimal(self) -> None: + package = Package("pkg") + + self.assertEqual(0, len(package.DeclaredItems)) + self.assertEqual(0, len(package.DeferredConstants)) + self.assertEqual(0, len(package.Components)) + self.assertIsNone(package.PackageBody) + + def test_IndexDeferredConstant(self) -> None: + """``Package`` overrides ``_IndexOtherDeclaredItem`` to additionally index deferred constants + and components - not covered by the generic ``IndexDeclaredItems`` in Regions.py, so tested + here specifically.""" + deferredConstant = DeferredConstant(["BITS"], SimpleSubtypeSymbol(SimpleName("positive"))) + package = Package("pkg", declaredItems=[deferredConstant]) + package.IndexDeclaredItems() + + self.assertIs(deferredConstant, package.DeferredConstants["bits"]) + + def test_IndexComponent(self) -> None: + component = Component("comp") + package = Package("pkg", declaredItems=[component]) + package.IndexDeclaredItems() + + self.assertIs(component, package.Components["comp"]) + + +class PackageBodies(TestCase): + def test_Minimal(self) -> None: + packageSymbol = PackageSymbol(SimpleName("pkg")) + body = PackageBody(packageSymbol) + + self.assertIs(packageSymbol, body.Package) + self.assertIs(body, packageSymbol.Parent) + self.assertEqual(0, len(body.DeclaredItems)) + + def test_LinkDeclaredItemsToPackage_IsANoOpStub(self) -> None: + body = PackageBody(PackageSymbol(SimpleName("pkg"))) + + self.assertIsNone(body.LinkDeclaredItemsToPackage()) + + +class Entities(TestCase): + def test_Minimal(self) -> None: + entity = Entity("ent") + + self.assertEqual(0, len(entity.DeclaredItems)) + self.assertEqual(0, len(entity.Statements)) + self.assertEqual(0, len(entity.Architectures)) + + +class Architectures(TestCase): + def test_Minimal(self) -> None: + entitySymbol = _entitySymbol() + architecture = Architecture("rtl", entitySymbol) + + self.assertIs(entitySymbol, architecture.Entity) + self.assertIs(architecture, entitySymbol.Parent) + self.assertEqual(0, len(architecture.DeclaredItems)) + self.assertEqual(0, len(architecture.Statements)) + + def test_RegionStartsEmpty(self) -> None: + architecture = Architecture("rtl", _entitySymbol()) + + self.assertEqual(0, len(architecture.Types)) + self.assertEqual(0, len(architecture.Subtypes)) + self.assertEqual(0, len(architecture.Constants)) + self.assertEqual(0, len(architecture.Signals)) + self.assertEqual(0, len(architecture.SharedVariables)) + self.assertEqual(0, len(architecture.Files)) + self.assertEqual(0, len(architecture.Functions)) + self.assertEqual(0, len(architecture.Procedures)) + self.assertEqual(0, len(architecture.Components)) + + def test_IndexDeclaredItems(self) -> None: + fullType = FullType("my_type") + subtype = Subtype("my_subtype", SimpleSubtypeSymbol(SimpleName("bit"))) + constant = Constant(["C"], SimpleSubtypeSymbol(SimpleName("natural"))) + signal = Signal(["s"], SimpleSubtypeSymbol(SimpleName("bit"))) + sharedVariable = SharedVariable(["sv"], SimpleSubtypeSymbol(SimpleName("natural"))) + file = File(["f"], SimpleSubtypeSymbol(SimpleName("text"))) + function = Function("f_func", SimpleSubtypeSymbol(SimpleName("integer"))) + procedure = Procedure("p_proc") + + architecture = Architecture( + "rtl", _entitySymbol(), + declaredItems=[fullType, subtype, constant, signal, sharedVariable, file, function, procedure], + ) + architecture.IndexDeclaredItems() + + self.assertIs(fullType, architecture.Types["my_type"]) + self.assertIs(subtype, architecture.Subtypes["my_subtype"]) + self.assertIs(constant, architecture.Constants["c"]) + self.assertIs(signal, architecture.Signals["s"]) + self.assertIs(sharedVariable, architecture.SharedVariables["sv"]) + self.assertIs(file, architecture.Files["f"]) + self.assertEqual(1, len(architecture.Functions["f_func"])) + self.assertIs(function, architecture.Functions["f_func"][0]) + self.assertEqual(1, len(architecture.Procedures["p_proc"])) + self.assertIs(procedure, architecture.Procedures["p_proc"][0]) + + def test_IndexDeclaredItems_VariablesAreNotYetIndexed(self) -> None: + """Still-open gap (documented in Regions.py's own TODO): variables declared directly in a + concurrent declaration region raise a warning instead of being indexed anywhere - locked in as + current behaviour, not a regression.""" + variable = Variable(["v"], SimpleSubtypeSymbol(SimpleName("natural"))) + architecture = Architecture("rtl", _entitySymbol(), declaredItems=[variable]) + + architecture.IndexDeclaredItems() # must not raise, only warn + + def test_IndexDeclaredItems_Overloads(self) -> None: + """Regression test: two overloads sharing a name used to silently collide into one entry (a + flat ``Dict[str, Function]``) - the second always overwrote the first, with no error or + warning. Fixed by collecting overloads into a list per name instead. + + FIXME: this only *avoids the collision* - it doesn't actually resolve overloads by signature + (matching call-site argument types against each candidate's parameter/return types). A real + textual-signature-based attempt was tried and rejected as unreliable (aliased/case-differing + subtype names would be misjudged as distinct, and symbol resolution happening after indexing + could change the comparison basis). For now, ``Functions``/``Procedures`` just return every + overload found under a given name, unresolved.""" + overload1 = Function("f", SimpleSubtypeSymbol(SimpleName("integer"))) + overload2 = Function("f", SimpleSubtypeSymbol(SimpleName("boolean"))) + architecture = Architecture("rtl", _entitySymbol(), declaredItems=[overload1, overload2]) + architecture.IndexDeclaredItems() + + self.assertEqual(1, len(architecture.Functions)) + self.assertEqual([overload1, overload2], architecture.Functions["f"]) + + def test_IndexDeclaredItems_ProcedureOverloads(self) -> None: + """Same as above, but for procedures.""" + overload1 = Procedure("p") + overload2 = Procedure("p") + architecture = Architecture("rtl", _entitySymbol(), declaredItems=[overload1, overload2]) + architecture.IndexDeclaredItems() + + self.assertEqual(1, len(architecture.Procedures)) + self.assertEqual([overload1, overload2], architecture.Procedures["p"]) + + +class Components(TestCase): + def test_Minimal(self) -> None: + component = Component("comp") + + self.assertEqual("comp", component.Identifier) + self.assertIsNone(component.IsBlackbox) + self.assertEqual(0, len(component.GenericItems)) + self.assertEqual(0, len(component.PortItems)) + self.assertIsNone(component.Entity) + + def test_WithGenericAndPortItems(self) -> None: + """Uses bare ``ModelEntity`` stand-ins - ``Component``'s own generic-/port-item loops only + append and set ``.Parent``, they don't care about the item's real type (real + ``GenericInterfaceItemMixin``/``PortInterfaceItemMixin`` classes are covered in their own + slice).""" + genericItem = ModelEntity() + portItem = ModelEntity() + component = Component("comp", genericItems=[genericItem], portItems=[portItem]) + + self.assertEqual(1, len(component.GenericItems)) + self.assertIs(genericItem, component.GenericItems[0]) + self.assertIs(component, genericItem.Parent) + self.assertEqual(1, len(component.PortItems)) + self.assertIs(portItem, component.PortItems[0]) + self.assertIs(component, portItem.Parent) + + def test_EntitySetter_AlsoClearsBlackboxFlag(self) -> None: + component = Component("comp") + entity = Entity("ent") + component.Entity = entity + + self.assertIs(entity, component.Entity) + self.assertFalse(component.IsBlackbox) + + +class Configurations(TestCase): + """The *design-unit* ``configuration cfg of ent is ... end configuration;`` - not to be confused + with ``pyVHDLModel.Configuration.ComponentConfiguration``/``BlockConfiguration``, which are tested + in tests/unit/Configuration.py.""" + + def test_Minimal(self) -> None: + from pyVHDLModel.Configuration import BlockConfiguration + from pyVHDLModel.Symbol import Symbol, PossibleReference + + entitySymbol = _entitySymbol() + blockSpec = Symbol(SimpleName("rtl"), PossibleReference.Architecture | PossibleReference.Label) + blockConfiguration = BlockConfiguration(blockSpec) + configuration = Configuration("cfg", entitySymbol, blockConfiguration) + + self.assertIs(entitySymbol, configuration.Entity) + self.assertIs(configuration, entitySymbol.Parent) + self.assertIs(blockConfiguration, configuration.BlockConfiguration) + self.assertIs(configuration, blockConfiguration.Parent) diff --git a/tests/unit/Instantiation/Expression.py b/tests/unit/Instantiation/Expression.py new file mode 100644 index 000000000..61e0f1b76 --- /dev/null +++ b/tests/unit/Instantiation/Expression.py @@ -0,0 +1,458 @@ +# ==================================================================================================================== # +# __ ___ _ ____ _ __ __ _ _ # +# _ __ _ \ \ / / | | | _ \| | | \/ | ___ __| | ___| | # +# | '_ \| | | \ \ / /| |_| | | | | | | |\/| |/ _ \ / _` |/ _ \ | # +# | |_) | |_| |\ V / | _ | |_| | |___| | | | (_) | (_| | __/ | # +# | .__/ \__, | \_/ |_| |_|____/|_____|_| |_|\___/ \__,_|\___|_| # +# |_| |___/ # +# ==================================================================================================================== # +# Authors: # +# Patrick Lehmann # +# # +# License: # +# ==================================================================================================================== # +# Copyright 2026-2026 Patrick Lehmann - Boetzingen, Germany # +# # +# Licensed under the Apache License, Version 2.0 (the "License"); # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at # +# # +# http://www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +# # +# SPDX-License-Identifier: Apache-2.0 # +# ==================================================================================================================== # +# +""" +Tests for pyVHDLModel.Expression. + +Most of this module is dozens of leaf subclasses that only fix a ``_FORMAT`` class variable +(``AdditionExpression``, ``EqualExpression``, ``RotateLeftExpression``, ...). Their shared +construction/parent-wiring behaviour is tested once via the immediate base class +(``UnaryExpression``/``BinaryExpression``), then every leaf subclass's ``_FORMAT`` is checked +table-driven in one ``str()``-formatting test rather than one hand-written test per class. +""" +from unittest import TestCase + +from pyVHDLModel.Base import Direction, SimpleRange +from pyVHDLModel.Name import SimpleName +from pyVHDLModel.Symbol import SimpleSubtypeSymbol +from pyVHDLModel.Expression import ( + BaseExpression, Literal, + NullLiteral, EnumerationLiteral, IntegerLiteral, FloatingPointLiteral, + PhysicalIntegerLiteral, PhysicalFloatingLiteral, CharacterLiteral, StringLiteral, + BinaryBitStringLiteral, OctalBitStringLiteral, DecimalBitStringLiteral, HexadecimalBitStringLiteral, + UnaryExpression, + NegationExpression, IdentityExpression, InverseExpression, + UnaryAndExpression, UnaryNandExpression, UnaryOrExpression, UnaryNorExpression, + UnaryXorExpression, UnaryXnorExpression, AbsoluteExpression, TypeConversion, SubExpression, + BinaryExpression, + AscendingRangeExpression, DescendingRangeExpression, + AdditionExpression, SubtractionExpression, ConcatenationExpression, + MultiplyExpression, DivisionExpression, RemainderExpression, ModuloExpression, ExponentiationExpression, + AndExpression, NandExpression, OrExpression, NorExpression, XorExpression, XnorExpression, + EqualExpression, UnequalExpression, GreaterThanExpression, GreaterEqualExpression, + LessThanExpression, LessEqualExpression, + MatchingEqualExpression, MatchingUnequalExpression, MatchingGreaterThanExpression, + MatchingGreaterEqualExpression, MatchingLessThanExpression, MatchingLessEqualExpression, + ShiftRightLogicExpression, ShiftLeftLogicExpression, + ShiftRightArithmeticExpression, ShiftLeftArithmeticExpression, + RotateRightExpression, RotateLeftExpression, + QualifiedExpression, TernaryExpression, WhenElseExpression, + FunctionCall, SubtypeAllocation, QualifiedExpressionAllocation, + AggregateElement, SimpleAggregateElement, IndexedAggregateElement, RangedAggregateElement, + NamedAggregateElement, OthersAggregateElement, Aggregate, +) + + +if __name__ == "__main__": # pragma: no cover + print("ERROR: you called a testcase declaration file as an executable module.") + print("Use: 'python -m unitest '") + exit(1) + + +class Literals(TestCase): + """``BaseExpression`` itself (``class BaseExpression(ModelEntity): pass``) is never instantiated + directly here, matching the same reasoning as ``Allocation``/``Type`` (see tests/unit/Type.py): + it has no ``__init__`` override of its own (so there's no independent forwarding-bug risk to + check, unlike e.g. the type classes), no VHDL construct is ever "just a BaseExpression", and its + ``Parent``-wiring behaviour is already covered via plain ``ModelEntity`` in tests/unit/Base.py - + every concrete literal/operator/etc. class tested below already exercises it transitively.""" + + + def test_NullLiteral(self) -> None: + self.assertEqual("null", str(NullLiteral())) + + def test_EnumerationLiteral(self) -> None: + literal = EnumerationLiteral("'1'") + + self.assertEqual("'1'", literal.Value) + self.assertEqual("'1'", str(literal)) + + def test_IntegerLiteral(self) -> None: + literal = IntegerLiteral(42) + + self.assertEqual(42, literal.Value) + self.assertEqual("42", str(literal)) + + def test_FloatingPointLiteral(self) -> None: + literal = FloatingPointLiteral(4.2) + + self.assertEqual(4.2, literal.Value) + self.assertEqual("4.2", str(literal)) + + def test_CharacterLiteral(self) -> None: + literal = CharacterLiteral("'a'") + + self.assertEqual("'a'", literal.Value) + self.assertEqual("'a'", str(literal)) + + def test_StringLiteral(self) -> None: + literal = StringLiteral("hello") + + self.assertEqual("hello", literal.Value) + self.assertEqual("\"hello\"", str(literal)) + + +class PhysicalLiterals(TestCase): + """``PhysicalLiteral`` itself is never instantiated directly - its own ``_value`` is only declared + by ``PhysicalIntegerLiteral``/``PhysicalFloatingLiteral``, so it's exercised only through those two + concrete subclasses.""" + + def test_PhysicalIntegerLiteral(self) -> None: + literal = PhysicalIntegerLiteral(5, "ns") + + self.assertEqual(5, literal.Value) + self.assertEqual("ns", literal.UnitName) + self.assertEqual("5 ns", str(literal)) + + def test_PhysicalFloatingLiteral(self) -> None: + literal = PhysicalFloatingLiteral(2.5, "ns") + + self.assertEqual(2.5, literal.Value) + self.assertEqual("ns", literal.UnitName) + self.assertEqual("2.5 ns", str(literal)) + + +class BitStringLiterals(TestCase): + """``BitStringLiteral`` itself is abstract in practice (``_base`` is only set by its four concrete + subclasses as a ``ClassVar``); ``Length``/``Signed``/``BinaryValue``/``Bits`` are all still-open + gaps beyond ``Value`` itself for ``BinaryValue``/``Bits`` (see the class's own ``.. todo``-less but + clearly unfinished state - both always stay ``None``, there's no code anywhere that computes + them yet).""" + + def test_Binary(self) -> None: + literal = BinaryBitStringLiteral("101") + + self.assertEqual("101", literal.Value) + self.assertIsNone(literal.BinaryValue) + self.assertIsNone(literal.Bits) + self.assertIsNone(literal.Length) + self.assertIsNone(literal.IsSigned) + self.assertEqual("b\"101\"", str(literal)) + + def test_Octal(self) -> None: + self.assertEqual("o\"17\"", str(OctalBitStringLiteral("17"))) + + def test_Decimal(self) -> None: + self.assertEqual("d\"9\"", str(DecimalBitStringLiteral("9"))) + + def test_Hexadecimal(self) -> None: + self.assertEqual("x\"FF\"", str(HexadecimalBitStringLiteral("FF"))) + + def test_WithLength(self) -> None: + """``8x"F"`` - explicit-length metadata (C.2 in the gap analysis).""" + literal = HexadecimalBitStringLiteral("F", length=8) + + self.assertEqual(8, literal.Length) + self.assertEqual("8x\"F\"", str(literal)) + + def test_Signed(self) -> None: + """``sx"F"`` - signed metadata (C.2 in the gap analysis).""" + literal = HexadecimalBitStringLiteral("F", isSigned=True) + + self.assertTrue(literal.IsSigned) + self.assertEqual("sx\"F\"", str(literal)) + + def test_Unsigned(self) -> None: + """``ux"F"`` - unsigned metadata (C.2 in the gap analysis).""" + literal = HexadecimalBitStringLiteral("F", isSigned=False) + + self.assertFalse(literal.IsSigned) + self.assertEqual("ux\"F\"", str(literal)) + + +class UnaryExpressions(TestCase): + """Regression test: ``operand.Parent = self`` was previously commented out + (``# FIXME: operand is provided as None``) - confirmed stale in the gap analysis (every real + construction site always provides a real operand) and now re-enabled. Tested once here via + ``NegationExpression``; every other unary subclass shares the identical, unoverridden + constructor.""" + + def test_Construction(self) -> None: + operand = IntegerLiteral(1) + expression = NegationExpression(operand) + + self.assertIs(operand, expression.Operand) + self.assertIs(expression, operand.Parent) + + +_UNARY_FORMATS = ( + (NegationExpression, "-1"), + (IdentityExpression, "+1"), + (InverseExpression, "not 1"), + (UnaryAndExpression, "and 1"), + (UnaryNandExpression, "nand 1"), + (UnaryOrExpression, "or 1"), + (UnaryNorExpression, "nor 1"), + (UnaryXorExpression, "xor 1"), + (UnaryXnorExpression, "xnor 1"), + (AbsoluteExpression, "abs 1"), + (SubExpression, "(1)"), +) + + +class UnaryExpressionFormats(TestCase): + def test_AllVariants(self) -> None: + for expressionClass, expected in _UNARY_FORMATS: + with self.subTest(expression=expressionClass.__name__): + self.assertEqual(expected, str(expressionClass(IntegerLiteral(1)))) + + def test_SubExpression_IsAlsoAParenthesisExpression(self) -> None: + """``SubExpression`` mixes in ``ParenthesisExpression``, whose own ``Operand`` hardcodes + ``return None`` - but MRO puts ``UnaryExpression`` first, so the real operand wins and + ``ParenthesisExpression.Operand`` is never actually reached through this class.""" + operand = IntegerLiteral(1) + expression = SubExpression(operand) + + self.assertIs(operand, expression.Operand) + + +class TypeConversions(TestCase): + """Regression test: ``TypeConversion`` previously had no field for the target type at all (just + ``pass``, inheriting ``UnaryExpression`` unchanged) - unlike every other ``UnaryExpression`` + subclass, its "operator" is the target type name itself, not a fixed ``_FORMAT`` string, so + ``str()`` always raised ``AttributeError``. Fixed by adding ``_targetSubtype`` (mirroring + ``QualifiedExpression._subtype``) and a dedicated ``__str__``.""" + + def test_Construction(self) -> None: + """``natural(x)``""" + targetSubtype = SimpleSubtypeSymbol(SimpleName("natural")) + operand = IntegerLiteral(1) + expression = TypeConversion(targetSubtype, operand) + + self.assertIs(targetSubtype, expression.TargetSubtype) + self.assertIs(expression, targetSubtype.Parent) + self.assertIs(operand, expression.Operand) + self.assertIs(expression, operand.Parent) + self.assertEqual("natural?(1)", str(expression)) + + +class BinaryExpressions(TestCase): + """Both operands' ``Parent`` wiring is already active (no FIXME here, unlike + ``UnaryExpression``) - tested once via ``AdditionExpression``; every other binary subclass shares + the identical, unoverridden constructor.""" + + def test_Construction(self) -> None: + left = IntegerLiteral(1) + right = IntegerLiteral(2) + expression = AdditionExpression(left, right) + + self.assertIs(left, expression.LeftOperand) + self.assertIs(right, expression.RightOperand) + self.assertIs(expression, left.Parent) + self.assertIs(expression, right.Parent) + + +_BINARY_FORMATS = ( + (AdditionExpression, "1 + 2"), + (SubtractionExpression, "1 - 2"), + (ConcatenationExpression, "1 & 2"), + (MultiplyExpression, "1 * 2"), + (DivisionExpression, "1 / 2"), + (RemainderExpression, "1 rem 2"), + (ModuloExpression, "1 mod 2"), + (ExponentiationExpression, "1**2"), + (AndExpression, "1 and 2"), + (NandExpression, "1 nand 2"), + (OrExpression, "1 or 2"), + (NorExpression, "1 nor 2"), + (XorExpression, "1 xor 2"), + (XnorExpression, "1 xnor 2"), + (EqualExpression, "1 = 2"), + (UnequalExpression, "1 /= 2"), + (GreaterThanExpression, "1 > 2"), + (GreaterEqualExpression, "1 >= 2"), + (LessThanExpression, "1 < 2"), + (LessEqualExpression, "1 <= 2"), + (MatchingEqualExpression, "1 ?= 2"), + (MatchingUnequalExpression, "1 ?/= 2"), + (MatchingGreaterThanExpression, "1 ?> 2"), + (MatchingGreaterEqualExpression, "1 ?>= 2"), + (MatchingLessThanExpression, "1 ?< 2"), + (MatchingLessEqualExpression, "1 ?<= 2"), + (ShiftRightLogicExpression, "1 srl 2"), + (ShiftLeftLogicExpression, "1 sll 2"), + (ShiftRightArithmeticExpression, "1 sra 2"), + (ShiftLeftArithmeticExpression, "1 sla 2"), + (RotateRightExpression, "1 ror 2"), + (RotateLeftExpression, "1 rol 2"), +) + + +class BinaryExpressionFormats(TestCase): + def test_AllVariants(self) -> None: + for expressionClass, expected in _BINARY_FORMATS: + with self.subTest(expression=expressionClass.__name__): + self.assertEqual(expected, str(expressionClass(IntegerLiteral(1), IntegerLiteral(2)))) + + +class RangeExpressions(TestCase): + """``AscendingRangeExpression``/``DescendingRangeExpression`` additionally expose ``Direction`` + over the plain ``BinaryExpression`` shape.""" + + def test_Ascending(self) -> None: + expression = AscendingRangeExpression(IntegerLiteral(0), IntegerLiteral(7)) + + self.assertIs(Direction.To, expression.Direction) + self.assertEqual("0 to 7", str(expression)) + + def test_Descending(self) -> None: + expression = DescendingRangeExpression(IntegerLiteral(7), IntegerLiteral(0)) + + self.assertIs(Direction.DownTo, expression.Direction) + self.assertEqual("7 downto 0", str(expression)) + + +class QualifiedExpressions(TestCase): + def test_Construction(self) -> None: + subtype = SimpleSubtypeSymbol(SimpleName("bit_vector")) + operand = IntegerLiteral(1) + expression = QualifiedExpression(subtype, operand) + + self.assertIs(operand, expression.Operand) + self.assertIs(subtype, expression.Subtype) + self.assertIs(expression, operand.Parent) + self.assertIs(expression, subtype.Parent) + self.assertEqual("bit_vector?'(1)", str(expression)) + + +class TernaryExpressions(TestCase): + """Regression test: the constructor previously never accepted or set its three operands at all + (``# FIXME: parameters and initializers are missing !!``), and ``__str__`` separately indexed past + the end of the 4-element ``_FORMAT`` tuple (``self._FORMAT[4]``). Both fixed together - the + constructor now takes the three operands (wiring ``Parent`` for each), and ``__str__`` reads + ``_FORMAT[3]``. + + ``TernaryExpression`` itself deliberately exposes no public ``FirstOperand``/``SecondOperand``/ + ``ThirdOperand`` properties (see its class docstring) - only ``WhenElseExpression``'s own + ``ThenValue``/``Condition``/``ElseValue`` are public, so this is tested only through that concrete + subclass.""" + + def test_Construction(self) -> None: + """``thenValue when condition else elseValue``""" + thenValue = IntegerLiteral(1) + condition = IntegerLiteral(2) + elseValue = IntegerLiteral(3) + expression = WhenElseExpression(thenValue, condition, elseValue) + + self.assertIs(thenValue, expression.ThenValue) + self.assertIs(expression, thenValue.Parent) + self.assertIs(condition, expression.Condition) + self.assertIs(expression, condition.Parent) + self.assertIs(elseValue, expression.ElseValue) + self.assertIs(expression, elseValue.Parent) + self.assertEqual("1 when 2 else 3", str(expression)) + + +class FunctionCallAndAllocation(TestCase): + """``Allocation`` itself (``class Allocation(BaseExpression): pass``) is never instantiated + directly anywhere - real VHDL always uses one of its two concrete subclasses below (``new T`` or + ``new T'(expr)``), so unlike ``FunctionCall`` (which *is* the real, direct class for a function + call expression - there's no further subclass to move it to), there's no bare ``Allocation()`` + test here.""" + + def test_FunctionCall(self) -> None: + call = FunctionCall() + + self.assertIsInstance(call, BaseExpression) + + def test_SubtypeAllocation(self) -> None: + subtype = SimpleSubtypeSymbol(SimpleName("integer")) + allocation = SubtypeAllocation(subtype) + + self.assertIs(subtype, allocation.Subtype) + self.assertIs(allocation, subtype.Parent) + self.assertEqual("new integer?", str(allocation)) + + def test_QualifiedExpressionAllocation(self) -> None: + subtype = SimpleSubtypeSymbol(SimpleName("integer")) + qualifiedExpression = QualifiedExpression(subtype, IntegerLiteral(1)) + allocation = QualifiedExpressionAllocation(qualifiedExpression) + + self.assertIs(qualifiedExpression, allocation.QualifiedExpression) + self.assertIs(allocation, qualifiedExpression.Parent) + self.assertEqual("new integer?'(1)", str(allocation)) + + +class AggregateElements(TestCase): + def test_SimpleAggregateElement(self) -> None: + expression = IntegerLiteral(1) + element = SimpleAggregateElement(expression) + + self.assertIs(expression, element.Expression) + self.assertIs(element, expression.Parent) + self.assertEqual("1", str(element)) + + def test_IndexedAggregateElement(self) -> None: + index = IntegerLiteral(0) + expression = IntegerLiteral(1) + element = IndexedAggregateElement(index, expression) + + self.assertIs(index, element.Index) + self.assertIs(expression, element.Expression) + self.assertIs(element, expression.Parent) + self.assertEqual("0 => 1", str(element)) + + def test_RangedAggregateElement(self) -> None: + rng = SimpleRange(IntegerLiteral(0), IntegerLiteral(3), Direction.To) + expression = IntegerLiteral(1) + element = RangedAggregateElement(rng, expression) + + self.assertIs(rng, element.Range) + self.assertIs(element, rng.Parent) + self.assertIs(element, expression.Parent) + self.assertEqual("0 to 3 => 1", str(element)) + + def test_NamedAggregateElement(self) -> None: + name = SimpleSubtypeSymbol(SimpleName("field")) + expression = IntegerLiteral(1) + element = NamedAggregateElement(name, expression) + + self.assertIs(name, element.Name) + self.assertIs(element, name.Parent) + self.assertEqual("field? => 1", str(element)) + + def test_OthersAggregateElement(self) -> None: + expression = IntegerLiteral(1) + element = OthersAggregateElement(expression) + + self.assertIs(expression, element.Expression) + self.assertEqual("others => 1", str(element)) + + +class Aggregates(TestCase): + def test_Construction(self) -> None: + element1 = SimpleAggregateElement(IntegerLiteral(1)) + element2 = SimpleAggregateElement(IntegerLiteral(2)) + aggregate = Aggregate([element1, element2]) + + self.assertEqual(2, len(aggregate.Elements)) + self.assertIs(aggregate, element1.Parent) + self.assertIs(aggregate, element2.Parent) + self.assertEqual("(1, 2)", str(aggregate)) diff --git a/tests/unit/Instantiation/GenericInstantiation.py b/tests/unit/Instantiation/GenericInstantiation.py new file mode 100644 index 000000000..f1ed0887b --- /dev/null +++ b/tests/unit/Instantiation/GenericInstantiation.py @@ -0,0 +1,119 @@ +# ==================================================================================================================== # +# __ ___ _ ____ _ __ __ _ _ # +# _ __ _ \ \ / / | | | _ \| | | \/ | ___ __| | ___| | # +# | '_ \| | | \ \ / /| |_| | | | | | | |\/| |/ _ \ / _` |/ _ \ | # +# | |_) | |_| |\ V / | _ | |_| | |___| | | | (_) | (_| | __/ | # +# | .__/ \__, | \_/ |_| |_|____/|_____|_| |_|\___/ \__,_|\___|_| # +# |_| |___/ # +# ==================================================================================================================== # +# Authors: # +# Patrick Lehmann # +# # +# License: # +# ==================================================================================================================== # +# Copyright 2026-2026 Patrick Lehmann - Boetzingen, Germany # +# # +# Licensed under the Apache License, Version 2.0 (the "License"); # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at # +# # +# http://www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +# # +# SPDX-License-Identifier: Apache-2.0 # +# ==================================================================================================================== # +# +"""Tests for pyVHDLModel.Instantiation (VHDL-2008 generic subprogram/package instantiation).""" +from unittest import TestCase + +from pyVHDLModel import VHDLModelException +from pyVHDLModel.Name import SimpleName +from pyVHDLModel.Symbol import SubprogramReferenceSymbol, PackageReferenceSymbol, SimpleSubtypeSymbol +from pyVHDLModel.Expression import IntegerLiteral +from pyVHDLModel.Association import GenericAssociationItem +from pyVHDLModel.DesignUnit import Package, Component +from pyVHDLModel.Instantiation import ProcedureInstantiation, FunctionInstantiation, PackageInstantiation + + +if __name__ == "__main__": # pragma: no cover + print("ERROR: you called a testcase declaration file as an executable module.") + print("Use: 'python -m unitest '") + exit(1) + + +class ProcedureInstantiations(TestCase): + """``procedure p is new q generic map (...);``""" + + def test_Construction(self) -> None: + reference = SubprogramReferenceSymbol(SimpleName("q")) + generic = GenericAssociationItem(SimpleName("T"), IntegerLiteral(1)) + instantiation = ProcedureInstantiation("p", reference, [generic]) + + self.assertEqual("p", instantiation.Identifier) + self.assertIs(reference, instantiation.SubprogramReference) + self.assertIs(instantiation, reference.Parent) + self.assertEqual(1, len(instantiation.GenericAssociationItems)) + self.assertIs(instantiation, generic.Parent) + + def test_NoGenericAssociations(self) -> None: + instantiation = ProcedureInstantiation("p", SubprogramReferenceSymbol(SimpleName("q"))) + + self.assertEqual(0, len(instantiation.GenericAssociationItems)) + + +class FunctionInstantiations(TestCase): + """``function f is new g generic map (...);`` - ``ReturnType`` is deliberately ``Nullable``, see + the class's own docstring (never resolvable at the parse-only level, a genuinely unresolved + forward reference like ``Symbol.Reference``, not a workaround).""" + + def test_Construction(self) -> None: + reference = SubprogramReferenceSymbol(SimpleName("g")) + instantiation = FunctionInstantiation("f", reference) + + self.assertIs(reference, instantiation.SubprogramReference) + self.assertIsNone(instantiation.ReturnType) + self.assertTrue(instantiation.IsPure) + + def test_Impure(self) -> None: + instantiation = FunctionInstantiation("f", SubprogramReferenceSymbol(SimpleName("g")), isPure=False) + + self.assertFalse(instantiation.IsPure) + + +class PackageInstantiations(TestCase): + """``package p is new q generic map (...);``""" + + def test_Construction(self) -> None: + reference = PackageReferenceSymbol(SimpleName("q")) + generic = GenericAssociationItem(SimpleName("T"), IntegerLiteral(1)) + instantiation = PackageInstantiation("p", reference, genericAssociationItems=[generic]) + + self.assertIs(reference, instantiation.PackageReference) + self.assertIs(instantiation, reference.Parent) + self.assertEqual(1, len(instantiation.GenericAssociationItems)) + self.assertIs(instantiation, generic.Parent) + + def test_Instantiate_UnlinkedReference_Raises(self) -> None: + reference = PackageReferenceSymbol(SimpleName("q")) + instantiation = PackageInstantiation("p", reference) + + with self.assertRaises(VHDLModelException): + instantiation.Instantiate() + + def test_Instantiate_CopiesComponentsFromGenericPackage(self) -> None: + component = Component("comp") + genericPackage = Package("q", declaredItems=[component]) + genericPackage.IndexDeclaredItems() + + reference = PackageReferenceSymbol(SimpleName("q")) + reference.Package = genericPackage + instantiation = PackageInstantiation("p", reference) + + instantiation.Instantiate() + + self.assertIs(component, instantiation.Components["comp"]) diff --git a/tests/unit/Instantiation/Interface.py b/tests/unit/Instantiation/Interface.py new file mode 100644 index 000000000..c24646cde --- /dev/null +++ b/tests/unit/Instantiation/Interface.py @@ -0,0 +1,363 @@ +# ==================================================================================================================== # +# __ ___ _ ____ _ __ __ _ _ # +# _ __ _ \ \ / / | | | _ \| | __| | ___ _ __ ___ # +# | '_ \| | | \ \ / /| |_| | | | | | / _` |/ _ \| '_ ` _ \ # +# | |_) | |_| |\ V / | _ | |_| | |___ | (_| | (_) | | | | | | # +# | .__/ \__, | \_/ |_| |_|____/|_____(_)__,_|\___/|_| |_| |_| # +# |_| |___/ # +# ==================================================================================================================== # +# Authors: # +# Patrick Lehmann # +# # +# License: # +# ==================================================================================================================== # +# Copyright 2026-2026 Patrick Lehmann - Boetzingen, Germany # +# # +# Licensed under the Apache License, Version 2.0 (the "License"); # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at # +# # +# http://www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +# # +# SPDX-License-Identifier: Apache-2.0 # +# ==================================================================================================================== # +# +"""Tests for mode views (VHDL-2019), the Port/Parameter signal interface item split, generic/ +parameter interface item kinds, and the With*Mixin/*Group classes.""" +from unittest import TestCase + +from pyVHDLModel.Base import Mode +from pyVHDLModel.Name import SimpleName +from pyVHDLModel.Symbol import SimpleSubtypeSymbol, ModeViewSymbol +from pyVHDLModel.Expression import IntegerLiteral +from pyVHDLModel.Interface import ModeViewDeclaration, SimpleModeViewElement, CompositeModeViewElement +from pyVHDLModel.Interface import PortSignalInterfaceItem, PortSimpleSignalInterfaceItem, PortViewSignalInterfaceItem +from pyVHDLModel.Interface import ParameterSignalInterfaceItem, ParameterSimpleSignalInterfaceItem, ParameterViewSignalInterfaceItem +from pyVHDLModel.Interface import ( + GenericConstantInterfaceItem, GenericTypeInterfaceItem, + GenericProcedureInterfaceItem, GenericFunctionInterfaceItem, + InterfacePackage, GenericPackageInterfaceItem, + ParameterConstantInterfaceItem, ParameterVariableInterfaceItem, ParameterFileInterfaceItem, + WithGenericsMixin, WithPortsMixin, WithParametersMixin, + InterfaceGroup, GenericGroup, PortGroup, ParameterGroup, +) + + +def _subtype(name: str = "bit") -> SimpleSubtypeSymbol: + return SimpleSubtypeSymbol(SimpleName(name)) + + +if __name__ == "__main__": # pragma: no cover + print("ERROR: you called a testcase declaration file as an executable module.") + print("Use: 'python -m unitest '") + exit(1) + + +class ModeViewSymbols(TestCase): + def test_Unresolved(self) -> None: + name = SimpleName("MyView") + symbol = ModeViewSymbol(name) + + self.assertIs(name, symbol.Name) + self.assertFalse(symbol.IsResolved) + self.assertIsNone(symbol.Reference) + self.assertIsNone(symbol.ModeView) + + def test_Resolved(self) -> None: + symbol = ModeViewSymbol(SimpleName("MyView")) + subtype = SimpleSubtypeSymbol(SimpleName("RecordType")) + modeView = ModeViewDeclaration("MyView", subtype) + + symbol.ModeView = modeView + + self.assertTrue(symbol.IsResolved) + self.assertIs(modeView, symbol.ModeView) + self.assertIs(modeView, symbol.Reference) + + +class ModeViewDeclarations(TestCase): + def test_SimpleElements(self) -> None: + subtype = SimpleSubtypeSymbol(SimpleName("RecordType")) + elements = [ + SimpleModeViewElement(["a"], Mode.Out), + SimpleModeViewElement(["b"], Mode.In), + ] + modeView = ModeViewDeclaration("MyView", subtype, elements) + + self.assertEqual("MyView", modeView.Identifier) + self.assertIs(subtype, modeView.Subtype) + self.assertEqual(2, len(modeView.Elements)) + + a, b = modeView.Elements + self.assertIsInstance(a, SimpleModeViewElement) + self.assertEqual(("a",), a.Identifiers) + self.assertEqual(Mode.Out, a.Mode) + self.assertIsInstance(b, SimpleModeViewElement) + self.assertEqual(("b",), b.Identifiers) + self.assertEqual(Mode.In, b.Mode) + + def test_MultipleIdentifiersSharingOneMode(self) -> None: + """``a, b : out;`` - one element definition, multiple target field names.""" + element = SimpleModeViewElement(["a", "b"], Mode.Out) + + self.assertEqual(("a", "b"), element.Identifiers) + self.assertEqual(Mode.Out, element.Mode) + + def test_CompositeElement(self) -> None: + """``b : view InnerView;`` - a nested/hierarchical mode view reference.""" + subtype = SimpleSubtypeSymbol(SimpleName("OuterRecord")) + innerViewSymbol = ModeViewSymbol(SimpleName("InnerView")) + elements = [ + SimpleModeViewElement(["a"], Mode.Out), + CompositeModeViewElement(["b"], innerViewSymbol), + ] + modeView = ModeViewDeclaration("OuterView", subtype, elements) + + b = modeView.Elements[1] + self.assertIsInstance(b, CompositeModeViewElement) + self.assertEqual(("b",), b.Identifiers) + self.assertIs(innerViewSymbol, b.ModeViewName) + + +class PortSignalInterfaceItems(TestCase): + def test_SimpleMode(self) -> None: + port = PortSimpleSignalInterfaceItem(["p"], Mode.In, SimpleSubtypeSymbol(SimpleName("bit"))) + + self.assertIsInstance(port, PortSignalInterfaceItem) + self.assertEqual(Mode.In, port.Mode) + self.assertIsNotNone(port.Subtype) + + def test_ModeView(self) -> None: + modeViewIndication = ModeViewSymbol(SimpleName("MyView")) + port = PortViewSignalInterfaceItem(["p"], modeViewIndication) + + self.assertIsInstance(port, PortSignalInterfaceItem) + self.assertIs(modeViewIndication, port.ModeViewIndication) + self.assertIs(modeViewIndication, port.Subtype) + + +class ParameterSignalInterfaceItems(TestCase): + def test_SimpleMode(self) -> None: + parameter = ParameterSimpleSignalInterfaceItem(["s"], Mode.In, SimpleSubtypeSymbol(SimpleName("bit"))) + + self.assertIsInstance(parameter, ParameterSignalInterfaceItem) + self.assertEqual(Mode.In, parameter.Mode) + + def test_ModeView(self) -> None: + modeViewIndication = ModeViewSymbol(SimpleName("MyView")) + parameter = ParameterViewSignalInterfaceItem(["s"], modeViewIndication) + + self.assertIsInstance(parameter, ParameterSignalInterfaceItem) + self.assertIs(modeViewIndication, parameter.ModeViewIndication) + self.assertIs(modeViewIndication, parameter.Subtype) + + +class GenericInterfaceItems(TestCase): + def test_GenericConstantInterfaceItem(self) -> None: + """``generic (G : positive := 8);``""" + default = IntegerLiteral(8) + item = GenericConstantInterfaceItem(["G"], Mode.In, _subtype("positive"), defaultExpression=default) + + self.assertEqual(("G",), item.Identifiers) + self.assertIs(Mode.In, item.Mode) + self.assertIs(default, item.DefaultExpression) + + def test_GenericTypeInterfaceItem(self) -> None: + """``generic (type T);`` (VHDL-2008)""" + item = GenericTypeInterfaceItem("T") + + self.assertEqual("T", item.Identifier) + + def test_GenericProcedureInterfaceItem(self) -> None: + """``generic (procedure proc);`` (VHDL-2008)""" + item = GenericProcedureInterfaceItem("proc") + + self.assertEqual("proc", item.Identifier) + + def test_GenericProcedureInterfaceItem_WithDocumentation(self) -> None: + """Regression test: the constructor used to forward ``(identifier, documentation, parent)`` + positionally into ``Procedure.__init__``'s real signature + ``(identifier, genericItems, parameterItems, ...)``, so a non-``None`` ``documentation`` landed + in the ``genericItems`` slot and got iterated as if it were a list of interface items (crashing + on anything but ``None``/an empty iterable). Fixed by forwarding both as keyword arguments.""" + item = GenericProcedureInterfaceItem("proc", documentation="some documentation") + + self.assertEqual("proc", item.Identifier) + self.assertEqual("some documentation", item.Documentation) + + def test_GenericFunctionInterfaceItem(self) -> None: + """``generic (function func return boolean);`` (VHDL-2008) - regression test (HIGH PRIORITY, + confirmed live via pyGHDL.dom's actual translation dispatch, not just a landmine): + ``GenericFunctionInterfaceItem`` had no ``returnType`` parameter of its own, so + ``super().__init__(identifier, documentation, parent)`` misaligned against + ``Function.__init__``'s real signature - ``documentation`` landed in the ``returnType`` slot, + and ``Function.__init__`` unconditionally does ``returnType.Parent = self``, which crashed on + anything but a real ``Symbol`` (including the plain, no-docs case, since ``None`` has no + ``Parent`` either). Fixed by adding a real ``returnType`` parameter and forwarding + ``documentation``/``parent`` as keyword arguments.""" + returnType = _subtype("boolean") + item = GenericFunctionInterfaceItem("func", returnType) + + self.assertEqual("func", item.Identifier) + self.assertIs(returnType, item.ReturnType) + self.assertIs(item, returnType.Parent) + + def test_GenericPackageInterfaceItem(self) -> None: + """``generic (package p is new q generic map (<>));`` (VHDL-2008)""" + item = GenericPackageInterfaceItem("p") + + self.assertIsInstance(item, InterfacePackage) + self.assertEqual("p", item.Identifier) + + +class ParameterInterfaceItems(TestCase): + def test_ParameterConstantInterfaceItem(self) -> None: + """``procedure proc(constant c : in natural);``""" + item = ParameterConstantInterfaceItem(["c"], Mode.In, _subtype("natural")) + + self.assertIs(Mode.In, item.Mode) + + def test_ParameterVariableInterfaceItem(self) -> None: + """``procedure proc(variable v : inout natural);``""" + item = ParameterVariableInterfaceItem(["v"], Mode.InOut, _subtype("natural")) + + self.assertIs(Mode.InOut, item.Mode) + + def test_ParameterFileInterfaceItem(self) -> None: + """``procedure proc(file f : text);``""" + item = ParameterFileInterfaceItem(["f"], _subtype("text")) + + self.assertEqual(("f",), item.Identifiers) + + def test_ParameterConstantInterfaceItem_WithDocumentation(self) -> None: + """Regression test: every ``Generic*``/``Parameter*InterfaceItem`` class calls its mixin's own + ``__init__(self)`` (no arguments) *after* the primary base already set ``documentation`` + correctly. Since ``InterfaceItemMixin`` used to inherit ``DocumentedEntityMixin`` itself, that + trailing, argument-less call silently reset ``_documentation`` back to ``None`` every time - a + diamond back to the same mixin via two independent paths (the primary base and the interface- + item mixin), each initialized explicitly rather than cooperatively via ``super()``. Fixed by + removing ``DocumentedEntityMixin`` from ``InterfaceItemMixin``'s bases instead of removing the + call: every concrete interface item is already a documentable entity via its primary base + (constant, signal, variable, file, type, subprogram, or package), so the mixin never needed to + carry documentation of its own.""" + item = ParameterConstantInterfaceItem(["c"], Mode.In, _subtype("natural"), documentation="some documentation") + + self.assertEqual("some documentation", item.Documentation) + + +class WithGenericsPortsParametersMixins(TestCase): + """Tested via a minimal local host combining each mixin with itself (mixins can't be + instantiated standalone - see the design note in tests/unit/Base.py); real design-unit/ + subprogram hosts already exercise these mixins incidentally in their own slices, but nothing + elsewhere reads the ``*Count`` properties, so that's the focus here.""" + + def test_WithGenericsMixin(self) -> None: + class _Host(WithGenericsMixin): + pass + + item = GenericTypeInterfaceItem("T") + host = _Host([item]) + + self.assertEqual(1, host.GenericCount) + self.assertIs(item, host.GenericItems[0]) + + def test_WithPortsMixin(self) -> None: + class _Host(WithPortsMixin): + pass + + item = PortSimpleSignalInterfaceItem(["p"], Mode.In, _subtype()) + host = _Host([item]) + + self.assertEqual(1, host.PortCount) + + def test_WithParametersMixin(self) -> None: + class _Host(WithParametersMixin): + pass + + item = ParameterFileInterfaceItem(["f"], _subtype("text")) + host = _Host([item]) + + self.assertEqual(1, host.ParameterCount) + + +class Groups(TestCase): + """Regression test (missing base class): ``GenericGroup``/``ParameterGroup`` didn't list + ``WithGenericsMixin``/``WithParametersMixin`` as base classes at all (unlike ``PortGroup``, which + correctly lists ``WithPortsMixin``), so the slots-based metaclass never allocated storage for + ``_genericItems``/``_parameterItems`` - construction crashed immediately with + ``AttributeError: ... no __dict__ for setting new attributes``, even for the simplest, empty-list + case. Fixed by adding the missing base class to both, mirroring ``PortGroup``. + + Regression test (``__str__``): all three built their string via + ``p._identifier for p in self._xItems``, but interface items come in two incompatible shapes - + ``NamedEntityMixin``-based (singular ``_identifier`` - the four ``Generic*`` subprogram/package/ + type items) vs. ``MultipleNamedEntityMixin``-based (plural ``_identifiers`` - every + ``Constant``/``Signal``/``Variable``/``File``-derived item, i.e. every ``Port*``/``Parameter*`` + item and ``GenericConstantInterfaceItem``, since a single declaration can name several objects at + once: ``port (p1, p2 : in bit);``). ``PortGroup``/``ParameterGroup.__str__`` crashed for + essentially any realistic content, and ``GenericGroup.__str__`` crashed the moment a + ``GenericConstantInterfaceItem`` was included or a group mixed both item shapes. Fixed via the + shared ``_identifiersOf()`` helper, which flattens either shape into a plain tuple of names.""" + + def test_GenericGroup(self) -> None: + item = GenericTypeInterfaceItem("T") + group = GenericGroup([item], name="generics") + + self.assertEqual(1, len(group)) + self.assertEqual([item], list(group)) + self.assertEqual("GenericGroup: generics (1): T", str(group)) + + def test_GenericGroup_Empty(self) -> None: + group = GenericGroup([]) + + self.assertEqual(0, len(group)) + self.assertEqual("GenericGroup: None (0): ", str(group)) + + def test_GenericGroup_MixedItemShapes(self) -> None: + """``generic (type T; G : positive := 8);`` - a single generic clause legally mixes a + ``NamedEntityMixin``-based item (``GenericTypeInterfaceItem``) with a + ``MultipleNamedEntityMixin``-based one (``GenericConstantInterfaceItem``).""" + typeItem = GenericTypeInterfaceItem("T") + constantItem = GenericConstantInterfaceItem(["G"], Mode.In, _subtype("positive")) + group = GenericGroup([typeItem, constantItem]) + + self.assertEqual("GenericGroup: None (2): T, G", str(group)) + + def test_PortGroup(self) -> None: + item = PortSimpleSignalInterfaceItem(["p"], Mode.In, _subtype()) + group = PortGroup([item], name="ports") + + self.assertEqual(1, len(group)) + self.assertEqual([item], list(group)) + self.assertEqual("PortGroup: ports (1): p", str(group)) + + def test_PortGroup_MultipleIdentifiersPerItem(self) -> None: + """``port (p1, p2 : in bit);`` - one declaration, two port names.""" + item = PortSimpleSignalInterfaceItem(["p1", "p2"], Mode.In, _subtype()) + group = PortGroup([item]) + + self.assertEqual("PortGroup: None (1): p1, p2", str(group)) + + def test_ParameterGroup(self) -> None: + item = ParameterFileInterfaceItem(["f"], _subtype("text")) + group = ParameterGroup([item], name="parameters") + + self.assertEqual(1, len(group)) + self.assertEqual([item], list(group)) + self.assertEqual("ParameterGroup: parameters (1): f", str(group)) + + def test_ParameterGroup_Empty(self) -> None: + group = ParameterGroup([]) + + self.assertEqual(0, len(group)) + + def test_InterfaceGroup_NoName(self) -> None: + group = InterfaceGroup() + + self.assertIsNone(group.Identifier) diff --git a/tests/unit/Instantiate.py b/tests/unit/Instantiation/Model.py similarity index 84% rename from tests/unit/Instantiate.py rename to tests/unit/Instantiation/Model.py index 264b69073..73862a551 100644 --- a/tests/unit/Instantiate.py +++ b/tests/unit/Instantiation/Model.py @@ -29,23 +29,29 @@ # SPDX-License-Identifier: Apache-2.0 # # ==================================================================================================================== # # -"""Instantiation tests for the language model.""" +"""Instantiation tests for the model's top-level composition: names, symbols, design, library and document.""" from pathlib import Path from unittest import TestCase from pyTooling.Graph import Graph from pyVHDLModel import Design, Library, Document, IEEEFlavor, LibraryExistsInDesignError -from pyVHDLModel.Base import Direction, Range +from pyVHDLModel.Base import Direction, SimpleRange from pyVHDLModel.Name import SelectedName, SimpleName, AllName, AttributeName from pyVHDLModel.Object import Constant, Signal from pyVHDLModel.Symbol import LibraryReferenceSymbol, PackageReferenceSymbol, PackageMemberReferenceSymbol, SimpleSubtypeSymbol from pyVHDLModel.Symbol import AllPackageMembersReferenceSymbol, ContextReferenceSymbol, EntitySymbol from pyVHDLModel.Symbol import ArchitectureSymbol, PackageSymbol, EntityInstantiationSymbol from pyVHDLModel.Symbol import ComponentInstantiationSymbol, ConfigurationInstantiationSymbol +from pyVHDLModel.Symbol import SubprogramReferenceSymbol, ConstrainedScalarSubtypeSymbol +from pyVHDLModel.Symbol import Symbol, PossibleReference +from pyVHDLModel.Configuration import BlockConfiguration from pyVHDLModel.Expression import IntegerLiteral, FloatingPointLiteral -from pyVHDLModel.Type import Subtype, IntegerType, RealType, ArrayType, RecordType -from pyVHDLModel.DesignUnit import Package, PackageBody, Context, Entity, Architecture, Configuration +from pyVHDLModel.Type import Subtype, IntegerType, RealType, ArrayType, RecordType +from pyVHDLModel.DesignUnit import Package, PackageBody, Context, Entity, Architecture, Configuration +from pyVHDLModel.DesignUnit import LibraryClause +from pyVHDLModel.Association import GenericAssociationItem +from pyVHDLModel.Instantiation import PackageInstantiation, FunctionInstantiation, ProcedureInstantiation if __name__ == "__main__": # pragma: no cover @@ -335,8 +341,8 @@ def test_EntityInstantiationSymbol(self) -> None: self.assertTrue(symbol.IsResolved) self.assertIs(entity, symbol.Entity) - self.assertEqual("EntityInstantiationSymbol: 'Lib.Ent' -> Entity: 'liB.enT(%)'", repr(symbol)) - self.assertEqual("Entity: 'liB.enT(%)'", str(symbol)) + self.assertEqual("EntityInstantiationSymbol: 'Lib.Ent' -> Entity: 'liB.enT(?)'", repr(symbol)) + self.assertEqual("Entity: 'liB.enT(?)'", str(symbol)) def test_ComponentInstantiationSymbol(self) -> None: symbol = ComponentInstantiationSymbol(SimpleName("comp")) @@ -348,6 +354,19 @@ def test_ConfigurationInstantiationSymbol(self) -> None: self.assertEqual("cfg", symbol.Name.NormalizedIdentifier) + def test_ConstrainedScalarSubtypeSymbol(self) -> None: + """``signal s : integer range 0 to 15;`` - previously the range constraint was read by + pyGHDL.dom but had nowhere to go, since this class was a bare stub.""" + rng = SimpleRange(IntegerLiteral(0), IntegerLiteral(15), Direction.To) + symbol = ConstrainedScalarSubtypeSymbol(SimpleName("integer"), rng) + + self.assertIs(rng, symbol.Constraint) + + def test_ConstrainedScalarSubtypeSymbol_withoutConstraint(self) -> None: + symbol = ConstrainedScalarSubtypeSymbol(SimpleName("integer")) + + self.assertIsNone(symbol.Constraint) + class SimpleInstance(TestCase): def test_Design(self) -> None: @@ -421,6 +440,57 @@ def test_PackageBody(self) -> None: self.assertEqual("pack_1", packageBody.Identifier) self.assertEqual(0, len(packageBody.DeclaredItems)) + def test_PackageInstantiation(self) -> None: + packageReference = PackageReferenceSymbol(SimpleName("generic_pack")) + contextItems = [ + LibraryClause([ + LibraryReferenceSymbol(SimpleName("ieee")), + ]), + ] + genericAssociationItems = [ + GenericAssociationItem(SimpleName("WIDTH"), IntegerLiteral(16)), + ] + packageInstantiation = PackageInstantiation( + "pack_inst_1", packageReference, contextItems, genericAssociationItems, parent=None + ) + + self.assertIsNotNone(packageInstantiation) + self.assertEqual("pack_inst_1", packageInstantiation.Identifier) + self.assertIs(packageReference, packageInstantiation.PackageReference) + self.assertEqual(1, len(packageInstantiation.ContextItems)) + self.assertEqual(1, len(packageInstantiation.GenericAssociationItems)) + self.assertEqual("WIDTH", packageInstantiation.GenericAssociationItems[0].Formal.Identifier) + self.assertEqual(16, packageInstantiation.GenericAssociationItems[0].Actual.Value) + + def test_PackageInstantiation_withoutContextItemsOrGenerics(self) -> None: + packageReference = PackageReferenceSymbol(SimpleName("generic_pack")) + packageInstantiation = PackageInstantiation("pack_inst_1", packageReference, parent=None) + + self.assertIsNotNone(packageInstantiation) + self.assertEqual(0, len(packageInstantiation.ContextItems)) + self.assertEqual(0, len(packageInstantiation.GenericAssociationItems)) + + def test_FunctionInstantiation(self) -> None: + subprogramReference = SubprogramReferenceSymbol(SimpleName("generic_add")) + genericAssociationItems = [ + GenericAssociationItem(SimpleName("T"), SimpleSubtypeSymbol(SimpleName("integer"))), + ] + functionInstantiation = FunctionInstantiation("add_int", subprogramReference, True, genericAssociationItems) + + self.assertEqual("add_int", functionInstantiation.Identifier) + self.assertIs(subprogramReference, functionInstantiation.SubprogramReference) + self.assertEqual(1, len(functionInstantiation.GenericAssociationItems)) + self.assertTrue(functionInstantiation.IsPure) + self.assertIsNone(functionInstantiation.ReturnType) + + def test_ProcedureInstantiation(self) -> None: + subprogramReference = SubprogramReferenceSymbol(SimpleName("some_proc")) + procedureInstantiation = ProcedureInstantiation("my_proc", subprogramReference) + + self.assertEqual("my_proc", procedureInstantiation.Identifier) + self.assertIs(subprogramReference, procedureInstantiation.SubprogramReference) + self.assertEqual(0, len(procedureInstantiation.GenericAssociationItems)) + def test_Context(self) -> None: context = Context("ctx_1", parent=None) @@ -428,7 +498,12 @@ def test_Context(self) -> None: self.assertEqual("ctx_1", context.Identifier) def test_Configuration(self) -> None: - configuration = Configuration("conf_1", parent=None) + configuration = Configuration( + "conf_1", + EntitySymbol(SimpleName("entity_1")), + BlockConfiguration(Symbol(SimpleName("rtl"), PossibleReference.Architecture | PossibleReference.Label)), + parent=None + ) self.assertIsNotNone(configuration) self.assertEqual("conf_1", configuration.Identifier) @@ -440,13 +515,13 @@ def test_Subtype(self) -> None: self.assertEqual("bit", subtype.Identifier) def test_Integer(self) -> None: - integer = IntegerType("integer", Range(IntegerLiteral(0), IntegerLiteral(7), Direction.To), None) + integer = IntegerType("integer", SimpleRange(IntegerLiteral(0), IntegerLiteral(7), Direction.To), None) self.assertIsNotNone(integer) self.assertEqual("integer", integer.Identifier) def test_Real(self) -> None: - real = RealType("real", Range(FloatingPointLiteral(0.0), FloatingPointLiteral(1.0), Direction.To), None) + real = RealType("real", SimpleRange(FloatingPointLiteral(0.0), FloatingPointLiteral(1.0), Direction.To), None) self.assertIsNotNone(real) self.assertEqual("real", real.Identifier) @@ -527,7 +602,12 @@ def test_Configuration(self) -> None: path = Path("tests.vhdl") document = Document(path, parent=None) - configuration = Configuration("cfg_1", parent=None) + configuration = Configuration( + "cfg_1", + EntitySymbol(SimpleName("entity_1")), + BlockConfiguration(Symbol(SimpleName("rtl"), PossibleReference.Architecture | PossibleReference.Label)), + parent=None + ) document._AddConfiguration(configuration) self.assertEqual(1, len(document.Configurations)) @@ -554,7 +634,12 @@ def test_DesignUnits(self) -> None: context = Context("ctx_1", parent=None) document._AddDesignUnit(context) - configuration = Configuration("cfg_1", parent=None) + configuration = Configuration( + "cfg_1", + EntitySymbol(SimpleName("entity_1")), + BlockConfiguration(Symbol(SimpleName("rtl"), PossibleReference.Architecture | PossibleReference.Label)), + parent=None + ) document._AddDesignUnit(configuration) self.assertEqual(1, len(document.Entities)) @@ -603,7 +688,12 @@ def test_AddDocument(self) -> None: document._AddDesignUnit(Package("pack_1", parent=None)) document._AddDesignUnit(PackageBody(PackageSymbol(SimpleName("pack_1")), parent=None)) document._AddDesignUnit(Context("ctx_1", parent=None)) - document._AddDesignUnit(Configuration("cfg_1", parent=None)) + document._AddDesignUnit(Configuration( + "cfg_1", + EntitySymbol(SimpleName("entity_1")), + BlockConfiguration(Symbol(SimpleName("rtl"), PossibleReference.Architecture | PossibleReference.Label)), + parent=None + )) design.AddDocument(document, library) diff --git a/tests/unit/Instantiation/Name.py b/tests/unit/Instantiation/Name.py new file mode 100644 index 000000000..6cc5f9814 --- /dev/null +++ b/tests/unit/Instantiation/Name.py @@ -0,0 +1,187 @@ +# ==================================================================================================================== # +# __ ___ _ ____ _ __ __ _ _ # +# _ __ _ \ \ / / | | | _ \| | | \/ | ___ __| | ___| | # +# | '_ \| | | \ \ / /| |_| | | | | | | |\/| |/ _ \ / _` |/ _ \ | # +# | |_) | |_| |\ V / | _ | |_| | |___| | | | (_) | (_| | __/ | # +# | .__/ \__, | \_/ |_| |_|____/|_____|_| |_|\___/ \__,_|\___|_| # +# |_| |___/ # +# ==================================================================================================================== # +# Authors: # +# Patrick Lehmann # +# # +# License: # +# ==================================================================================================================== # +# Copyright 2026-2026 Patrick Lehmann - Boetzingen, Germany # +# # +# Licensed under the Apache License, Version 2.0 (the "License"); # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at # +# # +# http://www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +# # +# SPDX-License-Identifier: Apache-2.0 # +# ==================================================================================================================== # +# +"""Tests for pyVHDLModel.Name.""" +from unittest import TestCase + +from pyVHDLModel.Base import ModelEntity +from pyVHDLModel.Expression import IntegerLiteral +from pyVHDLModel.Association import GenericAssociationItem +from pyVHDLModel.Name import ( + Name, SimpleName, ParenthesisName, IndexedName, SlicedName, SelectedName, AttributeName, AllName, OpenName, +) + + +if __name__ == "__main__": # pragma: no cover + print("ERROR: you called a testcase declaration file as an executable module.") + print("Use: 'python -m unitest '") + exit(1) + + +class Names(TestCase): + def test_NoPrefixNoParent(self) -> None: + name = Name("foo") + + self.assertEqual("foo", name.Identifier) + self.assertEqual("foo", name.NormalizedIdentifier) + self.assertIsNone(name.Prefix) + self.assertFalse(name.HasPrefix) + self.assertIs(name, name.Root) + self.assertIsNone(name.Parent) + self.assertEqual("foo", str(name)) + self.assertEqual("Name: 'foo'", repr(name)) + + def test_IdentifierIsNormalized(self) -> None: + name = Name("FOO") + + self.assertEqual("FOO", name.Identifier) + self.assertEqual("foo", name.NormalizedIdentifier) + + def test_WithPrefix(self) -> None: + """``Root`` is inherited from the prefix's own root, so for a two-element chain it's simply the + prefix itself; see ``test_RootIsTransitiveAcrossAChain`` for a longer chain.""" + prefix = Name("pkg") + name = Name("member", prefix) + + self.assertIs(prefix, name.Prefix) + self.assertTrue(name.HasPrefix) + self.assertIs(prefix, name.Root) + + def test_RootIsTransitiveAcrossAChain(self) -> None: + root = Name("a") + middle = Name("b", root) + leaf = Name("c", middle) + + self.assertIs(root, middle.Root) + self.assertIs(root, leaf.Root) + self.assertIs(middle, leaf.Prefix) + + def test_WithParent(self) -> None: + parent = ModelEntity() + name = Name("foo", parent=parent) + + self.assertIs(parent, name.Parent) + + +class SimpleNames(TestCase): + def test_Construction(self) -> None: + name = SimpleName("sig") + + self.assertEqual("sig", name.Identifier) + self.assertFalse(name.HasPrefix) + + +class ParenthesisNames(TestCase): + """``arr(3)`` - e.g. an indexed name written through the generic ``ParenthesisName`` before it's + disambiguated into an indexed name, slice, or function call.""" + + def test_Construction(self) -> None: + prefix = SimpleName("arr") + association = GenericAssociationItem(None, IntegerLiteral(3)) + name = ParenthesisName(prefix, [association]) + + self.assertEqual(1, len(name.Associations)) + self.assertIs(association, name.Associations[0]) + self.assertIs(name, association.Parent) + self.assertEqual("arr(3)", str(name)) + + +class IndexedNames(TestCase): + def test_Construction(self) -> None: + prefix = SimpleName("arr") + index = IntegerLiteral(3) + name = IndexedName(prefix, [index]) + + self.assertEqual(1, len(name.Indices)) + self.assertIs(index, name.Indices[0]) + self.assertIs(name, index.Parent) + self.assertEqual("arr(3)", str(name)) + + +class SlicedNames(TestCase): + def test_Construction(self) -> None: + prefix = SimpleName("v") + name = SlicedName("", prefix) + + self.assertIs(prefix, name.Prefix) + + +class SelectedNames(TestCase): + def test_Construction(self) -> None: + prefix = SimpleName("pkg") + name = SelectedName("member", prefix) + + self.assertEqual("member", name.Identifier) + self.assertIs(prefix, name.Prefix) + self.assertEqual("pkg.member", str(name)) + + +class AttributeNames(TestCase): + def test_Construction(self) -> None: + prefix = SimpleName("sig") + name = AttributeName("range", prefix) + + self.assertEqual("range", name.Identifier) + self.assertEqual("sig'range", str(name)) + + +class AllNames(TestCase): + """``use ieee.numeric_std.all;`` - ``AllName`` is a ``SelectedName`` fixed to the identifier + ``"all"``.""" + + def test_Construction(self) -> None: + prefix = SimpleName("numeric_std") + name = AllName(prefix) + + self.assertEqual("all", name.Identifier) + self.assertEqual("numeric_std.all", str(name)) + + +class OpenNames(TestCase): + """Regression test: the ``parent`` argument was previously forwarded positionally into ``Name. + __init__``'s ``prefix`` parameter instead of its ``parent`` parameter (``super().__init__("open", + parent)``), so any ``OpenName(parent=...)`` call crashed with an ``AttributeError`` the moment + ``Name.__init__`` tried to read ``prefix._root`` off whatever non-``Name`` object had been passed + as ``parent``.""" + + def test_ConstructionWithoutParent(self) -> None: + name = OpenName() + + self.assertEqual("open", name.Identifier) + self.assertIsNone(name.Prefix) + self.assertIsNone(name.Parent) + self.assertEqual("open", str(name)) + + def test_ConstructionWithParent(self) -> None: + parent = ModelEntity() + name = OpenName(parent) + + self.assertIs(parent, name.Parent) + self.assertIsNone(name.Prefix) diff --git a/tests/unit/Instantiation/Object.py b/tests/unit/Instantiation/Object.py new file mode 100644 index 000000000..52f57608d --- /dev/null +++ b/tests/unit/Instantiation/Object.py @@ -0,0 +1,180 @@ +# ==================================================================================================================== # +# __ ___ _ ____ _ __ __ _ _ # +# _ __ _ \ \ / / | | | _ \| | | \/ | ___ __| | ___| | # +# | '_ \| | | \ \ / /| |_| | | | | | | |\/| |/ _ \ / _` |/ _ \ | # +# | |_) | |_| |\ V / | _ | |_| | |___| | | | (_) | (_| | __/ | # +# | .__/ \__, | \_/ |_| |_|____/|_____|_| |_|\___/ \__,_|\___|_| # +# |_| |___/ # +# ==================================================================================================================== # +# Authors: # +# Patrick Lehmann # +# # +# License: # +# ==================================================================================================================== # +# Copyright 2026-2026 Patrick Lehmann - Boetzingen, Germany # +# # +# Licensed under the Apache License, Version 2.0 (the "License"); # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at # +# # +# http://www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +# # +# SPDX-License-Identifier: Apache-2.0 # +# ==================================================================================================================== # +# +"""Tests for pyVHDLModel.Object.""" +from unittest import TestCase + +from pyVHDLModel.Name import SimpleName +from pyVHDLModel.Symbol import SimpleSubtypeSymbol +from pyVHDLModel.Expression import IntegerLiteral +from pyVHDLModel.Object import Constant, DeferredConstant, Variable, Signal, SharedVariable, File + + +if __name__ == "__main__": # pragma: no cover + print("ERROR: you called a testcase declaration file as an executable module.") + print("Use: 'python -m unitest '") + exit(1) + + +def _subtype(name: str = "natural") -> SimpleSubtypeSymbol: + return SimpleSubtypeSymbol(SimpleName(name)) + + +class ObjBaseBehaviour(TestCase): + """``Obj`` itself has no public subclass without a more specific meaning, so its shared behaviour + (multiple identifiers, subtype parent-wiring, the object-graph vertex) is tested once here via + ``Signal`` - any ``Obj`` subclass would do equally well, since none of this is overridden.""" + + def test_SingleIdentifier(self) -> None: + subtype = _subtype() + signal = Signal(["s"], subtype) + + self.assertEqual(("s",), signal.Identifiers) + self.assertEqual(("s",), signal.NormalizedIdentifiers) + self.assertIs(subtype, signal.Subtype) + self.assertIs(signal, subtype.Parent) + + def test_MultipleIdentifiers(self) -> None: + """``signal a, b, C : bit;`` declares three signals from one declaration.""" + signal = Signal(["a", "b", "C"], _subtype("bit")) + + self.assertEqual(("a", "b", "C"), signal.Identifiers) + self.assertEqual(("a", "b", "c"), signal.NormalizedIdentifiers) + + def test_ObjectVertexDefaultsToNone(self) -> None: + """``ObjectVertex`` is only populated once an object graph is built elsewhere; a freshly + constructed object was never inserted into one.""" + signal = Signal(["s"], _subtype()) + + self.assertIsNone(signal.ObjectVertex) + + def test_Documentation(self) -> None: + signal = Signal(["s"], _subtype(), documentation="a signal") + + self.assertEqual("a signal", signal.Documentation) + + def test_NoDocumentation(self) -> None: + signal = Signal(["s"], _subtype()) + + self.assertIsNone(signal.Documentation) + + +class WithDefaultExpression(TestCase): + """``WithDefaultExpressionMixin`` is shared by ``Constant``, ``Variable`` and ``Signal`` - tested + once via ``Signal`` for the parent-wiring behaviour, plus one smoke test per consumer below to + confirm each is actually wired up.""" + + def test_WithDefaultExpression(self) -> None: + default = IntegerLiteral(0) + signal = Signal(["s"], _subtype(), defaultExpression=default) + + self.assertIs(default, signal.DefaultExpression) + self.assertIs(signal, default.Parent) + + def test_WithoutDefaultExpression(self) -> None: + signal = Signal(["s"], _subtype()) + + self.assertIsNone(signal.DefaultExpression) + + +class Constants(TestCase): + def test_WithDefault(self) -> None: + default = IntegerLiteral(8) + constant = Constant(["BITS"], _subtype("positive"), defaultExpression=default) + + self.assertEqual(("BITS",), constant.Identifiers) + self.assertIs(default, constant.DefaultExpression) + + def test_WithoutDefault(self) -> None: + """Constructible without a default even though real VHDL always requires one for a (non- + deferred) constant - the model doesn't enforce that grammar rule itself.""" + constant = Constant(["BITS"], _subtype("positive")) + + self.assertIsNone(constant.DefaultExpression) + + +class DeferredConstants(TestCase): + """``constant BITS : positive;`` (in a package declaration, completed later in the package body).""" + + def test_Construction(self) -> None: + constant = DeferredConstant(["BITS"], _subtype("positive")) + + self.assertEqual(("BITS",), constant.Identifiers) + self.assertIsNone(constant.ConstantReference) + """``Symbol.__str__`` appends ``?`` for an unresolved reference - the subtype symbol here was + never resolved against a real type, so it renders as ``positive?``.""" + self.assertEqual("constant BITS : positive?", str(constant)) + + +class Variables(TestCase): + def test_WithDefault(self) -> None: + default = IntegerLiteral(0) + variable = Variable(["result"], _subtype("natural"), defaultExpression=default) + + self.assertIs(default, variable.DefaultExpression) + + def test_WithoutDefault(self) -> None: + variable = Variable(["result"], _subtype("natural")) + + self.assertIsNone(variable.DefaultExpression) + + +class Signals(TestCase): + def test_WithDefault(self) -> None: + default = IntegerLiteral(0) + signal = Signal(["counter"], _subtype("unsigned"), defaultExpression=default) + + self.assertIs(default, signal.DefaultExpression) + + def test_WithoutDefault(self) -> None: + signal = Signal(["counter"], _subtype("unsigned")) + + self.assertIsNone(signal.DefaultExpression) + + +class SharedVariables(TestCase): + """``shared variable`` - not implemented beyond the base ``Obj`` shape (see the ``.. todo::`` in + the class docstring).""" + + def test_Construction(self) -> None: + variable = SharedVariable(["v"], _subtype("natural")) + + self.assertEqual(("v",), variable.Identifiers) + self.assertIs(SharedVariable, type(variable)) + + +class Files(TestCase): + """``file`` - not implemented beyond the base ``Obj`` shape (see the ``.. todo::`` in the class + docstring); open-mode/logical-name are not modelled at all yet.""" + + def test_Construction(self) -> None: + file = File(["f"], _subtype("text")) + + self.assertEqual(("f",), file.Identifiers) diff --git a/tests/unit/Instantiation/Sequential.py b/tests/unit/Instantiation/Sequential.py new file mode 100644 index 000000000..a1925b94c --- /dev/null +++ b/tests/unit/Instantiation/Sequential.py @@ -0,0 +1,275 @@ +# ==================================================================================================================== # +# __ ___ _ ____ _ __ __ _ _ # +# _ __ _ \ \ / / | | | _ \| | | \/ | ___ __| | ___| | # +# | '_ \| | | \ \ / /| |_| | | | | | | |\/| |/ _ \ / _` |/ _ \ | # +# | |_) | |_| |\ V / | _ | |_| | |___| | | | (_) | (_| | __/ | # +# | .__/ \__, | \_/ |_| |_|____/|_____|_| |_|\___/ \__,_|\___|_| # +# |_| |___/ # +# ==================================================================================================================== # +# Authors: # +# Patrick Lehmann # +# # +# License: # +# ==================================================================================================================== # +# Copyright 2026-2026 Patrick Lehmann - Boetzingen, Germany # +# # +# Licensed under the Apache License, Version 2.0 (the "License"); # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at # +# # +# http://www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +# # +# SPDX-License-Identifier: Apache-2.0 # +# ==================================================================================================================== # +# +""" +Tests for pyVHDLModel.Sequential - the sequential statement kinds not already covered by +tests/unit/Assignment.py (conditional/selected/force/release assignments) or +tests/unit/Base.py (branches, report/assert statements, choices). +""" +from unittest import TestCase + +from pyVHDLModel.Base import ModelEntity, Direction, SimpleRange +from pyVHDLModel.Name import SimpleName +from pyVHDLModel.Symbol import SignalSymbol, VariableSymbol, Symbol, PossibleReference +from pyVHDLModel.Expression import IntegerLiteral, CharacterLiteral, PhysicalIntegerLiteral +from pyVHDLModel.Association import ParameterAssociationItem +from pyVHDLModel.Sequential import ( + SequentialProcedureCall, SequentialSignalAssignment, SequentialSimpleSignalAssignment, + CompoundStatement, IfBranch, ElseBranch, IfStatement, + IndexedChoice, RangedChoice, Case, OthersCase, CaseStatement, + LoopStatement, EndlessLoopStatement, ForLoopStatement, WhileLoopStatement, + NextStatement, ExitStatement, NullStatement, ReturnStatement, WaitStatement, +) +from pyVHDLModel.Base import WaveformElement + + +if __name__ == "__main__": # pragma: no cover + print("ERROR: you called a testcase declaration file as an executable module.") + print("Use: 'python -m unitest '") + exit(1) + + +def _signalTarget(name: str = "s") -> SignalSymbol: + return SignalSymbol(SimpleName(name)) + + +class SequentialProcedureCalls(TestCase): + """``ProcedureCallMixin`` is shared with ``ConcurrentProcedureCall`` (tests/unit/Concurrent.py); + tested once here since the mixin's own logic doesn't differ between the two.""" + + def test_WithParameters(self) -> None: + procedureName = Symbol(SimpleName("proc"), PossibleReference.Procedure) + parameter = ParameterAssociationItem(None, IntegerLiteral(1)) + call = SequentialProcedureCall(procedureName, [parameter], label="lbl") + + self.assertIs(procedureName, call.Procedure) + self.assertIs(call, procedureName.Parent) + self.assertEqual(1, len(call.ParameterAssociationItems)) + self.assertIs(call, parameter.Parent) + self.assertEqual("lbl", call.Label) + + def test_NoParameters(self) -> None: + call = SequentialProcedureCall(Symbol(SimpleName("proc"), PossibleReference.Procedure)) + + self.assertEqual(0, len(call.ParameterAssociationItems)) + self.assertIsNone(call.Label) + + +class SequentialSignalAssignments(TestCase): + def test_Construction(self) -> None: + target = _signalTarget() + assignment = SequentialSignalAssignment(target, label="lbl") + + self.assertIs(target, assignment.Target) + self.assertIs(assignment, target.Parent) + self.assertEqual("lbl", assignment.Label) + + +class SequentialSimpleSignalAssignments(TestCase): + def test_Construction(self) -> None: + """``s <= '1';``""" + target = _signalTarget() + waveformElement = WaveformElement(CharacterLiteral("'1'")) + assignment = SequentialSimpleSignalAssignment(target, [waveformElement]) + + self.assertIs(target, assignment.Target) + self.assertEqual(1, len(assignment.Waveform)) + self.assertIs(waveformElement, assignment.Waveform[0]) + self.assertIs(assignment, waveformElement.Parent) + + +class IfStatements(TestCase): + def test_IfOnly(self) -> None: + ifBranch = IfBranch(IntegerLiteral(1)) + statement = IfStatement(ifBranch) + + self.assertIs(ifBranch, statement.IfBranch) + self.assertIs(statement, ifBranch.Parent) + self.assertEqual(0, len(statement.ElsIfBranches)) + self.assertIsNone(statement.ElseBranch) + + def test_IfElsifElse(self) -> None: + from pyVHDLModel.Sequential import ElsifBranch + + ifBranch = IfBranch(IntegerLiteral(1)) + elsifBranch = ElsifBranch(IntegerLiteral(2)) + elseBranch = ElseBranch() + statement = IfStatement(ifBranch, [elsifBranch], elseBranch) + + self.assertEqual(1, len(statement.ElsIfBranches)) + self.assertIs(elsifBranch, statement.ElsIfBranches[0]) + self.assertIs(statement, elsifBranch.Parent) + self.assertIs(elseBranch, statement.ElseBranch) + self.assertIs(statement, elseBranch.Parent) + + +class Choices(TestCase): + """Regression test: ``IndexedChoice``'s ``expression.Parent = self`` was previously commented out + (``# FIXME: received None``) - confirmed stale in the gap analysis (every real construction site + always provides a real expression) and now re-enabled.""" + + def test_IndexedChoice(self) -> None: + expression = IntegerLiteral(0) + choice = IndexedChoice(expression) + + self.assertIs(expression, choice.Expression) + self.assertIs(choice, expression.Parent) + self.assertEqual("0", str(choice)) + + def test_RangedChoice(self) -> None: + rng = SimpleRange(IntegerLiteral(0), IntegerLiteral(3), Direction.To) + choice = RangedChoice(rng) + + self.assertIs(rng, choice.Range) + self.assertIs(choice, rng.Parent) + self.assertEqual("0 to 3", str(choice)) + + +class Cases(TestCase): + def test_Case(self) -> None: + choice = IndexedChoice(IntegerLiteral(0)) + case = Case([choice]) + + self.assertEqual(1, len(case.Choices)) + self.assertEqual("when 0 =>", str(case)) + + def test_Case_MultipleChoices(self) -> None: + """``when 0 | 1 =>``""" + case = Case([IndexedChoice(IntegerLiteral(0)), IndexedChoice(IntegerLiteral(1))]) + + self.assertEqual("when 0 | 1 =>", str(case)) + + def test_OthersCase(self) -> None: + case = OthersCase() + + self.assertEqual(0, len(case.Choices)) + self.assertEqual("when others =>", str(case)) + + +class CaseStatements(TestCase): + def test_Construction(self) -> None: + expression = IntegerLiteral(0) + case = Case([IndexedChoice(IntegerLiteral(0))]) + statement = CaseStatement(expression, [case]) + + self.assertIs(expression, statement.SelectExpression) + self.assertIs(statement, expression.Parent) + self.assertEqual(1, len(statement.Cases)) + self.assertIs(case, statement.Cases[0]) + self.assertIs(statement, case.Parent) + + +class LoopStatements(TestCase): + def test_EndlessLoopStatement(self) -> None: + statement = EndlessLoopStatement(label="lbl") + + self.assertEqual("lbl", statement.Label) + self.assertEqual(0, len(statement.Statements)) + + def test_ForLoopStatement(self) -> None: + rng = SimpleRange(IntegerLiteral(0), IntegerLiteral(3), Direction.To) + statement = ForLoopStatement("i", rng) + + self.assertEqual("i", statement.LoopIndex) + self.assertIs(rng, statement.Range) + self.assertIs(statement, rng.Parent) + + def test_WhileLoopStatement(self) -> None: + condition = IntegerLiteral(1) + statement = WhileLoopStatement(condition) + + self.assertIs(condition, statement.Condition) + + +class LoopControlStatements(TestCase): + """Regression test: ``_loopReference`` was declared but never initialized in + ``LoopControlStatement.__init__`` - the same crash-on-first-property-access shape as the fixed + ``DeferredConstant`` bug. ``NextStatement``/``ExitStatement`` add no state of their own, so the + fix is tested via both, once each.""" + + def test_NextStatement(self) -> None: + condition = IntegerLiteral(1) + statement = NextStatement(condition) + + self.assertIs(condition, statement.Condition) + self.assertIsNone(statement.LoopReference) + + def test_ExitStatement(self) -> None: + statement = ExitStatement() + + self.assertIsNone(statement.Condition) + self.assertIsNone(statement.LoopReference) + + +class NullStatements(TestCase): + def test_Construction(self) -> None: + statement = NullStatement(label="lbl") + + self.assertEqual("lbl", statement.Label) + + +class ReturnStatements(TestCase): + def test_WithValue(self) -> None: + value = IntegerLiteral(1) + statement = ReturnStatement(value) + + self.assertIs(value, statement.ReturnValue) + self.assertIs(statement, value.Parent) + + def test_WithoutValue(self) -> None: + """``return;`` (procedures) vs. ``return expr;`` (functions).""" + statement = ReturnStatement() + + self.assertIsNone(statement.ReturnValue) + + +class WaitStatements(TestCase): + def test_Empty(self) -> None: + """``wait;``""" + statement = WaitStatement() + + self.assertIsNone(statement.SensitivityList) + self.assertIsNone(statement.Condition) + self.assertIsNone(statement.Timeout) + + def test_Full(self) -> None: + """``wait on clock until condition for 10 ns;``""" + sensitivitySignal = _signalTarget("clock") + condition = IntegerLiteral(1) + timeout = PhysicalIntegerLiteral(10, "ns") + statement = WaitStatement([sensitivitySignal], condition, timeout) + + self.assertEqual(1, len(statement.SensitivityList)) + self.assertIs(sensitivitySignal, statement.SensitivityList[0]) + self.assertIs(statement, sensitivitySignal.Parent) + self.assertIs(condition, statement.Condition) + self.assertIs(statement, condition.Parent) + self.assertIs(timeout, statement.Timeout) + self.assertIs(statement, timeout.Parent) diff --git a/tests/unit/Instantiation/Subprogram.py b/tests/unit/Instantiation/Subprogram.py new file mode 100644 index 000000000..822347c6f --- /dev/null +++ b/tests/unit/Instantiation/Subprogram.py @@ -0,0 +1,135 @@ +# ==================================================================================================================== # +# __ ___ _ ____ _ __ __ _ _ # +# _ __ _ \ \ / / | | | _ \| | | \/ | ___ __| | ___| | # +# | '_ \| | | \ \ / /| |_| | | | | | | |\/| |/ _ \ / _` |/ _ \ | # +# | |_) | |_| |\ V / | _ | |_| | |___| | | | (_) | (_| | __/ | # +# | .__/ \__, | \_/ |_| |_|____/|_____|_| |_|\___/ \__,_|\___|_| # +# |_| |___/ # +# ==================================================================================================================== # +# Authors: # +# Patrick Lehmann # +# # +# License: # +# ==================================================================================================================== # +# Copyright 2026-2026 Patrick Lehmann - Boetzingen, Germany # +# # +# Licensed under the Apache License, Version 2.0 (the "License"); # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at # +# # +# http://www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +# # +# SPDX-License-Identifier: Apache-2.0 # +# ==================================================================================================================== # +# +"""Tests for pyVHDLModel.Subprogram.""" +from unittest import TestCase + +from pyVHDLModel.Name import SimpleName +from pyVHDLModel.Symbol import SimpleSubtypeSymbol +from pyVHDLModel.Sequential import NullStatement +from pyVHDLModel.Type import ProtectedType +from pyVHDLModel.Subprogram import Procedure, Function, ProcedureMethod, FunctionMethod + + +if __name__ == "__main__": # pragma: no cover + print("ERROR: you called a testcase declaration file as an executable module.") + print("Use: 'python -m unitest '") + exit(1) + + +def _returnType(name: str = "integer") -> SimpleSubtypeSymbol: + return SimpleSubtypeSymbol(SimpleName(name)) + + +class Procedures(TestCase): + """``Subprogram`` itself is not meant to be instantiated directly (there's no VHDL construct that + is "just" a subprogram, only procedures and functions) - its shared declared-items/statements/ + generic-items/parameter-items wiring is tested once here via ``Procedure``, the simpler of its two + concrete subclasses, rather than on the base class itself.""" + + def test_Minimal(self) -> None: + procedure = Procedure("proc") + + self.assertEqual("proc", procedure.Identifier) + self.assertFalse(procedure.IsPure) + self.assertEqual(0, len(procedure.GenericItems)) + self.assertEqual(0, len(procedure.ParameterItems)) + self.assertEqual(0, len(procedure.DeclaredItems)) + self.assertEqual(0, len(procedure.Statements)) + + def test_WithDeclaredItemsAndStatements(self) -> None: + declaredItem = Procedure("nested") + statement = NullStatement() + procedure = Procedure("proc", declaredItems=[declaredItem], statements=[statement]) + + self.assertEqual(1, len(procedure.DeclaredItems)) + self.assertIs(procedure, declaredItem.Parent) + self.assertEqual(1, len(procedure.Statements)) + self.assertIs(procedure, statement.Parent) + + def test_WithGenericAndParameterItems(self) -> None: + """Uses bare ``Procedure`` stand-ins for the generic/parameter items - only ``.Parent``-wiring + is exercised here, and real ``GenericInterfaceItemMixin``/``ParameterInterfaceItemMixin`` + classes are covered in tests/unit/Interface.py.""" + genericItem = Procedure("generic_item") + parameterItem = Procedure("parameter_item") + procedure = Procedure("proc", genericItems=[genericItem], parameterItems=[parameterItem]) + + self.assertEqual(1, len(procedure.GenericItems)) + self.assertIs(procedure, genericItem.Parent) + self.assertEqual(1, len(procedure.ParameterItems)) + self.assertIs(procedure, parameterItem.Parent) + + +class Functions(TestCase): + def test_Minimal(self) -> None: + returnType = _returnType() + function = Function("func", returnType) + + self.assertIs(returnType, function.ReturnType) + self.assertIs(function, returnType.Parent) + self.assertTrue(function.IsPure) + + def test_Impure(self) -> None: + function = Function("func", _returnType(), isPure=False) + + self.assertFalse(function.IsPure) + + +class MethodMixinHosts(TestCase): + """Regression test: ``MethodMixin.__init__`` set ``protectedType.Parent = self`` unconditionally, + but ``protectedType`` genuinely defaults to ``None`` at both call sites (a subprogram declared + directly in a protected type body still goes through the same constructor) - so the common, + no-argument case crashed immediately with ``AttributeError: 'NoneType' object has no attribute + 'Parent'``. Fixed to null-check like every other optional-reference mixin in this codebase.""" + + def test_ProcedureMethod_WithoutProtectedType(self) -> None: + method = ProcedureMethod("proc") + + self.assertIsNone(method.ProtectedType) + + def test_ProcedureMethod_WithProtectedType(self) -> None: + protectedType = ProtectedType("pt") + method = ProcedureMethod("proc", protectedType=protectedType) + + self.assertIs(protectedType, method.ProtectedType) + self.assertIs(method, protectedType.Parent) + + def test_FunctionMethod_WithoutProtectedType(self) -> None: + method = FunctionMethod("func", _returnType()) + + self.assertIsNone(method.ProtectedType) + + def test_FunctionMethod_WithProtectedType(self) -> None: + protectedType = ProtectedType("pt") + method = FunctionMethod("func", _returnType(), protectedType=protectedType) + + self.assertIs(protectedType, method.ProtectedType) + self.assertIs(method, protectedType.Parent) diff --git a/tests/unit/Instantiation/Symbol.py b/tests/unit/Instantiation/Symbol.py new file mode 100644 index 000000000..5c2ee56aa --- /dev/null +++ b/tests/unit/Instantiation/Symbol.py @@ -0,0 +1,221 @@ +# ==================================================================================================================== # +# __ ___ _ ____ _ __ __ _ _ # +# _ __ _ \ \ / / | | | _ \| | | \/ | ___ __| | ___| | # +# | '_ \| | | \ \ / /| |_| | | | | | | |\/| |/ _ \ / _` |/ _ \ | # +# | |_) | |_| |\ V / | _ | |_| | |___| | | | (_) | (_| | __/ | # +# | .__/ \__, | \_/ |_| |_|____/|_____|_| |_|\___/ \__,_|\___|_| # +# |_| |___/ # +# ==================================================================================================================== # +# Authors: # +# Patrick Lehmann # +# # +# License: # +# ==================================================================================================================== # +# Copyright 2026-2026 Patrick Lehmann - Boetzingen, Germany # +# # +# Licensed under the Apache License, Version 2.0 (the "License"); # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at # +# # +# http://www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +# # +# SPDX-License-Identifier: Apache-2.0 # +# ==================================================================================================================== # +# +""" +Tests for pyVHDLModel.Symbol. + +Most ``Symbol`` subclasses share an identical shape: constructed from just a ``Name``, fixed to one +``PossibleReference`` flag, with a single settable property used to store the resolved reference. +That shared shape is tested once, table-driven, in ``SimpleReferenceSymbols`` instead of one +hand-written test per class. Classes with distinct behaviour (constraints, the ``Symbol`` base +itself, the object/function-call symbols with no dedicated property) get their own test class. +""" +from unittest import TestCase + +from pyVHDLModel.Base import Direction, SimpleRange +from pyVHDLModel.Name import SimpleName, AllName +from pyVHDLModel.Expression import IntegerLiteral +from pyVHDLModel.Symbol import ( + PossibleReference, Symbol, + LibraryReferenceSymbol, PackageReferenceSymbol, ModeViewSymbol, SubprogramReferenceSymbol, + ConfigurationSymbol, VariableSymbol, SignalSymbol, ContextReferenceSymbol, + PackageMemberReferenceSymbol, AllPackageMembersReferenceSymbol, + EntityInstantiationSymbol, ComponentInstantiationSymbol, ConfigurationInstantiationSymbol, + EntitySymbol, ArchitectureSymbol, PackageSymbol, + RecordElementSymbol, SubtypeSymbol, SimpleSubtypeSymbol, + ConstrainedScalarSubtypeSymbol, ConstrainedArraySubtypeSymbol, ConstrainedRecordSubtypeSymbol, + SimpleObjectOrFunctionCallSymbol, IndexedObjectOrFunctionCallSymbol, +) + + +if __name__ == "__main__": # pragma: no cover + print("ERROR: you called a testcase declaration file as an executable module.") + print("Use: 'python -m unitest '") + exit(1) + + +class SymbolBase(TestCase): + """``Symbol`` itself (used directly for e.g. ``Alias.Name``, which has no single fixed + ``PossibleReference`` - see the design note in tests/unit/Declaration.py).""" + + def test_Unresolved(self) -> None: + name = SimpleName("s") + symbol = Symbol(name, PossibleReference.Signal | PossibleReference.Variable) + + self.assertIs(name, symbol.Name) + self.assertIsNone(symbol.Reference) + self.assertFalse(symbol.IsResolved) + self.assertFalse(bool(symbol)) + self.assertEqual("s?", str(symbol)) + self.assertEqual("Symbol: 's' -> unresolved", repr(symbol)) + + def test_Resolved(self) -> None: + """Resolution is exposed through each subclass's own named property (see + ``SimpleReferenceSymbols`` below); the base class only exposes the generic, readonly + ``Reference``/``IsResolved``/``__bool__``/``__str__`` machinery every subclass inherits.""" + symbol = LibraryReferenceSymbol(SimpleName("ieee")) + library = object() + symbol.Library = library + + self.assertIs(library, symbol.Reference) + self.assertTrue(symbol.IsResolved) + self.assertTrue(bool(symbol)) + self.assertEqual(str(library), str(symbol)) + self.assertIn("->", repr(symbol)) + + +# (SymbolClass, property name, expected PossibleReference) +_SIMPLE_REFERENCE_SYMBOLS = ( + (LibraryReferenceSymbol, "Library", PossibleReference.Library), + (PackageReferenceSymbol, "Package", PossibleReference.Package), + (ModeViewSymbol, "ModeView", PossibleReference.View), + (SubprogramReferenceSymbol, "Subprogram", PossibleReference.SubProgram), + (ConfigurationSymbol, "Configuration", PossibleReference.Configuration), + (VariableSymbol, "Variable", PossibleReference.Variable), + (SignalSymbol, "Signal", PossibleReference.Signal), + (ContextReferenceSymbol, "Context", PossibleReference.Context), + (PackageMemberReferenceSymbol, "Member", PossibleReference.PackageMember), + (EntityInstantiationSymbol, "Entity", PossibleReference.Entity), + (ComponentInstantiationSymbol, "Component", PossibleReference.Component), + (ConfigurationInstantiationSymbol, "Configuration", PossibleReference.Configuration), + (EntitySymbol, "Entity", PossibleReference.Entity), + (ArchitectureSymbol, "Architecture", PossibleReference.Architecture), + (PackageSymbol, "Package", PossibleReference.Package), +) + + +class SimpleReferenceSymbols(TestCase): + def test_AllVariants(self) -> None: + for symbolClass, propertyName, possibleReference in _SIMPLE_REFERENCE_SYMBOLS: + with self.subTest(symbol=symbolClass.__name__): + name = SimpleName("target") + symbol = symbolClass(name) + + self.assertIs(name, symbol.Name) + self.assertIs(possibleReference, symbol._possibleReferences) + self.assertIsNone(getattr(symbol, propertyName)) + self.assertFalse(symbol.IsResolved) + + target = object() + setattr(symbol, propertyName, target) + + self.assertIs(target, getattr(symbol, propertyName)) + self.assertIs(target, symbol.Reference) + self.assertTrue(symbol.IsResolved) + + def test_AllPackageMembersReferenceSymbol(self) -> None: + """Same shape as the table above, but the name must be an ``AllName`` + (``use pkg.all;``), not a plain ``Name``, and the property is plural (``Members``).""" + name = AllName(SimpleName("pkg")) + symbol = AllPackageMembersReferenceSymbol(name) + + self.assertIs(name, symbol.Name) + self.assertIs(PossibleReference.PackageMember, symbol._possibleReferences) + self.assertIsNone(symbol.Members) + + target = object() + symbol.Members = target + + self.assertIs(target, symbol.Members) + + +class NoPropertyReferenceSymbols(TestCase): + """``RecordElementSymbol``, ``SimpleObjectOrFunctionCallSymbol`` and + ``IndexedObjectOrFunctionCallSymbol`` fix a ``PossibleReference`` like the table above, but expose + no dedicated named property - only the base ``Symbol.Reference``.""" + + def test_RecordElementSymbol(self) -> None: + symbol = RecordElementSymbol(SimpleName("field")) + + self.assertIs(PossibleReference.RecordElement, symbol._possibleReferences) + self.assertIsNone(symbol.Reference) + + def test_SimpleObjectOrFunctionCallSymbol(self) -> None: + symbol = SimpleObjectOrFunctionCallSymbol(SimpleName("x")) + + self.assertIs(PossibleReference.SimpleNameInExpression, symbol._possibleReferences) + + def test_IndexedObjectOrFunctionCallSymbol(self) -> None: + symbol = IndexedObjectOrFunctionCallSymbol(SimpleName("x")) + + self.assertIs(PossibleReference.Object | PossibleReference.Function, symbol._possibleReferences) + + +class SubtypeSymbols(TestCase): + def test_SubtypeSymbol(self) -> None: + name = SimpleName("std_logic") + symbol = SubtypeSymbol(name) + + self.assertIs(PossibleReference.Type | PossibleReference.Subtype, symbol._possibleReferences) + self.assertIsNone(symbol.Subtype) + + target = object() + symbol.Subtype = target + + self.assertIs(target, symbol.Subtype) + + def test_SimpleSubtypeSymbol(self) -> None: + """``SimpleSubtypeSymbol`` adds no behaviour of its own over ``SubtypeSymbol``.""" + symbol = SimpleSubtypeSymbol(SimpleName("bit")) + + self.assertIsNone(symbol.Subtype) + + +class ConstrainedSubtypeSymbols(TestCase): + """``signal s : integer range 0 to 15;`` / ``std_logic_vector(7 downto 0)`` / record constraints.""" + + def test_ScalarConstraint_WithRange(self) -> None: + constraint = SimpleRange(IntegerLiteral(0), IntegerLiteral(15), Direction.To) + symbol = ConstrainedScalarSubtypeSymbol(SimpleName("integer"), constraint) + + self.assertIs(constraint, symbol.Constraint) + + def test_ScalarConstraint_WithoutRange(self) -> None: + """``None`` only means the range constraint was written as an attribute name + (``subtype s is t'range;``), which isn't implemented yet - not that the source omitted a + constraint (it never does for a constrained scalar subtype). See ``Constraint``'s docstring.""" + symbol = ConstrainedScalarSubtypeSymbol(SimpleName("integer")) + + self.assertIsNone(symbol.Constraint) + + def test_ArrayConstraint(self) -> None: + constraint = SimpleRange(IntegerLiteral(7), IntegerLiteral(0), Direction.DownTo) + symbol = ConstrainedArraySubtypeSymbol(SimpleName("std_logic_vector"), [constraint]) + + self.assertEqual(1, len(symbol.Constraints)) + self.assertIs(constraint, symbol.Constraints[0]) + + def test_RecordConstraint(self) -> None: + element = RecordElementSymbol(SimpleName("field")) + constraint = SimpleRange(IntegerLiteral(7), IntegerLiteral(0), Direction.DownTo) + symbol = ConstrainedRecordSubtypeSymbol(SimpleName("rec_t"), {element: constraint}) + + self.assertEqual(1, len(symbol.Constraints)) + self.assertIs(constraint, symbol.Constraints[element]) diff --git a/tests/unit/Instantiation/Type.py b/tests/unit/Instantiation/Type.py new file mode 100644 index 000000000..cf105a964 --- /dev/null +++ b/tests/unit/Instantiation/Type.py @@ -0,0 +1,310 @@ +# ==================================================================================================================== # +# __ ___ _ ____ _ __ __ _ _ # +# _ __ _ \ \ / / | | | _ \| | | \/ | ___ __| | ___| | # +# | '_ \| | | \ \ / /| |_| | | | | | | |\/| |/ _ \ / _` |/ _ \ | # +# | |_) | |_| |\ V / | _ | |_| | |___| | | | (_) | (_| | __/ | # +# | .__/ \__, | \_/ |_| |_|____/|_____|_| |_|\___/ \__,_|\___|_| # +# |_| |___/ # +# ==================================================================================================================== # +# Authors: # +# Patrick Lehmann # +# # +# License: # +# ==================================================================================================================== # +# Copyright 2026-2026 Patrick Lehmann - Boetzingen, Germany # +# # +# Licensed under the Apache License, Version 2.0 (the "License"); # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at # +# # +# http://www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +# # +# SPDX-License-Identifier: Apache-2.0 # +# ==================================================================================================================== # +# +"""Tests for pyVHDLModel.Type.""" +from unittest import TestCase + +from pyVHDLModel.Base import ModelEntity, Direction, RangeFromName, SimpleRange +from pyVHDLModel.Name import SimpleName, AttributeName +from pyVHDLModel.Symbol import RangeAttributeSymbol, SimpleSubtypeSymbol +from pyVHDLModel.Expression import IntegerLiteral, EnumerationLiteral, PhysicalIntegerLiteral +from pyVHDLModel.Type import ( + Subtype, RangedScalarType, + EnumeratedType, IntegerType, RealType, PhysicalType, + ArrayType, RecordTypeElement, RecordType, ProtectedType, ProtectedTypeBody, + AccessType, FileType, +) + + +if __name__ == "__main__": # pragma: no cover + print("ERROR: you called a testcase declaration file as an executable module.") + print("Use: 'python -m unitest '") + exit(1) + + +def _subtypeSymbol(name: str = "natural") -> SimpleSubtypeSymbol: + return SimpleSubtypeSymbol(SimpleName(name)) + + +def _range(left: int = 0, right: int = 15, direction: Direction = Direction.To) -> SimpleRange: + return SimpleRange(IntegerLiteral(left), IntegerLiteral(right), direction) + + +class ParentAndDocumentationWiringAcrossAllLeafTypes(TestCase): + """``BaseType`` provides identifier/documentation/parent handling and the (private, + regression-tested-here) ``_objectVertex`` slot to every type class - but ``Type``, ``AnonymousType``, + ``FullType``, ``ScalarType``, ``CompositeType`` and ``RangedScalarType`` are pure taxonomy markers + with no VHDL construct of their own (never directly instantiated anywhere in pyGHDL.dom, confirmed + by grep) and either add no ``__init__`` override at all, or (``RangedScalarType``) are only ever + reached through a further concrete subclass - so they are deliberately not instantiated directly + here. Instead, every *concrete* leaf type below is checked directly, one by one, rather than + assuming "tested once via the base class" is enough. + + That assumption is exactly what let a real bug through: unlike a true mixin (one shared + implementation), each concrete leaf class below defines its *own* ``__init__`` that independently + forwards to ``BaseType.__init__``, so each one carries its own, separate risk of getting that + forwarding call wrong. Confirmed and fixed here: every single one of them called + ``super().__init__(identifier, parent)`` positionally, which ``BaseType``'s + ``(identifier, documentation=None, parent=None)`` signature silently misinterprets - ``parent`` + landed in the ``documentation`` slot, and the real ``Parent`` chain was never set (e.g. + ``ProtectedType("pt", parent=parent)`` previously left ``.Parent`` as ``None`` and + ``.Documentation`` holding the parent object itself). + + None of these classes exposed their own ``documentation`` parameter at all (only ``BaseType`` + did) - fixed alongside the ``Parent`` bug, since it's the same forwarding call: every constructor + below now accepts ``documentation`` in the same position as ``BaseType`` itself, so each + ``super().__init__(identifier, documentation, parent)`` call is a plain, correctly-ordered + positional forward - no ``parent=`` keyword workaround needed anymore.""" + + def test_AllLeafTypes(self) -> None: + parent = ModelEntity() + builders = ( + ("Subtype", lambda: Subtype("t", _subtypeSymbol(), "doc", parent)), + ("EnumeratedType", lambda: EnumeratedType("t", [], "doc", parent)), + ("IntegerType", lambda: IntegerType("t", _range(), "doc", parent)), + ("RealType", lambda: RealType("t", _range(), "doc", parent)), + ("PhysicalType", lambda: PhysicalType("t", _range(), "ps", [], "doc", parent)), + ("ArrayType", lambda: ArrayType("t", [], _subtypeSymbol(), "doc", parent)), + ("RecordType", lambda: RecordType("t", None, "doc", parent)), + ("ProtectedType", lambda: ProtectedType("t", None, "doc", parent)), + ("ProtectedTypeBody", lambda: ProtectedTypeBody("t", None, "doc", parent)), + ("AccessType", lambda: AccessType("t", _subtypeSymbol(), "doc", parent)), + ("FileType", lambda: FileType("t", _subtypeSymbol(), "doc", parent)), + ) + for name, build in builders: + with self.subTest(type=name): + instance = build() + + self.assertIs(parent, instance.Parent) + self.assertEqual("doc", instance.Documentation) + + def test_ObjectVertexRegression(self) -> None: + """Regression test, checked once via ``EnumeratedType`` as a representative leaf class (the + bug was in ``BaseType.__init__`` itself, shared unchanged by every leaf type): the constructor + previously assigned a bare local variable (``_objectVertex = None``) instead of + ``self._objectVertex = None``, so the declared slot was never actually initialized by the + constructor - only ever set from the outside, later, by the object-graph builder in + pyVHDLModel/__init__.py. There's no public property for it (unlike + ``Object.Obj.ObjectVertex``), so this is checked via the private attribute directly.""" + enumType = EnumeratedType("t", []) + + self.assertIsNone(enumType._objectVertex) + + +class Subtypes(TestCase): + def test_Construction(self) -> None: + symbol = _subtypeSymbol("std_logic") + subtype = Subtype("my_std_logic", symbol) + + self.assertEqual("my_std_logic", subtype.Identifier) + self.assertIs(symbol, subtype.Type) + self.assertIsNone(subtype.BaseType) + self.assertIsNone(subtype.Range) + self.assertIsNone(subtype.ResolutionFunction) + self.assertEqual("subtype my_std_logic is None", str(subtype)) + + +class EnumeratedTypes(TestCase): + def test_Construction(self) -> None: + literal0 = EnumerationLiteral("'0'") + literal1 = EnumerationLiteral("'1'") + enumType = EnumeratedType("my_bit", [literal0, literal1]) + + self.assertEqual(2, len(enumType.Literals)) + self.assertIs(enumType, literal0.Parent) + self.assertIs(enumType, literal1.Parent) + self.assertEqual("my_bit is ('0', '1')", str(enumType)) + + def test_NoLiterals(self) -> None: + """``literals`` has no default value in the signature, but the body still guards for ``None`` + explicitly - accepted here even though nothing currently calls it that way.""" + enumType = EnumeratedType("empty", None) + + self.assertEqual(0, len(enumType.Literals)) + + +class IntegerTypes(TestCase): + """Also covers ``RangedScalarType.Range`` (shared, unmodified, by ``IntegerType``/``RealType``/ + ``PhysicalType``): it accepts any ``Range``, so either a ``SimpleRange`` or a ``RangeFromName`` for an + attribute-based range like ``type t is range r'range;`` - checked once here since this behaviour + genuinely is a single, shared implementation, unlike the constructor-forwarding concern above.""" + + def test_WithLiteralRange(self) -> None: + rng = _range(0, 15) + integerType = IntegerType("nibble", rng) + + self.assertIs(rng, integerType.Range) + self.assertEqual("nibble is range 0 to 15", str(integerType)) + + def test_WithAttributeRange(self) -> None: + rng = RangeFromName(RangeAttributeSymbol(AttributeName("range", SimpleName("r")))) + integerType = IntegerType("t", rng) + + self.assertIs(rng, integerType.Range) + + +class RealTypes(TestCase): + def test_Construction(self) -> None: + rng = _range(0, 1) + realType = RealType("fraction", rng) + + self.assertIs(rng, realType.Range) + self.assertEqual("fraction is range 0 to 1", str(realType)) + + +class PhysicalTypes(TestCase): + def test_Construction(self) -> None: + rng = _range(0, 1000) + femtoSeconds = PhysicalIntegerLiteral(1000, "fs") + physicalType = PhysicalType("my_time", rng, "ps", [("fs", femtoSeconds)]) + + self.assertIs(rng, physicalType.Range) + self.assertEqual("ps", physicalType.PrimaryUnit) + self.assertEqual(1, len(physicalType.SecondaryUnits)) + self.assertEqual("fs", physicalType.SecondaryUnits[0][0]) + self.assertIs(femtoSeconds, physicalType.SecondaryUnits[0][1]) + self.assertIs(physicalType, femtoSeconds.Parent) + self.assertEqual("my_time is range 0 to 1000 units ps; fs = 1000 fs;", str(physicalType)) + + def test_NoSecondaryUnits(self) -> None: + physicalType = PhysicalType("my_time", _range(0, 1000), "ps", []) + + self.assertEqual(0, len(physicalType.SecondaryUnits)) + + +class ArrayTypes(TestCase): + """Regression-tracking test, not a regression fix: still-open gap, already confirmed and + documented in the gap analysis - ``ArrayType.__init__`` deliberately (if unfortunately) never + wires up ``Parent`` for its indices or element subtype (both ``.Parent = self`` lines are + commented out with a FIXME). This locks in the *current* behaviour so a future fix shows up as an + intentional test change, not a silent regression.""" + + def test_Construction(self) -> None: + index = _range(0, 7) + elementSubtype = _subtypeSymbol("std_logic") + arrayType = ArrayType("my_vector", [index], elementSubtype) + + self.assertEqual(1, len(arrayType.Dimensions)) + self.assertIs(index, arrayType.Dimensions[0]) + self.assertIs(elementSubtype, arrayType.ElementType) + self.assertEqual("my_vector is array(0 to 7) of std_logic?", str(arrayType)) + + def test_ParentIsNotWiredYet(self) -> None: + """``index.Parent`` is genuinely ``None`` (``Range`` is a ``ModelEntity`` with a declared + ``Parent`` property), but ``elementSubtype.Parent`` doesn't even exist as an attribute - + ``Symbol`` isn't a ``ModelEntity`` (see the design note in tests/unit/Symbol.py) and nothing + ever assigns it here, so there's no ad-hoc attribute to find either.""" + index = _range(0, 7) + elementSubtype = _subtypeSymbol("std_logic") + arrayType = ArrayType("my_vector", [index], elementSubtype) + + self.assertIsNone(index.Parent) + self.assertFalse(hasattr(elementSubtype, "Parent")) + + +class RecordTypeElements(TestCase): + def test_Construction(self) -> None: + subtype = _subtypeSymbol("natural") + element = RecordTypeElement(["a", "b"], subtype) + + self.assertEqual(("a", "b"), element.Identifiers) + self.assertIs(subtype, element.Subtype) + self.assertIs(element, subtype.Parent) + self.assertEqual("a, b : natural?", str(element)) + + +class RecordTypes(TestCase): + def test_WithElements(self) -> None: + element = RecordTypeElement(["a"], _subtypeSymbol("natural")) + recordType = RecordType("my_record", [element]) + + self.assertEqual(1, len(recordType.Elements)) + self.assertIs(element, recordType.Elements[0]) + self.assertIs(recordType, element.Parent) + self.assertEqual("my_record is record a : natural?;", str(recordType)) + + def test_NoElements(self) -> None: + recordType = RecordType("my_record") + + self.assertEqual(0, len(recordType.Elements)) + + +class ProtectedTypes(TestCase): + """``methods`` accepts any pre-built ``ModelEntity`` for its parent-wiring; a plain ``ModelEntity`` + stand-in keeps this test independent from Subprogram.py, which gets its own dedicated slice.""" + + def test_WithMethods(self) -> None: + method = ModelEntity() + protectedType = ProtectedType("my_protected", [method]) + + self.assertEqual(1, len(protectedType.Methods)) + self.assertIs(protectedType, method.Parent) + + def test_NoMethods(self) -> None: + protectedType = ProtectedType("my_protected") + + self.assertEqual(0, len(protectedType.Methods)) + + +class ProtectedTypeBodies(TestCase): + def test_WithDeclaredItems(self) -> None: + method = ModelEntity() + body = ProtectedTypeBody("my_protected", [method]) + + self.assertEqual(1, len(body.Methods)) + self.assertIs(body, method.Parent) + + def test_NoDeclaredItems(self) -> None: + body = ProtectedTypeBody("my_protected") + + self.assertEqual(0, len(body.Methods)) + + +class AccessTypes(TestCase): + def test_Construction(self) -> None: + designated = _subtypeSymbol("natural") + accessType = AccessType("my_pointer", designated) + + self.assertIs(designated, accessType.DesignatedSubtype) + self.assertIs(accessType, designated.Parent) + self.assertEqual("my_pointer is access natural?", str(accessType)) + + +class FileTypes(TestCase): + """Regression test: ``__str__`` previously read ``"...is access ..."`` - copy-pasted from + ``AccessType`` - instead of the correct ``"...is file of ..."``.""" + + def test_Construction(self) -> None: + designated = _subtypeSymbol("character") + fileType = FileType("text", designated) + + self.assertIs(designated, fileType.DesignatedSubtype) + self.assertIs(fileType, designated.Parent) + self.assertEqual("text is file of character?", str(fileType)) diff --git a/tests/unit/Instantiation/__init__.py b/tests/unit/Instantiation/__init__.py new file mode 100644 index 000000000..db04534d3 --- /dev/null +++ b/tests/unit/Instantiation/__init__.py @@ -0,0 +1,39 @@ +# ==================================================================================================================== # +# __ ___ _ ____ _ __ __ _ _ # +# _ __ _ \ \ / / | | | _ \| | | \/ | ___ __| | ___| | # +# | '_ \| | | \ \ / /| |_| | | | | | | |\/| |/ _ \ / _` |/ _ \ | # +# | |_) | |_| |\ V / | _ | |_| | |___| | | | (_) | (_| | __/ | # +# | .__/ \__, | \_/ |_| |_|____/|_____|_| |_|\___/ \__,_|\___|_| # +# |_| |___/ # +# ==================================================================================================================== # +# Authors: # +# Patrick Lehmann # +# # +# License: # +# ==================================================================================================================== # +# Copyright 2026-2026 Patrick Lehmann - Boetzingen, Germany # +# # +# Licensed under the Apache License, Version 2.0 (the "License"); # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at # +# # +# http://www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +# # +# SPDX-License-Identifier: Apache-2.0 # +# ==================================================================================================================== # +# +""" +Instantiation tests: construct one language entity at a time. + +Each test class covers one VHDL feature; its methods cover the feature's variants - first the minimal +set of required parameters, then the optional ones. Assertions check the read-only properties and the +parent references. + +Tests that compose *multiple* entities into a hierarchy live in sibling packages (e.g. ``Namespace``). +""" diff --git a/tests/unit/Namespace/DesignUnits.py b/tests/unit/Namespace/DesignUnits.py new file mode 100644 index 000000000..674ee7be5 --- /dev/null +++ b/tests/unit/Namespace/DesignUnits.py @@ -0,0 +1,57 @@ +# ==================================================================================================================== # +# __ ___ _ ____ _ __ __ _ _ # +# _ __ _ \ \ / / | | | _ \| | | \/ | ___ __| | ___| | # +# | '_ \| | | \ \ / /| |_| | | | | | | |\/| |/ _ \ / _` |/ _ \ | # +# | |_) | |_| |\ V / | _ | |_| | |___| | | | (_) | (_| | __/ | # +# | .__/ \__, | \_/ |_| |_|____/|_____|_| |_|\___/ \__,_|\___|_| # +# |_| |___/ # +# ==================================================================================================================== # +# Authors: # +# Patrick Lehmann # +# # +# License: # +# ==================================================================================================================== # +# Copyright 2026-2026 Patrick Lehmann - Boetzingen, Germany # +# # +# Licensed under the Apache License, Version 2.0 (the "License"); # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at # +# # +# http://www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +# # +# SPDX-License-Identifier: Apache-2.0 # +# ==================================================================================================================== # +# +"""Namespaces owned by design units, and how ``IndexDeclaredItems`` populates them.""" +from unittest import TestCase + +from pyVHDLModel.DesignUnit import Architecture +from pyVHDLModel.Name import SimpleName +from pyVHDLModel.Object import Constant +from pyVHDLModel.Symbol import EntitySymbol, SimpleSubtypeSymbol + + +if __name__ == "__main__": # pragma: no cover + print("ERROR: you called a testcase declaration file as an executable module.") + print("Use: 'python -m unitest '") + exit(1) + + +def _entitySymbol(name: str = "e") -> EntitySymbol: + return EntitySymbol(SimpleName(name)) + + +class Architectures(TestCase): + def test_IndexDeclaredItems_AlsoPopulatesNamespace(self) -> None: + constant = Constant(["C"], SimpleSubtypeSymbol(SimpleName("natural"))) + architecture = Architecture("rtl", _entitySymbol(), declaredItems=[constant]) + architecture.IndexDeclaredItems() + + # Keyed by the normalized identifier, which is what the Find* methods look up. + self.assertIs(constant, architecture._namespace.Elements()["c"]) diff --git a/tests/unit/Namespace/Hiding.py b/tests/unit/Namespace/Hiding.py new file mode 100644 index 000000000..6a66aae15 --- /dev/null +++ b/tests/unit/Namespace/Hiding.py @@ -0,0 +1,235 @@ +# ==================================================================================================================== # +# __ ___ _ ____ _ __ __ _ _ # +# _ __ _ \ \ / / | | | _ \| | | \/ | ___ __| | ___| | # +# | '_ \| | | \ \ / /| |_| | | | | | | |\/| |/ _ \ / _` |/ _ \ | # +# | |_) | |_| |\ V / | _ | |_| | |___| | | | (_) | (_| | __/ | # +# | .__/ \__, | \_/ |_| |_|____/|_____|_| |_|\___/ \__,_|\___|_| # +# |_| |___/ # +# ==================================================================================================================== # +# Authors: # +# Patrick Lehmann # +# # +# License: # +# ==================================================================================================================== # +# Copyright 2026-2026 Patrick Lehmann - Boetzingen, Germany # +# # +# Licensed under the Apache License, Version 2.0 (the "License"); # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at # +# # +# http://www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +# # +# SPDX-License-Identifier: Apache-2.0 # +# ==================================================================================================================== # +# +""" +Hiding (shadowing) and scope nesting: the same identifier declared in an outer *and* an inner scope. + +VHDL lets the same name be declared in many nested declarative regions. Resolution must find the +innermost one, while items declared only in an outer region stay reachable, and the outer region itself +keeps seeing its own declaration. + +.. note:: + + Only some of VHDL's declarative regions are namespaces in this model today. Entity, architecture, + package, block, generate branch and generate case all own one. A **process**, a **subprogram** and the + VHDL-2019 **sequential block statement** don't, and entity **ports/generics** are never indexed into + the entity's namespace - so the scopes exercised below are the ones that currently *can* be tested. +""" +from pathlib import Path +from unittest import TestCase + +from pyVHDLModel import Design, Document, Library +from pyVHDLModel.Base import Direction, SimpleRange +from pyVHDLModel.Concurrent import ConcurrentBlockStatement, ForGenerateStatement +from pyVHDLModel.DesignUnit import Architecture, Entity +from pyVHDLModel.Expression import IntegerLiteral +from pyVHDLModel.Name import SimpleName +from pyVHDLModel.Object import Signal +from pyVHDLModel.Symbol import EntitySymbol, SignalSymbol, SimpleSubtypeSymbol + + +if __name__ == "__main__": # pragma: no cover + print("ERROR: you called a testcase declaration file as an executable module.") + print("Use: 'python -m unitest %s'" % __file__) + exit(1) + + +def _signal(identifier: str) -> Signal: + return Signal((identifier, ), SimpleSubtypeSymbol(SimpleName("natural"))) + + +class EntityAndArchitecture(TestCase): + """ + An architecture's namespace nests inside its entity's, which is what makes an entity's ports and + declarations *visible* in the architecture. + + Note this pair is a single declarative region in VHDL, so it is not a hiding scenario - see + :meth:`test_ArchitectureDeclarationShadowsEntityDeclaration_ButIsIllegalVHDL`. + + That link is established by :meth:`~pyVHDLModel.Library.LinkArchitectures`, not by assigning + ``Architecture.Parent`` - an architecture's parent is its document, and the entity relation goes + through the entity symbol. So this needs a real design that has been linked. + """ + + def setUp(self) -> None: + design = Design() + library = Library("work") + design.AddLibrary(library) + document = Document(Path("virtual.vhdl")) + + self._entitySignal = _signal("x") + self._entityOnlySignal = _signal("entityOnly") + self._entity = Entity("ent", declaredItems=[self._entitySignal, self._entityOnlySignal]) + document._AddDesignUnit(self._entity) + + self._architectureSignal = _signal("x") + self._architecture = Architecture("rtl", EntitySymbol(SimpleName("ent")), declaredItems=[self._architectureSignal]) + document._AddDesignUnit(self._architecture) + + design.AddDocument(document, library) + design.CreateDependencyGraph() + design.LinkArchitectures() + + self._entity.IndexDeclaredItems() + self._architecture.IndexDeclaredItems() + + def test_ArchitectureNamespaceNestsInsideEntityNamespace(self) -> None: + self.assertIs(self._entity._namespace, self._architecture._namespace.ParentNamespace) + + def test_ArchitectureDeclarationShadowsEntityDeclaration_ButIsIllegalVHDL(self) -> None: + """ + Documents current *model* behaviour, which does **not** match VHDL. + + An entity declaration and its architecture body form a **single** declarative region + (LRM 12.1), so re-declaring an entity-level name in the architecture is an error, not hiding. + Confirmed against GHDL: ``signal s`` in an architecture whose entity declares ``s`` gives + "identifier "s" already used for a declaration" - whereas a *process* variable shadowing an + architecture signal only warns (``-Whide``). + + The model represents the entity/architecture pair as two nested namespaces, which gets + *visibility* right (see the next test) but silently resolves a duplicate instead of rejecting + it. Duplicate-declaration detection is a separate, missing feature - see + ``pyVHDLModel.Findings.md``. + """ + found = self._architecture._namespace.FindObject(SignalSymbol(SimpleName("x"))) + + self.assertIs(self._architectureSignal, found) + + def test_ArchitectureInheritsEntityOnlyDeclaration(self) -> None: + found = self._architecture._namespace.FindObject(SignalSymbol(SimpleName("entityOnly"))) + + self.assertIs(self._entityOnlySignal, found) + + def test_EntityStillResolvesItsOwnDeclaration(self) -> None: + """Lookup only walks outwards - the outer namespace is unaffected by the inner one.""" + found = self._entity._namespace.FindObject(SignalSymbol(SimpleName("x"))) + + self.assertIs(self._entitySignal, found) + + +class BlocksInsideArchitecture(TestCase): + """A block's declarative region nests inside the enclosing architecture's.""" + + def setUp(self) -> None: + self._architectureSignal = _signal("x") + self._architectureOnlySignal = _signal("architectureOnly") + self._architecture = Architecture( + "rtl", + EntitySymbol(SimpleName("ent")), + declaredItems=[self._architectureSignal, self._architectureOnlySignal], + ) + self._architecture.IndexDeclaredItems() + + self._blockSignal = _signal("x") + self._block = ConcurrentBlockStatement("blk", declaredItems=[self._blockSignal]) + self._block.Parent = self._architecture + self._block.IndexDeclaredItems() + + def test_BlockDeclarationHidesArchitectureDeclaration(self) -> None: + found = self._block._namespace.FindObject(SignalSymbol(SimpleName("x"))) + + self.assertIs(self._blockSignal, found) + + def test_BlockInheritsArchitectureOnlyDeclaration(self) -> None: + found = self._block._namespace.FindObject(SignalSymbol(SimpleName("architectureOnly"))) + + self.assertIs(self._architectureOnlySignal, found) + + def test_ArchitectureDoesNotSeeBlockDeclaration(self) -> None: + """Resolution only walks outwards, never into a nested scope.""" + found = self._architecture._namespace.FindObject(SignalSymbol(SimpleName("x"))) + + self.assertIs(self._architectureSignal, found) + + +class NestedBlocks(TestCase): + """Three levels of nesting: the innermost declaration wins, and each level keeps its own.""" + + def setUp(self) -> None: + self._outerSignal = _signal("x") + self._outer = ConcurrentBlockStatement("outer", declaredItems=[self._outerSignal]) + self._outer.IndexDeclaredItems() + + self._middleSignal = _signal("x") + self._middle = ConcurrentBlockStatement("middle", declaredItems=[self._middleSignal]) + self._middle.Parent = self._outer + self._middle.IndexDeclaredItems() + + self._innerSignal = _signal("x") + self._inner = ConcurrentBlockStatement("inner", declaredItems=[self._innerSignal]) + self._inner.Parent = self._middle + self._inner.IndexDeclaredItems() + + def test_InnermostDeclarationWins(self) -> None: + self.assertIs(self._innerSignal, self._inner._namespace.FindObject(SignalSymbol(SimpleName("x")))) + + def test_EachLevelResolvesItsOwn(self) -> None: + self.assertIs(self._middleSignal, self._middle._namespace.FindObject(SignalSymbol(SimpleName("x")))) + self.assertIs(self._outerSignal, self._outer._namespace.FindObject(SignalSymbol(SimpleName("x")))) + + def test_UndeclaredNameWalksTheWholeChain(self) -> None: + """A name declared only at the outermost level is still reachable from the innermost scope.""" + onlyOutside = _signal("onlyOutside") + self._outer._namespace._elements["onlyoutside"] = onlyOutside + + self.assertIs(onlyOutside, self._inner._namespace.FindObject(SignalSymbol(SimpleName("onlyOutside")))) + + +class GeneratesInsideArchitecture(TestCase): + """A for-generate's declarative region nests inside the enclosing architecture's.""" + + def setUp(self) -> None: + self._architectureSignal = _signal("x") + self._architecture = Architecture( + "rtl", + EntitySymbol(SimpleName("ent")), + declaredItems=[self._architectureSignal], + ) + self._architecture.IndexDeclaredItems() + + self._generateSignal = _signal("x") + self._generate = ForGenerateStatement( + "gen", + "i", + SimpleRange(IntegerLiteral(0), IntegerLiteral(3), Direction.To), + declaredItems=[self._generateSignal], + ) + self._generate.Parent = self._architecture + self._generate.IndexDeclaredItems() + + def test_GenerateDeclarationHidesArchitectureDeclaration(self) -> None: + found = self._generate._namespace.FindObject(SignalSymbol(SimpleName("x"))) + + self.assertIs(self._generateSignal, found) + + def test_ArchitectureKeepsItsOwnDeclaration(self) -> None: + found = self._architecture._namespace.FindObject(SignalSymbol(SimpleName("x"))) + + self.assertIs(self._architectureSignal, found) diff --git a/tests/unit/Namespace/InterfaceItems.py b/tests/unit/Namespace/InterfaceItems.py new file mode 100644 index 000000000..5f749a052 --- /dev/null +++ b/tests/unit/Namespace/InterfaceItems.py @@ -0,0 +1,204 @@ +# ==================================================================================================================== # +# __ ___ _ ____ _ __ __ _ _ # +# _ __ _ \ \ / / | | | _ \| | | \/ | ___ __| | ___| | # +# | '_ \| | | \ \ / /| |_| | | | | | | |\/| |/ _ \ / _` |/ _ \ | # +# | |_) | |_| |\ V / | _ | |_| | |___| | | | (_) | (_| | __/ | # +# | .__/ \__, | \_/ |_| |_|____/|_____|_| |_|\___/ \__,_|\___|_| # +# |_| |___/ # +# ==================================================================================================================== # +# Authors: # +# Patrick Lehmann # +# # +# License: # +# ==================================================================================================================== # +# Copyright 2026-2026 Patrick Lehmann - Boetzingen, Germany # +# # +# Licensed under the Apache License, Version 2.0 (the "License"); # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at # +# # +# http://www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +# # +# SPDX-License-Identifier: Apache-2.0 # +# ==================================================================================================================== # +# +""" +Interface items - generics, ports and parameters - resolved through their region's namespace. + +An interface item shares the declarative region of the declarative part beside it. Verified against the +GHDL analyzer: ``port (g : in bit)`` beside ``generic (g : integer)``, ``signal x`` beside +``port (x : in bit)``, and a subprogram variable named like a parameter are each rejected with +"identifier already used for a declaration". So they go into the *same* namespace as the declared items, +not a separate one. +""" +from unittest import TestCase + +from pyVHDLModel.Base import Mode +from pyVHDLModel.Concurrent import ConcurrentBlockStatement, ProcessStatement +from pyVHDLModel.DesignUnit import Architecture, Entity, Package +from pyVHDLModel.Interface import ( + GenericConstantInterfaceItem, + GenericTypeInterfaceItem, + ParameterVariableInterfaceItem, + PortSimpleSignalInterfaceItem, +) +from pyVHDLModel.Name import SimpleName +from pyVHDLModel.Object import Signal +from pyVHDLModel.Subprogram import Procedure +from pyVHDLModel.Symbol import EntitySymbol, SignalSymbol, SimpleSubtypeSymbol, VariableSymbol + + +if __name__ == "__main__": # pragma: no cover + print("ERROR: you called a testcase declaration file as an executable module.") + print("Use: 'python -m unitest '") + exit(1) + + +def _subtypeSymbol() -> SimpleSubtypeSymbol: + return SimpleSubtypeSymbol(SimpleName("natural")) + + +def _port(*identifiers: str) -> PortSimpleSignalInterfaceItem: + return PortSimpleSignalInterfaceItem(identifiers, Mode.In, _subtypeSymbol()) + + +def _generic(*identifiers: str) -> GenericConstantInterfaceItem: + return GenericConstantInterfaceItem(identifiers, Mode.In, _subtypeSymbol()) + + +class EntityInterfaceItems(TestCase): + def test_PortIsIndexedIntoTheEntityNamespace(self) -> None: + port = _port("clk") + entity = Entity("ent", portItems=[port]) + entity.IndexDeclaredItems() + + self.assertIs(port, entity._namespace.Elements()["clk"]) + + def test_PortIsResolvable(self) -> None: + """A port signal interface item *is* a `Signal`, so `FindObject` resolves it.""" + port = _port("clk") + entity = Entity("ent", portItems=[port]) + entity.IndexDeclaredItems() + + found = entity._namespace.FindObject(SignalSymbol(SimpleName("clk"))) + + self.assertIs(port, found) + self.assertIsInstance(found, Signal) + + def test_EveryIdentifierOfAMultiIdentifierPortIsIndexed(self) -> None: + """``port (clk, rst : in bit)`` is one item with two identifiers.""" + port = _port("clk", "rst") + entity = Entity("ent", portItems=[port]) + entity.IndexDeclaredItems() + + self.assertIs(port, entity._namespace.Elements()["clk"]) + self.assertIs(port, entity._namespace.Elements()["rst"]) + + def test_GenericIsIndexed(self) -> None: + generic = _generic("width") + entity = Entity("ent", genericItems=[generic]) + entity.IndexDeclaredItems() + + self.assertIs(generic, entity._namespace.Elements()["width"]) + + def test_SingularGenericTypeIsIndexed(self) -> None: + """``generic (type T)`` is singularly named, unlike every other interface item.""" + genericType = GenericTypeInterfaceItem("T") + entity = Entity("ent", genericItems=[genericType]) + entity.IndexDeclaredItems() + + self.assertIs(genericType, entity._namespace.Elements()["t"]) + + def test_GenericsPortsAndDeclarationsShareOneNamespace(self) -> None: + generic = _generic("width") + port = _port("clk") + signal = Signal(("internal", ), _subtypeSymbol()) + entity = Entity("ent", genericItems=[generic], portItems=[port], declaredItems=[signal]) + entity.IndexDeclaredItems() + + self.assertEqual({"width", "clk", "internal"}, set(entity._namespace.Elements().keys())) + + def test_PortIsVisibleInTheArchitecture(self) -> None: + """The whole point: a port name resolves from inside the architecture.""" + port = _port("clk") + entity = Entity("ent", portItems=[port]) + entity.IndexDeclaredItems() + + architecture = Architecture("rtl", EntitySymbol(SimpleName("ent"))) + architecture._namespace.ParentNamespace = entity._namespace + architecture.IndexDeclaredItems() + + self.assertIs(port, architecture._namespace.FindObject(SignalSymbol(SimpleName("clk")))) + + def test_PortIsVisibleInsideAProcess(self) -> None: + port = _port("clk") + entity = Entity("ent", portItems=[port]) + entity.IndexDeclaredItems() + + architecture = Architecture("rtl", EntitySymbol(SimpleName("ent"))) + architecture._namespace.ParentNamespace = entity._namespace + architecture.IndexDeclaredItems() + + process = ProcessStatement("proc") + process.Parent = architecture + + self.assertIs(port, process.Namespace.FindObject(SignalSymbol(SimpleName("clk")))) + + +class PackageGenerics(TestCase): + def test_GenericIsIndexed(self) -> None: + """A generic package (VHDL-2008) has generics but no ports.""" + generic = _generic("width") + package = Package("gp", genericItems=[generic]) + package.IndexDeclaredItems() + + self.assertIs(generic, package._namespace.Elements()["width"]) + + +class BlockPorts(TestCase): + def test_PortIsIndexed(self) -> None: + """``ConcurrentBlockStatement`` hand-rolls its port list rather than using ``WithPortsMixin``.""" + port = _port("bp") + block = ConcurrentBlockStatement("blk", portItems=[port]) + block.IndexDeclaredItems() + + self.assertIs(port, block._namespace.Elements()["bp"]) + + +class SubprogramInterfaceItems(TestCase): + """ + ``Subprogram`` can't inherit ``WithGenericsMixin``/``WithParametersMixin``: + :mod:`pyVHDLModel.Interface` needs ``Procedure``/``Function`` as real base classes for its generic + subprogram interface items, so :mod:`pyVHDLModel.Subprogram` may never import it. Indexing therefore + reads the canonical field names, which covers hand-rolled and mixin-provided lists alike. + """ + + def test_ParameterIsIndexed(self) -> None: + parameter = ParameterVariableInterfaceItem(("p", ), Mode.In, _subtypeSymbol()) + procedure = Procedure("helper", parameterItems=[parameter]) + procedure.IndexDeclaredItems() + + self.assertIs(parameter, procedure.Namespace.Elements()["p"]) + self.assertIs(parameter, procedure.Namespace.FindObject(VariableSymbol(SimpleName("p")))) + + def test_EveryIdentifierOfAMultiIdentifierParameterIsIndexed(self) -> None: + parameter = ParameterVariableInterfaceItem(("p", "q"), Mode.In, _subtypeSymbol()) + procedure = Procedure("helper", parameterItems=[parameter]) + procedure.IndexDeclaredItems() + + self.assertIs(parameter, procedure.Namespace.Elements()["p"]) + self.assertIs(parameter, procedure.Namespace.Elements()["q"]) + + def test_GenericAndParameterShareTheNamespace(self) -> None: + generic = _generic("g") + parameter = ParameterVariableInterfaceItem(("p", ), Mode.In, _subtypeSymbol()) + procedure = Procedure("helper", genericItems=[generic], parameterItems=[parameter]) + procedure.IndexDeclaredItems() + + self.assertEqual({"g", "p"}, set(procedure.Namespace.Elements().keys())) diff --git a/tests/unit/Namespace/Namespace.py b/tests/unit/Namespace/Namespace.py new file mode 100644 index 000000000..57ab4ff01 --- /dev/null +++ b/tests/unit/Namespace/Namespace.py @@ -0,0 +1,350 @@ +# ==================================================================================================================== # +# __ ___ _ ____ _ __ __ _ _ # +# _ __ _ \ \ / / | | | _ \| | | \/ | ___ __| | ___| | # +# | '_ \| | | \ \ / /| |_| | | | | | | |\/| |/ _ \ / _` |/ _ \ | # +# | |_) | |_| |\ V / | _ | |_| | |___| | | | (_) | (_| | __/ | # +# | .__/ \__, | \_/ |_| |_|____/|_____|_| |_|\___/ \__,_|\___|_| # +# |_| |___/ # +# ==================================================================================================================== # +# Authors: # +# Patrick Lehmann # +# # +# License: # +# ==================================================================================================================== # +# Copyright 2026-2026 Patrick Lehmann - Boetzingen, Germany # +# # +# Licensed under the Apache License, Version 2.0 (the "License"); # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at # +# # +# http://www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +# # +# SPDX-License-Identifier: Apache-2.0 # +# ==================================================================================================================== # +# +""" +Standalone tests for ``pyVHDLModel.Namespace``. + +``Namespace`` isn't a ``ModelEntity`` and has no public insert method - ``_elements`` is populated by +``IndexDeclaredItems`` as design units are analyzed. These tests therefore seed ``_elements`` directly, +keyed by *normalized* identifier exactly as the ``Find*`` methods look them up, so the resolution logic +can be exercised without building a whole design. +""" +from unittest import TestCase + +from pyVHDLModel.Base import Direction, SimpleRange +from pyVHDLModel.DesignUnit import Component +from pyVHDLModel.Expression import IntegerLiteral +from pyVHDLModel.Name import SimpleName +from pyVHDLModel.Namespace import ExtendedKeyError, Namespace +from pyVHDLModel.Object import Constant, Signal, Variable +from pyVHDLModel.Symbol import ( + ComponentInstantiationSymbol, + PossibleReference, + SignalSymbol, + SimpleSubtypeSymbol, + Symbol, + VariableSymbol, +) +from pyVHDLModel.Type import IntegerType, Subtype + + +if __name__ == "__main__": # pragma: no cover + print("ERROR: you called a testcase declaration file as an executable module.") + print("Use: 'python -m unitest '") + exit(1) + + +def _subtypeSymbol(name: str = "natural") -> SimpleSubtypeSymbol: + return SimpleSubtypeSymbol(SimpleName(name)) + + +def _integerType(identifier: str = "myInteger") -> IntegerType: + return IntegerType(identifier, SimpleRange(IntegerLiteral(0), IntegerLiteral(7), Direction.To)) + + +class Namespaces(TestCase): + """Construction and the parent/sub-namespace wiring.""" + + def test_Construction(self) -> None: + namespace = Namespace("architecture") + + self.assertEqual("architecture", namespace.Name) + self.assertIsNone(namespace.ParentNamespace) + self.assertEqual(0, len(namespace.SubNamespaces)) + self.assertEqual(0, len(namespace.Elements())) + + def test_ConstructionWithParent(self) -> None: + parent = Namespace("entity") + namespace = Namespace("architecture", parent) + + self.assertIs(parent, namespace.ParentNamespace) + # The constructor only stores the parent - it doesn't register the child. + self.assertEqual(0, len(parent.SubNamespaces)) + + def test_ParentNamespaceSetter_RegistersInParent(self) -> None: + parent = Namespace("entity") + namespace = Namespace("architecture") + namespace.ParentNamespace = parent + + self.assertIs(parent, namespace.ParentNamespace) + self.assertIs(namespace, parent.SubNamespaces["architecture"]) + + +class FindComponent(TestCase): + """``FindComponent`` - the reference implementation of the ``ExtendedKeyError`` chaining pattern.""" + + def test_Found(self) -> None: + namespace = Namespace("architecture") + component = Component("comp") + namespace._elements["comp"] = component + + self.assertIs(component, namespace.FindComponent(ComponentInstantiationSymbol(SimpleName("comp")))) + + def test_FoundViaNormalizedIdentifier(self) -> None: + namespace = Namespace("architecture") + component = Component("Comp") + namespace._elements["comp"] = component + + # Lookup is case-insensitive because it goes through the normalized identifier. + self.assertIs(component, namespace.FindComponent(ComponentInstantiationSymbol(SimpleName("COMP")))) + + def test_FoundButWrongKind_RaisesTypeError(self) -> None: + namespace = Namespace("architecture") + namespace._elements["comp"] = _integerType("comp") + + with self.assertRaises(TypeError) as context: + namespace.FindComponent(ComponentInstantiationSymbol(SimpleName("comp"))) + + self.assertIn("not a component", str(context.exception)) + # The note reports what was actually found, so the message needn't carry the type. + self.assertIn("Got type 'pyVHDLModel.Type.IntegerType'.", context.exception.__notes__) + + def test_NotFoundWithoutParent_RaisesExtendedKeyError(self) -> None: + namespace = Namespace("architecture") + + with self.assertRaises(ExtendedKeyError) as context: + namespace.FindComponent(ComponentInstantiationSymbol(SimpleName("comp"))) + + self.assertEqual("comp", context.exception.key) + self.assertEqual((namespace, ), context.exception.searchedNamespaces) + + def test_NotFoundLocally_FoundInParent(self) -> None: + parent = Namespace("entity") + namespace = Namespace("architecture", parent) + component = Component("comp") + parent._elements["comp"] = component + + self.assertIs(component, namespace.FindComponent(ComponentInstantiationSymbol(SimpleName("comp")))) + + def test_NotFoundAnywhere_ChainsSearchedNamespaces(self) -> None: + outer = Namespace("library") + middle = Namespace("entity", outer) + inner = Namespace("architecture", middle) + + with self.assertRaises(ExtendedKeyError) as context: + inner.FindComponent(ComponentInstantiationSymbol(SimpleName("comp"))) + + exception = context.exception + + # Namespaces accumulate innermost-first as the walk unwinds. + self.assertEqual((inner, middle, outer), exception.searchedNamespaces) + self.assertEqual("comp", exception.key) + self.assertIn("architecture, entity, library", str(exception)) + + # Each level re-raises `from` the level above it. + self.assertIsInstance(exception.__cause__, ExtendedKeyError) + + +class FindSubtype(TestCase): + """``FindSubtype`` - resolves both subtypes and full types, filtered by the symbol's possible references.""" + + def test_FoundSubtype(self) -> None: + namespace = Namespace("package") + subtype = Subtype("byte", _subtypeSymbol()) + namespace._elements["byte"] = subtype + + symbol = Symbol(SimpleName("byte"), PossibleReference.Subtype) + + self.assertIs(subtype, namespace.FindSubtype(symbol)) + + def test_FoundFullType(self) -> None: + namespace = Namespace("package") + integerType = _integerType("nibble") + namespace._elements["nibble"] = integerType + + symbol = Symbol(SimpleName("nibble"), PossibleReference.Type) + + self.assertIs(integerType, namespace.FindSubtype(symbol)) + + def test_FoundSubtypeButNotExpected_RaisesTypeError(self) -> None: + namespace = Namespace("package") + namespace._elements["byte"] = Subtype("byte", _subtypeSymbol()) + + symbol = Symbol(SimpleName("byte"), PossibleReference.Signal) + + with self.assertRaises(TypeError) as context: + namespace.FindSubtype(symbol) + + self.assertIn("was not expected", str(context.exception)) + # Two notes: what was found, and what the symbol would have accepted. + self.assertIn("Got type 'pyVHDLModel.Type.Subtype'.", context.exception.__notes__) + self.assertIn("Expected one of: PossibleReference.Signal.", context.exception.__notes__) + + def test_FoundTypeButNotExpected_RaisesTypeError(self) -> None: + namespace = Namespace("package") + namespace._elements["nibble"] = _integerType("nibble") + + symbol = Symbol(SimpleName("nibble"), PossibleReference.Signal) + + with self.assertRaises(TypeError) as context: + namespace.FindSubtype(symbol) + + self.assertIn("was not expected", str(context.exception)) + + def test_FoundButWrongKind_RaisesTypeError(self) -> None: + namespace = Namespace("package") + namespace._elements["sig"] = Signal(("sig", ), _subtypeSymbol()) + + symbol = Symbol(SimpleName("sig"), PossibleReference.Subtype) + + with self.assertRaises(TypeError) as context: + namespace.FindSubtype(symbol) + + self.assertIn("not a type or subtype", str(context.exception)) + + def test_NotFoundWithoutParent_RaisesExtendedKeyError(self) -> None: + namespace = Namespace("package") + + symbol = Symbol(SimpleName("byte"), PossibleReference.Subtype) + + with self.assertRaises(ExtendedKeyError) as context: + namespace.FindSubtype(symbol) + + exception = context.exception + self.assertEqual("byte", exception.key) + self.assertEqual((namespace, ), exception.searchedNamespaces) + self.assertIn("Subtype 'byte' not found", str(exception)) + + def test_NotFoundLocally_FoundInParent(self) -> None: + parent = Namespace("package") + namespace = Namespace("architecture", parent) + subtype = Subtype("byte", _subtypeSymbol()) + parent._elements["byte"] = subtype + + symbol = Symbol(SimpleName("byte"), PossibleReference.Subtype) + + self.assertIs(subtype, namespace.FindSubtype(symbol)) + + def test_NotFoundAnywhere_ChainsSearchedNamespaces(self) -> None: + outer = Namespace("library") + middle = Namespace("package", outer) + inner = Namespace("architecture", middle) + + symbol = Symbol(SimpleName("byte"), PossibleReference.Subtype) + + with self.assertRaises(ExtendedKeyError) as context: + inner.FindSubtype(symbol) + + exception = context.exception + self.assertEqual((inner, middle, outer), exception.searchedNamespaces) + self.assertIn("architecture, package, library", str(exception)) + self.assertIsInstance(exception.__cause__, ExtendedKeyError) + + +class FindObject(TestCase): + """``FindObject`` - resolves signals, constants and variables.""" + + def test_FoundSignal(self) -> None: + namespace = Namespace("architecture") + signal = Signal(("clk", ), _subtypeSymbol()) + namespace._elements["clk"] = signal + + self.assertIs(signal, namespace.FindObject(SignalSymbol(SimpleName("clk")))) + + def test_FoundSignalViaSignalAttribute(self) -> None: + namespace = Namespace("architecture") + signal = Signal(("clk", ), _subtypeSymbol()) + namespace._elements["clk"] = signal + + # A signal attribute (e.g. `clk'event`) resolves to the signal itself. + symbol = Symbol(SimpleName("clk"), PossibleReference.SignalAttribute) + + self.assertIs(signal, namespace.FindObject(symbol)) + + def test_FoundConstant(self) -> None: + namespace = Namespace("architecture") + constant = Constant(("width", ), _subtypeSymbol()) + namespace._elements["width"] = constant + + symbol = Symbol(SimpleName("width"), PossibleReference.Constant) + + self.assertIs(constant, namespace.FindObject(symbol)) + + def test_FoundVariable(self) -> None: + namespace = Namespace("process") + variable = Variable(("index", ), _subtypeSymbol()) + namespace._elements["index"] = variable + + self.assertIs(variable, namespace.FindObject(VariableSymbol(SimpleName("index")))) + + def test_FoundButNotExpected_RaisesTypeError(self) -> None: + namespace = Namespace("architecture") + namespace._elements["clk"] = Signal(("clk", ), _subtypeSymbol()) + + symbol = Symbol(SimpleName("clk"), PossibleReference.Constant) + + with self.assertRaises(TypeError) as context: + namespace.FindObject(symbol) + + self.assertIn("was not expected", str(context.exception)) + + def test_FoundButWrongKind_RaisesTypeError(self) -> None: + namespace = Namespace("architecture") + namespace._elements["byte"] = Subtype("byte", _subtypeSymbol()) + + with self.assertRaises(TypeError) as context: + namespace.FindObject(SignalSymbol(SimpleName("byte"))) + + # Regression: this branch used to report "not a type or subtype", copy-pasted from FindSubtype. + self.assertIn("not an object", str(context.exception)) + self.assertIn("Got type 'pyVHDLModel.Type.Subtype'.", context.exception.__notes__) + + def test_NotFoundWithoutParent_RaisesExtendedKeyError(self) -> None: + namespace = Namespace("architecture") + + with self.assertRaises(ExtendedKeyError) as context: + namespace.FindObject(SignalSymbol(SimpleName("clk"))) + + exception = context.exception + self.assertEqual("clk", exception.key) + self.assertEqual((namespace, ), exception.searchedNamespaces) + # Regression: this message used to say "Subtype 'clk' not found". + self.assertIn("Object 'clk' not found", str(exception)) + + def test_NotFoundLocally_FoundInParent(self) -> None: + parent = Namespace("entity") + namespace = Namespace("architecture", parent) + signal = Signal(("clk", ), _subtypeSymbol()) + parent._elements["clk"] = signal + + self.assertIs(signal, namespace.FindObject(SignalSymbol(SimpleName("clk")))) + + def test_NotFoundAnywhere_ChainsSearchedNamespaces(self) -> None: + outer = Namespace("library") + middle = Namespace("entity", outer) + inner = Namespace("architecture", middle) + + with self.assertRaises(ExtendedKeyError) as context: + inner.FindObject(SignalSymbol(SimpleName("clk"))) + + exception = context.exception + self.assertEqual((inner, middle, outer), exception.searchedNamespaces) + self.assertIn("architecture, entity, library", str(exception)) + self.assertIsInstance(exception.__cause__, ExtendedKeyError) diff --git a/tests/unit/Namespace/SequentialRegions.py b/tests/unit/Namespace/SequentialRegions.py new file mode 100644 index 000000000..b76ec2bea --- /dev/null +++ b/tests/unit/Namespace/SequentialRegions.py @@ -0,0 +1,235 @@ +# ==================================================================================================================== # +# __ ___ _ ____ _ __ __ _ _ # +# _ __ _ \ \ / / | | | _ \| | | \/ | ___ __| | ___| | # +# | '_ \| | | \ \ / /| |_| | | | | | | |\/| |/ _ \ / _` |/ _ \ | # +# | |_) | |_| |\ V / | _ | |_| | |___| | | | (_) | (_| | __/ | # +# | .__/ \__, | \_/ |_| |_|____/|_____|_| |_|\___/ \__,_|\___|_| # +# |_| |___/ # +# ==================================================================================================================== # +# Authors: # +# Patrick Lehmann # +# # +# License: # +# ==================================================================================================================== # +# Copyright 2026-2026 Patrick Lehmann - Boetzingen, Germany # +# # +# Licensed under the Apache License, Version 2.0 (the "License"); # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at # +# # +# http://www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +# # +# SPDX-License-Identifier: Apache-2.0 # +# ==================================================================================================================== # +# +""" +Namespaces of *sequential* declaration regions: process statements and subprogram bodies. + +VHDL's ``process_declarative_item`` and ``subprogram_declarative_item`` rules are identical, so both use +``SequentialDeclarationRegionMixin``. Unlike a concurrent region it can declare a **variable**, which is +the usual thing to hide an outer signal with. +""" +from unittest import TestCase + +from pyVHDLModel.Concurrent import ConcurrentBlockStatement, ProcessStatement +from pyVHDLModel.DesignUnit import Architecture +from pyVHDLModel.Name import SimpleName +from pyVHDLModel.Namespace import ExtendedKeyError +from pyVHDLModel.Object import Constant, Signal, Variable +from pyVHDLModel.Subprogram import Function, Procedure +from pyVHDLModel.Symbol import ( + EntitySymbol, + PossibleReference, + SignalSymbol, + SimpleSubtypeSymbol, + Symbol, + VariableSymbol, +) +from pyVHDLModel.Type import IntegerType + + +if __name__ == "__main__": # pragma: no cover + print("ERROR: you called a testcase declaration file as an executable module.") + print("Use: 'python -m unitest '") + exit(1) + + +def _subtypeSymbol() -> SimpleSubtypeSymbol: + return SimpleSubtypeSymbol(SimpleName("natural")) + + +def _variable(identifier: str) -> Variable: + return Variable((identifier, ), _subtypeSymbol()) + + +def _signal(identifier: str) -> Signal: + return Signal((identifier, ), _subtypeSymbol()) + + +def _architecture(*declaredItems) -> Architecture: + architecture = Architecture("rtl", EntitySymbol(SimpleName("ent")), declaredItems=list(declaredItems)) + architecture.IndexDeclaredItems() + + return architecture + + +class ProcessNamespaces(TestCase): + def test_NamespaceIsNamedAfterTheLabel(self) -> None: + process = ProcessStatement("proc") + + self.assertEqual("proc", process.Namespace.Name) + + def test_UnlabelledProcessHasAnUnnamedNamespace(self) -> None: + process = ProcessStatement() + + self.assertIsNone(process.Namespace.Name) + + def test_IndexDeclaredItemsPopulatesVariables(self) -> None: + """A variable is exactly what a concurrent region can *not* declare.""" + variable = _variable("counter") + process = ProcessStatement("proc", declaredItems=[variable]) + process.IndexDeclaredItems() + + self.assertIs(variable, process.Variables["counter"]) + self.assertIs(variable, process.Namespace.Elements()["counter"]) + + def test_IndexDeclaredItemsPopulatesEveryKind(self) -> None: + integerType = IntegerType("nibble", None) + constant = Constant(("width", ), _subtypeSymbol()) + variable = _variable("index") + nestedProcedure = Procedure("helper") + + process = ProcessStatement("proc", declaredItems=[integerType, constant, variable, nestedProcedure]) + process.IndexDeclaredItems() + + self.assertIs(integerType, process.Types["nibble"]) + self.assertIs(constant, process.Constants["width"]) + self.assertIs(variable, process.Variables["index"]) + self.assertIs(nestedProcedure, process.Procedures["helper"][0]) + self.assertEqual(4, len(process.Namespace.Elements())) + + def test_NamespaceNestsInsideTheArchitecture(self) -> None: + architecture = _architecture() + process = ProcessStatement("proc") + process.Parent = architecture + + self.assertIs(architecture._namespace, process.Namespace.ParentNamespace) + + def test_ProcessVariableHidesArchitectureSignal(self) -> None: + architectureSignal = _signal("x") + architecture = _architecture(architectureSignal) + + processVariable = _variable("x") + process = ProcessStatement("proc", declaredItems=[processVariable]) + process.Parent = architecture + process.IndexDeclaredItems() + + self.assertIs(processVariable, process.Namespace.FindObject(VariableSymbol(SimpleName("x")))) + # Hiding is one-directional. + self.assertIs(architectureSignal, architecture._namespace.FindObject(SignalSymbol(SimpleName("x")))) + + def test_ProcessInheritsArchitectureDeclaration(self) -> None: + architectureSignal = _signal("clk") + architecture = _architecture(architectureSignal) + + process = ProcessStatement("proc") + process.Parent = architecture + + self.assertIs(architectureSignal, process.Namespace.FindObject(SignalSymbol(SimpleName("clk")))) + + def test_ProcessInsideABlockNestsInsideTheBlock(self) -> None: + architecture = _architecture() + block = ConcurrentBlockStatement("blk") + block.Parent = architecture + block.IndexDeclaredItems() + + process = ProcessStatement("proc") + process.Parent = block + + self.assertIs(block._namespace, process.Namespace.ParentNamespace) + + +class SubprogramNamespaces(TestCase): + def test_NamespaceIsNamedAfterTheIdentifier(self) -> None: + procedure = Procedure("DoIt") + + self.assertEqual("doit", procedure.Namespace.Name) + + def test_IndexDeclaredItemsPopulatesVariables(self) -> None: + variable = _variable("temp") + function = Function("compute", _subtypeSymbol(), declaredItems=[variable]) + function.IndexDeclaredItems() + + self.assertIs(variable, function.Variables["temp"]) + self.assertIs(variable, function.Namespace.Elements()["temp"]) + + def test_NamespaceNestsInsideTheArchitecture(self) -> None: + architecture = _architecture() + procedure = Procedure("helper") + procedure.Parent = architecture + + self.assertIs(architecture._namespace, procedure.Namespace.ParentNamespace) + + def test_SubprogramVariableHidesArchitectureSignal(self) -> None: + architectureSignal = _signal("x") + architecture = _architecture(architectureSignal) + + subprogramVariable = _variable("x") + procedure = Procedure("helper", declaredItems=[subprogramVariable]) + procedure.Parent = architecture + procedure.IndexDeclaredItems() + + self.assertIs(subprogramVariable, procedure.Namespace.FindObject(VariableSymbol(SimpleName("x")))) + self.assertIs(architectureSignal, architecture._namespace.FindObject(SignalSymbol(SimpleName("x")))) + + def test_SubprogramInheritsArchitectureDeclaration(self) -> None: + constant = Constant(("width", ), _subtypeSymbol()) + architecture = _architecture(constant) + + procedure = Procedure("helper") + procedure.Parent = architecture + + symbol = Symbol(SimpleName("width"), PossibleReference.Constant) + + self.assertIs(constant, procedure.Namespace.FindObject(symbol)) + + def test_NestedSubprogramNestsInsideTheOuterSubprogram(self) -> None: + """A subprogram is itself a sequential declaration region, so subprograms nest.""" + outerVariable = _variable("outer") + inner = Procedure("inner") + outer = Procedure("outer", declaredItems=[outerVariable, inner]) + outer.IndexDeclaredItems() + + self.assertIs(outer._namespace, inner.Namespace.ParentNamespace) + self.assertIs(outerVariable, inner.Namespace.FindObject(VariableSymbol(SimpleName("outer")))) + + def test_NestedSubprogramVariableHidesTheOuterOne(self) -> None: + outerVariable = _variable("x") + innerVariable = _variable("x") + inner = Procedure("inner", declaredItems=[innerVariable]) + outer = Procedure("outer", declaredItems=[outerVariable, inner]) + outer.IndexDeclaredItems() + inner.IndexDeclaredItems() + + self.assertIs(innerVariable, inner.Namespace.FindObject(VariableSymbol(SimpleName("x")))) + self.assertIs(outerVariable, outer.Namespace.FindObject(VariableSymbol(SimpleName("x")))) + + def test_UnresolvedNameReportsTheWholeChain(self) -> None: + architecture = _architecture() + procedure = Procedure("helper") + procedure.Parent = architecture + process = ProcessStatement("proc") + process.Parent = architecture + + with self.assertRaises(ExtendedKeyError) as context: + procedure.Namespace.FindObject(VariableSymbol(SimpleName("missing"))) + + # The subprogram's namespace and the architecture's are both reported as searched. + self.assertEqual(2, len(context.exception.searchedNamespaces)) + self.assertIn("helper, rtl", str(context.exception)) diff --git a/tests/unit/Namespace/Statements.py b/tests/unit/Namespace/Statements.py new file mode 100644 index 000000000..8356ef5ed --- /dev/null +++ b/tests/unit/Namespace/Statements.py @@ -0,0 +1,118 @@ +# ==================================================================================================================== # +# __ ___ _ ____ _ __ __ _ _ # +# _ __ _ \ \ / / | | | _ \| | | \/ | ___ __| | ___| | # +# | '_ \| | | \ \ / /| |_| | | | | | | |\/| |/ _ \ / _` |/ _ \ | # +# | |_) | |_| |\ V / | _ | |_| | |___| | | | (_) | (_| | __/ | # +# | .__/ \__, | \_/ |_| |_|____/|_____|_| |_|\___/ \__,_|\___|_| # +# |_| |___/ # +# ==================================================================================================================== # +# Authors: # +# Patrick Lehmann # +# # +# License: # +# ==================================================================================================================== # +# Copyright 2026-2026 Patrick Lehmann - Boetzingen, Germany # +# # +# Licensed under the Apache License, Version 2.0 (the "License"); # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at # +# # +# http://www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +# # +# SPDX-License-Identifier: Apache-2.0 # +# ==================================================================================================================== # +# +""" +Namespaces created by concurrent statements: blocks and the three generate statements. + +Namespace-connection happens in the ``Parent`` property *setter* override, not in the constructor - +passing ``parent=`` to ``__init__`` assigns ``self._parent`` directly and never reaches the overridden +setter. Every test here therefore assigns ``.Parent`` after construction. +""" +from unittest import TestCase + +from pyVHDLModel.Base import Direction, SimpleRange +from pyVHDLModel.Concurrent import ( + CaseGenerateStatement, + ConcurrentBlockStatement, + ElseGenerateBranch, + ElsifGenerateBranch, + ForGenerateStatement, + GenerateCase, + IfGenerateBranch, + IfGenerateStatement, + IndexedGenerateChoice, +) +from pyVHDLModel.DesignUnit import Architecture +from pyVHDLModel.Expression import IntegerLiteral +from pyVHDLModel.Name import SimpleName +from pyVHDLModel.Symbol import EntitySymbol + + +if __name__ == "__main__": # pragma: no cover + print("ERROR: you called a testcase declaration file as an executable module.") + print("Use: 'python -m unitest '") + exit(1) + + +def _entitySymbol(name: str = "e") -> EntitySymbol: + return EntitySymbol(SimpleName(name)) + + +class BlockStatements(TestCase): + def test_NestedBlock_ConnectsNamespaceOnParentAssignment(self) -> None: + outer = ConcurrentBlockStatement("outer") + inner = ConcurrentBlockStatement("inner") + inner.Parent = outer + + self.assertIs(outer, inner.Parent) + self.assertIs(outer._namespace, inner._namespace.ParentNamespace) + + +class IfGenerateStatements(TestCase): + def test_AllBranchesConnectToTheArchitecture(self) -> None: + """Also a regression test: the fallback that names the if-branch's namespace after the + statement's own label (for the common, unlabelled-branch case) compared + ``namespace._name == ""``, but an unlabelled ``GenerateBranch`` always constructs its + namespace with ``_normalizedAlternativeLabel``, which defaults to ``None`` - not ``""`` - so + the fallback never actually fired. Fixed to compare against ``None``.""" + architecture = Architecture("rtl", _entitySymbol()) + ifBranch = IfGenerateBranch(IntegerLiteral(1)) + elsifBranch = ElsifGenerateBranch(IntegerLiteral(2)) + elseBranch = ElseGenerateBranch() + statement = IfGenerateStatement("gen", ifBranch, [elsifBranch], elseBranch) + + statement.Parent = architecture + + self.assertIs(architecture._namespace, ifBranch._namespace.ParentNamespace) + self.assertIs(architecture._namespace, elsifBranch._namespace.ParentNamespace) + self.assertIs(architecture._namespace, elseBranch._namespace.ParentNamespace) + self.assertEqual("gen", ifBranch._namespace._name) + + +class CaseGenerateStatements(TestCase): + def test_CaseConnectsToTheArchitecture(self) -> None: + architecture = Architecture("rtl", _entitySymbol()) + case = GenerateCase([IndexedGenerateChoice(IntegerLiteral(0))]) + statement = CaseGenerateStatement("gen", IntegerLiteral(0), [case]) + + statement.Parent = architecture + + self.assertIs(architecture._namespace, case._namespace.ParentNamespace) + + +class ForGenerateStatements(TestCase): + def test_ConnectsToTheArchitecture(self) -> None: + architecture = Architecture("rtl", _entitySymbol()) + rng = SimpleRange(IntegerLiteral(0), IntegerLiteral(3), Direction.To) + statement = ForGenerateStatement("gen", "i", rng) + + statement.Parent = architecture + + self.assertIs(architecture._namespace, statement._namespace.ParentNamespace) diff --git a/tests/unit/Namespace/__init__.py b/tests/unit/Namespace/__init__.py new file mode 100644 index 000000000..fcc45fa67 --- /dev/null +++ b/tests/unit/Namespace/__init__.py @@ -0,0 +1,37 @@ +# ==================================================================================================================== # +# __ ___ _ ____ _ __ __ _ _ # +# _ __ _ \ \ / / | | | _ \| | | \/ | ___ __| | ___| | # +# | '_ \| | | \ \ / /| |_| | | | | | | |\/| |/ _ \ / _` |/ _ \ | # +# | |_) | |_| |\ V / | _ | |_| | |___| | | | (_) | (_| | __/ | # +# | .__/ \__, | \_/ |_| |_|____/|_____|_| |_|\___/ \__,_|\___|_| # +# |_| |___/ # +# ==================================================================================================================== # +# Authors: # +# Patrick Lehmann # +# # +# License: # +# ==================================================================================================================== # +# Copyright 2026-2026 Patrick Lehmann - Boetzingen, Germany # +# # +# Licensed under the Apache License, Version 2.0 (the "License"); # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at # +# # +# http://www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +# # +# SPDX-License-Identifier: Apache-2.0 # +# ==================================================================================================================== # +# +""" +Namespace tests: multiple language entities composed into a hierarchy. + +Where the ``Instantiation`` package constructs one entity at a time, these tests check what emerges from +the *combination*: how items are resolved across nested namespaces, and how namespaces get connected as +entities are attached to their parents. +"""