]> git.sur5r.net Git - cc65/commitdiff
Adding functionality to StrBuf
authorcuz <cuz@b7a2c559-68d2-44c3-8de9-860c34a00d81>
Sun, 9 Sep 2001 20:49:20 +0000 (20:49 +0000)
committercuz <cuz@b7a2c559-68d2-44c3-8de9-860c34a00d81>
Sun, 9 Sep 2001 20:49:20 +0000 (20:49 +0000)
git-svn-id: svn://svn.cc65.org/cc65/trunk@885 b7a2c559-68d2-44c3-8de9-860c34a00d81

src/cc65/asmstmt.c
src/cc65/codegen.c
src/cc65/codegen.h
src/cc65/expr.c
src/cc65/expr.h
src/cc65/litpool.c
src/cc65/litpool.h
src/cc65/scanner.c
src/cc65/scanner.h
src/common/strbuf.c
src/common/strbuf.h

index 76f5596d92a4b75971ef84016a60f3446e99eaf9..b34f78591dc26b9cb1e160561237993c4a5605ee 100644 (file)
 
 #include <string.h>
 
-/* cc65 */         
+/* common */
+#include "xsprintf.h"
+
+/* cc65 */
 #include "codegen.h"
+#include "datatype.h"
 #include "error.h"
+#include "expr.h"
+#include "function.h"
 #include "litpool.h"
 #include "scanner.h"
+#include "symtab.h"
 #include "asmstmt.h"
 
 
 
 /*****************************************************************************/
-/*                                  Code                                    */
+/*                                  Code                                    */
 /*****************************************************************************/
 
 
 
+static void AsmRangeError (unsigned Arg)
+/* Print a diagnostic about a range error in the argument with the given number */
+{
+    Error ("Range error in argument %u", Arg);
+}
+
+
+
+static void AsmErrorSkip (void)
+/* Called in case of an error, skips tokens until the closing paren or a
+ * semicolon is reached.
+ */
+{
+    static const token_t TokenList[] = { TOK_RPAREN, TOK_SEMI };
+    SkipTokens (TokenList, sizeof(TokenList) / sizeof(TokenList[0]));
+}
+
+
+
+static SymEntry* AsmGetSym (unsigned Arg, unsigned Type)
+/* Find the symbol with the name currently in NextTok. The symbol must be of
+ * the given type. On errors, NULL is returned.
+ */
+{
+    SymEntry* Sym;
+
+    /* We expect an argument separated by a comma */
+    ConsumeComma ();
+
+    /* Argument must be an identifier */
+    if (CurTok.Tok != TOK_IDENT) {
+               Error ("Identifier expected for argument %u", Arg);
+       AsmErrorSkip ();
+               return 0;
+    }
+
+    /* Get a pointer to the symbol table entry */
+    Sym = FindSym (CurTok.Ident);
+
+    /* Did we find a symbol with this name? */
+    if (Sym == 0) {
+       Error ("Undefined symbol `%s' for argument %u", CurTok.Ident, Arg);
+       AsmErrorSkip ();
+       return 0;
+    }
+
+    /* We found the symbol - skip the name token */
+    NextToken ();
+
+    /* Check if we have a global symbol */
+    if ((Sym->Flags & Type) != Type) {
+               Error ("Type of argument %u differs from format specifier", Arg);
+       AsmErrorSkip ();
+       return 0;
+    }
+
+    /* Mark the symbol as referenced */
+    Sym->Flags |= SC_REF;
+
+    /* Return it */
+    return Sym;
+}
+
+
+
+static void ParseByteArg (StrBuf* T, unsigned Arg)
+/* Parse the %b format specifier */
+{
+    ExprDesc Expr;
+    char     Buf [16];
+
+    /* We expect an argument separated by a comma */
+    ConsumeComma ();
+
+    /* Evaluate the expression */
+    ConstSubExpr (hie1, &Expr);
+
+    /* Check the range but allow negative values if the type is signed */
+    if (IsSignUnsigned (Expr.Type)) {
+       if (Expr.ConstVal < 0 || Expr.ConstVal > 0xFF) {
+           AsmRangeError (Arg);
+           Expr.ConstVal = 0;
+       }
+    } else {
+       if (Expr.ConstVal < -128 || Expr.ConstVal > 127) {
+           AsmRangeError (Arg);
+           Expr.ConstVal = 0;
+       }
+    }
+
+    /* Convert into a hex number */
+    xsprintf (Buf, sizeof (Buf), "$%02lX", Expr.ConstVal & 0xFF);
+
+    /* Add the number to the target buffer */
+    SB_AppendStr (T, Buf);
+}
+
+
+
+static void ParseWordArg (StrBuf* T, unsigned Arg)
+/* Parse the %w format specifier */
+{
+    ExprDesc Expr;
+    char     Buf [16];
+
+    /* We expect an argument separated by a comma */
+    ConsumeComma ();
+
+    /* Evaluate the expression */
+    ConstSubExpr (hie1, &Expr);
+
+    /* Check the range but allow negative values if the type is signed */
+    if (IsSignUnsigned (Expr.Type)) {
+       if (Expr.ConstVal < 0 || Expr.ConstVal > 0xFFFF) {
+           AsmRangeError (Arg);
+           Expr.ConstVal = 0;
+       }
+    } else {
+       if (Expr.ConstVal < -32768 || Expr.ConstVal > 32767) {
+           AsmRangeError (Arg);
+           Expr.ConstVal = 0;
+       }
+    }
+
+    /* Convert into a hex number */
+    xsprintf (Buf, sizeof (Buf), "$%04lX", Expr.ConstVal & 0xFFFF);
+
+    /* Add the number to the target buffer */
+    SB_AppendStr (T, Buf);
+}
+
+
+
+static void ParseLongArg (StrBuf* T, unsigned Arg)
+/* Parse the %l format specifier */
+{
+    ExprDesc Expr;
+    char     Buf [16];
+
+    /* We expect an argument separated by a comma */
+    ConsumeComma ();
+
+    /* Evaluate the expression */
+    ConstSubExpr (hie1, &Expr);
+
+    /* Convert into a hex number */
+    xsprintf (Buf, sizeof (Buf), "$%08lX", Expr.ConstVal & 0xFFFFFFFF);
+
+    /* Add the number to the target buffer */
+    SB_AppendStr (T, Buf);
+}
+
+
+
+static void ParseGVarArg (StrBuf* T, unsigned Arg)
+/* Parse the %v format specifier */
+{
+    /* Parse the symbol name parameter and check the type */
+    SymEntry* Sym = AsmGetSym (Arg, SC_STATIC);
+    if (Sym == 0) {
+       /* Some sort of error */
+       return;
+    }
+
+    /* Check for external linkage */
+    if (Sym->Flags & (SC_EXTERN | SC_STORAGE)) {
+       /* External linkage */
+       /* ### FIXME: Asm name should be generated by codegen */
+       SB_AppendChar (T, '_');
+       SB_AppendStr (T, Sym->Name);
+    } else {
+       /* Static variable */
+       char Buf [16];
+       xsprintf (Buf, sizeof (Buf), "L%04X", Sym->V.Label);
+       SB_AppendStr (T, Buf);
+    }
+}
+
+
+
+static void ParseLVarArg (StrBuf* T, unsigned Arg)
+/* Parse the %o format specifier */
+{
+    unsigned Offs;
+    char Buf [16];
+
+    /* Parse the symbol name parameter and check the type */
+    SymEntry* Sym = AsmGetSym (Arg, SC_AUTO);
+    if (Sym == 0) {
+       /* Some sort of error */
+       return;
+    }
+
+    /* The symbol may be a parameter to a variadic function. In this case, we
+     * don't have a fixed stack offset, so check it and bail out with an error
+     * if this is the case.
+     */
+    if ((Sym->Flags & SC_PARAM) == SC_PARAM && IsVariadic (CurrentFunc)) {
+       Error ("Argument %u has no fixed stack offset", Arg);
+       AsmErrorSkip ();
+       return;
+    }
+
+    /* Calculate the current offset from SP */
+    Offs = Sym->V.Offs - oursp;
+
+    /* Output the offset */
+    xsprintf (Buf, sizeof (Buf), (Offs > 0xFF)? "$%04X" : "$%02X", Offs);
+    SB_AppendStr (T, Buf);
+}
+
+
+
+static void ParseAsm (void)
+/* Parse the contents of the ASM statement */
+{
+    unsigned I;
+    unsigned Arg;
+
+    /* Create a target string buffer */
+    StrBuf T = AUTO_STRBUF_INITIALIZER;
+
+    /* Create a string buffer from the string literal */
+    StrBuf S = AUTO_STRBUF_INITIALIZER;
+    GetLiteralStrBuf (&S, CurTok.IVal);
+
+    /* Reset the string pointer, effectivly clearing the string from the
+     * string table. Since we're working with one token lookahead, this
+     * will fail if the next token is also a string token, but that's a
+     * syntax error anyway, because we expect a right paren.
+     */
+    ResetLiteralPoolOffs (CurTok.IVal);
+
+    /* Skip the string token */
+    NextToken ();
+
+    /* Parse the statement. It may contain several lines and one or more
+     * of the following place holders:
+     *   %b    - Numerical 8 bit value
+     *   %w    - Numerical 16 bit value
+     *   %l    - Numerical 32 bit value
+     *   %v    - Assembler name of a (global) variable
+     *   %o    - Stack offset of a (local) variable
+     *   %%    - The % sign
+     */
+    I = 0;
+    Arg = 0;
+    while (I < SB_GetLen (&S)) {
+
+               /* Get the next character */
+               char C = SB_AtUnchecked (&S, I++);
+
+               /* If it is a newline, the current line is ready to go */
+               if (C == '\n') {
+
+                   /* Pass it to the backend and start over */
+                   g_asmcode (&T);
+                   SB_Clear (&T);
+
+               } else if (C == '%') {
+
+                   /* Format specifier */
+                   ++Arg;
+
+                   /* Check if we have characters left */
+                   if (I >= SB_GetLen (&S)) {
+                       Error ("Error in __asm__ format specifier %u", Arg);
+               AsmErrorSkip ();
+               goto Done;
+           } else {
+               C = SB_AtUnchecked (&S, I++);
+               switch (C) {
+                   case 'b':   ParseByteArg (&T, Arg);         break;
+                   case 'w':   ParseWordArg (&T, Arg);         break;
+                   case 'l':   ParseLongArg (&T, Arg);         break;
+                   case 'v':   ParseGVarArg (&T, Arg);         break;
+                   case 'o':   ParseLVarArg (&T, Arg);         break;
+                   case '%':   SB_AppendChar (&T, '%');        break;
+                   default:
+                       Error ("Error in __asm__ format specifier %u", Arg);
+                       AsmErrorSkip ();
+                       goto Done;
+               }
+           }
+
+       } else {
+
+           /* A normal character, just copy it */
+           SB_AppendChar (&T, C);
+
+       }
+    }
+
+    /* If the target buffer is not empty, we have a last line in there */
+    if (!SB_IsEmpty (&T)) {
+       g_asmcode (&T);
+    }
+
+Done:
+    /* Call the string buf destructors */
+    DoneStrBuf (&S);
+    DoneStrBuf (&T);
+}
+
+
+
 void AsmStatement (void)
 /* This function parses ASM statements. The syntax of the ASM directive
  * looks like the one defined for C++ (C has no ASM directive), that is,
@@ -64,39 +377,21 @@ void AsmStatement (void)
 
     /* String literal */
     if (CurTok.Tok != TOK_SCONST) {
+
+       /* Print a diagnostic */
        Error ("String literal expected");
+
+       /* Try some smart error recovery: Skip tokens until we reach the
+        * enclosing paren, or a semicolon.
+        */
+       AsmErrorSkip ();
+
     } else {
 
-       /* The string literal may consist of more than one line of assembler
-        * code. Separate the single lines and output the code.
-        */
-       const char* S = GetLiteral (CurTok.IVal);
-       while (*S) {
-
-                   /* Separate the lines */
-           const char* E = strchr (S, '\n');
-           if (E) {
-               /* Found a newline */
-               g_asmcode (S, E-S);
-               S = E+1;
-           } else {
-               int Len = strlen (S);
-               g_asmcode (S, Len);
-               S += Len;
-           }
-       }
-
-       /* Reset the string pointer, effectivly clearing the string from the
-        * string table. Since we're working with one token lookahead, this
-        * will fail if the next token is also a string token, but that's a
-        * syntax error anyway, because we expect a right paren.
-        */
-       ResetLiteralPoolOffs (CurTok.IVal);
+       /* Parse the ASM statement */
+       ParseAsm ();
     }
 
-    /* Skip the string token */
-    NextToken ();
-
     /* Closing paren needed */
     ConsumeRParen ();
 }
index 7732f7f3437f794049b623a23a099fd6633e4c84..1d537111b17c93f951d6e070c209cbd31b17a717 100644 (file)
@@ -39,6 +39,7 @@
 
 /* common */
 #include "check.h"
+#include "strbuf.h"
 #include "version.h"
 #include "xmalloc.h"
 #include "xsprintf.h"
@@ -3877,16 +3878,10 @@ void g_zerobytes (unsigned n)
 
 
 
-void g_asmcode (const char* Line, int Len)
-/* Output one line of assembler code. If Len is greater than zero, it is used
- * as the maximum number of characters to use from Line.
- */
+void g_asmcode (struct StrBuf* B)
+/* Output one line of assembler code. */
 {
-    if (Len >= 0) {
-       AddCodeLine ("%.*s", Len, Line);
-    } else {
-       AddCodeLine ("%s", Line);
-    }
+    AddCodeLine ("%.*s", SB_GetLen (B), SB_GetConstBuf (B));
 }
 
 
index 9d1420fca4412adf11fc2bce3c3f59ccd84fedef..91274ec42f48a5a5f21944e44606cddd8444756e 100644 (file)
@@ -84,6 +84,9 @@
 /* Compiler relative stackpointer */
 extern int oursp;
 
+/* Forward */
+struct StrBuf;
+
 
 
 /*****************************************************************************/
@@ -437,10 +440,8 @@ void g_zerobytes (unsigned n);
 
 
 
-void g_asmcode (const char* Line, int Len);
-/* Output one line of assembler code. If Len is greater than zero, it is used
- * as the maximum number of characters to use from Line.
- */
+void g_asmcode (struct StrBuf* B);
+/* Output one line of assembler code. */
 
 
 
index 46484528e925977bbad457ddd9b0bb2c586610f6..d059da243311479f61e4fadb4e3a39a5b5b5888e 100644 (file)
@@ -468,7 +468,7 @@ static void MakeConstIntExpr (ExprDesc* Expr, long Value)
 
 
 
-static void ConstSubExpr (int (*F) (ExprDesc*), ExprDesc* Expr)
+void ConstSubExpr (int (*F) (ExprDesc*), ExprDesc* Expr)
 /* Will evaluate an expression via the given function. If the result is not
  * a constant, a diagnostic will be printed, and the value is replaced by
  * a constant one to make sure there are no internal errors that result
index 00f18f3b3174198c9cc5b5642e9813757a40ef36..1a6d18a8f4c486055a4fee08415271a3d2788f84 100644 (file)
@@ -60,10 +60,11 @@ struct ExprDesc {
 
 
 
-void doasm (void);
-/* This function parses ASM statements. The syntax of the ASM directive
- * looks like the one defined for C++ (C has no ASM directive), that is,
- * a string literal in parenthesis.
+void ConstSubExpr (int (*F) (ExprDesc*), ExprDesc* Expr);
+/* Will evaluate an expression via the given function. If the result is not
+ * a constant, a diagnostic will be printed, and the value is replaced by
+ * a constant one to make sure there are no internal errors that result
+ * from this input error.
  */
 
 unsigned assignadjust (type* lhst, ExprDesc* rhs);
index b1675ad870d2c102924d3c53d6d84ad28ad2517b..e5bc9b737a29ca5bb652a02d6379a76057ac2315 100644 (file)
@@ -37,7 +37,6 @@
 
 /* common */
 #include "check.h"
-#include "strbuf.h"
 #include "tgttrans.h"
 
 /* cc65 */
@@ -165,6 +164,17 @@ const char* GetLiteral (unsigned Offs)
 
 
 
+void GetLiteralStrBuf (StrBuf* Target, unsigned Offs)
+/* Copy the string starting at Offs and lasting to the end of the buffer
+ * into Target.
+ */
+{                                        
+    CHECK (Offs <= SB_GetLen (&LiteralPool));
+    SB_Slice (Target, &LiteralPool, Offs, SB_GetLen (&LiteralPool) - Offs);
+}
+
+
+
 void PrintLiteralPoolStats (FILE* F)
 /* Print statistics about the literal space used */
 {
index e9ad3c5cb1f3cc24238630e7984dae2391dee286..99fd27bfbe15c0fda7d2ce18a8b8bb6b68a0c5d4 100644 (file)
 
 #include <stdio.h>
 
+/* common */
+#include "strbuf.h"
+
 
 
 /*****************************************************************************/
-/*                                  Data                                    */
+/*                                  Data                                    */
 /*****************************************************************************/
 
 
@@ -88,6 +91,11 @@ unsigned AddLiteral (const char* S);
 const char* GetLiteral (unsigned Offs);
 /* Get a pointer to the literal with the given offset in the pool */
 
+void GetLiteralStrBuf (StrBuf* Target, unsigned Offs);
+/* Copy the string starting at Offs and lasting to the end of the buffer
+ * into Target.
+ */
+
 void PrintLiteralPoolStats (FILE* F);
 /* Print statistics about the literal space used */
 
index 2009826ea9a24c465a1f5e148e7e40fe8a92752e..3584a56a138e46d330ddc5e64ed0a6f2c6d4b638 100644 (file)
@@ -801,6 +801,30 @@ void NextToken (void)
 
 
 
+void SkipTokens (const token_t* TokenList, unsigned TokenCount)
+/* Skip tokens until we reach TOK_CEOF or a token in the given token list.
+ * This routine is used for error recovery.
+ */
+{                                                             
+    while (CurTok.Tok != TOK_CEOF) {
+
+       /* Check if the current token is in the token list */
+       unsigned I;
+       for (I = 0; I < TokenCount; ++I) {
+           if (CurTok.Tok == TokenList[I]) {
+               /* Found a token in the list */
+               return;
+           }
+       }
+
+       /* Not in the list: Skip it */
+       NextToken ();
+
+    }
+}
+
+
+
 void Consume (token_t Token, const char* ErrorMsg)
 /* Eat token if it is the next in the input stream, otherwise print an error
  * message.
index 97614b62ed13e45847cb9e7e6ea3ead354244bd7..e78446efbe2efa370e875c237f2730fe6e46aee6 100644 (file)
@@ -191,7 +191,7 @@ extern Token NextTok;               /* The next token */
 
 
 /*****************************************************************************/
-/*                                  code                                    */
+/*                                  code                                    */
 /*****************************************************************************/
 
 
@@ -205,6 +205,11 @@ int IsSym (char* s);
 void NextToken (void);
 /* Get next token from input stream */
 
+void SkipTokens (const token_t* TokenList, unsigned TokenCount);
+/* Skip tokens until we reach TOK_CEOF or a token in the given token list.
+ * This routine is used for error recovery.
+ */
+
 void Consume (token_t Token, const char* ErrorMsg);
 /* Eat token if it is the next in the input stream, otherwise print an error
  * message.
index f428037fbdd7c8fe6c635ed6ee145608b43d26d8..f20b4cdc6cf0f793e56e8eb3be07fd3bed84cca8 100644 (file)
 
 
 /*****************************************************************************/
-/*                                 Helpers                                  */
+/*                                  Data                                    */
 /*****************************************************************************/
 
 
 
-void SB_Realloc (StrBuf* B, unsigned NewSize)
-/* Reallocate the string buffer space, make sure at least NewSize bytes are
- * available.
- */
-{
-    /* Get the current size, use a minimum of 8 bytes */
-    unsigned NewAllocated = B->Allocated;
-    if (NewAllocated == 0) {
-       NewAllocated = 8;
-    }
-
-    /* Round up to the next power of two */
-    while (NewAllocated < NewSize) {
-       NewAllocated *= 2;
-    }
-
-    /* Reallocate the buffer */
-    B->Buf       = xrealloc (B->Buf, NewAllocated);
-    B->Allocated = NewAllocated;
-}
+/* An empty string buf */
+const StrBuf EmptyStrBuf = STATIC_STRBUF_INITIALIZER;
 
 
 
@@ -119,6 +101,29 @@ void FreeStrBuf (StrBuf* B)
 
 
 
+void SB_Realloc (StrBuf* B, unsigned NewSize)
+/* Reallocate the string buffer space, make sure at least NewSize bytes are
+ * available.
+ */
+{
+    /* Get the current size, use a minimum of 8 bytes */
+    unsigned NewAllocated = B->Allocated;
+    if (NewAllocated == 0) {
+       NewAllocated = 8;
+    }
+
+    /* Round up to the next power of two */
+    while (NewAllocated < NewSize) {
+       NewAllocated *= 2;
+    }
+
+    /* Reallocate the buffer */
+    B->Buf       = xrealloc (B->Buf, NewAllocated);
+    B->Allocated = NewAllocated;
+}
+
+
+
 void SB_Terminate (StrBuf* B)
 /* Zero terminate the given string buffer. NOTE: The terminating zero is not
  * accounted for in B->Len, if you want that, you have to use AppendChar!
@@ -133,6 +138,18 @@ void SB_Terminate (StrBuf* B)
 
 
 
+void SB_CopyBuf (StrBuf* Target, const char* Buf, unsigned Size)
+/* Copy Buf to Target, discarding the old contents of Target */
+{
+    if (Target->Allocated < Size) {
+       SB_Realloc (Target, Size);
+    }
+    memcpy (Target->Buf, Buf, Size);
+    Target->Len = Size;
+}
+
+
+
 void SB_AppendChar (StrBuf* B, char C)
 /* Append a character to a string buffer */
 {
@@ -159,18 +176,6 @@ void SB_AppendBuf (StrBuf* B, const char* S, unsigned Size)
 
 
 
-void SB_Copy (StrBuf* Target, const StrBuf* Source)
-/* Copy Source to Target, discarding the old contents of Target */
-{
-    if (Target->Allocated < Source->Allocated) {
-       SB_Realloc (Target, Source->Allocated);
-    }
-    memcpy (Target->Buf, Source->Buf, Source->Len);
-    Target->Len = Source->Len;
-}
-
-
-
 void SB_Slice (StrBuf* Target, const StrBuf* Source, unsigned Start, unsigned Len)
 /* Copy a slice from Source into Target. The current contents of Target are
  * destroyed. If Start is greater than the length of Source, or if Len
index 6a819e71f6cf5a43bb69cfb9ed4240bb4e1b4679..1c92e0fee53d4eaba7e78a5f16c62fc1a2139dd0 100644 (file)
@@ -42,6 +42,7 @@
 
 /* common */
 #include "attrib.h"
+#include "check.h"
 #include "inline.h"
 
 
@@ -59,9 +60,15 @@ struct StrBuf {
     char*       Buf;
 };
 
+/* An empty string buf */
+extern const StrBuf EmptyStrBuf;
+
 /* Initializer for static string bufs */
 #define STATIC_STRBUF_INITIALIZER      { 0, 0, 0 }
 
+/* Initializer for auto string bufs */
+#define AUTO_STRBUF_INITIALIZER         EmptyStrBuf
+
 
 
 /*****************************************************************************/
@@ -117,6 +124,39 @@ INLINE char* SB_GetBuf (StrBuf* B)
 #  define SB_GetBuf(B)     (B)->Buf
 #endif
 
+#if defined(HAVE_INLINE)
+INLINE char SB_At (const StrBuf* B, unsigned Index)
+/* Get a character from the buffer */
+{
+    PRECONDITION (Index < B->Len);
+    return B->Buf[Index];
+}
+#else
+#  define SB_At(B, Index)                      \
+       (PRECONDITION ((Index) < (B)->Len),     \
+        (B)->Buf[Index])
+#endif
+
+#if defined(HAVE_INLINE)
+INLINE char SB_AtUnchecked (const StrBuf* B, unsigned Index)
+/* Get a character from the buffer */
+{
+    return B->Buf[Index];
+}
+#else
+#  define SB_AtUnchecked(B, Index)      ((B)->Buf[Index])
+#endif
+
+#if defined(HAVE_INLINE)
+INLINE int SB_IsEmpty (const StrBuf* B)
+/* Return true if the string buffer is empty */
+{
+    return (B->Len == 0);
+}
+#else
+#  define SB_IsEmpty(B) ((B)->Len == 0)
+#endif
+
 #if defined(HAVE_INLINE)
 INLINE void SB_Clear (StrBuf* B)
 /* Clear the string buffer (make it empty) */
@@ -132,6 +172,29 @@ void SB_Terminate (StrBuf* B);
  * accounted for in B->Len, if you want that, you have to use AppendChar!
  */
 
+void SB_CopyBuf (StrBuf* Target, const char* Buf, unsigned Size);
+/* Copy Buf to Target, discarding the old contents of Target */
+
+#if defined(HAVE_INLINE)
+INLINE void SB_CopyStr (StrBuf* Target, const char* S)
+/* Copy S to Target, discarding the old contents of Target */
+{
+    SB_CopyBuf (Target, S, strlen (S));
+}
+#else
+#  define SB_CopyStr(Target, S) SB_CopyBuf (Target, S, strlen (S))
+#endif
+
+#if defined(HAVE_INLINE)
+INLINE void SB_Copy (StrBuf* Target, const StrBuf* Source)
+/* Copy Source to Target, discarding the old contents of Target */
+{
+    SB_CopyBuf (Target, Source->Buf, Source->Len);
+}
+#else
+#  define SB_Copy(Target, Source)       SB_CopyBuf (Target, (Source)->Buf, (Source)->Len)
+#endif
+
 void SB_AppendChar (StrBuf* B, char C);
 /* Append a character to a string buffer */
 
@@ -148,9 +211,6 @@ INLINE void SB_AppendStr (StrBuf* B, const char* S)
 #  define SB_AppendStr(B, S)    SB_AppendBuf (B, S, strlen (S))
 #endif
 
-void SB_Copy (StrBuf* Target, const StrBuf* Source);
-/* Copy Source to Target, discarding the old contents of Target */
-
 #if defined(HAVE_INLINE)
 INLINE void SB_Append (StrBuf* Target, const StrBuf* Source)
 /* Append the contents of Source to Target */