# =============================================================================
#               ____ _   _ ____  _          _
#  _ __  _   _ / ___| | | |  _ \| |      __| | ___  _ __ ___
# | '_ \| | | | |  _| |_| | | | | |     / _` |/ _ \| '_ ` _ \
# | |_) | |_| | |_| |  _  | |_| | |___ | (_| | (_) | | | | | |
# | .__/ \__, |\____|_| |_|____/|_____(_)__,_|\___/|_| |_| |_|
# |_|    |___/
# =============================================================================
# Authors:
#   Patrick Lehmann
#
# Package module:   DOM: IIR to *** translations.
#
# License:
# ============================================================================
#  Copyright (C) 2019-2021 Tristan Gingold
#
#  This program is free software: you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation, either version 2 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program.  If not, see <gnu.org/licenses>.
#
# SPDX-License-Identifier: GPL-2.0-or-later
# ============================================================================
from typing import List, Generator, Type

from pyTooling.Decorators import export

from pyGHDL.dom.Sequential import (
    IfStatement,
    ForLoopStatement,
    CaseStatement,
    SequentialReportStatement,
    SequentialAssertStatement,
    WaitStatement,
    SequentialSimpleSignalAssignment,
    NullStatement,
    SequentialProcedureCall,
)
from pyVHDLModel.SyntaxModel import (
    ConstraintUnion,
    Direction,
    ExpressionUnion,
    SubtypeOrSymbol,
    BaseType,
    GenericInterfaceItem,
    PortInterfaceItem,
    ParameterInterfaceItem,
    ModelEntity,
    Name,
    ConcurrentStatement,
    SequentialStatement,
    AssociationItem,
)

from pyGHDL.libghdl import utils, name_table
from pyGHDL.libghdl._types import Iir
from pyGHDL.libghdl.vhdl import nodes
from pyGHDL.dom import Position, DOMException
from pyGHDL.dom._Utils import (
    GetNameOfNode,
    GetIirKindOfNode,
)
from pyGHDL.dom.Names import (
    SimpleName,
    SelectedName,
    AttributeName,
    ParenthesisName,
    AllName,
    OpenName,
)
from pyGHDL.dom.Symbol import (
    SimpleObjectOrFunctionCallSymbol,
    SimpleSubtypeSymbol,
    ConstrainedCompositeSubtypeSymbol,
    IndexedObjectOrFunctionCallSymbol,
    ConstrainedScalarSubtypeSymbol,
)
from pyGHDL.dom.Type import (
    IntegerType,
    Subtype,
    ArrayType,
    RecordType,
    EnumeratedType,
    AccessType,
    ProtectedType,
    ProtectedTypeBody,
    FileType,
    PhysicalType,
    IncompleteType,
)
from pyGHDL.dom.Range import Range
from pyGHDL.dom.Literal import (
    IntegerLiteral,
    CharacterLiteral,
    FloatingPointLiteral,
    StringLiteral,
    PhysicalIntegerLiteral,
    PhysicalFloatingLiteral,
    NullLiteral,
)
from pyGHDL.dom.Object import Variable
from pyGHDL.dom.Expression import (
    SubtractionExpression,
    AdditionExpression,
    MultiplyExpression,
    DivisionExpression,
    InverseExpression,
    ExponentiationExpression,
    Aggregate,
    NegationExpression,
    ParenthesisExpression,
    ConcatenationExpression,
    QualifiedExpression,
    ModuloExpression,
    RemainderExpression,
    AndExpression,
    NandExpression,
    OrExpression,
    NorExpression,
    XorExpression,
    XnorExpression,
    EqualExpression,
    UnequalExpression,
    LessThanExpression,
    GreaterThanExpression,
    GreaterEqualExpression,
    LessEqualExpression,
    ShiftLeftLogicExpression,
    ShiftRightLogicExpression,
    ShiftLeftArithmeticExpression,
    ShiftRightArithmeticExpression,
    RotateLeftExpression,
    RotateRightExpression,
    RangeExpression,
    QualifiedExpressionAllocation,
    SubtypeAllocation,
    IdentityExpression,
    AbsoluteExpression,
    MatchingGreaterEqualExpression,
    MatchingEqualExpression,
    MatchingUnequalExpression,
    MatchingLessThanExpression,
    MatchingLessEqualExpression,
    MatchingGreaterThanExpression,
)
from pyGHDL.dom.Concurrent import (
    ConcurrentBlockStatement,
    EntityInstantiation,
    ConfigurationInstantiation,
    ComponentInstantiation,
    ProcessStatement,
    IfGenerateStatement,
    ForGenerateStatement,
    CaseGenerateStatement,
    ConcurrentSimpleSignalAssignment,
    ConcurrentProcedureCall,
    GenericAssociationItem,
    PortAssociationItem,
    ParameterAssociationItem,
    ConcurrentAssertStatement,
)
from pyGHDL.dom.Subprogram import Function, Procedure
from pyGHDL.dom.Misc import Alias
from pyGHDL.dom.PSL import DefaultClock


__all__ = []


@export
def GetNameFromNode(node: Iir) -> Name:
    kind = GetIirKindOfNode(node)
    if kind == nodes.Iir_Kind.Simple_Name:
        name = GetNameOfNode(node)
        return SimpleName(node, name)
    elif kind == nodes.Iir_Kind.Selected_Name:
        name = GetNameOfNode(node)
        prefixName = GetNameFromNode(nodes.Get_Prefix(node))
        return SelectedName(node, name, prefixName)
    elif kind == nodes.Iir_Kind.Attribute_Name:
        name = GetNameOfNode(node)
        prefixName = GetNameFromNode(nodes.Get_Prefix(node))
        return AttributeName(node, name, prefixName)
    elif kind == nodes.Iir_Kind.Parenthesis_Name:
        prefixName = GetNameFromNode(nodes.Get_Prefix(node))
        associations = GetAssociations(node)

        return ParenthesisName(node, prefixName, associations)
    elif kind == nodes.Iir_Kind.Selected_By_All_Name:
        prefixName = GetNameFromNode(nodes.Get_Prefix(node))
        return AllName(node, prefixName)
    else:
        raise DOMException("Unknown name kind '{kind}'".format(kind=kind.name))


def GetAssociations(node: Iir) -> List:
    associations = []
    for item in utils.chain_iter(nodes.Get_Association_Chain(node)):
        kind = GetIirKindOfNode(item)

        if kind in (
            nodes.Iir_Kind.Association_Element_By_Expression,
            nodes.Iir_Kind.Association_Element_By_Name,
        ):
            actual = nodes.Get_Actual(item)
            expr = GetExpressionFromNode(actual)

            associations.append(expr)
        else:
            raise DOMException(
                "Unknown association kind '{kind}' in array index/slice or function call '{node}'.".format(
                    kind=kind.name, node=node
                )
            )

    return associations


@export
def GetArrayConstraintsFromSubtypeIndication(
    subtypeIndication: Iir,
) -> List[ConstraintUnion]:
    constraints = []
    for constraint in utils.flist_iter(nodes.Get_Index_Constraint_List(subtypeIndication)):
        constraintKind = GetIirKindOfNode(constraint)
        if constraintKind == nodes.Iir_Kind.Range_Expression:
            constraints.append(RangeExpression.parse(constraint))
        elif constraintKind in (
            nodes.Iir_Kind.Simple_Name,
            nodes.Iir_Kind.Parenthesis_Name,
            nodes.Iir_Kind.Selected_Name,
            nodes.Iir_Kind.Attribute_Name,
        ):
            constraints.append(GetNameFromNode(constraint))
        else:
            position = Position.parse(constraint)
            raise DOMException(
                "Unknown constraint kind '{kind}' for constraint '{constraint}' in subtype indication '{indication}' at {file}:{line}:{column}.".format(
                    kind=constraintKind.name,
                    constraint=constraint,
                    indication=subtypeIndication,
                    file=position.Filename,
                    line=position.Line,
                    column=position.Column,
                )
            )

    return constraints


@export
def GetTypeFromNode(node: Iir) -> BaseType:
    typeName = GetNameOfNode(node)
    typeDefinition = nodes.Get_Type_Definition(node)
    if typeDefinition is nodes.Null_Iir:
        return IncompleteType(node, typeName)

    kind = GetIirKindOfNode(typeDefinition)
    if kind == nodes.Iir_Kind.Enumeration_Type_Definition:
        return EnumeratedType.parse(typeName, typeDefinition)
    elif kind == nodes.Iir_Kind.Array_Type_Definition:
        return ArrayType.parse(typeName, typeDefinition)
    elif kind == nodes.Iir_Kind.Record_Type_Definition:
        return RecordType.parse(typeName, typeDefinition)
    elif kind == nodes.Iir_Kind.Access_Type_Definition:
        return AccessType.parse(typeName, typeDefinition)
    elif kind == nodes.Iir_Kind.File_Type_Definition:
        return FileType.parse(typeName, typeDefinition)
    elif kind == nodes.Iir_Kind.Protected_Type_Declaration:
        return ProtectedType.parse(typeName, typeDefinition)
    else:
        position = Position.parse(typeDefinition)
        raise DOMException(
            "GetTypeFromNode: Unknown type definition kind '{kind}' for type '{name}' at {file}:{line}:{column}.".format(
                kind=kind.name,
                name=typeName,
                file=position.Filename,
                line=position.Line,
                column=position.Column,
            )
        )


@export
def GetAnonymousTypeFromNode(node: Iir) -> BaseType:
    typeName = GetNameOfNode(node)
    typeDefinition = nodes.Get_Type_Definition(node)
    if typeDefinition is nodes.Null_Iir:
        return IncompleteType(node, typeName)

    kind = GetIirKindOfNode(typeDefinition)
    if kind == nodes.Iir_Kind.Range_Expression:
        r = GetRangeFromNode(typeDefinition)
        return IntegerType(node, typeName, r)

    elif kind in (nodes.Iir_Kind.Attribute_Name, nodes.Iir_Kind.Parenthesis_Name):
        n = GetNameFromNode(typeDefinition)

        return IntegerType(node, typeName, n)
    elif kind == nodes.Iir_Kind.Physical_Type_Definition:
        return PhysicalType.parse(typeName, typeDefinition)

    elif kind == nodes.Iir_Kind.Array_Subtype_Definition:
        print("[NOT IMPLEMENTED] Array_Subtype_Definition")

        return ArrayType(typeDefinition, "????", [], None)
    else:
        position = Position.parse(typeDefinition)
        raise DOMException(
            "GetAnonymousTypeFromNode: Unknown type definition kind '{kind}' for type '{name}' at {file}:{line}:{column}.".format(
                kind=kind.name,
                name=typeName,
                file=position.Filename,
                line=position.Line,
                column=position.Column,
            )
        )


@export
def GetSubtypeIndicationFromNode(node: Iir, entity: str, name: str) -> SubtypeOrSymbol:
    subtypeIndicationNode = nodes.Get_Subtype_Indication(node)
    return GetSubtypeIndicationFromIndicationNode(subtypeIndicationNode, entity, name)


@export
def GetSubtypeIndicationFromIndicationNode(subtypeIndicationNode: Iir, entity: str, name: str) -> SubtypeOrSymbol:
    if subtypeIndicationNode is nodes.Null_Iir:
        raise ValueError("Parameter 'subtypeIndicationNode' is 'Null_Iir'.")

    kind = GetIirKindOfNode(subtypeIndicationNode)
    if kind in (
        nodes.Iir_Kind.Simple_Name,
        nodes.Iir_Kind.Selected_Name,
        nodes.Iir_Kind.Attribute_Name,
    ):
        return GetSimpleTypeFromNode(subtypeIndicationNode)
    elif kind == nodes.Iir_Kind.Subtype_Definition:
        return GetScalarConstrainedSubtypeFromNode(subtypeIndicationNode)
    elif kind == nodes.Iir_Kind.Array_Subtype_Definition:
        return GetCompositeConstrainedSubtypeFromNode(subtypeIndicationNode)
    else:
        raise DOMException(
            "Unknown kind '{kind}' for an subtype indication in a {entity} of `{name}`.".format(
                kind=kind.name, entity=entity, name=name
            )
        )


@export
def GetSimpleTypeFromNode(subtypeIndicationNode: Iir) -> SimpleSubtypeSymbol:
    subtypeName = GetNameFromNode(subtypeIndicationNode)
    return SimpleSubtypeSymbol(subtypeIndicationNode, subtypeName)


@export
def GetScalarConstrainedSubtypeFromNode(
    subtypeIndicationNode: Iir,
) -> ConstrainedScalarSubtypeSymbol:
    typeMark = nodes.Get_Subtype_Type_Mark(subtypeIndicationNode)
    typeMarkName = GetNameOfNode(typeMark)
    simpleTypeMark = SimpleName(typeMark, typeMarkName)
    rangeConstraint = nodes.Get_Range_Constraint(subtypeIndicationNode)
    r = GetRangeFromNode(rangeConstraint)
    return ConstrainedScalarSubtypeSymbol(subtypeIndicationNode, simpleTypeMark, r)


@export
def GetCompositeConstrainedSubtypeFromNode(
    subtypeIndicationNode: Iir,
) -> ConstrainedCompositeSubtypeSymbol:
    typeMark = nodes.Get_Subtype_Type_Mark(subtypeIndicationNode)
    typeMarkName = GetNameOfNode(typeMark)
    simpleTypeMark = SimpleName(typeMark, typeMarkName)

    constraints = GetArrayConstraintsFromSubtypeIndication(subtypeIndicationNode)
    return ConstrainedCompositeSubtypeSymbol(subtypeIndicationNode, simpleTypeMark, constraints)


@export
def GetSubtypeFromNode(subtypeNode: Iir) -> SubtypeOrSymbol:
    subtypeName = GetNameOfNode(subtypeNode)

    return Subtype(subtypeNode, subtypeName)


@export
def GetRangeFromNode(node: Iir) -> Range:
    direction = nodes.Get_Direction(node)
    leftBound = nodes.Get_Left_Limit_Expr(node)
    rightBound = nodes.Get_Right_Limit_Expr(node)

    return Range(
        GetExpressionFromNode(leftBound),
        GetExpressionFromNode(rightBound),
        Direction.DownTo if direction else Direction.To,
    )


__EXPRESSION_TRANSLATION = {
    nodes.Iir_Kind.Simple_Name: SimpleObjectOrFunctionCallSymbol,
    nodes.Iir_Kind.Selected_Name: IndexedObjectOrFunctionCallSymbol,
    nodes.Iir_Kind.Attribute_Name: IndexedObjectOrFunctionCallSymbol,
    nodes.Iir_Kind.Parenthesis_Name: IndexedObjectOrFunctionCallSymbol,
    nodes.Iir_Kind.Null_Literal: NullLiteral,
    nodes.Iir_Kind.Integer_Literal: IntegerLiteral,
    nodes.Iir_Kind.Floating_Point_Literal: FloatingPointLiteral,
    nodes.Iir_Kind.Physical_Int_Literal: PhysicalIntegerLiteral,
    nodes.Iir_Kind.Physical_Fp_Literal: PhysicalFloatingLiteral,
    nodes.Iir_Kind.Character_Literal: CharacterLiteral,
    nodes.Iir_Kind.String_Literal8: StringLiteral,
    nodes.Iir_Kind.Identity_Operator: IdentityExpression,
    nodes.Iir_Kind.Negation_Operator: NegationExpression,
    nodes.Iir_Kind.Absolute_Operator: AbsoluteExpression,
    nodes.Iir_Kind.Range_Expression: RangeExpression,
    nodes.Iir_Kind.Addition_Operator: AdditionExpression,
    nodes.Iir_Kind.Concatenation_Operator: ConcatenationExpression,
    nodes.Iir_Kind.Not_Operator: InverseExpression,
    nodes.Iir_Kind.Parenthesis_Expression: ParenthesisExpression,
    nodes.Iir_Kind.Substraction_Operator: SubtractionExpression,
    nodes.Iir_Kind.Multiplication_Operator: MultiplyExpression,
    nodes.Iir_Kind.Division_Operator: DivisionExpression,
    nodes.Iir_Kind.Modulus_Operator: ModuloExpression,
    nodes.Iir_Kind.Remainder_Operator: RemainderExpression,
    nodes.Iir_Kind.Exponentiation_Operator: ExponentiationExpression,
    nodes.Iir_Kind.And_Operator: AndExpression,
    nodes.Iir_Kind.Nand_Operator: NandExpression,
    nodes.Iir_Kind.Or_Operator: OrExpression,
    nodes.Iir_Kind.Nor_Operator: NorExpression,
    nodes.Iir_Kind.Xor_Operator: XorExpression,
    nodes.Iir_Kind.Xnor_Operator: XnorExpression,
    nodes.Iir_Kind.Equality_Operator: EqualExpression,
    nodes.Iir_Kind.Inequality_Operator: UnequalExpression,
    nodes.Iir_Kind.Less_Than_Operator: LessThanExpression,
    nodes.Iir_Kind.Less_Than_Or_Equal_Operator: LessEqualExpression,
    nodes.Iir_Kind.Greater_Than_Operator: GreaterThanExpression,
    nodes.Iir_Kind.Greater_Than_Or_Equal_Operator: GreaterEqualExpression,
    nodes.Iir_Kind.Match_Equality_Operator: MatchingEqualExpression,
    nodes.Iir_Kind.Match_Inequality_Operator: MatchingUnequalExpression,
    nodes.Iir_Kind.Match_Less_Than_Operator: MatchingLessThanExpression,
    nodes.Iir_Kind.Match_Less_Than_Or_Equal_Operator: MatchingLessEqualExpression,
    nodes.Iir_Kind.Match_Greater_Than_Operator: MatchingGreaterThanExpression,
    nodes.Iir_Kind.Match_Greater_Than_Or_Equal_Operator: MatchingGreaterEqualExpression,
    nodes.Iir_Kind.Sll_Operator: ShiftLeftLogicExpression,
    nodes.Iir_Kind.Srl_Operator: ShiftRightLogicExpression,
    nodes.Iir_Kind.Sla_Operator: ShiftLeftArithmeticExpression,
    nodes.Iir_Kind.Sra_Operator: ShiftRightArithmeticExpression,
    nodes.Iir_Kind.Rol_Operator: RotateLeftExpression,
    nodes.Iir_Kind.Ror_Operator: RotateRightExpression,
    nodes.Iir_Kind.Qualified_Expression: QualifiedExpression,
    nodes.Iir_Kind.Aggregate: Aggregate,
    nodes.Iir_Kind.Allocator_By_Subtype: SubtypeAllocation,
    nodes.Iir_Kind.Allocator_By_Expression: QualifiedExpressionAllocation,
}


@export
def GetExpressionFromNode(node: Iir) -> ExpressionUnion:
    kind = GetIirKindOfNode(node)

    try:
        cls = __EXPRESSION_TRANSLATION[kind]
    except KeyError:
        position = Position.parse(node)
        raise DOMException(
            "Unknown expression kind '{kind}' in expression '{expr}' at {file}:{line}:{column}.".format(
                kind=kind.name,
                expr=node,
                file=position.Filename,
                line=position.Line,
                column=position.Column,
            )
        )

    return cls.parse(node)


@export
def GetGenericsFromChainedNodes(
    nodeChain: Iir,
) -> Generator[GenericInterfaceItem, None, None]:
    from pyGHDL.dom.InterfaceItem import (
        GenericTypeInterfaceItem,
        GenericPackageInterfaceItem,
        GenericProcedureInterfaceItem,
        GenericFunctionInterfaceItem,
    )

    generic = nodeChain
    while generic != nodes.Null_Iir:
        kind = GetIirKindOfNode(generic)
        if kind == nodes.Iir_Kind.Interface_Constant_Declaration:
            from pyGHDL.dom.InterfaceItem import GenericConstantInterfaceItem

            genericConstant = GenericConstantInterfaceItem.parse(generic)

            # Lookahead for generics with multiple identifiers at once
            if nodes.Get_Has_Identifier_List(generic):
                nextNode = nodes.Get_Chain(generic)
                for nextGeneric in utils.chain_iter(nextNode):
                    # Consecutive identifiers are found, if the subtype indication is Null
                    if nodes.Get_Subtype_Indication(nextGeneric) == nodes.Null_Iir:
                        genericConstant.Identifiers.append(GetNameOfNode(nextGeneric))
                    else:
                        generic = nextGeneric
                        break

                    # The last consecutive identifiers has no Identifier_List flag
                    if not nodes.Get_Has_Identifier_List(nextGeneric):
                        generic = nodes.Get_Chain(nextGeneric)
                        break
                else:
                    generic = nodes.Null_Iir
            else:
                generic = nodes.Get_Chain(generic)

            yield genericConstant
            continue
        else:
            if kind == nodes.Iir_Kind.Interface_Type_Declaration:
                yield GenericTypeInterfaceItem.parse(generic)
            elif kind == nodes.Iir_Kind.Interface_Package_Declaration:
                yield GenericPackageInterfaceItem.parse(generic)
            elif kind == nodes.Iir_Kind.Interface_Procedure_Declaration:
                yield GenericProcedureInterfaceItem.parse(generic)
            elif kind == nodes.Iir_Kind.Interface_Function_Declaration:
                yield GenericFunctionInterfaceItem.parse(generic)
            else:
                position = Position.parse(generic)
                raise DOMException(
                    "Unknown generic kind '{kind}' in generic '{generic}' at {file}:{line}:{column}.".format(
                        kind=kind.name,
                        generic=generic,
                        file=position.Filename,
                        line=position.Line,
                        column=position.Column,
                    )
                )

        generic = nodes.Get_Chain(generic)


@export
def GetPortsFromChainedNodes(
    nodeChain: Iir,
) -> Generator[PortInterfaceItem, None, None]:

    port = nodeChain
    while port != nodes.Null_Iir:
        kind = GetIirKindOfNode(port)
        if kind == nodes.Iir_Kind.Interface_Signal_Declaration:
            from pyGHDL.dom.InterfaceItem import PortSignalInterfaceItem

            portSignal = PortSignalInterfaceItem.parse(port)

            # Lookahead for ports with multiple identifiers at once
            if nodes.Get_Has_Identifier_List(port):
                nextNode = nodes.Get_Chain(port)
                for nextPort in utils.chain_iter(nextNode):
                    # Consecutive identifiers are found, if the subtype indication is Null
                    if nodes.Get_Subtype_Indication(nextPort) == nodes.Null_Iir:
                        portSignal.Identifiers.append(GetNameOfNode(nextPort))
                    else:
                        port = nextPort
                        break

                    # The last consecutive identifiers has no Identifier_List flag
                    if not nodes.Get_Has_Identifier_List(nextPort):
                        port = nodes.Get_Chain(nextPort)
                        break
                else:
                    port = nodes.Null_Iir
            else:
                port = nodes.Get_Chain(port)

            yield portSignal
            continue
        else:
            position = Position.parse(port)
            raise DOMException(
                "Unknown port kind '{kind}' in port '{port}' at {file}:{line}:{column}.".format(
                    kind=kind.name,
                    port=port,
                    file=position.Filename,
                    line=position.Line,
                    column=position.Column,
                )
            )


@export
def GetParameterFromChainedNodes(
    nodeChain: Iir,
) -> Generator[ParameterInterfaceItem, None, None]:

    parameter = nodeChain
    while parameter != nodes.Null_Iir:
        kind = GetIirKindOfNode(parameter)
        if kind == nodes.Iir_Kind.Interface_Constant_Declaration:
            from pyGHDL.dom.InterfaceItem import ParameterConstantInterfaceItem

            param = ParameterConstantInterfaceItem.parse(parameter)
        elif kind == nodes.Iir_Kind.Interface_Variable_Declaration:
            from pyGHDL.dom.InterfaceItem import ParameterVariableInterfaceItem

            param = ParameterVariableInterfaceItem.parse(parameter)
        elif kind == nodes.Iir_Kind.Interface_Signal_Declaration:
            from pyGHDL.dom.InterfaceItem import ParameterSignalInterfaceItem

            param = ParameterSignalInterfaceItem.parse(parameter)
        elif kind == nodes.Iir_Kind.Interface_File_Declaration:
            from pyGHDL.dom.InterfaceItem import ParameterFileInterfaceItem

            param = ParameterFileInterfaceItem.parse(parameter)
        else:
            position = Position.parse(parameter)
            raise DOMException(
                "Unknown parameter kind '{kind}' in parameter '{param}' at {file}:{line}:{column}.".format(
                    kind=kind.name,
                    param=parameter,
                    file=position.Filename,
                    line=position.Line,
                    column=position.Column,
                )
            )

        # Lookahead for parameters with multiple identifiers at once
        if nodes.Get_Has_Identifier_List(parameter):
            nextNode = nodes.Get_Chain(parameter)
            for nextParameter in utils.chain_iter(nextNode):
                # Consecutive identifiers are found, if the subtype indication is Null
                if nodes.Get_Subtype_Indication(nextParameter) == nodes.Null_Iir:
                    param.Identifiers.append(GetNameOfNode(nextParameter))
                else:
                    parameter = nextParameter
                    break

                # The last consecutive identifiers has no Identifier_List flag
                if not nodes.Get_Has_Identifier_List(nextParameter):
                    parameter = nodes.Get_Chain(nextParameter)
                    break
            else:
                parameter = nodes.Null_Iir
        else:
            parameter = nodes.Get_Chain(parameter)

        yield param


def GetMapAspect(mapAspect: Iir, cls: Type, entity: str) -> Generator[AssociationItem, None, None]:
    for generic in utils.chain_iter(mapAspect):
        kind = GetIirKindOfNode(generic)
        if kind is nodes.Iir_Kind.Association_Element_By_Expression:
            formalNode = nodes.Get_Formal(generic)
            if formalNode is nodes.Null_Iir:
                formal = None
            else:
                formal = GetNameFromNode(formalNode)

            actual = GetExpressionFromNode(nodes.Get_Actual(generic))

            yield cls(generic, actual, formal)
        elif kind is nodes.Iir_Kind.Association_Element_Open:
            formalNode = nodes.Get_Formal(generic)
            if formalNode is nodes.Null_Iir:
                formal = None
            else:
                formal = GetNameFromNode(formalNode)

            yield cls(generic, OpenName(generic), formal)
        else:
            pos = Position.parse(generic)
            raise DOMException(
                "Unknown association kind '{kind}' in {entity} map at line {line}.".format(
                    kind=kind.name, entity=entity, line=pos.Line
                )
            )


def GetGenericMapAspect(
    genericMapAspect: Iir,
) -> Generator[GenericAssociationItem, None, None]:
    return GetMapAspect(genericMapAspect, GenericAssociationItem, "generic")


def GetPortMapAspect(portMapAspect: Iir) -> Generator[PortAssociationItem, None, None]:
    return GetMapAspect(portMapAspect, PortAssociationItem, "port")


def GetParameterMapAspect(
    parameterMapAspect: Iir,
) -> Generator[ParameterAssociationItem, None, None]:
    return GetMapAspect(parameterMapAspect, ParameterAssociationItem, "parameter")


def GetDeclaredItemsFromChainedNodes(nodeChain: Iir, entity: str, name: str) -> Generator[ModelEntity, None, None]:
    item = nodeChain
    lastKind = None
    while item != nodes.Null_Iir:
        kind = GetIirKindOfNode(item)
        if kind == nodes.Iir_Kind.Constant_Declaration:
            from pyGHDL.dom.Object import Constant

            obj = Constant.parse(item)

        elif kind == nodes.Iir_Kind.Variable_Declaration:
            from pyGHDL.dom.Object import SharedVariable

            if nodes.Get_Shared_Flag(item):
                obj = SharedVariable.parse(item)
            else:
                obj = Variable.parse(item)
        elif kind == nodes.Iir_Kind.Signal_Declaration:
            from pyGHDL.dom.Object import Signal

            obj = Signal.parse(item)
        elif kind == nodes.Iir_Kind.File_Declaration:
            from pyGHDL.dom.Object import File

            obj = File.parse(item)
        else:
            if kind == nodes.Iir_Kind.Type_Declaration:
                yield GetTypeFromNode(item)

            elif kind == nodes.Iir_Kind.Anonymous_Type_Declaration:
                yield GetAnonymousTypeFromNode(item)

            elif kind == nodes.Iir_Kind.Subtype_Declaration:
                yield GetSubtypeFromNode(item)

            elif kind == nodes.Iir_Kind.Function_Declaration:
                if nodes.Get_Has_Body(item):
                    yield Function.parse(item)
                else:
                    print("[NOT IMPLEMENTED] function declaration without body")

                lastKind = kind
                item = nodes.Get_Chain(item)
                continue
            elif kind == nodes.Iir_Kind.Function_Body:
                if lastKind is nodes.Iir_Kind.Function_Declaration:
                    pass
                else:
                    position = Position.parse(item)
                    raise DOMException(
                        "Found unexpected function body '{functionName}' in {entity} '{name}' at {file}:{line}:{column}.".format(
                            functionName=GetNameOfNode(item),
                            entity=entity,
                            name=name,
                            file=position.Filename,
                            line=position.Line,
                            column=position.Column,
                        )
                    )
            elif kind == nodes.Iir_Kind.Procedure_Declaration:
                if nodes.Get_Has_Body(item):
                    yield Procedure.parse(item)
                else:
                    print("[NOT IMPLEMENTED] procedure declaration without body")

                lastKind = kind
                item = nodes.Get_Chain(item)
                continue
            elif kind == nodes.Iir_Kind.Procedure_Body:
                if lastKind is nodes.Iir_Kind.Procedure_Declaration:
                    pass
                else:
                    position = Position.parse(item)
                    raise DOMException(
                        "Found unexpected procedure body '{functionName}' in {entity} '{name}' at {file}:{line}:{column}.".format(
                            functionName=GetNameOfNode(item),
                            entity=entity,
                            name=name,
                            file=position.Filename,
                            line=position.Line,
                            column=position.Column,
                        )
                    )
            elif kind == nodes.Iir_Kind.Protected_Type_Body:
                yield ProtectedTypeBody.parse(item)
            elif kind == nodes.Iir_Kind.Object_Alias_Declaration:
                yield GetAliasFromNode(item)
            elif kind == nodes.Iir_Kind.Component_Declaration:
                from pyGHDL.dom.DesignUnit import Component

                yield Component.parse(item)
            elif kind == nodes.Iir_Kind.Attribute_Declaration:
                from pyGHDL.dom.Attribute import Attribute

                yield Attribute.parse(item)
            elif kind == nodes.Iir_Kind.Attribute_Specification:
                from pyGHDL.dom.Attribute import AttributeSpecification

                yield AttributeSpecification.parse(item)
            elif kind == nodes.Iir_Kind.Use_Clause:
                from pyGHDL.dom.DesignUnit import UseClause

                yield UseClause.parse(item)
            elif kind == nodes.Iir_Kind.Package_Declaration:
                from pyGHDL.dom.DesignUnit import Package

                yield Package.parse(item, None)  # TODO: Can it have a context?
            elif kind == nodes.Iir_Kind.Package_Instantiation_Declaration:
                from pyGHDL.dom.DesignUnit import PackageInstantiation

                yield PackageInstantiation.parse(item)
            elif kind == nodes.Iir_Kind.Configuration_Specification:
                print("[NOT IMPLEMENTED] Configuration specification in {name}".format(name=name))
            elif kind == nodes.Iir_Kind.Psl_Default_Clock:
                yield DefaultClock.parse(item)
            elif kind == nodes.Iir_Kind.Group_Declaration:
                print("[NOT IMPLEMENTED] Group declaration in {name}".format(name=name))
            elif kind == nodes.Iir_Kind.Group_Template_Declaration:
                print("[NOT IMPLEMENTED] Group template declaration in {name}".format(name=name))
            elif kind == nodes.Iir_Kind.Disconnection_Specification:
                print("[NOT IMPLEMENTED] Disconnect specification in {name}".format(name=name))
            else:
                position = Position.parse(item)
                raise DOMException(
                    "Unknown declared item kind '{kind}' in {entity} '{name}' at {file}:{line}:{column}.".format(
                        kind=kind.name,
                        entity=entity,
                        name=name,
                        file=position.Filename,
                        line=position.Line,
                        column=position.Column,
                    )
                )

            lastKind = None
            item = nodes.Get_Chain(item)
            continue

        # Lookahead for objects with multiple identifiers at once
        if nodes.Get_Has_Identifier_List(item):
            nextNode = nodes.Get_Chain(item)
            for nextItem in utils.chain_iter(nextNode):
                # Consecutive identifiers are found, if the subtype indication is Null
                if nodes.Get_Subtype_Indication(nextItem) == nodes.Null_Iir:
                    obj.Identifiers.append(GetNameOfNode(nextItem))
                else:
                    item = nextItem
                    break

                # The last consecutive identifiers has no Identifier_List flag
                if not nodes.Get_Has_Identifier_List(nextItem):
                    item = nodes.Get_Chain(nextItem)
                    break
            else:
                item = nodes.Null_Iir
        else:
            item = nodes.Get_Chain(item)

        yield obj


def GetConcurrentStatementsFromChainedNodes(
    nodeChain: Iir, entity: str, name: str
) -> Generator[ConcurrentStatement, None, None]:
    for statement in utils.chain_iter(nodeChain):
        label = nodes.Get_Label(statement)
        label = name_table.Get_Name_Ptr(label) if label != nodes.Null_Iir else None

        pos = Position.parse(statement)

        kind = GetIirKindOfNode(statement)
        if kind == nodes.Iir_Kind.Sensitized_Process_Statement:
            yield ProcessStatement.parse(statement, label, True)

        elif kind == nodes.Iir_Kind.Process_Statement:
            yield ProcessStatement.parse(statement, label, False)

        elif kind == nodes.Iir_Kind.Concurrent_Simple_Signal_Assignment:
            yield ConcurrentSimpleSignalAssignment.parse(statement, label)
        elif kind == nodes.Iir_Kind.Concurrent_Conditional_Signal_Assignment:
            print(
                "[NOT IMPLEMENTED] Concurrent (conditional) signal assignment (label: '{label}') at line {line}".format(
                    label=label, line=pos.Line
                )
            )
        elif kind == nodes.Iir_Kind.Concurrent_Selected_Signal_Assignment:
            print(
                "[NOT IMPLEMENTED] Concurrent (selected) signal assignment (label: '{label}') at line {line}".format(
                    label=label, line=pos.Line
                )
            )
        elif kind == nodes.Iir_Kind.Concurrent_Procedure_Call_Statement:
            yield ConcurrentProcedureCall.parse(statement, label)
        elif kind == nodes.Iir_Kind.Component_Instantiation_Statement:
            instantiatedUnit = nodes.Get_Instantiated_Unit(statement)
            instantiatedUnitKind = GetIirKindOfNode(instantiatedUnit)
            if instantiatedUnitKind == nodes.Iir_Kind.Entity_Aspect_Entity:
                yield EntityInstantiation.parse(statement, instantiatedUnit, label)
            elif instantiatedUnitKind == nodes.Iir_Kind.Entity_Aspect_Configuration:
                yield ConfigurationInstantiation.parse(statement, instantiatedUnit, label)
            elif instantiatedUnitKind == nodes.Iir_Kind.Simple_Name:
                yield ComponentInstantiation.parse(statement, instantiatedUnit, label)
            else:
                raise DOMException(
                    "Unknown instantiation kind '{kind}' in instantiation of label {label} at {file}:{line}:{column}.".format(
                        kind=instantiatedUnitKind.name,
                        label=label,
                        file=pos.Filename,
                        line=pos.Line,
                        column=pos.Column,
                    )
                )
        elif kind == nodes.Iir_Kind.Block_Statement:
            yield ConcurrentBlockStatement.parse(statement, label)
        elif kind == nodes.Iir_Kind.If_Generate_Statement:
            yield IfGenerateStatement.parse(statement, label)
        elif kind == nodes.Iir_Kind.Case_Generate_Statement:
            yield CaseGenerateStatement.parse(statement, label)
        elif kind == nodes.Iir_Kind.For_Generate_Statement:
            yield ForGenerateStatement.parse(statement, label)
        elif kind == nodes.Iir_Kind.Psl_Assert_Directive:
            yield ConcurrentAssertStatement.parse(statement, label)
        else:
            raise DOMException(
                "Unknown statement of kind '{kind}' in {entity} '{name}' at {file}:{line}:{column}.".format(
                    kind=kind.name,
                    entity=entity,
                    name=name,
                    file=pos.Filename,
                    line=pos.Line,
                    column=pos.Column,
                )
            )


def GetSequentialStatementsFromChainedNodes(
    nodeChain: Iir, entity: str, name: str
) -> Generator[SequentialStatement, None, None]:
    for statement in utils.chain_iter(nodeChain):
        label = nodes.Get_Label(statement)
        label = name_table.Get_Name_Ptr(label) if label != nodes.Null_Iir else None

        pos = Position.parse(statement)
        kind = GetIirKindOfNode(statement)
        if kind == nodes.Iir_Kind.If_Statement:
            yield IfStatement.parse(statement, label)
        elif kind == nodes.Iir_Kind.For_Loop_Statement:
            yield ForLoopStatement.parse(statement, label)
        elif kind == nodes.Iir_Kind.Case_Statement:
            yield CaseStatement.parse(statement, label)
        elif kind == nodes.Iir_Kind.Simple_Signal_Assignment_Statement:
            yield SequentialSimpleSignalAssignment.parse(statement, label)
        elif kind in (
            nodes.Iir_Kind.Variable_Assignment_Statement,
            nodes.Iir_Kind.Conditional_Variable_Assignment_Statement,
        ):
            print(
                "[NOT IMPLEMENTED] Variable assignment (label: '{label}') at line {line}".format(
                    label=label, line=pos.Line
                )
            )
        elif kind == nodes.Iir_Kind.Wait_Statement:
            yield WaitStatement.parse(statement, label)
        elif kind == nodes.Iir_Kind.Procedure_Call_Statement:
            yield SequentialProcedureCall.parse(statement, label)
        elif kind == nodes.Iir_Kind.Report_Statement:
            yield SequentialReportStatement.parse(statement, label)
        elif kind == nodes.Iir_Kind.Assertion_Statement:
            yield SequentialAssertStatement.parse(statement, label)
        elif kind == nodes.Iir_Kind.Null_Statement:
            yield NullStatement(statement, label)
        else:
            raise DOMException(
                "Unknown statement of kind '{kind}' in {entity} '{name}' at {file}:{line}:{column}.".format(
                    kind=kind.name,
                    entity=entity,
                    name=name,
                    file=pos.Filename,
                    line=pos.Line,
                    column=pos.Column,
                )
            )


def GetAliasFromNode(aliasNode: Iir):
    aliasName = GetNameOfNode(aliasNode)

    return Alias(aliasNode, aliasName)
