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

@@ -63,6 +63,16 @@ static std::string reptChain(FileStackNode const &node) {
using TraceNode = std::pair<std::string, uint32_t>;
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) {
assume(node.type != NODE_REPT && std::holds_alternative<std::string>(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<std::string> uniqueIDStr = nullptr;
std::shared_ptr<MacroArgs> macroArgs = nullptr;
auto fileInfo =
std::make_shared<FileStackNode>(NODE_FILE, filePath == "-" ? "<stdin>" : filePath);
std::make_shared<FileStackNode>(NODE_FILE, filePath == "-" ? "<stdin>" : 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 &macro, std::shared_ptr<MacroArgs> macroArgs) {
static void
newMacroContext(Symbol const &macro, std::shared_ptr<MacroArgs> macroArgs, bool isQuiet) {
checkRecursionDepth();
Context &oldContext = contextStack.top();
@@ -287,7 +298,7 @@ static void newMacroContext(Symbol const &macro, std::shared_ptr<MacroArgs> macr
fileInfoName.append("::");
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
fileInfo->parent = oldContext.fileInfo;
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);
}
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<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
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<std::string> 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 &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);
if (!macro) {
@@ -380,15 +394,15 @@ void fstk_RunMacro(std::string const &macroName, std::shared_ptr<MacroArgs> 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<std::string> 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));
}

View File

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

View File

@@ -83,10 +83,10 @@ static void writeSection(Section const &sect, 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 {

View File

@@ -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 <std::string> LABEL "label"
%token <std::string> LOCAL "local label"
%token <std::string> ANON "anonymous label"
%token <std::string> QMACRO "quiet macro"
/******************** Data types ********************/
@@ -398,6 +400,7 @@
%type <SectionType> sect_type
%type <StrFmtArgList> strfmt_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
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;
}
}

View File

@@ -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`,

View File

@@ -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;
}

View File

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

View File

@@ -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<FileStackNodeType>(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::monostate, std::vector<uint32_t>, std::string>(fileName),
.isQuiet = false,
.parent = nullptr,
.lineNo = 0,
});