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

@@ -52,6 +52,7 @@ all: rgbasm rgblink rgbfix rgbgfx
common_obj := \ common_obj := \
src/extern/getopt.o \ src/extern/getopt.o \
src/diagnostics.o \ src/diagnostics.o \
src/style.o \
src/usage.o src/usage.o
rgbasm_obj := \ rgbasm_obj := \

View File

@@ -28,6 +28,7 @@
#define STDERR_FILENO 2 #define STDERR_FILENO 2
#define ssize_t int #define ssize_t int
#define SSIZE_MAX INT_MAX #define SSIZE_MAX INT_MAX
#define isatty _isatty
#else #else
#include <fcntl.h> // IWYU pragma: export #include <fcntl.h> // IWYU pragma: export
#include <limits.h> // IWYU pragma: export #include <limits.h> // IWYU pragma: export

42
include/style.hpp Normal file
View File

@@ -0,0 +1,42 @@
// SPDX-License-Identifier: MIT
#ifndef RGBDS_STYLE_HPP
#define RGBDS_STYLE_HPP
#include <stdio.h>
#if defined(_MSC_VER) || defined(__MINGW32__) || defined(__CYGWIN__)
#define STYLE_ANSI 0
#else
#define STYLE_ANSI 1
#endif
enum StyleColor {
#if STYLE_ANSI
// Values analogous to ANSI foreground and background SGR colors
STYLE_BLACK,
STYLE_RED,
STYLE_GREEN,
STYLE_YELLOW,
STYLE_BLUE,
STYLE_MAGENTA,
STYLE_CYAN,
STYLE_GRAY,
#else
// Values analogous to `FOREGROUND_*` constants from `windows.h`
STYLE_BLACK,
STYLE_BLUE, // bit 0
STYLE_GREEN, // bit 1
STYLE_CYAN, // STYLE_BLUE | STYLE_GREEN
STYLE_RED, // bit 2
STYLE_MAGENTA, // STYLE_BLUE | STYLE_RED
STYLE_YELLOW, // STYLE_GREEN | STYLE_RED
STYLE_GRAY, // STYLE_BLUE | STYLE_GREEN | STYLE_RED
#endif
};
void style_Enable(bool enable);
void style_Set(FILE *file, StyleColor color, bool bold);
void style_Reset(FILE *file);
#endif // RGBDS_STYLE_HPP

View File

@@ -4,12 +4,14 @@
#define RGBDS_USAGE_HPP #define RGBDS_USAGE_HPP
#include <stdarg.h> #include <stdarg.h>
#include <string>
#include <utility>
#include <vector>
class Usage { struct Usage {
char const *usage; std::string name;
std::vector<std::string> flags;
public: std::vector<std::pair<std::vector<std::string>, std::vector<std::string>>> options;
Usage(char const *usage_) : usage(usage_) {}
[[noreturn]] [[noreturn]]
void printAndExit(int code) const; void printAndExit(int code) const;

View File

@@ -10,6 +10,7 @@
.Nm .Nm
.Op Fl EhVvw .Op Fl EhVvw
.Op Fl b Ar chars .Op Fl b Ar chars
.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
.Op Fl g Ar chars .Op Fl g Ar chars
.Op Fl I Ar path .Op Fl I Ar path
@@ -65,6 +66,18 @@ letters,
.Sq # , .Sq # ,
or or
.Sq @ . .Sq @ .
.It Fl \-color Ar when
Specify when to highlight warning and error messages with color:
.Ql always ,
.Ql never ,
or
.Ql auto .
.Ql auto
determines whether to use colors based on the
.Ql Lk https://no-color.org/ NO_COLOR
or
.Ql Lk https://force-color.org/ FORCE_COLOR
environment variables, or whether the output is to a TTY.
.It Fl D Ar name Ns Oo = Ns Ar value Oc , Fl \-define Ar name Ns Oo = Ns Ar value Oc .It Fl D Ar name Ns Oo = Ns Ar value Oc , Fl \-define Ar name Ns Oo = Ns Ar value Oc
Add a string symbol to the compiled source code. Add a string symbol to the compiled source code.
This is equivalent to This is equivalent to

View File

@@ -10,6 +10,7 @@
.Nm .Nm
.Op Fl hjOsVvw .Op Fl hjOsVvw
.Op Fl C | c .Op Fl C | c
.Op Fl \-color Ar when
.Op Fl f Ar fix_spec .Op Fl f Ar fix_spec
.Op Fl i Ar game_id .Op Fl i Ar game_id
.Op Fl k Ar licensee_str .Op Fl k Ar licensee_str
@@ -46,13 +47,13 @@ can be a path to a file, or
to read from standard input. to read from standard input.
.Pp .Pp
Note that options can be abbreviated as long as the abbreviation is unambiguous: Note that options can be abbreviated as long as the abbreviation is unambiguous:
.Fl \-color-o .Fl \-verb
is is
.Fl \-color-only , .Fl \-verbose ,
but but
.Fl \-color .Fl \-ver
is invalid because it could also be is invalid because it could also be
.Fl \-color-compatible . .Fl \-version .
Options later in the command line override those set earlier. Options later in the command line override those set earlier.
Accepted options are as follows: Accepted options are as follows:
.Bl -tag -width Ds .Bl -tag -width Ds
@@ -70,6 +71,18 @@ to 0x80.
This overrides This overrides
.Fl c .Fl c
if it was set prior. if it was set prior.
.It Fl \-color Ar when
Specify when to highlight warning and error messages with color:
.Ql always ,
.Ql never ,
or
.Ql auto .
.Ql auto
determines whether to use colors based on the
.Ql Lk https://no-color.org/ NO_COLOR
or
.Ql Lk https://force-color.org/ FORCE_COLOR
environment variables, or whether the output is to a TTY.
.It Fl f Ar fix_spec , Fl \-fix-spec Ar fix_spec .It Fl f Ar fix_spec , Fl \-fix-spec Ar fix_spec
Fix certain header values that the Game Boy checks for correctness. Fix certain header values that the Game Boy checks for correctness.
Alternatively, intentionally trash these values by writing their binary inverse instead. Alternatively, intentionally trash these values by writing their binary inverse instead.

View File

@@ -15,6 +15,7 @@
.Op Fl a Ar attrmap | Fl A .Op Fl a Ar attrmap | Fl A
.Op Fl b Ar base_ids .Op Fl b Ar base_ids
.Op Fl c Ar pal_spec .Op Fl c Ar pal_spec
.Op Fl \-color Ar when
.Op Fl d Ar depth .Op Fl d Ar depth
.Op Fl i Ar input_tiles .Op Fl i Ar input_tiles
.Op Fl L Ar slice .Op Fl L Ar slice
@@ -195,6 +196,18 @@ See
.Sx PALETTE SPECIFICATION FORMATS .Sx PALETTE SPECIFICATION FORMATS
for a list of formats and their descriptions. for a list of formats and their descriptions.
.El .El
.It Fl \-color Ar when
Specify when to highlight warning and error messages with color:
.Ql always ,
.Ql never ,
or
.Ql auto .
.Ql auto
determines whether to use colors based on the
.Ql Lk https://no-color.org/ NO_COLOR
or
.Ql Lk https://force-color.org/ FORCE_COLOR
environment variables, or whether the output is to a TTY.
.It Fl d Ar depth , Fl \-depth Ar depth .It Fl d Ar depth , Fl \-depth Ar depth
Set the bit depth of the output tile data, in bits per pixel (bpp), either 1 or 2 (the default). Set the bit depth of the output tile data, in bits per pixel (bpp), either 1 or 2 (the default).
This changes how tile data is output, and the maximum number of colors per palette (2 and 4 respectively). This changes how tile data is output, and the maximum number of colors per palette (2 and 4 respectively).

View File

@@ -9,6 +9,7 @@
.Sh SYNOPSIS .Sh SYNOPSIS
.Nm .Nm
.Op Fl dhMtVvwx .Op Fl dhMtVvwx
.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
.Op Fl n Ar sym_file .Op Fl n Ar sym_file
@@ -63,6 +64,18 @@ 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 \-color Ar when
Specify when to highlight warning and error messages with color:
.Ql always ,
.Ql never ,
or
.Ql auto .
.Ql auto
determines whether to use colors based on the
.Ql Lk https://no-color.org/ NO_COLOR
or
.Ql Lk https://force-color.org/ FORCE_COLOR
environment variables, or whether the output is to a TTY.
.It Fl d , Fl \-dmg .It Fl d , Fl \-dmg
Enable DMG mode. Enable DMG mode.
Prohibit the use of sections that doesn't exist on a DMG, such as VRAM bank 1. Prohibit the use of sections that doesn't exist on a DMG, such as VRAM bank 1.

View File

@@ -5,6 +5,7 @@ configure_file(version.cpp _version.cpp ESCAPE_QUOTES)
set(common_src set(common_src
"extern/getopt.cpp" "extern/getopt.cpp"
"diagnostics.cpp" "diagnostics.cpp"
"style.cpp"
"usage.cpp" "usage.cpp"
"_version.cpp" "_version.cpp"
) )

View File

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

View File

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

View File

@@ -18,6 +18,7 @@
#include <unordered_map> #include <unordered_map>
#include "helpers.hpp" #include "helpers.hpp"
#include "style.hpp"
#include "util.hpp" #include "util.hpp"
#include "verbosity.hpp" #include "verbosity.hpp"
@@ -845,9 +846,15 @@ void lexer_DumpStringExpansions() {
for (Expansion &exp : lexerState->expansions) { for (Expansion &exp : lexerState->expansions) {
// Only register EQUS expansions, not string args // Only register EQUS expansions, not string args
if (exp.name) { 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 // Functions to discard non-tokenized characters

View File

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

View File

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

View File

@@ -1,3 +1,5 @@
// SPDX-License-Identifier: MIT
#include "diagnostics.hpp" #include "diagnostics.hpp"
void warnx(char const *fmt, ...) { void warnx(char const *fmt, ...) {

View File

@@ -16,6 +16,7 @@
#include "extern/getopt.hpp" #include "extern/getopt.hpp"
#include "helpers.hpp" #include "helpers.hpp"
#include "platform.hpp" #include "platform.hpp"
#include "style.hpp"
#include "usage.hpp" #include "usage.hpp"
#include "version.hpp" #include "version.hpp"
@@ -27,6 +28,9 @@ static constexpr off_t BANK_SIZE = 0x4000;
// Short options // Short options
static char const *optstring = "Ccf:hi:jk:L:l:m:n:Oo:p:r:st:VvW:w"; static char const *optstring = "Ccf:hi:jk:L:l:m:n:Oo:p:r:st:VvW:w";
// Variables for the long-only options
static int longOpt; // `--color`
// Equivalent long options // Equivalent long options
// Please keep in the same order as short opts. // Please keep in the same order as short opts.
// Also, make sure long opts don't create ambiguity: // Also, make sure long opts don't create ambiguity:
@@ -55,27 +59,34 @@ static option const longopts[] = {
{"version", no_argument, nullptr, 'V'}, {"version", no_argument, nullptr, 'V'},
{"validate", no_argument, nullptr, 'v'}, {"validate", no_argument, nullptr, 'v'},
{"warning", required_argument, nullptr, 'W'}, {"warning", required_argument, nullptr, 'W'},
{nullptr, no_argument, nullptr, 0 } {"color", required_argument, &longOpt, 'c'},
{nullptr, no_argument, nullptr, 0 },
}; };
// clang-format off: long string literal // clang-format off: nested initializers
static Usage usage( static Usage usage = {
"Usage: rgbfix [-hjOsVvw] [-C | -c] [-f <fix_spec>] [-i <game_id>] [-k <licensee>]\n" .name = "rgbfix",
" [-L <logo_file>] [-l <licensee_byte>] [-m <mbc_type>]\n" .flags = {
" [-n <rom_version>] [-p <pad_value>] [-r <ram_size>] [-t <title_str>]\n" "[-hjOsVvw]", "[-C | -c]", "[-f <fix_spec>]", "[-i <game_id>]", "[-k <licensee>]",
" [-W warning] <file> ...\n" "[-L <logo_file>]", "[-l <licensee_byte>]", "[-m <mbc_type>]", "[-n <rom_version>]",
"Useful options:\n" "[-p <pad_value>]", "[-r <ram_size>]", "[-t <title_str>]", "[-W warning]", "<file> ...",
" -m, --mbc-type <value> set the MBC type byte to this value; `-m help'\n" },
" or `-m list' prints the accepted values\n" .options = {
" -p, --pad-value <value> pad to the next valid size using this value\n" {
" -r, --ram-size <code> set the cart RAM size byte to this value\n" {"-m", "--mbc-type <value>"},
" -o, --output <path> set the output file\n" {
" -V, --version print RGBFIX version and exit\n" "set the MBC type byte to this value; `-m help'",
" -v, --validate fix the header logo and both checksums (`-f lhg')\n" "or `-m list' prints the accepted values",
" -W, --warning <warning> enable or disable warnings\n" },
"\n" },
"For help, use `man rgbfix' or go to https://rgbds.gbdev.io/docs/\n" {{"-p", "--pad-value <value>"}, {"pad to the next valid size using this value"}},
); {{"-r", "--ram-size <code>"}, {"set the cart RAM size byte to this value"}},
{{"-o", "--output <path>"}, {"set the output file"}},
{{"-V", "--version"}, {"print RGBFIX version and exit"}},
{{"-v", "--validate"}, {"fix the header logo and both checksums (`-f lhg')"}},
{{"-W", "--warning <warning>"}, {"enable or disable warnings"}},
},
};
// clang-format on // clang-format on
static uint8_t tpp1Rev[2]; static uint8_t tpp1Rev[2];
@@ -817,6 +828,19 @@ int main(int argc, char *argv[]) {
warnings.state.warningsEnabled = false; warnings.state.warningsEnabled = false;
break; break;
// Long-only options
case 0:
if (longOpt == '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;
default: default:
usage.printAndExit(1); // LCOV_EXCL_LINE usage.printAndExit(1); // LCOV_EXCL_LINE
} }

View File

@@ -1,3 +1,5 @@
// SPDX-License-Identifier: MIT
#include "fix/mbc.hpp" #include "fix/mbc.hpp"
#include <stdlib.h> #include <stdlib.h>

View File

@@ -1,5 +1,9 @@
// SPDX-License-Identifier: MIT
#include "fix/warning.hpp" #include "fix/warning.hpp"
#include "style.hpp"
// clang-format off: nested initializers // clang-format off: nested initializers
Diagnostics<WarningLevel, WarningID> warnings = { Diagnostics<WarningLevel, WarningID> warnings = {
.metaWarnings = { .metaWarnings = {
@@ -20,6 +24,7 @@ Diagnostics<WarningLevel, WarningID> warnings = {
uint32_t checkErrors(char const *filename) { uint32_t checkErrors(char const *filename) {
if (warnings.nbErrors > 0) { if (warnings.nbErrors > 0) {
style_Set(stderr, STYLE_RED, true);
fprintf( fprintf(
stderr, stderr,
"Fixing \"%s\" failed with %" PRIu64 " error%s\n", "Fixing \"%s\" failed with %" PRIu64 " error%s\n",
@@ -27,13 +32,16 @@ uint32_t checkErrors(char const *filename) {
warnings.nbErrors, warnings.nbErrors,
warnings.nbErrors == 1 ? "" : "s" warnings.nbErrors == 1 ? "" : "s"
); );
style_Reset(stderr);
} }
return warnings.nbErrors; return warnings.nbErrors;
} }
void error(char const *fmt, ...) { void error(char const *fmt, ...) {
va_list ap; va_list ap;
style_Set(stderr, STYLE_RED, true);
fputs("error: ", stderr); fputs("error: ", stderr);
style_Reset(stderr);
va_start(ap, fmt); va_start(ap, fmt);
vfprintf(stderr, fmt, ap); vfprintf(stderr, fmt, ap);
va_end(ap); va_end(ap);
@@ -44,7 +52,9 @@ void error(char const *fmt, ...) {
void fatal(char const *fmt, ...) { void fatal(char const *fmt, ...) {
va_list ap; va_list ap;
style_Set(stderr, STYLE_RED, true);
fputs("FATAL: ", stderr); fputs("FATAL: ", stderr);
style_Reset(stderr);
va_start(ap, fmt); va_start(ap, fmt);
vfprintf(stderr, fmt, ap); vfprintf(stderr, fmt, ap);
va_end(ap); va_end(ap);
@@ -62,7 +72,9 @@ void warning(WarningID id, char const *fmt, ...) {
break; break;
case WarningBehavior::ENABLED: case WarningBehavior::ENABLED:
style_Set(stderr, STYLE_YELLOW, true);
fprintf(stderr, "warning: [-W%s]\n ", flag); fprintf(stderr, "warning: [-W%s]\n ", flag);
style_Reset(stderr);
va_start(ap, fmt); va_start(ap, fmt);
vfprintf(stderr, fmt, ap); vfprintf(stderr, fmt, ap);
va_end(ap); va_end(ap);
@@ -70,7 +82,9 @@ void warning(WarningID id, char const *fmt, ...) {
break; break;
case WarningBehavior::ERROR: case WarningBehavior::ERROR:
style_Set(stderr, STYLE_RED, true);
fprintf(stderr, "error: [-Werror=%s]\n ", flag); fprintf(stderr, "error: [-Werror=%s]\n ", flag);
style_Reset(stderr);
va_start(ap, fmt); va_start(ap, fmt);
vfprintf(stderr, fmt, ap); vfprintf(stderr, fmt, ap);
va_end(ap); va_end(ap);

View File

@@ -18,6 +18,7 @@
#include "extern/getopt.hpp" #include "extern/getopt.hpp"
#include "file.hpp" #include "file.hpp"
#include "platform.hpp" #include "platform.hpp"
#include "style.hpp"
#include "usage.hpp" #include "usage.hpp"
#include "verbosity.hpp" #include "verbosity.hpp"
#include "version.hpp" #include "version.hpp"
@@ -44,6 +45,9 @@ static struct LocalOptions {
// Short options // Short options
static char const *optstring = "-Aa:B:b:Cc:d:hi:L:l:mN:n:Oo:Pp:Qq:r:s:Tt:U:uVvW:wXx:YZ"; static char const *optstring = "-Aa:B:b:Cc:d:hi:L:l:mN:n:Oo:Pp:Qq:r:s:Tt:U:uVvW:wXx:YZ";
// Variables for the long-only options
static int longOpt; // `--color`
// Equivalent long options // Equivalent long options
// Please keep in the same order as short opts. // Please keep in the same order as short opts.
// Also, make sure long opts don't create ambiguity: // Also, make sure long opts don't create ambiguity:
@@ -85,26 +89,29 @@ static option const longopts[] = {
{"trim-end", required_argument, nullptr, 'x'}, {"trim-end", required_argument, nullptr, 'x'},
{"mirror-y", no_argument, nullptr, 'Y'}, {"mirror-y", no_argument, nullptr, 'Y'},
{"columns", no_argument, nullptr, 'Z'}, {"columns", no_argument, nullptr, 'Z'},
{nullptr, no_argument, nullptr, 0 } {"color", required_argument, &longOpt, 'c'},
{nullptr, no_argument, nullptr, 0 },
}; };
// clang-format off: long string literal // clang-format off: nested initializers
static Usage usage( static Usage usage = {
"Usage: rgbgfx [-r stride] [-ChmOuVXYZ] [-v [-v ...]] [-a <attr_map> | -A]\n" .name = "rgbgfx",
" [-b <base_ids>] [-c <colors>] [-d <depth>] [-i <tileset_file>]\n" .flags = {
" [-L <slice>] [-l <base_pal>] [-N <nb_tiles>] [-n <nb_pals>]\n" "[-r stride]", "[-ChmOuVXYZ]", "[-v [-v ...]]", "[-a <attr_map> | -A]", "[-b <base_ids>]",
" [-o <out_file>] [-p <pal_file> | -P] [-q <pal_map> | -Q]\n" "[-c <colors>]", "[-d <depth>]", "[-i <tileset_file>]", "[-L <slice>]", "[-l <base_pal>]",
" [-s <nb_colors>] [-t <tile_map> | -T] [-x <nb_tiles>] <file>\n" "[-N <nb_tiles>]", "[-n <nb_pals>]", "[-o <out_file>]", "[-p <pal_file> | -P]",
"Useful options:\n" "[-q <pal_map> | -Q]", "[-s <nb_colors>]", "[-t <tile_map> | -T]", "[-x <nb_tiles>]",
" -m, --mirror-tiles optimize out mirrored tiles\n" "<file>",
" -o, --output <path> output the tile data to this path\n" },
" -t, --tilemap <path> output the tile map to this path\n" .options = {
" -u, --unique-tiles optimize out identical tiles\n" {{"-m", "--mirror-tiles"}, {"optimize out mirrored tiles"}},
" -V, --version print RGBGFX version and exit\n" {{"-o", "--output <path>"}, {"output the tile data to this path"}},
" -W, --warning <warning> enable or disable warnings\n" {{"-t", "--tilemap <path>"}, {"output the tile map to this path"}},
"\n" {{"-u", "--unique-tiles"}, {"optimize out identical tiles"}},
"For help, use `man rgbgfx' or go to https://rgbds.gbdev.io/docs/\n" {{"-V", "--version"}, {"print RGBGFX version and exit"}},
); {{"-W", "--warning <warning>"}, {"enable or disable warnings"}},
},
};
// clang-format on // clang-format on
// Parses a number at the beginning of a string, moving the pointer to skip the parsed characters. // Parses a number at the beginning of a string, moving the pointer to skip the parsed characters.
@@ -552,6 +559,17 @@ static char *parseArgv(int argc, char *argv[]) {
case 'Z': case 'Z':
options.columnMajor = true; options.columnMajor = true;
break; break;
case 0: // Long-only options
if (longOpt == '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 1: // Positional argument, requested by leading `-` in opt string case 1: // Positional argument, requested by leading `-` in opt string
if (musl_optarg[0] == '@') { if (musl_optarg[0] == '@') {
// Instruct the caller to process that at-file // Instruct the caller to process that at-file

View File

@@ -630,7 +630,7 @@ static void outputUnoptimizedTileData(
uint64_t nbKeptTiles = nbTiles > options.trim ? nbTiles - options.trim : 0; uint64_t nbKeptTiles = nbTiles > options.trim ? nbTiles - options.trim : 0;
uint64_t tileIdx = 0; uint64_t tileIdx = 0;
for (auto [tile, attr] : zip(image.visitAsTiles(), attrmap)) { for (auto const &[tile, attr] : zip(image.visitAsTiles(), attrmap)) {
// Do not emit fully-background tiles. // Do not emit fully-background tiles.
if (attr.isBackgroundTile()) { if (attr.isBackgroundTile()) {
++tileIdx; ++tileIdx;
@@ -799,7 +799,7 @@ static UniqueTiles dedupTiles(
} }
bool inputWithoutOutput = !options.inputTileset.empty() && options.output.empty(); bool inputWithoutOutput = !options.inputTileset.empty() && options.output.empty();
for (auto [tile, attr] : zip(image.visitAsTiles(), attrmap)) { for (auto const &[tile, attr] : zip(image.visitAsTiles(), attrmap)) {
if (attr.isBackgroundTile()) { if (attr.isBackgroundTile()) {
attr.xFlip = false; attr.xFlip = false;
attr.yFlip = false; attr.yFlip = false;

View File

@@ -7,6 +7,8 @@
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include "style.hpp"
// clang-format off: nested initializers // clang-format off: nested initializers
Diagnostics<WarningLevel, WarningID> warnings = { Diagnostics<WarningLevel, WarningID> warnings = {
.metaWarnings = { .metaWarnings = {
@@ -25,12 +27,14 @@ Diagnostics<WarningLevel, WarningID> warnings = {
[[noreturn]] [[noreturn]]
void giveUp() { void giveUp() {
style_Set(stderr, STYLE_RED, true);
fprintf( fprintf(
stderr, stderr,
"Conversion aborted after %" PRIu64 " error%s\n", "Conversion aborted after %" PRIu64 " error%s\n",
warnings.nbErrors, warnings.nbErrors,
warnings.nbErrors == 1 ? "" : "s" warnings.nbErrors == 1 ? "" : "s"
); );
style_Reset(stderr);
exit(1); exit(1);
} }
@@ -42,7 +46,9 @@ void requireZeroErrors() {
void error(char const *fmt, ...) { void error(char const *fmt, ...) {
va_list ap; va_list ap;
style_Set(stderr, STYLE_RED, true);
fputs("error: ", stderr); fputs("error: ", stderr);
style_Reset(stderr);
va_start(ap, fmt); va_start(ap, fmt);
vfprintf(stderr, fmt, ap); vfprintf(stderr, fmt, ap);
va_end(ap); va_end(ap);
@@ -54,7 +60,9 @@ void error(char const *fmt, ...) {
[[noreturn]] [[noreturn]]
void fatal(char const *fmt, ...) { void fatal(char const *fmt, ...) {
va_list ap; va_list ap;
style_Set(stderr, STYLE_RED, true);
fputs("FATAL: ", stderr); fputs("FATAL: ", stderr);
style_Reset(stderr);
va_start(ap, fmt); va_start(ap, fmt);
vfprintf(stderr, fmt, ap); vfprintf(stderr, fmt, ap);
va_end(ap); va_end(ap);
@@ -73,7 +81,9 @@ void warning(WarningID id, char const *fmt, ...) {
break; break;
case WarningBehavior::ENABLED: case WarningBehavior::ENABLED:
style_Set(stderr, STYLE_YELLOW, true);
fprintf(stderr, "warning: [-W%s]\n ", flag); fprintf(stderr, "warning: [-W%s]\n ", flag);
style_Reset(stderr);
va_start(ap, fmt); va_start(ap, fmt);
vfprintf(stderr, fmt, ap); vfprintf(stderr, fmt, ap);
va_end(ap); va_end(ap);
@@ -81,7 +91,9 @@ void warning(WarningID id, char const *fmt, ...) {
break; break;
case WarningBehavior::ERROR: case WarningBehavior::ERROR:
style_Set(stderr, STYLE_RED, true);
fprintf(stderr, "error: [-Werror=%s]\n ", flag); fprintf(stderr, "error: [-Werror=%s]\n ", flag);
style_Reset(stderr);
va_start(ap, fmt); va_start(ap, fmt);
vfprintf(stderr, fmt, ap); vfprintf(stderr, fmt, ap);
va_end(ap); va_end(ap);

View File

@@ -16,6 +16,7 @@
#include "itertools.hpp" #include "itertools.hpp"
#include "platform.hpp" #include "platform.hpp"
#include "script.hpp" // Generated from script.y #include "script.hpp" // Generated from script.y
#include "style.hpp"
#include "usage.hpp" #include "usage.hpp"
#include "util.hpp" // UpperMap, printChar #include "util.hpp" // UpperMap, printChar
#include "verbosity.hpp" #include "verbosity.hpp"
@@ -37,6 +38,9 @@ static char const *linkerScriptName = nullptr; // -l
// Short options // Short options
static char const *optstring = "dhl:m:Mn:O:o:p:S:tVvW:wx"; static char const *optstring = "dhl:m:Mn:O:o:p:S:tVvW:wx";
// Variables for the long-only options
static int longOpt; // `--color`
// Equivalent long options // Equivalent long options
// Please keep in the same order as short opts. // Please keep in the same order as short opts.
// Also, make sure long opts don't create ambiguity: // Also, make sure long opts don't create ambiguity:
@@ -61,26 +65,28 @@ static option const longopts[] = {
{"warning", required_argument, nullptr, 'W'}, {"warning", required_argument, nullptr, 'W'},
{"wramx", no_argument, nullptr, 'w'}, {"wramx", no_argument, nullptr, 'w'},
{"nopad", no_argument, nullptr, 'x'}, {"nopad", no_argument, nullptr, 'x'},
{nullptr, no_argument, nullptr, 0 } {"color", required_argument, &longOpt, 'c'},
{nullptr, no_argument, nullptr, 0 },
}; };
// clang-format off: long string literal // clang-format off: nested initializers
static Usage usage( static Usage usage = {
"Usage: rgblink [-dhMtVvwx] [-l script] [-m map_file] [-n sym_file]\n" .name = "rgblink",
" [-O overlay_file] [-o out_file] [-p pad_value]\n" .flags = {
" [-S spec] <file> ...\n" "[-dhMtVvwx]", "[-l script]", "[-m map_file]", "[-n sym_file]", "[-O overlay_file]",
"Useful options:\n" "[-o out_file]", "[-p pad_value]", "[-S spec]", "<file> ...",
" -l, --linkerscript <path> set the input linker script\n" },
" -m, --map <path> set the output map file\n" .options = {
" -n, --sym <path> set the output symbol list file\n" {{"-l", "--linkerscript <path>"}, {"set the input linker script"}},
" -o, --output <path> set the output file\n" {{"-m", "--map <path>"}, {"set the output map file"}},
" -p, --pad <value> set the value to pad between sections with\n" {{"-n", "--sym <path>"}, {"set the output symbol list file"}},
" -x, --nopad disable padding of output binary\n" {{"-o", "--output <path>"}, {"set the output file"}},
" -V, --version print RGBLINK version and exit\n" {{"-p", "--pad <value>"}, {"set the value to pad between sections with"}},
" -W, --warning <warning> enable or disable warnings\n" {{"-x", "--nopad"}, {"disable padding of output binary"}},
"\n" {{"-V", "--version"}, {"print RGBLINK version and exit"}},
"For help, use `man rgblink' or go to https://rgbds.gbdev.io/docs/\n" {{"-W", "--warning <warning>"}, {"enable or disable warnings"}},
); },
};
// clang-format on // clang-format on
// LCOV_EXCL_START // LCOV_EXCL_START
@@ -174,21 +180,29 @@ std::string const &FileStackNode::dump(uint32_t curLineNo) const {
if (std::holds_alternative<std::vector<uint32_t>>(data)) { if (std::holds_alternative<std::vector<uint32_t>>(data)) {
assume(parent); // REPT nodes use their parent's name assume(parent); // REPT nodes use their parent's name
std::string const &lastName = parent->dump(lineNo); std::string const &lastName = parent->dump(lineNo);
style_Set(stderr, STYLE_CYAN, false);
fputs(" -> ", stderr); fputs(" -> ", stderr);
style_Set(stderr, STYLE_CYAN, true);
fputs(lastName.c_str(), stderr); fputs(lastName.c_str(), stderr);
for (uint32_t iter : iters()) { for (uint32_t iter : iters()) {
fprintf(stderr, "::REPT~%" PRIu32, iter); fprintf(stderr, "::REPT~%" PRIu32, iter);
} }
style_Set(stderr, STYLE_CYAN, false);
fprintf(stderr, "(%" PRIu32 ")", curLineNo); fprintf(stderr, "(%" PRIu32 ")", curLineNo);
style_Reset(stderr);
return lastName; return lastName;
} else { } else {
if (parent) { if (parent) {
parent->dump(lineNo); parent->dump(lineNo);
style_Set(stderr, STYLE_CYAN, false);
fputs(" -> ", stderr); fputs(" -> ", stderr);
} }
std::string const &nodeName = name(); std::string const &nodeName = name();
style_Set(stderr, STYLE_CYAN, true);
fputs(nodeName.c_str(), stderr); fputs(nodeName.c_str(), stderr);
style_Set(stderr, STYLE_CYAN, false);
fprintf(stderr, "(%" PRIu32 ")", curLineNo); fprintf(stderr, "(%" PRIu32 ")", curLineNo);
style_Reset(stderr);
return nodeName; return nodeName;
} }
} }
@@ -388,6 +402,17 @@ int main(int argc, char *argv[]) {
// implies tiny mode // implies tiny mode
options.is32kMode = true; options.is32kMode = true;
break; break;
case 0: // Long-only options
if (longOpt == '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;
default: default:
usage.printAndExit(1); // LCOV_EXCL_LINE usage.printAndExit(1); // LCOV_EXCL_LINE
} }

View File

@@ -5,6 +5,8 @@
#include <inttypes.h> #include <inttypes.h>
#include <stdarg.h> #include <stdarg.h>
#include "style.hpp"
#include "link/main.hpp" #include "link/main.hpp"
// clang-format off: nested initializers // clang-format off: nested initializers
@@ -33,24 +35,29 @@ static void printDiag(
char const *fmt, char const *fmt,
va_list args, va_list args,
char const *type, char const *type,
StyleColor color,
char const *flagfmt, char const *flagfmt,
char const *flag char const *flag
) { ) {
style_Set(stderr, color, true);
fprintf(stderr, "%s: ", type); fprintf(stderr, "%s: ", type);
if (src) { if (src) {
src->dump(lineNo); src->dump(lineNo);
fputs(": ", stderr); fputs(": ", stderr);
} }
if (flagfmt) { if (flagfmt) {
style_Set(stderr, color, true);
fprintf(stderr, flagfmt, flag); fprintf(stderr, flagfmt, flag);
fputs("\n ", stderr); fputs("\n ", stderr);
} }
style_Reset(stderr);
vfprintf(stderr, fmt, args); vfprintf(stderr, fmt, args);
putc('\n', stderr); putc('\n', stderr);
} }
[[noreturn]] [[noreturn]]
static void abortLinking(char const *verb) { static void abortLinking(char const *verb) {
style_Set(stderr, STYLE_RED, true);
fprintf( fprintf(
stderr, stderr,
"Linking %s with %" PRIu64 " error%s\n", "Linking %s with %" PRIu64 " error%s\n",
@@ -58,27 +65,28 @@ static void abortLinking(char const *verb) {
warnings.nbErrors, warnings.nbErrors,
warnings.nbErrors == 1 ? "" : "s" warnings.nbErrors == 1 ? "" : "s"
); );
style_Reset(stderr);
exit(1); exit(1);
} }
void warning(FileStackNode const *src, uint32_t lineNo, char const *fmt, ...) { void warning(FileStackNode const *src, uint32_t lineNo, char const *fmt, ...) {
va_list args; va_list args;
va_start(args, fmt); va_start(args, fmt);
printDiag(src, lineNo, fmt, args, "warning", nullptr, 0); printDiag(src, lineNo, fmt, args, "warning", STYLE_YELLOW, nullptr, nullptr);
va_end(args); va_end(args);
} }
void warning(char const *fmt, ...) { void warning(char const *fmt, ...) {
va_list args; va_list args;
va_start(args, fmt); va_start(args, fmt);
printDiag(nullptr, 0, fmt, args, "warning", nullptr, 0); printDiag(nullptr, 0, fmt, args, "warning", STYLE_YELLOW, nullptr, nullptr);
va_end(args); va_end(args);
} }
void error(FileStackNode const *src, uint32_t lineNo, char const *fmt, ...) { void error(FileStackNode const *src, uint32_t lineNo, char const *fmt, ...) {
va_list args; va_list args;
va_start(args, fmt); va_start(args, fmt);
printDiag(src, lineNo, fmt, args, "error", nullptr, 0); printDiag(src, lineNo, fmt, args, "error", STYLE_RED, nullptr, nullptr);
va_end(args); va_end(args);
warnings.incrementErrors(); warnings.incrementErrors();
@@ -87,7 +95,7 @@ void error(FileStackNode const *src, uint32_t lineNo, char const *fmt, ...) {
void error(char const *fmt, ...) { void error(char const *fmt, ...) {
va_list args; va_list args;
va_start(args, fmt); va_start(args, fmt);
printDiag(nullptr, 0, fmt, args, "error", nullptr, 0); printDiag(nullptr, 0, fmt, args, "error", STYLE_RED, nullptr, nullptr);
va_end(args); va_end(args);
warnings.incrementErrors(); warnings.incrementErrors();
@@ -95,7 +103,9 @@ void error(char const *fmt, ...) {
void errorNoDump(char const *fmt, ...) { void errorNoDump(char const *fmt, ...) {
va_list args; va_list args;
style_Set(stderr, STYLE_RED, true);
fputs("error: ", stderr); fputs("error: ", stderr);
style_Reset(stderr);
va_start(args, fmt); va_start(args, fmt);
vfprintf(stderr, fmt, args); vfprintf(stderr, fmt, args);
va_end(args); va_end(args);
@@ -104,7 +114,13 @@ void errorNoDump(char const *fmt, ...) {
} }
void scriptError(char const *name, uint32_t lineNo, char const *fmt, va_list args) { void scriptError(char const *name, uint32_t lineNo, char const *fmt, va_list args) {
fprintf(stderr, "error: %s(%" PRIu32 "): ", name, lineNo); style_Set(stderr, STYLE_RED, true);
fputs("error: ", stderr);
style_Set(stderr, STYLE_CYAN, true);
fputs(name, stderr);
style_Set(stderr, STYLE_CYAN, false);
fprintf(stderr, "(%" PRIu32 "): ", lineNo);
style_Reset(stderr);
vfprintf(stderr, fmt, args); vfprintf(stderr, fmt, args);
putc('\n', stderr); putc('\n', stderr);
@@ -115,7 +131,7 @@ void scriptError(char const *name, uint32_t lineNo, char const *fmt, va_list arg
void fatal(FileStackNode const *src, uint32_t lineNo, char const *fmt, ...) { void fatal(FileStackNode const *src, uint32_t lineNo, char const *fmt, ...) {
va_list args; va_list args;
va_start(args, fmt); va_start(args, fmt);
printDiag(src, lineNo, fmt, args, "FATAL", nullptr, 0); printDiag(src, lineNo, fmt, args, "FATAL", STYLE_RED, nullptr, nullptr);
va_end(args); va_end(args);
warnings.incrementErrors(); warnings.incrementErrors();
@@ -126,7 +142,7 @@ void fatal(FileStackNode const *src, uint32_t lineNo, char const *fmt, ...) {
void fatal(char const *fmt, ...) { void fatal(char const *fmt, ...) {
va_list args; va_list args;
va_start(args, fmt); va_start(args, fmt);
printDiag(nullptr, 0, fmt, args, "FATAL", nullptr, 0); printDiag(nullptr, 0, fmt, args, "FATAL", STYLE_RED, nullptr, nullptr);
va_end(args); va_end(args);
warnings.incrementErrors(); warnings.incrementErrors();
@@ -150,11 +166,11 @@ void warning(FileStackNode const *src, uint32_t lineNo, WarningID id, char const
break; break;
case WarningBehavior::ENABLED: case WarningBehavior::ENABLED:
printDiag(src, lineNo, fmt, args, "warning", "[-W%s]", flag); printDiag(src, lineNo, fmt, args, "warning", STYLE_RED, "[-W%s]", flag);
break; break;
case WarningBehavior::ERROR: case WarningBehavior::ERROR:
printDiag(src, lineNo, fmt, args, "error", "[-Werror=%s]", flag); printDiag(src, lineNo, fmt, args, "error", STYLE_YELLOW, "[-Werror=%s]", flag);
warnings.incrementErrors(); warnings.incrementErrors();
break; break;

98
src/style.cpp Normal file
View File

@@ -0,0 +1,98 @@
// SPDX-License-Identifier: MIT
// This implementation was based on https://github.com/agauniyal/rang/
// and adapted for RGBDS.
#include "style.hpp"
#include <stdlib.h> // getenv
#include <string.h>
#include "platform.hpp" // isatty
#if !STYLE_ANSI
// clang-format off: maintain `include` order
#define WIN32_LEAN_AND_MEAN // Include less from `windows.h`
#include <windows.h>
// clang-format on
#endif
enum Tribool { TRI_NO, TRI_YES, TRI_MAYBE };
#if !STYLE_ANSI
static const HANDLE outHandle = GetStdHandle(STD_OUTPUT_HANDLE);
static const HANDLE errHandle = GetStdHandle(STD_ERROR_HANDLE);
static const WORD defaultAttrib = []() {
if (CONSOLE_SCREEN_BUFFER_INFO info; GetConsoleScreenBufferInfo(outHandle, &info)
|| GetConsoleScreenBufferInfo(errHandle, &info)) {
return info.wAttributes;
}
return static_cast<WORD>((STYLE_BLACK << 4) | (STYLE_GRAY | 8));
}();
static HANDLE getHandle(FILE *file) {
return file == stdout ? outHandle : file == stderr ? errHandle : INVALID_HANDLE_VALUE;
}
#endif // !STYLE_ANSI
static Tribool forceStyle = []() {
if (char const *forceColor = getenv("FORCE_COLOR");
forceColor && strcmp(forceColor, "") && strcmp(forceColor, "0")) {
return TRI_YES;
}
if (char const *noColor = getenv("NO_COLOR");
noColor && strcmp(noColor, "") && strcmp(noColor, "0")) {
return TRI_NO;
}
return TRI_MAYBE;
}();
static bool isTerminal(FILE *file) {
static bool isOutTerminal = isatty(STDOUT_FILENO);
static bool isErrTerminal = isatty(STDERR_FILENO);
return (file == stdout && isOutTerminal) || (file == stderr && isErrTerminal);
}
static bool allowStyle(FILE *file) {
return forceStyle == TRI_YES || (forceStyle == TRI_MAYBE && isTerminal(file));
}
void style_Enable(bool enable) {
forceStyle = enable ? TRI_YES : TRI_NO;
}
void style_Set(FILE *file, StyleColor color, bool bold) {
if (!allowStyle(file)) {
return;
}
// LCOV_EXCL_START
#if STYLE_ANSI
fprintf(file, "\033[%dm", static_cast<int>(color) + (bold ? 90 : 30));
#else
if (HANDLE handle = getHandle(file); handle != INVALID_HANDLE_VALUE) {
fflush(file);
SetConsoleTextAttribute(handle, (defaultAttrib & ~0xF) | (color | (bold ? 8 : 0)));
}
#endif
// LCOV_EXCL_STOP
}
void style_Reset(FILE *file) {
if (!allowStyle(file)) {
return;
}
// LCOV_EXCL_START
#if STYLE_ANSI
fputs("\033[m", file);
#else
if (HANDLE handle = getHandle(file); handle != INVALID_HANDLE_VALUE) {
fflush(file);
SetConsoleTextAttribute(handle, defaultAttrib);
}
#endif
// LCOV_EXCL_STOP
}

View File

@@ -2,17 +2,111 @@
#include "usage.hpp" #include "usage.hpp"
#include <algorithm>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include "helpers.hpp"
#include "style.hpp"
static constexpr size_t maxLineLen = 79;
// LCOV_EXCL_START
void Usage::printAndExit(int code) const { void Usage::printAndExit(int code) const {
fputs(usage, stderr); FILE *file = code ? stderr : stdout;
// Print "Usage: <program name>"
style_Set(file, STYLE_GREEN, true);
fputs("Usage: ", file);
style_Set(file, STYLE_CYAN, true);
fputs(name.c_str(), file);
size_t padFlags = literal_strlen("Usage: ") + name.length();
// Print the flags after the program name, indented to the same level
style_Set(file, STYLE_CYAN, false);
size_t flagsWidth = padFlags;
for (std::string const &flag : flags) {
if (flagsWidth + 1 + flag.length() > maxLineLen) {
fprintf(file, "\n%*c", static_cast<int>(padFlags), ' ');
flagsWidth = padFlags;
}
fprintf(file, " %s", flag.c_str());
flagsWidth += 1 + flag.length();
}
fputs("\n\n", file);
// Measure the options' flags
size_t padOpts = 0;
for (auto const &item : options) {
size_t pad = 0;
for (size_t i = 0; i < item.first.size(); ++i) {
if (i > 0) {
pad += literal_strlen(", ");
}
pad += item.first[i].length();
}
if (pad > padOpts) {
padOpts = pad;
}
}
int optIndent = static_cast<int>(literal_strlen(" ") + padOpts);
// Print the options
if (!options.empty()) {
style_Set(file, STYLE_GREEN, true);
fputs("Useful options:\n", file);
}
for (auto const &[opts, description] : options) {
fputs(" ", file);
// Print the comma-separated options
size_t optWidth = 0;
for (size_t i = 0; i < opts.size(); ++i) {
if (i > 0) {
style_Reset(file);
fputs(", ", file);
optWidth += literal_strlen(", ");
}
style_Set(file, STYLE_CYAN, false);
fputs(opts[i].c_str(), file);
optWidth += opts[i].length();
}
if (optWidth < padOpts) {
fprintf(file, "%*c", static_cast<int>(padOpts - optWidth), ' ');
}
// Print the description lines, indented to the same level
for (size_t i = 0; i < description.size(); ++i) {
style_Reset(file);
if (i > 0) {
fprintf(file, "\n%*c", optIndent, ' ');
}
fprintf(file, " %s", description[i].c_str());
}
putc('\n', file);
}
// Print the link for further help information
style_Reset(file);
fputs("\nFor more help, use `", file);
style_Set(file, STYLE_CYAN, true);
fprintf(file, "man %s", name.c_str());
style_Reset(file);
fputs("' or go to ", file);
style_Set(file, STYLE_BLUE, true);
fputs("https://rgbds.gbdev.io/docs/", file);
style_Reset(file);
putc('\n', file);
exit(code); exit(code);
} }
void Usage::printAndExit(char const *fmt, ...) const { void Usage::printAndExit(char const *fmt, ...) const {
va_list args; va_list args;
style_Set(stderr, STYLE_RED, true);
fputs("FATAL: ", stderr); fputs("FATAL: ", stderr);
style_Reset(stderr);
va_start(args, fmt); va_start(args, fmt);
vfprintf(stderr, fmt, args); vfprintf(stderr, fmt, args);
va_end(args); va_end(args);
@@ -20,3 +114,5 @@ void Usage::printAndExit(char const *fmt, ...) const {
printAndExit(1); printAndExit(1);
} }
// LCOV_EXCL_STOP

View File

@@ -1,3 +1,5 @@
// SPDX-License-Identifier: MIT
#include "verbosity.hpp" #include "verbosity.hpp"
#include <array> #include <array>
@@ -8,16 +10,18 @@
static Verbosity verbosity = VERB_NONE; static Verbosity verbosity = VERB_NONE;
bool checkVerbosity(Verbosity level) {
return verbosity >= level;
}
// LCOV_EXCL_START
void incrementVerbosity() { void incrementVerbosity() {
if (verbosity < VERB_VVVVVV) { if (verbosity < VERB_VVVVVV) {
verbosity = static_cast<Verbosity>(verbosity + 1); verbosity = static_cast<Verbosity>(verbosity + 1);
} }
} }
bool checkVerbosity(Verbosity level) {
return verbosity >= level;
}
void printVVVVVVerbosity() { void printVVVVVVerbosity() {
if (!checkVerbosity(VERB_VVVVVV)) { if (!checkVerbosity(VERB_VVVVVV)) {
return; return;
@@ -67,3 +71,5 @@ void printVVVVVVerbosity() {
} }
putc('\n', stderr); putc('\n', stderr);
} }
// LCOV_EXCL_STOP