1 /*****************************************************************************/
5 /* Get next token and handle token level functions */
9 /* (C) 2000-2011, Ullrich von Bassewitz */
10 /* Roemerstrasse 52 */
11 /* D-70794 Filderstadt */
12 /* EMail: uz@cc65.org */
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. */
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: */
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 */
32 /*****************************************************************************/
55 /*****************************************************************************/
57 /*****************************************************************************/
61 static unsigned RawMode = 0; /* Raw token mode flag/counter */
65 /*****************************************************************************/
67 /*****************************************************************************/
71 static 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
77 if (CurTok.Tok != TOK_STRCON) {
78 Error ("String constant expected");
88 /*****************************************************************************/
90 /*****************************************************************************/
94 static 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.
102 /* Create the token list */
103 TokList* List = NewTokList ();
105 /* Determine if the list is enclosed in curly braces. */
106 token_t Term = GetTokListTerm (TOK_RPAREN);
108 /* Read the token list */
109 unsigned Current = 0;
110 while (CurTok.Tok != Term) {
112 /* Check for end of line or end of input */
113 if (TokIsSep (CurTok.Tok)) {
114 Error ("Unexpected end of line");
118 /* Collect tokens in the given range */
119 if (Current >= Start && Current < Start+Count) {
120 /* Add the current token to the list */
124 /* Get the next token */
129 /* Eat the terminator token */
132 /* If the list was enclosed in curly braces, we do expect now a right paren */
133 if (Term == TOK_RCURLY) {
137 /* Return the list of collected tokens */
143 static void FuncConcat (void)
144 /* Handle the .CONCAT function */
146 StrBuf Buf = STATIC_STRBUF_INITIALIZER;
151 /* Left paren expected */
154 /* Concatenate any number of strings */
157 /* Next token must be a string */
158 if (!LookAtStrCon ()) {
163 /* Append the string */
164 SB_Append (&Buf, &CurTok.SVal);
166 /* Skip the string token */
169 /* Comma means another argument */
170 if (CurTok.Tok == TOK_COMMA) {
178 /* We expect a closing parenthesis, but will not skip it but replace it
179 ** by the string token just created.
181 if (CurTok.Tok != TOK_RPAREN) {
182 Error ("`)' expected");
184 CurTok.Tok = TOK_STRCON;
185 SB_Copy (&CurTok.SVal, &Buf);
186 SB_Terminate (&CurTok.SVal);
189 /* Free the string buffer */
195 static void NoIdent (void)
196 /* Print an error message and skip the remainder of the line */
198 Error ("Argument of .IDENT is not a valid identifier");
204 static void FuncIdent (void)
205 /* Handle the .IDENT function */
207 StrBuf Buf = STATIC_STRBUF_INITIALIZER;
214 /* Left paren expected */
217 /* The function expects a string argument */
218 if (!LookAtStrCon ()) {
222 /* Check that the string contains a valid identifier. While doing so,
223 ** determine if it is a cheap local, or global one.
225 SB_Reset (&CurTok.SVal);
227 /* Check for a cheap local symbol */
228 if (SB_Peek (&CurTok.SVal) == LocalStart) {
229 SB_Skip (&CurTok.SVal);
230 Id = TOK_LOCAL_IDENT;
235 /* Next character must be a valid identifier start */
236 if (!IsIdStart (SB_Get (&CurTok.SVal))) {
240 for (I = SB_GetIndex (&CurTok.SVal); I < SB_GetLen (&CurTok.SVal); ++I) {
241 if (!IsIdChar (SB_AtUnchecked (&CurTok.SVal, I))) {
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.
253 SB_Copy (&Buf, &CurTok.SVal);
255 if (CurTok.Tok != TOK_RPAREN) {
256 Error ("`)' expected");
259 SB_Copy (&CurTok.SVal, &Buf);
260 SB_Terminate (&CurTok.SVal);
263 /* Free buffer memory */
269 static void FuncLeft (void)
270 /* Handle the .LEFT function */
278 /* Left paren expected */
281 /* Count argument. Correct negative counts to zero. */
282 Count = ConstExpression ();
288 /* Read the token list */
289 List = CollectTokens (0, (unsigned) Count);
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).
300 /* Insert it into the scanner feed */
301 PushTokList (List, ".LEFT");
303 /* Skip the current token */
309 static void FuncMid (void)
310 /* Handle the .MID function */
319 /* Left paren expected */
322 /* Start argument. Since the start argument can get negative with
323 ** expressions like ".tcount(arg)-2", we correct it to zero silently.
325 Start = ConstExpression ();
326 if (Start < 0 || Start > 100) {
331 /* Count argument. Similar as above, we will accept negative counts and
332 ** correct them to zero silently.
334 Count = ConstExpression ();
340 /* Read the token list */
341 List = CollectTokens ((unsigned) Start, (unsigned) Count);
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).
352 /* Insert it into the scanner feed */
353 PushTokList (List, ".MID");
355 /* Skip the current token */
361 static void FuncRight (void)
362 /* Handle the .RIGHT function */
370 /* Left paren expected */
373 /* Count argument. Correct negative counts to zero. */
374 Count = ConstExpression ();
380 /* Read the complete token list */
381 List = CollectTokens (0, 9999);
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;
388 /* Remove it from the list */
389 List->Root = List->Root->Next;
394 /* Corrent the token counter */
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).
407 /* Insert it into the scanner feed */
408 PushTokList (List, ".RIGHT");
410 /* Skip the current token */
416 static void InvalidFormatString (void)
417 /* Print an error message and skip the remainder of the line */
419 Error ("Invalid format string");
425 static void FuncSPrintF (void)
426 /* Handle the .SPRINTF function */
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 */
434 long IVal; /* Integer value */
438 /* Skip the .SPRINTF token */
441 /* Left paren expected */
444 /* First argument is a format string. Remember and skip it */
445 if (!LookAtStrCon ()) {
448 SB_Copy (&Format, &CurTok.SVal);
451 /* Walk over the format string, generating the function result in R */
454 /* Get the next char from the format string and check for EOS */
455 if (SB_Peek (&Format) == '\0') {
459 /* Check for a format specifier */
460 if (SB_Peek (&Format) != '%') {
461 /* No format specifier, just copy */
462 SB_AppendChar (&R, SB_Get (&Format));
466 if (SB_Peek (&Format) == '%') {
468 SB_AppendChar (&R, '%');
472 if (SB_Peek (&Format) == '\0') {
473 InvalidFormatString ();
477 /* Since a format specifier follows, we do expect another argument for
478 ** the .sprintf function.
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...
487 SB_AppendChar (&F1, '%');
489 /* Check for flags */
491 while ((C = SB_Peek (&Format)) != '\0' && !Done) {
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;
502 /* We do only support a numerical width field */
503 while (IsDigit (SB_Peek (&Format))) {
504 SB_AppendChar (&F1, SB_Get (&Format));
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));
515 /* Length modifiers aren't supported, so read the conversion specs */
516 switch (SB_Peek (&Format)) {
524 /* Our ints are actually longs, so we use the 'l' modifier when
525 ** calling xsprintf later. Terminate the format string.
527 SB_AppendChar (&F1, 'l');
528 SB_AppendChar (&F1, SB_Get (&Format));
531 /* The argument must be a constant expression */
532 IVal = ConstExpression ();
534 /* Format this argument according to the spec */
535 SB_Printf (&R1, SB_GetConstBuf (&F1), IVal);
537 /* Append the formatted argument to the result */
543 /* Add the format spec and terminate the format */
544 SB_AppendChar (&F1, SB_Get (&Format));
547 /* The argument must be a string constant */
548 if (!LookAtStrCon ()) {
550 SB_CopyStr (&CurTok.SVal, "**undefined**");
553 /* Format this argument according to the spec */
554 SB_Printf (&R1, SB_GetConstBuf (&F1), SB_GetConstBuf (&CurTok.SVal));
556 /* Skip the string constant */
559 /* Append the formatted argument to the result */
565 /* Add the format spec and terminate the format */
566 SB_AppendChar (&F1, SB_Get (&Format));
569 /* The argument must be a constant expression */
570 IVal = ConstExpression ();
572 /* Check for a valid character range */
573 if (IVal <= 0 || IVal > 255) {
574 Error ("Char argument out of range");
578 /* Format this argument according to the spec. Be sure to pass
579 ** an int as the char value.
581 SB_Printf (&R1, SB_GetConstBuf (&F1), (int) IVal);
583 /* Append the formatted argument to the result */
589 Error ("Invalid format string");
596 /* Terminate the result string */
599 /* We expect a closing parenthesis, but will not skip it but replace it
600 ** by the string token just created.
602 if (CurTok.Tok != TOK_RPAREN) {
603 Error ("`)' expected");
605 CurTok.Tok = TOK_STRCON;
606 SB_Copy (&CurTok.SVal, &R);
607 SB_Terminate (&CurTok.SVal);
611 /* Delete the string buffers */
620 static void FuncString (void)
621 /* Handle the .STRING function */
623 StrBuf Buf = STATIC_STRBUF_INITIALIZER;
628 /* Left paren expected */
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);
636 } else if (CurTok.Tok == TOK_NAMESPACE || CurTok.Tok == TOK_IDENT) {
638 /* Parse a fully qualified symbol name. We cannot use
639 ** ParseScopedSymName here since the name may be invalid.
643 NameSpace = (CurTok.Tok == TOK_NAMESPACE);
645 SB_AppendStr (&Buf, "::");
647 SB_Append (&Buf, &CurTok.SVal);
650 } while ((NameSpace != 0 && CurTok.Tok == TOK_IDENT) ||
651 (NameSpace == 0 && CurTok.Tok == TOK_NAMESPACE));
654 /* Numeric expression */
655 long Val = ConstExpression ();
656 SB_Printf (&Buf, "%ld", Val);
659 /* We expect a closing parenthesis, but will not skip it but replace it
660 ** by the string token just created.
662 if (CurTok.Tok != TOK_RPAREN) {
663 Error ("`)' expected");
665 CurTok.Tok = TOK_STRCON;
666 SB_Copy (&CurTok.SVal, &Buf);
667 SB_Terminate (&CurTok.SVal);
670 /* Free string memory */
677 /* Get next token and handle token level functions */
679 /* Get the next raw token */
682 /* In raw mode, or when output is suppressed via conditional assembly,
683 ** pass the token unchanged.
685 if (RawMode == 0 && IfCond) {
687 /* Execute token handling functions */
688 switch (CurTok.Tok) {
728 void Consume (token_t Expected, const char* ErrMsg)
729 /* Consume Expected, print an error if we don't find it */
731 if (CurTok.Tok == Expected) {
734 Error ("%s", ErrMsg);
740 void ConsumeSep (void)
741 /* Consume a separator token */
743 /* We expect a separator token */
746 /* If we are at end of line, skip it */
747 if (CurTok.Tok == TOK_SEP) {
754 void ConsumeLParen (void)
755 /* Consume a left paren */
757 Consume (TOK_LPAREN, "`(' expected");
762 void ConsumeRParen (void)
763 /* Consume a right paren */
765 Consume (TOK_RPAREN, "`)' expected");
770 void ConsumeComma (void)
771 /* Consume a comma */
773 Consume (TOK_COMMA, "`,' expected");
778 void SkipUntilSep (void)
779 /* Skip tokens until we reach a line separator or end of file */
781 while (!TokIsSep (CurTok.Tok)) {
788 void ExpectSep (void)
789 /* Check if we've reached a line separator, and output an error if not. Do
790 ** not skip the line separator.
793 if (!TokIsSep (CurTok.Tok)) {
794 ErrorSkip ("Unexpected trailing garbage characters");
800 void 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
805 ** Calls to EnterRawTokenMode and LeaveRawTokenMode may be nested.
813 void LeaveRawTokenMode (void)
814 /* Leave raw token mode. */
816 PRECONDITION (RawMode > 0);