From 23086bccff03507368b29163b4237f80e72d49a1 Mon Sep 17 00:00:00 2001 From: Kugel Fuhr <98353208+kugelfuhr@users.noreply.github.com> Date: Sun, 6 Jul 2025 10:41:12 +0200 Subject: [PATCH] Much improved diagnostics for macros. --- src/ca65/macro.c | 99 +++++++++++++++++++++++++++++++++--------------- 1 file changed, 69 insertions(+), 30 deletions(-) diff --git a/src/ca65/macro.c b/src/ca65/macro.c index 845efec5a..4345eeb86 100644 --- a/src/ca65/macro.c +++ b/src/ca65/macro.c @@ -102,10 +102,12 @@ struct Macro { unsigned TokCount; /* Number of tokens for this macro */ TokNode* TokRoot; /* Root of token list */ TokNode* TokLast; /* Pointer to last token in list */ + FilePos DefPos; /* Position of definition */ StrBuf Name; /* Macro name, dynamically allocated */ unsigned Expansions; /* Number of active macro expansions */ unsigned char Style; /* Macro style */ unsigned char Incomplete; /* Macro is currently built */ + unsigned char HasError; /* Macro has errors */ }; /* Hash table functions */ @@ -232,7 +234,7 @@ static void FreeIdDescList (IdDesc* ID) -static Macro* NewMacro (const StrBuf* Name, unsigned char Style) +static Macro* NewMacro (const StrBuf* Name, const FilePos* P, unsigned char Style) /* Generate a new macro entry, initialize and return it */ { /* Allocate memory */ @@ -247,11 +249,14 @@ static Macro* NewMacro (const StrBuf* Name, unsigned char Style) M->TokCount = 0; M->TokRoot = 0; M->TokLast = 0; + M->DefPos = *P; SB_Init (&M->Name); SB_Copy (&M->Name, Name); + SB_Terminate (&M->Name); /* So the name can be used with %s */ M->Expansions = 0; M->Style = Style; M->Incomplete = 1; + M->HasError = 0; /* Insert the macro into the hash table */ HT_Insert (&MacroTab, &M->Node); @@ -364,19 +369,26 @@ static void FreeMacExp (MacExp* E) -static void MacSkipDef (unsigned Style) +static void MacSkipDef (unsigned Style, const FilePos* StartPos) /* Skip a macro definition */ { if (Style == MAC_STYLE_CLASSIC) { - /* Skip tokens until we reach the final .endmacro */ - while (CurTok.Tok != TOK_ENDMACRO && CurTok.Tok != TOK_EOF) { + /* Skip tokens until we reach the final .endmacro. Be liberal about + ** .endmacro here since we had errors anyway when this function is + ** called. + */ + while (1) { + if (CurTok.Tok == TOK_EOF) { + ErrorExpect ("Expected '.ENDMACRO'"); + PNotification (StartPos, "Macro definition started here"); + break; + } + if (CurTok.Tok == TOK_ENDMACRO) { + NextTok (); + break; + } NextTok (); } - if (CurTok.Tok != TOK_EOF) { - SkipUntilSep (); - } else { - Error ("'.ENDMACRO' expected"); - } } else { /* Skip until end of line */ SkipUntilSep (); @@ -388,26 +400,23 @@ static void MacSkipDef (unsigned Style) void MacDef (unsigned Style) /* Parse a macro definition */ { + Macro* Existing; Macro* M; TokNode* N; - FilePos Pos; int HaveParams; - int LastTokWasSep; + + /* Remember the current file position */ + FilePos StartPos = CurTok.Pos; /* For classic macros, remember if we are at the beginning of the line. ** If the macro name and parameters pass our checks then we will be on a ** new line, so set it now */ - LastTokWasSep = 1; - - /* Save the position of the start of the macro definition to allow - ** using Perror to display the error if .endmacro isn't found - */ - Pos = CurTok.Pos; + int LastTokWasSep = 1; /* We expect a macro name here */ if (!Expect (TOK_IDENT, "Expected an identifier")) { - MacSkipDef (Style); + MacSkipDef (Style, &StartPos); return; } if (!UbiquitousIdents && FindInstruction (&CurTok.SVal) >= 0) { @@ -415,21 +424,25 @@ void MacDef (unsigned Style) ** allowed if not explicitly enabled. */ Error ("Cannot use an instruction as macro name"); - MacSkipDef (Style); + MacSkipDef (Style, &StartPos); return; } /* Did we already define that macro? */ - if (HT_Find (&MacroTab, &CurTok.SVal) != 0) { + Existing = HT_Find (&MacroTab, &CurTok.SVal); + if (Existing != 0) { /* Macro is already defined */ Error ("A macro named '%m%p' is already defined", &CurTok.SVal); + PNotification (&Existing->DefPos, + "Previous definition of macro '%s' was here", + SB_GetConstBuf (&Existing->Name)); /* Skip tokens until we reach the final .endmacro */ - MacSkipDef (Style); + MacSkipDef (Style, &StartPos); return; } /* Define the macro */ - M = NewMacro (&CurTok.SVal, Style); + M = NewMacro (&CurTok.SVal, &StartPos, Style); /* Switch to raw token mode and skip the macro name */ EnterRawTokenMode (); @@ -463,7 +476,8 @@ void MacDef (unsigned Style) while (1) { if (SB_Compare (&List->Id, &CurTok.SVal) == 0) { - Error ("Duplicate symbol '%m%p'", &CurTok.SVal); + Error ("Duplicate macro parameter '%m%p'", &CurTok.SVal); + M->HasError = 1; } if (List->Next == 0) { break; @@ -513,7 +527,10 @@ void MacDef (unsigned Style) } /* May not have end of file in a macro definition */ if (CurTok.Tok == TOK_EOF) { - PError (&Pos, "'.ENDMACRO' expected for macro '%m%p'", &M->Name); + Error ("Missing '.ENDMACRO' for definition of macro '%s'", + SB_GetConstBuf (&M->Name)); + PNotification (&StartPos, "Macro definition started here"); + M->HasError = 1; goto Done; } } else { @@ -535,6 +552,7 @@ void MacDef (unsigned Style) if (CurTok.Tok != TOK_IDENT && CurTok.Tok != TOK_LOCAL_IDENT) { ErrorExpect ("Expected an identifier"); SkipUntilSep (); + M->HasError = 1; break; } @@ -553,7 +571,10 @@ void MacDef (unsigned Style) } /* We need end of line after the locals */ - ConsumeSep (); + if (!ExpectSep ()) { + M->HasError = 1; + } + NextTok (); continue; } @@ -626,7 +647,8 @@ void MacUndef (const StrBuf* Name, unsigned char Style) return; } if (M->Expansions > 0) { - Error ("Cannot delete a macro that is currently expanded"); + Error ("Cannot delete macro '%s' which is currently expanded", + SB_GetConstBuf (&M->Name)); return; } @@ -813,7 +835,11 @@ static void StartExpClassic (MacExp* E) /* Check for maximum parameter count */ if (E->ParamCount >= E->M->ParamCount) { - ErrorSkip ("Too many macro parameters"); + ErrorSkip ("Too many parameters for macro '%s'", + SB_GetConstBuf (&E->M->Name)); + PNotification (&E->M->DefPos, + "See definition of macro '%s' which was here", + SB_GetConstBuf (&E->M->Name)); break; } @@ -827,7 +853,7 @@ static void StartExpClassic (MacExp* E) /* Check for end of file */ if (CurTok.Tok == TOK_EOF) { - Error ("Unexpected end of file"); + Error ("Unexpected end of file in macro parameter list"); FreeMacExp (E); return; } @@ -970,9 +996,21 @@ void MacExpandStart (Macro* M) Pos = CurTok.Pos; NextTok (); + /* We cannot expand a macro with errors */ + if (M->HasError) { + PError (&Pos, "Macro '%s' contains errors and cannot be expanded", + SB_GetConstBuf (&M->Name)); + PNotification (&M->DefPos, "Definition of macro '%s' was here", + SB_GetConstBuf (&M->Name)); + return; + } + /* We cannot expand an incomplete macro */ if (M->Incomplete) { - PError (&Pos, "Cannot expand an incomplete macro"); + PError (&Pos, "Macro '%s' is incomplete and cannot be expanded", + SB_GetConstBuf (&M->Name)); + PNotification (&M->DefPos, "Definition of macro '%s' was here", + SB_GetConstBuf (&M->Name)); return; } @@ -980,7 +1018,8 @@ void MacExpandStart (Macro* M) ** to force an endless loop and assembler crash. */ if (MacExpansions >= MAX_MACEXPANSIONS) { - PError (&Pos, "Too many nested macro expansions"); + PError (&Pos, "Too many nested macro expansions for macro '%s'", + SB_GetConstBuf (&M->Name)); return; }