From 0c4b4e8fd051ee5a51beac0ee70e990b761f62e7 Mon Sep 17 00:00:00 2001 From: Brandon Lucas Date: Sat, 14 Feb 2026 14:16:54 -0500 Subject: [PATCH] feat: add toString, string concat, keyword escaping, and conditional RC - Add escape_c_keyword() to mangle C reserved words (double, int, etc.) - Implement toString() mapping to lux_int_to_string() - Add string concatenation detection for BinaryOp::Add using lux_string_concat() - Add is_string_expr() helper for string expression detection - Update infer_expr_type() for toString, string concat, and if expressions - Implement complex conditionals RC handling: use if-statements instead of ternaries when branches create RC values to avoid allocating unused branches Co-Authored-By: Claude Opus 4.5 --- src/codegen/c_backend.rs | 164 +++++++++++++++++++++++++++++++++++---- 1 file changed, 148 insertions(+), 16 deletions(-) diff --git a/src/codegen/c_backend.rs b/src/codegen/c_backend.rs index 675bef4..cd0766b 100644 --- a/src/codegen/c_backend.rs +++ b/src/codegen/c_backend.rs @@ -292,12 +292,13 @@ impl CBackend { fn emit_expr_with_env(&mut self, expr: &Expr, captured: &HashSet<&str>) -> Result { match expr { Expr::Var(ident) => { + let escaped = self.escape_c_keyword(&ident.name); if captured.contains(ident.name.as_str()) { - Ok(format!("env->{}", ident.name)) + Ok(format!("env->{}", escaped)) } else if self.functions.contains(&ident.name) { Ok(self.mangle_name(&ident.name)) } else { - Ok(ident.name.clone()) + Ok(escaped) } } Expr::Literal(lit) => self.emit_literal(lit), @@ -1381,6 +1382,59 @@ impl CBackend { format!("{}_lux", name) } + /// Escape C reserved keywords by adding underscore prefix + fn escape_c_keyword(&self, name: &str) -> String { + // C reserved keywords that might conflict with Lux variable names + const C_KEYWORDS: &[&str] = &[ + // C89/C90 keywords + "auto", "break", "case", "char", "const", "continue", "default", + "do", "double", "else", "enum", "extern", "float", "for", "goto", + "if", "int", "long", "register", "return", "short", "signed", + "sizeof", "static", "struct", "switch", "typedef", "union", + "unsigned", "void", "volatile", "while", + // C99 keywords + "inline", "restrict", "_Bool", "_Complex", "_Imaginary", + // C11 keywords + "_Alignas", "_Alignof", "_Atomic", "_Generic", "_Noreturn", + "_Static_assert", "_Thread_local", + // Common type aliases and macros that might conflict + "bool", "true", "false", "NULL", + // Standard library identifiers that might conflict + "stdin", "stdout", "stderr", "errno", "main", + ]; + + if C_KEYWORDS.contains(&name) { + format!("_{}", name) + } else { + name.to_string() + } + } + + /// Check if an expression is a string expression (for concatenation detection) + fn is_string_expr(&self, expr: &Expr) -> bool { + match expr { + // String literals + Expr::Literal(lit) => matches!(lit.kind, LiteralKind::String(_)), + + // toString function call + Expr::Call { func, .. } => { + if let Expr::Var(ident) = func.as_ref() { + ident.name == "toString" + } else { + false + } + } + + // String concatenation with + + Expr::BinaryOp { op, left, right, .. } => { + matches!(op, BinaryOp::Add) + && (self.is_string_expr(left) || self.is_string_expr(right)) + } + + _ => false, + } + } + fn emit_forward_declarations(&mut self, program: &Program) -> Result<(), CGenError> { for decl in &program.declarations { if let Declaration::Function(f) = decl { @@ -1505,7 +1559,8 @@ impl CBackend { let param_strs: Result, _> = params.iter().map(|p| { let c_type = self.type_expr_to_c(&p.typ)?; - Ok(format!("{} {}", c_type, p.name.name)) + let escaped_name = self.escape_c_keyword(&p.name.name); + Ok(format!("{} {}", c_type, escaped_name)) }).collect(); Ok(param_strs?.join(", ")) @@ -1522,7 +1577,8 @@ impl CBackend { let variant_name = &ident.name; Ok(format!("({}){{{}_TAG_{}}}", type_name, type_name, variant_name.to_uppercase())) } else { - Ok(ident.name.clone()) + // Escape C reserved keywords + Ok(self.escape_c_keyword(&ident.name)) } } @@ -1530,6 +1586,15 @@ impl CBackend { let l = self.emit_expr(left)?; let r = self.emit_expr(right)?; + // Check for string concatenation + if matches!(op, BinaryOp::Add) { + let left_is_string = self.is_string_expr(left); + let right_is_string = self.is_string_expr(right); + if left_is_string || right_is_string { + return Ok(format!("lux_string_concat({}, {})", l, r)); + } + } + let op_str = match op { BinaryOp::Add => "+", BinaryOp::Sub => "-", @@ -1563,10 +1628,47 @@ impl CBackend { } Expr::If { condition, then_branch, else_branch, .. } => { - let cond = self.emit_expr(condition)?; - let then_val = self.emit_expr(then_branch)?; - let else_val = self.emit_expr(else_branch)?; - Ok(format!("({} ? {} : {})", cond, then_val, else_val)) + // Check if branches create RC values - if so, use if-statement to avoid + // allocating both when only one is needed + let then_creates_rc = self.expr_creates_rc_value(then_branch); + let else_creates_rc = self.expr_creates_rc_value(else_branch); + + if then_creates_rc || else_creates_rc { + // Use if-statement pattern to avoid allocating unused branch + let result_type = self.infer_expr_type(then_branch) + .or_else(|| self.infer_expr_type(else_branch)) + .unwrap_or_else(|| "LuxInt".to_string()); + let result_var = format!("_if_result_{}", self.fresh_name()); + + // Declare result variable + self.writeln(&format!("{} {};", result_type, result_var)); + + // Emit condition + let cond = self.emit_expr(condition)?; + self.writeln(&format!("if ({}) {{", cond)); + self.indent += 1; + + // Emit then branch + let then_val = self.emit_expr(then_branch)?; + self.writeln(&format!("{} = {};", result_var, then_val)); + self.indent -= 1; + self.writeln("} else {"); + self.indent += 1; + + // Emit else branch + let else_val = self.emit_expr(else_branch)?; + self.writeln(&format!("{} = {};", result_var, else_val)); + self.indent -= 1; + self.writeln("}"); + + Ok(result_var) + } else { + // Simple ternary for non-RC values + let cond = self.emit_expr(condition)?; + let then_val = self.emit_expr(then_branch)?; + let else_val = self.emit_expr(else_branch)?; + Ok(format!("({} ? {} : {})", cond, then_val, else_val)) + } } Expr::Let { name, value, body, .. } => { @@ -1594,6 +1696,15 @@ impl CBackend { } } + // Check for built-in functions like toString + if let Expr::Var(ident) = func.as_ref() { + if ident.name == "toString" { + // toString converts Int to String + let arg = self.emit_expr(&args[0])?; + return Ok(format!("lux_int_to_string({})", arg)); + } + } + let arg_strs: Result, _> = args.iter().map(|a| self.emit_expr(a)).collect(); let args_str = arg_strs?.join(", "); @@ -1699,12 +1810,12 @@ impl CBackend { // Generate unique closure ID let id = self.fresh_name(); - // Determine parameter types + // Determine parameter types (escape C keywords) let param_pairs: Vec<(String, String)> = params.iter() .map(|p| { let typ = self.type_expr_to_c(&p.typ) .unwrap_or_else(|_| "LuxInt".to_string()); - (p.name.name.clone(), typ) + (self.escape_c_keyword(&p.name.name), typ) }) .collect(); @@ -1713,9 +1824,9 @@ impl CBackend { .map(|t| self.type_expr_to_c(t).unwrap_or_else(|_| "LuxInt".to_string())) .unwrap_or_else(|| "LuxInt".to_string()); - // Determine captured variable types (default to LuxInt for now) + // Determine captured variable types (default to LuxInt for now, escape C keywords) let env_fields: Vec<(String, String)> = free_vars.iter() - .map(|v| (v.clone(), "LuxInt".to_string())) + .map(|v| (self.escape_c_keyword(v), "LuxInt".to_string())) .collect(); // Store closure info for later emission @@ -1770,14 +1881,15 @@ impl CBackend { } else { "LuxInt".to_string() }; - self.writeln(&format!("{} {} = {};", typ, name.name, val)); + let escaped_name = self.escape_c_keyword(&name.name); + self.writeln(&format!("{} {} = {};", typ, escaped_name, val)); // Register RC variable if it creates a new RC value if self.expr_creates_rc_value(value) { - self.register_rc_var(&name.name, &typ); + self.register_rc_var(&escaped_name, &typ); } else if let Some(adt_name) = self.expr_creates_adt_with_pointers(value) { // ADT with pointer fields - needs field cleanup at scope exit - self.register_rc_var_with_adt(&name.name, &typ, Some(adt_name)); + self.register_rc_var_with_adt(&escaped_name, &typ, Some(adt_name)); } } Statement::Expr(e) => { @@ -2497,6 +2609,10 @@ impl CBackend { Expr::Call { func, .. } => { // Check if calling a constructor if let Expr::Var(ident) = func.as_ref() { + // toString returns LuxString + if ident.name == "toString" { + return Some("LuxString".to_string()); + } if let Some(type_name) = self.variant_to_type.get(&ident.name) { return Some(type_name.clone()); } @@ -2507,10 +2623,25 @@ impl CBackend { } None } + Expr::BinaryOp { op, left, right, .. } => { + // String concatenation with + returns LuxString + if matches!(op, BinaryOp::Add) { + if self.is_string_expr(left) || self.is_string_expr(right) { + return Some("LuxString".to_string()); + } + } + // For other binary ops, infer from operands + self.infer_expr_type(left).or_else(|| self.infer_expr_type(right)) + } Expr::Block { result, .. } => { // Type of block is the type of the result expression self.infer_expr_type(result) } + Expr::If { then_branch, else_branch, .. } => { + // Type of if is the type of either branch (they should match) + self.infer_expr_type(then_branch) + .or_else(|| self.infer_expr_type(else_branch)) + } Expr::List { .. } => Some("LuxList*".to_string()), Expr::EffectOp { effect, operation, .. } => { // List operations have known return types @@ -2625,7 +2756,8 @@ impl CBackend { Pattern::Wildcard(_) => vec![], Pattern::Var(ident) => { let typ = expected_type.unwrap_or("LuxInt").to_string(); - vec![(ident.name.clone(), scrutinee.to_string(), typ)] + let escaped_name = self.escape_c_keyword(&ident.name); + vec![(escaped_name, scrutinee.to_string(), typ)] } Pattern::Literal(_) => vec![], Pattern::Constructor { name, fields, .. } => {