Implement ? suffix to "quiet" a context and exclude it from backtraces (#1800)

This commit is contained in:
Rangi
2025-08-18 21:34:58 -04:00
committed by GitHub
parent 77a105e189
commit b7e0783ae7
32 changed files with 392 additions and 139 deletions

View File

@@ -42,7 +42,7 @@ local args=(
'(-v --verbose)'{-v,--verbose}'[Enable verbose output]' '(-v --verbose)'{-v,--verbose}'[Enable verbose output]'
-w'[Disable all warnings]' -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:' '(-b --binary-digits)'{-b,--binary-digits}'+[Change chars for binary constants]:digit spec:'
--color'[Whether to use color in output]:color:(auto always never)' --color'[Whether to use color in output]:color:(auto always never)'
'*'{-D,--define}'+[Define a string symbol]:name + value (default 1):' '*'{-D,--define}'+[Define a string symbol]:name + value (default 1):'

View File

@@ -28,7 +28,7 @@ local args=(
'(-w --wramx)'{-w,--wramx}'[Disable WRAM banking]' '(-w --wramx)'{-w,--wramx}'[Disable WRAM banking]'
'(-x --nopad)'{-x,--nopad}'[Disable padding the end of the final file]' '(-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)' --color'[Whether to use color in output]:color:(auto always never)'
'(-l --linkerscript)'{-l,--linkerscript}"+[Use a linker script]:linker script:_files -g '*.link'" '(-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]' '(-M --no-sym-in-map)'{-M,--no-sym-in-map}'[Do not output symbol names in map file]'

View File

@@ -24,6 +24,7 @@ struct FileStackNode {
std::string // NODE_FILE, NODE_MACRO std::string // NODE_FILE, NODE_MACRO
> >
data; data;
bool isQuiet; // Whether to omit this node from error reporting
std::shared_ptr<FileStackNode> parent; // Pointer to parent node, for error reporting std::shared_ptr<FileStackNode> parent; // Pointer to parent node, for error reporting
// Line at which the parent context was exited // Line at which the parent context was exited
@@ -40,8 +41,12 @@ struct FileStackNode {
std::string &name() { return std::get<std::string>(data); } std::string &name() { return std::get<std::string>(data); }
std::string const &name() const { return std::get<std::string>(data); } std::string const &name() const { return std::get<std::string>(data); }
FileStackNode(FileStackNodeType type_, std::variant<std::vector<uint32_t>, std::string> data_) FileStackNode(
: type(type_), data(data_) {} FileStackNodeType type_,
std::variant<std::vector<uint32_t>, std::string> data_,
bool isQuiet_
)
: type(type_), data(data_), isQuiet(isQuiet_) {}
void printBacktrace(uint32_t curLineNo) const; void printBacktrace(uint32_t curLineNo) const;
}; };
@@ -62,16 +67,19 @@ bool fstk_FileError(std::string const &path, char const *functionName);
bool fstk_FailedOnMissingInclude(); bool fstk_FailedOnMissingInclude();
bool yywrap(); bool yywrap();
bool fstk_RunInclude(std::string const &path); bool fstk_RunInclude(std::string const &path, bool isQuiet);
void fstk_RunMacro(std::string const &macroName, std::shared_ptr<MacroArgs> macroArgs); void fstk_RunMacro(
void fstk_RunRept(uint32_t count, int32_t reptLineNo, ContentSpan const &span); std::string const &macroName, std::shared_ptr<MacroArgs> macroArgs, bool isQuiet
);
void fstk_RunRept(uint32_t count, int32_t reptLineNo, ContentSpan const &span, bool isQuiet);
void fstk_RunFor( void fstk_RunFor(
std::string const &symName, std::string const &symName,
int32_t start, int32_t start,
int32_t stop, int32_t stop,
int32_t step, int32_t step,
int32_t reptLineNo, int32_t reptLineNo,
ContentSpan const &span ContentSpan const &span,
bool isQuiet
); );
bool fstk_Break(); bool fstk_Break();

View File

@@ -30,8 +30,9 @@ bool sym_IsPC(Symbol const *sym); // Forward declaration for `getSection`
struct Symbol { struct Symbol {
std::string name; std::string name;
SymbolType type; SymbolType type;
bool isExported; // Whether the symbol is to be exported bool isBuiltin;
bool isBuiltin; // Whether the symbol is a built-in bool isExported; // Not relevant for SYM_MACRO or SYM_EQUS
bool isQuiet; // Only relevant for SYM_MACRO
Section *section; Section *section;
std::shared_ptr<FileStackNode> src; // Where the symbol was defined std::shared_ptr<FileStackNode> src; // Where the symbol was defined
uint32_t fileLine; // Line 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 // Find a scoped symbol by name; do not return `@` or `_NARG` when they have no value
Symbol *sym_FindScopedValidSymbol(std::string const &symName); Symbol *sym_FindScopedValidSymbol(std::string const &symName);
Symbol const *sym_GetPC(); 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_Ref(std::string const &symName);
Symbol *sym_AddString(std::string const &symName, std::shared_ptr<std::string> value); Symbol *sym_AddString(std::string const &symName, std::shared_ptr<std::string> value);
Symbol *sym_RedefString(std::string const &symName, std::shared_ptr<std::string> value); Symbol *sym_RedefString(std::string const &symName, std::shared_ptr<std::string> value);

View File

@@ -11,58 +11,74 @@
#include "style.hpp" #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); bool trace_ParseTraceDepth(char const *arg);
template<typename T, typename M, typename N> template<typename T, typename M, typename N>
void trace_PrintBacktrace(std::vector<T> const &stack, M getName, N getLineNo) { void trace_PrintBacktrace(std::vector<T> 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); style_Set(stderr, STYLE_CYAN, true);
fputs(getName(item), stderr); fputs(getName(item), stderr);
style_Set(stderr, STYLE_CYAN, false); style_Set(stderr, STYLE_CYAN, false);
fprintf(stderr, "(%" PRIu32 ")", getLineNo(item)); fprintf(stderr, "(%" PRIu32 ")", getLineNo(item));
if (!tracing.collapse) {
putc('\n', stderr);
}
}; };
size_t n = stack.size(); if (tracing.collapse) {
if (traceDepth == TRACE_COLLAPSE) {
fputs(" ", stderr); // Just three spaces; the fourth will be handled by the loop fputs(" ", stderr); // Just three spaces; the fourth will be handled by the loop
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<size_t>(traceDepth) >= n) { if (tracing.depth == 0 || static_cast<size_t>(tracing.depth) >= n) {
for (size_t i = 0; i < n; ++i) { for (size_t i = 0; i < n; ++i) {
style_Reset(stderr); printLocation(i);
fprintf(stderr, " %s ", i == 0 ? "at" : "<-");
printLocation(stack[n - i - 1]);
putc('\n', stderr);
} }
} else { } else {
size_t last = traceDepth / 2; size_t last = tracing.depth / 2;
size_t first = traceDepth - last; size_t first = tracing.depth - last;
size_t skipped = n - traceDepth; size_t skipped = n - tracing.depth;
for (size_t i = 0; i < first; ++i) { for (size_t i = 0; i < first; ++i) {
style_Reset(stderr); printLocation(i);
fprintf(stderr, " %s ", i == 0 ? "at" : "<-");
printLocation(stack[n - i - 1]);
putc('\n', stderr);
} }
style_Reset(stderr); style_Reset(stderr);
fprintf(stderr, " ...%zu more%s\n", skipped, last ? "..." : "");
for (size_t i = n - last; i < n; ++i) { if (tracing.collapse) {
style_Reset(stderr);
fputs(" <-", stderr); fputs(" <-", stderr);
printLocation(stack[n - i - 1]); } 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); putc('\n', stderr);
} }
for (size_t i = n - last; i < n; ++i) {
printLocation(i);
}
} }
if (tracing.collapse) {
putc('\n', stderr);
}
style_Reset(stderr); style_Reset(stderr);
} }

View File

@@ -19,6 +19,7 @@ struct FileStackNode {
std::string // NODE_FILE, NODE_MACRO std::string // NODE_FILE, NODE_MACRO
> >
data; data;
bool isQuiet; // Whether to omit this node from error reporting
FileStackNode *parent; FileStackNode *parent;
// Line at which the parent context was exited; meaningless for the root level // Line at which the parent context was exited; meaningless for the root level

View File

@@ -9,7 +9,7 @@
#include "helpers.hpp" // assume #include "helpers.hpp" // assume
#define RGBDS_OBJECT_VERSION_STRING "RGB9" #define RGBDS_OBJECT_VERSION_STRING "RGB9"
#define RGBDS_OBJECT_REV 12U #define RGBDS_OBJECT_REV 13U
enum AssertionType { ASSERT_WARN, ASSERT_ERROR, ASSERT_FATAL }; enum AssertionType { ASSERT_WARN, ASSERT_ERROR, ASSERT_FATAL };
@@ -78,12 +78,18 @@ enum SectionType {
SECTTYPE_INVALID 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 { enum FileStackNodeType {
NODE_REPT, NODE_REPT,
NODE_FILE, NODE_FILE,
NODE_MACRO, NODE_MACRO,
}; };
static constexpr uint8_t FSTACKNODE_QUIET_BIT = 7;
// Nont-`const` members may be patched in RGBLINK depending on CLI flags // Nont-`const` members may be patched in RGBLINK depending on CLI flags
extern struct SectionTypeInfo { extern struct SectionTypeInfo {
std::string const name; std::string const name;

View File

@@ -9,7 +9,7 @@
.Sh SYNOPSIS .Sh SYNOPSIS
.Nm .Nm
.Op Fl EhVvw .Op Fl EhVvw
.Op Fl B Ar depth .Op Fl B Ar param
.Op Fl b Ar chars .Op Fl b Ar chars
.Op Fl \-color Ar when .Op Fl \-color Ar when
.Op Fl D Ar name Ns Op = Ns Ar value .Op Fl D Ar name Ns Op = Ns Ar value
@@ -53,15 +53,29 @@ is invalid because it could also be
.Fl \-version . .Fl \-version .
The arguments are as follows: The arguments are as follows:
.Bl -tag -width Ds .Bl -tag -width Ds
.It Fl B Ar depth , Fl \-backtrace Ar depth .It Fl B Ar param , Fl \-backtrace Ar param
Specifies the maximum depth for which Configures how location backtraces are printed if warnings or errors occur.
.Nm This flag may be specified multiple times with different parameters that combine meaningfully.
will print location backtraces for warnings or errors. If
Deeper backtraces than that will be abbreviated. .Ar param
.Fl B Ar 0 is a positive number, it specifies the maximum backtrace depth, abbreviating deeper ones.
allows unlimited-depth backtraces. Other valid parameter values are the following:
.Fl B Ar collapse .Bl -tag -width Ds
will print the entire location trace on one line. .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 .It Fl b Ar chars , Fl \-binary-digits Ar chars
Allow two characters to be used for binary constants in addition to the default Allow two characters to be used for binary constants in addition to the default
.Sq 0 .Sq 0

View File

@@ -1681,7 +1681,8 @@ $ rgbasm -o a.o a.asm
$ rgbasm -o b.o b.asm $ rgbasm -o b.o b.asm
$ rgbasm -o c.o c.asm $ rgbasm -o c.o c.asm
$ rgblink a.o b.o c.o $ 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 Linking failed with 1 error
.Ed .Ed
.Pp .Pp
@@ -2516,6 +2517,68 @@ PUSHO b.X, g.oOX
DW `..ooOOXX DW `..ooOOXX
POPO POPO
.Ed .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 .Sh SEE ALSO
.Xr rgbasm 1 , .Xr rgbasm 1 ,
.Xr rgblink 1 , .Xr rgblink 1 ,

View File

@@ -79,12 +79,16 @@ order, meaning the node with ID 0 is the last one in the list!
.It Cm LONG Ar ParentLineNo .It Cm LONG Ar ParentLineNo
Line at which the parent node's context was exited; meaningless for the root node. Line at which the parent node's context was exited; meaningless for the root node.
.It Cm BYTE Ar Type .It Cm BYTE Ar Type
Bits 0\(en6 indicate the node's type:
.Bl -column "Value" -compact .Bl -column "Value" -compact
.It Sy Value Ta Sy Meaning .It Sy Value Ta Sy Meaning
.It 0 Ta REPT node .It 0 Ta REPT node
.It 1 Ta File node .It 1 Ta File node
.It 2 Ta Macro node .It 2 Ta Macro node
.El .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 .It Cm IF Ar Type No \(!= 0
If the node is not a REPT node... If the node is not a REPT node...
.Pp .Pp

View File

@@ -9,7 +9,7 @@
.Sh SYNOPSIS .Sh SYNOPSIS
.Nm .Nm
.Op Fl dhMtVvwx .Op Fl dhMtVvwx
.Op Fl B Ar depth .Op Fl B Ar param
.Op Fl \-color Ar when .Op Fl \-color Ar when
.Op Fl l Ar linker_script .Op Fl l Ar linker_script
.Op Fl m Ar map_file .Op Fl m Ar map_file
@@ -65,15 +65,29 @@ is invalid because it could also be
.Fl \-version . .Fl \-version .
The arguments are as follows: The arguments are as follows:
.Bl -tag -width Ds .Bl -tag -width Ds
.It Fl B Ar depth , Fl \-backtrace Ar depth .It Fl B Ar param , Fl \-backtrace Ar param
Specifies the maximum depth for which Configures how location backtraces are printed if warnings or errors occur.
.Nm This flag may be specified multiple times with different parameters that combine meaningfully.
will print location backtraces for warnings or errors. If
Deeper backtraces than that will be abbreviated. .Ar param
.Fl B Ar 0 is a positive number, it specifies the maximum backtrace depth, abbreviating deeper ones.
allows unlimited-depth backtraces. Other valid parameter values are the following:
.Fl B Ar collapse .Bl -tag -width Ds
will print the entire location trace on one line. .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 .It Fl \-color Ar when
Specify when to highlight warning and error messages with color: Specify when to highlight warning and error messages with color:
.Ql always , .Ql always ,

View File

@@ -63,6 +63,16 @@ static std::string reptChain(FileStackNode const &node) {
using TraceNode = std::pair<std::string, uint32_t>; using TraceNode = std::pair<std::string, uint32_t>;
static std::vector<TraceNode> backtrace(FileStackNode const &node, uint32_t curLineNo) { static std::vector<TraceNode> 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) { if (!node.parent) {
assume(node.type != NODE_REPT && std::holds_alternative<std::string>(node.data)); assume(node.type != NODE_REPT && std::holds_alternative<std::string>(node.data));
return { 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(); checkRecursionDepth();
std::shared_ptr<std::string> uniqueIDStr = nullptr; std::shared_ptr<std::string> uniqueIDStr = nullptr;
std::shared_ptr<MacroArgs> macroArgs = nullptr; std::shared_ptr<MacroArgs> macroArgs = nullptr;
auto fileInfo = auto fileInfo =
std::make_shared<FileStackNode>(NODE_FILE, filePath == "-" ? "<stdin>" : filePath); std::make_shared<FileStackNode>(NODE_FILE, filePath == "-" ? "<stdin>" : filePath, isQuiet);
if (!contextStack.empty()) { if (!contextStack.empty()) {
Context &oldContext = contextStack.top(); Context &oldContext = contextStack.top();
fileInfo->parent = oldContext.fileInfo; fileInfo->parent = oldContext.fileInfo;
@@ -269,7 +279,8 @@ static void newFileContext(std::string const &filePath, bool updateStateNow) {
context.lexerState.setFileAsNextState(filePath, updateStateNow); context.lexerState.setFileAsNextState(filePath, updateStateNow);
} }
static void newMacroContext(Symbol const &macro, std::shared_ptr<MacroArgs> macroArgs) { static void
newMacroContext(Symbol const &macro, std::shared_ptr<MacroArgs> macroArgs, bool isQuiet) {
checkRecursionDepth(); checkRecursionDepth();
Context &oldContext = contextStack.top(); Context &oldContext = contextStack.top();
@@ -287,7 +298,7 @@ static void newMacroContext(Symbol const &macro, std::shared_ptr<MacroArgs> macr
fileInfoName.append("::"); fileInfoName.append("::");
fileInfoName.append(macro.name); fileInfoName.append(macro.name);
auto fileInfo = std::make_shared<FileStackNode>(NODE_MACRO, fileInfoName); auto fileInfo = std::make_shared<FileStackNode>(NODE_MACRO, fileInfoName, isQuiet);
assume(!contextStack.empty()); // The top level context cannot be a MACRO assume(!contextStack.empty()); // The top level context cannot be a MACRO
fileInfo->parent = oldContext.fileInfo; fileInfo->parent = oldContext.fileInfo;
fileInfo->lineNo = lexer_GetLineNo(); fileInfo->lineNo = lexer_GetLineNo();
@@ -301,7 +312,8 @@ static void newMacroContext(Symbol const &macro, std::shared_ptr<MacroArgs> macr
context.lexerState.setViewAsNextState("MACRO", macro.getMacro(), macro.fileLine); 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(); checkRecursionDepth();
Context &oldContext = contextStack.top(); 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())); fileInfoIters.insert(fileInfoIters.end(), RANGE(oldContext.fileInfo->iters()));
} }
auto fileInfo = std::make_shared<FileStackNode>(NODE_REPT, fileInfoIters); auto fileInfo = std::make_shared<FileStackNode>(NODE_REPT, fileInfoIters, isQuiet);
assume(!contextStack.empty()); // The top level context cannot be a REPT assume(!contextStack.empty()); // The top level context cannot be a REPT
fileInfo->parent = oldContext.fileInfo; fileInfo->parent = oldContext.fileInfo;
fileInfo->lineNo = reptLineNo; fileInfo->lineNo = reptLineNo;
@@ -356,15 +368,17 @@ bool fstk_FailedOnMissingInclude() {
return failedOnMissingInclude; return failedOnMissingInclude;
} }
bool fstk_RunInclude(std::string const &path) { bool fstk_RunInclude(std::string const &path, bool isQuiet) {
if (std::optional<std::string> fullPath = fstk_FindFile(path); fullPath) { if (std::optional<std::string> fullPath = fstk_FindFile(path); fullPath) {
newFileContext(*fullPath, false); newFileContext(*fullPath, isQuiet, false);
return false; return false;
} }
return fstk_FileError(path, "INCLUDE"); return fstk_FileError(path, "INCLUDE");
} }
void fstk_RunMacro(std::string const &macroName, std::shared_ptr<MacroArgs> macroArgs) { void fstk_RunMacro(
std::string const &macroName, std::shared_ptr<MacroArgs> macroArgs, bool isQuiet
) {
Symbol *macro = sym_FindExactSymbol(macroName); Symbol *macro = sym_FindExactSymbol(macroName);
if (!macro) { if (!macro) {
@@ -380,15 +394,15 @@ void fstk_RunMacro(std::string const &macroName, std::shared_ptr<MacroArgs> macr
return; 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) { if (count == 0) {
return; return;
} }
newReptContext(reptLineNo, span, count); newReptContext(reptLineNo, span, count, isQuiet);
} }
void fstk_RunFor( void fstk_RunFor(
@@ -397,7 +411,8 @@ void fstk_RunFor(
int32_t stop, int32_t stop,
int32_t step, int32_t step,
int32_t reptLineNo, int32_t reptLineNo,
ContentSpan const &span ContentSpan const &span,
bool isQuiet
) { ) {
if (Symbol *sym = sym_AddVar(symName, start); sym->type != SYM_VAR) { if (Symbol *sym = sym_AddVar(symName, start); sym->type != SYM_VAR) {
return; return;
@@ -422,7 +437,7 @@ void fstk_RunFor(
return; return;
} }
Context &context = newReptContext(reptLineNo, span, count); Context &context = newReptContext(reptLineNo, span, count, isQuiet);
context.isForLoop = true; context.isForLoop = true;
context.forValue = start; context.forValue = start;
context.forStep = step; context.forStep = step;
@@ -447,11 +462,11 @@ void fstk_NewRecursionDepth(size_t newDepth) {
} }
void fstk_Init(std::string const &mainPath) { void fstk_Init(std::string const &mainPath) {
newFileContext(mainPath, true); newFileContext(mainPath, false, true);
for (std::string const &name : preIncludeNames) { for (std::string const &name : preIncludeNames) {
if (std::optional<std::string> fullPath = fstk_FindFile(name); fullPath) { if (std::optional<std::string> fullPath = fstk_FindFile(name); fullPath) {
newFileContext(*fullPath, false); newFileContext(*fullPath, false, false);
} else { } else {
error("Error reading pre-included file \"%s\": %s", name.c_str(), strerror(errno)); error("Error reading pre-included file \"%s\": %s", name.c_str(), strerror(errno));
} }

View File

@@ -1561,9 +1561,8 @@ static bool isGarbageCharacter(int c) {
if (isWhitespace(c)) { if (isWhitespace(c)) {
return false; return false;
} }
// Printable characters which are nevertheless garbage: braces should have been interpolated, // Printable characters which are nevertheless garbage: braces should have been interpolated
// and question mark is unused if (c == '{' || c == '}') {
if (c == '{' || c == '}' || c == '?') {
return true; return true;
} }
// All other printable characters are not garbage (i.e. `yylex_NORMAL` handles them), and // All other printable characters are not garbage (i.e. `yylex_NORMAL` handles them), and
@@ -1614,6 +1613,9 @@ static Token yylex_NORMAL() {
case '~': case '~':
return Token(T_(OP_NOT)); return Token(T_(OP_NOT));
case '?':
return Token(T_(QUESTIONMARK));
case '@': { case '@': {
std::string symName("@"); std::string symName("@");
return Token(T_(SYMBOL), 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 // We need to distinguish between:
// macro invocations (which start with `SYMBOL`). // - 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 // 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 // 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 // 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 ':' // Instead, we have separate `SYMBOL`, `LABEL`, and `QMACRO` tokens, and decide which
// character *immediately* follows the identifier. Thus, "Label:" and "mac:" are treated // one to lex depending on the character *immediately* following the identifier.
// as label definitions, but "Label :" and "mac :" are treated as macro invocations. // 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.
// The alternative would be a "lexer hack" like C, where identifiers would lex as a if (token.type == T_(SYMBOL)) {
// `SYMBOL` if they are already defined, otherwise as a `LABEL`. c = peek();
if (token.type == T_(SYMBOL) && peek() == ':') { token.type = c == ':' ? T_(LABEL) : c == '?' ? T_(QMACRO) : T_(SYMBOL);
token.type = T_(LABEL);
} }
return token; return token;

View File

@@ -83,10 +83,10 @@ static void writeSection(Section const &sect, FILE *file) {
putLong(sect.size, file); putLong(sect.size, file);
assume((sect.type & SECTTYPE_TYPE_MASK) == sect.type);
bool isUnion = sect.modifier == SECTION_UNION; bool isUnion = sect.modifier == SECTION_UNION;
bool isFragment = sect.modifier == SECTION_FRAGMENT; bool isFragment = sect.modifier == SECTION_FRAGMENT;
putc(sect.type | isUnion << SECTTYPE_UNION_BIT | isFragment << SECTTYPE_FRAGMENT_BIT, file);
putc(sect.type | isUnion << 7 | isFragment << 6, file);
putLong(sect.org, file); putLong(sect.org, file);
putLong(sect.bank, 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) { static void writeFileStackNode(FileStackNode const &node, FILE *file) {
putLong(node.parent ? node.parent->ID : UINT32_MAX, file); putLong(node.parent ? node.parent->ID : UINT32_MAX, file);
putLong(node.lineNo, file); putLong(node.lineNo, file);
putc(node.type, file);
putc(node.type | node.isQuiet << FSTACKNODE_QUIET_BIT, file);
if (node.type != NODE_REPT) { if (node.type != NODE_REPT) {
putString(node.name(), file); putString(node.name(), file);
} else { } else {

View File

@@ -98,6 +98,7 @@
%token LBRACK "[" RBRACK "]" %token LBRACK "[" RBRACK "]"
%token LBRACKS "[[" RBRACKS "]]" %token LBRACKS "[[" RBRACKS "]]"
%token LPAREN "(" RPAREN ")" %token LPAREN "(" RPAREN ")"
%token QUESTIONMARK "?"
// Arithmetic operators // Arithmetic operators
%token OP_ADD "+" OP_SUB "-" %token OP_ADD "+" OP_SUB "-"
@@ -322,6 +323,7 @@
%token <std::string> LABEL "label" %token <std::string> LABEL "label"
%token <std::string> LOCAL "local label" %token <std::string> LOCAL "local label"
%token <std::string> ANON "anonymous label" %token <std::string> ANON "anonymous label"
%token <std::string> QMACRO "quiet macro"
/******************** Data types ********************/ /******************** Data types ********************/
@@ -398,6 +400,7 @@
%type <SectionType> sect_type %type <SectionType> sect_type
%type <StrFmtArgList> strfmt_args %type <StrFmtArgList> strfmt_args
%type <StrFmtArgList> strfmt_va_args %type <StrFmtArgList> strfmt_va_args
%type <bool> maybe_quiet
%% %%
@@ -544,7 +547,13 @@ macro:
// Parsing 'macro_args' will restore the lexer's normal mode // Parsing 'macro_args' will restore the lexer's normal mode
lexer_SetMode(LEXER_RAW); lexer_SetMode(LEXER_RAW);
} macro_args { } 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: rept:
POP_REPT uconst NEWLINE capture_rept endofline { POP_REPT maybe_quiet uconst NEWLINE capture_rept endofline {
if ($4.span.ptr) { if ($5.span.ptr) {
fstk_RunRept($2, $4.lineNo, $4.span); fstk_RunRept($3, $5.lineNo, $5.span, $2);
} }
} }
; ;
@@ -791,11 +809,11 @@ rept:
for: for:
POP_FOR { POP_FOR {
lexer_ToggleStringExpansion(false); lexer_ToggleStringExpansion(false);
} SYMBOL { } maybe_quiet SYMBOL {
lexer_ToggleStringExpansion(true); lexer_ToggleStringExpansion(true);
} COMMA for_args NEWLINE capture_rept endofline { } COMMA for_args NEWLINE capture_rept endofline {
if ($8.span.ptr) { if ($9.span.ptr) {
fstk_RunFor($3, $6.start, $6.stop, $6.step, $8.lineNo, $8.span); fstk_RunFor($4, $7.start, $7.stop, $7.step, $9.lineNo, $9.span, $3);
} }
} }
; ;
@@ -835,11 +853,11 @@ break:
def_macro: def_macro:
POP_MACRO { POP_MACRO {
lexer_ToggleStringExpansion(false); lexer_ToggleStringExpansion(false);
} SYMBOL { } maybe_quiet SYMBOL {
lexer_ToggleStringExpansion(true); lexer_ToggleStringExpansion(true);
} NEWLINE capture_macro endofline { } NEWLINE capture_macro endofline {
if ($6.span.ptr) { if ($7.span.ptr) {
sym_AddMacro($3, $6.lineNo, $6.span); sym_AddMacro($4, $7.lineNo, $7.span, $3);
} }
} }
; ;
@@ -1002,8 +1020,8 @@ export_def:
; ;
include: include:
label POP_INCLUDE string endofline { label POP_INCLUDE maybe_quiet string endofline {
if (fstk_RunInclude($3)) { if (fstk_RunInclude($4, $3)) {
YYACCEPT; YYACCEPT;
} }
} }

View File

@@ -205,8 +205,9 @@ static Symbol &createSymbol(std::string const &symName) {
Symbol &sym = symbols[symName]; Symbol &sym = symbols[symName];
sym.name = symName; sym.name = symName;
sym.isExported = false;
sym.isBuiltin = false; sym.isBuiltin = false;
sym.isExported = false;
sym.isQuiet = false;
sym.section = nullptr; sym.section = nullptr;
sym.src = fstk_GetFileStack(); sym.src = fstk_GetFileStack();
sym.fileLine = sym.src ? lexer_GetLineNo() : 0; sym.fileLine = sym.src ? lexer_GetLineNo() : 0;
@@ -616,7 +617,9 @@ void sym_Export(std::string const &symName) {
sym->isExported = true; 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); Symbol *sym = createNonrelocSymbol(symName, false);
if (!sym) { if (!sym) {
@@ -625,6 +628,7 @@ Symbol *sym_AddMacro(std::string const &symName, int32_t defLineNo, ContentSpan
sym->type = SYM_MACRO; sym->type = SYM_MACRO;
sym->data = span; sym->data = span;
sym->isQuiet = isQuiet;
sym->src = fstk_GetFileStack(); sym->src = fstk_GetFileStack();
// The symbol is created at the line after the `ENDM`, // The symbol is created at the line after the `ENDM`,

View File

@@ -6,16 +6,24 @@
#include "platform.hpp" // strcasecmp #include "platform.hpp" // strcasecmp
uint64_t traceDepth = 0; Tracing tracing;
bool trace_ParseTraceDepth(char const *arg) { bool trace_ParseTraceDepth(char const *arg) {
if (!strcasecmp(arg, "collapse")) { if (!strcasecmp(arg, "collapse")) {
traceDepth = TRACE_COLLAPSE; tracing.collapse = true;
return 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; char *endptr;
traceDepth = strtoul(arg, &endptr, 0); tracing.depth = strtoul(arg, &endptr, 0);
return arg[0] != '\0' && *endptr == '\0';
return arg[0] != '\0' && *endptr == '\0' && traceDepth != TRACE_COLLAPSE; }
} }

View File

@@ -9,6 +9,16 @@
using TraceNode = std::pair<std::string, uint32_t>; using TraceNode = std::pair<std::string, uint32_t>;
static std::vector<TraceNode> backtrace(FileStackNode const &node, uint32_t curLineNo) { static std::vector<TraceNode> 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) { if (!node.parent) {
assume(node.type != NODE_REPT && std::holds_alternative<std::string>(node.data)); assume(node.type != NODE_REPT && std::holds_alternative<std::string>(node.data));
return { return {

View File

@@ -105,14 +105,12 @@ static void readFileStackNode(
tryReadLong( tryReadLong(
node.lineNo, file, "%s: Cannot read node #%" PRIu32 "'s line number: %s", fileName, nodeID node.lineNo, file, "%s: Cannot read node #%" PRIu32 "'s line number: %s", fileName, nodeID
); );
tryGetc(
FileStackNodeType, uint8_t type;
node.type, tryGetc(uint8_t, type, file, "%s: Cannot read node #%" PRIu32 "'s type: %s", fileName, nodeID);
file, node.type = static_cast<FileStackNodeType>(type & ~(1 << FSTACKNODE_QUIET_BIT));
"%s: Cannot read node #%" PRIu32 "'s type: %s", node.isQuiet = (type & (1 << FSTACKNODE_QUIET_BIT)) != 0;
fileName,
nodeID
);
switch (node.type) { switch (node.type) {
case NODE_FILE: case NODE_FILE:
case NODE_MACRO: case NODE_MACRO:
@@ -318,14 +316,14 @@ static void readSection(
tryGetc( tryGetc(
uint8_t, byte, file, "%s: Cannot read \"%s\"'s type: %s", fileName, section.name.c_str() 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); fatal("\"%s\" has unknown section type 0x%02x", section.name.c_str(), type);
} else { } else {
section.type = SectionType(type); section.type = SectionType(type);
} }
if (byte >> 7) { if (byte & (1 << SECTTYPE_UNION_BIT)) {
section.modifier = SECTION_UNION; section.modifier = SECTION_UNION;
} else if (byte >> 6) { } else if (byte & (1 << SECTTYPE_FRAGMENT_BIT)) {
section.modifier = SECTION_FRAGMENT; section.modifier = SECTION_FRAGMENT;
} else { } else {
section.modifier = SECTION_NORMAL; section.modifier = SECTION_NORMAL;
@@ -458,6 +456,7 @@ void obj_ReadFile(char const *fileName, unsigned int fileID) {
nodes[fileID].push_back({ nodes[fileID].push_back({
.type = NODE_FILE, .type = NODE_FILE,
.data = std::variant<std::monostate, std::vector<uint32_t>, std::string>(fileName), .data = std::variant<std::monostate, std::vector<uint32_t>, std::string>(fileName),
.isQuiet = false,
.parent = nullptr, .parent = nullptr,
.lineNo = 0, .lineNo = 0,
}); });

View File

@@ -1 +1 @@
-B 5 -B 5 -B no-collapse

View File

@@ -1 +1 @@
-B 0 -B no-collapse

View File

@@ -1,5 +1,5 @@
assert 1 +# 1 == 2 assert 1 +# 1 == 2
assert 2 ?<EFBFBD>* 2 == 4 assert 2 #<EFBFBD>* 2 == 4
assert 3 **?<EFBFBD>?##?? 3 == 27 assert 3 **#<EFBFBD>}<EFBFBD># 3 == 27
charmap "x", 4 charmap "x", 4
assert 4 <<#<EFBFBD>'x' == 64 assert 4 <<#<EFBFBD>'x' == 64

View File

@@ -1,13 +1,11 @@
error: Invalid character '#' error: Invalid character '#'
at garbage_sequence.asm(1) 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) 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) at garbage_sequence.asm(3)
error: Invalid character '#' error: Invalid character '#'
at garbage_sequence.asm(3) at garbage_sequence.asm(3)
error: Invalid characters '#', '?', '?'
at garbage_sequence.asm(3)
error: Invalid characters '#', 0xFF (is the file UTF-8?) error: Invalid characters '#', 0xFF (is the file UTF-8?)
at garbage_sequence.asm(5) at garbage_sequence.asm(5)
Assembly aborted with 6 errors! Assembly aborted with 5 errors!

View File

@@ -1,10 +1,10 @@
error: syntax error, unexpected label, expecting symbol error: syntax error, unexpected label, expecting symbol
at lex-label.asm(1) 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) at lex-label.asm(3)
error: syntax error, unexpected ENDM error: syntax error, unexpected ENDM
at lex-label.asm(4) 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) at lex-label.asm(6)
error: syntax error, unexpected ENDR error: syntax error, unexpected ENDR
at lex-label.asm(7) at lex-label.asm(7)

View File

@@ -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?

View File

@@ -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)

View File

@@ -0,0 +1 @@
-B all

View File

@@ -0,0 +1 @@
warn "from quiet include"

View File

@@ -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?

View File

@@ -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)

View File

@@ -0,0 +1 @@
warn "from quiet include"

View File

@@ -1 +1 @@
-B 0 -B no-collapse