arxlang / astx

https://astx.arxlang.org/
Other
3 stars 4 forks source link

`ClassDeclStmt`/`ClassDefStmt` - Declares and defines a class. #90

Open xmnlab opened 2 months ago

xmnlab commented 2 months ago

from gpt:

Understanding the Existing Structure

From the modules you've shared, we can summarize the key points:


Designing the Class Declaration and Definition Classes

Given your existing structure, we'll proceed to:


1. Updating ASTKind Enum

In astx/base.py, add new kinds for the class statements:

# astx/base.py

@public
class ASTKind(Enum):
    """The expression kind class used for downcasting."""

    # ... existing kinds ...

    # Class statements
    ClassDeclarationKind = -700
    ClassDefinitionKind = -701

    # ... rest of the code ...

2. Creating astx/classes.py Module

Create a new module astx/classes.py to contain class-related AST nodes.

# astx/classes.py

from __future__ import annotations
from typing import Optional, List

from public import public

from astx.base import (
    NO_SOURCE_LOCATION,
    ASTKind,
    ASTNodes,
    Expr,
    SourceLocation,
    StatementType,
)
from astx.types import ReprStruct
from astx.modifiers import VisibilityKind
from astx.blocks import Block

@public
class ClassDeclarationStmt(StatementType):
    """AST class for class declaration."""

    name: str
    bases: List[Expr]
    decorators: List[Expr]
    visibility: VisibilityKind
    is_abstract: bool
    metaclass: Optional[Expr]

    def __init__(
        self,
        name: str,
        bases: Optional[List[Expr]] = None,
        decorators: Optional[List[Expr]] = None,
        visibility: VisibilityKind = VisibilityKind.public,
        is_abstract: bool = False,
        metaclass: Optional[Expr] = None,
        loc: SourceLocation = NO_SOURCE_LOCATION,
        parent: Optional[ASTNodes] = None,
    ) -> None:
        super().__init__(loc=loc, parent=parent)
        self.name = name
        self.bases = bases or []
        self.decorators = decorators or []
        self.visibility = visibility
        self.is_abstract = is_abstract
        self.metaclass = metaclass
        self.kind = ASTKind.ClassDeclarationKind

    def __str__(self) -> str:
        modifiers = []
        if self.visibility != VisibilityKind.public:
            modifiers.append(self.visibility.name.lower())
        if self.is_abstract:
            modifiers.append('abstract')
        modifiers_str = ' '.join(modifiers)
        bases_str = ", ".join(str(base) for base in self.bases) if self.bases else ""
        decorators_str = "".join(f"@{decorator}\n" for decorator in self.decorators)
        metaclass_str = f" metaclass={self.metaclass}" if self.metaclass else ""
        class_str = f"class {self.name}"
        if bases_str:
            class_str += f"({bases_str})"
        class_str += f"{metaclass_str}"
        return f"{decorators_str}{modifiers_str} {class_str}".strip()

    def get_struct(self, simplified: bool = False) -> ReprStruct:
        key = f"ClassDeclaration[{self.name}]"
        value = {
            "visibility": self.visibility.name.lower(),
            "is_abstract": self.is_abstract,
            "bases": [base.get_struct(simplified) for base in self.bases],
            "decorators": [decorator.get_struct(simplified) for decorator in self.decorators],
            "metaclass": self.metaclass.get_struct(simplified) if self.metaclass else None,
        }
        return self._prepare_struct(key, value, simplified)

Explanation:


3. Defining ClassDefinitionStmt

@public
class ClassDefinitionStmt(ClassDeclarationStmt):
    """AST class for class definition."""

    body: Block

    def __init__(
        self,
        name: str,
        bases: Optional[List[Expr]] = None,
        decorators: Optional[List[Expr]] = None,
        visibility: VisibilityKind = VisibilityKind.public,
        is_abstract: bool = False,
        metaclass: Optional[Expr] = None,
        body: Optional[Block] = None,
        loc: SourceLocation = NO_SOURCE_LOCATION,
        parent: Optional[ASTNodes] = None,
    ) -> None:
        super().__init__(
            name=name,
            bases=bases,
            decorators=decorators,
            visibility=visibility,
            is_abstract=is_abstract,
            metaclass=metaclass,
            loc=loc,
            parent=parent,
        )
        self.body = body if body is not None else Block(name=f"{name}_body")
        self.kind = ASTKind.ClassDefinitionKind

    def __str__(self) -> str:
        class_decl_str = super().__str__()
        if not self.body.nodes:
            body_str = "    pass"
        else:
            body_str = "\n    ".join(str(stmt) for stmt in self.body.nodes)
        return f"{class_decl_str}:\n    {body_str}"

    def get_struct(self, simplified: bool = False) -> ReprStruct:
        value = super().get_struct(simplified)
        value["body"] = self.body.get_struct(simplified)
        key = f"ClassDefinition[{self.name}]"
        return self._prepare_struct(key, value, simplified)

Explanation:


4. Updating astx/__init__.py

Add the new module and classes to your package's __init__.py:

# astx/__init__.py

from astx import (
    # ... existing imports ...
    classes,
)
from astx.classes import ClassDeclarationStmt, ClassDefinitionStmt

__all__ = [
    # ... existing exports ...
    "classes",
    "ClassDeclarationStmt",
    "ClassDefinitionStmt",
]

5. Ensuring Integration with Existing Structures


Example Usage

1. Creating a Class Declaration

from astx.classes import ClassDeclarationStmt
from astx.variables import Variable
from astx.base import SourceLocation
from astx.modifiers import VisibilityKind

# Base classes
base1 = Variable(name="BaseClass1")
base2 = Variable(name="BaseClass2")

# Decorators
decorator1 = Variable(name="decorator_one")

# Create a class declaration
class_decl = ClassDeclarationStmt(
    name="MyClass",
    bases=[base1, base2],
    decorators=[decorator1],
    visibility=VisibilityKind.public,
    is_abstract=True,
    loc=SourceLocation(line=1, col=0)
)

# Print the string representation
print(class_decl)

Output:

@Variable[decorator_one]
abstract class MyClass(BaseClass1, BaseClass2)

2. Creating a Class Definition

from astx.classes import ClassDefinitionStmt
from astx.variables import VariableDeclaration
from astx.callables import Function, FunctionPrototype, Arguments, FunctionReturn
from astx.blocks import Block
from astx.base import SourceLocation
from astx.datatypes import Int32, LiteralInt32
from astx.modifiers import VisibilityKind, ScopeKind, MutabilityKind

# Method prototype
prototype = FunctionPrototype(
    name="my_method",
    args=Arguments(),
    return_type=Int32,
    scope=ScopeKind.local,
    visibility=VisibilityKind.public,
    loc=SourceLocation(line=2, col=4)
)

# Method body
method_body = Block(name="my_method_body")
return_stmt = FunctionReturn(
    value=LiteralInt32(42),
    loc=SourceLocation(line=3, col=8)
)
method_body.append(return_stmt)

method = Function(
    prototype=prototype,
    body=method_body,
    loc=SourceLocation(line=2, col=4)
)

# Class body
class_body = Block(name="MyClass_body")
class_body.append(method)

# Variable declaration in class
var_decl = VariableDeclaration(
    name="my_variable",
    type_=Int32,
    mutability=MutabilityKind.mutable,
    visibility=VisibilityKind.private,
    value=LiteralInt32(10),
    loc=SourceLocation(line=4, col=4)
)
class_body.append(var_decl)

# Create a class definition
class_def = ClassDefinitionStmt(
    name="MyClass",
    bases=[],
    decorators=[],
    visibility=VisibilityKind.public,
    is_abstract=False,
    body=class_body,
    loc=SourceLocation(line=1, col=0)
)

# Print the string representation
print(class_def)

Output:

class MyClass():
    Function[my_method]
    VariableDeclaration[my_variable, Int32]

Testing and Validation


Documentation


Conclusion

By adding ClassDeclarationStmt and ClassDefinitionStmt classes to your astx module, you've extended your AST framework to support class declarations and definitions. These classes integrate seamlessly with your existing codebase, following your established conventions and utilizing existing components.


Next Steps

xmnlab commented 1 week ago
("Module(body=[ClassDef(name='A', bases=[Name(id='B', ctx=Load()), "
 "Name(id='C', ctx=Load())], keywords=[], body=[Pass()], decorator_list=[])], "
 'type_ignores=[])')
xmnlab commented 1 week ago

add a new attribute:

xmnlab commented 5 days ago

let's try to not overthink too much about that .. let's keep it simple for now and we can improve that later.

This is some recommendation from gpt with some changes mine

@public
@typechecked
class ClassDefStmt(ClassDeclStmt):
    """AST class for class definition, including attributes and methods."""

    attributes: Iterable[VariableDeclaration]
    methods: Iterable[Function]
    body: Block

    def __init__(
        self,
        name: str,
        bases: Optional[List[Expr]] = None,
        decorators: Optional[List[Expr]] = None,
        visibility: VisibilityKind = VisibilityKind.public,
        is_abstract: bool = False,
        metaclass: Optional[Expr] = None,
        attributes: Iterable[VariableDeclaration] = [],
        methods: Iterable[Function] = [],
        loc: SourceLocation = NO_SOURCE_LOCATION,
        parent: Optional[ASTNodes] = None,
    ) -> None:
        super().__init__(
            name=name,
            bases=bases,
            decorators=decorators,
            visibility=visibility,
            is_abstract=is_abstract,
            metaclass=metaclass,
            loc=loc,
            parent=parent,
        )
        self.attributes = attributes if attributes is not None else []
        self.methods = methods if methods is not None else []
        # Construct body as a block containing attributes and methods
        self.body = Block(name=f"{name}_body")
        for attr in self.attributes:
            self.body.append(attr)
        for method in self.methods:
            self.body.append(method)
        self.kind = ASTKind.ClassDefinitionKind

    def __str__(self) -> str:
        class_decl_str = super().__str__()
        if not self.body.nodes:
            body_str = "    pass"
        else:
            body_str = "\n    ".join(str(stmt) for stmt in self.body.nodes)
        return f"{class_decl_str}:\n    {body_str}"

    def get_struct(self, simplified: bool = False) -> ReprStruct:
        value = super().get_struct(simplified)
        value["attributes"] = [attr.get_struct(simplified) for attr in self.attributes]
        value["methods"] = [method.get_struct(simplified) for method in self.methods]
        value["body"] = self.body.get_struct(simplified)
        key = f"ClassDefinition[{self.name}]"
        return self._prepare_struct(key, value, simplified)