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