diff --git a/src/hvm.c b/src/hvm.c index 8d116de9..3a68857c 100644 --- a/src/hvm.c +++ b/src/hvm.c @@ -809,7 +809,7 @@ static inline Port enter(Net* net, Port var) { } // Atomically Links `A ~ B`. -static inline void link(Net* net, TM* tm, Port A, Port B) { +static inline void link_ports(Net* net, TM* tm, Port A, Port B) { // Attempts to directionally point `A ~> B` while (true) { // If `A` is NODE: swap `A` and `B`, and continue @@ -842,7 +842,7 @@ static inline void link(Net* net, TM* tm, Port A, Port B) { // Links `A ~ B` (as a pair). static inline void link_pair(Net* net, TM* tm, Pair AB) { - link(net, tm, get_fst(AB), get_snd(AB)); + link_ports(net, tm, get_fst(AB), get_snd(AB)); } // Interactions diff --git a/src/run.c b/src/run.c index 13b3fa94..8512c30c 100644 --- a/src/run.c +++ b/src/run.c @@ -1,6 +1,13 @@ +#ifndef __APPLE__ +#define _XOPEN_SOURCE 500 +#endif + #include #include #include +#include +#include +#include #include "hvm.c" // Readback: λ-Encoded Ctr @@ -716,6 +723,76 @@ Port io_dl_close(Net* net, Book* book, Port argm) { return inject_ok(net, new_port(ERA, 0)); } +// Deletes a single file or an empty directory at the specified path. +// Returns Ok(None) if successful, or Err(reason) if an error occurs. +// This function attempts to remove both files and empty directories without +// first checking the type of the path. +// Returns: Result<*, IOError> +Port io_remove(Net* net, Book* book, Port argm) { + Str path = readback_str(net, book, argm); + + int result = remove(path.buf); + free(path.buf); + + if (result == 0) { + return inject_ok(net, new_port(ERA, 0)); + } else { + return inject_io_err_inner(net, new_port(NUM, new_i24(errno))); + } +} + +int remove_all_aux(const char* path, const struct stat* stat, int flags, struct FTW* ftw) { + return remove(path); +} + +int remove_all(const char* path) { + struct stat st; + if (stat(path, &st) != 0) { + return remove(path); + } + if (S_ISDIR(st.st_mode)) { + return nftw(path, remove_all_aux, 32, FTW_DEPTH | FTW_PHYS); + } else { + return remove(path); + } +} + +// Removes any file or directory recursively at the specified path. +// it will delete the directory and all its contents. +// Returns Ok(None) if successful, or Err(reason) if an error occurs. +// Note: For non-recursive deletion of an empty directory, +// this function behaves the same as delete_file(path). +// Returns: Result<*, IOError> +Port io_remove_all(Net* net, Book* book, Port argm) { + Str path = readback_str(net, book, argm); + + int res = remove_all(path.buf); + free(path.buf); + + if (0 == res) { + return inject_ok(net, new_port(ERA, 0)); + } else { + return inject_io_err_inner(net, new_port(NUM, new_i24(errno))); + } +} + +// Creates a new directory with the given path. +// Returns Ok(None) if sucessfull, or Err(reason) if an error occurs. +// Returns: Result<*, IOError> +Port io_mkdir(Net* net, Book* book, Port argm) { + Str name = readback_str(net, book, argm); + + const mode_t mode = 0777; + int status = mkdir(name.buf, mode); + free(name.buf); + + if (status) { + return inject_io_err_inner(net, new_port(NUM, new_i24(errno))); + } else { + return inject_ok(net, new_port(ERA, 0)); + } +} + // Book Loader // ----------- @@ -731,6 +808,9 @@ void book_init(Book* book) { book->ffns_buf[book->ffns_len++] = (FFn){"DL_OPEN", io_dl_open}; book->ffns_buf[book->ffns_len++] = (FFn){"DL_CALL", io_dl_call}; book->ffns_buf[book->ffns_len++] = (FFn){"DL_CLOSE", io_dl_open}; + book->ffns_buf[book->ffns_len++] = (FFn){"RM", io_remove}; + book->ffns_buf[book->ffns_len++] = (FFn){"RM_ALL", io_remove_all}; + book->ffns_buf[book->ffns_len++] = (FFn){"MKDIR", io_mkdir}; } // Monadic IO Evaluator diff --git a/src/run.cu b/src/run.cu index 970f8a01..24b74ecd 100644 --- a/src/run.cu +++ b/src/run.cu @@ -1,6 +1,9 @@ #include #include #include +#include +#include +#include #include "hvm.cu" // Readback: λ-Encoded Ctr @@ -834,6 +837,76 @@ Port io_dl_close(GNet* gnet, Book* book, Port argm) { return gnet_inject_ok(gnet, new_port(ERA, 0)); } +// Removes a single file or an empty directory at the specified path. +// Returns Ok(None) if successful, or Err(reason) if an error occurs. +// This function attempts to remove both files and empty directories without +// first checking the type of the path. +// Returns: Result<*, IOError> +Port io_remove(GNet* gnet, Port argm) { + Str s = gnet_readback_str(gnet, argm); + + int result = remove(s.buf); + free(s.buf); + + if (result == 0) { + return gnet_inject_ok(gnet, new_port(ERA, 0)); + } else { + return gnet_inject_io_err_inner(gnet, new_port(NUM, new_i24(errno))); + } +} + +int remove_all_aux(const char* path, const struct stat* stat, int flags, struct FTW* ftw) { + return remove(path); +} + +int remove_all(const char* path) { + struct stat st; + if (stat(path, &st) != 0) { + return remove(path); + } + if (S_ISDIR(st.st_mode)) { + return nftw(path, remove_all_aux, 32, FTW_DEPTH | FTW_PHYS); + } else { + return remove(path); + } +} + +// Removes any file or directory recursively at the specified path. +// it will delete the directory and all its contents. +// Returns Ok(None) if successful, or Err(reason) if an error occurs. +// Note: For non-recursive deletion of an empty directory, +// this function behaves the same as delete_file(path). +// Returns: Result<*, IOError> +Port io_remove_all(GNet* gnet, Port argm) { + Str path = gnet_readback_str(gnet, argm); + + int res = remove_all(path.buf); + free(path.buf); + + if (0 == res) { + return gnet_inject_ok(gnet, new_port(ERA, 0)); + } else { + return gnet_inject_io_err_inner(gnet, new_port(NUM, new_i24(errno))); + } +} + +// Creates a new directory with the given path. +// Returns Ok(None) if sucessfull, or Err(reason) if an error occurs. +// Returns: Result<*, IOError> +Port io_mkdir(GNet* gnet, Port argm) { + Str name = gnet_readback_str(gnet, argm); + + const mode_t mode = 0777; + int status = mkdir(name.buf, mode); + free(name.buf); + + if (status) { + return gnet_inject_io_err_inner(gnet, new_port(NUM, new_i24(errno))); + } else { + return gnet_inject_ok(gnet, new_port(ERA, 0)); + } +} + void book_init(Book* book) { book->ffns_buf[book->ffns_len++] = (FFn){"READ", io_read}; book->ffns_buf[book->ffns_len++] = (FFn){"OPEN", io_open}; @@ -846,6 +919,9 @@ void book_init(Book* book) { book->ffns_buf[book->ffns_len++] = (FFn){"DL_OPEN", io_dl_open}; book->ffns_buf[book->ffns_len++] = (FFn){"DL_CALL", io_dl_call}; book->ffns_buf[book->ffns_len++] = (FFn){"DL_CLOSE", io_dl_open}; + book->ffns_buf[book->ffns_len++] = (FFn){"RM", io_remove}; + book->ffns_buf[book->ffns_len++] = (FFn){"RM_ALL", io_remove_all}; + book->ffns_buf[book->ffns_len++] = (FFn){"MKDIR", io_mkdir}; cudaMemcpyToSymbol(BOOK, book, sizeof(Book)); } diff --git a/tests/programs/io/mkdir.bend b/tests/programs/io/mkdir.bend new file mode 100644 index 00000000..38f2ba7b --- /dev/null +++ b/tests/programs/io/mkdir.bend @@ -0,0 +1,19 @@ +#{ + Creates the batata directory and then removes it. +#} + +test-io = 1 + +mkdir path = + (call "MKDIR" path) + +rm_all path = + (call "RM_ALL" path) + +main = + let path = "./batata" + with IO { + ask * = (mkdir path) + ask s = (rm_all path) + (wrap s) + } diff --git a/tests/programs/io/remove_all_file.bend b/tests/programs/io/remove_all_file.bend new file mode 100644 index 00000000..ca2d96bf --- /dev/null +++ b/tests/programs/io/remove_all_file.bend @@ -0,0 +1,16 @@ +#{ + Calls the rm_all function with a file path as argument. +#} + +test-io = 1 + +rm_all path = + (call "RM_ALL" path) + +main = + let temp = "./temp.txt" + with IO { + ask * = (IO/FS/write_file temp (String/encode_utf8 "Contents")) + ask s = (rm_all temp) + (wrap s) + } diff --git a/tests/programs/io/remove_all_recursive.bend b/tests/programs/io/remove_all_recursive.bend new file mode 100644 index 00000000..17c3a709 --- /dev/null +++ b/tests/programs/io/remove_all_recursive.bend @@ -0,0 +1,35 @@ +#{ + Creates the following tree structure and then removes A and its children. + A + |-- a.txt + |-- AA + | `-- aa.txt + `-- AB + `-- ab.txt +#} + +test-io = 1 + +mkdir path = + (call "MKDIR" path) + +rm_all path = + (call "RM_ALL" path) + +test = + with IO { + ask * = (mkdir "A") + ask * = (mkdir "A/AA") + ask * = (mkdir "A/AB") + ask * = (IO/FS/write_file "A/a.txt" (String/encode_utf8 "a")) + ask * = (IO/FS/write_file "A/AA/aa.txt" (String/encode_utf8 "aa")) + ask * = (IO/FS/write_file "A/AB/ab.txt" (String/encode_utf8 "ab")) + ask s = (rm_all "./A") + (wrap s) + } + +main = + with IO { + ask res = (test) + (wrap res) + } diff --git a/tests/programs/io/remove_empty_dir.bend b/tests/programs/io/remove_empty_dir.bend new file mode 100644 index 00000000..94f9507e --- /dev/null +++ b/tests/programs/io/remove_empty_dir.bend @@ -0,0 +1,18 @@ +#{ + Uses the rm function to remove an empty directory. +#} + +test-io = 1 + +mkdir path = + (call "MKDIR" path) + +rm path = + (call "RM" path) + +main = + with IO { + ask * = (mkdir "temp") + ask s = (rm "temp") + (wrap s) + } diff --git a/tests/programs/io/remove_file.bend b/tests/programs/io/remove_file.bend new file mode 100644 index 00000000..e36fe6f5 --- /dev/null +++ b/tests/programs/io/remove_file.bend @@ -0,0 +1,16 @@ +#{ + Creates a temporary file and then removes it. +#} + +test-io = 1 + +rm path = + (call "RM" path) + +main = + let path = "./temp.txt" + with IO { + ask * = (IO/FS/write_file path (String/encode_utf8 "Contents")) + ask s = (rm path) + (wrap s) + } diff --git a/tests/programs/io/remove_non_existing_file.bend b/tests/programs/io/remove_non_existing_file.bend new file mode 100644 index 00000000..5072b091 --- /dev/null +++ b/tests/programs/io/remove_non_existing_file.bend @@ -0,0 +1,15 @@ +#{ + Tries to remove a non existing file. +#} + +test-io = 1 + +rm path = + (call "RM" path) + +main = + use path = "./non_existing.txt" + with IO { + ask s = (rm path) + (wrap s) + }