diff --git a/src/astx/base.py b/src/astx/base.py index 7692c00d..4bd736f0 100644 --- a/src/astx/base.py +++ b/src/astx/base.py @@ -160,6 +160,9 @@ class ASTKind(Enum): DateDTKind = -625 DateTimeDTKind = -626 + # string formatting + FormattedValueKind = -627 + # imports(packages) ImportStmtKind = -700 ImportFromStmtKind = -701 diff --git a/src/astx/literals/string.py b/src/astx/literals/string.py index f44ac4fe..fb758752 100644 --- a/src/astx/literals/string.py +++ b/src/astx/literals/string.py @@ -8,10 +8,13 @@ NO_SOURCE_LOCATION, ReprStruct, SourceLocation, + ASTKind, ) from astx.literals.base import Literal from astx.tools.typing import typechecked from astx.types.string import String, UTF8Char, UTF8String +from astx.base import DataType +from typing import Optional @public @@ -87,3 +90,39 @@ def get_struct(self, simplified: bool = False) -> ReprStruct: key = f"LiteralUTF8Char: {self.value}" value = self.value return self._prepare_struct(key, value, simplified) + + +@public +@typechecked +class FormattedValue(DataType): + """AST class for f-string formatted values (e.g., {x:.2f}).""" + + value: DataType + format_spec: Optional[str] + + def __init__( + self, + value: DataType, + format_spec: Optional[str] = None, + loc: SourceLocation = NO_SOURCE_LOCATION, + parent: Optional[ASTNodes] = None, + ) -> None: + """Initialize the FormattedValue instance.""" + super().__init__(loc=loc, parent=parent) + self.value = value + self.format_spec = format_spec + self.kind = ASTKind.FormattedValueKind + + def __str__(self) -> str: + """Return a string representation of the object.""" + format_str = f":{self.format_spec}" if self.format_spec else "" + return f"FormattedValue({self.value}{format_str})" + + def get_struct(self, simplified: bool = False) -> ReprStruct: + """Return the AST structure of the object.""" + key = "FORMATTED-VALUE" + value = { + "value": self.value.get_struct(simplified), + "format_spec": self.format_spec if self.format_spec else None + } + return self._prepare_struct(key, value, simplified) diff --git a/src/astx/tools/transpilers/python.py b/src/astx/tools/transpilers/python.py index a15246cd..938d9fc6 100644 --- a/src/astx/tools/transpilers/python.py +++ b/src/astx/tools/transpilers/python.py @@ -674,3 +674,10 @@ def visit(self, node: astx.LiteralDict) -> str: for key, value in node.elements.items() ) return f"{{{items_code}}}" + + @dispatch # type: ignore[no-redef] + def visit(self, node: astx.FormattedValue) -> str: + """Handle FormattedValue nodes for f-string formatting.""" + value = self.visit(node.value) + format_spec = f":{node.format_spec}" if node.format_spec else "" + return f"{{{value}{format_spec}}}" diff --git a/tests/tools/transpilers/test_python.py b/tests/tools/transpilers/test_python.py index 41fe58f3..56ff9f27 100644 --- a/tests/tools/transpilers/test_python.py +++ b/tests/tools/transpilers/test_python.py @@ -298,6 +298,26 @@ def test_literal_float32() -> None: assert generated_code == expected_code, "generated_code != expected_code" +def test_formatted_value() -> None: + """Test astx.FormattedValue for f-string formatting.""" + # Create a FormattedValue node with a float value and format spec + value = astx.LiteralFloat32(value=3.14159) + formatted_value = astx.FormattedValue(value=value, format_spec=".2f") + + # Generate Python code + generated_code = translate(formatted_value) + expected_code = "{3.14159:.2f}" + + assert generated_code == expected_code, "generated_code != expected_code" + + # Test without format spec + formatted_value_no_spec = astx.FormattedValue(value=value) + generated_code = translate(formatted_value_no_spec) + expected_code = "{3.14159}" + + assert generated_code == expected_code, "generated_code != expected_code" + + def test_literal_float64() -> None: """Test astx.LiteralFloat64.""" # Create a LiteralFloat64 node