1 /*****************************************************************************/
5 /* Get next token and handle token level functions */
9 /* (C) 2000-2009, 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 /*****************************************************************************/
54 /*****************************************************************************/
56 /*****************************************************************************/
60 static unsigned RawMode = 0; /* Raw token mode flag/counter */
64 /*****************************************************************************/
66 /*****************************************************************************/
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
76 if (Tok != TOK_STRCON) {
77 Error ("String constant expected");
87 /*****************************************************************************/
89 /*****************************************************************************/
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.
101 /* Create the token list */
102 TokList* List = NewTokList ();
104 /* Determine if the list is enclosed in curly braces. */
105 Token Term = GetTokListTerm (TOK_RPAREN);
107 /* Read the token list */
108 unsigned Current = 0;
109 while (Tok != Term) {
111 /* Check for end of line or end of input */
112 if (TokIsSep (Tok)) {
113 Error ("Unexpected end of line");
117 /* Collect tokens in the given range */
118 if (Current >= Start && Current < Start+Count) {
119 /* Add the current token to the list */
123 /* Get the next token */
128 /* Eat the terminator token */
131 /* If the list was enclosed in curly braces, we do expect now a right paren */
132 if (Term == TOK_RCURLY) {
136 /* Return the list of collected tokens */
142 static void FuncConcat (void)
143 /* Handle the .CONCAT function */
145 StrBuf Buf = STATIC_STRBUF_INITIALIZER;
150 /* Left paren expected */
153 /* Concatenate any number of strings */
156 /* Next token must be a string */
157 if (!LookAtStrCon ()) {
162 /* Append the string */
163 SB_Append (&Buf, &SVal);
165 /* Skip the string token */
168 /* Comma means another argument */
169 if (Tok == TOK_COMMA) {
177 /* We expect a closing parenthesis, but will not skip it but replace it
178 * by the string token just created.
180 if (Tok != TOK_RPAREN) {
181 Error ("`)' expected");
184 SB_Copy (&SVal, &Buf);
187 /* Free the string buffer */
193 static void NoIdent (void)
194 /* Print an error message and skip the remainder of the line */
196 Error ("Argument of .IDENT is not a valid identifier");
202 static void FuncIdent (void)
203 /* Handle the .IDENT function */
205 StrBuf Buf = STATIC_STRBUF_INITIALIZER;
212 /* Left paren expected */
215 /* The function expects a string argument */
216 if (!LookAtStrCon ()) {
220 /* Check that the string contains a valid identifier. While doing so,
221 * determine if it is a cheap local, or global one.
225 /* Check for a cheap local symbol */
226 if (SB_Peek (&SVal) == LocalStart) {
228 Id = TOK_LOCAL_IDENT;
233 /* Next character must be a valid identifier start */
234 if (!IsIdStart (SB_Get (&SVal))) {
238 for (I = SB_GetIndex (&SVal); I < SB_GetLen (&SVal); ++I) {
239 if (!IsIdChar (SB_AtUnchecked (&SVal, I))) {
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.
251 SB_Copy (&Buf, &SVal);
253 if (Tok != TOK_RPAREN) {
254 Error ("`)' expected");
257 SB_Copy (&SVal, &Buf);
260 /* Free buffer memory */
266 static void FuncLeft (void)
267 /* Handle the .LEFT function */
275 /* Left paren expected */
278 /* Count argument. Correct negative counts to zero. */
279 Count = ConstExpression ();
285 /* Read the token list */
286 List = CollectTokens (0, (unsigned) Count);
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).
297 /* Insert it into the scanner feed */
298 PushTokList (List, ".LEFT");
300 /* Skip the current token */
306 static void FuncMid (void)
307 /* Handle the .MID function */
316 /* Left paren expected */
319 /* Start argument. Since the start argument can get negative with
320 * expressions like ".tcount(arg)-2", we correct it to zero silently.
322 Start = ConstExpression ();
323 if (Start < 0 || Start > 100) {
328 /* Count argument. Similar as above, we will accept negative counts and
329 * correct them to zero silently.
331 Count = ConstExpression ();
337 /* Read the token list */
338 List = CollectTokens ((unsigned) Start, (unsigned) Count);
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).
349 /* Insert it into the scanner feed */
350 PushTokList (List, ".MID");
352 /* Skip the current token */
358 static void FuncRight (void)
359 /* Handle the .RIGHT function */
367 /* Left paren expected */
370 /* Count argument. Correct negative counts to zero. */
371 Count = ConstExpression ();
377 /* Read the complete token list */
378 List = CollectTokens (0, 9999);
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;
385 /* Remove it from the list */
386 List->Root = List->Root->Next;
391 /* Corrent the token counter */
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).
404 /* Insert it into the scanner feed */
405 PushTokList (List, ".RIGHT");
407 /* Skip the current token */
413 static void InvalidFormatString (void)
414 /* Print an error message and skip the remainder of the line */
416 Error ("Invalid format string");
422 static void FuncSPrintF (void)
423 /* Handle the .SPRINTF function */
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 */
431 long IVal; /* Integer value */
435 /* Skip the .SPRINTF token */
438 /* Left paren expected */
441 /* First argument is a format string. Remember and skip it */
442 if (!LookAtStrCon ()) {
445 SB_Copy (&Format, &SVal);
448 /* Walk over the format string, generating the function result in R */
451 /* Get the next char from the format string and check for EOS */
452 if (SB_Peek (&Format) == '\0') {
456 /* Check for a format specifier */
457 if (SB_Peek (&Format) != '%') {
458 /* No format specifier, just copy */
459 SB_AppendChar (&R, SB_Get (&Format));
463 if (SB_Peek (&Format) == '%') {
465 SB_AppendChar (&R, '%');
469 if (SB_Peek (&Format) == '\0') {
470 InvalidFormatString ();
474 /* Since a format specifier follows, we do expect anotehr argument for
475 * the .sprintf function.
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...
484 SB_AppendChar (&F1, '%');
486 /* Check for flags */
488 while ((C = SB_Peek (&Format)) != '\0' && !Done) {
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;
499 /* We do only support a numerical width field */
500 while (IsDigit (SB_Peek (&Format))) {
501 SB_AppendChar (&F1, SB_Get (&Format));
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));
512 /* Length modifiers aren't supported, so read the conversion specs */
513 switch (SB_Peek (&Format)) {
521 /* Our ints are actually longs, so we use the 'l' modifier when
522 * calling xsprintf later. Terminate the format string.
524 SB_AppendChar (&F1, 'l');
525 SB_AppendChar (&F1, SB_Get (&Format));
528 /* The argument must be a constant expression */
529 IVal = ConstExpression ();
531 /* Format this argument according to the spec */
532 SB_Printf (&R1, SB_GetConstBuf (&F1), IVal);
534 /* Append the formatted argument to the result */
540 /* Add the format spec and terminate the format */
541 SB_AppendChar (&F1, SB_Get (&Format));
544 /* The argument must be a string constant */
545 if (!LookAtStrCon ()) {
547 SB_CopyStr (&SVal, "**undefined**");
550 /* Format this argument according to the spec */
551 SB_Printf (&R1, SB_GetConstBuf (&F1), SVal);
553 /* Skip the string constant */
556 /* Append the formatted argument to the result */
562 /* Add the format spec and terminate the format */
563 SB_AppendChar (&F1, SB_Get (&Format));
566 /* The argument must be a constant expression */
567 IVal = ConstExpression ();
569 /* Check for a valid character range */
570 if (IVal <= 0 || IVal > 255) {
571 Error ("Char argument out of range");
575 /* Format this argument according to the spec. Be sure to pass
576 * an int as the char value.
578 SB_Printf (&R1, SB_GetConstBuf (&F1), (int) IVal);
580 /* Append the formatted argument to the result */
586 Error ("Invalid format string");
593 /* Terminate the result string */
596 /* We expect a closing parenthesis, but will not skip it but replace it
597 * by the string token just created.
599 if (Tok != TOK_RPAREN) {
600 Error ("`)' expected");
607 /* Delete the string buffers */
616 static void FuncString (void)
617 /* Handle the .STRING function */
619 StrBuf Buf = STATIC_STRBUF_INITIALIZER;
624 /* Left paren expected */
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);
633 /* Numeric expression */
634 long Val = ConstExpression ();
635 SB_Printf (&Buf, "%ld", Val);
638 /* We expect a closing parenthesis, but will not skip it but replace it
639 * by the string token just created.
641 if (Tok != TOK_RPAREN) {
642 Error ("`)' expected");
645 SB_Copy (&SVal, &Buf);
648 /* Free string memory */
655 /* Get next token and handle token level functions */
657 /* Get the next raw token */
660 /* In raw mode, pass the token unchanged */
663 /* Execute token handling functions */
704 void Consume (Token Expected, const char* ErrMsg)
705 /* Consume Expected, print an error if we don't find it */
707 if (Tok == Expected) {
710 Error ("%s", ErrMsg);
716 void ConsumeSep (void)
717 /* Consume a separator token */
719 /* We expect a separator token */
722 /* If we are at end of line, skip it */
723 if (Tok == TOK_SEP) {
730 void ConsumeLParen (void)
731 /* Consume a left paren */
733 Consume (TOK_LPAREN, "`(' expected");
738 void ConsumeRParen (void)
739 /* Consume a right paren */
741 Consume (TOK_RPAREN, "`)' expected");
746 void ConsumeComma (void)
747 /* Consume a comma */
749 Consume (TOK_COMMA, "`,' expected");
754 void SkipUntilSep (void)
755 /* Skip tokens until we reach a line separator or end of file */
757 while (!TokIsSep (Tok)) {
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.
769 if (!TokIsSep (Tok)) {
770 ErrorSkip ("Unexpected trailing garbage characters");
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
781 * Calls to EnterRawTokenMode and LeaveRawTokenMode may be nested.
789 void LeaveRawTokenMode (void)
790 /* Leave raw token mode. */
792 PRECONDITION (RawMode > 0);