aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorBruce Hill <bruce@bruce-hill.com>2025-08-31 15:34:18 -0400
committerBruce Hill <bruce@bruce-hill.com>2025-08-31 15:34:18 -0400
commitf775d6602989eec9d37deb16979922004a77a70c (patch)
tree866eee67d5800202575ffb401c750bef253dc1dc /src
parentb025a65b45e8f2e8a56eb087743f9e9e7109b9f8 (diff)
parent64fe11554a0aeea1f176116a2a483c623d4cf60a (diff)
Merge branch 'main' into formatter
Diffstat (limited to 'src')
-rw-r--r--src/ast.h2
-rw-r--r--src/compile/cli.c47
-rw-r--r--src/compile/functions.c46
-rw-r--r--src/parse/functions.c22
-rw-r--r--src/stdlib/cli.c389
-rw-r--r--src/stdlib/cli.h21
-rw-r--r--src/stdlib/stdlib.c374
-rw-r--r--src/stdlib/stdlib.h15
-rw-r--r--src/stdlib/tomo.h1
-rw-r--r--src/tomo.c12
-rw-r--r--src/typecheck.c20
-rw-r--r--src/types.h2
12 files changed, 503 insertions, 448 deletions
diff --git a/src/ast.h b/src/ast.h
index 3ac24635..02c5ad74 100644
--- a/src/ast.h
+++ b/src/ast.h
@@ -67,7 +67,7 @@ typedef struct ast_list_s {
typedef struct arg_ast_s {
file_t *file;
const char *start, *end;
- const char *name;
+ const char *name, *alias;
type_ast_t *type;
ast_t *value;
struct arg_ast_s *next;
diff --git a/src/compile/cli.c b/src/compile/cli.c
index 4a3be333..b082239d 100644
--- a/src/compile/cli.c
+++ b/src/compile/cli.c
@@ -2,6 +2,7 @@
#include "../environment.h"
#include "../stdlib/datatypes.h"
+#include "../stdlib/optionals.h"
#include "../stdlib/text.h"
#include "../stdlib/util.h"
#include "../typecheck.h"
@@ -25,6 +26,14 @@ static Text_t get_flag_options(type_t *t, const char *separator) {
}
}
+static OptionalText_t flagify(const char *name, bool prefix) {
+ if (!name) return NONE_TEXT;
+ Text_t flag = Text$from_str(name);
+ flag = Text$replace(flag, Text("_"), Text("-"));
+ if (prefix) flag = flag.length == 1 ? Texts("-", flag) : Texts("--", flag);
+ return flag;
+}
+
public
Text_t compile_cli_arg_call(env_t *env, Text_t fn_name, type_t *fn_type, const char *version) {
DeclareMatch(fn_info, fn_type, FunctionType);
@@ -49,20 +58,22 @@ Text_t compile_cli_arg_call(env_t *env, Text_t fn_name, type_t *fn_type, const c
for (arg_t *arg = fn_info->args; arg; arg = arg->next) {
usage = Texts(usage, " ");
type_t *t = get_arg_type(main_env, arg);
- Text_t flag = Text$replace(Text$from_str(arg->name), Text("_"), Text("-"));
if (arg->default_val || arg->type->tag == OptionalType) {
- if (strlen(arg->name) == 1) {
- if (t->tag == BoolType || (t->tag == OptionalType && Match(t, OptionalType)->type->tag == BoolType))
- usage = Texts(usage, "[-", flag, "]");
- else usage = Texts(usage, "[-", flag, " ", get_flag_options(t, "|"), "]");
- } else {
- if (t->tag == BoolType || (t->tag == OptionalType && Match(t, OptionalType)->type->tag == BoolType))
- usage = Texts(usage, "[--", flag, "]");
- else if (t->tag == ListType) usage = Texts(usage, "[--", flag, " ", get_flag_options(t, "|"), "]");
- else usage = Texts(usage, "[--", flag, "=", get_flag_options(t, "|"), "]");
- }
+ OptionalText_t flag = flagify(arg->name, true);
+ assert(flag.length >= 0);
+ OptionalText_t alias_flag = flagify(arg->alias, true);
+ Text_t flags = alias_flag.length >= 0 ? Texts(flag, "|", alias_flag) : flag;
+ 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, "|"), "]");
+ else usage = Texts(usage, "[", flags, "=", get_flag_options(t, "|"), "]");
} else {
- if (t->tag == BoolType) usage = Texts(usage, "<--", flag, "|--no-", flag, ">");
+ OptionalText_t flag = flagify(arg->name, false);
+ assert(flag.length >= 0);
+ OptionalText_t alias_flag = flagify(arg->alias, true);
+ if (t->tag == BoolType)
+ usage = Texts(usage, "<--", flag, alias_flag.length >= 0 ? Texts("|", alias_flag) : EMPTY_TEXT,
+ "|--no-", flag, ">");
else if (t->tag == EnumType) usage = Texts(usage, get_flag_options(t, "|"));
else if (t->tag == ListType) usage = Texts(usage, "[", flag, "...]");
else usage = Texts(usage, "<", flag, ">");
@@ -76,9 +87,10 @@ Text_t compile_cli_arg_call(env_t *env, Text_t fn_name, type_t *fn_type, const c
for (arg_t *arg = fn_info->args; arg; arg = arg->next) {
type_t *opt_type = arg->type->tag == OptionalType ? arg->type : Type(OptionalType, .type = arg->type);
- code = Texts(code, compile_declaration(opt_type, Texts("_$", arg->name)));
+ code = Texts(code, compile_declaration(opt_type, Texts("_$", Text$from_str(arg->name))));
if (arg->default_val) {
- Text_t default_val = compile(env, arg->default_val);
+ Text_t default_val =
+ arg->type ? compile_to_type(env, arg->default_val, arg->type) : compile(env, arg->default_val);
if (arg->type->tag != OptionalType) default_val = promote_to_optional(arg->type, default_val);
code = Texts(code, " = ", default_val);
} else {
@@ -92,7 +104,12 @@ Text_t compile_cli_arg_call(env_t *env, Text_t fn_name, type_t *fn_type, const c
for (arg_t *arg = fn_info->args; arg; arg = arg->next) {
code = Texts(code, ",\n{", quoted_text(Text$replace(Text$from_str(arg->name), Text("_"), Text("-"))), ", ",
(arg->default_val || arg->type->tag == OptionalType) ? "false" : "true", ", ",
- compile_type_info(arg->type), ", &", Texts("_$", arg->name), "}");
+ compile_type_info(arg->type), ", &", Texts("_$", Text$from_str(arg->name)), "}");
+ if (arg->alias) {
+ code = Texts(code, ",\n{", quoted_text(Text$replace(Text$from_str(arg->alias), Text("_"), Text("-"))), ", ",
+ (arg->default_val || arg->type->tag == OptionalType) ? "false" : "true", ", ",
+ compile_type_info(arg->type), ", &", Texts("_$", Text$from_str(arg->name)), "}");
+ }
}
code = Texts(code, ");\n");
diff --git a/src/compile/functions.c b/src/compile/functions.c
index 665c7379..01de26e3 100644
--- a/src/compile/functions.c
+++ b/src/compile/functions.c
@@ -71,29 +71,31 @@ Text_t compile_arguments(env_t *env, ast_t *call_ast, arg_t *spec_args, arg_ast_
for (arg_t *spec_arg = spec_args; spec_arg; spec_arg = spec_arg->next) {
int64_t i = 1;
// Find keyword:
- if (spec_arg->name) {
- for (arg_ast_t *call_arg = call_args; call_arg; call_arg = call_arg->next) {
- if (call_arg->name && streq(call_arg->name, spec_arg->name)) {
- Text_t value;
- if (spec_arg->type->tag == IntType && call_arg->value->tag == Int) {
- value = compile_int_to_type(env, call_arg->value, spec_arg->type);
- } else if (spec_arg->type->tag == NumType && call_arg->value->tag == Int) {
- OptionalInt_t int_val = Int$from_str(Match(call_arg->value, Int)->str);
- if (int_val.small == 0) code_err(call_arg->value, "Failed to parse this integer");
- if (Match(spec_arg->type, NumType)->bits == TYPE_NBITS64)
- value = Text$from_str(String(hex_double(Num$from_int(int_val, false))));
- else value = Text$from_str(String(hex_double((double)Num32$from_int(int_val, false)), "f"));
- } else {
- env_t *arg_env = with_enum_scope(env, spec_arg->type);
- value = compile_maybe_incref(arg_env, call_arg->value, spec_arg->type);
- }
- Table$str_set(&used_args, call_arg->name, call_arg);
- if (code.length > 0) code = Texts(code, ", ");
- code = Texts(code, value);
- goto found_it;
- }
+ assert(spec_arg->name);
+ for (arg_ast_t *call_arg = call_args; call_arg; call_arg = call_arg->next) {
+ if (!call_arg->name) continue;
+ if (!(streq(call_arg->name, spec_arg->name) || (spec_arg->alias && streq(call_arg->name, spec_arg->alias))))
+ continue;
+
+ Text_t value;
+ if (spec_arg->type->tag == IntType && call_arg->value->tag == Int) {
+ value = compile_int_to_type(env, call_arg->value, spec_arg->type);
+ } else if (spec_arg->type->tag == NumType && call_arg->value->tag == Int) {
+ OptionalInt_t int_val = Int$from_str(Match(call_arg->value, Int)->str);
+ if (int_val.small == 0) code_err(call_arg->value, "Failed to parse this integer");
+ if (Match(spec_arg->type, NumType)->bits == TYPE_NBITS64)
+ value = Text$from_str(String(hex_double(Num$from_int(int_val, false))));
+ else value = Text$from_str(String(hex_double((double)Num32$from_int(int_val, false)), "f"));
+ } else {
+ env_t *arg_env = with_enum_scope(env, spec_arg->type);
+ value = compile_maybe_incref(arg_env, call_arg->value, spec_arg->type);
}
+ Table$str_set(&used_args, call_arg->name, call_arg);
+ if (code.length > 0) code = Texts(code, ", ");
+ code = Texts(code, value);
+ goto found_it;
}
+
// Find positional:
for (arg_ast_t *call_arg = call_args; call_arg; call_arg = call_arg->next) {
if (call_arg->name) continue;
@@ -161,7 +163,7 @@ 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 public signature doesn't match this call site.\n"
+ "This function's signature doesn't match this call site.\n"
"The signature is: ",
type_to_text(fn_t),
"\n"
diff --git a/src/parse/functions.c b/src/parse/functions.c
index b50519b7..6cb085b7 100644
--- a/src/parse/functions.c
+++ b/src/parse/functions.c
@@ -27,7 +27,7 @@ arg_ast_t *parse_args(parse_ctx_t *ctx, const char **pos) {
typedef struct name_list_s {
const char *start, *end;
- const char *name;
+ const char *name, *alias;
struct name_list_s *next;
} name_list_t;
@@ -39,19 +39,29 @@ arg_ast_t *parse_args(parse_ctx_t *ctx, const char **pos) {
const char *name_start = *pos;
whitespace(ctx, pos);
+ const char *alias = NULL;
+ if (match(pos, "|")) {
+ whitespace(pos);
+ alias = get_id(pos);
+ if (!alias) parser_err(ctx, *pos, *pos, "I expected an argument alias after `|`");
+ }
+
if (match(pos, ":")) {
type = expect(ctx, *pos - 1, pos, parse_type, "I expected a type here");
whitespace(ctx, pos);
if (match(pos, "="))
default_val = expect(ctx, *pos - 1, pos, parse_term, "I expected a value after this '='");
- names = new (name_list_t, .start = name_start, .end = *pos, .name = name, .next = names);
+ names =
+ new (name_list_t, .start = name_start, .end = *pos, .name = name, .alias = alias, .next = names);
break;
} else if (strncmp(*pos, "==", 2) != 0 && match(pos, "=")) {
default_val = expect(ctx, *pos - 1, pos, parse_term, "I expected a value after this '='");
- names = new (name_list_t, .start = name_start, .end = *pos, .name = name, .next = names);
+ names =
+ new (name_list_t, .start = name_start, .end = *pos, .name = name, .alias = alias, .next = names);
break;
} else if (name) {
- names = new (name_list_t, .start = name_start, .end = *pos, .name = name, .next = names);
+ names =
+ new (name_list_t, .start = name_start, .end = *pos, .name = name, .alias = alias, .next = names);
spaces(pos);
if (!match(pos, ",")) break;
} else {
@@ -66,8 +76,8 @@ arg_ast_t *parse_args(parse_ctx_t *ctx, const char **pos) {
REVERSE_LIST(names);
for (; names; names = names->next)
- args = new (arg_ast_t, .start = names->start, .end = names->end, .name = names->name, .type = type,
- .value = default_val, .next = args);
+ args = new (arg_ast_t, .start = names->start, .end = names->end, .name = names->name, .alias = names->name,
+ .type = type, .value = default_val, .next = args);
if (!match_separator(ctx, pos)) break;
}
diff --git a/src/stdlib/cli.c b/src/stdlib/cli.c
new file mode 100644
index 00000000..62174f19
--- /dev/null
+++ b/src/stdlib/cli.c
@@ -0,0 +1,389 @@
+// Comman-line argument parsing
+
+#include <ctype.h>
+#include <execinfo.h>
+#include <fcntl.h>
+#include <gc.h>
+#include <math.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <sys/param.h>
+#include <time.h>
+
+#include "../config.h"
+#include "bools.h"
+#include "bytes.h"
+#include "cli.h"
+#include "integers.h"
+#include "metamethods.h"
+#include "nums.h"
+#include "optionals.h"
+#include "paths.h"
+#include "print.h"
+#include "stdlib.h"
+#include "tables.h"
+#include "text.h"
+#include "util.h"
+
+static bool is_numeric_type(const TypeInfo_t *info) {
+ return (info == &Num$info || info == &Num32$info || info == &Int$info || info == &Int64$info || info == &Int32$info
+ || info == &Int16$info || info == &Int8$info || info == &Byte$info);
+}
+
+static bool parse_single_arg(const TypeInfo_t *info, char *arg, void *dest) {
+ if (!arg) return false;
+
+ if (info->tag == OptionalInfo && streq(arg, "none")) return true;
+
+ while (info->tag == OptionalInfo)
+ info = info->OptionalInfo.type;
+
+ if (info == &Int$info) {
+ OptionalInt_t parsed = Int$from_str(arg);
+ if (parsed.small != 0) *(OptionalInt_t *)dest = parsed;
+ return parsed.small != 0;
+ } else if (info == &Int64$info) {
+ OptionalInt64_t parsed = Int64$parse(Text$from_str(arg), NULL);
+ if (!parsed.is_none) *(OptionalInt64_t *)dest = parsed;
+ return !parsed.is_none;
+ } else if (info == &Int32$info) {
+ OptionalInt32_t parsed = Int32$parse(Text$from_str(arg), NULL);
+ if (!parsed.is_none) *(OptionalInt32_t *)dest = parsed;
+ return !parsed.is_none;
+ } else if (info == &Int16$info) {
+ OptionalInt16_t parsed = Int16$parse(Text$from_str(arg), NULL);
+ if (!parsed.is_none) *(OptionalInt16_t *)dest = parsed;
+ return !parsed.is_none;
+ } else if (info == &Int8$info) {
+ OptionalInt8_t parsed = Int8$parse(Text$from_str(arg), NULL);
+ if (!parsed.is_none) *(OptionalInt8_t *)dest = parsed;
+ return !parsed.is_none;
+ } else if (info == &Byte$info) {
+ OptionalByte_t parsed = Byte$parse(Text$from_str(arg), NULL);
+ if (!parsed.is_none) *(OptionalByte_t *)dest = parsed;
+ return !parsed.is_none;
+ } else if (info == &Bool$info) {
+ OptionalBool_t parsed = Bool$parse(Text$from_str(arg), NULL);
+ if (parsed != NONE_BOOL) *(OptionalBool_t *)dest = parsed;
+ return parsed != NONE_BOOL;
+ } else if (info == &Num$info) {
+ OptionalNum_t parsed = Num$parse(Text$from_str(arg), NULL);
+ if (!isnan(parsed)) *(OptionalNum_t *)dest = parsed;
+ return !isnan(parsed);
+ } else if (info == &Num32$info) {
+ OptionalNum32_t parsed = Num32$parse(Text$from_str(arg), NULL);
+ if (!isnan(parsed)) *(OptionalNum32_t *)dest = parsed;
+ return !isnan(parsed);
+ } else if (info == &Path$info) {
+ *(OptionalPath_t *)dest = Path$from_str(arg);
+ return true;
+ } else if (info->tag == TextInfo) {
+ *(OptionalText_t *)dest = Text$from_str(arg);
+ return true;
+ } else if (info->tag == EnumInfo) {
+ for (int t = 0; t < info->EnumInfo.num_tags; t++) {
+ NamedType_t named = info->EnumInfo.tags[t];
+ size_t len = strlen(named.name);
+ if (strncmp(arg, named.name, len) == 0 && (arg[len] == '\0' || arg[len] == ':')) {
+ *(int32_t *)dest = (t + 1);
+
+ // Simple tag (no associated data):
+ if (!named.type || (named.type->tag == StructInfo && named.type->StructInfo.num_fields == 0))
+ return true;
+
+ // Single-argument tag:
+ if (arg[len] != ':') print_err("Invalid value for ", t, ".", named.name, ": ", arg);
+ size_t offset = sizeof(int32_t);
+ if (named.type->align > 0 && offset % (size_t)named.type->align > 0)
+ offset += (size_t)named.type->align - (offset % (size_t)named.type->align);
+ if (!parse_single_arg(named.type, arg + len + 1, dest + offset)) return false;
+ return true;
+ }
+ }
+ print_err("Invalid value for ", info->EnumInfo.name, ": ", arg);
+ } else if (info->tag == StructInfo) {
+ if (info->StructInfo.num_fields == 0) return true;
+ else if (info->StructInfo.num_fields == 1) return parse_single_arg(info->StructInfo.fields[0].type, arg, dest);
+
+ Text_t t = generic_as_text(NULL, false, info);
+ print_err("Unsupported multi-argument struct type for argument parsing: ", t);
+ } else if (info->tag == ListInfo) {
+ print_err("List arguments must be specified as `--flag ...` not `--flag=...`");
+ } else if (info->tag == TableInfo) {
+ print_err("Table arguments must be specified as `--flag ...` not `--flag=...`");
+ } else {
+ Text_t t = generic_as_text(NULL, false, info);
+ print_err("Unsupported type for argument parsing: ", t);
+ }
+ return false;
+}
+
+static List_t parse_list(const TypeInfo_t *item_info, int n, char *args[]) {
+ int64_t padded_size = item_info->size;
+ if ((padded_size % item_info->align) > 0)
+ padded_size = padded_size + item_info->align - (padded_size % item_info->align);
+
+ List_t items = {
+ .stride = padded_size,
+ .length = n,
+ .data = GC_MALLOC((size_t)(padded_size * n)),
+ };
+ for (int i = 0; i < n; i++) {
+ bool success = parse_single_arg(item_info, args[i], items.data + items.stride * i);
+ if (!success) print_err("Couldn't parse argument: ", args[i]);
+ }
+ return items;
+}
+
+// Arguments take the form key=value, with a guarantee that there is an '='
+static Table_t parse_table(const TypeInfo_t *table, int n, char *args[]) {
+ const TypeInfo_t *key = table->TableInfo.key, *value = table->TableInfo.value;
+ int64_t padded_size = key->size;
+ if ((padded_size % value->align) > 0) padded_size = padded_size + value->align - (padded_size % value->align);
+ int64_t value_offset = padded_size;
+ padded_size += value->size;
+ if ((padded_size % key->align) > 0) padded_size = padded_size + key->align - (padded_size % key->align);
+
+ List_t entries = {
+ .stride = padded_size,
+ .length = n,
+ .data = GC_MALLOC((size_t)(padded_size * n)),
+ };
+ for (int i = 0; i < n; i++) {
+ char *key_arg = args[i];
+ char *equals = strchr(key_arg, '=');
+ assert(equals);
+ char *value_arg = equals + 1;
+ *equals = '\0';
+
+ bool success = parse_single_arg(key, key_arg, entries.data + entries.stride * i);
+ if (!success) print_err("Couldn't parse table key: ", key_arg);
+
+ success = parse_single_arg(value, value_arg, entries.data + entries.stride * i + value_offset);
+ if (!success) print_err("Couldn't parse table value: ", value_arg);
+
+ *equals = '=';
+ }
+ return Table$from_entries(entries, table);
+}
+
+#ifdef __GNUC__
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wstack-protector"
+#endif
+public
+void _tomo_parse_args(int argc, char *argv[], Text_t usage, Text_t help, const char *version, int spec_len,
+ cli_arg_t spec[spec_len]) {
+ bool populated_args[spec_len];
+ bool used_args[argc];
+ memset(populated_args, 0, sizeof(populated_args));
+ memset(used_args, 0, sizeof(used_args));
+ for (int i = 1; i < argc;) {
+ if (argv[i][0] == '-' && argv[i][1] == '-') {
+ if (argv[i][2] == '\0') { // "--" signals the rest of the arguments are literal
+ used_args[i] = true;
+ i += 1;
+ break;
+ }
+
+ for (int s = 0; s < spec_len; s++) {
+ const TypeInfo_t *non_opt_type = spec[s].type;
+ while (non_opt_type->tag == OptionalInfo)
+ non_opt_type = non_opt_type->OptionalInfo.type;
+
+ if (non_opt_type == &Bool$info && strncmp(argv[i], "--no-", strlen("--no-")) == 0
+ && strcmp(argv[i] + strlen("--no-"), spec[s].name) == 0) {
+ *(OptionalBool_t *)spec[s].dest = false;
+ populated_args[s] = true;
+ used_args[i] = true;
+ goto next_arg;
+ }
+
+ if (strncmp(spec[s].name, argv[i] + 2, strlen(spec[s].name)) != 0) continue;
+
+ char after_name = argv[i][2 + strlen(spec[s].name)];
+ if (after_name == '\0') { // --foo val
+ used_args[i] = true;
+ if (non_opt_type->tag == ListInfo) {
+ int num_args = 0;
+ while (i + 1 + num_args < argc) {
+ if (argv[i + 1 + num_args][0] == '-') break;
+ used_args[i + 1 + num_args] = true;
+ num_args += 1;
+ }
+ populated_args[s] = true;
+ *(OptionalList_t *)spec[s].dest =
+ parse_list(non_opt_type->ListInfo.item, num_args, &argv[i + 1]);
+ } else if (non_opt_type->tag == TableInfo) {
+ int num_args = 0;
+ while (i + 1 + num_args < argc) {
+ if (argv[i + 1 + num_args][0] == '-' || !strchr(argv[i + 1 + num_args], '=')) break;
+ used_args[i + 1 + num_args] = true;
+ num_args += 1;
+ }
+ populated_args[s] = true;
+ *(OptionalTable_t *)spec[s].dest = parse_table(non_opt_type, num_args, &argv[i + 1]);
+ } else if (non_opt_type == &Bool$info) { // --flag
+ populated_args[s] = true;
+ *(OptionalBool_t *)spec[s].dest = true;
+ } else {
+ if (i + 1 >= argc) print_err("Missing argument: ", argv[i], "\n", usage);
+ used_args[i + 1] = true;
+ populated_args[s] = parse_single_arg(spec[s].type, argv[i + 1], spec[s].dest);
+ if (!populated_args[s])
+ print_err("Couldn't parse argument: ", argv[i], " ", argv[i + 1], "\n", usage);
+ }
+ goto next_arg;
+ } else if (after_name == '=') { // --foo=val
+ used_args[i] = true;
+ populated_args[s] =
+ parse_single_arg(spec[s].type, 2 + argv[i] + strlen(spec[s].name) + 1, spec[s].dest);
+ if (!populated_args[s]) print_err("Couldn't parse argument: ", argv[i], "\n", usage);
+ goto next_arg;
+ } else {
+ continue;
+ }
+ }
+
+ if (streq(argv[i], "--help")) {
+ print(help);
+ exit(0);
+ }
+ if (streq(argv[i], "--version")) {
+ print(version);
+ exit(0);
+ }
+ print_err("Unrecognized argument: ", argv[i], "\n", usage);
+ } else if (argv[i][0] == '-' && argv[i][1] && argv[i][1] != '-' && !isdigit(argv[i][1])) { // Single flag args
+ used_args[i] = true;
+ for (char *f = argv[i] + 1; *f; f++) {
+ char flag[] = {'-', *f, 0};
+ for (int s = 0; s < spec_len; s++) {
+ if (spec[s].name[0] != *f || strlen(spec[s].name) > 1) continue;
+
+ const TypeInfo_t *non_opt_type = spec[s].type;
+ while (non_opt_type->tag == OptionalInfo)
+ non_opt_type = non_opt_type->OptionalInfo.type;
+
+ if (f[1] == '=') {
+ populated_args[s] = parse_single_arg(spec[s].type, f + 2, spec[s].dest);
+ if (!populated_args[s]) print_err("Couldn't parse argument: ", argv[i], "\n", usage);
+ f += strlen(f) - 1;
+ } else if (non_opt_type->tag == ListInfo) {
+ if (f[1]) print_err("No value provided for ", flag, "\n", usage);
+ int num_args = 0;
+ while (i + 1 + num_args < argc) {
+ if (argv[i + 1 + num_args][0] == '-') break;
+ used_args[i + 1 + num_args] = true;
+ num_args += 1;
+ }
+ populated_args[s] = true;
+ *(OptionalList_t *)spec[s].dest =
+ parse_list(non_opt_type->ListInfo.item, num_args, &argv[i + 1]);
+ } else if (non_opt_type->tag == TableInfo) {
+ int num_args = 0;
+ while (i + 1 + num_args < argc) {
+ if (argv[i + 1 + num_args][0] == '-' || !strchr(argv[i + 1 + num_args], '=')) break;
+ used_args[i + 1 + num_args] = true;
+ num_args += 1;
+ }
+ populated_args[s] = true;
+ *(OptionalTable_t *)spec[s].dest = parse_table(non_opt_type, num_args, &argv[i + 1]);
+ } else if (non_opt_type == &Bool$info) { // -f
+ populated_args[s] = true;
+ *(OptionalBool_t *)spec[s].dest = true;
+ } else if (is_numeric_type(non_opt_type) && (f[1] == '-' || f[1] == '.' || isdigit(f[1]))) { // -O3
+ size_t len = strspn(f + 1, "-0123456789._");
+ populated_args[s] =
+ parse_single_arg(spec[s].type, String(string_slice(f + 1, len)), spec[s].dest);
+ if (!populated_args[s]) print_err("Couldn't parse argument: ", argv[i], "\n", usage);
+ f += len;
+ } else {
+ if (f[1] || i + 1 >= argc) print_err("No value provided for ", flag, "\n", usage);
+ used_args[i + 1] = true;
+ populated_args[s] = parse_single_arg(spec[s].type, argv[i + 1], spec[s].dest);
+ if (!populated_args[s])
+ print_err("Couldn't parse argument: ", argv[i], " ", argv[i + 1], "\n", usage);
+ }
+ goto next_flag;
+ }
+
+ if (*f == 'h') {
+ print(help);
+ exit(0);
+ }
+ print_err("Unrecognized flag: ", flag, "\n", usage);
+ next_flag:;
+ }
+ } else {
+ // Handle positional args later
+ i += 1;
+ continue;
+ }
+
+ next_arg:
+ while (used_args[i] && i < argc)
+ i += 1;
+ }
+
+ // Get remaining positional arguments
+ bool ignore_dashes = false;
+ for (int i = 1, s = 0; i < argc; i++) {
+ if (!ignore_dashes && streq(argv[i], "--")) {
+ ignore_dashes = true;
+ continue;
+ }
+ if (used_args[i]) continue;
+
+ while (populated_args[s]) {
+ next_non_bool_flag:
+ ++s;
+ if (s >= spec_len) print_err("Extra argument: ", argv[i], "\n", usage);
+ }
+
+ const TypeInfo_t *non_opt_type = spec[s].type;
+ while (non_opt_type->tag == OptionalInfo)
+ non_opt_type = non_opt_type->OptionalInfo.type;
+
+ // You can't specify boolean flags positionally
+ if (non_opt_type == &Bool$info) goto next_non_bool_flag;
+
+ if (non_opt_type->tag == ListInfo) {
+ int num_args = 0;
+ while (i + num_args < argc) {
+ if (!ignore_dashes && (argv[i + num_args][0] == '-' && !isdigit(argv[i + num_args][1]))) break;
+ used_args[i + num_args] = true;
+ num_args += 1;
+ }
+ populated_args[s] = true;
+ *(OptionalList_t *)spec[s].dest = parse_list(non_opt_type->ListInfo.item, num_args, &argv[i]);
+ } else if (non_opt_type->tag == TableInfo) {
+ int num_args = 0;
+ while (i + num_args < argc) {
+ if ((argv[i + num_args][0] == '-' && !isdigit(argv[i + num_args][1]))
+ || !strchr(argv[i + num_args], '='))
+ break;
+ used_args[i + num_args] = true;
+ num_args += 1;
+ }
+ populated_args[s] = true;
+ *(OptionalTable_t *)spec[s].dest = parse_table(non_opt_type, num_args, &argv[i]);
+ } else {
+ populated_args[s] = parse_single_arg(spec[s].type, argv[i], spec[s].dest);
+ }
+
+ if (!populated_args[s]) print_err("Invalid value for ", spec[s].name, ": ", argv[i], "\n", usage);
+ }
+
+ for (int s = 0; s < spec_len; s++) {
+ if (!populated_args[s] && spec[s].required) {
+ if (spec[s].type->tag == ListInfo) *(OptionalList_t *)spec[s].dest = (List_t){};
+ else if (spec[s].type->tag == TableInfo) *(OptionalTable_t *)spec[s].dest = (Table_t){};
+ else print_err("The required argument '", spec[s].name, "' was not provided\n", usage);
+ }
+ }
+}
+#ifdef __GNUC__
+#pragma GCC diagnostic pop
+#endif
diff --git a/src/stdlib/cli.h b/src/stdlib/cli.h
new file mode 100644
index 00000000..a2058754
--- /dev/null
+++ b/src/stdlib/cli.h
@@ -0,0 +1,21 @@
+// Command line argument parsing
+
+#pragma once
+
+#include <stdbool.h>
+
+#include "datatypes.h"
+#include "types.h"
+
+typedef struct {
+ const char *name;
+ bool required;
+ const TypeInfo_t *type;
+ void *dest;
+} cli_arg_t;
+
+void _tomo_parse_args(int argc, char *argv[], Text_t usage, Text_t help, const char *version, int spec_len,
+ cli_arg_t spec[spec_len]);
+#define tomo_parse_args(argc, argv, usage, help, version, ...) \
+ _tomo_parse_args(argc, argv, usage, help, version, sizeof((cli_arg_t[]){__VA_ARGS__}) / sizeof(cli_arg_t), \
+ (cli_arg_t[]){__VA_ARGS__})
diff --git a/src/stdlib/stdlib.c b/src/stdlib/stdlib.c
index b215ac3b..bf36ca0d 100644
--- a/src/stdlib/stdlib.c
+++ b/src/stdlib/stdlib.c
@@ -1,12 +1,10 @@
// Built-in functions
-#include <ctype.h>
#include <errno.h>
#include <execinfo.h>
#include <fcntl.h>
#include <gc.h>
#include <locale.h>
-#include <math.h>
#include <signal.h>
#include <stdbool.h>
#include <stdint.h>
@@ -15,18 +13,13 @@
#include <time.h>
#include "../config.h"
-#include "bools.h"
#include "files.h"
-#include "integers.h"
#include "metamethods.h"
-#include "nums.h"
#include "optionals.h"
-#include "paths.h"
#include "print.h"
#include "siphash.h"
#include "stacktrace.h"
#include "stdlib.h"
-#include "tables.h"
#include "text.h"
#include "util.h"
@@ -76,349 +69,6 @@ void tomo_init(void) {
sigaction(SIGILL, &sigact, (struct sigaction *)NULL);
}
-static bool parse_single_arg(const TypeInfo_t *info, char *arg, void *dest) {
- if (!arg) return false;
-
- if (info->tag == OptionalInfo && streq(arg, "none")) return true;
-
- while (info->tag == OptionalInfo)
- info = info->OptionalInfo.type;
-
- if (info == &Int$info) {
- OptionalInt_t parsed = Int$from_str(arg);
- if (parsed.small != 0) *(OptionalInt_t *)dest = parsed;
- return parsed.small != 0;
- } else if (info == &Int64$info) {
- OptionalInt64_t parsed = Int64$parse(Text$from_str(arg), NULL);
- if (!parsed.is_none) *(OptionalInt64_t *)dest = parsed;
- return !parsed.is_none;
- } else if (info == &Int32$info) {
- OptionalInt32_t parsed = Int32$parse(Text$from_str(arg), NULL);
- if (!parsed.is_none) *(OptionalInt32_t *)dest = parsed;
- return !parsed.is_none;
- } else if (info == &Int16$info) {
- OptionalInt16_t parsed = Int16$parse(Text$from_str(arg), NULL);
- if (!parsed.is_none) *(OptionalInt16_t *)dest = parsed;
- return !parsed.is_none;
- } else if (info == &Int8$info) {
- OptionalInt8_t parsed = Int8$parse(Text$from_str(arg), NULL);
- if (!parsed.is_none) *(OptionalInt8_t *)dest = parsed;
- return !parsed.is_none;
- } else if (info == &Bool$info) {
- OptionalBool_t parsed = Bool$parse(Text$from_str(arg), NULL);
- if (parsed != NONE_BOOL) *(OptionalBool_t *)dest = parsed;
- return parsed != NONE_BOOL;
- } else if (info == &Num$info) {
- OptionalNum_t parsed = Num$parse(Text$from_str(arg), NULL);
- if (!isnan(parsed)) *(OptionalNum_t *)dest = parsed;
- return !isnan(parsed);
- } else if (info == &Num32$info) {
- OptionalNum32_t parsed = Num32$parse(Text$from_str(arg), NULL);
- if (!isnan(parsed)) *(OptionalNum32_t *)dest = parsed;
- return !isnan(parsed);
- } else if (info == &Path$info) {
- *(OptionalPath_t *)dest = Path$from_str(arg);
- return true;
- } else if (info->tag == TextInfo) {
- *(OptionalText_t *)dest = Text$from_str(arg);
- return true;
- } else if (info->tag == EnumInfo) {
- for (int t = 0; t < info->EnumInfo.num_tags; t++) {
- NamedType_t named = info->EnumInfo.tags[t];
- size_t len = strlen(named.name);
- if (strncmp(arg, named.name, len) == 0 && (arg[len] == '\0' || arg[len] == ':')) {
- *(int32_t *)dest = (t + 1);
-
- // Simple tag (no associated data):
- if (!named.type || (named.type->tag == StructInfo && named.type->StructInfo.num_fields == 0))
- return true;
-
- // Single-argument tag:
- if (arg[len] != ':') print_err("Invalid value for ", t, ".", named.name, ": ", arg);
- size_t offset = sizeof(int32_t);
- if (named.type->align > 0 && offset % (size_t)named.type->align > 0)
- offset += (size_t)named.type->align - (offset % (size_t)named.type->align);
- if (!parse_single_arg(named.type, arg + len + 1, dest + offset)) return false;
- return true;
- }
- }
- print_err("Invalid value for ", info->EnumInfo.name, ": ", arg);
- } else if (info->tag == StructInfo) {
- if (info->StructInfo.num_fields == 0) return true;
- else if (info->StructInfo.num_fields == 1) return parse_single_arg(info->StructInfo.fields[0].type, arg, dest);
-
- Text_t t = generic_as_text(NULL, false, info);
- print_err("Unsupported multi-argument struct type for argument parsing: ", t);
- } else if (info->tag == ListInfo) {
- print_err("List arguments must be specified as `--flag ...` not `--flag=...`");
- } else if (info->tag == TableInfo) {
- print_err("Table arguments must be specified as `--flag ...` not `--flag=...`");
- } else {
- Text_t t = generic_as_text(NULL, false, info);
- print_err("Unsupported type for argument parsing: ", t);
- }
- return false;
-}
-
-static List_t parse_list(const TypeInfo_t *item_info, int n, char *args[]) {
- int64_t padded_size = item_info->size;
- if ((padded_size % item_info->align) > 0)
- padded_size = padded_size + item_info->align - (padded_size % item_info->align);
-
- List_t items = {
- .stride = padded_size,
- .length = n,
- .data = GC_MALLOC((size_t)(padded_size * n)),
- };
- for (int i = 0; i < n; i++) {
- bool success = parse_single_arg(item_info, args[i], items.data + items.stride * i);
- if (!success) print_err("Couldn't parse argument: ", args[i]);
- }
- return items;
-}
-
-// Arguments take the form key=value, with a guarantee that there is an '='
-static Table_t parse_table(const TypeInfo_t *table, int n, char *args[]) {
- const TypeInfo_t *key = table->TableInfo.key, *value = table->TableInfo.value;
- int64_t padded_size = key->size;
- if ((padded_size % value->align) > 0) padded_size = padded_size + value->align - (padded_size % value->align);
- int64_t value_offset = padded_size;
- padded_size += value->size;
- if ((padded_size % key->align) > 0) padded_size = padded_size + key->align - (padded_size % key->align);
-
- List_t entries = {
- .stride = padded_size,
- .length = n,
- .data = GC_MALLOC((size_t)(padded_size * n)),
- };
- for (int i = 0; i < n; i++) {
- char *key_arg = args[i];
- char *equals = strchr(key_arg, '=');
- assert(equals);
- char *value_arg = equals + 1;
- *equals = '\0';
-
- bool success = parse_single_arg(key, key_arg, entries.data + entries.stride * i);
- if (!success) print_err("Couldn't parse table key: ", key_arg);
-
- success = parse_single_arg(value, value_arg, entries.data + entries.stride * i + value_offset);
- if (!success) print_err("Couldn't parse table value: ", value_arg);
-
- *equals = '=';
- }
- return Table$from_entries(entries, table);
-}
-
-#ifdef __GNUC__
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wstack-protector"
-#endif
-public
-void _tomo_parse_args(int argc, char *argv[], Text_t usage, Text_t help, const char *version, int spec_len,
- cli_arg_t spec[spec_len]) {
- bool populated_args[spec_len];
- bool used_args[argc];
- memset(populated_args, 0, sizeof(populated_args));
- memset(used_args, 0, sizeof(used_args));
- for (int i = 1; i < argc;) {
- if (argv[i][0] == '-' && argv[i][1] == '-') {
- if (argv[i][2] == '\0') { // "--" signals the rest of the arguments are literal
- used_args[i] = true;
- i += 1;
- break;
- }
-
- for (int s = 0; s < spec_len; s++) {
- const TypeInfo_t *non_opt_type = spec[s].type;
- while (non_opt_type->tag == OptionalInfo)
- non_opt_type = non_opt_type->OptionalInfo.type;
-
- if (non_opt_type == &Bool$info && strncmp(argv[i], "--no-", strlen("--no-")) == 0
- && strcmp(argv[i] + strlen("--no-"), spec[s].name) == 0) {
- *(OptionalBool_t *)spec[s].dest = false;
- populated_args[s] = true;
- used_args[i] = true;
- goto next_arg;
- }
-
- if (strncmp(spec[s].name, argv[i] + 2, strlen(spec[s].name)) != 0) continue;
-
- char after_name = argv[i][2 + strlen(spec[s].name)];
- if (after_name == '\0') { // --foo val
- used_args[i] = true;
- if (non_opt_type->tag == ListInfo) {
- int num_args = 0;
- while (i + 1 + num_args < argc) {
- if (argv[i + 1 + num_args][0] == '-') break;
- used_args[i + 1 + num_args] = true;
- num_args += 1;
- }
- populated_args[s] = true;
- *(OptionalList_t *)spec[s].dest =
- parse_list(non_opt_type->ListInfo.item, num_args, &argv[i + 1]);
- } else if (non_opt_type->tag == TableInfo) {
- int num_args = 0;
- while (i + 1 + num_args < argc) {
- if (argv[i + 1 + num_args][0] == '-' || !strchr(argv[i + 1 + num_args], '=')) break;
- used_args[i + 1 + num_args] = true;
- num_args += 1;
- }
- populated_args[s] = true;
- *(OptionalTable_t *)spec[s].dest = parse_table(non_opt_type, num_args, &argv[i + 1]);
- } else if (non_opt_type == &Bool$info) { // --flag
- populated_args[s] = true;
- *(OptionalBool_t *)spec[s].dest = true;
- } else {
- if (i + 1 >= argc) print_err("Missing argument: ", argv[i], "\n", usage);
- used_args[i + 1] = true;
- populated_args[s] = parse_single_arg(spec[s].type, argv[i + 1], spec[s].dest);
- if (!populated_args[s])
- print_err("Couldn't parse argument: ", argv[i], " ", argv[i + 1], "\n", usage);
- }
- goto next_arg;
- } else if (after_name == '=') { // --foo=val
- used_args[i] = true;
- populated_args[s] =
- parse_single_arg(spec[s].type, 2 + argv[i] + strlen(spec[s].name) + 1, spec[s].dest);
- if (!populated_args[s]) print_err("Couldn't parse argument: ", argv[i], "\n", usage);
- goto next_arg;
- } else {
- continue;
- }
- }
-
- if (streq(argv[i], "--help")) {
- print(help);
- exit(0);
- }
- if (streq(argv[i], "--version")) {
- print(version);
- exit(0);
- }
- print_err("Unrecognized argument: ", argv[i], "\n", usage);
- } else if (argv[i][0] == '-' && argv[i][1] && argv[i][1] != '-' && !isdigit(argv[i][1])) { // Single flag args
- used_args[i] = true;
- for (char *f = argv[i] + 1; *f; f++) {
- char flag[] = {'-', *f, 0};
- for (int s = 0; s < spec_len; s++) {
- if (spec[s].name[0] != *f || strlen(spec[s].name) > 1) continue;
-
- const TypeInfo_t *non_opt_type = spec[s].type;
- while (non_opt_type->tag == OptionalInfo)
- non_opt_type = non_opt_type->OptionalInfo.type;
-
- if (non_opt_type->tag == ListInfo) {
- if (f[1]) print_err("No value provided for ", flag, "\n", usage);
- int num_args = 0;
- while (i + 1 + num_args < argc) {
- if (argv[i + 1 + num_args][0] == '-') break;
- used_args[i + 1 + num_args] = true;
- num_args += 1;
- }
- populated_args[s] = true;
- *(OptionalList_t *)spec[s].dest =
- parse_list(non_opt_type->ListInfo.item, num_args, &argv[i + 1]);
- } else if (non_opt_type->tag == TableInfo) {
- int num_args = 0;
- while (i + 1 + num_args < argc) {
- if (argv[i + 1 + num_args][0] == '-' || !strchr(argv[i + 1 + num_args], '=')) break;
- used_args[i + 1 + num_args] = true;
- num_args += 1;
- }
- populated_args[s] = true;
- *(OptionalTable_t *)spec[s].dest = parse_table(non_opt_type, num_args, &argv[i + 1]);
- } else if (non_opt_type == &Bool$info) { // -f
- populated_args[s] = true;
- *(OptionalBool_t *)spec[s].dest = true;
- } else {
- if (f[1] || i + 1 >= argc) print_err("No value provided for ", flag, "\n", usage);
- used_args[i + 1] = true;
- populated_args[s] = parse_single_arg(spec[s].type, argv[i + 1], spec[s].dest);
- if (!populated_args[s])
- print_err("Couldn't parse argument: ", argv[i], " ", argv[i + 1], "\n", usage);
- }
- goto next_flag;
- }
-
- if (*f == 'h') {
- print(help);
- exit(0);
- }
- print_err("Unrecognized flag: ", flag, "\n", usage);
- next_flag:;
- }
- } else {
- // Handle positional args later
- i += 1;
- continue;
- }
-
- next_arg:
- while (used_args[i] && i < argc)
- i += 1;
- }
-
- // Get remaining positional arguments
- bool ignore_dashes = false;
- for (int i = 1, s = 0; i < argc; i++) {
- if (!ignore_dashes && streq(argv[i], "--")) {
- ignore_dashes = true;
- continue;
- }
- if (used_args[i]) continue;
-
- while (populated_args[s]) {
- next_non_bool_flag:
- ++s;
- if (s >= spec_len) print_err("Extra argument: ", argv[i], "\n", usage);
- }
-
- const TypeInfo_t *non_opt_type = spec[s].type;
- while (non_opt_type->tag == OptionalInfo)
- non_opt_type = non_opt_type->OptionalInfo.type;
-
- // You can't specify boolean flags positionally
- if (non_opt_type == &Bool$info) goto next_non_bool_flag;
-
- if (non_opt_type->tag == ListInfo) {
- int num_args = 0;
- while (i + num_args < argc) {
- if (!ignore_dashes && (argv[i + num_args][0] == '-' && !isdigit(argv[i + num_args][1]))) break;
- used_args[i + num_args] = true;
- num_args += 1;
- }
- populated_args[s] = true;
- *(OptionalList_t *)spec[s].dest = parse_list(non_opt_type->ListInfo.item, num_args, &argv[i]);
- } else if (non_opt_type->tag == TableInfo) {
- int num_args = 0;
- while (i + num_args < argc) {
- if ((argv[i + num_args][0] == '-' && !isdigit(argv[i + num_args][1]))
- || !strchr(argv[i + num_args], '='))
- break;
- used_args[i + num_args] = true;
- num_args += 1;
- }
- populated_args[s] = true;
- *(OptionalTable_t *)spec[s].dest = parse_table(non_opt_type, num_args, &argv[i]);
- } else {
- populated_args[s] = parse_single_arg(spec[s].type, argv[i], spec[s].dest);
- }
-
- if (!populated_args[s]) print_err("Invalid value for ", spec[s].name, ": ", argv[i], "\n", usage);
- }
-
- for (int s = 0; s < spec_len; s++) {
- if (!populated_args[s] && spec[s].required) {
- if (spec[s].type->tag == ListInfo) *(OptionalList_t *)spec[s].dest = (List_t){};
- else if (spec[s].type->tag == TableInfo) *(OptionalTable_t *)spec[s].dest = (Table_t){};
- else print_err("The required argument '", spec[s].name, "' was not provided\n", usage);
- }
- }
-}
-#ifdef __GNUC__
-#pragma GCC diagnostic pop
-#endif
-
public
_Noreturn void fail_text(Text_t message) { fail(message); }
@@ -581,30 +231,6 @@ cleanup:
}
public
-bool pop_flag(char **argv, int *i, const char *flag, Text_t *result) {
- if (argv[*i][0] != '-' || argv[*i][1] != '-') {
- return false;
- } else if (streq(argv[*i] + 2, flag)) {
- *result = EMPTY_TEXT;
- argv[*i] = NULL;
- *i += 1;
- return true;
- } else if (strncmp(argv[*i] + 2, "no-", 3) == 0 && streq(argv[*i] + 5, flag)) {
- *result = Text("no");
- argv[*i] = NULL;
- *i += 1;
- return true;
- } else if (strncmp(argv[*i] + 2, flag, strlen(flag)) == 0 && argv[*i][2 + strlen(flag)] == '=') {
- *result = Text$from_str(argv[*i] + 2 + strlen(flag) + 1);
- argv[*i] = NULL;
- *i += 1;
- return true;
- } else {
- return false;
- }
-}
-
-public
void sleep_num(double seconds) {
struct timespec ts;
ts.tv_sec = (time_t)seconds;
diff --git a/src/stdlib/stdlib.h b/src/stdlib/stdlib.h
index bbf8a43a..9bae5a6c 100644
--- a/src/stdlib/stdlib.h
+++ b/src/stdlib/stdlib.h
@@ -5,7 +5,7 @@
#include <signal.h>
#include <stdbool.h>
#include <stdint.h>
-#include <stdio.h>
+#include <stdio.h> // IWYU pragma: export
#include "datatypes.h"
#include "print.h"
@@ -15,19 +15,7 @@
extern bool USE_COLOR;
extern Text_t TOMO_VERSION_TEXT;
-typedef struct {
- const char *name;
- bool required;
- const TypeInfo_t *type;
- void *dest;
-} cli_arg_t;
-
void tomo_init(void);
-void _tomo_parse_args(int argc, char *argv[], Text_t usage, Text_t help, const char *version, int spec_len,
- cli_arg_t spec[spec_len]);
-#define tomo_parse_args(argc, argv, usage, help, version, ...) \
- _tomo_parse_args(argc, argv, usage, help, version, sizeof((cli_arg_t[]){__VA_ARGS__}) / sizeof(cli_arg_t), \
- (cli_arg_t[]){__VA_ARGS__})
#define fail(...) \
({ \
@@ -95,7 +83,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);
-bool pop_flag(char **argv, int *i, const char *flag, Text_t *result);
void sleep_num(double seconds);
OptionalText_t getenv_text(Text_t name);
void setenv_text(Text_t name, Text_t value);
diff --git a/src/stdlib/tomo.h b/src/stdlib/tomo.h
index ff16bee1..1ff065b9 100644
--- a/src/stdlib/tomo.h
+++ b/src/stdlib/tomo.h
@@ -11,6 +11,7 @@
#include "bools.h" // IWYU pragma: export
#include "bytes.h" // IWYU pragma: export
#include "c_strings.h" // IWYU pragma: export
+#include "cli.h" // IWYU pragma: export
#include "datatypes.h" // IWYU pragma: export
#include "enums.h" // IWYU pragma: export
#include "files.h" // IWYU pragma: export
diff --git a/src/tomo.c b/src/tomo.c
index 7c4b556c..433597b0 100644
--- a/src/tomo.c
+++ b/src/tomo.c
@@ -319,7 +319,7 @@ int main(int argc, char *argv[]) {
if (files.length < 1) print_err("No file specified!");
- quiet = !verbose;
+ if (!compile_exe && !stop_at_transpile && !stop_at_obj_compilation) quiet = !verbose;
for (int64_t i = 0; i < files.length; i++) {
Path_t path = *(Path_t *)(files.data + i * files.stride);
@@ -445,7 +445,7 @@ void build_library(Path_t lib_dir) {
int status = pclose(prog);
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) exit(EXIT_FAILURE);
- if (!quiet) print("Compiled library:\t", shared_lib);
+ if (!quiet) print("Compiled library:\t", Path$relative_to(shared_lib, Path$current_dir()));
}
void install_library(Path_t lib_dir) {
@@ -767,7 +767,7 @@ void transpile_header(env_t *base_env, Path_t path) {
Text$print(header, h_code);
if (fclose(header) == -1) print_err("Failed to write header file: ", h_filename);
- if (!quiet) print("Transpiled header:\t", h_filename);
+ if (!quiet) print("Transpiled header:\t", Path$relative_to(h_filename, Path$current_dir()));
if (show_codegen.length > 0) xsystem(show_codegen, " <", h_filename);
}
@@ -806,7 +806,7 @@ void transpile_code(env_t *base_env, Path_t path) {
if (fclose(c_file) == -1) print_err("Failed to output C code to ", c_filename);
- if (!quiet) print("Transpiled code:\t", c_filename);
+ if (!quiet) print("Transpiled code:\t", Path$relative_to(c_filename, Path$current_dir()));
if (show_codegen.length > 0) xsystem(show_codegen, " <", c_filename);
}
@@ -822,7 +822,7 @@ void compile_object_file(Path_t path) {
Path$write(build_file(path, ".config"), config_summary, 0644);
- if (!quiet) print("Compiled object:\t", obj_file);
+ if (!quiet) print("Compiled object:\t", Path$relative_to(obj_file, Path$current_dir()));
}
Path_t compile_executable(env_t *base_env, Path_t path, Path_t exe_path, List_t object_files, List_t extra_ldlibs) {
@@ -862,6 +862,6 @@ Path_t compile_executable(env_t *base_env, Path_t path, Path_t exe_path, List_t
int status = pclose(runner);
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) exit(EXIT_FAILURE);
- if (!quiet) print("Compiled executable:\t", exe_path);
+ if (!quiet) print("Compiled executable:\t", Path$relative_to(exe_path, Path$current_dir()));
return exe_path;
}
diff --git a/src/typecheck.c b/src/typecheck.c
index 5b7a453d..9e808111 100644
--- a/src/typecheck.c
+++ b/src/typecheck.c
@@ -104,7 +104,7 @@ type_t *parse_type_ast(env_t *env, type_ast_t *ast) {
"longer exist on the stack.");
arg_t *type_args = NULL;
for (arg_ast_t *arg = fn->args; arg; arg = arg->next) {
- type_args = new (arg_t, .name = arg->name, .next = type_args);
+ type_args = new (arg_t, .name = arg->name, .alias = arg->alias, .next = type_args);
if (arg->type) type_args->type = parse_type_ast(env, arg->type);
else if (arg->value) type_args->type = get_type(env, arg->value);
@@ -397,8 +397,8 @@ void bind_statement(env_t *env, ast_t *statement) {
"\nTry using a @",
type_to_str(field_t), " pointer for this field.");
}
- fields = new (arg_t, .name = field_ast->name, .type = field_t, .default_val = field_ast->value,
- .next = fields);
+ fields = new (arg_t, .name = field_ast->name, .alias = field_ast->alias, .type = field_t,
+ .default_val = field_ast->value, .next = fields);
}
REVERSE_LIST(fields);
type->__data.StructType.fields = fields; // populate placeholder
@@ -453,8 +453,8 @@ void bind_statement(env_t *env, ast_t *statement) {
"\nTry using a @",
type_to_str(field_t), " pointer for this field.");
}
- fields = new (arg_t, .name = field_ast->name, .type = field_t, .default_val = field_ast->value,
- .next = fields);
+ fields = new (arg_t, .name = field_ast->name, .alias = field_ast->alias, .type = field_t,
+ .default_val = field_ast->value, .next = fields);
}
REVERSE_LIST(fields);
env_t *member_ns = namespace_env(env, String(def->name, "$", tag_ast->name));
@@ -586,7 +586,7 @@ type_t *get_function_def_type(env_t *env, ast_t *ast) {
env_t *scope = fresh_scope(env);
for (arg_ast_t *arg = arg_asts; arg; arg = arg->next) {
type_t *t = arg->type ? parse_type_ast(env, arg->type) : get_type(env, arg->value);
- args = new (arg_t, .name = arg->name, .type = t, .default_val = arg->value, .next = args);
+ args = new (arg_t, .name = arg->name, .alias = arg->alias, .type = t, .default_val = arg->value, .next = args);
set_binding(scope, arg->name, t, EMPTY_TEXT);
}
REVERSE_LIST(args);
@@ -918,7 +918,8 @@ type_t *get_type(env_t *env, ast_t *ast) {
return t; // Constructor
arg_t *arg_types = NULL;
for (arg_ast_t *arg = call->args; arg; arg = arg->next)
- arg_types = new (arg_t, .type = get_type(env, arg->value), .name = arg->name, .next = arg_types);
+ arg_types = new (arg_t, .type = get_type(env, arg->value), .name = arg->name, .alias = arg->alias,
+ .next = arg_types);
REVERSE_LIST(arg_types);
code_err(call->fn, "I couldn't find a type constructor for ",
type_to_text(Type(FunctionType, .args = arg_types, .ret = t)));
@@ -1400,7 +1401,7 @@ type_t *get_type(env_t *env, ast_t *ast) {
env_t *scope = fresh_scope(env); // For now, just use closed variables in scope normally
for (arg_ast_t *arg = lambda->args; arg; arg = arg->next) {
type_t *t = get_arg_ast_type(env, arg);
- args = new (arg_t, .name = arg->name, .type = t, .next = args);
+ args = new (arg_t, .name = arg->name, .alias = arg->alias, .type = t, .next = args);
set_binding(scope, arg->name, t, EMPTY_TEXT);
}
REVERSE_LIST(args);
@@ -1618,7 +1619,8 @@ bool is_valid_call(env_t *env, arg_t *spec_args, arg_ast_t *call_args, call_opts
for (arg_t *spec_arg = spec_args; spec_arg; spec_arg = spec_arg->next) {
if (!options.underscores && spec_arg->name[0] == '_') continue;
- if (!streq(call_arg->name, spec_arg->name)) continue;
+ if (!(streq(call_arg->name, spec_arg->name) || (spec_arg->alias && streq(call_arg->name, spec_arg->alias))))
+ continue;
type_t *spec_type = get_arg_type(env, spec_arg);
env_t *arg_scope = with_enum_scope(env, spec_type);
type_t *call_type = get_arg_ast_type(arg_scope, call_arg);
diff --git a/src/types.h b/src/types.h
index ed5628e8..5b5c4fb9 100644
--- a/src/types.h
+++ b/src/types.h
@@ -9,7 +9,7 @@
typedef struct type_s type_t;
typedef struct arg_s {
- const char *name;
+ const char *name, *alias;
type_t *type;
ast_t *default_val;
struct arg_s *next;