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.)
|
//! - More effects (File, Http, etc.)
|
||||||
|
|
||||||
use crate::ast::*;
|
use crate::ast::*;
|
||||||
|
use crate::modules::Module;
|
||||||
use std::collections::{HashSet, HashMap};
|
use std::collections::{HashSet, HashMap};
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
|
|
||||||
@@ -139,6 +140,12 @@ pub struct CBackend {
|
|||||||
function_behaviors: HashMap<String, FunctionBehavior>,
|
function_behaviors: HashMap<String, FunctionBehavior>,
|
||||||
/// Whether to enable behavioral type optimizations
|
/// Whether to enable behavioral type optimizations
|
||||||
enable_behavioral_optimizations: bool,
|
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 {
|
impl CBackend {
|
||||||
@@ -167,6 +174,9 @@ impl CBackend {
|
|||||||
var_types: HashMap::new(),
|
var_types: HashMap::new(),
|
||||||
function_behaviors: HashMap::new(),
|
function_behaviors: HashMap::new(),
|
||||||
enable_behavioral_optimizations: true,
|
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
|
/// 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.output.clear();
|
||||||
self.closures.clear();
|
self.closures.clear();
|
||||||
self.emit_prelude();
|
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(), "Ok".to_string()), vec!["void*".to_string()]);
|
||||||
self.variant_field_types.insert(("Result".to_string(), "Err".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
|
// First pass: collect all function names, types, and effects
|
||||||
for decl in &program.declarations {
|
for decl in &program.declarations {
|
||||||
match decl {
|
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)?;
|
self.emit_type_definitions(program)?;
|
||||||
|
|
||||||
// Emit ADT drop function (after type definitions so we know the ADT structure)
|
// Emit ADT drop function (after type definitions so we know the ADT structure)
|
||||||
self.emit_adt_drop_function();
|
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)?;
|
self.emit_forward_declarations(program)?;
|
||||||
|
|
||||||
// Generate function bodies to a temporary buffer
|
// Generate function bodies to a temporary buffer
|
||||||
// This collects closures that need to be emitted
|
// This collects closures that need to be emitted
|
||||||
let saved_output = std::mem::take(&mut self.output);
|
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 {
|
for decl in &program.declarations {
|
||||||
match decl {
|
match decl {
|
||||||
Declaration::Function(f) => {
|
Declaration::Function(f) => {
|
||||||
@@ -282,6 +300,328 @@ impl CBackend {
|
|||||||
Ok(self.output.clone())
|
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
|
/// Emit all collected closure definitions
|
||||||
fn emit_closures(&mut self) -> Result<(), CGenError> {
|
fn emit_closures(&mut self) -> Result<(), CGenError> {
|
||||||
if self.closures.is_empty() {
|
if self.closures.is_empty() {
|
||||||
@@ -363,6 +703,8 @@ impl CBackend {
|
|||||||
let escaped = self.escape_c_keyword(&ident.name);
|
let escaped = self.escape_c_keyword(&ident.name);
|
||||||
if captured.contains(ident.name.as_str()) {
|
if captured.contains(ident.name.as_str()) {
|
||||||
Ok(format!("env->{}", escaped))
|
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) {
|
} else if self.functions.contains(&ident.name) {
|
||||||
Ok(self.mangle_name(&ident.name))
|
Ok(self.mangle_name(&ident.name))
|
||||||
} else {
|
} else {
|
||||||
@@ -675,6 +1017,15 @@ impl CBackend {
|
|||||||
self.writeln(" return result;");
|
self.writeln(" return result;");
|
||||||
self.writeln("}");
|
self.writeln("}");
|
||||||
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("static LuxBool lux_string_eq(LuxString a, LuxString b) {");
|
||||||
self.writeln(" return strcmp(a, b) == 0;");
|
self.writeln(" return strcmp(a, b) == 0;");
|
||||||
self.writeln("}");
|
self.writeln("}");
|
||||||
@@ -2392,6 +2743,9 @@ impl CBackend {
|
|||||||
// This is a constructor - emit struct literal
|
// This is a constructor - emit struct literal
|
||||||
let variant_name = &ident.name;
|
let variant_name = &ident.name;
|
||||||
Ok(format!("({}){{{}_TAG_{}}}", type_name, type_name, variant_name.to_uppercase()))
|
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) {
|
} else if self.functions.contains(&ident.name) {
|
||||||
// Function used as a value — wrap in a closure struct
|
// Function used as a value — wrap in a closure struct
|
||||||
let mangled = self.mangle_name(&ident.name);
|
let mangled = self.mangle_name(&ident.name);
|
||||||
@@ -2626,19 +2980,68 @@ impl CBackend {
|
|||||||
|
|
||||||
self.writeln(&format!("{} {} = {};", var_type, var_name, val));
|
self.writeln(&format!("{} {} = {};", var_type, var_name, val));
|
||||||
|
|
||||||
// Substitute the name in the body
|
// Register the variable rename so nested expressions can find it
|
||||||
// For now, assume the variable is directly usable
|
let old_rename = self.var_renames.insert(name.name.clone(), var_name.clone());
|
||||||
let body_result = self.emit_expr_with_substitution(body, &name.name, &var_name)?;
|
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)
|
Ok(body_result)
|
||||||
}
|
}
|
||||||
|
|
||||||
Expr::Call { func, args, .. } => {
|
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::Field { object, field, .. } = func.as_ref() {
|
||||||
if let Expr::Var(module_name) = object.as_ref() {
|
if let Expr::Var(module_name) = object.as_ref() {
|
||||||
if module_name.name == "List" {
|
if module_name.name == "List" {
|
||||||
return self.emit_list_operation(&field.name, args);
|
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);
|
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
|
// Built-in Console effect
|
||||||
if effect.name == "Console" {
|
if effect.name == "Console" {
|
||||||
if operation.name == "print" {
|
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
|
// For other effects, emit generic evidence-passing call
|
||||||
let arg_strs: Result<Vec<_>, _> = args.iter().map(|a| self.emit_expr(a)).collect();
|
let arg_strs: Result<Vec<_>, _> = args.iter().map(|a| self.emit_expr(a)).collect();
|
||||||
if self.has_evidence {
|
if self.has_evidence {
|
||||||
@@ -4282,7 +4739,15 @@ impl CBackend {
|
|||||||
"parse" => return Some("Option".to_string()),
|
"parse" => return Some("Option".to_string()),
|
||||||
_ => return Some("LuxFloat".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::List { .. } => Some("LuxList*".to_string()),
|
||||||
Expr::EffectOp { effect, operation, args, .. } => {
|
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
|
// List operations have known return types
|
||||||
if effect.name == "List" {
|
if effect.name == "List" {
|
||||||
match operation.name.as_str() {
|
match operation.name.as_str() {
|
||||||
@@ -4392,7 +4871,13 @@ impl CBackend {
|
|||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
} else {
|
} 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, .. } => {
|
Expr::Match { arms, .. } => {
|
||||||
@@ -5612,7 +6097,8 @@ mod tests {
|
|||||||
fn generate(source: &str) -> Result<String, CGenError> {
|
fn generate(source: &str) -> Result<String, CGenError> {
|
||||||
let program = Parser::parse_source(source).expect("Parse error");
|
let program = Parser::parse_source(source).expect("Parse error");
|
||||||
let mut backend = CBackend::new();
|
let mut backend = CBackend::new();
|
||||||
backend.generate(&program)
|
let modules = HashMap::new();
|
||||||
|
backend.generate(&program, &modules)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -95,6 +95,10 @@ pub enum BuiltinFn {
|
|||||||
StringLastIndexOf,
|
StringLastIndexOf,
|
||||||
StringRepeat,
|
StringRepeat,
|
||||||
|
|
||||||
|
// Int/Float operations
|
||||||
|
IntToString,
|
||||||
|
FloatToString,
|
||||||
|
|
||||||
// JSON operations
|
// JSON operations
|
||||||
JsonParse,
|
JsonParse,
|
||||||
JsonStringify,
|
JsonStringify,
|
||||||
@@ -1071,6 +1075,18 @@ impl Interpreter {
|
|||||||
]));
|
]));
|
||||||
env.define("Math", math_module);
|
env.define("Math", math_module);
|
||||||
|
|
||||||
|
// Int module
|
||||||
|
let int_module = Value::Record(HashMap::from([
|
||||||
|
("toString".to_string(), Value::Builtin(BuiltinFn::IntToString)),
|
||||||
|
]));
|
||||||
|
env.define("Int", int_module);
|
||||||
|
|
||||||
|
// Float module
|
||||||
|
let float_module = Value::Record(HashMap::from([
|
||||||
|
("toString".to_string(), Value::Builtin(BuiltinFn::FloatToString)),
|
||||||
|
]));
|
||||||
|
env.define("Float", float_module);
|
||||||
|
|
||||||
// JSON module
|
// JSON module
|
||||||
let json_module = Value::Record(HashMap::from([
|
let json_module = Value::Record(HashMap::from([
|
||||||
("parse".to_string(), Value::Builtin(BuiltinFn::JsonParse)),
|
("parse".to_string(), Value::Builtin(BuiltinFn::JsonParse)),
|
||||||
@@ -2251,6 +2267,26 @@ impl Interpreter {
|
|||||||
Ok(EvalResult::Value(Value::String(result)))
|
Ok(EvalResult::Value(Value::String(result)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BuiltinFn::IntToString => {
|
||||||
|
if args.len() != 1 {
|
||||||
|
return Err(err("Int.toString requires 1 argument"));
|
||||||
|
}
|
||||||
|
match &args[0] {
|
||||||
|
Value::Int(n) => Ok(EvalResult::Value(Value::String(format!("{}", n)))),
|
||||||
|
v => Ok(EvalResult::Value(Value::String(format!("{}", v)))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BuiltinFn::FloatToString => {
|
||||||
|
if args.len() != 1 {
|
||||||
|
return Err(err("Float.toString requires 1 argument"));
|
||||||
|
}
|
||||||
|
match &args[0] {
|
||||||
|
Value::Float(f) => Ok(EvalResult::Value(Value::String(format!("{}", f)))),
|
||||||
|
v => Ok(EvalResult::Value(Value::String(format!("{}", v)))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
BuiltinFn::TypeOf => {
|
BuiltinFn::TypeOf => {
|
||||||
if args.len() != 1 {
|
if args.len() != 1 {
|
||||||
return Err(err("typeOf requires 1 argument"));
|
return Err(err("typeOf requires 1 argument"));
|
||||||
@@ -3856,6 +3892,26 @@ impl Interpreter {
|
|||||||
}
|
}
|
||||||
Ok(Value::Unit)
|
Ok(Value::Unit)
|
||||||
}
|
}
|
||||||
|
("Test", "assertEqualMsg") => {
|
||||||
|
let expected = request.args.first().cloned().unwrap_or(Value::Unit);
|
||||||
|
let actual = request.args.get(1).cloned().unwrap_or(Value::Unit);
|
||||||
|
let label = match request.args.get(2) {
|
||||||
|
Some(Value::String(s)) => s.clone(),
|
||||||
|
_ => "Values not equal".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if Value::values_equal(&expected, &actual) {
|
||||||
|
self.test_results.borrow_mut().passed += 1;
|
||||||
|
} else {
|
||||||
|
self.test_results.borrow_mut().failed += 1;
|
||||||
|
self.test_results.borrow_mut().failures.push(TestFailure {
|
||||||
|
message: label,
|
||||||
|
expected: Some(format!("{}", expected)),
|
||||||
|
actual: Some(format!("{}", actual)),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Ok(Value::Unit)
|
||||||
|
}
|
||||||
("Test", "assertNotEqual") => {
|
("Test", "assertNotEqual") => {
|
||||||
let a = request.args.first().cloned().unwrap_or(Value::Unit);
|
let a = request.args.first().cloned().unwrap_or(Value::Unit);
|
||||||
let b = request.args.get(1).cloned().unwrap_or(Value::Unit);
|
let b = request.args.get(1).cloned().unwrap_or(Value::Unit);
|
||||||
|
|||||||
13
src/main.rs
13
src/main.rs
@@ -174,9 +174,14 @@ fn main() {
|
|||||||
.and_then(|s| s.parse::<u16>().ok())
|
.and_then(|s| s.parse::<u16>().ok())
|
||||||
.unwrap_or(8080);
|
.unwrap_or(8080);
|
||||||
|
|
||||||
let dir = args.get(2)
|
let port_value_idx = args.iter()
|
||||||
.filter(|a| !a.starts_with('-'))
|
.position(|a| a == "--port" || a == "-p")
|
||||||
.map(|s| s.as_str())
|
.map(|i| i + 1);
|
||||||
|
let dir = args.iter().enumerate()
|
||||||
|
.skip(2)
|
||||||
|
.filter(|(i, a)| !a.starts_with('-') && Some(*i) != port_value_idx)
|
||||||
|
.map(|(_, a)| a.as_str())
|
||||||
|
.next()
|
||||||
.unwrap_or(".");
|
.unwrap_or(".");
|
||||||
|
|
||||||
serve_static_files(dir, port);
|
serve_static_files(dir, port);
|
||||||
@@ -842,7 +847,7 @@ fn compile_to_c(path: &str, output_path: Option<&str>, run_after: bool, emit_c:
|
|||||||
|
|
||||||
// Generate C code
|
// Generate C code
|
||||||
let mut backend = CBackend::new();
|
let mut backend = CBackend::new();
|
||||||
let c_code = match backend.generate(&program) {
|
let c_code = match backend.generate(&program, loader.module_cache()) {
|
||||||
Ok(code) => code,
|
Ok(code) => code,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("{} C codegen: {}", c(colors::RED, "error:"), e);
|
eprintln!("{} C codegen: {}", c(colors::RED, "error:"), e);
|
||||||
|
|||||||
@@ -305,6 +305,11 @@ impl ModuleLoader {
|
|||||||
self.cache.iter()
|
self.cache.iter()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the module cache (for passing to C backend)
|
||||||
|
pub fn module_cache(&self) -> &HashMap<String, Module> {
|
||||||
|
&self.cache
|
||||||
|
}
|
||||||
|
|
||||||
/// Clear the module cache
|
/// Clear the module cache
|
||||||
pub fn clear_cache(&mut self) {
|
pub fn clear_cache(&mut self) {
|
||||||
self.cache.clear();
|
self.cache.clear();
|
||||||
|
|||||||
27
src/types.rs
27
src/types.rs
@@ -1146,6 +1146,15 @@ impl TypeEnv {
|
|||||||
],
|
],
|
||||||
return_type: Type::Unit,
|
return_type: Type::Unit,
|
||||||
},
|
},
|
||||||
|
EffectOpDef {
|
||||||
|
name: "assertEqualMsg".to_string(),
|
||||||
|
params: vec![
|
||||||
|
("expected".to_string(), Type::Var(0)),
|
||||||
|
("actual".to_string(), Type::Var(0)),
|
||||||
|
("label".to_string(), Type::String),
|
||||||
|
],
|
||||||
|
return_type: Type::Unit,
|
||||||
|
},
|
||||||
EffectOpDef {
|
EffectOpDef {
|
||||||
name: "assertNotEqual".to_string(),
|
name: "assertNotEqual".to_string(),
|
||||||
params: vec![
|
params: vec![
|
||||||
@@ -1881,6 +1890,24 @@ impl TypeEnv {
|
|||||||
]);
|
]);
|
||||||
env.bind("Math", TypeScheme::mono(math_module_type));
|
env.bind("Math", TypeScheme::mono(math_module_type));
|
||||||
|
|
||||||
|
// Int module
|
||||||
|
let int_module_type = Type::Record(vec![
|
||||||
|
(
|
||||||
|
"toString".to_string(),
|
||||||
|
Type::function(vec![Type::Int], Type::String),
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
env.bind("Int", TypeScheme::mono(int_module_type));
|
||||||
|
|
||||||
|
// Float module
|
||||||
|
let float_module_type = Type::Record(vec![
|
||||||
|
(
|
||||||
|
"toString".to_string(),
|
||||||
|
Type::function(vec![Type::Float], Type::String),
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
env.bind("Float", TypeScheme::mono(float_module_type));
|
||||||
|
|
||||||
env
|
env
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user