From 19c8450aa0a9ea008a3e5fd4ec44f7c3761db663 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sun, 7 Dec 2025 22:53:45 -0500 Subject: Switch paths to use Result return values instead of fail() --- src/stdlib/datatypes.h | 35 ++++++++++++++++ src/stdlib/optionals.h | 2 +- src/stdlib/paths.c | 111 ++++++++++++++++++++++++++----------------------- src/stdlib/paths.h | 22 +++++----- src/stdlib/result.c | 65 +++++++++++++++++++++++++++++ src/stdlib/result.h | 9 ++++ src/stdlib/tomo.h | 1 + 7 files changed, 182 insertions(+), 63 deletions(-) create mode 100644 src/stdlib/result.c create mode 100644 src/stdlib/result.h (limited to 'src/stdlib') diff --git a/src/stdlib/datatypes.h b/src/stdlib/datatypes.h index 49c4d835..3cd99f38 100644 --- a/src/stdlib/datatypes.h +++ b/src/stdlib/datatypes.h @@ -152,6 +152,41 @@ typedef struct { }; } Path_t; +#define $OptionalPath$$type Path_t +#define OptionalPath_t Path_t + +typedef struct Result$Success$$struct { +} Result$Success$$type; + +typedef struct { + Result$Success$$type value; + bool has_value; +} $OptionalResult$Success$$type; + +typedef struct Result$Failure$$struct { + Text_t reason; +} Result$Failure$$type; + +typedef struct { + Result$Failure$$type value; + bool has_value; +} $OptionalResult$Failure$$type; + +#define Result$Success ((Result$$type){.$tag = Result$tag$Success}) +#define SuccessResult Result$Success +#define Result$tagged$Failure(msg) ((Result$$type){.$tag = Result$tag$Failure, .Failure.reason = msg}) +#define FailureResult(...) Result$tagged$Failure(Texts(__VA_ARGS__)) + +typedef struct Result$$struct { + enum { Result$tag$none, Result$tag$Success, Result$tag$Failure } $tag; + union { + Result$Success$$type Success; + Result$Failure$$type Failure; + }; +} Result$$type; + +#define Result_t Result$$type + #define OptionalBool_t uint8_t #define OptionalList_t List_t #define OptionalTable_t Table_t diff --git a/src/stdlib/optionals.h b/src/stdlib/optionals.h index 985d6611..700a4ada 100644 --- a/src/stdlib/optionals.h +++ b/src/stdlib/optionals.h @@ -15,7 +15,7 @@ #define NONE_TABLE ((OptionalTable_t){.entries.data = NULL}) #define NONE_CLOSURE ((OptionalClosure_t){.fn = NULL}) #define NONE_TEXT ((OptionalText_t){.tag = TEXT_NONE}) -#define NONE_PATH ((Path_t){.$tag = Path$tag$none}) +#define NONE_PATH ((OptionalPath_t){.$tag = Path$tag$none}) PUREFUNC bool is_none(const void *obj, const TypeInfo_t *non_optional_type); PUREFUNC uint64_t Optional$hash(const void *obj, const TypeInfo_t *type); diff --git a/src/stdlib/paths.c b/src/stdlib/paths.c index 9fb37ee3..ed8383fd 100644 --- a/src/stdlib/paths.c +++ b/src/stdlib/paths.c @@ -128,8 +128,10 @@ Path_t Path$_concat(int n, Path_t items[n]) { public Path_t Path$resolved(Path_t path, Path_t relative_to) { - if (path.$tag == Path$tag$RelativePath - && !(relative_to.$tag == Path$tag$RelativePath && relative_to.components.length == 0)) { + if (path.$tag == Path$tag$HomePath) { + return Path$expand_home(path); + } else if (path.$tag == Path$tag$RelativePath + && !(relative_to.$tag == Path$tag$RelativePath && relative_to.components.length == 0)) { Path_t result = { .$tag = relative_to.$tag, .components = relative_to.components, @@ -144,16 +146,18 @@ Path_t Path$resolved(Path_t path, Path_t relative_to) { public Path_t Path$relative_to(Path_t path, Path_t relative_to) { - if (path.$tag != relative_to.$tag) - fail("Cannot create a path relative to a different path with a mismatching type: (", path, ") relative to (", - relative_to, ")"); + if (path.$tag != relative_to.$tag) { + path = Path$resolved(path, Path$current_dir()); + relative_to = Path$resolved(relative_to, Path$current_dir()); + } Path_t result = Path$tagged$RelativePath(EMPTY_LIST); int64_t shared = 0; - for (; shared < (int64_t)path.components.length && shared < (int64_t)relative_to.components.length; shared++) { + while (shared < (int64_t)path.components.length && shared < (int64_t)relative_to.components.length) { Text_t *p = (Text_t *)(path.components.data + shared * path.components.stride); Text_t *r = (Text_t *)(relative_to.components.data + shared * relative_to.components.stride); if (!Text$equal_values(*p, *r)) break; + shared += 1; } for (int64_t i = shared; i < (int64_t)relative_to.components.length; i++) @@ -163,7 +167,6 @@ Path_t Path$relative_to(Path_t path, Path_t relative_to) { Text_t *p = (Text_t *)(path.components.data + i * path.components.stride); List$insert(&result.components, p, I(0), sizeof(Text_t)); } - // clean_components(&result.components); return result; } @@ -277,7 +280,7 @@ OptionalInt64_t Path$changed(Path_t path, bool follow_symlinks) { return (OptionalInt64_t){.value = (int64_t)sb.st_ctime}; } -static void _write(Path_t path, List_t bytes, int mode, int permissions) { +static Result_t _write(Path_t path, List_t bytes, int mode, int permissions) { path = Path$expand_home(path); const char *path_str = Path$as_c_string(path); int fd = open(path_str, mode, permissions); @@ -287,36 +290,38 @@ static void _write(Path_t path, List_t bytes, int mode, int permissions) { // be closed by GC finalizers. GC_gcollect(); fd = open(path_str, mode, permissions); - if (fd == -1) fail("Could not write to file: ", path_str, "\n", strerror(errno)); } + if (fd == -1) return FailureResult("Could not write to file: ", path_str, " (", strerror(errno), ")"); } if (bytes.stride != 1) List$compact(&bytes, 1); ssize_t written = write(fd, bytes.data, (size_t)bytes.length); - if (written != (ssize_t)bytes.length) fail("Could not write to file: ", path_str, "\n", strerror(errno)); + if (written != (ssize_t)bytes.length) + return FailureResult("Could not write to file: ", path_str, " (", strerror(errno), ")"); close(fd); + return SuccessResult; } public -void Path$write(Path_t path, Text_t text, int permissions) { +Result_t Path$write(Path_t path, Text_t text, int permissions) { List_t bytes = Text$utf8(text); - _write(path, bytes, O_WRONLY | O_CREAT | O_TRUNC, permissions); + return _write(path, bytes, O_WRONLY | O_CREAT | O_TRUNC, permissions); } public -void Path$write_bytes(Path_t path, List_t bytes, int permissions) { - _write(path, bytes, O_WRONLY | O_CREAT | O_TRUNC, permissions); +Result_t Path$write_bytes(Path_t path, List_t bytes, int permissions) { + return _write(path, bytes, O_WRONLY | O_CREAT | O_TRUNC, permissions); } public -void Path$append(Path_t path, Text_t text, int permissions) { +Result_t Path$append(Path_t path, Text_t text, int permissions) { List_t bytes = Text$utf8(text); - _write(path, bytes, O_WRONLY | O_APPEND | O_CREAT, permissions); + return _write(path, bytes, O_WRONLY | O_APPEND | O_CREAT, permissions); } public -void Path$append_bytes(Path_t path, List_t bytes, int permissions) { - _write(path, bytes, O_WRONLY | O_APPEND | O_CREAT, permissions); +Result_t Path$append_bytes(Path_t path, List_t bytes, int permissions) { + return _write(path, bytes, O_WRONLY | O_APPEND | O_CREAT, permissions); } public @@ -347,8 +352,7 @@ OptionalList_t Path$read_bytes(Path_t path, OptionalInt_t count) { memcpy(content, mem, (size_t)sb.st_size); content[sb.st_size] = '\0'; close(fd); - if (count.small && (int64_t)sb.st_size < target_count) - fail("Could not read ", target_count, " bytes from ", path, " (only got ", (uint64_t)sb.st_size, ")"); + if (count.small && (int64_t)sb.st_size < target_count) return NONE_LIST; int64_t len = count.small ? target_count : (int64_t)sb.st_size; return (List_t){.data = content, .atomic = 1, .stride = 1, .length = (uint64_t)len}; } else { @@ -376,8 +380,7 @@ OptionalList_t Path$read_bytes(Path_t path, OptionalInt_t count) { len += (size_t)just_read; } close(fd); - if (count.small != 0 && (int64_t)len < target_count) - fail("Could not read ", target_count, " bytes from ", path, " (only got ", (uint64_t)len, ")"); + if (count.small != 0 && (int64_t)len < target_count) return NONE_LIST; return (List_t){.data = content, .atomic = 1, .stride = 1, .length = (uint64_t)len}; } } @@ -408,23 +411,24 @@ OptionalText_t Path$group(Path_t path, bool follow_symlinks) { } public -void Path$set_owner(Path_t path, OptionalText_t owner, OptionalText_t group, bool follow_symlinks) { +Result_t Path$set_owner(Path_t path, OptionalText_t owner, OptionalText_t group, bool follow_symlinks) { uid_t owner_id = (uid_t)-1; if (owner.tag == TEXT_NONE) { struct passwd *pwd = getpwnam(Text$as_c_string(owner)); - if (pwd == NULL) fail("Not a valid user: ", owner); + if (pwd == NULL) return FailureResult("Not a valid user: ", owner); owner_id = pwd->pw_uid; } gid_t group_id = (gid_t)-1; if (group.tag == TEXT_NONE) { struct group *grp = getgrnam(Text$as_c_string(group)); - if (grp == NULL) fail("Not a valid group: ", group); + if (grp == NULL) return FailureResult("Not a valid group: ", group); group_id = grp->gr_gid; } const char *path_str = Path$as_c_string(path); int result = follow_symlinks ? chown(path_str, owner_id, group_id) : lchown(path_str, owner_id, group_id); - if (result < 0) fail("Could not set owner!"); + if (result < 0) return FailureResult("Could not set owner!"); + return SuccessResult; } static int _remove_files(const char *path, const struct stat *sbuf, int type, struct FTW *ftwb) { @@ -446,29 +450,30 @@ static int _remove_files(const char *path, const struct stat *sbuf, int type, st } public -void Path$remove(Path_t path, bool ignore_missing) { +Result_t Path$remove(Path_t path, bool ignore_missing) { path = Path$expand_home(path); const char *path_str = Path$as_c_string(path); struct stat sb; if (lstat(path_str, &sb) != 0) { - if (!ignore_missing) fail("Could not remove file: ", path_str, " (", strerror(errno), ")"); - return; + if (!ignore_missing) return FailureResult("Could not remove file: ", path_str, " (", strerror(errno), ")"); + return SuccessResult; } if ((sb.st_mode & S_IFMT) == S_IFREG || (sb.st_mode & S_IFMT) == S_IFLNK) { if (unlink(path_str) != 0 && !ignore_missing) - fail("Could not remove file: ", path_str, " (", strerror(errno), ")"); + return FailureResult("Could not remove file: ", path_str, " (", strerror(errno), ")"); } else if ((sb.st_mode & S_IFMT) == S_IFDIR) { const int num_open_fd = 10; if (nftw(path_str, _remove_files, num_open_fd, FTW_DEPTH | FTW_MOUNT | FTW_PHYS) < 0) - fail("Could not remove directory: %s (%s)", path_str, strerror(errno)); + return FailureResult("Could not remove directory: ", path_str, " (", strerror(errno), ")"); } else { - fail("Could not remove path: ", path_str, " (not a file or directory)"); + return FailureResult("Could not remove path: ", path_str, " (not a file or directory)"); } + return SuccessResult; } public -void Path$create_directory(Path_t path, int permissions, bool recursive) { +Result_t Path$create_directory(Path_t path, int permissions, bool recursive) { retry: path = Path$expand_home(path); const char *c_path = Path$as_c_string(path); @@ -478,19 +483,20 @@ retry: Path$create_directory(Path$parent(path), permissions, recursive); goto retry; } else if (errno != EEXIST) { - fail("Could not create directory: ", c_path, " (", strerror(errno), ")"); + return FailureResult("Could not create directory: ", c_path, " (", strerror(errno), ")"); } } + return SuccessResult; } -static List_t _filtered_children(Path_t path, bool include_hidden, mode_t filter) { +static OptionalList_t _filtered_children(Path_t path, bool include_hidden, mode_t filter) { path = Path$expand_home(path); struct dirent *dir; List_t children = EMPTY_LIST; const char *path_str = Path$as_c_string(path); size_t path_len = strlen(path_str); DIR *d = opendir(path_str); - if (!d) fail("Could not open directory: ", path, " (", strerror(errno), ")"); + if (!d) return NONE_LIST; if (path_str[path_len - 1] == '/') --path_len; @@ -511,18 +517,22 @@ static List_t _filtered_children(Path_t path, bool include_hidden, mode_t filter } public -List_t Path$children(Path_t path, bool include_hidden) { return _filtered_children(path, include_hidden, (mode_t)-1); } +OptionalList_t Path$children(Path_t path, bool include_hidden) { + return _filtered_children(path, include_hidden, (mode_t)-1); +} public -List_t Path$files(Path_t path, bool include_hidden) { return _filtered_children(path, include_hidden, S_IFREG); } +OptionalList_t Path$files(Path_t path, bool include_hidden) { + return _filtered_children(path, include_hidden, S_IFREG); +} public -List_t Path$subdirectories(Path_t path, bool include_hidden) { +OptionalList_t Path$subdirectories(Path_t path, bool include_hidden) { return _filtered_children(path, include_hidden, S_IFDIR); } public -Path_t Path$unique_directory(Path_t path) { +OptionalPath_t Path$unique_directory(Path_t path) { path = Path$expand_home(path); const char *path_str = Path$as_c_string(path); size_t len = strlen(path_str); @@ -532,12 +542,12 @@ Path_t Path$unique_directory(Path_t path) { buf[len] = '\0'; if (buf[len - 1] == '/') buf[--len] = '\0'; char *created = mkdtemp(buf); - if (!created) fail("Failed to create temporary directory: ", path_str, " (", strerror(errno), ")"); + if (!created) return NONE_PATH; return Path$from_str(created); } public -Path_t Path$write_unique_bytes(Path_t path, List_t bytes) { +OptionalPath_t Path$write_unique_bytes(Path_t path, List_t bytes) { path = Path$expand_home(path); const char *path_str = Path$as_c_string(path); size_t len = strlen(path_str); @@ -553,23 +563,23 @@ Path_t Path$write_unique_bytes(Path_t path, List_t bytes) { ++suffixlen; int fd = mkstemps(buf, suffixlen); - if (fd == -1) fail("Could not write to unique file: ", buf, "\n", strerror(errno)); + if (fd == -1) return NONE_PATH; if (bytes.stride != 1) List$compact(&bytes, 1); ssize_t written = write(fd, bytes.data, (size_t)bytes.length); - if (written != (ssize_t)bytes.length) fail("Could not write to file: ", buf, "\n", strerror(errno)); + if (written != (ssize_t)bytes.length) fail("Could not write to file: ", buf, " (", strerror(errno), ")"); close(fd); return Path$from_str(buf); } public -Path_t Path$write_unique(Path_t path, Text_t text) { return Path$write_unique_bytes(path, Text$utf8(text)); } +OptionalPath_t Path$write_unique(Path_t path, Text_t text) { return Path$write_unique_bytes(path, Text$utf8(text)); } public -Path_t Path$parent(Path_t path) { +OptionalPath_t Path$parent(Path_t path) { if (path.$tag == Path$tag$AbsolutePath && path.components.length == 0) { - return path; + return NONE_PATH; } else if (path.components.length > 0 && !Text$equal_values( *(Text_t *)(path.components.data + path.components.stride * ((int64_t)path.components.length - 1)), @@ -631,11 +641,10 @@ public Path_t Path$sibling(Path_t path, Text_t name) { return Path$child(Path$parent(path), name); } public -Path_t Path$with_extension(Path_t path, Text_t extension, bool replace) { - if (path.components.length == 0) fail("A path with no components can't have an extension!"); +OptionalPath_t Path$with_extension(Path_t path, Text_t extension, bool replace) { + if (path.components.length == 0) return NONE_PATH; - if (Text$has(extension, Text("/")) || Text$has(extension, Text(";"))) - fail("Path extension has invalid characters: ", extension); + if (Text$has(extension, Text("/")) || Text$has(extension, Text(";"))) return NONE_PATH; Path_t result = { .$tag = path.$tag, diff --git a/src/stdlib/paths.h b/src/stdlib/paths.h index 7a175099..4e52866e 100644 --- a/src/stdlib/paths.h +++ b/src/stdlib/paths.h @@ -34,24 +34,24 @@ bool Path$can_execute(Path_t path); OptionalInt64_t Path$modified(Path_t path, bool follow_symlinks); OptionalInt64_t Path$accessed(Path_t path, bool follow_symlinks); OptionalInt64_t Path$changed(Path_t path, bool follow_symlinks); -void Path$write(Path_t path, Text_t text, int permissions); -void Path$write_bytes(Path_t path, List_t bytes, int permissions); -void Path$append(Path_t path, Text_t text, int permissions); -void Path$append_bytes(Path_t path, List_t bytes, int permissions); +Result_t Path$write(Path_t path, Text_t text, int permissions); +Result_t Path$write_bytes(Path_t path, List_t bytes, int permissions); +Result_t Path$append(Path_t path, Text_t text, int permissions); +Result_t Path$append_bytes(Path_t path, List_t bytes, int permissions); OptionalText_t Path$read(Path_t path); OptionalList_t Path$read_bytes(Path_t path, OptionalInt_t limit); -void Path$set_owner(Path_t path, OptionalText_t owner, OptionalText_t group, bool follow_symlinks); +Result_t Path$set_owner(Path_t path, OptionalText_t owner, OptionalText_t group, bool follow_symlinks); OptionalText_t Path$owner(Path_t path, bool follow_symlinks); OptionalText_t Path$group(Path_t path, bool follow_symlinks); -void Path$remove(Path_t path, bool ignore_missing); -void Path$create_directory(Path_t path, int permissions, bool recursive); +Result_t Path$remove(Path_t path, bool ignore_missing); +Result_t Path$create_directory(Path_t path, int permissions, bool recursive); List_t Path$children(Path_t path, bool include_hidden); List_t Path$files(Path_t path, bool include_hidden); List_t Path$subdirectories(Path_t path, bool include_hidden); -Path_t Path$unique_directory(Path_t path); -Path_t Path$write_unique(Path_t path, Text_t text); -Path_t Path$write_unique_bytes(Path_t path, List_t bytes); -Path_t Path$parent(Path_t path); +OptionalPath_t Path$unique_directory(Path_t path); +OptionalPath_t Path$write_unique(Path_t path, Text_t text); +OptionalPath_t Path$write_unique_bytes(Path_t path, List_t bytes); +OptionalPath_t Path$parent(Path_t path); Text_t Path$base_name(Path_t path); Text_t Path$extension(Path_t path, bool full); bool Path$has_extension(Path_t path, Text_t extension); diff --git a/src/stdlib/result.c b/src/stdlib/result.c new file mode 100644 index 00000000..8fd2ca1e --- /dev/null +++ b/src/stdlib/result.c @@ -0,0 +1,65 @@ +// Result (Success/Failure) type info +#include +#include +#include +#include +#include + +#include "enums.h" +#include "structs.h" +#include "text.h" +#include "util.h" + +public +const TypeInfo_t Result$Success$$info = { + .size = sizeof(Result$Success$$type), + .align = __alignof__(Result$Success$$type), + .tag = StructInfo, + .StructInfo = + { + .name = "Success", + .num_fields = 0, + }, + .metamethods = Struct$metamethods, +}; + +public +const TypeInfo_t Result$Failure$$info = { + .size = sizeof(Result$Failure$$type), + .align = __alignof__(Result$Failure$$type), + .tag = StructInfo, + .StructInfo = + { + .name = "Failure", + .num_fields = 1, + .fields = + (NamedType_t[1]){ + {.name = "reason", .type = &Text$info}, + }, + }, + .metamethods = Struct$metamethods, +}; + +public +const TypeInfo_t Result$$info = { + .size = sizeof(Result_t), + .align = __alignof__(Result_t), + .tag = EnumInfo, + .EnumInfo = + { + .name = "Result", + .num_tags = 2, + .tags = + (NamedType_t[2]){ + { + .name = "Success", + .type = &Result$Success$$info, + }, + { + .name = "Failure", + .type = &Result$Failure$$info, + }, + }, + }, + .metamethods = Enum$metamethods, +}; diff --git a/src/stdlib/result.h b/src/stdlib/result.h new file mode 100644 index 00000000..328480e7 --- /dev/null +++ b/src/stdlib/result.h @@ -0,0 +1,9 @@ +#pragma once + +// Result type for Success/Failure + +#include "types.h" + +extern const TypeInfo_t Result$Success$$info; +extern const TypeInfo_t Result$Failure$$info; +extern const TypeInfo_t Result$$info; diff --git a/src/stdlib/tomo.h b/src/stdlib/tomo.h index 1ff065b9..ff8f90a2 100644 --- a/src/stdlib/tomo.h +++ b/src/stdlib/tomo.h @@ -25,6 +25,7 @@ #include "paths.h" // IWYU pragma: export #include "pointers.h" // IWYU pragma: export #include "print.h" // IWYU pragma: export +#include "result.h" // IWYU pragma: export #include "siphash.h" // IWYU pragma: export #include "stacktrace.h" // IWYU pragma: export #include "structs.h" // IWYU pragma: export -- cgit v1.2.3