Use colored/styled text output for diagnostics and usage info (#1775)

This commit is contained in:
Rangi
2025-08-04 17:02:24 -04:00
committed by GitHub
parent d992b21141
commit 23ce888d65
27 changed files with 656 additions and 197 deletions

View File

@@ -72,7 +72,7 @@ bool charmap_ForEach(
});
mapFunc(charmap.name);
for (auto [nodeIdx, mapping] : mappings) {
for (auto const &[nodeIdx, mapping] : mappings) {
charFunc(mapping, charmap.nodes[nodeIdx].value);
}
}

View File

@@ -15,6 +15,7 @@
#include "helpers.hpp"
#include "linkdefs.hpp"
#include "platform.hpp" // S_ISDIR (stat macro)
#include "style.hpp"
#include "verbosity.hpp"
#include "asm/lexer.hpp"
@@ -63,19 +64,27 @@ std::string const &FileStackNode::dump(uint32_t curLineNo) const {
if (std::holds_alternative<std::vector<uint32_t>>(data)) {
assume(parent); // REPT nodes use their parent's name
std::string const &lastName = parent->dump(lineNo);
style_Set(stderr, STYLE_CYAN, false);
fputs(" -> ", stderr);
style_Set(stderr, STYLE_CYAN, true);
fputs(lastName.c_str(), stderr);
fputs(reptChain().c_str(), stderr);
style_Set(stderr, STYLE_CYAN, false);
fprintf(stderr, "(%" PRIu32 ")", curLineNo);
style_Reset(stderr);
return lastName;
} else {
if (parent) {
parent->dump(lineNo);
style_Set(stderr, STYLE_CYAN, false);
fputs(" -> ", stderr);
}
std::string const &nodeName = name();
style_Set(stderr, STYLE_CYAN, true);
fputs(nodeName.c_str(), stderr);
style_Set(stderr, STYLE_CYAN, false);
fprintf(stderr, "(%" PRIu32 ")", curLineNo);
style_Reset(stderr);
return nodeName;
}
}

View File

@@ -18,6 +18,7 @@
#include <unordered_map>
#include "helpers.hpp"
#include "style.hpp"
#include "util.hpp"
#include "verbosity.hpp"
@@ -845,9 +846,15 @@ void lexer_DumpStringExpansions() {
for (Expansion &exp : lexerState->expansions) {
// Only register EQUS expansions, not string args
if (exp.name) {
fprintf(stderr, "while expanding symbol \"%s\"\n", exp.name->c_str());
style_Set(stderr, STYLE_CYAN, false);
fputs("while expanding symbol \"", stderr);
style_Set(stderr, STYLE_CYAN, true);
fputs(exp.name->c_str(), stderr);
style_Set(stderr, STYLE_CYAN, false);
fputs("\"\n", stderr);
}
}
style_Reset(stderr);
}
// Functions to discard non-tokenized characters

View File

@@ -15,6 +15,7 @@
#include "extern/getopt.hpp"
#include "helpers.hpp"
#include "parser.hpp" // Generated from parser.y
#include "style.hpp"
#include "usage.hpp"
#include "util.hpp" // UpperMap
#include "verbosity.hpp"
@@ -36,7 +37,7 @@ static std::unordered_map<std::string, std::vector<StateFeature>> stateFileSpecs
static char const *optstring = "b:D:Eg:hI:M:o:P:p:Q:r:s:VvW:wX:";
// Variables for the long-only options
static int depType; // Variants of `-M`
static int longOpt; // `--color` and variants of `-M`
// Equivalent long options
// Please keep in the same order as short opts.
@@ -53,11 +54,6 @@ static option const longopts[] = {
{"help", no_argument, nullptr, 'h'},
{"include", required_argument, nullptr, 'I'},
{"dependfile", required_argument, nullptr, 'M'},
{"MC", no_argument, &depType, 'C'},
{"MG", no_argument, &depType, 'G'},
{"MP", no_argument, &depType, 'P'},
{"MQ", required_argument, &depType, 'Q'},
{"MT", required_argument, &depType, 'T'},
{"output", required_argument, nullptr, 'o'},
{"preinclude", required_argument, nullptr, 'P'},
{"pad-value", required_argument, nullptr, 'p'},
@@ -68,27 +64,34 @@ static option const longopts[] = {
{"verbose", no_argument, nullptr, 'v'},
{"warning", required_argument, nullptr, 'W'},
{"max-errors", required_argument, nullptr, 'X'},
{nullptr, no_argument, nullptr, 0 }
{"color", required_argument, &longOpt, 'c'},
{"MC", no_argument, &longOpt, 'C'},
{"MG", no_argument, &longOpt, 'G'},
{"MP", no_argument, &longOpt, 'P'},
{"MQ", required_argument, &longOpt, 'Q'},
{"MT", required_argument, &longOpt, 'T'},
{nullptr, no_argument, nullptr, 0 },
};
// clang-format off: long string literal
static Usage usage(
"Usage: rgbasm [-EhVvw] [-b chars] [-D name[=value]] [-g chars] [-I path]\n"
" [-M depend_file] [-MC] [-MG] [-MP] [-MT target_file] [-MQ target_file]\n"
" [-o out_file] [-P include_file] [-p pad_value] [-Q precision]\n"
" [-r depth] [-s features:state_file] [-W warning] [-X max_errors]\n"
" <file>\n"
"Useful options:\n"
" -E, --export-all export all labels\n"
" -M, --dependfile <path> set the output dependency file\n"
" -o, --output <path> set the output object file\n"
" -p, --pad-value <value> set the value to use for `ds'\n"
" -s, --state <features>:<path> set an output state file\n"
" -V, --version print RGBASM version and exit\n"
" -W, --warning <warning> enable or disable warnings\n"
"\n"
"For help, use `man rgbasm' or go to https://rgbds.gbdev.io/docs/\n"
);
// clang-format off: nested initializers
static Usage usage = {
.name = "rgbasm",
.flags = {
"[-EhVvw]", "[-b chars]", "[-D name[=value]]", "[-g chars]", "[-I path]",
"[-M depend_file]", "[-MC]", "[-MG]", "[-MP]", "[-MT target_file]", "[-MQ target_file]",
"[-o out_file]", "[-P include_file]", "[-p pad_value]", "[-Q precision]", "[-r depth]",
"[-s features:state_file]", "[-W warning]", "[-X max_errors]", "<file>",
},
.options = {
{{"-E", "--export-all"}, {"export all labels"}},
{{"-M", "--dependfile <path>"}, {"set the output dependency file"}},
{{"-o", "--output <path>"}, {"set the output object file"}},
{{"-p", "--pad-value <value>"}, {"set the value to use for `ds'"}},
{{"-s", "--state <features>:<path>"}, {"set an output state file"}},
{{"-V", "--version"}, {"print RGBASM version and exit"}},
{{"-W", "--warning <warning>"}, {"enable or disable warnings"}},
},
};
// clang-format on
// LCOV_EXCL_START
@@ -160,7 +163,7 @@ static void verboseOutputConfig(int argc, char *argv[]) {
"char",
"macro",
};
for (auto [name, features] : stateFileSpecs) {
for (auto const &[name, features] : stateFileSpecs) {
fprintf(stderr, "\t - %s: ", name == "-" ? "<stdout>" : name.c_str());
for (size_t i = 0; i < features.size(); ++i) {
if (i > 0) {
@@ -449,7 +452,17 @@ int main(int argc, char *argv[]) {
// Long-only options
case 0:
switch (depType) {
switch (longOpt) {
case 'c':
if (!strcasecmp(musl_optarg, "always")) {
style_Enable(true);
} else if (!strcasecmp(musl_optarg, "never")) {
style_Enable(false);
} else if (strcasecmp(musl_optarg, "auto")) {
fatal("Invalid argument for option '--color'");
}
break;
case 'C':
options.missingIncludeState = GEN_CONTINUE;
break;
@@ -465,7 +478,7 @@ int main(int argc, char *argv[]) {
case 'Q':
case 'T': {
std::string newTarget = musl_optarg;
if (depType == 'Q') {
if (longOpt == 'Q') {
newTarget = escapeMakeChars(newTarget);
}
if (!options.targetFileName.empty()) {
@@ -549,7 +562,7 @@ int main(int argc, char *argv[]) {
out_WriteObject();
for (auto [name, features] : stateFileSpecs) {
for (auto const &[name, features] : stateFileSpecs) {
out_WriteState(name, features);
}

View File

@@ -12,6 +12,7 @@
#include "diagnostics.hpp"
#include "helpers.hpp"
#include "itertools.hpp"
#include "style.hpp"
#include "asm/fstack.hpp"
#include "asm/lexer.hpp"
@@ -64,14 +65,24 @@ Diagnostics<WarningLevel, WarningID> warnings = {
// clang-format on
static void printDiag(
char const *fmt, va_list args, char const *type, char const *flagfmt, char const *flag
char const *fmt,
va_list args,
char const *type,
StyleColor color,
char const *flagfmt,
char const *flag
) {
fputs(type, stderr);
fputs(": ", stderr);
style_Set(stderr, color, true);
fprintf(stderr, "%s: ", type);
if (fstk_DumpCurrent()) {
fprintf(stderr, flagfmt, flag);
putc(':', stderr);
if (flagfmt) {
style_Set(stderr, color, true);
fprintf(stderr, flagfmt, flag);
}
fputs("\n ", stderr);
}
style_Reset(stderr);
vfprintf(stderr, fmt, args);
putc('\n', stderr);
@@ -82,13 +93,16 @@ static void incrementErrors() {
// This intentionally makes 0 act as "unlimited"
warnings.incrementErrors();
if (warnings.nbErrors == options.maxErrors) {
style_Set(stderr, STYLE_RED, true);
fprintf(
stderr,
"Assembly aborted after the maximum of %" PRIu64 " error%s! (configure with "
"'-X/--max-errors')\n",
"Assembly aborted after the maximum of %" PRIu64 " error%s!",
warnings.nbErrors,
warnings.nbErrors == 1 ? "" : "s"
);
style_Set(stderr, STYLE_RED, false);
fputs(" (configure with '-X/--max-errors')\n", stderr);
style_Reset(stderr);
exit(1);
}
}
@@ -97,17 +111,19 @@ void error(char const *fmt, ...) {
va_list args;
va_start(args, fmt);
printDiag(fmt, args, "error", ":", nullptr);
printDiag(fmt, args, "error", STYLE_RED, nullptr, nullptr);
va_end(args);
incrementErrors();
}
void error(std::function<void()> callback) {
style_Set(stderr, STYLE_RED, true);
fputs("error: ", stderr);
if (fstk_DumpCurrent()) {
fputs(":\n ", stderr);
}
style_Reset(stderr);
callback();
putc('\n', stderr);
lexer_DumpStringExpansions();
@@ -120,7 +136,7 @@ void fatal(char const *fmt, ...) {
va_list args;
va_start(args, fmt);
printDiag(fmt, args, "FATAL", ":", nullptr);
printDiag(fmt, args, "FATAL", STYLE_RED, nullptr, nullptr);
va_end(args);
exit(1);
@@ -128,12 +144,14 @@ void fatal(char const *fmt, ...) {
void requireZeroErrors() {
if (warnings.nbErrors != 0) {
style_Set(stderr, STYLE_RED, true);
fprintf(
stderr,
"Assembly aborted with %" PRIu64 " error%s!\n",
warnings.nbErrors,
warnings.nbErrors == 1 ? "" : "s"
);
style_Reset(stderr);
exit(1);
}
}
@@ -149,11 +167,11 @@ void warning(WarningID id, char const *fmt, ...) {
break;
case WarningBehavior::ENABLED:
printDiag(fmt, args, "warning", ": [-W%s]", flag);
printDiag(fmt, args, "warning", STYLE_YELLOW, " [-W%s]", flag);
break;
case WarningBehavior::ERROR:
printDiag(fmt, args, "error", ": [-Werror=%s]", flag);
printDiag(fmt, args, "error", STYLE_RED, " [-Werror=%s]", flag);
break;
}