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 <noreply@anthropic.com>
This commit is contained in:
2026-02-14 14:16:54 -05:00
parent f6569f1821
commit 0c4b4e8fd0

View File

@@ -292,12 +292,13 @@ impl CBackend {
fn emit_expr_with_env(&mut self, expr: &Expr, captured: &HashSet<&str>) -> Result<String, CGenError> {
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<Vec<_>, _> = 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,11 +1628,48 @@ impl CBackend {
}
Expr::If { condition, then_branch, else_branch, .. } => {
// 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, .. } => {
let val = self.emit_expr(value)?;
@@ -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<Vec<_>, _> = 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, .. } => {