]> git.draconx.ca Git - dxcommon.git/blob - scripts/gen-strtab.awk
Add script for generating miscellaneous string tables.
[dxcommon.git] / scripts / gen-strtab.awk
1 #!/bin/awk -f
2 #
3 # Copyright © 2021 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 # A string is defined by beginning a line with an & character, which must
20 # be immediately followed by a C identifier.  A nonempty sequence of
21 # whitespace (with at most one newline) separates the identifier from the
22 # beginning of the string itself.  This whitespace is never included in the
23 # output.
24 #
25 # The string is then interpreted as follows:
26 #
27 #   - Leading blanks on each line are ignored.
28 #   - The sequences \\, \a, \b, \t, \n, \v, \f and \r can be entered and
29 #     mean the same as they do in C string literals.  The "\\" sequence
30 #     prevents any special interpretation of the second backslash.
31 #   - Newlines in the input are included in the output, except for the
32 #     where the entire string (including its identifier) are on one line.
33 #   - If this is not desired, a newline which is immediately preceded by an
34 #     unescaped backslash will deleted, along with the backslash.
35 #   - All other backslashes are deleted.  This can be used to prevent special
36 #     handling of whitespace, # or & characters at the beginning of a line.
37 #
38 # The output defines a variable, strtab, which contains all of the strings,
39 # and each identifier in the input is declared as an emumeration constant
40 # whose value is the offset of the associated string within strtab.
41 #
42 # The object-like macro STRTAB_MAX_OFFSET is defined and expands to the
43 # greatest string offset, suitable for use in #if preprocessing directives.
44
45 END {
46   print "/*"
47   if (FILENAME) {
48     print " * Automatically generated by gen-strtab.awk from " FILENAME
49   } else {
50     print " * Automatically generated by gen-strtab.awk"
51   }
52   print " * Do not edit."
53   print " */"
54 }
55
56 BEGIN {
57   collected = ident = ""
58   startline = endline = 0
59   num_vars = 0
60 }
61
62 $0 ~ /^[#]/ { next }
63
64 $0 ~ /^[&]/ {
65   if (ident) {
66     finish_string_input(strings, ident, collected)
67     vars[num_vars++] = ident
68   }
69
70   sub(/^[&]/, "", $1)
71   startline = NR
72   ident = $1
73
74   $1 = ""
75   collected = ""
76 }
77
78 ident {
79   sub(/^[ \t]*/, "")
80   if (collected) {
81     collected = collected "\n" $0
82   } else {
83     collected = $0
84   }
85
86   endline = NR
87 }
88
89 END {
90   if (ident) {
91     finish_string_input(strings, ident, collected)
92     vars[num_vars++] = ident
93   }
94 }
95
96 END {
97   strtab = ""
98   strtab_len = 0
99   count = bucketsort(sorted_strings, strings)
100   max = 0
101
102   print "\n#define STR_L10N_(x)"
103   print "#ifndef N_"
104   print "#  define N_(x) x"
105   print "#endif"
106   print "\nstatic const char strtab[] ="
107
108   for (i = 0; i < count; i++) {
109     s = sorted_strings[i]
110     gsub(/\\\\/, "\2", s)
111     if ((n = index(strtab "\1", s "\1")) > 0) {
112       offsets[sorted_strings[i]] = real_length(substr(strtab, 1, n-1))
113       print "\tSTR_L10N_(N_(\"" sorted_strings[i] "\"))"
114     } else if (strtab) {
115       strtab = strtab "\1" s
116       offsets[sorted_strings[i]] = strtab_len + 1
117       strtab_len += real_length(s) + 1
118     } else {
119       strtab = s
120       offsets[sorted_strings[i]] = 0
121       strtab_len += real_length(s)
122     }
123   }
124
125   gsub(/\2/, "\\\\", strtab)
126   gsub(/\1/, "\")\"\\0\"\n\tN_(\"", strtab)
127   print "\tN_(\"" strtab "\")"
128   print "\t\"\";"
129
130   print "enum {"
131   for (i = 0; i < num_vars; i++) {
132     sep = (i+1) != num_vars ? "," : ""
133     s = vars[i]
134     o = offsets[strings[s]]
135     print "\t" s " = " o sep
136     if (o > max) {
137       max = o
138     }
139   }
140   print "};"
141   print "\n#define STRTAB_MAX_OFFSET " max
142 }
143
144 # finish_input_string(strings, ident, val)
145 #
146 # Deal with backslash-escapes and special characters in val, then set
147 # strings[ident] = val.
148 function finish_string_input(strings, ident, val, n, tmpval)
149 {
150   gsub(/\\\\/, "\1", val)
151   val = val (endline > startline ? "\n" : "")
152   gsub(/\\\n/, "", val)
153
154   tmpval = ""
155   while ((n = match(val, /\\[^abtnvfr]/)) > 0) {
156     tmpval = tmpval substr(val, 1, n-1)
157     val = substr(val, n+1)
158   }
159   tmpval = tmpval val
160
161   # Escape special characters
162   gsub(/"/, "\\\"", tmpval)
163   gsub(/\t/, "\\t", tmpval)
164   gsub(/\n/, "\\n", tmpval)
165   gsub(/\1/, "\\\\", tmpval)
166
167   strings[ident] = tmpval
168 }
169
170 function real_length(s, t)
171 {
172   t = length(s)
173   return t - gsub(/\\./, "&", s)
174 }
175
176 # bucketsort(dst, src)
177 #
178 # Sort the elements of src by descending string length,
179 # placing them into dst[0] ... dst[n].
180 #
181 # Returns the number of elements.
182 function bucketsort(dst, src, buckets, max, count, i, t)
183 {
184   for (t in src) {
185     i = length(src[t])
186     if (i > max) { max = i }
187     buckets[i]++
188   }
189
190   for (i = max; i > 0; i--) {
191     if (i in buckets) {
192       t = buckets[i]
193       buckets[i] = count
194       count += t
195     }
196   }
197
198   for (t in src) {
199     i = length(t = src[t])
200     dst[buckets[i]++] = t
201   }
202
203   return count
204 }