aboutsummaryrefslogtreecommitdiff
path: root/src/stdlib
diff options
context:
space:
mode:
authorBruce Hill <bruce@bruce-hill.com>2025-12-11 13:50:01 -0500
committerBruce Hill <bruce@bruce-hill.com>2025-12-11 13:52:46 -0500
commit7f8f2117799cdfa6b62909a9182b5adade1d0bd2 (patch)
tree1db466db870768e952f50572453660e090e434e0 /src/stdlib
parent630f910563b6f27dd34a4a0496a43d32539eadcb (diff)
parent02886fab651d3f64d2c8ded5597e6c075dc69b5f (diff)
Merge branch 'dev' into constructive-reals
Diffstat (limited to 'src/stdlib')
-rw-r--r--src/stdlib/bigint.c116
-rw-r--r--src/stdlib/bigint.h2
-rw-r--r--src/stdlib/bytes.c4
-rw-r--r--src/stdlib/bytes.h2
-rw-r--r--src/stdlib/cli.c25
-rw-r--r--src/stdlib/datatypes.h86
-rw-r--r--src/stdlib/floatX.c.h1
-rw-r--r--src/stdlib/intX.c.h4
-rw-r--r--src/stdlib/intX.h2
-rw-r--r--src/stdlib/memory.c2
-rw-r--r--src/stdlib/metamethods.c6
-rw-r--r--src/stdlib/metamethods.h1
-rw-r--r--src/stdlib/optionals.h2
-rw-r--r--src/stdlib/paths.c361
-rw-r--r--src/stdlib/paths.h31
-rw-r--r--src/stdlib/print.c2
-rw-r--r--src/stdlib/result.c65
-rw-r--r--src/stdlib/result.h9
-rw-r--r--src/stdlib/stacktrace.c2
-rw-r--r--src/stdlib/stdlib.c48
-rw-r--r--src/stdlib/stdlib.h10
-rw-r--r--src/stdlib/structs.c11
-rw-r--r--src/stdlib/structs.h2
-rw-r--r--src/stdlib/tables.c12
-rw-r--r--src/stdlib/tables.h2
-rw-r--r--src/stdlib/text.c27
-rw-r--r--src/stdlib/text.h2
-rw-r--r--src/stdlib/tomo.h1
28 files changed, 569 insertions, 269 deletions
diff --git a/src/stdlib/bigint.c b/src/stdlib/bigint.c
index 41e8e6db..2d145bd5 100644
--- a/src/stdlib/bigint.c
+++ b/src/stdlib/bigint.c
@@ -393,52 +393,98 @@ PUREFUNC Closure_t Int$onward(Int_t first, Int_t step) {
}
public
-OptionalInt_t Int$from_str(const char *str) {
- mpz_t i;
- int result;
- if (strncmp(str, "0x", 2) == 0) {
- result = mpz_init_set_str(i, str + 2, 16);
- } else if (strncmp(str, "0o", 2) == 0) {
- result = mpz_init_set_str(i, str + 2, 8);
- } else if (strncmp(str, "0b", 2) == 0) {
- result = mpz_init_set_str(i, str + 2, 2);
- } else {
- result = mpz_init_set_str(i, str, 10);
- }
- if (result != 0) return NONE_INT;
- return Int$from_mpz(i);
-}
+Int_t Int$from_str(const char *str) { return Int$parse(Text$from_str(str), NONE_INT, NULL); }
public
-OptionalInt_t Int$parse(Text_t text, Text_t *remainder) {
+OptionalInt_t Int$parse(Text_t text, OptionalInt_t base, Text_t *remainder) {
const char *str = Text$as_c_string(text);
- mpz_t i;
- int result;
- if (strncmp(str, "0x", 2) == 0) {
- const char *end = str + 2 + strspn(str + 2, "0123456789abcdefABCDEF");
- if (remainder) *remainder = Text$from_str(end);
- else if (*end != '\0') return NONE_INT;
- result = mpz_init_set_str(i, String(string_slice(str + 2, (size_t)(end - (str + 2)))), 16);
+ bool negative = (*str == '-');
+ if (negative || *str == '+') str += 1;
+ const char *end = str;
+ int32_t base32;
+ if (base.small != 0) {
+ base32 = Int32$from_int(base, false);
+ switch (base32) {
+ case 16:
+ if (strncmp(str, "0x", 2) == 0) {
+ base16_prefix:
+ str += 2;
+ }
+ end = str + strspn(str, "0123456789abcdefABCDEF");
+ break;
+ case 10:
+ base10:
+ end = str + strspn(str, "0123456789");
+ break;
+ case 8:
+ if (strncmp(str, "0o", 2) == 0) {
+ base8_prefix:
+ str += 2;
+ }
+ end = str + strspn(str, "01234567");
+ break;
+ case 2:
+ if (strncmp(str, "0b", 2) == 0) {
+ base2_prefix:
+ str += 2;
+ }
+ end = str + strspn(str, "01");
+ break;
+ case 1: {
+ str += strspn(str, "0");
+ size_t n = strspn(str, "1");
+ end = str + n;
+ if (remainder) *remainder = Text$from_str(end);
+ else if (*end != '\0') return NONE_INT;
+ return Int$from_int64((int64_t)n);
+ }
+ default: {
+ if (base32 < 1 || base32 > 36) {
+ if (remainder) *remainder = text;
+ return NONE_INT;
+ }
+ for (; *end; end++) {
+ char c = *end;
+ int32_t digit;
+ if ('0' <= c && c <= '9') {
+ digit = (c - (int)'0');
+ } else if ('a' <= c && c <= 'z') {
+ digit = (c - (int)'a');
+ } else if ('A' <= c && c <= 'Z') {
+ digit = (c - (int)'A');
+ } else {
+ break;
+ }
+ if (digit >= base32) break;
+ }
+ }
+ }
+ } else if (strncmp(str, "0x", 2) == 0) {
+ base32 = 16;
+ goto base16_prefix;
} else if (strncmp(str, "0o", 2) == 0) {
- const char *end = str + 2 + strspn(str + 2, "01234567");
- if (remainder) *remainder = Text$from_str(end);
- else if (*end != '\0') return NONE_INT;
- result = mpz_init_set_str(i, String(string_slice(str + 2, (size_t)(end - (str + 2)))), 8);
+ base32 = 8;
+ goto base8_prefix;
} else if (strncmp(str, "0b", 2) == 0) {
- const char *end = str + 2 + strspn(str + 2, "01");
- if (remainder) *remainder = Text$from_str(end);
- else if (*end != '\0') return NONE_INT;
- result = mpz_init_set_str(i, String(string_slice(str + 2, (size_t)(end - (str + 2)))), 2);
+ base32 = 2;
+ goto base2_prefix;
} else {
- const char *end = str + strspn(str, "0123456789");
- if (remainder) *remainder = Text$from_str(end);
- else if (*end != '\0') return NONE_INT;
- result = mpz_init_set_str(i, String(string_slice(str, (size_t)(end - str))), 10);
+ base32 = 10;
+ goto base10;
}
+
+ if (remainder) *remainder = Text$from_str(end);
+ else if (*end != '\0') return NONE_INT;
+
+ mpz_t i;
+ int result = mpz_init_set_str(i, String(string_slice(str, (size_t)(end - str))), base32);
if (result != 0) {
if (remainder) *remainder = text;
return NONE_INT;
}
+ if (negative) {
+ mpz_neg(i, i);
+ }
return Int$from_mpz(i);
}
diff --git a/src/stdlib/bigint.h b/src/stdlib/bigint.h
index e50a6847..b57844a4 100644
--- a/src/stdlib/bigint.h
+++ b/src/stdlib/bigint.h
@@ -24,7 +24,7 @@ Text_t Int$octal(Int_t i, Int_t digits, bool prefix);
PUREFUNC Closure_t Int$to(Int_t first, Int_t last, OptionalInt_t step);
PUREFUNC Closure_t Int$onward(Int_t first, Int_t step);
OptionalInt_t Int$from_str(const char *str);
-OptionalInt_t Int$parse(Text_t text, Text_t *remainder);
+OptionalInt_t Int$parse(Text_t text, OptionalInt_t base, Text_t *remainder);
Int_t Int$abs(Int_t x);
Int_t Int$power(Int_t base, Int_t exponent);
Int_t Int$gcd(Int_t x, Int_t y);
diff --git a/src/stdlib/bytes.c b/src/stdlib/bytes.c
index ab689ae4..4416d804 100644
--- a/src/stdlib/bytes.c
+++ b/src/stdlib/bytes.c
@@ -33,8 +33,8 @@ public
CONSTFUNC bool Byte$is_between(const Byte_t x, const Byte_t low, const Byte_t high) { return low <= x && x <= high; }
public
-OptionalByte_t Byte$parse(Text_t text, Text_t *remainder) {
- OptionalInt_t full_int = Int$parse(text, remainder);
+OptionalByte_t Byte$parse(Text_t text, OptionalInt_t base, Text_t *remainder) {
+ OptionalInt_t full_int = Int$parse(text, base, remainder);
if (full_int.small != 0L && Int$compare_value(full_int, I(0)) >= 0 && Int$compare_value(full_int, I(255)) <= 0) {
return (OptionalByte_t){.has_value = true, .value = Byte$from_int(full_int, true)};
} else {
diff --git a/src/stdlib/bytes.h b/src/stdlib/bytes.h
index 2f948177..6581f300 100644
--- a/src/stdlib/bytes.h
+++ b/src/stdlib/bytes.h
@@ -18,7 +18,7 @@ Byte_t Byte$from_int(Int_t i, bool truncate);
Byte_t Byte$from_int64(int64_t i, bool truncate);
Byte_t Byte$from_int32(int32_t i, bool truncate);
Byte_t Byte$from_int16(int16_t i, bool truncate);
-OptionalByte_t Byte$parse(Text_t text, Text_t *remainder);
+OptionalByte_t Byte$parse(Text_t text, OptionalInt_t base, Text_t *remainder);
Closure_t Byte$to(Byte_t first, Byte_t last, OptionalInt8_t step);
MACROLIKE Byte_t Byte$from_int8(int8_t i) { return (Byte_t)i; }
diff --git a/src/stdlib/cli.c b/src/stdlib/cli.c
index 8301a2c2..cc2fa0b8 100644
--- a/src/stdlib/cli.c
+++ b/src/stdlib/cli.c
@@ -202,7 +202,7 @@ static List_t parse_arg_list(List_t args, const char *flag, void *dest, const Ty
if ((type->tag == TextInfo || type == &CString$info) && arg[0] == '\\' && arg[1] == '-') {
arg = arg + 1;
} else if (arg[0] == '-') {
- print_err("Not a valid argument for flag ", flag, ": ", arg);
+ print_err("Not a valid flag: ", arg);
}
}
@@ -215,7 +215,10 @@ static List_t parse_arg_list(List_t args, const char *flag, void *dest, const Ty
return List$from(args, I(2));
} else {
args = parse_arg_list(args, flag, dest, nonnull, allow_dashes);
- if (nonnull == &Int64$info) ((OptionalInt64_t *)dest)->has_value = true;
+ if (nonnull == &Int$info || nonnull == &Path$info || nonnull == &Num$info || nonnull == &Num32$info
+ || nonnull->tag == TextInfo || nonnull->tag == EnumInfo)
+ return args;
+ else if (nonnull == &Int64$info) ((OptionalInt64_t *)dest)->has_value = true;
else if (nonnull == &Int32$info) ((OptionalInt32_t *)dest)->has_value = true;
else if (nonnull == &Int16$info) ((OptionalInt16_t *)dest)->has_value = true;
else if (nonnull == &Int8$info) ((OptionalInt8_t *)dest)->has_value = true;
@@ -235,23 +238,23 @@ static List_t parse_arg_list(List_t args, const char *flag, void *dest, const Ty
if (parsed.small == 0) print_err("Could not parse argument for ", flag, ": ", arg);
*(Int_t *)dest = parsed;
} else if (type == &Int64$info) {
- OptionalInt64_t parsed = Int64$parse(Text$from_str(arg), NULL);
+ OptionalInt64_t parsed = Int64$parse(Text$from_str(arg), NONE_INT, NULL);
if (!parsed.has_value) print_err("Could not parse argument for ", flag, ": ", arg);
*(Int64_t *)dest = parsed.value;
} else if (type == &Int32$info) {
- OptionalInt32_t parsed = Int32$parse(Text$from_str(arg), NULL);
+ OptionalInt32_t parsed = Int32$parse(Text$from_str(arg), NONE_INT, NULL);
if (!parsed.has_value) print_err("Could not parse argument for ", flag, ": ", arg);
*(Int32_t *)dest = parsed.value;
} else if (type == &Int16$info) {
- OptionalInt16_t parsed = Int16$parse(Text$from_str(arg), NULL);
+ OptionalInt16_t parsed = Int16$parse(Text$from_str(arg), NONE_INT, NULL);
if (!parsed.has_value) print_err("Could not parse argument for ", flag, ": ", arg);
*(Int16_t *)dest = parsed.value;
} else if (type == &Int8$info) {
- OptionalInt8_t parsed = Int8$parse(Text$from_str(arg), NULL);
+ OptionalInt8_t parsed = Int8$parse(Text$from_str(arg), NONE_INT, NULL);
if (!parsed.has_value) print_err("Could not parse argument for ", flag, ": ", arg);
*(Int8_t *)dest = parsed.value;
} else if (type == &Byte$info) {
- OptionalByte_t parsed = Byte$parse(Text$from_str(arg), NULL);
+ OptionalByte_t parsed = Byte$parse(Text$from_str(arg), NONE_INT, NULL);
if (!parsed.has_value) print_err("Could not parse argument for ", flag, ": ", arg);
*(Byte_t *)dest = parsed.value;
} else if (type == &Bool$info) {
@@ -322,7 +325,8 @@ bool pop_cli_flag(List_t *args, char short_flag, const char *flag, void *dest, c
// Case: --flag values...
if (i + 1 >= (int64_t)args->length) print_err("No value provided for flag: ", flag);
List_t values = List$slice(*args, I(i + 2), I(-1));
- *args = parse_arg_list(values, flag, dest, type, false);
+ List_t remaining_args = parse_arg_list(values, flag, dest, type, false);
+ *args = List$concat(List$to(*args, I(i)), remaining_args, sizeof(const char *));
return true;
} else if (starts_with(arg + 2, flag) && arg[2 + strlen(flag)] == '=') {
// Case: --flag=...
@@ -338,7 +342,8 @@ bool pop_cli_flag(List_t *args, char short_flag, const char *flag, void *dest, c
} else {
values = List(arg_value);
}
- *args = parse_arg_list(values, flag, dest, type, false);
+ List_t remaining_args = parse_arg_list(values, flag, dest, type, false);
+ *args = List$concat(List$to(*args, I(i)), remaining_args, sizeof(const char *));
return true;
}
} else if (short_flag && arg[0] == '-' && arg[1] != '-' && strchr(arg + 1, short_flag)) {
@@ -397,7 +402,7 @@ bool pop_cli_flag(List_t *args, char short_flag, const char *flag, void *dest, c
List_t texts = Text$split(Text$from_str(arg_value), Text(","));
values = EMPTY_LIST;
for (int64_t j = 0; j < (int64_t)texts.length; j++)
- List$insert_value(&texts, Text$as_c_string(*(Text_t *)(texts.data + j * texts.stride)), I(0),
+ List$insert_value(&values, Text$as_c_string(*(Text_t *)(texts.data + j * texts.stride)), I(0),
sizeof(const char *));
} else {
// Case: -fVALUE
diff --git a/src/stdlib/datatypes.h b/src/stdlib/datatypes.h
index c177a0a5..7c829cac 100644
--- a/src/stdlib/datatypes.h
+++ b/src/stdlib/datatypes.h
@@ -86,13 +86,18 @@ typedef struct table_s {
struct table_s *fallback;
} Table_t;
-typedef struct Empty$$struct {
-} Empty$$type;
+typedef struct Present$$struct {
+} Present$$type;
+
+#define PRESENT_STRUCT ((Present$$type){})
typedef struct {
+ Present$$type value;
bool has_value;
- Empty$$type value;
-} $OptionalEmpty$$type;
+} $OptionalPresent$$type;
+
+#define NONE_PRESENT_STRUCT (($OptionalPresent$$type){.has_value = false})
+#define OPTIONAL_PRESENT_STRUCT (($OptionalPresent$$type){.has_value = true})
typedef struct {
void *fn, *userdata;
@@ -123,15 +128,82 @@ typedef struct Text_s {
};
} Text_t;
-typedef enum PathEnum { PATHTYPE_NONE, PATHTYPE_RELATIVE, PATHTYPE_ABSOLUTE, PATHTYPE_HOME } PathType_t;
-#define OptionalPathType_t PathType_t
+typedef struct Path$AbsolutePath$$struct {
+ List_t components;
+} Path$AbsolutePath$$type;
+
+typedef struct {
+ Path$AbsolutePath$$type value;
+ bool has_value;
+} $OptionalPath$AbsolutePath$$type;
+
+typedef struct Path$RelativePath$$struct {
+ List_t components;
+} Path$RelativePath$$type;
typedef struct {
- PathType_t type;
+ Path$RelativePath$$type value;
+ bool has_value;
+} $OptionalPath$RelativePath$$type;
+
+typedef struct Path$HomePath$$struct {
List_t components;
+} Path$HomePath$$type;
+
+typedef struct {
+ Path$HomePath$$type value;
+ bool has_value;
+} $OptionalPath$HomePath$$type;
+
+#define Path$tagged$AbsolutePath(comps) ((Path_t){.$tag = Path$tag$AbsolutePath, .AbsolutePath.components = comps})
+#define Path$tagged$RelativePath(comps) ((Path_t){.$tag = Path$tag$RelativePath, .RelativePath.components = comps})
+#define Path$tagged$HomePath(comps) ((Path_t){.$tag = Path$tag$HomePath, .HomePath.components = comps})
+
+typedef struct {
+ enum { Path$tag$none, Path$tag$AbsolutePath, Path$tag$RelativePath, Path$tag$HomePath } $tag;
+ union {
+ Path$RelativePath$$type RelativePath;
+ Path$AbsolutePath$$type AbsolutePath;
+ Path$HomePath$$type HomePath;
+ List_t components;
+ };
} 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/floatX.c.h b/src/stdlib/floatX.c.h
index 0961631c..54477ee3 100644
--- a/src/stdlib/floatX.c.h
+++ b/src/stdlib/floatX.c.h
@@ -67,6 +67,7 @@ PUREFUNC int32_t NAMESPACED(compare)(const void *x, const void *y, const TypeInf
#elif FLOATX_C_H__BITS == 32
public
PUREFUNC Text_t NAMESPACED(value_as_text)(FLOAT_T x) { return Float64$value_as_text((double)x); }
+public
PUREFUNC Text_t NAMESPACED(as_text)(const void *x, bool colorize, const TypeInfo_t *info) {
(void)info;
if (!x) return Text(TYPE_STR);
diff --git a/src/stdlib/intX.c.h b/src/stdlib/intX.c.h
index 0e665591..0910c7f1 100644
--- a/src/stdlib/intX.c.h
+++ b/src/stdlib/intX.c.h
@@ -188,8 +188,8 @@ Closure_t NAMESPACED(onward)(INT_T first, INT_T step) {
return (Closure_t){.fn = _next_int, .userdata = range};
}
public
-PUREFUNC OPT_T NAMESPACED(parse)(Text_t text, Text_t *remainder) {
- OptionalInt_t full_int = Int$parse(text, remainder);
+PUREFUNC OPT_T NAMESPACED(parse)(Text_t text, OptionalInt_t base, Text_t *remainder) {
+ OptionalInt_t full_int = Int$parse(text, base, remainder);
if (full_int.small == 0L) return (OPT_T){.has_value = false};
if (Int$compare_value(full_int, I(NAMESPACED(min))) < 0) {
return (OPT_T){.has_value = false};
diff --git a/src/stdlib/intX.h b/src/stdlib/intX.h
index 03aa7247..4d8f8e3d 100644
--- a/src/stdlib/intX.h
+++ b/src/stdlib/intX.h
@@ -46,7 +46,7 @@ List_t NAMESPACED(bits)(INTX_T x);
bool NAMESPACED(get_bit)(INTX_T x, Int_t bit_index);
Closure_t NAMESPACED(to)(INTX_T first, INTX_T last, OPT_T step);
Closure_t NAMESPACED(onward)(INTX_T first, INTX_T step);
-PUREFUNC OPT_T NAMESPACED(parse)(Text_t text, Text_t *remainder);
+PUREFUNC OPT_T NAMESPACED(parse)(Text_t text, OptionalInt_t base, Text_t *remainder);
CONSTFUNC bool NAMESPACED(is_between)(const INTX_T x, const INTX_T low, const INTX_T high);
CONSTFUNC INTX_T NAMESPACED(clamped)(INTX_T x, INTX_T min, INTX_T max);
MACROLIKE CONSTFUNC INTX_T NAMESPACED(from_byte)(Byte_t b) { return (INTX_T)b; }
diff --git a/src/stdlib/memory.c b/src/stdlib/memory.c
index 2ae47c36..fd396463 100644
--- a/src/stdlib/memory.c
+++ b/src/stdlib/memory.c
@@ -17,7 +17,7 @@ public
Text_t Memory$as_text(const void *p, bool colorize, const TypeInfo_t *info) {
(void)info;
if (!p) return Text("Memory");
- Text_t text = Text$from_str(String("Memory<", *(void **)p, ">"));
+ Text_t text = Text$from_str(String("Memory<", (void *)p, ">"));
return colorize ? Texts(Text("\x1b[0;34;1m"), text, Text("\x1b[m")) : text;
}
diff --git a/src/stdlib/metamethods.c b/src/stdlib/metamethods.c
index 3eff2dd3..70b8e4e1 100644
--- a/src/stdlib/metamethods.c
+++ b/src/stdlib/metamethods.c
@@ -85,12 +85,6 @@ void generic_deserialize(List_t bytes, void *outval, const TypeInfo_t *type) {
fclose(input);
}
-public
-int generic_print(const void *obj, bool colorize, const TypeInfo_t *type) {
- Text_t text = generic_as_text(obj, colorize, type);
- return Text$print(stdout, text) + fputc('\n', stdout);
-}
-
__attribute__((noreturn)) public
void cannot_serialize(const void *obj, FILE *out, Table_t *pointers, const TypeInfo_t *type) {
(void)obj, (void)out, (void)pointers;
diff --git a/src/stdlib/metamethods.h b/src/stdlib/metamethods.h
index 05d91c5c..7db041e7 100644
--- a/src/stdlib/metamethods.h
+++ b/src/stdlib/metamethods.h
@@ -16,6 +16,5 @@ void _serialize(const void *obj, FILE *out, Table_t *pointers, const TypeInfo_t
List_t generic_serialize(const void *x, const TypeInfo_t *type);
void _deserialize(FILE *input, void *outval, List_t *pointers, const TypeInfo_t *type);
void generic_deserialize(List_t bytes, void *outval, const TypeInfo_t *type);
-int generic_print(const void *obj, bool colorize, const TypeInfo_t *type);
void cannot_serialize(const void *, FILE *, Table_t *, const TypeInfo_t *type);
void cannot_deserialize(FILE *, void *, List_t *, const TypeInfo_t *type);
diff --git a/src/stdlib/optionals.h b/src/stdlib/optionals.h
index d067ec94..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){.type = PATHTYPE_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 810f98b1..ed8383fd 100644
--- a/src/stdlib/paths.c
+++ b/src/stdlib/paths.c
@@ -29,11 +29,8 @@
#include "types.h"
#include "util.h"
-// Use inline version of the siphash code for performance:
-#include "siphash-internals.h"
-
-static const Path_t HOME_PATH = {.type = PATHTYPE_HOME}, ROOT_PATH = {.type = PATHTYPE_ABSOLUTE},
- CURDIR_PATH = {.type = PATHTYPE_RELATIVE};
+static const Path_t HOME_PATH = Path$tagged$HomePath(EMPTY_LIST), ROOT_PATH = Path$tagged$AbsolutePath(EMPTY_LIST),
+ CURDIR_PATH = Path$tagged$RelativePath(EMPTY_LIST);
static void clean_components(List_t *components) {
for (int64_t i = 0; i < (int64_t)components->length;) {
@@ -62,40 +59,41 @@ Path_t Path$from_str(const char *str) {
if (strchr(str, ';') != NULL) fail("Path has illegal character (semicolon): ", str);
- Path_t result = {.components = {}};
+ Path_t result = {};
if (str[0] == '/') {
- result.type = PATHTYPE_ABSOLUTE;
+ result.$tag = Path$tag$AbsolutePath;
str += 1;
} else if (str[0] == '~' && str[1] == '/') {
- result.type = PATHTYPE_HOME;
+ result.$tag = Path$tag$HomePath;
str += 2;
} else if (str[0] == '.' && str[1] == '/') {
- result.type = PATHTYPE_RELATIVE;
+ result.$tag = Path$tag$RelativePath;
str += 2;
} else {
- result.type = PATHTYPE_RELATIVE;
+ result.$tag = Path$tag$RelativePath;
}
+ List_t components = EMPTY_LIST;
while (str && *str) {
size_t component_len = strcspn(str, "/");
if (component_len > 0) {
if (component_len == 1 && str[0] == '.') {
// ignore /./
- } else if (component_len == 2 && strncmp(str, "..", 2) == 0 && result.components.length > 1
+ } else if (component_len == 2 && strncmp(str, "..", 2) == 0 && components.length > 1
&& !Text$equal_values(
Text(".."),
- *(Text_t *)(result.components.data
- + result.components.stride * ((int64_t)result.components.length - 1)))) {
+ *(Text_t *)(components.data + components.stride * ((int64_t)components.length - 1)))) {
// Pop off /foo/baz/.. -> /foo
- List$remove_at(&result.components, I((int64_t)result.components.length), I(1), sizeof(Text_t));
+ List$remove_at(&components, I((int64_t)components.length), I(1), sizeof(Text_t));
} else {
Text_t component = Text$from_strn(str, component_len);
- List$insert_value(&result.components, component, I(0), sizeof(Text_t));
+ List$insert_value(&components, component, I(0), sizeof(Text_t));
}
str += component_len;
}
str += strspn(str, "/");
}
+ result.components = components;
return result;
}
@@ -104,12 +102,12 @@ Path_t Path$from_text(Text_t text) { return Path$from_str(Text$as_c_string(text)
public
Path_t Path$expand_home(Path_t path) {
- if (path.type == PATHTYPE_HOME) {
+ if (path.$tag == Path$tag$HomePath) {
Path_t pwd = Path$from_str(getenv("HOME"));
- List_t components = List$concat(pwd.components, path.components, sizeof(Text_t));
- assert(components.length == path.components.length + pwd.components.length);
+ List_t components = List$concat(pwd.AbsolutePath.components, path.HomePath.components, sizeof(Text_t));
+ assert(components.length == path.HomePath.components.length + pwd.AbsolutePath.components.length);
clean_components(&components);
- path = (Path_t){.type = PATHTYPE_ABSOLUTE, .components = components};
+ path = Path$tagged$AbsolutePath(components);
}
return path;
}
@@ -120,7 +118,7 @@ Path_t Path$_concat(int n, Path_t items[n]) {
Path_t result = items[0];
LIST_INCREF(result.components);
for (int i = 1; i < n; i++) {
- if (items[i].type != PATHTYPE_RELATIVE)
+ if (items[i].$tag != Path$tag$RelativePath)
fail("Cannot concatenate an absolute or home-based path onto another path: (", items[i], ")");
List$insert_all(&result.components, items[i].components, I(0), sizeof(Text_t));
}
@@ -130,10 +128,14 @@ Path_t Path$_concat(int n, Path_t items[n]) {
public
Path_t Path$resolved(Path_t path, Path_t relative_to) {
- if (path.type == PATHTYPE_RELATIVE
- && !(relative_to.type == PATHTYPE_RELATIVE && relative_to.components.length == 0)) {
- Path_t result = {.type = relative_to.type};
- result.components = relative_to.components;
+ 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,
+ };
LIST_INCREF(result.components);
List$insert_all(&result.components, path.components, I(0), sizeof(Text_t));
clean_components(&result.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.type != relative_to.type)
- 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 = {.type = PATHTYPE_RELATIVE};
+ 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,43 +450,53 @@ 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) {
+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);
int status = mkdir(c_path, (mode_t)permissions);
- if (status != 0 && errno != EEXIST) fail("Could not create directory: ", c_path, " (", strerror(errno), ")");
+ if (status != 0) {
+ if (recursive && errno == ENOENT) {
+ Path$create_directory(Path$parent(path), permissions, recursive);
+ goto retry;
+ } else if (errno != EEXIST) {
+ 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;
@@ -503,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);
@@ -524,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);
@@ -545,30 +563,30 @@ 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) {
- if (path.type == PATHTYPE_ABSOLUTE && path.components.length == 0) {
- return path;
+OptionalPath_t Path$parent(Path_t path) {
+ if (path.$tag == Path$tag$AbsolutePath && path.components.length == 0) {
+ 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)),
Text(".."))) {
- return (Path_t){.type = path.type, .components = List$slice(path.components, I(1), I(-2))};
+ return (Path_t){.$tag = path.$tag, .components = List$slice(path.components, I(1), I(-2))};
} else {
- Path_t result = {.type = path.type, .components = path.components};
+ Path_t result = {.$tag = path.$tag, .components = path.components};
LIST_INCREF(result.components);
List$insert_value(&result.components, Text(".."), I(0), sizeof(Text_t));
return result;
@@ -579,8 +597,8 @@ public
PUREFUNC Text_t Path$base_name(Path_t path) {
if (path.components.length >= 1)
return *(Text_t *)(path.components.data + path.components.stride * ((int64_t)path.components.length - 1));
- else if (path.type == PATHTYPE_HOME) return Text("~");
- else if (path.type == PATHTYPE_RELATIVE) return Text(".");
+ else if (path.$tag == Path$tag$HomePath) return Text("~");
+ else if (path.$tag == Path$tag$RelativePath) return Text(".");
else return EMPTY_TEXT;
}
@@ -610,7 +628,7 @@ public
Path_t Path$child(Path_t path, Text_t name) {
if (Text$has(name, Text("/")) || Text$has(name, Text(";"))) fail("Path name has invalid characters: ", name);
Path_t result = {
- .type = path.type,
+ .$tag = path.$tag,
.components = path.components,
};
LIST_INCREF(result.components);
@@ -623,14 +641,13 @@ 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 = {
- .type = path.type,
+ .$tag = path.$tag,
.components = path.components,
};
LIST_INCREF(result.components);
@@ -704,6 +721,28 @@ OptionalClosure_t Path$by_line(Path_t path) {
}
public
+OptionalList_t Path$lines(Path_t path) {
+ const char *path_str = Path$as_c_string(path);
+ FILE *f = fopen(path_str, "r");
+ if (f == NULL) {
+ if (errno == EMFILE || errno == ENFILE) {
+ // If we hit file handle limits, run GC collection to try to clean up any lingering file handles that will
+ // be closed by GC finalizers.
+ GC_gcollect();
+ f = fopen(path_str, "r");
+ }
+ }
+
+ if (f == NULL) return NONE_LIST;
+
+ List_t lines = EMPTY_LIST;
+ for (OptionalText_t line; (line = _next_line(&f)).tag != TEXT_NONE;) {
+ List$insert(&lines, &line, I(0), sizeof(line));
+ }
+ return lines;
+}
+
+public
List_t Path$glob(Path_t path) {
glob_t glob_result;
int status = glob(Path$as_c_string(path), GLOB_BRACE | GLOB_TILDE, NULL, &glob_result);
@@ -730,55 +769,19 @@ Path_t Path$current_dir(void) {
}
public
-PUREFUNC uint64_t Path$hash(const void *obj, const TypeInfo_t *type) {
- (void)type;
- Path_t *path = (Path_t *)obj;
- siphash sh;
- siphashinit(&sh, (uint64_t)path->type);
- for (int64_t i = 0; i < (int64_t)path->components.length; i++) {
- uint64_t item_hash = Text$hash(path->components.data + i * path->components.stride, &Text$info);
- siphashadd64bits(&sh, item_hash);
- }
- return siphashfinish_last_part(&sh, (uint64_t)path->components.length);
-}
-
-public
-PUREFUNC int32_t Path$compare(const void *va, const void *vb, const TypeInfo_t *type) {
- (void)type;
- Path_t *a = (Path_t *)va, *b = (Path_t *)vb;
- int diff = ((int)a->type - (int)b->type);
- if (diff != 0) return diff;
- return List$compare(&a->components, &b->components, List$info(&Text$info));
-}
-
-public
-PUREFUNC bool Path$equal(const void *va, const void *vb, const TypeInfo_t *type) {
- (void)type;
- Path_t *a = (Path_t *)va, *b = (Path_t *)vb;
- if (a->type != b->type) return false;
- return List$equal(&a->components, &b->components, List$info(&Text$info));
-}
-
-public
-PUREFUNC bool Path$equal_values(Path_t a, Path_t b) {
- if (a.type != b.type) return false;
- return List$equal(&a.components, &b.components, List$info(&Text$info));
-}
-
-public
int Path$print(FILE *f, Path_t path) {
if (path.components.length == 0) {
- if (path.type == PATHTYPE_ABSOLUTE) return fputs("/", f);
- else if (path.type == PATHTYPE_RELATIVE) return fputs(".", f);
- else if (path.type == PATHTYPE_HOME) return fputs("~", f);
+ if (path.$tag == Path$tag$AbsolutePath) return fputs("/", f);
+ else if (path.$tag == Path$tag$RelativePath) return fputs(".", f);
+ else if (path.$tag == Path$tag$HomePath) return fputs("~", f);
}
int n = 0;
- if (path.type == PATHTYPE_ABSOLUTE) {
+ if (path.$tag == Path$tag$AbsolutePath) {
n += fputc('/', f);
- } else if (path.type == PATHTYPE_HOME) {
+ } else if (path.$tag == Path$tag$HomePath) {
n += fputs("~/", f);
- } else if (path.type == PATHTYPE_RELATIVE) {
+ } else if (path.$tag == Path$tag$RelativePath) {
if (!Text$equal_values(*(Text_t *)path.components.data, Text(".."))) n += fputs("./", f);
}
@@ -799,9 +802,9 @@ Text_t Path$as_text(const void *obj, bool color, const TypeInfo_t *type) {
if (!obj) return Text("Path");
Path_t *path = (Path_t *)obj;
Text_t text = Text$join(Text("/"), path->components);
- if (path->type == PATHTYPE_HOME) text = Text$concat(path->components.length > 0 ? Text("~/") : Text("~"), text);
- else if (path->type == PATHTYPE_ABSOLUTE) text = Text$concat(Text("/"), text);
- else if (path->type == PATHTYPE_RELATIVE
+ if (path->$tag == Path$tag$HomePath) text = Text$concat(path->components.length > 0 ? Text("~/") : Text("~"), text);
+ else if (path->$tag == Path$tag$AbsolutePath) text = Text$concat(Text("/"), text);
+ else if (path->$tag == Path$tag$RelativePath
&& (path->components.length == 0 || !Text$equal_values(*(Text_t *)(path->components.data), Text(".."))))
text = Text$concat(path->components.length > 0 ? Text("./") : Text("."), text);
@@ -811,52 +814,80 @@ Text_t Path$as_text(const void *obj, bool color, const TypeInfo_t *type) {
}
public
-CONSTFUNC bool Path$is_none(const void *obj, const TypeInfo_t *type) {
- (void)type;
- return ((Path_t *)obj)->type == PATHTYPE_NONE;
-}
+const TypeInfo_t Path$AbsolutePath$$info = {
+ .size = sizeof(Path$AbsolutePath$$type),
+ .align = __alignof__(Path$AbsolutePath$$type),
+ .metamethods = Struct$metamethods,
+ .tag = StructInfo,
+ .StructInfo =
+ {
+ .name = "AbsolutePath",
+ .num_fields = 1,
+ .fields = (NamedType_t[1]){{
+ .name = "components",
+ .type = List$info(&Text$info),
+ }},
+ },
+};
public
-void Path$serialize(const void *obj, FILE *out, Table_t *pointers, const TypeInfo_t *type) {
- (void)type;
- Path_t *path = (Path_t *)obj;
- fputc((int)path->type, out);
- List$serialize(&path->components, out, pointers, List$info(&Text$info));
-}
+const TypeInfo_t Path$RelativePath$$info = {
+ .size = sizeof(Path$RelativePath$$type),
+ .align = __alignof__(Path$RelativePath$$type),
+ .metamethods = Struct$metamethods,
+ .tag = StructInfo,
+ .StructInfo =
+ {
+ .name = "RelativePath",
+ .num_fields = 1,
+ .fields = (NamedType_t[1]){{
+ .name = "components",
+ .type = List$info(&Text$info),
+ }},
+ },
+};
public
-void Path$deserialize(FILE *in, void *obj, List_t *pointers, const TypeInfo_t *type) {
- (void)type;
- Path_t path = {};
- path.type = fgetc(in);
- List$deserialize(in, &path.components, pointers, List$info(&Text$info));
- *(Path_t *)obj = path;
-}
-
-public
-const TypeInfo_t Path$info = {.size = sizeof(Path_t),
- .align = __alignof__(Path_t),
- .tag = OpaqueInfo,
- .metamethods = {
- .as_text = Path$as_text,
- .hash = Path$hash,
- .compare = Path$compare,
- .equal = Path$equal,
- .is_none = Path$is_none,
- .serialize = Path$serialize,
- .deserialize = Path$deserialize,
- }};
-
-public
-const TypeInfo_t PathType$info = {
- .size = sizeof(PathType_t),
- .align = __alignof__(PathType_t),
- .metamethods = PackedDataEnum$metamethods,
+const TypeInfo_t Path$HomePath$$info = {
+ .size = sizeof(Path$HomePath$$type),
+ .align = __alignof__(Path$HomePath$$type),
+ .metamethods = Struct$metamethods,
+ .tag = StructInfo,
+ .StructInfo =
+ {
+ .name = "HomePath",
+ .num_fields = 1,
+ .fields = (NamedType_t[1]){{
+ .name = "components",
+ .type = List$info(&Text$info),
+ }},
+ },
+};
+
+public
+const TypeInfo_t Path$info = {
+ .size = sizeof(Path_t),
+ .align = __alignof__(Path_t),
.tag = EnumInfo,
.EnumInfo =
{
- .name = "PathType",
+ .name = "Path",
.num_tags = 3,
- .tags = ((NamedType_t[3]){{.name = "Relative"}, {.name = "Absolute"}, {.name = "Home"}}),
+ .tags =
+ (NamedType_t[3]){
+ {.name = "AbsolutePath", &Path$AbsolutePath$$info},
+ {.name = "RelativePath", &Path$RelativePath$$info},
+ {.name = "HomePath", &Path$HomePath$$info},
+ },
+ },
+ .metamethods =
+ {
+ .as_text = Path$as_text,
+ .compare = Enum$compare,
+ .equal = Enum$equal,
+ .hash = Enum$hash,
+ .is_none = Enum$is_none,
+ .serialize = Enum$serialize,
+ .deserialize = Enum$deserialize,
},
};
diff --git a/src/stdlib/paths.h b/src/stdlib/paths.h
index ce6de1c8..881a3c78 100644
--- a/src/stdlib/paths.h
+++ b/src/stdlib/paths.h
@@ -11,7 +11,11 @@
Path_t Path$from_str(const char *str);
Path_t Path$from_text(Text_t text);
+// This function is defined as an extern in `src/stdlib/print.h`
// int Path$print(FILE *f, Path_t path);
+// UNSAFE: this works because each type of path has a .components in the same place
+#define Path$components(path) ((path).components)
+// END UNSAFE
const char *Path$as_c_string(Path_t path);
#define Path(str) Path$from_str(str)
Path_t Path$_concat(int n, Path_t items[n]);
@@ -31,24 +35,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);
+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);
@@ -57,6 +61,7 @@ Path_t Path$sibling(Path_t path, Text_t name);
Path_t Path$with_extension(Path_t path, Text_t extension, bool replace);
Path_t Path$current_dir(void);
Closure_t Path$by_line(Path_t path);
+OptionalList_t Path$lines(Path_t path);
List_t Path$glob(Path_t path);
uint64_t Path$hash(const void *obj, const TypeInfo_t *);
@@ -68,5 +73,7 @@ bool Path$is_none(const void *obj, const TypeInfo_t *type);
void Path$serialize(const void *obj, FILE *out, Table_t *pointers, const TypeInfo_t *type);
void Path$deserialize(FILE *in, void *obj, List_t *pointers, const TypeInfo_t *type);
+extern const TypeInfo_t Path$AbsolutePath$$info;
+extern const TypeInfo_t Path$RelativePath$$info;
+extern const TypeInfo_t Path$HomePath$$info;
extern const TypeInfo_t Path$info;
-extern const TypeInfo_t PathType$info;
diff --git a/src/stdlib/print.c b/src/stdlib/print.c
index ef570f94..7da1343f 100644
--- a/src/stdlib/print.c
+++ b/src/stdlib/print.c
@@ -43,6 +43,7 @@ int _print_hex(FILE *f, hex_format_t hex) {
int printed = 0;
if (!hex.no_prefix) printed += fputs("0x", f);
if (hex.digits > 0) {
+ hex.digits -= (hex.n == 0); // Don't need a leading zero for a zero
for (uint64_t n = hex.n; n > 0 && hex.digits > 0; n /= 16) {
hex.digits -= 1;
}
@@ -68,6 +69,7 @@ int _print_oct(FILE *f, oct_format_t oct) {
int printed = 0;
if (!oct.no_prefix) printed += fputs("0o", f);
if (oct.digits > 0) {
+ oct.digits -= (oct.n == 0); // Don't need a leading zero for a zero
for (uint64_t n = oct.n; n > 0 && oct.digits > 0; n /= 8)
oct.digits -= 1;
for (; oct.digits > 0; oct.digits -= 1)
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 <err.h>
+#include <gc.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <sys/param.h>
+
+#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/stacktrace.c b/src/stdlib/stacktrace.c
index b21d0cbc..ea939f62 100644
--- a/src/stdlib/stacktrace.c
+++ b/src/stdlib/stacktrace.c
@@ -98,7 +98,7 @@ void print_stacktrace(FILE *out, int offset) {
cwd[cwd_len++] = '/';
cwd[cwd_len] = '\0';
- const char *install_dir = String(TOMO_PATH, "/lib/tomo_" TOMO_VERSION "/");
+ const char *install_dir = String(TOMO_PATH, "/lib/tomo@" TOMO_VERSION "/");
static void *stack[1024];
int64_t size = (int64_t)backtrace(stack, sizeof(stack) / sizeof(stack[0]));
diff --git a/src/stdlib/stdlib.c b/src/stdlib/stdlib.c
index c3ea1d36..f4e6d678 100644
--- a/src/stdlib/stdlib.c
+++ b/src/stdlib/stdlib.c
@@ -5,6 +5,7 @@
#include <fcntl.h>
#include <gc.h>
#include <locale.h>
+#include <math.h>
#include <signal.h>
#include <stdbool.h>
#include <stdint.h>
@@ -72,6 +73,7 @@ void tomo_init(void) {
sigemptyset(&sigact.sa_mask);
sigact.sa_flags = 0;
sigaction(SIGILL, &sigact, (struct sigaction *)NULL);
+ atexit(tomo_cleanup);
}
public
@@ -145,7 +147,7 @@ void say(Text_t text, bool newline) {
public
_Noreturn void tomo_exit(Text_t text, int32_t status) {
if (text.length > 0) print(text);
- _exit(status);
+ exit(status);
}
public
@@ -204,11 +206,16 @@ cleanup:
}
public
-void sleep_float64(double seconds) {
+void sleep_seconds(double seconds) {
+ if (seconds < 0) fail("Cannot sleep for a negative amount of time: ", seconds);
+ else if (isnan(seconds)) fail("Cannot sleep for a time that is NaN");
struct timespec ts;
ts.tv_sec = (time_t)seconds;
ts.tv_nsec = (long)((seconds - (double)ts.tv_sec) * 1e9);
- nanosleep(&ts, NULL);
+ while (nanosleep(&ts, NULL) != 0) {
+ if (errno == EINTR) continue;
+ fail("Failed to sleep for the requested time (", strerror(errno), ")");
+ }
}
public
@@ -218,4 +225,37 @@ OptionalText_t getenv_text(Text_t name) {
}
public
-void setenv_text(Text_t name, Text_t value) { setenv(Text$as_c_string(name), Text$as_c_string(value), 1); }
+void setenv_text(Text_t name, OptionalText_t value) {
+ int status;
+ if (value.tag == TEXT_NONE) {
+ status = unsetenv(Text$as_c_string(name));
+ } else {
+ status = setenv(Text$as_c_string(name), Text$as_c_string(value), 1);
+ }
+ if (status != 0) {
+ if (errno == EINVAL) fail("Invalid environment variable name: ", Text$quoted(name, false, Text("\"")));
+ else fail("Failed to set environment variable (", strerror(errno));
+ }
+}
+
+typedef struct cleanup_s {
+ Closure_t cleanup_fn;
+ struct cleanup_s *next;
+} cleanup_t;
+
+static cleanup_t *cleanups = NULL;
+
+public
+void tomo_at_cleanup(Closure_t fn) { cleanups = new (cleanup_t, .cleanup_fn = fn, .next = cleanups); }
+
+public
+void tomo_cleanup(void) {
+ while (cleanups) {
+ // NOTE: we *must* remove the cleanup function from the stack before calling it,
+ // otherwise it will cause an infinite loop if the cleanup function fails or exits.
+ void (*run_cleanup)(void *) = cleanups->cleanup_fn.fn;
+ void *userdata = cleanups->cleanup_fn.userdata;
+ cleanups = cleanups->next;
+ run_cleanup(userdata);
+ }
+}
diff --git a/src/stdlib/stdlib.h b/src/stdlib/stdlib.h
index e52b5cd1..3afe3529 100644
--- a/src/stdlib/stdlib.h
+++ b/src/stdlib/stdlib.h
@@ -16,9 +16,12 @@ extern bool USE_COLOR;
extern Text_t TOMO_VERSION_TEXT;
void tomo_init(void);
+void tomo_at_cleanup(Closure_t fn);
+void tomo_cleanup(void);
#define fail(...) \
({ \
+ tomo_cleanup(); \
fflush(stdout); \
if (USE_COLOR) fputs("\x1b[31;7m ==================== ERROR ==================== \033[m\n\n", stderr); \
else fputs("==================== ERROR ====================\n\n", stderr); \
@@ -30,11 +33,12 @@ void tomo_init(void);
else fputs("\n", stderr); \
fflush(stderr); \
raise(SIGABRT); \
- _exit(1); \
+ exit(1); \
})
#define fail_source(filename, start, end, ...) \
({ \
+ tomo_cleanup(); \
fflush(stdout); \
if (USE_COLOR) fputs("\x1b[31;7m ==================== ERROR ==================== \n\n\x1b[0;1m", stderr); \
else fputs("==================== ERROR ====================\n\n", stderr); \
@@ -50,7 +54,7 @@ void tomo_init(void);
if (USE_COLOR) fputs("\x1b[m", stderr); \
fflush(stderr); \
raise(SIGABRT); \
- _exit(1); \
+ exit(1); \
})
_Noreturn void fail_text(Text_t message);
@@ -75,6 +79,6 @@ Text_t ask(Text_t prompt, bool bold, bool force_tty);
_Noreturn void tomo_exit(Text_t text, int32_t status);
Closure_t spawn(Closure_t fn);
-void sleep_num(double seconds);
+void sleep_seconds(double seconds);
OptionalText_t getenv_text(Text_t name);
void setenv_text(Text_t name, Text_t value);
diff --git a/src/stdlib/structs.c b/src/stdlib/structs.c
index 89b5581b..da8f1461 100644
--- a/src/stdlib/structs.c
+++ b/src/stdlib/structs.c
@@ -215,14 +215,3 @@ void Struct$deserialize(FILE *in, void *outval, List_t *pointers, const TypeInfo
}
}
}
-
-public
-const TypeInfo_t Empty$$info = {.size = 0,
- .align = 0,
- .tag = StructInfo,
- .metamethods = Struct$metamethods,
- .StructInfo.name = "Empty",
- .StructInfo.num_fields = 0};
-
-public
-const Empty$$type EMPTY = {};
diff --git a/src/stdlib/structs.h b/src/stdlib/structs.h
index 83904377..a582e9fb 100644
--- a/src/stdlib/structs.h
+++ b/src/stdlib/structs.h
@@ -15,8 +15,6 @@ PUREFUNC bool PackedData$equal(const void *x, const void *y, const TypeInfo_t *t
PUREFUNC Text_t Struct$as_text(const void *obj, bool colorize, const TypeInfo_t *type);
void Struct$serialize(const void *obj, FILE *out, Table_t *pointers, const TypeInfo_t *type);
void Struct$deserialize(FILE *in, void *outval, List_t *pointers, const TypeInfo_t *type);
-extern const TypeInfo_t Empty$$info;
-extern const Empty$$type EMPTY;
#define Struct$metamethods \
{ \
diff --git a/src/stdlib/tables.c b/src/stdlib/tables.c
index 6e774c53..a801957f 100644
--- a/src/stdlib/tables.c
+++ b/src/stdlib/tables.c
@@ -18,6 +18,7 @@
#include "metamethods.h"
#include "pointers.h"
#include "siphash.h"
+#include "structs.h"
#include "tables.h"
#include "text.h"
#include "types.h"
@@ -757,3 +758,14 @@ void Table$deserialize(FILE *in, void *outval, List_t *pointers, const TypeInfo_
*(Table_t *)outval = t;
}
+
+public
+const TypeInfo_t Present$$info = {.size = 0,
+ .align = 0,
+ .tag = StructInfo,
+ .metamethods = Struct$metamethods,
+ .StructInfo.name = "Present",
+ .StructInfo.num_fields = 0};
+
+public
+const Present$$type PRESENT = {};
diff --git a/src/stdlib/tables.h b/src/stdlib/tables.h
index cc2b3b91..cf1c3625 100644
--- a/src/stdlib/tables.h
+++ b/src/stdlib/tables.h
@@ -130,6 +130,8 @@ void Table$deserialize(FILE *in, void *outval, List_t *pointers, const TypeInfo_
#define Table$length(t) ((t).entries.length)
+extern const TypeInfo_t Present$$info;
+extern const Present$$type PRESENT;
extern const TypeInfo_t CStrToVoidStarTable;
#define Table$metamethods \
diff --git a/src/stdlib/text.c b/src/stdlib/text.c
index f323d88d..b4b27fed 100644
--- a/src/stdlib/text.c
+++ b/src/stdlib/text.c
@@ -763,8 +763,10 @@ static Text_t Text$from_components(List_t graphemes, Table_t unique_clusters) {
public
OptionalText_t Text$from_strn(const char *str, size_t len) {
int64_t ascii_span = 0;
- for (size_t i = 0; i < len && isascii(str[i]); i++)
+ for (size_t i = 0; i < len && isascii(str[i]); i++) {
ascii_span++;
+ if (str[i] == 0) return NONE_TEXT;
+ }
if (ascii_span == (int64_t)len) { // All ASCII
char *copy = GC_MALLOC_ATOMIC(len);
@@ -786,12 +788,15 @@ OptionalText_t Text$from_strn(const char *str, size_t len) {
uint32_t buf[256];
size_t u32_len = sizeof(buf) / sizeof(buf[0]);
uint32_t *u32s = u8_to_u32(pos, (size_t)(next - pos), buf, &u32_len);
+ if (u32s == NULL) return NONE_TEXT;
uint32_t buf2[256];
size_t u32_normlen = sizeof(buf2) / sizeof(buf2[0]);
uint32_t *u32s_normalized = u32_normalize(UNINORM_NFC, u32s, u32_len, buf2, &u32_normlen);
+ if (u32s_normalized == NULL) return NONE_TEXT;
int32_t g = get_synthetic_grapheme(u32s_normalized, (int64_t)u32_normlen);
+ if (g == 0) return NONE_TEXT;
List$insert(&graphemes, &g, I(0), sizeof(int32_t));
Table$get_or_setdefault(&unique_clusters, int32_t, uint8_t, g, (uint8_t)unique_clusters.entries.length,
Table$info(&Int32$info, &Byte$info));
@@ -1057,8 +1062,8 @@ PUREFUNC public int32_t Text$compare(const void *va, const void *vb, const TypeI
bool _matches(TextIter_t *text_state, TextIter_t *target_state, int64_t pos) {
for (int64_t i = 0; i < (int64_t)target_state->stack[0].text.length; i++) {
int32_t text_i = Text$get_grapheme_fast(text_state, pos + i);
- int32_t prefix_i = Text$get_grapheme_fast(target_state, i);
- if (text_i != prefix_i) return false;
+ int32_t target_i = Text$get_grapheme_fast(target_state, i);
+ if (text_i != target_i) return false;
}
return true;
}
@@ -1107,6 +1112,19 @@ static bool _has_grapheme(TextIter_t *text, int32_t g) {
}
public
+OptionalInt_t Text$find(Text_t text, Text_t target, Int_t start) {
+ if (text.length < target.length) return NONE_INT;
+ if (target.length <= 0) return I(1);
+ TextIter_t text_state = NEW_TEXT_ITER_STATE(text), target_state = NEW_TEXT_ITER_STATE(target);
+ for (int64_t i = Int64$from_int(start, false) - 1; i < text.length - target.length + 1; i++) {
+ if (_matches(&text_state, &target_state, i)) {
+ return Int$from_int64(i + 1);
+ }
+ }
+ return NONE_INT;
+}
+
+public
Text_t Text$trim(Text_t text, Text_t to_trim, bool left, bool right) {
int64_t first = 0;
TextIter_t text_state = NEW_TEXT_ITER_STATE(text), trim_state = NEW_TEXT_ITER_STATE(to_trim);
@@ -1135,6 +1153,7 @@ Text_t Text$translate(Text_t text, Table_t translations) {
struct {
Text_t target, replacement;
} *entry = replacement_list.data + r * replacement_list.stride;
+ if (entry->target.length <= 0) continue;
TextIter_t target_state = NEW_TEXT_ITER_STATE(entry->target);
if (_matches(&text_state, &target_state, i)) {
if (i > span_start) result = concat2(result, Text$slice(text, I(span_start + 1), I(i)));
@@ -1156,6 +1175,7 @@ Text_t Text$translate(Text_t text, Table_t translations) {
public
Text_t Text$replace(Text_t text, Text_t target, Text_t replacement) {
+ if (target.length <= 0) return text;
TextIter_t text_state = NEW_TEXT_ITER_STATE(text), target_state = NEW_TEXT_ITER_STATE(target);
Text_t result = EMPTY_TEXT;
int64_t span_start = 0;
@@ -1605,6 +1625,7 @@ static INLINE const char *codepoint_name(ucs4_t c) {
char *found_name = unicode_character_name(c, name);
if (found_name) return found_name;
const uc_block_t *block = uc_block(c);
+ if (!block) return "???";
assert(block);
return String(block->name, "-", hex(c, .no_prefix = true, .uppercase = true));
}
diff --git a/src/stdlib/text.h b/src/stdlib/text.h
index 37ed056a..8c0c43b3 100644
--- a/src/stdlib/text.h
+++ b/src/stdlib/text.h
@@ -50,6 +50,7 @@ static inline Text_t Text_from_text(Text_t t) { return t; }
Text_t Text$_concat(int n, Text_t items[n]);
#define Text$concat(...) Text$_concat(sizeof((Text_t[]){__VA_ARGS__}) / sizeof(Text_t), (Text_t[]){__VA_ARGS__})
#define Texts(...) Text$concat(MAP_LIST(convert_to_text, __VA_ARGS__))
+// This function is defined as an extern in `src/stdlib/print.h`
// int Text$print(FILE *stream, Text_t t);
Text_t Text$slice(Text_t text, Int_t first_int, Int_t last_int);
Text_t Text$from(Text_t text, Int_t first);
@@ -84,6 +85,7 @@ PUREFUNC bool Text$starts_with(Text_t text, Text_t prefix, Text_t *remainder);
PUREFUNC bool Text$ends_with(Text_t text, Text_t suffix, Text_t *remainder);
Text_t Text$without_prefix(Text_t text, Text_t prefix);
Text_t Text$without_suffix(Text_t text, Text_t suffix);
+OptionalInt_t Text$find(Text_t text, Text_t target, Int_t start);
Text_t Text$replace(Text_t text, Text_t target, Text_t replacement);
Text_t Text$translate(Text_t text, Table_t translations);
PUREFUNC bool Text$has(Text_t text, Text_t target);
diff --git a/src/stdlib/tomo.h b/src/stdlib/tomo.h
index b6166890..6a9ba621 100644
--- a/src/stdlib/tomo.h
+++ b/src/stdlib/tomo.h
@@ -26,6 +26,7 @@
#include "pointers.h" // IWYU pragma: export
#include "print.h" // IWYU pragma: export
#include "reals.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