From 24076742a48f5b5033fe6e9b74d03c771c03bd2b Mon Sep 17 00:00:00 2001 From: Ana Krelling Date: Mon, 2 Dec 2024 09:22:58 -0500 Subject: [PATCH 1/3] add ClassDeclStmt and ClassDefStmt --- src/astx/__init__.py | 6 ++ src/astx/base.py | 4 ++ src/astx/callables.py | 4 +- src/astx/classes.py | 162 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 174 insertions(+), 2 deletions(-) create mode 100644 src/astx/classes.py diff --git a/src/astx/__init__.py b/src/astx/__init__.py index 6206371..6f1aa51 100644 --- a/src/astx/__init__.py +++ b/src/astx/__init__.py @@ -38,6 +38,10 @@ FunctionReturn, LambdaExpr, ) +from astx.classes import ( + ClassDeclStmt, + ClassDefStmt, +) from astx.flows import ( ForCountLoopExpr, ForCountLoopStmt, @@ -235,6 +239,8 @@ def get_version() -> str: "Complex", "Complex32", "Complex64", + "ClassDefStmt", + "ClassDeclStmt", "LiteralComplex", "LiteralComplex32", "LiteralComplex64", diff --git a/src/astx/base.py b/src/astx/base.py index 85bb3ca..ff68a0e 100644 --- a/src/astx/base.py +++ b/src/astx/base.py @@ -143,6 +143,10 @@ class ASTKind(Enum): TypeCastExprKind = -809 + # classes + ClassDefStmtKind = -900 + ClassDeclStmtKind = -901 + class ASTMeta(type): def __str__(cls) -> str: diff --git a/src/astx/callables.py b/src/astx/callables.py index 6c6256a..130e49e 100644 --- a/src/astx/callables.py +++ b/src/astx/callables.py @@ -58,7 +58,7 @@ def __str__(self) -> str: return f"Argument[{self.name}, {type_}]" def get_struct(self, simplified: bool = False) -> ReprStruct: - """Return a string that represents the object.""" + """Return the AST structure of the object.""" key = f"Argument[{self.name}, {self.type_}] = {self.default}" value = cast(ReprStruct, self.default) return self._prepare_struct(key, value, simplified) @@ -79,7 +79,7 @@ def __str__(self) -> str: return f"Arguments({len(self.nodes)})" def get_struct(self, simplified: bool = False) -> ReprStruct: - """Return a string that represents the object.""" + """Return the AST structure of the object.""" args_nodes = [] for node in self.nodes: diff --git a/src/astx/classes.py b/src/astx/classes.py new file mode 100644 index 0000000..c801890 --- /dev/null +++ b/src/astx/classes.py @@ -0,0 +1,162 @@ +"""Module for classes definitions/declarations.""" + +from __future__ import annotations + +from typing import Iterable, List, Optional + +from public import public +from typeguard import typechecked + +from astx.base import ( + NO_SOURCE_LOCATION, + ASTKind, + ASTNodes, + Expr, + ReprStruct, + SourceLocation, + StatementType, +) +from astx.blocks import Block +from astx.callables import Function +from astx.modifiers import VisibilityKind +from astx.variables import VariableDeclaration + + +@public +@typechecked +class ClassDeclStmt(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: + """Initialize ClassDeclStmt instance.""" + 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.ClassDeclStmtKind + + def __str__(self) -> str: + """Return a string that represents the object.""" + 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: + """Return the AST structure of the object.""" + 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) + + +@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: + """Initialize ClassDefStmt instance.""" + 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.ClassDefStmtKind + + def __str__(self) -> str: + """Return a string that represents the object.""" + 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: + """Return the AST structure of the object.""" + 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) From 2dc36179b4d6917af5b18de3908f13e8452e10fd Mon Sep 17 00:00:00 2001 From: Ana Krelling Date: Tue, 3 Dec 2024 17:02:30 -0500 Subject: [PATCH 2/3] fix get_struct method --- src/astx/classes.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/astx/classes.py b/src/astx/classes.py index c801890..19d416a 100644 --- a/src/astx/classes.py +++ b/src/astx/classes.py @@ -150,13 +150,15 @@ def __str__(self) -> str: def get_struct(self, simplified: bool = False) -> ReprStruct: """Return the AST structure of the object.""" - value = super().get_struct(simplified) + # value = super().get_struct(simplified) + key = f"ClassDefinition[{self.name}]" + value = {} 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}]" + value["body"] = [self.body.get_struct(simplified=True)] + return self._prepare_struct(key, value, simplified) From b37a15ababfb7a1fee0108b0aa80428fa870558a Mon Sep 17 00:00:00 2001 From: Ana Krelling Date: Fri, 6 Dec 2024 08:34:02 -0500 Subject: [PATCH 3/3] fix classdef get_struct --- src/astx/classes.py | 57 ++++++++++++++++++++++++++++++++------------- 1 file changed, 41 insertions(+), 16 deletions(-) diff --git a/src/astx/classes.py b/src/astx/classes.py index 19d416a..7b795c9 100644 --- a/src/astx/classes.py +++ b/src/astx/classes.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import Iterable, List, Optional +from typing import Iterable, List, Optional, cast from public import public from typeguard import typechecked @@ -11,6 +11,7 @@ NO_SOURCE_LOCATION, ASTKind, ASTNodes, + DictDataTypesStruct, Expr, ReprStruct, SourceLocation, @@ -34,6 +35,9 @@ class ClassDeclStmt(StatementType): is_abstract: bool metaclass: Optional[Expr] + # just for consistency, shouldn't the default values for the methods + # be the default values for the instance? Specifically for bases and + # decorators,[] vs None def __init__( self, name: str, @@ -80,19 +84,38 @@ def __str__(self) -> str: def get_struct(self, simplified: bool = False) -> ReprStruct: """Return the AST structure of the object.""" - 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, + abstract = ", abstract" if self.is_abstract else "" + vis = self.visibility.name.lower() + key = f"CLASS-DECL[{self.name}{abstract}, {vis}]" + + bases_dict: ReprStruct = {} + decors_dict: ReprStruct = {} + metaclass_dict: ReprStruct = {} + # if self.bases is not XX:# none does not work, + # [] does not work. len(self.bases)>=1 works + if len(self.bases) != 0: # default is empty list + bases_dict = { + "bases": [b.get_struct(simplified) for b in self.bases] + } + + if len(self.decorators) != 0: # default is empty list + decors_dict = { + "decorators": [ + d.get_struct(simplified) for d in self.decorators + ] + } + + if self.metaclass: # default is None + metaclass_dict = { + "metaclass": self.metaclass.get_struct(simplified) + } + + value: ReprStruct = { + **cast(DictDataTypesStruct, bases_dict), + **cast(DictDataTypesStruct, decors_dict), + **cast(DictDataTypesStruct, metaclass_dict), } + return self._prepare_struct(key, value, simplified) @@ -105,7 +128,8 @@ class ClassDefStmt(ClassDeclStmt): methods: Iterable[Function] body: Block - def __init__( + # what does it mean to have body up here but not down? + def __init__( # shouldn't there be body here? self, name: str, bases: Optional[List[Expr]] = None, @@ -148,10 +172,11 @@ def __str__(self) -> str: body_str = "\n ".join(str(stmt) for stmt in self.body.nodes) return f"{class_decl_str}:\n {body_str}" + # how can there be a body in the ast w/o a body in the init? def get_struct(self, simplified: bool = False) -> ReprStruct: """Return the AST structure of the object.""" - # value = super().get_struct(simplified) - key = f"ClassDefinition[{self.name}]" + # can I put the same way as above here, just to keep it consistent? + key = f"CLASS-DEF[{self.name}]" value = {} value["attributes"] = [ attr.get_struct(simplified) for attr in self.attributes