]> git.sur5r.net Git - cc65/blob - src/cc65/pragma.c
47f4da0afda46fe22ff422e57ccd79c9a2cbc9bd
[cc65] / src / cc65 / pragma.c
1 /*****************************************************************************/
2 /*                                                                           */
3 /*                                 pragma.c                                  */
4 /*                                                                           */
5 /*                  Pragma handling for the cc65 C compiler                  */
6 /*                                                                           */
7 /*                                                                           */
8 /*                                                                           */
9 /* (C) 1998-2009, Ullrich von Bassewitz                                      */
10 /*                Roemerstrasse 52                                           */
11 /*                D-70794 Filderstadt                                        */
12 /* EMail:         uz@cc65.org                                                */
13 /*                                                                           */
14 /*                                                                           */
15 /* This software is provided 'as-is', without any expressed or implied       */
16 /* warranty.  In no event will the authors be held liable for any damages    */
17 /* arising from the use of this software.                                    */
18 /*                                                                           */
19 /* Permission is granted to anyone to use this software for any purpose,     */
20 /* including commercial applications, and to alter it and redistribute it    */
21 /* freely, subject to the following restrictions:                            */
22 /*                                                                           */
23 /* 1. The origin of this software must not be misrepresented; you must not   */
24 /*    claim that you wrote the original software. If you use this software   */
25 /*    in a product, an acknowledgment in the product documentation would be  */
26 /*    appreciated but is not required.                                       */
27 /* 2. Altered source versions must be plainly marked as such, and must not   */
28 /*    be misrepresented as being the original software.                      */
29 /* 3. This notice may not be removed or altered from any source              */
30 /*    distribution.                                                          */
31 /*                                                                           */
32 /*****************************************************************************/
33
34
35
36 #include <stdlib.h>
37 #include <string.h>
38
39 /* common */
40 #include "chartype.h"
41 #include "segnames.h"
42 #include "tgttrans.h"
43
44 /* cc65 */
45 #include "codegen.h"
46 #include "error.h"
47 #include "expr.h"
48 #include "global.h"
49 #include "litpool.h"
50 #include "scanner.h"
51 #include "scanstrbuf.h"
52 #include "symtab.h"
53 #include "pragma.h"
54
55
56
57 /*****************************************************************************/
58 /*                                   data                                    */
59 /*****************************************************************************/
60
61
62
63 /* Tokens for the #pragmas */
64 typedef enum {
65     PR_ILLEGAL = -1,
66     PR_BSSSEG,
67     PR_CHARMAP,
68     PR_CHECKSTACK,
69     PR_CODESEG,
70     PR_CODESIZE,
71     PR_DATASEG,
72     PR_OPTIMIZE,
73     PR_REGVARADDR,
74     PR_REGVARS,
75     PR_RODATASEG,
76     PR_SIGNEDCHARS,
77     PR_STATICLOCALS,
78     PR_WARN,
79     PR_ZPSYM,
80     PR_COUNT
81 } pragma_t;
82
83 /* Pragma table */
84 static const struct Pragma {
85     const char* Key;            /* Keyword */
86     pragma_t    Tok;            /* Token */
87 } Pragmas[PR_COUNT] = {
88     {   "bssseg",       PR_BSSSEG       },
89     {   "charmap",      PR_CHARMAP      },
90     {   "checkstack",   PR_CHECKSTACK   },
91     {   "codeseg",      PR_CODESEG      },
92     {   "codesize",     PR_CODESIZE     },
93     {   "dataseg",      PR_DATASEG      },
94     {   "optimize",     PR_OPTIMIZE     },
95     {   "regvaraddr",   PR_REGVARADDR   },
96     {   "regvars",      PR_REGVARS      },
97     {   "rodataseg",    PR_RODATASEG    },
98     {   "signedchars",  PR_SIGNEDCHARS  },
99     {   "staticlocals", PR_STATICLOCALS },
100     {   "warn",         PR_WARN         },
101     {   "zpsym",        PR_ZPSYM        },
102 };
103
104 /* Result of ParsePushPop */
105 typedef enum {
106     PP_NONE,
107     PP_POP,
108     PP_PUSH,
109     PP_ERROR,
110 } PushPopResult;
111
112
113
114 /*****************************************************************************/
115 /*                             Helper functions                              */
116 /*****************************************************************************/
117
118
119
120 static void PragmaErrorSkip (void)
121 /* Called in case of an error, skips tokens until the closing paren or a
122  * semicolon is reached.
123  */
124 {
125     static const token_t TokenList[] = { TOK_RPAREN, TOK_SEMI };
126     SkipTokens (TokenList, sizeof(TokenList) / sizeof(TokenList[0]));
127 }
128
129
130
131 static int CmpKey (const void* Key, const void* Elem)
132 /* Compare function for bsearch */
133 {
134     return strcmp ((const char*) Key, ((const struct Pragma*) Elem)->Key);
135 }
136
137
138
139 static pragma_t FindPragma (const StrBuf* Key)
140 /* Find a pragma and return the token. Return PR_ILLEGAL if the keyword is
141  * not a valid pragma.
142  */
143 {
144     struct Pragma* P;
145     P = bsearch (SB_GetConstBuf (Key), Pragmas, PR_COUNT, sizeof (Pragmas[0]), CmpKey);
146     return P? P->Tok : PR_ILLEGAL;
147 }
148
149
150
151 static int GetComma (StrBuf* B)
152 /* Expects and skips a comma in B. Prints an error and returns zero if no
153  * comma is found. Return a value <> 0 otherwise.
154  */
155 {
156     SB_SkipWhite (B);
157     if (SB_Get (B) != ',') {
158         Error ("Comma expected");
159         return 0;
160     }
161     SB_SkipWhite (B);
162     return 1;
163 }
164
165
166
167 static int GetString (StrBuf* B, StrBuf* S)
168 /* Expects and skips a string in B. Prints an error and returns zero if no
169  * string is found. Returns a value <> 0 otherwise.
170  */
171 {
172     if (!SB_GetString (B, S)) {
173         Error ("String literal expected");
174         return 0;
175     }
176     return 1;
177 }
178
179
180
181 static int GetNumber (StrBuf* B, long* Val)
182 /* Expects and skips a number in B. Prints an eror and returns zero if no
183  * number is found. Returns a value <> 0 otherwise.
184  */
185 {
186     if (!SB_GetNumber (B, Val)) {
187         Error ("Constant integer expected");
188         return 0;
189     }
190     return 1;
191 }
192
193
194
195 static IntStack* GetWarning (StrBuf* B)
196 /* Get a warning name from the string buffer. Returns a pointer to the intstack
197  * that holds the state of the warning, and NULL in case of errors. The
198  * function will output error messages in case of problems.
199  */
200 {
201     IntStack* S = 0;
202     StrBuf W = AUTO_STRBUF_INITIALIZER;
203
204     /* The warning name is a symbol but the '-' char is allowed within */
205     if (SB_GetSym (B, &W, "-")) {
206
207         /* Map the warning name to an IntStack that contains its state */
208         S = FindWarning (SB_GetConstBuf (&W));
209
210         /* Handle errors */
211         if (S == 0) {
212             Error ("Pragma expects a warning name as first argument");
213         }
214     }
215
216     /* Deallocate the string */
217     SB_Done (&W);
218
219     /* Done */
220     return S;
221 }
222
223
224
225 static int HasStr (StrBuf* B, const char* E)
226 /* Checks if E follows in B. If so, skips it and returns true */
227 {
228     unsigned Len = strlen (E);
229     if (SB_GetLen (B) - SB_GetIndex (B) >= Len) {
230         if (strncmp (SB_GetConstBuf (B) + SB_GetIndex (B), E, Len) == 0) {
231             /* Found */
232             SB_SkipMultiple (B, Len);
233             return 1;
234         }
235     }
236     return 0;
237 }
238
239
240
241 static PushPopResult ParsePushPop (StrBuf* B)
242 /* Check for and parse the "push" and "pop" keywords. In case of "push", a
243  * following comma is expected and skipped.
244  */
245 {
246     StrBuf Ident      = AUTO_STRBUF_INITIALIZER;
247     PushPopResult Res = PP_NONE;
248
249
250     /* Try to read an identifier */
251     if (SB_GetSym (B, &Ident, 0)) {
252
253         /* Check if we have a first argument named "pop" */
254         if (SB_CompareStr (&Ident, "pop") == 0) {
255
256             Res = PP_POP;
257
258         /* Check if we have a first argument named "push" */
259         } else if (SB_CompareStr (&Ident, "push") == 0) {
260
261             Res = PP_PUSH;
262
263             /* Skip the following comma */
264             if (!GetComma (B)) {
265                 Res = PP_ERROR;
266             }
267
268         } else {
269
270             /* Unknown keyword */
271             Error ("Invalid pragma arguments");
272             Res = PP_ERROR;
273         }
274     }
275
276     /* Free the string buffer and return the result */
277     SB_Done (&Ident);
278     return Res;
279 }
280
281
282
283 static void PopInt (IntStack* S)
284 /* Pops an integer from an IntStack. Prints an error if the stack is empty */
285 {
286     if (IS_GetCount (S) < 2) {
287         Error ("Cannot pop, stack is empty");
288     } else {
289         IS_Drop (S);
290     }
291 }
292
293
294
295 static void PushInt (IntStack* S, long Val)
296 /* Pushes an integer onto an IntStack. Prints an error if the stack is full */
297 {
298     if (IS_IsFull (S)) {
299         Error ("Cannot push: stack overflow");
300     } else {
301         IS_Push (S, Val);
302     }
303 }
304
305
306
307 static int BoolKeyword (StrBuf* Ident)
308 /* Check if the identifier in Ident is a keyword for a boolean value. Currently
309  * accepted are true/false/on/off.
310  */
311 {
312     if (SB_CompareStr (Ident, "true") == 0) {
313         return 1;
314     }
315     if (SB_CompareStr (Ident, "on") == 0) {
316         return 1;
317     }
318     if (SB_CompareStr (Ident, "false") == 0) {
319         return 0;
320     }
321     if (SB_CompareStr (Ident, "off") == 0) {
322         return 0;
323     }
324
325     /* Error */
326     Error ("Pragma argument must be one of `on', `off', `true' or `false'");
327     return 0;
328 }
329
330
331
332 /*****************************************************************************/
333 /*                         Pragma handling functions                         */
334 /*****************************************************************************/
335
336
337
338 static void StringPragma (StrBuf* B, void (*Func) (const char*))
339 /* Handle a pragma that expects a string parameter */
340 {
341     StrBuf S = AUTO_STRBUF_INITIALIZER;
342
343     /* We expect a string here */
344     if (GetString (B, &S)) {
345         /* Call the given function with the string argument */
346         Func (SB_GetConstBuf (&S));
347     }
348
349     /* Call the string buf destructor */
350     SB_Done (&S);
351 }
352
353
354
355 static void SegNamePragma (StrBuf* B, segment_t Seg)
356 /* Handle a pragma that expects a segment name parameter */
357 {
358     StrBuf      S = AUTO_STRBUF_INITIALIZER;
359     const char* Name;
360
361     /* Check for the "push" or "pop" keywords */
362     int Push = 0;
363     switch (ParsePushPop (B)) {
364
365         case PP_NONE:
366             break;
367
368         case PP_PUSH:
369             Push = 1;
370             break;
371
372         case PP_POP:
373             /* Pop the old value and output it */
374             PopSegName (Seg);
375             g_segname (Seg);
376
377             /* Done */
378             goto ExitPoint;
379
380         case PP_ERROR:
381             /* Bail out */
382             goto ExitPoint;
383
384         default:
385             Internal ("Invalid result from ParsePushPop");
386
387     }
388
389     /* A string argument must follow */
390     if (!GetString (B, &S)) {
391         goto ExitPoint;
392     }
393
394     /* Get the string */
395     Name = SB_GetConstBuf (&S);
396
397     /* Check if the name is valid */
398     if (ValidSegName (Name)) {
399
400         /* Set the new name */
401         if (Push) {
402             PushSegName (Seg, Name);
403         } else {
404             SetSegName (Seg, Name);
405         }
406         g_segname (Seg);
407
408     } else {
409
410         /* Segment name is invalid */
411         Error ("Illegal segment name: `%s'", Name);
412
413     }
414
415 ExitPoint:
416     /* Call the string buf destructor */
417     SB_Done (&S);
418 }
419
420
421
422 static void CharMapPragma (StrBuf* B)
423 /* Change the character map */
424 {
425     long Index, C;
426
427     /* Read the character index */
428     if (!GetNumber (B, &Index)) {
429         return;
430     }
431     if (Index < 1 || Index > 255) {
432         if (Index == 0) {
433             /* For groepaz */
434             Error ("Remapping 0 is not allowed");
435         } else {
436             Error ("Character index out of range");
437         }
438         return;
439     }
440
441     /* Comma follows */
442     if (!GetComma (B)) {
443         return;
444     }
445
446     /* Read the character code */
447     if (!GetNumber (B, &C)) {
448         return;
449     }
450     if (C < 1 || C > 255) {
451         if (C == 0) {
452             /* For groepaz */
453             Error ("Remapping 0 is not allowed");
454         } else {
455             Error ("Character code out of range");
456         }
457         return;
458     }
459
460     /* Remap the character */
461     TgtTranslateSet ((unsigned) Index, (unsigned char) C);
462 }
463
464
465
466 static void WarnPragma (StrBuf* B)
467 /* Enable/disable warnings */
468 {
469     long   Val;
470     int    Push;
471
472     /* A warning name must follow */
473     IntStack* S =GetWarning (B);
474     if (S == 0) {
475         return;
476     }
477
478     /* Comma follows */
479     if (!GetComma (B)) {
480         return;
481     }
482
483     /* Check for the "push" or "pop" keywords */
484     switch (ParsePushPop (B)) {
485
486         case PP_NONE:
487             Push = 0;
488             break;
489
490         case PP_PUSH:
491             Push = 1;
492             break;
493
494         case PP_POP:
495             /* Pop the old value and bail out */
496             PopInt (S);
497             return;
498
499         case PP_ERROR:
500             /* Bail out */
501             return;
502
503         default:
504             Internal ("Invalid result from ParsePushPop");
505     }
506
507     /* Boolean argument follows */
508     if (HasStr (B, "true") || HasStr (B, "on")) {
509         Val = 1;
510     } else if (HasStr (B, "false") || HasStr (B, "off")) {
511         Val = 0;
512     } else if (!SB_GetNumber (B, &Val)) {
513         Error ("Invalid pragma argument");
514         return;
515     }
516
517     /* Set/push the new value */
518     if (Push) {
519         PushInt (S, Val);
520     } else {
521         IS_Set (S, Val);
522     }
523 }
524
525
526
527 static void FlagPragma (StrBuf* B, IntStack* Stack)
528 /* Handle a pragma that expects a boolean paramater */
529 {
530     StrBuf Ident = AUTO_STRBUF_INITIALIZER;
531     long   Val;
532     int    Push;
533
534
535     /* Try to read an identifier */
536     int IsIdent = SB_GetSym (B, &Ident, 0);
537
538     /* Check if we have a first argument named "pop" */
539     if (IsIdent && SB_CompareStr (&Ident, "pop") == 0) {
540         PopInt (Stack);
541         /* No other arguments allowed */
542         return;
543     }
544
545     /* Check if we have a first argument named "push" */
546     if (IsIdent && SB_CompareStr (&Ident, "push") == 0) {
547         Push = 1;
548         if (!GetComma (B)) {
549             goto ExitPoint;
550         }
551         IsIdent = SB_GetSym (B, &Ident, 0);
552     } else {
553         Push = 0;
554     }
555
556     /* Boolean argument follows */
557     if (IsIdent) {
558         Val = BoolKeyword (&Ident);
559     } else if (!GetNumber (B, &Val)) {
560         goto ExitPoint;
561     }
562
563     /* Set/push the new value */
564     if (Push) {
565         PushInt (Stack, Val);
566     } else {
567         IS_Set (Stack, Val);
568     }
569
570 ExitPoint:
571     /* Free the identifier */
572     SB_Done (&Ident);
573 }
574
575
576
577 static void IntPragma (StrBuf* B, IntStack* Stack, long Low, long High)
578 /* Handle a pragma that expects an int paramater */
579 {
580     long  Val;
581     int   Push;
582
583     /* Check for the "push" or "pop" keywords */
584     switch (ParsePushPop (B)) {
585
586         case PP_NONE:
587             Push = 0;
588             break;
589
590         case PP_PUSH:
591             Push = 1;
592             break;
593
594         case PP_POP:
595             /* Pop the old value and bail out */
596             PopInt (Stack);
597             return;
598
599         case PP_ERROR:
600             /* Bail out */
601             return;
602
603         default:
604             Internal ("Invalid result from ParsePushPop");
605
606     }
607
608     /* Integer argument follows */
609     if (!GetNumber (B, &Val)) {
610         return;
611     }
612
613     /* Check the argument */
614     if (Val < Low || Val > High) {
615         Error ("Pragma argument out of bounds (%ld-%ld)", Low, High);
616         return;
617     }
618
619     /* Set/push the new value */
620     if (Push) {
621         PushInt (Stack, Val);
622     } else {
623         IS_Set (Stack, Val);
624     }
625 }
626
627
628
629 static void ParsePragma (void)
630 /* Parse the contents of the _Pragma statement */
631 {
632     pragma_t Pragma;
633     StrBuf   Ident = AUTO_STRBUF_INITIALIZER;
634
635     /* Create a string buffer from the string literal */
636     StrBuf B = AUTO_STRBUF_INITIALIZER;
637     GetLiteralStrBuf (&B, CurTok.IVal);
638
639     /* Reset the string pointer, effectivly clearing the string from the
640      * string table. Since we're working with one token lookahead, this
641      * will fail if the next token is also a string token, but that's a
642      * syntax error anyway, because we expect a right paren.
643      */
644     ResetLiteralPoolOffs (CurTok.IVal);
645
646     /* Skip the string token */
647     NextToken ();
648
649     /* Get the pragma name from the string */
650     SB_SkipWhite (&B);
651     if (!SB_GetSym (&B, &Ident, "-")) {
652         Error ("Invalid pragma");
653         goto ExitPoint;
654     }
655
656     /* Search for the name */
657     Pragma = FindPragma (&Ident);
658
659     /* Do we know this pragma? */
660     if (Pragma == PR_ILLEGAL) {
661         /* According to the ANSI standard, we're not allowed to generate errors
662          * for unknown pragmas, however, we're allowed to warn - and we will
663          * do so. Otherwise one typo may give you hours of bug hunting...
664          */
665         Warning ("Unknown pragma `%s'", SB_GetConstBuf (&Ident));
666         goto ExitPoint;
667     }
668
669     /* Check for an open paren */
670     SB_SkipWhite (&B);
671     if (SB_Get (&B) != '(') {
672         Error ("'(' expected");
673         goto ExitPoint;
674     }
675
676     /* Skip white space before the argument */
677     SB_SkipWhite (&B);
678
679     /* Switch for the different pragmas */
680     switch (Pragma) {
681
682         case PR_BSSSEG:
683             SegNamePragma (&B, SEG_BSS);
684             break;
685
686         case PR_CHARMAP:
687             CharMapPragma (&B);
688             break;
689
690         case PR_CHECKSTACK:
691             FlagPragma (&B, &CheckStack);
692             break;
693
694         case PR_CODESEG:
695             SegNamePragma (&B, SEG_CODE);
696             break;
697
698         case PR_CODESIZE:
699             IntPragma (&B, &CodeSizeFactor, 10, 1000);
700             break;
701
702         case PR_DATASEG:
703             SegNamePragma (&B, SEG_DATA);
704             break;
705
706         case PR_OPTIMIZE:
707             FlagPragma (&B, &Optimize);
708             break;
709
710         case PR_REGVARADDR:
711             FlagPragma (&B, &AllowRegVarAddr);
712             break;
713
714         case PR_REGVARS:
715             FlagPragma (&B, &EnableRegVars);
716             break;
717
718         case PR_RODATASEG:
719             SegNamePragma (&B, SEG_RODATA);
720             break;
721
722         case PR_SIGNEDCHARS:
723             FlagPragma (&B, &SignedChars);
724             break;
725
726         case PR_STATICLOCALS:
727             FlagPragma (&B, &StaticLocals);
728             break;
729
730         case PR_WARN:
731             WarnPragma (&B);
732             break;
733
734         case PR_ZPSYM:
735             StringPragma (&B, MakeZPSym);
736             break;
737
738         default:
739             Internal ("Invalid pragma");
740     }
741
742     /* Closing paren expected */
743     SB_SkipWhite (&B);
744     if (SB_Get (&B) != ')') {
745         Error ("')' expected");
746         goto ExitPoint;
747     }
748     SB_SkipWhite (&B);
749
750     /* Allow an optional semicolon to be compatible with the old syntax */
751     if (SB_Peek (&B) == ';') {
752         SB_Skip (&B);
753         SB_SkipWhite (&B);
754     }
755
756     /* Make sure nothing follows */
757     if (SB_Peek (&B) != '\0') {
758         Error ("Unexpected input following pragma directive");
759     }
760
761 ExitPoint:
762     /* Release the string buffers */
763     SB_Done (&B);
764     SB_Done (&Ident);
765 }
766
767
768
769 void DoPragma (void)
770 /* Handle pragmas. These come always in form of the new C99 _Pragma() operator. */
771 {
772     /* Skip the token itself */
773     NextToken ();
774
775     /* We expect an opening paren */
776     if (!ConsumeLParen ()) {
777         return;
778     }
779
780     /* String literal */
781     if (CurTok.Tok != TOK_SCONST) {
782
783         /* Print a diagnostic */
784         Error ("String literal expected");
785
786         /* Try some smart error recovery: Skip tokens until we reach the
787          * enclosing paren, or a semicolon.
788          */
789         PragmaErrorSkip ();
790
791     } else {
792
793         /* Parse the _Pragma statement */
794         ParsePragma ();
795     }
796
797     /* Closing paren needed */
798     ConsumeRParen ();
799 }
800
801
802