use formalang::ast::{Literal, NumberValue, PrimitiveType};
use formalang::ir::{IrExpr, ResolvedType};
use wasm_encoder::{Ieee32, Ieee64, InstructionSink, MemArg, ValType};
use super::{LowerContext, LowerError};
use crate::layout::{OPTIONAL_TAG_ALIGN, OPTIONAL_TAG_NIL, OPTIONAL_TAG_SIZE};
use crate::module::MEMORY_INDEX;
#[expect(
clippy::too_many_lines,
reason = "exhaustive match over every primitive / non-primitive Literal arm; splitting out one helper per arm hides the dispatch"
)]
pub fn lower_literal(
expr: &IrExpr,
sink: &mut InstructionSink<'_>,
ctx: &LowerContext<'_>,
) -> Result<(), LowerError> {
let IrExpr::Literal { value, ty, .. } = expr else {
return Err(LowerError::NotYetImplemented {
what: "lower_literal called with non-literal expression".to_owned(),
});
};
if matches!(value, Literal::Nil) {
return lower_nil(ty, sink, ctx);
}
if matches!(
value,
Literal::String(_) | Literal::Path(_) | Literal::Regex { .. }
) {
return lower_string_literal(value, ty, sink, ctx);
}
let prim = match ty {
ResolvedType::Primitive(p) => *p,
ResolvedType::Struct(_)
| ResolvedType::Trait(_)
| ResolvedType::Enum(_)
| ResolvedType::Tuple(_)
| ResolvedType::Generic { .. }
| ResolvedType::TypeParam(_)
| ResolvedType::External { .. }
| ResolvedType::Closure { .. }
| ResolvedType::Error => {
return Err(LowerError::LiteralTypeMismatch {
kind: literal_kind_tag(value),
ty: ty.clone(),
});
}
};
match (value, prim) {
(Literal::Boolean(b), PrimitiveType::Boolean) => {
sink.i32_const(i32::from(*b));
}
(Literal::Boolean(_), other) => {
return Err(LowerError::LiteralTypeMismatch {
kind: "Boolean".to_owned(),
ty: ResolvedType::Primitive(other),
});
}
(Literal::Number(n), PrimitiveType::I32) => {
let v = n
.value
.as_i32()
.ok_or_else(|| LowerError::LiteralOutOfRange {
payload: number_value_string(&n.value),
target: PrimitiveType::I32,
})?;
sink.i32_const(v);
}
(Literal::Number(n), PrimitiveType::I64) => {
let v = n
.value
.as_i64()
.ok_or_else(|| LowerError::LiteralOutOfRange {
payload: number_value_string(&n.value),
target: PrimitiveType::I64,
})?;
sink.i64_const(v);
}
(Literal::Number(n), PrimitiveType::F32) => {
sink.f32_const(Ieee32::from(n.value.as_f32()));
}
(Literal::Number(n), PrimitiveType::F64) => {
sink.f64_const(Ieee64::from(n.value.as_f64()));
}
(Literal::Number(_), other) => {
return Err(LowerError::LiteralTypeMismatch {
kind: "Number".to_owned(),
ty: ResolvedType::Primitive(other),
});
}
(Literal::String(_), _) => {
return Err(LowerError::LiteralTypeMismatch {
kind: "String".to_owned(),
ty: ty.clone(),
});
}
(Literal::Path(_), _) => {
return Err(LowerError::NotYetImplemented {
what: "Literal::Path (Phase 2)".to_owned(),
});
}
(Literal::Regex { .. }, _) => {
return Err(LowerError::NotYetImplemented {
what: "Literal::Regex (Phase 2)".to_owned(),
});
}
(Literal::Nil, _) => {
return Err(LowerError::LiteralTypeMismatch {
kind: "Nil".to_owned(),
ty: ty.clone(),
});
}
(other, _) => {
return Err(LowerError::NotYetImplemented {
what: format!("Literal::{}", literal_kind_tag(other)),
});
}
}
Ok(())
}
fn lower_string_literal(
value: &Literal,
ty: &ResolvedType,
sink: &mut InstructionSink<'_>,
ctx: &LowerContext<'_>,
) -> Result<(), LowerError> {
let text: &str = match value {
Literal::String(s) | Literal::Path(s) => s.as_str(),
Literal::Regex { pattern, .. } => pattern.as_str(),
Literal::Number(_) | Literal::Boolean(_) | Literal::Nil => {
return Err(LowerError::LiteralTypeMismatch {
kind: literal_kind_tag(value),
ty: ty.clone(),
});
}
other => {
return Err(LowerError::LiteralTypeMismatch {
kind: literal_kind_tag(other),
ty: ty.clone(),
});
}
};
if !matches!(
ty,
ResolvedType::Primitive(PrimitiveType::String | PrimitiveType::Path | PrimitiveType::Regex,)
) {
return Err(LowerError::LiteralTypeMismatch {
kind: literal_kind_tag(value),
ty: ty.clone(),
});
}
let header_offset = ctx.string_header_offset(text)?;
let signed = i32::try_from(header_offset).unwrap_or(i32::MAX);
sink.i32_const(signed);
Ok(())
}
fn lower_nil(
ty: &ResolvedType,
sink: &mut InstructionSink<'_>,
ctx: &LowerContext<'_>,
) -> Result<(), LowerError> {
let module = ctx.module()?;
if crate::compound::optional_inner(ty, module).is_none() {
return Err(LowerError::LiteralTypeMismatch {
kind: "Nil".to_owned(),
ty: ty.clone(),
});
}
let alloc_idx = ctx.bump_allocator()?;
let base_local = ctx.next_scratch_local(ValType::I32)?;
sink.i32_const(i32::try_from(OPTIONAL_TAG_SIZE).unwrap_or(i32::MAX));
sink.call(alloc_idx);
sink.local_set(base_local);
sink.local_get(base_local);
sink.i32_const(i32::try_from(OPTIONAL_TAG_NIL).unwrap_or(0));
sink.i32_store(MemArg {
offset: 0,
align: OPTIONAL_TAG_ALIGN.trailing_zeros(),
memory_index: MEMORY_INDEX,
});
sink.local_get(base_local);
Ok(())
}
fn literal_kind_tag(lit: &Literal) -> String {
match lit {
Literal::String(_) => "String".to_owned(),
Literal::Number(_) => "Number".to_owned(),
Literal::Boolean(_) => "Boolean".to_owned(),
Literal::Regex { .. } => "Regex".to_owned(),
Literal::Path(_) => "Path".to_owned(),
Literal::Nil => "Nil".to_owned(),
_ => "Unknown".to_owned(),
}
}
fn number_value_string(v: &NumberValue) -> String {
match v {
NumberValue::Integer(n) => n.to_string(),
NumberValue::Float(f) => f.to_string(),
_ => "<unknown>".to_owned(),
}
}