Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
Tracked by #73
xmnlab opened this issue Sep 17, 2024 · 3 comments · May be fixed by #153
Open
Tracked by #73

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

xmnlab opened this issue Sep 17, 2024 · 3 comments · May be fixed by #153
Assignees
Labels

Comments

@xmnlab
Copy link
Contributor

xmnlab commented Sep 17, 2024

from gpt:

Understanding the Existing Structure

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

  • Base Classes and Enums:

    • AST: The root class for all AST nodes (astx/base.py).
    • StatementType: Inherits from AST, used for statements.
    • Expr: Inherits from AST, used for expressions.
    • ASTKind: An enumeration of different AST node kinds.
  • Modules:

    • astx.base: Contains base classes and fundamental definitions.
    • astx.callables: Contains function-related classes like Function, FunctionPrototype, etc.
    • astx.variables: Contains variable-related classes like Variable, VariableDeclaration.
    • astx.blocks: Contains the Block class for sequences of statements.
    • astx.modifiers: Contains enums for VisibilityKind, MutabilityKind, and ScopeKind.
    • astx.datatypes: Contains data type classes and operations.
    • astx.packages: Contains Module, Package, and Program classes for broader scopes.
    • astx.flows, astx.symbol_table, astx.viz, etc.
  • Conventions:

    • Classes representing statements inherit from StatementType.
    • Classes representing expressions inherit from Expr.
    • AST node kinds are defined in the ASTKind enum.
    • Modifiers like VisibilityKind, MutabilityKind, and ScopeKind are used to specify attributes.

Designing the Class Declaration and Definition Classes

Given your existing structure, we'll proceed to:

  • Define new AST kinds in ASTKind.
  • Create a new module astx/classes.py to contain class-related AST nodes.
  • Implement ClassDeclarationStmt and ClassDefinitionStmt classes, inheriting from StatementType.
  • Use existing constructs like Expr, ASTNodes, Block, and modifiers.
  • Update astx/__init__.py to include the new module and classes.

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:

  • Inheritance: Inherits from StatementType as class declarations are statements.
  • Attributes:
    • name: The class name.
    • bases: List of base classes (Expr instances).
    • decorators: List of decorators (Expr instances).
    • visibility: Access modifier from VisibilityKind.
    • is_abstract: Indicates if the class is abstract.
    • metaclass: Optional metaclass (Expr instance).
  • Methods:
    • __str__: Provides a readable string representation.
    • get_struct: Returns a structured representation for serialization.

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:

  • Inheritance: Inherits from ClassDeclarationStmt, adding the body attribute.
  • Attributes:
    • body: A Block containing the class body (list of statements).
  • Methods:
    • __str__: Extends the string representation to include the class body.
    • get_struct: Extends the structured representation to include the body.

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

  • Modifiers: Utilize VisibilityKind and other modifiers from astx.modifiers.
  • Expressions: Use Expr and its subclasses for bases, decorators, and metaclass.
  • Blocks: Use Block from astx.blocks for the class body.

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

  • Unit Tests: Create test cases to instantiate ClassDeclarationStmt and ClassDefinitionStmt with various configurations.
  • Integration Tests: Ensure that these classes interact correctly with the rest of the astx framework.
  • Visualization: Use astx.viz to visualize the AST of the class definitions.

Documentation

  • Docstrings: Ensure that all new classes and methods have comprehensive docstrings.
  • Module Documentation: Update your module's documentation to include the new classes module and provide usage examples.

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

  • Review: Examine the provided code and examples to ensure they meet your requirements.
  • Implement: Add the new module and classes to your codebase.
  • Test: Write tests to verify the functionality and integration.
  • Refine: Adjust the classes as needed based on your specific needs.
@xmnlab xmnlab changed the title ClassDeclarationStmt - Declares a class. ClassDeclarationStmt/ClassDefinitionStmt - Declares a class. Sep 18, 2024
@xmnlab xmnlab changed the title ClassDeclarationStmt/ClassDefinitionStmt - Declares a class. ClassDeclarationStmt/ClassDefinitionStmt - Declares and defines a class. Sep 18, 2024
@xmnlab xmnlab changed the title ClassDeclarationStmt/ClassDefinitionStmt - Declares and defines a class. ClassDeclStmt/ClassDefStmt - Declares and defines a class. Nov 22, 2024
@xmnlab
Copy link
Contributor Author

xmnlab commented Nov 22, 2024

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

@xmnlab
Copy link
Contributor Author

xmnlab commented Nov 22, 2024

add a new attribute:

  • inheritance: Optional[list[str]] = None

@xmnlab
Copy link
Contributor Author

xmnlab commented Nov 25, 2024

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)

@apkrelling apkrelling linked a pull request Dec 2, 2024 that will close this issue
21 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants