diff --git a/src/pystencilssfg/composer/basic_composer.py b/src/pystencilssfg/composer/basic_composer.py index c0b420f6c51a2fd228a4d96f3451aa83c8612205..936d5c5a6d5aa6988bcf1f2f615c872ef87f162c 100644 --- a/src/pystencilssfg/composer/basic_composer.py +++ b/src/pystencilssfg/composer/basic_composer.py @@ -111,7 +111,7 @@ class KernelsAdder: self._kernel_namespace.add_kernel(khandle) for header in kernel.required_headers: - # TODO: Find current source file by traversing namespace blocks upward? + assert self._ctx.impl_file is not None self._ctx.impl_file.includes.append(HeaderFile.parse(header)) return khandle @@ -217,7 +217,7 @@ class SfgBasicComposer(SfgIComposer): generator.generate(SfgComposer(self)) @property - def kernels(self) -> SfgKernelNamespace: + def kernels(self) -> KernelsAdder: """The default kernel namespace. Add kernels like:: @@ -227,12 +227,20 @@ class SfgBasicComposer(SfgIComposer): """ return self.kernel_namespace("kernels") - def kernel_namespace(self, name: str) -> SfgKernelNamespace: + def kernel_namespace(self, name: str) -> KernelsAdder: """Return the kernel namespace of the given name, creating it if it does not exist yet.""" - # TODO: Find the default kernel namespace as a child entity of the current - # namespace, or create it if it does not exist - # Then create a new namespace block, place it at the cursor position, and expose - # it to the user via an adder + kns = self._cursor.get_entity("kernels") + if kns is None: + kns = SfgKernelNamespace("kernels", self._cursor.current_namespace) + self._cursor.add_entity(kns) + elif not isinstance(kns, SfgKernelNamespace): + raise ValueError( + f"The existing entity {kns.fqname} is not a kernel namespace" + ) + + kns_block = SfgNamespaceBlock(kns) + self._cursor.write_impl(kns_block) + return KernelsAdder(self._ctx, kns_block) def include(self, header_file: str | HeaderFile, private: bool = False): """Include a header file. @@ -258,7 +266,9 @@ class SfgBasicComposer(SfgIComposer): if private: if self._ctx.impl_file is None: - raise ValueError("Cannot emit a private include since no implementation file is being generated") + raise ValueError( + "Cannot emit a private include since no implementation file is being generated" + ) self._ctx.impl_file.includes.append(header_file) else: self._ctx.header_file.includes.append(header_file) @@ -271,32 +281,23 @@ class SfgBasicComposer(SfgIComposer): Returns: The created class object """ - cls = self._struct_from_numpy_dtype(name, dtype, add_constructor=add_constructor) - self._ctx.add_entity(cls) + cls = self._struct_from_numpy_dtype( + name, dtype, add_constructor=add_constructor + ) + self._cursor.add_entity(cls) self._cursor.write_header(SfgEntityDecl(cls)) return cls - def kernel_function( - self, name: str, ast_or_kernel_handle: Kernel | SfgKernelHandle - ): + def kernel_function(self, name: str, kernel: Kernel | SfgKernelHandle): """Create a function comprising just a single kernel call. Args: ast_or_kernel_handle: Either a pystencils AST, or a kernel handle for an already registered AST. """ - if self._ctx.get_function(name) is not None: - raise ValueError(f"Function {name} already exists.") - - if isinstance(ast_or_kernel_handle, Kernel): - khandle = self._ctx.default_kernel_namespace.add(ast_or_kernel_handle) - tree = SfgKernelCallNode(khandle) - elif isinstance(ast_or_kernel_handle, SfgKernelHandle): - tree = SfgKernelCallNode(ast_or_kernel_handle) - else: - raise TypeError("Invalid type of argument `ast_or_kernel_handle`!") + if isinstance(kernel, Kernel): + kernel = self.kernels.add(kernel, name) - func = SfgFunction(name, tree) - self._ctx.add_function(func) + self.function(name)(self.call(kernel)) def function( self, @@ -329,10 +330,14 @@ class SfgBasicComposer(SfgIComposer): def sequencer(*args: SequencerArg): tree = make_sequence(*args) func = SfgFunction( - name, self._cursor.current_namespace, tree, return_type=create_type(returns), inline=inline + name, + self._cursor.current_namespace, + tree, + return_type=create_type(returns), + inline=inline, ) - self._ctx.add_entity(func) - + self._cursor.add_entity(func) + if inline: self._cursor.write_header(SfgEntityDef(func)) else: @@ -530,11 +535,15 @@ class SfgBasicComposer(SfgIComposer): (asvar(c) if isinstance(c, _VarLike) else c) for c in lhs_components ] return SfgDeferredVectorMapping(components, rhs) - + def _struct_from_numpy_dtype( self, struct_name: str, dtype: np.dtype, add_constructor: bool = True ): - cls = SfgClass(struct_name, self._cursor.current_namespace, class_keyword=SfgClassKeyword.STRUCT) + cls = SfgClass( + struct_name, + self._cursor.current_namespace, + class_keyword=SfgClassKeyword.STRUCT, + ) fields = dtype.fields if fields is None: @@ -556,12 +565,13 @@ class SfgBasicComposer(SfgIComposer): constr_inits.append(f"{member}({arg})") if add_constructor: - cls.default.append_member(SfgEntityDef(SfgConstructor(constr_params, constr_inits))) + cls.default.append_member( + SfgEntityDef(SfgConstructor(constr_params, constr_inits)) + ) return cls - def make_statements(arg: ExprLike) -> SfgStatements: return SfgStatements(str(arg), (), depends(arg), includes(arg)) diff --git a/src/pystencilssfg/composer/class_composer.py b/src/pystencilssfg/composer/class_composer.py index 489823b9ce619be88e3220ce4b941cf49c62b298..1e3e6a3085b3bc1a716930ccbc8d74381b33e05d 100644 --- a/src/pystencilssfg/composer/class_composer.py +++ b/src/pystencilssfg/composer/class_composer.py @@ -1,26 +1,28 @@ from __future__ import annotations from typing import Sequence +from itertools import takewhile, dropwhile from pystencils.types import PsCustomType, UserTypeSpec, create_type +from ..context import SfgContext from ..lang import ( - _VarLike, VarLike, ExprLike, asvar, SfgVar, ) +from ..ir.call_tree import SfgCallTreeNode from ..ir.source_components import ( SfgClass, - SfgClassMember, - SfgInClassDefinition, SfgConstructor, SfgMethod, SfgMemberVariable, SfgClassKeyword, SfgVisibility, SfgVisibilityBlock, + SfgEntityDecl, + SfgEntityDef, ) from ..exceptions import SfgException @@ -40,31 +42,87 @@ class SfgClassComposer(SfgComposerMixIn): Its interface is exposed by :class:`SfgComposer`. """ - class VisibilityContext: + class VisibilityBlockSequencer: """Represent a visibility block in the composer syntax. Returned by `private`, `public`, and `protected`. """ def __init__(self, visibility: SfgVisibility): - self._vis_block = SfgVisibilityBlock(visibility) - - def members(self): - yield from self._vis_block.members() + self._visibility = visibility + self._args: tuple[ + SfgClassComposer.MethodSequencer + | SfgClassComposer.ConstructorBuilder + | VarLike + | str, + ..., + ] def __call__( self, *args: ( - SfgClassMember | SfgClassComposer.ConstructorBuilder | VarLike | str + SfgClassComposer.MethodSequencer + | SfgClassComposer.ConstructorBuilder + | VarLike + | str ), ): - for arg in args: - self._vis_block.append_member(SfgClassComposer._resolve_member(arg)) + self._args = args + return self + def _resolve(self, ctx: SfgContext, cls: SfgClass): + vis_block = SfgVisibilityBlock(self._visibility) + for arg in self._args: + match arg: + case ( + SfgClassComposer.MethodSequencer() + | SfgClassComposer.ConstructorBuilder() + ): + arg._resolve(ctx, cls, vis_block) + case str(): + vis_block.elements.append(arg) + case _: + var = asvar(arg) + member_var = SfgMemberVariable(var.name, var.dtype, cls) + cls.add_member(member_var, vis_block.visibility) + vis_block.elements.append(member_var) + + class MethodSequencer: + def __init__( + self, + name: str, + returns: UserTypeSpec = PsCustomType("void"), + inline: bool = False, + const: bool = False, + ) -> None: + self._name = name + self._returns = create_type(returns) + self._inline = inline + self._const = const + self._tree: SfgCallTreeNode + + def __call__(self, *args: SequencerArg): + self._tree = make_sequence(*args) return self - def resolve(self, cls: SfgClass) -> None: - cls.append_visibility_block(self._vis_block) + def _resolve( + self, ctx: SfgContext, cls: SfgClass, vis_block: SfgVisibilityBlock + ): + method = SfgMethod( + self._name, + cls, + self._tree, + return_type=self._returns, + inline=self._inline, + const=self._const, + ) + cls.add_member(method, vis_block.visibility) + + if self._inline: + vis_block.elements.append(SfgEntityDef(method)) + else: + vis_block.elements.append(SfgEntityDecl(method)) + ctx._cursor.write_impl(SfgEntityDef(method)) class ConstructorBuilder: """Composer syntax for constructor building. @@ -107,13 +165,19 @@ class SfgClassComposer(SfgComposerMixIn): self._body = body return self - def resolve(self) -> SfgConstructor: - return SfgConstructor( + def _resolve( + self, ctx: SfgContext, cls: SfgClass, vis_block: SfgVisibilityBlock + ): + ctor = SfgConstructor( + cls, parameters=self._params, initializers=self._initializers, body=self._body if self._body is not None else "", ) + cls.add_member(ctor, vis_block.visibility) + vis_block.elements.append(SfgEntityDef(ctor)) + def klass(self, class_name: str, bases: Sequence[str] = ()): """Create a class and add it to the underlying context. @@ -133,19 +197,19 @@ class SfgClassComposer(SfgComposerMixIn): return self._class(class_name, SfgClassKeyword.STRUCT, bases) @property - def public(self) -> SfgClassComposer.VisibilityContext: + def public(self) -> SfgClassComposer.VisibilityBlockSequencer: """Create a `public` visibility block in a class body""" - return SfgClassComposer.VisibilityContext(SfgVisibility.PUBLIC) + return SfgClassComposer.VisibilityBlockSequencer(SfgVisibility.PUBLIC) @property - def protected(self) -> SfgClassComposer.VisibilityContext: + def protected(self) -> SfgClassComposer.VisibilityBlockSequencer: """Create a `protected` visibility block in a class or struct body""" - return SfgClassComposer.VisibilityContext(SfgVisibility.PROTECTED) + return SfgClassComposer.VisibilityBlockSequencer(SfgVisibility.PROTECTED) @property - def private(self) -> SfgClassComposer.VisibilityContext: + def private(self) -> SfgClassComposer.VisibilityBlockSequencer: """Create a `private` visibility block in a class or struct body""" - return SfgClassComposer.VisibilityContext(SfgVisibility.PRIVATE) + return SfgClassComposer.VisibilityBlockSequencer(SfgVisibility.PRIVATE) def constructor(self, *params: VarLike): """In a class or struct body or visibility block, add a constructor. @@ -172,76 +236,55 @@ class SfgClassComposer(SfgComposerMixIn): const: Whether or not the method is const-qualified. """ - def sequencer(*args: SequencerArg): - tree = make_sequence(*args) - return SfgMethod( - name, - tree, - return_type=create_type(returns), - inline=inline, - const=const, - ) - - return sequencer + return SfgClassComposer.MethodSequencer(name, returns, inline, const) # INTERNALS def _class(self, class_name: str, keyword: SfgClassKeyword, bases: Sequence[str]): - if self._ctx.get_class(class_name) is not None: - raise ValueError(f"Class or struct {class_name} already exists.") + if self._cursor.get_entity(class_name) is not None: + raise ValueError( + f"Another entity with name {class_name} already exists in the current namespace." + ) - cls = SfgClass(class_name, class_keyword=keyword, bases=bases) - self._ctx.add_class(cls) + cls = SfgClass( + class_name, + self._cursor.current_namespace, + class_keyword=keyword, + bases=bases, + ) + self._cursor.add_entity(cls) def sequencer( *args: ( - SfgClassComposer.VisibilityContext - | SfgClassMember + SfgClassComposer.VisibilityBlockSequencer + | SfgClassComposer.MethodSequencer | SfgClassComposer.ConstructorBuilder | VarLike | str ), ): - default_ended = False - - for arg in args: - if isinstance(arg, SfgClassComposer.VisibilityContext): - default_ended = True - arg.resolve(cls) - elif isinstance( - arg, - ( - SfgClassMember, - SfgClassComposer.ConstructorBuilder, - str, - ) - + _VarLike, - ): - if default_ended: - raise SfgException( - "Composer Syntax Error: " - "Cannot add members with default visibility after a visibility block." - ) - else: - cls.default.append_member(self._resolve_member(arg)) + default_vis_sequencer = SfgClassComposer.VisibilityBlockSequencer( + SfgVisibility.DEFAULT + ) + + def argfilter(arg): + return not isinstance(arg, SfgClassComposer.VisibilityBlockSequencer) + + default_vis_args = takewhile( + argfilter, + args, + ) + default_vis_sequencer(*default_vis_args)._resolve(self._ctx, cls) # type: ignore + + for arg in dropwhile(argfilter, args): + if isinstance(arg, SfgClassComposer.VisibilityBlockSequencer): + arg._resolve(self._ctx, cls) else: - raise SfgException(f"{arg} is not a valid class member.") + raise SfgException( + "Composer Syntax Error: " + "Cannot add members with default visibility after a visibility block." + ) - return sequencer + self._cursor.write_header(SfgEntityDef(cls)) - @staticmethod - def _resolve_member( - arg: SfgClassMember | SfgClassComposer.ConstructorBuilder | VarLike | str, - ) -> SfgClassMember: - match arg: - case _ if isinstance(arg, _VarLike): - var = asvar(arg) - return SfgMemberVariable(var.name, var.dtype) - case str(): - return SfgInClassDefinition(arg) - case SfgClassComposer.ConstructorBuilder(): - return arg.resolve() - case SfgClassMember(): - return arg - case _: - raise ValueError(f"Invalid class member: {arg}") + return sequencer diff --git a/src/pystencilssfg/composer/mixin.py b/src/pystencilssfg/composer/mixin.py index 3ee8efa61227184686001045bf3f8cb23525bf02..34b1c5856a3b923f4b15939110bf3bfbc0ba5970 100644 --- a/src/pystencilssfg/composer/mixin.py +++ b/src/pystencilssfg/composer/mixin.py @@ -1,6 +1,6 @@ from __future__ import annotations -from ..context import SfgContext +from ..context import SfgContext, SfgCursor from .basic_composer import SfgBasicComposer @@ -14,6 +14,7 @@ class SfgComposerMixIn: def __init__(self) -> None: self._ctx: SfgContext + self._cursor: SfgCursor @property def _composer(self) -> SfgBasicComposer: diff --git a/src/pystencilssfg/context.py b/src/pystencilssfg/context.py index 577dcbd0a0d6b2d55176f92aae11213971d3da86..48d0720c9927542bdca291c206f28f55f9172437 100644 --- a/src/pystencilssfg/context.py +++ b/src/pystencilssfg/context.py @@ -5,11 +5,10 @@ from .config import CodeStyle from .ir.source_components import ( SfgSourceFile, SfgNamespace, - SfgKernelNamespace, SfgNamespaceBlock, SfgNamespaceElement, SfgCodeEntity, - SfgClass, + SfgGlobalNamespace, ) from .exceptions import SfgException @@ -37,9 +36,14 @@ class SfgContext: self._header_file = header_file self._impl_file = impl_file - self._entities: dict[str, SfgCodeEntity] = dict() + self._global_namespace = SfgGlobalNamespace() - self._cursor: SfgCursor + current_ns: SfgNamespace = self._global_namespace + if outer_namespace is not None: + for token in outer_namespace.split("::"): + current_ns = SfgNamespace(token, current_ns) + + self._cursor = SfgCursor(self, current_ns) @property def argv(self) -> Sequence[str]: @@ -85,28 +89,18 @@ class SfgContext: if self._impl_file is not None: yield self._impl_file - def get_entity(self, fqname: str) -> SfgCodeEntity | None: - # TODO: Only track top-level entities here, traverse namespaces to find qualified entities - return self._entities.get(fqname, None) - - def add_entity(self, entity: SfgCodeEntity) -> None: - fqname = entity.fqname - if fqname in self._entities: - raise ValueError(f"Another entity with name {fqname} already exists") - self._entities[fqname] = entity + @property + def global_namespace(self) -> SfgNamespace: + return self._global_namespace class SfgCursor: """Cursor that tracks the current location in the source file(s) during execution of the generator script.""" - def __init__(self, ctx: SfgContext, namespace: str | None = None) -> None: + def __init__(self, ctx: SfgContext, namespace: SfgNamespace) -> None: self._ctx = ctx - self._cur_namespace: SfgNamespace | None - if namespace is not None: - self._cur_namespace = ctx.get_namespace(namespace) - else: - self._cur_namespace = None + self._cur_namespace: SfgNamespace = namespace self._loc: dict[SfgSourceFile, list[SfgNamespaceElement]] for f in self._ctx.files: @@ -120,9 +114,15 @@ class SfgCursor: # TODO: Enter and exit namespace blocks @property - def current_namespace(self) -> SfgNamespace | None: + def current_namespace(self) -> SfgNamespace: return self._cur_namespace + def get_entity(self, name: str) -> SfgCodeEntity | None: + return self._cur_namespace.get_entity(name) + + def add_entity(self, entity: SfgCodeEntity): + self._cur_namespace.add_entity(entity) + def write_header(self, elem: SfgNamespaceElement) -> None: self._loc[self._ctx.header_file].append(elem) diff --git a/src/pystencilssfg/ir/analysis.py b/src/pystencilssfg/ir/analysis.py index 0b42594033da16efbde3ab8da3433dfcad03a097..c550975cd0b5750f980f52145cbd2ee7e5a3f93a 100644 --- a/src/pystencilssfg/ir/analysis.py +++ b/src/pystencilssfg/ir/analysis.py @@ -15,7 +15,6 @@ def collect_includes(obj: Any) -> set[HeaderFile]: SfgClass, SfgConstructor, SfgMemberVariable, - SfgInClassDefinition, ) match obj: @@ -58,9 +57,6 @@ def collect_includes(obj: Any) -> set[HeaderFile]: case SfgMemberVariable(): return includes(obj) - case SfgInClassDefinition(): - return set() - case _: raise SfgException( f"Can't collect includes from object of type {type(obj)}" diff --git a/src/pystencilssfg/ir/call_tree.py b/src/pystencilssfg/ir/call_tree.py index a5d2c5a35b1795817305515b74797c2bf3f2b91b..9a29f2f0715d18878f7a84926504c51ebf78c213 100644 --- a/src/pystencilssfg/ir/call_tree.py +++ b/src/pystencilssfg/ir/call_tree.py @@ -210,7 +210,7 @@ class SfgKernelCallNode(SfgCallTreeLeaf): def get_code(self, ctx: SfgContext) -> str: ast_params = self._kernel_handle.parameters - fnc_name = self._kernel_handle.fully_qualified_name + fnc_name = self._kernel_handle.fqname call_parameters = ", ".join([p.name for p in ast_params]) return f"{fnc_name}({call_parameters});" @@ -228,7 +228,7 @@ class SfgCudaKernelInvocation(SfgCallTreeLeaf): from pystencils import Target from pystencils.codegen import GpuKernel - func = kernel_handle.get_kernel_function() + func = kernel_handle.get_kernel() if not (isinstance(func, GpuKernel) and func.target == Target.CUDA): raise ValueError( "An `SfgCudaKernelInvocation` node can only call a CUDA kernel." @@ -247,7 +247,7 @@ class SfgCudaKernelInvocation(SfgCallTreeLeaf): def get_code(self, ctx: SfgContext) -> str: ast_params = self._kernel_handle.parameters - fnc_name = self._kernel_handle.fully_qualified_name + fnc_name = self._kernel_handle.fqname call_parameters = ", ".join([p.name for p in ast_params]) grid_args = [self._num_blocks, self._threads_per_block] diff --git a/src/pystencilssfg/ir/source_components.py b/src/pystencilssfg/ir/source_components.py index d8fb0f57e5a91b55b6e5fa44115cb18e50794d22..15b27fb428b3a66827185d550b93d0bab78475f8 100644 --- a/src/pystencilssfg/ir/source_components.py +++ b/src/pystencilssfg/ir/source_components.py @@ -39,9 +39,9 @@ class SfgCodeEntity: Each code entity has a name and an optional enclosing namespace. """ - def __init__(self, name: str, parent_namespace: SfgNamespace | None) -> None: + def __init__(self, name: str, parent_namespace: SfgNamespace) -> None: self._name = name - self._namespace: SfgNamespace | None = parent_namespace + self._namespace: SfgNamespace = parent_namespace @property def name(self) -> str: @@ -51,7 +51,7 @@ class SfgCodeEntity: @property def fqname(self) -> str: """Fully qualified name of this entity""" - if self._namespace is not None: + if not isinstance(self._namespace, SfgGlobalNamespace): return self._namespace.fqname + "::" + self._name else: return self._name @@ -73,7 +73,31 @@ class SfgNamespace(SfgCodeEntity): parent: Parent namespace enclosing this namespace """ - # TODO: Namespaces must keep track of their child entities + def __init__(self, name: str, parent_namespace: SfgNamespace) -> None: + super().__init__(name, parent_namespace) + + self._entities: dict[str, SfgCodeEntity] = dict() + + def get_entity(self, name: str) -> SfgCodeEntity | None: + return self._entities.get(name, None) + + def add_entity(self, entity: SfgCodeEntity): + if entity.name in self._entities: + raise ValueError( + f"Another entity with the name {entity.fqname} already exists" + ) + self._entities[entity.name] = entity + + +class SfgGlobalNamespace(SfgNamespace): + """The C++ global namespace.""" + + def __init__(self) -> None: + super().__init__("", self) + + @property + def fqname(self) -> str: + return "" class SfgKernelHandle(SfgCodeEntity): @@ -117,9 +141,9 @@ class SfgKernelHandle(SfgCodeEntity): class SfgKernelNamespace(SfgNamespace): """A namespace grouping together a number of kernels.""" - def __init__(self, name: str, parent: SfgNamespace | None): + def __init__(self, name: str, parent: SfgNamespace): super().__init__(name, parent) - self._kernels: dict[str, SfgKernelHandle] = [] + self._kernels: dict[str, SfgKernelHandle] = dict() @property def name(self): @@ -200,7 +224,7 @@ class SfgFunction(SfgCodeEntity): def __init__( self, name: str, - namespace: SfgNamespace | None, + namespace: SfgNamespace, tree: SfgCallTreeNode, return_type: PsType = void, inline: bool = False, @@ -272,8 +296,8 @@ class SfgClassKeyword(Enum): class SfgClassMember(ABC): """Base class for class member entities""" - def __init__(self) -> None: - self._cls: SfgClass | None = None + def __init__(self, cls: SfgClass) -> None: + self._cls: SfgClass = cls self._visibility: SfgVisibility | None = None @property @@ -290,26 +314,13 @@ class SfgClassMember(ABC): ) return self._visibility - @property - def is_bound(self) -> bool: - return self._cls is not None - - def _bind(self, cls: SfgClass, vis: SfgVisibility): - if self.is_bound: - raise SfgException( - f"Binding {self} to class {cls.class_name} failed: " - f"{self} was already bound to {self.owning_class.class_name}" - ) - self._cls = cls - self._vis = vis - class SfgMemberVariable(SfgVar, SfgClassMember): """Variable that is a field of a class""" - def __init__(self, name: str, dtype: PsType): + def __init__(self, name: str, dtype: PsType, cls: SfgClass): SfgVar.__init__(self, name, dtype) - SfgClassMember.__init__(self) + SfgClassMember.__init__(self, cls) class SfgMethod(SfgClassMember): @@ -320,12 +331,13 @@ class SfgMethod(SfgClassMember): def __init__( self, name: str, + cls: SfgClass, tree: SfgCallTreeNode, return_type: PsType = void, inline: bool = False, const: bool = False, ): - super().__init__() + super().__init__(cls) self._name = name self._tree = tree @@ -368,11 +380,12 @@ class SfgConstructor(SfgClassMember): def __init__( self, + cls: SfgClass, parameters: Sequence[SfgVar] = (), initializers: Sequence[str] = (), body: str = "", ): - SfgClassMember.__init__(self) + super().__init__(cls) self._parameters = tuple(parameters) self._initializers = tuple(initializers) self._body = body @@ -409,7 +422,7 @@ class SfgClass(SfgCodeEntity): def __init__( self, name: str, - namespace: SfgNamespace | None, + namespace: SfgNamespace, class_keyword: SfgClassKeyword = SfgClassKeyword.CLASS, bases: Sequence[str] = (), ): @@ -422,7 +435,6 @@ class SfgClass(SfgCodeEntity): self._bases_classes = tuple(bases) self._default_block = SfgVisibilityBlock(SfgVisibility.DEFAULT) - self._default_block._bind(self) self._blocks = [self._default_block] self._constructors: list[SfgConstructor] = [] @@ -451,14 +463,10 @@ class SfgClass(SfgCodeEntity): raise SfgException( "Can't add another block with DEFAULT visibility to a class. Use `.default` instead." ) - - block._bind(self) - for m in block.members(): - self._add_member(m, block.visibility) self._blocks.append(block) - def visibility_blocks(self) -> Generator[SfgVisibilityBlock, None, None]: - yield from self._blocks + def visibility_blocks(self) -> tuple[SfgVisibilityBlock, ...]: + return tuple(self._blocks) def members( self, visibility: SfgVisibility | None = None @@ -497,26 +505,16 @@ class SfgClass(SfgCodeEntity): else: yield from self._methods - # PRIVATE - - def _add_member(self, member: SfgClassMember, vis: SfgVisibility): + def add_member(self, member: SfgClassMember, vis: SfgVisibility): if isinstance(member, SfgConstructor): - self._add_constructor(member) + self._constructors.append(member) elif isinstance(member, SfgMemberVariable): self._add_member_variable(member) elif isinstance(member, SfgMethod): - self._add_method(member) + self._methods.append(member) else: raise SfgException(f"{member} is not a valid class member.") - member._bind(self, vis) - - def _add_constructor(self, constr: SfgConstructor): - self._constructors.append(constr) - - def _add_method(self, method: SfgMethod): - self._methods.append(method) - def _add_member_variable(self, variable: SfgMemberVariable): if variable.name in self._member_vars: raise SfgException( @@ -598,22 +596,13 @@ class SfgVisibilityBlock: def visibility(self) -> SfgVisibility: return self._vis - def append_member(self, element: SfgClassBodyElement): - if isinstance(element, (SfgEntityDecl, SfgEntityDef)): - member = element.entity - elif isinstance(element, SfgClassMember): - member = element - else: - member = None - - if self._cls is not None and member is not None: - self._cls._add_member(member, self._vis) - - self._elements.append(element) - @property - def elements(self) -> tuple[SfgClassBodyElement, ...]: - return tuple(self._elements) + def elements(self) -> list[SfgClassBodyElement]: + return self._elements + + @elements.setter + def elements(self, elems: Iterable[SfgClassBodyElement]): + self._elements = list(elems) def members(self) -> Generator[SfgClassMember, None, None]: for elem in self._elements: @@ -623,18 +612,6 @@ class SfgVisibilityBlock: case SfgMemberVariable(): yield elem - @property - def is_bound(self) -> bool: - return self._cls is not None - - def _bind(self, cls: SfgClass): - if self._cls is not None: - raise SfgException( - f"Binding visibility block to class {cls.class_name} failed: " - f"was already bound to {self._cls.class_name}" - ) - self._cls = cls - class SfgNamespaceBlock: """A C++ namespace.