Skip to content

Commit 908fc0f

Browse files
committed
feat: add f-string support infrastructure in ZynML grammar
- Add fold_concat() helper function in grammar interpreter to desugar f-strings into nested concat(a, b) calls instead of multi-arg concat - Add to_string mapping to $IO$format_dynamic for type conversion - Add signature for $IO$string_concat in zrtl_io plugin - Update f-string grammar rules to use fold_concat(parts) Note: F-strings are not yet fully functional due to prelude bool return type issue (is_some/is_none returning i32 instead of i8) that causes function compilation to be skipped.
1 parent ddb78e1 commit 908fc0f

7 files changed

Lines changed: 113 additions & 12 deletions

File tree

crates/zyn_peg/src/runtime2/interpreter.rs

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1798,6 +1798,73 @@ impl<'g> GrammarInterpreter<'g> {
17981798

17991799
Ok(acc)
18001800
}
1801+
"fold_concat" => {
1802+
// fold_concat(parts) - fold string parts into nested concat calls
1803+
// e.g., fold_concat(["a", "b", "c"]) -> concat(concat("a", "b"), "c")
1804+
// Used by f-string desugaring
1805+
if args.len() != 1 {
1806+
return Err("fold_concat() requires exactly 1 argument (parts list)".to_string());
1807+
}
1808+
let parts = self.eval_expr(&args[0], state)?;
1809+
1810+
// Get the parts as a list
1811+
let parts_list = match parts {
1812+
ParsedValue::List(items) => items,
1813+
ParsedValue::Optional(None) | ParsedValue::None => vec![],
1814+
ParsedValue::Optional(Some(inner)) => {
1815+
match *inner {
1816+
ParsedValue::List(items) => items,
1817+
other => vec![other],
1818+
}
1819+
}
1820+
other => vec![other],
1821+
};
1822+
1823+
// Handle edge cases
1824+
if parts_list.is_empty() {
1825+
// Return empty string
1826+
return Ok(ParsedValue::Expression(Box::new(typed_node(
1827+
TypedExpression::Literal(zyntax_typed_ast::TypedLiteral::String(
1828+
state.intern(""),
1829+
)),
1830+
Type::Primitive(zyntax_typed_ast::PrimitiveType::String),
1831+
span,
1832+
))));
1833+
}
1834+
1835+
if parts_list.len() == 1 {
1836+
// Just return the single part as an expression
1837+
return self.parsed_value_to_expr(parts_list.into_iter().next().unwrap(), state)
1838+
.map(|e| ParsedValue::Expression(Box::new(e)));
1839+
}
1840+
1841+
// Fold left: concat(concat(a, b), c)
1842+
let concat_name = state.intern("concat");
1843+
let mut iter = parts_list.into_iter();
1844+
let first = iter.next().unwrap();
1845+
let mut acc = self.parsed_value_to_expr(first, state)?;
1846+
1847+
for part in iter {
1848+
let part_expr = self.parsed_value_to_expr(part, state)?;
1849+
// Create concat(acc, part)
1850+
acc = typed_node(
1851+
TypedExpression::Call(TypedCall {
1852+
callee: Box::new(typed_node(
1853+
TypedExpression::Variable(concat_name),
1854+
Type::Unknown,
1855+
span,
1856+
)),
1857+
positional_args: vec![acc, part_expr],
1858+
named_args: vec![],
1859+
type_args: vec![],
1860+
}),
1861+
Type::Primitive(zyntax_typed_ast::PrimitiveType::String),
1862+
span,
1863+
);
1864+
}
1865+
1866+
Ok(ParsedValue::Expression(Box::new(acc)))
1867+
}
18011868
_ => Err(format!("unknown helper function: {}", function)),
18021869
}
18031870
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Test direct concat call
2+
import prelude
3+
4+
def main() {
5+
let a = "Hello"
6+
let b = " World"
7+
let c = extern concat(a, b)
8+
println(c)
9+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// Minimal concat test without prelude
2+
def main() {
3+
let a = "Hello"
4+
let b = " World"
5+
let c = extern concat(a, b)
6+
extern println_str(c)
7+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Test basic string concat
2+
import prelude
3+
4+
def main() {
5+
let a = "Hello"
6+
let b = " World"
7+
let c = extern string_concat(a, b)
8+
println(c)
9+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Test f-string functionality
2+
import prelude
3+
4+
def main() {
5+
let name = "World"
6+
println(f"Hello {name}!")
7+
8+
let x = 42
9+
println(f"The answer is {x}")
10+
11+
let a = 10
12+
let b = 20
13+
println(f"a={a}, b={b}, sum={a}")
14+
}

crates/zynml/ml.zyn

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -217,9 +217,10 @@
217217
println_array_f64: "$IO$println_array_f64",
218218

219219
// String operations
220-
concat: "$IO$string_concat", // Used by f-string desugaring
220+
concat: "$IO$string_concat", // Used by f-string desugaring (binary concat)
221221
string_concat: "$IO$string_concat",
222222
string_free: "$IO$string_free",
223+
to_string: "$IO$format_dynamic", // Used by f-string desugaring for interpolations
223224

224225
// === File Operations (zrtl_fs) ===
225226
file_read: "$FS$read_string",
@@ -2683,24 +2684,18 @@ duration_unit = { "ms" | "s" | "m" | "h" | "d" }
26832684
// """
26842685
//
26852686
// Format specifiers (after colon) are supported:
2686-
// F-strings desugar to concat(parts...) where each interpolation is wrapped in to_string()
2687-
// Example: f"Hello {name}!" -> concat("Hello ", to_string(name), "!")
2687+
// F-strings desugar to nested concat(a, b) calls where each interpolation is wrapped in to_string()
2688+
// Example: f"Hello {name}!" -> concat(concat("Hello ", to_string(name)), "!")
26882689
//
26892690
f_string_literal = { f_string_triple | f_string_regular }
26902691

26912692
// f"""multiline {expr} string"""
26922693
f_string_triple = { "f\"\"\"" ~ parts:f_string_part* ~ "\"\"\"" }
2693-
-> TypedExpression::Call {
2694-
callee: TypedExpression::Variable { name: intern("concat") },
2695-
args: parts,
2696-
}
2694+
-> fold_concat(parts)
26972695

26982696
// f"single line {expr} string"
26992697
f_string_regular = { "f\"" ~ parts:f_string_part* ~ "\"" }
2700-
-> TypedExpression::Call {
2701-
callee: TypedExpression::Variable { name: intern("concat") },
2702-
args: parts,
2703-
}
2698+
-> fold_concat(parts)
27042699

27052700
// Parts of an f-string: either text or interpolated expression
27062701
f_string_part = { f_string_interp | f_string_text }

plugins/zrtl_io/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -918,7 +918,7 @@ zrtl_plugin! {
918918
("$IO$println_array_f64", io_println_array_f64),
919919

920920
// String operations
921-
("$IO$string_concat", io_string_concat),
921+
("$IO$string_concat", io_string_concat, (i64, i64) -> i64),
922922
]
923923
}
924924

0 commit comments

Comments
 (0)