`write'++: !interactive: urlxenc() attachment paths (Ralph Corderoy)..
[s-mailx.git] / mk-okey-map.pl
blob8bf5acda938b3e05576eaa13b748f7fe4f474288
1 #!/usr/bin/env perl
2 require 5.008_001;
3 use utf8;
4 #@ Parse 'enum okeys' from nail.h and create 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 $OUT = 'okeys.h';
15 ## -- >8 -- 8< -- ##
17 use diagnostics -verbose;
18 use strict;
19 use warnings;
21 use sigtrap qw(handler cleanup normal-signals);
23 my ($S, @ENTS, $CTOOL, $CTOOL_EXE) = ($VERB ? ' ' : '');
25 sub main_fun{
26 if(@ARGV) {$VERB = 0; $S = ''}
28 parse_nail_h();
30 create_c_tool();
32 hash_em();
33 dump_map();
35 reverser();
37 cleanup(undef);
38 exit 0
41 sub cleanup{
42 die "$CTOOL_EXE: couldn't unlink: $^E"
43 if $CTOOL_EXE && -f $CTOOL_EXE && 1 != unlink $CTOOL_EXE;
44 die "$CTOOL: couldn't unlink: $^E"
45 if $CTOOL && -f $CTOOL && 1 != unlink $CTOOL;
46 die "Terminating due to signal $_[0]" if $_[0]
49 sub parse_nail_h{
50 die "nail.h: open: $^E" unless open F, '<', 'nail.h';
51 my ($init) = (0);
52 while(<F>){
53 # Only want the enum okeys content
54 if(/^enum okeys/) {$init = 1; next}
55 if(/^};/) {if($init) {$init = 2; last}; next}
56 $init || next;
58 # Ignore empty and comment lines
59 /^$/ && next;
60 /^\s*\/\*/ && next;
62 # An entry may have a comment with special directives
63 /^\s*(\w+),?\s*(?:\/\*\s*(?:{(.*)})\s*\*\/\s*)?$/;
64 next unless $1;
65 my ($k, $x) = ($1, $2);
66 my %vals;
67 $vals{enum} = $k;
68 $vals{bool} = ($k =~ /^ok_b/ ? 1 : 0);
69 $k = $1 if $k =~ /^ok_[bv]_(.+)$/;
70 $k =~ s/_/-/g;
71 $vals{name} = $k;
72 if($x){
73 while($x && $x =~ /^([^,]+?)(?:,(.*))?$/){
74 $x = $2;
75 $1 =~ /([^=]+)=(.+)/;
76 die "Unsupported special directive: $1"
77 if($1 ne 'name' &&
78 $1 ne 'rdonly' && $1 ne 'nodel' && $1 ne 'notempty' &&
79 $1 ne 'nocntrls' && $1 ne 'num' && $1 ne 'posnum' &&
80 $1 ne 'vip' && $1 ne 'virt' &&
81 $1 ne 'env' && $1 ne 'import' &&
82 $1 ne 'i3val' && $1 ne 'defval');
83 $vals{$1} = $2
86 push @ENTS, \%vals
88 if($init != 2) {die 'nail.h does not have the expected content'}
89 close F
92 sub create_c_tool{
93 $CTOOL = './tmp-okey-tool-' . $$ . '.c';
94 $CTOOL_EXE = $CTOOL . '.exe';
96 die "$CTOOL: open: $^E" unless open F, '>', $CTOOL;
97 # xxx optimize: could read lines and write lines in HASH_MODE..
98 print F '#define MAX_DISTANCE_PENALTY ', $MAXDISTANCE_PENALTY, "\n";
99 # >>>>>>>>>>>>>>>>>>>
100 print F <<'_EOT';
101 #define __CREATE_OKEY_MAP_PL
102 #include <stdint.h>
103 #include <stdlib.h>
104 #include <stdio.h>
105 #include <string.h>
107 #ifndef NELEM
108 # define NELEM(A) (sizeof(A) / sizeof(A[0]))
109 #endif
111 #define ui32_t uint32_t
112 #define ui16_t uint16_t
113 #define ui8_t uint8_t
115 enum a_amv_var_flags{
116 a_AMV_VF_NONE = 0,
117 a_AMV_VF_BOOL = 1<<0, /* ok_b_* */
118 a_AMV_VF_VIRT = 1<<1, /* "Stateless" automatic variable */
119 a_AMV_VF_RDONLY = 1<<2, /* May not be set by user */
120 a_AMV_VF_NODEL = 1<<3, /* May not be deleted */
121 a_AMV_VF_NOTEMPTY = 1<<4, /* May not be assigned an empty value */
122 a_AMV_VF_NOCNTRLS = 1<<5, /* Value may not contain control characters */
123 a_AMV_VF_NUM = 1<<6, /* Value must be a 32-bit number */
124 a_AMV_VF_POSNUM = 1<<7, /* Value must be positive 32-bit number */
125 a_AMV_VF_VIP = 1<<8, /* Wants _var_check_vips() evaluation */
126 a_AMV_VF_IMPORT = 1<<9, /* Import ONLY from environ (before PS_STARTED) */
127 a_AMV_VF_ENV = 1<<10, /* Update environment on change */
128 a_AMV_VF_I3VAL = 1<<11, /* Has an initial value */
129 a_AMV_VF_DEFVAL = 1<<12, /* Has a default value */
130 a_AMV_VF_LINKED = 1<<13, /* `environ' linked */
131 a_AMV_VF__MASK = (1<<(13+1)) - 1
134 struct a_amv_var_map{
135 ui32_t avm_hash;
136 ui16_t avm_keyoff;
137 ui16_t avm_flags; /* enum a_amv_var_flags */
140 #ifdef HASH_MODE
141 /* NOTE: copied over verbatim from auxlily.c */
142 static ui32_t
143 torek_hash(char const *name){
144 /* Chris Torek's hash.
145 * NOTE: need to change *at least* mk-okey-map.pl when changing the
146 * algorithm!! */
147 ui32_t h = 0;
149 while(*name != '\0'){
150 h *= 33;
151 h += *name++;
153 return h;
156 #else
157 /* Include what has been written in HASH_MODE */
158 # define n_CTA(A,S)
159 # include "okeys.h"
161 static ui8_t seen_wraparound;
162 static size_t longest_distance;
164 static size_t
165 next_prime(size_t no){ /* blush (brute force) */
166 jredo:
167 ++no;
168 for(size_t i = 3; i < no; i += 2)
169 if(no % i == 0)
170 goto jredo;
171 return no;
174 static size_t *
175 reversy(size_t size){
176 struct a_amv_var_map const *vmp = a_amv_var_map,
177 *vmaxp = vmp + NELEM(a_amv_var_map);
178 size_t ldist = 0, *arr;
180 arr = malloc(sizeof *arr * size);
181 for(size_t i = 0; i < size; ++i)
182 arr[i] = NELEM(a_amv_var_map);
184 seen_wraparound = 0;
185 longest_distance = 0;
187 while(vmp < vmaxp){
188 ui32_t hash = vmp->avm_hash, i = hash % size, l;
190 for(l = 0; arr[i] != NELEM(a_amv_var_map); ++l)
191 if(++i == size){
192 seen_wraparound = 1;
193 i = 0;
195 if(l > longest_distance)
196 longest_distance = l;
197 arr[i] = (size_t)(vmp++ - a_amv_var_map);
199 return arr;
201 #endif /* !HASH_MODE */
204 main(int argc, char **argv){
205 #ifdef HASH_MODE
206 size_t h = torek_hash(argv[1]);
208 printf("%lu\n", (unsigned long)h);
210 #else
211 size_t *arr, size = NELEM(a_amv_var_map);
213 fprintf(stderr, "Starting reversy, okeys=%zu\n", size);
214 for(;;){
215 arr = reversy(size = next_prime(size));
216 fprintf(stderr, " - size=%zu longest_distance=%zu seen_wraparound=%d\n",
217 size, longest_distance, seen_wraparound);
218 if(longest_distance <= MAX_DISTANCE_PENALTY)
219 break;
220 free(arr);
223 printf(
224 "#define a_AMV_VAR_REV_ILL %zuu\n"
225 "#define a_AMV_VAR_REV_PRIME %zuu\n"
226 "#define a_AMV_VAR_REV_LONGEST %zuu\n"
227 "#define a_AMV_VAR_REV_WRAPAROUND %d\n"
228 "static %s const a_amv_var_revmap[a_AMV_VAR_REV_PRIME] = {\n%s",
229 NELEM(a_amv_var_map), size, longest_distance, seen_wraparound,
230 argv[1], (argc > 2 ? " " : ""));
231 for(size_t i = 0; i < size; ++i)
232 printf("%s%zuu", (i == 0 ? ""
233 : (i % 10 == 0 ? (argc > 2 ? ",\n " : ",\n")
234 : (argc > 2 ? ", " : ","))),
235 arr[i]);
236 printf("\n};\n");
237 #endif
238 return 0;
240 _EOT
241 # <<<<<<<<<<<<<<<<<<<
242 close F
245 sub hash_em{
246 system("c99 -DHASH_MODE -I. -o $CTOOL_EXE $CTOOL");
248 foreach my $e (@ENTS){
249 my $h = `$CTOOL_EXE $e->{name}`;
250 chomp $h;
251 $e->{hash} = $h
255 sub dump_map{
256 die "$OUT: open: $^E" unless open F, '>', $OUT;
257 print F "/*@ $OUT, generated by $0 on ", scalar gmtime(), ".\n",
258 " *@ See accmacvar.c for more */\n\n";
260 print F 'static char const a_amv_var_names[] = {', "\n";
261 my ($i, $alen) = (0, 0);
262 my (%virts, %defvals, %i3vals);
263 foreach my $e (@ENTS){
264 $e->{keyoff} = $alen;
265 my $k = $e->{name};
266 my $l = length $k;
267 my $a = join '\',\'', split(//, $k);
268 my (@fa);
269 if($e->{bool}) {push @fa, 'a_AMV_VF_BOOL'}
270 if($e->{virt}){
271 # Virtuals are implicitly rdonly and nodel
272 $e->{rdonly} = $e->{nodel} = 1;
273 $virts{$k} = $e;
274 push @fa, 'a_AMV_VF_VIRT'
276 if($e->{i3val}){
277 $i3vals{$k} = $e;
278 push @fa, 'a_AMV_VF_I3VAL'
280 if($e->{defval}){
281 $e->{notempty} = 1;
282 $defvals{$k} = $e;
283 push @fa, 'a_AMV_VF_DEFVAL'
285 if($e->{import}){
286 $e->{env} = 1;
287 push @fa, 'a_AMV_VF_IMPORT'
289 if($e->{rdonly}) {push @fa, 'a_AMV_VF_RDONLY'}
290 if($e->{nodel}) {push @fa, 'a_AMV_VF_NODEL'}
291 if($e->{notempty}) {push @fa, 'a_AMV_VF_NOTEMPTY'}
292 if($e->{nocntrls}) {push @fa, 'a_AMV_VF_NOCNTRLS'}
293 if($e->{num}) {push @fa, 'a_AMV_VF_NUM'}
294 if($e->{posnum}) {push @fa, 'a_AMV_VF_POSNUM'}
295 if($e->{vip}) {push @fa, 'a_AMV_VF_VIP'}
296 if($e->{env}) {push @fa, 'a_AMV_VF_ENV'}
297 $e->{flags} = \@fa;
298 my $f = join('|', @fa);
299 $f = ', ' . $f if length $f;
300 print F "${S}/* $i. [$alen]+$l $k$f */\n" if $VERB;
301 print F "${S}'$a','\\0',\n";
302 ++$i;
303 $alen += $l + 1
305 print F '};', "\n\n";
307 print F 'n_CTA(a_AMV_VF_NONE == 0, "Value not 0 as expected");', "\n";
308 print F 'static struct a_amv_var_map const a_amv_var_map[] = {', "\n";
309 foreach my $e (@ENTS){
310 my $f = $VERB ? 'a_AMV_VF_NONE' : '0';
311 my $fa = join '|', @{$e->{flags}};
312 $f .= '|' . $fa if length $fa;
313 my $n = $1 if $e->{enum} =~ /ok_._(.*)/;
314 print F "${S}{$e->{hash}u, $e->{keyoff}u, $f},";
315 if($VERB) {print F "${S}/* $n */\n"}
316 else {print F "\n"}
318 print F '};', "\n\n";
320 # We have at least version stuff in here
321 # The problem is that struct var uses a variable sized character buffer
322 # which cannot be initialized in a conforming way :(
323 print F <<_EOT;
324 #ifndef __CREATE_OKEY_MAP_PL
325 # ifdef HAVE_PUTENV
326 # define a_X(X) X
327 # else
328 # define a_X(X)
329 # endif
331 /* Unfortunately init of varsized buffer won't work: define "subclass"es */
332 _EOT
333 my @skeys = sort keys %virts;
335 foreach(@skeys){
336 my $e = $virts{$_};
337 $e->{vname} = $1 if $e->{enum} =~ /ok_._(.*)/;
338 $e->{vstruct} = "var_virt_$e->{vname}";
339 print F "static char const a_amv_$e->{vstruct}_val[] = {$e->{virt}};\n";
340 print F "static struct{\n";
341 print F "${S}struct a_amv_var *av_link;\n";
342 print F "${S}char const *av_value;\n";
343 print F "${S}a_X(char *av_env;)\n";
344 print F "${S}ui16_t av_flags;\n";
345 print F "${S}char const av_name[", length($e->{name}), " +1];\n";
346 my $f = $VERB ? 'a_AMV_VF_NONE' : '0';
347 my $fa = join '|', @{$e->{flags}};
348 $f .= '|' . $fa if length $fa;
349 print F "} const a_amv_$e->{vstruct} = ",
350 "{NULL, a_amv_$e->{vstruct}_val, a_X(0 COMMA) $f, ",
351 "\"$e->{name}\"};\n\n"
353 print F "# undef a_X\n";
355 print F "\n";
356 print F '#define a_AMV_VAR_VIRTS_CNT ', scalar @skeys, "\n";
357 print F 'static struct a_amv_var_virt const a_amv_var_virts[] = {', "\n";
358 foreach(@skeys){
359 my $e = $virts{$_};
360 my $n = $1 if $e->{enum} =~ /ok_._(.*)/;
361 print F "${S}{$e->{enum}, {0,}, (void const*)&a_amv_$e->{vstruct}},\n";
363 print F "};\n";
366 @skeys = sort keys %i3vals;
368 print F "\n";
369 print F '#define a_AMV_VAR_I3VALS_CNT ', scalar @skeys, "\n";
370 print F 'static struct a_amv_var_defval const a_amv_var_i3vals[] = {', "\n";
371 foreach(@skeys){
372 my $e = $i3vals{$_};
373 print F "${S}{", $e->{enum}, ', {0,}, ',
374 (!$e->{bool} ? $e->{i3val} : "NULL"), "},\n"
376 print F "};\n";
379 @skeys = sort keys %defvals;
381 print F "\n";
382 print F '#define a_AMV_VAR_DEFVALS_CNT ', scalar @skeys, "\n";
383 print F 'static struct a_amv_var_defval const a_amv_var_defvals[] = {', "\n";
384 foreach(@skeys){
385 my $e = $defvals{$_};
386 print F "${S}{", $e->{enum}, ', {0,}, ',
387 (!$e->{bool} ? $e->{defval} : "NULL"), "},\n"
389 print F "};\n";
391 print F "#endif /* __CREATE_OKEY_MAP_PL */\n\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