]> git.sur5r.net Git - cc65/blob - src/cc65/preproc.c
9d441c30e810983a01b016bc50da4d09f445e4fe
[cc65] / src / cc65 / preproc.c
1
2 /* C pre-processor functions */
3
4 #include <stdio.h>
5 #include <string.h>
6 #include <stdlib.h>
7 #include <errno.h>
8
9 /* common */
10 #include "chartype.h"
11 #include "print.h"
12 #include "xmalloc.h"
13
14 /* cc65 */
15 #include "codegen.h"
16 #include "error.h"
17 #include "expr.h"
18 #include "global.h"
19 #include "ident.h"
20 #include "incpath.h"
21 #include "input.h"
22 #include "lineinfo.h"
23 #include "macrotab.h"
24 #include "scanner.h"
25 #include "util.h"
26 #include "preproc.h"
27
28
29
30 /*****************************************************************************/
31 /*                                 Forwards                                  */
32 /*****************************************************************************/
33
34
35
36 static int Pass1 (const char* From, char* To);
37 /* Preprocessor pass 1. Remove whitespace and comments. */
38
39
40
41 /*****************************************************************************/
42 /*                                   Data                                    */
43 /*****************************************************************************/
44
45
46
47 /* Set when the pp calls expr() recursively */
48 unsigned char Preprocessing = 0;
49
50 /* Management data for #if */
51 #define N_IFDEF         16
52 static int i_ifdef = -1;
53 static char s_ifdef[N_IFDEF];
54
55 /* Buffer for macro expansion */
56 static char mlinebuf [LINESIZE];
57 static char* mline = mlinebuf;
58 static char* mptr;
59
60 /* Flag: Expand macros in this line */
61 static int ExpandMacros = 1;
62
63
64
65 /*****************************************************************************/
66 /*                   Low level preprocessor token handling                   */
67 /*****************************************************************************/
68
69
70
71 /* Types of preprocessor tokens */
72 typedef enum {
73     PP_DEFINE,
74     PP_ELSE,
75     PP_ENDIF,
76     PP_ERROR,
77     PP_IF,
78     PP_IFDEF,
79     PP_IFNDEF,
80     PP_INCLUDE,
81     PP_LINE,
82     PP_PRAGMA,
83     PP_UNDEF,
84     PP_ILLEGAL
85 } pptoken_t;
86
87
88
89 /* Preprocessor keyword to token mapping table */
90 static const struct PPToken {
91     const char* Key;            /* Keyword */
92     pptoken_t   Tok;            /* Token */
93 } PPTokens[] = {
94     {   "define",       PP_DEFINE       },
95     {   "else",         PP_ELSE         },
96     {   "endif",        PP_ENDIF        },
97     {   "error",        PP_ERROR        },
98     {   "if",           PP_IF           },
99     {   "ifdef",        PP_IFDEF        },
100     {   "ifndef",       PP_IFNDEF       },
101     {   "include",      PP_INCLUDE      },
102     {   "line",         PP_LINE         },
103     {   "pragma",       PP_PRAGMA       },
104     {   "undef",        PP_UNDEF        },
105 };
106
107 /* Number of preprocessor tokens */
108 #define PPTOKEN_COUNT   (sizeof(PPTokens) / sizeof(PPTokens[0]))
109
110
111
112 static int CmpToken (const void* Key, const void* Elem)
113 /* Compare function for bsearch */
114 {
115     return strcmp ((const char*) Key, ((const struct PPToken*) Elem)->Key);
116 }
117
118
119
120 static pptoken_t FindPPToken (const char* Ident)
121 /* Find a preprocessor token and return ut. Return PP_ILLEGAL if the identifier
122  * is not a valid preprocessor token.
123  */
124 {
125     struct PPToken* P;
126     P = bsearch (Ident, PPTokens, PPTOKEN_COUNT, sizeof (PPTokens[0]), CmpToken);
127     return P? P->Tok : PP_ILLEGAL;
128 }
129
130
131
132 /*****************************************************************************/
133 /*                                   Code                                    */
134 /*****************************************************************************/
135
136
137
138 static int keepch (char c)
139 /* Put character c into translation buffer. */
140 {
141     return (*mptr++ = c);
142 }
143
144
145
146 static void keepstr (const char* S)
147 /* Put string str into translation buffer. */
148 {
149     while (*S) {
150         keepch (*S++);
151     }
152 }
153
154
155
156 static void Comment (void)
157 /* Remove a C comment from line. */
158 {
159     /* Remember the current line number, so we can output better error
160      * messages if the comment is not terminated in the current file.
161      */
162     unsigned StartingLine = GetCurrentLine();
163
164     /* Skip the start of comment chars */
165     NextChar ();
166     NextChar ();
167
168     /* Skip the comment */
169     while (CurC != '*' || NextC != '/') {
170         if (CurC == '\0') {
171             if (NextLine () == 0) {
172                 PPError ("End-of-file reached in comment starting at line %u",
173                          StartingLine);
174                 return;
175             }
176         } else {
177             if (CurC == '/' && NextC == '*') {
178                 PPWarning ("`/*' found inside a comment");
179             }
180             NextChar ();
181         }
182     }
183
184     /* Skip the end of comment chars */
185     NextChar ();
186     NextChar ();
187 }
188
189
190
191 static void SkipBlank (void)
192 /* Skip blanks and tabs in the input stream. */
193 {
194     while (IsBlank (CurC)) {
195         NextChar ();
196     }
197 }
198
199
200
201 static char* CopyQuotedString (char* Target)
202 /* Copy a single or double quoted string from the input to Target. Return the
203  * new target pointer. Target will not be terminated after the copy.
204  */
205 {
206     /* Remember the quote character, copy it to the target buffer and skip it */
207     char Quote = CurC;
208     *Target++  = CurC;
209     NextChar ();
210
211     /* Copy the characters inside the string */
212     while (CurC != '\0' && CurC != Quote) {
213         /* Keep an escaped char */
214         if (CurC == '\\') {
215             *Target++ = CurC;
216             NextChar ();
217         }
218         /* Copy the character */
219         *Target++ = CurC;
220         NextChar ();
221     }
222
223     /* If we had a terminating quote, copy it */
224     if (CurC != '\0') {
225         *Target++ = CurC;
226         NextChar ();
227     }
228
229     /* Return the new target pointer */
230     return Target;
231 }
232
233
234
235 /*****************************************************************************/
236 /*                                Macro stuff                                */
237 /*****************************************************************************/
238
239
240
241 static int MacName (char* Ident)
242 /* Get macro symbol name.  If we have an error, print a diagnostic message
243  * and clear line.
244  */
245 {
246     if (IsSym (Ident) == 0) {
247         PPError ("Identifier expected");
248         ClearLine ();
249         return 0;
250     } else {
251         return 1;
252     }
253 }
254
255
256
257 static void ExpandMacroArgs (Macro* M)
258 /* Expand the arguments of a macro */
259 {
260     ident       Ident;
261     const char* Replacement;
262     const char* SavePtr;
263
264     /* Save the current line pointer and setup the new ones */
265     SavePtr = lptr;
266     InitLine (M->Replacement);
267
268     /* Copy the macro replacement checking for parameters to replace */
269     while (CurC != '\0') {
270         /* If the next token is an identifier, check for a macro arg */
271         if (IsIdent (CurC)) {
272             SymName (Ident);
273             Replacement = FindMacroArg (M, Ident);
274             if (Replacement) {
275                 /* Macro arg, keep the replacement */
276                 keepstr (Replacement);
277             } else {
278                 /* No macro argument, keep the original identifier */
279                 keepstr (Ident);
280             }
281         } else if (CurC == '#' && IsIdent (NextC)) {
282             NextChar ();
283             SymName (Ident);
284             Replacement = FindMacroArg (M, Ident);
285             if (Replacement) {
286                 keepch ('\"');
287                 keepstr (Replacement);
288                 keepch ('\"');
289             } else {
290                 keepch ('#');
291                 keepstr (Ident);
292             }
293         } else if (IsQuote (CurC)) {
294             mptr = CopyQuotedString (mptr);
295         } else {
296             *mptr++ = CurC;
297             NextChar ();
298         }
299     }
300
301     /* Reset the line pointer */
302     InitLine (SavePtr);
303 }
304
305
306
307 static int MacroCall (Macro* M)
308 /* Process a function like macro */
309 {
310     unsigned    ArgCount;       /* Macro argument count */
311     unsigned    ParCount;       /* Number of open parenthesis */
312     char        Buf[LINESIZE];  /* Argument buffer */
313     const char* ArgStart;
314     char*       B;
315
316     /* Expect an argument list */
317     SkipBlank ();
318     if (CurC != '(') {
319         PPError ("Illegal macro call");
320         return 0;
321     }
322
323     /* Eat the left paren */
324     NextChar ();
325
326     /* Read the actual macro arguments and store pointers to these arguments
327      * into the array of actual arguments in the macro definition.
328      */
329     ArgCount = 0;
330     ParCount = 0;
331     ArgStart = Buf;
332     B        = Buf;
333     while (1) {
334         if (CurC == '(') {
335             /* Nested parenthesis */
336             *B++ = CurC;
337             NextChar ();
338             ++ParCount;
339         } else if (IsQuote (CurC)) {
340             B = CopyQuotedString (B);
341         } else if (CurC == ',' || CurC == ')') {
342             if (ParCount == 0) {
343                 /* End of actual argument */
344                 *B++ = '\0';
345                 while (IsBlank(*ArgStart)) {
346                     ++ArgStart;
347                 }
348                 if (ArgCount < M->ArgCount) {
349                     M->ActualArgs[ArgCount++] = ArgStart;
350                 } else if (CurC != ')' || *ArgStart != '\0' || M->ArgCount > 0) {
351                     /* Be sure not to count the single empty argument for a
352                      * macro that does not have arguments.
353                      */
354                     ++ArgCount;
355                 }
356
357                 /* Check for end of macro param list */
358                 if (CurC == ')') {
359                     NextChar ();
360                     break;
361                 }
362
363                 /* Start the next param */
364                 ArgStart = B;
365                 NextChar ();
366             } else {
367                 /* Comma or right paren inside nested parenthesis */
368                 if (CurC == ')') {
369                     --ParCount;
370                 }
371                 *B++ = CurC;
372                 NextChar ();
373             }
374         } else if (IsBlank (CurC)) {
375             /* Squeeze runs of blanks */
376             *B++ = ' ';
377             SkipBlank ();
378         } else if (CurC == '\0') {
379             /* End of line inside macro argument list - read next line */
380             if (NextLine () == 0) {
381                 return 0;
382             }
383         } else {
384             /* Just copy the character */
385             *B++ = CurC;
386             NextChar ();
387         }
388     }
389
390     /* Compare formal argument count with actual */
391     if (M->ArgCount != ArgCount) {
392         PPError ("Macro argument count mismatch");
393         /* Be sure to make enough empty arguments available */
394         while (ArgCount < M->ArgCount) {
395             M->ActualArgs [ArgCount++] = "";
396         }
397     }
398
399     /* Preprocess the line, replacing macro parameters */
400     ExpandMacroArgs (M);
401
402     /* Done */
403     return 1;
404 }
405
406
407
408 static void ExpandMacro (Macro* M)
409 /* Expand a macro */
410 {
411     /* Check if this is a function like macro */
412     if (M->ArgCount >= 0) {
413         /* Function like macro */
414         if (MacroCall (M) == 0) {
415             ClearLine ();
416         }
417     } else {
418         /* Just copy the replacement text */
419         keepstr (M->Replacement);
420     }
421 }
422
423
424
425 static void DefineMacro (void)
426 /* Handle a macro definition. */
427 {
428     char*       saveptr;
429     ident       Ident;
430     char        Buf[LINESIZE];
431     Macro*      M;
432     Macro*      Existing;
433
434     /* Read the macro name */
435     SkipBlank ();
436     if (!MacName (Ident)) {
437         return;
438     }
439
440     /* Get an existing macro definition with this name */
441     Existing = FindMacro (Ident);
442
443     /* Create a new macro definition */
444     M = NewMacro (Ident);
445
446     /* Check if this is a function like macro */
447     if (CurC == '(') {
448
449         /* Skip the left paren */
450         NextChar ();
451
452         /* Set the marker that this is a function like macro */
453         M->ArgCount = 0;
454
455         /* Read the formal parameter list */
456         while (1) {
457             SkipBlank ();
458             if (CurC == ')')
459                 break;
460             if (MacName (Ident) == 0) {
461                 return;
462             }
463             AddMacroArg (M, Ident);
464             SkipBlank ();
465             if (CurC != ',')
466                 break;
467             NextChar ();
468         }
469
470         /* Check for a right paren and eat it if we find one */
471         if (CurC != ')') {
472             PPError ("`)' expected");
473             ClearLine ();
474             return;
475         }
476         NextChar ();
477     }
478
479     /* Insert the macro into the macro table and allocate the ActualArgs array */
480     InsertMacro (M);
481
482     /* Remove whitespace and comments from the line, store the preprocessed
483      * line into Buf.
484      */
485     SkipBlank ();
486     saveptr = mptr;
487     Pass1 (lptr, Buf);
488     mptr = saveptr;
489
490     /* Create a copy of the replacement */
491     M->Replacement = xstrdup (Buf);
492
493     /* If we have an existing macro, check if the redefinition is identical.
494      * Print a diagnostic if not.
495      */
496     if (Existing) {
497         if (MacroCmp (M, Existing) != 0) {
498             PPError ("Macro redefinition is not identical");
499         }
500     }
501 }
502
503
504
505 /*****************************************************************************/
506
507 /*****************************************************************************/
508
509
510
511 static int Pass1 (const char* From, char* To)
512 /* Preprocessor pass 1. Remove whitespace and comments. */
513 {
514     int         done;
515     ident       Ident;
516     int         HaveParen;
517
518     /* Initialize reading from "From" */
519     InitLine (From);
520
521     /* Target is "To" */
522     mptr = To;
523
524     /* Loop removing ws and comments */
525     done = 1;
526     while (CurC != '\0') {
527         if (IsBlank (CurC)) {
528             keepch (' ');
529             SkipBlank ();
530         } else if (IsIdent (CurC)) {
531             SymName (Ident);
532             if (Preprocessing && strcmp(Ident, "defined") == 0) {
533                 /* Handle the "defined" operator */
534                 SkipBlank();
535                 HaveParen = 0;
536                 if (CurC == '(') {
537                     HaveParen = 1;
538                     NextChar ();
539                     SkipBlank();
540                 }
541                 if (!IsIdent (CurC)) {
542                     PPError ("Identifier expected");
543                     *mptr++ = '0';
544                 } else {
545                     SymName (Ident);
546                     *mptr++ = IsMacro (Ident)? '1' : '0';
547                     if (HaveParen) {
548                         SkipBlank();
549                         if (CurC != ')') {
550                             PPError ("`)' expected");
551                         } else {
552                             NextChar ();
553                         }
554                     }
555                 }
556             } else {
557                 if (MaybeMacro (Ident[0])) {
558                     done = 0;
559                 }
560                 keepstr (Ident);
561             }
562         } else if (IsQuote (CurC)) {
563             mptr = CopyQuotedString (mptr);
564         } else if (CurC == '/' && NextC == '*') {
565             keepch (' ');
566             Comment ();
567         } else if (ANSI == 0 && CurC == '/' && NextC == '/') {
568             keepch (' ');
569             /* Beware: Because line continuation chars are handled when reading
570              * lines, we may only skip til the end of the source line, which
571              * may not be the same as the end of the input line. The end of the
572              * source line is denoted by a lf (\n) character.
573              */
574             do {
575                 NextChar ();
576             } while (CurC != '\n' && CurC != '\0');
577             if (CurC == '\n') {
578                 NextChar ();
579             }
580         } else {
581             *mptr++ = CurC;
582             NextChar ();
583         }
584     }
585     keepch ('\0');
586     return done;
587 }
588
589
590
591 static int Pass2 (const char* From, char* To)
592 /* Preprocessor pass 2.  Perform macro substitution. */
593 {
594     int         no_chg;
595     ident       Ident;
596     Macro*      M;
597
598     /* Initialize reading from "From" */
599     InitLine (From);
600
601     /* Target is "To" */
602     mptr = To;
603
604     /* Loop substituting macros */
605     no_chg = 1;
606     while (CurC != '\0') {
607         /* If we have an identifier, check if it's a macro */
608         if (IsIdent (CurC)) {
609             SymName (Ident);
610             M = FindMacro (Ident);
611             if (M) {
612                 ExpandMacro (M);
613                 no_chg = 0;
614             } else {
615                 keepstr (Ident);
616             }
617         } else if (IsQuote (CurC)) {
618             mptr = CopyQuotedString (mptr);
619         } else {
620             *mptr++ = CurC;
621             NextChar ();
622         }
623     }
624     return no_chg;
625 }
626
627
628
629 static void xlateline (void)
630 /* Translate one line. */
631 {
632     int cnt;
633     int Done;
634
635     Done = Pass1 (line, mline);
636     if (ExpandMacros == 0) {
637         Done = 1;
638         ExpandMacros = 1;       /* Reset to default */
639     }
640     cnt = 5;
641     do {
642         /* Swap mline and line */
643         char* p = line;
644         line = mline;
645         mline = p;
646         if (Done)
647             break;
648         Done = Pass2 (line, mline);
649         keepch ('\0');
650     } while (--cnt);
651
652     /* Reinitialize line parsing */
653     InitLine (line);
654 }
655
656
657
658 static void doundef (void)
659 /* Process #undef directive */
660 {
661     ident Ident;
662
663     SkipBlank ();
664     if (MacName (Ident)) {
665         UndefineMacro (Ident);
666     }
667 }
668
669
670
671 static int setmflag (int skip, int flag, int cond)
672 /* setmflag( skip, flag, cond ) */
673 {
674     if (skip) {
675         s_ifdef[++i_ifdef] = 3;
676         return (1);
677     } else {
678         s_ifdef[++i_ifdef] = 6;
679         return (flag ^ cond);
680     }
681 }
682
683
684
685 static int doiff (int skip)
686 /* Process #if directive */
687 {
688     ExprDesc lval;
689     char* S;
690
691     /* We're about to abuse the compiler expression parser to evaluate the
692      * #if expression. Save the current tokens to come back here later.
693      * NOTE: Yes, this is a hack, but it saves a complete separate expression
694      * evaluation for the preprocessor.
695      */
696     Token sv1 = CurTok;
697     Token sv2 = NextTok;
698
699     /* Make sure the line infos for the tokens won't get removed */
700     if (sv1.LI) {
701         UseLineInfo (sv1.LI);
702     }
703     if (sv2.LI) {
704         UseLineInfo (sv2.LI);
705     }
706
707     /* Remove the #if from the line and add two semicolons as sentinels */
708     SkipBlank ();
709     S = line;
710     while (CurC != '\0') {
711         *S++ = CurC;
712         NextChar ();
713     }
714     *S++ = ';';
715     *S++ = ';';
716     *S   = '\0';
717
718     /* Start over parsing from line */
719     InitLine (line);
720
721     /* Switch into special preprocessing mode */
722     Preprocessing = 1;
723
724     /* Expand macros in this line */
725     xlateline ();
726
727     /* Prime the token pump (remove old tokens from the stream) */
728     NextToken ();
729     NextToken ();
730
731     /* Call the expression parser */
732     constexpr (&lval);
733
734     /* End preprocessing mode */
735     Preprocessing = 0;
736
737     /* Reset the old tokens */
738     CurTok  = sv1;
739     NextTok = sv2;
740
741     /* Set the #if condition according to the expression result */
742     return (setmflag (skip, 1, lval.ConstVal != 0));
743 }
744
745
746
747 static int doifdef (int skip, int flag)
748 /* Process #ifdef if flag == 1, or #ifndef if flag == 0. */
749 {
750     ident Ident;
751
752     SkipBlank ();
753     if (MacName (Ident) == 0) {
754         return 0;
755     } else {
756         return setmflag (skip, flag, IsMacro(Ident));
757     }
758 }
759
760
761
762 static void doinclude (void)
763 /* Open an include file. */
764 {
765     char        RTerm;
766     unsigned    DirSpec;
767
768
769     /* Skip blanks */
770     SkipBlank ();
771
772     /* Get the next char and check for a valid file name terminator. Setup
773      * the include directory spec (SYS/USR) by looking at the terminator.
774      */
775     switch (CurC) {
776
777         case '\"':
778             RTerm   = '\"';
779             DirSpec = INC_USER;
780             break;
781
782         case '<':
783             RTerm   = '>';
784             DirSpec = INC_SYS;
785             break;
786
787         default:
788             PPError ("`\"' or `<' expected");
789             goto Done;
790     }
791     NextChar ();
792
793     /* Copy the filename into mline. Since mline has the same size as the
794      * input line, we don't need to check for an overflow here.
795      */
796     mptr = mline;
797     while (CurC != '\0' && CurC != RTerm) {
798         *mptr++ = CurC;
799         NextChar ();
800     }
801     *mptr = '\0';
802
803     /* Check if we got a terminator */
804     if (CurC != RTerm) {
805         /* No terminator found */
806         PPError ("Missing terminator or file name too long");
807         goto Done;
808     }
809
810     /* Open the include file */
811     OpenIncludeFile (mline, DirSpec);
812
813 Done:
814     /* Clear the remaining line so the next input will come from the new
815      * file (if open)
816      */
817     ClearLine ();
818 }
819
820
821
822 static void doerror (void)
823 /* Print an error */
824 {
825     SkipBlank ();
826     if (CurC == '\0') {
827         PPError ("Invalid #error directive");
828     } else {
829         PPError ("#error: %s", lptr);
830     }
831
832     /* clear rest of line */
833     ClearLine ();
834 }
835
836
837
838 void Preprocess (void)
839 /* Preprocess a line */
840 {
841     int         Skip;
842     ident       Directive;
843
844     /* Skip white space at the beginning of the line */
845     SkipBlank ();
846
847     /* Check for stuff to skip */
848     Skip = 0;
849     while (CurC == '\0' || CurC == '#' || Skip) {
850
851         /* Check for preprocessor lines lines */
852         if (CurC == '#') {
853             NextChar ();
854             SkipBlank ();
855             if (CurC == '\0') {
856                 /* Ignore the empty preprocessor directive */
857                 continue;
858             }
859             if (!IsSym (Directive)) {
860                 PPError ("Preprocessor directive expected");
861                 ClearLine ();
862             } else {
863                 switch (FindPPToken (Directive)) {
864
865                     case PP_DEFINE:
866                         if (!Skip) {
867                             DefineMacro ();
868                         }
869                         break;
870
871                     case PP_ELSE:
872                         if (s_ifdef[i_ifdef] & 2) {
873                             if (s_ifdef[i_ifdef] & 4) {
874                                 Skip = !Skip;
875                             }
876                             s_ifdef[i_ifdef] ^= 2;
877                         } else {
878                             PPError ("Unexpected `#else'");
879                         }
880                         break;
881
882                     case PP_ENDIF:
883                         if (i_ifdef >= 0) {
884                             Skip = s_ifdef[i_ifdef--] & 1;
885                         } else {
886                             PPError ("Unexpected `#endif'");
887                         }
888                         break;
889
890                     case PP_ERROR:
891                         if (!Skip) {
892                             doerror ();
893                         }
894                         break;
895
896                     case PP_IF:
897                         Skip = doiff (Skip);
898                         break;
899
900                     case PP_IFDEF:
901                         Skip = doifdef (Skip, 1);
902                         break;
903
904                     case PP_IFNDEF:
905                         Skip = doifdef (Skip, 0);
906                         break;
907
908                     case PP_INCLUDE:
909                         if (!Skip) {
910                             doinclude ();
911                         }
912                         break;
913
914                     case PP_LINE:
915                         /* Not allowed in strict ANSI mode */
916                         if (ANSI) {
917                             PPError ("Preprocessor directive expected");
918                             ClearLine ();
919                         }
920                         break;
921
922                     case PP_PRAGMA:
923                         if (!Skip) {
924                             /* Don't expand macros in this line */
925                             ExpandMacros = 0;
926                             /* #pragma is handled on the scanner level */
927                             goto Done;
928                         }
929                         break;
930
931                     case PP_UNDEF:
932                         if (!Skip) {
933                             doundef ();
934                         }
935                         break;
936
937                     default:
938                         PPError ("Preprocessor directive expected");
939                         ClearLine ();
940                 }
941             }
942
943         }
944         if (NextLine () == 0) {
945             if (i_ifdef >= 0) {
946                 PPError ("`#endif' expected");
947             }
948             return;
949         }
950         SkipBlank ();
951     }
952
953 Done:
954     xlateline ();
955     Print (stdout, 2, "line: %s\n", line);
956 }
957