use crate::ast::{Expr, Literal, Spanned, Stmt, StrPart, TopLevel, Type};
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 buffer_ty = Type::Named("Buffer".to_string());
let mut buf = intrinsic_call(
line,
"__buf_new",
vec![sp_at_typed(
line,
Expr::Literal(Literal::Int(cap_hint)),
Type::Int,
)],
);
buf.set_ty(buffer_ty.clone());
for part in parts {
let part_str = match part {
StrPart::Literal(s) => {
sp_at_typed(line, Expr::Literal(Literal::Str(s.clone())), Type::Str)
}
StrPart::Parsed(inner) => {
let call = intrinsic_call(line, "__to_str", vec![(**inner).clone()]);
call.set_ty(Type::Str);
call
}
};
buf = intrinsic_call(line, "__buf_append", vec![buf, part_str]);
buf.set_ty(buffer_ty.clone());
}
let finalized = intrinsic_call(line, "__buf_finalize", vec![buf]);
finalized.set_ty(Type::Str);
finalized
}
fn sp_at(line: usize, node: Expr) -> Spanned<Expr> {
Spanned::new(node, line)
}
fn sp_at_typed(line: usize, node: Expr, ty: Type) -> Spanned<Expr> {
let s = Spanned::new(node, line);
s.set_ty(ty);
s
}
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))
}