+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), SB_GetConstBuf (&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);
+ SB_Terminate (&CurTok.SVal);
+ }
+
+
+ /* Delete the string buffers */
+ SB_Done (&Format);
+ SB_Done (&R);
+ SB_Done (&F1);
+ SB_Done (&R1);
+}
+
+
+