Add n_SHEXP_PARSE_META_KEEP (for `vput')
[s-mailx.git] / mk-errors.sh
blob2e6e47d147c4812ab2da4c16c1665c1ec5adb5ac
1 #!/bin/sh -
2 #@ Create gen-errors.h. And see auxlily.c.
3 # Public Domain
5 # Acceptable "longest distance" from hash-modulo-index to key
6 MAXDISTANCE_PENALTY=5
8 # Generate a more verbose output. Not for shipout versions.
9 VERB=1
11 MAILX='LC_ALL=C s-nail -#:/'
12 OUT=gen-errors.h
16 LC_ALL=C
17 export LC_ALL MAXDISTANCE_PENALTY VERB MAILX OUT
19 : ${awk:=`command -v awk`}
21 # The set of errors we support
22 ERRORS="\
23 NONE='No error' \
24 2BIG='Argument list too long' \
25 ACCES='Permission denied' \
26 ADDRINUSE='Address already in use' \
27 ADDRNOTAVAIL='Cannot assign requested address' \
28 AFNOSUPPORT='Address family not supported by protocol family' \
29 AGAIN='Resource temporarily unavailable' \
30 ALREADY='Operation already in progress' \
31 BADF='Bad file descriptor' \
32 BADMSG='Bad message' \
33 BUSY='Device busy' \
34 CANCELED='Operation canceled' \
35 CHILD='No child processes' \
36 CONNABORTED='Software caused connection abort' \
37 CONNREFUSED='Connection refused' \
38 CONNRESET='Connection reset by peer' \
39 DEADLK='Resource deadlock avoided' \
40 DESTADDRREQ='Destination address required' \
41 DOM='Numerical argument out of domain' \
42 DQUOT='Disc quota exceeded' \
43 EXIST='File exists' \
44 FAULT='Bad address' \
45 FBIG='File too large' \
46 HOSTUNREACH='No route to host' \
47 IDRM='Identifier removed' \
48 ILSEQ='Illegal byte sequence' \
49 INPROGRESS='Operation now in progress' \
50 INTR='Interrupted system call' \
51 INVAL='Invalid argument' \
52 IO='Input/output error' \
53 ISCONN='Socket is already connected' \
54 ISDIR='Is a directory' \
55 MFILE='Too many open files' \
56 MLINK='Too many links' \
57 MSGSIZE='Message too long' \
58 MULTIHOP='Multihop attempted' \
59 NAMETOOLONG='File name too long' \
60 NETDOWN='Network is down' \
61 NETRESET='Network dropped connection on reset' \
62 NETUNREACH='Network is unreachable' \
63 NFILE='Too many open files in system' \
64 NOBUFS='No buffer space available' \
65 NODATA='No data available' \
66 NODEV='Operation not supported by device' \
67 NOENT='No such file or directory' \
68 NOEXEC='Exec format error' \
69 NOLCK='No locks available' \
70 NOLINK='Link has been severed' \
71 NOMEM='Cannot allocate memory' \
72 NOMSG='No message of desired type' \
73 NOPROTOOPT='Protocol not available' \
74 NOSPC='No space left on device' \
75 NOSR='Out of streams resource' \
76 NOSTR='Device not a stream' \
77 NOSYS='Function not implemented' \
78 NOTCONN='Socket is not connected' \
79 NOTDIR='Not a directory' \
80 NOTEMPTY='Directory not empty' \
81 NOTOBACCO='Snorkeling on empty pipe' \
82 NOTSOCK='Socket operation on non-socket' \
83 NOTSUP='Operation not supported' \
84 NOTTY='Inappropriate ioctl for device' \
85 NXIO='Device not configured' \
86 OPNOTSUPP='Operation not supported' \
87 OVERFLOW='Value too large to be stored in data type' \
88 PERM='Operation not permitted' \
89 PIPE='Broken pipe' \
90 PROTO='Protocol error' \
91 PROTONOSUPPORT='Protocol not supported' \
92 PROTOTYPE='Protocol wrong type for socket' \
93 RANGE='Result too large' \
94 ROFS='Read-only filesystem' \
95 SPIPE='Invalid seek' \
96 SRCH='No such process' \
97 STALE='Stale NFS file handle' \
98 TIME='Timer expired' \
99 TIMEDOUT='Operation timed out' \
100 TXTBSY='Text file busy' \
101 WOULDBLOCK='Operation would block' \
102 XDEV='Cross-device link' \
104 export ERRORS
106 error_parse() {
107 j=\'
108 ${awk} -v dodoc="${1}" -v incnone="${2}" -v input="${ERRORS}" '
109 BEGIN{
110 for(i = 0;;){
111 voff = match(input, /[[:alnum:]_]+(='${j}'[^'${j}']+)?/)
112 if(voff == 0)
113 break
114 v = substr(input, voff, RLENGTH)
115 input = substr(input, voff + RLENGTH)
116 doff = index(v, "=")
117 if(doff > 0){
118 d = substr(v, doff + 2, length(v) - doff - 1)
119 v = substr(v, 1, doff - 1)
121 if(!incnone && v == "NONE")
122 continue
123 print dodoc ? d : v
129 config() {
130 [ -n "${TARGET}" ] || {
131 echo >&2 'Invalid usage'
132 exit 1
134 # Note this may be ISO C89, so we cannot
135 cat <<__EOT__
136 #include <errno.h>
137 #include <limits.h>
138 #include <stdio.h>
139 #include <stdlib.h>
140 #include <string.h>
141 #if defined __STDC_VERSION__ && __STDC_VERSION__ + 0 >= 199901L
142 # include <stdint.h>
143 #else
144 # include <inttypes.h>
145 #endif
146 #ifdef UINT32_MAX
147 typedef uint32_t ui32_t;
148 typedef int32_t si32_t;
149 #elif ULONG_MAX == 0xFFFFFFFFu
150 typedef unsigned long int ui32_t;
151 typedef signed long int si32_t;
152 #else
153 typedef unsigned int ui32_t;
154 typedef signed int si32_t;
155 #endif
156 struct a_in {struct a_in *next; char const *name; si32_t no; ui32_t uno;};
157 static int a_sortin(void const *a, void const *b){
158 return (*(struct a_in const* const *)a)->uno -
159 (*(struct a_in const* const *)b)->uno;
161 int main(void){
162 char line[1024], *lp;
163 struct a_in *head, *tail, *np, **nap;
164 ui32_t maxsub, umax;
165 si32_t xavail = 0, total = 1, imin = 0, imax = 0, voidoff = 0,
166 i, telloff, j;
167 FILE *ifp = fopen("${OUT}", "r"), *ofp = fopen("${TARGET}", "a");
168 if(ifp == NULL || ofp == NULL){
169 fprintf(stderr, "ERROR: cannot open in- or output\n");
170 return 1;
173 /* Create a list of all errors */
174 head = tail = malloc(sizeof *head);
175 head->next = NULL; head->name = "n_ERR_NONE"; head->no = 0;
176 __EOT__
177 for n in `error_parse 0 0`; do
178 cat <<__EOT__
179 ++total;
180 #ifdef E${n}
181 i = E${n};
182 #else
183 i = --xavail;
184 #endif
185 if(imin > i) imin = i; if(imax < i) imax = i;
186 np = malloc(sizeof *np);
187 np->next = NULL; np->name = "n_ERR_${n}"; np->no = i;
188 tail->next = np; tail = np;
189 __EOT__
190 done
191 cat <<__EOT__
192 /* The unsigned type used for storage */
194 fputs("#define n__ERR_NUMBER_TYPE ", ofp);
195 if((ui32_t)imax <= 0xFFu && (ui32_t)-imin <= 0xFFu){
196 fputs("ui8_t\n", ofp);
197 maxsub = 0xFFu;
198 }else if(((ui32_t)imax <= 0xFFFFu && (ui32_t)-imin <= 0xFFFFu)){
199 fputs("ui16_t\n", ofp);
200 maxsub = 0xFFFFu;
201 }else{
202 fputs("ui32_t\n", ofp);
203 maxsub = 0xFFFFFFFFu;
206 /* Now that we know the storage type, create the unsigned numbers */
207 for(umax = 0, np = head; np != NULL; np = np->next){
208 if(np->no < 0)
209 np->uno = maxsub + np->no + 1;
210 else
211 np->uno = np->no;
212 if(np->uno > umax)
213 umax = np->uno;
215 if(umax <= (ui32_t)imax){
216 fprintf(stderr, "ERROR: errno ranges overlap\n");
217 return 1;
220 /* Sort this list */
222 nap = malloc(sizeof(*nap) * (unsigned)total);
223 for(i = 0, np = head; np != NULL; ++i, np = np->next)
224 nap[i] = np;
225 if(i != total){
226 fprintf(stderr, "ERROR: implementation error i != total\n");
227 return 1;
229 qsort(nap, (ui32_t)i, sizeof *nap, &a_sortin);
231 /* The enumeration of numbers */
233 fputs("enum n_err_number{\\n", ofp);
234 for(i = 0; i < total; ++i)
235 fprintf(ofp, " %s = %lu,\\n",
236 nap[i]->name, (unsigned long)nap[i]->uno);
237 fprintf(ofp, " n__ERR_NUMBER = %ld\\n", (long)total);
238 fputs("};\\n", ofp);
240 /* The binary search mapping table from OS error value to our internal
241 * a_aux_err_map[] error description table */
243 /* A real hack to have a fast NUMBER -> MAP mapping without compiling
244 * and running a second C program during config */
245 while((lp = fgets(line, sizeof line, ifp)) != NULL &&
246 strstr(lp, "a_aux_err_map[]") == NULL)
248 if(feof(ifp) || ferror(ifp)){
249 fprintf(stderr, "ERROR: I/O error when reading \"ifp\"\n");
250 return 1;
252 telloff = (int)ftell(ifp);
254 fprintf(ofp, "#define n__ERR_NUMBER_TO_MAPOFF \\\\\\n");
255 for(xavail = 0, i = 0; i < total; ++i){
256 if(i == 0 || nap[i]->no != nap[i - 1]->no){
257 if(fseek(ifp, telloff, SEEK_SET) == -1){
258 fprintf(stderr, "ERROR: I/O error when searching \"ifp\", I.\n");
259 return 1;
261 j = 0;
262 while((lp = fgets(line, sizeof line, ifp)) != NULL &&
263 strstr(lp, nap[i]->name) == NULL)
264 ++j;
265 if(feof(ifp) || ferror(ifp)){
266 fprintf(stderr, "ERROR: I/O error when reading \"ifp\", II.\n");
267 return 1;
269 fprintf(ofp, "\ta_X(%lu, %lu) %s%s%s\\\\\\n",
270 (unsigned long)nap[i]->uno, (long)(ui32_t)j,
271 ((${VERB}) ? "/* " : ""), ((${VERB}) ? nap[i]->name : ""),
272 ((${VERB}) ? " */ " : ""));
273 if(!strcmp("n_ERR_NOTOBACCO", nap[i]->name))
274 voidoff = j;
275 ++xavail;
278 fprintf(ofp, "\\t/* %ld unique members */\\n", (long)xavail);
279 fprintf(ofp, "#define n__ERR_NUMBER_VOIDOFF %ld\\n", (long)voidoff);
280 fclose(ofp);
281 return 0;
283 __EOT__
284 exit 0
287 if [ ${#} -ne 0 ]; then
288 if [ "${1}" = noverbose ]; then
289 shift
290 VERB=0
291 export VERB
295 if [ ${#} -eq 1 ]; then
296 [ "${1}" = config ] && config
297 elif [ ${#} -eq 0 ]; then
298 # Now start perl(1) without PERL5OPT set to avoid multibyte sequence errors
299 PERL5OPT= PERL5LIB= exec perl -x "${0}"
301 echo >&2 'Invalid usage'
302 exit 1
304 # Thanks to perl(5) and it's -x / #! perl / __END__ mechanism!
305 # Why can env(1) not be used for such easy things in #!?
306 #!perl
308 use diagnostics -verbose;
309 use strict;
310 use warnings;
312 use FileHandle;
313 use IPC::Open2;
315 use sigtrap qw(handler cleanup normal-signals);
317 my ($S, @ENTS, $CTOOL, $CTOOL_EXE) = ($ENV{VERB} ? ' ' : '');
319 sub main_fun{
320 create_ents();
322 create_c_tool();
324 hash_em();
326 dump_map();
328 reverser();
330 cleanup(undef);
331 exit 0
334 sub cleanup{
335 die "$CTOOL_EXE: couldn't unlink: $^E"
336 if $CTOOL_EXE && -f $CTOOL_EXE && 1 != unlink $CTOOL_EXE;
337 die "$CTOOL: couldn't unlink: $^E"
338 if $CTOOL && -f $CTOOL && 1 != unlink $CTOOL;
339 die "Terminating due to signal $_[0]" if $_[0]
342 sub create_ents{
343 my $input = $ENV{ERRORS};
344 while($input =~ /([[:alnum:]_]+)='([^']+)'(.*)/){
345 $input = $3;
346 my %vals;
347 $vals{name} = $1;
348 $vals{doc} = $2;
349 push @ENTS, \%vals
353 sub create_c_tool{
354 $CTOOL = './tmp-errors-tool-' . $$ . '.c';
355 $CTOOL_EXE = $CTOOL . '.exe';
357 die "$CTOOL: open: $^E" unless open F, '>', $CTOOL;
358 print F '#define MAX_DISTANCE_PENALTY ', $ENV{MAXDISTANCE_PENALTY}, "\n";
359 # >>>>>>>>>>>>>>>>>>>
360 print F <<'_EOT';
361 #define __CREATE_ERRORS_SH
362 #include <stdint.h>
363 #include <stdlib.h>
364 #include <stdio.h>
365 #include <string.h>
367 #define n_NELEM(A) (sizeof(A) / sizeof(A[0]))
369 #define ui32_t uint32_t
370 #define si32_t int32_t
371 #define ui16_t uint16_t
372 #define ui8_t uint8_t
374 struct a_aux_err_map{
375 ui32_t aem_hash; /* Hash of name */
376 ui32_t aem_nameoff; /* Into a_aux_err_names[] */
377 ui32_t aem_docoff; /* Into a_aux_err docs[] */
378 si32_t aem_errno; /* The OS errno value for this one */
381 #include "gen-errors.h"
383 static ui8_t seen_wraparound;
384 static size_t longest_distance;
386 static size_t
387 next_prime(size_t no){ /* blush (brute force) */
388 jredo:
389 ++no;
390 for(size_t i = 3; i < no; i += 2)
391 if(no % i == 0)
392 goto jredo;
393 return no;
396 static size_t *
397 reversy(size_t size){
398 struct a_aux_err_map const *aemp = a_aux_err_map,
399 *aemaxp = aemp + n_NELEM(a_aux_err_map);
400 size_t ldist = 0, *arr;
402 arr = malloc(sizeof *arr * size);
403 for(size_t i = 0; i < size; ++i)
404 arr[i] = n_NELEM(a_aux_err_map);
406 seen_wraparound = 0;
407 longest_distance = 0;
409 while(aemp < aemaxp){
410 ui32_t hash = aemp->aem_hash, i = hash % size, l;
412 for(l = 0; arr[i] != n_NELEM(a_aux_err_map); ++l)
413 if(++i == size){
414 seen_wraparound = 1;
415 i = 0;
417 if(l > longest_distance)
418 longest_distance = l;
419 arr[i] = (size_t)(aemp++ - a_aux_err_map);
421 return arr;
425 main(int argc, char **argv){
426 size_t *arr, size = n_NELEM(a_aux_err_map);
428 fprintf(stderr, "Starting reversy, okeys=%zu\n", size);
429 for(;;){
430 arr = reversy(size = next_prime(size));
431 fprintf(stderr, " - size=%zu longest_distance=%zu seen_wraparound=%d\n",
432 size, longest_distance, seen_wraparound);
433 if(longest_distance <= MAX_DISTANCE_PENALTY)
434 break;
435 free(arr);
438 printf(
439 "#define a_AUX_ERR_REV_ILL %zuu\n"
440 "#define a_AUX_ERR_REV_PRIME %zuu\n"
441 "#define a_AUX_ERR_REV_LONGEST %zuu\n"
442 "#define a_AUX_ERR_REV_WRAPAROUND %d\n"
443 "static %s const a_aux_err_revmap[a_AUX_ERR_REV_PRIME] = {\n%s",
444 n_NELEM(a_aux_err_map), size, longest_distance, seen_wraparound,
445 argv[1], (argc > 2 ? " " : ""));
446 for(size_t i = 0; i < size; ++i)
447 printf("%s%zuu", (i == 0 ? ""
448 : (i % 10 == 0 ? (argc > 2 ? ",\n " : ",\n")
449 : (argc > 2 ? ", " : ","))),
450 arr[i]);
451 printf("\n};\n");
452 return 0;
454 _EOT
455 # <<<<<<<<<<<<<<<<<<<
456 close F
459 sub hash_em{
460 die "hash_em: open: $^E"
461 unless my $pid = open2 *RFD, *WFD, $ENV{MAILX};
462 foreach my $e (@ENTS){
463 print WFD "vexpr hash $e->{name}\n";
464 my $h = <RFD>;
465 chomp $h;
466 $e->{hash} = $h
468 print WFD "x\n";
469 waitpid $pid, 0;
472 sub dump_map{
473 die "$ENV{OUT}: open: $^E" unless open F, '>', $ENV{OUT};
474 print F "/*@ $ENV{OUT}, generated by $0 on ", scalar gmtime(), ".\n",
475 " *@ See auxlily.c for more */\n\n";
477 print F 'static char const a_aux_err_names[] = {', "\n";
478 my ($i, $alen) = (0, 0);
479 foreach my $e (@ENTS){
480 $e->{nameoff} = $alen;
481 my $k = $e->{name};
482 my $l = length $k;
483 my $a = join '\',\'', split(//, $k);
484 my (@fa);
485 print F "${S}/* $i. [$alen]+$l $k */\n" if $ENV{VERB};
486 print F "${S}'$a','\\0',\n";
487 ++$i;
488 $alen += $l + 1
490 print F '};', "\n\n";
492 print F '#undef a_X', "\n", '#define a_X(X)', "\n";
493 print F 'static char const a_aux_err_docs[] = {', "\n";
494 ($i, $alen) = (0, 0);
495 foreach my $e (@ENTS){
496 $e->{docoff} = $alen;
497 my $k = $e->{doc};
498 my $l = length $k;
499 my $a = join '\',\'', split(//, $k);
500 my (@fa);
501 print F "${S}/* $i. [$alen]+$l $e->{name} */ ",
502 "a_X(N_(\"$e->{doc}\"))\n" if $ENV{VERB};
503 print F "${S}'$a','\\0',\n";
504 ++$i;
505 $alen += $l + 1
507 print F '};', "\n", '#undef a_X', "\n\n";
509 print F <<_EOT;
510 #undef a_X
511 #ifndef __CREATE_ERRORS_SH
512 # define a_X(X) X
513 #else
514 # define a_X(X) 0
515 #endif
516 static struct a_aux_err_map const a_aux_err_map[] = {
517 _EOT
518 foreach my $e (@ENTS){
519 print F "${S}{$e->{hash}u, $e->{nameoff}u, $e->{docoff}u, ",
520 "a_X(n_ERR_$e->{name})},\n"
522 print F '};', "\n", '#undef a_X', "\n\n";
524 die "$ENV{OUT}: close: $^E" unless close F
527 sub reverser{
528 my $argv2 = $ENV{VERB} ? ' verb' : '';
529 system("c99 -I. -o $CTOOL_EXE $CTOOL");
530 my $t = (@ENTS < 0xFF ? 'ui8_t' : (@ENTS < 0xFFFF ? 'ui16_t' : 'ui32_t'));
531 `$CTOOL_EXE $t$argv2 >> $ENV{OUT}`
534 {package main; main_fun()}
536 # s-it-mode