--- /dev/null
+#!/bin/awk -f
+#
+# Copyright © 2023 Nick Bowler
+#
+# Hackjob to replace gperf's generated string table with a custom function.
+#
+# Sometimes it is not desired to use a gperf-generated string table, as this
+# can be quite large and is mostly wasted space if we have some other means
+# of creating strings. This script replaces the "wordlist" initializer with
+# one that does not include any strings, and replaces the reference to the
+# ".name" member of the wordlist with a function call which can be supplied
+# by the user.
+#
+# This transformation only occurs if the %define word-array-name is used to
+# define an identifier that ends in _wrapped. If this option is not used,
+# no modifications are made to the gperf output, so that this script can be
+# used in generic build recipes and selectively enabled/disabled.
+#
+# To work, the following gperf options are assumed:
+#
+# %struct-type
+# %null-strings
+#
+# Do not use %pic. Since this script removes all keyword strings from the
+# table initializer it is not required to avoid relocations in PIC code.
+#
+# The user must supply a function with the following signature in the gperf file:
+#
+# const char *wordlist_func(const struct tag *);
+#
+# where "wordlist" is the same identifier provided to word-array-name, without
+# the _wrapped suffix, and "tag" is the identifier used for the gperf struct-type
+# declaration. This function returns the string corresponding to a given table
+# entry.
+#
+# The structure declaration should not include the first "name" member which is
+# ordinarily required. All struct members for nonempty table entries are set
+# exactly as specified in the gperf input, without the keyword name. All empty
+# table entries are initialized with {0}.
+
+BEGIN {
+ wl_tag = wl_name = "";
+ in_wordlist = 0;
+ linecount = 0;
+}
+
+NF == 1 && $1 == "};" {
+ in_wordlist = 0;
+}
+
+!in_wordlist && wl_name {
+ # Convert wordlist "name" member references to function call.
+ re = wl_name "_wrapped\\[key\\]\\.name";
+ gsub(re, wl_name "_func(" wl_name "_wrapped+key)");
+}
+
+in_wordlist && $1 ~ /^[^#]/ {
+ # Convert empty wordlist entries to {0}.
+ gsub(/[{][(]char\*[)]0[}]/, "{0}");
+
+ # Remove string portion of populated entry initializers.
+ gsub(/\\"/, "\1");
+ sub(/"[^"]*",? */, "");
+ gsub("\1", "\\\"");
+}
+
+# Locate the wordlist array definition, which is identified by a magic
+# name ending in "_wrapped"
+$NF == "=" && $(NF-1) ~ /._wrapped\[\]$/ {
+ wl_tag = $(NF-2);
+ wl_name = $(NF-1);
+ sub(/_[^_]*$/, "", wl_name);
+
+ in_wordlist = 1;
+ dump_lines();
+}
+
+!wl_name {
+ # Buffer lines until we know the structure and wordlist names
+ lines[linecount++] = $0;
+ next;
+}
+
+{ print; }
+
+END { dump_lines(); }
+
+function dump_lines(i, flag) {
+ flag = 0;
+ for (i = 0; i < linecount; i++) {
+ if (wl_name && !(flag > 0 || lines[i] ~ /^\//)) {
+ print "/* Postprocessed by gperf-wordwrap.awk */";
+ flag = 1;
+ }
+
+ if (flag == 1 && (lines[i] ~ "struct *" wl_tag " *{")) {
+ print "static const char *" wl_name "_func(const struct " wl_tag " *);"
+ flag = 2;
+ }
+
+ print lines[i];
+ delete lines[i];
+ }
+ linecount = 0;
+}