From: Nick Bowler Date: Tue, 5 Dec 2023 02:16:36 +0000 (-0500) Subject: help_print_optstring: New table-based implementation. X-Git-Url: https://git.draconx.ca/gitweb/dxcommon.git/commitdiff_plain/ac9f1d42b0caa9411e4e8e493c9919c1aaa9ac4f help_print_optstring: New table-based implementation. By moving the format strings into a single table and a table-based lookup to select the matching string, we can drastically simplify the control flow in this function. This results in about a ~35% code size reduction compiled with gcc. The tables are generated using gen-strtab.awk but we manually copy it into the source file, as downstream users just pick out help.c and help.h by themselves. --- diff --git a/src/help-glo.str b/src/help-glo.str new file mode 100644 index 0000000..90600e9 --- /dev/null +++ b/src/help-glo.str @@ -0,0 +1,10 @@ +# Strings for options listing (getopt_long_only variant) +# +# For the convenience of downstream users, (to the detriment of this project's +# maintenance), manually process this file with gen-strtab.awk as needed and +# incorporate the output directly into help.c + +@macro +&fmt_long_optional_arg \ -%s [%s] +&fmt_long_mandatory_arg \ -%s %s +&fmt_long_without_arg \ -%s diff --git a/src/help-std.str b/src/help-std.str new file mode 100644 index 0000000..09717df --- /dev/null +++ b/src/help-std.str @@ -0,0 +1,13 @@ +# Strings for options listing (normal variant) +# +# For the convenience of downstream users, (to the detriment of this project's +# maintenance), manually process this file with gen-strtab.awk as needed and +# incorporate the output directly into help.c + +@macro +&fmt_short_optional_arg \ -%c, --%s[=%s] +&fmt_long_optional_arg \ --%s[=%s] +&fmt_short_mandatory_arg \ -%c, --%s=%s +&fmt_long_mandatory_arg \ --%s=%s +&fmt_short_without_arg \ -%c, --%s +&fmt_long_without_arg \ --%s diff --git a/src/help.c b/src/help.c index 3aa0830..c537eef 100644 --- a/src/help.c +++ b/src/help.c @@ -16,6 +16,7 @@ # include #endif #include +#include #include #include #include @@ -26,6 +27,9 @@ #ifndef ENABLE_NLS # define ENABLE_NLS 0 #endif +#ifndef HELP_GETOPT_LONG_ONLY +# define HELP_GETOPT_LONG_ONLY 0 +#endif #if ENABLE_NLS # include @@ -37,7 +41,8 @@ # define mbsnwidth(a, b, c) (assert(0), 0) #endif -#define _(s) gettext(s) +#define STR_L10N_(x) +#define N_(s) s /* Returns a single numeric value depending on the type of option: * @@ -53,8 +58,10 @@ static int option_type(const struct option *opt) { int ret = opt->has_arg & 3; +#if !HELP_GETOPT_LONG_ONLY if (!opt->flag) ret |= (opt->val <= CHAR_MAX) << 2; +#endif return ret; } @@ -68,6 +75,56 @@ enum { OPT_LONG_WITHOUT_ARG = 0, }; +#if HELP_GETOPT_LONG_ONLY +/* String table generated by gen-strtab.awk from help-glo.str */ +#define STRTAB_INITIALIZER \ + N_(" -%s [%s]") \ + "\0" N_(" -%s %s") \ + "\0" N_(" -%s") \ + "" +enum { + fmt_long_optional_arg = 0, + fmt_long_mandatory_arg = 11, + fmt_long_without_arg = 20 +}; +#else +/* String table generated by gen-strtab.awk from help-std.str */ +#define STRTAB_INITIALIZER \ + N_(" -%c, --%s[=%s]") \ + "\0" N_(" -%c, --%s=%s") \ + "\0" N_(" --%s[=%s]") \ + "\0" N_(" -%c, --%s") \ + "\0" N_(" --%s=%s") \ + "\0" N_(" --%s") \ + "" +enum { + fmt_short_optional_arg = 0, + fmt_long_optional_arg = 32, + fmt_short_mandatory_arg = 17, + fmt_long_mandatory_arg = 56, + fmt_short_without_arg = 44, + fmt_long_without_arg = 66 +}; +#endif + +static const struct optstr { + unsigned char map[HELP_GETOPT_LONG_ONLY ? 3 : 7]; + char tab[sizeof (STRTAB_INITIALIZER)]; +} optstr = { + { + offsetof(struct optstr, tab) + fmt_long_without_arg, + offsetof(struct optstr, tab) + fmt_long_mandatory_arg, + offsetof(struct optstr, tab) + fmt_long_optional_arg, +#if !HELP_GETOPT_LONG_ONLY + 0, + offsetof(struct optstr, tab) + fmt_short_without_arg, + offsetof(struct optstr, tab) + fmt_short_mandatory_arg, + offsetof(struct optstr, tab) + fmt_short_optional_arg, +#endif + }, + STRTAB_INITIALIZER +}; + /* * When NLS is enabled, we assume snprintf is available. The GNU libintl * library should provide a suitable fallback if necessary. @@ -82,65 +139,28 @@ static inline int fake_snprintf(char *buf, size_t n, const char *fmt, ...) } #endif +#define has_short_char(type) (!HELP_GETOPT_LONG_ONLY && (type & 4)) + int help_print_optstring(const struct option *opt, const char *argname, int l) { + int type = option_type(opt); + const char *fmt, *nls_arg; char optstring[256]; int w; + fmt = (char *)&optstr + optstr.map[type]; if (!ENABLE_NLS) goto no_translate; - switch (option_type(opt)) { -#if HELP_GETOPT_LONG_ONLY - case OPT_SHORT_WITH_OPTIONAL_ARG: - case OPT_LONG_WITH_OPTIONAL_ARG: - w = help_do_snprintf(optstring, sizeof optstring, - _(" -%s [%s]"), opt->name, - pgettext_expr(opt->name, argname)); - break; - case OPT_SHORT_WITH_MANDATORY_ARG: - case OPT_LONG_WITH_MANDATORY_ARG: - w = help_do_snprintf(optstring, sizeof optstring, - _(" -%s %s"), opt->name, - pgettext_expr(opt->name, argname)); - break; - case OPT_SHORT_WITHOUT_ARG: - case OPT_LONG_WITHOUT_ARG: - w = help_do_snprintf(optstring, sizeof optstring, - _(" -%s"), opt->name); - break; -#else - case OPT_SHORT_WITH_OPTIONAL_ARG: - w = help_do_snprintf(optstring, sizeof optstring, - _(" -%c, --%s[=%s]"), opt->val, opt->name, - pgettext_expr(opt->name, argname)); - break; - case OPT_LONG_WITH_OPTIONAL_ARG: - w = help_do_snprintf(optstring, sizeof optstring, - _(" --%s[=%s]"), opt->name, - pgettext_expr(opt->name, argname)); - break; - case OPT_SHORT_WITH_MANDATORY_ARG: + nls_arg = argname ? pgettext_expr(opt->name, argname) : argname; + if (has_short_char(type)) { w = help_do_snprintf(optstring, sizeof optstring, - _(" -%c, --%s=%s"), opt->val, opt->name, - pgettext_expr(opt->name, argname)); - break; - case OPT_LONG_WITH_MANDATORY_ARG: + gettext(fmt), opt->val, opt->name, + nls_arg); + } else { w = help_do_snprintf(optstring, sizeof optstring, - _(" --%s=%s"), opt->name, - pgettext_expr(opt->name, argname)); - break; - case OPT_SHORT_WITHOUT_ARG: - w = help_do_snprintf(optstring, sizeof optstring, - _(" -%c, --%s"), opt->val, opt->name); - break; - case OPT_LONG_WITHOUT_ARG: - w = help_do_snprintf(optstring, sizeof optstring, - _(" --%s"), opt->name); - break; -#endif - default: - assert(0); + gettext(fmt), opt->name, + nls_arg); } if (w < 0 || w >= sizeof optstring) @@ -151,42 +171,10 @@ int help_print_optstring(const struct option *opt, const char *argname, int l) goto out; no_translate: - switch (option_type(opt)) { -#if HELP_GETOPT_LONG_ONLY - case OPT_SHORT_WITH_OPTIONAL_ARG: - case OPT_LONG_WITH_OPTIONAL_ARG: - w = printf(" -%s [%s]", opt->name, argname); - break; - case OPT_SHORT_WITH_MANDATORY_ARG: - case OPT_LONG_WITH_MANDATORY_ARG: - w = printf(" -%s %s", opt->name, argname); - break; - case OPT_SHORT_WITHOUT_ARG: - case OPT_LONG_WITHOUT_ARG: - w = printf(" -%s", opt->name); - break; -#else - case OPT_SHORT_WITH_OPTIONAL_ARG: - w = printf(" -%c, --%s[=%s]", opt->val, opt->name, argname); - break; - case OPT_LONG_WITH_OPTIONAL_ARG: - w = printf(" --%s[=%s]", opt->name, argname); - break; - case OPT_SHORT_WITH_MANDATORY_ARG: - w = printf(" -%c, --%s=%s", opt->val, opt->name, argname); - break; - case OPT_LONG_WITH_MANDATORY_ARG: - w = printf(" --%s=%s", opt->name, argname); - break; - case OPT_SHORT_WITHOUT_ARG: - w = printf(" -%c, --%s", opt->val, opt->name); - break; - case OPT_LONG_WITHOUT_ARG: - w = printf(" --%s", opt->name); - break; -#endif - default: - assert(0); + if (has_short_char(type)) { + w = printf(fmt, opt->val, opt->name, argname); + } else { + w = printf(fmt, opt->name, argname); } out: if (w < 0 || w > l) { diff --git a/t/nls/gettext.h b/t/nls/gettext.h index 8392992..95bdf14 100644 --- a/t/nls/gettext.h +++ b/t/nls/gettext.h @@ -14,9 +14,6 @@ #include #include -#define gettext(s) (s) -#define pgettext_expr(a, s) (s) - /* * We don't need a real snprintf for testing help functionality; substitute * using vsprintf to ease testing on C89 implementations. @@ -34,4 +31,11 @@ static inline int help_do_snprintf(char *buf, size_t n, const char *fmt, ...) return ret; } +/* + * Do nothing, but try to tickle a crash if a bogus string argument is + * passed as the real functions dereference the provided pointers. + */ +#define gettext(s) (*(volatile char *)s, s) +#define pgettext_expr(c, s) (*(volatile char *)c + *(volatile char *)s, s) + #endif