Compare commits

...

27 Commits

Author SHA1 Message Date
Rangi42
df5162edca Use loops instead of tail calls and musttail
gcc 15.2.1 20250813 complains "address of automatic variable can
escape to `musttail` call" from `-Wmaybe-musttail-local-addr`,
and guaranteeing tail-call optimization cross-platform is more
trouble than it's worth.
2025-10-27 12:05:27 -04:00
Rangi42
2519d1e698 Mention REDEF and FOR regarding EQUS expansion
Fixes #1851
2025-10-27 10:54:16 -04:00
Rangi42
ca383c91ca Revert "More accurate 8-bit <=> 5-bit RGB color conversion (#1827)"
This reverts commit 223b3d1921.
2025-10-24 13:32:59 -04:00
Rangi
8bedd710d7 Simplify musl's getopt, including removing optind, optopt, opterr, idx, and longonly 2025-10-23 13:39:28 -04:00
Rangi
efb5a88edb Show conventional colored "error:"/"FATAL:" for CLI option errors 2025-10-23 12:40:29 -04:00
Rangi
f065243cd2 Enable RGBGFX's CLI "at-files" for all programs (#1848) 2025-10-22 17:05:59 -04:00
Rangi
a0bb830679 More consistent man page descriptions for CLI options 2025-10-22 14:48:24 -04:00
Rangi
7654c6e27a More consistent man page descriptions for warning diagnostics 2025-10-22 14:03:34 -04:00
Rangi
400375b2e5 Share some handling between two tests of rgbasm -M - 2025-10-20 20:57:48 -04:00
Rangi
7462bccb72 Move struct Palette into its own file (#1850) 2025-10-20 16:59:24 -04:00
Rangi
2873e0b8c8 Use musttail attribute to guarantee tail recursion (#1849) 2025-10-20 15:56:22 -04:00
Rangi
1badba03d8 Clean up some #define callables
These are used where anonymous functions would not be sufficient
2025-10-13 13:14:49 -04:00
Rangi42
64bcef99bd Lower default -Wtrunction= level to 1 2025-10-13 11:48:33 -04:00
Rangi42
aa672bbec9 Rephrase PURGE documentation and raise the default level to 2
Fixes #1847
2025-10-13 11:43:29 -04:00
Rangi42
651877e094 Avoid reusing a static local variable (too fragile) 2025-10-08 21:19:23 -04:00
Rangi
26c48cc409 Add RGBGFX test for libpng warning with invalid bKGD chunk 2025-10-08 15:44:15 -04:00
Rangi
23b9039716 Give clearer names to template parameters 2025-10-08 14:55:43 -04:00
Rangi
711fba5e35 Add more tests for things that only the external tests had covered 2025-10-08 13:32:48 -04:00
Rangi
089fc11e31 A local label starting with a keyword (e.g. jr.local) is an error 2025-10-08 12:23:08 -04:00
Rangi42
837f552987 Fix bank increment never happening due to unsigned overflow 2025-10-07 16:20:24 -04:00
Rangi
cb8c973453 Add test for undefined __SCOPE__ 2025-10-06 17:51:21 -04:00
Rangi
cca3794dd0 Mention libpng in its internal warning and error messages 2025-10-06 17:03:51 -04:00
Rangi
02c2408f58 Implement reversed template for reversing for-each loops 2025-10-06 16:50:47 -04:00
Rangi
fba0562650 Fix repeated REPT nodes in backtraces 2025-10-06 16:36:55 -04:00
Rangi
0c9920d4a6 Use C++ iterator for fragment/union "pieces" of RGBLINK sections 2025-10-05 15:07:25 -04:00
Rangi
7733ccdeb6 Implement __SCOPE__ (#1845) 2025-10-04 16:41:21 -04:00
Rangi
13e85b5151 Replace all ctype.h functions with locale-independent ones 2025-10-03 12:52:24 -04:00
152 changed files with 2750 additions and 2138 deletions

View File

@@ -71,6 +71,7 @@ Language: Cpp
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
PPIndentWidth: -1
PenaltyBreakScopeResolution: 1000
PointerAlignment: Right
QualifierAlignment: Right
ReflowComments: true

View File

@@ -120,6 +120,9 @@ These files in the `src/` directory are shared across multiple programs: often a
- **`backtrace.cpp`:**
Generic printing of location backtraces for RGBASM and RGBLINK. Allows configuring backtrace styles with a command-line flag (conventionally `-B/--backtrace`). Renders warnings in yellow, errors in red, and locations in cyan.
- **`cli.cpp`:**
A function for parsing command-line options, including RGBDS-specific "at-files" (a filename containing more options, prepended with an "`@`").
This is the only file to use the extern/getopt.cpp variables and functions.
- **`diagnostics.cpp`:**
Generic warning/error diagnostic support for all programs. Allows command-line flags (conventionally `-W`) to have `no-`, `error=`, or `no-error=` prefixes, and `=` level suffixes; allows "meta" flags to affect groups of individual flags; and counts how many total errors there have been. Every program has its own `warning.cpp` file that uses this.
- **`linkdefs.cpp`:**
@@ -220,6 +223,8 @@ These files have been copied ("vendored") from external authors and adapted for
Functions for sorting colors within palettes, which works differently for grayscale, RGB, or indexed-color palettes.
- **`pal_spec.cpp`:**
Functions for parsing various formats of palette specifications (from `-c/--colors`).
- **`palette.cpp`:**
`Palette` methods for working with up to four GBC-native (RGB555) colors.
- **`png.cpp`:**
`Png` methods for reading PNG image files, standardizing them to 8-bit RGBA pixels while also reading their indexed palette if there is one.
- **`process.cpp`:**

View File

@@ -51,6 +51,7 @@ all: rgbasm rgblink rgbfix rgbgfx
common_obj := \
src/extern/getopt.o \
src/cli.o \
src/diagnostics.o \
src/style.o \
src/usage.o \
@@ -118,6 +119,7 @@ rgbgfx_obj := \
src/gfx/pal_packing.o \
src/gfx/pal_sorting.o \
src/gfx/pal_spec.o \
src/gfx/palette.o \
src/gfx/png.o \
src/gfx/process.o \
src/gfx/reverse.o \

View File

@@ -3,6 +3,7 @@
#ifndef RGBDS_ASM_MAIN_HPP
#define RGBDS_ASM_MAIN_HPP
#include <optional>
#include <stdint.h>
#include <stdio.h>
#include <string>
@@ -20,10 +21,10 @@ struct Options {
char binDigits[2] = {'0', '1'}; // -b
char gfxDigits[4] = {'0', '1', '2', '3'}; // -g
FILE *dependFile = nullptr; // -M
std::string targetFileName; // -MQ, -MT
std::optional<std::string> targetFileName{}; // -MQ, -MT
MissingInclude missingIncludeState = INC_ERROR; // -MC, -MG
bool generatePhonyDeps = false; // -MP
std::string objectFileName; // -o
std::optional<std::string> objectFileName{}; // -o
uint8_t padByte = 0; // -p
uint64_t maxErrors = 0; // -X
@@ -35,7 +36,7 @@ struct Options {
void printDep(std::string const &depName) {
if (dependFile) {
fprintf(dependFile, "%s: %s\n", targetFileName.c_str(), depName.c_str());
fprintf(dependFile, "%s: %s\n", targetFileName->c_str(), depName.c_str());
}
}
};

View File

@@ -11,6 +11,10 @@
#include "style.hpp"
#define TRACE_SEPARATOR "<-"
#define NODE_SEPARATOR "::"
#define REPT_NODE_PREFIX "REPT~"
struct Tracing {
uint64_t depth = 0;
bool collapse = false;
@@ -21,20 +25,20 @@ extern Tracing tracing;
bool trace_ParseTraceDepth(char const *arg);
template<typename T, typename M, typename N>
void trace_PrintBacktrace(std::vector<T> const &stack, M getName, N getLineNo) {
template<typename NodeT, typename NameFnT, typename LineNoFnT>
void trace_PrintBacktrace(std::vector<NodeT> const &stack, NameFnT getName, LineNoFnT getLineNo) {
size_t n = stack.size();
if (n == 0) {
return; // LCOV_EXCL_LINE
}
auto printLocation = [&](size_t i) {
T const &item = stack[n - i - 1];
NodeT const &item = stack[n - i - 1];
style_Reset(stderr);
if (!tracing.collapse) {
fputs(" ", stderr); // Just three spaces; the fourth will be printed next
}
fprintf(stderr, " %s ", i == 0 ? "at" : "<-");
fprintf(stderr, " %s ", i == 0 ? "at" : TRACE_SEPARATOR);
style_Set(stderr, STYLE_CYAN, true);
fputs(getName(item), stderr);
style_Set(stderr, STYLE_CYAN, false);
@@ -62,7 +66,7 @@ void trace_PrintBacktrace(std::vector<T> const &stack, M getName, N getLineNo) {
style_Reset(stderr);
if (tracing.collapse) {
fputs(" <-", stderr);
fputs(" " TRACE_SEPARATOR, stderr);
} else {
fputs(" ", stderr); // Just three spaces; the fourth will be printed next
}

21
include/cli.hpp Normal file
View File

@@ -0,0 +1,21 @@
// SPDX-License-Identifier: MIT
#ifndef RGBDS_CLI_HPP
#define RGBDS_CLI_HPP
#include <stdarg.h>
#include <string>
#include "extern/getopt.hpp" // option
#include "usage.hpp"
void cli_ParseArgs(
int argc,
char *argv[],
char const *shortOpts,
option const *longOpts,
void (*parseArg)(int, char *),
Usage usage
);
#endif // RGBDS_CLI_HPP

View File

@@ -30,35 +30,35 @@ struct WarningState {
std::pair<WarningState, std::optional<uint32_t>> getInitialWarningState(std::string &flag);
template<typename L>
template<typename LevelEnumT>
struct WarningFlag {
char const *name;
L level;
LevelEnumT level;
};
enum WarningBehavior { DISABLED, ENABLED, ERROR };
template<typename W>
template<typename WarningEnumT>
struct ParamWarning {
W firstID;
W lastID;
WarningEnumT firstID;
WarningEnumT lastID;
uint8_t defaultLevel;
};
template<typename W>
template<typename WarningEnumT>
struct DiagnosticsState {
WarningState flagStates[W::NB_WARNINGS];
WarningState metaStates[W::NB_WARNINGS];
WarningState flagStates[WarningEnumT::NB_WARNINGS];
WarningState metaStates[WarningEnumT::NB_WARNINGS];
bool warningsEnabled = true;
bool warningsAreErrors = false;
};
template<typename L, typename W>
template<typename LevelEnumT, typename WarningEnumT>
struct Diagnostics {
std::vector<WarningFlag<L>> metaWarnings;
std::vector<WarningFlag<L>> warningFlags;
std::vector<ParamWarning<W>> paramWarnings;
DiagnosticsState<W> state;
std::vector<WarningFlag<LevelEnumT>> metaWarnings;
std::vector<WarningFlag<LevelEnumT>> warningFlags;
std::vector<ParamWarning<WarningEnumT>> paramWarnings;
DiagnosticsState<WarningEnumT> state;
uint64_t nbErrors;
void incrementErrors() {
@@ -67,12 +67,12 @@ struct Diagnostics {
}
}
WarningBehavior getWarningBehavior(W id) const;
WarningBehavior getWarningBehavior(WarningEnumT id) const;
void processWarningFlag(char const *flag);
};
template<typename L, typename W>
WarningBehavior Diagnostics<L, W>::getWarningBehavior(W id) const {
template<typename LevelEnumT, typename WarningEnumT>
WarningBehavior Diagnostics<LevelEnumT, WarningEnumT>::getWarningBehavior(WarningEnumT id) const {
// Check if warnings are globally disabled
if (!state.warningsEnabled) {
return WarningBehavior::DISABLED;
@@ -112,7 +112,7 @@ WarningBehavior Diagnostics<L, W>::getWarningBehavior(W id) const {
}
// If no meta flag is specified, check the default state of this warning flag
if (warningFlags[id].level == L::LEVEL_DEFAULT) { // enabled by default
if (warningFlags[id].level == LevelEnumT::LEVEL_DEFAULT) { // enabled by default
return enabledBehavior;
}
@@ -120,8 +120,8 @@ WarningBehavior Diagnostics<L, W>::getWarningBehavior(W id) const {
return WarningBehavior::DISABLED;
}
template<typename L, typename W>
void Diagnostics<L, W>::processWarningFlag(char const *flag) {
template<typename LevelEnumT, typename WarningEnumT>
void Diagnostics<LevelEnumT, WarningEnumT>::processWarningFlag(char const *flag) {
std::string rootFlag = flag;
// Check for `-Werror` or `-Wno-error` to return early
@@ -140,8 +140,8 @@ void Diagnostics<L, W>::processWarningFlag(char const *flag) {
// Try to match the flag against a parametric warning
// If there was an equals sign, it will have set `param`; if not, `param` will be 0,
// which applies to all levels
for (ParamWarning<W> const &paramWarning : paramWarnings) {
W baseID = paramWarning.firstID;
for (ParamWarning<WarningEnumT> const &paramWarning : paramWarnings) {
WarningEnumT baseID = paramWarning.firstID;
uint8_t maxParam = paramWarning.lastID - baseID + 1;
assume(paramWarning.defaultLevel <= maxParam);
@@ -183,13 +183,13 @@ void Diagnostics<L, W>::processWarningFlag(char const *flag) {
}
// Try to match against a "meta" warning
for (WarningFlag<L> const &metaWarning : metaWarnings) {
for (WarningFlag<LevelEnumT> const &metaWarning : metaWarnings) {
if (rootFlag != metaWarning.name) {
continue;
}
// Set each of the warning flags that meets this level
for (W id : EnumSeq(W::NB_WARNINGS)) {
for (WarningEnumT id : EnumSeq(WarningEnumT::NB_WARNINGS)) {
if (metaWarning.level >= warningFlags[id].level) {
state.metaStates[id].update(flagState);
}
@@ -198,7 +198,7 @@ void Diagnostics<L, W>::processWarningFlag(char const *flag) {
}
// Try to match against a "normal" flag
for (W id : EnumSeq(W::NB_PLAIN_WARNINGS)) {
for (WarningEnumT id : EnumSeq(WarningEnumT::NB_PLAIN_WARNINGS)) {
if (rootFlag == warningFlags[id].name) {
state.flagStates[id].update(flagState);
return;

View File

@@ -12,7 +12,7 @@ static constexpr int optional_argument = 2;
// clang-format on
extern char *musl_optarg;
extern int musl_optind, musl_opterr, musl_optopt, musl_optreset;
extern int musl_optind, musl_optopt;
struct option {
char const *name;
@@ -21,8 +21,6 @@ struct option {
int val;
};
int musl_getopt_long_only(
int argc, char **argv, char const *optstring, option const *longopts, int *idx
);
int musl_getopt_long_only(int argc, char **argv, char const *optstring, option const *longopts);
#endif // RGBDS_EXTERN_GETOPT_HPP

View File

@@ -3,7 +3,9 @@
#ifndef RGBDS_FIX_MAIN_HPP
#define RGBDS_FIX_MAIN_HPP
#include <optional>
#include <stdint.h>
#include <string>
#include "fix/mbc.hpp" // UNSPECIFIED, MbcType
@@ -28,19 +30,19 @@ struct Options {
uint16_t ramSize = UNSPECIFIED; // -r
bool sgb = false; // -s
char const *gameID = nullptr; // -i
std::optional<std::string> gameID; // -i
uint8_t gameIDLen;
char const *newLicensee = nullptr; // -k
std::optional<std::string> newLicensee; // -k
uint8_t newLicenseeLen;
char const *logoFilename = nullptr; // -L
std::optional<std::string> logoFilename; // -L
uint8_t logo[48] = {};
MbcType cartridgeType = MBC_NONE; // -m
uint8_t tpp1Rev[2];
char const *title = nullptr; // -t
std::optional<std::string> title; // -t
uint8_t titleLen;
};

23
include/gfx/flip.hpp Normal file
View File

@@ -0,0 +1,23 @@
// SPDX-License-Identifier: MIT
#ifndef RGBDS_GFX_FLIP_HPP
#define RGBDS_GFX_FLIP_HPP
#include <array>
#include <stdint.h>
// Flipping tends to happen fairly often, so take a bite out of dcache to speed it up
static std::array<uint16_t, 256> flipTable = ([]() constexpr {
std::array<uint16_t, 256> table{};
for (uint16_t i = 0; i < table.size(); ++i) {
// To flip all the bits, we'll flip both nibbles, then each nibble half, etc.
uint16_t byte = i;
byte = (byte & 0b0000'1111) << 4 | (byte & 0b1111'0000) >> 4;
byte = (byte & 0b0011'0011) << 2 | (byte & 0b1100'1100) >> 2;
byte = (byte & 0b0101'0101) << 1 | (byte & 0b1010'1010) >> 1;
table[i] = byte;
}
return table;
})();
#endif // RGBDS_GFX_FLIP_HPP

View File

@@ -5,12 +5,11 @@
#include <array>
#include <optional>
#include <stddef.h>
#include <stdint.h>
#include <string>
#include <vector>
#include "helpers.hpp"
#include "helpers.hpp" // assume
#include "gfx/rgba.hpp"
@@ -69,35 +68,4 @@ struct Options {
extern Options options;
struct Palette {
// An array of 4 GBC-native (RGB555) colors
std::array<uint16_t, 4> colors{UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX};
void addColor(uint16_t color);
uint8_t indexOf(uint16_t color) const;
uint16_t &operator[](size_t index) { return colors[index]; }
uint16_t const &operator[](size_t index) const { return colors[index]; }
decltype(colors)::iterator begin();
decltype(colors)::iterator end();
decltype(colors)::const_iterator begin() const;
decltype(colors)::const_iterator end() const;
uint8_t size() const;
};
// Flipping tends to happen fairly often, so take a bite out of dcache to speed it up
static std::array<uint16_t, 256> flipTable = ([]() constexpr {
std::array<uint16_t, 256> table{};
for (uint16_t i = 0; i < table.size(); ++i) {
// To flip all the bits, we'll flip both nibbles, then each nibble half, etc.
uint16_t byte = i;
byte = (byte & 0b0000'1111) << 4 | (byte & 0b1111'0000) >> 4;
byte = (byte & 0b0011'0011) << 2 | (byte & 0b1100'1100) >> 2;
byte = (byte & 0b0101'0101) << 1 | (byte & 0b1010'1010) >> 1;
table[i] = byte;
}
return table;
})();
#endif // RGBDS_GFX_MAIN_HPP

27
include/gfx/palette.hpp Normal file
View File

@@ -0,0 +1,27 @@
// SPDX-License-Identifier: MIT
#ifndef RGBDS_GFX_PALETTE_HPP
#define RGBDS_GFX_PALETTE_HPP
#include <array>
#include <stddef.h>
#include <stdint.h>
struct Palette {
// An array of 4 GBC-native (RGB555) colors
std::array<uint16_t, 4> colors{UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX};
void addColor(uint16_t color);
uint8_t indexOf(uint16_t color) const;
uint16_t &operator[](size_t index) { return colors[index]; }
uint16_t const &operator[](size_t index) const { return colors[index]; }
decltype(colors)::iterator begin();
decltype(colors)::iterator end();
decltype(colors)::const_iterator begin() const;
decltype(colors)::const_iterator end() const;
uint8_t size() const;
};
#endif // RGBDS_GFX_PALETTE_HPP

View File

@@ -18,7 +18,10 @@ struct Rgba {
: red(rgba >> 24), green(rgba >> 16), blue(rgba >> 8), alpha(rgba) {}
static constexpr Rgba fromCGBColor(uint16_t color) {
constexpr auto _5to8 = [](uint8_t c) -> uint8_t { return ((c & 0b11111) * 255 + 15) / 31; };
constexpr auto _5to8 = [](uint8_t channel) -> uint8_t {
channel &= 0b11111; // For caller's convenience
return channel << 3 | channel >> 2;
};
return {
_5to8(color),
_5to8(color >> 5),

View File

@@ -100,16 +100,16 @@ static inline int clz(unsigned int x) {
#define RRANGE(s) std::rbegin(s), std::rend(s)
// MSVC does not inline `strlen()` or `.length()` of a constant string
template<int N>
static constexpr int literal_strlen(char const (&)[N]) {
return N - 1;
template<int SizeOfString>
static constexpr int literal_strlen(char const (&)[SizeOfString]) {
return SizeOfString - 1; // Don't count the ending '\0'
}
// For ad-hoc RAII in place of a `defer` statement or cross-platform `__attribute__((cleanup))`
template<typename T>
template<typename DeferredFnT>
struct Defer {
T deferred;
Defer(T func) : deferred(func) {}
DeferredFnT deferred;
Defer(DeferredFnT func) : deferred(func) {}
~Defer() { deferred(); }
};

View File

@@ -12,9 +12,31 @@
#include <unordered_map>
#include <utility>
template<typename T>
// A wrapper around iterables to reverse their iteration order; used in `for`-each loops.
template<typename IterableT>
struct ReversedIterable {
IterableT &_iterable;
};
template<typename IterableT>
auto begin(ReversedIterable<IterableT> r) {
return std::rbegin(r._iterable);
}
template<typename IterableT>
auto end(ReversedIterable<IterableT> r) {
return std::rend(r._iterable);
}
template<typename IterableT>
ReversedIterable<IterableT> reversed(IterableT &&_iterable) {
return {_iterable};
}
// A map from `std::string` keys to `ItemT` items, iterable in the order the items were inserted.
template<typename ItemT>
class InsertionOrderedMap {
std::deque<T> list;
std::deque<ItemT> list;
std::unordered_map<std::string, size_t> map; // Indexes into `list`
public:
@@ -24,25 +46,25 @@ public:
bool contains(std::string const &name) const { return map.find(name) != map.end(); }
T &operator[](size_t i) { return list[i]; }
ItemT &operator[](size_t i) { return list[i]; }
typename decltype(list)::iterator begin() { return list.begin(); }
typename decltype(list)::iterator end() { return list.end(); }
typename decltype(list)::const_iterator begin() const { return list.begin(); }
typename decltype(list)::const_iterator end() const { return list.end(); }
T &add(std::string const &name) {
ItemT &add(std::string const &name) {
map[name] = list.size();
return list.emplace_back();
}
T &add(std::string const &name, T &&value) {
ItemT &add(std::string const &name, ItemT &&value) {
map[name] = list.size();
list.emplace_back(std::move(value));
return list.back();
}
T &addAnonymous() {
ItemT &addAnonymous() {
// Add the new item to the list, but do not update the map
return list.emplace_back();
}
@@ -55,43 +77,45 @@ public:
}
};
template<typename T>
// An iterable of `enum` values in the half-open range [start, stop).
template<typename EnumT>
class EnumSeq {
T _start;
T _stop;
EnumT _start;
EnumT _stop;
class Iterator {
T _value;
EnumT _value;
public:
explicit Iterator(T value) : _value(value) {}
explicit Iterator(EnumT value) : _value(value) {}
Iterator &operator++() {
_value = static_cast<T>(_value + 1);
_value = static_cast<EnumT>(_value + 1);
return *this;
}
T operator*() const { return _value; }
EnumT operator*() const { return _value; }
bool operator==(Iterator const &rhs) const { return _value == rhs._value; }
};
public:
explicit EnumSeq(T stop) : _start(static_cast<T>(0)), _stop(stop) {}
explicit EnumSeq(T start, T stop) : _start(start), _stop(stop) {}
explicit EnumSeq(EnumT stop) : _start(static_cast<EnumT>(0)), _stop(stop) {}
explicit EnumSeq(EnumT start, EnumT stop) : _start(start), _stop(stop) {}
Iterator begin() { return Iterator(_start); }
Iterator end() { return Iterator(_stop); }
};
// Only needed inside `ZipContainer` below.
// This is not a fully generic implementation; its current use cases only require for-loop behavior.
// We also assume that all iterators have the same length.
template<typename... Ts>
template<typename... IteratorTs>
class ZipIterator {
std::tuple<Ts...> _iters;
std::tuple<IteratorTs...> _iters;
public:
explicit ZipIterator(std::tuple<Ts...> &&iters) : _iters(iters) {}
explicit ZipIterator(std::tuple<IteratorTs...> &&iters) : _iters(iters) {}
ZipIterator &operator++() {
std::apply([](auto &&...it) { (++it, ...); }, _iters);
@@ -109,12 +133,14 @@ public:
}
};
template<typename... Ts>
// Only needed inside `zip` below.
template<typename... IterableTs>
class ZipContainer {
std::tuple<Ts...> _containers;
std::tuple<IterableTs...> _containers;
public:
explicit ZipContainer(Ts &&...containers) : _containers(std::forward<Ts>(containers)...) {}
explicit ZipContainer(IterableTs &&...containers)
: _containers(std::forward<IterableTs>(containers)...) {}
auto begin() {
return ZipIterator(std::apply(
@@ -137,15 +163,19 @@ public:
}
};
// Only needed inside `zip` below.
// Take ownership of objects and rvalue refs passed to us, but not lvalue refs
template<typename T>
using Holder = std::
conditional_t<std::is_lvalue_reference_v<T>, T, std::remove_cv_t<std::remove_reference_t<T>>>;
template<typename IterableT>
using ZipHolder = std::conditional_t<
std::is_lvalue_reference_v<IterableT>,
IterableT,
std::remove_cv_t<std::remove_reference_t<IterableT>>>;
// Iterates over N containers at once, yielding tuples of N items at a time.
// Does the same number of iterations as the first container's iterator!
template<typename... Ts>
static constexpr auto zip(Ts &&...cs) {
return ZipContainer<Holder<Ts>...>(std::forward<Ts>(cs)...);
template<typename... IterableTs>
static constexpr auto zip(IterableTs &&...containers) {
return ZipContainer<ZipHolder<IterableTs>...>(std::forward<IterableTs>(containers)...);
}
#endif // RGBDS_ITERTOOLS_HPP

View File

@@ -10,6 +10,6 @@ void lexer_TraceCurrent();
void lexer_IncludeFile(std::string &&path);
void lexer_IncLineNo();
bool lexer_Init(char const *linkerScriptName);
bool lexer_Init(std::string const &linkerScriptName);
#endif // RGBDS_LINK_LEXER_HPP

View File

@@ -3,15 +3,17 @@
#ifndef RGBDS_LINK_MAIN_HPP
#define RGBDS_LINK_MAIN_HPP
#include <optional>
#include <stdint.h>
#include <string>
struct Options {
bool isDmgMode; // -d
char const *mapFileName; // -m
std::optional<std::string> mapFileName; // -m
bool noSymInMap; // -M
char const *symFileName; // -n
char const *overlayFileName; // -O
char const *outputFileName; // -o
std::optional<std::string> symFileName; // -n
std::optional<std::string> overlayFileName; // -O
std::optional<std::string> outputFileName; // -o
uint8_t padValue; // -p
bool hasPadValue = false;
// Setting these three to 0 disables the functionality

View File

@@ -3,10 +3,13 @@
#ifndef RGBDS_LINK_OBJECT_HPP
#define RGBDS_LINK_OBJECT_HPP
#include <stddef.h>
#include <string>
// Read an object (.o) file, and add its info to the data structures.
void obj_ReadFile(char const *fileName, unsigned int fileID);
void obj_ReadFile(std::string const &filePath, size_t fileID);
// Sets up object file reading
void obj_Setup(unsigned int nbFiles);
void obj_Setup(size_t nbFiles);
#endif // RGBDS_LINK_OBJECT_HPP

View File

@@ -49,6 +49,39 @@ struct Section {
std::vector<Symbol> *fileSymbols;
std::vector<Symbol *> symbols;
std::unique_ptr<Section> nextPiece; // The next fragment or union "piece" of this section
private:
// Template class for both const and non-const iterators over the "pieces" of this section
template<typename SectionT>
class PiecesIterable {
SectionT *_firstPiece;
class Iterator {
SectionT *_piece;
public:
explicit Iterator(SectionT *piece) : _piece(piece) {}
Iterator &operator++() {
_piece = _piece->nextPiece.get();
return *this;
}
SectionT &operator*() const { return *_piece; }
bool operator==(Iterator const &rhs) const { return _piece == rhs._piece; }
};
public:
explicit PiecesIterable(SectionT *firstPiece) : _firstPiece(firstPiece) {}
Iterator begin() { return Iterator(_firstPiece); }
Iterator end() { return Iterator(nullptr); }
};
public:
PiecesIterable<Section> pieces() { return PiecesIterable(this); }
PiecesIterable<Section const> pieces() const { return PiecesIterable(this); }
};
// Execute a callback for each section currently registered.

View File

@@ -4,7 +4,6 @@
#define RGBDS_UTIL_HPP
#include <algorithm>
#include <ctype.h> // toupper
#include <numeric>
#include <optional>
#include <stddef.h>
@@ -22,6 +21,7 @@ enum NumberBase {
BASE_16 = 16,
};
// Locale-independent character class functions
bool isNewline(int c);
bool isBlankSpace(int c);
bool isWhitespace(int c);
@@ -35,6 +35,10 @@ bool isOctDigit(int c);
bool isHexDigit(int c);
bool isAlphanumeric(int c);
// Locale-independent character transform functions
char toLower(char c);
char toUpper(char c);
bool startsIdentifier(int c);
bool continuesIdentifier(int c);
@@ -48,19 +52,20 @@ struct Uppercase {
// FNV-1a hash of an uppercased string
constexpr size_t operator()(std::string const &str) const {
return std::accumulate(RANGE(str), 0x811C9DC5, [](size_t hash, char c) {
return (hash ^ toupper(c)) * 16777619;
return (hash ^ toUpper(c)) * 16777619;
});
}
// Compare two strings without case-sensitivity (by converting to uppercase)
constexpr bool operator()(std::string const &str1, std::string const &str2) const {
return std::equal(RANGE(str1), RANGE(str2), [](char c1, char c2) {
return toupper(c1) == toupper(c2);
return toUpper(c1) == toUpper(c2);
});
}
};
template<typename T>
using UpperMap = std::unordered_map<std::string, T, Uppercase, Uppercase>;
// An unordered map from case-insensitive `std::string` keys to `ItemT` items
template<typename ItemT>
using UpperMap = std::unordered_map<std::string, ItemT, Uppercase, Uppercase>;
#endif // RGBDS_UTIL_HPP

View File

@@ -461,24 +461,6 @@ Previously we had
.Pp
Instead, now we have
.Ql p ** q ** r == p ** (q ** r) .
.Ss 8-bit and 5-bit color conversion
Changed in 1.0.0.
.Pp
RGBGFX takes 8-bit RGB colors as its PNG input, and outputs 5-bit GBC colors.
Its
.Ql -r/--reverse
mode does the opposite 5-bit to 8-bit conversion.
Instead of the previous inaccurate conversions, we now do accurate rounding to the nearest equivalent.
.Pp
Previously to convert an 8-bit color channel to 5-bit, we truncated it as
.Ql c >> 3 ;
and to reverse a 5-bit color channel to 8-bit, we extended it as
.Ql (c << 3) | (c >> 2) .
.Pp
Instead, now we round 8-bit to 5-bit as
.Ql (c * 31 + 127) / 255 ,
and round 5-bit to 8-bit as
.Ql (c * 255 + 15) / 31 .
.Sh BUGS
These are misfeatures that may have been possible by mistake.
They do not get deprecated, just fixed.

View File

@@ -36,14 +36,14 @@ The
program creates an RGB object file from an assembly source file.
The object file format is documented in
.Xr rgbds 5 .
.Pp
The input
.Ar asmfile
can be a path to a file, or
.Cm \-
to read from standard input.
.Pp
Note that options can be abbreviated as long as the abbreviation is unambiguous:
.Sh ARGUMENTS
.Nm
accepts the usual short and long options, such as
.Fl V
and
.Fl -version .
Options later in the command line override those set earlier, except for when duplicate options are considered an error.
Options can be abbreviated as long as the abbreviation is unambiguous:
.Fl \-verb
is
.Fl \-verbose ,
@@ -51,7 +51,52 @@ but
.Fl \-ver
is invalid because it could also be
.Fl \-version .
The arguments are as follows:
.Pp
Unless otherwise noted, passing
.Ql -
(a single dash) as a file name makes
.Nm
use standard input (for input files) or standard output (for output files).
To suppress this behavior, and open a file in the current directory actually called
.Ql - ,
pass
.Ql ./-
instead.
Using standard input or output for more than one file in a single command may produce unexpected results.
.Pp
.Nm
accepts decimal, hexadecimal, octal, and binary for numeric option arguments.
Decimal numbers are written as usual; hexadecimal numbers must be prefixed with either
.Ql $
or
.Ql 0x ;
octal numbers must be prefixed with either
.Ql &
or
.Ql 0o ;
and binary numbers must be prefixed with either
.Ql %
or
.Ql 0b .
(The prefixes
.Ql $
and
.Ql &
will likely need escaping or quoting to avoid being interpreted by the shell.)
Leading zeros (after the base prefix, if any) are accepted, and letters are not case-sensitive.
For example, all of these are equivalent:
.Ql 42 ,
.Ql 042 ,
.Ql 0x2A ,
.Ql 0X2A ,
.Ql 0x2a ,
.Ql &52 ,
.Ql 0o52 ,
.Ql 0O052 ,
.Ql 0b00101010 ,
.Ql 0B101010 .
.Pp
The following options are accepted:
.Bl -tag -width Ds
.It Fl B Ar param , Fl \-backtrace Ar param
Configures how location backtraces are printed if warnings or errors occur.
@@ -306,6 +351,23 @@ disables this behavior.
The default is 100 if
.Nm
is printing errors to a terminal, and 0 otherwise.
.It @ Ns Ar at_file
Read more options and arguments from a file, as if its contents were given on the command line.
Arguments are separated by whitespace or newlines.
Lines starting with a hash sign
.Pq Ql #
are considered comments and ignored.
.Pp
No shell processing is performed, such as wildcard or variable expansion.
There is no support for escaping or quoting whitespace to be included in arguments.
The standard
.Ql --
to stop option processing also disables at-file processing.
Note that while
.Ql --
can be used
.Em inside
an at-file, it only disables option processing within that at-file, and processing continues in the parent scope.
.El
.Sh DIAGNOSTICS
Warnings are diagnostic messages that indicate possibly erroneous behavior that does not necessarily compromise the assembling process.
@@ -344,9 +406,9 @@ Enables literally every warning.
.Pp
The following warnings are actual warning flags; with each description, the corresponding warning flag is included.
Note that each of these flags also has a negation (for example,
.Fl Wcharmap-redef
.Fl Wobsolete
enables the warning that
.Fl Wno-charmap-redef
.Fl Wno-obsolete
disables; and
.Fl Wall
enables every warning that
@@ -441,10 +503,10 @@ or
.Fl Wno-purge
disables this warning.
.Fl Wpurge=1
or just
.Fl Wpurge
warns when purging any exported symbol (regardless of type).
.Fl Wpurge=2
or just
.Fl Wpurge
also warns when purging any label (even if not exported).
.It Fl Wshift
Warn when shifting right a negative value.
@@ -460,10 +522,10 @@ or
.Fl Wno-truncation
disables this warning.
.Fl Wtruncation=1
warns when an N-bit value is 2**N or greater, or less than -2**N.
.Fl Wtruncation=2
or just
.Fl Wtruncation
warns when an N-bit value is 2**N or greater, or less than -2**N.
.Fl Wtruncation=2
also warns when an N-bit value is less than -2**(N-1), which will not fit in two's complement encoding.
.It Fl Wunmapped-char=
Warn when a character goes through charmap conversion but has no defined mapping.

View File

@@ -154,6 +154,8 @@ of string constants:
will be expanded in all of
.Ql DEF({name}) ,
.Ql DEF {name} EQU/=/EQUS/etc ... ,
.Ql REDEF {name} EQU/=/EQUS/etc ... ,
.Ql FOR {name}, ... ,
.Ql PURGE {name} ,
and
.Ql MACRO {name} ,
@@ -1556,6 +1558,8 @@ in the C programming language.
This expansion is disabled in a few contexts:
.Ql DEF(name) ,
.Ql DEF name EQU/=/EQUS/etc ... ,
.Ql REDEF name EQU/=/EQUS/etc ... ,
.Ql FOR name, ... ,
.Ql PURGE name ,
and
.Ql MACRO name
@@ -1711,15 +1715,21 @@ Note also that only exported symbols will appear in symbol and map files produce
.Ss Purging symbols
.Ic PURGE
allows you to completely remove a symbol from the symbol table, as if it had never been defined.
.Bd -literal -offset indent
DEF value EQU 42
PURGE value
DEF value EQUS "I'm a string now"
ASSERT DEF(value)
PURGE value
ASSERT !DEF(value)
.Ed
.Pp
Be
.Em very
careful when purging symbols, especially labels, because it could result in unpredictable errors if something depends on the missing symbol (for example, expressions the linker needs to calculate).
.Bd -literal -offset indent
DEF Kamikaze EQUS "I don't want to live anymore"
AOLer: DB "Me too lol"
PURGE Kamikaze, AOLer
ASSERT !DEF(Kamikaze) && !DEF(AOLer)
.Ed
careful when purging symbols that have been referenced in section data, or that have been exported, because it could result in unpredictable errors if something depends on the missing symbol (for example, expressions the linker needs to calculate).
Purging labels at all is
.Em not
recommended.
.Pp
String constants are not expanded within the symbol names.
.Ss Predeclared symbols
@@ -1729,6 +1739,7 @@ The following symbols are defined by the assembler:
.It Dv @ Ta Ic EQU Ta PC value (essentially, the current memory address)
.It Dv . Ta Ic EQUS Ta The current global label scope
.It Dv .. Ta Ic EQUS Ta The current local label scope
.It Dv __SCOPE__ Ta Ic EQUS Ta The innermost current label scope level (empty, ".", or "..")
.It Dv _RS Ta Ic = Ta _RS Counter
.It Dv _NARG Ta Ic EQU Ta Number of arguments passed to macro, updated by Ic SHIFT
.It Dv __ISO_8601_LOCAL__ Ta Ic EQUS Ta ISO 8601 timestamp (local)

View File

@@ -39,23 +39,67 @@ Developers are advised to fill those fields with 0x00 bytes in their source code
.Nm ,
and to have already populated whichever fields they don't specify using
.Nm .
.Pp
The input
.Ar file
can be a path to a file, or
.Cm \-
to read from standard input.
.Pp
Note that options can be abbreviated as long as the abbreviation is unambiguous:
.Fl \-verb
is
.Fl \-verbose ,
but
.Sh ARGUMENTS
.Nm
accepts the usual short and long options, such as
.Fl V
and
.Fl -version .
Options later in the command line override those set earlier, except for when duplicate options are considered an error.
Options can be abbreviated as long as the abbreviation is unambiguous:
.Fl \-ver
is
.Fl \-version ,
but
.Fl \-v
is invalid because it could also be
.Fl \-version .
Options later in the command line override those set earlier.
Accepted options are as follows:
.Fl \-validate .
.Pp
Unless otherwise noted, passing
.Ql -
(a single dash) as a file name makes
.Nm
use standard input (for input files) or standard output (for output files).
To suppress this behavior, and open a file in the current directory actually called
.Ql - ,
pass
.Ql ./-
instead.
Using standard input or output for more than one file in a single command may produce unexpected results.
.Pp
.Nm
accepts decimal, hexadecimal, octal, and binary for numeric option arguments.
Decimal numbers are written as usual; hexadecimal numbers must be prefixed with either
.Ql $
or
.Ql 0x ;
octal numbers must be prefixed with either
.Ql &
or
.Ql 0o ;
and binary numbers must be prefixed with either
.Ql %
or
.Ql 0b .
(The prefixes
.Ql $
and
.Ql &
will likely need escaping or quoting to avoid being interpreted by the shell.)
Leading zeros (after the base prefix, if any) are accepted, and letters are not case-sensitive.
For example, all of these are equivalent:
.Ql 42 ,
.Ql 042 ,
.Ql 0x2A ,
.Ql 0X2A ,
.Ql 0x2a ,
.Ql &52 ,
.Ql 0o52 ,
.Ql 0O052 ,
.Ql 0b00101010 ,
.Ql 0B101010 .
.Pp
The following options are accepted:
.Bl -tag -width Ds
.It Fl C , Fl \-color-only
Set the Game Boy Color\(enonly flag
@@ -199,6 +243,23 @@ See the
section for a list of warnings.
.It Fl w
Disable all warning output, even when turned into errors.
.It @ Ns Ar at_file
Read more options and arguments from a file, as if its contents were given on the command line.
Arguments are separated by whitespace or newlines.
Lines starting with a hash sign
.Pq Ql #
are considered comments and ignored.
.Pp
No shell processing is performed, such as wildcard or variable expansion.
There is no support for escaping or quoting whitespace to be included in arguments.
The standard
.Ql --
to stop option processing also disables at-file processing.
Note that while
.Ql --
can be used
.Em inside
an at-file, it only disables option processing within that at-file, and processing continues in the parent scope.
.El
.Sh DIAGNOSTICS
Warnings are diagnostic messages that indicate possibly erroneous behavior that does not necessarily compromise the header-fixing process.
@@ -212,7 +273,7 @@ to prevent turning all warnings into errors.
.It Fl Werror=
Make the specified warning or meta warning into an error.
A warning's name is appended
.Pq example: Fl Werror=overwrite ,
.Pq example: Fl Werror=obsolete ,
and this warning is implicitly enabled and turned into an error.
This can be negated as
.Fl Wno-error=
@@ -234,10 +295,10 @@ Enables literally every warning.
.El
.Pp
The following warnings are actual warning flags; with each description, the corresponding warning flag is included.
Note that each of these flag also has a negation (for example,
.Fl Wtruncation
Note that each of these flags also has a negation (for example,
.Fl Wobsolete
enables the warning that
.Fl Wno-truncation
.Fl Wno-obsolete
disables; and
.Fl Wall
enables every warning that

View File

@@ -43,7 +43,13 @@ is to divide the input PNG into 8\[tmu]8 pixel
convert each of those squares into 1bpp or 2bpp tile data, and save all of the tile data in a file.
It also has options to generate a tile map, attribute map, and/or palette set as well; more on that and how the conversion process can be tweaked below.
.Sh ARGUMENTS
Note that options can be abbreviated as long as the abbreviation is unambiguous:
.Nm
accepts the usual short and long options, such as
.Fl V
and
.Fl -version .
Options later in the command line override those set earlier, except for when duplicate options are considered an error.
Options can be abbreviated as long as the abbreviation is unambiguous:
.Fl \-verb
is
.Fl \-verbose ,
@@ -52,26 +58,6 @@ but
is invalid because it could also be
.Fl \-version .
.Pp
.Nm
accepts decimal, binary, and hexadecimal numbers in option arguments.
Decimal numbers are written as usual; binary numbers must be prefixed with either
.Ql %
or
.Ql 0b ,
and hexadecimal numbers must be prefixed with either
.Ql $
(which will likely need escaping or quoting to avoid being interpreted by the shell), or
.Ql 0x .
Leading zeros (after the base prefix, if any) are accepted, and letters are not case-sensitive.
All of these are equivalent:
.Ql 42 ,
.Ql 042 ,
.Ql 0b00101010 ,
.Ql 0B101010 ,
.Ql 0x2A ,
.Ql 0X2A ,
.Ql 0x2a .
.Pp
Unless otherwise noted, passing
.Ql -
(a single dash) as a file name makes
@@ -82,7 +68,39 @@ To suppress this behavior, and open a file in the current directory actually cal
pass
.Ql ./-
instead.
Using standard input or output more than once in a single command will likely produce unexpected results.
Using standard input or output for more than one file in a single command may produce unexpected results.
.Pp
.Nm
accepts decimal, hexadecimal, octal, and binary for numeric option arguments.
Decimal numbers are written as usual; hexadecimal numbers must be prefixed with either
.Ql $
or
.Ql 0x ;
octal numbers must be prefixed with either
.Ql &
or
.Ql 0o ;
and binary numbers must be prefixed with either
.Ql %
or
.Ql 0b .
(The prefixes
.Ql $
and
.Ql &
will likely need escaping or quoting to avoid being interpreted by the shell.)
Leading zeros (after the base prefix, if any) are accepted, and letters are not case-sensitive.
For example, all of these are equivalent:
.Ql 42 ,
.Ql 042 ,
.Ql 0x2A ,
.Ql 0X2A ,
.Ql 0x2a ,
.Ql &52 ,
.Ql 0o52 ,
.Ql 0O052 ,
.Ql 0b00101010 ,
.Ql 0B101010 .
.Pp
The following options are accepted:
.Bl -tag -width Ds
@@ -469,69 +487,53 @@ Implies
.It Fl Z , Fl \-columns
Read squares from the PNG in column-major order (column by column), instead of the default row-major order (line by line).
This primarily affects tile map and attribute map output, although it may also change generated tile data and palettes.
.It @ Ns Ar at_file
Read more options and arguments from a file, as if its contents were given on the command line.
Arguments are separated by whitespace or newlines.
Lines starting with a hash sign
.Pq Ql #
are considered comments and ignored.
.Pp
No shell processing is performed, such as wildcard or variable expansion.
There is no support for escaping or quoting whitespace to be included in arguments.
The standard
.Ql --
to stop option processing also disables at-file processing.
Note that while
.Ql --
can be used
.Em inside
an at-file, it only disables option processing within that at-file, and processing continues in the parent scope.
.Pp
See
.Sx At-files
below for an explanation of how this can be useful.
.El
.Ss At-files
In a given project, many images are to be converted with different flags.
The traditional way of solving this problem has been to specify the different flags for each image in the Makefile / build script; this can be inconvenient, as it centralizes all those flags away from the images they concern.
The traditional way of solving this problem has been to specify the different flags for each image in the Makefile or build script; this can be inconvenient, as it centralizes all those flags away from the images they concern.
.Pp
To avoid these drawbacks,
.Nm
supports
To avoid these drawbacks, you can use
.Dq at-files :
any command-line argument that begins with an at sign
.Pq Ql @
is interpreted as one.
The rest of the argument (without the @, that is) is interpreted as the path to a file, whose contents are interpreted as if given on the command line.
is interpreted as one, as documented above.
At-files can be stored right next to the corresponding image, for example:
.Pp
.Dl $ rgbgfx -o image.2bpp -t image.tilemap @image.flags image.png
.Pp
This will read additional flags from file
This will read additional flags from the file
.Ql image.flags ,
which could contains for example
which could contain, for example,
.Ql -b 128
to specify a base offset for the image's tiles.
The above command could be generated from the following
.Xr make 1
rule, for example:
rule:
.Bd -literal -offset indent
%.2bpp %.tilemap: %.flags %.png
rgbgfx -o $*.2bpp -t $*.tilemap @$*.flags $*.png
.Ed
.Pp
Since the contents of at-files are interpreted by
.Nm ,
.Sy no shell processing is performed ;
for example, shell variables are not expanded
.Ql ( $PWD ,
.Ql %WINDIR% ,
etc.).
In at-files, lines that are empty or contain only whitespace are ignored; lines that begin with a hash sign
.Pq Ql # ,
optionally preceded by whitespace, are considered comments and also ignored.
Each line can contain any number of arguments, which are separated by whitespace.
.Pq \&No quoting feature to prevent this is provided.
.Pp
Note that a leading
.Ql @
has no special meaning on option arguments, and that the standard
.Ql --
to stop option processing also disables at-file processing.
For example, the following command line reads command-line options from
.Ql tilesets/town.flags
then
.Ql tilesets.flags ,
but processes
.Ql @tilesets/town.png
as the input image and outputs tile data to
.Ql @tilesets/town.2bpp :
.Pp
.Dl $ rgbgfx -o @tilesets/town.2bpp @tilesets/town.flags @tilesets.flags -- @tilesets/town.png
.Pp
At-files can also specify the input image directly, and call for more at-files, both using the regular syntax.
Note that while
.Ql --
can be used in an at-file (with identical semantics), it is only effective inside of it\(emnormal option processing continues in the parent scope.
.Sh PALETTE SPECIFICATION FORMATS
The following formats are supported:
.Bl -tag -width Ds
@@ -769,7 +771,7 @@ to prevent turning all warnings into errors.
.It Fl Werror=
Make the specified warning or meta warning into an error.
A warning's name is appended
.Pq example: Fl Werror=embedded ,
.Pq example: Fl Werror=obsolete ,
and this warning is implicitly enabled and turned into an error.
This can be negated as
.Fl Wno-error=
@@ -791,10 +793,10 @@ Enables literally every warning.
.El
.Pp
The following warnings are actual warning flags; with each description, the corresponding warning flag is included.
Note that each of these flag also has a negation (for example,
.Fl Wtrim-nonempty
Note that each of these flags also has a negation (for example,
.Fl Wobsolete
enables the warning that
.Fl Wno-trim-nonempty
.Fl Wno-obsolete
disables; and
.Fl Wall
enables every warning that

View File

@@ -48,14 +48,14 @@ Also, if your ROM is designed for a monochrome Game Boy, you can make sure that
option, which implies
.Fl w
but also prohibits the use of banked VRAM.
.Pp
The input
.Ar file
can be a path to a file, or
.Cm \-
to read from standard input.
.Pp
Note that options can be abbreviated as long as the abbreviation is unambiguous:
.Sh ARGUMENTS
.Nm
accepts the usual short and long options, such as
.Fl V
and
.Fl -version .
Options later in the command line override those set earlier, except for when duplicate options are considered an error.
Options can be abbreviated as long as the abbreviation is unambiguous:
.Fl \-verb
is
.Fl \-verbose ,
@@ -63,7 +63,52 @@ but
.Fl \-ver
is invalid because it could also be
.Fl \-version .
The arguments are as follows:
.Pp
Unless otherwise noted, passing
.Ql -
(a single dash) as a file name makes
.Nm
use standard input (for input files) or standard output (for output files).
To suppress this behavior, and open a file in the current directory actually called
.Ql - ,
pass
.Ql ./-
instead.
Using standard input or output for more than one file in a single command may produce unexpected results.
.Pp
.Nm
accepts decimal, hexadecimal, octal, and binary for numeric option arguments.
Decimal numbers are written as usual; hexadecimal numbers must be prefixed with either
.Ql $
or
.Ql 0x ;
octal numbers must be prefixed with either
.Ql &
or
.Ql 0o ;
and binary numbers must be prefixed with either
.Ql %
or
.Ql 0b .
(The prefixes
.Ql $
and
.Ql &
will likely need escaping or quoting to avoid being interpreted by the shell.)
Leading zeros (after the base prefix, if any) are accepted, and letters are not case-sensitive.
For example, all of these are equivalent:
.Ql 42 ,
.Ql 042 ,
.Ql 0x2A ,
.Ql 0X2A ,
.Ql 0x2a ,
.Ql &52 ,
.Ql 0o52 ,
.Ql 0O052 ,
.Ql 0b00101010 ,
.Ql 0B101010 .
.Pp
The following options are accepted:
.Bl -tag -width Ds
.It Fl B Ar param , Fl \-backtrace Ar param
Configures how location backtraces are printed if warnings or errors occur.
@@ -190,6 +235,23 @@ You can use this to make binary files that are not a ROM.
When making a ROM, note that not using this is not a replacement for
.Xr rgbfix 1 Ap s Fl p
option!
.It @ Ns Ar at_file
Read more options and arguments from a file, as if its contents were given on the command line.
Arguments are separated by whitespace or newlines.
Lines starting with a hash sign
.Pq Ql #
are considered comments and ignored.
.Pp
No shell processing is performed, such as wildcard or variable expansion.
There is no support for escaping or quoting whitespace to be included in arguments.
The standard
.Ql --
to stop option processing also disables at-file processing.
Note that while
.Ql --
can be used
.Em inside
an at-file, it only disables option processing within that at-file, and processing continues in the parent scope.
.El
.Ss Scrambling algorithm
The default section placement algorithm tries to place sections into as few banks as possible.
@@ -258,7 +320,7 @@ to prevent turning all warnings into errors.
.It Fl Werror=
Make the specified warning or meta warning into an error.
A warning's name is appended
.Pq example: Fl Werror=assert ,
.Pq example: Fl Werror=obsolete ,
and this warning is implicitly enabled and turned into an error.
This can be negated as
.Fl Wno-error=
@@ -280,7 +342,7 @@ Enables literally every warning.
.El
.Pp
The following warnings are actual warning flags; with each description, the corresponding warning flag is included.
Note that each of these flag also has a negation (for example,
Note that each of these flags also has a negation (for example,
.Fl Wobsolete
enables the warning that
.Fl Wno-obsolete
@@ -327,10 +389,10 @@ or
.Fl Wno-truncation
disables this warning.
.Fl Wtruncation=1
warns when an N-bit value is 2**N or greater, or less than -2**N.
.Fl Wtruncation=2
or just
.Fl Wtruncation
warns when an N-bit value is 2**N or greater, or less than -2**N.
.Fl Wtruncation=2
also warns when an N-bit value is less than -2**(N-1), which will not fit in two's complement encoding.
.El
.Sh EXAMPLES

View File

@@ -4,6 +4,7 @@ configure_file(version.cpp _version.cpp ESCAPE_QUOTES)
set(common_src
"extern/getopt.cpp"
"cli.cpp"
"diagnostics.cpp"
"style.cpp"
"usage.cpp"
@@ -92,6 +93,7 @@ set(rgbgfx_src
"gfx/pal_packing.cpp"
"gfx/pal_sorting.cpp"
"gfx/pal_spec.cpp"
"gfx/palette.cpp"
"gfx/png.cpp"
"gfx/process.cpp"
"gfx/reverse.cpp"

View File

@@ -38,8 +38,8 @@ struct Charmap {
};
// Traverse the trie depth-first to derive the character mappings in definition order
template<typename F>
bool forEachChar(Charmap const &charmap, F callback) {
template<typename CallbackFnT>
bool forEachChar(Charmap const &charmap, CallbackFnT callback) {
// clang-format off: nested initializers
for (std::stack<std::pair<size_t, std::string>> prefixes({{0, ""}}); !prefixes.empty();) {
// clang-format on

View File

@@ -20,6 +20,7 @@
#include "backtrace.hpp"
#include "helpers.hpp"
#include "itertools.hpp" // reversed
#include "linkdefs.hpp"
#include "platform.hpp" // strncasecmp
#include "verbosity.hpp"
@@ -56,16 +57,6 @@ static std::vector<std::string> includePaths = {""}; // -I
static std::deque<std::string> preIncludeNames; // -P
static bool failedOnMissingInclude = false;
static std::string reptChain(FileStackNode const &node) {
std::string chain;
std::vector<uint32_t> const &nodeIters = node.iters();
for (uint32_t i = nodeIters.size(); i--;) {
chain.append("::REPT~");
chain.append(std::to_string(nodeIters[i]));
}
return chain;
}
using TraceNode = std::pair<std::string, uint32_t>;
static std::vector<TraceNode> backtrace(FileStackNode const &node, uint32_t curLineNo) {
@@ -89,7 +80,12 @@ static std::vector<TraceNode> backtrace(FileStackNode const &node, uint32_t curL
std::vector<TraceNode> traceNodes = backtrace(*node.parent, node.lineNo);
if (std::holds_alternative<std::vector<uint32_t>>(node.data)) {
assume(!traceNodes.empty()); // REPT nodes use their parent's name
traceNodes.emplace_back(traceNodes.back().first + reptChain(node), curLineNo);
std::string reptName = traceNodes.back().first;
if (std::vector<uint32_t> const &nodeIters = node.iters(); !nodeIters.empty()) {
reptName.append(NODE_SEPARATOR REPT_NODE_PREFIX);
reptName.append(std::to_string(nodeIters.front()));
}
traceNodes.emplace_back(reptName, curLineNo);
} else {
traceNodes.emplace_back(node.name(), curLineNo);
}
@@ -299,9 +295,13 @@ static void
}
}
if (macro.src->type == NODE_REPT) {
fileInfoName.append(reptChain(*macro.src));
std::vector<uint32_t> const &srcIters = macro.src->iters();
for (uint32_t iter : reversed(srcIters)) {
fileInfoName.append(NODE_SEPARATOR REPT_NODE_PREFIX);
fileInfoName.append(std::to_string(iter));
}
fileInfoName.append("::");
}
fileInfoName.append(NODE_SEPARATOR);
fileInfoName.append(macro.name);
auto fileInfo = std::make_shared<FileStackNode>(NODE_MACRO, fileInfoName, isQuiet);

View File

@@ -19,6 +19,7 @@
#include <stdlib.h>
#include <string.h>
#include <string>
#include <string_view>
#include <unordered_map>
#include <utility>
#include <variant>
@@ -56,7 +57,7 @@ struct Token {
// This map lists all RGBASM keywords which `yylex_NORMAL` lexes as identifiers.
// All non-identifier tokens are lexed separately.
static UpperMap<int> const keywordDict{
static UpperMap<int> const keywords{
{"ADC", T_(SM83_ADC) },
{"ADD", T_(SM83_ADD) },
{"AND", T_(SM83_AND) },
@@ -715,6 +716,7 @@ int LexerState::peekCharAhead() {
static std::pair<Symbol const *, std::shared_ptr<std::string>> readInterpolation(size_t depth);
static int peek() {
for (;;) {
int c = lexerState->peekChar();
if (lexerState->expansionScanDistance > 0) {
@@ -731,7 +733,6 @@ static int peek() {
if (!isMacroChar(lexerState->peekCharAhead())) {
return c;
}
// If character is a macro arg char, do macro arg expansion
shiftChar();
if (std::shared_ptr<std::string> str = readMacroArg(); str) {
@@ -742,19 +743,18 @@ static int peek() {
// https://en.wikipedia.org/wiki/Painted_blue
lexerState->expansionScanDistance += str->length();
}
return peek(); // Tail recursion
// Continue in the next iteration
} else if (c == '{') {
// If character is an open brace, do symbol interpolation
shiftChar();
if (auto interp = readInterpolation(0); interp.first && interp.second) {
beginExpansion(interp.second, interp.first->name);
}
return peek(); // Tail recursion
// Continue in the next iteration
} else {
return c;
}
}
}
static void shiftChar() {
@@ -815,8 +815,8 @@ static int nextChar() {
return peek();
}
template<typename P>
static int skipChars(P predicate) {
template<typename PredicateFnT>
static int skipChars(PredicateFnT predicate) {
int c = peek();
while (predicate(c)) {
c = nextChar();
@@ -1269,21 +1269,27 @@ static uint32_t readGfxConstant() {
static Token readIdentifier(char firstChar, bool raw) {
std::string identifier(1, firstChar);
bool keywordBeforeLocal = false;
int tokenType = firstChar == '.' ? T_(LOCAL) : T_(SYMBOL);
// Continue reading while the char is in the identifier charset
for (int c = peek(); continuesIdentifier(c); c = nextChar()) {
identifier += c;
// If the char was a dot, the identifier is a local label
if (c == '.') {
tokenType = T_(LOCAL);
}
// Check for a keyword before a non-raw local label
if (!raw && tokenType != T_(LOCAL) && keywords.find(identifier) != keywords.end()) {
keywordBeforeLocal = true;
}
// Attempt to check for a keyword if the identifier is not raw or a local label
tokenType = T_(LOCAL);
}
identifier += c;
}
// Check for a keyword if the identifier is not raw and not a local label
if (!raw && tokenType != T_(LOCAL)) {
if (auto search = keywordDict.find(identifier); search != keywordDict.end()) {
if (auto search = keywords.find(identifier); search != keywords.end()) {
return Token(search->second);
}
}
@@ -1293,6 +1299,14 @@ static Token readIdentifier(char firstChar, bool raw) {
tokenType = T_(SYMBOL);
}
// A keyword before a non-raw local label is an error
if (keywordBeforeLocal) {
error(
"Identifier \"%s\" begins with a keyword; did you mean to put a space between them?",
identifier.c_str()
);
}
return Token(tokenType, identifier);
}
@@ -1336,7 +1350,7 @@ static std::pair<Symbol const *, std::shared_ptr<std::string>> readInterpolation
if (identifier.starts_with('#')) {
// Skip a '#' raw symbol prefix, but after expanding any nested interpolations.
identifier.erase(0, 1);
} else if (keywordDict.find(identifier) != keywordDict.end()) {
} else if (keywords.find(identifier) != keywords.end()) {
// Don't allow symbols that alias keywords without a '#' prefix.
error(
"Interpolated symbol `%s` is a reserved keyword; add a '#' prefix to use it as a raw "
@@ -1680,7 +1694,7 @@ static Token yylex_NORMAL() {
return Token(nextToken);
}
for (;; lexerState->atLineStart = false) {
for (;;) {
int c = bumpChar();
switch (c) {
@@ -1692,7 +1706,7 @@ static Token yylex_NORMAL() {
case ' ':
case '\t':
continue;
break;
// Handle unambiguous single-char tokens
@@ -1744,7 +1758,7 @@ static Token yylex_NORMAL() {
if (peek() == '*') {
shiftChar();
discardBlockComment();
continue;
break;
}
return oneOrTwo('=', T_(POP_DIVEQ), T_(OP_DIV));
@@ -1882,7 +1896,7 @@ static Token yylex_NORMAL() {
// Macro args were handled by `peek`, and character escapes do not exist
// outside of string literals, so this must be a line continuation.
discardLineContinuation();
continue;
break;
// Handle raw strings... or fall through if '#' is not followed by '"'
@@ -1903,7 +1917,7 @@ static Token yylex_NORMAL() {
c = bumpChar();
} else if (!startsIdentifier(c)) {
reportGarbageCharacters(c);
continue;
break;
}
Token token = readIdentifier(c, raw);
@@ -1928,7 +1942,7 @@ static Token yylex_NORMAL() {
if (Symbol const *sym = sym_FindExactSymbol(std::get<std::string>(token.value));
sym && sym->type == SYM_EQUS) {
beginExpansion(sym->getEqus(), sym->name);
return yylex_NORMAL(); // Tail recursion
continue; // Restart, reading from the new buffer
}
}
@@ -1953,6 +1967,10 @@ static Token yylex_NORMAL() {
return token;
}
// If we exited the switch, i.e. read some characters without yet returning a token,
// we can't be at the start of the line
lexerState->atLineStart = false;
}
}
@@ -2266,8 +2284,8 @@ yy::parser::symbol_type yylex() {
}
}
template<typename F>
static Capture makeCapture(char const *name, F callback) {
template<typename CallbackFnT>
static Capture makeCapture(char const *name, CallbackFnT callback) {
// Due to parser internals, it reads the EOL after the expression before calling this.
// Thus, we don't need to keep one in the buffer afterwards.
// The following assumption checks that.

View File

@@ -19,8 +19,8 @@
#include <vector>
#include "backtrace.hpp"
#include "cli.hpp"
#include "diagnostics.hpp"
#include "extern/getopt.hpp"
#include "helpers.hpp"
#include "parser.hpp" // Generated from parser.y
#include "platform.hpp"
@@ -40,13 +40,17 @@
Options options;
static char const *dependFileName = nullptr; // -M
static std::unordered_map<std::string, std::vector<StateFeature>> stateFileSpecs; // -s
// Flags which must be processed after the option parsing finishes
static struct LocalOptions {
std::optional<std::string> dependFileName; // -M
std::unordered_map<std::string, std::vector<StateFeature>> stateFileSpecs; // -s
std::optional<std::string> inputFileName; // <file>
} localOptions;
// Short options
static char const *optstring = "B:b:D:Eg:hI:M:o:P:p:Q:r:s:VvW:wX:";
// Variables for the long-only options
// Long-only option variable
static int longOpt; // `--color` and variants of `-M`
// Equivalent long options
@@ -105,134 +109,6 @@ static Usage usage = {
};
// clang-format on
// LCOV_EXCL_START
static void verboseOutputConfig(int argc, char *argv[]) {
if (!checkVerbosity(VERB_CONFIG)) {
return;
}
style_Set(stderr, STYLE_MAGENTA, false);
fprintf(stderr, "rgbasm %s\n", get_package_version_string());
printVVVVVVerbosity();
fputs("Options:\n", stderr);
// -E/--export-all
if (options.exportAll) {
fputs("\tExport all labels by default\n", stderr);
}
// -b/--binary-digits
if (options.binDigits[0] != '0' || options.binDigits[1] != '1') {
fprintf(
stderr, "\tBinary digits: '%c', '%c'\n", options.binDigits[0], options.binDigits[1]
);
}
// -g/--gfx-chars
if (options.gfxDigits[0] != '0' || options.gfxDigits[1] != '1' || options.gfxDigits[2] != '2'
|| options.gfxDigits[3] != '3') {
fprintf(
stderr,
"\tGraphics characters: '%c', '%c', '%c', '%c'\n",
options.gfxDigits[0],
options.gfxDigits[1],
options.gfxDigits[2],
options.gfxDigits[3]
);
}
// -Q/--q-precision
fprintf(
stderr,
"\tFixed-point precision: Q%d.%" PRIu8 "\n",
32 - options.fixPrecision,
options.fixPrecision
);
// -p/--pad-value
fprintf(stderr, "\tPad value: 0x%02" PRIx8 "\n", options.padByte);
// -r/--recursion-depth
fprintf(stderr, "\tMaximum recursion depth %zu\n", options.maxRecursionDepth);
// -X/--max-errors
if (options.maxErrors) {
fprintf(stderr, "\tMaximum %" PRIu64 " errors\n", options.maxErrors);
}
// -D/--define
static bool hasDefines = false; // `static` so `sym_ForEach` callback can see it
sym_ForEach([](Symbol &sym) {
if (!sym.isBuiltin && sym.type == SYM_EQUS) {
if (!hasDefines) {
fputs("\tDefinitions:\n", stderr);
hasDefines = true;
}
fprintf(stderr, "\t - def %s equs \"%s\"\n", sym.name.c_str(), sym.getEqus()->c_str());
}
});
// -s/--state
if (!stateFileSpecs.empty()) {
fputs("\tOutput state files:\n", stderr);
static char const *featureNames[NB_STATE_FEATURES] = {
"equ",
"var",
"equs",
"char",
"macro",
};
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) {
fputs(", ", stderr);
}
fputs(featureNames[features[i]], stderr);
}
putc('\n', stderr);
}
}
// asmfile
if (musl_optind < argc) {
fprintf(stderr, "\tInput asm file: %s", argv[musl_optind]);
if (musl_optind + 1 < argc) {
fprintf(stderr, " (and %d more)", argc - musl_optind - 1);
}
putc('\n', stderr);
}
// -o/--output
if (!options.objectFileName.empty()) {
fprintf(stderr, "\tOutput object file: %s\n", options.objectFileName.c_str());
}
fstk_VerboseOutputConfig();
if (dependFileName) {
fprintf(
stderr,
"\tOutput dependency file: %s\n",
strcmp(dependFileName, "-") ? dependFileName : "<stdout>"
);
// -MT or -MQ
if (!options.targetFileName.empty()) {
fprintf(stderr, "\tTarget file(s): %s\n", options.targetFileName.c_str());
}
// -MG or -MC
switch (options.missingIncludeState) {
case INC_ERROR:
fputs("\tExit with an error on a missing dependency\n", stderr);
break;
case GEN_EXIT:
fputs("\tExit normally on a missing dependency\n", stderr);
break;
case GEN_CONTINUE:
fputs("\tContinue processing after a missing dependency\n", stderr);
break;
}
// -MP
if (options.generatePhonyDeps) {
fputs("\tGenerate phony dependencies\n", stderr);
}
}
fputs("Ready.\n", stderr);
style_Reset(stderr);
}
// LCOV_EXCL_STOP
static std::string escapeMakeChars(std::string &str) {
std::string escaped;
size_t pos = 0;
@@ -295,46 +171,29 @@ static std::vector<StateFeature> parseStateFeatures(char *str) {
return features;
}
int main(int argc, char *argv[]) {
// Support SOURCE_DATE_EPOCH for reproducible builds
// https://reproducible-builds.org/docs/source-date-epoch/
time_t now = time(nullptr);
if (char const *sourceDateEpoch = getenv("SOURCE_DATE_EPOCH"); sourceDateEpoch) {
// Use `strtoul`, not `parseWholeNumber`, because SOURCE_DATE_EPOCH does
// not conventionally support our custom base prefixes
now = static_cast<time_t>(strtoul(sourceDateEpoch, nullptr, 0));
}
sym_Init(now);
// Maximum of 100 errors only applies if rgbasm is printing errors to a terminal
if (isatty(STDERR_FILENO)) {
options.maxErrors = 100; // LCOV_EXCL_LINE
}
// Parse CLI options
for (int ch; (ch = musl_getopt_long_only(argc, argv, optstring, longopts, nullptr)) != -1;) {
static void parseArg(int ch, char *arg) {
switch (ch) {
case 'B':
if (!trace_ParseTraceDepth(musl_optarg)) {
if (!trace_ParseTraceDepth(arg)) {
fatal("Invalid argument for option '-B'");
}
break;
case 'b':
if (strlen(musl_optarg) == 2) {
opt_B(musl_optarg);
if (strlen(arg) == 2) {
opt_B(arg);
} else {
fatal("Must specify exactly 2 characters for option '-b'");
}
break;
case 'D': {
char *equals = strchr(musl_optarg, '=');
char *equals = strchr(arg, '=');
if (equals) {
*equals = '\0';
sym_AddString(musl_optarg, std::make_shared<std::string>(equals + 1));
sym_AddString(arg, std::make_shared<std::string>(equals + 1));
} else {
sym_AddString(musl_optarg, std::make_shared<std::string>("1"));
sym_AddString(arg, std::make_shared<std::string>("1"));
}
break;
}
@@ -344,8 +203,8 @@ int main(int argc, char *argv[]) {
break;
case 'g':
if (strlen(musl_optarg) == 4) {
opt_G(musl_optarg);
if (strlen(arg) == 4) {
opt_G(arg);
} else {
fatal("Must specify exactly 4 characters for option '-g'");
}
@@ -357,32 +216,33 @@ int main(int argc, char *argv[]) {
// LCOV_EXCL_STOP
case 'I':
fstk_AddIncludePath(musl_optarg);
fstk_AddIncludePath(arg);
break;
case 'M':
if (dependFileName) {
if (localOptions.dependFileName) {
warnx(
"Overriding dependency file \"%s\"",
strcmp(dependFileName, "-") ? dependFileName : "<stdout>"
*localOptions.dependFileName == "-" ? "<stdout>"
: localOptions.dependFileName->c_str()
);
}
dependFileName = musl_optarg;
localOptions.dependFileName = arg;
break;
case 'o':
if (!options.objectFileName.empty()) {
warnx("Overriding output file \"%s\"", options.objectFileName.c_str());
if (options.objectFileName) {
warnx("Overriding output file \"%s\"", options.objectFileName->c_str());
}
options.objectFileName = musl_optarg;
options.objectFileName = arg;
break;
case 'P':
fstk_AddPreIncludeFile(musl_optarg);
fstk_AddPreIncludeFile(arg);
break;
case 'p':
if (std::optional<uint64_t> padByte = parseWholeNumber(musl_optarg); !padByte) {
if (std::optional<uint64_t> padByte = parseWholeNumber(arg); !padByte) {
fatal("Invalid argument for option '-p'");
} else if (*padByte > 0xFF) {
fatal("Argument for option '-p' must be between 0 and 0xFF");
@@ -392,7 +252,7 @@ int main(int argc, char *argv[]) {
break;
case 'Q': {
char const *precisionArg = musl_optarg;
char const *precisionArg = arg;
if (precisionArg[0] == '.') {
++precisionArg;
}
@@ -408,7 +268,7 @@ int main(int argc, char *argv[]) {
}
case 'r':
if (std::optional<uint64_t> maxDepth = parseWholeNumber(musl_optarg); !maxDepth) {
if (std::optional<uint64_t> maxDepth = parseWholeNumber(arg); !maxDepth) {
fatal("Invalid argument for option '-r'");
} else if (errno == ERANGE) {
fatal("Argument for option '-r' is out of range");
@@ -418,19 +278,19 @@ int main(int argc, char *argv[]) {
break;
case 's': {
// Split "<features>:<name>" so `musl_optarg` is "<features>" and `name` is "<name>"
char *name = strchr(musl_optarg, ':');
// Split "<features>:<name>" so `arg` is "<features>" and `name` is "<name>"
char *name = strchr(arg, ':');
if (!name) {
fatal("Invalid argument for option '-s'");
}
*name++ = '\0';
std::vector<StateFeature> features = parseStateFeatures(musl_optarg);
std::vector<StateFeature> features = parseStateFeatures(arg);
if (stateFileSpecs.find(name) != stateFileSpecs.end()) {
if (localOptions.stateFileSpecs.find(name) != localOptions.stateFileSpecs.end()) {
warnx("Overriding state file \"%s\"", name);
}
stateFileSpecs.emplace(name, std::move(features));
localOptions.stateFileSpecs.emplace(name, std::move(features));
break;
}
@@ -445,7 +305,7 @@ int main(int argc, char *argv[]) {
// LCOV_EXCL_STOP
case 'W':
opt_W(musl_optarg);
opt_W(arg);
break;
case 'w':
@@ -453,7 +313,7 @@ int main(int argc, char *argv[]) {
break;
case 'X':
if (std::optional<uint64_t> maxErrors = parseWholeNumber(musl_optarg); !maxErrors) {
if (std::optional<uint64_t> maxErrors = parseWholeNumber(arg); !maxErrors) {
fatal("Invalid argument for option '-X'");
} else if (*maxErrors > UINT64_MAX) {
fatal("Argument for option '-X' must be between 0 and %" PRIu64, UINT64_MAX);
@@ -465,7 +325,7 @@ int main(int argc, char *argv[]) {
case 0: // Long-only options
switch (longOpt) {
case 'c':
if (!style_Parse(musl_optarg)) {
if (!style_Parse(arg)) {
fatal("Invalid argument for option '--color'");
}
break;
@@ -484,66 +344,223 @@ int main(int argc, char *argv[]) {
case 'Q':
case 'T': {
std::string newTarget = musl_optarg;
std::string newTarget = arg;
if (longOpt == 'Q') {
newTarget = escapeMakeChars(newTarget);
}
if (!options.targetFileName.empty()) {
options.targetFileName += ' ';
if (options.targetFileName) {
*options.targetFileName += ' ';
*options.targetFileName += newTarget;
} else {
options.targetFileName = newTarget;
}
options.targetFileName += newTarget;
break;
}
}
break;
case 1: // Positional argument
if (localOptions.inputFileName) {
usage.printAndExit("More than one input file specified");
}
localOptions.inputFileName = arg;
break;
// LCOV_EXCL_START
default:
usage.printAndExit(1);
// LCOV_EXCL_STOP
}
}
// LCOV_EXCL_START
static void verboseOutputConfig() {
if (!checkVerbosity(VERB_CONFIG)) {
return;
}
if (options.targetFileName.empty() && !options.objectFileName.empty()) {
style_Set(stderr, STYLE_MAGENTA, false);
fprintf(stderr, "rgbasm %s\n", get_package_version_string());
printVVVVVVerbosity();
fputs("Options:\n", stderr);
// -E/--export-all
if (options.exportAll) {
fputs("\tExport all labels by default\n", stderr);
}
// -b/--binary-digits
if (options.binDigits[0] != '0' || options.binDigits[1] != '1') {
fprintf(
stderr, "\tBinary digits: '%c', '%c'\n", options.binDigits[0], options.binDigits[1]
);
}
// -g/--gfx-chars
if (options.gfxDigits[0] != '0' || options.gfxDigits[1] != '1' || options.gfxDigits[2] != '2'
|| options.gfxDigits[3] != '3') {
fprintf(
stderr,
"\tGraphics characters: '%c', '%c', '%c', '%c'\n",
options.gfxDigits[0],
options.gfxDigits[1],
options.gfxDigits[2],
options.gfxDigits[3]
);
}
// -Q/--q-precision
fprintf(
stderr,
"\tFixed-point precision: Q%d.%" PRIu8 "\n",
32 - options.fixPrecision,
options.fixPrecision
);
// -p/--pad-value
fprintf(stderr, "\tPad value: 0x%02" PRIx8 "\n", options.padByte);
// -r/--recursion-depth
fprintf(stderr, "\tMaximum recursion depth %zu\n", options.maxRecursionDepth);
// -X/--max-errors
if (options.maxErrors) {
fprintf(stderr, "\tMaximum %" PRIu64 " errors\n", options.maxErrors);
}
// -D/--define
static bool hasDefines = false; // `static` so `sym_ForEach` callback can see it
sym_ForEach([](Symbol &sym) {
if (!sym.isBuiltin && sym.type == SYM_EQUS) {
if (!hasDefines) {
fputs("\tDefinitions:\n", stderr);
hasDefines = true;
}
fprintf(stderr, "\t - def %s equs \"%s\"\n", sym.name.c_str(), sym.getEqus()->c_str());
}
});
// -s/--state
if (!localOptions.stateFileSpecs.empty()) {
fputs("\tOutput state files:\n", stderr);
static char const *featureNames[NB_STATE_FEATURES] = {
"equ",
"var",
"equs",
"char",
"macro",
};
for (auto const &[name, features] : localOptions.stateFileSpecs) {
fprintf(stderr, "\t - %s: ", name == "-" ? "<stdout>" : name.c_str());
for (size_t i = 0; i < features.size(); ++i) {
if (i > 0) {
fputs(", ", stderr);
}
fputs(featureNames[features[i]], stderr);
}
putc('\n', stderr);
}
}
// asmfile
if (localOptions.inputFileName) {
fprintf(
stderr,
"\tInput asm file: %s\n",
*localOptions.inputFileName == "-" ? "<stdin>" : localOptions.inputFileName->c_str()
);
}
// -o/--output
if (options.objectFileName) {
fprintf(stderr, "\tOutput object file: %s\n", options.objectFileName->c_str());
}
fstk_VerboseOutputConfig();
if (localOptions.dependFileName) {
fprintf(stderr, "\tOutput dependency file: %s\n", localOptions.dependFileName->c_str());
// -MT or -MQ
if (options.targetFileName) {
fprintf(stderr, "\tTarget file(s): %s\n", options.targetFileName->c_str());
}
// -MG or -MC
switch (options.missingIncludeState) {
case INC_ERROR:
fputs("\tExit with an error on a missing dependency\n", stderr);
break;
case GEN_EXIT:
fputs("\tExit normally on a missing dependency\n", stderr);
break;
case GEN_CONTINUE:
fputs("\tContinue processing after a missing dependency\n", stderr);
break;
}
// -MP
if (options.generatePhonyDeps) {
fputs("\tGenerate phony dependencies\n", stderr);
}
}
fputs("Ready.\n", stderr);
style_Reset(stderr);
}
// LCOV_EXCL_STOP
int main(int argc, char *argv[]) {
// Support SOURCE_DATE_EPOCH for reproducible builds
// https://reproducible-builds.org/docs/source-date-epoch/
time_t now = time(nullptr);
if (char const *sourceDateEpoch = getenv("SOURCE_DATE_EPOCH"); sourceDateEpoch) {
// Use `strtoul`, not `parseWholeNumber`, because SOURCE_DATE_EPOCH does
// not conventionally support our custom base prefixes
now = static_cast<time_t>(strtoul(sourceDateEpoch, nullptr, 0));
}
sym_Init(now);
// Maximum of 100 errors only applies if rgbasm is printing errors to a terminal
if (isatty(STDERR_FILENO)) {
options.maxErrors = 100; // LCOV_EXCL_LINE
}
cli_ParseArgs(argc, argv, optstring, longopts, parseArg, usage);
if (!options.targetFileName && options.objectFileName) {
options.targetFileName = options.objectFileName;
}
verboseOutputConfig(argc, argv);
verboseOutputConfig();
if (argc == musl_optind) {
if (!localOptions.inputFileName) {
usage.printAndExit("No input file specified (pass \"-\" to read from standard input)");
} else if (argc != musl_optind + 1) {
usage.printAndExit("More than one input file specified");
}
std::string mainFileName = argv[musl_optind];
// LCOV_EXCL_START
verbosePrint(
VERB_NOTICE,
"Assembling \"%s\"\n",
*localOptions.inputFileName == "-" ? "<stdin>" : localOptions.inputFileName->c_str()
);
// LCOV_EXCL_STOP
verbosePrint(VERB_NOTICE, "Assembling \"%s\"\n", mainFileName.c_str()); // LCOV_EXCL_LINE
if (dependFileName) {
if (options.targetFileName.empty()) {
if (localOptions.dependFileName) {
if (!options.targetFileName) {
fatal("Dependency files can only be created if a target file is specified with either "
"'-o', '-MQ' or '-MT'");
}
if (strcmp("-", dependFileName)) {
options.dependFile = fopen(dependFileName, "w");
if (*localOptions.dependFileName == "-") {
options.dependFile = stdout;
} else {
options.dependFile = fopen(localOptions.dependFileName->c_str(), "w");
if (options.dependFile == nullptr) {
// LCOV_EXCL_START
fatal("Failed to open dependency file \"%s\": %s", dependFileName, strerror(errno));
fatal(
"Failed to open dependency file \"%s\": %s",
localOptions.dependFileName->c_str(),
strerror(errno)
);
// LCOV_EXCL_STOP
}
} else {
options.dependFile = stdout;
}
}
options.printDep(mainFileName);
options.printDep(*localOptions.inputFileName);
charmap_New(DEFAULT_CHARMAP_NAME, nullptr);
// Init lexer and file stack, providing file info
fstk_Init(mainFileName);
fstk_Init(*localOptions.inputFileName);
// Perform parse (`yy::parser` is auto-generated from `parser.y`)
if (yy::parser parser; parser.parse() != 0) {
@@ -569,7 +586,7 @@ int main(int argc, char *argv[]) {
out_WriteObject();
for (auto const &[name, features] : stateFileSpecs) {
for (auto const &[name, features] : localOptions.stateFileSpecs) {
out_WriteState(name, features);
}

View File

@@ -184,30 +184,29 @@ static void writeFileStackNode(FileStackNode const &node, FILE *file) {
putLong(nodeIters.size(), file);
// Iters are stored by decreasing depth, so reverse the order for output
for (uint32_t i = nodeIters.size(); i--;) {
putLong(nodeIters[i], file);
for (uint32_t iter : reversed(nodeIters)) {
putLong(iter, file);
}
}
}
void out_WriteObject() {
if (options.objectFileName.empty()) {
if (!options.objectFileName) {
return;
}
static FILE *file; // `static` so `sect_ForEach` callback can see it
if (options.objectFileName != "-") {
file = fopen(options.objectFileName.c_str(), "wb");
char const *objectFileName = options.objectFileName->c_str();
if (*options.objectFileName != "-") {
file = fopen(objectFileName, "wb");
} else {
options.objectFileName = "<stdout>";
objectFileName = "<stdout>";
(void)setmode(STDOUT_FILENO, O_BINARY);
file = stdout;
}
if (!file) {
// LCOV_EXCL_START
fatal(
"Failed to open object file \"%s\": %s", options.objectFileName.c_str(), strerror(errno)
);
fatal("Failed to open object file \"%s\": %s", objectFileName, strerror(errno));
// LCOV_EXCL_STOP
}
Defer closeFile{[&] { fclose(file); }};
@@ -223,12 +222,10 @@ void out_WriteObject() {
putLong(fileStackNodes.size(), file);
for (auto it = fileStackNodes.begin(); it != fileStackNodes.end(); ++it) {
FileStackNode const &node = **it;
writeFileStackNode(node, file);
writeFileStackNode(**it, file);
// The list is supposed to have decrementing IDs
assume(it + 1 == fileStackNodes.end() || it[1]->ID == node.ID - 1);
assume(it + 1 == fileStackNodes.end() || it[1]->ID == it[0]->ID - 1);
}
for (Symbol const *sym : objectSymbols) {

View File

@@ -32,7 +32,6 @@
%code {
#include <algorithm>
#include <ctype.h>
#include <inttypes.h>
#include <optional>
#include <stdio.h>
@@ -42,6 +41,7 @@
#include "extern/utf8decoder.hpp"
#include "helpers.hpp"
#include "util.hpp" // toLower, toUpper
#include "asm/charmap.hpp"
#include "asm/fixpoint.hpp"
@@ -57,8 +57,10 @@
yy::parser::symbol_type yylex(); // Provided by lexer.cpp
template <typename N, typename S>
static auto handleSymbolByType(std::string const &symName, N numCallback, S strCallback) {
template<typename NumCallbackFnT, typename StrCallbackFnT>
static auto handleSymbolByType(
std::string const &symName, NumCallbackFnT numCallback, StrCallbackFnT strCallback
) {
if (Symbol *sym = sym_FindScopedSymbol(symName); sym && sym->type == SYM_EQUS) {
return strCallback(*sym->getEqus());
} else {
@@ -1599,11 +1601,11 @@ string_literal:
}
| OP_STRUPR LPAREN string RPAREN {
$$ = std::move($3);
std::transform(RANGE($$), $$.begin(), [](char c) { return toupper(c); });
std::transform(RANGE($$), $$.begin(), toUpper);
}
| OP_STRLWR LPAREN string RPAREN {
$$ = std::move($3);
std::transform(RANGE($$), $$.begin(), [](char c) { return tolower(c); });
std::transform(RANGE($$), $$.begin(), toLower);
}
| OP_STRRPL LPAREN string COMMA string COMMA string RPAREN {
$$ = act_StringReplace($3, $5, $7);

View File

@@ -39,6 +39,7 @@ static Symbol const *localScope = nullptr; // Current section's local label sco
static Symbol *PCSymbol;
static Symbol *NARGSymbol;
static Symbol *SCOPESymbol;
static Symbol *globalScopeSymbol;
static Symbol *localScopeSymbol;
static Symbol *RSSymbol;
@@ -67,6 +68,19 @@ static int32_t NARGCallback() {
}
}
static std::shared_ptr<std::string> SCOPECallback() {
if (localScope) {
return std::make_shared<std::string>("..");
} else if (globalScope) {
return std::make_shared<std::string>(".");
} else {
if (!sect_GetSymbolSection()) {
error("`__SCOPE__` has no value outside of a section");
}
return std::make_shared<std::string>("");
}
}
static std::shared_ptr<std::string> globalScopeCallback() {
if (!globalScope) {
error("`.` has no value outside of a label scope");
@@ -296,6 +310,10 @@ Symbol *sym_FindScopedValidSymbol(std::string const &symName) {
if (sym == localScopeSymbol && !localScope) {
return nullptr;
}
// `__SCOPE__` has no value outside of a section
if (sym == SCOPESymbol && !sect_GetSymbolSection()) {
return nullptr;
}
return sym;
}
@@ -683,6 +701,11 @@ void sym_Init(time_t now) {
localScopeSymbol->data = localScopeCallback;
localScopeSymbol->isBuiltin = true;
SCOPESymbol = &createSymbol("__SCOPE__"s);
SCOPESymbol->type = SYM_EQUS;
SCOPESymbol->data = SCOPECallback;
SCOPESymbol->isBuiltin = true;
RSSymbol = sym_AddVar("_RS"s, 0);
RSSymbol->isBuiltin = true;

View File

@@ -52,8 +52,8 @@ Diagnostics<WarningLevel, WarningID> warnings = {
},
.paramWarnings = {
{WARNING_NUMERIC_STRING_1, WARNING_NUMERIC_STRING_2, 1},
{WARNING_PURGE_1, WARNING_PURGE_2, 1},
{WARNING_TRUNCATION_1, WARNING_TRUNCATION_2, 2},
{WARNING_PURGE_1, WARNING_PURGE_2, 2},
{WARNING_TRUNCATION_1, WARNING_TRUNCATION_2, 1},
{WARNING_UNMAPPED_CHAR_1, WARNING_UNMAPPED_CHAR_2, 1},
},
.state = DiagnosticsState<WarningID>(),

156
src/cli.cpp Normal file
View File

@@ -0,0 +1,156 @@
#include "cli.hpp"
#include <errno.h>
#include <fstream>
#include <stdio.h>
#include <string.h>
#include <string>
#include <vector>
#include "extern/getopt.hpp"
#include "style.hpp"
#include "usage.hpp"
#include "util.hpp" // isBlankSpace
using namespace std::literals;
// Turn an at-file's contents into an argv that `getopt` can handle, appending them to `argPool`.
static std::vector<size_t>
readAtFile(std::string const &path, std::vector<char> &argPool, Usage usage) {
std::vector<size_t> argvOfs;
std::filebuf file;
if (!file.open(path, std::ios_base::in)) {
style_Set(stderr, STYLE_RED, true);
fputs("FATAL: ", stderr);
style_Reset(stderr);
fprintf(stderr, "Failed to open at-file \"%s\": %s\n", path.c_str(), strerror(errno));
usage.printAndExit(1);
}
for (;;) {
int c = file.sbumpc();
// First, discard any leading blank space
while (isBlankSpace(c)) {
c = file.sbumpc();
}
// If it's a comment, discard everything until EOL
if (c == '#') {
c = file.sbumpc();
while (c != EOF && !isNewline(c)) {
c = file.sbumpc();
}
}
if (c == EOF) {
return argvOfs;
} else if (isNewline(c)) {
continue; // Start processing the next line
}
// Alright, now we can parse the line
do {
argvOfs.push_back(argPool.size());
// Read one argument (until the next whitespace char).
// We know there is one because we already have its first character in `c`.
for (; c != EOF && !isWhitespace(c); c = file.sbumpc()) {
argPool.push_back(c);
}
argPool.push_back('\0');
// Discard blank space until the next argument (candidate)
while (isBlankSpace(c)) {
c = file.sbumpc();
}
} while (c != EOF && !isNewline(c)); // End if we reached EOL
}
}
void cli_ParseArgs(
int argc,
char *argv[],
char const *shortOpts,
option const *longOpts,
void (*parseArg)(int, char *),
Usage usage
) {
struct AtFileStackEntry {
int parentInd; // Saved offset into parent argv
std::vector<char *> argv; // This context's arg pointer vec
AtFileStackEntry(int parentInd_, std::vector<char *> argv_)
: parentInd(parentInd_), argv(argv_) {}
};
std::vector<AtFileStackEntry> atFileStack;
int curArgc = argc;
char **curArgv = argv;
std::string optString = "-"s + shortOpts; // Request position arguments with a leading '-'
std::vector<std::vector<char>> argPools;
for (;;) {
char *atFileName = nullptr;
for (int ch;
(ch = musl_getopt_long_only(curArgc, curArgv, optString.c_str(), longOpts)) != -1;) {
if (ch == 1 && musl_optarg[0] == '@') {
atFileName = &musl_optarg[1];
break;
} else {
parseArg(ch, musl_optarg);
}
}
if (atFileName) {
// We need to allocate a new arg pool for each at-file, so as not to invalidate pointers
// previous at-files may have generated to their own arg pools.
// But for the same reason, the arg pool must also outlive the at-file's stack entry!
std::vector<char> &argPool = argPools.emplace_back();
// Copy `argv[0]` for error reporting, and because option parsing skips it
AtFileStackEntry &stackEntry =
atFileStack.emplace_back(musl_optind, std::vector{atFileName});
// It would be nice to compute the char pointers on the fly, but reallocs don't allow
// that; so we must compute the offsets after the pool is fixed
std::vector<size_t> offsets = readAtFile(&musl_optarg[1], argPool, usage);
stackEntry.argv.reserve(offsets.size() + 2); // Avoid a bunch of reallocs
for (size_t ofs : offsets) {
stackEntry.argv.push_back(&argPool.data()[ofs]);
}
stackEntry.argv.push_back(nullptr); // Don't forget the arg vector terminator!
curArgc = stackEntry.argv.size() - 1;
curArgv = stackEntry.argv.data();
musl_optind = 1; // Don't use 0 because we're not scanning a different argv per se
} else {
if (musl_optind != curArgc) {
// This happens if `--` is passed, process the remaining arg(s) as positional
assume(musl_optind < curArgc);
for (int i = musl_optind; i < curArgc; ++i) {
parseArg(1, argv[i]); // Positional argument
}
}
// Pop off the top stack entry, or end parsing if none
if (atFileStack.empty()) {
break;
}
// OK to restore `optind` directly, because `optpos` must be 0 right now.
// (Providing 0 would be a "proper" reset, but we want to resume parsing)
musl_optind = atFileStack.back().parentInd;
atFileStack.pop_back();
if (atFileStack.empty()) {
curArgc = argc;
curArgv = argv;
} else {
std::vector<char *> &vec = atFileStack.back().argv;
curArgc = vec.size();
curArgv = vec.data();
}
}
}
}

203
src/extern/getopt.cpp vendored
View File

@@ -11,27 +11,24 @@
#include <string.h>
#include <wchar.h>
#include "style.hpp"
char *musl_optarg;
int musl_optind = 1, musl_opterr = 1, musl_optopt;
int musl_optreset = 0;
int musl_optind = 1, musl_optopt;
static int musl_optpos;
static void musl_getopt_msg(char const *a, char const *b, char const *c, size_t l) {
FILE *f = stderr;
if (fputs(a, f) >= 0 && fwrite(b, strlen(b), 1, f) && fwrite(c, 1, l, f) == l) {
putc('\n', f);
}
static void musl_getopt_msg(char const *msg, char const *param) {
style_Set(stderr, STYLE_RED, true);
fputs("error: ", stderr);
style_Reset(stderr);
fputs(msg, stderr);
fputs(param, stderr);
putc('\n', stderr);
}
static int getopt(int argc, char *argv[], char const *optstring) {
int i;
wchar_t c, d;
int k, l;
char *optchar;
if (!musl_optind || musl_optreset) {
musl_optreset = 0;
static int musl_getopt(int argc, char *argv[], char const *optstring) {
if (!musl_optind) {
musl_optpos = 0;
musl_optind = 1;
}
@@ -40,7 +37,9 @@ static int getopt(int argc, char *argv[], char const *optstring) {
return -1;
}
if (argv[musl_optind][0] != '-') {
char *argi = argv[musl_optind];
if (argi[0] != '-') {
if (optstring[0] == '-') {
musl_optarg = argv[musl_optind++];
return 1;
@@ -48,26 +47,28 @@ static int getopt(int argc, char *argv[], char const *optstring) {
return -1;
}
if (!argv[musl_optind][1]) {
if (!argi[1]) {
return -1;
}
if (argv[musl_optind][1] == '-' && !argv[musl_optind][2]) {
return musl_optind++, -1;
if (argi[1] == '-' && !argi[2]) {
++musl_optind;
return -1;
}
if (!musl_optpos) {
++musl_optpos;
}
k = mbtowc(&c, argv[musl_optind] + musl_optpos, MB_LEN_MAX);
wchar_t c;
int k = mbtowc(&c, argi + musl_optpos, MB_LEN_MAX);
if (k < 0) {
k = 1;
c = 0xFFFD; // replacement char
}
optchar = argv[musl_optind] + musl_optpos;
char *optchar = argi + musl_optpos;
musl_optpos += k;
if (!argv[musl_optind][musl_optpos]) {
if (!argi[musl_optpos]) {
++musl_optind;
musl_optpos = 0;
}
@@ -76,8 +77,9 @@ static int getopt(int argc, char *argv[], char const *optstring) {
++optstring;
}
i = 0;
d = 0;
int i = 0;
wchar_t d = 0;
int l;
do {
l = mbtowc(&d, optstring + i, MB_LEN_MAX);
if (l > 0) {
@@ -89,8 +91,8 @@ static int getopt(int argc, char *argv[], char const *optstring) {
if (d != c || c == ':') {
musl_optopt = c;
if (optstring[0] != ':' && musl_opterr) {
musl_getopt_msg(argv[0], ": unrecognized option: ", optchar, k);
if (optstring[0] != ':') {
musl_getopt_msg("unrecognized option: ", optchar);
}
return '?';
}
@@ -105,9 +107,7 @@ static int getopt(int argc, char *argv[], char const *optstring) {
if (optstring[0] == ':') {
return ':';
}
if (musl_opterr) {
musl_getopt_msg(argv[0], ": option requires an argument: ", optchar, k);
}
musl_getopt_msg("option requires an argument: ", optchar);
return '?';
}
}
@@ -116,73 +116,27 @@ static int getopt(int argc, char *argv[], char const *optstring) {
static void permute(char **argv, int dest, int src) {
char *tmp = argv[src];
int i;
for (i = src; i > dest; --i) {
for (int i = src; i > dest; --i) {
argv[i] = argv[i - 1];
}
argv[dest] = tmp;
}
static int musl_getopt_long_core(
int argc, char **argv, char const *optstring, option const *longopts, int *idx, int longonly
);
static int musl_getopt_long(
int argc, char **argv, char const *optstring, option const *longopts, int *idx, int longonly
) {
int ret, skipped, resumed;
if (!musl_optind || musl_optreset) {
musl_optreset = 0;
musl_optpos = 0;
musl_optind = 1;
}
if (musl_optind >= argc || !argv[musl_optind]) {
return -1;
}
skipped = musl_optind;
if (optstring[0] != '+' && optstring[0] != '-') {
int i;
for (i = musl_optind;; ++i) {
if (i >= argc || !argv[i]) {
return -1;
}
if (argv[i][0] == '-' && argv[i][1]) {
break;
}
}
musl_optind = i;
}
resumed = musl_optind;
ret = musl_getopt_long_core(argc, argv, optstring, longopts, idx, longonly);
if (resumed > skipped) {
int i, cnt = musl_optind - resumed;
for (i = 0; i < cnt; ++i) {
permute(argv, skipped, musl_optind - 1);
}
musl_optind = skipped + cnt;
}
return ret;
}
static int musl_getopt_long_core(
int argc, char **argv, char const *optstring, option const *longopts, int *idx, int longonly
) {
static int
musl_getopt_long_core(int argc, char **argv, char const *optstring, option const *longopts) {
musl_optarg = 0;
if (longopts && argv[musl_optind][0] == '-'
&& ((longonly && argv[musl_optind][1] && argv[musl_optind][1] != '-')
|| (argv[musl_optind][1] == '-' && argv[musl_optind][2]))) {
int colon = optstring[optstring[0] == '+' || optstring[0] == '-'] == ':';
int i, cnt, match = 0;
if (char *argi = argv[musl_optind];
!longopts || argi[0] != '-'
|| ((!argi[1] || argi[1] == '-') && (argi[1] != '-' || !argi[2]))) {
return musl_getopt(argc, argv, optstring);
}
bool colon = optstring[optstring[0] == '+' || optstring[0] == '-'] == ':';
int i = 0, cnt = 0, match = 0;
char *arg = 0, *opt, *start = argv[musl_optind] + 1;
for (cnt = i = 0; longopts[i].name; ++i) {
for (; longopts[i].name; ++i) {
char const *name = longopts[i].name;
opt = start;
if (*opt == '-') {
++opt;
@@ -202,12 +156,10 @@ static int musl_getopt_long_core(
}
++cnt;
}
if (cnt == 1 && longonly && arg - start == mblen(start, MB_LEN_MAX)) {
if (cnt == 1 && arg - start == mblen(start, MB_LEN_MAX)) {
int l = arg - start;
for (i = 0; optstring[i]; ++i) {
int j = 0;
while (j < l && start[j] == optstring[i + j]) {
++j;
}
@@ -224,15 +176,10 @@ static int musl_getopt_long_core(
if (*opt == '=') {
if (!longopts[i].has_arg) {
musl_optopt = longopts[i].val;
if (colon || !musl_opterr) {
if (colon) {
return '?';
}
musl_getopt_msg(
argv[0],
": option does not take an argument: ",
longopts[i].name,
strlen(longopts[i].name)
);
musl_getopt_msg("option does not take an argument: ", longopts[i].name);
return '?';
}
musl_optarg = opt + 1;
@@ -243,22 +190,11 @@ static int musl_getopt_long_core(
if (colon) {
return ':';
}
if (!musl_opterr) {
return '?';
}
musl_getopt_msg(
argv[0],
": option requires an argument: ",
longopts[i].name,
strlen(longopts[i].name)
);
musl_getopt_msg("option requires an argument: ", longopts[i].name);
return '?';
}
++musl_optind;
}
if (idx) {
*idx = i;
}
if (longopts[i].flag) {
*longopts[i].flag = longopts[i].val;
return 0;
@@ -267,23 +203,48 @@ static int musl_getopt_long_core(
}
if (argv[musl_optind][1] == '-') {
musl_optopt = 0;
if (!colon && musl_opterr) {
if (!colon) {
musl_getopt_msg(
argv[0],
cnt ? ": option is ambiguous: " : ": unrecognized option: ",
argv[musl_optind] + 2,
strlen(argv[musl_optind] + 2)
cnt ? "option is ambiguous: " : "unrecognized option: ", argv[musl_optind] + 2
);
}
++musl_optind;
return '?';
}
}
return getopt(argc, argv, optstring);
return musl_getopt(argc, argv, optstring);
}
int musl_getopt_long_only(
int argc, char **argv, char const *optstring, option const *longopts, int *idx
) {
return musl_getopt_long(argc, argv, optstring, longopts, idx, 1);
int musl_getopt_long_only(int argc, char **argv, char const *optstring, option const *longopts) {
if (!musl_optind) {
musl_optpos = 0;
musl_optind = 1;
}
if (musl_optind >= argc || !argv[musl_optind]) {
return -1;
}
int skipped = musl_optind;
if (optstring[0] != '+' && optstring[0] != '-') {
int i = musl_optind;
for (;; ++i) {
if (i >= argc || !argv[i]) {
return -1;
}
if (argv[i][0] == '-' && argv[i][1]) {
break;
}
}
musl_optind = i;
}
int resumed = musl_optind;
int ret = musl_getopt_long_core(argc, argv, optstring, longopts);
if (resumed > skipped) {
int cnt = musl_optind - resumed;
for (int i = 0; i < cnt; ++i) {
permute(argv, skipped, musl_optind - 1);
}
musl_optind = skipped + cnt;
}
return ret;
}

View File

@@ -141,7 +141,11 @@ static void
if (options.title) {
overwriteBytes(
rom0, 0x134, reinterpret_cast<uint8_t const *>(options.title), options.titleLen, "title"
rom0,
0x134,
reinterpret_cast<uint8_t const *>(options.title->c_str()),
options.titleLen,
"title"
);
}
@@ -149,7 +153,7 @@ static void
overwriteBytes(
rom0,
0x13F,
reinterpret_cast<uint8_t const *>(options.gameID),
reinterpret_cast<uint8_t const *>(options.gameID->c_str()),
options.gameIDLen,
"manufacturer code"
);
@@ -163,7 +167,7 @@ static void
overwriteBytes(
rom0,
0x144,
reinterpret_cast<uint8_t const *>(options.newLicensee),
reinterpret_cast<uint8_t const *>(options.newLicensee->c_str()),
options.newLicenseeLen,
"new licensee code"
);

View File

@@ -12,8 +12,8 @@
#include <stdlib.h>
#include <string.h>
#include "cli.hpp"
#include "diagnostics.hpp"
#include "extern/getopt.hpp"
#include "helpers.hpp"
#include "platform.hpp"
#include "style.hpp"
@@ -27,10 +27,16 @@
Options options;
// Flags which must be processed after the option parsing finishes
static struct LocalOptions {
std::optional<std::string> outputFileName; // -o
std::vector<std::string> inputFileNames; // <file>...
} localOptions;
// 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
// Long-only option variable
static int longOpt; // `--color`
// Equivalent long options
@@ -91,13 +97,191 @@ static Usage usage = {
};
// clang-format on
static void parseByte(uint16_t &output, char name) {
if (std::optional<uint64_t> value = parseWholeNumber(musl_optarg); !value) {
static uint16_t parseByte(char const *input, char name) {
if (std::optional<uint64_t> value = parseWholeNumber(input); !value) {
fatal("Invalid argument for option '-%c'", name);
} else if (*value > 0xFF) {
fatal("Argument for option '-%c' must be between 0 and 0xFF", name);
} else {
output = *value;
return *value;
}
}
static void parseArg(int ch, char *arg) {
switch (ch) {
case 'C':
case 'c':
options.model = ch == 'c' ? BOTH : CGB;
if (options.titleLen > 15) {
options.titleLen = 15;
assume(options.title.has_value());
warning(
WARNING_TRUNCATION, "Truncating title \"%s\" to 15 chars", options.title->c_str()
);
}
break;
case 'f':
options.fixSpec = 0;
while (*arg) {
switch (*arg) {
#define overrideSpec(cur, bad, curFlag, badFlag) \
case cur: \
if (options.fixSpec & badFlag) { \
warnx("'%c' overriding '%c' in fix spec", cur, bad); \
} \
options.fixSpec = (options.fixSpec & ~badFlag) | curFlag; \
break
#define overrideSpecPair(fix, fixFlag, trash, trashFlag) \
overrideSpec(fix, trash, fixFlag, trashFlag); \
overrideSpec(trash, fix, trashFlag, fixFlag)
overrideSpecPair('l', FIX_LOGO, 'L', TRASH_LOGO);
overrideSpecPair('h', FIX_HEADER_SUM, 'H', TRASH_HEADER_SUM);
overrideSpecPair('g', FIX_GLOBAL_SUM, 'G', TRASH_GLOBAL_SUM);
#undef overrideSpec
#undef overrideSpecPair
default:
fatal("Invalid character '%c' in fix spec", *arg);
}
++arg;
}
break;
// LCOV_EXCL_START
case 'h':
usage.printAndExit(0);
// LCOV_EXCL_STOP
case 'i': {
options.gameID = arg;
size_t len = options.gameID->length();
if (len > 4) {
len = 4;
warning(
WARNING_TRUNCATION, "Truncating game ID \"%s\" to 4 chars", options.gameID->c_str()
);
}
options.gameIDLen = len;
if (options.titleLen > 11) {
options.titleLen = 11;
assume(options.title.has_value());
warning(
WARNING_TRUNCATION, "Truncating title \"%s\" to 11 chars", options.title->c_str()
);
}
break;
}
case 'j':
options.japanese = false;
break;
case 'k': {
options.newLicensee = arg;
size_t len = options.newLicensee->length();
if (len > 2) {
len = 2;
warning(
WARNING_TRUNCATION,
"Truncating new licensee \"%s\" to 2 chars",
options.newLicensee->c_str()
);
}
options.newLicenseeLen = len;
break;
}
case 'L':
options.logoFilename = arg;
break;
case 'l':
options.oldLicensee = parseByte(arg, 'l');
break;
case 'm':
options.cartridgeType = mbc_ParseName(arg, options.tpp1Rev[0], options.tpp1Rev[1]);
if (options.cartridgeType == ROM_RAM || options.cartridgeType == ROM_RAM_BATTERY) {
warning(WARNING_MBC, "MBC \"%s\" is under-specified and poorly supported", arg);
}
break;
case 'n':
options.romVersion = parseByte(arg, 'n');
break;
case 'O':
warning(WARNING_OBSOLETE, "'-O' is deprecated; use '-Wno-overwrite' instead");
warnings.processWarningFlag("no-overwrite");
break;
case 'o':
localOptions.outputFileName = arg;
break;
case 'p':
options.padValue = parseByte(arg, 'p');
break;
case 'r':
options.ramSize = parseByte(arg, 'r');
break;
case 's':
options.sgb = true;
break;
case 't': {
options.title = arg;
size_t len = options.title->length();
uint8_t maxLen = options.gameID ? 11 : options.model != DMG ? 15 : 16;
if (len > maxLen) {
len = maxLen;
warning(
WARNING_TRUNCATION,
"Truncating title \"%s\" to %u chars",
options.title->c_str(),
maxLen
);
}
options.titleLen = len;
break;
}
// LCOV_EXCL_START
case 'V':
printf("rgbfix %s\n", get_package_version_string());
exit(0);
case 'v':
options.fixSpec = FIX_LOGO | FIX_HEADER_SUM | FIX_GLOBAL_SUM;
break;
// LCOV_EXCL_STOP
case 'W':
warnings.processWarningFlag(arg);
break;
case 'w':
warnings.state.warningsEnabled = false;
break;
case 0: // Long-only options
if (longOpt == 'c' && !style_Parse(arg)) {
fatal("Invalid argument for option '--color'");
}
break;
case 1: // Positional arguments
localOptions.inputFileNames.push_back(arg);
break;
// LCOV_EXCL_START
default:
usage.printAndExit(1);
// LCOV_EXCL_STOP
}
}
@@ -110,18 +294,19 @@ static uint8_t const nintendoLogo[] = {
static void initLogo() {
if (options.logoFilename) {
FILE *logoFile;
if (strcmp(options.logoFilename, "-")) {
logoFile = fopen(options.logoFilename, "rb");
char const *logoFilename = options.logoFilename->c_str();
if (*options.logoFilename != "-") {
logoFile = fopen(logoFilename, "rb");
} else {
// LCOV_EXCL_START
options.logoFilename = "<stdin>";
logoFilename = "<stdin>";
(void)setmode(STDIN_FILENO, O_BINARY);
logoFile = stdin;
// LCOV_EXCL_STOP
}
if (!logoFile) {
// LCOV_EXCL_START
fatal("Failed to open \"%s\" for reading: %s", options.logoFilename, strerror(errno));
fatal("Failed to open \"%s\" for reading: %s", logoFilename, strerror(errno));
// LCOV_EXCL_STOP
}
Defer closeLogo{[&] { fclose(logoFile); }};
@@ -129,7 +314,7 @@ static void initLogo() {
uint8_t logoBpp[sizeof(options.logo)];
if (size_t nbRead = fread(logoBpp, 1, sizeof(logoBpp), logoFile);
nbRead != sizeof(options.logo) || fgetc(logoFile) != EOF || ferror(logoFile)) {
fatal("\"%s\" is not %zu bytes", options.logoFilename, sizeof(options.logo));
fatal("\"%s\" is not %zu bytes", logoFilename, sizeof(options.logo));
}
auto highs = [&logoBpp](size_t i) {
return (logoBpp[i * 2] & 0xF0) | ((logoBpp[i * 2 + 1] & 0xF0) >> 4);
@@ -161,176 +346,7 @@ static void initLogo() {
}
int main(int argc, char *argv[]) {
char const *outputFilename = nullptr;
// Parse CLI options
for (int ch; (ch = musl_getopt_long_only(argc, argv, optstring, longopts, nullptr)) != -1;) {
switch (ch) {
case 'C':
case 'c':
options.model = ch == 'c' ? BOTH : CGB;
if (options.titleLen > 15) {
options.titleLen = 15;
assume(options.title != nullptr);
warning(WARNING_TRUNCATION, "Truncating title \"%s\" to 15 chars", options.title);
}
break;
case 'f':
options.fixSpec = 0;
while (*musl_optarg) {
switch (*musl_optarg) {
#define OVERRIDE_SPEC(cur, bad, curFlag, badFlag) \
case STR(cur)[0]: \
if (options.fixSpec & badFlag) { \
warnx("'" STR(cur) "' overriding '" STR(bad) "' in fix spec"); \
} \
options.fixSpec = (options.fixSpec & ~badFlag) | curFlag; \
break
#define overrideSpecs(fix, fixFlag, trash, trashFlag) \
OVERRIDE_SPEC(fix, trash, fixFlag, trashFlag); \
OVERRIDE_SPEC(trash, fix, trashFlag, fixFlag)
overrideSpecs(l, FIX_LOGO, L, TRASH_LOGO);
overrideSpecs(h, FIX_HEADER_SUM, H, TRASH_HEADER_SUM);
overrideSpecs(g, FIX_GLOBAL_SUM, G, TRASH_GLOBAL_SUM);
#undef OVERRIDE_SPEC
#undef overrideSpecs
default:
fatal("Invalid character '%c' in fix spec", *musl_optarg);
}
++musl_optarg;
}
break;
// LCOV_EXCL_START
case 'h':
usage.printAndExit(0);
// LCOV_EXCL_STOP
case 'i': {
options.gameID = musl_optarg;
size_t len = strlen(options.gameID);
if (len > 4) {
len = 4;
warning(WARNING_TRUNCATION, "Truncating game ID \"%s\" to 4 chars", options.gameID);
}
options.gameIDLen = len;
if (options.titleLen > 11) {
options.titleLen = 11;
assume(options.title != nullptr);
warning(WARNING_TRUNCATION, "Truncating title \"%s\" to 11 chars", options.title);
}
break;
}
case 'j':
options.japanese = false;
break;
case 'k': {
options.newLicensee = musl_optarg;
size_t len = strlen(options.newLicensee);
if (len > 2) {
len = 2;
warning(
WARNING_TRUNCATION,
"Truncating new licensee \"%s\" to 2 chars",
options.newLicensee
);
}
options.newLicenseeLen = len;
break;
}
case 'L':
options.logoFilename = musl_optarg;
break;
case 'l':
parseByte(options.oldLicensee, 'l');
break;
case 'm':
options.cartridgeType =
mbc_ParseName(musl_optarg, options.tpp1Rev[0], options.tpp1Rev[1]);
if (options.cartridgeType == ROM_RAM || options.cartridgeType == ROM_RAM_BATTERY) {
warning(
WARNING_MBC, "MBC \"%s\" is under-specified and poorly supported", musl_optarg
);
}
break;
case 'n':
parseByte(options.romVersion, 'n');
break;
case 'O':
warning(WARNING_OBSOLETE, "'-O' is deprecated; use '-Wno-overwrite' instead");
warnings.processWarningFlag("no-overwrite");
break;
case 'o':
outputFilename = musl_optarg;
break;
case 'p':
parseByte(options.padValue, 'p');
break;
case 'r':
parseByte(options.ramSize, 'r');
break;
case 's':
options.sgb = true;
break;
case 't': {
options.title = musl_optarg;
size_t len = strlen(options.title);
uint8_t maxLen = options.gameID ? 11 : options.model != DMG ? 15 : 16;
if (len > maxLen) {
len = maxLen;
warning(
WARNING_TRUNCATION, "Truncating title \"%s\" to %u chars", options.title, maxLen
);
}
options.titleLen = len;
break;
}
// LCOV_EXCL_START
case 'V':
printf("rgbfix %s\n", get_package_version_string());
exit(0);
case 'v':
options.fixSpec = FIX_LOGO | FIX_HEADER_SUM | FIX_GLOBAL_SUM;
break;
// LCOV_EXCL_STOP
case 'W':
warnings.processWarningFlag(musl_optarg);
break;
case 'w':
warnings.state.warningsEnabled = false;
break;
case 0: // Long-only options
if (longOpt == 'c' && !style_Parse(musl_optarg)) {
fatal("Invalid argument for option '--color'");
}
break;
// LCOV_EXCL_START
default:
usage.printAndExit(1);
// LCOV_EXCL_STOP
}
}
cli_ParseArgs(argc, argv, optstring, longopts, parseArg, usage);
if ((options.cartridgeType & 0xFF00) == TPP1 && !options.japanese) {
warning(
@@ -382,19 +398,20 @@ int main(int argc, char *argv[]) {
initLogo();
argv += musl_optind;
if (!*argv) {
if (localOptions.inputFileNames.empty()) {
usage.printAndExit("No input file specified (pass \"-\" to read from standard input)");
}
if (outputFilename && argc != musl_optind + 1) {
if (localOptions.outputFileName && localOptions.inputFileNames.size() != 1) {
usage.printAndExit("If '-o' is set then only a single input file may be specified");
}
char const *outputFileName =
localOptions.outputFileName ? localOptions.outputFileName->c_str() : nullptr;
bool failed = warnings.nbErrors > 0;
do {
failed |= fix_ProcessFile(*argv, outputFilename);
} while (*++argv);
for (std::string const &inputFileName : localOptions.inputFileNames) {
failed |= fix_ProcessFile(inputFileName.c_str(), outputFileName);
}
return failed;
}

View File

@@ -12,7 +12,7 @@
#include "helpers.hpp" // unreachable_
#include "platform.hpp" // strcasecmp
#include "util.hpp" // isBlankSpace, isLower, isDigit
#include "util.hpp"
#include "fix/warning.hpp"
@@ -103,12 +103,10 @@ static void skipMBCSpace(char const *&ptr) {
}
static char normalizeMBCChar(char c) {
if (isLower(c)) {
c = c - 'a' + 'A'; // Uppercase for comparison with `mbc_Name`s
} else if (c == '_') {
c = ' '; // Treat underscores as spaces
if (c == '_') {
return ' '; // Treat underscores as spaces
}
return c;
return toUpper(c); // Uppercase for comparison with `mbc_Name`s
}
[[noreturn]]

View File

@@ -2,8 +2,6 @@
#include "gfx/main.hpp"
#include <algorithm>
#include <errno.h>
#include <inttypes.h>
#include <ios>
#include <optional>
@@ -12,11 +10,12 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <string>
#include <string_view>
#include <vector>
#include "cli.hpp"
#include "diagnostics.hpp"
#include "extern/getopt.hpp"
#include "file.hpp"
#include "helpers.hpp"
#include "platform.hpp"
@@ -36,22 +35,23 @@ using namespace std::literals::string_view_literals;
Options options;
// Flags which must be processed after the option parsing finishes
static struct LocalOptions {
char const *externalPalSpec;
bool autoAttrmap;
bool autoTilemap;
bool autoPalettes;
bool autoPalmap;
bool groupOutputs;
bool reverse;
std::optional<std::string> externalPalSpec; // -c
bool autoAttrmap; // -A
bool autoTilemap; // -T
bool autoPalettes; // -P
bool autoPalmap; // -Q
bool groupOutputs; // -O
bool reverse; // -r
bool autoAny() const { return autoAttrmap || autoTilemap || autoPalettes || autoPalmap; }
} 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";
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
// Long-only option variable
static int longOpt; // `--color`
// Equivalent long options
@@ -138,75 +138,8 @@ static void skipBlankSpace(char const *&arg) {
arg += strspn(arg, " \t");
}
static void registerInput(char const *arg) {
if (!options.input.empty()) {
usage.printAndExit(
"Input image specified more than once! (first \"%s\", then \"%s\")",
options.input.c_str(),
arg
);
} else if (arg[0] == '\0') { // Empty input path
usage.printAndExit("Input image path cannot be empty");
} else {
options.input = arg;
}
}
// Turn an at-file's contents into an argv that `getopt` can handle, appending them to `argPool`.
static std::vector<size_t> readAtFile(std::string const &path, std::vector<char> &argPool) {
File file;
if (!file.open(path, std::ios_base::in)) {
fatal("Error reading at-file \"%s\": %s", file.c_str(path), strerror(errno));
}
for (std::vector<size_t> argvOfs;;) {
int c = file->sbumpc();
// First, discard any leading blank space
while (isBlankSpace(c)) {
c = file->sbumpc();
}
// If it's a comment, discard everything until EOL
if (c == '#') {
c = file->sbumpc();
while (c != EOF && !isNewline(c)) {
c = file->sbumpc();
}
}
if (c == EOF) {
return argvOfs;
} else if (isNewline(c)) {
continue; // Start processing the next line
}
// Alright, now we can parse the line
do {
argvOfs.push_back(argPool.size());
// Read one argument (until the next whitespace char).
// We know there is one because we already have its first character in `c`.
for (; c != EOF && !isWhitespace(c); c = file->sbumpc()) {
argPool.push_back(c);
}
argPool.push_back('\0');
// Discard blank space until the next argument (candidate)
while (isBlankSpace(c)) {
c = file->sbumpc();
}
} while (c != EOF && !isNewline(c)); // End if we reached EOL
}
}
// Parses an arg vector, modifying `options` and `localOptions` as options are read.
// The `localOptions` struct is for flags which must be processed after the option parsing finishes.
// Returns `nullptr` if the vector was fully parsed, or a pointer (which is part of the arg vector)
// to an "at-file" path if one is encountered.
static char *parseArgv(int argc, char *argv[]) {
for (int ch; (ch = musl_getopt_long_only(argc, argv, optstring, longopts, nullptr)) != -1;) {
char const *arg = musl_optarg; // Make a copy for scanning
static void parseArg(int ch, char *arg) {
char const *argPtr = arg; // Make a copy for scanning
switch (ch) {
case 'A':
@@ -218,45 +151,39 @@ static char *parseArgv(int argc, char *argv[]) {
if (!options.attrmap.empty()) {
warnx("Overriding attrmap file \"%s\"", options.attrmap.c_str());
}
options.attrmap = musl_optarg;
options.attrmap = arg;
break;
case 'B':
parseBackgroundPalSpec(musl_optarg);
parseBackgroundPalSpec(arg);
break;
case 'b': {
uint16_t number = readNumber(arg, "Bank 0 base tile ID", 0);
uint16_t number = readNumber(argPtr, "Bank 0 base tile ID", 0);
if (number >= 256) {
error("Bank 0 base tile ID must be below 256");
} else {
options.baseTileIDs[0] = number;
}
if (*arg == '\0') {
if (*argPtr == '\0') {
options.baseTileIDs[1] = 0;
break;
}
skipBlankSpace(arg);
if (*arg != ',') {
error(
"Base tile IDs must be one or two comma-separated numbers, not \"%s\"",
musl_optarg
);
skipBlankSpace(argPtr);
if (*argPtr != ',') {
error("Base tile IDs must be one or two comma-separated numbers, not \"%s\"", arg);
break;
}
++arg; // Skip comma
skipBlankSpace(arg);
number = readNumber(arg, "Bank 1 base tile ID", 0);
++argPtr; // Skip comma
skipBlankSpace(argPtr);
number = readNumber(argPtr, "Bank 1 base tile ID", 0);
if (number >= 256) {
error("Bank 1 base tile ID must be below 256");
} else {
options.baseTileIDs[1] = number;
}
if (*arg != '\0') {
error(
"Base tile IDs must be one or two comma-separated numbers, not \"%s\"",
musl_optarg
);
if (*argPtr != '\0') {
error("Base tile IDs must be one or two comma-separated numbers, not \"%s\"", arg);
break;
}
break;
@@ -267,31 +194,31 @@ static char *parseArgv(int argc, char *argv[]) {
break;
case 'c':
localOptions.externalPalSpec = nullptr; // Allow overriding a previous pal spec
if (musl_optarg[0] == '#') {
localOptions.externalPalSpec = std::nullopt; // Allow overriding a previous pal spec
if (arg[0] == '#') {
options.palSpecType = Options::EXPLICIT;
parseInlinePalSpec(musl_optarg);
} else if (strcasecmp(musl_optarg, "embedded") == 0) {
parseInlinePalSpec(arg);
} else if (strcasecmp(arg, "embedded") == 0) {
// Use PLTE, error out if missing
options.palSpecType = Options::EMBEDDED;
} else if (strcasecmp(musl_optarg, "auto") == 0) {
} else if (strcasecmp(arg, "auto") == 0) {
options.palSpecType = Options::NO_SPEC;
} else if (strcasecmp(musl_optarg, "dmg") == 0) {
} else if (strcasecmp(arg, "dmg") == 0) {
options.palSpecType = Options::DMG;
parseDmgPalSpec(0xE4); // Same darkest-first order as `sortGrayscale`
} else if (strncasecmp(musl_optarg, "dmg=", literal_strlen("dmg=")) == 0) {
} else if (strncasecmp(arg, "dmg=", literal_strlen("dmg=")) == 0) {
options.palSpecType = Options::DMG;
parseDmgPalSpec(&musl_optarg[literal_strlen("dmg=")]);
parseDmgPalSpec(&arg[literal_strlen("dmg=")]);
} else {
options.palSpecType = Options::EXPLICIT;
localOptions.externalPalSpec = musl_optarg;
localOptions.externalPalSpec = arg;
}
break;
case 'd':
options.bitDepth = readNumber(arg, "Bit depth", 2);
if (*arg != '\0') {
error("Bit depth ('-b') argument must be a valid number, not \"%s\"", musl_optarg);
options.bitDepth = readNumber(argPtr, "Bit depth", 2);
if (*argPtr != '\0') {
error("Bit depth ('-b') argument must be a valid number, not \"%s\"", arg);
} else if (options.bitDepth != 1 && options.bitDepth != 2) {
error("Bit depth must be 1 or 2, not %" PRIu8, options.bitDepth);
options.bitDepth = 2;
@@ -307,54 +234,54 @@ static char *parseArgv(int argc, char *argv[]) {
if (!options.inputTileset.empty()) {
warnx("Overriding input tileset file \"%s\"", options.inputTileset.c_str());
}
options.inputTileset = musl_optarg;
options.inputTileset = arg;
break;
case 'L':
options.inputSlice.left = readNumber(arg, "Input slice left coordinate");
options.inputSlice.left = readNumber(argPtr, "Input slice left coordinate");
if (options.inputSlice.left > INT16_MAX) {
error("Input slice left coordinate is out of range!");
break;
}
skipBlankSpace(arg);
if (*arg != ',') {
error("Missing comma after left coordinate in \"%s\"", musl_optarg);
skipBlankSpace(argPtr);
if (*argPtr != ',') {
error("Missing comma after left coordinate in \"%s\"", arg);
break;
}
++arg;
skipBlankSpace(arg);
options.inputSlice.top = readNumber(arg, "Input slice upper coordinate");
skipBlankSpace(arg);
if (*arg != ':') {
error("Missing colon after upper coordinate in \"%s\"", musl_optarg);
++argPtr;
skipBlankSpace(argPtr);
options.inputSlice.top = readNumber(argPtr, "Input slice upper coordinate");
skipBlankSpace(argPtr);
if (*argPtr != ':') {
error("Missing colon after upper coordinate in \"%s\"", arg);
break;
}
++arg;
skipBlankSpace(arg);
options.inputSlice.width = readNumber(arg, "Input slice width");
skipBlankSpace(arg);
++argPtr;
skipBlankSpace(argPtr);
options.inputSlice.width = readNumber(argPtr, "Input slice width");
skipBlankSpace(argPtr);
if (options.inputSlice.width == 0) {
error("Input slice width may not be 0!");
}
if (*arg != ',') {
error("Missing comma after width in \"%s\"", musl_optarg);
if (*argPtr != ',') {
error("Missing comma after width in \"%s\"", arg);
break;
}
++arg;
skipBlankSpace(arg);
options.inputSlice.height = readNumber(arg, "Input slice height");
++argPtr;
skipBlankSpace(argPtr);
options.inputSlice.height = readNumber(argPtr, "Input slice height");
if (options.inputSlice.height == 0) {
error("Input slice height may not be 0!");
}
if (*arg != '\0') {
error("Unexpected extra characters after slice spec in \"%s\"", musl_optarg);
if (*argPtr != '\0') {
error("Unexpected extra characters after slice spec in \"%s\"", arg);
}
break;
case 'l': {
uint16_t number = readNumber(arg, "Base palette ID", 0);
if (*arg != '\0') {
error("Base palette ID must be a valid number, not \"%s\"", musl_optarg);
uint16_t number = readNumber(argPtr, "Base palette ID", 0);
if (*argPtr != '\0') {
error("Base palette ID must be a valid number, not \"%s\"", arg);
} else if (number >= 256) {
error("Base palette ID must be below 256");
} else {
@@ -373,41 +300,35 @@ static char *parseArgv(int argc, char *argv[]) {
break;
case 'N':
options.maxNbTiles[0] = readNumber(arg, "Number of tiles in bank 0", 256);
options.maxNbTiles[0] = readNumber(argPtr, "Number of tiles in bank 0", 256);
if (options.maxNbTiles[0] > 256) {
error("Bank 0 cannot contain more than 256 tiles");
}
if (*arg == '\0') {
if (*argPtr == '\0') {
options.maxNbTiles[1] = 0;
break;
}
skipBlankSpace(arg);
if (*arg != ',') {
error(
"Bank capacity must be one or two comma-separated numbers, not \"%s\"",
musl_optarg
);
skipBlankSpace(argPtr);
if (*argPtr != ',') {
error("Bank capacity must be one or two comma-separated numbers, not \"%s\"", arg);
break;
}
++arg; // Skip comma
skipBlankSpace(arg);
options.maxNbTiles[1] = readNumber(arg, "Number of tiles in bank 1", 256);
++argPtr; // Skip comma
skipBlankSpace(argPtr);
options.maxNbTiles[1] = readNumber(argPtr, "Number of tiles in bank 1", 256);
if (options.maxNbTiles[1] > 256) {
error("Bank 1 cannot contain more than 256 tiles");
}
if (*arg != '\0') {
error(
"Bank capacity must be one or two comma-separated numbers, not \"%s\"",
musl_optarg
);
if (*argPtr != '\0') {
error("Bank capacity must be one or two comma-separated numbers, not \"%s\"", arg);
break;
}
break;
case 'n': {
uint16_t number = readNumber(arg, "Number of palettes", 256);
if (*arg != '\0') {
error("Number of palettes ('-n') must be a valid number, not \"%s\"", musl_optarg);
uint16_t number = readNumber(argPtr, "Number of palettes", 256);
if (*argPtr != '\0') {
error("Number of palettes ('-n') must be a valid number, not \"%s\"", arg);
}
if (number > 256) {
error("Number of palettes ('-n') must not exceed 256!");
@@ -427,7 +348,7 @@ static char *parseArgv(int argc, char *argv[]) {
if (!options.output.empty()) {
warnx("Overriding tile data file %s", options.output.c_str());
}
options.output = musl_optarg;
options.output = arg;
break;
case 'P':
@@ -439,7 +360,7 @@ static char *parseArgv(int argc, char *argv[]) {
if (!options.palettes.empty()) {
warnx("Overriding palettes file %s", options.palettes.c_str());
}
options.palettes = musl_optarg;
options.palettes = arg;
break;
case 'Q':
@@ -451,23 +372,21 @@ static char *parseArgv(int argc, char *argv[]) {
if (!options.palmap.empty()) {
warnx("Overriding palette map file %s", options.palmap.c_str());
}
options.palmap = musl_optarg;
options.palmap = arg;
break;
case 'r':
localOptions.reverse = true;
options.reversedWidth = readNumber(arg, "Reversed image stride");
if (*arg != '\0') {
error(
"Reversed image stride ('-r') must be a valid number, not \"%s\"", musl_optarg
);
options.reversedWidth = readNumber(argPtr, "Reversed image stride");
if (*argPtr != '\0') {
error("Reversed image stride ('-r') must be a valid number, not \"%s\"", arg);
}
break;
case 's':
options.nbColorsPerPal = readNumber(arg, "Number of colors per palette", 4);
if (*arg != '\0') {
error("Palette size ('-s') must be a valid number, not \"%s\"", musl_optarg);
options.nbColorsPerPal = readNumber(argPtr, "Number of colors per palette", 4);
if (*argPtr != '\0') {
error("Palette size ('-s') must be a valid number, not \"%s\"", arg);
}
if (options.nbColorsPerPal > 4) {
error("Palette size ('-s') must not exceed 4!");
@@ -485,7 +404,7 @@ static char *parseArgv(int argc, char *argv[]) {
if (!options.tilemap.empty()) {
warnx("Overriding tilemap file %s", options.tilemap.c_str());
}
options.tilemap = musl_optarg;
options.tilemap = arg;
break;
// LCOV_EXCL_START
@@ -499,7 +418,7 @@ static char *parseArgv(int argc, char *argv[]) {
// LCOV_EXCL_STOP
case 'W':
warnings.processWarningFlag(musl_optarg);
warnings.processWarningFlag(arg);
break;
case 'w':
@@ -507,9 +426,9 @@ static char *parseArgv(int argc, char *argv[]) {
break;
case 'x':
options.trim = readNumber(arg, "Number of tiles to trim", 0);
if (*arg != '\0') {
error("Tile trim ('-x') argument must be a valid number, not \"%s\"", musl_optarg);
options.trim = readNumber(argPtr, "Number of tiles to trim", 0);
if (*argPtr != '\0') {
error("Tile trim ('-x') argument must be a valid number, not \"%s\"", arg);
}
break;
@@ -528,17 +447,22 @@ static char *parseArgv(int argc, char *argv[]) {
break;
case 0: // Long-only options
if (longOpt == 'c' && !style_Parse(musl_optarg)) {
if (longOpt == 'c' && !style_Parse(arg)) {
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
return &musl_optarg[1];
case 1: // Positional argument
if (!options.input.empty()) {
usage.printAndExit(
"Input image specified more than once! (first \"%s\", then \"%s\")",
options.input.c_str(),
arg
);
} else if (arg[0] == '\0') { // Empty input path
usage.printAndExit("Input image path cannot be empty");
} else {
registerInput(musl_optarg);
options.input = arg;
}
break;
@@ -547,9 +471,6 @@ static char *parseArgv(int argc, char *argv[]) {
usage.printAndExit(1);
// LCOV_EXCL_STOP
}
}
return nullptr; // Done processing this argv
}
// LCOV_EXCL_START
@@ -718,70 +639,7 @@ static void replaceExtension(std::string &path, char const *extension) {
}
int main(int argc, char *argv[]) {
struct AtFileStackEntry {
int parentInd; // Saved offset into parent argv
std::vector<char *> argv; // This context's arg pointer vec
AtFileStackEntry(int parentInd_, std::vector<char *> argv_)
: parentInd(parentInd_), argv(argv_) {}
};
std::vector<AtFileStackEntry> atFileStack;
// Parse CLI options
int curArgc = argc;
char **curArgv = argv;
std::vector<std::vector<char>> argPools;
for (;;) {
char *atFileName = parseArgv(curArgc, curArgv);
if (atFileName) {
// We need to allocate a new arg pool for each at-file, so as not to invalidate pointers
// previous at-files may have generated to their own arg pools.
// But for the same reason, the arg pool must also outlive the at-file's stack entry!
std::vector<char> &argPool = argPools.emplace_back();
// Copy `argv[0]` for error reporting, and because option parsing skips it
AtFileStackEntry &stackEntry =
atFileStack.emplace_back(musl_optind, std::vector{atFileName});
// It would be nice to compute the char pointers on the fly, but reallocs don't allow
// that; so we must compute the offsets after the pool is fixed
std::vector<size_t> offsets = readAtFile(&musl_optarg[1], argPool);
stackEntry.argv.reserve(offsets.size() + 2); // Avoid a bunch of reallocs
for (size_t ofs : offsets) {
stackEntry.argv.push_back(&argPool.data()[ofs]);
}
stackEntry.argv.push_back(nullptr); // Don't forget the arg vector terminator!
curArgc = stackEntry.argv.size() - 1;
curArgv = stackEntry.argv.data();
musl_optind = 1; // Don't use 0 because we're not scanning a different argv per se
continue; // Begin scanning that arg vector
}
if (musl_optind != curArgc) {
// This happens if `--` is passed, process the remaining arg(s) as positional
assume(musl_optind < curArgc);
for (int i = musl_optind; i < curArgc; ++i) {
registerInput(argv[i]);
}
}
// Pop off the top stack entry, or end parsing if none
if (atFileStack.empty()) {
break;
}
// OK to restore `optind` directly, because `optpos` must be 0 right now.
// (Providing 0 would be a "proper" reset, but we want to resume parsing)
musl_optind = atFileStack.back().parentInd;
atFileStack.pop_back();
if (atFileStack.empty()) {
curArgc = argc;
curArgv = argv;
} else {
std::vector<char *> &vec = atFileStack.back().argv;
curArgc = vec.size();
curArgv = vec.data();
}
}
cli_ParseArgs(argc, argv, optstring, longopts, parseArg, usage);
if (options.nbColorsPerPal == 0) {
options.nbColorsPerPal = 1u << options.bitDepth;
@@ -824,7 +682,7 @@ int main(int argc, char *argv[]) {
// Execute deferred external pal spec parsing, now that all other params are known
if (localOptions.externalPalSpec) {
parseExternalPalSpec(localOptions.externalPalSpec);
parseExternalPalSpec(localOptions.externalPalSpec->c_str());
}
verboseOutputConfig(); // LCOV_EXCL_LINE
@@ -848,47 +706,3 @@ int main(int argc, char *argv[]) {
requireZeroErrors();
return 0;
}
void Palette::addColor(uint16_t color) {
for (size_t i = 0; true; ++i) {
assume(i < colors.size()); // The packing should guarantee this
if (colors[i] == color) { // The color is already present
break;
} else if (colors[i] == UINT16_MAX) { // Empty slot
colors[i] = color;
break;
}
}
}
// Returns the ID of the color in the palette, or `size()` if the color is not in
uint8_t Palette::indexOf(uint16_t color) const {
return color == Rgba::transparent
? 0
: std::find(begin(), colors.end(), color) - begin() + options.hasTransparentPixels;
}
auto Palette::begin() -> decltype(colors)::iterator {
// Skip the first slot if reserved for transparency
return colors.begin() + options.hasTransparentPixels;
}
auto Palette::end() -> decltype(colors)::iterator {
// Return an iterator pointing past the last non-empty element.
// Since the palette may contain gaps, we must scan from the end.
return std::find_if(RRANGE(colors), [](uint16_t c) { return c != UINT16_MAX; }).base();
}
auto Palette::begin() const -> decltype(colors)::const_iterator {
// Same as the non-const begin().
return colors.begin() + options.hasTransparentPixels;
}
auto Palette::end() const -> decltype(colors)::const_iterator {
// Same as the non-const end().
return std::find_if(RRANGE(colors), [](uint16_t c) { return c != UINT16_MAX; }).base();
}
uint8_t Palette::size() const {
return end() - colors.begin();
}

View File

@@ -70,7 +70,7 @@ public:
private:
// Template class for both const and non-const iterators over the non-empty `_assigned` slots
template<typename I, template<typename> typename Constness>
template<typename IteratorT, template<typename> typename Constness>
class AssignedSetsIter {
public:
friend class AssignedSets;
@@ -84,7 +84,7 @@ private:
private:
Constness<decltype(_assigned)> *_array = nullptr;
I _iter{};
IteratorT _iter{};
AssignedSetsIter(decltype(_array) array, decltype(_iter) &&iter)
: _array(array), _iter(iter) {}
@@ -164,11 +164,11 @@ public:
size_t nbColorSets() const { return std::distance(RANGE(*this)); }
private:
template<typename I>
template<typename IteratorT>
static void addUniqueColors(
std::unordered_set<uint16_t> &colors,
I iter,
I const &end,
IteratorT iter,
IteratorT const &end,
std::vector<ColorSet> const &colorSets
) {
for (; iter != end; ++iter) {
@@ -177,27 +177,31 @@ private:
}
}
// This function should stay private because it returns a reference to a unique object
std::unordered_set<uint16_t> &uniqueColors() const {
// We check for *distinct* colors by stuffing them into a `set`; this should be
// faster than "back-checking" on every element (O(n^2))
static std::unordered_set<uint16_t> colors;
colors.clear();
public:
// Returns the set of distinct colors
std::unordered_set<uint16_t> uniqueColors() const {
std::unordered_set<uint16_t> colors;
addUniqueColors(colors, RANGE(*this), *_colorSets);
return colors;
}
public:
// Returns the number of distinct colors
size_t volume() const { return uniqueColors().size(); }
bool canFit(ColorSet const &colorSet) const {
std::unordered_set<uint16_t> &colors = uniqueColors();
std::unordered_set<uint16_t> colors = uniqueColors();
colors.insert(RANGE(colorSet));
return colors.size() <= options.maxOpaqueColors();
}
// Counts how many of our color sets this color also belongs to
uint32_t multiplicity(uint16_t color) const {
return std::count_if(RANGE(*this), [this, &color](ColorSetAttrs const &attrs) {
ColorSet const &pal = (*_colorSets)[attrs.colorSetIndex];
return std::find(RANGE(pal), color) != pal.end();
});
}
// The `relSizeOf` method below should compute the sum, for each color in `colorSet`, of
// the reciprocal of the "multiplicity" of the color across "our" color sets.
// However, literally computing the reciprocals would involve floating-point division, which
@@ -217,41 +221,36 @@ public:
// Computes the "relative size" of a color set on this palette;
// it's a measure of how much this color set would "cost" to introduce.
uint32_t relSizeOf(ColorSet const &colorSet) const {
// NOTE: this function must not call `uniqueColors`, or one of its callers will break!
uint32_t relSize = 0;
for (uint16_t color : colorSet) {
// How many of our color sets does this color also belong to?
uint32_t multiplicity =
std::count_if(RANGE(*this), [this, &color](ColorSetAttrs const &attrs) {
ColorSet const &pal = (*_colorSets)[attrs.colorSetIndex];
return std::find(RANGE(pal), color) != pal.end();
});
uint32_t n = multiplicity(color);
// We increase the denominator by 1 here; the reference code does this,
// but the paper does not. Not adding 1 makes a multiplicity of 0 cause a division by 0
// (that is, if the color is not found in any color set), and adding 1 still seems
// to preserve the paper's reasoning.
//
// The scale factor should ensure integer divisions only.
assume(scaleFactor % (multiplicity + 1) == 0);
relSize += scaleFactor / (multiplicity + 1);
assume(scaleFactor % (n + 1) == 0);
relSize += scaleFactor / (n + 1);
}
return relSize;
}
// Computes the "relative size" of a set of color sets on this palette
template<typename I>
size_t combinedVolume(I &&begin, I const &end, std::vector<ColorSet> const &colorSets) const {
std::unordered_set<uint16_t> &colors = uniqueColors();
addUniqueColors(colors, std::forward<I>(begin), end, colorSets);
template<typename IteratorT>
size_t combinedVolume(
IteratorT &&begin, IteratorT const &end, std::vector<ColorSet> const &colorSets
) const {
std::unordered_set<uint16_t> colors = uniqueColors();
addUniqueColors(colors, std::forward<IteratorT>(begin), end, colorSets);
return colors.size();
}
// Computes the "relative size" of a set of colors on this palette
template<typename I>
size_t combinedVolume(I &&begin, I &&end) const {
std::unordered_set<uint16_t> &colors = uniqueColors();
colors.insert(std::forward<I>(begin), std::forward<I>(end));
template<typename IteratorT>
size_t combinedVolume(IteratorT &&begin, IteratorT &&end) const {
std::unordered_set<uint16_t> colors = uniqueColors();
colors.insert(std::forward<IteratorT>(begin), std::forward<IteratorT>(end));
return colors.size();
}
};

View File

@@ -11,7 +11,7 @@
#include "helpers.hpp"
#include "verbosity.hpp"
#include "gfx/main.hpp"
#include "gfx/palette.hpp"
#include "gfx/rgba.hpp"
void sortIndexed(std::vector<Palette> &palettes, std::vector<Rgba> const &embPal) {

View File

@@ -188,14 +188,6 @@ static bool readLine(std::filebuf &file, std::string &buffer) {
}
}
#define requireLine(kind, filename, file, buffer) \
do { \
if (!readLine(file, buffer)) { \
error(kind " palette file \"%s\" is shorter than expected", filename); \
return; \
} \
} while (0)
static void warnExtraColors(
char const *kind, char const *filename, uint16_t nbColors, uint16_t maxNbColors
) {
@@ -210,15 +202,15 @@ static void warnExtraColors(
}
// Parses the initial part of a string_view, advancing the "read index" as it does
template<typename U> // Should be uint*_t
static std::optional<U> parseDec(std::string const &str, size_t &n) {
template<typename UintT> // Should be uint*_t
static std::optional<UintT> parseDec(std::string const &str, size_t &n) {
uintmax_t value = 0;
auto result = std::from_chars(str.data() + n, str.data() + str.length(), value);
if (static_cast<bool>(result.ec)) {
return std::nullopt;
}
n = result.ptr - str.data();
return std::optional<U>{value};
return std::optional<UintT>{value};
}
static std::optional<Rgba> parseColor(std::string const &str, size_t &n, uint16_t i) {
@@ -254,21 +246,28 @@ static std::optional<Rgba> parseColor(std::string const &str, size_t &n, uint16_
static void parsePSPFile(char const *filename, std::filebuf &file) {
// https://www.selapa.net/swatches/colors/fileformats.php#psp_pal
#define requireLine() \
do { \
line.clear(); \
if (!readLine(file, line)) { \
error("PSP palette file \"%s\" is shorter than expected", filename); \
return; \
} \
} while (0)
std::string line;
if (!readLine(file, line) || line != "JASC-PAL") {
error("File \"%s\" is not a valid PSP palette file", filename);
return;
}
line.clear();
requireLine("PSP", filename, file, line);
requireLine();
if (line != "0100") {
error("Unsupported PSP palette file version \"%s\"", line.c_str());
return;
}
line.clear();
requireLine("PSP", filename, file, line);
requireLine();
size_t n = 0;
std::optional<uint16_t> nbColors = parseDec<uint16_t>(line, n);
if (!nbColors || n != line.length()) {
@@ -284,8 +283,7 @@ static void parsePSPFile(char const *filename, std::filebuf &file) {
options.palSpec.clear();
for (uint16_t i = 0; i < *nbColors; ++i) {
line.clear();
requireLine("PSP", filename, file, line);
requireLine();
n = 0;
std::optional<Rgba> color = parseColor(line, n, i + 1);
@@ -306,6 +304,8 @@ static void parsePSPFile(char const *filename, std::filebuf &file) {
}
options.palSpec.back()[i % options.nbColorsPerPal] = *color;
}
#undef requireLine
}
static void parseGPLFile(char const *filename, std::filebuf &file) {

55
src/gfx/palette.cpp Normal file
View File

@@ -0,0 +1,55 @@
// SPDX-License-Identifier: MIT
#include "gfx/palette.hpp"
#include <algorithm>
#include <stdint.h>
#include "helpers.hpp"
#include "gfx/main.hpp"
#include "gfx/rgba.hpp"
void Palette::addColor(uint16_t color) {
for (size_t i = 0; true; ++i) {
assume(i < colors.size()); // The packing should guarantee this
if (colors[i] == color) { // The color is already present
break;
} else if (colors[i] == UINT16_MAX) { // Empty slot
colors[i] = color;
break;
}
}
}
// Returns the ID of the color in the palette, or `size()` if the color is not in
uint8_t Palette::indexOf(uint16_t color) const {
return color == Rgba::transparent
? 0
: std::find(begin(), colors.end(), color) - begin() + options.hasTransparentPixels;
}
auto Palette::begin() -> decltype(colors)::iterator {
// Skip the first slot if reserved for transparency
return colors.begin() + options.hasTransparentPixels;
}
auto Palette::end() -> decltype(colors)::iterator {
// Return an iterator pointing past the last non-empty element.
// Since the palette may contain gaps, we must scan from the end.
return std::find_if(RRANGE(colors), [](uint16_t c) { return c != UINT16_MAX; }).base();
}
auto Palette::begin() const -> decltype(colors)::const_iterator {
// Same as the non-const begin().
return colors.begin() + options.hasTransparentPixels;
}
auto Palette::end() const -> decltype(colors)::const_iterator {
// Same as the non-const end().
return std::find_if(RRANGE(colors), [](uint16_t c) { return c != UINT16_MAX; }).base();
}
uint8_t Palette::size() const {
return end() - colors.begin();
}

View File

@@ -31,15 +31,19 @@ struct Input {
[[noreturn]]
static void handleError(png_structp png, char const *msg) {
Input const &input = *reinterpret_cast<Input *>(png_get_error_ptr(png));
fatal("Error reading PNG image (\"%s\"): %s", input.filename, msg);
fatal(
"libpng error while reading PNG image (\"%s\"): %s",
reinterpret_cast<Input *>(png_get_error_ptr(png))->filename,
msg
);
}
static void handleWarning(png_structp png, char const *msg) {
Input const &input = *reinterpret_cast<Input *>(png_get_error_ptr(png));
warnx("In PNG image (\"%s\"): %s", input.filename, msg);
warnx(
"libpng found while reading PNG image (\"%s\"): %s",
reinterpret_cast<Input *>(png_get_error_ptr(png))->filename,
msg
);
}
static void readData(png_structp png, png_bytep data, size_t length) {

View File

@@ -26,9 +26,11 @@
#include "verbosity.hpp"
#include "gfx/color_set.hpp"
#include "gfx/flip.hpp"
#include "gfx/main.hpp"
#include "gfx/pal_packing.hpp"
#include "gfx/pal_sorting.hpp"
#include "gfx/palette.hpp"
#include "gfx/png.hpp"
#include "gfx/rgba.hpp"
#include "gfx/warning.hpp"
@@ -695,12 +697,6 @@ static void outputUnoptimizedMaps(
uint8_t tileID = 0;
uint8_t bank = 0;
for (AttrmapEntry const &attr : attrmap) {
if (tileID == options.maxNbTiles[bank]) {
assume(bank == 0);
bank = 1;
tileID = 0;
}
if (tilemapOutput.has_value()) {
(*tilemapOutput)
->sputc((attr.isBackgroundTile() ? 0 : tileID) + options.baseTileIDs[bank]);
@@ -714,8 +710,17 @@ static void outputUnoptimizedMaps(
}
// Background tiles are skipped in the tile data, so they should be skipped in the maps too.
if (!attr.isBackgroundTile()) {
if (attr.isBackgroundTile()) {
continue;
}
// Compare with `maxNbTiles` *before* incrementing, due to unsigned overflow!
if (tileID + 1 < options.maxNbTiles[bank]) {
++tileID;
} else {
assume(bank == 0);
bank = 1;
tileID = 0;
}
}
}

View File

@@ -23,6 +23,7 @@
#include "helpers.hpp" // assume
#include "verbosity.hpp"
#include "gfx/flip.hpp"
#include "gfx/main.hpp"
#include "gfx/rgba.hpp"
#include "gfx/warning.hpp"
@@ -61,16 +62,16 @@ static std::vector<uint8_t> readInto(std::string const &path) {
[[noreturn]]
static void pngError(png_structp png, char const *msg) {
fatal(
"Error writing reversed image (\"%s\"): %s",
static_cast<char const *>(png_get_error_ptr(png)),
"libpng error while writing reversed image (\"%s\"): %s",
reinterpret_cast<char const *>(png_get_error_ptr(png)),
msg
);
}
static void pngWarning(png_structp png, char const *msg) {
warnx(
"While writing reversed image (\"%s\"): %s",
static_cast<char const *>(png_get_error_ptr(png)),
"libpng found while writing reversed image (\"%s\"): %s",
reinterpret_cast<char const *>(png_get_error_ptr(png)),
msg
);
}

View File

@@ -50,10 +50,9 @@ uint16_t Rgba::cgbColor() const {
g = reverse_curve[g];
b = reverse_curve[b];
} else {
constexpr auto _8to5 = [](uint8_t c) -> uint8_t { return (c * 31 + 127) / 255; };
r = _8to5(r);
g = _8to5(g);
b = _8to5(b);
r >>= 3;
g >>= 3;
b >>= 3;
}
return r | g << 5 | b << 10;
}

View File

@@ -53,9 +53,9 @@ static void initFreeSpace() {
static void assignSection(Section &section, MemoryLocation const &location) {
// Propagate the assigned location to all UNIONs/FRAGMENTs
// so `jr` patches in them will have the correct offset
for (Section *piece = &section; piece != nullptr; piece = piece->nextPiece.get()) {
piece->org = location.address;
piece->bank = location.bank;
for (Section &piece : section.pieces()) {
piece.org = location.address;
piece.bank = location.bank;
}
out_AddSection(section);
}
@@ -118,6 +118,7 @@ static MemoryLocation getStartLocation(Section const &section) {
static std::optional<size_t> getPlacement(Section const &section, MemoryLocation &location) {
SectionTypeInfo const &typeInfo = sectionTypeInfo[section.type];
for (;;) {
// Switch to the beginning of the next bank
std::deque<FreeSpace> &bankMem = memory[section.type][location.bank - typeInfo.firstBank];
size_t spaceIdx = 0;
@@ -136,12 +137,11 @@ static std::optional<size_t> getPlacement(Section const &section, MemoryLocation
// Go to the next *possible* location
if (section.isAddressFixed) {
// If the address is fixed, there can be only one candidate block per bank;
// if we already reached it, give up.
if (location.address < section.org) {
location.address = section.org;
} else {
break; // Try again in next bank
// if we already reached it, give up and try again in the next bank.
if (location.address >= section.org) {
break;
}
location.address = section.org;
} else if (section.isAlignFixed) {
// Move to next aligned location
// Move back to alignment boundary
@@ -203,7 +203,8 @@ static std::optional<size_t> getPlacement(Section const &section, MemoryLocation
return std::nullopt;
}
return getPlacement(section, location); // Tail recursion
// Try again in the next iteration.
}
}
static std::string getSectionDescription(Section const &section) {

View File

@@ -35,12 +35,12 @@ static std::vector<TraceNode> backtrace(FileStackNode const &node, uint32_t curL
std::vector<TraceNode> traceNodes = backtrace(*node.parent, node.lineNo);
if (std::holds_alternative<std::vector<uint32_t>>(node.data)) {
assume(!traceNodes.empty()); // REPT nodes use their parent's name
std::string reptChain = traceNodes.back().first;
for (uint32_t iter : node.iters()) {
reptChain.append("::REPT~");
reptChain.append(std::to_string(iter));
std::string reptName = traceNodes.back().first;
if (std::vector<uint32_t> const &nodeIters = node.iters(); !nodeIters.empty()) {
reptName.append(NODE_SEPARATOR REPT_NODE_PREFIX);
reptName.append(std::to_string(nodeIters.back()));
}
traceNodes.emplace_back(reptChain, curLineNo);
traceNodes.emplace_back(reptName, curLineNo);
} else {
traceNodes.emplace_back(node.name(), curLineNo);
}

View File

@@ -251,8 +251,8 @@ void layout_PlaceSection(std::string const &name, bool isOptional) {
);
} else {
// SDCC areas don't have a type assigned yet, so the linker script gives them one.
for (Section *piece = section; piece != nullptr; piece = piece->nextPiece.get()) {
piece->type = activeType;
for (Section &piece : section->pieces()) {
piece.type = activeType;
}
}
} else if (section->type != activeType) {

View File

@@ -307,10 +307,10 @@ yy::parser::symbol_type yylex() {
// Not marking as unreachable; this will generate a warning if any codepath forgets to return.
}
bool lexer_Init(char const *linkerScriptName) {
bool lexer_Init(std::string const &linkerScriptName) {
if (LexerStackEntry &newContext = lexerStack.emplace_back(std::string(linkerScriptName));
!newContext.file.open(newContext.path, std::ios_base::in)) {
error("Failed to open linker script \"%s\"", linkerScriptName);
error("Failed to open linker script \"%s\"", linkerScriptName.c_str());
lexerStack.clear();
return false;
}

View File

@@ -13,8 +13,8 @@
#include <utility>
#include "backtrace.hpp"
#include "cli.hpp"
#include "diagnostics.hpp"
#include "extern/getopt.hpp"
#include "linkdefs.hpp"
#include "script.hpp" // Generated from script.y
#include "style.hpp"
@@ -33,12 +33,16 @@
Options options;
static char const *linkerScriptName = nullptr; // -l
// Flags which must be processed after the option parsing finishes
static struct LocalOptions {
std::optional<std::string> linkerScriptName; // -l
std::vector<std::string> inputFileNames; // <file>...
} localOptions;
// Short options
static char const *optstring = "B:dhl:m:Mn:O:o:p:S:tVvW:wx";
// Variables for the long-only options
// Long-only option variable
static int longOpt; // `--color`
// Equivalent long options
@@ -90,97 +94,6 @@ static Usage usage = {
};
// clang-format on
// LCOV_EXCL_START
static void verboseOutputConfig(int argc, char *argv[]) {
if (!checkVerbosity(VERB_CONFIG)) {
return;
}
style_Set(stderr, STYLE_MAGENTA, false);
fprintf(stderr, "rgblink %s\n", get_package_version_string());
printVVVVVVerbosity();
fputs("Options:\n", stderr);
// -d/--dmg
if (options.isDmgMode) {
fputs("\tDMG mode prohibits non-DMG section types\n", stderr);
}
// -t/--tiny
if (options.is32kMode) {
fputs("\tROM0 covers the full 32 KiB of ROM\n", stderr);
}
// -w/--wramx
if (options.isWRAM0Mode) {
fputs("\tWRAM0 covers the full 8 KiB of WRAM\n", stderr);
}
// -x/--nopad
if (options.disablePadding) {
fputs("\tNo padding at the end of the ROM file\n", stderr);
}
// -p/--pad
fprintf(stderr, "\tPad value: 0x%02" PRIx8 "\n", options.padValue);
// -S/--scramble
if (options.scrambleROMX || options.scrambleWRAMX || options.scrambleSRAM) {
fputs("\tScramble: ", stderr);
if (options.scrambleROMX) {
fprintf(stderr, "ROMX = %" PRIu16, options.scrambleROMX);
if (options.scrambleWRAMX || options.scrambleSRAM) {
fputs(", ", stderr);
}
}
if (options.scrambleWRAMX) {
fprintf(stderr, "WRAMX = %" PRIu16, options.scrambleWRAMX);
if (options.scrambleSRAM) {
fputs(", ", stderr);
}
}
if (options.scrambleSRAM) {
fprintf(stderr, "SRAM = %" PRIu16, options.scrambleSRAM);
}
putc('\n', stderr);
}
// file ...
if (musl_optind < argc) {
fprintf(stderr, "\tInput object files: ");
for (int i = musl_optind; i < argc; ++i) {
if (i > musl_optind) {
fputs(", ", stderr);
}
if (i - musl_optind == 10) {
fprintf(stderr, "and %d more", argc - i);
break;
}
fputs(argv[i], stderr);
}
putc('\n', stderr);
}
auto printPath = [](char const *name, char const *path) {
if (path) {
fprintf(stderr, "\t%s: %s\n", name, path);
}
};
// -O/--overlay
printPath("Overlay file", options.overlayFileName);
// -l/--linkerscript
printPath("Linker script", linkerScriptName);
// -o/--output
printPath("Output ROM file", options.outputFileName);
// -m/--map
printPath("Output map file", options.mapFileName);
// -M/--no-sym-in-map
if (options.mapFileName && options.noSymInMap) {
fputs("\tNo symbols in map file\n", stderr);
}
// -n/--sym
printPath("Output sym file", options.symFileName);
fputs("Ready.\n", stderr);
style_Reset(stderr);
}
// LCOV_EXCL_STOP
static size_t skipBlankSpace(char const *str) {
return strspn(str, " \t");
}
@@ -292,12 +205,10 @@ static void parseScrambleSpec(char *spec) {
}
}
int main(int argc, char *argv[]) {
// Parse CLI options
for (int ch; (ch = musl_getopt_long_only(argc, argv, optstring, longopts, nullptr)) != -1;) {
static void parseArg(int ch, char *arg) {
switch (ch) {
case 'B':
if (!trace_ParseTraceDepth(musl_optarg)) {
if (!trace_ParseTraceDepth(arg)) {
fatal("Invalid argument for option '-B'");
}
break;
@@ -313,10 +224,10 @@ int main(int argc, char *argv[]) {
// LCOV_EXCL_STOP
case 'l':
if (linkerScriptName) {
warnx("Overriding linker script file \"%s\"", linkerScriptName);
if (localOptions.linkerScriptName) {
warnx("Overriding linker script file \"%s\"", localOptions.linkerScriptName->c_str());
}
linkerScriptName = musl_optarg;
localOptions.linkerScriptName = arg;
break;
case 'M':
@@ -325,34 +236,34 @@ int main(int argc, char *argv[]) {
case 'm':
if (options.mapFileName) {
warnx("Overriding map file \"%s\"", options.mapFileName);
warnx("Overriding map file \"%s\"", options.mapFileName->c_str());
}
options.mapFileName = musl_optarg;
options.mapFileName = arg;
break;
case 'n':
if (options.symFileName) {
warnx("Overriding sym file \"%s\"", options.symFileName);
warnx("Overriding sym file \"%s\"", options.symFileName->c_str());
}
options.symFileName = musl_optarg;
options.symFileName = arg;
break;
case 'O':
if (options.overlayFileName) {
warnx("Overriding overlay file \"%s\"", options.overlayFileName);
warnx("Overriding overlay file \"%s\"", options.overlayFileName->c_str());
}
options.overlayFileName = musl_optarg;
options.overlayFileName = arg;
break;
case 'o':
if (options.outputFileName) {
warnx("Overriding output file \"%s\"", options.outputFileName);
warnx("Overriding output file \"%s\"", options.outputFileName->c_str());
}
options.outputFileName = musl_optarg;
options.outputFileName = arg;
break;
case 'p':
if (std::optional<uint64_t> value = parseWholeNumber(musl_optarg); !value) {
if (std::optional<uint64_t> value = parseWholeNumber(arg); !value) {
fatal("Invalid argument for option '-p'");
} else if (*value > 0xFF) {
fatal("Argument for option '-p' must be between 0 and 0xFF");
@@ -363,7 +274,7 @@ int main(int argc, char *argv[]) {
break;
case 'S':
parseScrambleSpec(musl_optarg);
parseScrambleSpec(arg);
break;
case 't':
@@ -381,7 +292,7 @@ int main(int argc, char *argv[]) {
// LCOV_EXCL_STOP
case 'W':
warnings.processWarningFlag(musl_optarg);
warnings.processWarningFlag(arg);
break;
case 'w':
@@ -395,21 +306,120 @@ int main(int argc, char *argv[]) {
break;
case 0: // Long-only options
if (longOpt == 'c' && !style_Parse(musl_optarg)) {
if (longOpt == 'c' && !style_Parse(arg)) {
fatal("Invalid argument for option '--color'");
}
break;
case 1: // Positional argument
localOptions.inputFileNames.push_back(arg);
break;
// LCOV_EXCL_START
default:
usage.printAndExit(1);
// LCOV_EXCL_STOP
}
}
// LCOV_EXCL_START
static void verboseOutputConfig() {
if (!checkVerbosity(VERB_CONFIG)) {
return;
}
verboseOutputConfig(argc, argv);
style_Set(stderr, STYLE_MAGENTA, false);
if (musl_optind == argc) {
fprintf(stderr, "rgblink %s\n", get_package_version_string());
printVVVVVVerbosity();
fputs("Options:\n", stderr);
// -d/--dmg
if (options.isDmgMode) {
fputs("\tDMG mode prohibits non-DMG section types\n", stderr);
}
// -t/--tiny
if (options.is32kMode) {
fputs("\tROM0 covers the full 32 KiB of ROM\n", stderr);
}
// -w/--wramx
if (options.isWRAM0Mode) {
fputs("\tWRAM0 covers the full 8 KiB of WRAM\n", stderr);
}
// -x/--nopad
if (options.disablePadding) {
fputs("\tNo padding at the end of the ROM file\n", stderr);
}
// -p/--pad
fprintf(stderr, "\tPad value: 0x%02" PRIx8 "\n", options.padValue);
// -S/--scramble
if (options.scrambleROMX || options.scrambleWRAMX || options.scrambleSRAM) {
fputs("\tScramble: ", stderr);
if (options.scrambleROMX) {
fprintf(stderr, "ROMX = %" PRIu16, options.scrambleROMX);
if (options.scrambleWRAMX || options.scrambleSRAM) {
fputs(", ", stderr);
}
}
if (options.scrambleWRAMX) {
fprintf(stderr, "WRAMX = %" PRIu16, options.scrambleWRAMX);
if (options.scrambleSRAM) {
fputs(", ", stderr);
}
}
if (options.scrambleSRAM) {
fprintf(stderr, "SRAM = %" PRIu16, options.scrambleSRAM);
}
putc('\n', stderr);
}
// file ...
if (!localOptions.inputFileNames.empty()) {
fprintf(stderr, "\tInput object files: ");
size_t nbFiles = localOptions.inputFileNames.size();
for (size_t i = 0; i < nbFiles; ++i) {
if (i > 0) {
fputs(", ", stderr);
}
if (i == 10) {
fprintf(stderr, "and %zu more", nbFiles - i);
break;
}
fputs(localOptions.inputFileNames[i].c_str(), stderr);
}
putc('\n', stderr);
}
auto printPath = [](char const *name, std::optional<std::string> const &path) {
if (path) {
fprintf(stderr, "\t%s: %s\n", name, path->c_str());
}
};
// -O/--overlay
printPath("Overlay file", options.overlayFileName);
// -l/--linkerscript
printPath("Linker script", localOptions.linkerScriptName);
// -o/--output
printPath("Output ROM file", options.outputFileName);
// -m/--map
printPath("Output map file", options.mapFileName);
// -M/--no-sym-in-map
if (options.mapFileName && options.noSymInMap) {
fputs("\tNo symbols in map file\n", stderr);
}
// -n/--sym
printPath("Output sym file", options.symFileName);
fputs("Ready.\n", stderr);
style_Reset(stderr);
}
// LCOV_EXCL_STOP
int main(int argc, char *argv[]) {
cli_ParseArgs(argc, argv, optstring, longopts, parseArg, usage);
verboseOutputConfig();
if (localOptions.inputFileNames.empty()) {
usage.printAndExit("No input file specified (pass \"-\" to read from standard input)");
}
@@ -427,16 +437,17 @@ int main(int argc, char *argv[]) {
}
// Read all object files first,
obj_Setup(argc - musl_optind);
for (int i = musl_optind; i < argc; ++i) {
obj_ReadFile(argv[i], argc - i - 1);
size_t nbFiles = localOptions.inputFileNames.size();
obj_Setup(nbFiles);
for (size_t i = 0; i < nbFiles; ++i) {
obj_ReadFile(localOptions.inputFileNames[i], nbFiles - i - 1);
}
// apply the linker script's modifications,
if (linkerScriptName) {
if (localOptions.linkerScriptName) {
verbosePrint(VERB_NOTICE, "Reading linker script...\n");
if (lexer_Init(linkerScriptName)) {
if (lexer_Init(*localOptions.linkerScriptName)) {
if (yy::parser parser; parser.parse() != 0) {
// Exited due to YYABORT or YYNOMEM
fatal("Unrecoverable error while reading linker script"); // LCOV_EXCL_LINE

View File

@@ -405,9 +405,10 @@ static void readAssertion(
tryReadString(assert.message, file, "%s: Cannot read assertion's message: %s", fileName);
}
void obj_ReadFile(char const *fileName, unsigned int fileID) {
void obj_ReadFile(std::string const &filePath, size_t fileID) {
FILE *file;
if (strcmp(fileName, "-")) {
char const *fileName = filePath.c_str();
if (filePath != "-") {
file = fopen(fileName, "rb");
} else {
fileName = "<stdin>";
@@ -484,8 +485,8 @@ void obj_ReadFile(char const *fileName, unsigned int fileID) {
tryReadLong(nbNodes, file, "%s: Cannot read number of nodes: %s", fileName);
nodes[fileID].resize(nbNodes);
verbosePrint(VERB_INFO, "Reading %u nodes...\n", nbNodes);
for (uint32_t i = nbNodes; i--;) {
readFileStackNode(file, nodes[fileID], i, fileName);
for (uint32_t nodeID = nbNodes; nodeID--;) {
readFileStackNode(file, nodes[fileID], nodeID, fileName);
}
// This file's symbols, kept to link sections to them
@@ -553,6 +554,6 @@ void obj_ReadFile(char const *fileName, unsigned int fileID) {
}
}
void obj_Setup(unsigned int nbFiles) {
void obj_Setup(size_t nbFiles) {
nodes.resize(nbFiles);
}

View File

@@ -208,15 +208,16 @@ static void
static void writeROM() {
if (options.outputFileName) {
if (strcmp(options.outputFileName, "-")) {
outputFile = fopen(options.outputFileName, "wb");
char const *outputFileName = options.outputFileName->c_str();
if (*options.outputFileName != "-") {
outputFile = fopen(outputFileName, "wb");
} else {
options.outputFileName = "<stdout>";
outputFileName = "<stdout>";
(void)setmode(STDOUT_FILENO, O_BINARY);
outputFile = stdout;
}
if (!outputFile) {
fatal("Failed to open output file \"%s\": %s", options.outputFileName, strerror(errno));
fatal("Failed to open output file \"%s\": %s", outputFileName, strerror(errno));
}
}
Defer closeOutputFile{[&] {
@@ -226,17 +227,16 @@ static void writeROM() {
}};
if (options.overlayFileName) {
if (strcmp(options.overlayFileName, "-")) {
overlayFile = fopen(options.overlayFileName, "rb");
char const *overlayFileName = options.overlayFileName->c_str();
if (*options.overlayFileName != "-") {
overlayFile = fopen(overlayFileName, "rb");
} else {
options.overlayFileName = "<stdin>";
overlayFileName = "<stdin>";
(void)setmode(STDIN_FILENO, O_BINARY);
overlayFile = stdin;
}
if (!overlayFile) {
fatal(
"Failed to open overlay file \"%s\": %s", options.overlayFileName, strerror(errno)
);
fatal("Failed to open overlay file \"%s\": %s", overlayFileName, strerror(errno));
}
}
Defer closeOverlayFile{[&] {
@@ -314,16 +314,16 @@ static bool compareSymbols(SortedSymbol const &sym1, SortedSymbol const &sym2) {
< std::tie(sym2.addr, sym2_local, sym2.parentAddr, sym2_name);
}
template<typename F>
static void forEachSortedSection(SortedSections const &bankSections, F callback) {
template<typename CallbackFnT>
static void forEachSortedSection(SortedSections const &bankSections, CallbackFnT callback) {
for (Section const *sect : bankSections.zeroLenSections) {
for (; sect != nullptr; sect = sect->nextPiece.get()) {
callback(*sect);
for (Section const &piece : sect->pieces()) {
callback(piece);
}
}
for (Section const *sect : bankSections.sections) {
for (; sect != nullptr; sect = sect->nextPiece.get()) {
callback(*sect);
for (Section const &piece : sect->pieces()) {
callback(piece);
}
}
}
@@ -415,8 +415,8 @@ static void writeSectionName(std::string const &name, FILE *file) {
}
}
template<typename F>
uint16_t forEachSection(SortedSections const &sectList, F callback) {
template<typename CallbackFnT>
uint16_t forEachSection(SortedSections const &sectList, CallbackFnT callback) {
uint16_t used = 0;
auto section = sectList.sections.begin();
auto zeroLenSection = sectList.zeroLenSections.begin();
@@ -433,31 +433,32 @@ uint16_t forEachSection(SortedSections const &sectList, F callback) {
return used;
}
static void writeMapSymbols(Section const *sect) {
uint16_t org = sect->org;
for (bool announced = true; sect != nullptr; sect = sect->nextPiece.get(), announced = false) {
for (Symbol *sym : sect->symbols) {
static void writeMapSymbols(Section const &sect) {
bool announced = true;
for (Section const &piece : sect.pieces()) {
for (Symbol *sym : piece.symbols) {
// Don't output symbols that begin with an illegal character
if (sym->name.empty() || !startsIdentifier(sym->name[0])) {
continue;
}
// Announce this "piece" before its contents
if (!announced) {
if (sect->modifier == SECTION_UNION) {
assume(sect.modifier == piece.modifier);
if (sect.modifier == SECTION_UNION) {
fputs("\t ; Next union\n", mapFile);
} else if (sect->modifier == SECTION_FRAGMENT) {
} else if (sect.modifier == SECTION_FRAGMENT) {
fputs("\t ; Next fragment\n", mapFile);
}
announced = true;
}
assume(std::holds_alternative<Label>(sym->data));
uint32_t address = std::get<Label>(sym->data).offset + org;
uint32_t address = std::get<Label>(sym->data).offset + sect.org;
// Space matches "\tSECTION: $xxxx ..."
fprintf(mapFile, "\t $%04" PRIx32 " = ", address);
writeSymName(sym->name, mapFile);
putc('\n', mapFile);
}
announced = false;
}
}
@@ -487,7 +488,7 @@ static void writeMapBank(SortedSections const &sectList, SectionType type, uint3
if (!options.noSymInMap) {
// Also print symbols in the following "pieces"
writeMapSymbols(&sect);
writeMapSymbols(sect);
}
});
@@ -547,15 +548,16 @@ static void writeSym() {
return;
}
if (strcmp(options.symFileName, "-")) {
symFile = fopen(options.symFileName, "w");
char const *symFileName = options.symFileName->c_str();
if (*options.symFileName != "-") {
symFile = fopen(symFileName, "w");
} else {
options.symFileName = "<stdout>";
symFileName = "<stdout>";
(void)setmode(STDOUT_FILENO, O_TEXT); // May have been set to O_BINARY previously
symFile = stdout;
}
if (!symFile) {
fatal("Failed to open sym file \"%s\": %s", options.symFileName, strerror(errno));
fatal("Failed to open sym file \"%s\": %s", symFileName, strerror(errno));
}
Defer closeSymFile{[&] { fclose(symFile); }};
@@ -597,15 +599,16 @@ static void writeMap() {
return;
}
if (strcmp(options.mapFileName, "-")) {
mapFile = fopen(options.mapFileName, "w");
char const *mapFileName = options.mapFileName->c_str();
if (*options.mapFileName != "-") {
mapFile = fopen(mapFileName, "w");
} else {
options.mapFileName = "<stdout>";
mapFileName = "<stdout>";
(void)setmode(STDOUT_FILENO, O_TEXT); // May have been set to O_BINARY previously
mapFile = stdout;
}
if (!mapFile) {
fatal("Failed to open map file \"%s\": %s", options.mapFileName, strerror(errno));
fatal("Failed to open map file \"%s\": %s", mapFileName, strerror(errno));
}
Defer closeMapFile{[&] { fclose(mapFile); }};

View File

@@ -586,8 +586,8 @@ static void applyPatches(Section &section) {
return;
}
for (Section *piece = &section; piece != nullptr; piece = piece->nextPiece.get()) {
applyFilePatches(*piece, section);
for (Section &piece : section.pieces()) {
applyFilePatches(piece, section);
}
}

View File

@@ -138,23 +138,30 @@ void sdobj_ReadFile(FileStackNode const &src, FILE *file, std::vector<Symbol> &f
line.reserve(256);
char const *token;
#define getToken(ptr, ...) \
do { \
token = strtok((ptr), delim); \
if (!token) { \
fatalAt(where, __VA_ARGS__); \
} \
} while (0)
#define expectEol(...) \
#define expectEol(lineType) \
do { \
token = strtok(nullptr, delim); \
if (token) { \
fatalAt(where, __VA_ARGS__); \
fatalAt(where, "'%c' line is too long", (lineType)); \
} \
} while (0)
#define expectNext(ptr, lineType) \
do { \
token = strtok((ptr), delim); \
if (!token) { \
fatalAt(where, "'%c' line is too short", (lineType)); \
} \
} while (0)
#define expectRelocation() \
do { \
token = strtok(nullptr, delim); \
if (!token) { \
fatalAt(where, "Incomplete relocation"); \
} \
} while (0)
#define expectToken(expected, lineType) \
do { \
getToken(nullptr, "'%c' line is too short", (lineType)); \
expectNext(nullptr, lineType); \
if (strcasecmp(token, (expected)) != 0) { \
fatalAt( \
where, \
@@ -223,12 +230,15 @@ void sdobj_ReadFile(FileStackNode const &src, FILE *file, std::vector<Symbol> &f
}
// Expected format: "A areas S global symbols"
getToken(line.data(), "Empty 'H' line");
token = strtok(line.data(), delim);
if (!token) {
fatalAt(where, "Empty 'H' line");
}
uint32_t expectedNbAreas = readInt(where, token, numberBase);
expectToken("areas", 'H');
getToken(nullptr, "'H' line is too short");
expectNext(nullptr, 'H');
uint32_t expectedNbSymbols = readInt(where, token, numberBase);
fileSymbols.reserve(expectedNbSymbols);
@@ -236,7 +246,7 @@ void sdobj_ReadFile(FileStackNode const &src, FILE *file, std::vector<Symbol> &f
expectToken("symbols", 'H');
expectEol("'H' line is too long");
expectEol('H');
// Now, let's parse the rest of the lines as they come!
@@ -267,7 +277,7 @@ void sdobj_ReadFile(FileStackNode const &src, FILE *file, std::vector<Symbol> &f
curSection->src = where.src;
curSection->lineNo = where.lineNo;
getToken(line.data(), "'A' line is too short");
expectNext(line.data(), 'A');
assume(strlen(token) != 0); // This should be impossible, tokens are non-empty
// The following is required for fragment offsets to be reliably predicted
for (FileSection &entry : fileSections) {
@@ -279,7 +289,7 @@ void sdobj_ReadFile(FileStackNode const &src, FILE *file, std::vector<Symbol> &f
expectToken("size", 'A');
getToken(nullptr, "'A' line is too short");
expectNext(nullptr, 'A');
uint32_t tmp = readInt(where, token, numberBase);
@@ -294,7 +304,7 @@ void sdobj_ReadFile(FileStackNode const &src, FILE *file, std::vector<Symbol> &f
expectToken("flags", 'A');
getToken(nullptr, "'A' line is too short");
expectNext(nullptr, 'A');
tmp = readInt(where, token, numberBase);
if (tmp & (1 << AREA_PAGING)) {
fatalAt(where, "Paging is not supported");
@@ -313,12 +323,12 @@ void sdobj_ReadFile(FileStackNode const &src, FILE *file, std::vector<Symbol> &f
expectToken("addr", 'A');
getToken(nullptr, "'A' line is too short");
expectNext(nullptr, 'A');
tmp = readInt(where, token, numberBase);
curSection->org = tmp; // Truncation keeps the address portion only
curSection->bank = tmp >> 16;
expectEol("'A' line is too long");
expectEol('A');
// Init the rest of the members
curSection->offset = 0;
@@ -366,10 +376,10 @@ void sdobj_ReadFile(FileStackNode const &src, FILE *file, std::vector<Symbol> &f
symbol.src = where.src;
symbol.lineNo = where.lineNo;
getToken(line.data(), "'S' line is too short");
expectNext(line.data(), 'S');
symbol.name = token;
getToken(nullptr, "'S' line is too short");
expectNext(nullptr, 'S');
if (int32_t value = readInt(where, &token[3], numberBase); !fileSections.empty()) {
// Symbols in sections are labels; their value is an offset
@@ -438,7 +448,7 @@ void sdobj_ReadFile(FileStackNode const &src, FILE *file, std::vector<Symbol> &f
fileSections.back().section->symbols.push_back(&symbol);
}
expectEol("'S' line is too long");
expectEol('S');
break;
}
@@ -467,13 +477,13 @@ void sdobj_ReadFile(FileStackNode const &src, FILE *file, std::vector<Symbol> &f
}
// First two bytes are ignored
getToken(line.data(), "'R' line is too short");
getToken(nullptr, "'R' line is too short");
expectNext(line.data(), 'R');
expectNext(nullptr, 'R');
uint16_t areaIdx;
getToken(nullptr, "'R' line is too short");
expectNext(nullptr, 'R');
areaIdx = readByte(where, token, numberBase);
getToken(nullptr, "'R' line is too short");
expectNext(nullptr, 'R');
areaIdx |= static_cast<uint16_t>(readByte(where, token, numberBase)) << 8;
if (areaIdx >= fileSections.size()) {
fatalAt(
@@ -537,12 +547,12 @@ void sdobj_ReadFile(FileStackNode const &src, FILE *file, std::vector<Symbol> &f
uint16_t flags = readByte(where, token, numberBase);
if ((flags & 0xF0) == 0xF0) {
getToken(nullptr, "Incomplete relocation");
expectRelocation();
flags = (flags & 0x0F)
| static_cast<uint16_t>(readByte(where, token, numberBase)) << 4;
}
getToken(nullptr, "Incomplete relocation");
expectRelocation();
uint8_t offset = readByte(where, token, numberBase);
if (offset < addrSize) {
@@ -562,10 +572,10 @@ void sdobj_ReadFile(FileStackNode const &src, FILE *file, std::vector<Symbol> &f
);
}
getToken(nullptr, "Incomplete relocation");
expectRelocation();
uint16_t idx = readByte(where, token, numberBase);
getToken(nullptr, "Incomplete relocation");
expectRelocation();
idx |= static_cast<uint16_t>(readByte(where, token, numberBase));
// Loudly fail on unknown flags
@@ -819,8 +829,9 @@ void sdobj_ReadFile(FileStackNode const &src, FILE *file, std::vector<Symbol> &f
}
#undef expectEol
#undef expectNext
#undef expectRelocation
#undef expectToken
#undef getToken
if (!data.empty()) {
warningAt(where, "Last 'T' line had no 'R' line (ignored)");

View File

@@ -31,7 +31,7 @@ Diagnostics<WarningLevel, WarningID> warnings = {
{"truncation", LEVEL_EVERYTHING},
},
.paramWarnings = {
{WARNING_TRUNCATION_1, WARNING_TRUNCATION_2, 2},
{WARNING_TRUNCATION_1, WARNING_TRUNCATION_2, 1},
},
.state = DiagnosticsState<WarningID>(),
.nbErrors = 0,

View File

@@ -58,6 +58,14 @@ bool isAlphanumeric(int c) {
return isLetter(c) || isDigit(c);
}
char toLower(char c) {
return isUpper(c) ? c - 'A' + 'a' : c;
}
char toUpper(char c) {
return isLower(c) ? c - 'a' + 'A' : c;
}
bool startsIdentifier(int c) {
// This returns false for anonymous labels, which internally start with a '!',
// and for section fragment literal labels, which internally start with a '$'.

View File

@@ -0,0 +1,3 @@
section "test", rom0
incbin "incbin-mg-noexist.bin", 2
println "never reached"

View File

@@ -0,0 +1 @@
-MG

View File

@@ -1,3 +1,3 @@
section "test", rom0
incbin "incbin-mg-noexist.bin", 2
incbin "incbin-mg-noexist.bin"
println "never reached"

10
test/asm/const-low.asm Normal file
View File

@@ -0,0 +1,10 @@
section "good", romx, align[8, 1]
Alpha:
static_assert LOW(Alpha) == 1
db 99
Beta:
static_assert LOW(Beta) == 2
section "bad", romx, align[7, 3]
Gamma:
static_assert LOW(Gamma) == 3

5
test/asm/const-low.err Normal file
View File

@@ -0,0 +1,5 @@
error: Expected constant expression: `Gamma` is not constant at assembly time
at const-low.asm(10)
error: Assertion failed
at const-low.asm(10)
Assembly aborted with 2 errors!

View File

@@ -1 +0,0 @@
-M /dev/null -MG

View File

@@ -0,0 +1,2 @@
a.o: errors-after-missing-include/a.asm
a.o: does not exist

2
test/asm/flag-Q.asm Normal file
View File

@@ -0,0 +1,2 @@
section "test", rom0
dl 3.14159

1
test/asm/flag-Q.flags Normal file
View File

@@ -0,0 +1 @@
-Q .24

1
test/asm/flag-Q.out.bin Normal file
View File

@@ -0,0 +1 @@
>?$

4
test/asm/flag-p.asm Normal file
View File

@@ -0,0 +1,4 @@
section "test", rom0
db 1, 2, 3
ds 3
db 4, 5, 6

1
test/asm/flag-p.flags Normal file
View File

@@ -0,0 +1 @@
-p 0x42

1
test/asm/flag-p.out.bin Normal file
View File

@@ -0,0 +1 @@
BBB

4
test/asm/incbin.asm Normal file
View File

@@ -0,0 +1,4 @@
section "test", rom0
incbin "data.bin"
incbin "data.bin", $70
incbin "data.bin", $20, 10

1
test/asm/incbin.out.bin Normal file
View File

@@ -0,0 +1 @@
QÇÀíð+)W-N^wY7×¾ Ô cʃÅ.YzÎö»+áƒåÑ‚‚'挮‡g}©!°XX6¨"]|Ó6`<60>93ãQu/·]Æ@üäˆ-baüDð$ºsFí$!B(ìx„ ©äÒÑy¯«¯Ü½œ» {@O°Á½ Û {@O°Á½ ÛÎö»+áƒåÑ‚‚

View File

@@ -0,0 +1,3 @@
section "test", rom0
#call.local1
call.local2

View File

@@ -0,0 +1,3 @@
error: Identifier "call.local2" begins with a keyword; did you mean to put a space between them?
at keyword-global.asm(3)
Assembly aborted with 1 error!

View File

@@ -0,0 +1,2 @@
section "test", rom0
incbin "data.bin"

View File

@@ -1 +1 @@
-M - -MT preserve$dollars$$ -MQ escape$dollars$$
-M - -MT preserve$dollars$$ -MQ escape$dollars$$ -MP

View File

@@ -1 +1,3 @@
preserve$dollars$$ escape$$dollars$$$$: make-deps.asm
preserve$dollars$$ escape$$dollars$$$$: data.bin
data.bin:

View File

@@ -9,10 +9,10 @@ warning: Line 5 [-Wuser]
warning: Line 8 [-Wuser]
at rept-line-no.asm(8)
warning: Line 12 [-Wuser]
at rept-line-no.asm::REPT~1::REPT~1::REPT~1(12) <- rept-line-no.asm::REPT~1(11) <- rept-line-no.asm(10)
at rept-line-no.asm::REPT~1::REPT~1(12) <- rept-line-no.asm::REPT~1(11) <- rept-line-no.asm(10)
warning: Line 12 [-Wuser]
at rept-line-no.asm::REPT~1::REPT~1::REPT~2(12) <- rept-line-no.asm::REPT~1(11) <- rept-line-no.asm(10)
at rept-line-no.asm::REPT~1::REPT~2(12) <- rept-line-no.asm::REPT~1(11) <- rept-line-no.asm(10)
warning: Line 12 [-Wuser]
at rept-line-no.asm::REPT~2::REPT~2::REPT~1(12) <- rept-line-no.asm::REPT~2(11) <- rept-line-no.asm(10)
at rept-line-no.asm::REPT~2::REPT~1(12) <- rept-line-no.asm::REPT~2(11) <- rept-line-no.asm(10)
warning: Line 12 [-Wuser]
at rept-line-no.asm::REPT~2::REPT~2::REPT~2(12) <- rept-line-no.asm::REPT~2(11) <- rept-line-no.asm(10)
at rept-line-no.asm::REPT~2::REPT~2(12) <- rept-line-no.asm::REPT~2(11) <- rept-line-no.asm(10)

View File

@@ -1,5 +1,5 @@
warning: round 1 [-Wuser]
at rept-macro-fstack-trace.asm::outer::REPT~1::inner::REPT~1::REPT~1::REPT~1(13)
at rept-macro-fstack-trace.asm::outer::REPT~1::inner::REPT~1::REPT~1(13)
<- rept-macro-fstack-trace.asm::outer::REPT~1::inner::REPT~1(12)
<- rept-macro-fstack-trace.asm::outer::REPT~1::inner(11)
<- rept-macro-fstack-trace.asm::outer::REPT~1(13)
@@ -7,7 +7,7 @@ warning: round 1 [-Wuser]
<- rept-macro-fstack-trace.asm::REPT~1(18)
<- rept-macro-fstack-trace.asm(17)
warning: round 2 [-Wuser]
at rept-macro-fstack-trace.asm::outer::REPT~1::inner::REPT~1::REPT~1::REPT~2(13)
at rept-macro-fstack-trace.asm::outer::REPT~1::inner::REPT~1::REPT~2(13)
<- rept-macro-fstack-trace.asm::outer::REPT~1::inner::REPT~1(12)
<- rept-macro-fstack-trace.asm::outer::REPT~1::inner(11)
<- rept-macro-fstack-trace.asm::outer::REPT~1(13)
@@ -15,7 +15,7 @@ warning: round 2 [-Wuser]
<- rept-macro-fstack-trace.asm::REPT~1(18)
<- rept-macro-fstack-trace.asm(17)
warning: round 3 [-Wuser]
at rept-macro-fstack-trace.asm::outer::REPT~1::inner::REPT~2::REPT~2::REPT~1(13)
at rept-macro-fstack-trace.asm::outer::REPT~1::inner::REPT~2::REPT~1(13)
<- rept-macro-fstack-trace.asm::outer::REPT~1::inner::REPT~2(12)
<- rept-macro-fstack-trace.asm::outer::REPT~1::inner(11)
<- rept-macro-fstack-trace.asm::outer::REPT~1(13)
@@ -23,7 +23,7 @@ warning: round 3 [-Wuser]
<- rept-macro-fstack-trace.asm::REPT~1(18)
<- rept-macro-fstack-trace.asm(17)
warning: round 4 [-Wuser]
at rept-macro-fstack-trace.asm::outer::REPT~1::inner::REPT~2::REPT~2::REPT~2(13)
at rept-macro-fstack-trace.asm::outer::REPT~1::inner::REPT~2::REPT~2(13)
<- rept-macro-fstack-trace.asm::outer::REPT~1::inner::REPT~2(12)
<- rept-macro-fstack-trace.asm::outer::REPT~1::inner(11)
<- rept-macro-fstack-trace.asm::outer::REPT~1(13)
@@ -31,7 +31,7 @@ warning: round 4 [-Wuser]
<- rept-macro-fstack-trace.asm::REPT~1(18)
<- rept-macro-fstack-trace.asm(17)
warning: round 5 [-Wuser]
at rept-macro-fstack-trace.asm::outer::REPT~1::inner::REPT~1::REPT~1::REPT~1(13)
at rept-macro-fstack-trace.asm::outer::REPT~1::inner::REPT~1::REPT~1(13)
<- rept-macro-fstack-trace.asm::outer::REPT~1::inner::REPT~1(12)
<- rept-macro-fstack-trace.asm::outer::REPT~1::inner(11)
<- rept-macro-fstack-trace.asm::outer::REPT~2(13)
@@ -39,7 +39,7 @@ warning: round 5 [-Wuser]
<- rept-macro-fstack-trace.asm::REPT~1(18)
<- rept-macro-fstack-trace.asm(17)
warning: round 6 [-Wuser]
at rept-macro-fstack-trace.asm::outer::REPT~1::inner::REPT~1::REPT~1::REPT~2(13)
at rept-macro-fstack-trace.asm::outer::REPT~1::inner::REPT~1::REPT~2(13)
<- rept-macro-fstack-trace.asm::outer::REPT~1::inner::REPT~1(12)
<- rept-macro-fstack-trace.asm::outer::REPT~1::inner(11)
<- rept-macro-fstack-trace.asm::outer::REPT~2(13)
@@ -47,7 +47,7 @@ warning: round 6 [-Wuser]
<- rept-macro-fstack-trace.asm::REPT~1(18)
<- rept-macro-fstack-trace.asm(17)
warning: round 7 [-Wuser]
at rept-macro-fstack-trace.asm::outer::REPT~1::inner::REPT~2::REPT~2::REPT~1(13)
at rept-macro-fstack-trace.asm::outer::REPT~1::inner::REPT~2::REPT~1(13)
<- rept-macro-fstack-trace.asm::outer::REPT~1::inner::REPT~2(12)
<- rept-macro-fstack-trace.asm::outer::REPT~1::inner(11)
<- rept-macro-fstack-trace.asm::outer::REPT~2(13)
@@ -55,7 +55,7 @@ warning: round 7 [-Wuser]
<- rept-macro-fstack-trace.asm::REPT~1(18)
<- rept-macro-fstack-trace.asm(17)
warning: round 8 [-Wuser]
at rept-macro-fstack-trace.asm::outer::REPT~1::inner::REPT~2::REPT~2::REPT~2(13)
at rept-macro-fstack-trace.asm::outer::REPT~1::inner::REPT~2::REPT~2(13)
<- rept-macro-fstack-trace.asm::outer::REPT~1::inner::REPT~2(12)
<- rept-macro-fstack-trace.asm::outer::REPT~1::inner(11)
<- rept-macro-fstack-trace.asm::outer::REPT~2(13)
@@ -63,7 +63,7 @@ warning: round 8 [-Wuser]
<- rept-macro-fstack-trace.asm::REPT~1(18)
<- rept-macro-fstack-trace.asm(17)
warning: round 9 [-Wuser]
at rept-macro-fstack-trace.asm::outer::REPT~3::inner::REPT~1::REPT~1::REPT~1(13)
at rept-macro-fstack-trace.asm::outer::REPT~3::inner::REPT~1::REPT~1(13)
<- rept-macro-fstack-trace.asm::outer::REPT~3::inner::REPT~1(12)
<- rept-macro-fstack-trace.asm::outer::REPT~3::inner(11)
<- rept-macro-fstack-trace.asm::outer::REPT~3(13)
@@ -71,7 +71,7 @@ warning: round 9 [-Wuser]
<- rept-macro-fstack-trace.asm::REPT~1(18)
<- rept-macro-fstack-trace.asm(17)
warning: round 10 [-Wuser]
at rept-macro-fstack-trace.asm::outer::REPT~3::inner::REPT~1::REPT~1::REPT~2(13)
at rept-macro-fstack-trace.asm::outer::REPT~3::inner::REPT~1::REPT~2(13)
<- rept-macro-fstack-trace.asm::outer::REPT~3::inner::REPT~1(12)
<- rept-macro-fstack-trace.asm::outer::REPT~3::inner(11)
<- rept-macro-fstack-trace.asm::outer::REPT~3(13)
@@ -79,7 +79,7 @@ warning: round 10 [-Wuser]
<- rept-macro-fstack-trace.asm::REPT~1(18)
<- rept-macro-fstack-trace.asm(17)
warning: round 11 [-Wuser]
at rept-macro-fstack-trace.asm::outer::REPT~3::inner::REPT~2::REPT~2::REPT~1(13)
at rept-macro-fstack-trace.asm::outer::REPT~3::inner::REPT~2::REPT~1(13)
<- rept-macro-fstack-trace.asm::outer::REPT~3::inner::REPT~2(12)
<- rept-macro-fstack-trace.asm::outer::REPT~3::inner(11)
<- rept-macro-fstack-trace.asm::outer::REPT~3(13)
@@ -87,7 +87,7 @@ warning: round 11 [-Wuser]
<- rept-macro-fstack-trace.asm::REPT~1(18)
<- rept-macro-fstack-trace.asm(17)
warning: round 12 [-Wuser]
at rept-macro-fstack-trace.asm::outer::REPT~3::inner::REPT~2::REPT~2::REPT~2(13)
at rept-macro-fstack-trace.asm::outer::REPT~3::inner::REPT~2::REPT~2(13)
<- rept-macro-fstack-trace.asm::outer::REPT~3::inner::REPT~2(12)
<- rept-macro-fstack-trace.asm::outer::REPT~3::inner(11)
<- rept-macro-fstack-trace.asm::outer::REPT~3(13)
@@ -95,7 +95,7 @@ warning: round 12 [-Wuser]
<- rept-macro-fstack-trace.asm::REPT~1(18)
<- rept-macro-fstack-trace.asm(17)
warning: round 13 [-Wuser]
at rept-macro-fstack-trace.asm::outer::REPT~3::inner::REPT~1::REPT~1::REPT~1(13)
at rept-macro-fstack-trace.asm::outer::REPT~3::inner::REPT~1::REPT~1(13)
<- rept-macro-fstack-trace.asm::outer::REPT~3::inner::REPT~1(12)
<- rept-macro-fstack-trace.asm::outer::REPT~3::inner(11)
<- rept-macro-fstack-trace.asm::outer::REPT~4(13)
@@ -103,7 +103,7 @@ warning: round 13 [-Wuser]
<- rept-macro-fstack-trace.asm::REPT~1(18)
<- rept-macro-fstack-trace.asm(17)
warning: round 14 [-Wuser]
at rept-macro-fstack-trace.asm::outer::REPT~3::inner::REPT~1::REPT~1::REPT~2(13)
at rept-macro-fstack-trace.asm::outer::REPT~3::inner::REPT~1::REPT~2(13)
<- rept-macro-fstack-trace.asm::outer::REPT~3::inner::REPT~1(12)
<- rept-macro-fstack-trace.asm::outer::REPT~3::inner(11)
<- rept-macro-fstack-trace.asm::outer::REPT~4(13)
@@ -111,7 +111,7 @@ warning: round 14 [-Wuser]
<- rept-macro-fstack-trace.asm::REPT~1(18)
<- rept-macro-fstack-trace.asm(17)
warning: round 15 [-Wuser]
at rept-macro-fstack-trace.asm::outer::REPT~3::inner::REPT~2::REPT~2::REPT~1(13)
at rept-macro-fstack-trace.asm::outer::REPT~3::inner::REPT~2::REPT~1(13)
<- rept-macro-fstack-trace.asm::outer::REPT~3::inner::REPT~2(12)
<- rept-macro-fstack-trace.asm::outer::REPT~3::inner(11)
<- rept-macro-fstack-trace.asm::outer::REPT~4(13)
@@ -119,7 +119,7 @@ warning: round 15 [-Wuser]
<- rept-macro-fstack-trace.asm::REPT~1(18)
<- rept-macro-fstack-trace.asm(17)
warning: round 16 [-Wuser]
at rept-macro-fstack-trace.asm::outer::REPT~3::inner::REPT~2::REPT~2::REPT~2(13)
at rept-macro-fstack-trace.asm::outer::REPT~3::inner::REPT~2::REPT~2(13)
<- rept-macro-fstack-trace.asm::outer::REPT~3::inner::REPT~2(12)
<- rept-macro-fstack-trace.asm::outer::REPT~3::inner(11)
<- rept-macro-fstack-trace.asm::outer::REPT~4(13)
@@ -127,42 +127,42 @@ warning: round 16 [-Wuser]
<- rept-macro-fstack-trace.asm::REPT~1(18)
<- rept-macro-fstack-trace.asm(17)
warning: round 17 [-Wuser]
at rept-macro-fstack-trace.asm::foo::REPT~1::REPT~1::REPT~1(24)
at rept-macro-fstack-trace.asm::foo::REPT~1::REPT~1(24)
<- rept-macro-fstack-trace.asm::foo::REPT~1(23)
<- rept-macro-fstack-trace.asm::foo(22)
<- rept-macro-fstack-trace.asm::REPT~1::REPT~1::bar::REPT~1::REPT~1::REPT~1(34)
<- rept-macro-fstack-trace.asm::REPT~1::REPT~1::bar::REPT~1::REPT~1(34)
<- rept-macro-fstack-trace.asm::REPT~1::REPT~1::bar::REPT~1(33)
<- rept-macro-fstack-trace.asm::REPT~1::REPT~1::bar(32)
<- rept-macro-fstack-trace.asm::REPT~1::REPT~1::REPT~1(38)
<- rept-macro-fstack-trace.asm::REPT~1::REPT~1(38)
<- rept-macro-fstack-trace.asm::REPT~1(30)
<- rept-macro-fstack-trace.asm(29)
warning: round 18 [-Wuser]
at rept-macro-fstack-trace.asm::foo::REPT~1::REPT~1::REPT~1(24)
at rept-macro-fstack-trace.asm::foo::REPT~1::REPT~1(24)
<- rept-macro-fstack-trace.asm::foo::REPT~1(23)
<- rept-macro-fstack-trace.asm::foo(22)
<- rept-macro-fstack-trace.asm::REPT~1::REPT~1::bar::REPT~1::REPT~1::REPT~2(34)
<- rept-macro-fstack-trace.asm::REPT~1::REPT~1::bar::REPT~1::REPT~2(34)
<- rept-macro-fstack-trace.asm::REPT~1::REPT~1::bar::REPT~1(33)
<- rept-macro-fstack-trace.asm::REPT~1::REPT~1::bar(32)
<- rept-macro-fstack-trace.asm::REPT~1::REPT~1::REPT~1(38)
<- rept-macro-fstack-trace.asm::REPT~1::REPT~1(38)
<- rept-macro-fstack-trace.asm::REPT~1(30)
<- rept-macro-fstack-trace.asm(29)
warning: round 19 [-Wuser]
at rept-macro-fstack-trace.asm::foo::REPT~1::REPT~1::REPT~1(24)
at rept-macro-fstack-trace.asm::foo::REPT~1::REPT~1(24)
<- rept-macro-fstack-trace.asm::foo::REPT~1(23)
<- rept-macro-fstack-trace.asm::foo(22)
<- rept-macro-fstack-trace.asm::REPT~1::REPT~1::bar::REPT~2::REPT~2::REPT~1(34)
<- rept-macro-fstack-trace.asm::REPT~1::REPT~1::bar::REPT~2::REPT~1(34)
<- rept-macro-fstack-trace.asm::REPT~1::REPT~1::bar::REPT~2(33)
<- rept-macro-fstack-trace.asm::REPT~1::REPT~1::bar(32)
<- rept-macro-fstack-trace.asm::REPT~1::REPT~1::REPT~1(38)
<- rept-macro-fstack-trace.asm::REPT~1::REPT~1(38)
<- rept-macro-fstack-trace.asm::REPT~1(30)
<- rept-macro-fstack-trace.asm(29)
warning: round 20 [-Wuser]
at rept-macro-fstack-trace.asm::foo::REPT~1::REPT~1::REPT~1(24)
at rept-macro-fstack-trace.asm::foo::REPT~1::REPT~1(24)
<- rept-macro-fstack-trace.asm::foo::REPT~1(23)
<- rept-macro-fstack-trace.asm::foo(22)
<- rept-macro-fstack-trace.asm::REPT~1::REPT~1::bar::REPT~2::REPT~2::REPT~2(34)
<- rept-macro-fstack-trace.asm::REPT~1::REPT~1::bar::REPT~2::REPT~2(34)
<- rept-macro-fstack-trace.asm::REPT~1::REPT~1::bar::REPT~2(33)
<- rept-macro-fstack-trace.asm::REPT~1::REPT~1::bar(32)
<- rept-macro-fstack-trace.asm::REPT~1::REPT~1::REPT~1(38)
<- rept-macro-fstack-trace.asm::REPT~1::REPT~1(38)
<- rept-macro-fstack-trace.asm::REPT~1(30)
<- rept-macro-fstack-trace.asm(29)

23
test/asm/rept-trace.asm Normal file
View File

@@ -0,0 +1,23 @@
section "test", rom0
for v1, 4
if v1 == 3
for v2, 3
if v2 == 2
for v3, 2
if v3 == 1
rept 1
macro m
static_assert \1
endm
endr
rept 1
rept 2
m 0
endr
endr
endc
endr
endc
endr
endc
endr

17
test/asm/rept-trace.err Normal file
View File

@@ -0,0 +1,17 @@
error: Assertion failed
at rept-trace.asm::REPT~4::REPT~3::REPT~2::REPT~1::m(10)
<- rept-trace.asm::REPT~4::REPT~3::REPT~2::REPT~1::REPT~1(15)
<- rept-trace.asm::REPT~4::REPT~3::REPT~2::REPT~1(14)
<- rept-trace.asm::REPT~4::REPT~3::REPT~2(13)
<- rept-trace.asm::REPT~4::REPT~3(6)
<- rept-trace.asm::REPT~4(4)
<- rept-trace.asm(2)
error: Assertion failed
at rept-trace.asm::REPT~4::REPT~3::REPT~2::REPT~1::m(10)
<- rept-trace.asm::REPT~4::REPT~3::REPT~2::REPT~1::REPT~2(15)
<- rept-trace.asm::REPT~4::REPT~3::REPT~2::REPT~1(14)
<- rept-trace.asm::REPT~4::REPT~3::REPT~2(13)
<- rept-trace.asm::REPT~4::REPT~3(6)
<- rept-trace.asm::REPT~4(4)
<- rept-trace.asm(2)
Assembly aborted with 2 errors!

View File

@@ -0,0 +1 @@
-Bno-collapse

27
test/asm/scope-level.asm Normal file
View File

@@ -0,0 +1,27 @@
assert !def(__SCOPE__)
section "test", rom0
assert !def(.)
assert !def(..)
assert #__SCOPE__ === ""
Alpha.local1:
assert !def(.)
assert #.. === "Alpha.local1"
assert #__SCOPE__ === ".."
Beta:
assert #. === "Beta"
assert !def(..)
assert #__SCOPE__ === "."
Alpha.local2:
assert #. === "Beta"
assert #.. === "Alpha.local2"
assert #__SCOPE__ === ".."
.newLocal:
assert #. === "Beta"
assert #.. === "Beta.newLocal"
assert #__SCOPE__ === ".."

View File

@@ -61,10 +61,9 @@ else
fi
for i in *.asm notexist.asm; do
flags=${i%.asm}.flags
RGBASMFLAGS="-Weverything -Bcollapse"
if [ -f "$flags" ]; then
RGBASMFLAGS="$RGBASMFLAGS $(head -n 1 "$flags")" # Allow other lines to serve as comments
if [ -f "${i%.asm}.flags" ]; then
RGBASMFLAGS="$RGBASMFLAGS @${i%.asm}.flags"
fi
for variant in '' ' piped'; do
(( tests++ ))
@@ -134,7 +133,6 @@ for i in *.asm notexist.asm; do
done
for i in cli/*.flags; do
RGBASMFLAGS="$(head -n 1 "$i")" # Allow other lines to serve as comments
(( tests++ ))
echo "${bold}${green}${i%.flags}...${rescolors}${resbold}"
if [ -e "${i%.flags}.out" ]; then
@@ -147,7 +145,7 @@ for i in cli/*.flags; do
else
desired_errput=/dev/null
fi
"$RGBASM" $RGBASMFLAGS >"$output" 2>"$errput"
"$RGBASM" "@$i" >"$output" 2>"$errput"
tryDiff "$desired_output" "$output" out
our_rc=$?
@@ -163,32 +161,39 @@ done
# These tests do their own thing
i="continues-after-missing-include"
RGBASMFLAGS="-Weverything -Bcollapse -M - -MG -MC"
# Piping the .asm file to rgbasm would not make sense for dependency generation,
# so just test the normal variant
(( tests++ ))
echo "${bold}${green}${i%.asm}...${rescolors}${resbold}"
"$RGBASM" $RGBASMFLAGS -o "$o" "$i"/a.asm >"$output" 2>"$errput"
fixed_output="$input"
if which cygpath &>/dev/null; then
evaluateDepTest () {
i="$1"
RGBASMFLAGS="-Weverything -Bcollapse -M - $2"
# Piping the .asm file to rgbasm would not make sense for dependency generation,
# so just test the normal variant
(( tests++ ))
echo "${bold}${green}${i%.asm}...${rescolors}${resbold}"
"$RGBASM" $RGBASMFLAGS -o "$o" "$i"/a.asm >"$output" 2>"$errput"
fixed_output="$input"
if which cygpath &>/dev/null; then
# MinGW needs the Windows path substituted but with forward slash separators;
# Cygwin has `cygpath` but just needs the original path substituted.
subst1="$(printf '%s\n' "$o" | sed 's:[][\/.^$*]:\\&:g')"
subst2="$(printf '%s\n' "$(cygpath -w "$o")" | sed -e 's:\\:/:g' -e 's:[][\/.^$*]:\\&:g')"
sed -e "s/$subst1/a.o/g" -e "s/$subst2/a.o/g" "$output" >"$fixed_output"
else
else
subst="$(printf '%s\n' "$o" | sed 's:[][\/.^$*]:\\&:g')"
sed "s/$subst/a.o/g" "$output" >"$fixed_output"
fi
tryDiff "$i"/a.out "$fixed_output" out
our_rc=$?
tryDiff "$i"/a.err "$errput" err
(( our_rc = our_rc || $? ))
(( rc = rc || our_rc ))
if [[ $our_rc -ne 0 ]]; then
fi
tryDiff "$i"/a.out "$fixed_output" out
our_rc=$?
tryDiff "$i"/a.err "$errput" err
(( our_rc = our_rc || $? ))
(( rc = rc || our_rc ))
if [[ $our_rc -ne 0 ]]; then
(( failed++ ))
fi
fi
}
evaluateDepTest "continues-after-missing-include" "-MG -MC"
evaluateDepTest "errors-after-missing-include" "-MG"
i="state-file"
if which cygpath &>/dev/null; then

View File

@@ -3,6 +3,11 @@ assert !DEF(@)
println @
println "{@}?"
; not inside a section
assert !DEF(__SCOPE__)
println __SCOPE__
println "{__SCOPE__}?"
; not inside a global scope
assert !DEF(.)
println .
@@ -23,6 +28,10 @@ assert DEF(@)
println @
println "{@}!"
assert DEF(__SCOPE__)
println __SCOPE__
println "{__SCOPE__}!"
GlobalScope:
assert DEF(.)
println .

View File

@@ -2,16 +2,20 @@ error: PC has no value outside of a section
at undefined-builtins.asm(3)
error: Interpolated symbol `@` does not exist
at undefined-builtins.asm(4)
error: `.` has no value outside of a label scope
error: `__SCOPE__` has no value outside of a section
at undefined-builtins.asm(8)
error: Interpolated symbol `.` does not exist
error: Interpolated symbol `__SCOPE__` does not exist
at undefined-builtins.asm(9)
error: `..` has no value outside of a local label scope
error: `.` has no value outside of a label scope
at undefined-builtins.asm(13)
error: Interpolated symbol `..` does not exist
error: Interpolated symbol `.` does not exist
at undefined-builtins.asm(14)
error: `_NARG` has no value outside of a macro
error: `..` has no value outside of a local label scope
at undefined-builtins.asm(18)
error: Interpolated symbol `_NARG` does not exist
error: Interpolated symbol `..` does not exist
at undefined-builtins.asm(19)
Assembly aborted with 8 errors!
error: `_NARG` has no value outside of a macro
at undefined-builtins.asm(23)
error: Interpolated symbol `_NARG` does not exist
at undefined-builtins.asm(24)
Assembly aborted with 10 errors!

View File

@@ -3,11 +3,15 @@ $0
?
?
?
$0
?
$42
$42!
!
$42
GlobalScope!
$42

View File

@@ -32,7 +32,7 @@ MACRO try
ENDM
try Wno-truncation
try Wtruncation
try Wtruncation ; defaults to 1
try Wtruncation=0
try Wtruncation=1
try Wtruncation=2

View File

@@ -6,14 +6,6 @@ warning: Expression must be 16-bit [-Wtruncation]
at warn-truncation.asm::try(25) <- warn-truncation.asm(35)
warning: Expression must be 16-bit [-Wtruncation]
at warn-truncation.asm::try(26) <- warn-truncation.asm(35)
warning: Expression must be 8-bit; use `LOW()` to force 8-bit [-Wtruncation]
at warn-truncation.asm::try(28) <- warn-truncation.asm(35)
warning: Expression must be 16-bit [-Wtruncation]
at warn-truncation.asm::try(29) <- warn-truncation.asm(35)
warning: Expression must be 8-bit; use `LOW()` to force 8-bit [-Wtruncation]
at warn-truncation.asm::try(30) <- warn-truncation.asm(35)
warning: Expression must be 16-bit [-Wtruncation]
at warn-truncation.asm::try(31) <- warn-truncation.asm(35)
warning: Expression must be 8-bit; use `LOW()` to force 8-bit [-Wtruncation]
at warn-truncation.asm::try(23) <- warn-truncation.asm(37)
warning: Expression must be 8-bit; use `LOW()` to force 8-bit [-Wtruncation]

View File

@@ -1,2 +1,2 @@
-w -m mbc3+ram -r 0
The "-w" suppresses "-Wmbc" and "-Woverwrite" warnings
# The "-w" suppresses "-Wmbc" and "-Woverwrite" warnings

View File

@@ -1 +1 @@
-m '$2a'
-m $2a

View File

@@ -1 +1 @@
-i 'FOUR!'
-i FOUR!

View File

@@ -1,2 +1,2 @@
-Cf h
Checks that the header checksum properly accounts for header modifications
# Checks that the header checksum properly accounts for header modifications

View File

@@ -1,2 +1,2 @@
-Wno-overwrite -Cjv -t PM_CRYSTAL -i BYTE -n 0 -k 01 -l 0x33 -m 0x10 -r 3 -p 0
Checks that the -Wno-overwrite flag suppresses "Overwrote a non-zero byte" warnings from the rest
# Checks that the -Wno-overwrite flag suppresses "Overwrote a non-zero byte" warnings from the rest

View File

@@ -50,10 +50,14 @@ tryCmp () {
}
runTest () {
if grep -qF ' ./' "$2/$1.flags"; then
flags=$(
head -n 1 "$2/$1.flags" | # Allow other lines to serve as comments
sed "s# ./# ${src//#/\\#}/#g" # Prepend src directory to path arguments
)
else
flags="@$2/$1.flags"
fi
for variant in '' ' piped' ' output'; do
(( tests++ ))

Some files were not shown because too many files have changed in this diff Show More