NEWS: v14.9.0 ("Long-tailed tit")
[s-mailx.git] / mk-okey-map.pl
blob74d45a52d8933aa5176666a7e9dee0010349854d
1 #!/usr/bin/env perl
2 require 5.008_001;
3 use utf8;
4 #@ Parse 'enum okeys' from nail.h and create gen-okeys.h. And see accmacvar.c.
5 # Public Domain
7 # Acceptable "longest distance" from hash-modulo-index to key
8 my $MAXDISTANCE_PENALTY = 5;
10 # Generate a more verbose output. Not for shipout versions.
11 my $VERB = 1;
13 my $MAILX = 'LC_ALL=C s-nail -#:/';
14 my $OUT = 'gen-okeys.h';
16 ## -- >8 -- 8< -- ##
18 use diagnostics -verbose;
19 use strict;
20 use warnings;
22 use FileHandle;
23 use IPC::Open2;
25 use sigtrap qw(handler cleanup normal-signals);
27 my ($S, @ENTS, $CTOOL, $CTOOL_EXE) = ($VERB ? ' ' : '');
29 sub main_fun{
30 if(@ARGV) {$VERB = 0; $S = ''}
32 parse_nail_h();
34 create_c_tool();
36 hash_em();
38 dump_map();
40 reverser();
42 cleanup(undef);
43 exit 0
46 sub cleanup{
47 die "$CTOOL_EXE: couldn't unlink: $^E"
48 if $CTOOL_EXE && -f $CTOOL_EXE && 1 != unlink $CTOOL_EXE;
49 die "$CTOOL: couldn't unlink: $^E"
50 if $CTOOL && -f $CTOOL && 1 != unlink $CTOOL;
51 die "Terminating due to signal $_[0]" if $_[0]
54 sub parse_nail_h{
55 die "nail.h: open: $^E" unless open F, '<', 'nail.h';
56 my ($init) = (0);
57 while(<F>){
58 # Only want the enum okeys content
59 if(/^enum okeys/) {$init = 1; next}
60 if(/^};/) {if($init) {$init = 2; last}; next}
61 $init || next;
63 # Ignore empty and comment lines
64 /^$/ && next;
65 /^\s*\/\*/ && next;
67 # An entry may have a comment with special directives
68 /^\s*(\w+),?\s*(?:\/\*\s*(?:{(.*)})\s*\*\/\s*)?$/;
69 next unless $1;
70 my ($k, $x) = ($1, $2);
71 my %vals;
72 $vals{enum} = $k;
73 $vals{bool} = ($k =~ /^ok_b/ ? 1 : 0);
74 $k = $1 if $k =~ /^ok_[bv]_(.+)$/;
75 $k =~ s/_/-/g;
76 $vals{name} = $k;
77 if($x){
78 while($x && $x =~ /^([^,]+?)(?:,(.*))?$/){
79 $x = $2;
80 $1 =~ /([^=]+)=(.+)/;
81 die "Unsupported special directive: $1"
82 if($1 ne 'name' &&
83 $1 ne 'virt' && $1 ne 'nolopts' &&
84 $1 ne 'rdonly' && $1 ne 'nodel' && $1 ne 'notempty' &&
85 $1 ne 'nocntrls' &&
86 $1 ne 'num' && $1 ne 'posnum' && $1 ne 'lower' &&
87 $1 ne 'vip' && $1 ne 'import' && $1 ne 'env' &&
88 $1 ne 'i3val' && $1 ne 'defval');
89 $vals{$1} = $2
92 push @ENTS, \%vals
94 if($init != 2) {die 'nail.h does not have the expected content'}
95 close F
98 sub create_c_tool{
99 $CTOOL = './tmp-okey-tool-' . $$ . '.c';
100 $CTOOL_EXE = $CTOOL . '.exe';
102 die "$CTOOL: open: $^E" unless open F, '>', $CTOOL;
103 print F '#define MAX_DISTANCE_PENALTY ', $MAXDISTANCE_PENALTY, "\n";
104 # >>>>>>>>>>>>>>>>>>>
105 print F <<'_EOT';
106 #define __CREATE_OKEY_MAP_PL
107 #include <stdint.h>
108 #include <stdlib.h>
109 #include <stdio.h>
110 #include <string.h>
112 #define n_NELEM(A) (sizeof(A) / sizeof(A[0]))
114 #define ui32_t uint32_t
115 #define ui16_t uint16_t
116 #define ui8_t uint8_t
118 enum a_amv_var_flags{
119 a_AMV_VF_NONE = 0,
120 a_AMV_VF_BOOL = 1<<0, /* ok_b_* */
121 a_AMV_VF_VIRT = 1<<1, /* "Stateless" automatic variable */
122 a_AMV_VF_NOLOPTS = 1<<2, /* May not be tracked by `localopts' */
123 a_AMV_VF_RDONLY = 1<<3, /* May not be set by user */
124 a_AMV_VF_NODEL = 1<<4, /* May not be deleted */
125 a_AMV_VF_NOTEMPTY = 1<<5, /* May not be assigned an empty value */
126 a_AMV_VF_NOCNTRLS = 1<<6, /* Value may not contain control characters */
127 a_AMV_VF_NUM = 1<<7, /* Value must be a 32-bit number */
128 a_AMV_VF_POSNUM = 1<<8, /* Value must be positive 32-bit number */
129 a_AMV_VF_LOWER = 1<<9, /* Values will be stored in a lowercase version */
130 a_AMV_VF_VIP = 1<<10, /* Wants _var_check_vips() evaluation */
131 a_AMV_VF_IMPORT = 1<<11, /* Import ONLY from environ (pre n_PSO_STARTED) */
132 a_AMV_VF_ENV = 1<<12, /* Update environment on change */
133 a_AMV_VF_I3VAL = 1<<13, /* Has an initial value */
134 a_AMV_VF_DEFVAL = 1<<14, /* Has a default value */
135 a_AMV_VF_LINKED = 1<<15, /* `environ' linked */
136 a_AMV_VF__MASK = (1<<(15+1)) - 1
139 struct a_amv_var_map{
140 ui32_t avm_hash;
141 ui16_t avm_keyoff;
142 ui16_t avm_flags; /* enum a_amv_var_flags */
145 #define n_CTA(A,S)
146 #include "gen-okeys.h"
148 static ui8_t seen_wraparound;
149 static size_t longest_distance;
151 static size_t
152 next_prime(size_t no){ /* blush (brute force) */
153 jredo:
154 ++no;
155 for(size_t i = 3; i < no; i += 2)
156 if(no % i == 0)
157 goto jredo;
158 return no;
161 static size_t *
162 reversy(size_t size){
163 struct a_amv_var_map const *vmp = a_amv_var_map,
164 *vmaxp = vmp + n_NELEM(a_amv_var_map);
165 size_t ldist = 0, *arr;
167 arr = malloc(sizeof *arr * size);
168 for(size_t i = 0; i < size; ++i)
169 arr[i] = n_NELEM(a_amv_var_map);
171 seen_wraparound = 0;
172 longest_distance = 0;
174 while(vmp < vmaxp){
175 ui32_t hash = vmp->avm_hash, i = hash % size, l;
177 for(l = 0; arr[i] != n_NELEM(a_amv_var_map); ++l)
178 if(++i == size){
179 seen_wraparound = 1;
180 i = 0;
182 if(l > longest_distance)
183 longest_distance = l;
184 arr[i] = (size_t)(vmp++ - a_amv_var_map);
186 return arr;
190 main(int argc, char **argv){
191 size_t *arr, size = n_NELEM(a_amv_var_map);
193 fprintf(stderr, "Starting reversy, okeys=%zu\n", size);
194 for(;;){
195 arr = reversy(size = next_prime(size));
196 fprintf(stderr, " - size=%zu longest_distance=%zu seen_wraparound=%d\n",
197 size, longest_distance, seen_wraparound);
198 if(longest_distance <= MAX_DISTANCE_PENALTY)
199 break;
200 free(arr);
203 printf(
204 "#define a_AMV_VAR_REV_ILL %zuu\n"
205 "#define a_AMV_VAR_REV_PRIME %zuu\n"
206 "#define a_AMV_VAR_REV_LONGEST %zuu\n"
207 "#define a_AMV_VAR_REV_WRAPAROUND %d\n"
208 "static %s const a_amv_var_revmap[a_AMV_VAR_REV_PRIME] = {\n%s",
209 n_NELEM(a_amv_var_map), size, longest_distance, seen_wraparound,
210 argv[1], (argc > 2 ? " " : ""));
211 for(size_t i = 0; i < size; ++i)
212 printf("%s%zuu", (i == 0 ? ""
213 : (i % 10 == 0 ? (argc > 2 ? ",\n " : ",\n")
214 : (argc > 2 ? ", " : ","))),
215 arr[i]);
216 printf("\n};\n");
217 return 0;
219 _EOT
220 # <<<<<<<<<<<<<<<<<<<
221 close F
224 sub hash_em{
225 die "hash_em: open: $^E"
226 unless my $pid = open2 *RFD, *WFD, $MAILX;
227 foreach my $e (@ENTS){
228 print WFD "vexpr hash $e->{name}\n";
229 my $h = <RFD>;
230 chomp $h;
231 $e->{hash} = $h
233 print WFD "x\n";
234 waitpid $pid, 0;
237 sub dump_map{
238 die "$OUT: open: $^E" unless open F, '>', $OUT;
239 print F "/*@ $OUT, generated by $0 on ", scalar gmtime(), ".\n",
240 " *@ See accmacvar.c for more */\n\n";
242 print F 'static char const a_amv_var_names[] = {', "\n";
243 my ($i, $alen) = (0, 0);
244 my (%virts, %defvals, %i3vals);
245 foreach my $e (@ENTS){
246 $e->{keyoff} = $alen;
247 my $k = $e->{name};
248 my $l = length $k;
249 my $a = join '\',\'', split(//, $k);
250 my (@fa);
251 if($e->{bool}) {push @fa, 'a_AMV_VF_BOOL'}
252 if($e->{virt}){
253 # Virtuals are implicitly rdonly and nodel
254 $e->{rdonly} = $e->{nodel} = 1;
255 $virts{$k} = $e;
256 push @fa, 'a_AMV_VF_VIRT'
258 if(defined $e->{i3val}){
259 $i3vals{$k} = $e;
260 push @fa, 'a_AMV_VF_I3VAL'
262 if($e->{defval}){
263 $e->{notempty} = 1;
264 $defvals{$k} = $e;
265 push @fa, 'a_AMV_VF_DEFVAL'
267 if($e->{import}){
268 $e->{env} = 1;
269 push @fa, 'a_AMV_VF_IMPORT'
271 if($e->{nolopts}) {push @fa, 'a_AMV_VF_NOLOPTS'}
272 if($e->{rdonly}) {push @fa, 'a_AMV_VF_RDONLY'}
273 if($e->{nodel}) {push @fa, 'a_AMV_VF_NODEL'}
274 if($e->{notempty}) {push @fa, 'a_AMV_VF_NOTEMPTY'}
275 if($e->{nocntrls}) {push @fa, 'a_AMV_VF_NOCNTRLS'}
276 if($e->{num}) {push @fa, 'a_AMV_VF_NUM'}
277 if($e->{posnum}) {push @fa, 'a_AMV_VF_POSNUM'}
278 if($e->{lower}) {push @fa, 'a_AMV_VF_LOWER'}
279 if($e->{vip}) {push @fa, 'a_AMV_VF_VIP'}
280 if($e->{env}) {push @fa, 'a_AMV_VF_ENV'}
281 $e->{flags} = \@fa;
282 my $f = join('|', @fa);
283 $f = ', ' . $f if length $f;
284 print F "${S}/* $i. [$alen]+$l $k$f */\n" if $VERB;
285 print F "${S}'$a','\\0',\n";
286 ++$i;
287 $alen += $l + 1
289 print F '};', "\n\n";
291 print F 'n_CTA(a_AMV_VF_NONE == 0, "Value not 0 as expected");', "\n";
292 print F 'static struct a_amv_var_map const a_amv_var_map[] = {', "\n";
293 foreach my $e (@ENTS){
294 my $f = $VERB ? 'a_AMV_VF_NONE' : '0';
295 my $fa = join '|', @{$e->{flags}};
296 $f .= '|' . $fa if length $fa;
297 print F "${S}{$e->{hash}u, $e->{keyoff}u, $f},";
298 if($VERB) {print F "${S}/* $e->{name} */\n"}
299 else {print F "\n"}
301 print F '};', "\n\n";
303 # We have at least version stuff in here
304 # The problem is that struct var uses a variable sized character buffer
305 # which cannot be initialized in a conforming way :(
306 print F <<_EOT;
307 #ifndef __CREATE_OKEY_MAP_PL
308 # ifdef HAVE_PUTENV
309 # define a_X(X) X
310 # else
311 # define a_X(X)
312 # endif
314 /* Unfortunately init of varsized buffer won't work: define "subclass"es */
315 _EOT
316 my @skeys = sort keys %virts;
318 foreach(@skeys){
319 my $e = $virts{$_};
320 $e->{vname} = $1 if $e->{enum} =~ /ok_._(.*)/;
321 $e->{vstruct} = "var_virt_$e->{vname}";
322 print F "static char const a_amv_$e->{vstruct}_val[] = {$e->{virt}};\n";
323 print F "static struct{\n";
324 print F "${S}struct a_amv_var *av_link;\n";
325 print F "${S}char const *av_value;\n";
326 print F "${S}a_X(char *av_env;)\n";
327 print F "${S}ui16_t av_flags;\n";
328 print F "${S}char const av_name[", length($e->{name}), " +1];\n";
329 my $f = $VERB ? 'a_AMV_VF_NONE' : '0';
330 my $fa = join '|', @{$e->{flags}};
331 $f .= '|' . $fa if length $fa;
332 print F "} const a_amv_$e->{vstruct} = ",
333 "{NULL, a_amv_$e->{vstruct}_val, a_X(0 COMMA) $f, ",
334 "\"$e->{name}\"};\n\n"
336 print F "# undef a_X\n";
338 print F "\n";
339 print F '#define a_AMV_VAR_VIRTS_CNT ', scalar @skeys, "\n";
340 print F 'static struct a_amv_var_virt const a_amv_var_virts[] = {', "\n";
341 foreach(@skeys){
342 my $e = $virts{$_};
343 my $n = $1 if $e->{enum} =~ /ok_._(.*)/;
344 print F "${S}{$e->{enum}, {0,}, (void const*)&a_amv_$e->{vstruct}},\n";
346 print F "};\n";
349 @skeys = sort keys %i3vals;
351 print F "\n";
352 print F '#define a_AMV_VAR_I3VALS_CNT ', scalar @skeys, "\n";
353 print F 'static struct a_amv_var_defval const a_amv_var_i3vals[] = {', "\n";
354 foreach(@skeys){
355 my $e = $i3vals{$_};
356 print F "${S}{", $e->{enum}, ', {0,}, ',
357 (!$e->{bool} ? $e->{i3val} : "NULL"), "},\n"
359 print F "};\n";
362 @skeys = sort keys %defvals;
364 print F "\n";
365 print F '#define a_AMV_VAR_DEFVALS_CNT ', scalar @skeys, "\n";
366 print F 'static struct a_amv_var_defval const a_amv_var_defvals[] = {', "\n";
367 foreach(@skeys){
368 my $e = $defvals{$_};
369 print F "${S}{", $e->{enum}, ', {0,}, ',
370 (!$e->{bool} ? $e->{defval} : "NULL"), "},\n"
372 print F "};\n";
374 print F "#endif /* __CREATE_OKEY_MAP_PL */\n";
376 # Special var backing [#@*?]|[1-9][0-9]*|0
377 $i = 0;
378 print F "\n";
379 foreach my $e (@ENTS){
380 if($e->{name} eq '--special-param'){
381 print F "#define a_AMV_VAR__SPECIAL_PARAM_MAP_IDX ${i}u\n"
383 # The rest are only speedups
384 elsif($e->{name} eq '?'){
385 print F "#define a_AMV_VAR__QM_MAP_IDX ${i}u\n"
386 }elsif($e->{name} eq '!'){
387 print F "#define a_AMV_VAR__EM_MAP_IDX ${i}u\n"
389 ++$i
392 print F "\n";
393 die "$OUT: close: $^E" unless close F
396 sub reverser{
397 my $argv2 = $VERB ? ' verb' : '';
398 system("c99 -I. -o $CTOOL_EXE $CTOOL");
399 my $t = (@ENTS < 0xFF ? 'ui8_t' : (@ENTS < 0xFFFF ? 'ui16_t' : 'ui32_t'));
400 `$CTOOL_EXE $t$argv2 >> $OUT`
403 {package main; main_fun()}
405 # s-it-mode