feat: add File.copy and propagate effectful callback effects (WISH-7, WISH-14)
File.copy(source, dest) copies files via interpreter (std::fs::copy) and C backend (fread/fwrite). Effectful callbacks passed to higher-order functions like List.map/forEach now propagate their effects to the enclosing function's inferred effect set. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1149,6 +1149,7 @@ impl CBackend {
|
||||
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 (*copy)(void* env, LuxString src, LuxString dst);");
|
||||
self.writeln(" LuxList* (*readDir)(void* env, LuxString path);");
|
||||
self.writeln(" void* env;");
|
||||
self.writeln("} LuxFileHandler;");
|
||||
@@ -1336,6 +1337,20 @@ impl CBackend {
|
||||
self.writeln(" mkdir(path, 0755);");
|
||||
self.writeln("}");
|
||||
self.writeln("");
|
||||
self.writeln("static void lux_file_copy(LuxString src, LuxString dst) {");
|
||||
self.writeln(" FILE* fin = fopen(src, \"rb\");");
|
||||
self.writeln(" if (!fin) return;");
|
||||
self.writeln(" FILE* fout = fopen(dst, \"wb\");");
|
||||
self.writeln(" if (!fout) { fclose(fin); return; }");
|
||||
self.writeln(" char buf[4096];");
|
||||
self.writeln(" size_t n;");
|
||||
self.writeln(" while ((n = fread(buf, 1, sizeof(buf), fin)) > 0) {");
|
||||
self.writeln(" fwrite(buf, 1, n, fout);");
|
||||
self.writeln(" }");
|
||||
self.writeln(" fclose(fin);");
|
||||
self.writeln(" fclose(fout);");
|
||||
self.writeln("}");
|
||||
self.writeln("");
|
||||
self.writeln("#include <dirent.h>");
|
||||
self.writeln("// Forward declarations needed by lux_file_readDir");
|
||||
self.writeln("static LuxList* lux_list_new(int64_t capacity);");
|
||||
@@ -1391,6 +1406,11 @@ impl CBackend {
|
||||
self.writeln(" lux_file_mkdir(path);");
|
||||
self.writeln("}");
|
||||
self.writeln("");
|
||||
self.writeln("static void default_file_copy(void* env, LuxString src, LuxString dst) {");
|
||||
self.writeln(" (void)env;");
|
||||
self.writeln(" lux_file_copy(src, dst);");
|
||||
self.writeln("}");
|
||||
self.writeln("");
|
||||
self.writeln("static LuxList* default_file_readDir(void* env, LuxString path) {");
|
||||
self.writeln(" (void)env;");
|
||||
self.writeln(" return lux_file_readDir(path);");
|
||||
@@ -1404,6 +1424,7 @@ impl CBackend {
|
||||
self.writeln(" .delete_file = default_file_delete,");
|
||||
self.writeln(" .isDir = default_file_isDir,");
|
||||
self.writeln(" .mkdir = default_file_mkdir,");
|
||||
self.writeln(" .copy = default_file_copy,");
|
||||
self.writeln(" .readDir = default_file_readDir,");
|
||||
self.writeln(" .env = NULL");
|
||||
self.writeln("};");
|
||||
@@ -3679,6 +3700,16 @@ impl CBackend {
|
||||
}
|
||||
return Ok("NULL".to_string());
|
||||
}
|
||||
"copy" => {
|
||||
let src = self.emit_expr(&args[0])?;
|
||||
let dst = self.emit_expr(&args[1])?;
|
||||
if self.has_evidence {
|
||||
self.writeln(&format!("ev->file->copy(ev->file->env, {}, {});", src, dst));
|
||||
} else {
|
||||
self.writeln(&format!("lux_file_copy({}, {});", src, dst));
|
||||
}
|
||||
return Ok("NULL".to_string());
|
||||
}
|
||||
"readDir" | "listDir" => {
|
||||
let path = self.emit_expr(&args[0])?;
|
||||
let temp = format!("_readdir_{}", self.fresh_name());
|
||||
@@ -5175,7 +5206,7 @@ impl CBackend {
|
||||
} else if effect.name == "File" {
|
||||
match operation.name.as_str() {
|
||||
"read" => Some("LuxString".to_string()),
|
||||
"write" | "append" | "delete" | "mkdir" => Some("void".to_string()),
|
||||
"write" | "append" | "delete" | "mkdir" | "copy" => Some("void".to_string()),
|
||||
"exists" | "isDir" => Some("LuxBool".to_string()),
|
||||
"readDir" | "listDir" => Some("LuxList*".to_string()),
|
||||
_ => None,
|
||||
|
||||
Reference in New Issue
Block a user