diff --git a/contrib/zsh_compl/_rgbasm b/contrib/zsh_compl/_rgbasm index be17b3e7..8347401c 100644 --- a/contrib/zsh_compl/_rgbasm +++ b/contrib/zsh_compl/_rgbasm @@ -42,7 +42,7 @@ local args=( '(-v --verbose)'{-v,--verbose}'[Enable verbose output]' -w'[Disable all warnings]' - '(-B --backtrace)'{-B,--backtrace}'+[Set backtrace depth or style]:depth:' + '(-B --backtrace)'{-B,--backtrace}'+[Set backtrace depth or style]:param:' '(-b --binary-digits)'{-b,--binary-digits}'+[Change chars for binary constants]:digit spec:' --color'[Whether to use color in output]:color:(auto always never)' '*'{-D,--define}'+[Define a string symbol]:name + value (default 1):' diff --git a/contrib/zsh_compl/_rgblink b/contrib/zsh_compl/_rgblink index 6ddccc94..fa4f5831 100644 --- a/contrib/zsh_compl/_rgblink +++ b/contrib/zsh_compl/_rgblink @@ -28,7 +28,7 @@ local args=( '(-w --wramx)'{-w,--wramx}'[Disable WRAM banking]' '(-x --nopad)'{-x,--nopad}'[Disable padding the end of the final file]' - '(-B --backtrace)'{-B,--backtrace}'+[Set backtrace depth or style]:depth:' + '(-B --backtrace)'{-B,--backtrace}'+[Set backtrace depth or style]:param:' --color'[Whether to use color in output]:color:(auto always never)' '(-l --linkerscript)'{-l,--linkerscript}"+[Use a linker script]:linker script:_files -g '*.link'" '(-M --no-sym-in-map)'{-M,--no-sym-in-map}'[Do not output symbol names in map file]' diff --git a/include/asm/fstack.hpp b/include/asm/fstack.hpp index 8ee60720..85e11e87 100644 --- a/include/asm/fstack.hpp +++ b/include/asm/fstack.hpp @@ -24,6 +24,7 @@ struct FileStackNode { std::string // NODE_FILE, NODE_MACRO > data; + bool isQuiet; // Whether to omit this node from error reporting std::shared_ptr parent; // Pointer to parent node, for error reporting // Line at which the parent context was exited @@ -40,8 +41,12 @@ struct FileStackNode { std::string &name() { return std::get(data); } std::string const &name() const { return std::get(data); } - FileStackNode(FileStackNodeType type_, std::variant, std::string> data_) - : type(type_), data(data_) {} + FileStackNode( + FileStackNodeType type_, + std::variant, std::string> data_, + bool isQuiet_ + ) + : type(type_), data(data_), isQuiet(isQuiet_) {} void printBacktrace(uint32_t curLineNo) const; }; @@ -62,16 +67,19 @@ bool fstk_FileError(std::string const &path, char const *functionName); bool fstk_FailedOnMissingInclude(); bool yywrap(); -bool fstk_RunInclude(std::string const &path); -void fstk_RunMacro(std::string const ¯oName, std::shared_ptr macroArgs); -void fstk_RunRept(uint32_t count, int32_t reptLineNo, ContentSpan const &span); +bool fstk_RunInclude(std::string const &path, bool isQuiet); +void fstk_RunMacro( + std::string const ¯oName, std::shared_ptr macroArgs, bool isQuiet +); +void fstk_RunRept(uint32_t count, int32_t reptLineNo, ContentSpan const &span, bool isQuiet); void fstk_RunFor( std::string const &symName, int32_t start, int32_t stop, int32_t step, int32_t reptLineNo, - ContentSpan const &span + ContentSpan const &span, + bool isQuiet ); bool fstk_Break(); diff --git a/include/asm/symbol.hpp b/include/asm/symbol.hpp index 2c6c83ae..967994ae 100644 --- a/include/asm/symbol.hpp +++ b/include/asm/symbol.hpp @@ -30,8 +30,9 @@ bool sym_IsPC(Symbol const *sym); // Forward declaration for `getSection` struct Symbol { std::string name; SymbolType type; - bool isExported; // Whether the symbol is to be exported - bool isBuiltin; // Whether the symbol is a built-in + bool isBuiltin; + bool isExported; // Not relevant for SYM_MACRO or SYM_EQUS + bool isQuiet; // Only relevant for SYM_MACRO Section *section; std::shared_ptr src; // Where the symbol was defined uint32_t fileLine; // Line where the symbol was defined @@ -88,7 +89,9 @@ Symbol *sym_FindScopedSymbol(std::string const &symName); // Find a scoped symbol by name; do not return `@` or `_NARG` when they have no value Symbol *sym_FindScopedValidSymbol(std::string const &symName); Symbol const *sym_GetPC(); -Symbol *sym_AddMacro(std::string const &symName, int32_t defLineNo, ContentSpan const &span); +Symbol *sym_AddMacro( + std::string const &symName, int32_t defLineNo, ContentSpan const &span, bool isQuiet +); Symbol *sym_Ref(std::string const &symName); Symbol *sym_AddString(std::string const &symName, std::shared_ptr value); Symbol *sym_RedefString(std::string const &symName, std::shared_ptr value); diff --git a/include/backtrace.hpp b/include/backtrace.hpp index 16fcbfcf..2c23a89a 100644 --- a/include/backtrace.hpp +++ b/include/backtrace.hpp @@ -11,58 +11,74 @@ #include "style.hpp" -static constexpr uint64_t TRACE_COLLAPSE = UINT64_MAX; +struct Tracing { + uint64_t depth = 0; + bool collapse = false; + bool loud = false; +}; -extern uint64_t traceDepth; +extern Tracing tracing; bool trace_ParseTraceDepth(char const *arg); template void trace_PrintBacktrace(std::vector const &stack, M getName, N getLineNo) { - auto printLocation = [&](T const &item) { + size_t n = stack.size(); + if (n == 0) { + return; // LCOV_EXCL_LINE + } + + auto printLocation = [&](size_t i) { + T const &item = stack[n - i - 1]; + style_Reset(stderr); + if (!tracing.collapse) { + fputs(" ", stderr); // Just three spaces; the fourth will be printed next + } + fprintf(stderr, " %s ", i == 0 ? "at" : "<-"); style_Set(stderr, STYLE_CYAN, true); fputs(getName(item), stderr); style_Set(stderr, STYLE_CYAN, false); fprintf(stderr, "(%" PRIu32 ")", getLineNo(item)); + if (!tracing.collapse) { + putc('\n', stderr); + } }; - size_t n = stack.size(); - - if (traceDepth == TRACE_COLLAPSE) { + if (tracing.collapse) { fputs(" ", stderr); // Just three spaces; the fourth will be handled by the loop + } + + if (tracing.depth == 0 || static_cast(tracing.depth) >= n) { for (size_t i = 0; i < n; ++i) { - style_Reset(stderr); - fprintf(stderr, " %s ", i == 0 ? "at" : "<-"); - printLocation(stack[n - i - 1]); - } - putc('\n', stderr); - } else if (traceDepth == 0 || static_cast(traceDepth) >= n) { - for (size_t i = 0; i < n; ++i) { - style_Reset(stderr); - fprintf(stderr, " %s ", i == 0 ? "at" : "<-"); - printLocation(stack[n - i - 1]); - putc('\n', stderr); + printLocation(i); } } else { - size_t last = traceDepth / 2; - size_t first = traceDepth - last; - size_t skipped = n - traceDepth; + size_t last = tracing.depth / 2; + size_t first = tracing.depth - last; + size_t skipped = n - tracing.depth; for (size_t i = 0; i < first; ++i) { - style_Reset(stderr); - fprintf(stderr, " %s ", i == 0 ? "at" : "<-"); - printLocation(stack[n - i - 1]); - putc('\n', stderr); + printLocation(i); } style_Reset(stderr); - fprintf(stderr, " ...%zu more%s\n", skipped, last ? "..." : ""); - for (size_t i = n - last; i < n; ++i) { - style_Reset(stderr); - fputs(" <- ", stderr); - printLocation(stack[n - i - 1]); + + if (tracing.collapse) { + fputs(" <-", stderr); + } else { + fputs(" ", stderr); // Just three spaces; the fourth will be printed next + } + fprintf(stderr, " ...%zu more%s", skipped, last ? "..." : ""); + if (!tracing.collapse) { putc('\n', stderr); } + + for (size_t i = n - last; i < n; ++i) { + printLocation(i); + } } + if (tracing.collapse) { + putc('\n', stderr); + } style_Reset(stderr); } diff --git a/include/link/fstack.hpp b/include/link/fstack.hpp index 6b2e530b..9057fe75 100644 --- a/include/link/fstack.hpp +++ b/include/link/fstack.hpp @@ -19,6 +19,7 @@ struct FileStackNode { std::string // NODE_FILE, NODE_MACRO > data; + bool isQuiet; // Whether to omit this node from error reporting FileStackNode *parent; // Line at which the parent context was exited; meaningless for the root level diff --git a/include/linkdefs.hpp b/include/linkdefs.hpp index 816393c4..a12b1215 100644 --- a/include/linkdefs.hpp +++ b/include/linkdefs.hpp @@ -9,7 +9,7 @@ #include "helpers.hpp" // assume #define RGBDS_OBJECT_VERSION_STRING "RGB9" -#define RGBDS_OBJECT_REV 12U +#define RGBDS_OBJECT_REV 13U enum AssertionType { ASSERT_WARN, ASSERT_ERROR, ASSERT_FATAL }; @@ -78,12 +78,18 @@ enum SectionType { SECTTYPE_INVALID }; +static constexpr uint8_t SECTTYPE_TYPE_MASK = 0b111; +static constexpr uint8_t SECTTYPE_UNION_BIT = 7; +static constexpr uint8_t SECTTYPE_FRAGMENT_BIT = 6; + enum FileStackNodeType { NODE_REPT, NODE_FILE, NODE_MACRO, }; +static constexpr uint8_t FSTACKNODE_QUIET_BIT = 7; + // Nont-`const` members may be patched in RGBLINK depending on CLI flags extern struct SectionTypeInfo { std::string const name; diff --git a/man/rgbasm.1 b/man/rgbasm.1 index b235058f..ea8acafe 100644 --- a/man/rgbasm.1 +++ b/man/rgbasm.1 @@ -9,7 +9,7 @@ .Sh SYNOPSIS .Nm .Op Fl EhVvw -.Op Fl B Ar depth +.Op Fl B Ar param .Op Fl b Ar chars .Op Fl \-color Ar when .Op Fl D Ar name Ns Op = Ns Ar value @@ -53,15 +53,29 @@ is invalid because it could also be .Fl \-version . The arguments are as follows: .Bl -tag -width Ds -.It Fl B Ar depth , Fl \-backtrace Ar depth -Specifies the maximum depth for which -.Nm -will print location backtraces for warnings or errors. -Deeper backtraces than that will be abbreviated. -.Fl B Ar 0 -allows unlimited-depth backtraces. -.Fl B Ar collapse -will print the entire location trace on one line. +.It Fl B Ar param , Fl \-backtrace Ar param +Configures how location backtraces are printed if warnings or errors occur. +This flag may be specified multiple times with different parameters that combine meaningfully. +If +.Ar param +is a positive number, it specifies the maximum backtrace depth, abbreviating deeper ones. +Other valid parameter values are the following: +.Bl -tag -width Ds +.It Cm 0 +Do not limit the maximum backtrace depth; this is the default. +.It Cm all +Force all locations to be printed, even "quiet" ones (see +.Dq Excluding locations from backtraces +in +.Xr rgbasm 5 +for details). +.It Cm no-all +Do not print "quieted" locations in backtraces; this is the default. +.It Cm collapse +Print all locations on one line. +.It Cm no-collapse +Print one location per line; this is the default. +.El .It Fl b Ar chars , Fl \-binary-digits Ar chars Allow two characters to be used for binary constants in addition to the default .Sq 0 diff --git a/man/rgbasm.5 b/man/rgbasm.5 index 6648c394..bb4ee85b 100644 --- a/man/rgbasm.5 +++ b/man/rgbasm.5 @@ -1681,7 +1681,8 @@ $ rgbasm -o a.o a.asm $ rgbasm -o b.o b.asm $ rgbasm -o c.o c.asm $ rgblink a.o b.o c.o -error: c.asm(2): Unknown symbol "LabelA" +error: Undefined symbol "LabelA" + at c.asm(2) Linking failed with 1 error .Ed .Pp @@ -2516,6 +2517,68 @@ PUSHO b.X, g.oOX DW `..ooOOXX POPO .Ed +.Ss Excluding locations from backtraces +Errors and warnings print +.Em backtraces +showing the location in the source file where the problem occurred, tracing the origin of the problem even through a chain of +.Ic REPT , +.Ic FOR , +.Ic MACRO , +and +.Ic INCLUDE +locations. +Sometimes there are locations you would like to ignore; for example, a common utility macro when you only care about the line where the macro is used, or an +.Ic INCLUDE +file that only serves to include other files and is just filler in the backtrace. +.Pp +In those cases, you can +.Em silence +a location with a question mark +.Sq \&? +after the token: all of the locations created by a +.Sq REPT? , +.Sq FOR? , +or +.Sq MACRO? +will not be printed, and any location created by a +.Sq INCLUDE? , +or a macro invocation whose name is immediately followed by a +.Sq \&? , +will not be printed. +For example, if this were assembled as +.Ql example.asm : +.Bd -literal -offset indent +MACRO lb + assert -128 <= (\e2) && (\e2) < 256, "\e2 is not a byte" + assert -128 <= (\e3) && (\e3) < 256, "\e3 is not a byte" + ld \e1, (LOW(\e2) << 8) | LOW(\e3) +ENDM +SECTION "Code", ROM0 + lb hl, $123, $45 +.Ed +.Pp +This would print an error backtrace: +.Bd -literal -offset indent +error: Assertion failed: $123 is not a byte + at example.asm::lb(2) + <- example.asm(7) +.Ed +.Pp +But if +.Ql MACRO +were changed to +.Ql MACRO? , +or +.Ql lb hl +were changed to +.Ql lb? hl , +then the error backtrace would not mention the location within the +.Ql lb +macro: +.Bd -literal -offset indent +error: Assertion failed: $123 is not a byte + at example.asm(7) +.Ed .Sh SEE ALSO .Xr rgbasm 1 , .Xr rgblink 1 , diff --git a/man/rgbds.5 b/man/rgbds.5 index 67e96021..6479f525 100644 --- a/man/rgbds.5 +++ b/man/rgbds.5 @@ -79,12 +79,16 @@ order, meaning the node with ID 0 is the last one in the list! .It Cm LONG Ar ParentLineNo Line at which the parent node's context was exited; meaningless for the root node. .It Cm BYTE Ar Type +Bits 0\(en6 indicate the node's type: .Bl -column "Value" -compact .It Sy Value Ta Sy Meaning .It 0 Ta REPT node .It 1 Ta File node .It 2 Ta Macro node .El +.Pp +Bit\ 7 being set means that the node is "quieted" +.Pq see Do Excluding locations from backtraces Dc in Xr rgbasm 5 . .It Cm IF Ar Type No \(!= 0 If the node is not a REPT node... .Pp diff --git a/man/rgblink.1 b/man/rgblink.1 index d968ed90..b72a3faf 100644 --- a/man/rgblink.1 +++ b/man/rgblink.1 @@ -9,7 +9,7 @@ .Sh SYNOPSIS .Nm .Op Fl dhMtVvwx -.Op Fl B Ar depth +.Op Fl B Ar param .Op Fl \-color Ar when .Op Fl l Ar linker_script .Op Fl m Ar map_file @@ -65,15 +65,29 @@ is invalid because it could also be .Fl \-version . The arguments are as follows: .Bl -tag -width Ds -.It Fl B Ar depth , Fl \-backtrace Ar depth -Specifies the maximum depth for which -.Nm -will print location backtraces for warnings or errors. -Deeper backtraces than that will be abbreviated. -.Fl B Ar 0 -allows unlimited-depth backtraces. -.Fl B Ar collapse -will print the entire location trace on one line. +.It Fl B Ar param , Fl \-backtrace Ar param +Configures how location backtraces are printed if warnings or errors occur. +This flag may be specified multiple times with different parameters that combine meaningfully. +If +.Ar param +is a positive number, it specifies the maximum backtrace depth, abbreviating deeper ones. +Other valid parameter values are the following: +.Bl -tag -width Ds +.It Cm 0 +Do not limit the maximum backtrace depth; this is the default. +.It Cm all +Force all locations to be printed, even "quiet" ones (see +.Dq Excluding locations from backtraces +in +.Xr rgbasm 5 +for details). +.It Cm no-all +Do not print "quieted" locations in backtraces; this is the default. +.It Cm collapse +Print all locations on one line. +.It Cm no-collapse +Print one location per line; this is the default. +.El .It Fl \-color Ar when Specify when to highlight warning and error messages with color: .Ql always , diff --git a/src/asm/fstack.cpp b/src/asm/fstack.cpp index 081cead8..da18714e 100644 --- a/src/asm/fstack.cpp +++ b/src/asm/fstack.cpp @@ -63,6 +63,16 @@ static std::string reptChain(FileStackNode const &node) { using TraceNode = std::pair; static std::vector backtrace(FileStackNode const &node, uint32_t curLineNo) { + if (node.isQuiet && !tracing.loud) { + if (node.parent) { + // Quiet REPT nodes will pass their interior line number up to their parent, + // which is more precise than the parent's own line number (since that will be + // the line number of the "REPT?" or "FOR?" itself). + return backtrace(*node.parent, node.type == NODE_REPT ? curLineNo : node.lineNo); + } + return {}; // LCOV_EXCL_LINE + } + if (!node.parent) { assume(node.type != NODE_REPT && std::holds_alternative(node.data)); return { @@ -244,14 +254,14 @@ static void checkRecursionDepth() { } } -static void newFileContext(std::string const &filePath, bool updateStateNow) { +static void newFileContext(std::string const &filePath, bool isQuiet, bool updateStateNow) { checkRecursionDepth(); std::shared_ptr uniqueIDStr = nullptr; std::shared_ptr macroArgs = nullptr; auto fileInfo = - std::make_shared(NODE_FILE, filePath == "-" ? "" : filePath); + std::make_shared(NODE_FILE, filePath == "-" ? "" : filePath, isQuiet); if (!contextStack.empty()) { Context &oldContext = contextStack.top(); fileInfo->parent = oldContext.fileInfo; @@ -269,7 +279,8 @@ static void newFileContext(std::string const &filePath, bool updateStateNow) { context.lexerState.setFileAsNextState(filePath, updateStateNow); } -static void newMacroContext(Symbol const ¯o, std::shared_ptr macroArgs) { +static void + newMacroContext(Symbol const ¯o, std::shared_ptr macroArgs, bool isQuiet) { checkRecursionDepth(); Context &oldContext = contextStack.top(); @@ -287,7 +298,7 @@ static void newMacroContext(Symbol const ¯o, std::shared_ptr macr fileInfoName.append("::"); fileInfoName.append(macro.name); - auto fileInfo = std::make_shared(NODE_MACRO, fileInfoName); + auto fileInfo = std::make_shared(NODE_MACRO, fileInfoName, isQuiet); assume(!contextStack.empty()); // The top level context cannot be a MACRO fileInfo->parent = oldContext.fileInfo; fileInfo->lineNo = lexer_GetLineNo(); @@ -301,7 +312,8 @@ static void newMacroContext(Symbol const ¯o, std::shared_ptr macr context.lexerState.setViewAsNextState("MACRO", macro.getMacro(), macro.fileLine); } -static Context &newReptContext(int32_t reptLineNo, ContentSpan const &span, uint32_t count) { +static Context & + newReptContext(int32_t reptLineNo, ContentSpan const &span, uint32_t count, bool isQuiet) { checkRecursionDepth(); Context &oldContext = contextStack.top(); @@ -312,7 +324,7 @@ static Context &newReptContext(int32_t reptLineNo, ContentSpan const &span, uint fileInfoIters.insert(fileInfoIters.end(), RANGE(oldContext.fileInfo->iters())); } - auto fileInfo = std::make_shared(NODE_REPT, fileInfoIters); + auto fileInfo = std::make_shared(NODE_REPT, fileInfoIters, isQuiet); assume(!contextStack.empty()); // The top level context cannot be a REPT fileInfo->parent = oldContext.fileInfo; fileInfo->lineNo = reptLineNo; @@ -356,15 +368,17 @@ bool fstk_FailedOnMissingInclude() { return failedOnMissingInclude; } -bool fstk_RunInclude(std::string const &path) { +bool fstk_RunInclude(std::string const &path, bool isQuiet) { if (std::optional fullPath = fstk_FindFile(path); fullPath) { - newFileContext(*fullPath, false); + newFileContext(*fullPath, isQuiet, false); return false; } return fstk_FileError(path, "INCLUDE"); } -void fstk_RunMacro(std::string const ¯oName, std::shared_ptr macroArgs) { +void fstk_RunMacro( + std::string const ¯oName, std::shared_ptr macroArgs, bool isQuiet +) { Symbol *macro = sym_FindExactSymbol(macroName); if (!macro) { @@ -380,15 +394,15 @@ void fstk_RunMacro(std::string const ¯oName, std::shared_ptr macr return; } - newMacroContext(*macro, macroArgs); + newMacroContext(*macro, macroArgs, isQuiet || macro->isQuiet); } -void fstk_RunRept(uint32_t count, int32_t reptLineNo, ContentSpan const &span) { +void fstk_RunRept(uint32_t count, int32_t reptLineNo, ContentSpan const &span, bool isQuiet) { if (count == 0) { return; } - newReptContext(reptLineNo, span, count); + newReptContext(reptLineNo, span, count, isQuiet); } void fstk_RunFor( @@ -397,7 +411,8 @@ void fstk_RunFor( int32_t stop, int32_t step, int32_t reptLineNo, - ContentSpan const &span + ContentSpan const &span, + bool isQuiet ) { if (Symbol *sym = sym_AddVar(symName, start); sym->type != SYM_VAR) { return; @@ -422,7 +437,7 @@ void fstk_RunFor( return; } - Context &context = newReptContext(reptLineNo, span, count); + Context &context = newReptContext(reptLineNo, span, count, isQuiet); context.isForLoop = true; context.forValue = start; context.forStep = step; @@ -447,11 +462,11 @@ void fstk_NewRecursionDepth(size_t newDepth) { } void fstk_Init(std::string const &mainPath) { - newFileContext(mainPath, true); + newFileContext(mainPath, false, true); for (std::string const &name : preIncludeNames) { if (std::optional fullPath = fstk_FindFile(name); fullPath) { - newFileContext(*fullPath, false); + newFileContext(*fullPath, false, false); } else { error("Error reading pre-included file \"%s\": %s", name.c_str(), strerror(errno)); } diff --git a/src/asm/lexer.cpp b/src/asm/lexer.cpp index 9ef0c895..495b5ea1 100644 --- a/src/asm/lexer.cpp +++ b/src/asm/lexer.cpp @@ -1561,9 +1561,8 @@ static bool isGarbageCharacter(int c) { if (isWhitespace(c)) { return false; } - // Printable characters which are nevertheless garbage: braces should have been interpolated, - // and question mark is unused - if (c == '{' || c == '}' || c == '?') { + // Printable characters which are nevertheless garbage: braces should have been interpolated + if (c == '{' || c == '}') { return true; } // All other printable characters are not garbage (i.e. `yylex_NORMAL` handles them), and @@ -1614,6 +1613,9 @@ static Token yylex_NORMAL() { case '~': return Token(T_(OP_NOT)); + case '?': + return Token(T_(QUESTIONMARK)); + case '@': { std::string symName("@"); return Token(T_(SYMBOL), symName); @@ -1926,22 +1928,23 @@ static Token yylex_NORMAL() { } } - // We need to distinguish between label definitions (which start with `LABEL`) and - // macro invocations (which start with `SYMBOL`). + // We need to distinguish between: + // - label definitions (which are followed by a ':' and use the token `LABEL`) + // - quiet macro invocations (which are followed by a '?' and use the token `QMACRO`) + // - regular macro invocations (which use the token `SYMBOL`) // // If we had one `IDENTIFIER` token, the parser would need to perform "lookahead" to // determine which rule applies. But since macros need to enter "raw" mode to parse // their arguments, which may not even be valid tokens in "normal" mode, we cannot use - // lookahead to check for the presence of a `COLON`. + // lookahead to check for the presence of a `COLON` or `QUESTIONMARK`. // - // Instead, we have separate `SYMBOL` and `LABEL` tokens, lexing as a `LABEL` if a ':' - // character *immediately* follows the identifier. Thus, "Label:" and "mac:" are treated - // as label definitions, but "Label :" and "mac :" are treated as macro invocations. - // - // The alternative would be a "lexer hack" like C, where identifiers would lex as a - // `SYMBOL` if they are already defined, otherwise as a `LABEL`. - if (token.type == T_(SYMBOL) && peek() == ':') { - token.type = T_(LABEL); + // Instead, we have separate `SYMBOL`, `LABEL`, and `QMACRO` tokens, and decide which + // one to lex depending on the character *immediately* following the identifier. + // Thus "name:" is a label definition, and "name?" is a quiet macro invocation, but + // "name :" and "name ?" and just "name" are all regular macro invocations. + if (token.type == T_(SYMBOL)) { + c = peek(); + token.type = c == ':' ? T_(LABEL) : c == '?' ? T_(QMACRO) : T_(SYMBOL); } return token; diff --git a/src/asm/output.cpp b/src/asm/output.cpp index 94749399..47706218 100644 --- a/src/asm/output.cpp +++ b/src/asm/output.cpp @@ -83,10 +83,10 @@ static void writeSection(Section const §, FILE *file) { putLong(sect.size, file); + assume((sect.type & SECTTYPE_TYPE_MASK) == sect.type); bool isUnion = sect.modifier == SECTION_UNION; bool isFragment = sect.modifier == SECTION_FRAGMENT; - - putc(sect.type | isUnion << 7 | isFragment << 6, file); + putc(sect.type | isUnion << SECTTYPE_UNION_BIT | isFragment << SECTTYPE_FRAGMENT_BIT, file); putLong(sect.org, file); putLong(sect.bank, file); @@ -268,7 +268,9 @@ static void writeAssert(Assertion const &assert, FILE *file) { static void writeFileStackNode(FileStackNode const &node, FILE *file) { putLong(node.parent ? node.parent->ID : UINT32_MAX, file); putLong(node.lineNo, file); - putc(node.type, file); + + putc(node.type | node.isQuiet << FSTACKNODE_QUIET_BIT, file); + if (node.type != NODE_REPT) { putString(node.name(), file); } else { diff --git a/src/asm/parser.y b/src/asm/parser.y index 9c682b81..83243e58 100644 --- a/src/asm/parser.y +++ b/src/asm/parser.y @@ -98,6 +98,7 @@ %token LBRACK "[" RBRACK "]" %token LBRACKS "[[" RBRACKS "]]" %token LPAREN "(" RPAREN ")" +%token QUESTIONMARK "?" // Arithmetic operators %token OP_ADD "+" OP_SUB "-" @@ -322,6 +323,7 @@ %token LABEL "label" %token LOCAL "local label" %token ANON "anonymous label" +%token QMACRO "quiet macro" /******************** Data types ********************/ @@ -398,6 +400,7 @@ %type sect_type %type strfmt_args %type strfmt_va_args +%type maybe_quiet %% @@ -544,7 +547,13 @@ macro: // Parsing 'macro_args' will restore the lexer's normal mode lexer_SetMode(LEXER_RAW); } macro_args { - fstk_RunMacro($1, $3); + fstk_RunMacro($1, $3, false); + } + | QMACRO { + // Parsing 'macro_args' will restore the lexer's normal mode + lexer_SetMode(LEXER_RAW); + } macro_args { + fstk_RunMacro($1, $3, true); } ; @@ -780,10 +789,19 @@ load: } ; +maybe_quiet: + %empty { + $$ = false; + } + | QUESTIONMARK { + $$ = true; + } +; + rept: - POP_REPT uconst NEWLINE capture_rept endofline { - if ($4.span.ptr) { - fstk_RunRept($2, $4.lineNo, $4.span); + POP_REPT maybe_quiet uconst NEWLINE capture_rept endofline { + if ($5.span.ptr) { + fstk_RunRept($3, $5.lineNo, $5.span, $2); } } ; @@ -791,11 +809,11 @@ rept: for: POP_FOR { lexer_ToggleStringExpansion(false); - } SYMBOL { + } maybe_quiet SYMBOL { lexer_ToggleStringExpansion(true); } COMMA for_args NEWLINE capture_rept endofline { - if ($8.span.ptr) { - fstk_RunFor($3, $6.start, $6.stop, $6.step, $8.lineNo, $8.span); + if ($9.span.ptr) { + fstk_RunFor($4, $7.start, $7.stop, $7.step, $9.lineNo, $9.span, $3); } } ; @@ -835,11 +853,11 @@ break: def_macro: POP_MACRO { lexer_ToggleStringExpansion(false); - } SYMBOL { + } maybe_quiet SYMBOL { lexer_ToggleStringExpansion(true); } NEWLINE capture_macro endofline { - if ($6.span.ptr) { - sym_AddMacro($3, $6.lineNo, $6.span); + if ($7.span.ptr) { + sym_AddMacro($4, $7.lineNo, $7.span, $3); } } ; @@ -1002,8 +1020,8 @@ export_def: ; include: - label POP_INCLUDE string endofline { - if (fstk_RunInclude($3)) { + label POP_INCLUDE maybe_quiet string endofline { + if (fstk_RunInclude($4, $3)) { YYACCEPT; } } diff --git a/src/asm/symbol.cpp b/src/asm/symbol.cpp index b77b5208..537ee652 100644 --- a/src/asm/symbol.cpp +++ b/src/asm/symbol.cpp @@ -205,8 +205,9 @@ static Symbol &createSymbol(std::string const &symName) { Symbol &sym = symbols[symName]; sym.name = symName; - sym.isExported = false; sym.isBuiltin = false; + sym.isExported = false; + sym.isQuiet = false; sym.section = nullptr; sym.src = fstk_GetFileStack(); sym.fileLine = sym.src ? lexer_GetLineNo() : 0; @@ -616,7 +617,9 @@ void sym_Export(std::string const &symName) { sym->isExported = true; } -Symbol *sym_AddMacro(std::string const &symName, int32_t defLineNo, ContentSpan const &span) { +Symbol *sym_AddMacro( + std::string const &symName, int32_t defLineNo, ContentSpan const &span, bool isQuiet +) { Symbol *sym = createNonrelocSymbol(symName, false); if (!sym) { @@ -625,6 +628,7 @@ Symbol *sym_AddMacro(std::string const &symName, int32_t defLineNo, ContentSpan sym->type = SYM_MACRO; sym->data = span; + sym->isQuiet = isQuiet; sym->src = fstk_GetFileStack(); // The symbol is created at the line after the `ENDM`, diff --git a/src/backtrace.cpp b/src/backtrace.cpp index b1fdb6b8..a6061fe8 100644 --- a/src/backtrace.cpp +++ b/src/backtrace.cpp @@ -6,16 +6,24 @@ #include "platform.hpp" // strcasecmp -uint64_t traceDepth = 0; +Tracing tracing; bool trace_ParseTraceDepth(char const *arg) { if (!strcasecmp(arg, "collapse")) { - traceDepth = TRACE_COLLAPSE; + tracing.collapse = true; return true; + } else if (!strcasecmp(arg, "no-collapse")) { + tracing.collapse = false; + return true; + } else if (!strcasecmp(arg, "all")) { + tracing.loud = true; + return true; + } else if (!strcasecmp(arg, "no-all")) { + tracing.loud = false; + return true; + } else { + char *endptr; + tracing.depth = strtoul(arg, &endptr, 0); + return arg[0] != '\0' && *endptr == '\0'; } - - char *endptr; - traceDepth = strtoul(arg, &endptr, 0); - - return arg[0] != '\0' && *endptr == '\0' && traceDepth != TRACE_COLLAPSE; } diff --git a/src/link/fstack.cpp b/src/link/fstack.cpp index 2b28bb8f..c36471c5 100644 --- a/src/link/fstack.cpp +++ b/src/link/fstack.cpp @@ -9,6 +9,16 @@ using TraceNode = std::pair; static std::vector backtrace(FileStackNode const &node, uint32_t curLineNo) { + if (node.isQuiet && !tracing.loud) { + if (node.parent) { + // Quiet REPT nodes will pass their interior line number up to their parent, + // which is more precise than the parent's own line number (since that will be + // the line number of the "REPT?" or "FOR?" itself). + return backtrace(*node.parent, node.type == NODE_REPT ? curLineNo : node.lineNo); + } + return {}; // LCOV_EXCL_LINE + } + if (!node.parent) { assume(node.type != NODE_REPT && std::holds_alternative(node.data)); return { diff --git a/src/link/object.cpp b/src/link/object.cpp index 8702bb7a..2bf18eb6 100644 --- a/src/link/object.cpp +++ b/src/link/object.cpp @@ -105,14 +105,12 @@ static void readFileStackNode( tryReadLong( node.lineNo, file, "%s: Cannot read node #%" PRIu32 "'s line number: %s", fileName, nodeID ); - tryGetc( - FileStackNodeType, - node.type, - file, - "%s: Cannot read node #%" PRIu32 "'s type: %s", - fileName, - nodeID - ); + + uint8_t type; + tryGetc(uint8_t, type, file, "%s: Cannot read node #%" PRIu32 "'s type: %s", fileName, nodeID); + node.type = static_cast(type & ~(1 << FSTACKNODE_QUIET_BIT)); + node.isQuiet = (type & (1 << FSTACKNODE_QUIET_BIT)) != 0; + switch (node.type) { case NODE_FILE: case NODE_MACRO: @@ -318,14 +316,14 @@ static void readSection( tryGetc( uint8_t, byte, file, "%s: Cannot read \"%s\"'s type: %s", fileName, section.name.c_str() ); - if (uint8_t type = byte & 0x3F; type >= SECTTYPE_INVALID) { + if (uint8_t type = byte & SECTTYPE_TYPE_MASK; type >= SECTTYPE_INVALID) { fatal("\"%s\" has unknown section type 0x%02x", section.name.c_str(), type); } else { section.type = SectionType(type); } - if (byte >> 7) { + if (byte & (1 << SECTTYPE_UNION_BIT)) { section.modifier = SECTION_UNION; - } else if (byte >> 6) { + } else if (byte & (1 << SECTTYPE_FRAGMENT_BIT)) { section.modifier = SECTION_FRAGMENT; } else { section.modifier = SECTION_NORMAL; @@ -458,6 +456,7 @@ void obj_ReadFile(char const *fileName, unsigned int fileID) { nodes[fileID].push_back({ .type = NODE_FILE, .data = std::variant, std::string>(fileName), + .isQuiet = false, .parent = nullptr, .lineNo = 0, }); diff --git a/test/asm/backtrace-depth.flags b/test/asm/backtrace-depth.flags index af0af78c..abcbd339 100644 --- a/test/asm/backtrace-depth.flags +++ b/test/asm/backtrace-depth.flags @@ -1 +1 @@ --B 5 +-B 5 -B no-collapse diff --git a/test/asm/expansion-diagnostics.flags b/test/asm/expansion-diagnostics.flags index 95904423..40bf09e6 100644 --- a/test/asm/expansion-diagnostics.flags +++ b/test/asm/expansion-diagnostics.flags @@ -1 +1 @@ --B 0 +-B no-collapse diff --git a/test/asm/garbage_sequence.asm b/test/asm/garbage_sequence.asm index 69cda417..8a7cc25b 100644 --- a/test/asm/garbage_sequence.asm +++ b/test/asm/garbage_sequence.asm @@ -1,5 +1,5 @@ assert 1 +# 1 == 2 -assert 2 ?ÿ* 2 == 4 -assert 3 **?ÿ?##?? 3 == 27 +assert 2 #ÿ* 2 == 4 +assert 3 **#ÿ}ÿ# 3 == 27 charmap "x", 4 assert 4 <<#ÿ'x' == 64 diff --git a/test/asm/garbage_sequence.err b/test/asm/garbage_sequence.err index b3588648..68612a17 100644 --- a/test/asm/garbage_sequence.err +++ b/test/asm/garbage_sequence.err @@ -1,13 +1,11 @@ error: Invalid character '#' at garbage_sequence.asm(1) -error: Invalid characters '?', 0xFF (is the file UTF-8?) +error: Invalid characters '#', 0xFF (is the file UTF-8?) at garbage_sequence.asm(2) -error: Invalid characters '?', 0xFF, '?' (is the file UTF-8?) +error: Invalid characters '#', 0xFF, '}', 0xFF (is the file UTF-8?) at garbage_sequence.asm(3) error: Invalid character '#' at garbage_sequence.asm(3) -error: Invalid characters '#', '?', '?' - at garbage_sequence.asm(3) error: Invalid characters '#', 0xFF (is the file UTF-8?) at garbage_sequence.asm(5) -Assembly aborted with 6 errors! +Assembly aborted with 5 errors! diff --git a/test/asm/lex-label.err b/test/asm/lex-label.err index e661a7ec..c641a29b 100644 --- a/test/asm/lex-label.err +++ b/test/asm/lex-label.err @@ -1,10 +1,10 @@ error: syntax error, unexpected label, expecting symbol at lex-label.asm(1) -error: syntax error, unexpected label, expecting symbol +error: syntax error, unexpected label, expecting ? or symbol at lex-label.asm(3) error: syntax error, unexpected ENDM at lex-label.asm(4) -error: syntax error, unexpected label, expecting symbol +error: syntax error, unexpected label, expecting ? or symbol at lex-label.asm(6) error: syntax error, unexpected ENDR at lex-label.asm(7) diff --git a/test/asm/loud-backtrace.asm b/test/asm/loud-backtrace.asm new file mode 100644 index 00000000..091efaf4 --- /dev/null +++ b/test/asm/loud-backtrace.asm @@ -0,0 +1,20 @@ +macro mac + warn "from macro" +endm +mac ; normal + +macro? quiet + warn "from quiet macro" +endm +quiet +rept? 1 + warn "from quiet rept" +endr +for? x, 1 + warn "from quiet for (x={d:x})" +endr +include? "loud-backtrace.inc" +macro loud + warn "from loud macro" +endm +mac? diff --git a/test/asm/loud-backtrace.err b/test/asm/loud-backtrace.err new file mode 100644 index 00000000..af16cee7 --- /dev/null +++ b/test/asm/loud-backtrace.err @@ -0,0 +1,12 @@ +warning: from macro [-Wuser] + at loud-backtrace.asm::mac(2) <- loud-backtrace.asm(4) +warning: from quiet macro [-Wuser] + at loud-backtrace.asm::quiet(7) <- loud-backtrace.asm(9) +warning: from quiet rept [-Wuser] + at loud-backtrace.asm::REPT~1(11) <- loud-backtrace.asm(10) +warning: from quiet for (x=0) [-Wuser] + at loud-backtrace.asm::REPT~1(14) <- loud-backtrace.asm(13) +warning: from quiet include [-Wuser] + at loud-backtrace.inc(1) <- loud-backtrace.asm(16) +warning: from macro [-Wuser] + at loud-backtrace.asm::mac(2) <- loud-backtrace.asm(20) diff --git a/test/asm/loud-backtrace.flags b/test/asm/loud-backtrace.flags new file mode 100644 index 00000000..fbe5fd01 --- /dev/null +++ b/test/asm/loud-backtrace.flags @@ -0,0 +1 @@ +-B all diff --git a/test/asm/loud-backtrace.inc b/test/asm/loud-backtrace.inc new file mode 100644 index 00000000..0b244809 --- /dev/null +++ b/test/asm/loud-backtrace.inc @@ -0,0 +1 @@ +warn "from quiet include" diff --git a/test/asm/quiet-backtrace.asm b/test/asm/quiet-backtrace.asm new file mode 100644 index 00000000..a7e99383 --- /dev/null +++ b/test/asm/quiet-backtrace.asm @@ -0,0 +1,20 @@ +macro mac + warn "from macro" +endm +mac ; normal + +macro? quiet + warn "from quiet macro" +endm +quiet +rept? 1 + warn "from quiet rept" +endr +for? x, 1 + warn "from quiet for (x={d:x})" +endr +include? "quiet-backtrace.inc" +macro loud + warn "from loud macro" +endm +mac? diff --git a/test/asm/quiet-backtrace.err b/test/asm/quiet-backtrace.err new file mode 100644 index 00000000..dcb338bb --- /dev/null +++ b/test/asm/quiet-backtrace.err @@ -0,0 +1,12 @@ +warning: from macro [-Wuser] + at quiet-backtrace.asm::mac(2) <- quiet-backtrace.asm(4) +warning: from quiet macro [-Wuser] + at quiet-backtrace.asm(9) +warning: from quiet rept [-Wuser] + at quiet-backtrace.asm(11) +warning: from quiet for (x=0) [-Wuser] + at quiet-backtrace.asm(14) +warning: from quiet include [-Wuser] + at quiet-backtrace.asm(16) +warning: from macro [-Wuser] + at quiet-backtrace.asm(20) diff --git a/test/asm/quiet-backtrace.inc b/test/asm/quiet-backtrace.inc new file mode 100644 index 00000000..0b244809 --- /dev/null +++ b/test/asm/quiet-backtrace.inc @@ -0,0 +1 @@ +warn "from quiet include" diff --git a/test/asm/rept-macro-fstack-trace.flags b/test/asm/rept-macro-fstack-trace.flags index 95904423..40bf09e6 100644 --- a/test/asm/rept-macro-fstack-trace.flags +++ b/test/asm/rept-macro-fstack-trace.flags @@ -1 +1 @@ --B 0 +-B no-collapse