arxlang / astx

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

`ImportExpr`/`ImportFromExpr` - Represents an import expression. #88

Closed xmnlab closed 1 month ago

xmnlab commented 1 month ago

from gpt:

Understanding the Requirements

In your AST framework:


Use Cases for ImportExpr and ImportFromExpr

In some programming languages, importing can be performed at runtime within expressions. Examples include:

In such cases, the import operation is an expression that can be used within other expressions or assigned to variables.


Designing ImportExpr and ImportFromExpr Classes

Inheritance Hierarchy

Class Definitions

  1. ImportExpr

    • Inherits from Expr.
    • Represents an import operation as an expression.
    • Should encapsulate the module name and any aliasing.
  2. ImportFromExpr

    • Inherits from Expr.
    • Represents a from ... import ... operation as an expression.
    • Should encapsulate the module name, the names being imported, and any aliasing.

Defining the Classes

1. Updating ASTKind Enum

First, we'll need to add new kinds to the ASTKind enum for these expressions.

# In src/astx/base.py, within the ASTKind enum, add:

ImportExprKind = -800
ImportFromExprKind = -801

2. Implementing ImportExpr Class

from astx.base import Expr, ASTKind, SourceLocation, NO_SOURCE_LOCATION, ASTNodes
from astx.types import ReprStruct
from typing import Optional
from public import public

@public
class ImportExpr(Expr):
    """Represents an import operation as an expression."""

    module: str
    alias: Optional[str]

    def __init__(
        self,
        module: str,
        alias: Optional[str] = None,
        loc: SourceLocation = NO_SOURCE_LOCATION,
        parent: Optional[ASTNodes] = None,
    ) -> None:
        super().__init__(loc=loc, parent=parent)
        self.module = module
        self.alias = alias
        self.kind = ASTKind.ImportExprKind
        # You can set the type_ attribute if needed
        # self.type_ = ModuleType or similar

    def __str__(self) -> str:
        """Return a string representation of the import expression."""
        if self.alias:
            return f"import {self.module} as {self.alias}"
        else:
            return f"import {self.module}"

    def get_struct(self, simplified: bool = False) -> ReprStruct:
        """Return the AST structure of the import expression."""
        key = "ImportExpr"
        value = {
            "module": self.module,
            "alias": self.alias,
        }
        return self._prepare_struct(key, value, simplified)

Explanation:

3. Implementing ImportFromExpr Class

@public
class ImportFromExpr(Expr):
    """Represents a 'from ... import ...' operation as an expression."""

    module: Optional[str]
    names: list[str]
    aliases: dict[str, Optional[str]]  # Mapping from name to alias
    level: int  # Number of leading dots for relative imports

    def __init__(
        self,
        module: Optional[str],
        names: list[str],
        aliases: Optional[dict[str, Optional[str]]] = None,
        level: int = 0,
        loc: SourceLocation = NO_SOURCE_LOCATION,
        parent: Optional[ASTNodes] = None,
    ) -> None:
        super().__init__(loc=loc, parent=parent)
        self.module = module
        self.names = names
        self.aliases = aliases or {}
        self.level = level
        self.kind = ASTKind.ImportFromExprKind
        # You can set the type_ attribute if needed

    def __str__(self) -> str:
        """Return a string representation of the import-from expression."""
        level_dots = "." * self.level
        module_str = f"{level_dots}{self.module}" if self.module else level_dots
        imports = []
        for name in self.names:
            alias = self.aliases.get(name)
            if alias:
                imports.append(f"{name} as {alias}")
            else:
                imports.append(name)
        imports_str = ", ".join(imports)
        return f"from {module_str} import {imports_str}"

    def get_struct(self, simplified: bool = False) -> ReprStruct:
        """Return the AST structure of the import-from expression."""
        key = "ImportFromExpr"
        value = {
            "module": self.module,
            "level": self.level,
            "imports": [
                {"name": name, "alias": self.aliases.get(name)}
                for name in self.names
            ],
        }
        return self._prepare_struct(key, value, simplified)

Explanation:


Considerations

1. Type Annotations

2. Dynamic Imports

3. Integration with Existing Classes

Using AliasExpr in ImportFromExpr:

from typing import List

@public
class ImportFromExpr(Expr):
    """Represents a 'from ... import ...' operation as an expression."""

    module: Optional[str]
    aliases: List[AliasExpr]
    level: int  # Number of leading dots for relative imports

    def __init__(
        self,
        module: Optional[str],
        aliases: List[AliasExpr],
        level: int = 0,
        loc: SourceLocation = NO_SOURCE_LOCATION,
        parent: Optional[ASTNodes] = None,
    ) -> None:
        super().__init__(loc=loc, parent=parent)
        self.module = module
        self.aliases = aliases
        self.level = level
        self.kind = ASTKind.ImportFromExprKind

    def __str__(self) -> str:
        """Return a string representation of the import-from expression."""
        level_dots = "." * self.level
        module_str = f"{level_dots}{self.module}" if self.module else level_dots
        imports_str = ", ".join(str(alias) for alias in self.aliases)
        return f"from {module_str} import {imports_str}"

    def get_struct(self, simplified: bool = False) -> ReprStruct:
        """Return the AST structure of the import-from expression."""
        key = "ImportFromExpr"
        value = {
            "module": self.module,
            "level": self.level,
            "imports": [alias.get_struct(simplified) for alias in self.aliases],
        }
        return self._prepare_struct(key, value, simplified)

Example Usage

1. Importing a Module as an Expression

# Create an import expression
import_expr = ImportExpr(module="math", alias=None)

# Use the import expression in an assignment
variable = Variable(name="math_module")
assignment = VariableAssignment(variable=variable, value=import_expr)

# Now 'math_module' holds the imported 'math' module

2. Importing Specific Names from a Module

# Create alias expressions
alias1 = AliasExpr(name="sqrt", asname="square_root")
alias2 = AliasExpr(name="pi")

# Create an import-from expression
import_from_expr = ImportFromExpr(module="math", aliases=[alias1, alias2])

# Use the import-from expression in code
# For example, as part of a function call or another expression

Conclusion

By designing ImportExpr and ImportFromExpr as subclasses of Expr, you can represent import operations as expressions within your AST framework. This allows for dynamic importing and integration with other expressions in the language.


Next Steps


Additional Considerations

1. Error Handling

2. Contextual Semantics

3. Runtime Behavior

apkrelling commented 1 month ago

@xmnlab , would it be possible for you to put here quick examples of what the output of the visit method of the transpiler for these classes should be? Thank you!

xmnlab commented 1 month ago

@apkrelling python doesn't have this import or import-from features as expressions, but we can workaround that.

just for the record some other programming languages would offer that (from gpt):

but with python we could do it with __import__:


# Using __import__() as an expression
module = __import__('module_name')

for the import-from, maybe you could do something like this:

name = getattr(__import__('module_name', fromlist=['name']), 'name')

in the case of multiple imports


name1, name2 = (
    getattr(__import__('module_name', fromlist=['name1']), 'name1'), 
    getattr(__import__('module_name', fromlist=['name2']), 'name2'), 
)
apkrelling commented 1 month ago

Hello @xmnlab , about the AST ascii viz showing the wrong node names: The node names that will show on the AST ascii viz are the keys of the get_struct dictionary. The way we are currently designing the AliasExpr, ImportStmt, ImportFromStmt, ImportExpr, and ImportFomExpr classes, the names that we want as node names are the values. So the get_struct method would need to be modified to make them keys, not values. See the example below: weird_ast

apkrelling commented 1 month ago

The ascii viz problem is solved on PR #122 .