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 := \
src/extern/getopt.o \
src/diagnostics.o \
src/style.o \
src/usage.o
rgbasm_obj := \

View File

@@ -28,6 +28,7 @@
#define STDERR_FILENO 2
#define ssize_t int
#define SSIZE_MAX INT_MAX
#define isatty _isatty
#else
#include <fcntl.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
#include <stdarg.h>
#include <string>
#include <utility>
#include <vector>
class Usage {
char const *usage;
public:
Usage(char const *usage_) : usage(usage_) {}
struct Usage {
std::string name;
std::vector<std::string> flags;
std::vector<std::pair<std::vector<std::string>, std::vector<std::string>>> options;
[[noreturn]]
void printAndExit(int code) const;

View File

@@ -10,6 +10,7 @@
.Nm
.Op Fl EhVvw
.Op Fl b Ar chars
.Op Fl \-color Ar when
.Op Fl D Ar name Ns Op = Ns Ar value
.Op Fl g Ar chars
.Op Fl I Ar path
@@ -65,6 +66,18 @@ letters,
.Sq # ,
or
.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
Add a string symbol to the compiled source code.
This is equivalent to

View File

@@ -10,6 +10,7 @@
.Nm
.Op Fl hjOsVvw
.Op Fl C | c
.Op Fl \-color Ar when
.Op Fl f Ar fix_spec
.Op Fl i Ar game_id
.Op Fl k Ar licensee_str
@@ -46,13 +47,13 @@ can be a path to a file, or
to read from standard input.
.Pp
Note that options can be abbreviated as long as the abbreviation is unambiguous:
.Fl \-color-o
.Fl \-verb
is
.Fl \-color-only ,
.Fl \-verbose ,
but
.Fl \-color
.Fl \-ver
is invalid because it could also be
.Fl \-color-compatible .
.Fl \-version .
Options later in the command line override those set earlier.
Accepted options are as follows:
.Bl -tag -width Ds
@@ -70,6 +71,18 @@ to 0x80.
This overrides
.Fl c
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
Fix certain header values that the Game Boy checks for correctness.
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 b Ar base_ids
.Op Fl c Ar pal_spec
.Op Fl \-color Ar when
.Op Fl d Ar depth
.Op Fl i Ar input_tiles
.Op Fl L Ar slice
@@ -195,6 +196,18 @@ See
.Sx PALETTE SPECIFICATION FORMATS
for a list of formats and their descriptions.
.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
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).

View File

@@ -9,6 +9,7 @@
.Sh SYNOPSIS
.Nm
.Op Fl dhMtVvwx
.Op Fl \-color Ar when
.Op Fl l Ar linker_script
.Op Fl m Ar map_file
.Op Fl n Ar sym_file
@@ -63,6 +64,18 @@ is invalid because it could also be
.Fl \-version .
The arguments are as follows:
.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
Enable DMG mode.
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
"extern/getopt.cpp"
"diagnostics.cpp"
"style.cpp"
"usage.cpp"
"_version.cpp"
)

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

View File

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

View File

@@ -16,6 +16,7 @@
#include "extern/getopt.hpp"
#include "helpers.hpp"
#include "platform.hpp"
#include "style.hpp"
#include "usage.hpp"
#include "version.hpp"
@@ -27,6 +28,9 @@ static constexpr off_t BANK_SIZE = 0x4000;
// Short options
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
// Please keep in the same order as short opts.
// Also, make sure long opts don't create ambiguity:
@@ -35,47 +39,54 @@ static char const *optstring = "Ccf:hi:jk:L:l:m:n:Oo:p:r:st:VvW:w";
// This is because long opt matching, even to a single char, is prioritized
// over short opt matching.
static option const longopts[] = {
{"color-only", no_argument, nullptr, 'C'},
{"color-compatible", no_argument, nullptr, 'c'},
{"fix-spec", required_argument, nullptr, 'f'},
{"help", no_argument, nullptr, 'h'},
{"game-id", required_argument, nullptr, 'i'},
{"non-japanese", no_argument, nullptr, 'j'},
{"new-licensee", required_argument, nullptr, 'k'},
{"logo", required_argument, nullptr, 'L'},
{"old-licensee", required_argument, nullptr, 'l'},
{"mbc-type", required_argument, nullptr, 'm'},
{"rom-version", required_argument, nullptr, 'n'},
{"overwrite", no_argument, nullptr, 'O'},
{"output", required_argument, nullptr, 'o'},
{"pad-value", required_argument, nullptr, 'p'},
{"ram-size", required_argument, nullptr, 'r'},
{"sgb-compatible", no_argument, nullptr, 's'},
{"title", required_argument, nullptr, 't'},
{"version", no_argument, nullptr, 'V'},
{"validate", no_argument, nullptr, 'v'},
{"warning", required_argument, nullptr, 'W'},
{nullptr, no_argument, nullptr, 0 }
{"color-only", no_argument, nullptr, 'C'},
{"color-compatible", no_argument, nullptr, 'c'},
{"fix-spec", required_argument, nullptr, 'f'},
{"help", no_argument, nullptr, 'h'},
{"game-id", required_argument, nullptr, 'i'},
{"non-japanese", no_argument, nullptr, 'j'},
{"new-licensee", required_argument, nullptr, 'k'},
{"logo", required_argument, nullptr, 'L'},
{"old-licensee", required_argument, nullptr, 'l'},
{"mbc-type", required_argument, nullptr, 'm'},
{"rom-version", required_argument, nullptr, 'n'},
{"overwrite", no_argument, nullptr, 'O'},
{"output", required_argument, nullptr, 'o'},
{"pad-value", required_argument, nullptr, 'p'},
{"ram-size", required_argument, nullptr, 'r'},
{"sgb-compatible", no_argument, nullptr, 's'},
{"title", required_argument, nullptr, 't'},
{"version", no_argument, nullptr, 'V'},
{"validate", no_argument, nullptr, 'v'},
{"warning", required_argument, nullptr, 'W'},
{"color", required_argument, &longOpt, 'c'},
{nullptr, no_argument, nullptr, 0 },
};
// clang-format off: long string literal
static Usage usage(
"Usage: rgbfix [-hjOsVvw] [-C | -c] [-f <fix_spec>] [-i <game_id>] [-k <licensee>]\n"
" [-L <logo_file>] [-l <licensee_byte>] [-m <mbc_type>]\n"
" [-n <rom_version>] [-p <pad_value>] [-r <ram_size>] [-t <title_str>]\n"
" [-W warning] <file> ...\n"
"Useful options:\n"
" -m, --mbc-type <value> set the MBC type byte to this value; `-m help'\n"
" or `-m list' prints the accepted values\n"
" -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"
" -o, --output <path> set the output file\n"
" -V, --version print RGBFIX version and exit\n"
" -v, --validate fix the header logo and both checksums (`-f lhg')\n"
" -W, --warning <warning> enable or disable warnings\n"
"\n"
"For help, use `man rgbfix' or go to https://rgbds.gbdev.io/docs/\n"
);
// clang-format off: nested initializers
static Usage usage = {
.name = "rgbfix",
.flags = {
"[-hjOsVvw]", "[-C | -c]", "[-f <fix_spec>]", "[-i <game_id>]", "[-k <licensee>]",
"[-L <logo_file>]", "[-l <licensee_byte>]", "[-m <mbc_type>]", "[-n <rom_version>]",
"[-p <pad_value>]", "[-r <ram_size>]", "[-t <title_str>]", "[-W warning]", "<file> ...",
},
.options = {
{
{"-m", "--mbc-type <value>"},
{
"set the MBC type byte to this value; `-m help'",
"or `-m list' prints the accepted values",
},
},
{{"-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
static uint8_t tpp1Rev[2];
@@ -817,6 +828,19 @@ int main(int argc, char *argv[]) {
warnings.state.warningsEnabled = false;
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:
usage.printAndExit(1); // LCOV_EXCL_LINE
}

View File

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

View File

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

View File

@@ -18,6 +18,7 @@
#include "extern/getopt.hpp"
#include "file.hpp"
#include "platform.hpp"
#include "style.hpp"
#include "usage.hpp"
#include "verbosity.hpp"
#include "version.hpp"
@@ -44,6 +45,9 @@ static struct LocalOptions {
// 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";
// Variables for the long-only options
static int longOpt; // `--color`
// Equivalent long options
// Please keep in the same order as short opts.
// Also, make sure long opts don't create ambiguity:
@@ -52,59 +56,62 @@ static char const *optstring = "-Aa:B:b:Cc:d:hi:L:l:mN:n:Oo:Pp:Qq:r:s:Tt:U:uVvW:
// This is because long opt matching, even to a single char, is prioritized
// over short opt matching.
static option const longopts[] = {
{"auto-attr-map", no_argument, nullptr, 'A'},
{"attr-map", required_argument, nullptr, 'a'},
{"background-color", required_argument, nullptr, 'B'},
{"base-tiles", required_argument, nullptr, 'b'},
{"color-curve", no_argument, nullptr, 'C'},
{"colors", required_argument, nullptr, 'c'},
{"depth", required_argument, nullptr, 'd'},
{"help", no_argument, nullptr, 'h'},
{"input-tileset", required_argument, nullptr, 'i'},
{"slice", required_argument, nullptr, 'L'},
{"base-palette", required_argument, nullptr, 'l'},
{"mirror-tiles", no_argument, nullptr, 'm'},
{"nb-tiles", required_argument, nullptr, 'N'},
{"nb-palettes", required_argument, nullptr, 'n'},
{"group-outputs", no_argument, nullptr, 'O'},
{"output", required_argument, nullptr, 'o'},
{"auto-palette", no_argument, nullptr, 'P'},
{"palette", required_argument, nullptr, 'p'},
{"auto-palette-map", no_argument, nullptr, 'Q'},
{"palette-map", required_argument, nullptr, 'q'},
{"reverse", required_argument, nullptr, 'r'},
{"palette-size", required_argument, nullptr, 's'},
{"auto-tilemap", no_argument, nullptr, 'T'},
{"tilemap", required_argument, nullptr, 't'},
{"unit-size", required_argument, nullptr, 'U'},
{"unique-tiles", no_argument, nullptr, 'u'},
{"version", no_argument, nullptr, 'V'},
{"verbose", no_argument, nullptr, 'v'},
{"warning", required_argument, nullptr, 'W'},
{"mirror-x", no_argument, nullptr, 'X'},
{"trim-end", required_argument, nullptr, 'x'},
{"mirror-y", no_argument, nullptr, 'Y'},
{"columns", no_argument, nullptr, 'Z'},
{nullptr, no_argument, nullptr, 0 }
{"auto-attr-map", no_argument, nullptr, 'A'},
{"attr-map", required_argument, nullptr, 'a'},
{"background-color", required_argument, nullptr, 'B'},
{"base-tiles", required_argument, nullptr, 'b'},
{"color-curve", no_argument, nullptr, 'C'},
{"colors", required_argument, nullptr, 'c'},
{"depth", required_argument, nullptr, 'd'},
{"help", no_argument, nullptr, 'h'},
{"input-tileset", required_argument, nullptr, 'i'},
{"slice", required_argument, nullptr, 'L'},
{"base-palette", required_argument, nullptr, 'l'},
{"mirror-tiles", no_argument, nullptr, 'm'},
{"nb-tiles", required_argument, nullptr, 'N'},
{"nb-palettes", required_argument, nullptr, 'n'},
{"group-outputs", no_argument, nullptr, 'O'},
{"output", required_argument, nullptr, 'o'},
{"auto-palette", no_argument, nullptr, 'P'},
{"palette", required_argument, nullptr, 'p'},
{"auto-palette-map", no_argument, nullptr, 'Q'},
{"palette-map", required_argument, nullptr, 'q'},
{"reverse", required_argument, nullptr, 'r'},
{"palette-size", required_argument, nullptr, 's'},
{"auto-tilemap", no_argument, nullptr, 'T'},
{"tilemap", required_argument, nullptr, 't'},
{"unit-size", required_argument, nullptr, 'U'},
{"unique-tiles", no_argument, nullptr, 'u'},
{"version", no_argument, nullptr, 'V'},
{"verbose", no_argument, nullptr, 'v'},
{"warning", required_argument, nullptr, 'W'},
{"mirror-x", no_argument, nullptr, 'X'},
{"trim-end", required_argument, nullptr, 'x'},
{"mirror-y", no_argument, nullptr, 'Y'},
{"columns", no_argument, nullptr, 'Z'},
{"color", required_argument, &longOpt, 'c'},
{nullptr, no_argument, nullptr, 0 },
};
// clang-format off: long string literal
static Usage usage(
"Usage: rgbgfx [-r stride] [-ChmOuVXYZ] [-v [-v ...]] [-a <attr_map> | -A]\n"
" [-b <base_ids>] [-c <colors>] [-d <depth>] [-i <tileset_file>]\n"
" [-L <slice>] [-l <base_pal>] [-N <nb_tiles>] [-n <nb_pals>]\n"
" [-o <out_file>] [-p <pal_file> | -P] [-q <pal_map> | -Q]\n"
" [-s <nb_colors>] [-t <tile_map> | -T] [-x <nb_tiles>] <file>\n"
"Useful options:\n"
" -m, --mirror-tiles optimize out mirrored tiles\n"
" -o, --output <path> output the tile data to this path\n"
" -t, --tilemap <path> output the tile map to this path\n"
" -u, --unique-tiles optimize out identical tiles\n"
" -V, --version print RGBGFX version and exit\n"
" -W, --warning <warning> enable or disable warnings\n"
"\n"
"For help, use `man rgbgfx' or go to https://rgbds.gbdev.io/docs/\n"
);
// clang-format off: nested initializers
static Usage usage = {
.name = "rgbgfx",
.flags = {
"[-r stride]", "[-ChmOuVXYZ]", "[-v [-v ...]]", "[-a <attr_map> | -A]", "[-b <base_ids>]",
"[-c <colors>]", "[-d <depth>]", "[-i <tileset_file>]", "[-L <slice>]", "[-l <base_pal>]",
"[-N <nb_tiles>]", "[-n <nb_pals>]", "[-o <out_file>]", "[-p <pal_file> | -P]",
"[-q <pal_map> | -Q]", "[-s <nb_colors>]", "[-t <tile_map> | -T]", "[-x <nb_tiles>]",
"<file>",
},
.options = {
{{"-m", "--mirror-tiles"}, {"optimize out mirrored tiles"}},
{{"-o", "--output <path>"}, {"output the tile data to this path"}},
{{"-t", "--tilemap <path>"}, {"output the tile map to this path"}},
{{"-u", "--unique-tiles"}, {"optimize out identical tiles"}},
{{"-V", "--version"}, {"print RGBGFX version and exit"}},
{{"-W", "--warning <warning>"}, {"enable or disable warnings"}},
},
};
// clang-format on
// 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':
options.columnMajor = true;
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
if (musl_optarg[0] == '@') {
// 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 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.
if (attr.isBackgroundTile()) {
++tileIdx;
@@ -799,7 +799,7 @@ static UniqueTiles dedupTiles(
}
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()) {
attr.xFlip = false;
attr.yFlip = false;

View File

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

View File

@@ -16,6 +16,7 @@
#include "itertools.hpp"
#include "platform.hpp"
#include "script.hpp" // Generated from script.y
#include "style.hpp"
#include "usage.hpp"
#include "util.hpp" // UpperMap, printChar
#include "verbosity.hpp"
@@ -37,6 +38,9 @@ static char const *linkerScriptName = nullptr; // -l
// Short options
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
// Please keep in the same order as short opts.
// Also, make sure long opts don't create ambiguity:
@@ -45,42 +49,44 @@ static char const *optstring = "dhl:m:Mn:O:o:p:S:tVvW:wx";
// This is because long opt matching, even to a single char, is prioritized
// over short opt matching.
static option const longopts[] = {
{"dmg", no_argument, nullptr, 'd'},
{"help", no_argument, nullptr, 'h'},
{"linkerscript", required_argument, nullptr, 'l'},
{"map", required_argument, nullptr, 'm'},
{"no-sym-in-map", no_argument, nullptr, 'M'},
{"sym", required_argument, nullptr, 'n'},
{"overlay", required_argument, nullptr, 'O'},
{"output", required_argument, nullptr, 'o'},
{"pad", required_argument, nullptr, 'p'},
{"scramble", required_argument, nullptr, 'S'},
{"tiny", no_argument, nullptr, 't'},
{"version", no_argument, nullptr, 'V'},
{"verbose", no_argument, nullptr, 'v'},
{"warning", required_argument, nullptr, 'W'},
{"wramx", no_argument, nullptr, 'w'},
{"nopad", no_argument, nullptr, 'x'},
{nullptr, no_argument, nullptr, 0 }
{"dmg", no_argument, nullptr, 'd'},
{"help", no_argument, nullptr, 'h'},
{"linkerscript", required_argument, nullptr, 'l'},
{"map", required_argument, nullptr, 'm'},
{"no-sym-in-map", no_argument, nullptr, 'M'},
{"sym", required_argument, nullptr, 'n'},
{"overlay", required_argument, nullptr, 'O'},
{"output", required_argument, nullptr, 'o'},
{"pad", required_argument, nullptr, 'p'},
{"scramble", required_argument, nullptr, 'S'},
{"tiny", no_argument, nullptr, 't'},
{"version", no_argument, nullptr, 'V'},
{"verbose", no_argument, nullptr, 'v'},
{"warning", required_argument, nullptr, 'W'},
{"wramx", no_argument, nullptr, 'w'},
{"nopad", no_argument, nullptr, 'x'},
{"color", required_argument, &longOpt, 'c'},
{nullptr, no_argument, nullptr, 0 },
};
// clang-format off: long string literal
static Usage usage(
"Usage: rgblink [-dhMtVvwx] [-l script] [-m map_file] [-n sym_file]\n"
" [-O overlay_file] [-o out_file] [-p pad_value]\n"
" [-S spec] <file> ...\n"
"Useful options:\n"
" -l, --linkerscript <path> set the input linker script\n"
" -m, --map <path> set the output map file\n"
" -n, --sym <path> set the output symbol list file\n"
" -o, --output <path> set the output file\n"
" -p, --pad <value> set the value to pad between sections with\n"
" -x, --nopad disable padding of output binary\n"
" -V, --version print RGBLINK version and exit\n"
" -W, --warning <warning> enable or disable warnings\n"
"\n"
"For help, use `man rgblink' or go to https://rgbds.gbdev.io/docs/\n"
);
// clang-format off: nested initializers
static Usage usage = {
.name = "rgblink",
.flags = {
"[-dhMtVvwx]", "[-l script]", "[-m map_file]", "[-n sym_file]", "[-O overlay_file]",
"[-o out_file]", "[-p pad_value]", "[-S spec]", "<file> ...",
},
.options = {
{{"-l", "--linkerscript <path>"}, {"set the input linker script"}},
{{"-m", "--map <path>"}, {"set the output map file"}},
{{"-n", "--sym <path>"}, {"set the output symbol list file"}},
{{"-o", "--output <path>"}, {"set the output file"}},
{{"-p", "--pad <value>"}, {"set the value to pad between sections with"}},
{{"-x", "--nopad"}, {"disable padding of output binary"}},
{{"-V", "--version"}, {"print RGBLINK version and exit"}},
{{"-W", "--warning <warning>"}, {"enable or disable warnings"}},
},
};
// clang-format on
// 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)) {
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);
for (uint32_t iter : iters()) {
fprintf(stderr, "::REPT~%" PRIu32, iter);
}
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;
}
}
@@ -388,6 +402,17 @@ int main(int argc, char *argv[]) {
// implies tiny mode
options.is32kMode = true;
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:
usage.printAndExit(1); // LCOV_EXCL_LINE
}

View File

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

View File

@@ -14,7 +14,7 @@ SectionTypeInfo sectionTypeInfo[SECTTYPE_INVALID] = {
.size = 0x2000, // Patched to 0x1000 if !isWRAM0Mode
.firstBank = 0,
.lastBank = 0,
},
},
{
.name = "VRAM"s,
.startAddr = 0x8000,
@@ -28,42 +28,42 @@ SectionTypeInfo sectionTypeInfo[SECTTYPE_INVALID] = {
.size = 0x4000,
.firstBank = 1,
.lastBank = 65535,
},
},
{
.name = "ROM0"s,
.startAddr = 0x0000,
.size = 0x8000, // Patched to 0x4000 if !is32kMode
.firstBank = 0,
.lastBank = 0,
},
},
{
.name = "HRAM"s,
.startAddr = 0xFF80,
.size = 0x007F,
.firstBank = 0,
.lastBank = 0,
},
},
{
.name = "WRAMX"s,
.startAddr = 0xD000,
.size = 0x1000,
.firstBank = 1,
.lastBank = 7,
},
},
{
.name = "SRAM"s,
.startAddr = 0xA000,
.size = 0x2000,
.firstBank = 0,
.lastBank = 255,
},
},
{
.name = "OAM"s,
.startAddr = 0xFE00,
.size = 0x00A0,
.firstBank = 0,
.lastBank = 0,
},
},
};
// clang-format on

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 <algorithm>
#include <stdio.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 {
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);
}
void Usage::printAndExit(char const *fmt, ...) const {
va_list args;
style_Set(stderr, STYLE_RED, true);
fputs("FATAL: ", stderr);
style_Reset(stderr);
va_start(args, fmt);
vfprintf(stderr, fmt, args);
va_end(args);
@@ -20,3 +114,5 @@ void Usage::printAndExit(char const *fmt, ...) const {
printAndExit(1);
}
// LCOV_EXCL_STOP

View File

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