diff --git a/src/codegen/c_backend.rs b/src/codegen/c_backend.rs index 56712d0..344e2d6 100644 --- a/src/codegen/c_backend.rs +++ b/src/codegen/c_backend.rs @@ -858,6 +858,7 @@ impl CBackend { self.writeln("#include "); self.writeln("#include "); self.writeln("#include "); + self.writeln("#include "); self.writeln(""); self.writeln("// === Lux Runtime Types ==="); self.writeln(""); @@ -3029,6 +3030,10 @@ impl CBackend { self.register_rc_var(&temp, "LuxString"); return Ok(temp); } + // Math module + if module_name.name == "Math" { + return self.emit_math_operation(&field.name, args); + } // Check for user-defined module function let key = (module_name.name.clone(), field.name.clone()); if let Some(c_name) = self.module_functions.get(&key).cloned() { @@ -3392,6 +3397,11 @@ impl CBackend { } } + // Math module (treated as effect by parser but handled as direct C calls) + if effect.name == "Math" { + return self.emit_math_operation(&operation.name, args); + } + // Built-in Console effect if effect.name == "Console" { if operation.name == "print" { @@ -3854,12 +3864,34 @@ impl CBackend { } } - Expr::Record { fields, .. } => { - let field_strs: Result, _> = fields.iter().map(|(name, val)| { - let v = self.emit_expr(val)?; - Ok(format!(".{} = {}", name.name, v)) - }).collect(); - Ok(format!("{{ {} }}", field_strs?.join(", "))) + Expr::Record { + spread, fields, .. + } => { + if let Some(spread_expr) = spread { + // Evaluate spread source, then override fields + let base = self.emit_expr(spread_expr)?; + if fields.is_empty() { + Ok(base) + } else { + // Copy spread into a temp, then override fields + let temp = format!("_spread_{}", self.fresh_name()); + self.writeln(&format!("__auto_type {} = {};", temp, base)); + for (name, val) in fields { + let v = self.emit_expr(val)?; + self.writeln(&format!("{}.{} = {};", temp, name.name, v)); + } + Ok(temp) + } + } else { + let field_strs: Result, _> = fields + .iter() + .map(|(name, val)| { + let v = self.emit_expr(val)?; + Ok(format!(".{} = {}", name.name, v)) + }) + .collect(); + Ok(format!("{{ {} }}", field_strs?.join(", "))) + } } Expr::Field { object, field, .. } => { @@ -3929,6 +3961,64 @@ impl CBackend { } } + /// Emit code for Math module operations (Math.sin, Math.cos, etc.) + fn emit_math_operation(&mut self, op: &str, args: &[Expr]) -> Result { + match op { + "abs" => { + let x = self.emit_expr(&args[0])?; + Ok(format!("fabs({})", x)) + } + "min" => { + let a = self.emit_expr(&args[0])?; + let b = self.emit_expr(&args[1])?; + Ok(format!("fmin({}, {})", a, b)) + } + "max" => { + let a = self.emit_expr(&args[0])?; + let b = self.emit_expr(&args[1])?; + Ok(format!("fmax({}, {})", a, b)) + } + "sqrt" => { + let x = self.emit_expr(&args[0])?; + Ok(format!("sqrt({})", x)) + } + "pow" => { + let base = self.emit_expr(&args[0])?; + let exp = self.emit_expr(&args[1])?; + Ok(format!("pow({}, {})", base, exp)) + } + "floor" => { + let x = self.emit_expr(&args[0])?; + Ok(format!("(int64_t)floor({})", x)) + } + "ceil" => { + let x = self.emit_expr(&args[0])?; + Ok(format!("(int64_t)ceil({})", x)) + } + "round" => { + let x = self.emit_expr(&args[0])?; + Ok(format!("(int64_t)round({})", x)) + } + "sin" => { + let x = self.emit_expr(&args[0])?; + Ok(format!("sin({})", x)) + } + "cos" => { + let x = self.emit_expr(&args[0])?; + Ok(format!("cos({})", x)) + } + "atan2" => { + let y = self.emit_expr(&args[0])?; + let x = self.emit_expr(&args[1])?; + Ok(format!("atan2({}, {})", y, x)) + } + _ => Err(CGenError { + message: format!("Math.{} not supported in C backend", op), + span: None, + }), + } + } + /// Emit code for List module operations (List.map, List.filter, etc.) fn emit_list_operation(&mut self, op: &str, args: &[Expr]) -> Result { match op { @@ -5831,7 +5921,10 @@ impl CBackend { } self.collect_free_vars(body, &inner_bound, free); } - Expr::Record { fields, .. } => { + Expr::Record { spread, fields, .. } => { + if let Some(spread_expr) = spread { + self.collect_free_vars(spread_expr, bound, free); + } for (_, val) in fields { self.collect_free_vars(val, bound, free); } diff --git a/src/interpreter.rs b/src/interpreter.rs index 53a7efc..203faf5 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -74,6 +74,9 @@ pub enum BuiltinFn { MathFloor, MathCeil, MathRound, + MathSin, + MathCos, + MathAtan2, // Additional List operations ListIsEmpty, @@ -1072,6 +1075,9 @@ impl Interpreter { ("floor".to_string(), Value::Builtin(BuiltinFn::MathFloor)), ("ceil".to_string(), Value::Builtin(BuiltinFn::MathCeil)), ("round".to_string(), Value::Builtin(BuiltinFn::MathRound)), + ("sin".to_string(), Value::Builtin(BuiltinFn::MathSin)), + ("cos".to_string(), Value::Builtin(BuiltinFn::MathCos)), + ("atan2".to_string(), Value::Builtin(BuiltinFn::MathAtan2)), ])); env.define("Math", math_module); @@ -1564,8 +1570,28 @@ impl Interpreter { self.eval_expr_tail(result, &block_env, tail) } - Expr::Record { fields, .. } => { + Expr::Record { + spread, fields, .. + } => { let mut record = HashMap::new(); + + // If there's a spread, evaluate it and start with its fields + if let Some(spread_expr) = spread { + let spread_val = self.eval_expr(spread_expr, env)?; + if let Value::Record(spread_fields) = spread_val { + record = spread_fields; + } else { + return Err(RuntimeError { + message: format!( + "Spread expression must evaluate to a record, got {}", + spread_val.type_name() + ), + span: Some(expr.span()), + }); + } + } + + // Override with explicit fields for (name, expr) in fields { let val = self.eval_expr(expr, env)?; record.insert(name.name.clone(), val); @@ -2514,6 +2540,45 @@ impl Interpreter { } } + BuiltinFn::MathSin => { + if args.len() != 1 { + return Err(err("Math.sin requires 1 argument")); + } + match &args[0] { + Value::Float(n) => Ok(EvalResult::Value(Value::Float(n.sin()))), + Value::Int(n) => Ok(EvalResult::Value(Value::Float((*n as f64).sin()))), + v => Err(err(&format!("Math.sin expects number, got {}", v.type_name()))), + } + } + + BuiltinFn::MathCos => { + if args.len() != 1 { + return Err(err("Math.cos requires 1 argument")); + } + match &args[0] { + Value::Float(n) => Ok(EvalResult::Value(Value::Float(n.cos()))), + Value::Int(n) => Ok(EvalResult::Value(Value::Float((*n as f64).cos()))), + v => Err(err(&format!("Math.cos expects number, got {}", v.type_name()))), + } + } + + BuiltinFn::MathAtan2 => { + if args.len() != 2 { + return Err(err("Math.atan2 requires 2 arguments: y, x")); + } + let y = match &args[0] { + Value::Float(n) => *n, + Value::Int(n) => *n as f64, + v => return Err(err(&format!("Math.atan2 expects number, got {}", v.type_name()))), + }; + let x = match &args[1] { + Value::Float(n) => *n, + Value::Int(n) => *n as f64, + v => return Err(err(&format!("Math.atan2 expects number, got {}", v.type_name()))), + }; + Ok(EvalResult::Value(Value::Float(y.atan2(x)))) + } + // Additional List operations BuiltinFn::ListIsEmpty => { let list = Self::expect_arg_1::>(&args, "List.isEmpty", span)?; @@ -5095,6 +5160,7 @@ mod tests { // Create a simple migration that adds a field // Migration: old.name -> { name: old.name, email: "unknown" } let migration_body = Expr::Record { + spread: None, fields: vec![ ( Ident::new("name", Span::default()), diff --git a/src/types.rs b/src/types.rs index 2d14315..cd20a16 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1887,6 +1887,18 @@ impl TypeEnv { "round".to_string(), Type::function(vec![Type::var()], Type::Int), ), + ( + "sin".to_string(), + Type::function(vec![Type::Float], Type::Float), + ), + ( + "cos".to_string(), + Type::function(vec![Type::Float], Type::Float), + ), + ( + "atan2".to_string(), + Type::function(vec![Type::Float, Type::Float], Type::Float), + ), ]); env.bind("Math", TypeScheme::mono(math_module_type));