From cd1fd8ac8e1ba1be52f93d2a87b5be31c55876fd Mon Sep 17 00:00:00 2001 From: "H. Peter Anvin" Date: Thu, 25 Apr 2024 15:14:20 -0700 Subject: [PATCH] nasm.c: tidy up the help text and break it into topics The help output has gotten way too long to be shown on a single command line. It can of course be piped to a pager, but to be a little nicer to the user, break it up into subtopics that can be individually displayed. --help all (-h all) can still show all the help information as a single data dump. Signed-off-by: H. Peter Anvin --- asm/nasm.c | 462 +++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 325 insertions(+), 137 deletions(-) diff --git a/asm/nasm.c b/asm/nasm.c index 97d926fc2..e97a0af96 100644 --- a/asm/nasm.c +++ b/asm/nasm.c @@ -1,6 +1,6 @@ /* ----------------------------------------------------------------------- * * - * Copyright 1996-2022 The NASM Authors - All Rights Reserved + * Copyright 1996-2024 The NASM Authors - All Rights Reserved * See the file AUTHORS included with the NASM distribution for * the specific copyright holders. * @@ -76,7 +76,7 @@ static void parse_cmdline(int, char **, int); static void assemble_file(const char *, struct strlist *); static bool skip_this_pass(errflags severity); static void usage(void); -static void help(FILE *); +static void help(FILE *out, const char *what); struct error_format { const char *beforeline; /* Before line number, if present */ @@ -752,8 +752,10 @@ int main(int argc, char **argv) /* * Get a parameter for a command line option. * First arg must be in the form of e.g. -f... + * + * get_param() errors on a missing argument; get_opt_param() does not. */ -static char *get_param(char *p, char *q, bool *advance) +static char *get_opt_param(char *p, char *q, bool *advance) { *advance = false; if (p[2]) /* the parameter's in the option */ @@ -762,9 +764,15 @@ static char *get_param(char *p, char *q, bool *advance) *advance = true; return q; } - nasm_nonfatalf(ERR_USAGE, "option `-%c' requires an argument", p[1]); return NULL; } +static char *get_param(char *p, char *q, bool *advance) +{ + char *r = get_opt_param(p, q, advance); + if (!r) + nasm_nonfatalf(ERR_USAGE, "option `-%c' requires an argument", p[1]); + return r; +} /* * Copy a filename @@ -956,7 +964,7 @@ struct textargs { static const struct textargs textopts[] = { {"v", OPT_VERSION, ARG_NO, 0}, {"version", OPT_VERSION, ARG_NO, 0}, - {"help", OPT_HELP, ARG_NO, 0}, + {"help", OPT_HELP, ARG_MAYBE, 0}, {"abort-on-panic", OPT_ABORT_ON_PANIC, ARG_NO, 0}, {"prefix", OPT_MANGLE, ARG_YES, LM_GPREFIX}, {"postfix", OPT_MANGLE, ARG_YES, LM_GSUFFIX}, @@ -1113,12 +1121,17 @@ static bool process_arg(char *p, char *q, int pass) case 'X': /* specify error reporting format */ if (pass == 1) { - if (!nasm_stricmp("vc", param) || !nasm_stricmp("msvc", param) || !nasm_stricmp("ms", param)) + if (!nasm_stricmp("vc", param) || + !nasm_stricmp("msvc", param) || + !nasm_stricmp("ms", param)) errfmt = &errfmt_msvc; - else if (!nasm_stricmp("gnu", param) || !nasm_stricmp("gcc", param)) + else if (!nasm_stricmp("gnu", param) || + !nasm_stricmp("gcc", param)) errfmt = &errfmt_gnu; else - nasm_fatalf(ERR_USAGE, "unrecognized error reporting format `%s'", param); + nasm_fatalf(ERR_USAGE, + "unrecognized error reporting format `%s'", + param); } break; @@ -1131,13 +1144,14 @@ static bool process_arg(char *p, char *q, int pass) break; case 'h': - help(stdout); + case '?': + help(stdout, get_opt_param(p, q, &advance)); exit(0); /* never need usage message here */ break; case 'y': /* legacy option */ - dfmt_list(stdout); + help(stdout, "-F"); exit(0); break; @@ -1330,13 +1344,17 @@ static bool process_arg(char *p, char *q, int pass) ppopt |= PP_NOLINE; break; case OPT_DEBUG: - debug_nasm = param ? strtoul(param, NULL, 10) : debug_nasm+1; + debug_nasm = param ? + strtoul(param, NULL, 10) : debug_nasm+1; break; case OPT_REPRODUCIBLE: reproducible = true; break; case OPT_HELP: - help(stdout); + /* Allow --help topic without *requiring* topic */ + if (!param) + param = q; + help(stdout, param); exit(0); default: panic(); @@ -2210,143 +2228,313 @@ done: static void usage(void) { - fprintf(error_file, "Type %s -h for help.\n", _progname); + fprintf(error_file, + "Usage: %s [-@ response_file] [options...] [--] filename\n" + " For additional help:\n" + " %s -h [run|topics|all|-option]\n", + _progname, _progname); } -static void help(FILE *out) +enum help_with { + HW_ALL = 0, + HW_OPT = 1, + HW_ERR = 2, + HW_LIMIT = 3 +}; + +static inline bool help_is(int with, int h) { + return with == HW_ALL || with == h; +} +static inline bool help_opt(int with) +{ + return help_is(with, HW_OPT); +} +static inline bool help_optor(int with, int h) +{ + return help_opt(with) || with == h; +} + +static void help(FILE *out, const char *what) { int i; + int with; - fprintf(out, - "Usage: %s [-@ response_file] [options...] [--] filename\n" - " %s -v (or --v)\n", - _progname, _progname); - fputs( - "\n" - "Options (values in brackets indicate defaults):\n" - "\n" - " -h show this text and exit (also --help)\n" - " -v (or --v) print the NASM version number and exit\n" - " -@ file response file; one command line option per line\n" - "\n" - " -o outfile write output to outfile\n" - " --keep-all output files will not be removed even if an error happens\n" - "\n" - " -Xformat specify error reporting format (gnu or vc)\n" - " -s redirect error messages to stdout\n" - " -Zfile redirect error messages to file\n" - "\n" - " -M generate Makefile dependencies on stdout\n" - " -MG d:o, missing files assumed generated\n" - " -MF file set Makefile dependency file\n" - " -MD file assemble and generate dependencies\n" - " -MT file dependency target name\n" - " -MQ file dependency target name (quoted)\n" - " -MP emit phony targets\n" - "\n" - " -f format select output file format\n" - , out); - ofmt_list(ofmt, out); - fputs( - "\n" - " -g generate debugging information\n" - " -F format select a debugging format (output format dependent)\n" - " -gformat same as -g -F format\n" - , out); - dfmt_list(out); - fputs( - "\n" - " -l listfile write listing to a list file\n" - " -Lflags... add optional information to the list file\n" - " -Lb show builtin macro packages (standard and %use)\n" - " -Ld show byte and repeat counts in decimal, not hex\n" - " -Le show the preprocessed output\n" - " -Lf ignore .nolist (force output)\n" - " -Lm show multi-line macro calls with expanded parameters\n" - " -Lp output a list file every pass, in case of errors\n" - " -Ls show all single-line macro definitions\n" - " -Lw flush the output after every line (very slow!)\n" - " -L+ enable all listing options except -Lw (very verbose!)\n" - "\n" - " -Oflags... optimize opcodes, immediates and branch offsets\n" - " -O0 no optimization\n" - " -O1 minimal optimization\n" - " -Ox multipass optimization (default)\n" - " -Ov display the number of passes executed at the end\n" - " -t assemble in limited SciTech TASM compatible mode\n" - "\n" - " -E (or -e) preprocess only (writes output to stdout by default)\n" - " -a don't preprocess (assemble only)\n" - " -Ipath add a pathname to the include file path\n" - " -Pfile pre-include a file (also --include)\n" - " -Dmacro[=str] pre-define a macro\n" - " -Umacro undefine a macro\n" - " --pragma str pre-executes a specific %%pragma\n" - " --before str add line (usually a preprocessor statement) before the input\n" - " --no-line ignore %line directives in input\n" - "\n" - " --prefix str prepend the given string to the names of all extern,\n" - " common and global symbols (also --gprefix)\n" - " --suffix str append the given string to the names of all extern,\n" - " common and global symbols (also --gprefix)\n" - " --lprefix str prepend the given string to local symbols\n" - " --lpostfix str append the given string to local symbols\n" - "\n" - " --reproducible attempt to produce run-to-run identical output\n" - "\n" - " -w+x enable warning x (also -Wx)\n" - " -w-x disable warning x (also -Wno-x)\n" - " -w[+-]error promote all warnings to errors (also -Werror)\n" - " -w[+-]error=x promote warning x to errors (also -Werror=x)\n" - , out); +#define SEE(x) (with == HW_OPT ? " (see -h " x ")" : "") - fprintf(out, " %-20s %s\n", - warning_name[WARN_IDX_ALL], warning_help[WARN_IDX_ALL]); + with = HW_ERR; - for (i = 1; i < WARN_IDX_ALL; i++) { - const char *me = warning_name[i]; - const char *prev = warning_name[i-1]; - const char *next = warning_name[i+1]; + if (!what || !*what || !strcmp(what, "-") || !strcmp(what, "--") || + !nasm_stricmp(what, "opt") || + !nasm_stricmp(what, "run") || + !nasm_stricmp(what, "cmd")) { + with = HW_OPT; + } else if (!nasm_stricmp(what, "all")) { + with = HW_ALL; + } else if (!nasm_strnicmp(what, "--limit", 7) || + !nasm_strnicmp(what, "limit", 5)) { + with = HW_LIMIT; + } else if (!nasm_stricmp(what, "--help") || + !nasm_stricmp(what, "help") || + !nasm_stricmp(what, "list") || + !nasm_stricmp(what, "topics") || + !nasm_stricmp(what, "index")) { + with = 'h'; + } else if ((what[0] == '-' && what[1] && !what[2]) || + (what[0] && !what[1])) { + with = what[0] == '-' ? what[1] : what[0]; + switch (with) { + case 'h': + case 'X': + case 'L': + case 'f': + case 'F': + case 'O': + case 'w': + case 'M': + break; + case 'g': + with = 'F'; + break; + case 'W': + with = 'w'; + break; + case '?': + with = 'h'; + break; + case '*': + with = HW_ALL; + break; + default: + with = HW_ERR; + break; + } + } - if (prev) { - int prev_len = strlen(prev); - const char *dash = me; + if (with == HW_ERR) { + fprintf(out, "Sorry, no help available for `%s'\n", what); + return; + } - while ((dash = strchr(dash+1, '-'))) { - int prefix_len = dash - me; /* Not including final dash */ - if (strncmp(next, me, prefix_len+1)) { - /* Only one or last option with this prefix */ - break; - } - if (prefix_len >= prev_len || - strncmp(prev, me, prefix_len) || - (prev[prefix_len] != '-' && prev[prefix_len] != '\0')) { - /* This prefix is different from the previous option */ - fprintf(out, " %-20.*s all warnings prefixed with \"%.*s\"\n", - prefix_len, me, prefix_len+1, me); + if (help_opt(with)) { + fprintf(out, + "Usage: %s [-@ response_file] [options...] [--] filename\n" + " Options:\n" + " -v (or --v) print the NASM version number and exit\n" + " -@ file response file; one command line option per line\n", + _progname); + } + if (help_optor(with, 'h')) { + fputs( + " -h list command line options and exit (also --help)\n" + , out); + if (with == HW_OPT) { + fputs( + " -h -opt show additional help for option \"-opt\"\n" + , out); + } + fputs( + " -h all show all available command line help\n" + " -h topics show list of help topics (also -h list)\n" + , out); + } + if (help_is(with, 'h')) { + fputs( + " -h -X show list of error message formats\n" + " -h -M show list of dependency generation options\n" + " -h -f show list of object code output formats\n" + " -h -F show list of debug information formats\n" + " -h -L show list of list file option flags\n" + " -h -O show list of optimization flags\n" + " -h -w show list of warning classes and defaults\n" + " -h --limit show list of resource limits and defaults\n" + , out); + } + if (help_opt(with)) { + fputs( + " -o outfile write output to outfile\n" + " --keep-all output files will not be removed even if an error happens\n" + , out); + } + if (help_optor(with, 'X')) { + fprintf(out, + " -Xformat specify error reporting format%s\n", + SEE("-X")); + } + if (help_is(with, 'X')) { + fputs( + " -Xgnu report errors in GNU format (default)\n" + " -Xgcc alias for -Xgnu\n" + " -Xvc report errors in Visual Studio format\n" + " -Xmsvc alias for -Xvc\n" + " -Xms alias for -Xvc\n" + , out); + } + if (help_opt(with)) { + fputs( + " -s redirect error messages to stdout\n" + " -Zfile redirect error messages to file\n" + , out); + } + if (help_optor(with, 'M')) { + fprintf(out, + " -M... generate Makefile dependencies%s\n", + with == HW_OPT + ? " (see -h -M)" + : " (multiple options permitted):"); + } + if (help_is(with, 'M')) { + fputs( + " -M generate Makefile dependencies on stdout\n" + " -MG d:o, missing files assumed generated\n" + " -MF file set Makefile dependency file\n" + " -MD file assemble and generate dependencies\n" + " -MT file dependency target name\n" + " -MQ file dependency target name (quoted)\n" + " -MP emit phony targets\n" + " -MW generate output in Watcom wmake syntax\n" + , out); + } + if (help_optor(with, 'f')) { + fprintf(out, + " -f format select output file format%s\n", + SEE("-f")); + } + if (help_is(with, 'f')) { + ofmt_list(ofmt, out); + } + if (help_optor(with, 'F')) { + fprintf(out, + " -g generate debugging information\n" + " -F format select a debugging format%s\n" + " -gformat same as -g -F format\n", + SEE("-F")); + } + if (help_is(with, 'F')) { + dfmt_list(out); + } + if (help_optor(with, 'L')) { + fprintf(out, + " -l listfile write listing to a list file\n" + " -Lflags... add information to the list file%s\n", + SEE("-L")); + } + if (help_is(with, 'L')) { + fputs( + " -Lb show builtin macro packages (standard and %use)\n" + " -Ld show byte and repeat counts in decimal, not hex\n" + " -Le show the preprocessed output\n" + " -Lf ignore .nolist (force output)\n" + " -Lm show multi-line macro calls with expanded parameters\n" + " -Lp output a list file every pass, in case of errors\n" + " -Ls show all single-line macro definitions\n" + " -Lw flush the output after every line (very slow!)\n" + " -L+ enable all listing options except -Lw (very verbose!)\n" + , out); + } + if (help_optor(with, 'O')) { + fprintf(out, + " -Oflags... select optimization%s\n", SEE("-O")); + } + if (help_is(with, 'O')) { + fputs( + " -O0 no optimization\n" + " -O1 minimal optimization\n" + " -Ox multipass optimization (default, recommended)\n" + " -Ov display the number of passes executed at the end\n" + , out); + } + if (help_opt(with)) { + fputs( + " -t assemble in limited SciTech TASM compatible mode\n" + " -E (or -e) preprocess only (writes output to stdout by default)\n" + " -a don't preprocess (assemble only)\n" + " -Ipath add a pathname to the include file path\n" + " -Pfile pre-include a file (also --include)\n" + " -Dmacro[=str] pre-define a macro\n" + " -Umacro undefine a macro\n" + , out); + } + if (help_optor(with, 'w')) { + fprintf(out, + " -w+x enable warning x %s(also -Wx)\n" + " -w-x disable warning x (also -Wno-x)\n" + " -w[+-]error promote all warnings to errors (also -Werror)\n" + " -w[+-]error=x promote warning x to errors (also -Werror=x)\n", + SEE("-w")); + } + if (help_is(with, 'w')) { + fputs(" Defaults in brackets:\n", out); + + fprintf(out, " %-20s %s\n", + warning_name[WARN_IDX_ALL], warning_help[WARN_IDX_ALL]); + + for (i = 1; i < WARN_IDX_ALL; i++) { + static const char nl_indent[] = + "\n "; + const char *me = warning_name[i]; + const char *prev = warning_name[i-1]; + const char *next = warning_name[i+1]; + + if (prev) { + int prev_len = strlen(prev); + const char *dash = me; + + while ((dash = strchr(dash+1, '-'))) { + int prefix_len = dash - me; /* Not including final dash */ + if (strncmp(next, me, prefix_len+1)) { + /* Only one or last option with this prefix */ + break; + } + if (prefix_len >= prev_len || + strncmp(prev, me, prefix_len) || + (prev[prefix_len] != '-' && prev[prefix_len] != '\0')) { + /* This prefix is different from the previous option */ + fprintf(out, " %-20.*s%sall warnings prefixed with \"%.*s\"\n", + prefix_len, me, + prefix_len > 19 ? nl_indent : " ", + prefix_len+1, me); + } } } - } - fprintf(out, " %-20s %s%s\n", - warning_name[i], warning_help[i], - (warning_default[i] & WARN_ST_ERROR) ? " [error]" : - (warning_default[i] & WARN_ST_ENABLED) ? " [on]" : " [off]"); - } - - fputs( - "\n" - " --limit-X val set execution limit X\n" - , out); - - - for (i = 0; i <= LIMIT_MAX; i++) { - fprintf(out, " %-20s %s [", - limit_info[i].name, limit_info[i].help); - if (nasm_limit[i] < LIMIT_MAX_VAL) { - fprintf(out, "%"PRId64"]\n", nasm_limit[i]); - } else { - fputs("unlimited]\n", out); + fprintf(out, " %-20s%s%s%s\n", + warning_name[i], + strlen(warning_name[i]) > 19 ? nl_indent : " ", + warning_help[i], + (warning_default[i] & WARN_ST_ERROR) ? " [error]" : + (warning_default[i] & WARN_ST_ENABLED) ? " [on]" : " [off]"); } } + if (help_opt(with)) { + fputs( + " --pragma str pre-executes a specific %pragma\n" + " --before str add line (usually a preprocessor statement) before the input\n" + " --no-line ignore %line directives in input\n" + " --prefix str prepend the given string to the names of all extern,\n" + " common and global symbols (also --gprefix)\n" + " --suffix str append the given string to the names of all extern,\n" + " common and global symbols (also --gprefix)\n" + " --lprefix str prepend the given string to local symbols\n" + " --lpostfix str append the given string to local symbols\n" + " --reproducible attempt to produce run-to-run identical output\n" + , out); + } + if (help_optor(with, HW_LIMIT)) { + fprintf(out, " --limit-X val set execution limit X%s\n", + SEE("--limit")); + } + if (help_is(with, HW_LIMIT)) { + fputs(" Defaults in brackets:\n", out); + + for (i = 0; i <= LIMIT_MAX; i++) { + fprintf(out, " %-20s %s [", + limit_info[i].name, limit_info[i].help); + if (nasm_limit[i] < LIMIT_MAX_VAL) { + fprintf(out, "%"PRId64"]\n", nasm_limit[i]); + } else { + fputs("unlimited]\n", out); + } + } + } +#undef SEE }