Skip to content
Snippets Groups Projects
syntax.py 6.63 KiB
from __future__ import annotations

from enum import Enum, auto
from typing import (
    Iterable,
    TypeVar,
    Generic,
)

from ..lang import HeaderFile

from .entities import (
    SfgNamespace,
    SfgKernelHandle,
    SfgFunction,
    SfgClassMember,
    SfgVisibility,
    SfgClass,
)

#   =========================================================================================================
#
#   SYNTACTICAL ELEMENTS
#
#   These classes model *code elements*, which represent the actual syntax objects that populate the output
#   files, their namespaces and class bodies.
#
#   =========================================================================================================


SourceEntity_T = TypeVar(
    "SourceEntity_T",
    bound=SfgKernelHandle | SfgFunction | SfgClassMember | SfgClass,
    covariant=True,
)
"""Source entities that may have declarations and definitions."""


class SfgEntityDecl(Generic[SourceEntity_T]):
    """Declaration of a function, class, method, or constructor"""

    __match_args__ = ("entity",)

    def __init__(self, entity: SourceEntity_T) -> None:
        self._entity = entity

    @property
    def entity(self) -> SourceEntity_T:
        return self._entity


class SfgEntityDef(Generic[SourceEntity_T]):
    """Definition of a function, class, method, or constructor"""

    __match_args__ = ("entity",)

    def __init__(self, entity: SourceEntity_T) -> None:
        self._entity = entity

    @property
    def entity(self) -> SourceEntity_T:
        return self._entity


SfgClassBodyElement = str | SfgEntityDecl[SfgClassMember] | SfgEntityDef[SfgClassMember]
"""Elements that may be placed in the visibility blocks of a class body."""


class SfgVisibilityBlock:
    """Visibility-qualified block inside a class definition body.

    Visibility blocks host the code elements placed inside a class body:
    method and constructor declarations,
    in-class method and constructor definitions,
    as well as variable declarations and definitions.

    Args:
        visibility: The visibility qualifier of this block
    """

    __match_args__ = ("visibility", "elements")

    def __init__(self, visibility: SfgVisibility) -> None:
        self._vis = visibility
        self._elements: list[SfgClassBodyElement] = []
        self._cls: SfgClass | None = None

    @property
    def visibility(self) -> SfgVisibility:
        return self._vis

    @property
    def elements(self) -> list[SfgClassBodyElement]:
        return self._elements

    @elements.setter
    def elements(self, elems: Iterable[SfgClassBodyElement]):
        self._elements = list(elems)


class SfgNamespaceBlock:
    """A C++ namespace block.

    Args:
        namespace: Namespace associated with this block
        label: Label printed at the opening brace of this block.
            This may be the namespace name, or a compressed qualified
            name containing one or more of its parent namespaces.
    """

    __match_args__ = (
        "namespace",
        "elements",
        "label",
    )

    def __init__(self, namespace: SfgNamespace, label: str | None = None) -> None:
        self._namespace = namespace
        self._label = label if label is not None else namespace.name
        self._elements: list[SfgNamespaceElement] = []

    @property
    def namespace(self) -> SfgNamespace:
        return self._namespace

    @property
    def label(self) -> str:
        return self._label

    @property
    def elements(self) -> list[SfgNamespaceElement]:
        """Sequence of source elements that make up the body of this namespace"""
        return self._elements

    @elements.setter
    def elements(self, elems: Iterable[SfgNamespaceElement]):
        self._elements = list(elems)


class SfgClassBody:
    """Body of a class definition."""

    __match_args__ = ("associated_class", "visibility_blocks")

    def __init__(
        self,
        cls: SfgClass,
        default_block: SfgVisibilityBlock,
        vis_blocks: Iterable[SfgVisibilityBlock],
    ) -> None:
        self._cls = cls
        assert default_block.visibility == SfgVisibility.DEFAULT
        self._default_block = default_block
        self._blocks = [self._default_block] + list(vis_blocks)

    @property
    def associated_class(self) -> SfgClass:
        return self._cls

    @property
    def default(self) -> SfgVisibilityBlock:
        return self._default_block

    def append_visibility_block(self, block: SfgVisibilityBlock):
        if block.visibility == SfgVisibility.DEFAULT:
            raise ValueError(
                "Can't add another block with DEFAULT visibility to this class body."
            )
        self._blocks.append(block)

    @property
    def visibility_blocks(self) -> tuple[SfgVisibilityBlock, ...]:
        return tuple(self._blocks)


SfgNamespaceElement = (
    str | SfgNamespaceBlock | SfgClassBody | SfgEntityDecl | SfgEntityDef
)
"""Elements that may be placed inside a namespace, including the global namespace."""


class SfgSourceFileType(Enum):
    HEADER = auto()
    TRANSLATION_UNIT = auto()


class SfgSourceFile:
    """A C++ source file.

    Args:
        name: Name of the file (without parent directories), e.g. ``Algorithms.cpp``
        file_type: Type of the source file (header or translation unit)
        prelude: Optionally, text of the prelude comment printed at the top of the file
    """

    def __init__(
        self, name: str, file_type: SfgSourceFileType, prelude: str | None = None
    ) -> None:
        self._name: str = name
        self._file_type: SfgSourceFileType = file_type
        self._prelude: str | None = prelude
        self._includes: list[HeaderFile] = []
        self._elements: list[SfgNamespaceElement] = []

    @property
    def name(self) -> str:
        """Name of this source file"""
        return self._name

    @property
    def file_type(self) -> SfgSourceFileType:
        """File type of this source file"""
        return self._file_type

    @property
    def prelude(self) -> str | None:
        """Text of the prelude comment"""
        return self._prelude

    @prelude.setter
    def prelude(self, text: str | None):
        self._prelude = text

    @property
    def includes(self) -> list[HeaderFile]:
        """Sequence of header files to be included at the top of this file"""
        return self._includes

    @includes.setter
    def includes(self, incl: Iterable[HeaderFile]):
        self._includes = list(incl)

    @property
    def elements(self) -> list[SfgNamespaceElement]:
        """Sequence of source elements comprising the body of this file"""
        return self._elements

    @elements.setter
    def elements(self, elems: Iterable[SfgNamespaceElement]):
        self._elements = list(elems)