feat: add Random, Time, and File effects to C backend
New effects with evidence passing support: Random effect: - int(min, max) - random integer in range - float() - random float 0-1 - bool() - random boolean Time effect: - now() - milliseconds since epoch - sleep(ms) - pause execution File effect: - read(path) - read file contents - write(path, content) - write file - append(path, content) - append to file - exists(path) - check if file exists - delete(path) - delete file - isDir(path) - check if directory - mkdir(path) - create directory Also fixed: - Function calls as statements now properly emit in generated C - Return type inference for all effect operations Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -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<Int>): List<Int> =
|
||||
|
||||
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
|
||||
|
||||
@@ -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 <time.h>");
|
||||
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 <sys/stat.h>");
|
||||
self.writeln("#include <unistd.h>");
|
||||
self.writeln("#include <errno.h>");
|
||||
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<Vec<_>, _> = 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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user