From a8be9efcd3c3db6f2d6b78de2074d87e296459f8 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Thu, 5 Sep 2024 14:38:37 -0400 Subject: Rework CLI compilation so that all of the argument parsing is written to the .tm.c file and the runner program is *just* a single function call to the function that parses args and runs the main function. Also improved some CLI usage error code --- builtins/functions.c | 4 +-- compile.c | 88 ++++++++++++++++++++++++++++------------------------ tomo.c | 54 ++++++++++++++++++++------------ 3 files changed, 85 insertions(+), 61 deletions(-) diff --git a/builtins/functions.c b/builtins/functions.c index c11dd7e6..0ccc44cb 100644 --- a/builtins/functions.c +++ b/builtins/functions.c @@ -38,8 +38,8 @@ public void tomo_init(void) srand48(seed); Int$init_random(seed); - if (register_printf_specifier('k', printf_text, printf_text_size)) - errx(1, "Couldn't set printf specifier"); + if (register_printf_specifier('k', printf_text, printf_text_size)) + errx(1, "Couldn't set printf specifier"); } static void print_stack_trace(FILE *out) diff --git a/compile.c b/compile.c index caba764b..08dce21d 100644 --- a/compile.c +++ b/compile.c @@ -3059,58 +3059,65 @@ static CORD get_flag_options(type_t *t, CORD separator) CORD compile_cli_arg_call(env_t *env, CORD fn_name, type_t *fn_type) { auto fn_info = Match(fn_type, FunctionType); + + 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; + + 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"); + if (!fn_info->args) { + code = CORD_all(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", + "USAGE_ERR(\"This program doesn't take any arguments.\");\n", fn_name, "();\n"); } + env_t *main_env = fresh_scope(env); - CORD usage = CORD_EMPTY; - bool has_help = false; + bool explicit_help_flag = false; for (arg_t *arg = fn_info->args; arg; arg = arg->next) { - if (streq(arg->name, "help")) has_help = true; - 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, "|"), "]"); - } else { - if (t->tag == BoolType) - 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) - usage = CORD_all(usage, "<", flag, "...>"); - else - usage = CORD_all(usage, "<", flag, ">"); + if (streq(arg->name, "help")) { + explicit_help_flag = true; + break; } } - if (!has_help) - usage = CORD_all(" [--help]", usage); - - - CORD code = CORD_EMPTY; - CORD usage_code = "usage"; - binding_t *usage_binding = get_binding(env, "USAGE"); - if (usage_binding) - usage_code = usage_binding->code; - else + 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, "|"), "]"); + } else { + if (t->tag == BoolType) + 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) + usage = CORD_all(usage, "<", flag, "...>"); + else + usage = CORD_all(usage, "<", flag, ">"); + } + } code = CORD_all(code, "Text_t usage = Texts(Text(\"Usage: \"), Text$from_str(argv[0])", usage == CORD_EMPTY ? CORD_EMPTY : CORD_all(", Text(", CORD_quoted(usage), ")"), ");\n"); - - CORD help_code = "usage"; - binding_t *help_binding = get_binding(env, "HELP"); - if (help_binding) - help_code = help_binding->code; - - code = CORD_all(code, "#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"); + } // Declare args: for (arg_t *arg = fn_info->args; arg; arg = arg->next) { @@ -3129,7 +3136,8 @@ CORD compile_cli_arg_call(env_t *env, CORD fn_name, type_t *fn_type) "break;\n" "}\n" "if (strncmp(argv[i], \"--\", 2) != 0) {\n++i;\ncontinue;\n}\n"); - if (!has_help) { + + 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" diff --git a/tomo.c b/tomo.c index 0bcdb29b..4f353ce0 100644 --- a/tomo.c +++ b/tomo.c @@ -421,17 +421,38 @@ int transpile_code(env_t *base_env, const char *filename, bool force_retranspile CORD c_code = compile_file(module_env, ast); + FILE *out; + bool is_popened = false; if (autofmt) { - FILE *prog = CORD_RUN(autofmt, " 2>/dev/null >", c_filename); - CORD_put(c_code, prog); - if (pclose(prog) == -1) - errx(1, "Failed to output autoformatted C code to %s: %s", c_filename, autofmt); + out = CORD_RUN(autofmt, " 2>/dev/null >'", c_filename, "'"); + if (!out) + errx(1, "Failed to run autoformat program: %s", autofmt); + is_popened = true; } else { - FILE *c_file = fopen(c_filename, "w"); - if (!c_file) + out = fopen(c_filename, "w"); + if (!out) errx(1, "Couldn't open file: %s", c_filename); - CORD_put(c_code, c_file); - if (fclose(c_file)) + is_popened = false; + } + + CORD_put(c_code, out); + + binding_t *main_binding = get_binding(module_env, "main"); + if (main_binding && main_binding->type->tag == FunctionType) { + CORD_put(CORD_all( + "int ", main_binding->code, "$parse_and_run(int argc, char *argv[]) {\n" + "tomo_init();\n" + "\n", + compile_cli_arg_call(module_env, main_binding->code, main_binding->type), + "return 0;\n" + "}\n"), out); + } + + if (is_popened) { + if (pclose(out) == -1) + errx(1, "Failed to output autoformatted C code to %s: %s", c_filename, autofmt); + } else { + if (fclose(out)) errx(1, "Failed to close file: %s", c_filename); } @@ -439,7 +460,7 @@ int transpile_code(env_t *base_env, const char *filename, bool force_retranspile printf("Transpiled to %s\n", c_filename); if (show_codegen) { - FILE *out = CORD_RUN("bat -P ", c_filename); + out = CORD_RUN("bat -P ", c_filename); pclose(out); } @@ -471,26 +492,21 @@ int compile_executable(env_t *base_env, const char *filename, CORD object_files) errx(1, "Could not parse file %s", filename); env_t *env = load_module_env(base_env, ast); binding_t *main_binding = get_binding(env, "main"); - if (!main_binding || main_binding->type->tag != FunctionType) { + if (!main_binding || main_binding->type->tag != FunctionType) errx(1, "No main() function has been defined for %s, so it can't be run!", filename); - } const char *bin_name = GC_strndup(filename, strlen(filename) - strlen(".tm")); - FILE *runner = CORD_RUN(autofmt, " | ", cc, " ", cflags, " ", ldflags, " ", ldlibs, " ", object_files, " -x c - -o ", bin_name); + FILE *runner = CORD_RUN(cc, " ", cflags, " ", ldflags, " ", ldlibs, " ", object_files, " -x c - -o ", bin_name); CORD program = CORD_all( - "#include \n" - "#include \"", filename, ".h\"\n" - "\n" + "extern int ", main_binding->code, "$parse_and_run(int argc, char *argv[]);\n" "int main(int argc, char *argv[]) {\n" - "tomo_init();\n" - "\n", - CORD_all(compile_cli_arg_call(env, main_binding->code, main_binding->type), "return 0;\n"), + "\treturn ", main_binding->code, "$parse_and_run(argc, argv);\n" "}\n" ); if (show_codegen) { - FILE *out = CORD_RUN(autofmt, " | bat -P --file-name=run.c"); + FILE *out = CORD_RUN("bat -P --file-name=run.c"); CORD_put(program, out); pclose(out); } -- cgit v1.2.3