#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,
/* 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 ();
}
/* common */
#include "check.h"
+#include "strbuf.h"
#include "version.h"
#include "xmalloc.h"
#include "xsprintf.h"
-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));
}
/* Compiler relative stackpointer */
extern int oursp;
+/* Forward */
+struct StrBuf;
+
/*****************************************************************************/
-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. */
-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
-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);
/* common */
#include "check.h"
-#include "strbuf.h"
#include "tgttrans.h"
/* cc65 */
+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 */
{
#include <stdio.h>
+/* common */
+#include "strbuf.h"
+
/*****************************************************************************/
-/* Data */
+/* Data */
/*****************************************************************************/
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 */
+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.
/*****************************************************************************/
-/* code */
+/* code */
/*****************************************************************************/
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.
/*****************************************************************************/
-/* 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;
+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!
+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 */
{
-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
/* common */
#include "attrib.h"
+#include "check.h"
#include "inline.h"
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
+
/*****************************************************************************/
# 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) */
* 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 */
# 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 */