use crate::ast::{Expr, Literal, Spanned, Stmt, StrPart, TopLevel};
pub fn lower_interpolation_pass(items: &mut [TopLevel]) {
for item in items.iter_mut() {
if let TopLevel::FnDef(fd) = item {
let body_arc = std::sync::Arc::make_mut(&mut fd.body);
let crate::ast::FnBody::Block(stmts) = body_arc;
for stmt in stmts.iter_mut() {
lower_in_stmt(stmt);
}
}
}
}
fn lower_in_stmt(stmt: &mut Stmt) {
match stmt {
Stmt::Binding(_, _, expr) | Stmt::Expr(expr) => lower_in_expr(expr),
}
}
fn lower_in_expr(expr: &mut Spanned<Expr>) {
match &mut expr.node {
Expr::FnCall(callee, args) => {
lower_in_expr(callee);
for a in args.iter_mut() {
lower_in_expr(a);
}
}
Expr::TailCall(data) => {
for a in data.args.iter_mut() {
lower_in_expr(a);
}
}
Expr::Attr(inner, _) => lower_in_expr(inner),
Expr::List(items) | Expr::Tuple(items) | Expr::IndependentProduct(items, _) => {
for it in items.iter_mut() {
lower_in_expr(it);
}
}
Expr::MapLiteral(entries) => {
for (k, v) in entries.iter_mut() {
lower_in_expr(k);
lower_in_expr(v);
}
}
Expr::RecordCreate { fields, .. } => {
for (_, v) in fields.iter_mut() {
lower_in_expr(v);
}
}
Expr::RecordUpdate { base, updates, .. } => {
lower_in_expr(base);
for (_, v) in updates.iter_mut() {
lower_in_expr(v);
}
}
Expr::Match { subject, arms } => {
lower_in_expr(subject);
for arm in arms.iter_mut() {
lower_in_expr(&mut arm.body);
}
}
Expr::BinOp(_, left, right) => {
lower_in_expr(left);
lower_in_expr(right);
}
Expr::Constructor(_, inner) => {
if let Some(inner) = inner.as_deref_mut() {
lower_in_expr(inner);
}
}
Expr::ErrorProp(inner) => lower_in_expr(inner),
Expr::InterpolatedStr(parts) => {
for part in parts.iter_mut() {
if let StrPart::Parsed(inner) = part {
lower_in_expr(inner);
}
}
}
_ => {}
}
if let Expr::InterpolatedStr(parts) = &expr.node {
let line = expr.line;
let pipeline = build_buffer_pipeline(line, parts);
*expr = pipeline;
}
}
fn build_buffer_pipeline(line: usize, parts: &[StrPart]) -> Spanned<Expr> {
if parts.is_empty() {
return sp_at(line, Expr::Literal(Literal::Str(String::new())));
}
let cap_hint: i64 = parts
.iter()
.map(|p| match p {
StrPart::Literal(s) => s.len() as i64,
StrPart::Parsed(_) => 16,
})
.sum::<i64>()
.max(16);
let mut buf = intrinsic_call(
line,
"__buf_new",
vec![sp_at(line, Expr::Literal(Literal::Int(cap_hint)))],
);
for part in parts {
let part_str = match part {
StrPart::Literal(s) => sp_at(line, Expr::Literal(Literal::Str(s.clone()))),
StrPart::Parsed(inner) => intrinsic_call(line, "__to_str", vec![(**inner).clone()]),
};
buf = intrinsic_call(line, "__buf_append", vec![buf, part_str]);
}
intrinsic_call(line, "__buf_finalize", vec![buf])
}
fn sp_at(line: usize, node: Expr) -> Spanned<Expr> {
Spanned { node, line }
}
fn intrinsic_call(line: usize, name: &str, args: Vec<Spanned<Expr>>) -> Spanned<Expr> {
let callee = sp_at(line, Expr::Ident(name.to_string()));
sp_at(line, Expr::FnCall(Box::new(callee), args))
}