From af1bd79fd91d1a1efde3cf084643f065c61d330a Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sat, 15 Nov 2025 14:10:28 -0500 Subject: Make EMPTY_TEXT into a macro --- src/stdlib/text.c | 7 ------- src/stdlib/text.h | 3 ++- 2 files changed, 2 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/stdlib/text.c b/src/stdlib/text.c index d40e1306..f323d88d 100644 --- a/src/stdlib/text.c +++ b/src/stdlib/text.c @@ -146,13 +146,6 @@ static int32_t num_synthetic_graphemes = 0; static Text_t simple_concatenation(Text_t a, Text_t b); -public -Text_t EMPTY_TEXT = { - .length = 0, - .tag = TEXT_ASCII, - .ascii = 0, -}; - PUREFUNC static bool graphemes_equal(const void *va, const void *vb, const TypeInfo_t *info) { (void)info; ucs4_t *a = *(ucs4_t **)va; diff --git a/src/stdlib/text.h b/src/stdlib/text.h index 821325a9..12b4bc20 100644 --- a/src/stdlib/text.h +++ b/src/stdlib/text.h @@ -27,6 +27,8 @@ typedef struct { #define Text(str) ((Text_t){.length = sizeof(str) - 1, .tag = TEXT_ASCII, .ascii = "" str}) +#define EMPTY_TEXT ((Text_t){.length = 0, .tag = TEXT_ASCII, .ascii = 0}) + static inline Text_t Text_from_str_literal(const char *str) { return (Text_t){.length = strlen(str), .tag = TEXT_ASCII, .ascii = str}; } @@ -121,7 +123,6 @@ MACROLIKE int32_t Text$get_grapheme(Text_t text, int64_t index) { } extern const TypeInfo_t Text$info; -extern Text_t EMPTY_TEXT; #define Text$metamethods \ { \ -- cgit v1.2.3 From 38ea5c4753e13b04e7c41c89bf0b58b5d47cc86d Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sat, 15 Nov 2025 18:12:16 -0500 Subject: Bugfix for int parsing --- src/stdlib/bigint.c | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/stdlib/bigint.c b/src/stdlib/bigint.c index be01a001..46957c2d 100644 --- a/src/stdlib/bigint.c +++ b/src/stdlib/bigint.c @@ -415,25 +415,28 @@ OptionalInt_t Int$parse(Text_t text, Text_t *remainder) { mpz_t i; int result; if (strncmp(str, "0x", 2) == 0) { - const char *end = str + 2 + strspn(str + 2, "0123456789abcdefABCDEF"); + str += 2; + const char *end = str + strspn(str, "0123456789abcdefABCDEF"); if (remainder) *remainder = Text$from_str(end); else if (*end != '\0') return NONE_INT; - result = mpz_init_set_str(i, str + 2, 16); + result = mpz_init_set_str(i, String(string_slice(str, (size_t)(end - str))), 16); } else if (strncmp(str, "0o", 2) == 0) { - const char *end = str + 2 + strspn(str + 2, "01234567"); + str += 2; + const char *end = str + strspn(str, "01234567"); if (remainder) *remainder = Text$from_str(end); else if (*end != '\0') return NONE_INT; - result = mpz_init_set_str(i, str + 2, 8); + result = mpz_init_set_str(i, String(string_slice(str, (size_t)(end - str))), 8); } else if (strncmp(str, "0b", 2) == 0) { - const char *end = str + 2 + strspn(str + 2, "01"); + str += 2; + const char *end = str + strspn(str, "01"); if (remainder) *remainder = Text$from_str(end); else if (*end != '\0') return NONE_INT; - result = mpz_init_set_str(i, str + 2, 2); + result = mpz_init_set_str(i, String(string_slice(str, (size_t)(end - str))), 2); } 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, str, 10); + result = mpz_init_set_str(i, String(string_slice(str, (size_t)(end - str))), 10); } if (result != 0) { if (remainder) *remainder = text; -- cgit v1.2.3 From a1884f7a85cbee5a67cf48c9e7b088fdea8b8b38 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sat, 15 Nov 2025 18:12:57 -0500 Subject: Fix for potential issue with codepoint names --- src/stdlib/text.c | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/stdlib/text.c b/src/stdlib/text.c index f323d88d..8e800c8a 100644 --- a/src/stdlib/text.c +++ b/src/stdlib/text.c @@ -1605,6 +1605,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)); } -- cgit v1.2.3 From 290c72732f21f1cddb3a0f8ec3213e4ec321da14 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sat, 15 Nov 2025 18:13:44 -0500 Subject: Add Path.lines() --- src/environment.c | 1 + src/stdlib/paths.c | 22 ++++++++++++++++++++++ src/stdlib/paths.h | 1 + 3 files changed, 24 insertions(+) (limited to 'src') diff --git a/src/environment.c b/src/environment.c index 96595ac7..8f49f86e 100644 --- a/src/environment.c +++ b/src/environment.c @@ -325,6 +325,7 @@ env_t *global_env(bool source_mapping) { {"is_pipe", "Path$is_pipe", "func(path:Path, follow_symlinks=yes -> Bool)"}, // {"is_socket", "Path$is_socket", "func(path:Path, follow_symlinks=yes -> Bool)"}, // {"is_symlink", "Path$is_symlink", "func(path:Path -> Bool)"}, // + {"lines", "Path$lines", "func(path:Path -> [Text]?)"}, // {"modified", "Path$modified", "func(path:Path, follow_symlinks=yes -> Int64?)"}, // {"owner", "Path$owner", "func(path:Path, follow_symlinks=yes -> Text?)"}, // {"parent", "Path$parent", "func(path:Path -> Path)"}, // diff --git a/src/stdlib/paths.c b/src/stdlib/paths.c index 810f98b1..704421c2 100644 --- a/src/stdlib/paths.c +++ b/src/stdlib/paths.c @@ -703,6 +703,28 @@ OptionalClosure_t Path$by_line(Path_t path) { return (Closure_t){.fn = (void *)_next_line, .userdata = wrapper}; } +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 = {}; + 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; diff --git a/src/stdlib/paths.h b/src/stdlib/paths.h index ce6de1c8..3b1f3ce6 100644 --- a/src/stdlib/paths.h +++ b/src/stdlib/paths.h @@ -57,6 +57,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 *); -- cgit v1.2.3 From 76b434a6be6c05d7d3dae5b77ec8fd886fd715d0 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sat, 15 Nov 2025 18:16:48 -0500 Subject: Bugfix for CLI arg parsing --- src/stdlib/cli.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/stdlib/cli.c b/src/stdlib/cli.c index 22ed9b94..3c474160 100644 --- a/src/stdlib/cli.c +++ b/src/stdlib/cli.c @@ -397,7 +397,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 -- cgit v1.2.3 From d6de03feb280d179efa07e1727f42955b25d733c Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Fri, 21 Nov 2025 22:13:21 -0500 Subject: Fix optional path none checks --- src/compile/optionals.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/compile/optionals.c b/src/compile/optionals.c index b4930d02..ffe16248 100644 --- a/src/compile/optionals.c +++ b/src/compile/optionals.c @@ -92,8 +92,8 @@ Text_t check_none(type_t *t, Text_t value) { // NOTE: these use statement expressions ({...;}) because some compilers // complain about excessive parens around equality comparisons if (t->tag == PointerType || t->tag == FunctionType || t->tag == CStringType) return Texts("(", value, " == NULL)"); - else if (t == PATH_TYPE) return Texts("((", value, ").type.$tag == PATHTYPE_NONE)"); - else if (t == PATH_TYPE_TYPE) return Texts("((", value, ").$tag == PATHTYPE_NONE)"); + else if (t == PATH_TYPE) return Texts("((", value, ").type == PATHTYPE_NONE)"); + else if (t == PATH_TYPE_TYPE) return Texts("((", value, ") == PATHTYPE_NONE)"); else if (t->tag == BigIntType) return Texts("((", value, ").small == 0)"); else if (t->tag == ClosureType) return Texts("((", value, ").fn == NULL)"); else if (t->tag == NumType) -- cgit v1.2.3 From 3c80527f5d788ef80abe5cd3a28aabe168a6d69c Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Fri, 21 Nov 2025 22:19:16 -0500 Subject: Bugfix for optional path CLI args --- src/stdlib/cli.c | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/stdlib/cli.c b/src/stdlib/cli.c index 3c474160..3523296a 100644 --- a/src/stdlib/cli.c +++ b/src/stdlib/cli.c @@ -220,6 +220,7 @@ static List_t parse_arg_list(List_t args, const char *flag, void *dest, const Ty else if (nonnull == &Int16$info) ((OptionalInt16_t *)dest)->has_value = true; else if (nonnull == &Int8$info) ((OptionalInt8_t *)dest)->has_value = true; else if (nonnull == &Byte$info) ((OptionalByte_t *)dest)->has_value = true; + else if (nonnull == &Path$info) return args; else if (nonnull->tag == StructInfo && nonnull != &Path$info) *(bool *)(dest + nonnull->size) = true; else print_err("Unsupported type: ", generic_as_text(NULL, true, nonnull)); return args; -- cgit v1.2.3 From 0c93c3c614aa5b955b40e805226d8609c85d0923 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Fri, 21 Nov 2025 22:20:56 -0500 Subject: Bugfix for empty file lines --- src/stdlib/paths.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/stdlib/paths.c b/src/stdlib/paths.c index 704421c2..22effad7 100644 --- a/src/stdlib/paths.c +++ b/src/stdlib/paths.c @@ -718,7 +718,7 @@ OptionalList_t Path$lines(Path_t path) { if (f == NULL) return NONE_LIST; - List_t lines = {}; + List_t lines = EMPTY_LIST; for (OptionalText_t line; (line = _next_line(&f)).tag != TEXT_NONE;) { List$insert(&lines, &line, I(0), sizeof(line)); } -- cgit v1.2.3 From a529e344c07a064de1391c1a4bf354fe1a95707d Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sat, 22 Nov 2025 14:15:40 -0500 Subject: Fix hex/octal leading zeroes for zero --- src/stdlib/print.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src') 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) -- cgit v1.2.3 From e0706bc707ea6a8be86cee9fde21971cde3d7a42 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sat, 22 Nov 2025 16:34:41 -0500 Subject: Bugfix for infinite loop in text.replace("", ...) with empty string --- src/stdlib/text.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src') diff --git a/src/stdlib/text.c b/src/stdlib/text.c index 8e800c8a..febcafce 100644 --- a/src/stdlib/text.c +++ b/src/stdlib/text.c @@ -1135,6 +1135,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 +1157,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; -- cgit v1.2.3 From 0aeacfbd83b0afe8a8ea654bc1554b8d7d29e9b1 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sat, 22 Nov 2025 16:56:57 -0500 Subject: CLI arg parsing fixes for optional types --- src/stdlib/cli.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/stdlib/cli.c b/src/stdlib/cli.c index 3523296a..08260171 100644 --- a/src/stdlib/cli.c +++ b/src/stdlib/cli.c @@ -215,12 +215,14 @@ 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) + 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; else if (nonnull == &Byte$info) ((OptionalByte_t *)dest)->has_value = true; - else if (nonnull == &Path$info) return args; else if (nonnull->tag == StructInfo && nonnull != &Path$info) *(bool *)(dest + nonnull->size) = true; else print_err("Unsupported type: ", generic_as_text(NULL, true, nonnull)); return args; -- cgit v1.2.3 From bb354d6d3626cdc0c2a1b802a954df244cd1facc Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sat, 22 Nov 2025 19:11:55 -0500 Subject: Fixes for conditional expressions for optional types --- src/ast.h | 1 + src/compile/conditionals.c | 15 ++++++++++----- src/compile/expressions.c | 4 +++- src/stdlib/cli.c | 2 +- src/typecheck.c | 3 ++- 5 files changed, 17 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/ast.h b/src/ast.h index aaa2a993..c01ba68a 100644 --- a/src/ast.h +++ b/src/ast.h @@ -287,6 +287,7 @@ struct ast_s { struct { } Unknown; struct { + struct type_s *type; } None; struct { bool b; diff --git a/src/compile/conditionals.c b/src/compile/conditionals.c index caebdbde..64be29fa 100644 --- a/src/compile/conditionals.c +++ b/src/compile/conditionals.c @@ -126,17 +126,22 @@ Text_t compile_if_expression(env_t *env, ast_t *ast) { } type_t *true_type = get_type(truthy_scope, if_->body); - type_t *false_type = get_type(falsey_scope, if_->else_body); + ast_t *else_body = if_->else_body; + if (else_body && else_body->tag == Block && Match(else_body, Block)->statements + && !Match(else_body, Block)->statements->next) + else_body = Match(else_body, Block)->statements->ast; + if (else_body == NULL || else_body->tag == None) else_body = WrapAST(ast, None, .type = true_type); + type_t *false_type = get_type(falsey_scope, else_body); if (true_type->tag == AbortType || true_type->tag == ReturnType) return Texts("({ ", decl_code, "if (", condition_code, ") ", compile_statement(truthy_scope, if_->body), "\n", - compile(falsey_scope, if_->else_body), "; })"); + compile(falsey_scope, else_body), "; })"); else if (false_type->tag == AbortType || false_type->tag == ReturnType) - return Texts("({ ", decl_code, "if (!(", condition_code, ")) ", compile_statement(falsey_scope, if_->else_body), + return Texts("({ ", decl_code, "if (!(", condition_code, ")) ", compile_statement(falsey_scope, else_body), "\n", compile(truthy_scope, if_->body), "; })"); else if (decl_code.length > 0) return Texts("({ ", decl_code, "(", condition_code, ") ? ", compile(truthy_scope, if_->body), " : ", - compile(falsey_scope, if_->else_body), ";})"); + compile(falsey_scope, else_body), ";})"); else return Texts("((", condition_code, ") ? ", compile(truthy_scope, if_->body), " : ", - compile(falsey_scope, if_->else_body), ")"); + compile(falsey_scope, else_body), ")"); } diff --git a/src/compile/expressions.c b/src/compile/expressions.c index 108bda80..130a267c 100644 --- a/src/compile/expressions.c +++ b/src/compile/expressions.c @@ -66,7 +66,9 @@ Text_t compile_empty(type_t *t) { Text_t compile(env_t *env, ast_t *ast) { switch (ast->tag) { case None: { - code_err(ast, "I can't figure out what this `none`'s type is!"); + type_t *type = Match(ast, None)->type; + if (type == NULL) code_err(ast, "I can't figure out what this `none`'s type is!"); + return compile_none(non_optional(type)); } case Bool: return Match(ast, Bool)->b ? Text("yes") : Text("no"); case Var: { diff --git a/src/stdlib/cli.c b/src/stdlib/cli.c index 08260171..359220e9 100644 --- a/src/stdlib/cli.c +++ b/src/stdlib/cli.c @@ -216,7 +216,7 @@ static List_t parse_arg_list(List_t args, const char *flag, void *dest, const Ty } else { args = parse_arg_list(args, flag, dest, nonnull, allow_dashes); if (nonnull == &Int$info || nonnull == &Path$info || nonnull == &Num$info || nonnull == &Num32$info - || nonnull->tag == TextInfo) + || 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; diff --git a/src/typecheck.c b/src/typecheck.c index 07c3acf9..a0e55a88 100644 --- a/src/typecheck.c +++ b/src/typecheck.c @@ -1376,7 +1376,6 @@ type_t *get_type(env_t *env, ast_t *ast) { case If: { DeclareMatch(if_, ast, If); - if (!if_->else_body) return Type(VoidType); env_t *truthy_scope = env; env_t *falsey_scope = env; @@ -1401,6 +1400,8 @@ type_t *get_type(env_t *env, ast_t *ast) { } type_t *true_t = get_type(truthy_scope, if_->body); + ast_t *else_body = if_->else_body; + if (!else_body) else_body = WrapAST(ast, None, .type = true_t); type_t *false_t = get_type(falsey_scope, if_->else_body); type_t *t_either = type_or_type(true_t, false_t); if (!t_either) -- cgit v1.2.3 From 7869c7447a181316abbdd0e4ff9c4331d56a1984 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sat, 22 Nov 2025 21:47:54 -0500 Subject: Fix command line aliases --- src/compile/cli.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/compile/cli.c b/src/compile/cli.c index e3d2329f..e5756521 100644 --- a/src/compile/cli.c +++ b/src/compile/cli.c @@ -144,7 +144,7 @@ Text_t compile_cli_arg_call(env_t *env, Text_t fn_name, type_t *fn_type, const c code = Texts(code, "{", quoted_text(Text$replace(Text$from_str(arg->name), Text("_"), Text("-"))), ", &", Texts("_$", Text$from_str(arg->name)), ", ", compile_type_info(arg->type), arg->default_val ? Text("") : Text(", .required=true"), - arg->alias ? Texts(", .short_flag=", quoted_text(Text$from_str(arg->name)), + arg->alias ? Texts(", .short_flag=", quoted_text(Text$from_str(arg->alias)), "[0]") // TODO: escape char properly : Text(""), -- cgit v1.2.3 From a453ebf215e5e3ec3b27fa5142af77d7e3ca0c92 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sat, 22 Nov 2025 21:49:55 -0500 Subject: Change error message --- src/stdlib/cli.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/stdlib/cli.c b/src/stdlib/cli.c index 359220e9..04538796 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); } } -- cgit v1.2.3 From cb9d3b1a2c2c59c368f6121a16a9ab928b0ff951 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sun, 23 Nov 2025 00:35:05 -0500 Subject: Added Text.find(text, target, start=1) --- src/environment.c | 1 + src/stdlib/text.c | 17 +++++++++++++++-- src/stdlib/text.h | 1 + 3 files changed, 17 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/environment.c b/src/environment.c index 8f49f86e..3a2995f7 100644 --- a/src/environment.c +++ b/src/environment.c @@ -353,6 +353,7 @@ env_t *global_env(bool source_mapping) { {"caseless_equals", "Text$equal_ignoring_case", "func(a,b:Text, language='C' -> Bool)"}, // {"codepoint_names", "Text$codepoint_names", "func(text:Text -> [Text])"}, // {"ends_with", "Text$ends_with", "func(text,suffix:Text, remainder:&Text? = none -> Bool)"}, // + {"find", "Text$find", "func(text,target:Text, start=1 -> Int?)"}, // {"from", "Text$from", "func(text:Text, first:Int -> Text)"}, // {"from_c_string", "Text$from_str", "func(str:CString -> Text?)"}, // {"from_codepoint_names", "Text$from_codepoint_names", "func(codepoint_names:[Text] -> Text?)"}, // diff --git a/src/stdlib/text.c b/src/stdlib/text.c index febcafce..e51af49c 100644 --- a/src/stdlib/text.c +++ b/src/stdlib/text.c @@ -1057,8 +1057,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; } @@ -1106,6 +1106,19 @@ static bool _has_grapheme(TextIter_t *text, int32_t g) { return false; } +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; diff --git a/src/stdlib/text.h b/src/stdlib/text.h index 12b4bc20..fba8b08f 100644 --- a/src/stdlib/text.h +++ b/src/stdlib/text.h @@ -84,6 +84,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); -- cgit v1.2.3 From eff8d4aa8c59c4f8c531eb19389041c44aa49941 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sun, 23 Nov 2025 00:57:47 -0500 Subject: Bugfix for stack memory not getting properly detected in places --- src/types.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'src') diff --git a/src/types.c b/src/types.c index 33c5e55b..73c02807 100644 --- a/src/types.c +++ b/src/types.c @@ -249,6 +249,21 @@ PUREFUNC bool has_stack_memory(type_t *t) { switch (t->tag) { case PointerType: return Match(t, PointerType)->is_stack; case OptionalType: return has_stack_memory(Match(t, OptionalType)->type); + case ListType: return has_stack_memory(Match(t, ListType)->item_type); + case TableType: + return has_stack_memory(Match(t, TableType)->key_type) || has_stack_memory(Match(t, TableType)->value_type); + case StructType: { + for (arg_t *field = Match(t, StructType)->fields; field; field = field->next) { + if (has_stack_memory(field->type)) return true; + } + return false; + } + case EnumType: { + for (tag_t *tag = Match(t, EnumType)->tags; tag; tag = tag->next) { + if (tag->type && has_stack_memory(tag->type)) return true; + } + return false; + } default: return false; } } -- cgit v1.2.3 From 5e57bafdaf9d26d9445cc89055563e81e13670d3 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sun, 23 Nov 2025 01:29:01 -0500 Subject: Bugfix with ID having a trailing newline --- src/naming.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/naming.c b/src/naming.c index 484e1998..9c0f09e4 100644 --- a/src/naming.c +++ b/src/naming.c @@ -94,11 +94,16 @@ Text_t valid_c_name(const char *name) { return Text$from_str(name); } +#include "stdlib/stdlib.h" public Text_t CONSTFUNC namespace_name(env_t *env, namespace_t *ns, Text_t name) { - for (; ns; ns = ns->parent) + if (Text$has(name, Text("\n"))) fail("WTF??"); + for (; ns; ns = ns->parent) { + if (strchr(ns->name, '\n')) fail("WTF"); name = Texts(ns->name, "$", name); + } if (env->id_suffix.length > 0) name = Texts(name, env->id_suffix); + if (Text$has(env->id_suffix, Text("\n"))) fail("WTF?????"); return name; } @@ -113,5 +118,6 @@ Text_t get_id_suffix(const char *filename) { Path_t id_file = Path$child(build_dir, Texts(Path$base_name(path), Text$from_str(".id"))); OptionalText_t id = Path$read(id_file); if (id.tag == TEXT_NONE) err(1, "Could not read ID file: %s", Path$as_c_string(id_file)); + id = Text$trim(id, Text(" \r\n"), true, true); return Texts("$", id); } -- cgit v1.2.3 From 1c77d596b28ee45e0234cfa7c5f5679492f2178e Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sun, 23 Nov 2025 01:32:48 -0500 Subject: Fix up some type comparisons --- src/compile/assertions.c | 4 +++- src/compile/comparisons.c | 8 ++++++-- src/compile/lists.c | 2 +- src/typecheck.c | 13 +++++++++---- 4 files changed, 19 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/compile/assertions.c b/src/compile/assertions.c index 5746b21e..34055998 100644 --- a/src/compile/assertions.c +++ b/src/compile/assertions.c @@ -33,7 +33,9 @@ Text_t compile_assertion(env_t *env, ast_t *ast) { type_t *lhs_t = get_type(env, cmp.lhs); type_t *rhs_t = get_type(with_enum_scope(env, lhs_t), cmp.rhs); type_t *operand_t; - if (cmp.lhs->tag == Int && is_numeric_type(rhs_t)) { + if (type_eq(lhs_t, rhs_t)) { + operand_t = lhs_t; + } else if (cmp.lhs->tag == Int && is_numeric_type(rhs_t)) { operand_t = rhs_t; } else if (cmp.rhs->tag == Int && is_numeric_type(lhs_t)) { operand_t = lhs_t; diff --git a/src/compile/comparisons.c b/src/compile/comparisons.c index ffa04b9d..62196cdf 100644 --- a/src/compile/comparisons.c +++ b/src/compile/comparisons.c @@ -28,7 +28,9 @@ Text_t compile_comparison(env_t *env, ast_t *ast) { type_t *lhs_t = get_type(env, binop.lhs); type_t *rhs_t = get_type(with_enum_scope(env, lhs_t), binop.rhs); type_t *operand_t; - if (binop.lhs->tag == Int && is_numeric_type(rhs_t)) { + if (type_eq(lhs_t, rhs_t)) { + operand_t = lhs_t; + } else if (binop.lhs->tag == Int && is_numeric_type(rhs_t)) { operand_t = rhs_t; } else if (binop.rhs->tag == Int && is_numeric_type(lhs_t)) { operand_t = lhs_t; @@ -68,7 +70,9 @@ Text_t compile_comparison(env_t *env, ast_t *ast) { type_t *lhs_t = get_type(env, cmp.lhs); type_t *rhs_t = get_type(env, cmp.rhs); type_t *operand_t; - if (cmp.lhs->tag == Int && is_numeric_type(rhs_t)) { + if (type_eq(lhs_t, rhs_t)) { + operand_t = lhs_t; + } else if (cmp.lhs->tag == Int && is_numeric_type(rhs_t)) { operand_t = rhs_t; } else if (cmp.rhs->tag == Int && is_numeric_type(lhs_t)) { operand_t = lhs_t; diff --git a/src/compile/lists.c b/src/compile/lists.c index 54ad6e7f..97b0b85d 100644 --- a/src/compile/lists.c +++ b/src/compile/lists.c @@ -53,7 +53,7 @@ list_comprehension: { // set_binding(scope, comprehension_name, list_type, comprehension_name); for (ast_list_t *item = list->items; item; item = item->next) { if (item->ast->tag == Comprehension) code = Texts(code, "\n", compile_statement(scope, item->ast)); - else code = Texts(code, compile_statement(env, add_to_list_comprehension(item->ast, comprehension_var))); + else code = Texts(code, compile_statement(scope, add_to_list_comprehension(item->ast, comprehension_var))); } code = Texts(code, " ", comprehension_name, "; })"); return code; diff --git a/src/typecheck.c b/src/typecheck.c index a0e55a88..27bb62c6 100644 --- a/src/typecheck.c +++ b/src/typecheck.c @@ -1218,6 +1218,7 @@ type_t *get_type(env_t *env, ast_t *ast) { binary_operands_t binop = BINARY_OPERANDS(ast); type_t *lhs_t = get_type(env, binop.lhs); type_t *rhs_t = get_type(env, binop.rhs); + if (type_eq(lhs_t, rhs_t)) return ast->tag == Compare ? Type(IntType, .bits = TYPE_IBITS32) : Type(BoolType); if ((binop.lhs->tag == Int && is_numeric_type(rhs_t)) || (binop.rhs->tag == Int && is_numeric_type(lhs_t)) || can_compile_to_type(env, binop.rhs, lhs_t) || can_compile_to_type(env, binop.lhs, rhs_t)) @@ -1696,9 +1697,7 @@ PUREFUNC bool can_compile_to_type(env_t *env, ast_t *ast, type_t *needed) { if (needed->tag == OptionalType && ast->tag == None) return true; - type_t *actual = get_type(env, ast); - if (actual->tag == OptionalType && needed->tag == OptionalType) return can_promote(actual, needed); - + env = with_enum_scope(env, needed); if (is_numeric_type(needed) && ast->tag == Int) return true; if (needed->tag == NumType && ast->tag == Num) return true; @@ -1720,7 +1719,13 @@ PUREFUNC bool can_compile_to_type(env_t *env, ast_t *ast, type_t *needed) { return false; } return true; - } else if (needed->tag == PointerType) { + } + + type_t *actual = get_type(env, ast); + if (type_eq(actual, needed)) return true; + if (actual->tag == OptionalType && needed->tag == OptionalType) return can_promote(actual, needed); + + if (needed->tag == PointerType) { DeclareMatch(ptr, needed, PointerType); if (ast->tag == HeapAllocate) return !ptr->is_stack && can_compile_to_type(env, Match(ast, HeapAllocate)->value, ptr->pointed); -- cgit v1.2.3 From 0fa9a52090eb5d9ce88220c0134a8d2af6eb8d94 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sun, 23 Nov 2025 13:28:48 -0500 Subject: Accessing enum fields now gives an optional value instead of a boolean --- src/compile/enums.c | 13 +++++++++---- src/stdlib/datatypes.h | 5 +++++ src/types.c | 4 +++- 3 files changed, 17 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/compile/enums.c b/src/compile/enums.c index ec7a1755..31af96ad 100644 --- a/src/compile/enums.c +++ b/src/compile/enums.c @@ -156,15 +156,20 @@ Text_t compile_enum_field_access(env_t *env, ast_t *ast) { for (tag_t *tag = e->tags; tag; tag = tag->next) { if (streq(f->field, tag->name)) { Text_t tag_name = namespace_name(e->env, e->env->namespace, Texts("tag$", tag->name)); - if (fielded_t->tag == PointerType) { + if (tag->type != NULL && Match(tag->type, StructType)->fields) { + return Texts("({ ", compile_declaration(value_t, Text("_e")), " = ", + compile_to_pointer_depth(env, f->fielded, 0, false), "; ", "_e.$tag == ", tag_name, " ? ", + promote_to_optional(tag->type, Texts("_e.", tag->name)), " : ", compile_none(tag->type), + "; })"); + } else if (fielded_t->tag == PointerType) { Text_t fielded = compile_to_pointer_depth(env, f->fielded, 1, false); - return Texts("((", fielded, ")->$tag == ", tag_name, ")"); + return Texts("((", fielded, ")->$tag == ", tag_name, " ? OPTIONAL_EMPTY_STRUCT : NONE_EMPTY_STRUCT)"); } else if (enum_has_fields(value_t)) { Text_t fielded = compile(env, f->fielded); - return Texts("((", fielded, ").$tag == ", tag_name, ")"); + return Texts("((", fielded, ").$tag == ", tag_name, " ? OPTIONAL_EMPTY_STRUCT : NONE_EMPTY_STRUCT)"); } else { Text_t fielded = compile(env, f->fielded); - return Texts("((", fielded, ") == ", tag_name, ")"); + return Texts("((", fielded, ") == ", tag_name, " ? OPTIONAL_EMPTY_STRUCT : NONE_EMPTY_STRUCT)"); } } } diff --git a/src/stdlib/datatypes.h b/src/stdlib/datatypes.h index caabdacd..d51db8ab 100644 --- a/src/stdlib/datatypes.h +++ b/src/stdlib/datatypes.h @@ -72,11 +72,16 @@ typedef struct table_s { typedef struct Empty$$struct { } Empty$$type; +#define EMPTY_STRUCT ((Empty$$type){}) + typedef struct { bool has_value; Empty$$type value; } $OptionalEmpty$$type; +#define NONE_EMPTY_STRUCT (($OptionalEmpty$$type){.has_value = false}) +#define OPTIONAL_EMPTY_STRUCT (($OptionalEmpty$$type){.has_value = true}) + typedef struct { void *fn, *userdata; } Closure_t; diff --git a/src/types.c b/src/types.c index 73c02807..51555560 100644 --- a/src/types.c +++ b/src/types.c @@ -633,7 +633,9 @@ type_t *get_field_type(type_t *t, const char *field_name) { case EnumType: { DeclareMatch(e, t, EnumType); for (tag_t *tag = e->tags; tag; tag = tag->next) { - if (streq(field_name, tag->name)) return Type(BoolType); + if (!streq(field_name, tag->name)) continue; + if (tag->type != NULL && Match(tag->type, StructType)->fields) return Type(OptionalType, tag->type); + else return Type(OptionalType, EMPTY_TYPE); } return NULL; } -- cgit v1.2.3 From 80505a7eb147422226d1b86da17f516982d0f4c8 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sun, 23 Nov 2025 13:52:15 -0500 Subject: Better error messages and bugfix for compile_to_type logic --- src/compile/functions.c | 12 ++++++------ src/typecheck.c | 25 +++++++++++++------------ src/types.c | 21 +++++++++++++-------- src/types.h | 1 + 4 files changed, 33 insertions(+), 26 deletions(-) (limited to 'src') diff --git a/src/compile/functions.c b/src/compile/functions.c index 4a2812ba..cce93e3d 100644 --- a/src/compile/functions.c +++ b/src/compile/functions.c @@ -163,12 +163,12 @@ Text_t compile_function_call(env_t *env, ast_t *ast) { args = new (arg_t, .name = a->name, .type = get_type(env, a->value), .next = args); REVERSE_LIST(args); code_err(ast, - "This function's signature doesn't match this call site.\n" - "The signature is: ", - type_to_text(fn_t), - "\n" - "But it's being called with: ", - type_to_text(Type(FunctionType, .args = args))); + "This function's signature doesn't match this call site. \n" + " The function takes these args: (", + arg_types_to_text(Match(fn_t, FunctionType)->args, ", "), + ") \n" + " But it's being called with: (", + arg_types_to_text(args, ", "), ")"); } } return Texts(fn, "(", compile_arguments(env, ast, Match(fn_t, FunctionType)->args, call->args), ")"); diff --git a/src/typecheck.c b/src/typecheck.c index 27bb62c6..98fbf6da 100644 --- a/src/typecheck.c +++ b/src/typecheck.c @@ -1588,8 +1588,9 @@ bool is_valid_call(env_t *env, arg_t *spec_args, arg_ast_t *call_args, call_opts for (; unused_args; unused_args = unused_args->next) { if (unused_args->name) continue; // Already handled the keyword args if (options.promotion) { - if (!can_compile_to_type(arg_scope, unused_args->value, spec_type)) + if (!can_compile_to_type(arg_scope, unused_args->value, spec_type)) { return false; // Positional arg trying to fill in + } } else { type_t *call_type = get_arg_ast_type(arg_scope, unused_args); type_t *complete_call_type = @@ -1701,16 +1702,16 @@ PUREFUNC bool can_compile_to_type(env_t *env, ast_t *ast, type_t *needed) { if (is_numeric_type(needed) && ast->tag == Int) return true; if (needed->tag == NumType && ast->tag == Num) return true; - needed = non_optional(needed); - if (needed->tag == ListType && ast->tag == List) { - type_t *item_type = Match(needed, ListType)->item_type; + type_t *non_optional_needed = non_optional(needed); + if (non_optional_needed->tag == ListType && ast->tag == List) { + type_t *item_type = Match(non_optional_needed, ListType)->item_type; for (ast_list_t *item = Match(ast, List)->items; item; item = item->next) { if (!can_compile_to_type(env, item->ast, item_type)) return false; } return true; - } else if (needed->tag == TableType && ast->tag == Table) { - type_t *key_type = Match(needed, TableType)->key_type; - type_t *value_type = Match(needed, TableType)->value_type; + } else if (non_optional_needed->tag == TableType && ast->tag == Table) { + type_t *key_type = Match(non_optional_needed, TableType)->key_type; + type_t *value_type = Match(non_optional_needed, TableType)->value_type; for (ast_list_t *entry = Match(ast, Table)->entries; entry; entry = entry->next) { if (entry->ast->tag != TableEntry) continue; // TODO: fix this DeclareMatch(e, entry->ast, TableEntry); @@ -1725,16 +1726,16 @@ PUREFUNC bool can_compile_to_type(env_t *env, ast_t *ast, type_t *needed) { if (type_eq(actual, needed)) return true; if (actual->tag == OptionalType && needed->tag == OptionalType) return can_promote(actual, needed); - if (needed->tag == PointerType) { - DeclareMatch(ptr, needed, PointerType); + if (non_optional_needed->tag == PointerType) { + DeclareMatch(ptr, non_optional_needed, PointerType); if (ast->tag == HeapAllocate) return !ptr->is_stack && can_compile_to_type(env, Match(ast, HeapAllocate)->value, ptr->pointed); else if (ast->tag == StackReference) return ptr->is_stack && can_compile_to_type(env, Match(ast, StackReference)->value, ptr->pointed); - else return can_promote(actual, needed); - } else if (actual->tag == OptionalType && needed->tag != OptionalType) { + else return can_promote(actual, non_optional_needed); + } else if (actual->tag == OptionalType && non_optional_needed->tag != OptionalType) { return false; } else { - return can_promote(actual, needed); + return can_promote(actual, non_optional_needed); } } diff --git a/src/types.c b/src/types.c index 51555560..edfee27d 100644 --- a/src/types.c +++ b/src/types.c @@ -12,6 +12,15 @@ #include "stdlib/util.h" #include "types.h" +Text_t arg_types_to_text(arg_t *args, const char *separator) { + Text_t text = EMPTY_TEXT; + for (arg_t *arg = args; arg; arg = arg->next) { + text = Texts(text, type_to_text(arg->type)); + if (arg->next) text = Texts(text, separator); + } + return text; +} + Text_t type_to_text(type_t *t) { if (!t) return Text("(Unknown type)"); @@ -45,15 +54,11 @@ Text_t type_to_text(type_t *t) { return type_to_text(Match(t, ClosureType)->fn); } case FunctionType: { - Text_t c = Text("func("); DeclareMatch(fn, t, FunctionType); - for (arg_t *arg = fn->args; arg; arg = arg->next) { - c = Texts(c, type_to_text(arg->type)); - if (arg->next) c = Texts(c, ","); - } - if (fn->ret && fn->ret->tag != VoidType) c = Texts(c, fn->args ? " -> " : "-> ", type_to_text(fn->ret)); - c = Texts(c, ")"); - return c; + Text_t text = Texts("func(", arg_types_to_text(fn->args, ",")); + if (fn->ret && fn->ret->tag != VoidType) text = Texts(text, fn->args ? " -> " : "-> ", type_to_text(fn->ret)); + text = Texts(text, ")"); + return text; } case StructType: { DeclareMatch(struct_, t, StructType); diff --git a/src/types.h b/src/types.h index 2a94a512..b8f9a42f 100644 --- a/src/types.h +++ b/src/types.h @@ -136,6 +136,7 @@ struct type_s { _make_function_type(ret, sizeof((arg_t[]){__VA_ARGS__}) / sizeof(arg_t), (arg_t[]){__VA_ARGS__}) Text_t type_to_text(type_t *t); +Text_t arg_types_to_text(arg_t *args, const char *separator); const char *get_type_name(type_t *t); PUREFUNC bool type_eq(type_t *a, type_t *b); PUREFUNC bool type_is_a(type_t *t, type_t *req); -- cgit v1.2.3 From 8700224e98f95807d896d214380796e6f80678d0 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sun, 23 Nov 2025 13:58:37 -0500 Subject: use exit() instead of _exit() --- src/stdlib/stdlib.c | 2 +- src/stdlib/stdlib.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/stdlib/stdlib.c b/src/stdlib/stdlib.c index 45a4bd00..157dbfd4 100644 --- a/src/stdlib/stdlib.c +++ b/src/stdlib/stdlib.c @@ -145,7 +145,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 diff --git a/src/stdlib/stdlib.h b/src/stdlib/stdlib.h index e52b5cd1..aadce27c 100644 --- a/src/stdlib/stdlib.h +++ b/src/stdlib/stdlib.h @@ -30,7 +30,7 @@ void tomo_init(void); else fputs("\n", stderr); \ fflush(stderr); \ raise(SIGABRT); \ - _exit(1); \ + exit(1); \ }) #define fail_source(filename, start, end, ...) \ @@ -50,7 +50,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); -- cgit v1.2.3 From 2a24b0a3fc3c4986572ae2c4ea0e8e387497a7f6 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sun, 23 Nov 2025 14:19:04 -0500 Subject: Add `at_cleanup()` function --- src/environment.c | 13 +++++++------ src/stdlib/stdlib.c | 23 +++++++++++++++++++++++ src/stdlib/stdlib.h | 4 ++++ 3 files changed, 34 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/environment.c b/src/environment.c index 3a2995f7..d8c82513 100644 --- a/src/environment.c +++ b/src/environment.c @@ -529,17 +529,18 @@ env_t *global_env(bool source_mapping) { struct { const char *name, *code, *type_str; } global_vars[] = { - {"USE_COLOR", "USE_COLOR", "Bool"}, + {"EMPTY", "EMPTY", "Empty"}, {"TOMO_VERSION", "TOMO_VERSION_TEXT", "Text"}, - {"say", "say", "func(text:Text, newline=yes)"}, - {"print", "say", "func(text:Text, newline=yes)"}, - {"getenv", "getenv_text", "func(name:Text -> Text?)"}, - {"setenv", "setenv_text", "func(name:Text, value:Text -> Text?)"}, + {"USE_COLOR", "USE_COLOR", "Bool"}, {"ask", "ask", "func(prompt:Text, bold=yes, force_tty=yes -> Text?)"}, + {"at_cleanup", "tomo_at_cleanup", "func(fn:func())"}, {"exit", "tomo_exit", "func(message:Text?=none, code=Int32(1) -> Abort)"}, {"fail", "fail_text", "func(message:Text -> Abort)"}, + {"getenv", "getenv_text", "func(name:Text -> Text?)"}, + {"print", "say", "func(text:Text, newline=yes)"}, + {"say", "say", "func(text:Text, newline=yes)"}, + {"setenv", "setenv_text", "func(name:Text, value:Text -> Text?)"}, {"sleep", "sleep_num", "func(seconds:Num)"}, - {"EMPTY", "EMPTY", "Empty"}, }; for (size_t i = 0; i < sizeof(global_vars) / sizeof(global_vars[0]); i++) { diff --git a/src/stdlib/stdlib.c b/src/stdlib/stdlib.c index 157dbfd4..21547efe 100644 --- a/src/stdlib/stdlib.c +++ b/src/stdlib/stdlib.c @@ -72,6 +72,7 @@ void tomo_init(void) { sigemptyset(&sigact.sa_mask); sigact.sa_flags = 0; sigaction(SIGILL, &sigact, (struct sigaction *)NULL); + atexit(tomo_cleanup); } public @@ -219,3 +220,25 @@ 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); } + +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 aadce27c..392b5f23 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); \ @@ -35,6 +38,7 @@ void tomo_init(void); #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); \ -- cgit v1.2.3 From 83a2bff4bb04b0189d17419baf8ca520992d5033 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sun, 23 Nov 2025 15:57:44 -0500 Subject: Added Metadata section for files instead of _HELP and _USAGE --- src/ast.c | 17 +++++- src/ast.h | 5 ++ src/compile/cli.c | 142 ++++++++++++++++++++++++----------------------- src/compile/cli.h | 3 +- src/compile/files.c | 1 + src/compile/statements.c | 1 + src/parse/expressions.c | 2 +- src/parse/files.c | 41 ++++++++++++-- src/parse/text.c | 12 ++-- src/parse/text.h | 4 +- src/tomo.c | 6 +- src/typecheck.c | 7 ++- 12 files changed, 156 insertions(+), 85 deletions(-) (limited to 'src') diff --git a/src/ast.c b/src/ast.c index 432ce2d4..b2730d21 100644 --- a/src/ast.c +++ b/src/ast.c @@ -274,6 +274,8 @@ Text_t ast_to_sexp(ast_t *ast) { T(Assert, "(Assert ", ast_to_sexp(data.expr), " ", optional_sexp("message", data.message), ")"); T(Use, "(Use ", optional_sexp("var", data.var), " ", quoted_text(data.path), ")"); T(InlineCCode, "(InlineCCode ", ast_list_to_sexp(data.chunks), optional_type_sexp("type", data.type_ast), ")"); + T(Metadata, "((Metadata ", Text$quoted(data.key, false, Text("\"")), " ", + Text$quoted(data.value, false, Text("\"")), ")"); default: errx(1, "S-expressions are not implemented for this AST"); #undef T } @@ -463,7 +465,8 @@ void ast_visit(ast_t *ast, void (*visitor)(ast_t *, void *), void *userdata) { case Int: case Num: case Path: - case TextLiteral: return; + case TextLiteral: + case Metadata: return; case TextJoin: ast_visit_list(Match(ast, TextJoin)->children, visitor, userdata); return; case Declare: { DeclareMatch(decl, ast, Declare); @@ -780,3 +783,15 @@ void type_ast_visit(ast_t *ast, void (*visitor)(type_ast_t *, void *), void *use Closure_t fn = {.fn = visitor, .userdata = userdata}; ast_visit(ast, _type_ast_visit, &fn); } + +OptionalText_t ast_metadata(ast_t *ast, const char *key) { + if (ast->tag != Block) return NONE_TEXT; + Text_t key_text = Text$from_str(key); + for (ast_list_t *stmt = Match(ast, Block)->statements; stmt; stmt = stmt->next) { + if (stmt->ast->tag == Metadata) { + DeclareMatch(m, stmt->ast, Metadata); + if (Text$equal_values(m->key, key_text)) return m->value; + } + } + return NONE_TEXT; +} diff --git a/src/ast.h b/src/ast.h index c01ba68a..7fa9092a 100644 --- a/src/ast.h +++ b/src/ast.h @@ -276,6 +276,7 @@ typedef enum { Use, InlineCCode, ExplicitlyTyped, + Metadata, } ast_e; #define NUM_AST_TAGS (ExplicitlyTyped + 1) @@ -458,6 +459,9 @@ struct ast_s { ast_t *ast; struct type_s *type; } ExplicitlyTyped; + struct { + Text_t key, value; + } Metadata; } __data; }; @@ -483,3 +487,4 @@ CONSTFUNC ast_e binop_tag(ast_e tag); CONSTFUNC bool is_binary_operation(ast_t *ast); void ast_visit(ast_t *ast, void (*visitor)(ast_t *, void *), void *userdata); void type_ast_visit(ast_t *ast, void (*visitor)(type_ast_t *, void *), void *userdata); +OptionalText_t ast_metadata(ast_t *ast, const char *key); diff --git a/src/compile/cli.c b/src/compile/cli.c index e5756521..b92a5784 100644 --- a/src/compile/cli.c +++ b/src/compile/cli.c @@ -58,80 +58,86 @@ static OptionalText_t flagify(const char *name, bool prefix) { return flag; } -public -Text_t compile_cli_arg_call(env_t *env, Text_t fn_name, type_t *fn_type, const char *version) { +static Text_t generate_usage(env_t *env, type_t *fn_type) { DeclareMatch(fn_info, fn_type, FunctionType); - - env_t *main_env = fresh_scope(env); - - Text_t code = EMPTY_TEXT; - binding_t *usage_binding = get_binding(env, "_USAGE"); - Text_t usage_code = usage_binding ? usage_binding->code : Text("usage"); - binding_t *help_binding = get_binding(env, "_HELP"); - Text_t help_code = help_binding ? help_binding->code : usage_code; - if (!usage_binding) { - bool explicit_help_flag = false; - for (arg_t *arg = fn_info->args; arg; arg = arg->next) { - if (streq(arg->name, "help")) { - explicit_help_flag = true; - break; - } + bool explicit_help_flag = false; + for (arg_t *arg = fn_info->args; arg; arg = arg->next) { + if (streq(arg->name, "help")) { + explicit_help_flag = true; + break; } - - Text_t usage = explicit_help_flag ? EMPTY_TEXT : Text(" [\x1b[1m--help\x1b[m]"); - for (arg_t *arg = fn_info->args; arg; arg = arg->next) { - usage = Texts(usage, " "); - type_t *t = get_arg_type(main_env, arg); - OptionalText_t flag = flagify(arg->name, arg->default_val != NULL); - assert(flag.tag != TEXT_NONE); - OptionalText_t alias_flag = flagify(arg->alias, arg->default_val != NULL); - Text_t flags = Texts("\x1b[1m", flag, "\x1b[m"); - if (alias_flag.tag != TEXT_NONE) flags = Texts(flags, ",\x1b[1m", alias_flag, "\x1b[m"); + } + env_t *main_env = fresh_scope(env); + Text_t usage = explicit_help_flag ? EMPTY_TEXT : Text(" [\x1b[1m--help\x1b[m]"); + for (arg_t *arg = fn_info->args; arg; arg = arg->next) { + usage = Texts(usage, " "); + type_t *t = get_arg_type(main_env, arg); + OptionalText_t flag = flagify(arg->name, arg->default_val != NULL); + assert(flag.tag != TEXT_NONE); + OptionalText_t alias_flag = flagify(arg->alias, arg->default_val != NULL); + Text_t flags = Texts("\x1b[1m", flag, "\x1b[m"); + if (alias_flag.tag != TEXT_NONE) flags = Texts(flags, ",\x1b[1m", alias_flag, "\x1b[m"); + if (t->tag == BoolType || (t->tag == OptionalType && Match(t, OptionalType)->type->tag == BoolType)) + flags = Texts(flags, "|\x1b[1m--no-", Text$without_prefix(flag, Text("--")), "\x1b[m"); + if (arg->default_val || value_type(t)->tag == BoolType) { if (t->tag == BoolType || (t->tag == OptionalType && Match(t, OptionalType)->type->tag == BoolType)) - flags = Texts(flags, "|\x1b[1m--no-", Text$without_prefix(flag, Text("--")), "\x1b[m"); - if (arg->default_val || value_type(t)->tag == BoolType) { - if (t->tag == BoolType || (t->tag == OptionalType && Match(t, OptionalType)->type->tag == BoolType)) - usage = Texts(usage, "[", flags, "]"); - else if (t->tag == ListType) usage = Texts(usage, "[", flags, " ", get_flag_options(t, Text("|")), "]"); - else usage = Texts(usage, "[", flags, " ", get_flag_options(t, Text("|")), "]"); - } else { - usage = Texts(usage, "\x1b[1m", get_flag_options(t, Text("|")), "\x1b[m"); - } + usage = Texts(usage, "[", flags, "]"); + else if (t->tag == ListType) usage = Texts(usage, "[", flags, " ", get_flag_options(t, Text("|")), "]"); + else usage = Texts(usage, "[", flags, " ", get_flag_options(t, Text("|")), "]"); + } else { + usage = Texts(usage, "\x1b[1m", get_flag_options(t, Text("|")), "\x1b[m"); } - code = Texts(code, - "Text_t usage = Texts(Text(\"\\x1b[1mUsage:\\x1b[m \"), " - "Text$from_str(argv[0])", - usage.length == 0 ? EMPTY_TEXT : Texts(", Text(", quoted_text(usage), ")"), ");\n"); } - if (!help_binding) { - Text_t help_text = fn_info->args ? Text("\n") : Text("\n\n\x1b[2;3m No arguments...\x1b[m"); - - for (arg_t *arg = fn_info->args; arg; arg = arg->next) { - help_text = Texts(help_text, "\n"); - type_t *t = get_arg_type(main_env, arg); - OptionalText_t flag = flagify(arg->name, true); - assert(flag.tag != TEXT_NONE); - OptionalText_t alias_flag = flagify(arg->alias, true); - Text_t flags = Texts("\x1b[33;1m", flag, "\x1b[m"); - if (alias_flag.tag != TEXT_NONE) flags = Texts(flags, ",\x1b[33;1m", alias_flag, "\x1b[m"); - if (t->tag == BoolType || (t->tag == OptionalType && Match(t, OptionalType)->type->tag == BoolType)) - flags = Texts(flags, "|\x1b[33;1m--no-", Text$without_prefix(flag, Text("--")), "\x1b[m"); - if (t->tag == BoolType || (t->tag == OptionalType && Match(t, OptionalType)->type->tag == BoolType)) - help_text = Texts(help_text, " ", flags); - else - help_text = Texts(help_text, " ", flags, " \x1b[1;34m", - get_flag_options(t, Text("\x1b[m | \x1b[1;34m")), "\x1b[m"); - - if (arg->comment.length > 0) help_text = Texts(help_text, " \x1b[3m", arg->comment, "\x1b[m"); - if (arg->default_val) { - Text_t default_text = - Text$from_strn(arg->default_val->start, (size_t)(arg->default_val->end - arg->default_val->start)); - help_text = Texts(help_text, " \x1b[2m(default:", default_text, ")\x1b[m"); - } + return usage; +} + +static Text_t generate_help(env_t *env, type_t *fn_type) { + DeclareMatch(fn_info, fn_type, FunctionType); + env_t *main_env = fresh_scope(env); + Text_t help_text = EMPTY_TEXT; + + for (arg_t *arg = fn_info->args; arg; arg = arg->next) { + help_text = Texts(help_text, "\n"); + type_t *t = get_arg_type(main_env, arg); + OptionalText_t flag = flagify(arg->name, true); + assert(flag.tag != TEXT_NONE); + OptionalText_t alias_flag = flagify(arg->alias, true); + Text_t flags = Texts("\x1b[33;1m", flag, "\x1b[m"); + if (alias_flag.tag != TEXT_NONE) flags = Texts(flags, ",\x1b[33;1m", alias_flag, "\x1b[m"); + if (t->tag == BoolType || (t->tag == OptionalType && Match(t, OptionalType)->type->tag == BoolType)) + flags = Texts(flags, "|\x1b[33;1m--no-", Text$without_prefix(flag, Text("--")), "\x1b[m"); + if (t->tag == BoolType || (t->tag == OptionalType && Match(t, OptionalType)->type->tag == BoolType)) + help_text = Texts(help_text, " ", flags); + else + help_text = Texts(help_text, " ", flags, " \x1b[1;34m", get_flag_options(t, Text("\x1b[m | \x1b[1;34m")), + "\x1b[m"); + + if (arg->comment.length > 0) help_text = Texts(help_text, " \x1b[3m", arg->comment, "\x1b[m"); + if (arg->default_val) { + Text_t default_text = + Text$from_strn(arg->default_val->start, (size_t)(arg->default_val->end - arg->default_val->start)); + help_text = Texts(help_text, " \x1b[2m(default:", default_text, ")\x1b[m"); } - code = Texts(code, "Text_t help = Texts(usage, ", quoted_text(Texts(help_text, "\n")), ");\n"); - help_code = Text("help"); } + return help_text; +} + +public +Text_t compile_cli_arg_call(env_t *env, ast_t *ast, Text_t fn_name, type_t *fn_type, const char *version) { + DeclareMatch(fn_info, fn_type, FunctionType); + + Text_t code = EMPTY_TEXT; + OptionalText_t usage = ast_metadata(ast, "USAGE"); + if (usage.tag == TEXT_NONE) usage = generate_usage(env, fn_type); + + OptionalText_t help = ast_metadata(ast, "HELP"); + if (help.tag == TEXT_NONE) help = generate_help(env, fn_type); + + code = Texts(code, + "Text_t usage = Texts(Text(\"\\x1b[1mUsage:\\x1b[m \"), " + "Text$from_str(argv[0]), Text(\" \")", + usage.length == 0 ? EMPTY_TEXT : Texts(", Text(", quoted_text(usage), ")"), ");\n", + "Text_t help = Texts(usage, Text(\"\\n\\n\"", quoted_text(help), "));\n"); for (arg_t *arg = fn_info->args; arg; arg = arg->next) { code = Texts(code, compile_declaration(arg->type, Texts("_$", Text$from_str(arg->name))), " = ", @@ -151,7 +157,7 @@ Text_t compile_cli_arg_call(env_t *env, Text_t fn_name, type_t *fn_type, const c "},\n"); } code = Texts(code, "};\n"); - code = Texts(code, "tomo_parse_args(argc, argv, ", usage_code, ", ", help_code, ", ", version_code, + code = Texts(code, "tomo_parse_args(argc, argv, usage, help, ", version_code, ", sizeof(cli_args)/sizeof(cli_args[0]), cli_args);\n"); // Lazily initialize default values to prevent side effects diff --git a/src/compile/cli.h b/src/compile/cli.h index fa60eccf..fbb7347b 100644 --- a/src/compile/cli.h +++ b/src/compile/cli.h @@ -2,9 +2,10 @@ #pragma once +#include "../ast.h" #include "../environment.h" #include "../stdlib/datatypes.h" #include "../types.h" -Text_t compile_cli_arg_call(env_t *env, Text_t fn_name, type_t *fn_type, const char *version); +Text_t compile_cli_arg_call(env_t *env, ast_t *ast, Text_t fn_name, type_t *fn_type, const char *version); Text_t compile_manpage(Text_t program, OptionalText_t synopsis, OptionalText_t description, arg_t *args); diff --git a/src/compile/files.c b/src/compile/files.c index a4cc07fe..b916f23f 100644 --- a/src/compile/files.c +++ b/src/compile/files.c @@ -142,6 +142,7 @@ Text_t compile_top_level_code(env_t *env, ast_t *ast) { } return code; } + case Metadata: default: return EMPTY_TEXT; } } diff --git a/src/compile/statements.c b/src/compile/statements.c index 0cd85b5d..c6ceccd9 100644 --- a/src/compile/statements.c +++ b/src/compile/statements.c @@ -216,6 +216,7 @@ static Text_t _compile_statement(env_t *env, ast_t *ast) { return EMPTY_TEXT; } } + case Metadata: return EMPTY_TEXT; default: // print("Is discardable: ", ast_to_sexp_str(ast), " ==> ", // is_discardable(env, ast)); diff --git a/src/parse/expressions.c b/src/parse/expressions.c index b43e4f3a..d031c49f 100644 --- a/src/parse/expressions.c +++ b/src/parse/expressions.c @@ -168,7 +168,7 @@ ast_t *parse_term_no_suffix(parse_ctx_t *ctx, const char *pos) { (void)(false || (term = parse_none(ctx, pos)) || (term = parse_num(ctx, pos)) // Must come before int || (term = parse_int(ctx, pos)) || (term = parse_negative(ctx, pos)) // Must come after num/int || (term = parse_heap_alloc(ctx, pos)) || (term = parse_stack_reference(ctx, pos)) - || (term = parse_bool(ctx, pos)) || (term = parse_text(ctx, pos)) || (term = parse_path(ctx, pos)) + || (term = parse_bool(ctx, pos)) || (term = parse_text(ctx, pos, true)) || (term = parse_path(ctx, pos)) || (term = parse_lambda(ctx, pos)) || (term = parse_parens(ctx, pos)) || (term = parse_table(ctx, pos)) || (term = parse_var(ctx, pos)) || (term = parse_list(ctx, pos)) || (term = parse_reduction(ctx, pos)) || (term = parse_pass(ctx, pos)) || (term = parse_defer(ctx, pos)) || (term = parse_skip(ctx, pos)) diff --git a/src/parse/files.c b/src/parse/files.c index 23e940e9..f5d9554a 100644 --- a/src/parse/files.c +++ b/src/parse/files.c @@ -7,8 +7,11 @@ #include #include "../ast.h" +#include "../stdlib/datatypes.h" +#include "../stdlib/paths.h" #include "../stdlib/stdlib.h" #include "../stdlib/tables.h" +#include "../stdlib/text.h" #include "../stdlib/util.h" #include "context.h" #include "errors.h" @@ -31,6 +34,35 @@ static ast_t *parse_top_declaration(parse_ctx_t *ctx, const char *pos) { return declaration; } +static ast_t *parse_metadata(parse_ctx_t *ctx, const char *pos) { + const char *start = pos; + const char *key = get_id(&pos); + if (!key) return NULL; + spaces(&pos); + if (!match(&pos, ":")) return NULL; + spaces(&pos); + ast_t *value = parse_text(ctx, pos, false); + Text_t value_text = EMPTY_TEXT; + if (value) { + for (ast_list_t *child = Match(value, TextJoin)->children; child; child = child->next) { + if (child->ast->tag != TextLiteral) + parser_err(ctx, child->ast->start, child->ast->end, "Text interpolations are not allowed in metadata"); + value_text = Texts(value_text, Match(child->ast, TextLiteral)->text); + } + } else { + value = parse_path(ctx, pos); + if (!value) return NULL; + Path_t path = Path$from_str(Match(value, Path)->path); + path = Path$resolved(path, Path$parent(Path$from_str(ctx->file->filename))); + OptionalText_t contents = Path$read(path); + if (contents.tag == TEXT_NONE) parser_err(ctx, value->start, value->end, "File not found: ", path); + value_text = Text$trim(contents, Text("\r\n\t "), true, true); + } + pos = value->end; + + return NewAST(ctx->file, start, pos, Metadata, .key = Text$from_str(key), .value = value_text); +} + ast_t *parse_file_body(parse_ctx_t *ctx, const char *pos) { const char *start = pos; whitespace(ctx, &pos); @@ -40,10 +72,11 @@ ast_t *parse_file_body(parse_ctx_t *ctx, const char *pos) { whitespace(ctx, &next); if (get_indent(ctx, next) != 0) break; ast_t *stmt; - if ((stmt = optional(ctx, &pos, parse_struct_def)) || (stmt = optional(ctx, &pos, parse_func_def)) - || (stmt = optional(ctx, &pos, parse_enum_def)) || (stmt = optional(ctx, &pos, parse_lang_def)) - || (stmt = optional(ctx, &pos, parse_convert_def)) || (stmt = optional(ctx, &pos, parse_use)) - || (stmt = optional(ctx, &pos, parse_inline_c)) || (stmt = optional(ctx, &pos, parse_top_declaration))) { + if ((stmt = optional(ctx, &pos, parse_metadata)) || (stmt = optional(ctx, &pos, parse_struct_def)) + || (stmt = optional(ctx, &pos, parse_func_def)) || (stmt = optional(ctx, &pos, parse_enum_def)) + || (stmt = optional(ctx, &pos, parse_lang_def)) || (stmt = optional(ctx, &pos, parse_convert_def)) + || (stmt = optional(ctx, &pos, parse_use)) || (stmt = optional(ctx, &pos, parse_inline_c)) + || (stmt = optional(ctx, &pos, parse_top_declaration))) { statements = new (ast_list_t, .ast = stmt, .next = statements); pos = stmt->end; whitespace(ctx, &pos); // TODO: check for newline diff --git a/src/parse/text.c b/src/parse/text.c index 7650955c..e23b8417 100644 --- a/src/parse/text.c +++ b/src/parse/text.c @@ -17,7 +17,7 @@ #include "types.h" #include "utils.h" -static ast_list_t *_parse_text_helper(parse_ctx_t *ctx, const char **out_pos) { +static ast_list_t *_parse_text_helper(parse_ctx_t *ctx, const char **out_pos, bool allow_interps) { const char *pos = *out_pos; int64_t starting_indent = get_indent(ctx, pos); @@ -41,6 +41,8 @@ static ast_list_t *_parse_text_helper(parse_ctx_t *ctx, const char **out_pos) { parser_err(ctx, pos, pos, "I expected a valid text here"); } + if (!allow_interps) interp = NULL; + ast_list_t *chunks = NULL; Text_t chunk = EMPTY_TEXT; const char *chunk_start = pos; @@ -123,9 +125,9 @@ static ast_list_t *_parse_text_helper(parse_ctx_t *ctx, const char **out_pos) { return chunks; } -ast_t *parse_text(parse_ctx_t *ctx, const char *pos) { +ast_t *parse_text(parse_ctx_t *ctx, const char *pos, bool allow_interps) { // ('"' ... '"' / "'" ... "'" / "`" ... "`") - // "$" [name] [interp-char] quote-char ... close-quote + // "$" [name] quote-char ... close-quote const char *start = pos; const char *lang = NULL; @@ -136,7 +138,7 @@ ast_t *parse_text(parse_ctx_t *ctx, const char *pos) { if (!(*pos == '"' || *pos == '\'' || *pos == '`')) return NULL; - ast_list_t *chunks = _parse_text_helper(ctx, &pos); + ast_list_t *chunks = _parse_text_helper(ctx, &pos, allow_interps); bool colorize = match(&pos, "~") && match_word(&pos, "colorized"); return NewAST(ctx->file, start, pos, TextJoin, .lang = lang, .children = chunks, .colorize = colorize); } @@ -157,7 +159,7 @@ ast_t *parse_inline_c(parse_ctx_t *ctx, const char *pos) { parser_err(ctx, pos, pos + 1, "This is not a valid string quotation character. Valid characters are: \"'`|/;([{<"); - ast_list_t *chunks = _parse_text_helper(ctx, &pos); + ast_list_t *chunks = _parse_text_helper(ctx, &pos, true); return NewAST(ctx->file, start, pos, InlineCCode, .chunks = chunks, .type_ast = type); } diff --git a/src/parse/text.h b/src/parse/text.h index 6ab3cab2..865bc5a4 100644 --- a/src/parse/text.h +++ b/src/parse/text.h @@ -1,9 +1,11 @@ // Logic for parsing text literals #pragma once +#include + #include "../ast.h" #include "context.h" -ast_t *parse_text(parse_ctx_t *ctx, const char *pos); +ast_t *parse_text(parse_ctx_t *ctx, const char *pos, bool allow_interps); ast_t *parse_inline_c(parse_ctx_t *ctx, const char *pos); ast_t *parse_path(parse_ctx_t *ctx, const char *pos); diff --git a/src/tomo.c b/src/tomo.c index fab725db..a04ccf59 100644 --- a/src/tomo.c +++ b/src/tomo.c @@ -841,7 +841,7 @@ void transpile_code(env_t *base_env, Path_t path) { namespace_name(module_env, module_env->namespace, Text("$initialize")), "();\n" "\n", - compile_cli_arg_call(module_env, main_binding->code, main_binding->type, version), + compile_cli_arg_call(module_env, ast, main_binding->code, main_binding->type, version), "return 0;\n" "}\n")); } @@ -877,7 +877,9 @@ Path_t compile_executable(env_t *base_env, Path_t path, Path_t exe_path, List_t Path_t manpage_file = build_file(Path$with_extension(path, Text(".1"), true), ""); if (clean_build || !Path$is_file(manpage_file, true) || is_stale(manpage_file, path, true)) { - Text_t manpage = compile_manpage(Path$base_name(exe_path), NONE_TEXT, NONE_TEXT, + OptionalText_t synopsys = ast_metadata(ast, "MANPAGE_SYNOPSYS"); + OptionalText_t description = ast_metadata(ast, "MANPAGE_DESCRIPTION"); + Text_t manpage = compile_manpage(Path$base_name(exe_path), synopsys, description, Match(main_binding->type, FunctionType)->args); if (!quiet) print("Wrote manpage:\t", Path$relative_to(manpage_file, Path$current_dir())); Path$write(manpage_file, manpage, 0644); diff --git a/src/typecheck.c b/src/typecheck.c index 98fbf6da..4e1f5554 100644 --- a/src/typecheck.c +++ b/src/typecheck.c @@ -1511,6 +1511,7 @@ type_t *get_type(env_t *env, ast_t *ast) { } case Unknown: code_err(ast, "I can't figure out the type of: ", ast_to_sexp_str(ast)); case ExplicitlyTyped: return Match(ast, ExplicitlyTyped)->type; + case Metadata: return Type(VoidType); } #ifdef __GNUC__ #pragma GCC diagnostic pop @@ -1529,7 +1530,8 @@ PUREFUNC bool is_discardable(env_t *env, ast_t *ast) { case StructDef: case EnumDef: case LangDef: - case Use: return true; + case Use: + case Metadata: return true; default: break; } type_t *t = get_type(env, ast); @@ -1686,7 +1688,8 @@ PUREFUNC bool is_constant(env_t *env, ast_t *ast) { default: return is_constant(env, binop.lhs) && is_constant(env, binop.rhs); } } - case Use: return true; + case Use: + case Metadata: return true; case FunctionCall: return false; case InlineCCode: return true; default: return false; -- cgit v1.2.3 From 8de3edded640dfb4dc27fd5afae77faa2bf4acd5 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Mon, 24 Nov 2025 01:12:41 -0500 Subject: Add MANPAGE metadata for overriding whole file --- src/compile/cli.c | 11 +++++++++-- src/compile/cli.h | 2 +- src/tomo.c | 7 ++----- 3 files changed, 12 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/compile/cli.c b/src/compile/cli.c index b92a5784..7cd3cc7c 100644 --- a/src/compile/cli.c +++ b/src/compile/cli.c @@ -187,14 +187,21 @@ Text_t compile_cli_arg_call(env_t *env, ast_t *ast, Text_t fn_name, type_t *fn_t } public -Text_t compile_manpage(Text_t program, OptionalText_t synopsis, OptionalText_t description, arg_t *args) { +Text_t compile_manpage(Text_t program, ast_t *ast, arg_t *args) { + OptionalText_t user_manpage = ast_metadata(ast, "MANPAGE"); + if (user_manpage.tag != TEXT_NONE) { + return user_manpage; + } + + OptionalText_t synopsys = ast_metadata(ast, "MANPAGE_SYNOPSYS"); + OptionalText_t description = ast_metadata(ast, "MANPAGE_DESCRIPTION"); Text_t date = Text(""); // TODO: use date Text_t man = Texts(".\\\" Automatically generated by Tomo\n" ".TH \"", Text$upper(program, Text("C")), "\" \"1\" \"", date, "\" \"\" \"\"\n" ".SH NAME\n", - program, " \\- ", synopsis.tag == TEXT_NONE ? Text("a Tomo program") : synopsis, "\n"); + program, " \\- ", synopsys.tag == TEXT_NONE ? Text("a Tomo program") : synopsys, "\n"); if (description.tag != TEXT_NONE) { man = Texts(man, ".SH DESCRIPTION\n", description, "\n"); diff --git a/src/compile/cli.h b/src/compile/cli.h index fbb7347b..907554a6 100644 --- a/src/compile/cli.h +++ b/src/compile/cli.h @@ -8,4 +8,4 @@ #include "../types.h" Text_t compile_cli_arg_call(env_t *env, ast_t *ast, Text_t fn_name, type_t *fn_type, const char *version); -Text_t compile_manpage(Text_t program, OptionalText_t synopsis, OptionalText_t description, arg_t *args); +Text_t compile_manpage(Text_t program, ast_t *ast, arg_t *args); diff --git a/src/tomo.c b/src/tomo.c index a04ccf59..58bfabb3 100644 --- a/src/tomo.c +++ b/src/tomo.c @@ -877,12 +877,9 @@ Path_t compile_executable(env_t *base_env, Path_t path, Path_t exe_path, List_t Path_t manpage_file = build_file(Path$with_extension(path, Text(".1"), true), ""); if (clean_build || !Path$is_file(manpage_file, true) || is_stale(manpage_file, path, true)) { - OptionalText_t synopsys = ast_metadata(ast, "MANPAGE_SYNOPSYS"); - OptionalText_t description = ast_metadata(ast, "MANPAGE_DESCRIPTION"); - Text_t manpage = compile_manpage(Path$base_name(exe_path), synopsys, description, - Match(main_binding->type, FunctionType)->args); - if (!quiet) print("Wrote manpage:\t", Path$relative_to(manpage_file, Path$current_dir())); + Text_t manpage = compile_manpage(Path$base_name(exe_path), ast, Match(main_binding->type, FunctionType)->args); Path$write(manpage_file, manpage, 0644); + if (!quiet) print("Wrote manpage:\t", Path$relative_to(manpage_file, Path$current_dir())); } else { if (verbose) whisper("Unchanged: ", manpage_file); } -- cgit v1.2.3 From 2d56b517d81ea06debdf3cc307a71bfe626544b5 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Mon, 24 Nov 2025 18:22:43 -0500 Subject: Bugfix for mac --- src/tomo.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/tomo.c b/src/tomo.c index 58bfabb3..97857286 100644 --- a/src/tomo.c +++ b/src/tomo.c @@ -475,7 +475,7 @@ void build_library(Path_t lib_dir) { FILE *prog = run_cmd(cc, " -O", optimization, " ", cflags, " ", ldflags, " ", ldlibs, " ", list_text(extra_ldlibs), #ifdef __APPLE__ - " -Wl,-install_name,@rpath/'lib", Path$base_name(lib_dir), SHARED_SUFFIX, + " -Wl,-install_name,@rpath/'lib", lib_name, SHARED_SUFFIX, "'" #else " -Wl,-soname,'lib", lib_name, SHARED_SUFFIX, -- cgit v1.2.3 From 19349dff649ac8039e8d13e07d33cbdc6f6655b9 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Mon, 24 Nov 2025 19:39:57 -0500 Subject: Bugfix for downloading the right branch of a module --- src/modules.c | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/modules.c b/src/modules.c index 9ebdca09..23b8a0a0 100644 --- a/src/modules.c +++ b/src/modules.c @@ -129,6 +129,7 @@ bool try_install_module(module_info_t mod, bool ask_confirmation) { } print("Installing ", mod.name, " from git..."); if (mod.revision) xsystem("git clone --depth=1 --revision ", mod.revision, " ", mod.git, " ", dest); + else if (mod.version) xsystem("git clone --depth=1 --branch ", mod.version, " ", mod.git, " ", dest); else xsystem("git clone --depth=1 ", mod.git, " ", dest); xsystem("tomo -L ", dest); return true; -- cgit v1.2.3 From 9b8c4beb1e64579792fca7c161c9e3b0691e67d4 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Mon, 24 Nov 2025 19:42:28 -0500 Subject: When running an executable, run it with a relative instead of absolute path so it shows up more nicely in the usage --- src/tomo.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/tomo.c b/src/tomo.c index 97857286..66ac0ddf 100644 --- a/src/tomo.c +++ b/src/tomo.c @@ -423,7 +423,8 @@ int main(int argc, char *argv[]) { pid_t child = i == (int64_t)run_files.length - 1 ? 0 : fork(); if (child == 0) { const char *prog_args[1 + args.length + 1]; - prog_args[0] = (char *)Path$as_c_string(exe_path); + Path_t relative_exe = Path$relative_to(exe_path, Path$current_dir()); + prog_args[0] = (char *)Path$as_c_string(relative_exe); for (int64_t j = 0; j < (int64_t)args.length; j++) prog_args[j + 1] = *(const char **)(args.data + j * args.stride); prog_args[1 + args.length] = NULL; -- cgit v1.2.3 From 04ea51a81720be79170f185879a465e7020ed42b Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Mon, 24 Nov 2025 20:16:50 -0500 Subject: Fix case where a conditional failure was mistaken for an always failure --- src/typecheck.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/typecheck.c b/src/typecheck.c index 4e1f5554..b89f7eca 100644 --- a/src/typecheck.c +++ b/src/typecheck.c @@ -1402,7 +1402,10 @@ type_t *get_type(env_t *env, ast_t *ast) { type_t *true_t = get_type(truthy_scope, if_->body); ast_t *else_body = if_->else_body; - if (!else_body) else_body = WrapAST(ast, None, .type = true_t); + if (!else_body) { + if (true_t->tag == AbortType) return Type(VoidType); + else_body = WrapAST(ast, None, .type = true_t); + } type_t *false_t = get_type(falsey_scope, if_->else_body); type_t *t_either = type_or_type(true_t, false_t); if (!t_either) -- cgit v1.2.3 From bbf7a60dcd524c2d86ac7efe7bc97d4a627b6d15 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Mon, 24 Nov 2025 20:22:36 -0500 Subject: Fix `from_num` incomplete renaming --- src/environment.c | 12 ++++++------ src/stdlib/bigint.h | 4 ++-- src/stdlib/intX.h | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/environment.c b/src/environment.c index d8c82513..6075ce49 100644 --- a/src/environment.c +++ b/src/environment.c @@ -459,7 +459,7 @@ env_t *global_env(bool source_mapping) { {"Int$from_int16", "func(i:Int16 -> Int)"}, // {"Int$from_int32", "func(i:Int32 -> Int)"}, // {"Int$from_int64", "func(i:Int64 -> Int)"}, // - {"Int$from_num", "func(n:Num, truncate=no -> Int)"}, // + {"Int$from_num64", "func(n:Num, truncate=no -> Int)"}, // {"Int$from_num32", "func(n:Num32, truncate=no -> Int)"}); ADD_CONSTRUCTORS("Int64", // {"Int64$from_bool", "func(b:Bool -> Int64)"}, // @@ -468,7 +468,7 @@ env_t *global_env(bool source_mapping) { {"Int64$from_int16", "func(i:Int16 -> Int64)"}, // {"Int64$from_int32", "func(i:Int32 -> Int64)"}, // {"Int64$from_int", "func(i:Int, truncate=no -> Int64)"}, // - {"Int64$from_num", "func(n:Num, truncate=no -> Int64)"}, // + {"Int64$from_num64", "func(n:Num, truncate=no -> Int64)"}, // {"Int64$from_num32", "func(n:Num32, truncate=no -> Int64)"}); ADD_CONSTRUCTORS("Int32", // {"Int32$from_bool", "func(b:Bool -> Int32)"}, // @@ -477,7 +477,7 @@ env_t *global_env(bool source_mapping) { {"Int32$from_int16", "func(i:Int16 -> Int32)"}, // {"Int32$from_int64", "func(i:Int64, truncate=no -> Int32)"}, // {"Int32$from_int", "func(i:Int, truncate=no -> Int32)"}, // - {"Int32$from_num", "func(n:Num, truncate=no -> Int32)"}, // + {"Int32$from_num64", "func(n:Num, truncate=no -> Int32)"}, // {"Int32$from_num32", "func(n:Num32, truncate=no -> Int32)"}); ADD_CONSTRUCTORS("Int16", // {"Int16$from_bool", "func(b:Bool -> Int16)"}, // @@ -486,7 +486,7 @@ env_t *global_env(bool source_mapping) { {"Int16$from_int32", "func(i:Int32, truncate=no -> Int16)"}, // {"Int16$from_int64", "func(i:Int64, truncate=no -> Int16)"}, // {"Int16$from_int", "func(i:Int, truncate=no -> Int16)"}, // - {"Int16$from_num", "func(n:Num, truncate=no -> Int16)"}, // + {"Int16$from_num64", "func(n:Num, truncate=no -> Int16)"}, // {"Int16$from_num32", "func(n:Num32, truncate=no -> Int16)"}); ADD_CONSTRUCTORS("Int8", // {"Int8$from_bool", "func(b:Bool -> Int8)"}, // @@ -495,7 +495,7 @@ env_t *global_env(bool source_mapping) { {"Int8$from_int32", "func(i:Int32, truncate=no -> Int8)"}, // {"Int8$from_int64", "func(i:Int64, truncate=no -> Int8)"}, // {"Int8$from_int", "func(i:Int, truncate=no -> Int8)"}, // - {"Int8$from_num", "func(n:Num, truncate=no -> Int8)"}, // + {"Int8$from_num64", "func(n:Num, truncate=no -> Int8)"}, // {"Int8$from_num32", "func(n:Num32, truncate=no -> Int8)"}); ADD_CONSTRUCTORS("Num", // {"Num$from_bool", "func(b:Bool -> Num)"}, // @@ -514,7 +514,7 @@ env_t *global_env(bool source_mapping) { {"Num32$from_int32", "func(i:Int32, truncate=no -> Num32)"}, // {"Num32$from_int64", "func(i:Int64, truncate=no -> Num32)"}, // {"Num32$from_int", "func(i:Int, truncate=no -> Num32)"}, // - {"Num32$from_num", "func(n:Num -> Num32)"}); + {"Num32$from_num64", "func(n:Num -> Num32)"}); ADD_CONSTRUCTORS("Path", // {"Path$escape_text", "func(text:Text -> Path)"}, // {"Path$escape_path", "func(path:Path -> Path)"}, // diff --git a/src/stdlib/bigint.h b/src/stdlib/bigint.h index 614e1501..a00bdf2f 100644 --- a/src/stdlib/bigint.h +++ b/src/stdlib/bigint.h @@ -189,13 +189,13 @@ MACROLIKE PUREFUNC bool Int$is_negative(Int_t x) { #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wfloat-equal" #endif -MACROLIKE PUREFUNC Int_t Int$from_num(double n, bool truncate) { +MACROLIKE PUREFUNC Int_t Int$from_num64(double n, bool truncate) { mpz_t result; mpz_init_set_d(result, n); if (!truncate && unlikely(mpz_get_d(result) != n)) fail("Could not convert to an integer without truncation: ", n); return Int$from_mpz(result); } -MACROLIKE PUREFUNC Int_t Int$from_num32(float n, bool truncate) { return Int$from_num((double)n, truncate); } +MACROLIKE PUREFUNC Int_t Int$from_num32(float n, bool truncate) { return Int$from_num64((double)n, truncate); } MACROLIKE Int_t Int$from_int64(int64_t i) { if likely (i >= SMALLEST_SMALL_INT && i <= BIGGEST_SMALL_INT) return (Int_t){.small = (i << 2L) | 1L}; mpz_t result; diff --git a/src/stdlib/intX.h b/src/stdlib/intX.h index 765543fd..0f4632c2 100644 --- a/src/stdlib/intX.h +++ b/src/stdlib/intX.h @@ -88,7 +88,7 @@ MACROLIKE PUREFUNC INTX_T NAMESPACED(unsigned_right_shifted)(INTX_T x, INTX_T y) void NAMESPACED(serialize)(const void *obj, FILE *out, Table_t *, const TypeInfo_t *); void NAMESPACED(deserialize)(FILE *in, void *outval, List_t *, const TypeInfo_t *); -MACROLIKE PUREFUNC INTX_T NAMESPACED(from_num)(Num_t n, bool truncate) { +MACROLIKE PUREFUNC INTX_T NAMESPACED(from_num64)(Num_t n, bool truncate) { INTX_T i = (INTX_T)n; if (!truncate && unlikely((Num_t)i != n)) fail("Could not convert Num to an " NAME_STR " without truncation: ", n); return i; -- cgit v1.2.3 From af585cf127a26148372fe54eb62d1c9fad6f2dba Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Mon, 24 Nov 2025 22:14:15 -0500 Subject: Fix for `when` blocks with a single expression inside --- src/compile/promotions.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'src') diff --git a/src/compile/promotions.c b/src/compile/promotions.c index b3cbb361..93ad5338 100644 --- a/src/compile/promotions.c +++ b/src/compile/promotions.c @@ -127,6 +127,10 @@ Text_t compile_to_type(env_t *env, ast_t *ast, type_t *t) { env = with_enum_scope(env, t); } + if (ast->tag == Block && Match(ast, Block)->statements && !Match(ast, Block)->statements->next) { + ast = Match(ast, Block)->statements->ast; + } + if (ast->tag == Int && is_numeric_type(non_optional(t))) { return compile_int_to_type(env, ast, t); } else if (ast->tag == Num && t->tag == NumType) { -- cgit v1.2.3 From 14c8cf34dd75fcf49cc56025efa93dd32e1958fd Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Mon, 24 Nov 2025 22:46:59 -0500 Subject: Fix for accidental deserialization of byte array when it should have been promoted to optional --- src/compile/promotions.c | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/src/compile/promotions.c b/src/compile/promotions.c index 93ad5338..5b0ccb95 100644 --- a/src/compile/promotions.c +++ b/src/compile/promotions.c @@ -26,18 +26,21 @@ bool promote(env_t *env, ast_t *ast, Text_t *code, type_t *actual, type_t *neede if (more_complete) return true; // Serialization/deserialization: - if (type_eq(needed, Type(ListType, Type(ByteType)))) { - *code = Texts("generic_serialize((", compile_declaration(actual, Text("[1]")), "){", *code, "}, ", - compile_type_info(actual), ")"); - return true; - } else if (type_eq(actual, Type(ListType, Type(ByteType)))) { - *code = Texts("({ ", compile_declaration(needed, Text("deserialized")), - ";\n" - "generic_deserialize(", - *code, ", &deserialized, ", compile_type_info(needed), - ");\n" - "deserialized; })"); - return true; + if (!type_eq(non_optional(value_type(needed)), Type(ListType, Type(ByteType))) + || !type_eq(non_optional(value_type(actual)), Type(ListType, Type(ByteType)))) { + if (type_eq(needed, Type(ListType, Type(ByteType)))) { + *code = Texts("generic_serialize((", compile_declaration(actual, Text("[1]")), "){", *code, "}, ", + compile_type_info(actual), ")"); + return true; + } else if (type_eq(actual, Type(ListType, Type(ByteType)))) { + *code = Texts("({ ", compile_declaration(needed, Text("deserialized")), + ";\n" + "generic_deserialize(", + *code, ", &deserialized, ", compile_type_info(needed), + ");\n" + "deserialized; })"); + return true; + } } // Optional promotion: -- cgit v1.2.3 From a21f9ddfd05c643049c22bb52ab3a60f41933492 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Wed, 26 Nov 2025 21:04:12 -0500 Subject: Bugfix for accidental violation of immutable value guarantees due to inner field members --- src/compile/assignments.c | 10 +++++----- src/compile/enums.c | 9 +++++++-- src/compile/expressions.c | 38 +++++++++++++++++++++++++++++++++----- src/compile/pointers.c | 4 ++-- src/types.c | 21 +++++++++++++++++++++ src/types.h | 1 + 6 files changed, 69 insertions(+), 14 deletions(-) (limited to 'src') diff --git a/src/compile/assignments.c b/src/compile/assignments.c index c0f45f5b..74a00e0b 100644 --- a/src/compile/assignments.c +++ b/src/compile/assignments.c @@ -101,7 +101,7 @@ Text_t compile_assignment_statement(env_t *env, ast_t *ast) { "variable's scope may outlive the scope of the " "stack memory."); env_t *val_env = with_enum_scope(env, lhs_t); - Text_t val = compile_to_type(val_env, assign->values->ast, lhs_t); + Text_t val = compile_maybe_incref(val_env, assign->values->ast, lhs_t); return Texts(compile_assignment(env, assign->targets->ast, val), ";\n"); } @@ -120,7 +120,7 @@ Text_t compile_assignment_statement(env_t *env, ast_t *ast) { "variable's scope may outlive the scope of the " "stack memory."); env_t *val_env = with_enum_scope(env, lhs_t); - Text_t val = compile_to_type(val_env, value->ast, lhs_t); + Text_t val = compile_maybe_incref(val_env, value->ast, lhs_t); code = Texts(code, compile_type(lhs_t), " $", i, " = ", val, ";\n"); i += 1; } @@ -178,13 +178,13 @@ Text_t compile_lvalue(env_t *env, ast_t *ast) { type_t *value_type = get_type(env, table_type->default_value); return Texts("*Table$get_or_setdefault(", compile_to_pointer_depth(env, index->indexed, 1, false), ", ", compile_type(table_type->key_type), ", ", compile_type(value_type), ", ", - compile_to_type(env, index->index, table_type->key_type), ", ", - compile_to_type(env, table_type->default_value, table_type->value_type), ", ", + compile_maybe_incref(env, index->index, table_type->key_type), ", ", + compile_maybe_incref(env, table_type->default_value, table_type->value_type), ", ", compile_type_info(container_t), ")"); } return Texts("*(", compile_type(Type(PointerType, table_type->value_type)), ")Table$reserve(", compile_to_pointer_depth(env, index->indexed, 1, false), ", ", "stack(", - compile_to_type(env, index->index, table_type->key_type), ")", ", NULL,", + compile_maybe_incref(env, index->index, table_type->key_type), ")", ", NULL,", compile_type_info(container_t), ")"); } else { code_err(ast, "I don't know how to assign to this target"); diff --git a/src/compile/enums.c b/src/compile/enums.c index 31af96ad..56d6432a 100644 --- a/src/compile/enums.c +++ b/src/compile/enums.c @@ -157,10 +157,15 @@ Text_t compile_enum_field_access(env_t *env, ast_t *ast) { if (streq(f->field, tag->name)) { Text_t tag_name = namespace_name(e->env, e->env->namespace, Texts("tag$", tag->name)); if (tag->type != NULL && Match(tag->type, StructType)->fields) { + Text_t member = compile_maybe_incref( + env, + WrapAST(ast, InlineCCode, + .chunks = new (ast_list_t, WrapAST(ast, TextLiteral, Texts("_e.", tag->name))), + .type = tag->type), + tag->type); return Texts("({ ", compile_declaration(value_t, Text("_e")), " = ", compile_to_pointer_depth(env, f->fielded, 0, false), "; ", "_e.$tag == ", tag_name, " ? ", - promote_to_optional(tag->type, Texts("_e.", tag->name)), " : ", compile_none(tag->type), - "; })"); + promote_to_optional(tag->type, member), " : ", compile_none(tag->type), "; })"); } else if (fielded_t->tag == PointerType) { Text_t fielded = compile_to_pointer_depth(env, f->fielded, 1, false); return Texts("((", fielded, ")->$tag == ", tag_name, " ? OPTIONAL_EMPTY_STRUCT : NONE_EMPTY_STRUCT)"); diff --git a/src/compile/expressions.c b/src/compile/expressions.c index 130a267c..a69a7d56 100644 --- a/src/compile/expressions.c +++ b/src/compile/expressions.c @@ -11,11 +11,39 @@ public Text_t compile_maybe_incref(env_t *env, ast_t *ast, type_t *t) { - if (is_idempotent(ast) && can_be_mutated(env, ast)) { - type_t *actual = get_type(with_enum_scope(env, t), ast); - if (t->tag == ListType && type_eq(t, actual)) return Texts("LIST_COPY(", compile_to_type(env, ast, t), ")"); - else if (t->tag == TableType && type_eq(t, actual)) - return Texts("TABLE_COPY(", compile_to_type(env, ast, t), ")"); + if (!has_refcounts(t) || !can_be_mutated(env, ast)) { + return compile_to_type(env, ast, t); + } + + // When using a struct as a value, we need to increment the refcounts of the inner fields as well: + if (t->tag == StructType) { + // If the struct is non-idempotent, we have to stash it in a local var first + if (is_idempotent(ast)) { + Text_t code = Texts("((", compile_type(t), "){"); + for (arg_t *field = Match(t, StructType)->fields; field; field = field->next) { + Text_t val = compile_maybe_incref(env, WrapAST(ast, FieldAccess, .fielded = ast, .field = field->name), + get_arg_type(env, field)); + code = Texts(code, val); + if (field->next) code = Texts(code, ", "); + } + return Texts(code, "})"); + } else { + Text_t code = Texts("({ ", compile_declaration(t, Text("_tmp")), " = ", compile_to_type(env, ast, t), "; ", + "((", compile_type(t), "){"); + ast_t *tmp = WrapAST(ast, InlineCCode, + .chunks = new (ast_list_t, .ast = WrapAST(ast, TextLiteral, Text("_tmp"))), .type = t); + for (arg_t *field = Match(t, StructType)->fields; field; field = field->next) { + Text_t val = compile_maybe_incref(env, WrapAST(ast, FieldAccess, .fielded = tmp, .field = field->name), + get_arg_type(env, field)); + code = Texts(code, val); + if (field->next) code = Texts(code, ", "); + } + return Texts(code, "}); })"); + } + } else if (t->tag == ListType && ast->tag != List && can_be_mutated(env, ast) && type_eq(get_type(env, ast), t)) { + return Texts("LIST_COPY(", compile_to_type(env, ast, t), ")"); + } else if (t->tag == TableType && ast->tag != Table && can_be_mutated(env, ast) && type_eq(get_type(env, ast), t)) { + return Texts("TABLE_COPY(", compile_to_type(env, ast, t), ")"); } return compile_to_type(env, ast, t); } diff --git a/src/compile/pointers.c b/src/compile/pointers.c index 11348330..98274cc8 100644 --- a/src/compile/pointers.c +++ b/src/compile/pointers.c @@ -55,13 +55,13 @@ Text_t compile_typed_allocation(env_t *env, ast_t *ast, type_t *pointer_type) { type_t *pointed = Match(pointer_type, PointerType)->pointed; switch (ast->tag) { case HeapAllocate: { - return Texts("heap(", compile_to_type(env, Match(ast, HeapAllocate)->value, pointed), ")"); + return Texts("heap(", compile_maybe_incref(env, Match(ast, HeapAllocate)->value, pointed), ")"); } case StackReference: { ast_t *subject = Match(ast, StackReference)->value; if (can_be_mutated(env, subject) && type_eq(pointed, get_type(env, subject))) return Texts("(&", compile_lvalue(env, subject), ")"); - else return Texts("stack(", compile_to_type(env, subject, pointed), ")"); + else return Texts("stack(", compile_maybe_incref(env, subject, pointed), ")"); } default: code_err(ast, "Not an allocation!"); } diff --git a/src/types.c b/src/types.c index edfee27d..4d15d493 100644 --- a/src/types.c +++ b/src/types.c @@ -249,6 +249,27 @@ PUREFUNC bool has_heap_memory(type_t *t) { } } +PUREFUNC bool has_refcounts(type_t *t) { + switch (t->tag) { + case ListType: return true; + case TableType: return true; + case OptionalType: return has_refcounts(Match(t, OptionalType)->type); + case StructType: { + for (arg_t *field = Match(t, StructType)->fields; field; field = field->next) { + if (has_refcounts(field->type)) return true; + } + return false; + } + case EnumType: { + for (tag_t *tag = Match(t, EnumType)->tags; tag; tag = tag->next) { + if (tag->type && has_refcounts(tag->type)) return true; + } + return false; + } + default: return false; + } +} + PUREFUNC bool has_stack_memory(type_t *t) { if (!t) return false; switch (t->tag) { diff --git a/src/types.h b/src/types.h index b8f9a42f..8a2175d9 100644 --- a/src/types.h +++ b/src/types.h @@ -150,6 +150,7 @@ typedef enum { } precision_cmp_e; PUREFUNC precision_cmp_e compare_precision(type_t *a, type_t *b); PUREFUNC bool has_heap_memory(type_t *t); +PUREFUNC bool has_refcounts(type_t *t); PUREFUNC bool has_stack_memory(type_t *t); PUREFUNC bool can_promote(type_t *actual, type_t *needed); PUREFUNC const char *enum_single_value_tag(type_t *enum_type, type_t *t); -- cgit v1.2.3 From 3ad07260f369dc285e5ae856b78a58c3b13ee622 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Wed, 26 Nov 2025 21:12:11 -0500 Subject: Close loophole in `when` statements that allowed mutating inner field members --- src/ast.h | 3 +++ src/compile/expressions.c | 3 +-- src/compile/whens.c | 12 ++++++++---- 3 files changed, 12 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/ast.h b/src/ast.h index 7fa9092a..5307fc2c 100644 --- a/src/ast.h +++ b/src/ast.h @@ -23,6 +23,9 @@ #define LiteralCode(code, ...) \ new (ast_t, .tag = InlineCCode, \ .__data.InlineCCode = {.chunks = new (ast_list_t, .ast = FakeAST(TextLiteral, code)), __VA_ARGS__}) +#define WrapLiteralCode(ast, code, ...) \ + new (ast_t, .tag = InlineCCode, .file = (ast)->file, .start = (ast)->start, .end = (ast)->end, \ + .__data.InlineCCode = {.chunks = new (ast_list_t, .ast = WrapAST(ast, TextLiteral, code)), __VA_ARGS__}) #define Match(x, _tag) \ ((x)->tag == _tag ? &(x)->__data._tag \ : (errx(1, __FILE__ ":%d This was supposed to be a " #_tag "\n", __LINE__), &(x)->__data._tag)) diff --git a/src/compile/expressions.c b/src/compile/expressions.c index a69a7d56..19e0672e 100644 --- a/src/compile/expressions.c +++ b/src/compile/expressions.c @@ -30,8 +30,7 @@ Text_t compile_maybe_incref(env_t *env, ast_t *ast, type_t *t) { } else { Text_t code = Texts("({ ", compile_declaration(t, Text("_tmp")), " = ", compile_to_type(env, ast, t), "; ", "((", compile_type(t), "){"); - ast_t *tmp = WrapAST(ast, InlineCCode, - .chunks = new (ast_list_t, .ast = WrapAST(ast, TextLiteral, Text("_tmp"))), .type = t); + ast_t *tmp = WrapLiteralCode(ast, Text("_tmp"), .type = t); for (arg_t *field = Match(t, StructType)->fields; field; field = field->next) { Text_t val = compile_maybe_incref(env, WrapAST(ast, FieldAccess, .fielded = tmp, .field = field->name), get_arg_type(env, field)); diff --git a/src/compile/whens.c b/src/compile/whens.c index 4f6a2a40..122c581c 100644 --- a/src/compile/whens.c +++ b/src/compile/whens.c @@ -83,8 +83,10 @@ Text_t compile_when_statement(env_t *env, ast_t *ast) { const char *var_name = Match(args->value, Var)->name; if (!streq(var_name, "_")) { Text_t var = Texts("_$", var_name); - code = Texts(code, compile_declaration(tag_type, var), " = _when_subject.", - valid_c_name(clause_tag_name), ";\n"); + ast_t *member = + WrapLiteralCode(ast, Texts("_when_subject.", valid_c_name(clause_tag_name)), .type = tag_type); + code = Texts(code, compile_declaration(tag_type, var), " = ", + compile_maybe_incref(env, member, tag_type), ";\n"); scope = fresh_scope(scope); set_binding(scope, Match(args->value, Var)->name, tag_type, EMPTY_TEXT); } @@ -101,8 +103,10 @@ Text_t compile_when_statement(env_t *env, ast_t *ast) { const char *var_name = Match(arg->value, Var)->name; if (!streq(var_name, "_")) { Text_t var = Texts("_$", var_name); - code = Texts(code, compile_declaration(field->type, var), " = _when_subject.", - valid_c_name(clause_tag_name), ".", valid_c_name(field->name), ";\n"); + ast_t *member = + WrapLiteralCode(ast, Texts("_when_subject.", valid_c_name(clause_tag_name)), .type = tag_type); + code = Texts(code, compile_declaration(field->type, var), " = ", + compile_maybe_incref(env, member, tag_type), ".", valid_c_name(field->name), ";\n"); set_binding(scope, Match(arg->value, Var)->name, field->type, var); } field = field->next; -- cgit v1.2.3 From 437be558a893ac70c030794df99a866e8ed01879 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Thu, 27 Nov 2025 12:05:49 -0500 Subject: Add `recursive` arg to Path.create_directory() --- src/environment.c | 3 ++- src/modules.c | 4 ++-- src/stdlib/paths.c | 12 ++++++++++-- src/stdlib/paths.h | 2 +- 4 files changed, 15 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/environment.c b/src/environment.c index 6075ce49..88a15bb5 100644 --- a/src/environment.c +++ b/src/environment.c @@ -310,7 +310,8 @@ env_t *global_env(bool source_mapping) { {"child", "Path$child", "func(path:Path, child:Text -> Path)"}, // {"children", "Path$children", "func(path:Path, include_hidden=no -> [Path])"}, // {"concatenated_with", "Path$concat", "func(a,b:Path -> Path)"}, // - {"create_directory", "Path$create_directory", "func(path:Path, permissions=Int32(0o755))"}, // + {"create_directory", "Path$create_directory", + "func(path:Path, permissions=Int32(0o755), recursive=yes)"}, // {"current_dir", "Path$current_dir", "func(->Path)"}, // {"exists", "Path$exists", "func(path:Path -> Bool)"}, // {"expand_home", "Path$expand_home", "func(path:Path -> Path)"}, // diff --git a/src/modules.c b/src/modules.c index 23b8a0a0..df6bade3 100644 --- a/src/modules.c +++ b/src/modules.c @@ -153,10 +153,10 @@ bool try_install_module(module_info_t mod, bool ask_confirmation) { const char *extension = p + 1; Path_t tmpdir = Path$unique_directory(Path("/tmp/tomo-module-XXXXXX")); tmpdir = Path$child(tmpdir, Text$from_str(mod.name)); - Path$create_directory(tmpdir, 0755); + Path$create_directory(tmpdir, 0755, true); xsystem("curl ", mod.url, " -o ", tmpdir); - Path$create_directory(dest, 0755); + Path$create_directory(dest, 0755, true); if (streq(extension, ".zip")) xsystem("unzip ", tmpdir, "/", filename, " -d ", dest); else if (streq(extension, ".tar.gz") || streq(extension, ".tar")) xsystem("tar xf ", tmpdir, "/", filename, " -C ", dest); diff --git a/src/stdlib/paths.c b/src/stdlib/paths.c index 22effad7..6c99cf0b 100644 --- a/src/stdlib/paths.c +++ b/src/stdlib/paths.c @@ -468,11 +468,19 @@ void Path$remove(Path_t path, bool ignore_missing) { } public -void Path$create_directory(Path_t path, int permissions) { +void 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) { + fail("Could not create directory: ", c_path, " (", strerror(errno), ")"); + } + } } static List_t _filtered_children(Path_t path, bool include_hidden, mode_t filter) { diff --git a/src/stdlib/paths.h b/src/stdlib/paths.h index 3b1f3ce6..677631b2 100644 --- a/src/stdlib/paths.h +++ b/src/stdlib/paths.h @@ -41,7 +41,7 @@ void Path$set_owner(Path_t path, OptionalText_t owner, OptionalText_t group, boo 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); +void 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); -- cgit v1.2.3 From 8b897851facaa177e2346e0d97fcba7411dfc0aa Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Thu, 27 Nov 2025 12:35:52 -0500 Subject: Update `setenv()` to take an optional value, also bugfix for `setenv()` returning a value. --- src/environment.c | 2 +- src/stdlib/stdlib.c | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/environment.c b/src/environment.c index 88a15bb5..43d22c3d 100644 --- a/src/environment.c +++ b/src/environment.c @@ -540,7 +540,7 @@ env_t *global_env(bool source_mapping) { {"getenv", "getenv_text", "func(name:Text -> Text?)"}, {"print", "say", "func(text:Text, newline=yes)"}, {"say", "say", "func(text:Text, newline=yes)"}, - {"setenv", "setenv_text", "func(name:Text, value:Text -> Text?)"}, + {"setenv", "setenv_text", "func(name:Text, value:Text?)"}, {"sleep", "sleep_num", "func(seconds:Num)"}, }; diff --git a/src/stdlib/stdlib.c b/src/stdlib/stdlib.c index 21547efe..8ec9e90b 100644 --- a/src/stdlib/stdlib.c +++ b/src/stdlib/stdlib.c @@ -219,7 +219,10 @@ 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) { + if (value.tag == TEXT_NONE) unsetenv(Text$as_c_string(name)); + else setenv(Text$as_c_string(name), Text$as_c_string(value), 1); +} typedef struct cleanup_s { Closure_t cleanup_fn; -- cgit v1.2.3 From 7e85117099cc77a8c083a3479246d5e130b8afe1 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Thu, 27 Nov 2025 12:53:24 -0500 Subject: Support EXECUTABLE metadata --- src/tomo.c | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/tomo.c b/src/tomo.c index 66ac0ddf..1aa62a0e 100644 --- a/src/tomo.c +++ b/src/tomo.c @@ -116,6 +116,7 @@ static bool is_stale_for_any(Path_t path, List_t relative_to, bool ignore_missin static Path_t build_file(Path_t path, const char *extension); static void wait_for_child_success(pid_t child); static bool is_config_outdated(Path_t path); +static Path_t get_exe_path(Path_t path); typedef struct { bool h : 1, c : 1, o : 1; @@ -368,7 +369,9 @@ int main(int argc, char *argv[]) { for (int64_t i = 0; i < (int64_t)compile_executables.length; i++) { Path_t path = *(Path_t *)(compile_executables.data + i * compile_executables.stride); - Path_t exe_path = Path$with_extension(path, Text(""), true); + Path_t exe_path = get_exe_path(path); + // Put executable as a sibling to the .tm file instead of in the .build directory + exe_path = Path$sibling(path, Path$base_name(exe_path)); pid_t child = fork(); if (child == 0) { env_t *env = global_env(source_mapping); @@ -399,7 +402,7 @@ int main(int argc, char *argv[]) { // Compile runnable files in parallel, then execute in serial: for (int64_t i = 0; i < (int64_t)run_files.length; i++) { Path_t path = *(Path_t *)(run_files.data + i * run_files.stride); - Path_t exe_path = build_file(Path$with_extension(path, Text(""), true), ""); + Path_t exe_path = get_exe_path(path); pid_t child = fork(); if (child == 0) { env_t *env = global_env(source_mapping); @@ -418,7 +421,7 @@ int main(int argc, char *argv[]) { // After parallel compilation, do serial execution: for (int64_t i = 0; i < (int64_t)run_files.length; i++) { Path_t path = *(Path_t *)(run_files.data + i * run_files.stride); - Path_t exe_path = build_file(Path$with_extension(path, Text(""), true), ""); + Path_t exe_path = get_exe_path(path); // Don't fork for the last program pid_t child = i == (int64_t)run_files.length - 1 ? 0 : fork(); if (child == 0) { @@ -449,6 +452,18 @@ void wait_for_child_success(pid_t child) { } } +Path_t get_exe_path(Path_t path) { + ast_t *ast = parse_file(Path$as_c_string(path), NULL); + OptionalText_t exe_name = ast_metadata(ast, "EXECUTABLE"); + if (exe_name.tag == TEXT_NONE) exe_name = Path$base_name(Path$with_extension(path, Text(""), true)); + + Path_t build_dir = Path$sibling(path, Text(".build")); + if (mkdir(Path$as_c_string(build_dir), 0755) != 0) { + if (!Path$is_directory(build_dir, true)) err(1, "Could not make .build directory"); + } + return Path$child(build_dir, exe_name); +} + Path_t build_file(Path_t path, const char *extension) { Path_t build_dir = Path$sibling(path, Text(".build")); if (mkdir(Path$as_c_string(build_dir), 0755) != 0) { -- cgit v1.2.3 From 69067d2512ea495224508a854ab617bed0ea5ab5 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Thu, 27 Nov 2025 13:37:22 -0500 Subject: Bugfix for infinite recursion in some type methods --- src/types.c | 59 +++++++++++++++++++++++++++++++++++++++++++++-------------- src/types.h | 6 +++--- 2 files changed, 48 insertions(+), 17 deletions(-) (limited to 'src') diff --git a/src/types.c b/src/types.c index 4d15d493..04784735 100644 --- a/src/types.c +++ b/src/types.c @@ -8,6 +8,7 @@ #include "environment.h" #include "stdlib/integers.h" +#include "stdlib/tables.h" #include "stdlib/text.h" #include "stdlib/util.h" #include "types.h" @@ -226,22 +227,27 @@ PUREFUNC precision_cmp_e compare_precision(type_t *a, type_t *b) { else return NUM_PRECISION_INCOMPARABLE; } -PUREFUNC bool has_heap_memory(type_t *t) { +bool _has_heap_memory(type_t *t, Table_t *visited) { + if (!t) return false; + Text_t type_text = type_to_text(t); + if (Table$get(*visited, &type_text, Set$info(&Text$info))) return false; + Table$set(visited, &type_text, NULL, Set$info(&Text$info)); + switch (t->tag) { case ListType: return true; case TableType: return true; case PointerType: return true; - case OptionalType: return has_heap_memory(Match(t, OptionalType)->type); + case OptionalType: return _has_heap_memory(Match(t, OptionalType)->type, visited); case BigIntType: return true; case StructType: { for (arg_t *field = Match(t, StructType)->fields; field; field = field->next) { - if (has_heap_memory(field->type)) return true; + if (_has_heap_memory(field->type, visited)) return true; } return false; } case EnumType: { for (tag_t *tag = Match(t, EnumType)->tags; tag; tag = tag->next) { - if (tag->type && has_heap_memory(tag->type)) return true; + if (tag->type && _has_heap_memory(tag->type, visited)) return true; } return false; } @@ -249,20 +255,30 @@ PUREFUNC bool has_heap_memory(type_t *t) { } } -PUREFUNC bool has_refcounts(type_t *t) { +bool has_heap_memory(type_t *t) { + Table_t visited = EMPTY_TABLE; + return _has_heap_memory(t, &visited); +} + +bool _has_refcounts(type_t *t, Table_t *visited) { + if (!t) return false; + Text_t type_text = type_to_text(t); + if (Table$get(*visited, &type_text, Set$info(&Text$info))) return false; + Table$set(visited, &type_text, NULL, Set$info(&Text$info)); + switch (t->tag) { case ListType: return true; case TableType: return true; - case OptionalType: return has_refcounts(Match(t, OptionalType)->type); + case OptionalType: return _has_refcounts(Match(t, OptionalType)->type, visited); case StructType: { for (arg_t *field = Match(t, StructType)->fields; field; field = field->next) { - if (has_refcounts(field->type)) return true; + if (_has_refcounts(field->type, visited)) return true; } return false; } case EnumType: { for (tag_t *tag = Match(t, EnumType)->tags; tag; tag = tag->next) { - if (tag->type && has_refcounts(tag->type)) return true; + if (tag->type && _has_refcounts(tag->type, visited)) return true; } return false; } @@ -270,23 +286,33 @@ PUREFUNC bool has_refcounts(type_t *t) { } } -PUREFUNC bool has_stack_memory(type_t *t) { +bool has_refcounts(type_t *t) { + Table_t visited = EMPTY_TABLE; + return _has_refcounts(t, &visited); +} + +bool _has_stack_memory(type_t *t, Table_t *visited) { if (!t) return false; + Text_t type_text = type_to_text(t); + if (Table$get(*visited, &type_text, Set$info(&Text$info))) return false; + Table$set(visited, &type_text, NULL, Set$info(&Text$info)); + switch (t->tag) { case PointerType: return Match(t, PointerType)->is_stack; - case OptionalType: return has_stack_memory(Match(t, OptionalType)->type); - case ListType: return has_stack_memory(Match(t, ListType)->item_type); + case OptionalType: return _has_stack_memory(Match(t, OptionalType)->type, visited); + case ListType: return _has_stack_memory(Match(t, ListType)->item_type, visited); case TableType: - return has_stack_memory(Match(t, TableType)->key_type) || has_stack_memory(Match(t, TableType)->value_type); + return _has_stack_memory(Match(t, TableType)->key_type, visited) + || _has_stack_memory(Match(t, TableType)->value_type, visited); case StructType: { for (arg_t *field = Match(t, StructType)->fields; field; field = field->next) { - if (has_stack_memory(field->type)) return true; + if (_has_stack_memory(field->type, visited)) return true; } return false; } case EnumType: { for (tag_t *tag = Match(t, EnumType)->tags; tag; tag = tag->next) { - if (tag->type && has_stack_memory(tag->type)) return true; + if (tag->type && _has_stack_memory(tag->type, visited)) return true; } return false; } @@ -294,6 +320,11 @@ PUREFUNC bool has_stack_memory(type_t *t) { } } +bool has_stack_memory(type_t *t) { + Table_t visited = EMPTY_TABLE; + return _has_stack_memory(t, &visited); +} + PUREFUNC const char *enum_single_value_tag(type_t *enum_type, type_t *t) { const char *found = NULL; for (tag_t *tag = Match(enum_type, EnumType)->tags; tag; tag = tag->next) { diff --git a/src/types.h b/src/types.h index 8a2175d9..76632c66 100644 --- a/src/types.h +++ b/src/types.h @@ -149,9 +149,9 @@ typedef enum { NUM_PRECISION_INCOMPARABLE } precision_cmp_e; PUREFUNC precision_cmp_e compare_precision(type_t *a, type_t *b); -PUREFUNC bool has_heap_memory(type_t *t); -PUREFUNC bool has_refcounts(type_t *t); -PUREFUNC bool has_stack_memory(type_t *t); +bool has_heap_memory(type_t *t); +bool has_refcounts(type_t *t); +bool has_stack_memory(type_t *t); PUREFUNC bool can_promote(type_t *actual, type_t *needed); PUREFUNC const char *enum_single_value_tag(type_t *enum_type, type_t *t); PUREFUNC bool is_int_type(type_t *t); -- cgit v1.2.3 From d2e82367925c7e10504c1caec1605806bb364629 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Thu, 27 Nov 2025 14:25:45 -0500 Subject: Bugfix for Num32$as_text() visibility --- src/stdlib/numX.c.h | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/stdlib/numX.c.h b/src/stdlib/numX.c.h index 7b030ab4..0e84708f 100644 --- a/src/stdlib/numX.c.h +++ b/src/stdlib/numX.c.h @@ -67,6 +67,7 @@ PUREFUNC int32_t NAMESPACED(compare)(const void *x, const void *y, const TypeInf #elif NUMX_C_H__BITS == 32 public PUREFUNC Text_t NAMESPACED(value_as_text)(NUM_T x) { return Num$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); -- cgit v1.2.3 From 06f5270edfa8231025125909880af27d42e2e52b Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sat, 29 Nov 2025 11:14:14 -0500 Subject: Fix accidental naming collision with _tmp var --- src/compile/expressions.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/compile/expressions.c b/src/compile/expressions.c index 19e0672e..8b339352 100644 --- a/src/compile/expressions.c +++ b/src/compile/expressions.c @@ -28,9 +28,12 @@ Text_t compile_maybe_incref(env_t *env, ast_t *ast, type_t *t) { } return Texts(code, "})"); } else { - Text_t code = Texts("({ ", compile_declaration(t, Text("_tmp")), " = ", compile_to_type(env, ast, t), "; ", + static int64_t tmp_index = 1; + Text_t tmp_name = Texts("_tmp", tmp_index); + tmp_index += 1; + Text_t code = Texts("({ ", compile_declaration(t, tmp_name), " = ", compile_to_type(env, ast, t), "; ", "((", compile_type(t), "){"); - ast_t *tmp = WrapLiteralCode(ast, Text("_tmp"), .type = t); + ast_t *tmp = WrapLiteralCode(ast, tmp_name, .type = t); for (arg_t *field = Match(t, StructType)->fields; field; field = field->next) { Text_t val = compile_maybe_incref(env, WrapAST(ast, FieldAccess, .fielded = tmp, .field = field->name), get_arg_type(env, field)); -- cgit v1.2.3 From e22fc67de035d4f998758ffcbb99515f47a8375a Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sat, 29 Nov 2025 11:20:15 -0500 Subject: Bugfix for default CLI argument initialization --- src/compile/cli.c | 1 - 1 file changed, 1 deletion(-) (limited to 'src') diff --git a/src/compile/cli.c b/src/compile/cli.c index 7cd3cc7c..a66fb47d 100644 --- a/src/compile/cli.c +++ b/src/compile/cli.c @@ -167,7 +167,6 @@ Text_t compile_cli_arg_call(env_t *env, ast_t *ast, Text_t fn_name, type_t *fn_t Text_t default_val; if (arg->type) { default_val = compile_to_type(env, arg->default_val, arg->type); - if (arg->type->tag != OptionalType) default_val = promote_to_optional(arg->type, default_val); } else { default_val = compile(env, arg->default_val); } -- cgit v1.2.3 From 6db7b88c930657c38512709f808b422d39484a12 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sat, 29 Nov 2025 13:47:23 -0500 Subject: Fix alignment --- src/tomo.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/tomo.c b/src/tomo.c index 1aa62a0e..e1743d47 100644 --- a/src/tomo.c +++ b/src/tomo.c @@ -195,8 +195,8 @@ int main(int argc, char *argv[]) { Text_t usage = Texts("\x1b[33;4;1mUsage:\x1b[m\n" "\x1b[1mRun a program:\x1b[m tomo file.tm [-- args...]\n" "\x1b[1mTranspile files:\x1b[m tomo -t file.tm\n" - "\x1b[1mCompile object file:\x1b[m tomo -c file.tm\n" - "\x1b[1mCompile executable:\x1b[m tomo -e file.tm\n" + "\x1b[1mCompile object file:\x1b[m tomo -c file.tm\n" + "\x1b[1mCompile executable:\x1b[m tomo -e file.tm\n" "\x1b[1mBuild libraries:\x1b[m tomo -L lib...\n" "\x1b[1mUninstall libraries:\x1b[m tomo -u lib...\n" "\x1b[1mOther flags:\x1b[m\n" -- cgit v1.2.3 From bb2f890fd470fff3e42698710b56c68164491d85 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sat, 29 Nov 2025 14:18:07 -0500 Subject: Overhaul to versioning system (paths go to `/tomo@TOMOVERSION/lib@LIBVERSION` instead of using underscores. Tomo versioning now uses date-based versions. --- src/compile/files.c | 2 +- src/compile/headers.c | 6 +++--- src/compile/statements.c | 4 ++-- src/modules.c | 10 +++++----- src/stdlib/stacktrace.c | 2 +- src/tomo.c | 22 +++++++++++----------- src/typecheck.c | 4 ++-- 7 files changed, 25 insertions(+), 25 deletions(-) (limited to 'src') diff --git a/src/compile/files.c b/src/compile/files.c index b916f23f..03c08bf6 100644 --- a/src/compile/files.c +++ b/src/compile/files.c @@ -194,7 +194,7 @@ Text_t compile_file(env_t *env, ast_t *ast) { const char *name = file_base_name(ast->file->filename); return Texts(env->do_source_mapping ? Texts("#line 1 ", quoted_str(ast->file->filename), "\n") : EMPTY_TEXT, "#define __SOURCE_FILE__ ", quoted_str(ast->file->filename), "\n", - "#include \n" + "#include \n" "#include \"", name, ".tm.h\"\n\n", includes, env->code->local_typedefs, "\n", env->code->lambdas, "\n", env->code->staticdefs, "\n", top_level_code, "public void ", diff --git a/src/compile/headers.c b/src/compile/headers.c index f132b312..1dcf7abb 100644 --- a/src/compile/headers.c +++ b/src/compile/headers.c @@ -158,7 +158,7 @@ Text_t compile_file_header(env_t *env, Path_t header_path, ast_t *ast) { Text_t header = Texts("#pragma once\n", env->do_source_mapping ? Texts("#line 1 ", quoted_str(ast->file->filename), "\n") : EMPTY_TEXT, - "#include \n"); + "#include \n"); compile_typedef_info_t info = {.env = env, .header = &header, .header_path = header_path}; visit_topologically(Match(ast, Block)->statements, (Closure_t){.fn = (void *)_make_typedefs, &info}); @@ -183,8 +183,8 @@ Text_t compile_statement_type_header(env_t *env, Path_t header_path, ast_t *ast) case USE_MODULE: { module_info_t mod = get_used_module_info(ast); glob_t tm_files; - const char *folder = mod.version ? String(mod.name, "_", mod.version) : mod.name; - if (glob(String(TOMO_PATH, "/lib/tomo_" TOMO_VERSION "/", folder, "/[!._0-9]*.tm"), GLOB_TILDE, NULL, + const char *folder = mod.version ? String(mod.name, "@", mod.version) : mod.name; + if (glob(String(TOMO_PATH, "/lib/tomo@" TOMO_VERSION "/", folder, "/[!._0-9]*.tm"), GLOB_TILDE, NULL, &tm_files) != 0) { if (!try_install_module(mod, true)) code_err(ast, "Could not find library"); diff --git a/src/compile/statements.c b/src/compile/statements.c index c6ceccd9..638f1341 100644 --- a/src/compile/statements.c +++ b/src/compile/statements.c @@ -196,8 +196,8 @@ static Text_t _compile_statement(env_t *env, ast_t *ast) { } else if (use->what == USE_MODULE) { module_info_t mod = get_used_module_info(ast); glob_t tm_files; - const char *folder = mod.version ? String(mod.name, "_", mod.version) : mod.name; - if (glob(String(TOMO_PATH, "/lib/tomo_" TOMO_VERSION "/", folder, "/[!._0-9]*.tm"), GLOB_TILDE, NULL, + const char *folder = mod.version ? String(mod.name, "@", mod.version) : mod.name; + if (glob(String(TOMO_PATH, "/lib/tomo@" TOMO_VERSION "/", folder, "/[!._0-9]*.tm"), GLOB_TILDE, NULL, &tm_files) != 0) { if (!try_install_module(mod, true)) code_err(ast, "Could not find library"); diff --git a/src/modules.c b/src/modules.c index df6bade3..36952ec8 100644 --- a/src/modules.c +++ b/src/modules.c @@ -28,7 +28,7 @@ const char *get_library_version(Path_t lib_dir) { Path_t changes_file = Path$child(lib_dir, Text("CHANGES.md")); OptionalText_t changes = Path$read(changes_file); if (changes.length <= 0) { - return "v0.0"; + return "v0"; } const char *changes_str = Text$as_c_string(Texts(Text("\n"), changes)); const char *version_line = strstr(changes_str, "\n## "); @@ -41,7 +41,7 @@ Text_t get_library_name(Path_t lib_dir) { Text_t name = Path$base_name(lib_dir); name = Text$without_prefix(name, Text("tomo-")); name = Text$without_suffix(name, Text("-tomo")); - Text_t suffix = Texts(Text("_"), Text$from_str(get_library_version(lib_dir))); + Text_t suffix = Texts(Text("@"), Text$from_str(get_library_version(lib_dir))); if (!Text$ends_with(name, suffix, NULL)) name = Texts(name, suffix); return name; } @@ -102,7 +102,7 @@ module_info_t get_used_module_info(ast_t *use) { const char *name = Match(use, Use)->path; module_info_t *info = new (module_info_t, .name = name); Path_t tomo_default_modules = - Path$from_text(Texts(Text$from_str(TOMO_PATH), "/lib/tomo_" TOMO_VERSION "/modules.ini")); + Path$from_text(Texts(Text$from_str(TOMO_PATH), "/lib/tomo@" TOMO_VERSION "/modules.ini")); read_modules_ini(tomo_default_modules, info); read_modules_ini(Path$sibling(Path$from_str(use->file->filename), Text("modules.ini")), info); read_modules_ini(Path$with_extension(Path$from_str(use->file->filename), Text(":modules.ini"), false), info); @@ -111,8 +111,8 @@ module_info_t get_used_module_info(ast_t *use) { } bool try_install_module(module_info_t mod, bool ask_confirmation) { - Path_t dest = Path$from_text(Texts(Text$from_str(TOMO_PATH), "/lib/tomo_" TOMO_VERSION "/", Text$from_str(mod.name), - "_", Text$from_str(mod.version))); + Path_t dest = Path$from_text(Texts(Text$from_str(TOMO_PATH), "/lib/tomo@" TOMO_VERSION "/", Text$from_str(mod.name), + "@", Text$from_str(mod.version))); if (Path$exists(dest)) return true; print("No such path: ", dest); 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/tomo.c b/src/tomo.c index e1743d47..e086166c 100644 --- a/src/tomo.c +++ b/src/tomo.c @@ -91,7 +91,7 @@ static OptionalText_t show_codegen = NONE_TEXT, " -D_BSD_SOURCE" #endif " -DGC_THREADS"), - ldlibs = Text("-lgc -lm -lgmp -lunistring -ltomo_" TOMO_VERSION), ldflags = Text(""), + ldlibs = Text("-lgc -lm -lgmp -lunistring -ltomo@" TOMO_VERSION), ldflags = Text(""), optimization = Text("2"), cc = Text(DEFAULT_C_COMPILER); static Text_t config_summary, @@ -167,7 +167,7 @@ int main(int argc, char *argv[]) { if (getenv("TOMO_PATH")) TOMO_PATH = getenv("TOMO_PATH"); - cflags = Texts("-I'", TOMO_PATH, "/include' -I'", TOMO_PATH, "/lib/tomo_" TOMO_VERSION "' ", cflags); + cflags = Texts("-I'", TOMO_PATH, "/include' -I'", TOMO_PATH, "/lib/tomo@" TOMO_VERSION "' ", cflags); // Set up environment variables: const char *PATH = getenv("PATH"); @@ -186,7 +186,7 @@ int main(int argc, char *argv[]) { // Run a tool: if ((streq(argv[1], "-r") || streq(argv[1], "--run")) && argc >= 3) { if (strcspn(argv[2], "/;$") == strlen(argv[2])) { - const char *program = String("'", TOMO_PATH, "'/lib/tomo_" TOMO_VERSION "/", argv[2], "/", argv[2]); + const char *program = String("'", TOMO_PATH, "'/lib/tomo@" TOMO_VERSION "/", argv[2], "/", argv[2]); execv(program, &argv[2]); } print_err("This is not an installed tomo program: ", argv[2]); @@ -218,7 +218,7 @@ int main(int argc, char *argv[]) { " --source-mapping|-m : toggle source mapping in generated code\n" " --changelog: show the Tomo changelog\n" " --run|-r: run a program from ", - TOMO_PATH, "/share/tomo_" TOMO_VERSION "/installed\n"); + TOMO_PATH, "/share/tomo@" TOMO_VERSION "/installed\n"); Text_t help = Texts(Text("\x1b[1mtomo\x1b[m: a compiler for the Tomo programming language"), Text("\n\n"), usage); cli_arg_t tomo_args[] = { {"run", &run_files, List$info(&Path$info), .short_flag = 'r'}, // @@ -292,7 +292,7 @@ int main(int argc, char *argv[]) { // Uninstall libraries: for (int64_t i = 0; i < (int64_t)uninstall_libraries.length; i++) { Text_t *u = (Text_t *)(uninstall_libraries.data + i * uninstall_libraries.stride); - xsystem(as_owner, "rm -rvf '", TOMO_PATH, "'/lib/tomo_" TOMO_VERSION "/", *u, " '", TOMO_PATH, "'/bin/", *u, + xsystem(as_owner, "rm -rvf '", TOMO_PATH, "'/lib/tomo@" TOMO_VERSION "/", *u, " '", TOMO_PATH, "'/bin/", *u, " '", TOMO_PATH, "'/man/man1/", *u, ".1"); print("Uninstalled ", *u); } @@ -509,7 +509,7 @@ void build_library(Path_t lib_dir) { void install_library(Path_t lib_dir) { Text_t lib_name = get_library_name(lib_dir); - Path_t dest = Path$child(Path$from_str(String(TOMO_PATH, "/lib/tomo_" TOMO_VERSION)), lib_name); + Path_t dest = Path$child(Path$from_str(String(TOMO_PATH, "/lib/tomo@" TOMO_VERSION)), lib_name); print("Installing ", lib_dir, " into ", dest); if (!Path$equal_values(lib_dir, dest)) { if (verbose) whisper("Clearing out any pre-existing version of ", lib_name); @@ -529,7 +529,7 @@ void install_library(Path_t lib_dir) { "' " ">/dev/null 2>/dev/null")); (void)result; - print("Installed \033[1m", lib_dir, "\033[m to ", TOMO_PATH, "/lib/tomo_" TOMO_VERSION "/", lib_name); + print("Installed \033[1m", lib_dir, "\033[m to ", TOMO_PATH, "/lib/tomo@" TOMO_VERSION "/", lib_name); } void compile_files(env_t *env, List_t to_compile, List_t *object_files, List_t *extra_ldlibs, compile_mode_t mode) { @@ -690,14 +690,14 @@ void build_file_dependency_graph(Path_t path, Table_t *to_compile, Table_t *to_l } case USE_MODULE: { module_info_t mod = get_used_module_info(stmt_ast); - const char *full_name = mod.version ? String(mod.name, "_", mod.version) : mod.name; - Text_t lib = Texts("-Wl,-rpath,'", TOMO_PATH, "/lib/tomo_" TOMO_VERSION "/", Text$from_str(full_name), - "' '", TOMO_PATH, "/lib/tomo_" TOMO_VERSION "/", Text$from_str(full_name), "/lib", + const char *full_name = mod.version ? String(mod.name, "@", mod.version) : mod.name; + Text_t lib = Texts("-Wl,-rpath,'", TOMO_PATH, "/lib/tomo@" TOMO_VERSION "/", Text$from_str(full_name), + "' '", TOMO_PATH, "/lib/tomo@" TOMO_VERSION "/", Text$from_str(full_name), "/lib", Text$from_str(full_name), SHARED_SUFFIX "'"); Table$set(to_link, &lib, NULL, Table$info(&Text$info, &Void$info)); List_t children = - Path$glob(Path$from_str(String(TOMO_PATH, "/lib/tomo_" TOMO_VERSION "/", full_name, "/[!._0-9]*.tm"))); + Path$glob(Path$from_str(String(TOMO_PATH, "/lib/tomo@" TOMO_VERSION "/", full_name, "/[!._0-9]*.tm"))); for (int64_t i = 0; i < (int64_t)children.length; i++) { Path_t *child = (Path_t *)(children.data + i * children.stride); Table_t discarded = {.entries = EMPTY_LIST, .fallback = to_compile}; diff --git a/src/typecheck.c b/src/typecheck.c index b89f7eca..37f4fcab 100644 --- a/src/typecheck.c +++ b/src/typecheck.c @@ -231,8 +231,8 @@ static env_t *load_module(env_t *env, ast_t *use_ast) { case USE_MODULE: { module_info_t mod = get_used_module_info(use_ast); glob_t tm_files; - const char *folder = mod.version ? String(mod.name, "_", mod.version) : mod.name; - if (glob(String(TOMO_PATH, "/lib/tomo_" TOMO_VERSION "/", folder, "/[!._0-9]*.tm"), GLOB_TILDE, NULL, &tm_files) + const char *folder = mod.version ? String(mod.name, "@", mod.version) : mod.name; + if (glob(String(TOMO_PATH, "/lib/tomo@" TOMO_VERSION "/", folder, "/[!._0-9]*.tm"), GLOB_TILDE, NULL, &tm_files) != 0) { if (!try_install_module(mod, true)) code_err(use_ast, "Couldn't find or install library: ", folder); } -- cgit v1.2.3 From 75b7ee2a08aefbdb2e2202718326fadd96ee0ef0 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sat, 29 Nov 2025 15:11:46 -0500 Subject: Fix for undefined behavior on structs/enums with padding --- src/types.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/types.c b/src/types.c index 04784735..6ed24f6c 100644 --- a/src/types.c +++ b/src/types.c @@ -459,15 +459,25 @@ PUREFUNC bool is_packed_data(type_t *t) { || t->tag == FunctionType) { return true; } else if (t->tag == StructType) { + size_t offset = 0; for (arg_t *field = Match(t, StructType)->fields; field; field = field->next) { if (!is_packed_data(field->type)) return false; + size_t align = type_align(field->type); + if (align > 0 && offset % align != 0) return false; + offset += type_size(field->type); } - return true; + size_t overall_align = type_align(t); + return overall_align == 0 || (offset % overall_align == 0); } else if (t->tag == EnumType) { + size_t offset = sizeof(int32_t); for (tag_t *tag = Match(t, EnumType)->tags; tag; tag = tag->next) { if (!is_packed_data(tag->type)) return false; + size_t align = type_align(tag->type); + if (align > 0 && offset % align != 0) return false; + offset += type_size(tag->type); } - return true; + size_t overall_align = type_align(t); + return overall_align == 0 || (offset % overall_align == 0); } else { return false; } -- cgit v1.2.3 From d302aaec38b9d295d39c4d87b53ee610bc9e0e07 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sun, 30 Nov 2025 13:08:57 -0500 Subject: Handle some text method edge cases with empty text better. --- src/stdlib/text.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/stdlib/text.c b/src/stdlib/text.c index e51af49c..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)); -- cgit v1.2.3 From 4d8aa867c7f4661167a4742fbdd865ed2449503e Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sun, 30 Nov 2025 14:12:01 -0500 Subject: Add `base` parameter to integer parsing functions --- src/compile/functions.c | 3 +- src/environment.c | 18 ++++---- src/stdlib/bigint.c | 119 ++++++++++++++++++++++++++++++++---------------- src/stdlib/bigint.h | 2 +- src/stdlib/bytes.c | 4 +- src/stdlib/bytes.h | 2 +- src/stdlib/cli.c | 10 ++-- src/stdlib/intX.c.h | 4 +- src/stdlib/intX.h | 2 +- src/typecheck.c | 3 +- 10 files changed, 106 insertions(+), 61 deletions(-) (limited to 'src') diff --git a/src/compile/functions.c b/src/compile/functions.c index cce93e3d..63d7d23d 100644 --- a/src/compile/functions.c +++ b/src/compile/functions.c @@ -6,6 +6,7 @@ #include "../stdlib/datatypes.h" #include "../stdlib/integers.h" #include "../stdlib/nums.h" +#include "../stdlib/optionals.h" #include "../stdlib/tables.h" #include "../stdlib/text.h" #include "../stdlib/util.h" @@ -703,7 +704,7 @@ Text_t compile_function(env_t *env, Text_t name_code, ast_t *ast, Text_t *static definition = Texts(definition, wrapper); } else if (cache && cache->tag == Int) { assert(args); - OptionalInt64_t cache_size = Int64$parse(Text$from_str(Match(cache, Int)->str), NULL); + OptionalInt64_t cache_size = Int64$parse(Text$from_str(Match(cache, Int)->str), NONE_INT, NULL); Text_t pop_code = EMPTY_TEXT; if (cache->tag == Int && cache_size.has_value && cache_size.value > 0) { // FIXME: this currently just deletes the first entry, but this diff --git a/src/environment.c b/src/environment.c index 43d22c3d..7e04fe79 100644 --- a/src/environment.c +++ b/src/environment.c @@ -93,7 +93,7 @@ env_t *global_env(bool source_mapping) { MAKE_TYPE("Empty", EMPTY_TYPE, Text("Empty$$type"), Text("Empty$$info")), MAKE_TYPE( // "Bool", Type(BoolType), Text("Bool_t"), Text("Bool$info"), - {"parse", "Bool$parse", "func(text:Text, remainder:&Text? = none -> Bool?)"}), + {"parse", "Bool$parse", "func(text:Text, remainder:&Text?=none -> Bool?)"}), MAKE_TYPE( // "Byte", Type(ByteType), Text("Byte_t"), Text("Byte$info"), {"get_bit", "Byte$get_bit", "func(x:Byte, bit_index:Int -> Bool)"}, // @@ -101,7 +101,7 @@ env_t *global_env(bool source_mapping) { {"is_between", "Byte$is_between", "func(x:Byte, low:Byte, high:Byte -> Bool)"}, // {"max", "Byte$max", "Byte"}, // {"min", "Byte$min", "Byte"}, // - {"parse", "Byte$parse", "func(text:Text, remainder:&Text? = none -> Byte?)"}, // + {"parse", "Byte$parse", "func(text:Text, base:Int?=none, remainder:&Text?=none -> Byte?)"}, // {"to", "Byte$to", "func(first:Byte, last:Byte, step:Int8?=none -> func(->Byte?))"}), MAKE_TYPE( // "Int", Type(BigIntType), Text("Int_t"), Text("Int$info"), {"abs", "Int$abs", "func(x:Int -> Int)"}, // @@ -126,7 +126,7 @@ env_t *global_env(bool source_mapping) { {"next_prime", "Int$next_prime", "func(x:Int -> Int)"}, // {"octal", "Int$octal", "func(i:Int, digits=0, prefix=yes -> Text)"}, // {"onward", "Int$onward", "func(first:Int,step=1 -> func(->Int?))"}, // - {"parse", "Int$parse", "func(text:Text, remainder:&Text? = none -> Int?)"}, // + {"parse", "Int$parse", "func(text:Text, base:Int?=none, remainder:&Text?=none -> Int?)"}, // {"plus", "Int$plus", "func(x,y:Int -> Int)"}, // {"power", "Int$power", "func(base:Int,exponent:Int -> Int)"}, // #if __GNU_MP_VERSION >= 6 @@ -145,7 +145,7 @@ env_t *global_env(bool source_mapping) { {"clamped", "Int64$clamped", "func(x,low,high:Int64 -> Int64)"}, // {"divided_by", "Int64$divided_by", "func(x,y:Int64 -> Int64)"}, // {"gcd", "Int64$gcd", "func(x,y:Int64 -> Int64)"}, // - {"parse", "Int64$parse", "func(text:Text, remainder:&Text? = none -> Int64?)"}, // + {"parse", "Int64$parse", "func(text:Text, base:Int?=none, remainder:&Text?=none -> Int64?)"}, // {"get_bit", "Int64$get_bit", "func(x:Int64, bit_index:Int -> Bool)"}, // {"hex", "Int64$hex", "func(i:Int64, digits=0, uppercase=yes, prefix=yes -> Text)"}, // {"is_between", "Int64$is_between", "func(x:Int64,low:Int64,high:Int64 -> Bool)"}, // @@ -167,7 +167,7 @@ env_t *global_env(bool source_mapping) { {"clamped", "Int32$clamped", "func(x,low,high:Int32 -> Int32)"}, // {"divided_by", "Int32$divided_by", "func(x,y:Int32 -> Int32)"}, // {"gcd", "Int32$gcd", "func(x,y:Int32 -> Int32)"}, // - {"parse", "Int32$parse", "func(text:Text, remainder:&Text? = none -> Int32?)"}, // + {"parse", "Int32$parse", "func(text:Text, base:Int?=none, remainder:&Text?=none -> Int32?)"}, // {"get_bit", "Int32$get_bit", "func(x:Int32, bit_index:Int -> Bool)"}, // {"hex", "Int32$hex", "func(i:Int32, digits=0, uppercase=yes, prefix=yes -> Text)"}, // {"is_between", "Int32$is_between", "func(x:Int32,low:Int32,high:Int32 -> Bool)"}, // @@ -189,7 +189,7 @@ env_t *global_env(bool source_mapping) { {"clamped", "Int16$clamped", "func(x,low,high:Int16 -> Int16)"}, // {"divided_by", "Int16$divided_by", "func(x,y:Int16 -> Int16)"}, // {"gcd", "Int16$gcd", "func(x,y:Int16 -> Int16)"}, // - {"parse", "Int16$parse", "func(text:Text, remainder:&Text? = none -> Int16?)"}, // + {"parse", "Int16$parse", "func(text:Text, base:Int?=none, remainder:&Text?=none -> Int16?)"}, // {"get_bit", "Int16$get_bit", "func(x:Int16, bit_index:Int -> Bool)"}, // {"hex", "Int16$hex", "func(i:Int16, digits=0, uppercase=yes, prefix=yes -> Text)"}, // {"is_between", "Int16$is_between", "func(x:Int16,low:Int16,high:Int16 -> Bool)"}, // @@ -211,7 +211,7 @@ env_t *global_env(bool source_mapping) { {"clamped", "Int8$clamped", "func(x,low,high:Int8 -> Int8)"}, // {"divided_by", "Int8$divided_by", "func(x,y:Int8 -> Int8)"}, // {"gcd", "Int8$gcd", "func(x,y:Int8 -> Int8)"}, // - {"parse", "Int8$parse", "func(text:Text, remainder:&Text? = none -> Int8?)"}, // + {"parse", "Int8$parse", "func(text:Text, base:Int?=none, remainder:&Text?=none -> Int8?)"}, // {"get_bit", "Int8$get_bit", "func(x:Int8, bit_index:Int -> Bool)"}, // {"hex", "Int8$hex", "func(i:Int8, digits=0, uppercase=yes, prefix=yes -> Text)"}, // {"is_between", "Int8$is_between", "func(x:Int8,low:Int8,high:Int8 -> Bool)"}, // @@ -245,7 +245,7 @@ env_t *global_env(bool source_mapping) { C(SQRT1_2), {"INF", "(Num_t)(INFINITY)", "Num"}, // {"TAU", "(Num_t)(2.*M_PI)", "Num"}, // {"mix", "Num$mix", "func(amount,x,y:Num -> Num)"}, // - {"parse", "Num$parse", "func(text:Text, remainder:&Text? = none -> Num?)"}, // + {"parse", "Num$parse", "func(text:Text, remainder:&Text?=none -> Num?)"}, // {"abs", "fabs", "func(n:Num -> Num)"}, // F_opt(acos), F_opt(acosh), F_opt(asin), F(asinh), F(atan), F_opt(atanh), F(cbrt), F(ceil), F_opt(cos), F(cosh), F(erf), F(erfc), F(exp), F(exp2), F(expm1), F(floor), F(j0), F(j1), F_opt(log), F_opt(log10), @@ -274,7 +274,7 @@ env_t *global_env(bool source_mapping) { {"INF", "(Num32_t)(INFINITY)", "Num32"}, // {"TAU", "(Num32_t)(2.f*M_PI)", "Num32"}, // {"mix", "Num32$mix", "func(amount,x,y:Num32 -> Num32)"}, // - {"parse", "Num32$parse", "func(text:Text, remainder:&Text? = none -> Num32?)"}, // + {"parse", "Num32$parse", "func(text:Text, remainder:&Text?=none -> Num32?)"}, // {"abs", "fabsf", "func(n:Num32 -> Num32)"}, // {"modulo", "Num32$mod", "func(x,y:Num32 -> Num32)"}, // {"modulo1", "Num32$mod1", "func(x,y:Num32 -> Num32)"}, // diff --git a/src/stdlib/bigint.c b/src/stdlib/bigint.c index 46957c2d..2d145bd5 100644 --- a/src/stdlib/bigint.c +++ b/src/stdlib/bigint.c @@ -393,55 +393,98 @@ PUREFUNC Closure_t Int$onward(Int_t first, Int_t step) { } public -Int_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) { - str += 2; - const char *end = str + strspn(str, "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, (size_t)(end - str))), 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) { - str += 2; - const char *end = str + strspn(str, "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, (size_t)(end - str))), 8); + base32 = 8; + goto base8_prefix; } else if (strncmp(str, "0b", 2) == 0) { - str += 2; - const char *end = str + strspn(str, "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, (size_t)(end - str))), 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 a00bdf2f..9ce4c800 100644 --- a/src/stdlib/bigint.h +++ b/src/stdlib/bigint.h @@ -23,7 +23,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 04538796..18da5c5e 100644 --- a/src/stdlib/cli.c +++ b/src/stdlib/cli.c @@ -238,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) { 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 0f4632c2..1c8b4a05 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/typecheck.c b/src/typecheck.c index 37f4fcab..e432759b 100644 --- a/src/typecheck.c +++ b/src/typecheck.c @@ -14,6 +14,7 @@ #include "naming.h" #include "parse/files.h" #include "parse/types.h" +#include "stdlib/optionals.h" #include "stdlib/paths.h" #include "stdlib/tables.h" #include "stdlib/text.h" @@ -1659,7 +1660,7 @@ PUREFUNC bool is_constant(env_t *env, ast_t *ast) { case None: return true; case Int: { DeclareMatch(info, ast, Int); - Int_t int_val = Int$parse(Text$from_str(info->str), NULL); + Int_t int_val = Int$parse(Text$from_str(info->str), NONE_INT, NULL); if (int_val.small == 0) return false; // Failed to parse return (Int$compare_value(int_val, I(BIGGEST_SMALL_INT)) <= 0); } -- cgit v1.2.3 From a6c7d8ecbd2d67472774ac9600333cfe97454aaf Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Mon, 1 Dec 2025 22:47:57 -0500 Subject: Clean up CLI --help output --- src/compile/cli.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/compile/cli.c b/src/compile/cli.c index a66fb47d..5130b97b 100644 --- a/src/compile/cli.c +++ b/src/compile/cli.c @@ -74,16 +74,17 @@ static Text_t generate_usage(env_t *env, type_t *fn_type) { type_t *t = get_arg_type(main_env, arg); OptionalText_t flag = flagify(arg->name, arg->default_val != NULL); assert(flag.tag != TEXT_NONE); - OptionalText_t alias_flag = flagify(arg->alias, arg->default_val != NULL); Text_t flags = Texts("\x1b[1m", flag, "\x1b[m"); - if (alias_flag.tag != TEXT_NONE) flags = Texts(flags, ",\x1b[1m", alias_flag, "\x1b[m"); if (t->tag == BoolType || (t->tag == OptionalType && Match(t, OptionalType)->type->tag == BoolType)) flags = Texts(flags, "|\x1b[1m--no-", Text$without_prefix(flag, Text("--")), "\x1b[m"); if (arg->default_val || value_type(t)->tag == BoolType) { if (t->tag == BoolType || (t->tag == OptionalType && Match(t, OptionalType)->type->tag == BoolType)) usage = Texts(usage, "[", flags, "]"); else if (t->tag == ListType) usage = Texts(usage, "[", flags, " ", get_flag_options(t, Text("|")), "]"); + else if (t->tag == EnumType) usage = Texts(usage, "[", flags, " val]"); else usage = Texts(usage, "[", flags, " ", get_flag_options(t, Text("|")), "]"); + } else if (t->tag == EnumType) { + usage = Texts(usage, "\x1b[1m", flag, "\x1b[m"); } else { usage = Texts(usage, "\x1b[1m", get_flag_options(t, Text("|")), "\x1b[m"); } @@ -103,7 +104,7 @@ static Text_t generate_help(env_t *env, type_t *fn_type) { assert(flag.tag != TEXT_NONE); OptionalText_t alias_flag = flagify(arg->alias, true); Text_t flags = Texts("\x1b[33;1m", flag, "\x1b[m"); - if (alias_flag.tag != TEXT_NONE) flags = Texts(flags, ",\x1b[33;1m", alias_flag, "\x1b[m"); + if (alias_flag.tag != TEXT_NONE) flags = Texts("\x1b[33;1m", alias_flag, "\x1b[0;2m,\x1b[m ", flags); if (t->tag == BoolType || (t->tag == OptionalType && Match(t, OptionalType)->type->tag == BoolType)) flags = Texts(flags, "|\x1b[33;1m--no-", Text$without_prefix(flag, Text("--")), "\x1b[m"); if (t->tag == BoolType || (t->tag == OptionalType && Match(t, OptionalType)->type->tag == BoolType)) @@ -137,7 +138,7 @@ Text_t compile_cli_arg_call(env_t *env, ast_t *ast, Text_t fn_name, type_t *fn_t "Text_t usage = Texts(Text(\"\\x1b[1mUsage:\\x1b[m \"), " "Text$from_str(argv[0]), Text(\" \")", usage.length == 0 ? EMPTY_TEXT : Texts(", Text(", quoted_text(usage), ")"), ");\n", - "Text_t help = Texts(usage, Text(\"\\n\\n\"", quoted_text(help), "));\n"); + "Text_t help = Texts(usage, Text(\"\\n\"", quoted_text(help), "\"\\n\"));\n"); for (arg_t *arg = fn_info->args; arg; arg = arg->next) { code = Texts(code, compile_declaration(arg->type, Texts("_$", Text$from_str(arg->name))), " = ", -- cgit v1.2.3 From ce26a80bdb61bb8928c49f42bca84c6da4df72c5 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Mon, 1 Dec 2025 23:43:43 -0500 Subject: Fix CLI arg parsing --- src/stdlib/cli.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/stdlib/cli.c b/src/stdlib/cli.c index 18da5c5e..e30f7ced 100644 --- a/src/stdlib/cli.c +++ b/src/stdlib/cli.c @@ -325,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=... @@ -341,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)) { -- cgit v1.2.3 From 129f2c794cd388b99d573697965117e12b89f2fe Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sat, 6 Dec 2025 13:40:20 -0500 Subject: Fix for memory pointer visualization --- src/stdlib/memory.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') 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; } -- cgit v1.2.3 From a13b39f1e1ea220a868d99508796d06492a40611 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sat, 6 Dec 2025 13:54:37 -0500 Subject: Allow discarding Empty() values --- src/compile/statements.c | 2 -- src/typecheck.c | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) (limited to 'src') diff --git a/src/compile/statements.c b/src/compile/statements.c index 638f1341..4bb8b432 100644 --- a/src/compile/statements.c +++ b/src/compile/statements.c @@ -218,8 +218,6 @@ static Text_t _compile_statement(env_t *env, ast_t *ast) { } case Metadata: return EMPTY_TEXT; default: - // print("Is discardable: ", ast_to_sexp_str(ast), " ==> ", - // is_discardable(env, ast)); if (!is_discardable(env, ast)) code_err(ast, "The ", type_to_text(get_type(env, ast)), " result of this statement cannot be discarded"); return Texts("(void)", compile(env, ast), ";"); diff --git a/src/typecheck.c b/src/typecheck.c index e432759b..6f8fa0ba 100644 --- a/src/typecheck.c +++ b/src/typecheck.c @@ -1539,7 +1539,7 @@ PUREFUNC bool is_discardable(env_t *env, ast_t *ast) { default: break; } type_t *t = get_type(env, ast); - return (t->tag == VoidType || t->tag == AbortType || t->tag == ReturnType); + return (t->tag == VoidType || t->tag == AbortType || t->tag == ReturnType || t == EMPTY_TYPE); } type_t *get_arg_ast_type(env_t *env, arg_ast_t *arg) { -- cgit v1.2.3 From 48491f94c96615e8055bcf72ed9009b1d921467f Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sat, 6 Dec 2025 14:55:00 -0500 Subject: Use `foo!` as sugar for `foo.FirstTag!` for enum values. Also, give better error messages for this kind of `!` assertion. --- src/ast.h | 6 +++--- src/compile/enums.c | 6 +----- src/compile/optionals.c | 56 ++++++++++++++++++++++++++++++++++++++++++------ src/compile/statements.c | 4 +++- src/typecheck.c | 12 +++++++++-- src/types.c | 4 ++-- 6 files changed, 68 insertions(+), 20 deletions(-) (limited to 'src') diff --git a/src/ast.h b/src/ast.h index 5307fc2c..b852da2a 100644 --- a/src/ast.h +++ b/src/ast.h @@ -23,9 +23,9 @@ #define LiteralCode(code, ...) \ new (ast_t, .tag = InlineCCode, \ .__data.InlineCCode = {.chunks = new (ast_list_t, .ast = FakeAST(TextLiteral, code)), __VA_ARGS__}) -#define WrapLiteralCode(ast, code, ...) \ - new (ast_t, .tag = InlineCCode, .file = (ast)->file, .start = (ast)->start, .end = (ast)->end, \ - .__data.InlineCCode = {.chunks = new (ast_list_t, .ast = WrapAST(ast, TextLiteral, code)), __VA_ARGS__}) +#define WrapLiteralCode(_ast, code, ...) \ + new (ast_t, .tag = InlineCCode, .file = (_ast)->file, .start = (_ast)->start, .end = (_ast)->end, \ + .__data.InlineCCode = {.chunks = new (ast_list_t, .ast = WrapAST(_ast, TextLiteral, code)), __VA_ARGS__}) #define Match(x, _tag) \ ((x)->tag == _tag ? &(x)->__data._tag \ : (errx(1, __FILE__ ":%d This was supposed to be a " #_tag "\n", __LINE__), &(x)->__data._tag)) diff --git a/src/compile/enums.c b/src/compile/enums.c index 56d6432a..853625ca 100644 --- a/src/compile/enums.c +++ b/src/compile/enums.c @@ -158,11 +158,7 @@ Text_t compile_enum_field_access(env_t *env, ast_t *ast) { Text_t tag_name = namespace_name(e->env, e->env->namespace, Texts("tag$", tag->name)); if (tag->type != NULL && Match(tag->type, StructType)->fields) { Text_t member = compile_maybe_incref( - env, - WrapAST(ast, InlineCCode, - .chunks = new (ast_list_t, WrapAST(ast, TextLiteral, Texts("_e.", tag->name))), - .type = tag->type), - tag->type); + env, WrapLiteralCode(ast, Texts("_e.", tag->name), .type = tag->type), tag->type); return Texts("({ ", compile_declaration(value_t, Text("_e")), " = ", compile_to_pointer_depth(env, f->fielded, 0, false), "; ", "_e.$tag == ", tag_name, " ? ", promote_to_optional(tag->type, member), " : ", compile_none(tag->type), "; })"); diff --git a/src/compile/optionals.c b/src/compile/optionals.c index ffe16248..5ad67602 100644 --- a/src/compile/optionals.c +++ b/src/compile/optionals.c @@ -115,12 +115,54 @@ public Text_t compile_non_optional(env_t *env, ast_t *ast) { ast_t *value = Match(ast, NonOptional)->value; if (value->tag == Index && Match(value, Index)->index != NULL) return compile_indexing(env, value, true); - type_t *t = get_type(env, value); - Text_t value_code = compile(env, value); + type_t *value_t = get_type(env, value); + if (value_t->tag == PointerType) { + // Dereference pointers automatically + return compile_non_optional(env, WrapAST(ast, NonOptional, WrapAST(ast, Index, .indexed = value))); + } int64_t line = get_line_number(ast->file, ast->start); - return Texts( - "({ ", compile_declaration(t, Text("opt")), " = ", value_code, "; ", "if unlikely (", - check_none(t, Text("opt")), ")\n", "#line ", line, "\n", "fail_source(", quoted_str(ast->file->filename), ", ", - (int64_t)(value->start - value->file->text), ", ", (int64_t)(value->end - value->file->text), ", ", - "\"This was expected to be a value, but it's `none`\\n\");\n", optional_into_nonnone(t, Text("opt")), "; })"); + if (value_t->tag == EnumType) { + // For this case: + // enum Foo(FirstField, SecondField(msg:Text)) + // e := ... + // e! + // We desugar into `e.FirstField!` using the first enum field + tag_t *first_tag = Match(value_t, EnumType)->tags; + if (!first_tag) code_err(ast, "'!' cannot be used on an empty enum"); + return compile_non_optional( + env, WrapAST(ast, NonOptional, WrapAST(value, FieldAccess, .fielded = value, .field = first_tag->name))); + } else if (value->tag == FieldAccess + && value_type(get_type(env, Match(value, FieldAccess)->fielded))->tag == EnumType) { + type_t *enum_t = value_type(get_type(env, Match(value, FieldAccess)->fielded)); + DeclareMatch(e, enum_t, EnumType); + DeclareMatch(f, value, FieldAccess); + for (tag_t *tag = e->tags; tag; tag = tag->next) { + if (streq(f->field, tag->name)) { + Text_t tag_name = namespace_name(e->env, e->env->namespace, Texts("tag$", tag->name)); + return Texts("({ ", compile_declaration(enum_t, Text("_test_enum")), " = ", + compile_to_pointer_depth(env, f->fielded, 0, true), ";", + "if unlikely (_test_enum.$tag != ", tag_name, ") {\n", "#line ", line, "\n", + "fail_source(", quoted_str(f->fielded->file->filename), ", ", + (int64_t)(f->fielded->start - f->fielded->file->text), ", ", + (int64_t)(f->fielded->end - f->fielded->file->text), ", ", "\"This was expected to be ", + tag->name, ", but it was: \", ", expr_as_text(Text("_test_enum"), enum_t, Text("false")), + ", \"\\n\");\n}\n", + Match(tag->type, StructType)->fields + ? compile_maybe_incref( + env, WrapLiteralCode(value, Texts("_test_enum.", tag->name), .type = tag->type), + tag->type) + : Text("EMPTY_STRUCT"), + "; })"); + } + } + code_err(ast, "The field '", f->field, "' is not a valid tag name of ", type_to_text(enum_t)); + } else { + Text_t value_code = compile(env, value); + return Texts("({ ", compile_declaration(value_t, Text("opt")), " = ", value_code, "; ", "if unlikely (", + check_none(value_t, Text("opt")), ")\n", "#line ", line, "\n", "fail_source(", + quoted_str(value->file->filename), ", ", (int64_t)(value->start - value->file->text), ", ", + (int64_t)(value->end - value->file->text), ", ", + "\"This was expected to be a value, but it's `none`\\n\");\n", + optional_into_nonnone(value_t, Text("opt")), "; })"); + } } diff --git a/src/compile/statements.c b/src/compile/statements.c index 4bb8b432..01fb1a0b 100644 --- a/src/compile/statements.c +++ b/src/compile/statements.c @@ -219,7 +219,9 @@ static Text_t _compile_statement(env_t *env, ast_t *ast) { case Metadata: return EMPTY_TEXT; default: if (!is_discardable(env, ast)) - code_err(ast, "The ", type_to_text(get_type(env, ast)), " result of this statement cannot be discarded"); + code_err( + ast, "The ", type_to_text(get_type(env, ast)), + " value of this statement is implicitly ignored. \n Use `_ := ` if you want to explicitly discard it."); return Texts("(void)", compile(env, ast), ";"); } } diff --git a/src/typecheck.c b/src/typecheck.c index 6f8fa0ba..41de1a6b 100644 --- a/src/typecheck.c +++ b/src/typecheck.c @@ -755,7 +755,12 @@ type_t *get_type(env_t *env, ast_t *ast) { } case NonOptional: { ast_t *value = Match(ast, NonOptional)->value; - type_t *t = get_type(env, value); + type_t *t = value_type(get_type(env, value)); + if (t->tag == EnumType) { + tag_t *first_tag = Match(t, EnumType)->tags; + if (!first_tag) code_err(ast, "'!' cannot be used on an empty enum"); + return first_tag->type; + } if (t->tag != OptionalType) code_err(value, "This value is not optional. Only optional values can use the '!' operator."); return Match(t, OptionalType)->type; @@ -1539,7 +1544,10 @@ PUREFUNC bool is_discardable(env_t *env, ast_t *ast) { default: break; } type_t *t = get_type(env, ast); - return (t->tag == VoidType || t->tag == AbortType || t->tag == ReturnType || t == EMPTY_TYPE); + if (t->tag == StructType) { + return (Match(t, StructType)->fields == NULL); + } + return (t->tag == VoidType || t->tag == AbortType || t->tag == ReturnType); } type_t *get_arg_ast_type(env_t *env, arg_ast_t *arg) { diff --git a/src/types.c b/src/types.c index 6ed24f6c..b34d8bf9 100644 --- a/src/types.c +++ b/src/types.c @@ -63,7 +63,7 @@ Text_t type_to_text(type_t *t) { } case StructType: { DeclareMatch(struct_, t, StructType); - return Text$from_str(struct_->name); + return Text$replace(Text$from_str(struct_->name), Text("$"), Text(".")); } case PointerType: { DeclareMatch(ptr, t, PointerType); @@ -73,7 +73,7 @@ Text_t type_to_text(type_t *t) { case EnumType: { DeclareMatch(enum_, t, EnumType); if (enum_->name != NULL && strncmp(enum_->name, "enum$", strlen("enum$")) != 0) - return Text$from_str(enum_->name); + return Text$replace(Text$from_str(enum_->name), Text("$"), Text(".")); Text_t text = Text("enum("); for (tag_t *tag = enum_->tags; tag; tag = tag->next) { text = Texts(text, Text$from_str(tag->name)); -- cgit v1.2.3 From 43b0a91664fc0b9a5222805c68c3505fd9634689 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sun, 7 Dec 2025 16:14:18 -0500 Subject: Remove debug code --- src/naming.c | 4 ---- 1 file changed, 4 deletions(-) (limited to 'src') diff --git a/src/naming.c b/src/naming.c index 9c0f09e4..d7bb0fb9 100644 --- a/src/naming.c +++ b/src/naming.c @@ -94,16 +94,12 @@ Text_t valid_c_name(const char *name) { return Text$from_str(name); } -#include "stdlib/stdlib.h" public Text_t CONSTFUNC namespace_name(env_t *env, namespace_t *ns, Text_t name) { - if (Text$has(name, Text("\n"))) fail("WTF??"); for (; ns; ns = ns->parent) { - if (strchr(ns->name, '\n')) fail("WTF"); name = Texts(ns->name, "$", name); } if (env->id_suffix.length > 0) name = Texts(name, env->id_suffix); - if (Text$has(env->id_suffix, Text("\n"))) fail("WTF?????"); return name; } -- cgit v1.2.3 From 85d1507d8b7e0139135e040b7b4b23c02097a155 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sun, 7 Dec 2025 18:44:58 -0500 Subject: Consolidate logic for enums with and without tags with fields. --- src/compile/enums.c | 48 +++++++------------------------------- src/compile/headers.c | 61 +++++++++++++++---------------------------------- src/compile/optionals.c | 30 ++++++++++-------------- src/compile/whens.c | 8 ++----- src/typecheck.c | 17 ++------------ src/types.c | 10 +------- src/types.h | 1 - 7 files changed, 43 insertions(+), 132 deletions(-) (limited to 'src') diff --git a/src/compile/enums.c b/src/compile/enums.c index 853625ca..24da79b8 100644 --- a/src/compile/enums.c +++ b/src/compile/enums.c @@ -12,8 +12,6 @@ Text_t compile_enum_typeinfo(env_t *env, const char *name, tag_ast_t *tags) { // Compile member types and constructors: Text_t member_typeinfos = EMPTY_TEXT; for (tag_ast_t *tag = tags; tag; tag = tag->next) { - if (!tag->fields) continue; - const char *tag_name = String(name, "$", tag->name); type_t *tag_type = Table$str_get(*env->types, tag_name); assert(tag_type && tag_type->tag == StructType); @@ -38,9 +36,7 @@ Text_t compile_enum_typeinfo(env_t *env, const char *name, tag_ast_t *tags) { for (tag_ast_t *tag = tags; tag; tag = tag->next) { const char *tag_type_name = String(name, "$", tag->name); type_t *tag_type = Table$str_get(*env->types, tag_type_name); - if (tag_type && Match(tag_type, StructType)->fields) - typeinfo = Texts(typeinfo, "{\"", tag->name, "\", ", compile_type_info(tag_type), "}, "); - else typeinfo = Texts(typeinfo, "{\"", tag->name, "\"}, "); + typeinfo = Texts(typeinfo, "{\"", tag->name, "\", ", compile_type_info(tag_type), "}, "); } typeinfo = Texts(typeinfo, "}}}};\n"); return Texts(member_typeinfos, typeinfo); @@ -49,8 +45,6 @@ Text_t compile_enum_typeinfo(env_t *env, const char *name, tag_ast_t *tags) { Text_t compile_enum_constructors(env_t *env, const char *name, tag_ast_t *tags) { Text_t constructors = EMPTY_TEXT; for (tag_ast_t *tag = tags; tag; tag = tag->next) { - if (!tag->fields) continue; - Text_t arg_sig = EMPTY_TEXT; for (arg_ast_t *field = tag->fields; field; field = field->next) { type_t *field_t = get_arg_ast_type(env, field); @@ -76,25 +70,16 @@ Text_t compile_enum_constructors(env_t *env, const char *name, tag_ast_t *tags) Text_t compile_enum_header(env_t *env, const char *name, tag_ast_t *tags) { Text_t all_defs = EMPTY_TEXT; Text_t none_name = namespace_name(env, env->namespace, Texts(name, "$none")); - Text_t enum_name = namespace_name(env, env->namespace, Texts(name, "$$enum")); Text_t enum_tags = Texts("{ ", none_name, "=0, "); assert(Table$str_get(*env->types, name)); - bool has_any_tags_with_fields = false; for (tag_ast_t *tag = tags; tag; tag = tag->next) { Text_t tag_name = namespace_name(env, env->namespace, Texts(name, "$tag$", tag->name)); enum_tags = Texts(enum_tags, tag_name); if (tag->next) enum_tags = Texts(enum_tags, ", "); - has_any_tags_with_fields = has_any_tags_with_fields || (tag->fields != NULL); } enum_tags = Texts(enum_tags, " }"); - if (!has_any_tags_with_fields) { - Text_t enum_def = Texts("enum ", enum_name, " ", enum_tags, ";\n"); - Text_t info = namespace_name(env, env->namespace, Texts(name, "$$info")); - return Texts(enum_def, "extern const TypeInfo_t ", info, ";\n"); - } - Text_t struct_name = namespace_name(env, env->namespace, Texts(name, "$$struct")); Text_t enum_def = Texts("struct ", struct_name, " {\n" @@ -103,7 +88,6 @@ Text_t compile_enum_header(env_t *env, const char *name, tag_ast_t *tags) { " $tag;\n" "union {\n"); for (tag_ast_t *tag = tags; tag; tag = tag->next) { - if (!tag->fields) continue; Text_t field_def = compile_struct_header(env, NewAST(tag->file, tag->start, tag->end, StructDef, .name = Text$as_c_string(Texts(name, "$", tag->name)), .fields = tag->fields)); @@ -117,8 +101,6 @@ Text_t compile_enum_header(env_t *env, const char *name, tag_ast_t *tags) { Text_t info = namespace_name(env, env->namespace, Texts(name, "$$info")); all_defs = Texts(all_defs, "extern const TypeInfo_t ", info, ";\n"); for (tag_ast_t *tag = tags; tag; tag = tag->next) { - if (!tag->fields) continue; - Text_t arg_sig = EMPTY_TEXT; for (arg_ast_t *field = tag->fields; field; field = field->next) { type_t *field_t = get_arg_ast_type(env, field); @@ -140,11 +122,8 @@ Text_t compile_empty_enum(type_t *t) { tag_t *tag = enum_->tags; assert(tag); assert(tag->type); - if (Match(tag->type, StructType)->fields) - return Texts("((", compile_type(t), "){.$tag=", tag->tag_value, ", .", tag->name, "=", compile_empty(tag->type), - "})"); - else if (enum_has_fields(t)) return Texts("((", compile_type(t), "){.$tag=", tag->tag_value, "})"); - else return Texts("((", compile_type(t), ")", tag->tag_value, ")"); + return Texts("((", compile_type(t), "){.$tag=", tag->tag_value, ", .", tag->name, "=", compile_empty(tag->type), + "})"); } public @@ -156,22 +135,11 @@ Text_t compile_enum_field_access(env_t *env, ast_t *ast) { for (tag_t *tag = e->tags; tag; tag = tag->next) { if (streq(f->field, tag->name)) { Text_t tag_name = namespace_name(e->env, e->env->namespace, Texts("tag$", tag->name)); - if (tag->type != NULL && Match(tag->type, StructType)->fields) { - Text_t member = compile_maybe_incref( - env, WrapLiteralCode(ast, Texts("_e.", tag->name), .type = tag->type), tag->type); - return Texts("({ ", compile_declaration(value_t, Text("_e")), " = ", - compile_to_pointer_depth(env, f->fielded, 0, false), "; ", "_e.$tag == ", tag_name, " ? ", - promote_to_optional(tag->type, member), " : ", compile_none(tag->type), "; })"); - } else if (fielded_t->tag == PointerType) { - Text_t fielded = compile_to_pointer_depth(env, f->fielded, 1, false); - return Texts("((", fielded, ")->$tag == ", tag_name, " ? OPTIONAL_EMPTY_STRUCT : NONE_EMPTY_STRUCT)"); - } else if (enum_has_fields(value_t)) { - Text_t fielded = compile(env, f->fielded); - return Texts("((", fielded, ").$tag == ", tag_name, " ? OPTIONAL_EMPTY_STRUCT : NONE_EMPTY_STRUCT)"); - } else { - Text_t fielded = compile(env, f->fielded); - return Texts("((", fielded, ") == ", tag_name, " ? OPTIONAL_EMPTY_STRUCT : NONE_EMPTY_STRUCT)"); - } + Text_t member = + compile_maybe_incref(env, WrapLiteralCode(ast, Texts("_e.", tag->name), .type = tag->type), tag->type); + return Texts("({ ", compile_declaration(value_t, Text("_e")), " = ", + compile_to_pointer_depth(env, f->fielded, 0, false), "; ", "_e.$tag == ", tag_name, " ? ", + promote_to_optional(tag->type, member), " : ", compile_none(tag->type), "; })"); } } code_err(ast, "The field '", f->field, "' is not a valid tag name of ", type_to_text(value_t)); diff --git a/src/compile/headers.c b/src/compile/headers.c index 1dcf7abb..dc57da77 100644 --- a/src/compile/headers.c +++ b/src/compile/headers.c @@ -79,28 +79,16 @@ static void _make_typedefs(compile_typedef_info_t *info, ast_t *ast) { *info->header = Texts(*info->header, "typedef struct ", struct_name, " ", type_name, ";\n"); } else if (ast->tag == EnumDef) { DeclareMatch(def, ast, EnumDef); - bool has_any_tags_with_fields = false; - for (tag_ast_t *tag = def->tags; tag; tag = tag->next) { - has_any_tags_with_fields = has_any_tags_with_fields || (tag->fields != NULL); - } + Text_t struct_name = namespace_name(info->env, info->env->namespace, Texts(def->name, "$$struct")); + Text_t type_name = namespace_name(info->env, info->env->namespace, Texts(def->name, "$$type")); + *info->header = Texts(*info->header, "typedef struct ", struct_name, " ", type_name, ";\n"); - if (has_any_tags_with_fields) { - Text_t struct_name = namespace_name(info->env, info->env->namespace, Texts(def->name, "$$struct")); - Text_t type_name = namespace_name(info->env, info->env->namespace, Texts(def->name, "$$type")); - *info->header = Texts(*info->header, "typedef struct ", struct_name, " ", type_name, ";\n"); - - for (tag_ast_t *tag = def->tags; tag; tag = tag->next) { - if (!tag->fields) continue; - Text_t tag_struct = - namespace_name(info->env, info->env->namespace, Texts(def->name, "$", tag->name, "$$struct")); - Text_t tag_type = - namespace_name(info->env, info->env->namespace, Texts(def->name, "$", tag->name, "$$type")); - *info->header = Texts(*info->header, "typedef struct ", tag_struct, " ", tag_type, ";\n"); - } - } else { - Text_t enum_name = namespace_name(info->env, info->env->namespace, Texts(def->name, "$$enum")); - Text_t type_name = namespace_name(info->env, info->env->namespace, Texts(def->name, "$$type")); - *info->header = Texts(*info->header, "typedef enum ", enum_name, " ", type_name, ";\n"); + for (tag_ast_t *tag = def->tags; tag; tag = tag->next) { + Text_t tag_struct = + namespace_name(info->env, info->env->namespace, Texts(def->name, "$", tag->name, "$$struct")); + Text_t tag_type = + namespace_name(info->env, info->env->namespace, Texts(def->name, "$", tag->name, "$$type")); + *info->header = Texts(*info->header, "typedef struct ", tag_struct, " ", tag_type, ";\n"); } } else if (ast->tag == LangDef) { DeclareMatch(def, ast, LangDef); @@ -124,29 +112,16 @@ static void add_type_headers(type_ast_t *type_ast, void *userdata) { // Force the type to get defined: (void)parse_type_ast(info->env, type_ast); DeclareMatch(enum_, type_ast, EnumTypeAST); - bool has_any_tags_with_fields = false; - for (tag_ast_t *tag = enum_->tags; tag; tag = tag->next) { - has_any_tags_with_fields = has_any_tags_with_fields || (tag->fields != NULL); - } - const char *name = String("enum$", (int64_t)(type_ast->start - type_ast->file->text)); - if (has_any_tags_with_fields) { - Text_t struct_name = namespace_name(info->env, info->env->namespace, Texts(name, "$$struct")); - Text_t type_name = namespace_name(info->env, info->env->namespace, Texts(name, "$$type")); - *info->header = Texts(*info->header, "typedef struct ", struct_name, " ", type_name, ";\n"); - - for (tag_ast_t *tag = enum_->tags; tag; tag = tag->next) { - if (!tag->fields) continue; - Text_t tag_struct = - namespace_name(info->env, info->env->namespace, Texts(name, "$", tag->name, "$$struct")); - Text_t tag_type = - namespace_name(info->env, info->env->namespace, Texts(name, "$", tag->name, "$$type")); - *info->header = Texts(*info->header, "typedef struct ", tag_struct, " ", tag_type, ";\n"); - } - } else { - Text_t enum_name = namespace_name(info->env, info->env->namespace, Texts(name, "$$enum")); - Text_t type_name = namespace_name(info->env, info->env->namespace, Texts(name, "$$type")); - *info->header = Texts(*info->header, "typedef enum ", enum_name, " ", type_name, ";\n"); + Text_t struct_name = namespace_name(info->env, info->env->namespace, Texts(name, "$$struct")); + Text_t type_name = namespace_name(info->env, info->env->namespace, Texts(name, "$$type")); + *info->header = Texts(*info->header, "typedef struct ", struct_name, " ", type_name, ";\n"); + + for (tag_ast_t *tag = enum_->tags; tag; tag = tag->next) { + Text_t tag_struct = + namespace_name(info->env, info->env->namespace, Texts(name, "$", tag->name, "$$struct")); + Text_t tag_type = namespace_name(info->env, info->env->namespace, Texts(name, "$", tag->name, "$$type")); + *info->header = Texts(*info->header, "typedef struct ", tag_struct, " ", tag_type, ";\n"); } *info->header = Texts(*info->header, compile_enum_header(info->env, name, enum_->tags)); diff --git a/src/compile/optionals.c b/src/compile/optionals.c index 5ad67602..b9a3742e 100644 --- a/src/compile/optionals.c +++ b/src/compile/optionals.c @@ -103,10 +103,7 @@ Text_t check_none(type_t *t, Text_t value) { else if (t->tag == BoolType) return Texts("((", value, ") == NONE_BOOL)"); else if (t->tag == TextType) return Texts("((", value, ").tag == TEXT_NONE)"); else if (t->tag == IntType || t->tag == ByteType || t->tag == StructType) return Texts("!(", value, ").has_value"); - else if (t->tag == EnumType) { - if (enum_has_fields(t)) return Texts("((", value, ").$tag == 0)"); - else return Texts("((", value, ") == 0)"); - } + else if (t->tag == EnumType) return Texts("((", value, ").$tag == 0)"); print_err("Optional check not implemented for: ", type_to_text(t)); return EMPTY_TEXT; } @@ -139,20 +136,17 @@ Text_t compile_non_optional(env_t *env, ast_t *ast) { for (tag_t *tag = e->tags; tag; tag = tag->next) { if (streq(f->field, tag->name)) { Text_t tag_name = namespace_name(e->env, e->env->namespace, Texts("tag$", tag->name)); - return Texts("({ ", compile_declaration(enum_t, Text("_test_enum")), " = ", - compile_to_pointer_depth(env, f->fielded, 0, true), ";", - "if unlikely (_test_enum.$tag != ", tag_name, ") {\n", "#line ", line, "\n", - "fail_source(", quoted_str(f->fielded->file->filename), ", ", - (int64_t)(f->fielded->start - f->fielded->file->text), ", ", - (int64_t)(f->fielded->end - f->fielded->file->text), ", ", "\"This was expected to be ", - tag->name, ", but it was: \", ", expr_as_text(Text("_test_enum"), enum_t, Text("false")), - ", \"\\n\");\n}\n", - Match(tag->type, StructType)->fields - ? compile_maybe_incref( - env, WrapLiteralCode(value, Texts("_test_enum.", tag->name), .type = tag->type), - tag->type) - : Text("EMPTY_STRUCT"), - "; })"); + return Texts( + "({ ", compile_declaration(enum_t, Text("_test_enum")), " = ", + compile_to_pointer_depth(env, f->fielded, 0, true), ";", + "if unlikely (_test_enum.$tag != ", tag_name, ") {\n", "#line ", line, "\n", "fail_source(", + quoted_str(f->fielded->file->filename), ", ", (int64_t)(f->fielded->start - f->fielded->file->text), + ", ", (int64_t)(f->fielded->end - f->fielded->file->text), ", ", "\"This was expected to be ", + tag->name, ", but it was: \", ", expr_as_text(Text("_test_enum"), enum_t, Text("false")), + ", \"\\n\");\n}\n", + compile_maybe_incref( + env, WrapLiteralCode(value, Texts("_test_enum.", tag->name), .type = tag->type), tag->type), + "; })"); } } code_err(ast, "The field '", f->field, "' is not a valid tag name of ", type_to_text(enum_t)); diff --git a/src/compile/whens.c b/src/compile/whens.c index 122c581c..618a667c 100644 --- a/src/compile/whens.c +++ b/src/compile/whens.c @@ -43,11 +43,7 @@ Text_t compile_when_statement(env_t *env, ast_t *ast) { DeclareMatch(enum_t, subject_t, EnumType); - Text_t code; - if (enum_has_fields(subject_t)) - code = Texts("WHEN(", compile_type(subject_t), ", ", compile(env, when->subject), ", _when_subject, {\n"); - else code = Texts("switch(", compile(env, when->subject), ") {\n"); - + Text_t code = Texts("WHEN(", compile_type(subject_t), ", ", compile(env, when->subject), ", _when_subject, {\n"); for (when_clause_t *clause = when->clauses; clause; clause = clause->next) { if (clause->pattern->tag == Var) { const char *clause_tag_name = Match(clause->pattern, Var)->name; @@ -132,7 +128,7 @@ Text_t compile_when_statement(env_t *env, ast_t *ast) { } else { code = Texts(code, "default: errx(1, \"Invalid tag!\");\n"); } - code = Texts(code, "\n}", enum_has_fields(subject_t) ? Text(")") : EMPTY_TEXT, "\n"); + code = Texts(code, "\n}", Text(")"), "\n"); return code; } diff --git a/src/typecheck.c b/src/typecheck.c index 41de1a6b..af726a14 100644 --- a/src/typecheck.c +++ b/src/typecheck.c @@ -125,11 +125,6 @@ type_t *parse_type_ast(env_t *env, type_ast_t *ast) { Table$str_set(env->types, enum_name, enum_type); - bool has_any_tags_with_fields = false; - for (tag_ast_t *tag_ast = tag_asts; tag_ast; tag_ast = tag_ast->next) { - has_any_tags_with_fields = has_any_tags_with_fields || tag_ast->fields; - } - for (tag_ast_t *tag_ast = tag_asts; tag_ast; tag_ast = tag_ast->next) { arg_t *fields = NULL; for (arg_ast_t *field_ast = tag_ast->fields; field_ast; field_ast = field_ast->next) { @@ -151,14 +146,11 @@ type_t *parse_type_ast(env_t *env, type_ast_t *ast) { set_binding(ns_env, tag_ast->name, constructor_t, tagged_name); binding_t binding = {.type = constructor_t, .code = tagged_name}; List$insert(&ns_env->namespace->constructors, &binding, I(1), sizeof(binding)); - } else if (has_any_tags_with_fields) { // Empty singleton value: + } else { // Empty singleton value: Text_t code = Texts("((", namespace_name(env, env->namespace, Texts(enum_name, "$$type")), "){", namespace_name(env, env->namespace, Texts(enum_name, "$tag$", tag_ast->name)), "})"); set_binding(ns_env, tag_ast->name, enum_type, code); - } else { - Text_t code = namespace_name(env, env->namespace, Texts(enum_name, "$tag$", tag_ast->name)); - set_binding(ns_env, tag_ast->name, enum_type, code); } Table$str_set(env->types, String(enum_name, "$", tag_ast->name), tag_type); @@ -441,9 +433,7 @@ void bind_statement(env_t *env, ast_t *statement) { ns_env->current_type = type; tag_t *tags = NULL; int64_t next_tag = 1; - bool has_any_tags_with_fields = false; for (tag_ast_t *tag_ast = def->tags; tag_ast; tag_ast = tag_ast->next) { - has_any_tags_with_fields = has_any_tags_with_fields || tag_ast->fields; arg_t *fields = NULL; for (arg_ast_t *field_ast = tag_ast->fields; field_ast; field_ast = field_ast->next) { type_t *field_t = get_arg_ast_type(env, field_ast); @@ -496,13 +486,10 @@ void bind_statement(env_t *env, ast_t *statement) { set_binding(ns_env, tag->name, constructor_t, tagged_name); binding_t binding = {.type = constructor_t, .code = tagged_name}; List$insert(&ns_env->namespace->constructors, &binding, I(1), sizeof(binding)); - } else if (has_any_tags_with_fields) { // Empty singleton value: + } else { // Empty singleton value: Text_t code = Texts("((", namespace_name(env, env->namespace, Texts(def->name, "$$type")), "){", namespace_name(env, env->namespace, Texts(def->name, "$tag$", tag->name)), "})"); set_binding(ns_env, tag->name, type, code); - } else { - Text_t code = namespace_name(env, env->namespace, Texts(def->name, "$tag$", tag->name)); - set_binding(ns_env, tag->name, type, code); } Table$str_set(env->types, String(def->name, "$", tag->name), tag->type); } diff --git a/src/types.c b/src/types.c index b34d8bf9..e3bf326c 100644 --- a/src/types.c +++ b/src/types.c @@ -701,8 +701,7 @@ type_t *get_field_type(type_t *t, const char *field_name) { DeclareMatch(e, t, EnumType); for (tag_t *tag = e->tags; tag; tag = tag->next) { if (!streq(field_name, tag->name)) continue; - if (tag->type != NULL && Match(tag->type, StructType)->fields) return Type(OptionalType, tag->type); - else return Type(OptionalType, EMPTY_TYPE); + return Type(OptionalType, tag->type); } return NULL; } @@ -835,10 +834,3 @@ type_t *_make_function_type(type_t *ret, int n, arg_t args[n]) { } return Type(FunctionType, .ret = ret, .args = &arg_pointers[0]); } - -PUREFUNC bool enum_has_fields(type_t *t) { - for (tag_t *e_tag = Match(t, EnumType)->tags; e_tag; e_tag = e_tag->next) { - if (e_tag->type != NULL && Match(e_tag->type, StructType)->fields) return true; - } - return false; -} diff --git a/src/types.h b/src/types.h index 76632c66..66e6ba12 100644 --- a/src/types.h +++ b/src/types.h @@ -166,4 +166,3 @@ PUREFUNC type_t *non_optional(type_t *t); type_t *get_field_type(type_t *t, const char *field_name); PUREFUNC type_t *get_iterated_type(type_t *t); type_t *_make_function_type(type_t *ret, int n, arg_t args[n]); -PUREFUNC bool enum_has_fields(type_t *t); -- cgit v1.2.3 From 09942b27ae6fd7e7d394b2d251ef77c8a6f69e07 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sun, 7 Dec 2025 19:02:13 -0500 Subject: Rename `Empty()` -> `Present()` for set-like tables --- src/compile/cli.c | 2 +- src/compile/lists.c | 2 +- src/compile/tables.c | 10 +++++----- src/environment.c | 8 ++++---- src/environment.h | 2 +- src/stdlib/datatypes.h | 14 +++++++------- src/stdlib/structs.c | 11 ----------- src/stdlib/structs.h | 2 -- src/stdlib/tables.c | 12 ++++++++++++ src/stdlib/tables.h | 2 ++ src/typecheck.c | 10 +++++----- src/types.c | 2 +- 12 files changed, 39 insertions(+), 38 deletions(-) (limited to 'src') diff --git a/src/compile/cli.c b/src/compile/cli.c index 5130b97b..63a467ca 100644 --- a/src/compile/cli.c +++ b/src/compile/cli.c @@ -38,7 +38,7 @@ static Text_t get_flag_options(type_t *t, Text_t separator) { } else if (t->tag == ListType) { Text_t item_option = get_flag_options(Match(t, ListType)->item_type, separator); return Texts(item_option, "1 ", item_option, "2..."); - } else if (t->tag == TableType && Match(t, TableType)->value_type == EMPTY_TYPE) { + } else if (t->tag == TableType && Match(t, TableType)->value_type == PRESENT_TYPE) { Text_t item_option = get_flag_options(Match(t, TableType)->key_type, separator); return Texts(item_option, "1 ", item_option, "2..."); } else if (t->tag == TableType) { diff --git a/src/compile/lists.c b/src/compile/lists.c index 97b0b85d..31255c1e 100644 --- a/src/compile/lists.c +++ b/src/compile/lists.c @@ -254,7 +254,7 @@ Text_t compile_list_method_call(env_t *env, ast_t *ast) { } else if (streq(call->name, "unique")) { self = compile_to_pointer_depth(env, call->self, 0, false); (void)compile_arguments(env, ast, NULL, call->args); - return Texts("Table$from_entries(", self, ", Table$info(", compile_type_info(item_t), ", &Empty$$info))"); + return Texts("Table$from_entries(", self, ", Table$info(", compile_type_info(item_t), ", &Present$$info))"); } else if (streq(call->name, "pop")) { EXPECT_POINTER(); arg_t *arg_spec = new (arg_t, .name = "index", .type = INT_TYPE, .default_val = FakeAST(Int, "-1")); diff --git a/src/compile/tables.c b/src/compile/tables.c index 54276c3b..e624f9fb 100644 --- a/src/compile/tables.c +++ b/src/compile/tables.c @@ -13,7 +13,7 @@ static ast_t *add_to_table_comprehension(ast_t *entry, ast_t *subject) { return WrapAST( entry, MethodCall, .name = "set", .self = subject, .args = new (arg_ast_t, .value = e->key, - .next = new (arg_ast_t, .value = e->value ? e->value : WrapAST(entry, Var, .name = "EMPTY")))); + .next = new (arg_ast_t, .value = e->value ? e->value : WrapAST(entry, Var, .name = "PRESENT")))); } Text_t compile_typed_table(env_t *env, ast_t *ast, type_t *table_type) { @@ -51,10 +51,10 @@ Text_t compile_typed_table(env_t *env, ast_t *ast, type_t *table_type) { for (ast_list_t *entry = table->entries; entry; entry = entry->next) { DeclareMatch(e, entry->ast, TableEntry); - code = Texts( - code, ",\n\t{", compile_to_type(key_scope, e->key, key_t), ", ", - compile_to_type(value_scope, e->value ? e->value : WrapAST(entry->ast, Var, .name = "EMPTY"), value_t), - "}"); + code = Texts(code, ",\n\t{", compile_to_type(key_scope, e->key, key_t), ", ", + compile_to_type(value_scope, e->value ? e->value : WrapAST(entry->ast, Var, .name = "PRESENT"), + value_t), + "}"); } return Texts(code, ")"); } diff --git a/src/environment.c b/src/environment.c index 7e04fe79..c5932e30 100644 --- a/src/environment.c +++ b/src/environment.c @@ -19,7 +19,7 @@ type_t *PATH_TYPE = NULL; public type_t *PATH_TYPE_TYPE = NULL; public -type_t *EMPTY_TYPE = NULL; +type_t *PRESENT_TYPE = NULL; static type_t *declare_type(env_t *env, const char *def_str) { ast_t *ast = parse_file_str(def_str); @@ -70,7 +70,7 @@ env_t *global_env(bool source_mapping) { PATH_TYPE_TYPE = declare_type(env, "enum PathType(Relative, Absolute, Home)"); PATH_TYPE = declare_type(env, "struct Path(type:PathType, components:[Text])"); - EMPTY_TYPE = declare_type(env, "struct Empty()"); + PRESENT_TYPE = declare_type(env, "struct Present()"); typedef struct { const char *name, *code, *type_str; @@ -90,7 +90,7 @@ env_t *global_env(bool source_mapping) { MAKE_TYPE("Void", Type(VoidType), Text("void"), Text("Void$info")), MAKE_TYPE("Abort", Type(AbortType), Text("void"), Text("Abort$info")), MAKE_TYPE("Memory", Type(MemoryType), Text("void"), Text("Memory$info")), - MAKE_TYPE("Empty", EMPTY_TYPE, Text("Empty$$type"), Text("Empty$$info")), + MAKE_TYPE("Present", PRESENT_TYPE, Text("Present$$type"), Text("Present$$info")), MAKE_TYPE( // "Bool", Type(BoolType), Text("Bool_t"), Text("Bool$info"), {"parse", "Bool$parse", "func(text:Text, remainder:&Text?=none -> Bool?)"}), @@ -530,7 +530,7 @@ env_t *global_env(bool source_mapping) { struct { const char *name, *code, *type_str; } global_vars[] = { - {"EMPTY", "EMPTY", "Empty"}, + {"PRESENT", "PRESENT", "Present"}, {"TOMO_VERSION", "TOMO_VERSION_TEXT", "Text"}, {"USE_COLOR", "USE_COLOR", "Bool"}, {"ask", "ask", "func(prompt:Text, bold=yes, force_tty=yes -> Text?)"}, diff --git a/src/environment.h b/src/environment.h index 6389cc7a..f48271b5 100644 --- a/src/environment.h +++ b/src/environment.h @@ -87,4 +87,4 @@ binding_t *get_namespace_binding(env_t *env, ast_t *self, const char *name); extern type_t *TEXT_TYPE; extern type_t *PATH_TYPE; extern type_t *PATH_TYPE_TYPE; -extern type_t *EMPTY_TYPE; +extern type_t *PRESENT_TYPE; diff --git a/src/stdlib/datatypes.h b/src/stdlib/datatypes.h index d51db8ab..fbd2ce73 100644 --- a/src/stdlib/datatypes.h +++ b/src/stdlib/datatypes.h @@ -69,18 +69,18 @@ typedef struct table_s { struct table_s *fallback; } Table_t; -typedef struct Empty$$struct { -} Empty$$type; +typedef struct Present$$struct { +} Present$$type; -#define EMPTY_STRUCT ((Empty$$type){}) +#define PRESENT_STRUCT ((Present$$type){}) typedef struct { bool has_value; - Empty$$type value; -} $OptionalEmpty$$type; + Present$$type value; +} $OptionalPresent$$type; -#define NONE_EMPTY_STRUCT (($OptionalEmpty$$type){.has_value = false}) -#define OPTIONAL_EMPTY_STRUCT (($OptionalEmpty$$type){.has_value = true}) +#define NONE_PRESENT_STRUCT (($OptionalPresent$$type){.has_value = false}) +#define OPTIONAL_PRESENT_STRUCT (($OptionalPresent$$type){.has_value = true}) typedef struct { void *fn, *userdata; 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/typecheck.c b/src/typecheck.c index af726a14..139f0655 100644 --- a/src/typecheck.c +++ b/src/typecheck.c @@ -74,7 +74,7 @@ type_t *parse_type_ast(env_t *env, type_ast_t *ast) { if (has_stack_memory(key_type)) code_err(key_type_ast, "Tables can't have stack references because the list may outlive the stack frame."); - type_t *val_type = table_type->value ? parse_type_ast(env, table_type->value) : EMPTY_TYPE; + type_t *val_type = table_type->value ? parse_type_ast(env, table_type->value) : PRESENT_TYPE; if (!val_type) code_err(ast, "I can't figure out what the value type for this entry is."); if (table_type->value && has_stack_memory(val_type)) @@ -809,7 +809,7 @@ type_t *get_type(env_t *env, ast_t *ast) { DeclareMatch(e, entry_ast, TableEntry); type_t *key_t = get_type(scope, e->key); - type_t *value_t = e->value ? get_type(scope, e->value) : EMPTY_TYPE; + type_t *value_t = e->value ? get_type(scope, e->value) : PRESENT_TYPE; type_t *key_merged = key_type ? type_or_type(key_type, key_t) : key_t; if (!key_merged) ambiguous_key_type = true; @@ -842,7 +842,7 @@ type_t *get_type(env_t *env, ast_t *ast) { } else if (comp->expr->tag == TableEntry) { DeclareMatch(e, comp->expr, TableEntry); return Type(TableType, .key_type = get_type(scope, e->key), - .value_type = e->value ? get_type(scope, e->value) : EMPTY_TYPE, .env = env); + .value_type = e->value ? get_type(scope, e->value) : PRESENT_TYPE, .env = env); } else { return Type(ListType, .item_type = get_type(scope, comp->expr)); } @@ -964,7 +964,7 @@ type_t *get_type(env_t *env, ast_t *ast) { else if (streq(call->name, "sorted")) return self_value_t; else if (streq(call->name, "to")) return self_value_t; else if (streq(call->name, "unique")) - return Type(TableType, .key_type = item_type, .value_type = EMPTY_TYPE); + return Type(TableType, .key_type = item_type, .value_type = PRESENT_TYPE); else code_err(ast, "There is no '", call->name, "' method for lists"); } case TableType: { @@ -1718,7 +1718,7 @@ PUREFUNC bool can_compile_to_type(env_t *env, ast_t *ast, type_t *needed) { if (entry->ast->tag != TableEntry) continue; // TODO: fix this DeclareMatch(e, entry->ast, TableEntry); if (!can_compile_to_type(env, e->key, key_type) - || !(e->value ? can_compile_to_type(env, e->value, value_type) : type_eq(value_type, EMPTY_TYPE))) + || !(e->value ? can_compile_to_type(env, e->value, value_type) : type_eq(value_type, PRESENT_TYPE))) return false; } return true; diff --git a/src/types.c b/src/types.c index e3bf326c..5c52992b 100644 --- a/src/types.c +++ b/src/types.c @@ -47,7 +47,7 @@ Text_t type_to_text(type_t *t) { } case TableType: { DeclareMatch(table, t, TableType); - return (table->value_type && table->value_type != EMPTY_TYPE) + return (table->value_type && table->value_type != PRESENT_TYPE) ? Texts("{", type_to_text(table->key_type), ":", type_to_text(table->value_type), "}") : Texts("{", type_to_text(table->key_type), "}"); } -- cgit v1.2.3 From 544b1fb6a70d55bf368b827136cf0f37a26e8288 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sun, 7 Dec 2025 21:42:45 -0500 Subject: Change Paths to be an enum of their different types. --- src/compile/expressions.c | 1 - src/compile/optionals.c | 11 +-- src/compile/types.c | 3 - src/environment.c | 13 +-- src/environment.h | 1 - src/stdlib/datatypes.h | 42 ++++++++- src/stdlib/optionals.h | 2 +- src/stdlib/paths.c | 226 ++++++++++++++++++++++------------------------ src/stdlib/paths.h | 7 +- src/tomo.c | 3 +- src/types.c | 2 - 11 files changed, 161 insertions(+), 150 deletions(-) (limited to 'src') diff --git a/src/compile/expressions.c b/src/compile/expressions.c index 8b339352..c3918de3 100644 --- a/src/compile/expressions.c +++ b/src/compile/expressions.c @@ -57,7 +57,6 @@ Text_t compile_empty(type_t *t) { if (t->tag == OptionalType) return compile_none(t); if (t == PATH_TYPE) return Text("NONE_PATH"); - else if (t == PATH_TYPE_TYPE) return Text("PATHTYPE_ABSOLUTE"); switch (t->tag) { case BigIntType: return Text("I(0)"); diff --git a/src/compile/optionals.c b/src/compile/optionals.c index b9a3742e..83f387e2 100644 --- a/src/compile/optionals.c +++ b/src/compile/optionals.c @@ -15,18 +15,14 @@ Text_t optional_into_nonnone(type_t *t, Text_t value) { switch (t->tag) { case IntType: case ByteType: return Texts(value, ".value"); - case StructType: - if (t == PATH_TYPE || t == PATH_TYPE_TYPE) return value; - return Texts(value, ".value"); + case StructType: return Texts(value, ".value"); default: return value; } } public Text_t promote_to_optional(type_t *t, Text_t code) { - if (t == PATH_TYPE || t == PATH_TYPE_TYPE) { - return code; - } else if (t->tag == IntType) { + if (t->tag == IntType) { switch (Match(t, IntType)->bits) { case TYPE_IBITS8: return Texts("((OptionalInt8_t){.has_value=true, .value=", code, "})"); case TYPE_IBITS16: return Texts("((OptionalInt16_t){.has_value=true, .value=", code, "})"); @@ -53,7 +49,6 @@ Text_t compile_none(type_t *t) { if (t == NULL) compiler_err(NULL, NULL, NULL, "I can't compile a `none` value with no type"); if (t == PATH_TYPE) return Text("NONE_PATH"); - else if (t == PATH_TYPE_TYPE) return Text("PATHTYPE_NONE"); switch (t->tag) { case BigIntType: return Text("NONE_INT"); @@ -92,8 +87,6 @@ Text_t check_none(type_t *t, Text_t value) { // NOTE: these use statement expressions ({...;}) because some compilers // complain about excessive parens around equality comparisons if (t->tag == PointerType || t->tag == FunctionType || t->tag == CStringType) return Texts("(", value, " == NULL)"); - else if (t == PATH_TYPE) return Texts("((", value, ").type == PATHTYPE_NONE)"); - else if (t == PATH_TYPE_TYPE) return Texts("((", value, ") == PATHTYPE_NONE)"); else if (t->tag == BigIntType) return Texts("((", value, ").small == 0)"); else if (t->tag == ClosureType) return Texts("((", value, ").fn == NULL)"); else if (t->tag == NumType) diff --git a/src/compile/types.c b/src/compile/types.c index 2b345b41..aac27f4c 100644 --- a/src/compile/types.c +++ b/src/compile/types.c @@ -12,7 +12,6 @@ public Text_t compile_type(type_t *t) { if (t == PATH_TYPE) return Text("Path_t"); - else if (t == PATH_TYPE_TYPE) return Text("PathType_t"); switch (t->tag) { case ReturnType: errx(1, "Shouldn't be compiling ReturnType to a type"); @@ -73,7 +72,6 @@ Text_t compile_type(type_t *t) { case TableType: return Texts("Optional", compile_type(nonnull)); case StructType: { if (nonnull == PATH_TYPE) return Text("OptionalPath_t"); - if (nonnull == PATH_TYPE_TYPE) return Text("OptionalPathType_t"); DeclareMatch(s, nonnull, StructType); return namespace_name(s->env, s->env->namespace->parent, Texts("$Optional", s->name, "$$type")); } @@ -90,7 +88,6 @@ public Text_t compile_type_info(type_t *t) { if (t == NULL) compiler_err(NULL, NULL, NULL, "Attempt to compile a NULL type"); if (t == PATH_TYPE) return Text("&Path$info"); - else if (t == PATH_TYPE_TYPE) return Text("&PathType$info"); switch (t->tag) { case BoolType: diff --git a/src/environment.c b/src/environment.c index c5932e30..bdbc8a4f 100644 --- a/src/environment.c +++ b/src/environment.c @@ -17,8 +17,6 @@ type_t *TEXT_TYPE = NULL; public type_t *PATH_TYPE = NULL; public -type_t *PATH_TYPE_TYPE = NULL; -public type_t *PRESENT_TYPE = NULL; static type_t *declare_type(env_t *env, const char *def_str) { @@ -67,8 +65,9 @@ env_t *global_env(bool source_mapping) { (void)bind_type(env, "Int", Type(BigIntType)); (void)bind_type(env, "Int32", Type(IntType, .bits = TYPE_IBITS32)); (void)bind_type(env, "Memory", Type(MemoryType)); - PATH_TYPE_TYPE = declare_type(env, "enum PathType(Relative, Absolute, Home)"); - PATH_TYPE = declare_type(env, "struct Path(type:PathType, components:[Text])"); + PATH_TYPE = declare_type( + env, + "enum Path(AbsolutePath(components:[Text]), RelativePath(components:[Text]), HomePath(components:[Text]))"); PRESENT_TYPE = declare_type(env, "struct Present()"); @@ -291,11 +290,6 @@ env_t *global_env(bool source_mapping) { #undef F_opt #undef F #undef C - MAKE_TYPE( // - "PathType", PATH_TYPE_TYPE, Text("PathType_t"), Text("PathType$info"), // - {"Relative", "PATHTYPE_RELATIVE", "PathType"}, // - {"Absolute", "PATHTYPE_ABSOLUTE", "PathType"}, // - {"Home", "PATHTYPE_HOME", "PathType"}), MAKE_TYPE( // "Path", PATH_TYPE, Text("Path_t"), Text("Path$info"), // {"accessed", "Path$accessed", "func(path:Path, follow_symlinks=yes -> Int64?)"}, // @@ -310,6 +304,7 @@ env_t *global_env(bool source_mapping) { {"child", "Path$child", "func(path:Path, child:Text -> Path)"}, // {"children", "Path$children", "func(path:Path, include_hidden=no -> [Path])"}, // {"concatenated_with", "Path$concat", "func(a,b:Path -> Path)"}, // + {"components", "Path$components", "func(path:Path -> [Text])"}, // {"create_directory", "Path$create_directory", "func(path:Path, permissions=Int32(0o755), recursive=yes)"}, // {"current_dir", "Path$current_dir", "func(->Path)"}, // diff --git a/src/environment.h b/src/environment.h index f48271b5..88d36d42 100644 --- a/src/environment.h +++ b/src/environment.h @@ -86,5 +86,4 @@ binding_t *get_namespace_binding(env_t *env, ast_t *self, const char *name); #define code_err(ast, ...) compiler_err((ast)->file, (ast)->start, (ast)->end, __VA_ARGS__) extern type_t *TEXT_TYPE; extern type_t *PATH_TYPE; -extern type_t *PATH_TYPE_TYPE; extern type_t *PRESENT_TYPE; diff --git a/src/stdlib/datatypes.h b/src/stdlib/datatypes.h index fbd2ce73..49c4d835 100644 --- a/src/stdlib/datatypes.h +++ b/src/stdlib/datatypes.h @@ -75,8 +75,8 @@ typedef struct Present$$struct { #define PRESENT_STRUCT ((Present$$type){}) typedef struct { - bool has_value; Present$$type value; + bool has_value; } $OptionalPresent$$type; #define NONE_PRESENT_STRUCT (($OptionalPresent$$type){.has_value = false}) @@ -111,14 +111,46 @@ 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 { - PathType_t type; + Path$AbsolutePath$$type value; + bool has_value; +} $OptionalPath$AbsolutePath$$type; + +typedef struct Path$RelativePath$$struct { + List_t components; +} Path$RelativePath$$type; + +typedef struct { + 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_t Path_t #define OptionalBool_t uint8_t #define OptionalList_t List_t diff --git a/src/stdlib/optionals.h b/src/stdlib/optionals.h index d067ec94..985d6611 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 ((Path_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 6c99cf0b..9fb37ee3 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,12 @@ 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$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,11 +144,11 @@ 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) + 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, ")"); - 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++) { Text_t *p = (Text_t *)(path.components.data + shared * path.components.stride); @@ -568,15 +568,15 @@ Path_t Path$write_unique(Path_t path, Text_t text) { return Path$write_unique_by public Path_t Path$parent(Path_t path) { - if (path.type == PATHTYPE_ABSOLUTE && path.components.length == 0) { + if (path.$tag == Path$tag$AbsolutePath && path.components.length == 0) { return 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; @@ -587,8 +587,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; } @@ -618,7 +618,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); @@ -638,7 +638,7 @@ Path_t Path$with_extension(Path_t path, Text_t extension, bool replace) { fail("Path extension has invalid characters: ", extension); Path_t result = { - .type = path.type, + .$tag = path.$tag, .components = path.components, }; LIST_INCREF(result.components); @@ -759,56 +759,20 @@ Path_t Path$current_dir(void) { return Path$from_str(cwd); } -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); } @@ -829,9 +793,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); @@ -841,52 +805,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 677631b2..7a175099 100644 --- a/src/stdlib/paths.h +++ b/src/stdlib/paths.h @@ -12,6 +12,9 @@ Path_t Path$from_str(const char *str); Path_t Path$from_text(Text_t text); // 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]); @@ -69,5 +72,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/tomo.c b/src/tomo.c index e086166c..04f0289a 100644 --- a/src/tomo.c +++ b/src/tomo.c @@ -28,6 +28,7 @@ #include "stdlib/c_strings.h" #include "stdlib/cli.h" #include "stdlib/datatypes.h" +#include "stdlib/enums.h" #include "stdlib/lists.h" #include "stdlib/optionals.h" #include "stdlib/paths.h" @@ -511,7 +512,7 @@ void install_library(Path_t lib_dir) { Text_t lib_name = get_library_name(lib_dir); Path_t dest = Path$child(Path$from_str(String(TOMO_PATH, "/lib/tomo@" TOMO_VERSION)), lib_name); print("Installing ", lib_dir, " into ", dest); - if (!Path$equal_values(lib_dir, dest)) { + if (!Enum$equal(&lib_dir, &dest, &Path$info)) { if (verbose) whisper("Clearing out any pre-existing version of ", lib_name); xsystem(as_owner, "rm -rf '", dest, "'"); if (verbose) whisper("Moving files to ", dest); diff --git a/src/types.c b/src/types.c index 5c52992b..46df5c64 100644 --- a/src/types.c +++ b/src/types.c @@ -516,7 +516,6 @@ PUREFUNC size_t unpadded_struct_size(type_t *t) { PUREFUNC size_t type_size(type_t *t) { if (t == PATH_TYPE) return sizeof(Path_t); - if (t == PATH_TYPE_TYPE) return sizeof(PathType_t); #ifdef __GNUC__ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wswitch-default" @@ -605,7 +604,6 @@ PUREFUNC size_t type_size(type_t *t) { PUREFUNC size_t type_align(type_t *t) { if (t == PATH_TYPE) return __alignof__(Path_t); - if (t == PATH_TYPE_TYPE) return __alignof__(PathType_t); #ifdef __GNUC__ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wswitch-default" -- cgit v1.2.3 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/environment.c | 27 +++++++----- src/environment.h | 1 + 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 + 9 files changed, 199 insertions(+), 74 deletions(-) create mode 100644 src/stdlib/result.c create mode 100644 src/stdlib/result.h (limited to 'src') diff --git a/src/environment.c b/src/environment.c index bdbc8a4f..f84b6697 100644 --- a/src/environment.c +++ b/src/environment.c @@ -18,6 +18,8 @@ public type_t *PATH_TYPE = NULL; public type_t *PRESENT_TYPE = NULL; +public +type_t *RESULT_TYPE = NULL; static type_t *declare_type(env_t *env, const char *def_str) { ast_t *ast = parse_file_str(def_str); @@ -68,6 +70,7 @@ env_t *global_env(bool source_mapping) { PATH_TYPE = declare_type( env, "enum Path(AbsolutePath(components:[Text]), RelativePath(components:[Text]), HomePath(components:[Text]))"); + RESULT_TYPE = declare_type(env, "enum Result(Success, Failure(reason:Text))"); PRESENT_TYPE = declare_type(env, "struct Present()"); @@ -90,6 +93,7 @@ env_t *global_env(bool source_mapping) { MAKE_TYPE("Abort", Type(AbortType), Text("void"), Text("Abort$info")), MAKE_TYPE("Memory", Type(MemoryType), Text("void"), Text("Memory$info")), MAKE_TYPE("Present", PRESENT_TYPE, Text("Present$$type"), Text("Present$$info")), + MAKE_TYPE("Result", RESULT_TYPE, Text("Result_t"), Text("Result$$info")), MAKE_TYPE( // "Bool", Type(BoolType), Text("Bool_t"), Text("Bool$info"), {"parse", "Bool$parse", "func(text:Text, remainder:&Text?=none -> Bool?)"}), @@ -293,8 +297,9 @@ env_t *global_env(bool source_mapping) { MAKE_TYPE( // "Path", PATH_TYPE, Text("Path_t"), Text("Path$info"), // {"accessed", "Path$accessed", "func(path:Path, follow_symlinks=yes -> Int64?)"}, // - {"append", "Path$append", "func(path:Path, text:Text, permissions=Int32(0o644))"}, // - {"append_bytes", "Path$append_bytes", "func(path:Path, bytes:[Byte], permissions=Int32(0o644))"}, // + {"append", "Path$append", "func(path:Path, text:Text, permissions=Int32(0o644) -> Result)"}, // + {"append_bytes", "Path$append_bytes", + "func(path:Path, bytes:[Byte], permissions=Int32(0o644) -> Result)"}, // {"base_name", "Path$base_name", "func(path:Path -> Text)"}, // {"by_line", "Path$by_line", "func(path:Path -> func(->Text?)?)"}, // {"can_execute", "Path$can_execute", "func(path:Path -> Bool)"}, // @@ -306,7 +311,7 @@ env_t *global_env(bool source_mapping) { {"concatenated_with", "Path$concat", "func(a,b:Path -> Path)"}, // {"components", "Path$components", "func(path:Path -> [Text])"}, // {"create_directory", "Path$create_directory", - "func(path:Path, permissions=Int32(0o755), recursive=yes)"}, // + "func(path:Path, permissions=Int32(0o755), recursive=yes -> Result)"}, // {"current_dir", "Path$current_dir", "func(->Path)"}, // {"exists", "Path$exists", "func(path:Path -> Bool)"}, // {"expand_home", "Path$expand_home", "func(path:Path -> Path)"}, // @@ -324,21 +329,21 @@ env_t *global_env(bool source_mapping) { {"lines", "Path$lines", "func(path:Path -> [Text]?)"}, // {"modified", "Path$modified", "func(path:Path, follow_symlinks=yes -> Int64?)"}, // {"owner", "Path$owner", "func(path:Path, follow_symlinks=yes -> Text?)"}, // - {"parent", "Path$parent", "func(path:Path -> Path)"}, // + {"parent", "Path$parent", "func(path:Path -> Path?)"}, // {"read", "Path$read", "func(path:Path -> Text?)"}, // {"read_bytes", "Path$read_bytes", "func(path:Path, limit:Int?=none -> [Byte]?)"}, // {"relative_to", "Path$relative_to", "func(path:Path, relative_to:Path -> Path)"}, // - {"remove", "Path$remove", "func(path:Path, ignore_missing=no)"}, // + {"remove", "Path$remove", "func(path:Path, ignore_missing=no -> Result)"}, // {"resolved", "Path$resolved", "func(path:Path, relative_to=(./) -> Path)"}, // - {"set_owner", "Path$set_owner", // - "func(path:Path, owner:Text?=none, group:Text?=none, follow_symlinks=yes)"}, // + {"set_owner", "Path$set_owner", + "func(path:Path, owner:Text?=none, group:Text?=none, follow_symlinks=yes -> Result)"}, // {"sibling", "Path$sibling", "func(path:Path, name:Text -> Path)"}, // {"subdirectories", "Path$children", "func(path:Path, include_hidden=no -> [Path])"}, // {"unique_directory", "Path$unique_directory", "func(path:Path -> Path)"}, // - {"write", "Path$write", "func(path:Path, text:Text, permissions=Int32(0o644))"}, // - {"write_bytes", "Path$write_bytes", "func(path:Path, bytes:[Byte], permissions=Int32(0o644))"}, // - {"write_unique", "Path$write_unique", "func(path:Path, text:Text -> Path)"}, // - {"write_unique_bytes", "Path$write_unique_bytes", "func(path:Path, bytes:[Byte] -> Path)"}), + {"write", "Path$write", "func(path:Path, text:Text, permissions=Int32(0o644) -> Result)"}, // + {"write_bytes", "Path$write_bytes", "func(path:Path, bytes:[Byte], permissions=Int32(0o644) -> Result)"}, // + {"write_unique", "Path$write_unique", "func(path:Path, text:Text -> Path?)"}, // + {"write_unique_bytes", "Path$write_unique_bytes", "func(path:Path, bytes:[Byte] -> Path?)"}), MAKE_TYPE( // "Text", TEXT_TYPE, Text("Text_t"), Text("Text$info"), // {"as_c_string", "Text$as_c_string", "func(text:Text -> CString)"}, // diff --git a/src/environment.h b/src/environment.h index 88d36d42..ba036f2e 100644 --- a/src/environment.h +++ b/src/environment.h @@ -87,3 +87,4 @@ binding_t *get_namespace_binding(env_t *env, ast_t *self, const char *name); extern type_t *TEXT_TYPE; extern type_t *PATH_TYPE; extern type_t *PRESENT_TYPE; +extern type_t *RESULT_TYPE; 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 From 890d16fae503a5ffbb9f3c3e70e02938b4f55756 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sun, 7 Dec 2025 22:56:01 -0500 Subject: Remove dead code --- src/stdlib/metamethods.c | 6 ------ src/stdlib/metamethods.h | 1 - 2 files changed, 7 deletions(-) (limited to 'src') 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); -- cgit v1.2.3 From 4c5d15115d2d6c08c080d7d4a39efe039658d616 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sun, 7 Dec 2025 22:57:28 -0500 Subject: Add comments --- src/stdlib/paths.h | 1 + src/stdlib/text.h | 1 + 2 files changed, 2 insertions(+) (limited to 'src') diff --git a/src/stdlib/paths.h b/src/stdlib/paths.h index 4e52866e..881a3c78 100644 --- a/src/stdlib/paths.h +++ b/src/stdlib/paths.h @@ -11,6 +11,7 @@ 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) diff --git a/src/stdlib/text.h b/src/stdlib/text.h index fba8b08f..9ad7441c 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); -- cgit v1.2.3 From 08c47e1fabd1a2fb43c18828db8ad845f7db3a99 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sun, 7 Dec 2025 23:08:08 -0500 Subject: More correct handling for sleep() --- src/environment.c | 2 +- src/stdlib/stdlib.c | 10 ++++++++-- src/stdlib/stdlib.h | 2 +- 3 files changed, 10 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/environment.c b/src/environment.c index f84b6697..cf662749 100644 --- a/src/environment.c +++ b/src/environment.c @@ -541,7 +541,7 @@ env_t *global_env(bool source_mapping) { {"print", "say", "func(text:Text, newline=yes)"}, {"say", "say", "func(text:Text, newline=yes)"}, {"setenv", "setenv_text", "func(name:Text, value:Text?)"}, - {"sleep", "sleep_num", "func(seconds:Num)"}, + {"sleep", "sleep_seconds", "func(seconds:Num)"}, }; for (size_t i = 0; i < sizeof(global_vars) / sizeof(global_vars[0]); i++) { diff --git a/src/stdlib/stdlib.c b/src/stdlib/stdlib.c index 8ec9e90b..defb263c 100644 --- a/src/stdlib/stdlib.c +++ b/src/stdlib/stdlib.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -205,11 +206,16 @@ cleanup: } public -void sleep_num(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 diff --git a/src/stdlib/stdlib.h b/src/stdlib/stdlib.h index 392b5f23..3afe3529 100644 --- a/src/stdlib/stdlib.h +++ b/src/stdlib/stdlib.h @@ -79,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); -- cgit v1.2.3 From 86228917b98a3ef4019e9e18fcafacc948ffcfd1 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sun, 7 Dec 2025 23:12:33 -0500 Subject: Better error handling for setenv() --- src/stdlib/stdlib.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/stdlib/stdlib.c b/src/stdlib/stdlib.c index defb263c..f4e6d678 100644 --- a/src/stdlib/stdlib.c +++ b/src/stdlib/stdlib.c @@ -226,8 +226,16 @@ OptionalText_t getenv_text(Text_t name) { public void setenv_text(Text_t name, OptionalText_t value) { - if (value.tag == TEXT_NONE) unsetenv(Text$as_c_string(name)); - else setenv(Text$as_c_string(name), Text$as_c_string(value), 1); + 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 { -- cgit v1.2.3 From 5a194a3e0dc01d7ba0d9ad81030284ebda13cbd6 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Mon, 8 Dec 2025 02:08:47 -0500 Subject: Add checks for unused variables --- src/ast.c | 25 ++--- src/ast.h | 5 +- src/compile/files.c | 3 +- src/compile/functions.c | 282 ++++++++++++++++++++++++++++++++++-------------- src/compile/headers.c | 6 +- 5 files changed, 223 insertions(+), 98 deletions(-) (limited to 'src') diff --git a/src/ast.c b/src/ast.c index b2730d21..e87ca005 100644 --- a/src/ast.c +++ b/src/ast.c @@ -443,19 +443,19 @@ CONSTFUNC ast_e binop_tag(ast_e tag) { } } -static void ast_visit_list(ast_list_t *ast_list, void (*visitor)(ast_t *, void *), void *userdata) { +static void ast_visit_list(ast_list_t *ast_list, visit_behavior_t (*visitor)(ast_t *, void *), void *userdata) { for (ast_list_t *ast = ast_list; ast; ast = ast->next) ast_visit(ast->ast, visitor, userdata); } -static void ast_visit_args(arg_ast_t *args, void (*visitor)(ast_t *, void *), void *userdata) { +static void ast_visit_args(arg_ast_t *args, visit_behavior_t (*visitor)(ast_t *, void *), void *userdata) { for (arg_ast_t *arg = args; arg; arg = arg->next) ast_visit(arg->value, visitor, userdata); } -void ast_visit(ast_t *ast, void (*visitor)(ast_t *, void *), void *userdata) { +void ast_visit(ast_t *ast, visit_behavior_t (*visitor)(ast_t *, void *), void *userdata) { if (!ast) return; - visitor(ast, userdata); + if (visitor(ast, userdata) == VISIT_STOP) return; switch (ast->tag) { case Unknown: @@ -686,26 +686,25 @@ void ast_visit(ast_t *ast, void (*visitor)(ast_t *, void *), void *userdata) { static void _recursive_type_ast_visit(type_ast_t *ast, void *userdata) { if (ast == NULL) return; - void (*visit)(type_ast_t *, void *) = ((Closure_t *)userdata)->fn; + visit_behavior_t (*visit)(type_ast_t *, void *) = ((Closure_t *)userdata)->fn; void *visitor_userdata = ((Closure_t *)userdata)->userdata; + if (visit(ast, visitor_userdata) == VISIT_STOP) return; + switch (ast->tag) { case UnknownTypeAST: - case VarTypeAST: visit(ast, visitor_userdata); break; + case VarTypeAST: break; case PointerTypeAST: { _recursive_type_ast_visit(Match(ast, PointerTypeAST)->pointed, userdata); - visit(ast, visitor_userdata); break; } case ListTypeAST: { _recursive_type_ast_visit(Match(ast, ListTypeAST)->item, userdata); - visit(ast, visitor_userdata); break; } case TableTypeAST: { DeclareMatch(table, ast, TableTypeAST); _recursive_type_ast_visit(table->key, userdata); _recursive_type_ast_visit(table->value, userdata); - visit(ast, visitor_userdata); break; } case FunctionTypeAST: { @@ -713,12 +712,10 @@ static void _recursive_type_ast_visit(type_ast_t *ast, void *userdata) { for (arg_ast_t *arg = fn->args; arg; arg = arg->next) _recursive_type_ast_visit(arg->type, userdata); _recursive_type_ast_visit(fn->ret, userdata); - visit(ast, visitor_userdata); break; } case OptionalTypeAST: { _recursive_type_ast_visit(Match(ast, OptionalTypeAST)->type, userdata); - visit(ast, visitor_userdata); break; } case EnumTypeAST: { @@ -727,14 +724,13 @@ static void _recursive_type_ast_visit(type_ast_t *ast, void *userdata) { _recursive_type_ast_visit(field->type, userdata); } } - visit(ast, visitor_userdata); break; } default: errx(1, "Invalid type AST"); } } -static void _type_ast_visit(ast_t *ast, void *userdata) { +static visit_behavior_t _type_ast_visit(ast_t *ast, void *userdata) { switch (ast->tag) { case Declare: { _recursive_type_ast_visit(Match(ast, Declare)->type, userdata); @@ -777,9 +773,10 @@ static void _type_ast_visit(ast_t *ast, void *userdata) { } default: break; } + return VISIT_PROCEED; } -void type_ast_visit(ast_t *ast, void (*visitor)(type_ast_t *, void *), void *userdata) { +void type_ast_visit(ast_t *ast, visit_behavior_t (*visitor)(type_ast_t *, void *), void *userdata) { Closure_t fn = {.fn = visitor, .userdata = userdata}; ast_visit(ast, _type_ast_visit, &fn); } diff --git a/src/ast.h b/src/ast.h index b852da2a..b6930ab7 100644 --- a/src/ast.h +++ b/src/ast.h @@ -488,6 +488,7 @@ void visit_topologically(ast_list_t *ast, Closure_t fn); CONSTFUNC bool is_update_assignment(ast_t *ast); CONSTFUNC ast_e binop_tag(ast_e tag); CONSTFUNC bool is_binary_operation(ast_t *ast); -void ast_visit(ast_t *ast, void (*visitor)(ast_t *, void *), void *userdata); -void type_ast_visit(ast_t *ast, void (*visitor)(type_ast_t *, void *), void *userdata); +typedef enum { VISIT_STOP, VISIT_PROCEED } visit_behavior_t; +void ast_visit(ast_t *ast, visit_behavior_t (*visitor)(ast_t *, void *), void *userdata); +void type_ast_visit(ast_t *ast, visit_behavior_t (*visitor)(type_ast_t *, void *), void *userdata); OptionalText_t ast_metadata(ast_t *ast, const char *key); diff --git a/src/compile/files.c b/src/compile/files.c index 03c08bf6..27c2e041 100644 --- a/src/compile/files.c +++ b/src/compile/files.c @@ -152,7 +152,7 @@ typedef struct { Text_t *code; } compile_info_t; -static void add_type_infos(type_ast_t *type_ast, void *userdata) { +static visit_behavior_t add_type_infos(type_ast_t *type_ast, void *userdata) { if (type_ast && type_ast->tag == EnumTypeAST) { compile_info_t *info = (compile_info_t *)userdata; // Force the type to get defined: @@ -164,6 +164,7 @@ static void add_type_infos(type_ast_t *type_ast, void *userdata) { compile_enum_constructors(info->env, String("enum$", (int64_t)(type_ast->start - type_ast->file->text)), Match(type_ast, EnumTypeAST)->tags)); } + return VISIT_PROCEED; } public diff --git a/src/compile/functions.c b/src/compile/functions.c index 63d7d23d..ec37c0ad 100644 --- a/src/compile/functions.c +++ b/src/compile/functions.c @@ -3,6 +3,7 @@ #include "../ast.h" #include "../environment.h" #include "../naming.h" +#include "../stdlib/c_strings.h" #include "../stdlib/datatypes.h" #include "../stdlib/integers.h" #include "../stdlib/nums.h" @@ -247,85 +248,6 @@ Text_t compile_function_call(env_t *env, ast_t *ast) { } } -public -Text_t compile_lambda(env_t *env, ast_t *ast) { - DeclareMatch(lambda, ast, Lambda); - Text_t name = namespace_name(env, env->namespace, Texts("lambda$", lambda->id)); - - env_t *body_scope = fresh_scope(env); - body_scope->deferred = NULL; - for (arg_ast_t *arg = lambda->args; arg; arg = arg->next) { - type_t *arg_type = get_arg_ast_type(env, arg); - set_binding(body_scope, arg->name, arg_type, Texts("_$", arg->name)); - } - - body_scope->fn = ast; - - Table_t closed_vars = get_closed_vars(env, lambda->args, ast); - if (Table$length(closed_vars) > 0) { // Create a typedef for the lambda's closure userdata - Text_t def = Text("typedef struct {"); - for (int64_t i = 0; i < (int64_t)closed_vars.entries.length; i++) { - struct { - const char *name; - binding_t *b; - } *entry = closed_vars.entries.data + closed_vars.entries.stride * i; - if (has_stack_memory(entry->b->type)) - code_err(ast, "This function is holding onto a reference to ", type_to_text(entry->b->type), - " stack memory in the variable `", entry->name, - "`, but the function may outlive the stack memory"); - if (entry->b->type->tag == ModuleType) continue; - set_binding(body_scope, entry->name, entry->b->type, Texts("userdata->", entry->name)); - def = Texts(def, compile_declaration(entry->b->type, Text$from_str(entry->name)), "; "); - } - def = Texts(def, "} ", name, "$userdata_t;"); - env->code->local_typedefs = Texts(env->code->local_typedefs, def); - } - - type_t *ret_t = get_function_return_type(env, ast); - Text_t code = Texts("static ", compile_type(ret_t), " ", name, "("); - for (arg_ast_t *arg = lambda->args; arg; arg = arg->next) { - type_t *arg_type = get_arg_ast_type(env, arg); - code = Texts(code, compile_type(arg_type), " _$", arg->name, ", "); - } - - Text_t userdata; - if (Table$length(closed_vars) == 0) { - code = Texts(code, "void *_)"); - userdata = Text("NULL"); - } else { - userdata = Texts("new(", name, "$userdata_t"); - for (int64_t i = 0; i < (int64_t)closed_vars.entries.length; i++) { - struct { - const char *name; - binding_t *b; - } *entry = closed_vars.entries.data + closed_vars.entries.stride * i; - if (entry->b->type->tag == ModuleType) continue; - binding_t *b = get_binding(env, entry->name); - assert(b); - Text_t binding_code = b->code; - if (entry->b->type->tag == ListType) userdata = Texts(userdata, ", LIST_COPY(", binding_code, ")"); - else if (entry->b->type->tag == TableType) userdata = Texts(userdata, ", TABLE_COPY(", binding_code, ")"); - else userdata = Texts(userdata, ", ", binding_code); - } - userdata = Texts(userdata, ")"); - code = Texts(code, name, "$userdata_t *userdata)"); - } - - Text_t body = EMPTY_TEXT; - for (ast_list_t *stmt = Match(lambda->body, Block)->statements; stmt; stmt = stmt->next) { - if (stmt->next || ret_t->tag == VoidType || ret_t->tag == AbortType - || get_type(body_scope, stmt->ast)->tag == ReturnType) - body = Texts(body, compile_statement(body_scope, stmt->ast), "\n"); - else body = Texts(body, compile_statement(body_scope, FakeAST(Return, stmt->ast)), "\n"); - bind_statement(body_scope, stmt->ast); - } - if ((ret_t->tag == VoidType || ret_t->tag == AbortType) && body_scope->deferred) - body = Texts(body, compile_statement(body_scope, FakeAST(Return)), "\n"); - - env->code->lambdas = Texts(env->code->lambdas, code, " {\n", body, "\n}\n"); - return Texts("((Closure_t){", name, ", ", userdata, "})"); -} - static void add_closed_vars(Table_t *closed_vars, env_t *enclosing_scope, env_t *env, ast_t *ast) { if (ast == NULL) return; @@ -595,6 +517,206 @@ Table_t get_closed_vars(env_t *env, arg_ast_t *args, ast_t *block) { return closed_vars; } +static visit_behavior_t find_used_variables(ast_t *ast, void *userdata) { + Table_t *vars = (Table_t *)userdata; + switch (ast->tag) { + case Var: { + const char *name = Match(ast, Var)->name; + Table$str_set(vars, name, ast); + return VISIT_STOP; + } + case Assign: { + for (ast_list_t *target = Match(ast, Assign)->targets; target; target = target->next) { + ast_t *var = target->ast; + for (;;) { + if (var->tag == Index) { + ast_t *index = Match(var, Index)->index; + if (index) ast_visit(index, find_used_variables, userdata); + var = Match(var, Index)->indexed; + } else if (var->tag == FieldAccess) { + var = Match(var, FieldAccess)->fielded; + } else { + break; + } + } + } + for (ast_list_t *val = Match(ast, Assign)->values; val; val = val->next) { + ast_visit(val->ast, find_used_variables, userdata); + } + return VISIT_STOP; + } + case UPDATE_CASES: { + binary_operands_t operands = BINARY_OPERANDS(ast); + ast_t *lhs = operands.lhs; + for (;;) { + if (lhs->tag == Index) { + ast_t *index = Match(lhs, Index)->index; + if (index) ast_visit(index, find_used_variables, userdata); + lhs = Match(lhs, Index)->indexed; + } else if (lhs->tag == FieldAccess) { + lhs = Match(lhs, FieldAccess)->fielded; + } else { + break; + } + } + ast_visit(operands.rhs, find_used_variables, userdata); + return VISIT_STOP; + } + case Declare: { + ast_visit(Match(ast, Declare)->value, find_used_variables, userdata); + return VISIT_STOP; + } + default: return VISIT_PROCEED; + } +} + +static visit_behavior_t find_assigned_variables(ast_t *ast, void *userdata) { + Table_t *vars = (Table_t *)userdata; + switch (ast->tag) { + case Assign: + for (ast_list_t *target = Match(ast, Assign)->targets; target; target = target->next) { + ast_t *var = target->ast; + for (;;) { + if (var->tag == Index) var = Match(var, Index)->indexed; + else if (var->tag == FieldAccess) var = Match(var, FieldAccess)->fielded; + else break; + } + if (var->tag == Var) { + const char *name = Match(var, Var)->name; + Table$str_set(vars, name, var); + } + } + return VISIT_STOP; + case UPDATE_CASES: { + binary_operands_t operands = BINARY_OPERANDS(ast); + ast_t *var = operands.lhs; + for (;;) { + if (var->tag == Index) var = Match(var, Index)->indexed; + else if (var->tag == FieldAccess) var = Match(var, FieldAccess)->fielded; + else break; + } + if (var->tag == Var) { + const char *name = Match(var, Var)->name; + Table$str_set(vars, name, var); + } + return VISIT_STOP; + } + case Declare: { + ast_t *var = Match(ast, Declare)->var; + const char *name = Match(var, Var)->name; + Table$str_set(vars, name, var); + return VISIT_STOP; + } + default: return VISIT_PROCEED; + } +} + +static void check_unused_vars(env_t *env, arg_ast_t *args, ast_t *body) { + Table_t used_vars = EMPTY_TABLE; + ast_visit(body, find_used_variables, &used_vars); + Table_t assigned_vars = EMPTY_TABLE; + ast_visit(body, find_assigned_variables, &assigned_vars); + + for (arg_ast_t *arg = args; arg; arg = arg->next) { + type_t *arg_type = get_arg_ast_type(env, arg); + if (arg_type->tag == PointerType) { + Table$str_remove(&assigned_vars, arg->name); + } + } + + Table_t unused = Table$without(assigned_vars, used_vars, Table$info(&CString$info, &Present$$info)); + for (int64_t i = 0; i < (int64_t)unused.entries.length; i++) { + struct { + const char *name; + } *entry = unused.entries.data + i * unused.entries.stride; + if (streq(entry->name, "_")) continue; + ast_t *var = Table$str_get(assigned_vars, entry->name); + code_err(var, "This variable was assigned to, but never read from."); + } +} + +public +Text_t compile_lambda(env_t *env, ast_t *ast) { + DeclareMatch(lambda, ast, Lambda); + Text_t name = namespace_name(env, env->namespace, Texts("lambda$", lambda->id)); + + env_t *body_scope = fresh_scope(env); + body_scope->deferred = NULL; + for (arg_ast_t *arg = lambda->args; arg; arg = arg->next) { + type_t *arg_type = get_arg_ast_type(env, arg); + set_binding(body_scope, arg->name, arg_type, Texts("_$", arg->name)); + } + + body_scope->fn = ast; + + Table_t closed_vars = get_closed_vars(env, lambda->args, ast); + if (Table$length(closed_vars) > 0) { // Create a typedef for the lambda's closure userdata + Text_t def = Text("typedef struct {"); + for (int64_t i = 0; i < (int64_t)closed_vars.entries.length; i++) { + struct { + const char *name; + binding_t *b; + } *entry = closed_vars.entries.data + closed_vars.entries.stride * i; + if (has_stack_memory(entry->b->type)) + code_err(ast, "This function is holding onto a reference to ", type_to_text(entry->b->type), + " stack memory in the variable `", entry->name, + "`, but the function may outlive the stack memory"); + if (entry->b->type->tag == ModuleType) continue; + set_binding(body_scope, entry->name, entry->b->type, Texts("userdata->", entry->name)); + def = Texts(def, compile_declaration(entry->b->type, Text$from_str(entry->name)), "; "); + } + def = Texts(def, "} ", name, "$userdata_t;"); + env->code->local_typedefs = Texts(env->code->local_typedefs, def); + } + + type_t *ret_t = get_function_return_type(env, ast); + Text_t code = Texts("static ", compile_type(ret_t), " ", name, "("); + for (arg_ast_t *arg = lambda->args; arg; arg = arg->next) { + type_t *arg_type = get_arg_ast_type(env, arg); + code = Texts(code, compile_type(arg_type), " _$", arg->name, ", "); + } + + Text_t userdata; + if (Table$length(closed_vars) == 0) { + code = Texts(code, "void *_)"); + userdata = Text("NULL"); + } else { + userdata = Texts("new(", name, "$userdata_t"); + for (int64_t i = 0; i < (int64_t)closed_vars.entries.length; i++) { + struct { + const char *name; + binding_t *b; + } *entry = closed_vars.entries.data + closed_vars.entries.stride * i; + if (entry->b->type->tag == ModuleType) continue; + binding_t *b = get_binding(env, entry->name); + assert(b); + Text_t binding_code = b->code; + if (entry->b->type->tag == ListType) userdata = Texts(userdata, ", LIST_COPY(", binding_code, ")"); + else if (entry->b->type->tag == TableType) userdata = Texts(userdata, ", TABLE_COPY(", binding_code, ")"); + else userdata = Texts(userdata, ", ", binding_code); + } + userdata = Texts(userdata, ")"); + code = Texts(code, name, "$userdata_t *userdata)"); + } + + Text_t body = EMPTY_TEXT; + for (ast_list_t *stmt = Match(lambda->body, Block)->statements; stmt; stmt = stmt->next) { + if (stmt->next || ret_t->tag == VoidType || ret_t->tag == AbortType + || get_type(body_scope, stmt->ast)->tag == ReturnType) + body = Texts(body, compile_statement(body_scope, stmt->ast), "\n"); + else body = Texts(body, compile_statement(body_scope, FakeAST(Return, stmt->ast)), "\n"); + bind_statement(body_scope, stmt->ast); + } + if ((ret_t->tag == VoidType || ret_t->tag == AbortType) && body_scope->deferred) + body = Texts(body, compile_statement(body_scope, FakeAST(Return)), "\n"); + + env->code->lambdas = Texts(env->code->lambdas, code, " {\n", body, "\n}\n"); + + check_unused_vars(env, lambda->args, lambda->body); + + return Texts("((Closure_t){", name, ", ", userdata, "})"); +} + public Text_t compile_function(env_t *env, Text_t name_code, ast_t *ast, Text_t *staticdefs) { bool is_private = false; @@ -785,6 +907,8 @@ Text_t compile_function(env_t *env, Text_t name_code, ast_t *ast, Text_t *static } } + check_unused_vars(env, args, body); + return definition; } diff --git a/src/compile/headers.c b/src/compile/headers.c index dc57da77..e90556a1 100644 --- a/src/compile/headers.c +++ b/src/compile/headers.c @@ -104,8 +104,8 @@ static void _define_types_and_funcs(compile_typedef_info_t *info, ast_t *ast) { compile_statement_namespace_header(info->env, info->header_path, ast)); } -static void add_type_headers(type_ast_t *type_ast, void *userdata) { - if (!type_ast) return; +static visit_behavior_t add_type_headers(type_ast_t *type_ast, void *userdata) { + if (!type_ast) return VISIT_STOP; if (type_ast->tag == EnumTypeAST) { compile_typedef_info_t *info = (compile_typedef_info_t *)userdata; @@ -126,6 +126,8 @@ static void add_type_headers(type_ast_t *type_ast, void *userdata) { *info->header = Texts(*info->header, compile_enum_header(info->env, name, enum_->tags)); } + + return VISIT_PROCEED; } public -- cgit v1.2.3