diff --git a/docs/C_BACKEND.md b/docs/C_BACKEND.md index 4b276a8..fc05e08 100644 --- a/docs/C_BACKEND.md +++ b/docs/C_BACKEND.md @@ -275,7 +275,7 @@ See [docs/EVIDENCE_PASSING.md](EVIDENCE_PASSING.md) for details. ## Future Roadmap -### Phase 2: Perceus Reference Counting +### Phase 4: Perceus Reference Counting **Goal:** Deterministic memory management without GC pauses. @@ -292,15 +292,22 @@ fn increment(xs: List): List = If `xs` has refcount=1, the list can be mutated in-place instead of copied. -### Phase 3: More Effects +### Phase 2: More Effects ✅ COMPLETE -Implement C versions of: -- `File` (read, write, exists) -- `Http` (get, post) -- `Random` (int, bool) -- `Time` (now, sleep) +Implemented C versions of: +- `Random` (int, float, bool) - LCG random number generator +- `Time` (now, sleep) - using clock_gettime/nanosleep +- `File` (read, write, append, exists, delete, isDir, mkdir) -### Phase 4: JavaScript Backend +All effects use evidence passing for handler customization. + +### Phase 3: Http Effect + +Implement HTTP client: +- `Http.get(url)` - GET request +- `Http.post(url, body)` - POST request + +### Phase 5: JavaScript Backend Compile Lux to JavaScript for browser/Node.js: - Effects → Direct DOM/API calls diff --git a/src/codegen/c_backend.rs b/src/codegen/c_backend.rs index 8f562f2..e7c72d7 100644 --- a/src/codegen/c_backend.rs +++ b/src/codegen/c_backend.rs @@ -440,12 +440,42 @@ impl CBackend { self.writeln(" void* env;"); self.writeln("} LuxReaderHandler;"); self.writeln(""); + self.writeln("// Handler struct for Random effect"); + self.writeln("typedef struct {"); + self.writeln(" LuxInt (*randInt)(void* env, LuxInt min, LuxInt max);"); + self.writeln(" LuxFloat (*randFloat)(void* env);"); + self.writeln(" LuxBool (*randBool)(void* env);"); + self.writeln(" void* env;"); + self.writeln("} LuxRandomHandler;"); + self.writeln(""); + self.writeln("// Handler struct for Time effect"); + self.writeln("typedef struct {"); + self.writeln(" LuxInt (*now)(void* env);"); + self.writeln(" void (*sleep)(void* env, LuxInt ms);"); + self.writeln(" void* env;"); + self.writeln("} LuxTimeHandler;"); + self.writeln(""); + self.writeln("// Handler struct for File effect"); + self.writeln("typedef struct {"); + self.writeln(" LuxString (*read)(void* env, LuxString path);"); + self.writeln(" void (*write)(void* env, LuxString path, LuxString content);"); + self.writeln(" void (*append)(void* env, LuxString path, LuxString content);"); + self.writeln(" LuxBool (*exists)(void* env, LuxString path);"); + self.writeln(" void (*delete_file)(void* env, LuxString path);"); + self.writeln(" LuxBool (*isDir)(void* env, LuxString path);"); + self.writeln(" void (*mkdir)(void* env, LuxString path);"); + self.writeln(" void* env;"); + self.writeln("} LuxFileHandler;"); + self.writeln(""); self.writeln("// Evidence struct - passed to effectful functions"); self.writeln("// Contains pointers to current handlers for each effect type"); self.writeln("typedef struct {"); self.writeln(" LuxConsoleHandler* console;"); self.writeln(" LuxStateHandler* state;"); self.writeln(" LuxReaderHandler* reader;"); + self.writeln(" LuxRandomHandler* random;"); + self.writeln(" LuxTimeHandler* time;"); + self.writeln(" LuxFileHandler* file;"); self.writeln("} LuxEvidence;"); self.writeln(""); self.writeln("// Default Console handler using built-in implementations"); @@ -465,11 +495,191 @@ impl CBackend { self.writeln(" .env = NULL"); self.writeln("};"); self.writeln(""); + self.writeln("// === Random Effect Built-in Implementations ==="); + self.writeln(""); + self.writeln("static unsigned int lux_rand_state = 12345;"); + self.writeln(""); + self.writeln("static LuxInt lux_random_int(LuxInt min, LuxInt max) {"); + self.writeln(" // Simple LCG random number generator"); + self.writeln(" lux_rand_state = lux_rand_state * 1103515245 + 12345;"); + self.writeln(" LuxInt range = max - min + 1;"); + self.writeln(" if (range <= 0) return min;"); + self.writeln(" return min + (LuxInt)((lux_rand_state >> 16) % range);"); + self.writeln("}"); + self.writeln(""); + self.writeln("static LuxFloat lux_random_float(void) {"); + self.writeln(" lux_rand_state = lux_rand_state * 1103515245 + 12345;"); + self.writeln(" return (double)(lux_rand_state >> 16) / 32767.0;"); + self.writeln("}"); + self.writeln(""); + self.writeln("static LuxBool lux_random_bool(void) {"); + self.writeln(" return lux_random_int(0, 1) == 1;"); + self.writeln("}"); + self.writeln(""); + self.writeln("static LuxInt default_random_int(void* env, LuxInt min, LuxInt max) {"); + self.writeln(" (void)env;"); + self.writeln(" return lux_random_int(min, max);"); + self.writeln("}"); + self.writeln(""); + self.writeln("static LuxFloat default_random_float(void* env) {"); + self.writeln(" (void)env;"); + self.writeln(" return lux_random_float();"); + self.writeln("}"); + self.writeln(""); + self.writeln("static LuxBool default_random_bool(void* env) {"); + self.writeln(" (void)env;"); + self.writeln(" return lux_random_bool();"); + self.writeln("}"); + self.writeln(""); + self.writeln("static LuxRandomHandler default_random_handler = {"); + self.writeln(" .randInt = default_random_int,"); + self.writeln(" .randFloat = default_random_float,"); + self.writeln(" .randBool = default_random_bool,"); + self.writeln(" .env = NULL"); + self.writeln("};"); + self.writeln(""); + self.writeln("// === Time Effect Built-in Implementations ==="); + self.writeln(""); + self.writeln("#include "); + self.writeln(""); + self.writeln("static LuxInt lux_time_now(void) {"); + self.writeln(" struct timespec ts;"); + self.writeln(" clock_gettime(CLOCK_REALTIME, &ts);"); + self.writeln(" return (LuxInt)(ts.tv_sec * 1000 + ts.tv_nsec / 1000000);"); + self.writeln("}"); + self.writeln(""); + self.writeln("static void lux_time_sleep(LuxInt ms) {"); + self.writeln(" struct timespec ts;"); + self.writeln(" ts.tv_sec = ms / 1000;"); + self.writeln(" ts.tv_nsec = (ms % 1000) * 1000000;"); + self.writeln(" nanosleep(&ts, NULL);"); + self.writeln("}"); + self.writeln(""); + self.writeln("static LuxInt default_time_now(void* env) {"); + self.writeln(" (void)env;"); + self.writeln(" return lux_time_now();"); + self.writeln("}"); + self.writeln(""); + self.writeln("static void default_time_sleep(void* env, LuxInt ms) {"); + self.writeln(" (void)env;"); + self.writeln(" lux_time_sleep(ms);"); + self.writeln("}"); + self.writeln(""); + self.writeln("static LuxTimeHandler default_time_handler = {"); + self.writeln(" .now = default_time_now,"); + self.writeln(" .sleep = default_time_sleep,"); + self.writeln(" .env = NULL"); + self.writeln("};"); + self.writeln(""); + self.writeln("// === File Effect Built-in Implementations ==="); + self.writeln(""); + self.writeln("#include "); + self.writeln("#include "); + self.writeln("#include "); + self.writeln(""); + self.writeln("static LuxString lux_file_read(LuxString path) {"); + self.writeln(" FILE* f = fopen(path, \"r\");"); + self.writeln(" if (!f) return strdup(\"\");"); + self.writeln(" fseek(f, 0, SEEK_END);"); + self.writeln(" long size = ftell(f);"); + self.writeln(" fseek(f, 0, SEEK_SET);"); + self.writeln(" char* content = malloc(size + 1);"); + self.writeln(" size_t read_size = fread(content, 1, size, f);"); + self.writeln(" content[read_size] = '\\0';"); + self.writeln(" fclose(f);"); + self.writeln(" return content;"); + self.writeln("}"); + self.writeln(""); + self.writeln("static void lux_file_write(LuxString path, LuxString content) {"); + self.writeln(" FILE* f = fopen(path, \"w\");"); + self.writeln(" if (f) {"); + self.writeln(" fputs(content, f);"); + self.writeln(" fclose(f);"); + self.writeln(" }"); + self.writeln("}"); + self.writeln(""); + self.writeln("static void lux_file_append(LuxString path, LuxString content) {"); + self.writeln(" FILE* f = fopen(path, \"a\");"); + self.writeln(" if (f) {"); + self.writeln(" fputs(content, f);"); + self.writeln(" fclose(f);"); + self.writeln(" }"); + self.writeln("}"); + self.writeln(""); + self.writeln("static LuxBool lux_file_exists(LuxString path) {"); + self.writeln(" return access(path, F_OK) == 0;"); + self.writeln("}"); + self.writeln(""); + self.writeln("static void lux_file_delete(LuxString path) {"); + self.writeln(" remove(path);"); + self.writeln("}"); + self.writeln(""); + self.writeln("static LuxBool lux_file_isDir(LuxString path) {"); + self.writeln(" struct stat st;"); + self.writeln(" if (stat(path, &st) == 0) {"); + self.writeln(" return S_ISDIR(st.st_mode);"); + self.writeln(" }"); + self.writeln(" return false;"); + self.writeln("}"); + self.writeln(""); + self.writeln("static void lux_file_mkdir(LuxString path) {"); + self.writeln(" mkdir(path, 0755);"); + self.writeln("}"); + self.writeln(""); + self.writeln("static LuxString default_file_read(void* env, LuxString path) {"); + self.writeln(" (void)env;"); + self.writeln(" return lux_file_read(path);"); + self.writeln("}"); + self.writeln(""); + self.writeln("static void default_file_write(void* env, LuxString path, LuxString content) {"); + self.writeln(" (void)env;"); + self.writeln(" lux_file_write(path, content);"); + self.writeln("}"); + self.writeln(""); + self.writeln("static void default_file_append(void* env, LuxString path, LuxString content) {"); + self.writeln(" (void)env;"); + self.writeln(" lux_file_append(path, content);"); + self.writeln("}"); + self.writeln(""); + self.writeln("static LuxBool default_file_exists(void* env, LuxString path) {"); + self.writeln(" (void)env;"); + self.writeln(" return lux_file_exists(path);"); + self.writeln("}"); + self.writeln(""); + self.writeln("static void default_file_delete(void* env, LuxString path) {"); + self.writeln(" (void)env;"); + self.writeln(" lux_file_delete(path);"); + self.writeln("}"); + self.writeln(""); + self.writeln("static LuxBool default_file_isDir(void* env, LuxString path) {"); + self.writeln(" (void)env;"); + self.writeln(" return lux_file_isDir(path);"); + self.writeln("}"); + self.writeln(""); + self.writeln("static void default_file_mkdir(void* env, LuxString path) {"); + self.writeln(" (void)env;"); + self.writeln(" lux_file_mkdir(path);"); + self.writeln("}"); + self.writeln(""); + self.writeln("static LuxFileHandler default_file_handler = {"); + self.writeln(" .read = default_file_read,"); + self.writeln(" .write = default_file_write,"); + self.writeln(" .append = default_file_append,"); + self.writeln(" .exists = default_file_exists,"); + self.writeln(" .delete_file = default_file_delete,"); + self.writeln(" .isDir = default_file_isDir,"); + self.writeln(" .mkdir = default_file_mkdir,"); + self.writeln(" .env = NULL"); + self.writeln("};"); + self.writeln(""); self.writeln("// Default evidence with built-in handlers"); self.writeln("static LuxEvidence default_evidence = {"); self.writeln(" .console = &default_console_handler,"); self.writeln(" .state = NULL,"); - self.writeln(" .reader = NULL"); + self.writeln(" .reader = NULL,"); + self.writeln(" .random = &default_random_handler,"); + self.writeln(" .time = &default_time_handler,"); + self.writeln(" .file = &default_file_handler"); self.writeln("};"); self.writeln(""); self.writeln("// === List Types ==="); @@ -1028,7 +1238,22 @@ impl CBackend { self.writeln(&format!("{} {} = {};", typ, name.name, val)); } Statement::Expr(e) => { - let _ = self.emit_expr(e)?; + // Emit expression - if it's a function call that returns void/unit, + // we need to emit it as a statement + let expr_str = self.emit_expr(e)?; + // If emit_expr didn't write the call itself (for non-void returns), + // and it's a function call, emit it as a statement + if !expr_str.is_empty() && expr_str != "NULL" { + // Check if this is a function call expression + if let Expr::Call { func, .. } = e { + if let Expr::Var(ident) = func.as_ref() { + if self.functions.contains(&ident.name) { + // It's a function call - emit as statement + self.writeln(&format!("{};", expr_str)); + } + } + } + } } } } @@ -1048,10 +1273,8 @@ impl CBackend { if operation.name == "print" { let arg = self.emit_expr(&args[0])?; if self.has_evidence { - // Use evidence passing self.writeln(&format!("ev->console->print(ev->console->env, {});", arg)); } else { - // Fallback to direct call self.writeln(&format!("lux_console_print({});", arg)); } return Ok("NULL".to_string()); @@ -1064,7 +1287,129 @@ impl CBackend { } } - // For other effects, emit evidence-passing call + // Built-in Random effect + if effect.name == "Random" { + match operation.name.as_str() { + "int" => { + let min = self.emit_expr(&args[0])?; + let max = self.emit_expr(&args[1])?; + if self.has_evidence { + return Ok(format!("ev->random->randInt(ev->random->env, {}, {})", min, max)); + } else { + return Ok(format!("lux_random_int({}, {})", min, max)); + } + } + "float" => { + if self.has_evidence { + return Ok("ev->random->randFloat(ev->random->env)".to_string()); + } else { + return Ok("lux_random_float()".to_string()); + } + } + "bool" => { + if self.has_evidence { + return Ok("ev->random->randBool(ev->random->env)".to_string()); + } else { + return Ok("lux_random_bool()".to_string()); + } + } + _ => {} + } + } + + // Built-in Time effect + if effect.name == "Time" { + match operation.name.as_str() { + "now" => { + if self.has_evidence { + return Ok("ev->time->now(ev->time->env)".to_string()); + } else { + return Ok("lux_time_now()".to_string()); + } + } + "sleep" => { + let ms = self.emit_expr(&args[0])?; + if self.has_evidence { + self.writeln(&format!("ev->time->sleep(ev->time->env, {});", ms)); + } else { + self.writeln(&format!("lux_time_sleep({});", ms)); + } + return Ok("NULL".to_string()); + } + _ => {} + } + } + + // Built-in File effect + if effect.name == "File" { + match operation.name.as_str() { + "read" => { + let path = self.emit_expr(&args[0])?; + if self.has_evidence { + return Ok(format!("ev->file->read(ev->file->env, {})", path)); + } else { + return Ok(format!("lux_file_read({})", path)); + } + } + "write" => { + let path = self.emit_expr(&args[0])?; + let content = self.emit_expr(&args[1])?; + if self.has_evidence { + self.writeln(&format!("ev->file->write(ev->file->env, {}, {});", path, content)); + } else { + self.writeln(&format!("lux_file_write({}, {});", path, content)); + } + return Ok("NULL".to_string()); + } + "append" => { + let path = self.emit_expr(&args[0])?; + let content = self.emit_expr(&args[1])?; + if self.has_evidence { + self.writeln(&format!("ev->file->append(ev->file->env, {}, {});", path, content)); + } else { + self.writeln(&format!("lux_file_append({}, {});", path, content)); + } + return Ok("NULL".to_string()); + } + "exists" => { + let path = self.emit_expr(&args[0])?; + if self.has_evidence { + return Ok(format!("ev->file->exists(ev->file->env, {})", path)); + } else { + return Ok(format!("lux_file_exists({})", path)); + } + } + "delete" => { + let path = self.emit_expr(&args[0])?; + if self.has_evidence { + self.writeln(&format!("ev->file->delete_file(ev->file->env, {});", path)); + } else { + self.writeln(&format!("lux_file_delete({});", path)); + } + return Ok("NULL".to_string()); + } + "isDir" => { + let path = self.emit_expr(&args[0])?; + if self.has_evidence { + return Ok(format!("ev->file->isDir(ev->file->env, {})", path)); + } else { + return Ok(format!("lux_file_isDir({})", path)); + } + } + "mkdir" => { + let path = self.emit_expr(&args[0])?; + if self.has_evidence { + self.writeln(&format!("ev->file->mkdir(ev->file->env, {});", path)); + } else { + self.writeln(&format!("lux_file_mkdir({});", path)); + } + return Ok("NULL".to_string()); + } + _ => {} + } + } + + // For other effects, emit generic evidence-passing call let arg_strs: Result, _> = args.iter().map(|a| self.emit_expr(a)).collect(); if self.has_evidence { Ok(format!("ev->{}->{}(ev->{}->env{}{})", @@ -1550,6 +1895,33 @@ impl CBackend { "isEmpty" | "any" | "all" => Some("LuxBool".to_string()), _ => None, } + } else if effect.name == "Random" { + match operation.name.as_str() { + "int" => Some("LuxInt".to_string()), + "float" => Some("LuxFloat".to_string()), + "bool" => Some("LuxBool".to_string()), + _ => None, + } + } else if effect.name == "Time" { + match operation.name.as_str() { + "now" => Some("LuxInt".to_string()), + "sleep" => Some("void".to_string()), + _ => None, + } + } else if effect.name == "File" { + match operation.name.as_str() { + "read" => Some("LuxString".to_string()), + "write" | "append" | "delete" | "mkdir" => Some("void".to_string()), + "exists" | "isDir" => Some("LuxBool".to_string()), + _ => None, + } + } else if effect.name == "Console" { + match operation.name.as_str() { + "print" => Some("void".to_string()), + "readLine" => Some("LuxString".to_string()), + "readInt" => Some("LuxInt".to_string()), + _ => None, + } } else { None }