]> git.draconx.ca Git - dxcommon.git/commitdiff
help_print_optstring: New table-based implementation.
authorNick Bowler <nbowler@draconx.ca>
Tue, 5 Dec 2023 02:16:36 +0000 (21:16 -0500)
committerNick Bowler <nbowler@draconx.ca>
Tue, 5 Dec 2023 03:58:27 +0000 (22:58 -0500)
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.

src/help-glo.str [new file with mode: 0644]
src/help-std.str [new file with mode: 0644]
src/help.c
t/nls/gettext.h

diff --git a/src/help-glo.str b/src/help-glo.str
new file mode 100644 (file)
index 0000000..90600e9
--- /dev/null
@@ -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 (file)
index 0000000..09717df
--- /dev/null
@@ -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
index 3aa083003a03710560976034a209cc44b2f5cb3a..c537eefa29f05eacf08c596158d23d6a6ef0ffd5 100644 (file)
@@ -16,6 +16,7 @@
 #      include <config.h>
 #endif
 #include <stdio.h>
+#include <stddef.h>
 #include <string.h>
 #include <assert.h>
 #include <limits.h>
@@ -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 <locale.h>
@@ -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) {
index 83929924f718ccde6bcb58fb095618f218b085bc..95bdf14152d29aceb16ed5fd18c58ba8dba5f3cf 100644 (file)
@@ -14,9 +14,6 @@
 #include <stdio.h>
 #include <stdarg.h>
 
-#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