]> git.draconx.ca Git - dxcommon.git/blob - scripts/gen-strtab.awk
gen-strtab.awk: Add options to tweak the output.
[dxcommon.git] / scripts / gen-strtab.awk
1 #!/bin/awk -f
2 #
3 # Copyright © 2021, 2023 Nick Bowler
4 #
5 # Generate a C string table based on an input string specification file.
6 #
7 # A string table is a single large char single array containing all of
8 # the specified (0-terminated) strings, which is then offset to obtain
9 # the desired string.  By storing these offsets instead of string pointers
10 # into read-only data structures, this can reduce the need for relocation
11 # processing at startup when programs are built in PIC mode.
12 #
13 # The string specification file is processed line by line.  Comment
14 # lines may be included by beginning the line with a # character, which
15 # must be the very first character on the line.  If a comment is encountered,
16 # processing immediately moves on to the next line and the result is as if
17 # the comment line were omitted from the input.
18 #
19 # Options may be used to alter the normal behaviour.  An option is placed
20 # on a line by itself beginning with an @ character, and may appear anywhere
21 # in the input file.  The following options are defined:
22 #
23 #   @nozero
24 #     All strings will have a non-zero offset in the strtab.
25 #
26 # A string is defined by beginning a line with an & character, which must
27 # be immediately followed by a C identifier.  A nonempty sequence of
28 # whitespace (with at most one newline) separates the identifier from the
29 # beginning of the string itself.  This whitespace is never included in the
30 # output.
31 #
32 # The string is then interpreted as follows:
33 #
34 #   - Leading blanks on each line are ignored.
35 #   - The sequences \\, \a, \b, \t, \n, \v, \f and \r can be entered and
36 #     mean the same as they do in C string literals.  The "\\" sequence
37 #     prevents any special interpretation of the second backslash.
38 #   - Newlines in the input are included in the output, except for the
39 #     where the entire string (including its identifier) are on one line.
40 #   - If this is not desired, a newline which is immediately preceded by an
41 #     unescaped backslash will deleted, along with the backslash.
42 #   - All other backslashes are deleted.  This can be used to prevent special
43 #     handling of whitespace, # or & characters at the beginning of a line.
44 #
45 # The output defines a variable, strtab, which contains all of the strings,
46 # and each identifier in the input is declared as an emumeration constant
47 # whose value is the offset of the associated string within strtab.
48 #
49 # The object-like macro STRTAB_MAX_OFFSET is defined and expands to the
50 # greatest string offset, suitable for use in #if preprocessing directives.
51 #
52 # License WTFPL2: Do What The Fuck You Want To Public License, version 2.
53 # This is free software: you are free to do what the fuck you want to.
54 # There is NO WARRANTY, to the extent permitted by law.
55
56 END {
57   print "/*"
58   if (FILENAME) {
59     print " * Automatically generated by gen-strtab.awk from " FILENAME
60   } else {
61     print " * Automatically generated by gen-strtab.awk"
62   }
63   print " * Do not edit."
64   print " */"
65 }
66
67 BEGIN {
68   opts["zero"] = 1
69   collected = ident = ""
70   startline = endline = 0
71   num_vars = 0
72 }
73
74 # Comments
75 NF == 0 || $0 ~ /^[#]/ { next }
76
77 # Options
78 sub(/^@/, "", $0) {
79   if (NF == 1) {
80     orig=$1
81     gsub(/-/, "_", $1);
82     val = !sub(/^no_?/, "", $1);
83     if ($1 in opts) {
84       opts[$1] = val;
85     } else {
86       print "error: unrecognized option: @" orig | "cat 1>&2"
87       exit 1
88     }
89   }
90   next
91 }
92
93 $0 ~ /^[&]/ {
94   if (ident) {
95     finish_string_input(strings, ident, collected)
96     vars[num_vars++] = ident
97   }
98
99   sub(/^[&]/, "", $1)
100   startline = NR
101   ident = $1
102
103   $1 = ""
104   collected = ""
105 }
106
107 ident {
108   sub(/^[ \t]*/, "")
109   if (collected) {
110     collected = collected "\n" $0
111   } else {
112     collected = $0
113   }
114
115   endline = NR
116 }
117
118 END {
119   if (ident) {
120     finish_string_input(strings, ident, collected)
121     vars[num_vars++] = ident
122   }
123 }
124
125 END {
126   strtab = ""
127   strtab_len = 0
128   count = bucketsort(sorted_strings, strings)
129   max = 0
130
131   print "\n#define STR_L10N_(x)"
132   print "#ifndef N_"
133   print "#  define N_(x) x"
134   print "#endif"
135   print "\nstatic const char strtab[] ="
136
137   if (!opts["zero"])
138     print "\t\"\\0\"";
139
140   for (i = 0; i < count; i++) {
141     s = sorted_strings[i]
142     gsub(/\\\\/, "\2", s)
143     if ((n = index(strtab "\1", s "\1")) > 0) {
144       offsets[sorted_strings[i]] = real_length(substr(strtab, 1, n-1))
145       print "\tSTR_L10N_(N_(\"" sorted_strings[i] "\"))"
146     } else if (strtab) {
147       strtab = strtab "\1" s
148       offsets[sorted_strings[i]] = strtab_len + 1
149       strtab_len += real_length(s) + 1
150     } else {
151       strtab = s
152       offsets[sorted_strings[i]] = 0
153       strtab_len += real_length(s)
154     }
155   }
156
157   gsub(/\2/, "\\\\", strtab)
158   gsub(/\1/, "\")\"\\0\"\n\tN_(\"", strtab)
159   print "\tN_(\"" strtab "\")"
160   print "\t\"\";"
161
162   print "enum {"
163   for (i = 0; i < num_vars; i++) {
164     sep = (i+1) != num_vars ? "," : ""
165     s = vars[i]
166     o = offsets[strings[s]] + !opts["zero"]
167     print "\t" s " = " o sep
168     if (o > max) {
169       max = o
170     }
171   }
172   print "};"
173   print "\n#define STRTAB_MAX_OFFSET " max
174 }
175
176 # finish_input_string(strings, ident, val)
177 #
178 # Deal with backslash-escapes and special characters in val, then set
179 # strings[ident] = val.
180 function finish_string_input(strings, ident, val, n, tmpval)
181 {
182   gsub(/\\\\/, "\1", val)
183   val = val (endline > startline ? "\n" : "")
184   gsub(/\\\n/, "", val)
185
186   tmpval = ""
187   while ((n = match(val, /\\[^abtnvfr]/)) > 0) {
188     tmpval = tmpval substr(val, 1, n-1)
189     val = substr(val, n+1)
190   }
191   tmpval = tmpval val
192
193   # Escape special characters
194   gsub(/"/, "\\\"", tmpval)
195   gsub(/\t/, "\\t", tmpval)
196   gsub(/\n/, "\\n", tmpval)
197   gsub(/\1/, "\\\\", tmpval)
198
199   strings[ident] = tmpval
200 }
201
202 function real_length(s, t)
203 {
204   t = length(s)
205   return t - gsub(/\\./, "&", s)
206 }
207
208 # bucketsort(dst, src)
209 #
210 # Sort the elements of src by descending string length,
211 # placing them into dst[0] ... dst[n].
212 #
213 # Returns the number of elements.
214 function bucketsort(dst, src, buckets, max, count, i, t)
215 {
216   for (t in src) {
217     i = length(src[t])
218     if (i > max) { max = i }
219     buckets[i]++
220   }
221
222   for (i = max; i > 0; i--) {
223     if (i in buckets) {
224       t = buckets[i]
225       buckets[i] = count
226       count += t
227     }
228   }
229
230   for (t in src) {
231     i = length(t = src[t])
232     dst[buckets[i]++] = t
233   }
234
235   return count
236 }