1 /******************************************************************************
2 Copyright (c) 1999 Daniel Stenberg
3 
4 Permission is hereby granted, free of charge, to any person obtaining a copy
5 of this software and associated documentation files (the "Software"), to deal
6 in the Software without restriction, including without limitation the rights
7 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 copies of the Software, and to permit persons to whom the Software is
9 furnished to do so, subject to the following conditions:
10 
11 The above copyright notice and this permission notice shall be included in
12 all copies or substantial portions of the Software.
13 
14 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 SOFTWARE.
21 ******************************************************************************/
22 #include <stdio.h>
23 #include <ctype.h>
24 
25 #include "cppdef.h"
26 #include "cpp.h"
27 
28 #ifdef _AMIGA
29 #include <proto/dos.h>
30 #endif
31 
32 FILE_LOCAL void fpp_dump_line(struct Global *, int *);
33 FILE_LOCAL ReturnCode fpp_doif(struct Global *, int);
34 INLINE FILE_LOCAL ReturnCode fpp_doinclude(struct Global *);
35 INLINE FILE_LOCAL int fpp_hasdirectory(char *, char *);
36 
37 
38 /*
39  * Generate (by hand-inspection) a set of unique values for each control
40  * operator.  Note that this is not guaranteed to work for non-Ascii
41  * machines.  CPP won't compile if there are hash conflicts.
42  */
43 
44 #define L_assert    ('a' + ('s' << 1))
45 #define L_define    ('d' + ('f' << 1))
46 #define L_elif      ('e' + ('i' << 1))
47 #define L_else      ('e' + ('s' << 1))
48 #define L_endif     ('e' + ('d' << 1))
49 #define L_error     ('e' + ('r' << 1))
50 #define L_if        ('i' + (EOS << 1))
51 #define L_ifdef     ('i' + ('d' << 1))
52 #define L_ifndef    ('i' + ('n' << 1))
53 #define L_include   ('i' + ('c' << 1))
54 #define L_line      ('l' + ('n' << 1))
55 #define L_nogood    (EOS + (EOS << 1))      /* To catch #i          */
56 #define L_pragma    ('p' + ('a' << 1))
57 #define L_undef     ('u' + ('d' << 1))
58 
fpp_control(struct Global * global,int * counter)59 ReturnCode fpp_control( struct Global *global,
60     int *counter )  /* Pending newline counter */
61 {
62     /*
63      * Process #control lines.  Simple commands are processed inline,
64      * while complex commands have their own subroutines.
65      *
66      * The counter is used to force out a newline before #line, and
67      * #pragma commands.  This prevents these commands from ending up at
68      * the end of the previous line if cpp is invoked with the -C option.
69      */
70 
71     int c;
72     char *tp;
73     int hash;
74     char *ep;
75     ReturnCode ret;
76 
77     c = fpp_skipws( global );
78 
79     if( c == '\n' || c == EOF_CHAR )
80         {
81         (*counter)++;
82 
83         return(FPP_OK);
84         }
85 
86     if( !isdigit(c) )
87         fpp_scanid( global, c );                  /* Get #word to tokenbuf        */
88     else
89         {
90         fpp_unget( global );                    /* Hack -- allow #123 as a      */
91 
92         strcpy( global->tokenbuf, "line" );   /* synonym for #line 123        */
93         }
94 
95     hash = (global->tokenbuf[1] == EOS) ? L_nogood : (global->tokenbuf[0] + (global->tokenbuf[2] << 1));
96 
97     switch( hash )
98         {
99         case L_assert:
100             tp = "assert";
101             break;
102         case L_define:
103             tp = "define";
104             break;
105         case L_elif:
106             tp = "elif";
107             break;
108         case L_else:
109             tp = "else";
110             break;
111         case L_endif:
112             tp = "endif";
113             break;
114         case L_error:
115             tp = "error";
116             break;
117         case L_if:
118             tp = "if";
119             break;
120         case L_ifdef:
121             tp = "ifdef";
122             break;
123         case L_ifndef:
124             tp = "ifndef";
125             break;
126         case L_include:
127             tp = "include";
128             break;
129         case L_line:
130             tp = "line";
131             break;
132         case L_pragma:
133             tp = "pragma";
134             break;
135         case L_undef:
136             tp = "undef";
137             break;
138         default:
139             hash = L_nogood;
140         case L_nogood:
141             tp = "";
142             break;
143         }
144 
145     if( !streq( tp, global->tokenbuf ) )
146         hash = L_nogood;
147 
148     /*
149      * hash is set to a unique value corresponding to the
150      * control keyword (or L_nogood if we think it's nonsense).
151      */
152     if( global->infile->fp == NULL )
153         fpp_cwarn( global, WARN_CONTROL_LINE_IN_MACRO, global->tokenbuf );
154 
155     if( !compiling )
156         {                       /* Not compiling now    */
157         switch( hash )
158             {
159             case L_if:              /* These can't turn     */
160             case L_ifdef:           /*  compilation on, but */
161             case L_ifndef:          /*   we must nest #if's */
162                 if( ++global->ifptr >= &global->ifstack[BLK_NEST] )
163                     {
164                     fpp_cfatal( global, FATAL_TOO_MANY_NESTINGS, global->tokenbuf );
165 
166                     return( FPP_TOO_MANY_NESTED_STATEMENTS );
167                     }
168 
169                 *global->ifptr = 0;       /* !WAS_COMPILING   */
170 
171             case L_line:            /* Many         */
172                 /*
173                  * Are pragma's always processed?
174                  */
175             case L_pragma:          /*  options     */
176             case L_include:         /*   are uninteresting  */
177             case L_define:          /*    if we     */
178             case L_undef:           /*     aren't           */
179             case L_assert:          /*  compiling.  */
180             case L_error:
181                 fpp_dump_line( global, counter );       /* Ignore rest of line  */
182                 return(FPP_OK);
183             }
184         }
185     /*
186      * Make sure that #line and #pragma are output on a fresh line.
187      */
188     if( *counter > 0 && (hash == L_line || hash == L_pragma) )
189         {
190         fpp_Putchar( global, '\n' );
191 
192         (*counter)--;
193         }
194 
195     switch( hash )
196         {
197         case L_line:
198             /*
199              * Parse the line to update the line number and "progname"
200              * field and line number for the next input line.
201              * Set wrongline to force it out later.
202              */
203             c = fpp_skipws( global );
204 
205             global->workp = global->work;       /* Save name in work    */
206 
207             while( c != '\n' && c != EOF_CHAR )
208                 {
209                 if( (ret = fpp_save( global, c )) )
210                     return(ret);
211 
212                 c = fpp_get( global );
213                 }
214 
215             fpp_unget( global );
216 
217             if( (ret = fpp_save( global, EOS )) )
218                 return(ret);
219 
220             /*
221              * Split #line argument into <line-number> and <name>
222              * We subtract 1 as we want the number of the next line.
223              */
224             global->line = atoi(global->work) - 1;     /* Reset line number    */
225 
226             for( tp = global->work; isdigit(*tp) || type[(unsigned)*tp] == SPA; tp++)
227                 ;             /* Skip over digits */
228 
229             if( *tp != EOS )
230                 {
231                 /* Got a filename, so:  */
232 
233                 if( *tp == '"' && (ep = strrchr(tp + 1, '"')) != NULL )
234                     {
235                     tp++;           /* Skip over left quote */
236 
237                     *ep = EOS;      /* And ignore right one */
238                     }
239 
240                 if( global->infile->progname != NULL )
241                     /* Give up the old name if it's allocated.   */
242                     free( global->infile->progname );
243 
244                 global->infile->progname = fpp_savestring( global, tp );
245                 }
246 
247             global->wrongline = FPP_TRUE;           /* Force output later   */
248             break;
249 
250         case L_include:
251             ret = fpp_doinclude( global );
252             if( ret )
253                 return(ret);
254             break;
255 
256         case L_define:
257             ret = fpp_dodefine( global );
258             if( ret )
259                 return(ret);
260             break;
261 
262         case L_undef:
263             fpp_doundef( global );
264             break;
265 
266         case L_else:
267             if( global->ifptr == &global->ifstack[0] )
268                 {
269                 fpp_cerror( global, ERROR_STRING_MUST_BE_IF, global->tokenbuf );
270 
271                 fpp_dump_line( global, counter );
272 
273                 return( FPP_OK );
274                 }
275             else if( (*global->ifptr & ELSE_SEEN) != 0 )
276                 {
277                 fpp_cerror( global, ERROR_STRING_MAY_NOT_FOLLOW_ELSE, global->tokenbuf );
278 
279                 fpp_dump_line( global, counter );
280 
281                 return( FPP_OK );
282                 }
283 
284             *global->ifptr |= ELSE_SEEN;
285 
286             if( (*global->ifptr & WAS_COMPILING) != 0 )
287                 {
288                 if( compiling || (*global->ifptr & FPP_TRUE_SEEN) != 0 )
289                     compiling = FPP_FALSE;
290                 else
291                     {
292                     compiling = FPP_TRUE;
293                     }
294                 }
295             break;
296 
297         case L_elif:
298             if( global->ifptr == &global->ifstack[0] )
299                 {
300                 fpp_cerror( global, ERROR_STRING_MUST_BE_IF, global->tokenbuf );
301 
302                 fpp_dump_line( global, counter );
303 
304                 return( FPP_OK );
305                 }
306             else if( (*global->ifptr & ELSE_SEEN) != 0 )
307                 {
308                 fpp_cerror( global, ERROR_STRING_MAY_NOT_FOLLOW_ELSE, global->tokenbuf );
309 
310                 fpp_dump_line( global, counter );
311 
312                 return( FPP_OK );
313                 }
314 
315             if( (*global->ifptr & (WAS_COMPILING | FPP_TRUE_SEEN)) != WAS_COMPILING )
316                 {
317                 compiling = FPP_FALSE;        /* Done compiling stuff */
318 
319                 fpp_dump_line( global, counter );   /* Skip this clause */
320 
321                 return( FPP_OK );
322                 }
323 
324             ret = fpp_doif( global, L_if );
325 
326             if( ret )
327                 return(ret);
328 
329             break;
330 
331         case L_error:
332             fpp_cerror(global, ERROR_ERROR);
333             break;
334 
335         case L_if:
336         case L_ifdef:
337         case L_ifndef:
338             if( ++global->ifptr < &global->ifstack[BLK_NEST] )
339                 {
340                 *global->ifptr = WAS_COMPILING;
341 
342                 ret = fpp_doif( global, hash );
343 
344                 if( ret )
345                     return(ret);
346 
347                 break;
348                 }
349 
350             fpp_cfatal( global, FATAL_TOO_MANY_NESTINGS, global->tokenbuf );
351 
352             return( FPP_TOO_MANY_NESTED_STATEMENTS );
353 
354         case L_endif:
355             if( global->ifptr == &global->ifstack[0] )
356                 {
357                 fpp_cerror( global, ERROR_STRING_MUST_BE_IF, global->tokenbuf );
358 
359                 fpp_dump_line( global, counter );
360 
361                 return(FPP_OK);
362                 }
363 
364             if( !compiling && (*global->ifptr & WAS_COMPILING) != 0 )
365                 global->wrongline = FPP_TRUE;
366 
367             compiling = ((*global->ifptr & WAS_COMPILING) != 0);
368 
369             --global->ifptr;
370 
371             break;
372 
373         case L_assert:
374             {
375             int result;
376 
377             ret = fpp_eval( global, &result );
378 
379             if(ret)
380                 return(ret);
381 
382             if( result == 0 )
383                 fpp_cerror( global, ERROR_PREPROC_FAILURE );
384             }
385             break;
386 
387         case L_pragma:
388             /*
389              * #pragma is provided to pass "options" to later
390              * passes of the compiler.  cpp doesn't have any yet.
391              */
392             fpp_Putstring( global, "#pragma " );
393 
394             while( (c = fpp_get( global ) ) != '\n' && c != EOF_CHAR )
395                 fpp_Putchar( global, c );
396 
397             fpp_unget( global );
398 
399             fpp_Putchar( global, '\n' );
400 
401             break;
402 
403         default:
404             /*
405              * Undefined #control keyword.
406              * Note: the correct behavior may be to warn and
407              * pass the line to a subsequent compiler pass.
408              * This would allow #asm or similar extensions.
409              */
410             if( global->warnillegalcpp )
411                 fpp_cwarn( global, WARN_ILLEGAL_COMMAND, global->tokenbuf );
412 
413             fpp_Putchar( global, '#' );
414             fpp_Putstring( global, global->tokenbuf );
415             fpp_Putchar( global, ' ' );
416 
417             while( (c = fpp_get( global ) ) != '\n' && c != EOF_CHAR )
418                 fpp_Putchar( global, c );
419 
420             fpp_unget( global );
421 
422             fpp_Putchar( global, '\n' );
423 
424             break;
425         }
426 
427     if( hash != L_include )
428         {
429         #if OLD_PREPROCESSOR
430         /*
431          * Ignore the rest of the #control line so you can write
432          *      #if foo
433          *      #endif  foo
434          */
435         fpp_dump_line( global, counter );         /* Take common exit */
436 
437         return( FPP_OK );
438         #else
439         if( fpp_skipws( global ) != '\n' )
440             {
441             fpp_cwarn( global, WARN_UNEXPECTED_TEXT_IGNORED );
442 
443             fpp_skipnl( global );
444             }
445         #endif
446         }
447 
448     (*counter)++;
449 
450     return( FPP_OK );
451 }
452 
453 FILE_LOCAL
fpp_dump_line(struct Global * global,int * counter)454 void fpp_dump_line(struct Global *global, int *counter)
455 {
456     fpp_skipnl( global );         /* Ignore rest of line  */
457 
458     (*counter)++;
459 }
460 
461 FILE_LOCAL
fpp_doif(struct Global * global,int hash)462 ReturnCode fpp_doif(struct Global *global, int hash)
463 {
464     /*
465      * Process an #if, #ifdef, or #ifndef. The latter two are straightforward,
466      * while #if needs a subroutine of its own to evaluate the expression.
467      *
468      * fpp_doif() is called only if compiling is FPP_TRUE.  If false, compilation
469      * is always supressed, so we don't need to evaluate anything.  This
470      * supresses unnecessary warnings.
471      */
472 
473     int c;
474     int found;
475     ReturnCode ret;
476 
477     if( (c = fpp_skipws( global ) ) == '\n' || c == EOF_CHAR )
478         {
479         fpp_unget( global );
480 
481         fpp_cerror( global, ERROR_MISSING_ARGUMENT );
482 
483         #if !OLD_PREPROCESSOR
484         fpp_skipnl( global );               /* Prevent an extra     */
485 
486         fpp_unget( global );                /* Error message        */
487         #endif
488 
489         return(FPP_OK);
490         }
491 
492     if( hash == L_if )
493         {
494         fpp_unget( global );
495 
496         ret = fpp_eval( global, &found );
497 
498         if( ret )
499             return( ret );
500 
501         found = (found != 0);     /* Evaluate expr, != 0 is  FPP_TRUE */
502 
503         hash = L_ifdef;       /* #if is now like #ifdef */
504         }
505     else
506         {
507         if( type[c] != LET )
508             {         /* Next non-blank isn't letter  */
509                           /* ... is an error          */
510             fpp_cerror( global, ERROR_MISSING_ARGUMENT );
511 
512             #if !OLD_PREPROCESSOR
513             fpp_skipnl( global );             /* Prevent an extra     */
514 
515             fpp_unget( global );              /* Error message        */
516             #endif
517 
518             return(FPP_OK);
519             }
520 
521         found = ( fpp_lookid( global, c ) != NULL ); /* Look for it in symbol table */
522         }
523 
524     if( found == (hash == L_ifdef) )
525         {
526         compiling = FPP_TRUE;
527 
528         *global->ifptr |= FPP_TRUE_SEEN;
529         }
530     else
531         compiling = FPP_FALSE;
532 
533     return(FPP_OK);
534 }
535 
536 INLINE FILE_LOCAL
fpp_doinclude(struct Global * global)537 ReturnCode fpp_doinclude( struct Global *global )
538 {
539     /*
540      *  Process the #include control line.
541      *  There are three variations:
542      *
543      *      #include "file" search somewhere relative to the
544      *                      current source file, if not found,
545      *                      treat as #include <file>.
546      *
547      *      #include <file> Search in an implementation-dependent
548      *                      list of places.
549      *
550      *      #include token  Expand the token, it must be one of
551      *                      "file" or <file>, process as such.
552      *
553      *  Note:   the November 12 draft forbids '>' in the #include <file> format.
554      *          This restriction is unnecessary and not implemented.
555      */
556 
557     int c;
558     int delim;
559     ReturnCode ret;
560 
561     delim = fpp_skipws( global );
562 
563     if( (ret = fpp_macroid( global, &delim )) )
564         return(ret);
565 
566     if( delim != '<' && delim != '"' )
567         {
568         fpp_cerror( global, ERROR_INCLUDE_SYNTAX );
569 
570         return( FPP_OK );
571         }
572 
573     if( delim == '<' )
574         delim = '>';
575 
576     global->workp = global->work;
577 
578     while( (c = fpp_get(global)) != '\n' && c != EOF_CHAR )
579         if( (ret = fpp_save( global, c )) )       /* Put it away.                */
580             return( ret );
581 
582     fpp_unget( global );                        /* Force nl after include      */
583 
584     /*
585      * The draft is unclear if the following should be done.
586      */
587     while( --global->workp >= global->work &&
588         (*global->workp == ' ' || *global->workp == '\t') )
589         ;               /* Trim blanks from filename    */
590 
591     if( *global->workp != delim )
592         {
593         fpp_cerror( global, ERROR_INCLUDE_SYNTAX );
594 
595         return(FPP_OK);
596         }
597 
598     *global->workp = EOS;         /* Terminate filename       */
599 
600     ret = fpp_openinclude( global, global->work, (delim == '"') );
601 
602     if( ret && global->warnnoinclude )
603         {
604         /*
605          * Warn if #include file isn't there.
606          */
607         fpp_cwarn( global, WARN_CANNOT_OPEN_INCLUDE, global->work );
608         }
609 
610     return( FPP_OK );
611 }
612 
613 #ifdef _AMIGA
614 ReturnCode MultiAssignLoad( struct Global *global, char *incptr, char *filename, char *tmpname );
615 #endif
616 
fpp_openinclude(struct Global * global,char * filename,int searchlocal)617 ReturnCode fpp_openinclude( struct Global *global,
618     char *filename,     /* Input file name         */
619     int searchlocal )   /* FPP_TRUE if #include "file" */
620 {
621     /*
622      * Actually open an include file.  This routine is only called from
623      * fpp_doinclude() above, but was written as a separate subroutine for
624      * programmer convenience.  It searches the list of directories
625      * and actually opens the file, linking it into the list of
626      * active files.  Returns ReturnCode. No error message is printed.
627      */
628 
629     char **incptr;
630     char tmpname[NWORK]; /* Filename work area    */
631     size_t len;
632 
633     if( filename[0] == '/' )
634         {
635         if( ! fpp_openfile( global, filename ) )
636             return(FPP_OK);
637         }
638 
639     if( searchlocal && global->allowincludelocal )
640         {
641         /*
642          * Look in local directory first.
643          * Try to open filename relative to the directory of the current
644          * source file (as opposed to the current directory). (ARF, SCK).
645          * Note that the fully qualified pathname is always built by
646          * discarding the last pathname component of the source file
647          * name then tacking on the #include argument.
648          */
649         if( fpp_hasdirectory( global->infile->filename, tmpname ) )
650             strcat( tmpname, filename );
651         else
652             strcpy( tmpname, filename );
653 
654         if( ! fpp_openfile( global, tmpname ) )
655             return(FPP_OK);
656         }
657 
658     /*
659      * Look in any directories specified by -I command line
660      * arguments, then in the builtin search list.
661      */
662     for( incptr = global->incdir; incptr < global->incend; incptr++ )
663         {
664         len = strlen(*incptr);
665 
666         if( len + strlen(filename) >= sizeof(tmpname) )
667             {
668             fpp_cfatal( global, FATAL_FILENAME_BUFFER_OVERFLOW );
669 
670             return( FPP_FILENAME_BUFFER_OVERFLOW );
671             }
672         else
673             {
674             if( (*incptr)[len-1] != '/' )
675                 sprintf( tmpname, "%s/%s", *incptr, filename );
676             else
677                 sprintf( tmpname, "%s%s", *incptr, filename );
678 
679             if( !fpp_openfile( global, tmpname ) )
680                 return(FPP_OK);
681             }
682         }
683 
684     return( FPP_NO_INCLUDE );
685 }
686 
687 INLINE FILE_LOCAL
fpp_hasdirectory(char * source,char * result)688 int fpp_hasdirectory( char *source,   /* Directory to examine         */
689     char *result )  /* Put directory stuff here     */
690 {
691     /*
692      * If a device or directory is found in the source filename string, the
693      * node/device/directory part of the string is copied to result and
694      * fpp_hasdirectory returns FPP_TRUE.  Else, nothing is copied and it returns FPP_FALSE.
695      */
696 
697     char *tp2;
698 
699     if( (tp2 = strrchr( source, '/' ) ) == NULL )
700         return(FPP_FALSE);
701 
702     strncpy( result, source, tp2 - source + 1 );
703 
704     result[tp2 - source + 1] = EOS;
705 
706     return( FPP_TRUE );
707 }
708 
709 #ifdef _AMIGA
710 //
711 //  amp July 9, 1997
712 //
713 //  Use the OS Luke...
714 //
715 //  We do the sneaky version and let the OS do all
716 //  the hard work so we don't have to mess around
717 //  a lot ;)
718 //
MultiAssignLoad(struct Global * global,char * incptr,char * filename,char * tmpname)719 ReturnCode MultiAssignLoad( struct Global *global, char *incptr, char *filename, char *tmpname )
720 
721 { /* MultiAssignLoad */
722 
723     struct MsgPort  *FSTask;
724     struct DevProc  *DevProc = NULL;
725     LONG            RtnCode = FPP_NO_INCLUDE;
726 
727     FSTask = GetFileSysTask();
728 
729     do
730         {
731         //
732         //  This should not bring up a requester.
733         //  check to see if cpp does in fact tweek
734         //  the process WindowPtr.
735         //
736         DevProc = GetDeviceProc( incptr, DevProc );
737 
738         if( DevProc )
739             {
740             SetFileSysTask( DevProc->dvp_Port );
741 
742             //
743             //  Normally we would pass the lock and filename
744             //  to the Load() routine, which would CD to the
745             //  directory and Open(filename), but in order to
746             //  satisfy the exisiting fpp_openfile() function, we
747             //  bite the bullet and build the complete pathspec
748             //  rather than add the standard Load() routine.
749             //
750             if( NameFromLock( DevProc->dvp_Lock, tmpname, NWORK ) )
751                 {
752                 AddPart( tmpname, filename, NWORK );
753 
754                 RtnCode = fpp_openfile( global, tmpname );
755 
756                 if( ! RtnCode )
757                     break;
758                 }
759             }
760 
761         } while ( RtnCode &&
762             DevProc &&
763             (DevProc->dvp_Flags & DVPF_ASSIGN) &&
764             IoErr() == ERROR_OBJECT_NOT_FOUND); /* repeat if multi-assign */
765 
766     SetFileSysTask( FSTask );
767 
768     if( DevProc )
769         FreeDeviceProc( DevProc );
770 
771     return RtnCode;
772 
773 } /* MultiAssignLoad */
774 #endif  //_AMIGA
775