diff --git a/libs/astx-transpilers/src/astx_transpilers/python_string.py b/libs/astx-transpilers/src/astx_transpilers/python_string.py index c6c0128..41fb0ff 100644 --- a/libs/astx-transpilers/src/astx_transpilers/python_string.py +++ b/libs/astx-transpilers/src/astx_transpilers/python_string.py @@ -120,6 +120,19 @@ def visit(self, node: astx.BreakStmt) -> str: """Handle BreakStmt nodes.""" return "break" + @dispatch # type: ignore[no-redef] + def visit(self, node: astx.Slice) -> str: + """Handle Slice nodes.""" + lower = self.visit(node.lower) if node.lower is not None else "" + upper = self.visit(node.upper) if node.upper is not None else "" + + if node.step is not None: + step = ":" + self.visit(node.step) + else: + step = "" + + return f"{lower}:{upper}{step}" + @dispatch # type: ignore[no-redef] def visit(self, node: astx.CaseStmt) -> str: """Handle CaseStmt nodes.""" diff --git a/libs/astx-transpilers/tests/test_python.py b/libs/astx-transpilers/tests/test_python.py index 91d82d3..f50f4bc 100644 --- a/libs/astx-transpilers/tests/test_python.py +++ b/libs/astx-transpilers/tests/test_python.py @@ -1385,6 +1385,58 @@ def test_transpiler_xnor_op() -> None: ) +def test_transpiler_slice() -> None: + """Test transpiling Slice expressions.""" + slice_full = astx.Slice( + lower=astx.LiteralInt32(1), + upper=astx.LiteralInt32(10), + step=astx.LiteralInt32(2), + ) + + code = transpiler.visit(slice_full) + expected_code = "1:10:2" + assert code == expected_code, ( + f"Expected '{expected_code}', but got '{code}'" + ) + + # Slice with missing lower bound + slice_no_lower = astx.Slice( + upper=astx.Variable(name="end"), step=astx.LiteralInt32(-1) + ) + code = transpiler.visit(slice_no_lower) + expected_code = ":end:-1" + assert code == expected_code, ( + f"Expected '{expected_code}', but got '{code}'" + ) + + slice_only_upper = astx.Slice(upper=astx.LiteralInt32(5)) + code = transpiler.visit(slice_only_upper) + expected_code = ":5" + assert code == expected_code, ( + f"Expected '{expected_code}', but got '{code}'" + ) + + slice_empty = astx.Slice() + code = transpiler.visit(slice_empty) + expected_code = ":" + assert code == expected_code, ( + f"Expected '{expected_code}', but got '{code}'" + ) + + my_list = astx.Variable(name="my_list") + subscript = astx.SubscriptExpr( + value=my_list, + lower=slice_full.lower, + upper=slice_full.upper, + step=slice_full.step, + ) + generated_code = translate(subscript) + expected_code = "my_list[1:10:2]" + assert generated_code == expected_code, ( + f"Expected '{expected_code}', but got '{generated_code}'" + ) + + def test_group_expr() -> None: """Test struct representation.""" grp = astx.ParenthesizedExpr( diff --git a/libs/astx/src/astx/__init__.py b/libs/astx/src/astx/__init__.py index 8e1c2ea..7faf44e 100644 --- a/libs/astx/src/astx/__init__.py +++ b/libs/astx/src/astx/__init__.py @@ -25,6 +25,7 @@ Identifier, OperatorType, ParenthesizedExpr, + Slice, SourceLocation, StatementType, Undefined, @@ -329,6 +330,7 @@ def get_version() -> str: "SetComprehension", "SetType", "SignedInteger", + "Slice", "SourceLocation", "StatementType", "String", diff --git a/libs/astx/src/astx/base.py b/libs/astx/src/astx/base.py index d79e106..29136a4 100644 --- a/libs/astx/src/astx/base.py +++ b/libs/astx/src/astx/base.py @@ -205,6 +205,7 @@ class ASTKind(Enum): NorOpKind = -1204 XnorOpKind = -1205 NotOpKind = -1206 + SliceKind = -1302 class ASTMeta(type): @@ -519,3 +520,79 @@ def get_struct(self, simplified: bool = False) -> ReprStruct: key = "PARENTHESIZED-EXPR" value = self.value.get_struct(simplified) return self._prepare_struct(key, value, simplified) + + +@public +@typechecked +class Slice(Expr): + """ + Represents a slice object [start:stop:step]. + + Used within subscript expressions for slicing operations. + Equivalent to Python's ast.Slice. + """ + + 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 Slice instance.""" + super().__init__(loc=loc, parent=parent) + self.lower = lower + self.upper = upper + self.step = step + self.kind = ASTKind.SliceKind + + def _get_part_str(self, part: Optional[Expr]) -> str: + """Get the string representation of a slice part.""" + if part is None: + return "" + + if hasattr(part, "value"): + value = getattr(part, "value") + if isinstance(value, (int, float, str, bool)): + return str(value) + + if hasattr(part, "name"): + return str(getattr(part, "name")) + + result: str = str(part) + return result + + def __str__(self) -> str: + """Return a string representation of the slice.""" + lower_str = self._get_part_str(self.lower) + upper_str = self._get_part_str(self.upper) + + if self.step is not None: + step_str = ":" + self._get_part_str(self.step) + else: + step_str = "" + + return f"{lower_str}:{upper_str}{step_str}" + + def get_struct(self, simplified: bool = False) -> ReprStruct: + """Return the AST structure of the slice.""" + slice_parts = {} + + if self.lower is not None: + slice_parts["lower"] = self.lower.get_struct(simplified) + + if self.upper is not None: + slice_parts["upper"] = self.upper.get_struct(simplified) + + if self.step is not None: + slice_parts["step"] = self.step.get_struct(simplified) + + key = "SLICE" + value = cast(ReprStruct, slice_parts) + + return self._prepare_struct(key, value, simplified) diff --git a/libs/astx/tests/test_base.py b/libs/astx/tests/test_base.py index 40f62f1..7182cd9 100644 --- a/libs/astx/tests/test_base.py +++ b/libs/astx/tests/test_base.py @@ -124,3 +124,28 @@ def test_parenthesized_expr_2() -> None: node_2 = astx.OrOp(node_1, node_1) assert node_2.get_struct(simplified=True) assert node_2.get_struct(simplified=False) + + +def test_slice_expr() -> None: + """Test slice expression.""" + # Simple slice with all components + slice_full = astx.Slice( + lower=astx.LiteralInt32(1), + upper=astx.LiteralInt32(10), + step=astx.LiteralInt32(2), + ) + + assert str(slice_full) == "1:10:2" + assert slice_full.get_struct(simplified=True) + + # Slice with missing components + slice_partial = astx.Slice(upper=astx.LiteralInt32(5)) + + assert str(slice_partial) == ":5" + assert slice_partial.get_struct(simplified=True) + + # Empty slice + slice_empty = astx.Slice() + + assert str(slice_empty) == ":" + assert slice_empty.get_struct(simplified=True)