]> git.draconx.ca Git - dxcommon.git/blobdiff - scripts/gen-options.awk
gen-options.awk: Add a more compact data representation.
[dxcommon.git] / scripts / gen-options.awk
index 1fd0fdb0b26aad50457873ea577f9d4612d59b80..679dc9904c4770fbcbec5f6d32dc991a42237ef4 100755 (executable)
@@ -1,6 +1,6 @@
 #!/bin/awk -f
 #
-# Copyright © 2021 Nick Bowler
+# Copyright © 2021, 2023 Nick Bowler
 #
 # Generate definitions helpful when using getopt_long from an options
 # specification file.
 #
 #   static const struct option lopts[] = { LOPTS_INITIALIZER, {0} };
 #
+# If none of the options have action specifications, then an alternate
+# set of macros is also defined, which encode the struct option array
+# into a more compact format that can be used to generate the full
+# 'struct option' array at runtime:
+#
+#   * the object-like macro LOPT_PACK_BITS expands to an integer constant
+#     expression, suitable for use in #if directives, that specifies the
+#     minimum number of bits required by the encoding.
+#
+#   * the object-like macro LOPTS_PACKED_INITIALIZER expands to a
+#     comma-separated sequence of integer constant expressions, suitable
+#     for initializing an array of integers.  All values are less than
+#     2^LOPT_PACK_BITS.
+#
+#   * the function-like macro LOPT_UNPACK(opt, x), where opt is an
+#     lvalue of type 'struct option', and x is one of the array
+#     elements initialized by LOPTS_PACKED_INITIALIZER.  This expands
+#     the encoded value and sets the name, has_arg and val members of
+#     opt appopriately.  The caller should ensure that the flag member
+#     is set to zero.
+#
 # The help text for an individual struct option element may be obtained by
 # the function
 #
@@ -100,6 +121,7 @@ END {
 }
 
 BEGIN {
+  has_actions = 0
   sopt_string = ""
   num_options = 0
   lopt = ""
@@ -150,6 +172,9 @@ $0 ~ /^-/ {
   # Extract action
   sub(/^[ \t]*/, "", work)
   if (!sopt && work ~ /^\([^, \t]+(,[ \t]*[^, \t]+)?\)/) {
+    # packed form is not possible w/ actions
+    has_actions = 1;
+
     n = split(work, a, /,[ \t]*/)
     if (n == 2) {
       flag = substr(a[1], 2) ", " substr(a[2], 1, length(a[2])-1)
@@ -221,9 +246,13 @@ END {
     print "\t" to_enum(opt), "= UCHAR_MAX+1 +", offsets[opt] sep
   }
   print "};"
-  print "#define lopt_str(x) (lopt_strings + (LOPT_ ## x - UCHAR_MAX - 1))\n"
+  print "#define lopt_str(x) (lopt_strings + (LOPT_ ## x - UCHAR_MAX - 1))"
+
+  if (!has_actions) {
+    output_packed_macros()
+  }
 
-  print "#define LOPTS_INITIALIZER \\"
+  print "\n#define LOPTS_INITIALIZER \\"
   for (i = 0; i < count; i++) {
     opt = options[i]
     sep = (i+1 == count ? "" : ", \\")
@@ -315,6 +344,97 @@ END {
   print "}"
 }
 
+# Emit the packed initializer macros.  This is used as an array initializer
+# that encodes the following information:
+#
+#   - short option character offset
+#   - arg value (0, 1 or 2), and
+#   - long option string offset
+#
+# as a single integer value for each option, in as few bits as practical.
+#
+# Currently, this only works if none of the options use action specifications
+# (as these would require encoding user-specified pointer expressions and
+# arbitrary int values).
+function output_packed_macros(i, tmp, accum, max)
+{
+  print "\n#define LOPT_PACK_BITS (LOPT_SC_BITS + LOPT_HA_BITS + LOPT_LS_BITS)";
+
+  # determine number of bits to encode offsets in SOPT_STRING
+  max = length(sopt_string);
+  accum = 0;
+  for (i = 1; i <= max; i *= 2) {
+    accum++;
+  }
+  print "#define LOPT_SC_BITS " accum;
+
+  # determine number of bits to encode has_arg values
+  max = 0;
+  for (i in optionspec) {
+    tmp = optionspec[i]; sub(/,.*/, "", tmp);
+    if (tmp > max)
+      max = tmp;
+  }
+  print "#define LOPT_HA_BITS " (max > 1 ? 2 : max > 0 ? 1 : 0);
+
+  # determine number of bits to encode offsets in lopt_strings
+  max = 0;
+  for (i in offsets) {
+    if (offsets[i] > max)
+      max = offsets[i];
+  }
+
+  accum = 0;
+  for (i = 1; i <= max; i *= 2) {
+    accum++;
+  }
+  print "#define LOPT_LS_BITS " accum;
+
+  # Now emit the packed initializer macro
+  print "\n#define LOPTS_PACKED_INITIALIZER \\";
+  accum = "";
+  for (i = 0; i < count; i++) {
+    if (accum)
+      print "\t" accum ", \\";
+
+    tmp = options[i];
+    accum = "("offsets[tmp] "ul" "<<LOPT_HA_BITS)";
+    max = tmp = optionspec[tmp];
+    sub(/,.*/, "", max)
+    accum = "((" accum "|" max ")<<LOPT_SC_BITS)";
+
+    sub(/.*[, ]/, "", tmp);
+    if (tmp ~ /^[']/) {
+      tmp = index(sopt_string, substr(tmp, 2, 1)) - 1;
+    } else {
+      tmp = length(sopt_string);
+    }
+    accum = accum "|" tmp;
+  }
+
+  if (accum)
+    print "\t" accum;
+
+  # Finally, the unpack helper macros
+  tmp = "(x) & ((1ul<<LOPT_SC_BITS)-1)";
+  print "\n#define LOPT_UNPACK_VAL(x) \\"
+  print "\t( SOPT_STRING[" tmp "] \\";
+  print "\t? SOPT_STRING[" tmp "] \\";
+  print "\t: 1u + UCHAR_MAX + ((x)>>(LOPT_SC_BITS+LOPT_HA_BITS)))";
+
+  print "\n#define LOPT_UNPACK_ARG(x) \\";
+  print "\t(((x)>>LOPT_SC_BITS)&((1ul<<LOPT_HA_BITS)-1))";
+
+  print "\n#define LOPT_UNPACK_NAME(x) \\"
+  print "\t(lopt_strings+((x)>>(LOPT_SC_BITS+LOPT_HA_BITS)))";
+
+  print "\n#define LOPT_UNPACK(opt, x) do { \\";
+  print "\t(opt).name = LOPT_UNPACK_NAME(x); \\"
+  print "\t(opt).has_arg = LOPT_UNPACK_ARG(x); \\"
+  print "\t(opt).val = LOPT_UNPACK_VAL(x); \\"
+  print "} while (0)";
+}
+
 # bucketsort(dst, src)
 #
 # Sort the elements of src by descending string length,