Skip to content

feat: Add FormattedValue support for f-string formatting #218

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/astx/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,9 @@ class ASTKind(Enum):
DateDTKind = -625
DateTimeDTKind = -626

# string formatting
FormattedValueKind = -627

# imports(packages)
ImportStmtKind = -700
ImportFromStmtKind = -701
Expand Down
39 changes: 39 additions & 0 deletions src/astx/literals/string.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
7 changes: 7 additions & 0 deletions src/astx/tools/transpilers/python.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}}}"
20 changes: 20 additions & 0 deletions tests/tools/transpilers/test_python.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading