feat: C backend module import support, Int/Float.toString, Test.assertEqualMsg
The C backend can now compile programs that import user-defined modules. Module-qualified calls like `mymodule.func(args)` are resolved to prefixed C functions (e.g., `mymodule_func_lux`), with full support for transitive imports and effect-passing. Also adds Int.toString/Float.toString to type system, interpreter, and C backend, and Test.assertEqualMsg for labeled test assertions. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -43,6 +43,7 @@
|
||||
//! - More effects (File, Http, etc.)
|
||||
|
||||
use crate::ast::*;
|
||||
use crate::modules::Module;
|
||||
use std::collections::{HashSet, HashMap};
|
||||
use std::fmt::Write;
|
||||
|
||||
@@ -139,6 +140,12 @@ pub struct CBackend {
|
||||
function_behaviors: HashMap<String, FunctionBehavior>,
|
||||
/// Whether to enable behavioral type optimizations
|
||||
enable_behavioral_optimizations: bool,
|
||||
/// Mapping from (module_name, func_name) to mangled C function name for imported modules
|
||||
module_functions: HashMap<(String, String), String>,
|
||||
/// Set of module names that have been imported (for resolving calls)
|
||||
imported_modules: HashSet<String>,
|
||||
/// Variable name renames: Lux name → C variable name (for let binding name mangling)
|
||||
var_renames: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl CBackend {
|
||||
@@ -167,6 +174,9 @@ impl CBackend {
|
||||
var_types: HashMap::new(),
|
||||
function_behaviors: HashMap::new(),
|
||||
enable_behavioral_optimizations: true,
|
||||
module_functions: HashMap::new(),
|
||||
imported_modules: HashSet::new(),
|
||||
var_renames: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,7 +201,7 @@ impl CBackend {
|
||||
}
|
||||
|
||||
/// Generate C code from a Lux program
|
||||
pub fn generate(&mut self, program: &Program) -> Result<String, CGenError> {
|
||||
pub fn generate(&mut self, program: &Program, modules: &HashMap<String, Module>) -> Result<String, CGenError> {
|
||||
self.output.clear();
|
||||
self.closures.clear();
|
||||
self.emit_prelude();
|
||||
@@ -208,6 +218,9 @@ impl CBackend {
|
||||
self.variant_field_types.insert(("Result".to_string(), "Ok".to_string()), vec!["void*".to_string()]);
|
||||
self.variant_field_types.insert(("Result".to_string(), "Err".to_string()), vec!["void*".to_string()]);
|
||||
|
||||
// Process imported modules before the main program
|
||||
self.process_imported_modules(program, modules)?;
|
||||
|
||||
// First pass: collect all function names, types, and effects
|
||||
for decl in &program.declarations {
|
||||
match decl {
|
||||
@@ -240,19 +253,24 @@ impl CBackend {
|
||||
}
|
||||
}
|
||||
|
||||
// Emit type definitions
|
||||
// Emit type definitions (modules first, then main program)
|
||||
self.emit_module_type_definitions(modules, program)?;
|
||||
self.emit_type_definitions(program)?;
|
||||
|
||||
// Emit ADT drop function (after type definitions so we know the ADT structure)
|
||||
self.emit_adt_drop_function();
|
||||
|
||||
// Emit forward declarations for regular functions
|
||||
// Emit forward declarations for module functions, then main program
|
||||
self.emit_module_forward_declarations()?;
|
||||
self.emit_forward_declarations(program)?;
|
||||
|
||||
// Generate function bodies to a temporary buffer
|
||||
// This collects closures that need to be emitted
|
||||
let saved_output = std::mem::take(&mut self.output);
|
||||
|
||||
// Emit module function bodies first
|
||||
self.emit_module_function_bodies(modules, program)?;
|
||||
|
||||
for decl in &program.declarations {
|
||||
match decl {
|
||||
Declaration::Function(f) => {
|
||||
@@ -282,6 +300,328 @@ impl CBackend {
|
||||
Ok(self.output.clone())
|
||||
}
|
||||
|
||||
/// Process imported modules: collect function names, types, and mappings
|
||||
fn process_imported_modules(&mut self, program: &Program, modules: &HashMap<String, Module>) -> Result<(), CGenError> {
|
||||
// Build mapping from import alias → module path
|
||||
let mut import_aliases: HashMap<String, String> = HashMap::new();
|
||||
for import in &program.imports {
|
||||
let module_path = import.path.segments.iter()
|
||||
.map(|s| s.name.as_str())
|
||||
.collect::<Vec<_>>()
|
||||
.join("/");
|
||||
let alias = if let Some(ref a) = import.alias {
|
||||
a.name.clone()
|
||||
} else {
|
||||
import.path.segments.last()
|
||||
.map(|s| s.name.clone())
|
||||
.unwrap_or_else(|| module_path.clone())
|
||||
};
|
||||
import_aliases.insert(alias.clone(), module_path.clone());
|
||||
self.imported_modules.insert(alias);
|
||||
}
|
||||
|
||||
// Process each imported module (and transitive imports)
|
||||
let mut processed = HashSet::new();
|
||||
for import in &program.imports {
|
||||
let module_path = import.path.segments.iter()
|
||||
.map(|s| s.name.as_str())
|
||||
.collect::<Vec<_>>()
|
||||
.join("/");
|
||||
let alias = if let Some(ref a) = import.alias {
|
||||
a.name.clone()
|
||||
} else {
|
||||
import.path.segments.last()
|
||||
.map(|s| s.name.clone())
|
||||
.unwrap_or_else(|| module_path.clone())
|
||||
};
|
||||
self.process_single_module(&alias, &module_path, modules, &mut processed)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Process a single module and its transitive imports
|
||||
fn process_single_module(
|
||||
&mut self,
|
||||
alias: &str,
|
||||
module_path: &str,
|
||||
modules: &HashMap<String, Module>,
|
||||
processed: &mut HashSet<String>,
|
||||
) -> Result<(), CGenError> {
|
||||
if processed.contains(module_path) {
|
||||
return Ok(());
|
||||
}
|
||||
processed.insert(module_path.to_string());
|
||||
|
||||
let module = match modules.get(module_path) {
|
||||
Some(m) => m,
|
||||
None => return Ok(()), // Module not found - might be a built-in
|
||||
};
|
||||
|
||||
// Process transitive imports first
|
||||
for sub_import in &module.program.imports {
|
||||
let sub_path = sub_import.path.segments.iter()
|
||||
.map(|s| s.name.as_str())
|
||||
.collect::<Vec<_>>()
|
||||
.join("/");
|
||||
let sub_alias = sub_import.path.segments.last()
|
||||
.map(|s| s.name.clone())
|
||||
.unwrap_or_else(|| sub_path.clone());
|
||||
self.process_single_module(&sub_alias, &sub_path, modules, processed)?;
|
||||
}
|
||||
|
||||
// Collect types from the module
|
||||
for decl in &module.program.declarations {
|
||||
if let Declaration::Type(t) = decl {
|
||||
self.collect_type(t)?;
|
||||
}
|
||||
}
|
||||
|
||||
// Collect functions from the module
|
||||
for decl in &module.program.declarations {
|
||||
if let Declaration::Function(f) = decl {
|
||||
if f.visibility == Visibility::Public || module.exports.contains(&f.name.name) {
|
||||
let mangled = format!("{}_{}_lux", alias, f.name.name);
|
||||
|
||||
self.functions.insert(mangled.clone());
|
||||
self.module_functions.insert(
|
||||
(alias.to_string(), f.name.name.clone()),
|
||||
mangled.clone(),
|
||||
);
|
||||
|
||||
// Store return type
|
||||
if let Ok(ret_type) = self.type_expr_to_c(&f.return_type) {
|
||||
self.function_return_types.insert(mangled.clone(), ret_type);
|
||||
}
|
||||
|
||||
// Store param types
|
||||
let param_types: Vec<String> = f.params.iter()
|
||||
.filter_map(|p| self.type_expr_to_c(&p.typ).ok())
|
||||
.collect();
|
||||
self.function_param_types.insert(mangled.clone(), param_types);
|
||||
|
||||
// Check for closures
|
||||
if matches!(&f.return_type, TypeExpr::Function { .. }) {
|
||||
self.closure_returning_functions.insert(mangled.clone());
|
||||
}
|
||||
|
||||
// Check for effects
|
||||
if !f.effects.is_empty() {
|
||||
self.effectful_functions.insert(mangled.clone());
|
||||
}
|
||||
|
||||
self.collect_behavioral_properties(f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Emit type definitions from imported modules
|
||||
fn emit_module_type_definitions(&mut self, modules: &HashMap<String, Module>, program: &Program) -> Result<(), CGenError> {
|
||||
let mut processed = HashSet::new();
|
||||
for import in &program.imports {
|
||||
let module_path = import.path.segments.iter()
|
||||
.map(|s| s.name.as_str())
|
||||
.collect::<Vec<_>>()
|
||||
.join("/");
|
||||
self.emit_module_types_recursive(&module_path, modules, &mut processed)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn emit_module_types_recursive(
|
||||
&mut self,
|
||||
module_path: &str,
|
||||
modules: &HashMap<String, Module>,
|
||||
processed: &mut HashSet<String>,
|
||||
) -> Result<(), CGenError> {
|
||||
if processed.contains(module_path) {
|
||||
return Ok(());
|
||||
}
|
||||
processed.insert(module_path.to_string());
|
||||
|
||||
let module = match modules.get(module_path) {
|
||||
Some(m) => m,
|
||||
None => return Ok(()),
|
||||
};
|
||||
|
||||
// Process transitive imports first
|
||||
for sub_import in &module.program.imports {
|
||||
let sub_path = sub_import.path.segments.iter()
|
||||
.map(|s| s.name.as_str())
|
||||
.collect::<Vec<_>>()
|
||||
.join("/");
|
||||
self.emit_module_types_recursive(&sub_path, modules, processed)?;
|
||||
}
|
||||
|
||||
for decl in &module.program.declarations {
|
||||
if let Declaration::Type(t) = decl {
|
||||
self.emit_type_def(t)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Emit forward declarations for module functions
|
||||
fn emit_module_forward_declarations(&mut self) -> Result<(), CGenError> {
|
||||
if self.module_functions.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
self.writeln("// === Imported Module Forward Declarations ===");
|
||||
// Forward declarations are emitted from stored info
|
||||
// We iterate module_functions and use function_return_types/function_param_types
|
||||
let entries: Vec<_> = self.module_functions.values().cloned().collect();
|
||||
for mangled in &entries {
|
||||
let ret_type = self.function_return_types.get(mangled)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| "LuxInt".to_string());
|
||||
let param_types = self.function_param_types.get(mangled)
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
let is_effectful = self.effectful_functions.contains(mangled);
|
||||
|
||||
let params_str = if param_types.is_empty() && !is_effectful {
|
||||
"void".to_string()
|
||||
} else {
|
||||
let mut parts = Vec::new();
|
||||
if is_effectful {
|
||||
parts.push("LuxEvidence* ev".to_string());
|
||||
}
|
||||
for (i, pt) in param_types.iter().enumerate() {
|
||||
parts.push(format!("{} param{}", pt, i));
|
||||
}
|
||||
if parts.is_empty() {
|
||||
"void".to_string()
|
||||
} else {
|
||||
parts.join(", ")
|
||||
}
|
||||
};
|
||||
|
||||
self.writeln(&format!("{} {}({});", ret_type, mangled, params_str));
|
||||
}
|
||||
self.writeln("");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Emit function bodies for imported modules
|
||||
fn emit_module_function_bodies(
|
||||
&mut self,
|
||||
modules: &HashMap<String, Module>,
|
||||
program: &Program,
|
||||
) -> Result<(), CGenError> {
|
||||
if self.module_functions.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.writeln("// === Imported Module Function Bodies ===");
|
||||
self.writeln("");
|
||||
|
||||
let mut processed = HashSet::new();
|
||||
for import in &program.imports {
|
||||
let module_path = import.path.segments.iter()
|
||||
.map(|s| s.name.as_str())
|
||||
.collect::<Vec<_>>()
|
||||
.join("/");
|
||||
let alias = if let Some(ref a) = import.alias {
|
||||
a.name.clone()
|
||||
} else {
|
||||
import.path.segments.last()
|
||||
.map(|s| s.name.clone())
|
||||
.unwrap_or_else(|| module_path.clone())
|
||||
};
|
||||
self.emit_module_bodies_recursive(&alias, &module_path, modules, &mut processed)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn emit_module_bodies_recursive(
|
||||
&mut self,
|
||||
alias: &str,
|
||||
module_path: &str,
|
||||
modules: &HashMap<String, Module>,
|
||||
processed: &mut HashSet<String>,
|
||||
) -> Result<(), CGenError> {
|
||||
if processed.contains(module_path) {
|
||||
return Ok(());
|
||||
}
|
||||
processed.insert(module_path.to_string());
|
||||
|
||||
let module = match modules.get(module_path) {
|
||||
Some(m) => m.clone(),
|
||||
None => return Ok(()),
|
||||
};
|
||||
|
||||
// Process transitive imports first
|
||||
for sub_import in &module.program.imports {
|
||||
let sub_path = sub_import.path.segments.iter()
|
||||
.map(|s| s.name.as_str())
|
||||
.collect::<Vec<_>>()
|
||||
.join("/");
|
||||
let sub_alias = sub_import.path.segments.last()
|
||||
.map(|s| s.name.clone())
|
||||
.unwrap_or_else(|| sub_path.clone());
|
||||
self.emit_module_bodies_recursive(&sub_alias, &sub_path, modules, processed)?;
|
||||
}
|
||||
|
||||
for decl in &module.program.declarations {
|
||||
if let Declaration::Function(f) = decl {
|
||||
if f.visibility == Visibility::Public || module.exports.contains(&f.name.name) {
|
||||
let mangled = format!("{}_{}_lux", alias, f.name.name);
|
||||
self.emit_function_with_name(f, &mangled)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Emit a function body with a custom mangled name
|
||||
fn emit_function_with_name(&mut self, func: &FunctionDecl, mangled_name: &str) -> Result<(), CGenError> {
|
||||
let ret_type = self.type_expr_to_c(&func.return_type)?;
|
||||
let params = self.emit_params(&func.params)?;
|
||||
|
||||
let is_effectful = !func.effects.is_empty();
|
||||
|
||||
let full_params = if is_effectful {
|
||||
if params == "void" {
|
||||
"LuxEvidence* ev".to_string()
|
||||
} else {
|
||||
format!("LuxEvidence* ev, {}", params)
|
||||
}
|
||||
} else {
|
||||
params
|
||||
};
|
||||
|
||||
self.writeln(&format!("{} {}({}) {{", ret_type, mangled_name, full_params));
|
||||
self.indent += 1;
|
||||
|
||||
let old_has_evidence = self.has_evidence;
|
||||
if is_effectful {
|
||||
self.has_evidence = true;
|
||||
}
|
||||
|
||||
let body_result = self.emit_expr(&func.body)?;
|
||||
|
||||
if ret_type != "void" && ret_type != "LuxUnit" {
|
||||
self.writeln(&format!("return {};", body_result));
|
||||
} else if ret_type == "LuxUnit" {
|
||||
self.writeln(&format!("(void){};", body_result));
|
||||
self.writeln("return 0;");
|
||||
}
|
||||
|
||||
self.has_evidence = old_has_evidence;
|
||||
|
||||
self.indent -= 1;
|
||||
self.writeln("}");
|
||||
self.writeln("");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Emit all collected closure definitions
|
||||
fn emit_closures(&mut self) -> Result<(), CGenError> {
|
||||
if self.closures.is_empty() {
|
||||
@@ -363,6 +703,8 @@ impl CBackend {
|
||||
let escaped = self.escape_c_keyword(&ident.name);
|
||||
if captured.contains(ident.name.as_str()) {
|
||||
Ok(format!("env->{}", escaped))
|
||||
} else if let Some(renamed) = self.var_renames.get(&ident.name) {
|
||||
Ok(renamed.clone())
|
||||
} else if self.functions.contains(&ident.name) {
|
||||
Ok(self.mangle_name(&ident.name))
|
||||
} else {
|
||||
@@ -675,6 +1017,15 @@ impl CBackend {
|
||||
self.writeln(" return result;");
|
||||
self.writeln("}");
|
||||
self.writeln("");
|
||||
self.writeln("static LuxString lux_float_to_string(LuxFloat f) {");
|
||||
self.writeln(" char buffer[64];");
|
||||
self.writeln(" snprintf(buffer, sizeof(buffer), \"%g\", f);");
|
||||
self.writeln(" size_t len = strlen(buffer);");
|
||||
self.writeln(" LuxString result = (LuxString)lux_rc_alloc(len + 1, LUX_TAG_STRING);");
|
||||
self.writeln(" memcpy(result, buffer, len + 1);");
|
||||
self.writeln(" return result;");
|
||||
self.writeln("}");
|
||||
self.writeln("");
|
||||
self.writeln("static LuxBool lux_string_eq(LuxString a, LuxString b) {");
|
||||
self.writeln(" return strcmp(a, b) == 0;");
|
||||
self.writeln("}");
|
||||
@@ -2392,6 +2743,9 @@ impl CBackend {
|
||||
// This is a constructor - emit struct literal
|
||||
let variant_name = &ident.name;
|
||||
Ok(format!("({}){{{}_TAG_{}}}", type_name, type_name, variant_name.to_uppercase()))
|
||||
} else if let Some(renamed) = self.var_renames.get(&ident.name) {
|
||||
// Variable has been renamed by a let binding
|
||||
Ok(renamed.clone())
|
||||
} else if self.functions.contains(&ident.name) {
|
||||
// Function used as a value — wrap in a closure struct
|
||||
let mangled = self.mangle_name(&ident.name);
|
||||
@@ -2626,19 +2980,68 @@ impl CBackend {
|
||||
|
||||
self.writeln(&format!("{} {} = {};", var_type, var_name, val));
|
||||
|
||||
// Substitute the name in the body
|
||||
// For now, assume the variable is directly usable
|
||||
let body_result = self.emit_expr_with_substitution(body, &name.name, &var_name)?;
|
||||
// Register the variable rename so nested expressions can find it
|
||||
let old_rename = self.var_renames.insert(name.name.clone(), var_name.clone());
|
||||
self.var_types.insert(var_name.clone(), var_type);
|
||||
|
||||
let body_result = self.emit_expr(body)?;
|
||||
|
||||
// Restore previous rename (or remove)
|
||||
if let Some(prev) = old_rename {
|
||||
self.var_renames.insert(name.name.clone(), prev);
|
||||
} else {
|
||||
self.var_renames.remove(&name.name);
|
||||
}
|
||||
|
||||
Ok(body_result)
|
||||
}
|
||||
|
||||
Expr::Call { func, args, .. } => {
|
||||
// Check for List module calls first (List.map, List.filter, etc.)
|
||||
// Check for module calls: List, String, Int, Float, and user-defined modules
|
||||
if let Expr::Field { object, field, .. } = func.as_ref() {
|
||||
if let Expr::Var(module_name) = object.as_ref() {
|
||||
if module_name.name == "List" {
|
||||
return self.emit_list_operation(&field.name, args);
|
||||
}
|
||||
// Int module
|
||||
if module_name.name == "Int" && field.name == "toString" {
|
||||
let arg = self.emit_expr(&args[0])?;
|
||||
let temp = format!("_int_to_string_{}", self.fresh_name());
|
||||
self.writeln(&format!("LuxString {} = lux_int_to_string({});", temp, arg));
|
||||
self.register_rc_var(&temp, "LuxString");
|
||||
return Ok(temp);
|
||||
}
|
||||
// Float module
|
||||
if module_name.name == "Float" && field.name == "toString" {
|
||||
let arg = self.emit_expr(&args[0])?;
|
||||
let temp = format!("_float_to_string_{}", self.fresh_name());
|
||||
self.writeln(&format!("LuxString {} = lux_float_to_string({});", temp, arg));
|
||||
self.register_rc_var(&temp, "LuxString");
|
||||
return Ok(temp);
|
||||
}
|
||||
// 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() {
|
||||
let arg_strs: Result<Vec<_>, _> = args.iter().map(|a| self.emit_expr(a)).collect();
|
||||
let args_str = arg_strs?.join(", ");
|
||||
let is_effectful = self.effectful_functions.contains(&c_name);
|
||||
let call_expr = if is_effectful && self.has_evidence {
|
||||
if args_str.is_empty() {
|
||||
format!("{}(ev)", c_name)
|
||||
} else {
|
||||
format!("{}(ev, {})", c_name, args_str)
|
||||
}
|
||||
} else if is_effectful {
|
||||
if args_str.is_empty() {
|
||||
format!("{}(&default_evidence)", c_name)
|
||||
} else {
|
||||
format!("{}(&default_evidence, {})", c_name, args_str)
|
||||
}
|
||||
} else {
|
||||
format!("{}({})", c_name, args_str)
|
||||
};
|
||||
return Ok(call_expr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2951,6 +3354,34 @@ impl CBackend {
|
||||
return self.emit_list_operation(&operation.name, args);
|
||||
}
|
||||
|
||||
// Int module
|
||||
if effect.name == "Int" {
|
||||
match operation.name.as_str() {
|
||||
"toString" => {
|
||||
let arg = self.emit_expr(&args[0])?;
|
||||
let temp = format!("_int_to_string_{}", self.fresh_name());
|
||||
self.writeln(&format!("LuxString {} = lux_int_to_string({});", temp, arg));
|
||||
self.register_rc_var(&temp, "LuxString");
|
||||
return Ok(temp);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
// Float module
|
||||
if effect.name == "Float" {
|
||||
match operation.name.as_str() {
|
||||
"toString" => {
|
||||
let arg = self.emit_expr(&args[0])?;
|
||||
let temp = format!("_float_to_string_{}", self.fresh_name());
|
||||
self.writeln(&format!("LuxString {} = lux_float_to_string({});", temp, arg));
|
||||
self.register_rc_var(&temp, "LuxString");
|
||||
return Ok(temp);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
// Built-in Console effect
|
||||
if effect.name == "Console" {
|
||||
if operation.name == "print" {
|
||||
@@ -3370,6 +3801,32 @@ impl CBackend {
|
||||
}
|
||||
}
|
||||
|
||||
// Check for user-defined module function (via EffectOp path)
|
||||
{
|
||||
let key = (effect.name.clone(), operation.name.clone());
|
||||
if let Some(c_name) = self.module_functions.get(&key).cloned() {
|
||||
let arg_strs: Result<Vec<_>, _> = args.iter().map(|a| self.emit_expr(a)).collect();
|
||||
let args_str = arg_strs?.join(", ");
|
||||
let is_effectful = self.effectful_functions.contains(&c_name);
|
||||
let call_expr = if is_effectful && self.has_evidence {
|
||||
if args_str.is_empty() {
|
||||
format!("{}(ev)", c_name)
|
||||
} else {
|
||||
format!("{}(ev, {})", c_name, args_str)
|
||||
}
|
||||
} else if is_effectful {
|
||||
if args_str.is_empty() {
|
||||
format!("{}(&default_evidence)", c_name)
|
||||
} else {
|
||||
format!("{}(&default_evidence, {})", c_name, args_str)
|
||||
}
|
||||
} else {
|
||||
format!("{}({})", c_name, args_str)
|
||||
};
|
||||
return Ok(call_expr);
|
||||
}
|
||||
}
|
||||
|
||||
// For other effects, emit generic evidence-passing call
|
||||
let arg_strs: Result<Vec<_>, _> = args.iter().map(|a| self.emit_expr(a)).collect();
|
||||
if self.has_evidence {
|
||||
@@ -4282,7 +4739,15 @@ impl CBackend {
|
||||
"parse" => return Some("Option".to_string()),
|
||||
_ => return Some("LuxFloat".to_string()),
|
||||
},
|
||||
_ => {}
|
||||
_ => {
|
||||
// Check user-defined module functions
|
||||
let key = (module.name.clone(), field.name.clone());
|
||||
if let Some(c_name) = self.module_functions.get(&key) {
|
||||
if let Some(ret_type) = self.function_return_types.get(c_name) {
|
||||
return Some(ret_type.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4319,6 +4784,20 @@ impl CBackend {
|
||||
}
|
||||
Expr::List { .. } => Some("LuxList*".to_string()),
|
||||
Expr::EffectOp { effect, operation, args, .. } => {
|
||||
// Int module
|
||||
if effect.name == "Int" {
|
||||
match operation.name.as_str() {
|
||||
"toString" => return Some("LuxString".to_string()),
|
||||
_ => return None,
|
||||
}
|
||||
}
|
||||
// Float module
|
||||
if effect.name == "Float" {
|
||||
match operation.name.as_str() {
|
||||
"toString" => return Some("LuxString".to_string()),
|
||||
_ => return None,
|
||||
}
|
||||
}
|
||||
// List operations have known return types
|
||||
if effect.name == "List" {
|
||||
match operation.name.as_str() {
|
||||
@@ -4392,7 +4871,13 @@ impl CBackend {
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
// Check user-defined module functions
|
||||
let key = (effect.name.clone(), operation.name.clone());
|
||||
if let Some(c_name) = self.module_functions.get(&key) {
|
||||
self.function_return_types.get(c_name).cloned()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
Expr::Match { arms, .. } => {
|
||||
@@ -5612,7 +6097,8 @@ mod tests {
|
||||
fn generate(source: &str) -> Result<String, CGenError> {
|
||||
let program = Parser::parse_source(source).expect("Parse error");
|
||||
let mut backend = CBackend::new();
|
||||
backend.generate(&program)
|
||||
let modules = HashMap::new();
|
||||
backend.generate(&program, &modules)
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user