]> git.sur5r.net Git - cc65/blob - src/ca65/nexttok.c
44888b5ca98eb4101a2d00f2588b77c7530862c0
[cc65] / src / ca65 / nexttok.c
1 /*****************************************************************************/
2 /*                                                                           */
3 /*                                 nexttok.c                                 */
4 /*                                                                           */
5 /*              Get next token and handle token level functions              */
6 /*                                                                           */
7 /*                                                                           */
8 /*                                                                           */
9 /* (C) 2000-2007 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 <stdio.h>
37 #include <string.h>
38
39 /* common */
40 #include "chartype.h"
41 #include "check.h"
42 #include "strbuf.h"
43
44 /* ca65 */
45 #include "error.h"
46 #include "expr.h"
47 #include "global.h"
48 #include "scanner.h"
49 #include "toklist.h"
50 #include "nexttok.h"
51
52
53
54 /*****************************************************************************/
55 /*                                   Data                                    */
56 /*****************************************************************************/
57
58
59
60 static unsigned RawMode = 0;            /* Raw token mode flag/counter */
61
62
63
64 /*****************************************************************************/
65 /*                              Error handling                               */
66 /*****************************************************************************/
67
68
69
70 static int LookAtStrCon (void)
71 /* Make sure the next token is a string constant. If not, print an error
72  * messages skip the remainder of the line and return false. Otherwise return
73  * true.
74  */
75 {
76     if (Tok != TOK_STRCON) {
77         Error ("String constant expected");
78         SkipUntilSep ();
79         return 0;
80     } else {
81         return 1;
82     }
83 }
84
85
86
87 /*****************************************************************************/
88 /*                                   Code                                    */
89 /*****************************************************************************/
90
91
92
93 static TokList* CollectTokens (unsigned Start, unsigned Count)
94 /* Read a list of tokens that is optionally enclosed in curly braces and
95  * terminated by a right paren. For all tokens starting at the one with index
96  * Start, and ending at (Start+Count-1), place them into a token list, and
97  * return this token list.
98  */
99 {
100
101     /* Create the token list */
102     TokList* List = NewTokList ();
103
104     /* Determine if the list is enclosed in curly braces. */
105     Token Term = GetTokListTerm (TOK_RPAREN);
106
107     /* Read the token list */
108     unsigned Current = 0;
109     while (Tok != Term) {
110
111         /* Check for end of line or end of input */
112         if (TokIsSep (Tok)) {
113             Error ("Unexpected end of line");
114             return List;
115         }
116
117         /* Collect tokens in the given range */
118         if (Current >= Start && Current < Start+Count) {
119             /* Add the current token to the list */
120             AddCurTok (List);
121         }
122
123         /* Get the next token */
124         ++Current;
125         NextTok ();
126     }
127
128     /* Eat the terminator token */
129     NextTok ();
130
131     /* If the list was enclosed in curly braces, we do expect now a right paren */
132     if (Term == TOK_RCURLY) {
133         ConsumeRParen ();
134     }
135
136     /* Return the list of collected tokens */
137     return List;
138 }
139
140
141
142 static void FuncConcat (void)
143 /* Handle the .CONCAT function */
144 {
145     char        Buf[MAX_STR_LEN+1];
146     char*       B;
147     unsigned    Length;
148     unsigned    L;
149
150     /* Skip it */
151     NextTok ();
152
153     /* Left paren expected */
154     ConsumeLParen ();
155
156     /* Concatenate any number of strings */
157     B = Buf;
158     B[0] = '\0';
159     Length = 0;
160     while (1) {
161
162         /* Next token must be a string */
163         if (!LookAtStrCon ()) {
164             return;
165         }
166
167         /* Get the length of the string const and check total length */
168         L = strlen (SVal);
169         if (Length + L > MAX_STR_LEN) {
170             Error ("String is too long");
171             /* Try to recover */
172             SkipUntilSep ();
173             return;
174         }
175
176         /* Add the new string */
177         memcpy (B, SVal, L);
178         Length += L;
179         B      += L;
180
181         /* Skip the string token */
182         NextTok ();
183
184         /* Comma means another argument */
185         if (Tok == TOK_COMMA) {
186             NextTok ();
187         } else {
188             /* Done */
189             break;
190         }
191     }
192
193     /* Terminate the string */
194     *B = '\0';
195
196     /* We expect a closing parenthesis, but will not skip it but replace it
197      * by the string token just created.
198      */
199     if (Tok != TOK_RPAREN) {
200         Error ("`)' expected");
201     } else {
202         Tok = TOK_STRCON;
203         strcpy (SVal, Buf);
204     }
205 }
206
207
208
209 static void NoIdent (void)
210 /* Print an error message and skip the remainder of the line */
211 {
212     Error ("Argument of .IDENT is not a valid identifier");
213     SkipUntilSep ();
214 }
215
216
217
218 static void FuncIdent (void)
219 /* Handle the .IDENT function */
220 {
221     char      Buf[sizeof(SVal)];
222     Token     Id;
223     unsigned  Len;
224     unsigned  I;
225
226     /* Skip it */
227     NextTok ();
228
229     /* Left paren expected */
230     ConsumeLParen ();
231
232     /* The function expects a string argument */
233     if (!LookAtStrCon ()) {
234         return;
235     }
236
237     /* Check that the string contains a valid identifier. While doing so,
238      * determine if it is a cheap local, or global one.
239      */
240     Len = strlen (SVal);
241     if (Len == 0) {
242         NoIdent ();
243         return;
244     }
245     I = 0;
246     if (SVal[0] == LocalStart) {
247         if (Len < 2) {
248             NoIdent ();
249             return;
250         }
251         I = 1;
252         Id = TOK_LOCAL_IDENT;
253     } else {
254         Id = TOK_IDENT;
255     }
256     if (!IsIdStart (SVal[I])) {
257         NoIdent ();
258         return;
259     }
260     while (I < Len) {
261         if (!IsIdChar (SVal[I])) {
262             NoIdent ();
263             return;
264         }
265         ++I;
266     }
267     if (IgnoreCase) {
268         UpcaseSVal ();
269     }
270
271     /* If anything is ok, save and skip the string. Check that the next token
272      * is a right paren, in which case SVal is untouched. Replace the token by
273      * a identifier token.
274      */
275     memcpy (Buf, SVal, Len+1);
276     NextTok ();
277     if (Tok != TOK_RPAREN) {
278         Error ("`)' expected");
279     } else {
280         Tok = Id;
281         memcpy (SVal, Buf, Len+1);
282     }
283 }
284
285
286
287 static void FuncLeft (void)
288 /* Handle the .LEFT function */
289 {
290     long        Count;
291     TokList*    List;
292
293     /* Skip it */
294     NextTok ();
295
296     /* Left paren expected */
297     ConsumeLParen ();
298
299     /* Count argument. Correct negative counts to zero. */
300     Count = ConstExpression ();
301     if (Count < 0) {
302         Count = 1;
303     }
304     ConsumeComma ();
305
306     /* Read the token list */
307     List = CollectTokens (0, (unsigned) Count);
308
309     /* Since we want to insert the list before the now current token, we have
310      * to save the current token in some way and then skip it. To do this, we
311      * will add the current token at the end of the token list (so the list
312      * will never be empty), push the token list, and then skip the current
313      * token. This will replace the current token by the first token from the
314      * list (which will be the old current token in case the list was empty).
315      */
316     AddCurTok (List);
317
318     /* Insert it into the scanner feed */
319     PushTokList (List, ".LEFT");
320
321     /* Skip the current token */
322     NextTok ();
323 }
324
325
326
327 static void FuncMid (void)
328 /* Handle the .MID function */
329 {
330     long        Start;
331     long        Count;
332     TokList*    List;
333
334     /* Skip it */
335     NextTok ();
336
337     /* Left paren expected */
338     ConsumeLParen ();
339
340     /* Start argument. Since the start argument can get negative with
341      * expressions like ".tcount(arg)-2", we correct it to zero silently.
342      */
343     Start = ConstExpression ();
344     if (Start < 0 || Start > 100) {
345         Start = 0;
346     }
347     ConsumeComma ();
348
349     /* Count argument. Similar as above, we will accept negative counts and
350      * correct them to zero silently.
351      */
352     Count = ConstExpression ();
353     if (Count < 0) {
354         Count = 0;
355     }
356     ConsumeComma ();
357
358     /* Read the token list */
359     List = CollectTokens ((unsigned) Start, (unsigned) Count);
360
361     /* Since we want to insert the list before the now current token, we have
362      * to save the current token in some way and then skip it. To do this, we
363      * will add the current token at the end of the token list (so the list
364      * will never be empty), push the token list, and then skip the current
365      * token. This will replace the current token by the first token from the
366      * list (which will be the old current token in case the list was empty).
367      */
368     AddCurTok (List);
369
370     /* Insert it into the scanner feed */
371     PushTokList (List, ".MID");
372
373     /* Skip the current token */
374     NextTok ();
375 }
376
377
378
379 static void FuncRight (void)
380 /* Handle the .RIGHT function */
381 {
382     long        Count;
383     TokList*    List;
384
385     /* Skip it */
386     NextTok ();
387
388     /* Left paren expected */
389     ConsumeLParen ();
390
391     /* Count argument. Correct negative counts to zero. */
392     Count = ConstExpression ();
393     if (Count < 0) {
394         Count = 0;
395     }
396     ConsumeComma ();
397
398     /* Read the complete token list */
399     List = CollectTokens (0, 9999);
400
401     /* Delete tokens from the list until Count tokens are remaining */
402     while (List->Count > (unsigned) Count) {
403         /* Get the first node */
404         TokNode* T = List->Root;
405
406         /* Remove it from the list */
407         List->Root = List->Root->Next;
408
409         /* Free the node */
410         FreeTokNode (T);
411
412         /* Corrent the token counter */
413         List->Count--;
414     }
415
416     /* Since we want to insert the list before the now current token, we have
417      * to save the current token in some way and then skip it. To do this, we
418      * will add the current token at the end of the token list (so the list
419      * will never be empty), push the token list, and then skip the current
420      * token. This will replace the current token by the first token from the
421      * list (which will be the old current token in case the list was empty).
422      */
423     AddCurTok (List);
424
425     /* Insert it into the scanner feed */
426     PushTokList (List, ".RIGHT");
427
428     /* Skip the current token */
429     NextTok ();
430 }
431
432
433
434 static void InvalidFormatString (void)
435 /* Print an error message and skip the remainder of the line */
436 {
437     Error ("Invalid format string");
438     SkipUntilSep ();
439 }
440
441
442
443 static void FuncSPrintF (void)
444 /* Handle the .SPRINTF function */
445 {
446     char        Format[sizeof (SVal)];              /* User given format */
447     const char* F = Format;                         /* User format pointer */
448     StrBuf      R = AUTO_STRBUF_INITIALIZER;        /* Result string */
449     StrBuf      F1 = AUTO_STRBUF_INITIALIZER;       /* One format spec from F */
450     StrBuf      R1 = AUTO_STRBUF_INITIALIZER;       /* One result */
451     int         Done;
452     long        IVal;                               /* Integer value */
453
454
455
456     /* Skip the .SPRINTF token */
457     NextTok ();
458
459     /* Left paren expected */
460     ConsumeLParen ();
461
462     /* First argument is a format string. Remember and skip it */
463     if (!LookAtStrCon ()) {
464         return;
465     }
466     strcpy (Format, SVal);
467     NextTok ();
468
469     /* Walk over the format string, generating the function result in R */
470     while (1) {
471
472         /* Get the next char from the format string and check for EOS */
473         if (*F == '\0') {
474             break;
475         }
476
477         /* Check for a format specifier */
478         if (*F != '%') {
479             /* No format specifier, just copy */
480             SB_AppendChar (&R, *F++);
481             continue;
482         }
483         if (*++F == '%') {
484             /* %% */
485             SB_AppendChar (&R, '%');
486             ++F;
487             continue;
488         }
489         if (*F == '\0') {
490             InvalidFormatString ();
491             break;
492         }
493
494         /* Since a format specifier follows, we do expect anotehr argument for
495          * the .sprintf function.
496          */
497         ConsumeComma ();
498
499         /* We will copy the format spec into F1 checking for the things we
500          * support, and later use xsprintf to do the actual formatting. This
501          * is easier than adding another printf implementation...
502          */
503         SB_Clear (&F1);
504         SB_AppendChar (&F1, '%');
505
506         /* Check for flags */
507         Done = 0;
508         while (*F != '\0' && !Done) {
509             switch (*F) {
510                 case '-': /* FALLTHROUGH */
511                 case '+': /* FALLTHROUGH */
512                 case ' ': /* FALLTHROUGH */
513                 case '#': /* FALLTHROUGH */
514                 case '0': SB_AppendChar (&F1, *F++);    break;
515                 default:  Done = 1;                     break;
516             }
517         }
518
519         /* We do only support a numerical width field */
520         while (IsDigit (*F)) {
521             SB_AppendChar (&F1, *F++);
522         }
523
524         /* Precision - only positive numerical fields supported */
525         if (*F == '.') {
526             SB_AppendChar (&F1, *F++);
527             while (IsDigit (*F)) {
528                 SB_AppendChar (&F1, *F++);
529             }
530         }
531
532         /* Length modifiers aren't supported, so read the conversion specs */
533         switch (*F) {
534
535             case 'd':
536             case 'i':
537             case 'o':
538             case 'u':
539             case 'X':
540             case 'x':
541                 /* Our ints are actually longs, so we use the 'l' modifier when
542                  * calling xsprintf later. Terminate the format string.
543                  */
544                 SB_AppendChar (&F1, 'l');
545                 SB_AppendChar (&F1, *F++);
546                 SB_Terminate (&F1);
547
548                 /* The argument must be a constant expression */
549                 IVal = ConstExpression ();
550
551                 /* Format this argument according to the spec */
552                 SB_Printf (&R1, SB_GetConstBuf (&F1), IVal);
553
554                 /* Append the formatted argument to the result */
555                 SB_Append (&R, &R1);
556
557                 break;
558
559             case 's':
560                 /* Add the format spec and terminate the format */
561                 SB_AppendChar (&F1, *F++);
562                 SB_Terminate (&F1);
563
564                 /* The argument must be a string constant */
565                 if (!LookAtStrCon ()) {
566                     /* Make it one */
567                     strcpy (SVal, "**undefined**");
568                 }
569
570                 /* Format this argument according to the spec */
571                 SB_Printf (&R1, SB_GetConstBuf (&F1), SVal);
572
573                 /* Skip the string constant */
574                 NextTok ();
575
576                 /* Append the formatted argument to the result */
577                 SB_Append (&R, &R1);
578
579                 break;
580
581             case 'c':
582                 /* Add the format spec and terminate the format */
583                 SB_AppendChar (&F1, *F++);
584                 SB_Terminate (&F1);
585
586                 /* The argument must be a constant expression */
587                 IVal = ConstExpression ();
588
589                 /* Check for a valid character range */
590                 if (IVal <= 0 || IVal > 255) {
591                     Error ("Char argument out of range");
592                     IVal = ' ';
593                 }
594
595                 /* Format this argument according to the spec. Be sure to pass
596                  * an int as the char value.
597                  */
598                 SB_Printf (&R1, SB_GetConstBuf (&F1), (int) IVal);
599
600                 /* Append the formatted argument to the result */
601                 SB_Append (&R, &R1);
602
603                 break;
604
605             default:
606                 Error ("Invalid format string");
607                 if (*F) {
608                     /* Don't skip beyond end of string */
609                     ++F;
610                 }
611                 break;
612         }
613
614     }
615
616     /* The length of the final result may not exceed the size of a string */
617     if (SB_GetLen (&R) >= sizeof (SVal)) {
618         Error ("Resulting string is too long");
619         SB_Cut (&R, sizeof (SVal) - 1);
620     }
621
622     /* Terminate the result string */
623     SB_Terminate (&R);
624
625     /* We expect a closing parenthesis, but will not skip it but replace it
626      * by the string token just created.
627      */
628     if (Tok != TOK_RPAREN) {
629         Error ("`)' expected");
630     } else {
631         Tok = TOK_STRCON;
632         memcpy (SVal, SB_GetConstBuf (&R), SB_GetLen (&R) + 1);
633     }
634
635
636     /* Delete the string buffers */
637     DoneStrBuf (&R);
638     DoneStrBuf (&F1);
639     DoneStrBuf (&R1);
640 }
641
642
643
644 static void FuncString (void)
645 /* Handle the .STRING function */
646 {
647     char Buf[MAX_STR_LEN+1];
648
649     /* Skip it */
650     NextTok ();
651
652     /* Left paren expected */
653     ConsumeLParen ();
654
655     /* Accept identifiers or numeric expressions */
656     if (Tok == TOK_IDENT || Tok == TOK_LOCAL_IDENT) {
657         /* Save the identifier, then skip it */
658         strcpy (Buf, SVal);
659         NextTok ();
660     } else {
661         /* Numeric expression */
662         long Val = ConstExpression ();
663         sprintf (Buf, "%ld", Val);
664     }
665
666     /* We expect a closing parenthesis, but will not skip it but replace it
667      * by the string token just created.
668      */
669     if (Tok != TOK_RPAREN) {
670         Error ("`)' expected");
671     } else {
672         Tok = TOK_STRCON;
673         strcpy (SVal, Buf);
674     }
675 }
676
677
678
679 void NextTok (void)
680 /* Get next token and handle token level functions */
681 {
682     /* Get the next raw token */
683     NextRawTok ();
684
685     /* In raw mode, pass the token unchanged */
686     if (RawMode == 0) {
687
688         /* Execute token handling functions */
689         switch (Tok) {
690
691             case TOK_CONCAT:
692                 FuncConcat ();
693                 break;
694
695             case TOK_LEFT:
696                 FuncLeft ();
697                 break;
698
699             case TOK_MAKEIDENT:
700                 FuncIdent ();
701                 break;
702
703             case TOK_MID:
704                 FuncMid ();
705                 break;
706
707             case TOK_RIGHT:
708                 FuncRight ();
709                 break;
710
711             case TOK_SPRINTF:
712                 FuncSPrintF ();
713                 break;
714
715             case TOK_STRING:
716                 FuncString ();
717                 break;
718
719             default:
720                 /* Quiet down gcc */
721                 break;
722
723         }
724     }
725 }
726
727
728
729 void Consume (Token Expected, const char* ErrMsg)
730 /* Consume Expected, print an error if we don't find it */
731 {
732     if (Tok == Expected) {
733         NextTok ();
734     } else {
735         Error (ErrMsg);
736     }
737 }
738
739
740
741 void ConsumeSep (void)
742 /* Consume a separator token */
743 {
744     /* We expect a separator token */
745     ExpectSep ();
746
747     /* If we are at end of line, skip it */
748     if (Tok == TOK_SEP) {
749         NextTok ();
750     }
751 }
752
753
754
755 void ConsumeLParen (void)
756 /* Consume a left paren */
757 {
758     Consume (TOK_LPAREN, "`(' expected");
759 }
760
761
762
763 void ConsumeRParen (void)
764 /* Consume a right paren */
765 {
766     Consume (TOK_RPAREN, "`)' expected");
767 }
768
769
770
771 void ConsumeComma (void)
772 /* Consume a comma */
773 {
774     Consume (TOK_COMMA, "`,' expected");
775 }
776
777
778
779 void SkipUntilSep (void)
780 /* Skip tokens until we reach a line separator or end of file */
781 {
782     while (!TokIsSep (Tok)) {
783         NextTok ();
784     }
785 }
786
787
788
789 void ExpectSep (void)
790 /* Check if we've reached a line separator, and output an error if not. Do
791  * not skip the line separator.
792  */
793 {
794     if (!TokIsSep (Tok)) {
795         ErrorSkip ("Unexpected trailing garbage characters");
796     }
797 }
798
799
800
801 void EnterRawTokenMode (void)
802 /* Enter raw token mode. In raw mode, token handling functions are not
803  * executed, but the function tokens are passed untouched to the upper
804  * layer. Raw token mode is used when storing macro tokens for later
805  * use.
806  * Calls to EnterRawTokenMode and LeaveRawTokenMode may be nested.
807  */
808 {
809     ++RawMode;
810 }
811
812
813
814 void LeaveRawTokenMode (void)
815 /* Leave raw token mode. */
816 {
817     PRECONDITION (RawMode > 0);
818     --RawMode;
819 }
820
821
822