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