]> git.draconx.ca Git - dxcommon.git/blob - scripts/gen-strtab.awk
Add a script to generate constant tree structures.
[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 # License WTFPL2: Do What The Fuck You Want To Public License, version 2.
46 # This is free software: you are free to do what the fuck you want to.
47 # There is NO WARRANTY, to the extent permitted by law.
48
49 END {
50   print "/*"
51   if (FILENAME) {
52     print " * Automatically generated by gen-strtab.awk from " FILENAME
53   } else {
54     print " * Automatically generated by gen-strtab.awk"
55   }
56   print " * Do not edit."
57   print " */"
58 }
59
60 BEGIN {
61   collected = ident = ""
62   startline = endline = 0
63   num_vars = 0
64 }
65
66 $0 ~ /^[#]/ { next }
67
68 $0 ~ /^[&]/ {
69   if (ident) {
70     finish_string_input(strings, ident, collected)
71     vars[num_vars++] = ident
72   }
73
74   sub(/^[&]/, "", $1)
75   startline = NR
76   ident = $1
77
78   $1 = ""
79   collected = ""
80 }
81
82 ident {
83   sub(/^[ \t]*/, "")
84   if (collected) {
85     collected = collected "\n" $0
86   } else {
87     collected = $0
88   }
89
90   endline = NR
91 }
92
93 END {
94   if (ident) {
95     finish_string_input(strings, ident, collected)
96     vars[num_vars++] = ident
97   }
98 }
99
100 END {
101   strtab = ""
102   strtab_len = 0
103   count = bucketsort(sorted_strings, strings)
104   max = 0
105
106   print "\n#define STR_L10N_(x)"
107   print "#ifndef N_"
108   print "#  define N_(x) x"
109   print "#endif"
110   print "\nstatic const char strtab[] ="
111
112   for (i = 0; i < count; i++) {
113     s = sorted_strings[i]
114     gsub(/\\\\/, "\2", s)
115     if ((n = index(strtab "\1", s "\1")) > 0) {
116       offsets[sorted_strings[i]] = real_length(substr(strtab, 1, n-1))
117       print "\tSTR_L10N_(N_(\"" sorted_strings[i] "\"))"
118     } else if (strtab) {
119       strtab = strtab "\1" s
120       offsets[sorted_strings[i]] = strtab_len + 1
121       strtab_len += real_length(s) + 1
122     } else {
123       strtab = s
124       offsets[sorted_strings[i]] = 0
125       strtab_len += real_length(s)
126     }
127   }
128
129   gsub(/\2/, "\\\\", strtab)
130   gsub(/\1/, "\")\"\\0\"\n\tN_(\"", strtab)
131   print "\tN_(\"" strtab "\")"
132   print "\t\"\";"
133
134   print "enum {"
135   for (i = 0; i < num_vars; i++) {
136     sep = (i+1) != num_vars ? "," : ""
137     s = vars[i]
138     o = offsets[strings[s]]
139     print "\t" s " = " o sep
140     if (o > max) {
141       max = o
142     }
143   }
144   print "};"
145   print "\n#define STRTAB_MAX_OFFSET " max
146 }
147
148 # finish_input_string(strings, ident, val)
149 #
150 # Deal with backslash-escapes and special characters in val, then set
151 # strings[ident] = val.
152 function finish_string_input(strings, ident, val, n, tmpval)
153 {
154   gsub(/\\\\/, "\1", val)
155   val = val (endline > startline ? "\n" : "")
156   gsub(/\\\n/, "", val)
157
158   tmpval = ""
159   while ((n = match(val, /\\[^abtnvfr]/)) > 0) {
160     tmpval = tmpval substr(val, 1, n-1)
161     val = substr(val, n+1)
162   }
163   tmpval = tmpval val
164
165   # Escape special characters
166   gsub(/"/, "\\\"", tmpval)
167   gsub(/\t/, "\\t", tmpval)
168   gsub(/\n/, "\\n", tmpval)
169   gsub(/\1/, "\\\\", tmpval)
170
171   strings[ident] = tmpval
172 }
173
174 function real_length(s, t)
175 {
176   t = length(s)
177   return t - gsub(/\\./, "&", s)
178 }
179
180 # bucketsort(dst, src)
181 #
182 # Sort the elements of src by descending string length,
183 # placing them into dst[0] ... dst[n].
184 #
185 # Returns the number of elements.
186 function bucketsort(dst, src, buckets, max, count, i, t)
187 {
188   for (t in src) {
189     i = length(src[t])
190     if (i > max) { max = i }
191     buckets[i]++
192   }
193
194   for (i = max; i > 0; i--) {
195     if (i in buckets) {
196       t = buckets[i]
197       buckets[i] = count
198       count += t
199     }
200   }
201
202   for (t in src) {
203     i = length(t = src[t])
204     dst[buckets[i]++] = t
205   }
206
207   return count
208 }