use super::builder::MirBuilder;
use super::types::{
BinOp, BlockId, Constant, Mutability, Operand, Place, Program, Rvalue, Type, UnOp,
};
use crate::frontend::ast::{
BinaryOp as AstBinOp, Expr, ExprKind, Literal, Param, Type as AstType, UnaryOp as AstUnOp,
};
use anyhow::{anyhow, Result};
use std::collections::HashMap;
pub struct LoweringContext {
builder: MirBuilder,
#[allow(dead_code)]
type_env: HashMap<String, Type>,
current_block: Option<BlockId>,
}
impl LoweringContext {
#[must_use]
pub fn new() -> Self {
Self {
builder: MirBuilder::new(),
type_env: HashMap::new(),
current_block: None,
}
}
pub fn lower_expr(&mut self, expr: &Expr) -> Result<Program> {
match &expr.kind {
ExprKind::Function {
name,
params,
return_type,
body,
..
} => self.lower_function(name, params, return_type.as_ref(), body),
_ => {
self.lower_main_expr(expr)
}
}
}
fn lower_function(
&mut self,
name: &str,
params: &[Param],
return_type: Option<&AstType>,
body: &Expr,
) -> Result<Program> {
let func_name = name.to_string();
let ret_ty = return_type.map_or(Type::Unit, Self::ast_to_mir_type);
self.builder.start_function(func_name.clone(), ret_ty);
for param in params {
let ty = Self::ast_to_mir_type(¶m.ty);
self.builder.add_param(param.name(), ty);
}
let entry = self.builder.new_block();
self.current_block = Some(entry);
let result = self.lower_expr_to_operand(body)?;
self.builder.return_(entry, Some(result));
let function = self
.builder
.finish_function()
.ok_or_else(|| anyhow!("Failed to finish function"))?;
let mut functions = HashMap::new();
functions.insert(func_name.clone(), function);
Ok(Program {
functions,
entry: func_name,
})
}
fn lower_main_expr(&mut self, expr: &Expr) -> Result<Program> {
self.builder.start_function("main".to_string(), Type::Unit);
let entry = self.builder.new_block();
self.current_block = Some(entry);
let _result = self.lower_expr_to_operand(expr)?;
self.builder
.return_(entry, Some(Operand::Constant(Constant::Unit)));
let function = self
.builder
.finish_function()
.ok_or_else(|| anyhow!("Failed to finish main function"))?;
let mut functions = HashMap::new();
functions.insert("main".to_string(), function);
Ok(Program {
functions,
entry: "main".to_string(),
})
}
fn lower_expr_to_operand(&mut self, expr: &Expr) -> Result<Operand> {
let block = self
.current_block
.ok_or_else(|| anyhow!("No current block"))?;
match &expr.kind {
ExprKind::Literal(lit) => Ok(Operand::Constant(Self::lower_literal(lit))),
ExprKind::Identifier(name) => {
if let Some(local) = self.builder.get_local(name) {
Ok(Operand::Copy(Place::Local(local)))
} else {
Err(anyhow!("Unbound variable: {name}"))
}
}
ExprKind::Binary { op, left, right } => {
let left_op = self.lower_expr_to_operand(left)?;
let right_op = self.lower_expr_to_operand(right)?;
let mir_op = Self::lower_binary_op(*op);
let result_ty = Self::infer_binary_result_type(*op);
let temp = self.builder.alloc_local(result_ty, false, None);
self.builder
.binary_op(block, temp, mir_op, left_op, right_op);
Ok(Operand::Move(Place::Local(temp)))
}
ExprKind::Unary { op, operand } => {
let operand_mir = self.lower_expr_to_operand(operand)?;
let mir_op = Self::lower_unary_op(*op);
let result_ty = Self::infer_unary_result_type(*op);
let temp = self.builder.alloc_local(result_ty, false, None);
self.builder.unary_op(block, temp, mir_op, operand_mir);
Ok(Operand::Move(Place::Local(temp)))
}
ExprKind::Let {
name, value, body, ..
} => {
let value_op = self.lower_expr_to_operand(value)?;
let local_ty = Type::I32; let local = self
.builder
.alloc_local(local_ty, false, Some(name.clone()));
self.builder
.assign(block, Place::Local(local), Rvalue::Use(value_op));
self.lower_expr_to_operand(body)
}
ExprKind::If {
condition,
then_branch,
else_branch,
} => {
let cond_op = self.lower_expr_to_operand(condition)?;
let then_block = self.builder.new_block();
let else_block = self.builder.new_block();
let merge_block = self.builder.new_block();
self.builder.branch(block, cond_op, then_block, else_block);
self.current_block = Some(then_block);
let then_result = self.lower_expr_to_operand(then_branch)?;
self.builder.goto(then_block, merge_block);
self.current_block = Some(else_block);
let _else_result = if let Some(else_expr) = else_branch {
self.lower_expr_to_operand(else_expr)?
} else {
Operand::Constant(Constant::Unit)
};
self.builder.goto(else_block, merge_block);
let result_ty = Type::I32; let result_temp = self.builder.alloc_local(result_ty, false, None);
self.current_block = Some(merge_block);
self.builder.assign(
merge_block,
Place::Local(result_temp),
Rvalue::Use(then_result),
);
Ok(Operand::Move(Place::Local(result_temp)))
}
ExprKind::Call { func, args } => {
let func_op = self.lower_expr_to_operand(func)?;
let mut arg_ops = Vec::new();
for arg in args {
arg_ops.push(self.lower_expr_to_operand(arg)?);
}
let result_ty = Type::I32; let result_temp = self.builder.alloc_local(result_ty, false, None);
let next_block = self.builder.call(block, result_temp, func_op, arg_ops);
self.current_block = Some(next_block);
Ok(Operand::Move(Place::Local(result_temp)))
}
ExprKind::Block(exprs) => {
if exprs.is_empty() {
Ok(Operand::Constant(Constant::Unit))
} else {
let mut result = Operand::Constant(Constant::Unit);
for expr in exprs {
result = self.lower_expr_to_operand(expr)?;
}
Ok(result)
}
}
_ => {
Ok(Operand::Constant(Constant::Unit))
}
}
}
fn lower_literal(lit: &Literal) -> Constant {
match lit {
Literal::Integer(i, _) => Constant::Int(i128::from(*i), Type::I32),
Literal::Float(f) => Constant::Float(*f, Type::F64),
Literal::String(s) => Constant::String(s.clone()),
Literal::Bool(b) => Constant::Bool(*b),
Literal::Char(c) => Constant::Char(*c),
Literal::Byte(b) => Constant::Int(i128::from(*b), Type::U8),
Literal::Unit => Constant::Unit,
Literal::Null => Constant::Unit, Literal::Atom(s) => Constant::Symbol(s.clone()),
}
}
fn lower_binary_op(op: AstBinOp) -> BinOp {
match op {
AstBinOp::Add => BinOp::Add,
AstBinOp::Subtract => BinOp::Sub,
AstBinOp::Multiply => BinOp::Mul,
AstBinOp::Divide => BinOp::Div,
AstBinOp::Modulo => BinOp::Rem,
AstBinOp::Power => BinOp::Pow,
AstBinOp::Equal => BinOp::Eq,
AstBinOp::NotEqual => BinOp::Ne,
AstBinOp::Less => BinOp::Lt,
AstBinOp::LessEqual => BinOp::Le,
AstBinOp::Greater => BinOp::Gt,
AstBinOp::GreaterEqual => BinOp::Ge,
AstBinOp::Gt => BinOp::Gt, AstBinOp::And => BinOp::And,
AstBinOp::Or => BinOp::Or,
AstBinOp::NullCoalesce => BinOp::NullCoalesce,
AstBinOp::BitwiseAnd => BinOp::BitAnd,
AstBinOp::BitwiseOr => BinOp::BitOr,
AstBinOp::BitwiseXor => BinOp::BitXor,
AstBinOp::LeftShift => BinOp::Shl,
AstBinOp::RightShift => BinOp::Shr,
AstBinOp::Send => BinOp::Send,
AstBinOp::In => BinOp::In,
}
}
fn lower_unary_op(op: AstUnOp) -> UnOp {
match op {
AstUnOp::Negate => UnOp::Neg,
AstUnOp::Not => UnOp::Not,
AstUnOp::BitwiseNot => UnOp::BitNot,
AstUnOp::Reference | AstUnOp::MutableReference => UnOp::Ref, AstUnOp::Deref => UnOp::Deref,
}
}
#[allow(clippy::only_used_in_recursion)]
fn ast_to_mir_type(ast_ty: &AstType) -> Type {
use crate::frontend::ast::TypeKind;
match &ast_ty.kind {
TypeKind::Named(name) => match name.as_str() {
"bool" => Type::Bool,
"i8" => Type::I8,
"i16" => Type::I16,
"i32" => Type::I32,
"i64" => Type::I64,
"i128" => Type::I128,
"u8" => Type::U8,
"u16" => Type::U16,
"u32" => Type::U32,
"u64" => Type::U64,
"u128" => Type::U128,
"f32" => Type::F32,
"f64" => Type::F64,
"String" => Type::String,
"()" => Type::Unit,
_ => Type::UserType(name.clone()),
},
TypeKind::Generic { base, params } => {
match base.as_str() {
"Vec" if params.len() == 1 => {
Type::Vec(Box::new(Self::ast_to_mir_type(¶ms[0])))
}
"Array" if params.len() == 1 => {
Type::Vec(Box::new(Self::ast_to_mir_type(¶ms[0])))
}
_ => Type::UserType(base.clone()),
}
}
TypeKind::Optional(inner) => {
Type::UserType(format!("Option<{:?}>", inner.kind))
}
TypeKind::Function { params, ret } => {
let param_types = params.iter().map(Self::ast_to_mir_type).collect();
Type::FnPtr(param_types, Box::new(Self::ast_to_mir_type(ret)))
}
TypeKind::List(inner) => Type::Vec(Box::new(Self::ast_to_mir_type(inner))),
TypeKind::Array { elem_type, size: _ } => {
Type::Vec(Box::new(Self::ast_to_mir_type(elem_type)))
}
TypeKind::DataFrame { .. } => {
Type::UserType("DataFrame".to_string())
}
TypeKind::Series { .. } => {
Type::UserType("Series".to_string())
}
TypeKind::Tuple(types) => {
let mir_types: Vec<_> = types.iter().map(Self::ast_to_mir_type).collect();
Type::Tuple(mir_types)
}
TypeKind::Reference { inner, .. } => {
Self::ast_to_mir_type(inner)
}
TypeKind::Refined { base, .. } => Self::ast_to_mir_type(base),
}
}
fn infer_binary_result_type(op: AstBinOp) -> Type {
match op {
AstBinOp::Add
| AstBinOp::Subtract
| AstBinOp::Multiply
| AstBinOp::Divide
| AstBinOp::Modulo
| AstBinOp::Power
| AstBinOp::BitwiseAnd
| AstBinOp::BitwiseOr
| AstBinOp::BitwiseXor
| AstBinOp::LeftShift
| AstBinOp::RightShift => Type::I32,
AstBinOp::Equal
| AstBinOp::NotEqual
| AstBinOp::Less
| AstBinOp::LessEqual
| AstBinOp::Greater
| AstBinOp::GreaterEqual
| AstBinOp::Gt
| AstBinOp::And
| AstBinOp::Or
| AstBinOp::In => Type::Bool, AstBinOp::NullCoalesce => Type::I32, AstBinOp::Send => Type::Unit, }
}
fn infer_unary_result_type(op: AstUnOp) -> Type {
match op {
AstUnOp::Negate | AstUnOp::BitwiseNot => Type::I32,
AstUnOp::Not => Type::Bool,
AstUnOp::Reference => Type::Ref(Box::new(Type::I32), Mutability::Immutable), AstUnOp::MutableReference => Type::Ref(Box::new(Type::I32), Mutability::Mutable), AstUnOp::Deref => Type::I32, }
}
}
impl Default for LoweringContext {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::panic)]
mod tests {
use super::*;
use crate::frontend::Parser;
#[test]
fn test_lower_literal() -> Result<()> {
let mut parser = Parser::new("42");
let ast = parser.parse()?;
let mut ctx = LoweringContext::new();
let program = ctx.lower_expr(&ast)?;
assert_eq!(program.entry, "main");
assert!(program.functions.contains_key("main"));
Ok(())
}
#[test]
fn test_lower_binary_expr() -> Result<()> {
let mut parser = Parser::new("1 + 2");
let ast = parser.parse()?;
let mut ctx = LoweringContext::new();
let program = ctx.lower_expr(&ast)?;
let main_func = &program.functions["main"];
assert!(!main_func.blocks.is_empty());
Ok(())
}
#[test]
fn test_lower_function() -> Result<()> {
let mut parser = Parser::new("fun add(x: i32, y: i32) -> i32 { x + y }");
let ast = parser.parse()?;
let mut ctx = LoweringContext::new();
let program = ctx.lower_expr(&ast)?;
assert!(program.functions.contains_key("add"));
let func = &program.functions["add"];
assert_eq!(func.params.len(), 2);
Ok(())
}
#[test]
fn test_lower_if_expr() -> Result<()> {
let mut parser = Parser::new("if true { 1 } else { 2 }");
let ast = parser.parse()?;
let mut ctx = LoweringContext::new();
let program = ctx.lower_expr(&ast)?;
let main_func = &program.functions["main"];
assert!(main_func.blocks.len() > 1);
Ok(())
}
#[test]
fn test_lowering_context_default() {
let ctx = LoweringContext::default();
assert!(ctx.current_block.is_none());
}
#[test]
fn test_lower_literal_integer() {
let lit = Literal::Integer(42, None);
let constant = LoweringContext::lower_literal(&lit);
assert!(matches!(constant, Constant::Int(42, Type::I32)));
}
#[test]
fn test_lower_literal_float() {
let lit = Literal::Float(3.14);
let constant = LoweringContext::lower_literal(&lit);
if let Constant::Float(f, Type::F64) = constant {
assert!((f - 3.14).abs() < f64::EPSILON);
} else {
panic!("Expected Float constant");
}
}
#[test]
fn test_lower_literal_string() {
let lit = Literal::String("hello".to_string());
let constant = LoweringContext::lower_literal(&lit);
assert!(matches!(constant, Constant::String(s) if s == "hello"));
}
#[test]
fn test_lower_literal_bool() {
let lit = Literal::Bool(true);
let constant = LoweringContext::lower_literal(&lit);
assert!(matches!(constant, Constant::Bool(true)));
}
#[test]
fn test_lower_literal_char() {
let lit = Literal::Char('x');
let constant = LoweringContext::lower_literal(&lit);
assert!(matches!(constant, Constant::Char('x')));
}
#[test]
fn test_lower_literal_unit() {
let lit = Literal::Unit;
let constant = LoweringContext::lower_literal(&lit);
assert!(matches!(constant, Constant::Unit));
}
#[test]
fn test_lower_binary_op_add() {
let op = LoweringContext::lower_binary_op(AstBinOp::Add);
assert!(matches!(op, BinOp::Add));
}
#[test]
fn test_lower_binary_op_subtract() {
let op = LoweringContext::lower_binary_op(AstBinOp::Subtract);
assert!(matches!(op, BinOp::Sub));
}
#[test]
fn test_lower_binary_op_equal() {
let op = LoweringContext::lower_binary_op(AstBinOp::Equal);
assert!(matches!(op, BinOp::Eq));
}
#[test]
fn test_lower_binary_op_and() {
let op = LoweringContext::lower_binary_op(AstBinOp::And);
assert!(matches!(op, BinOp::And));
}
#[test]
fn test_lower_unary_op_negate() {
let op = LoweringContext::lower_unary_op(AstUnOp::Negate);
assert!(matches!(op, UnOp::Neg));
}
#[test]
fn test_lower_unary_op_not() {
let op = LoweringContext::lower_unary_op(AstUnOp::Not);
assert!(matches!(op, UnOp::Not));
}
#[test]
fn test_infer_binary_result_type_arithmetic() {
let ty = LoweringContext::infer_binary_result_type(AstBinOp::Add);
assert!(matches!(ty, Type::I32));
}
#[test]
fn test_infer_binary_result_type_comparison() {
let ty = LoweringContext::infer_binary_result_type(AstBinOp::Equal);
assert!(matches!(ty, Type::Bool));
}
#[test]
fn test_infer_unary_result_type_negate() {
let ty = LoweringContext::infer_unary_result_type(AstUnOp::Negate);
assert!(matches!(ty, Type::I32));
}
#[test]
fn test_infer_unary_result_type_not() {
let ty = LoweringContext::infer_unary_result_type(AstUnOp::Not);
assert!(matches!(ty, Type::Bool));
}
#[test]
fn test_lower_let_expr() -> Result<()> {
let mut parser = Parser::new("let x = 5 in x");
let ast = parser.parse()?;
let mut ctx = LoweringContext::new();
let program = ctx.lower_expr(&ast)?;
assert!(program.functions.contains_key("main"));
Ok(())
}
#[test]
fn test_lower_block_expr() -> Result<()> {
let mut parser = Parser::new("{ 1; 2; 3 }");
let ast = parser.parse()?;
let mut ctx = LoweringContext::new();
let program = ctx.lower_expr(&ast)?;
assert!(program.functions.contains_key("main"));
Ok(())
}
#[test]
fn test_lower_function_with_bool_param() -> Result<()> {
let mut parser = Parser::new("fun f(x: bool) -> bool { x }");
let ast = parser.parse()?;
let mut ctx = LoweringContext::new();
let program = ctx.lower_expr(&ast)?;
let func = &program.functions["f"];
assert_eq!(func.params.len(), 1);
assert_eq!(func.locals[func.params[0].0].ty, Type::Bool);
Ok(())
}
#[test]
fn test_lower_function_with_i8_param() -> Result<()> {
let mut parser = Parser::new("fun f(x: i8) -> i8 { x }");
let ast = parser.parse()?;
let mut ctx = LoweringContext::new();
let program = ctx.lower_expr(&ast)?;
let func = &program.functions["f"];
assert_eq!(func.locals[func.params[0].0].ty, Type::I8);
Ok(())
}
#[test]
fn test_lower_function_with_i16_param() -> Result<()> {
let mut parser = Parser::new("fun f(x: i16) -> i16 { x }");
let ast = parser.parse()?;
let mut ctx = LoweringContext::new();
let program = ctx.lower_expr(&ast)?;
assert_eq!(program.functions["f"].locals[program.functions["f"].params[0].0].ty, Type::I16);
Ok(())
}
#[test]
fn test_lower_function_with_u8_param() -> Result<()> {
let mut parser = Parser::new("fun f(x: u8) -> u8 { x }");
let ast = parser.parse()?;
let mut ctx = LoweringContext::new();
let program = ctx.lower_expr(&ast)?;
assert_eq!(program.functions["f"].locals[program.functions["f"].params[0].0].ty, Type::U8);
Ok(())
}
#[test]
fn test_lower_function_with_u16_param() -> Result<()> {
let mut parser = Parser::new("fun f(x: u16) -> u16 { x }");
let ast = parser.parse()?;
let mut ctx = LoweringContext::new();
let program = ctx.lower_expr(&ast)?;
assert_eq!(program.functions["f"].locals[program.functions["f"].params[0].0].ty, Type::U16);
Ok(())
}
#[test]
fn test_lower_function_with_u32_param() -> Result<()> {
let mut parser = Parser::new("fun f(x: u32) -> u32 { x }");
let ast = parser.parse()?;
let mut ctx = LoweringContext::new();
let program = ctx.lower_expr(&ast)?;
assert_eq!(program.functions["f"].locals[program.functions["f"].params[0].0].ty, Type::U32);
Ok(())
}
#[test]
fn test_lower_function_with_u64_param() -> Result<()> {
let mut parser = Parser::new("fun f(x: u64) -> u64 { x }");
let ast = parser.parse()?;
let mut ctx = LoweringContext::new();
let program = ctx.lower_expr(&ast)?;
assert_eq!(program.functions["f"].locals[program.functions["f"].params[0].0].ty, Type::U64);
Ok(())
}
#[test]
fn test_lower_function_with_i128_param() -> Result<()> {
let mut parser = Parser::new("fun f(x: i128) -> i128 { x }");
let ast = parser.parse()?;
let mut ctx = LoweringContext::new();
let program = ctx.lower_expr(&ast)?;
assert_eq!(program.functions["f"].locals[program.functions["f"].params[0].0].ty, Type::I128);
Ok(())
}
#[test]
fn test_lower_function_with_u128_param() -> Result<()> {
let mut parser = Parser::new("fun f(x: u128) -> u128 { x }");
let ast = parser.parse()?;
let mut ctx = LoweringContext::new();
let program = ctx.lower_expr(&ast)?;
assert_eq!(program.functions["f"].locals[program.functions["f"].params[0].0].ty, Type::U128);
Ok(())
}
#[test]
fn test_lower_function_with_f32_param() -> Result<()> {
let mut parser = Parser::new("fun f(x: f32) -> f32 { x }");
let ast = parser.parse()?;
let mut ctx = LoweringContext::new();
let program = ctx.lower_expr(&ast)?;
assert_eq!(program.functions["f"].locals[program.functions["f"].params[0].0].ty, Type::F32);
Ok(())
}
#[test]
fn test_lower_function_with_f64_param() -> Result<()> {
let mut parser = Parser::new("fun f(x: f64) -> f64 { x }");
let ast = parser.parse()?;
let mut ctx = LoweringContext::new();
let program = ctx.lower_expr(&ast)?;
assert_eq!(program.functions["f"].locals[program.functions["f"].params[0].0].ty, Type::F64);
Ok(())
}
#[test]
fn test_lower_function_with_string_param() -> Result<()> {
let mut parser = Parser::new("fun f(x: String) -> String { x }");
let ast = parser.parse()?;
let mut ctx = LoweringContext::new();
let program = ctx.lower_expr(&ast)?;
assert_eq!(program.functions["f"].locals[program.functions["f"].params[0].0].ty, Type::String);
Ok(())
}
#[test]
fn test_lower_function_with_unit_return() -> Result<()> {
let mut parser = Parser::new("fun f(x: i32) -> () { x }");
let ast = parser.parse()?;
let mut ctx = LoweringContext::new();
let program = ctx.lower_expr(&ast)?;
let func = &program.functions["f"];
assert_eq!(func.return_ty, Type::Unit);
Ok(())
}
#[test]
fn test_lower_function_with_vec_param() -> Result<()> {
let mut parser = Parser::new("fun f(xs: Vec<i32>) -> i32 { 0 }");
let ast = parser.parse()?;
let mut ctx = LoweringContext::new();
let program = ctx.lower_expr(&ast)?;
let func = &program.functions["f"];
assert!(matches!(func.locals[func.params[0].0].ty, Type::Vec(_)));
Ok(())
}
#[test]
fn test_lower_function_with_user_type() -> Result<()> {
let mut parser = Parser::new("fun f(x: MyStruct) -> i32 { 0 }");
let ast = parser.parse()?;
let mut ctx = LoweringContext::new();
let program = ctx.lower_expr(&ast)?;
let func = &program.functions["f"];
assert!(matches!(&func.locals[func.params[0].0].ty, Type::UserType(n) if n == "MyStruct"));
Ok(())
}
#[test]
fn test_lower_function_with_tuple_param() -> Result<()> {
let mut parser = Parser::new("fun f(x: (i32, String)) -> i32 { 0 }");
let ast = parser.parse()?;
let mut ctx = LoweringContext::new();
let program = ctx.lower_expr(&ast)?;
let func = &program.functions["f"];
assert!(matches!(&func.locals[func.params[0].0].ty, Type::Tuple(ts) if ts.len() == 2));
Ok(())
}
#[test]
fn test_lower_unary_negate_expr() -> Result<()> {
let mut parser = Parser::new("-42");
let ast = parser.parse()?;
let mut ctx = LoweringContext::new();
let program = ctx.lower_expr(&ast)?;
let main_func = &program.functions["main"];
assert!(!main_func.blocks.is_empty());
Ok(())
}
#[test]
fn test_lower_unary_not_expr() -> Result<()> {
let mut parser = Parser::new("!true");
let ast = parser.parse()?;
let mut ctx = LoweringContext::new();
let program = ctx.lower_expr(&ast)?;
assert!(program.functions.contains_key("main"));
Ok(())
}
#[test]
fn test_lower_let_binding_with_body() -> Result<()> {
let mut parser = Parser::new("let x = 10 in x + 1");
let ast = parser.parse()?;
let mut ctx = LoweringContext::new();
let program = ctx.lower_expr(&ast)?;
let main_func = &program.functions["main"];
assert!(!main_func.blocks.is_empty());
assert!(
main_func.locals.iter().any(|l| l.name.as_deref() == Some("x")),
"Should have a local named 'x'"
);
Ok(())
}
#[test]
fn test_lower_call_expr() -> Result<()> {
let mut parser = Parser::new("fun apply(f: i32, x: i32) -> i32 { f(x) }");
let ast = parser.parse()?;
let mut ctx = LoweringContext::new();
let program = ctx.lower_expr(&ast)?;
assert!(program.functions.contains_key("apply"));
let func = &program.functions["apply"];
assert!(func.blocks.len() >= 2, "Call should create continuation block");
Ok(())
}
#[test]
fn test_lower_empty_block_expr() -> Result<()> {
let mut parser = Parser::new("{ }");
let ast = parser.parse()?;
let mut ctx = LoweringContext::new();
let program = ctx.lower_expr(&ast)?;
assert!(program.functions.contains_key("main"));
Ok(())
}
#[test]
fn test_lower_multi_expr_block() -> Result<()> {
let mut parser = Parser::new("{ 1; 2; 3 + 4 }");
let ast = parser.parse()?;
let mut ctx = LoweringContext::new();
let program = ctx.lower_expr(&ast)?;
let main_func = &program.functions["main"];
assert!(!main_func.blocks.is_empty());
Ok(())
}
#[test]
fn test_lower_if_no_else() -> Result<()> {
let mut parser = Parser::new("if true { 42 }");
let ast = parser.parse()?;
let mut ctx = LoweringContext::new();
let program = ctx.lower_expr(&ast)?;
let main_func = &program.functions["main"];
assert!(main_func.blocks.len() >= 3);
Ok(())
}
#[test]
fn test_lower_nested_binary_exprs() -> Result<()> {
let mut parser = Parser::new("1 + 2 * 3");
let ast = parser.parse()?;
let mut ctx = LoweringContext::new();
let program = ctx.lower_expr(&ast)?;
let main_func = &program.functions["main"];
assert!(!main_func.blocks.is_empty());
Ok(())
}
#[test]
fn test_lower_identifier_in_function() -> Result<()> {
let mut parser = Parser::new("fun f(x: i32) -> i32 { x }");
let ast = parser.parse()?;
let mut ctx = LoweringContext::new();
let program = ctx.lower_expr(&ast)?;
let func = &program.functions["f"];
assert!(!func.blocks.is_empty());
Ok(())
}
#[test]
fn test_lower_string_literal() -> Result<()> {
let mut parser = Parser::new("\"hello world\"");
let ast = parser.parse()?;
let mut ctx = LoweringContext::new();
let program = ctx.lower_expr(&ast)?;
assert!(program.functions.contains_key("main"));
Ok(())
}
#[test]
fn test_lower_subtract() -> Result<()> {
let mut parser = Parser::new("10 - 3");
let ast = parser.parse()?;
let mut ctx = LoweringContext::new();
let program = ctx.lower_expr(&ast)?;
assert!(program.functions.contains_key("main"));
Ok(())
}
#[test]
fn test_lower_divide() -> Result<()> {
let mut parser = Parser::new("10 / 2");
let ast = parser.parse()?;
let mut ctx = LoweringContext::new();
let program = ctx.lower_expr(&ast)?;
assert!(program.functions.contains_key("main"));
Ok(())
}
#[test]
fn test_lower_modulo() -> Result<()> {
let mut parser = Parser::new("10 % 3");
let ast = parser.parse()?;
let mut ctx = LoweringContext::new();
let program = ctx.lower_expr(&ast)?;
assert!(program.functions.contains_key("main"));
Ok(())
}
#[test]
fn test_lower_power() -> Result<()> {
let mut parser = Parser::new("2 ** 8");
let ast = parser.parse()?;
let mut ctx = LoweringContext::new();
let program = ctx.lower_expr(&ast)?;
assert!(program.functions.contains_key("main"));
Ok(())
}
#[test]
fn test_lower_equal() -> Result<()> {
let mut parser = Parser::new("1 == 2");
let ast = parser.parse()?;
let mut ctx = LoweringContext::new();
let program = ctx.lower_expr(&ast)?;
assert!(program.functions.contains_key("main"));
Ok(())
}
#[test]
fn test_lower_not_equal() -> Result<()> {
let mut parser = Parser::new("1 != 2");
let ast = parser.parse()?;
let mut ctx = LoweringContext::new();
let program = ctx.lower_expr(&ast)?;
assert!(program.functions.contains_key("main"));
Ok(())
}
#[test]
fn test_lower_less() -> Result<()> {
let mut parser = Parser::new("1 < 2");
let ast = parser.parse()?;
let mut ctx = LoweringContext::new();
let program = ctx.lower_expr(&ast)?;
assert!(program.functions.contains_key("main"));
Ok(())
}
#[test]
fn test_lower_less_equal() -> Result<()> {
let mut parser = Parser::new("1 <= 2");
let ast = parser.parse()?;
let mut ctx = LoweringContext::new();
let program = ctx.lower_expr(&ast)?;
assert!(program.functions.contains_key("main"));
Ok(())
}
#[test]
fn test_lower_greater() -> Result<()> {
let mut parser = Parser::new("1 > 2");
let ast = parser.parse()?;
let mut ctx = LoweringContext::new();
let program = ctx.lower_expr(&ast)?;
assert!(program.functions.contains_key("main"));
Ok(())
}
#[test]
fn test_lower_greater_equal() -> Result<()> {
let mut parser = Parser::new("1 >= 2");
let ast = parser.parse()?;
let mut ctx = LoweringContext::new();
let program = ctx.lower_expr(&ast)?;
assert!(program.functions.contains_key("main"));
Ok(())
}
#[test]
fn test_lower_and() -> Result<()> {
let mut parser = Parser::new("true && false");
let ast = parser.parse()?;
let mut ctx = LoweringContext::new();
let program = ctx.lower_expr(&ast)?;
assert!(program.functions.contains_key("main"));
Ok(())
}
#[test]
fn test_lower_or() -> Result<()> {
let mut parser = Parser::new("true || false");
let ast = parser.parse()?;
let mut ctx = LoweringContext::new();
let program = ctx.lower_expr(&ast)?;
assert!(program.functions.contains_key("main"));
Ok(())
}
#[test]
fn test_lower_bitwise_and() -> Result<()> {
let mut parser = Parser::new("5 & 3");
let ast = parser.parse()?;
let mut ctx = LoweringContext::new();
let program = ctx.lower_expr(&ast)?;
assert!(program.functions.contains_key("main"));
Ok(())
}
#[test]
fn test_lower_bitwise_or() -> Result<()> {
let mut parser = Parser::new("5 | 3");
let ast = parser.parse()?;
let mut ctx = LoweringContext::new();
let program = ctx.lower_expr(&ast)?;
assert!(program.functions.contains_key("main"));
Ok(())
}
#[test]
fn test_lower_bitwise_xor() -> Result<()> {
let mut parser = Parser::new("5 ^ 3");
let ast = parser.parse()?;
let mut ctx = LoweringContext::new();
let program = ctx.lower_expr(&ast)?;
assert!(program.functions.contains_key("main"));
Ok(())
}
#[test]
fn test_lower_left_shift() -> Result<()> {
let mut parser = Parser::new("1 << 4");
let ast = parser.parse()?;
let mut ctx = LoweringContext::new();
let program = ctx.lower_expr(&ast)?;
assert!(program.functions.contains_key("main"));
Ok(())
}
#[test]
fn test_lower_right_shift() -> Result<()> {
let mut parser = Parser::new("16 >> 2");
let ast = parser.parse()?;
let mut ctx = LoweringContext::new();
let program = ctx.lower_expr(&ast)?;
assert!(program.functions.contains_key("main"));
Ok(())
}
#[test]
fn test_lower_null_coalesce() -> Result<()> {
let mut parser = Parser::new("fun f(x: i32) -> i32 { x ?? 0 }");
let ast = parser.parse()?;
let mut ctx = LoweringContext::new();
let program = ctx.lower_expr(&ast)?;
assert!(program.functions.contains_key("f"));
Ok(())
}
#[test]
fn test_lower_in_operator() -> Result<()> {
let mut parser = Parser::new("fun f(x: i32, arr: Vec<i32>) -> bool { x in arr }");
let result = parser.parse();
if let Ok(ast) = result {
let mut ctx = LoweringContext::new();
let _ = ctx.lower_expr(&ast);
}
Ok(())
}
#[test]
fn test_lower_bitwise_not() -> Result<()> {
let mut parser = Parser::new("~42");
let ast = parser.parse()?;
let mut ctx = LoweringContext::new();
let program = ctx.lower_expr(&ast)?;
assert!(program.functions.contains_key("main"));
Ok(())
}
#[test]
fn test_lower_reference() -> Result<()> {
let mut parser = Parser::new("fun f(x: i32) -> i32 { &x }");
let ast = parser.parse()?;
let mut ctx = LoweringContext::new();
let program = ctx.lower_expr(&ast)?;
assert!(program.functions.contains_key("f"));
Ok(())
}
#[test]
fn test_lower_deref() -> Result<()> {
let mut parser = Parser::new("fun f(x: i32) -> i32 { *x }");
let ast = parser.parse()?;
let mut ctx = LoweringContext::new();
let program = ctx.lower_expr(&ast)?;
assert!(program.functions.contains_key("f"));
Ok(())
}
#[test]
fn test_lower_bool_param() -> Result<()> {
let mut parser = Parser::new("fun f(x: bool) -> bool { x }");
let ast = parser.parse()?;
let mut ctx = LoweringContext::new();
let program = ctx.lower_expr(&ast)?;
let func = &program.functions["f"];
assert!(!func.locals.is_empty());
Ok(())
}
#[test]
fn test_lower_i8_param() -> Result<()> {
let mut parser = Parser::new("fun f(x: i8) -> i8 { x }");
let ast = parser.parse()?;
let mut ctx = LoweringContext::new();
let program = ctx.lower_expr(&ast)?;
assert!(program.functions.contains_key("f"));
Ok(())
}
#[test]
fn test_lower_i16_param() -> Result<()> {
let mut parser = Parser::new("fun f(x: i16) -> i16 { x }");
let ast = parser.parse()?;
let mut ctx = LoweringContext::new();
let program = ctx.lower_expr(&ast)?;
assert!(program.functions.contains_key("f"));
Ok(())
}
#[test]
fn test_lower_i64_param() -> Result<()> {
let mut parser = Parser::new("fun f(x: i64) -> i64 { x }");
let ast = parser.parse()?;
let mut ctx = LoweringContext::new();
let program = ctx.lower_expr(&ast)?;
assert!(program.functions.contains_key("f"));
Ok(())
}
#[test]
fn test_lower_i128_param() -> Result<()> {
let mut parser = Parser::new("fun f(x: i128) -> i128 { x }");
let ast = parser.parse()?;
let mut ctx = LoweringContext::new();
let program = ctx.lower_expr(&ast)?;
assert!(program.functions.contains_key("f"));
Ok(())
}
#[test]
fn test_lower_u8_param() -> Result<()> {
let mut parser = Parser::new("fun f(x: u8) -> u8 { x }");
let ast = parser.parse()?;
let mut ctx = LoweringContext::new();
let program = ctx.lower_expr(&ast)?;
assert!(program.functions.contains_key("f"));
Ok(())
}
#[test]
fn test_lower_u16_param() -> Result<()> {
let mut parser = Parser::new("fun f(x: u16) -> u16 { x }");
let ast = parser.parse()?;
let mut ctx = LoweringContext::new();
let program = ctx.lower_expr(&ast)?;
assert!(program.functions.contains_key("f"));
Ok(())
}
#[test]
fn test_lower_u32_param() -> Result<()> {
let mut parser = Parser::new("fun f(x: u32) -> u32 { x }");
let ast = parser.parse()?;
let mut ctx = LoweringContext::new();
let program = ctx.lower_expr(&ast)?;
assert!(program.functions.contains_key("f"));
Ok(())
}
#[test]
fn test_lower_u64_param() -> Result<()> {
let mut parser = Parser::new("fun f(x: u64) -> u64 { x }");
let ast = parser.parse()?;
let mut ctx = LoweringContext::new();
let program = ctx.lower_expr(&ast)?;
assert!(program.functions.contains_key("f"));
Ok(())
}
#[test]
fn test_lower_u128_param() -> Result<()> {
let mut parser = Parser::new("fun f(x: u128) -> u128 { x }");
let ast = parser.parse()?;
let mut ctx = LoweringContext::new();
let program = ctx.lower_expr(&ast)?;
assert!(program.functions.contains_key("f"));
Ok(())
}
#[test]
fn test_lower_f32_param() -> Result<()> {
let mut parser = Parser::new("fun f(x: f32) -> f32 { x }");
let ast = parser.parse()?;
let mut ctx = LoweringContext::new();
let program = ctx.lower_expr(&ast)?;
assert!(program.functions.contains_key("f"));
Ok(())
}
#[test]
fn test_lower_f64_param() -> Result<()> {
let mut parser = Parser::new("fun f(x: f64) -> f64 { x }");
let ast = parser.parse()?;
let mut ctx = LoweringContext::new();
let program = ctx.lower_expr(&ast)?;
assert!(program.functions.contains_key("f"));
Ok(())
}
#[test]
fn test_lower_string_param() -> Result<()> {
let mut parser = Parser::new("fun f(x: String) -> String { x }");
let ast = parser.parse()?;
let mut ctx = LoweringContext::new();
let program = ctx.lower_expr(&ast)?;
assert!(program.functions.contains_key("f"));
Ok(())
}
#[test]
fn test_lower_unit_return() -> Result<()> {
let mut parser = Parser::new("fun f() -> () { }");
let ast = parser.parse()?;
let mut ctx = LoweringContext::new();
let program = ctx.lower_expr(&ast)?;
assert!(program.functions.contains_key("f"));
Ok(())
}
#[test]
fn test_lower_custom_type_param() -> Result<()> {
let mut parser = Parser::new("fun f(x: MyStruct) -> MyStruct { x }");
let ast = parser.parse()?;
let mut ctx = LoweringContext::new();
let program = ctx.lower_expr(&ast)?;
assert!(program.functions.contains_key("f"));
Ok(())
}
#[test]
fn test_lower_vec_type_param() -> Result<()> {
let mut parser = Parser::new("fun f(x: Vec<i32>) -> Vec<i32> { x }");
let ast = parser.parse()?;
let mut ctx = LoweringContext::new();
let program = ctx.lower_expr(&ast)?;
assert!(program.functions.contains_key("f"));
Ok(())
}
#[test]
fn test_lower_array_type_param() -> Result<()> {
let mut parser = Parser::new("fun f(x: Array<f64>) -> Array<f64> { x }");
let ast = parser.parse()?;
let mut ctx = LoweringContext::new();
let program = ctx.lower_expr(&ast)?;
assert!(program.functions.contains_key("f"));
Ok(())
}
#[test]
fn test_lower_float_literal() -> Result<()> {
let mut parser = Parser::new("3.14");
let ast = parser.parse()?;
let mut ctx = LoweringContext::new();
let program = ctx.lower_expr(&ast)?;
assert!(program.functions.contains_key("main"));
Ok(())
}
#[test]
fn test_lower_bool_literal_false() -> Result<()> {
let mut parser = Parser::new("false");
let ast = parser.parse()?;
let mut ctx = LoweringContext::new();
let program = ctx.lower_expr(&ast)?;
assert!(program.functions.contains_key("main"));
Ok(())
}
#[test]
fn test_lower_nil_literal() -> Result<()> {
let mut parser = Parser::new("None");
let ast = parser.parse()?;
let mut ctx = LoweringContext::new();
let _ = ctx.lower_expr(&ast);
Ok(())
}
#[test]
fn test_lower_if_else() -> Result<()> {
let mut parser = Parser::new("if true { 1 } else { 2 }");
let ast = parser.parse()?;
let mut ctx = LoweringContext::new();
let program = ctx.lower_expr(&ast)?;
let main_func = &program.functions["main"];
assert!(main_func.blocks.len() >= 3);
Ok(())
}
#[test]
fn test_lower_return_expr() -> Result<()> {
let mut parser = Parser::new("fun f() -> i32 { return 42 }");
let ast = parser.parse()?;
let mut ctx = LoweringContext::new();
let program = ctx.lower_expr(&ast)?;
assert!(program.functions.contains_key("f"));
Ok(())
}
#[test]
fn test_lower_multiple_params() -> Result<()> {
let mut parser = Parser::new("fun add(a: i32, b: i32) -> i32 { a + b }");
let ast = parser.parse()?;
let mut ctx = LoweringContext::new();
let program = ctx.lower_expr(&ast)?;
let func = &program.functions["add"];
assert!(func.locals.len() >= 2);
Ok(())
}
}
#[cfg(test)]
mod property_tests_lower {
use proptest::proptest;
proptest! {
#[test]
fn test_new_never_panics(input: String) {
let _input = if input.len() > 100 { &input[..100] } else { &input[..] };
let _ = std::panic::catch_unwind(|| {
});
}
}
}