diff options
| -rw-r--r-- | compile.c | 251 | ||||
| -rw-r--r-- | stdlib/stdlib.c | 287 | ||||
| -rw-r--r-- | stdlib/stdlib.h | 8 |
3 files changed, 337 insertions, 209 deletions
@@ -3466,53 +3466,42 @@ CORD compile_cli_arg_call(env_t *env, CORD fn_name, type_t *fn_type) { auto fn_info = Match(fn_type, FunctionType); + env_t *main_env = fresh_scope(env); + + CORD code = CORD_EMPTY; binding_t *usage_binding = get_binding(env, "_USAGE"); CORD usage_code = usage_binding ? usage_binding->code : "usage"; binding_t *help_binding = get_binding(env, "_HELP"); CORD help_code = help_binding ? help_binding->code : usage_code; - - if (!fn_info->args) { - CORD code = "Text_t usage = Texts(Text(\"Usage: \"), Text$from_str(argv[0]), Text(\" [--help]\"));\n"; - code = CORD_all(code, "if (argc > 1 && streq(argv[1], \"--help\")) {\n", - "Text$print(stdout, ", help_code, ");\n" - "puts(\"\");\n" - "return 0;\n}\n"); - - return CORD_all( - code, - "if (argc > 1)\n" - "errx(1, \"This program doesn't take any arguments.\\n%k\", &", usage_code, ");\n", - fn_name, "();\n"); - } - - CORD code = CORD_all( - "#define USAGE_ERR(fmt, ...) errx(1, fmt \"\\n%s\" __VA_OPT__(,) __VA_ARGS__, Text$as_c_string(", usage_code, "))\n" - "#define IS_FLAG(str, flag) (strncmp(str, flag, strlen(flag) == 0 && (str[strlen(flag)] == 0 || str[strlen(flag)] == '=')) == 0)\n"); - - env_t *main_env = fresh_scope(env); - - 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; + 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; + } } - } - if (!usage_binding) { CORD usage = explicit_help_flag ? CORD_EMPTY : " [--help]"; for (arg_t *arg = fn_info->args; arg; arg = arg->next) { usage = CORD_cat(usage, " "); type_t *t = get_arg_type(main_env, arg); CORD flag = CORD_replace(arg->name, "_", "-"); - if (arg->default_val) { - if (t->tag == BoolType) - usage = CORD_all(usage, "[--", flag, "|--no-", flag, "]"); - else - usage = CORD_all(usage, "[--", flag, "=", get_flag_options(t, "|"), "]"); + 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 = CORD_all(usage, "[-", flag, "]"); + else + usage = CORD_all(usage, "[-", flag, " ", get_flag_options(t, "|"), "]"); + } else { + if (t->tag == BoolType || (t->tag == OptionalType && Match(t, OptionalType)->type->tag == BoolType)) + usage = CORD_all(usage, "[--", flag, "]"); + else + usage = CORD_all(usage, "[--", flag, "=", get_flag_options(t, "|"), "]"); + } } else { if (t->tag == BoolType) - usage = CORD_all(usage, "[--", flag, "|--no-", flag, "]"); + usage = CORD_all(usage, "<--", flag, "|--no-", flag, ">"); else if (t->tag == EnumType) usage = CORD_all(usage, get_flag_options(t, "|")); else if (t->tag == ArrayType) @@ -3525,188 +3514,32 @@ CORD compile_cli_arg_call(env_t *env, CORD fn_name, type_t *fn_type) usage == CORD_EMPTY ? CORD_EMPTY : CORD_all(", Text(", CORD_quoted(usage), ")"), ");\n"); } - // Declare args: - for (arg_t *arg = fn_info->args; arg; arg = arg->next) { - type_t *t = get_arg_type(main_env, arg); - assert(arg->name); - type_t *optional = t->tag == OptionalType ? t : Type(OptionalType, .type=t); - type_t *non_optional = t->tag == OptionalType ? Match(t, OptionalType)->type : t; - code = CORD_all( - code, compile_declaration(optional, CORD_cat("$", arg->name)), " = ", compile_null(non_optional), ";\n"); - set_binding(main_env, arg->name, new(binding_t, .type=optional, .code=CORD_cat("$", arg->name))); - } - // Provide --flags: - code = CORD_all(code, "Text_t flag;\n" - "for (int i = 1; i < argc; ) {\n" - "if (streq(argv[i], \"--\")) {\n" - "argv[i] = NULL;\n" - "break;\n" - "}\n" - "if (strncmp(argv[i], \"--\", 2) != 0) {\n++i;\ncontinue;\n}\n"); - - if (!explicit_help_flag) { - code = CORD_all(code, "else if (pop_flag(argv, &i, \"help\", &flag)) {\n" - "Text$print(stdout, ", help_code, ");\n" - "puts(\"\");\n" - "return 0;\n" - "}\n"); - } + int num_args = 0; for (arg_t *arg = fn_info->args; arg; arg = arg->next) { - type_t *t = get_arg_type(main_env, arg); - type_t *non_optional = t->tag == OptionalType ? Match(t, OptionalType)->type : t; - CORD flag = CORD_replace(arg->name, "_", "-"); - switch (non_optional->tag) { - case BoolType: { - CORD is_null = check_null(Type(OptionalType, .type=non_optional), CORD_all("$", arg->name)); - code = CORD_all(code, "else if (pop_flag(argv, &i, \"", flag, "\", &flag)) {\n" - "if (flag.length != 0) {\n", - "$", arg->name, " = Bool$from_text(flag);\n" - "if (", is_null, ") \n" - "USAGE_ERR(\"Invalid argument for '--", flag, "'\");\n", - "} else {\n", - "$", arg->name, " = yes;\n", - "}\n" - "}\n"); - break; - } - case TextType: { - code = CORD_all(code, "else if (pop_flag(argv, &i, \"", flag, "\", &flag)) {\n", - "$", arg->name, " = ", streq(Match(non_optional, TextType)->lang, "Path") ? "Path$cleanup(flag)" : "flag",";\n", - "}\n"); - break; - } - case ArrayType: { - if (Match(non_optional, ArrayType)->item_type->tag != TextType) - compiler_err(NULL, NULL, NULL, "Main function has unsupported argument type: %T (only arrays of Text are supported)", non_optional); - code = CORD_all(code, "else if (pop_flag(argv, &i, \"", flag, "\", &flag)) {\n", - "$", arg->name, " = Text$split(flag, Pattern(\",\"));\n"); - if (streq(Match(Match(non_optional, ArrayType)->item_type, TextType)->lang, "Path")) { - code = CORD_all(code, "for (int64_t j = 0; j < $", arg->name, ".length; j++)\n" - "*(Path_t*)($", arg->name, ".data + j*$", arg->name, ".stride) " - "= Path$cleanup(*(Path_t*)($", arg->name, ".data + j*$", arg->name, ".stride));\n"); - } - code = CORD_all(code, "}\n"); - break; - } - case BigIntType: case IntType: case NumType: { - CORD is_null = check_null(Type(OptionalType, .type=non_optional), CORD_all("$", arg->name)); - CORD type_name = type_to_cord(non_optional); - code = CORD_all(code, "else if (pop_flag(argv, &i, \"", flag, "\", &flag)) {\n", - "if (flag.length == 0)\n" - "USAGE_ERR(\"No value provided for '--", flag, "'\");\n" - "$", arg->name, " = ", type_name, "$from_text(flag);\n" - "if (", is_null, ")\n" - "USAGE_ERR(\"Invalid value provided for '--", flag, "'\");\n", - "}\n"); - break; - } - case EnumType: { - env_t *enum_env = Match(non_optional, EnumType)->env; - code = CORD_all(code, "else if (pop_flag(argv, &i, \"", flag, "\", &flag)) {\n", - "if (flag.length == 0)\n" - "USAGE_ERR(\"No value provided for '--", flag, "'\");\n"); - for (tag_t *tag = Match(non_optional, EnumType)->tags; tag; tag = tag->next) { - if (tag->type && Match(tag->type, StructType)->fields) - compiler_err(NULL, NULL, NULL, - "The type %T has enum fields with member values, which is not yet supported for command line arguments."); - binding_t *b = get_binding(enum_env, tag->name); - code = CORD_all(code, - "if (Text$equal_ignoring_case(flag, Text(\"", tag->name, "\"))) {\n" - "$", arg->name, " = ", b->code, ";\n", - "} else "); - } - code = CORD_all(code, "USAGE_ERR(\"Invalid value provided for '--", flag, "', valid values are: ", - get_flag_options(non_optional, ", "), "\");\n", - "}\n"); - break; - } - default: - compiler_err(NULL, NULL, NULL, "Main function has unsupported argument type: %T", t); + type_t *opt_type = arg->type->tag == OptionalType ? arg->type : Type(OptionalType, .type=arg->type); + code = CORD_all(code, compile_declaration(opt_type, CORD_all("$", arg->name))); + if (arg->default_val) { + CORD default_val = compile(env, arg->default_val); + if (arg->type->tag != OptionalType) + default_val = promote_to_optional(arg->type, default_val); + code = CORD_all(code, " = ", default_val); + } else { + code = CORD_all(code, " = ", compile_null(arg->type)); } + code = CORD_all(code, ";\n"); + num_args += 1; } - code = CORD_all( - code, "else {\n" - "USAGE_ERR(\"Unrecognized argument: %s\", argv[i]);\n" - "}\n" - "}\n" - "int i = 1;\n" - "while (i < argc && argv[i] == NULL)\n" - "++i;\n"); - + code = CORD_all(code, "tomo_parse_args(", usage_code, ", ", help_code, ", ", + heap_strf("%d", num_args), ", (cli_arg_t[]){"); for (arg_t *arg = fn_info->args; arg; arg = arg->next) { - type_t *t = get_arg_type(main_env, arg); - type_t *non_optional = t->tag == OptionalType ? Match(t, OptionalType)->type : t; - CORD is_null = check_null(Type(OptionalType, .type=non_optional), CORD_all("$", arg->name)); - code = CORD_all(code, "if (", is_null, ") {\n"); - if (non_optional->tag == ArrayType) { - if (Match(non_optional, ArrayType)->item_type->tag != TextType) - compiler_err(NULL, NULL, NULL, "Main function has unsupported argument type: %T (only arrays of Text are supported)", non_optional); - - code = CORD_all( - code, "$", arg->name, " = (Array_t){};\n" - "for (; i < argc; i++) {\n" - "if (argv[i]) {\n"); - if (streq(Match(Match(non_optional, ArrayType)->item_type, TextType)->lang, "Path")) { - code = CORD_all(code, "Path_t arg = Path$cleanup(Text$from_str(argv[i]));\n"); - } else { - code = CORD_all(code, "Text_t arg = Text$from_str(argv[i]);\n"); - } - code = CORD_all(code, "Array$insert(&$", arg->name, ", &arg, I(0), sizeof(Text_t));\n" - "argv[i] = NULL;\n" - "}\n" - "}\n"); - } else if (arg->default_val) { - code = CORD_all(code, "$", arg->name, " = ", compile(main_env, arg->default_val), ";\n"); - } else { - code = CORD_all( - code, - "if (i < argc) {"); - if (non_optional->tag == TextType) { - code = CORD_all(code, "$", arg->name, " = Text$from_str(argv[i]);\n"); - if (streq(Match(non_optional, TextType)->lang, "Path")) - code = CORD_all(code, "$", arg->name, " = Path$cleanup($", arg->name, ");\n"); - - } else if (non_optional->tag == EnumType) { - env_t *enum_env = Match(non_optional, EnumType)->env; - for (tag_t *tag = Match(non_optional, EnumType)->tags; tag; tag = tag->next) { - if (tag->type && Match(tag->type, StructType)->fields) - compiler_err(NULL, NULL, NULL, - "The type %T has enum fields with member values, which is not yet supported for command line arguments.", - non_optional); - binding_t *b = get_binding(enum_env, tag->name); - code = CORD_all(code, - "if (strcasecmp(argv[i], \"", tag->name, "\") == 0) {\n" - "$", arg->name, " = ", b->code, ";\n", - "} else "); - } - code = CORD_all(code, "USAGE_ERR(\"Invalid value provided for '--", arg->name, "', valid values are: ", - get_flag_options(non_optional, ", "), "\");\n"); - } else { - code = CORD_all( - code, - "$", arg->name, " = ", type_to_cord(non_optional), "$from_text(Text$from_str(argv[i]))", ";\n" - "if (", is_null, ")\n" - "USAGE_ERR(\"Unable to parse this argument as a ", type_to_cord(non_optional), ": %s\", argv[i]);\n"); - } - code = CORD_all( - code, - "argv[i++] = NULL;\n" - "while (i < argc && argv[i] == NULL)\n" - "++i;\n"); - if (t->tag != OptionalType) { - code = CORD_all(code, "} else {\n" - "USAGE_ERR(\"Required argument '", arg->name, "' was not provided!\");\n"); - } - code = CORD_all(code, "}\n"); - } - code = CORD_all(code, "}\n"); + code = CORD_all(code, "{", CORD_quoted(CORD_replace(arg->name, "_", "-")), ", ", + (arg->default_val || arg->type->tag == OptionalType) ? "false" : "true", ", ", + compile_type_info(env, arg->type), + ", &", CORD_all("$", arg->name), "}, "); } - - - code = CORD_all(code, "for (; i < argc; i++) {\n" - "if (argv[i])\nUSAGE_ERR(\"Unexpected argument: %s\", argv[i]);\n}\n"); + code = CORD_all(code, "}, argc, argv);\n"); code = CORD_all(code, fn_name, "("); for (arg_t *arg = fn_info->args; arg; arg = arg->next) { diff --git a/stdlib/stdlib.c b/stdlib/stdlib.c index db60ca98..801283a7 100644 --- a/stdlib/stdlib.c +++ b/stdlib/stdlib.c @@ -10,11 +10,15 @@ #include <sys/random.h> #include <time.h> +#include "bools.h" #include "files.h" #include "integers.h" +#include "optionals.h" #include "metamethods.h" #include "patterns.h" +#include "paths.h" #include "siphash.h" +#include "stdlib.h" #include "tables.h" #include "text.h" #include "util.h" @@ -36,6 +40,289 @@ public void tomo_init(void) errx(1, "Couldn't set printf specifier"); } +static bool parse_single_arg(const TypeInfo *info, char *arg, void *dest) +{ + 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$from_text(Text$from_str(arg)); + if (!parsed.is_null) + *(OptionalInt64_t*)dest = parsed; + return !parsed.is_null; + } else if (info == &Int32$info) { + OptionalInt32_t parsed = Int32$from_text(Text$from_str(arg)); + if (!parsed.is_null) + *(OptionalInt32_t*)dest = parsed; + return !parsed.is_null; + } else if (info == &Int16$info) { + OptionalInt16_t parsed = Int16$from_text(Text$from_str(arg)); + if (!parsed.is_null) + *(OptionalInt16_t*)dest = parsed; + return !parsed.is_null; + } else if (info == &Int8$info) { + OptionalInt8_t parsed = Int8$from_text(Text$from_str(arg)); + if (!parsed.is_null) + *(OptionalInt8_t*)dest = parsed; + return !parsed.is_null; + } else if (info == &Bool$info) { + OptionalBool_t parsed = Bool$from_text(Text$from_str(arg)); + if (parsed != NULL_BOOL) + *(OptionalBool_t*)dest = parsed; + return parsed != NULL_BOOL; + } else if (info == &Num$info) { + OptionalNum_t parsed = Num$from_text(Text$from_str(arg)); + if (!isnan(parsed)) + *(OptionalNum_t*)dest = parsed; + return !isnan(parsed); + } else if (info == &Num32$info) { + OptionalNum32_t parsed = Num32$from_text(Text$from_str(arg)); + if (!isnan(parsed)) + *(OptionalNum32_t*)dest = parsed; + return !isnan(parsed); + } else if (info == &Path$info) { + Path_t path = Text$from_str(arg); + if (Text$equal_values(path, Path("~"))) { + path = Path("~/"); + } else if (Text$equal_values(path, Path("."))) { + path = Path("./"); + } else if (Text$equal_values(path, Path(".."))) { + path = Path("../"); + } else if (!Text$starts_with(path, Text("./")) + && !Text$starts_with(path, Text("../")) + && !Text$starts_with(path, Text("/")) + && !Text$starts_with(path, Text("~/"))) { + path = Text$concat(Text("./"), path); + } + *(OptionalText_t*)dest = path; + return true; + } else if (info->tag == TextInfo) { + *(OptionalText_t*)dest = Text$from_str(arg); + return true; + } else { + Text_t t = generic_as_text(NULL, false, info); + errx(1, "Unsupported type for argument parsing: %k", &t); + } +} + +static Array_t parse_array(const TypeInfo *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); + + Array_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) + errx(1, "Couldn't parse argument: %s", args[i]); + } + return items; +} + +#pragma GCC diagnostic ignored "-Wstack-protector" +public void tomo_parse_args(Text_t usage, Text_t help, int spec_len, cli_arg_t spec[spec_len], int argc, char *argv[]) +{ + bool populated_args[spec_len] = {}; + bool used_args[argc] = {}; + 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 *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 == ArrayInfo || non_opt_type->tag == TableInfo) { + 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; + const TypeInfo *item_type = non_opt_type->tag == ArrayInfo ? non_opt_type->ArrayInfo.item : non_opt_type->TableInfo.key; + Array_t items = parse_array(item_type, num_args, &argv[i+1]); + if (non_opt_type->tag == ArrayInfo) { + *(OptionalArray_t*)spec[s].dest = items; + } else { + *(OptionalTable_t*)spec[s].dest = Table$from_entries(items, non_opt_type); + } + } else if (non_opt_type == &Bool$info) { // --flag + populated_args[s] = true; + *(OptionalBool_t*)spec[s].dest = true; + } else { + used_args[i+1] = true; + populated_args[s] = parse_single_arg(spec[s].type, argv[i+1], spec[s].dest); + if (!populated_args[s]) + errx(1, "Couldn't parse argument: %s %s\n%k", argv[i], argv[i+1], &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]) + errx(1, "Couldn't parse argument: %s\n%k", argv[i], &usage); + goto next_arg; + } else { + continue; + } + } + + if (streq(argv[i], "--help")) { + say(help, true); + exit(0); + } + errx(1, "Unrecognized argument: %s\n%k", argv[i], &usage); + } else if (argv[i][0] == '-' && argv[i][1] && argv[i][1] != '-') { // Single flag args + used_args[i] = true; + for (char *f = argv[i] + 1; *f; f++) { + for (int s = 0; s < spec_len; s++) { + if (spec[s].name[0] != *f || strlen(spec[s].name) > 1) + continue; + + const TypeInfo *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 == ArrayInfo || non_opt_type->tag == TableInfo) { + if (f[1]) errx(1, "No value provided for -%c\n%k", *f, &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; + const TypeInfo *item_type = non_opt_type->tag == ArrayInfo ? non_opt_type->ArrayInfo.item : non_opt_type->TableInfo.key; + Array_t items = parse_array(item_type, num_args, &argv[i+1]); + if (non_opt_type->tag == ArrayInfo) { + *(OptionalArray_t*)spec[s].dest = items; + } else { + *(OptionalTable_t*)spec[s].dest = Table$from_entries(items, non_opt_type); + } + } 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) errx(1, "No value provided for -%c\n%k", *f, &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]) + errx(1, "Couldn't parse argument: %s %s\n%k", argv[i], argv[i+1], &usage); + } + goto next_flag; + } + + if (*f == 'h') { + say(help, true); + exit(0); + } + errx(1, "Unrecognized flag: -%c\n%k", *f, &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) + errx(1, "Extra argument: %s\n%k", argv[i], &usage); + } + + const TypeInfo *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 == ArrayInfo || non_opt_type->tag == TableInfo) { + int num_args = 0; + while (i + num_args < argc) { + if (!ignore_dashes && argv[i+num_args][0] == '-') + break; + used_args[i+num_args] = true; + num_args += 1; + } + populated_args[s] = true; + const TypeInfo *item_type = non_opt_type->tag == ArrayInfo ? non_opt_type->ArrayInfo.item : non_opt_type->TableInfo.key; + Array_t items = parse_array(item_type, num_args, &argv[i]); + if (non_opt_type->tag == ArrayInfo) { + *(OptionalArray_t*)spec[s].dest = items; + } else { + *(OptionalTable_t*)spec[s].dest = Table$from_entries(items, non_opt_type); + } + } else { + populated_args[s] = parse_single_arg(spec[s].type, argv[i], spec[s].dest); + } + + if (!populated_args[s]) + errx(1, "Invalid value for %s: %s\n%k", spec[s].name, argv[i], &usage); + } + + for (int s = 0; s < spec_len; s++) { + if (!populated_args[s] && spec[s].required) { + if (spec[s].type->tag == ArrayInfo) + *(OptionalArray_t*)spec[s].dest = (Array_t){}; + else if (spec[s].type->tag == TableInfo) + *(OptionalTable_t*)spec[s].dest = (Table_t){}; + else + errx(1, "The required argument '%s' was not provided\n%k", spec[s].name, &usage); + } + } +} + void print_stack_trace(FILE *out, int start, int stop) { // Print stack trace: diff --git a/stdlib/stdlib.h b/stdlib/stdlib.h index 2037fae4..b8e78e9b 100644 --- a/stdlib/stdlib.h +++ b/stdlib/stdlib.h @@ -12,7 +12,15 @@ extern bool USE_COLOR; +typedef struct { + const char *name; + bool required; + const TypeInfo *type; + void *dest; +} cli_arg_t; + void tomo_init(void); +void tomo_parse_args(Text_t usage, Text_t help, int spec_len, cli_arg_t spec[spec_len], int argc, char *argv[]); __attribute__((format(printf, 1, 2))) _Noreturn void fail(const char *fmt, ...); __attribute__((format(printf, 4, 5))) |
