]> sjero.net Git - wget/blob - src/ansi2knr.c
[svn] Initial revision
[wget] / src / ansi2knr.c
1 /* ansi2knr.c */
2 /* Convert ANSI C function definitions to K&R ("traditional C") syntax */
3
4 /*
5 ansi2knr is distributed in the hope that it will be useful, but WITHOUT ANY
6 WARRANTY.  No author or distributor accepts responsibility to anyone for the
7 consequences of using it or for whether it serves any particular purpose or
8 works at all, unless he says so in writing.  Refer to the GNU General Public
9 License (the "GPL") for full details.
10
11 Everyone is granted permission to copy, modify and redistribute ansi2knr,
12 but only under the conditions described in the GPL.  A copy of this license
13 is supposed to have been given to you along with ansi2knr so you can know
14 your rights and responsibilities.  It should be in a file named COPYLEFT.
15 Among other things, the copyright notice and this notice must be preserved
16 on all copies.
17
18 We explicitly state here what we believe is already implied by the GPL: if
19 the ansi2knr program is distributed as a separate set of sources and a
20 separate executable file which are aggregated on a storage medium together
21 with another program, this in itself does not bring the other program under
22 the GPL, nor does the mere fact that such a program or the procedures for
23 constructing it invoke the ansi2knr executable bring any other part of the
24 program under the GPL.
25 */
26
27 /*
28  * Usage:
29         ansi2knr input_file [output_file]
30  * If no output_file is supplied, output goes to stdout.
31  * There are no error messages.
32  *
33  * ansi2knr recognizes function definitions by seeing a non-keyword
34  * identifier at the left margin, followed by a left parenthesis,
35  * with a right parenthesis as the last character on the line,
36  * and with a left brace as the first token on the following line
37  * (ignoring possible intervening comments).
38  * It will recognize a multi-line header provided that no intervening
39  * line ends with a left or right brace or a semicolon.
40  * These algorithms ignore whitespace and comments, except that
41  * the function name must be the first thing on the line.
42  * The following constructs will confuse it:
43  *      - Any other construct that starts at the left margin and
44  *          follows the above syntax (such as a macro or function call).
45  *      - Some macros that tinker with the syntax of the function header.
46  */
47
48 /*
49  * The original and principal author of ansi2knr is L. Peter Deutsch
50  * <ghost@aladdin.com>.  Other authors are noted in the change history
51  * that follows (in reverse chronological order):
52         lpd 96-01-21 added code to cope with not HAVE_CONFIG_H and with
53                 compilers that don't understand void, as suggested by
54                 Tom Lane
55         lpd 96-01-15 changed to require that the first non-comment token
56                 on the line following a function header be a left brace,
57                 to reduce sensitivity to macros, as suggested by Tom Lane
58                 <tgl@sss.pgh.pa.us>
59         lpd 95-06-22 removed #ifndefs whose sole purpose was to define
60                 undefined preprocessor symbols as 0; changed all #ifdefs
61                 for configuration symbols to #ifs
62         lpd 95-04-05 changed copyright notice to make it clear that
63                 including ansi2knr in a program does not bring the entire
64                 program under the GPL
65         lpd 94-12-18 added conditionals for systems where ctype macros
66                 don't handle 8-bit characters properly, suggested by
67                 Francois Pinard <pinard@iro.umontreal.ca>;
68                 removed --varargs switch (this is now the default)
69         lpd 94-10-10 removed CONFIG_BROKETS conditional
70         lpd 94-07-16 added some conditionals to help GNU `configure',
71                 suggested by Francois Pinard <pinard@iro.umontreal.ca>;
72                 properly erase prototype args in function parameters,
73                 contributed by Jim Avera <jima@netcom.com>;
74                 correct error in writeblanks (it shouldn't erase EOLs)
75         lpd 89-xx-xx original version
76  */
77
78 /* Most of the conditionals here are to make ansi2knr work with */
79 /* or without the GNU configure machinery. */
80
81 #if HAVE_CONFIG_H
82 # include <config.h>
83 #endif
84
85 #include <stdio.h>
86 #include <ctype.h>
87
88 #if HAVE_CONFIG_H
89
90 /*
91    For properly autoconfiguring ansi2knr, use AC_CONFIG_HEADER(config.h).
92    This will define HAVE_CONFIG_H and so, activate the following lines.
93  */
94
95 # if STDC_HEADERS || HAVE_STRING_H
96 #  include <string.h>
97 # else
98 #  include <strings.h>
99 # endif
100
101 #else /* not HAVE_CONFIG_H */
102
103 /* Otherwise do it the hard way */
104
105 # ifdef BSD
106 #  include <strings.h>
107 # else
108 #  ifdef VMS
109     extern int strlen(), strncmp();
110 #  else
111 #   include <string.h>
112 #  endif
113 # endif
114
115 #endif /* not HAVE_CONFIG_H */
116
117 #if STDC_HEADERS
118 # include <stdlib.h>
119 #else
120 /*
121    malloc and free should be declared in stdlib.h,
122    but if you've got a K&R compiler, they probably aren't.
123  */
124 # ifdef MSDOS
125 #  include <malloc.h>
126 # else
127 #  ifdef VMS
128      extern char *malloc();
129      extern void free();
130 #  else
131      extern char *malloc();
132      extern int free();
133 #  endif
134 # endif
135
136 #endif
137
138 /*
139  * The ctype macros don't always handle 8-bit characters correctly.
140  * Compensate for this here.
141  */
142 #ifdef isascii
143 #  undef HAVE_ISASCII           /* just in case */
144 #  define HAVE_ISASCII 1
145 #else
146 #endif
147 #if STDC_HEADERS || !HAVE_ISASCII
148 #  define is_ascii(c) 1
149 #else
150 #  define is_ascii(c) isascii(c)
151 #endif
152
153 #define is_space(c) (is_ascii(c) && isspace(c))
154 #define is_alpha(c) (is_ascii(c) && isalpha(c))
155 #define is_alnum(c) (is_ascii(c) && isalnum(c))
156
157 /* Scanning macros */
158 #define isidchar(ch) (is_alnum(ch) || (ch) == '_')
159 #define isidfirstchar(ch) (is_alpha(ch) || (ch) == '_')
160
161 /* Forward references */
162 char *skipspace();
163 int writeblanks();
164 int test1();
165 int convert1();
166
167 /* The main program */
168 int
169 main(argc, argv)
170     int argc;
171     char *argv[];
172 {       FILE *in, *out;
173 #define bufsize 5000                    /* arbitrary size */
174         char *buf;
175         char *line;
176         char *more;
177         /*
178          * In previous versions, ansi2knr recognized a --varargs switch.
179          * If this switch was supplied, ansi2knr would attempt to convert
180          * a ... argument to va_alist and va_dcl; if this switch was not
181          * supplied, ansi2knr would simply drop any such arguments.
182          * Now, ansi2knr always does this conversion, and we only
183          * check for this switch for backward compatibility.
184          */
185         int convert_varargs = 1;
186
187         if ( argc > 1 && argv[1][0] == '-' )
188           {     if ( !strcmp(argv[1], "--varargs") )
189                   {     convert_varargs = 1;
190                         argc--;
191                         argv++;
192                   }
193                 else
194                   {     fprintf(stderr, "Unrecognized switch: %s\n", argv[1]);
195                         exit(1);
196                   }
197           }
198         if (argc < 2 || argc > 3)
199            {
200                 printf("Usage: ansi2knr input_file [output_file]\n");
201                         exit(1);
202                    }
203         in = fopen(argv[1], "r");
204         if ( in == NULL )
205           {
206             fprintf(stderr, "Cannot open input file %s\n", argv[1]);
207                 exit(1);
208            }
209         if (argc == 3)
210           {
211             out = fopen(argv[2], "w");
212             if ( out == NULL )
213               {
214                 fprintf(stderr, "Cannot open output file %s\n", argv[2]);
215                 exit(1);
216               }
217           }
218         else
219           {
220             out = stdout;
221           }
222         fprintf(out, "#line 1 \"%s\"\n", argv[1]);
223         buf = malloc(bufsize);
224         line = buf;
225         while ( fgets(line, (unsigned)(buf + bufsize - line), in) != NULL )
226            {
227 test:           line += strlen(line);
228                 switch ( test1(buf) )
229                    {
230                 case 2:                 /* a function header */
231                         convert1(buf, out, 1, convert_varargs);
232                         break;
233                 case 1:                 /* a function */
234                         /* Check for a { at the start of the next line. */
235                         more = ++line;
236 f:                      if ( line >= buf + (bufsize - 1) ) /* overflow check */
237                           goto wl;
238                         if ( fgets(line, (unsigned)(buf + bufsize - line), in) == NULL )
239                           goto wl;
240                         switch ( *skipspace(more, 1) )
241                           {
242                           case '{':
243                             /* Definitely a function header. */
244                             convert1(buf, out, 0, convert_varargs);
245                             fputs(more, out);
246                             break;
247                           case 0:
248                             /* The next line was blank or a comment: */
249                             /* keep scanning for a non-comment. */
250                             line += strlen(line);
251                             goto f;
252                           default:
253                             /* buf isn't a function header, but */
254                             /* more might be. */
255                             fputs(buf, out);
256                             strcpy(buf, more);
257                             line = buf;
258                             goto test;
259                           }
260                         break;
261                 case -1:                /* maybe the start of a function */
262                         if ( line != buf + (bufsize - 1) ) /* overflow check */
263                           continue;
264                         /* falls through */
265                 default:                /* not a function */
266 wl:                     fputs(buf, out);
267                         break;
268                    }
269                 line = buf;
270            }
271         if ( line != buf )
272           fputs(buf, out);
273         free(buf);
274         fclose(out);
275         fclose(in);
276         return 0;
277 }
278
279 /* Skip over space and comments, in either direction. */
280 char *
281 skipspace(p, dir)
282     register char *p;
283     register int dir;                   /* 1 for forward, -1 for backward */
284 {       for ( ; ; )
285            {    while ( is_space(*p) )
286                   p += dir;
287                 if ( !(*p == '/' && p[dir] == '*') )
288                   break;
289                 p += dir;  p += dir;
290                 while ( !(*p == '*' && p[dir] == '/') )
291                    {    if ( *p == 0 )
292                           return p;     /* multi-line comment?? */
293                         p += dir;
294                    }
295                 p += dir;  p += dir;
296            }
297         return p;
298 }
299
300 /*
301  * Write blanks over part of a string.
302  * Don't overwrite end-of-line characters.
303  */
304 int
305 writeblanks(start, end)
306     char *start;
307     char *end;
308 {       char *p;
309         for ( p = start; p < end; p++ )
310           if ( *p != '\r' && *p != '\n' )
311             *p = ' ';
312         return 0;
313 }
314
315 /*
316  * Test whether the string in buf is a function definition.
317  * The string may contain and/or end with a newline.
318  * Return as follows:
319  *      0 - definitely not a function definition;
320  *      1 - definitely a function definition;
321  *      2 - definitely a function prototype (NOT USED);
322  *      -1 - may be the beginning of a function definition,
323  *              append another line and look again.
324  * The reason we don't attempt to convert function prototypes is that
325  * Ghostscript's declaration-generating macros look too much like
326  * prototypes, and confuse the algorithms.
327  */
328 int
329 test1(buf)
330     char *buf;
331 {       register char *p = buf;
332         char *bend;
333         char *endfn;
334         int contin;
335
336         if ( !isidfirstchar(*p) )
337           return 0;             /* no name at left margin */
338         bend = skipspace(buf + strlen(buf) - 1, -1);
339         switch ( *bend )
340            {
341            case ';': contin = 0 /*2*/; break;
342            case ')': contin = 1; break;
343            case '{': return 0;          /* not a function */
344            case '}': return 0;          /* not a function */
345            default: contin = -1;
346            }
347         while ( isidchar(*p) )
348           p++;
349         endfn = p;
350         p = skipspace(p, 1);
351         if ( *p++ != '(' )
352           return 0;             /* not a function */
353         p = skipspace(p, 1);
354         if ( *p == ')' )
355           return 0;             /* no parameters */
356         /* Check that the apparent function name isn't a keyword. */
357         /* We only need to check for keywords that could be followed */
358         /* by a left parenthesis (which, unfortunately, is most of them). */
359            {    static char *words[] =
360                    {    "asm", "auto", "case", "char", "const", "double",
361                         "extern", "float", "for", "if", "int", "long",
362                         "register", "return", "short", "signed", "sizeof",
363                         "static", "switch", "typedef", "unsigned",
364                         "void", "volatile", "while", 0
365                    };
366                 char **key = words;
367                 char *kp;
368                 int len = endfn - buf;
369
370                 while ( (kp = *key) != 0 )
371                    {    if ( strlen(kp) == len && !strncmp(kp, buf, len) )
372                           return 0;     /* name is a keyword */
373                         key++;
374                    }
375            }
376         return contin;
377 }
378
379 /* Convert a recognized function definition or header to K&R syntax. */
380 int
381 convert1(buf, out, header, convert_varargs)
382     char *buf;
383     FILE *out;
384     int header;                 /* Boolean */
385     int convert_varargs;        /* Boolean */
386 {       char *endfn;
387         register char *p;
388         /*
389          * The breaks table contains pointers to the beginning and end
390          * of each argument.
391          */
392         char **breaks;
393         unsigned num_breaks = 2;        /* for testing */
394         char **btop;
395         char **bp;
396         char **ap;
397         char *vararg = 0;
398
399         /* Pre-ANSI implementations don't agree on whether strchr */
400         /* is called strchr or index, so we open-code it here. */
401         for ( endfn = buf; *(endfn++) != '('; )
402           ;
403 top:    p = endfn;
404         breaks = (char **)malloc(sizeof(char *) * num_breaks * 2);
405         if ( breaks == 0 )
406            {    /* Couldn't allocate break table, give up */
407                 fprintf(stderr, "Unable to allocate break table!\n");
408                 fputs(buf, out);
409                 return -1;
410            }
411         btop = breaks + num_breaks * 2 - 2;
412         bp = breaks;
413         /* Parse the argument list */
414         do
415            {    int level = 0;
416                 char *lp = NULL;
417                 char *rp;
418                 char *end = NULL;
419
420                 if ( bp >= btop )
421                    {    /* Filled up break table. */
422                         /* Allocate a bigger one and start over. */
423                         free((char *)breaks);
424                         num_breaks <<= 1;
425                         goto top;
426                    }
427                 *bp++ = p;
428                 /* Find the end of the argument */
429                 for ( ; end == NULL; p++ )
430                    {    switch(*p)
431                            {
432                            case ',':
433                                 if ( !level ) end = p;
434                                 break;
435                            case '(':
436                                 if ( !level ) lp = p;
437                                 level++;
438                                 break;
439                            case ')':
440                                 if ( --level < 0 ) end = p;
441                                 else rp = p;
442                                 break;
443                            case '/':
444                                 p = skipspace(p, 1) - 1;
445                                 break;
446                            default:
447                                 ;
448                            }
449                    }
450                 /* Erase any embedded prototype parameters. */
451                 if ( lp )
452                   writeblanks(lp + 1, rp);
453                 p--;                    /* back up over terminator */
454                 /* Find the name being declared. */
455                 /* This is complicated because of procedure and */
456                 /* array modifiers. */
457                 for ( ; ; )
458                    {    p = skipspace(p - 1, -1);
459                         switch ( *p )
460                            {
461                            case ']':    /* skip array dimension(s) */
462                            case ')':    /* skip procedure args OR name */
463                            {    int level = 1;
464                                 while ( level )
465                                  switch ( *--p )
466                                    {
467                                    case ']': case ')': level++; break;
468                                    case '[': case '(': level--; break;
469                                    case '/': p = skipspace(p, -1) + 1; break;
470                                    default: ;
471                                    }
472                            }
473                                 if ( *p == '(' && *skipspace(p + 1, 1) == '*' )
474                                    {    /* We found the name being declared */
475                                         while ( !isidfirstchar(*p) )
476                                           p = skipspace(p, 1) + 1;
477                                         goto found;
478                                    }
479                                 break;
480                            default:
481                                 goto found;
482                            }
483                    }
484 found:          if ( *p == '.' && p[-1] == '.' && p[-2] == '.' )
485                   {     if ( convert_varargs )
486                           {     *bp++ = "va_alist";
487                                 vararg = p-2;
488                           }
489                         else
490                           {     p++;
491                                 if ( bp == breaks + 1 ) /* sole argument */
492                                   writeblanks(breaks[0], p);
493                                 else
494                                   writeblanks(bp[-1] - 1, p);
495                                 bp--;
496                           }
497                    }
498                 else
499                    {    while ( isidchar(*p) ) p--;
500                         *bp++ = p+1;
501                    }
502                 p = end;
503            }
504         while ( *p++ == ',' );
505         *bp = p;
506         /* Make a special check for 'void' arglist */
507         if ( bp == breaks+2 )
508            {    p = skipspace(breaks[0], 1);
509                 if ( !strncmp(p, "void", 4) )
510                    {    p = skipspace(p+4, 1);
511                         if ( p == breaks[2] - 1 )
512                            {    bp = breaks;    /* yup, pretend arglist is empty */
513                                 writeblanks(breaks[0], p + 1);
514                            }
515                    }
516            }
517         /* Put out the function name and left parenthesis. */
518         p = buf;
519         while ( p != endfn ) putc(*p, out), p++;
520         /* Put out the declaration. */
521         if ( header )
522           {     fputs(");", out);
523                 for ( p = breaks[0]; *p; p++ )
524                   if ( *p == '\r' || *p == '\n' )
525                     putc(*p, out);
526           }
527         else
528           {     for ( ap = breaks+1; ap < bp; ap += 2 )
529                   {     p = *ap;
530                         while ( isidchar(*p) )
531                           putc(*p, out), p++;
532                         if ( ap < bp - 1 )
533                           fputs(", ", out);
534                   }
535                 fputs(")  ", out);
536                 /* Put out the argument declarations */
537                 for ( ap = breaks+2; ap <= bp; ap += 2 )
538                   (*ap)[-1] = ';';
539                 if ( vararg != 0 )
540                   {     *vararg = 0;
541                         fputs(breaks[0], out);          /* any prior args */
542                         fputs("va_dcl", out);           /* the final arg */
543                         fputs(bp[0], out);
544                   }
545                 else
546                   fputs(breaks[0], out);
547           }
548         free((char *)breaks);
549         return 0;
550 }