arxlang / astx

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

`IndexExpr` - Indexing into a list or array (e.g., `arr[index]`). #98

Open xmnlab opened 1 month ago

xmnlab commented 1 month ago

from gpt:

Updating the Design to Support Slices

In Python's AST, the Subscript node has a slice attribute that can be:

We'll mimic this behavior in our astx module by:


1. Defining SliceExpr

First, let's create the SliceExpr class.

# astx/expressions.py

from typing import Optional

@public
class SliceExpr(Expr):
    """AST class for slice expressions."""

    lower: Optional[Expr]
    upper: Optional[Expr]
    step: Optional[Expr]

    def __init__(
        self,
        lower: Optional[Expr] = None,
        upper: Optional[Expr] = None,
        step: Optional[Expr] = None,
        loc: SourceLocation = NO_SOURCE_LOCATION,
        parent: Optional[ASTNodes] = None,
    ) -> None:
        """
        Initialize the SliceExpr instance.

        Parameters:
        - lower: The lower bound of the slice (inclusive).
        - upper: The upper bound of the slice (exclusive).
        - step: The step size of the slice.
        - loc: The source location of the expression.
        - parent: The parent AST node.
        """
        super().__init__(loc=loc, parent=parent)
        self.lower = lower
        self.upper = upper
        self.step = step
        self.kind = ASTKind.SliceExprKind  # We'll need to add this to ASTKind

    def __str__(self) -> str:
        """Return a string representation of the slice."""
        lower_str = str(self.lower) if self.lower else ''
        upper_str = str(self.upper) if self.upper else ''
        step_str = str(self.step) if self.step else ''
        if self.step:
            return f"SliceExpr({lower_str}:{upper_str}:{step_str})"
        else:
            return f"SliceExpr({lower_str}:{upper_str})"

    def get_struct(self, simplified: bool = False) -> ReprStruct:
        """Return the AST structure of the slice expression."""
        key = "SliceExpr"
        value = {
            "lower": self.lower.get_struct(simplified) if self.lower else None,
            "upper": self.upper.get_struct(simplified) if self.upper else None,
            "step": self.step.get_struct(simplified) if self.step else None,
        }
        return self._prepare_struct(key, value, simplified)

Explanation:

Note: We need to add SliceExprKind to ASTKind.


2. Updating ASTKind Enum

Add the new kind for SliceExpr.

# astx/base.py

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

    # ... existing kinds ...

    # Expressions
    ForRangeExprKind = -800
    ForCounterExprKind = -801
    WhileExprKind = -802
    MemberAccessExprKind = -803
    IndexExprKind = -804
    SliceExprKind = -805  # Add this line

    # ... rest of the code ...

3. Modifying IndexExpr

Modify IndexExpr to accept either a single index (Expr) or a list of indices (list of Expr), which can include SliceExpr instances.

# astx/expressions.py

@public
class IndexExpr(Expr):
    """AST class for index expressions."""

    value: Expr
    index: Union[Expr, List[Expr]]  # Modify to accept single Expr or list of Expr

    def __init__(
        self,
        value: Expr,
        index: Union[Expr, List[Expr]],
        loc: SourceLocation = NO_SOURCE_LOCATION,
        parent: Optional[ASTNodes] = None,
    ) -> None:
        """
        Initialize the IndexExpr instance.

        Parameters:
        - value: The expression representing the object being indexed (e.g., an array or list).
        - index: The index or indices used to access the element(s).
          This can be:
            - A single Expr (e.g., Constant, Variable, SliceExpr)
            - A list of Expr instances (for multi-dimensional indexing)
        - loc: The source location of the expression.
        - parent: The parent AST node.
        """
        super().__init__(loc=loc, parent=parent)
        self.value = value
        self.index = index
        self.kind = ASTKind.IndexExprKind

    def __str__(self) -> str:
        """Return a string representation of the object."""
        if isinstance(self.index, list):
            index_str = ", ".join(str(idx) for idx in self.index)
        else:
            index_str = str(self.index)
        return f"IndexExpr({self.value}[{index_str}])"

    def get_struct(self, simplified: bool = False) -> ReprStruct:
        """Return the AST structure of the object."""
        key = "IndexExpr"
        if isinstance(self.index, list):
            index_value = [idx.get_struct(simplified) for idx in self.index]
        else:
            index_value = self.index.get_struct(simplified)
        value = {
            "collection": self.value.get_struct(simplified),
            "index": index_value,
        }
        return self._prepare_struct(key, value, simplified)

Explanation:


Example Usage

Let's revisit your examples and see how we can represent them using the updated IndexExpr and the new SliceExpr.

1. a[1]

from astx.expressions import IndexExpr
from astx.variables import Variable
from astx.datatypes import LiteralInt32
from astx.base import SourceLocation

# Variables
a_var = Variable(name="a", loc=SourceLocation(line=1, col=0))
index = LiteralInt32(value=1, loc=SourceLocation(line=1, col=2))

# IndexExpr
index_expr = IndexExpr(
    value=a_var,
    index=index,
    loc=SourceLocation(line=1, col=1)
)

# Print the string representation
print(index_expr)

Output:

IndexExpr(Variable[a][Literal[Int32]: 1])

2. a[0:1]

from astx.expressions import IndexExpr, SliceExpr
from astx.datatypes import LiteralInt32

# SliceExpr for 0:1
slice_expr = SliceExpr(
    lower=LiteralInt32(0),
    upper=LiteralInt32(1),
    loc=SourceLocation(line=1, col=2)
)

# IndexExpr
index_expr = IndexExpr(
    value=a_var,
    index=slice_expr,
    loc=SourceLocation(line=1, col=1)
)

# Print the string representation
print(index_expr)

Output:

IndexExpr(Variable[a][SliceExpr(Literal[Int32]: 0:Literal[Int32]: 1)])

3. a[:,:]

from astx.datatypes import LiteralNone  # Assuming LiteralNone represents 'None' or missing value

# SliceExpr for ':'
slice_expr1 = SliceExpr(loc=SourceLocation(line=1, col=2))
slice_expr2 = SliceExpr(loc=SourceLocation(line=1, col=3))

# IndexExpr with list of slices
index_expr = IndexExpr(
    value=a_var,
    index=[slice_expr1, slice_expr2],
    loc=SourceLocation(line=1, col=1)
)

# Print the string representation
print(index_expr)

Output:

IndexExpr(Variable[a][SliceExpr(:), SliceExpr(:)])

4. a[:,:,:]

# Three SliceExpr instances
slice_expr1 = SliceExpr(loc=SourceLocation(line=1, col=2))
slice_expr2 = SliceExpr(loc=SourceLocation(line=1, col=3))
slice_expr3 = SliceExpr(loc=SourceLocation(line=1, col=4))

# IndexExpr with list of slices
index_expr = IndexExpr(
    value=a_var,
    index=[slice_expr1, slice_expr2, slice_expr3],
    loc=SourceLocation(line=1, col=1)
)

# Print the string representation
print(index_expr)

Output:

IndexExpr(Variable[a][SliceExpr(:), SliceExpr(:), SliceExpr(:)])

Handling Slices with Step

For completeness, let's handle slices with steps, e.g., a[::2].

# SliceExpr for '::2'
slice_expr = SliceExpr(
    lower=None,
    upper=None,
    step=LiteralInt32(2),
    loc=SourceLocation(line=1, col=2)
)

# IndexExpr
index_expr = IndexExpr(
    value=a_var,
    index=slice_expr,
    loc=SourceLocation(line=1, col=1)
)

# Print the string representation
print(index_expr)

Output:

IndexExpr(Variable[a][SliceExpr(::Literal[Int32]: 2)])

Integration with Existing Structures

# astx/datatypes.py

@public
class LiteralNone(Literal):
    """LiteralNone data type class."""

    def __init__(
        self, loc: SourceLocation = NO_SOURCE_LOCATION
    ) -> None:
        """Initialize LiteralNone."""
        super().__init__(loc)
        self.value = None
        self.type_ = NoneType  # You may need to define a NoneType class
        self.loc = loc
@public
class NoneType(DataType):
    """NoneType data type expression."""

Alternatively, since slices often have None for missing values, and our SliceExpr allows lower, upper, and step to be None, we can avoid needing a LiteralNone and represent missing values by None.


Testing and Validation


Conclusion

By introducing the SliceExpr class and updating the IndexExpr class, we've extended the astx module to fully support slicing, similar to Python's AST Subscript node. This enhancement allows you to represent a wide range of indexing operations, including single indices, slices, and multi-dimensional indexing with slices.


Final Notes

xmnlab commented 1 month ago

example from python ast:


>>> ast.dump(ast.parse("a[1]"))
"Module(body=[Expr(value=Subscript(value=Name(id='a', ctx=Load()), slice=Constant(value=1), ctx=Load()))], type_ignores=[])"

>>> ast.dump(ast.parse("a[0:1]"))
"Module(body=[Expr(value=Subscript(value=Name(id='a', ctx=Load()), slice=Slice(lower=Constant(value=0), upper=Constant(value=1)), ctx=Load()))], type_ignores=[])"

>>> ast.dump(ast.parse("a[:,:]"))
"Module(body=[Expr(value=Subscript(value=Name(id='a', ctx=Load()), slice=Tuple(elts=[Slice(), Slice()], ctx=Load()), ctx=Load()))], type_ignores=[])"

>>> ast.dump(ast.parse("a[:,:,:]"))
"Module(body=[Expr(value=Subscript(value=Name(id='a', ctx=Load()), slice=Tuple(elts=[Slice(), Slice(), Slice()], ctx=Load()), ctx=Load()))], type_ignores=[])"