]> git.draconx.ca Git - dxcommon.git/blob - scripts/gen-tree.awk
gen-tree.awk: Add options to tweak the strtab output.
[dxcommon.git] / scripts / gen-tree.awk
1 #!/bin/awk -f
2 #
3 # Copyright © 2021-2022 Nick Bowler
4 #
5 # Generate one or more C array initializers to encode simple tree structures
6 # in a compact format.
7 #
8 # Each nonempty line of the input file is either an option specification
9 # or # a tree specification.  An option specification (described later)
10 # begins with an @ character.  Other lines specify tree nodes.
11 #
12 # The first field of a tree specification must be a valid C identifier,
13 # optionally followed by a comma.  The identifiers used on non-leaf nodes
14 # must be unique with respect to other non-leaf nodes, but leaf nodes may
15 # share the same identifier as other nodes.
16 #
17 # The tree structure is expressed through indentation.  Lines with no leading
18 # whitespace designate root nodes.  When the number of white-space characters
19 # at the beginning of a line is greater than that of the previous line, this
20 # introduces a new subtree rooted at the previous node.  When the number of
21 # leading white-space characters is less than the previous line, this concludes
22 # the definition of the current subtree.
23 #
24 # For example, the input on the left describes the tree on the right.
25 #
26 #    INPUT: root           TREE: root -- a -- b -- c
27 #              a                    |    |
28 #                 b                 |    + -- d
29 #                    c              |
30 #                 d                 + -- e -- f
31 #              e                         |
32 #                 f                      + -- g
33 #                 g
34 #
35 # For each root node X, the object-like macro X_INITIALIZER is defined.  This
36 # can be used to initialize an array with static storage duration.  Each line
37 # of the input is defines the initializer for one array element.  These are
38 # then ordered topologically beginning with the root, but the root itself is
39 # not included.  Each subtree is terminated by an "empty" element (with an
40 # initializer of {0}).
41 #
42 # If there is a comma after the identifier which begins a tree element, or
43 # if the identifier is the only text on the line, then the element initializer
44 # is constructed by surrounding the entire line by braces.  Otherwise, the
45 # identifier is removed and the remainder of the line is surrounded by braces
46 # to construct the initializer.  This allows the exact form of the initializer
47 # to be customized.
48 #
49 # For example, the above input will produce this (or an equivalent topological
50 # ordering) initializer:
51 #
52 #    OUTPUT: #define root_INITIALIZER \
53 #                 {a}, {e}, {0}, {b}, {d}, {0}, {f}, {g}, {0}, {c}, {0}
54 #
55 # Constants representing the array offset for each subtree are additionally
56 # defined.  For a non-leaf node X, the enumeration constant X_OFFSET gives the
57 # index of the first node rooted at X.  With the above example, the results
58 # are:
59 #
60 #    OUTPUT: enum { a_OFFSET = 3, b_OFFSET = 9, e_OFFSET = 6 };
61 #
62 # By using an array of structures to represent the tree and including these
63 # constants in initializers, it is possible for the flattened tree to describe
64 # its own structure.
65 #
66 # Finally, each node identifier is defined as an enumeration constant
67 # representing the nonzero offset into a string table tree_strtab.  These
68 # constants will normally be in scope when using the root_INITIALIZER macro,
69 # which means the resulting array initializers will use them.
70 #
71 # Options may be used to alter the normal behaviour.  They may be placed
72 # anywhere in the input file.  The following options are defined:
73 #
74 #   @nostrtab
75 #     Do not output a definition for tree_strtab or its associated
76 #     enumeration constants.
77 #
78 # License WTFPL2: Do What The Fuck You Want To Public License, version 2.
79 # This is free software: you are free to do what the fuck you want to.
80 # There is NO WARRANTY, to the extent permitted by law.
81
82 END {
83   print "/*"
84   if (FILENAME) {
85     print " * Automatically generated by gen-tree.awk from " FILENAME
86   } else {
87     print " * Automatically generated by gen-tree.awk"
88   }
89   print " * Do not edit."
90   print " */"
91 }
92
93 BEGIN {
94   opts["strtab"] = 1;
95
96   depth = max_depth = 0;
97   num_entries = 0;
98
99   tree_name = "";
100   entry_name = "";
101
102   indent_stack[0] = 0;
103 }
104
105 # Options
106 sub(/^@/, "", $0) {
107   if (NF == 1) {
108     orig=$1
109     gsub(/-/, "_", $1);
110     val = !sub(/^no_?/, "", $1);
111     if ($1 in opts) {
112       opts[$1] = val;
113     } else {
114       print "error: unrecognized option: @" orig | "cat 1>&2"
115       exit 1
116     }
117   }
118   next
119 }
120
121 NF == 0 { next }
122 { indent = index($0, $1) - 1 }
123
124 indent > 0 {
125   if (indent > indent_stack[depth]) {
126     indent_stack[++depth] = indent;
127     if (depth > 1) {
128       subtree_offsets[entry_name] = level_count[depth];
129       subtree_depth[entry_name] = depth;
130     }
131   } else {
132     while (indent < indent_stack[depth]) {
133       tree_items[depth] = tree_items[depth] ", \\\n\t{ 0 }";
134       level_count[depth]++;
135       depth--;
136     }
137   }
138
139   entry_name = $1; sub(/,$/, "", entry_name);
140   all_items[num_entries++] = entry_name;
141
142   # Construct the element initializer for this tree node.
143   $1 = " " $1;
144   if (NF > 1) {
145     # Check if entry name is followed by a comma.  If it is not, the entry
146     # name is excluded from the initializer.
147     check_str = $1$2;
148     gsub(/[ \t]/, "", check_str);
149     if (index(check_str, entry_name ",") == 0) {
150       $1 = "";
151     }
152   }
153   $1 = ", \\\n\t{" $1; $NF = $NF " }";
154
155   tree_items[depth] = tree_items[depth] $0;
156   level_count[depth]++;
157 }
158
159 indent == 0 && tree_identifier {
160   trees[tree_identifier] = format_items();
161   tree_identifier = "";
162 }
163 END {
164   if (tree_identifier)
165     trees[tree_identifier] = format_items()
166 }
167 indent == 0 { tree_identifier = $1 }
168
169 END {
170   prefix = "\nenum {\n";
171
172   if (opts["strtab"]) {
173     entry_strtab = "\1";
174     bucketsort(sorted_items, all_items);
175     for (i = 0; i < num_entries; i++) {
176       s = sorted_items[i];
177       if ((n = index(entry_strtab, s "\1")) > 0) {
178         entry_offsets[s] = n-1;
179       } else {
180         entry_offsets[s] = length(entry_strtab);
181         entry_strtab = entry_strtab s "\1";
182       }
183     }
184
185     gsub(/\1/, "\"\n\t\"\\0\" \"", entry_strtab);
186     sub(/^"/, "", entry_strtab);
187     sub(/\n[^\n]*$/, ";", entry_strtab);
188     print "\nstatic const char tree_strtab[] =" entry_strtab
189
190     for (item in entry_offsets) {
191       printf "%s\t%s = %d", prefix, item, entry_offsets[item];
192       prefix = ",\n";
193     }
194   }
195
196   for (i in subtree_offsets) {
197     printf "%s\t%s_OFFSET = %d", prefix, i, subtree_offsets[i];
198     prefix = ",\n";
199   }
200   if (!index(prefix, "enum")) {
201     print "\n};";
202   }
203 }
204
205 END {
206   for (tree in trees) {
207     print "\n" trees[tree];
208   }
209 }
210
211 function format_items(s, i)
212 {
213   while (depth > 0) {
214     tree_items[depth] = tree_items[depth] ", \\\n\t{ 0 }";
215     level_count[depth]++;
216     depth--;
217   }
218
219   for (i = 2; tree_items[i]; i++) {
220     level_count[i] += level_count[i-1];
221   }
222
223   for (i in subtree_depth) {
224     subtree_offsets[i] += level_count[subtree_depth[i]-1];
225     delete subtree_depth[i];
226   }
227
228   for (i = 1; tree_items[i]; i++) {
229     s = s tree_items[i];
230     delete tree_items[i];
231     delete level_count[i];
232   }
233
234   sub(/^,/, "", s);
235   return "#define " tree_identifier "_INITIALIZER" s;
236 }
237
238 # bucketsort(dst, src)
239 #
240 # Sort the elements of src by descending string length,
241 # placing them into dst[0] ... dst[n].
242 #
243 # Returns the number of elements.
244 function bucketsort(dst, src, buckets, max, count, i, t)
245 {
246   for (t in src) {
247     i = length(src[t])
248     if (i > max) { max = i }
249     buckets[i]++
250   }
251
252   for (i = max; i > 0; i--) {
253     if (i in buckets) {
254       t = buckets[i]
255       buckets[i] = count
256       count += t
257     }
258   }
259
260   for (t in src) {
261     i = length(t = src[t])
262     dst[buckets[i]++] = t
263   }
264
265   return count
266 }