/* */
/* */
/* */
-/* (C) 2000-2003 Ullrich von Bassewitz */
-/* Römerstraße 52 */
-/* D-70794 Filderstadt */
-/* EMail: uz@cc65.org */
+/* (C) 2000-2011, Ullrich von Bassewitz */
+/* Roemerstrasse 52 */
+/* D-70794 Filderstadt */
+/* EMail: uz@cc65.org */
/* */
/* */
/* This software is provided 'as-is', without any expressed or implied */
#include <string.h>
/* common */
+#include "chartype.h"
#include "check.h"
+#include "strbuf.h"
/* ca65 */
#include "error.h"
#include "expr.h"
+#include "global.h"
#include "scanner.h"
#include "toklist.h"
#include "nexttok.h"
+/*****************************************************************************/
+/* Error handling */
+/*****************************************************************************/
+
+
+
+static int LookAtStrCon (void)
+/* Make sure the next token is a string constant. If not, print an error
+ * messages skip the remainder of the line and return false. Otherwise return
+ * true.
+ */
+{
+ if (CurTok.Tok != TOK_STRCON) {
+ Error ("String constant expected");
+ SkipUntilSep ();
+ return 0;
+ } else {
+ return 1;
+ }
+}
+
+
+
/*****************************************************************************/
/* Code */
/*****************************************************************************/
static TokList* CollectTokens (unsigned Start, unsigned Count)
-/* Read a list of tokens that is terminated by a right paren. For all tokens
- * starting at the one with index Start, and ending at (Start+Count-1), place
- * them into a token list, and return this token list.
+/* Read a list of tokens that is optionally enclosed in curly braces and
+ * terminated by a right paren. For all tokens starting at the one with index
+ * Start, and ending at (Start+Count-1), place them into a token list, and
+ * return this token list.
*/
{
+
/* Create the token list */
TokList* List = NewTokList ();
+ /* Determine if the list is enclosed in curly braces. */
+ token_t Term = GetTokListTerm (TOK_RPAREN);
+
/* Read the token list */
unsigned Current = 0;
- unsigned Parens = 0;
- while (Parens != 0 || Tok != TOK_RPAREN) {
+ while (CurTok.Tok != Term) {
/* Check for end of line or end of input */
- if (TokIsSep (Tok)) {
+ if (TokIsSep (CurTok.Tok)) {
Error ("Unexpected end of line");
return List;
}
AddCurTok (List);
}
- /* Check for and count parenthesii */
- if (Tok == TOK_LPAREN) {
- ++Parens;
- } else if (Tok == TOK_RPAREN) {
- --Parens;
- }
-
/* Get the next token */
++Current;
NextTok ();
}
- /* Eat the closing paren */
- ConsumeRParen ();
+ /* Eat the terminator token */
+ NextTok ();
+
+ /* If the list was enclosed in curly braces, we do expect now a right paren */
+ if (Term == TOK_RCURLY) {
+ ConsumeRParen ();
+ }
/* Return the list of collected tokens */
return List;
static void FuncConcat (void)
/* Handle the .CONCAT function */
{
- char Buf[MAX_STR_LEN+1];
- char* B;
- unsigned Length;
- unsigned L;
+ StrBuf Buf = STATIC_STRBUF_INITIALIZER;
/* Skip it */
NextTok ();
ConsumeLParen ();
/* Concatenate any number of strings */
- B = Buf;
- B[0] = '\0';
- Length = 0;
while (1) {
/* Next token must be a string */
- if (Tok != TOK_STRCON) {
- Error ("String constant expected");
- SkipUntilSep ();
- return;
- }
-
- /* Get the length of the string const and check total length */
- L = strlen (SVal);
- if (Length + L > MAX_STR_LEN) {
- Error ("String is too long");
- /* Try to recover */
- SkipUntilSep ();
+ if (!LookAtStrCon ()) {
+ SB_Done (&Buf);
return;
}
- /* Add the new string */
- memcpy (B, SVal, L);
- Length += L;
- B += L;
+ /* Append the string */
+ SB_Append (&Buf, &CurTok.SVal);
/* Skip the string token */
NextTok ();
/* Comma means another argument */
- if (Tok == TOK_COMMA) {
+ if (CurTok.Tok == TOK_COMMA) {
NextTok ();
} else {
/* Done */
}
}
- /* Terminate the string */
- *B = '\0';
-
/* We expect a closing parenthesis, but will not skip it but replace it
* by the string token just created.
*/
- if (Tok != TOK_RPAREN) {
+ if (CurTok.Tok != TOK_RPAREN) {
+ Error ("`)' expected");
+ } else {
+ CurTok.Tok = TOK_STRCON;
+ SB_Copy (&CurTok.SVal, &Buf);
+ }
+
+ /* Free the string buffer */
+ SB_Done (&Buf);
+}
+
+
+
+static void NoIdent (void)
+/* Print an error message and skip the remainder of the line */
+{
+ Error ("Argument of .IDENT is not a valid identifier");
+ SkipUntilSep ();
+}
+
+
+
+static void FuncIdent (void)
+/* Handle the .IDENT function */
+{
+ StrBuf Buf = STATIC_STRBUF_INITIALIZER;
+ token_t Id;
+ unsigned I;
+
+ /* Skip it */
+ NextTok ();
+
+ /* Left paren expected */
+ ConsumeLParen ();
+
+ /* The function expects a string argument */
+ if (!LookAtStrCon ()) {
+ return;
+ }
+
+ /* Check that the string contains a valid identifier. While doing so,
+ * determine if it is a cheap local, or global one.
+ */
+ SB_Reset (&CurTok.SVal);
+
+ /* Check for a cheap local symbol */
+ if (SB_Peek (&CurTok.SVal) == LocalStart) {
+ SB_Skip (&CurTok.SVal);
+ Id = TOK_LOCAL_IDENT;
+ } else {
+ Id = TOK_IDENT;
+ }
+
+ /* Next character must be a valid identifier start */
+ if (!IsIdStart (SB_Get (&CurTok.SVal))) {
+ NoIdent ();
+ return;
+ }
+ for (I = SB_GetIndex (&CurTok.SVal); I < SB_GetLen (&CurTok.SVal); ++I) {
+ if (!IsIdChar (SB_AtUnchecked (&CurTok.SVal, I))) {
+ NoIdent ();
+ return;
+ }
+ }
+ if (IgnoreCase) {
+ UpcaseSVal ();
+ }
+
+ /* If anything is ok, save and skip the string. Check that the next token
+ * is a right paren, then replace the token by an identifier token.
+ */
+ SB_Copy (&Buf, &CurTok.SVal);
+ NextTok ();
+ if (CurTok.Tok != TOK_RPAREN) {
Error ("`)' expected");
} else {
- Tok = TOK_STRCON;
- strcpy (SVal, Buf);
+ CurTok.Tok = Id;
+ SB_Copy (&CurTok.SVal, &Buf);
}
+
+ /* Free buffer memory */
+ SB_Done (&Buf);
}
/* Left paren expected */
ConsumeLParen ();
- /* Count argument */
+ /* Count argument. Correct negative counts to zero. */
Count = ConstExpression ();
- if (Count < 0 || Count > 100) {
- Error ("Range error");
- Count = 1;
+ if (Count < 0) {
+ Count = 0;
}
ConsumeComma ();
/* Left paren expected */
ConsumeLParen ();
- /* Start argument */
+ /* Start argument. Since the start argument can get negative with
+ * expressions like ".tcount(arg)-2", we correct it to zero silently.
+ */
Start = ConstExpression ();
if (Start < 0 || Start > 100) {
- Error ("Range error");
Start = 0;
}
ConsumeComma ();
- /* Count argument */
+ /* Count argument. Similar as above, we will accept negative counts and
+ * correct them to zero silently.
+ */
Count = ConstExpression ();
- if (Count < 0 || Count > 100) {
- Error ("Range error");
- Count = 1;
+ if (Count < 0) {
+ Count = 0;
}
ConsumeComma ();
/* Left paren expected */
ConsumeLParen ();
- /* Count argument */
+ /* Count argument. Correct negative counts to zero. */
Count = ConstExpression ();
- if (Count < 0 || Count > 100) {
- Error ("Range error");
- Count = 1;
+ if (Count < 0) {
+ Count = 0;
}
ConsumeComma ();
+static void InvalidFormatString (void)
+/* Print an error message and skip the remainder of the line */
+{
+ Error ("Invalid format string");
+ SkipUntilSep ();
+}
+
+
+
+static void FuncSPrintF (void)
+/* Handle the .SPRINTF function */
+{
+ StrBuf Format = STATIC_STRBUF_INITIALIZER; /* User supplied format */
+ StrBuf R = STATIC_STRBUF_INITIALIZER; /* Result string */
+ StrBuf F1 = STATIC_STRBUF_INITIALIZER; /* One format spec from F */
+ StrBuf R1 = STATIC_STRBUF_INITIALIZER; /* One result */
+ char C;
+ int Done;
+ long IVal; /* Integer value */
+
+
+
+ /* Skip the .SPRINTF token */
+ NextTok ();
+
+ /* Left paren expected */
+ ConsumeLParen ();
+
+ /* First argument is a format string. Remember and skip it */
+ if (!LookAtStrCon ()) {
+ return;
+ }
+ SB_Copy (&Format, &CurTok.SVal);
+ NextTok ();
+
+ /* Walk over the format string, generating the function result in R */
+ while (1) {
+
+ /* Get the next char from the format string and check for EOS */
+ if (SB_Peek (&Format) == '\0') {
+ break;
+ }
+
+ /* Check for a format specifier */
+ if (SB_Peek (&Format) != '%') {
+ /* No format specifier, just copy */
+ SB_AppendChar (&R, SB_Get (&Format));
+ continue;
+ }
+ SB_Skip (&Format);
+ if (SB_Peek (&Format) == '%') {
+ /* %% */
+ SB_AppendChar (&R, '%');
+ SB_Skip (&Format);
+ continue;
+ }
+ if (SB_Peek (&Format) == '\0') {
+ InvalidFormatString ();
+ break;
+ }
+
+ /* Since a format specifier follows, we do expect anotehr argument for
+ * the .sprintf function.
+ */
+ ConsumeComma ();
+
+ /* We will copy the format spec into F1 checking for the things we
+ * support, and later use xsprintf to do the actual formatting. This
+ * is easier than adding another printf implementation...
+ */
+ SB_Clear (&F1);
+ SB_AppendChar (&F1, '%');
+
+ /* Check for flags */
+ Done = 0;
+ while ((C = SB_Peek (&Format)) != '\0' && !Done) {
+ switch (C) {
+ case '-': /* FALLTHROUGH */
+ case '+': /* FALLTHROUGH */
+ case ' ': /* FALLTHROUGH */
+ case '#': /* FALLTHROUGH */
+ case '0': SB_AppendChar (&F1, SB_Get (&Format)); break;
+ default: Done = 1; break;
+ }
+ }
+
+ /* We do only support a numerical width field */
+ while (IsDigit (SB_Peek (&Format))) {
+ SB_AppendChar (&F1, SB_Get (&Format));
+ }
+
+ /* Precision - only positive numerical fields supported */
+ if (SB_Peek (&Format) == '.') {
+ SB_AppendChar (&F1, SB_Get (&Format));
+ while (IsDigit (SB_Peek (&Format))) {
+ SB_AppendChar (&F1, SB_Get (&Format));
+ }
+ }
+
+ /* Length modifiers aren't supported, so read the conversion specs */
+ switch (SB_Peek (&Format)) {
+
+ case 'd':
+ case 'i':
+ case 'o':
+ case 'u':
+ case 'X':
+ case 'x':
+ /* Our ints are actually longs, so we use the 'l' modifier when
+ * calling xsprintf later. Terminate the format string.
+ */
+ SB_AppendChar (&F1, 'l');
+ SB_AppendChar (&F1, SB_Get (&Format));
+ SB_Terminate (&F1);
+
+ /* The argument must be a constant expression */
+ IVal = ConstExpression ();
+
+ /* Format this argument according to the spec */
+ SB_Printf (&R1, SB_GetConstBuf (&F1), IVal);
+
+ /* Append the formatted argument to the result */
+ SB_Append (&R, &R1);
+
+ break;
+
+ case 's':
+ /* Add the format spec and terminate the format */
+ SB_AppendChar (&F1, SB_Get (&Format));
+ SB_Terminate (&F1);
+
+ /* The argument must be a string constant */
+ if (!LookAtStrCon ()) {
+ /* Make it one */
+ SB_CopyStr (&CurTok.SVal, "**undefined**");
+ }
+
+ /* Format this argument according to the spec */
+ SB_Printf (&R1, SB_GetConstBuf (&F1), CurTok.SVal);
+
+ /* Skip the string constant */
+ NextTok ();
+
+ /* Append the formatted argument to the result */
+ SB_Append (&R, &R1);
+
+ break;
+
+ case 'c':
+ /* Add the format spec and terminate the format */
+ SB_AppendChar (&F1, SB_Get (&Format));
+ SB_Terminate (&F1);
+
+ /* The argument must be a constant expression */
+ IVal = ConstExpression ();
+
+ /* Check for a valid character range */
+ if (IVal <= 0 || IVal > 255) {
+ Error ("Char argument out of range");
+ IVal = ' ';
+ }
+
+ /* Format this argument according to the spec. Be sure to pass
+ * an int as the char value.
+ */
+ SB_Printf (&R1, SB_GetConstBuf (&F1), (int) IVal);
+
+ /* Append the formatted argument to the result */
+ SB_Append (&R, &R1);
+
+ break;
+
+ default:
+ Error ("Invalid format string");
+ SB_Skip (&Format);
+ break;
+ }
+
+ }
+
+ /* Terminate the result string */
+ SB_Terminate (&R);
+
+ /* We expect a closing parenthesis, but will not skip it but replace it
+ * by the string token just created.
+ */
+ if (CurTok.Tok != TOK_RPAREN) {
+ Error ("`)' expected");
+ } else {
+ CurTok.Tok = TOK_STRCON;
+ SB_Copy (&CurTok.SVal, &R);
+ }
+
+
+ /* Delete the string buffers */
+ SB_Done (&Format);
+ SB_Done (&R);
+ SB_Done (&F1);
+ SB_Done (&R1);
+}
+
+
+
static void FuncString (void)
/* Handle the .STRING function */
{
- char Buf[MAX_STR_LEN+1];
+ StrBuf Buf = STATIC_STRBUF_INITIALIZER;
/* Skip it */
NextTok ();
ConsumeLParen ();
/* Accept identifiers or numeric expressions */
- if (Tok == TOK_IDENT) {
+ if (CurTok.Tok == TOK_IDENT || CurTok.Tok == TOK_LOCAL_IDENT) {
/* Save the identifier, then skip it */
- strcpy (Buf, SVal);
+ SB_Copy (&Buf, &CurTok.SVal);
NextTok ();
} else {
/* Numeric expression */
long Val = ConstExpression ();
- sprintf (Buf, "%ld", Val);
+ SB_Printf (&Buf, "%ld", Val);
}
/* We expect a closing parenthesis, but will not skip it but replace it
* by the string token just created.
*/
- if (Tok != TOK_RPAREN) {
+ if (CurTok.Tok != TOK_RPAREN) {
Error ("`)' expected");
} else {
- Tok = TOK_STRCON;
- strcpy (SVal, Buf);
+ CurTok.Tok = TOK_STRCON;
+ SB_Copy (&CurTok.SVal, &Buf);
}
+
+ /* Free string memory */
+ SB_Done (&Buf);
}
if (RawMode == 0) {
/* Execute token handling functions */
- switch (Tok) {
+ switch (CurTok.Tok) {
case TOK_CONCAT:
FuncConcat ();
FuncLeft ();
break;
+ case TOK_MAKEIDENT:
+ FuncIdent ();
+ break;
+
case TOK_MID:
FuncMid ();
break;
FuncRight ();
break;
+ case TOK_SPRINTF:
+ FuncSPrintF ();
+ break;
+
case TOK_STRING:
FuncString ();
break;
-void Consume (enum Token Expected, const char* ErrMsg)
+void Consume (token_t Expected, const char* ErrMsg)
/* Consume Expected, print an error if we don't find it */
{
- if (Tok == Expected) {
+ if (CurTok.Tok == Expected) {
NextTok ();
} else {
- Error (ErrMsg);
+ Error ("%s", ErrMsg);
}
}
void ConsumeSep (void)
/* Consume a separator token */
{
- /* Accept an EOF as separator */
- if (Tok != TOK_EOF) {
- if (Tok != TOK_SEP) {
- Error ("Too many characters");
- SkipUntilSep ();
- } else {
- NextTok ();
- }
+ /* We expect a separator token */
+ ExpectSep ();
+
+ /* If we are at end of line, skip it */
+ if (CurTok.Tok == TOK_SEP) {
+ NextTok ();
}
}
void SkipUntilSep (void)
/* Skip tokens until we reach a line separator or end of file */
{
- while (!TokIsSep (Tok)) {
+ while (!TokIsSep (CurTok.Tok)) {
NextTok ();
}
}
+void ExpectSep (void)
+/* Check if we've reached a line separator, and output an error if not. Do
+ * not skip the line separator.
+ */
+{
+ if (!TokIsSep (CurTok.Tok)) {
+ ErrorSkip ("Unexpected trailing garbage characters");
+ }
+}
+
+
+
void EnterRawTokenMode (void)
/* Enter raw token mode. In raw mode, token handling functions are not
* executed, but the function tokens are passed untouched to the upper