mirror of
https://github.com/gbdev/rgbds.git
synced 2025-11-20 10:12:06 +00:00
Compare commits
35 Commits
v1.0.0-rc2
...
5c2c893ced
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5c2c893ced | ||
|
|
0f266d1c66 | ||
|
|
8ab4602ae5 | ||
|
|
04e3a904c2 | ||
|
|
395b03e88e | ||
|
|
fb9fa6038c | ||
|
|
35e5808423 | ||
|
|
558d3ca0fc | ||
|
|
df5162edca | ||
|
|
2519d1e698 | ||
|
|
ca383c91ca | ||
|
|
8bedd710d7 | ||
|
|
efb5a88edb | ||
|
|
f065243cd2 | ||
|
|
a0bb830679 | ||
|
|
7654c6e27a | ||
|
|
400375b2e5 | ||
|
|
7462bccb72 | ||
|
|
2873e0b8c8 | ||
|
|
1badba03d8 | ||
|
|
64bcef99bd | ||
|
|
aa672bbec9 | ||
|
|
651877e094 | ||
|
|
26c48cc409 | ||
|
|
23b9039716 | ||
|
|
711fba5e35 | ||
|
|
089fc11e31 | ||
|
|
837f552987 | ||
|
|
cb8c973453 | ||
|
|
cca3794dd0 | ||
|
|
02c2408f58 | ||
|
|
fba0562650 | ||
|
|
0c9920d4a6 | ||
|
|
7733ccdeb6 | ||
|
|
13e85b5151 |
@@ -71,6 +71,7 @@ Language: Cpp
|
|||||||
MaxEmptyLinesToKeep: 1
|
MaxEmptyLinesToKeep: 1
|
||||||
NamespaceIndentation: None
|
NamespaceIndentation: None
|
||||||
PPIndentWidth: -1
|
PPIndentWidth: -1
|
||||||
|
PenaltyBreakScopeResolution: 1000
|
||||||
PointerAlignment: Right
|
PointerAlignment: Right
|
||||||
QualifierAlignment: Right
|
QualifierAlignment: Right
|
||||||
ReflowComments: true
|
ReflowComments: true
|
||||||
|
|||||||
@@ -120,6 +120,9 @@ These files in the `src/` directory are shared across multiple programs: often a
|
|||||||
|
|
||||||
- **`backtrace.cpp`:**
|
- **`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.
|
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`:**
|
- **`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.
|
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`:**
|
- **`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.
|
Functions for sorting colors within palettes, which works differently for grayscale, RGB, or indexed-color palettes.
|
||||||
- **`pal_spec.cpp`:**
|
- **`pal_spec.cpp`:**
|
||||||
Functions for parsing various formats of palette specifications (from `-c/--colors`).
|
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.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.
|
`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`:**
|
- **`process.cpp`:**
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ else()
|
|||||||
-fsanitize=float-divide-by-zero)
|
-fsanitize=float-divide-by-zero)
|
||||||
add_compile_options(${SAN_FLAGS})
|
add_compile_options(${SAN_FLAGS})
|
||||||
add_link_options(${SAN_FLAGS})
|
add_link_options(${SAN_FLAGS})
|
||||||
add_definitions(-D_GLIBCXX_ASSERTIONS)
|
add_definitions(-D_GLIBCXX_ASSERTIONS -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_DEBUG)
|
||||||
# A non-zero optimization level is desired in debug mode, but allow overriding it nonetheless
|
# A non-zero optimization level is desired in debug mode, but allow overriding it nonetheless
|
||||||
set(CMAKE_CXX_FLAGS_DEBUG "-g -Og -fno-omit-frame-pointer -fno-optimize-sibling-calls ${CMAKE_CXX_FLAGS_DEBUG}"
|
set(CMAKE_CXX_FLAGS_DEBUG "-g -Og -fno-omit-frame-pointer -fno-optimize-sibling-calls ${CMAKE_CXX_FLAGS_DEBUG}"
|
||||||
CACHE STRING "" FORCE)
|
CACHE STRING "" FORCE)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
FROM debian:12-slim
|
FROM debian:12-slim
|
||||||
LABEL org.opencontainers.image.source=https://github.com/gbdev/rgbds
|
LABEL org.opencontainers.image.source=https://github.com/gbdev/rgbds
|
||||||
ARG version=1.0.0-rc2
|
ARG version=1.0.0
|
||||||
WORKDIR /rgbds
|
WORKDIR /rgbds
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|||||||
6
Makefile
6
Makefile
@@ -51,6 +51,7 @@ all: rgbasm rgblink rgbfix rgbgfx
|
|||||||
|
|
||||||
common_obj := \
|
common_obj := \
|
||||||
src/extern/getopt.o \
|
src/extern/getopt.o \
|
||||||
|
src/cli.o \
|
||||||
src/diagnostics.o \
|
src/diagnostics.o \
|
||||||
src/style.o \
|
src/style.o \
|
||||||
src/usage.o \
|
src/usage.o \
|
||||||
@@ -118,6 +119,7 @@ rgbgfx_obj := \
|
|||||||
src/gfx/pal_packing.o \
|
src/gfx/pal_packing.o \
|
||||||
src/gfx/pal_sorting.o \
|
src/gfx/pal_sorting.o \
|
||||||
src/gfx/pal_spec.o \
|
src/gfx/pal_spec.o \
|
||||||
|
src/gfx/palette.o \
|
||||||
src/gfx/png.o \
|
src/gfx/png.o \
|
||||||
src/gfx/process.o \
|
src/gfx/process.o \
|
||||||
src/gfx/reverse.o \
|
src/gfx/reverse.o \
|
||||||
@@ -220,8 +222,8 @@ develop:
|
|||||||
-Wformat=2 -Wformat-overflow=2 -Wformat-truncation=1 \
|
-Wformat=2 -Wformat-overflow=2 -Wformat-truncation=1 \
|
||||||
-Wno-format-nonliteral -Wno-strict-overflow -Wno-unused-but-set-variable \
|
-Wno-format-nonliteral -Wno-strict-overflow -Wno-unused-but-set-variable \
|
||||||
-Wno-type-limits -Wno-tautological-constant-out-of-range-compare -Wvla \
|
-Wno-type-limits -Wno-tautological-constant-out-of-range-compare -Wvla \
|
||||||
-D_GLIBCXX_ASSERTIONS -fsanitize=address -fsanitize=undefined \
|
-D_GLIBCXX_ASSERTIONS -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_DEBUG \
|
||||||
-fsanitize=float-divide-by-zero" \
|
-fsanitize=address -fsanitize=undefined -fsanitize=float-divide-by-zero" \
|
||||||
CXXFLAGS="-ggdb3 -Og -fno-omit-frame-pointer -fno-optimize-sibling-calls"
|
CXXFLAGS="-ggdb3 -Og -fno-omit-frame-pointer -fno-optimize-sibling-calls"
|
||||||
|
|
||||||
# Target used in development to debug with gdb.
|
# Target used in development to debug with gdb.
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#ifndef RGBDS_ASM_MAIN_HPP
|
#ifndef RGBDS_ASM_MAIN_HPP
|
||||||
#define RGBDS_ASM_MAIN_HPP
|
#define RGBDS_ASM_MAIN_HPP
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
@@ -20,10 +21,10 @@ struct Options {
|
|||||||
char binDigits[2] = {'0', '1'}; // -b
|
char binDigits[2] = {'0', '1'}; // -b
|
||||||
char gfxDigits[4] = {'0', '1', '2', '3'}; // -g
|
char gfxDigits[4] = {'0', '1', '2', '3'}; // -g
|
||||||
FILE *dependFile = nullptr; // -M
|
FILE *dependFile = nullptr; // -M
|
||||||
std::string targetFileName; // -MQ, -MT
|
std::optional<std::string> targetFileName{}; // -MQ, -MT
|
||||||
MissingInclude missingIncludeState = INC_ERROR; // -MC, -MG
|
MissingInclude missingIncludeState = INC_ERROR; // -MC, -MG
|
||||||
bool generatePhonyDeps = false; // -MP
|
bool generatePhonyDeps = false; // -MP
|
||||||
std::string objectFileName; // -o
|
std::optional<std::string> objectFileName{}; // -o
|
||||||
uint8_t padByte = 0; // -p
|
uint8_t padByte = 0; // -p
|
||||||
uint64_t maxErrors = 0; // -X
|
uint64_t maxErrors = 0; // -X
|
||||||
|
|
||||||
@@ -35,7 +36,7 @@ struct Options {
|
|||||||
|
|
||||||
void printDep(std::string const &depName) {
|
void printDep(std::string const &depName) {
|
||||||
if (dependFile) {
|
if (dependFile) {
|
||||||
fprintf(dependFile, "%s: %s\n", targetFileName.c_str(), depName.c_str());
|
fprintf(dependFile, "%s: %s\n", targetFileName->c_str(), depName.c_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,6 +11,10 @@
|
|||||||
|
|
||||||
#include "style.hpp"
|
#include "style.hpp"
|
||||||
|
|
||||||
|
#define TRACE_SEPARATOR "<-"
|
||||||
|
#define NODE_SEPARATOR "::"
|
||||||
|
#define REPT_NODE_PREFIX "REPT~"
|
||||||
|
|
||||||
struct Tracing {
|
struct Tracing {
|
||||||
uint64_t depth = 0;
|
uint64_t depth = 0;
|
||||||
bool collapse = false;
|
bool collapse = false;
|
||||||
@@ -21,20 +25,20 @@ extern Tracing tracing;
|
|||||||
|
|
||||||
bool trace_ParseTraceDepth(char const *arg);
|
bool trace_ParseTraceDepth(char const *arg);
|
||||||
|
|
||||||
template<typename T, typename M, typename N>
|
template<typename NodeT, typename NameFnT, typename LineNoFnT>
|
||||||
void trace_PrintBacktrace(std::vector<T> const &stack, M getName, N getLineNo) {
|
void trace_PrintBacktrace(std::vector<NodeT> const &stack, NameFnT getName, LineNoFnT getLineNo) {
|
||||||
size_t n = stack.size();
|
size_t n = stack.size();
|
||||||
if (n == 0) {
|
if (n == 0) {
|
||||||
return; // LCOV_EXCL_LINE
|
return; // LCOV_EXCL_LINE
|
||||||
}
|
}
|
||||||
|
|
||||||
auto printLocation = [&](size_t i) {
|
auto printLocation = [&](size_t i) {
|
||||||
T const &item = stack[n - i - 1];
|
NodeT const &item = stack[n - i - 1];
|
||||||
style_Reset(stderr);
|
style_Reset(stderr);
|
||||||
if (!tracing.collapse) {
|
if (!tracing.collapse) {
|
||||||
fputs(" ", stderr); // Just three spaces; the fourth will be printed next
|
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);
|
style_Set(stderr, STYLE_CYAN, true);
|
||||||
fputs(getName(item), stderr);
|
fputs(getName(item), stderr);
|
||||||
style_Set(stderr, STYLE_CYAN, false);
|
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);
|
style_Reset(stderr);
|
||||||
|
|
||||||
if (tracing.collapse) {
|
if (tracing.collapse) {
|
||||||
fputs(" <-", stderr);
|
fputs(" " TRACE_SEPARATOR, stderr);
|
||||||
} else {
|
} else {
|
||||||
fputs(" ", stderr); // Just three spaces; the fourth will be printed next
|
fputs(" ", stderr); // Just three spaces; the fourth will be printed next
|
||||||
}
|
}
|
||||||
|
|||||||
21
include/cli.hpp
Normal file
21
include/cli.hpp
Normal 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
|
||||||
@@ -30,35 +30,35 @@ struct WarningState {
|
|||||||
|
|
||||||
std::pair<WarningState, std::optional<uint32_t>> getInitialWarningState(std::string &flag);
|
std::pair<WarningState, std::optional<uint32_t>> getInitialWarningState(std::string &flag);
|
||||||
|
|
||||||
template<typename L>
|
template<typename LevelEnumT>
|
||||||
struct WarningFlag {
|
struct WarningFlag {
|
||||||
char const *name;
|
char const *name;
|
||||||
L level;
|
LevelEnumT level;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum WarningBehavior { DISABLED, ENABLED, ERROR };
|
enum WarningBehavior { DISABLED, ENABLED, ERROR };
|
||||||
|
|
||||||
template<typename W>
|
template<typename WarningEnumT>
|
||||||
struct ParamWarning {
|
struct ParamWarning {
|
||||||
W firstID;
|
WarningEnumT firstID;
|
||||||
W lastID;
|
WarningEnumT lastID;
|
||||||
uint8_t defaultLevel;
|
uint8_t defaultLevel;
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename W>
|
template<typename WarningEnumT>
|
||||||
struct DiagnosticsState {
|
struct DiagnosticsState {
|
||||||
WarningState flagStates[W::NB_WARNINGS];
|
WarningState flagStates[WarningEnumT::NB_WARNINGS];
|
||||||
WarningState metaStates[W::NB_WARNINGS];
|
WarningState metaStates[WarningEnumT::NB_WARNINGS];
|
||||||
bool warningsEnabled = true;
|
bool warningsEnabled = true;
|
||||||
bool warningsAreErrors = false;
|
bool warningsAreErrors = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename L, typename W>
|
template<typename LevelEnumT, typename WarningEnumT>
|
||||||
struct Diagnostics {
|
struct Diagnostics {
|
||||||
std::vector<WarningFlag<L>> metaWarnings;
|
std::vector<WarningFlag<LevelEnumT>> metaWarnings;
|
||||||
std::vector<WarningFlag<L>> warningFlags;
|
std::vector<WarningFlag<LevelEnumT>> warningFlags;
|
||||||
std::vector<ParamWarning<W>> paramWarnings;
|
std::vector<ParamWarning<WarningEnumT>> paramWarnings;
|
||||||
DiagnosticsState<W> state;
|
DiagnosticsState<WarningEnumT> state;
|
||||||
uint64_t nbErrors;
|
uint64_t nbErrors;
|
||||||
|
|
||||||
void incrementErrors() {
|
void incrementErrors() {
|
||||||
@@ -67,12 +67,12 @@ struct Diagnostics {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
WarningBehavior getWarningBehavior(W id) const;
|
WarningBehavior getWarningBehavior(WarningEnumT id) const;
|
||||||
void processWarningFlag(char const *flag);
|
void processWarningFlag(char const *flag);
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename L, typename W>
|
template<typename LevelEnumT, typename WarningEnumT>
|
||||||
WarningBehavior Diagnostics<L, W>::getWarningBehavior(W id) const {
|
WarningBehavior Diagnostics<LevelEnumT, WarningEnumT>::getWarningBehavior(WarningEnumT id) const {
|
||||||
// Check if warnings are globally disabled
|
// Check if warnings are globally disabled
|
||||||
if (!state.warningsEnabled) {
|
if (!state.warningsEnabled) {
|
||||||
return WarningBehavior::DISABLED;
|
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 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;
|
return enabledBehavior;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,8 +120,8 @@ WarningBehavior Diagnostics<L, W>::getWarningBehavior(W id) const {
|
|||||||
return WarningBehavior::DISABLED;
|
return WarningBehavior::DISABLED;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename L, typename W>
|
template<typename LevelEnumT, typename WarningEnumT>
|
||||||
void Diagnostics<L, W>::processWarningFlag(char const *flag) {
|
void Diagnostics<LevelEnumT, WarningEnumT>::processWarningFlag(char const *flag) {
|
||||||
std::string rootFlag = flag;
|
std::string rootFlag = flag;
|
||||||
|
|
||||||
// Check for `-Werror` or `-Wno-error` to return early
|
// 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
|
// 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,
|
// If there was an equals sign, it will have set `param`; if not, `param` will be 0,
|
||||||
// which applies to all levels
|
// which applies to all levels
|
||||||
for (ParamWarning<W> const ¶mWarning : paramWarnings) {
|
for (ParamWarning<WarningEnumT> const ¶mWarning : paramWarnings) {
|
||||||
W baseID = paramWarning.firstID;
|
WarningEnumT baseID = paramWarning.firstID;
|
||||||
uint8_t maxParam = paramWarning.lastID - baseID + 1;
|
uint8_t maxParam = paramWarning.lastID - baseID + 1;
|
||||||
assume(paramWarning.defaultLevel <= maxParam);
|
assume(paramWarning.defaultLevel <= maxParam);
|
||||||
|
|
||||||
@@ -183,13 +183,13 @@ void Diagnostics<L, W>::processWarningFlag(char const *flag) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Try to match against a "meta" warning
|
// Try to match against a "meta" warning
|
||||||
for (WarningFlag<L> const &metaWarning : metaWarnings) {
|
for (WarningFlag<LevelEnumT> const &metaWarning : metaWarnings) {
|
||||||
if (rootFlag != metaWarning.name) {
|
if (rootFlag != metaWarning.name) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set each of the warning flags that meets this level
|
// 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) {
|
if (metaWarning.level >= warningFlags[id].level) {
|
||||||
state.metaStates[id].update(flagState);
|
state.metaStates[id].update(flagState);
|
||||||
}
|
}
|
||||||
@@ -198,7 +198,7 @@ void Diagnostics<L, W>::processWarningFlag(char const *flag) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Try to match against a "normal" 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) {
|
if (rootFlag == warningFlags[id].name) {
|
||||||
state.flagStates[id].update(flagState);
|
state.flagStates[id].update(flagState);
|
||||||
return;
|
return;
|
||||||
|
|||||||
6
include/extern/getopt.hpp
vendored
6
include/extern/getopt.hpp
vendored
@@ -12,7 +12,7 @@ static constexpr int optional_argument = 2;
|
|||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
extern char *musl_optarg;
|
extern char *musl_optarg;
|
||||||
extern int musl_optind, musl_opterr, musl_optopt, musl_optreset;
|
extern int musl_optind, musl_optopt;
|
||||||
|
|
||||||
struct option {
|
struct option {
|
||||||
char const *name;
|
char const *name;
|
||||||
@@ -21,8 +21,6 @@ struct option {
|
|||||||
int val;
|
int val;
|
||||||
};
|
};
|
||||||
|
|
||||||
int musl_getopt_long_only(
|
int musl_getopt_long_only(int argc, char **argv, char const *optstring, option const *longopts);
|
||||||
int argc, char **argv, char const *optstring, option const *longopts, int *idx
|
|
||||||
);
|
|
||||||
|
|
||||||
#endif // RGBDS_EXTERN_GETOPT_HPP
|
#endif // RGBDS_EXTERN_GETOPT_HPP
|
||||||
|
|||||||
@@ -3,7 +3,9 @@
|
|||||||
#ifndef RGBDS_FIX_MAIN_HPP
|
#ifndef RGBDS_FIX_MAIN_HPP
|
||||||
#define RGBDS_FIX_MAIN_HPP
|
#define RGBDS_FIX_MAIN_HPP
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
#include "fix/mbc.hpp" // UNSPECIFIED, MbcType
|
#include "fix/mbc.hpp" // UNSPECIFIED, MbcType
|
||||||
|
|
||||||
@@ -28,19 +30,19 @@ struct Options {
|
|||||||
uint16_t ramSize = UNSPECIFIED; // -r
|
uint16_t ramSize = UNSPECIFIED; // -r
|
||||||
bool sgb = false; // -s
|
bool sgb = false; // -s
|
||||||
|
|
||||||
char const *gameID = nullptr; // -i
|
std::optional<std::string> gameID; // -i
|
||||||
uint8_t gameIDLen;
|
uint8_t gameIDLen;
|
||||||
|
|
||||||
char const *newLicensee = nullptr; // -k
|
std::optional<std::string> newLicensee; // -k
|
||||||
uint8_t newLicenseeLen;
|
uint8_t newLicenseeLen;
|
||||||
|
|
||||||
char const *logoFilename = nullptr; // -L
|
std::optional<std::string> logoFilename; // -L
|
||||||
uint8_t logo[48] = {};
|
uint8_t logo[48] = {};
|
||||||
|
|
||||||
MbcType cartridgeType = MBC_NONE; // -m
|
MbcType cartridgeType = MBC_NONE; // -m
|
||||||
uint8_t tpp1Rev[2];
|
uint8_t tpp1Rev[2];
|
||||||
|
|
||||||
char const *title = nullptr; // -t
|
std::optional<std::string> title; // -t
|
||||||
uint8_t titleLen;
|
uint8_t titleLen;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
23
include/gfx/flip.hpp
Normal file
23
include/gfx/flip.hpp
Normal 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
|
||||||
@@ -5,12 +5,11 @@
|
|||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <stddef.h>
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "helpers.hpp"
|
#include "helpers.hpp" // assume
|
||||||
|
|
||||||
#include "gfx/rgba.hpp"
|
#include "gfx/rgba.hpp"
|
||||||
|
|
||||||
@@ -69,35 +68,4 @@ struct Options {
|
|||||||
|
|
||||||
extern Options 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
|
#endif // RGBDS_GFX_MAIN_HPP
|
||||||
|
|||||||
27
include/gfx/palette.hpp
Normal file
27
include/gfx/palette.hpp
Normal 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
|
||||||
@@ -18,7 +18,10 @@ struct Rgba {
|
|||||||
: red(rgba >> 24), green(rgba >> 16), blue(rgba >> 8), alpha(rgba) {}
|
: red(rgba >> 24), green(rgba >> 16), blue(rgba >> 8), alpha(rgba) {}
|
||||||
|
|
||||||
static constexpr Rgba fromCGBColor(uint16_t color) {
|
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 {
|
return {
|
||||||
_5to8(color),
|
_5to8(color),
|
||||||
_5to8(color >> 5),
|
_5to8(color >> 5),
|
||||||
|
|||||||
@@ -100,16 +100,16 @@ static inline int clz(unsigned int x) {
|
|||||||
#define RRANGE(s) std::rbegin(s), std::rend(s)
|
#define RRANGE(s) std::rbegin(s), std::rend(s)
|
||||||
|
|
||||||
// MSVC does not inline `strlen()` or `.length()` of a constant string
|
// MSVC does not inline `strlen()` or `.length()` of a constant string
|
||||||
template<int N>
|
template<int SizeOfString>
|
||||||
static constexpr int literal_strlen(char const (&)[N]) {
|
static constexpr int literal_strlen(char const (&)[SizeOfString]) {
|
||||||
return N - 1;
|
return SizeOfString - 1; // Don't count the ending '\0'
|
||||||
}
|
}
|
||||||
|
|
||||||
// For ad-hoc RAII in place of a `defer` statement or cross-platform `__attribute__((cleanup))`
|
// For ad-hoc RAII in place of a `defer` statement or cross-platform `__attribute__((cleanup))`
|
||||||
template<typename T>
|
template<typename DeferredFnT>
|
||||||
struct Defer {
|
struct Defer {
|
||||||
T deferred;
|
DeferredFnT deferred;
|
||||||
Defer(T func) : deferred(func) {}
|
Defer(DeferredFnT func) : deferred(func) {}
|
||||||
~Defer() { deferred(); }
|
~Defer() { deferred(); }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -12,9 +12,31 @@
|
|||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <utility>
|
#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 {
|
class InsertionOrderedMap {
|
||||||
std::deque<T> list;
|
std::deque<ItemT> list;
|
||||||
std::unordered_map<std::string, size_t> map; // Indexes into `list`
|
std::unordered_map<std::string, size_t> map; // Indexes into `list`
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@@ -24,25 +46,25 @@ public:
|
|||||||
|
|
||||||
bool contains(std::string const &name) const { return map.find(name) != map.end(); }
|
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 begin() { return list.begin(); }
|
||||||
typename decltype(list)::iterator end() { return list.end(); }
|
typename decltype(list)::iterator end() { return list.end(); }
|
||||||
typename decltype(list)::const_iterator begin() const { return list.begin(); }
|
typename decltype(list)::const_iterator begin() const { return list.begin(); }
|
||||||
typename decltype(list)::const_iterator end() const { return list.end(); }
|
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();
|
map[name] = list.size();
|
||||||
return list.emplace_back();
|
return list.emplace_back();
|
||||||
}
|
}
|
||||||
|
|
||||||
T &add(std::string const &name, T &&value) {
|
ItemT &add(std::string const &name, ItemT &&value) {
|
||||||
map[name] = list.size();
|
map[name] = list.size();
|
||||||
list.emplace_back(std::move(value));
|
list.emplace_back(std::move(value));
|
||||||
return list.back();
|
return list.back();
|
||||||
}
|
}
|
||||||
|
|
||||||
T &addAnonymous() {
|
ItemT &addAnonymous() {
|
||||||
// Add the new item to the list, but do not update the map
|
// Add the new item to the list, but do not update the map
|
||||||
return list.emplace_back();
|
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 {
|
class EnumSeq {
|
||||||
T _start;
|
EnumT _start;
|
||||||
T _stop;
|
EnumT _stop;
|
||||||
|
|
||||||
class Iterator {
|
class Iterator {
|
||||||
T _value;
|
EnumT _value;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit Iterator(T value) : _value(value) {}
|
explicit Iterator(EnumT value) : _value(value) {}
|
||||||
|
|
||||||
Iterator &operator++() {
|
Iterator &operator++() {
|
||||||
_value = static_cast<T>(_value + 1);
|
_value = static_cast<EnumT>(_value + 1);
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
T operator*() const { return _value; }
|
EnumT operator*() const { return _value; }
|
||||||
|
|
||||||
bool operator==(Iterator const &rhs) const { return _value == rhs._value; }
|
bool operator==(Iterator const &rhs) const { return _value == rhs._value; }
|
||||||
};
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit EnumSeq(T stop) : _start(static_cast<T>(0)), _stop(stop) {}
|
explicit EnumSeq(EnumT stop) : _start(static_cast<EnumT>(0)), _stop(stop) {}
|
||||||
explicit EnumSeq(T start, T stop) : _start(start), _stop(stop) {}
|
explicit EnumSeq(EnumT start, EnumT stop) : _start(start), _stop(stop) {}
|
||||||
|
|
||||||
Iterator begin() { return Iterator(_start); }
|
Iterator begin() { return Iterator(_start); }
|
||||||
Iterator end() { return Iterator(_stop); }
|
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.
|
// 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.
|
// We also assume that all iterators have the same length.
|
||||||
template<typename... Ts>
|
template<typename... IteratorTs>
|
||||||
class ZipIterator {
|
class ZipIterator {
|
||||||
std::tuple<Ts...> _iters;
|
std::tuple<IteratorTs...> _iters;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit ZipIterator(std::tuple<Ts...> &&iters) : _iters(iters) {}
|
explicit ZipIterator(std::tuple<IteratorTs...> &&iters) : _iters(iters) {}
|
||||||
|
|
||||||
ZipIterator &operator++() {
|
ZipIterator &operator++() {
|
||||||
std::apply([](auto &&...it) { (++it, ...); }, _iters);
|
std::apply([](auto &&...it) { (++it, ...); }, _iters);
|
||||||
@@ -109,12 +133,14 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename... Ts>
|
// Only needed inside `zip` below.
|
||||||
|
template<typename... IterableTs>
|
||||||
class ZipContainer {
|
class ZipContainer {
|
||||||
std::tuple<Ts...> _containers;
|
std::tuple<IterableTs...> _containers;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit ZipContainer(Ts &&...containers) : _containers(std::forward<Ts>(containers)...) {}
|
explicit ZipContainer(IterableTs &&...containers)
|
||||||
|
: _containers(std::forward<IterableTs>(containers)...) {}
|
||||||
|
|
||||||
auto begin() {
|
auto begin() {
|
||||||
return ZipIterator(std::apply(
|
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
|
// Take ownership of objects and rvalue refs passed to us, but not lvalue refs
|
||||||
template<typename T>
|
template<typename IterableT>
|
||||||
using Holder = std::
|
using ZipHolder = std::conditional_t<
|
||||||
conditional_t<std::is_lvalue_reference_v<T>, T, std::remove_cv_t<std::remove_reference_t<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!
|
// Does the same number of iterations as the first container's iterator!
|
||||||
template<typename... Ts>
|
template<typename... IterableTs>
|
||||||
static constexpr auto zip(Ts &&...cs) {
|
static constexpr auto zip(IterableTs &&...containers) {
|
||||||
return ZipContainer<Holder<Ts>...>(std::forward<Ts>(cs)...);
|
return ZipContainer<ZipHolder<IterableTs>...>(std::forward<IterableTs>(containers)...);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // RGBDS_ITERTOOLS_HPP
|
#endif // RGBDS_ITERTOOLS_HPP
|
||||||
|
|||||||
@@ -10,6 +10,6 @@ void lexer_TraceCurrent();
|
|||||||
void lexer_IncludeFile(std::string &&path);
|
void lexer_IncludeFile(std::string &&path);
|
||||||
void lexer_IncLineNo();
|
void lexer_IncLineNo();
|
||||||
|
|
||||||
bool lexer_Init(char const *linkerScriptName);
|
bool lexer_Init(std::string const &linkerScriptName);
|
||||||
|
|
||||||
#endif // RGBDS_LINK_LEXER_HPP
|
#endif // RGBDS_LINK_LEXER_HPP
|
||||||
|
|||||||
@@ -3,16 +3,18 @@
|
|||||||
#ifndef RGBDS_LINK_MAIN_HPP
|
#ifndef RGBDS_LINK_MAIN_HPP
|
||||||
#define RGBDS_LINK_MAIN_HPP
|
#define RGBDS_LINK_MAIN_HPP
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
struct Options {
|
struct Options {
|
||||||
bool isDmgMode; // -d
|
bool isDmgMode; // -d
|
||||||
char const *mapFileName; // -m
|
std::optional<std::string> mapFileName; // -m
|
||||||
bool noSymInMap; // -M
|
bool noSymInMap; // -M
|
||||||
char const *symFileName; // -n
|
std::optional<std::string> symFileName; // -n
|
||||||
char const *overlayFileName; // -O
|
std::optional<std::string> overlayFileName; // -O
|
||||||
char const *outputFileName; // -o
|
std::optional<std::string> outputFileName; // -o
|
||||||
uint8_t padValue; // -p
|
uint8_t padValue; // -p
|
||||||
bool hasPadValue = false;
|
bool hasPadValue = false;
|
||||||
// Setting these three to 0 disables the functionality
|
// Setting these three to 0 disables the functionality
|
||||||
uint16_t scrambleROMX; // -S
|
uint16_t scrambleROMX; // -S
|
||||||
|
|||||||
@@ -3,10 +3,13 @@
|
|||||||
#ifndef RGBDS_LINK_OBJECT_HPP
|
#ifndef RGBDS_LINK_OBJECT_HPP
|
||||||
#define 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.
|
// 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
|
// Sets up object file reading
|
||||||
void obj_Setup(unsigned int nbFiles);
|
void obj_Setup(size_t nbFiles);
|
||||||
|
|
||||||
#endif // RGBDS_LINK_OBJECT_HPP
|
#endif // RGBDS_LINK_OBJECT_HPP
|
||||||
|
|||||||
@@ -49,6 +49,39 @@ struct Section {
|
|||||||
std::vector<Symbol> *fileSymbols;
|
std::vector<Symbol> *fileSymbols;
|
||||||
std::vector<Symbol *> symbols;
|
std::vector<Symbol *> symbols;
|
||||||
std::unique_ptr<Section> nextPiece; // The next fragment or union "piece" of this section
|
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.
|
// Execute a callback for each section currently registered.
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
#define RGBDS_UTIL_HPP
|
#define RGBDS_UTIL_HPP
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <ctype.h> // toupper
|
|
||||||
#include <numeric>
|
#include <numeric>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
@@ -22,6 +21,7 @@ enum NumberBase {
|
|||||||
BASE_16 = 16,
|
BASE_16 = 16,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Locale-independent character class functions
|
||||||
bool isNewline(int c);
|
bool isNewline(int c);
|
||||||
bool isBlankSpace(int c);
|
bool isBlankSpace(int c);
|
||||||
bool isWhitespace(int c);
|
bool isWhitespace(int c);
|
||||||
@@ -35,6 +35,10 @@ bool isOctDigit(int c);
|
|||||||
bool isHexDigit(int c);
|
bool isHexDigit(int c);
|
||||||
bool isAlphanumeric(int c);
|
bool isAlphanumeric(int c);
|
||||||
|
|
||||||
|
// Locale-independent character transform functions
|
||||||
|
char toLower(char c);
|
||||||
|
char toUpper(char c);
|
||||||
|
|
||||||
bool startsIdentifier(int c);
|
bool startsIdentifier(int c);
|
||||||
bool continuesIdentifier(int c);
|
bool continuesIdentifier(int c);
|
||||||
|
|
||||||
@@ -48,19 +52,20 @@ struct Uppercase {
|
|||||||
// FNV-1a hash of an uppercased string
|
// FNV-1a hash of an uppercased string
|
||||||
constexpr size_t operator()(std::string const &str) const {
|
constexpr size_t operator()(std::string const &str) const {
|
||||||
return std::accumulate(RANGE(str), 0x811C9DC5, [](size_t hash, char c) {
|
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)
|
// Compare two strings without case-sensitivity (by converting to uppercase)
|
||||||
constexpr bool operator()(std::string const &str1, std::string const &str2) const {
|
constexpr bool operator()(std::string const &str1, std::string const &str2) const {
|
||||||
return std::equal(RANGE(str1), RANGE(str2), [](char c1, char c2) {
|
return std::equal(RANGE(str1), RANGE(str2), [](char c1, char c2) {
|
||||||
return toupper(c1) == toupper(c2);
|
return toUpper(c1) == toUpper(c2);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename T>
|
// An unordered map from case-insensitive `std::string` keys to `ItemT` items
|
||||||
using UpperMap = std::unordered_map<std::string, T, Uppercase, Uppercase>;
|
template<typename ItemT>
|
||||||
|
using UpperMap = std::unordered_map<std::string, ItemT, Uppercase, Uppercase>;
|
||||||
|
|
||||||
#endif // RGBDS_UTIL_HPP
|
#endif // RGBDS_UTIL_HPP
|
||||||
|
|||||||
@@ -3,17 +3,14 @@
|
|||||||
#ifndef RGBDS_VERBOSITY_HPP
|
#ifndef RGBDS_VERBOSITY_HPP
|
||||||
#define RGBDS_VERBOSITY_HPP
|
#define RGBDS_VERBOSITY_HPP
|
||||||
|
|
||||||
|
#include <stdarg.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
#include "style.hpp"
|
|
||||||
|
|
||||||
// This macro does not evaluate its arguments unless the condition is true.
|
// This macro does not evaluate its arguments unless the condition is true.
|
||||||
#define verbosePrint(level, ...) \
|
#define verbosePrint(level, ...) \
|
||||||
do { \
|
do { \
|
||||||
if (checkVerbosity(level)) { \
|
if (checkVerbosity(level)) { \
|
||||||
style_Set(stderr, STYLE_MAGENTA, false); \
|
printVerbosely(__VA_ARGS__); \
|
||||||
fprintf(stderr, __VA_ARGS__); \
|
|
||||||
style_Reset(stderr); \
|
|
||||||
} \
|
} \
|
||||||
} while (0)
|
} while (0)
|
||||||
|
|
||||||
@@ -30,6 +27,9 @@ enum Verbosity {
|
|||||||
void incrementVerbosity();
|
void incrementVerbosity();
|
||||||
bool checkVerbosity(Verbosity level);
|
bool checkVerbosity(Verbosity level);
|
||||||
|
|
||||||
|
[[gnu::format(printf, 1, 2)]]
|
||||||
|
void printVerbosely(char const *fmt, ...);
|
||||||
|
|
||||||
void printVVVVVVerbosity();
|
void printVVVVVVerbosity();
|
||||||
|
|
||||||
#endif // RGBDS_VERBOSITY_HPP
|
#endif // RGBDS_VERBOSITY_HPP
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
#define PACKAGE_VERSION_MAJOR 1
|
#define PACKAGE_VERSION_MAJOR 1
|
||||||
#define PACKAGE_VERSION_MINOR 0
|
#define PACKAGE_VERSION_MINOR 0
|
||||||
#define PACKAGE_VERSION_PATCH 0
|
#define PACKAGE_VERSION_PATCH 0
|
||||||
#define PACKAGE_VERSION_RC 2
|
// #define PACKAGE_VERSION_RC 1
|
||||||
|
|
||||||
char const *get_package_version_string();
|
char const *get_package_version_string();
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
.\" SPDX-License-Identifier: MIT
|
.\" SPDX-License-Identifier: MIT
|
||||||
.\"
|
.\"
|
||||||
.Dd September 30, 2025
|
.Dd October 31, 2025
|
||||||
.Dt GBZ80 7
|
.Dt GBZ80 7
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
.\"
|
.\"
|
||||||
.\" SPDX-License-Identifier: MIT
|
.\" SPDX-License-Identifier: MIT
|
||||||
.\"
|
.\"
|
||||||
.Dd September 30, 2025
|
.Dd October 31, 2025
|
||||||
.Dt RGBASM-OLD 5
|
.Dt RGBASM-OLD 5
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
@@ -461,24 +461,6 @@ Previously we had
|
|||||||
.Pp
|
.Pp
|
||||||
Instead, now we have
|
Instead, now we have
|
||||||
.Ql p ** q ** r == p ** (q ** r) .
|
.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
|
.Sh BUGS
|
||||||
These are misfeatures that may have been possible by mistake.
|
These are misfeatures that may have been possible by mistake.
|
||||||
They do not get deprecated, just fixed.
|
They do not get deprecated, just fixed.
|
||||||
|
|||||||
94
man/rgbasm.1
94
man/rgbasm.1
@@ -1,6 +1,6 @@
|
|||||||
.\" SPDX-License-Identifier: MIT
|
.\" SPDX-License-Identifier: MIT
|
||||||
.\"
|
.\"
|
||||||
.Dd September 30, 2025
|
.Dd October 31, 2025
|
||||||
.Dt RGBASM 1
|
.Dt RGBASM 1
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
@@ -36,14 +36,14 @@ The
|
|||||||
program creates an RGB object file from an assembly source file.
|
program creates an RGB object file from an assembly source file.
|
||||||
The object file format is documented in
|
The object file format is documented in
|
||||||
.Xr rgbds 5 .
|
.Xr rgbds 5 .
|
||||||
.Pp
|
.Sh ARGUMENTS
|
||||||
The input
|
.Nm
|
||||||
.Ar asmfile
|
accepts the usual short and long options, such as
|
||||||
can be a path to a file, or
|
.Fl V
|
||||||
.Cm \-
|
and
|
||||||
to read from standard input.
|
.Fl -version .
|
||||||
.Pp
|
Options later in the command line override those set earlier, except for when duplicate options are considered an error.
|
||||||
Note that options can be abbreviated as long as the abbreviation is unambiguous:
|
Options can be abbreviated as long as the abbreviation is unambiguous:
|
||||||
.Fl \-verb
|
.Fl \-verb
|
||||||
is
|
is
|
||||||
.Fl \-verbose ,
|
.Fl \-verbose ,
|
||||||
@@ -51,7 +51,52 @@ but
|
|||||||
.Fl \-ver
|
.Fl \-ver
|
||||||
is invalid because it could also be
|
is invalid because it could also be
|
||||||
.Fl \-version .
|
.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
|
.Bl -tag -width Ds
|
||||||
.It Fl B Ar param , Fl \-backtrace Ar param
|
.It Fl B Ar param , Fl \-backtrace Ar param
|
||||||
Configures how location backtraces are printed if warnings or errors occur.
|
Configures how location backtraces are printed if warnings or errors occur.
|
||||||
@@ -306,6 +351,23 @@ disables this behavior.
|
|||||||
The default is 100 if
|
The default is 100 if
|
||||||
.Nm
|
.Nm
|
||||||
is printing errors to a terminal, and 0 otherwise.
|
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
|
.El
|
||||||
.Sh DIAGNOSTICS
|
.Sh DIAGNOSTICS
|
||||||
Warnings are diagnostic messages that indicate possibly erroneous behavior that does not necessarily compromise the assembling process.
|
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
|
.Pp
|
||||||
The following warnings are actual warning flags; with each description, the corresponding warning flag is included.
|
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,
|
Note that each of these flags also has a negation (for example,
|
||||||
.Fl Wcharmap-redef
|
.Fl Wobsolete
|
||||||
enables the warning that
|
enables the warning that
|
||||||
.Fl Wno-charmap-redef
|
.Fl Wno-obsolete
|
||||||
disables; and
|
disables; and
|
||||||
.Fl Wall
|
.Fl Wall
|
||||||
enables every warning that
|
enables every warning that
|
||||||
@@ -441,10 +503,10 @@ or
|
|||||||
.Fl Wno-purge
|
.Fl Wno-purge
|
||||||
disables this warning.
|
disables this warning.
|
||||||
.Fl Wpurge=1
|
.Fl Wpurge=1
|
||||||
or just
|
|
||||||
.Fl Wpurge
|
|
||||||
warns when purging any exported symbol (regardless of type).
|
warns when purging any exported symbol (regardless of type).
|
||||||
.Fl Wpurge=2
|
.Fl Wpurge=2
|
||||||
|
or just
|
||||||
|
.Fl Wpurge
|
||||||
also warns when purging any label (even if not exported).
|
also warns when purging any label (even if not exported).
|
||||||
.It Fl Wshift
|
.It Fl Wshift
|
||||||
Warn when shifting right a negative value.
|
Warn when shifting right a negative value.
|
||||||
@@ -460,10 +522,10 @@ or
|
|||||||
.Fl Wno-truncation
|
.Fl Wno-truncation
|
||||||
disables this warning.
|
disables this warning.
|
||||||
.Fl Wtruncation=1
|
.Fl Wtruncation=1
|
||||||
warns when an N-bit value is 2**N or greater, or less than -2**N.
|
|
||||||
.Fl Wtruncation=2
|
|
||||||
or just
|
or just
|
||||||
.Fl Wtruncation
|
.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.
|
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=
|
.It Fl Wunmapped-char=
|
||||||
Warn when a character goes through charmap conversion but has no defined mapping.
|
Warn when a character goes through charmap conversion but has no defined mapping.
|
||||||
|
|||||||
229
man/rgbasm.5
229
man/rgbasm.5
@@ -2,7 +2,7 @@
|
|||||||
.\"
|
.\"
|
||||||
.\" SPDX-License-Identifier: MIT
|
.\" SPDX-License-Identifier: MIT
|
||||||
.\"
|
.\"
|
||||||
.Dd September 30, 2025
|
.Dd October 31, 2025
|
||||||
.Dt RGBASM 5
|
.Dt RGBASM 5
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
@@ -27,10 +27,13 @@ but any program that processes RGBDS object files (described in
|
|||||||
can be used in its place.
|
can be used in its place.
|
||||||
.Sh SYNTAX
|
.Sh SYNTAX
|
||||||
The syntax is line-based, just as in any other assembler.
|
The syntax is line-based, just as in any other assembler.
|
||||||
Each line may have components in this order:
|
Each line may have components in either of these orders:
|
||||||
.Pp
|
.Bl -bullet -offset indent
|
||||||
.Dl Oo Ar directive Oc Oo ;\ Ns Ar comment Oc
|
.It
|
||||||
.Dl Oo Ar label : Oc Oo Ar instruction Oo :: Ar instruction ... Oc Oc Oo ;\ Ns Ar comment Oc
|
.Li Oo Ar label : Oc Oo Ar directive Oc Oo ;\ Ns Ar comment Oc
|
||||||
|
.It
|
||||||
|
.Li Oo Ar label : Oc Oo Ar instruction Oo :: Ar instruction ... Oc Oc Oo ;\ Ns Ar comment Oc
|
||||||
|
.El
|
||||||
.Pp
|
.Pp
|
||||||
Directives are commands to the assembler itself, such as
|
Directives are commands to the assembler itself, such as
|
||||||
.Ic PRINTLN ,
|
.Ic PRINTLN ,
|
||||||
@@ -41,6 +44,17 @@ or
|
|||||||
Labels tie a name to a specific location within a section (see
|
Labels tie a name to a specific location within a section (see
|
||||||
.Sx Labels
|
.Sx Labels
|
||||||
below).
|
below).
|
||||||
|
Labels are allowed before most directives, but not before
|
||||||
|
.Ic IF ,
|
||||||
|
.Ic ELIF ,
|
||||||
|
.Ic ELSE ,
|
||||||
|
.Ic ENDC ,
|
||||||
|
.Ic REPT ,
|
||||||
|
.Ic FOR ,
|
||||||
|
.Ic ENDR ,
|
||||||
|
.Ic MACRO ,
|
||||||
|
or
|
||||||
|
.Ic ENDM .
|
||||||
.Pp
|
.Pp
|
||||||
Instructions are assembled into Game Boy opcodes.
|
Instructions are assembled into Game Boy opcodes.
|
||||||
Multiple instructions on one line, as well as data directives (see
|
Multiple instructions on one line, as well as data directives (see
|
||||||
@@ -84,8 +98,8 @@ as the opposite condition code; for example,
|
|||||||
for
|
for
|
||||||
.Ic z .
|
.Ic z .
|
||||||
.Pp
|
.Pp
|
||||||
All reserved keywords (directives, register names, etc.) are case-insensitive;
|
All reserved keywords (directives, instructions, registers, built-in functions, etc.) are case-insensitive;
|
||||||
all identifiers (labels and other symbol names) are case-sensitive.
|
all identifiers (labels, variables, etc) are case-sensitive.
|
||||||
.Pp
|
.Pp
|
||||||
Comments are used to give humans information about the code, such as explanations.
|
Comments are used to give humans information about the code, such as explanations.
|
||||||
The assembler
|
The assembler
|
||||||
@@ -124,17 +138,17 @@ To do so, put a backslash at the end of the line:
|
|||||||
world!"\ \ \ \ \ \ \ \ \ \ \ ;\ Any leading space is included
|
world!"\ \ \ \ \ \ \ \ \ \ \ ;\ Any leading space is included
|
||||||
.Ed
|
.Ed
|
||||||
.Ss Symbol interpolation
|
.Ss Symbol interpolation
|
||||||
A funky feature is writing a symbol between
|
Symbols with string or numeric values can be
|
||||||
.Ql {braces} ,
|
.Dq interpolated
|
||||||
called
|
by writing them inside
|
||||||
.Dq symbol interpolation .
|
.Ql {braces} .
|
||||||
This will paste the symbol's contents as if they were part of the source file.
|
This will paste the symbol's contents as if they were part of the source file.
|
||||||
If it is a string symbol, its characters are simply inserted as-is.
|
If it is a string symbol, its characters are simply inserted as-is.
|
||||||
If it is a numeric symbol, its value is converted to hexadecimal notation with a dollar sign
|
If it is a numeric symbol, its value is converted to hexadecimal notation with a dollar sign
|
||||||
.Sq $
|
.Sq $
|
||||||
prepended.
|
prepended.
|
||||||
.Pp
|
.Pp
|
||||||
Symbol interpolations can be nested, too!
|
Symbol interpolations can be nested, too.
|
||||||
.Bd -literal -offset indent
|
.Bd -literal -offset indent
|
||||||
DEF topic EQUS "life, the universe, and \e"everything\e""
|
DEF topic EQUS "life, the universe, and \e"everything\e""
|
||||||
DEF meaning EQUS "answer"
|
DEF meaning EQUS "answer"
|
||||||
@@ -145,27 +159,29 @@ PRINTLN "The {meaning} to {topic} is {{meaning}}"
|
|||||||
PURGE topic, meaning, {meaning}
|
PURGE topic, meaning, {meaning}
|
||||||
.Ed
|
.Ed
|
||||||
.Pp
|
.Pp
|
||||||
Symbols can be
|
Symbols can be interpolated even in contexts that disable automatic expansion of string constants: that is,
|
||||||
.Em interpolated
|
|
||||||
even in the contexts that disable automatic
|
|
||||||
.Em expansion
|
|
||||||
of string constants:
|
|
||||||
.Ql name
|
.Ql name
|
||||||
will be expanded in all of
|
will be expanded in all of
|
||||||
.Ql DEF({name}) ,
|
.Ql DEF({name}) ,
|
||||||
.Ql DEF {name} EQU/=/EQUS/etc ... ,
|
.Ql DEF {name} EQU/=/EQUS/etc ... ,
|
||||||
|
.Ql REDEF {name} EQU/=/EQUS/etc ... ,
|
||||||
|
.Ql FOR {name}, ... ,
|
||||||
.Ql PURGE {name} ,
|
.Ql PURGE {name} ,
|
||||||
and
|
and
|
||||||
.Ql MACRO {name} ,
|
.Ql MACRO {name} ,
|
||||||
but, for example, won't be in
|
even though it won't be in
|
||||||
.Ql DEF(name) .
|
.Ql DEF(name) ,
|
||||||
|
.Ql PURGE {name} ,
|
||||||
|
etc.
|
||||||
.Pp
|
.Pp
|
||||||
It's possible to change the way symbols are printed by specifying a print format like so:
|
It's possible to change the way symbols are printed by specifying a print format like so:
|
||||||
.Ql {fmt:symbol} .
|
.Ql {fmt:symbol} .
|
||||||
The
|
The
|
||||||
.Ql fmt
|
.Ql fmt
|
||||||
specifier consists of these parts:
|
specifier consists of parts, which must be in the following order:
|
||||||
.Ql <sign><exact><align><pad><width><frac><prec><type> .
|
.Ql <sign><exact><align><pad><width><frac><prec><type> .
|
||||||
|
All the parts are optional except the required
|
||||||
|
.Ql <type> .
|
||||||
These parts are:
|
These parts are:
|
||||||
.Bl -column "<exact>"
|
.Bl -column "<exact>"
|
||||||
.It Sy Part Ta Sy Meaning
|
.It Sy Part Ta Sy Meaning
|
||||||
@@ -175,14 +191,17 @@ or
|
|||||||
.Ql \ .
|
.Ql \ .
|
||||||
If specified, prints this character in front of non-negative numbers.
|
If specified, prints this character in front of non-negative numbers.
|
||||||
.It Ql <exact> Ta May be
|
.It Ql <exact> Ta May be
|
||||||
.Ql # .
|
.Ql #
|
||||||
If specified, prints the value in an "exact" format: with a base prefix for non-decimal integer types
|
.Pq only allowed for non-decimal types .
|
||||||
.Pq So $ Sc , So & Sc , or So % Sc ;
|
If specified, prints the value in an "exact" format: with a base prefix
|
||||||
|
.Pq So $ Sc , So & Sc , or So % Sc
|
||||||
|
for non-decimal integer types
|
||||||
|
.Pq So x Sc / So X Sc , So o Sc , or So b Sc ;
|
||||||
with a
|
with a
|
||||||
.Ql q
|
.Ql q
|
||||||
precision suffix for fixed-point numbers; or with
|
precision suffix for fixed-point numbers; or with
|
||||||
.Ql \e
|
.Ql \e
|
||||||
escape characters for strings.
|
escape characters (but no enclosing quotes) for strings.
|
||||||
.It Ql <align> Ta May be
|
.It Ql <align> Ta May be
|
||||||
.Ql - .
|
.Ql - .
|
||||||
If specified, aligns left instead of right.
|
If specified, aligns left instead of right.
|
||||||
@@ -208,7 +227,7 @@ followed by zero
|
|||||||
.Ql 0
|
.Ql 0
|
||||||
\[en]
|
\[en]
|
||||||
.Ql 9
|
.Ql 9
|
||||||
prints zero fractional digits.)
|
prints zero fractional digits and no decimal point.)
|
||||||
.It Ql <prec> Ta May be
|
.It Ql <prec> Ta May be
|
||||||
.Ql q
|
.Ql q
|
||||||
followed by one or more
|
followed by one or more
|
||||||
@@ -222,13 +241,11 @@ option.
|
|||||||
.It Ql <type> Ta Specifies the type of value.
|
.It Ql <type> Ta Specifies the type of value.
|
||||||
.El
|
.El
|
||||||
.Pp
|
.Pp
|
||||||
All the format specifier parts are optional except the
|
Valid types are:
|
||||||
.Ql <type> .
|
|
||||||
Valid print types are:
|
|
||||||
.Bl -column -offset indent "Type" "Lowercase hexadecimal" "Example"
|
.Bl -column -offset indent "Type" "Lowercase hexadecimal" "Example"
|
||||||
.It Sy Type Ta Sy Format Ta Sy Example
|
.It Sy Type Ta Sy Format Ta Sy Example
|
||||||
.It Ql d Ta Signed decimal Ta -42
|
.It Ql d Ta Signed decimal Ta -42
|
||||||
.It Ql u Ta Unsigned decimal Ta 42
|
.It Ql u Ta Unsigned decimal Ta 4294967254
|
||||||
.It Ql x Ta Lowercase hexadecimal Ta 2a
|
.It Ql x Ta Lowercase hexadecimal Ta 2a
|
||||||
.It Ql X Ta Uppercase hexadecimal Ta 2A
|
.It Ql X Ta Uppercase hexadecimal Ta 2A
|
||||||
.It Ql b Ta Binary Ta 101010
|
.It Ql b Ta Binary Ta 101010
|
||||||
@@ -264,9 +281,10 @@ would be more appropriate; see
|
|||||||
.Sx String expressions
|
.Sx String expressions
|
||||||
below.
|
below.
|
||||||
.Sh EXPRESSIONS
|
.Sh EXPRESSIONS
|
||||||
An expression can be composed of many things.
|
There are two types of expressions: numeric and string.
|
||||||
|
.Pp
|
||||||
Numeric expressions are always evaluated using signed 32-bit math.
|
Numeric expressions are always evaluated using signed 32-bit math.
|
||||||
Zero is considered to be the only "false" number, all non-zero numbers (including negative) are "true".
|
In Boolean logic contexts, zero is considered to be the only "false" number, and all non-zero numbers (including negative) are "true".
|
||||||
.Pp
|
.Pp
|
||||||
An expression is said to be "constant" if
|
An expression is said to be "constant" if
|
||||||
.Nm
|
.Nm
|
||||||
@@ -278,18 +296,21 @@ However, some operators can be constant even with non-constant operands, as expl
|
|||||||
.Sx Operators
|
.Sx Operators
|
||||||
below.
|
below.
|
||||||
.Pp
|
.Pp
|
||||||
The instructions in the macro-language generally require constant expressions.
|
Directives generally require constant expressions: for example,
|
||||||
.Ss Numeric formats
|
.Ic REPT
|
||||||
There are a number of numeric formats.
|
requires the number of repetitions to be known at assembly time.
|
||||||
.Bl -column -offset indent "Precise fixed-point" "Possible prefixes"
|
.Ss Numeric literals
|
||||||
.It Sy Format type Ta Sy Possible prefixes Ta Sy Accepted characters
|
.Nm
|
||||||
|
supports a variety of numeric literals.
|
||||||
|
.Bl -column -offset indent "Precise fixed-point" "Prefixes" "Accepted characters"
|
||||||
|
.It Sy Format type Ta Sy Prefixes Ta Sy Accepted characters
|
||||||
.It Decimal Ta none Ta 0123456789
|
.It Decimal Ta none Ta 0123456789
|
||||||
.It Hexadecimal Ta Li $ , 0x , 0X Ta 0123456789ABCDEF
|
.It Hexadecimal Ta Li $ , 0x , 0X Ta 0123456789ABCDEF
|
||||||
.It Octal Ta Li & , 0o , 0O Ta 01234567
|
.It Octal Ta Li & , 0o , 0O Ta 01234567
|
||||||
.It Binary Ta Li % , 0b , 0B Ta 01
|
.It Binary Ta Li % , 0b , 0B Ta 01
|
||||||
.It Fixed-point Ta none Ta 01234.56789
|
.It Fixed-point Ta none Ta 01234.56789
|
||||||
.It Precise fixed-point Ta none Ta 12.34q8
|
.It Precise fixed-point Ta none Ta 12.34q8
|
||||||
.It Character constant Ta none Ta 'ABYZ'
|
.It Character constant Ta none Ta 'A'
|
||||||
.It Game Boy graphics Ta Li \` Ta 0123
|
.It Game Boy graphics Ta Li \` Ta 0123
|
||||||
.El
|
.El
|
||||||
.Pp
|
.Pp
|
||||||
@@ -311,15 +332,14 @@ for information on charmaps, and
|
|||||||
.Sx String expressions
|
.Sx String expressions
|
||||||
for information on escape characters allowed in character constants.
|
for information on escape characters allowed in character constants.
|
||||||
.Pp
|
.Pp
|
||||||
The last one, Game Boy graphics, is quite interesting and useful.
|
The last one, Game Boy graphics, expects up to eight digits between 0 and 3, corresponding to pixels' two-bit shade values.
|
||||||
After the backtick, 8 digits between 0 and 3 are expected, corresponding to pixel values.
|
The resulting numeric value is the two bytes of tile data which would produce that row of pixels.
|
||||||
The resulting value is the two bytes of tile data that would produce that row of pixels.
|
|
||||||
For example,
|
For example,
|
||||||
.Sq \`01012323
|
.Sq \`01012323
|
||||||
is equivalent to
|
is equivalent to
|
||||||
.Sq $0F55 .
|
.Sq $0F55 .
|
||||||
.Pp
|
.Pp
|
||||||
You can also use symbols, which are implicitly replaced with their value.
|
In place of a numeric literal, you can also use a numeric symbol's name, which is implicitly replaced with its value.
|
||||||
.Ss Operators
|
.Ss Operators
|
||||||
You can use these operators in numeric expressions (listed from highest to lowest precedence):
|
You can use these operators in numeric expressions (listed from highest to lowest precedence):
|
||||||
.Bl -column -offset indent "!= == <= >= < >"
|
.Bl -column -offset indent "!= == <= >= < >"
|
||||||
@@ -327,8 +347,8 @@ You can use these operators in numeric expressions (listed from highest to lowes
|
|||||||
.It Li \&( \&) Ta Grouping
|
.It Li \&( \&) Ta Grouping
|
||||||
.It Li FUNC() Ta Built-in function call
|
.It Li FUNC() Ta Built-in function call
|
||||||
.It Li ** Ta Exponentiation
|
.It Li ** Ta Exponentiation
|
||||||
.It Li + - ~ \&! Ta Unary plus, minus (negation), complement (bitwise negation), and Boolean negation
|
.It Li + - ~ \&! Ta Unary plus, unary minus (negation), complement (bitwise negation), and Boolean negation
|
||||||
.It Li * / % Ta Multiplication, division, and modulo (remainder)
|
.It Li * / % Ta Multiplication, division (rounding down), and modulo (remainder)
|
||||||
.It Li << >> >>> Ta Bit shifts (left, sign-extended right, zero-extended right)
|
.It Li << >> >>> Ta Bit shifts (left, sign-extended right, zero-extended right)
|
||||||
.It Li & \&| ^ Ta Bitwise AND/OR/XOR
|
.It Li & \&| ^ Ta Bitwise AND/OR/XOR
|
||||||
.It Li + - Ta Addition and subtraction
|
.It Li + - Ta Addition and subtraction
|
||||||
@@ -396,7 +416,7 @@ with a non-zero constant as either operand will be constant 1, even if the other
|
|||||||
returns 1 if the operand was 0, and 0 otherwise.
|
returns 1 if the operand was 0, and 0 otherwise.
|
||||||
Even a non-constant operand with any non-zero bits will return 0.
|
Even a non-constant operand with any non-zero bits will return 0.
|
||||||
.Ss Integer functions
|
.Ss Integer functions
|
||||||
Besides operators, there are also some functions which have more specialized uses.
|
Besides operators, there are also some functions which have more specialized uses:
|
||||||
.Bl -column "BITWIDTH(n)"
|
.Bl -column "BITWIDTH(n)"
|
||||||
.It Sy Name Ta Sy Operation
|
.It Sy Name Ta Sy Operation
|
||||||
.It Fn HIGH n Ta Equivalent to Ql Po Ns Ar n No & $FF00 Pc >> 8 .
|
.It Fn HIGH n Ta Equivalent to Ql Po Ns Ar n No & $FF00 Pc >> 8 .
|
||||||
@@ -408,12 +428,13 @@ delim $$
|
|||||||
.Ar n .
|
.Ar n .
|
||||||
Some useful formulas:
|
Some useful formulas:
|
||||||
.Ic BITWIDTH Ns ( Ar n Ns )\ \-\ 1
|
.Ic BITWIDTH Ns ( Ar n Ns )\ \-\ 1
|
||||||
equals $\[lf] log sub 2 ( n ) \[rf]$,
|
equals $\[lf] log sub 2 ( n ) \[rf]$;
|
||||||
.Ic BITWIDTH Ns Pq Ar n Ns \ \-\ 1
|
.Ic BITWIDTH Ns Pq Ar n Ns \ \-\ 1
|
||||||
equals $\[lc] log sub 2 ( n ) \[rc]$, and
|
equals $\[lc] log sub 2 ( n ) \[rc]$; and
|
||||||
.No 32\ \-\ Ns Ic BITWIDTH Ns Pq Ar n
|
.No 32\ \-\ Ns Ic BITWIDTH Ns Pq Ar n
|
||||||
equals $roman clz ( n )$.
|
equals $roman clz ( n )$, the count of leading zero bits in the binary representation of
|
||||||
.It Fn TZCOUNT n Ta Returns $roman ctz ( n )$, the count of trailing zero bits at the end of the binary representation of
|
.Ar n .
|
||||||
|
.It Fn TZCOUNT n Ta Returns $roman ctz ( n )$, the count of trailing zero bits in the binary representation of
|
||||||
.Ar n .
|
.Ar n .
|
||||||
.El
|
.El
|
||||||
.EQ
|
.EQ
|
||||||
@@ -434,21 +455,25 @@ command-line option, and/or by
|
|||||||
An individual fixed-point literal can specify its own precision, overriding the current default, by appending a
|
An individual fixed-point literal can specify its own precision, overriding the current default, by appending a
|
||||||
.Dq q
|
.Dq q
|
||||||
followed by the number of fractional bits: for example,
|
followed by the number of fractional bits: for example,
|
||||||
.Ql 1234.5q8
|
.Ql 789.25q8
|
||||||
is equal to $0004d2_80
|
is equal to $000315_40
|
||||||
.EQ
|
.EQ
|
||||||
delim $$
|
delim $$
|
||||||
.EN
|
.EN
|
||||||
($= 1234.5 * 2 sup 8$).
|
($= 789.25 * 2 sup 8$).
|
||||||
.Pp
|
.Pp
|
||||||
Since fixed-point values are still just integers, you can use them in normal integer expressions.
|
Since fixed-point values are still just integers, you can use them in normal integer expressions.
|
||||||
You can easily truncate a fixed-point number into an integer by shifting it right by the number of fractional bits.
|
You can easily truncate a fixed-point number into an integer by shifting it right by the number of fractional bits, or by dividing it by 1.0.
|
||||||
It follows that you can convert an integer to a fixed-point number by shifting it left that same amount.
|
It follows that you can convert an integer to a fixed-point number by shifting it left that same amount, or by multiplying it by 1.0.
|
||||||
|
For example,
|
||||||
|
.Ql 123.0 / 1.0 == 123 ,
|
||||||
|
and
|
||||||
|
.Ql 123 * 1.0 == 123.0 .
|
||||||
.Pp
|
.Pp
|
||||||
Note that the current number of fractional bits can be computed as
|
Note that the current number of fractional bits can be computed as
|
||||||
.Ic TZCOUNT Ns Pq 1.0 .
|
.Ic TZCOUNT Ns Pq 1.0 .
|
||||||
.Pp
|
.Pp
|
||||||
The following functions are designed to operate with fixed-point numbers:
|
The following functions are designed to operate with fixed-point numbers (which must be known constant):
|
||||||
.Bl -column -offset indent "ATAN2(y, x)"
|
.Bl -column -offset indent "ATAN2(y, x)"
|
||||||
.It Sy Name Ta Sy Operation
|
.It Sy Name Ta Sy Operation
|
||||||
.It Fn DIV x y Ta Fixed-point division
|
.It Fn DIV x y Ta Fixed-point division
|
||||||
@@ -456,7 +481,7 @@ The following functions are designed to operate with fixed-point numbers:
|
|||||||
.It Fn FMOD x y Ta Fixed-point modulo
|
.It Fn FMOD x y Ta Fixed-point modulo
|
||||||
.It Fn POW x y Ta $x sup y$
|
.It Fn POW x y Ta $x sup y$
|
||||||
.It Fn LOG x y Ta Logarithm of $x$ to the base $y$
|
.It Fn LOG x y Ta Logarithm of $x$ to the base $y$
|
||||||
.It Fn ROUND x Ta Round $x$ to the nearest integer
|
.It Fn ROUND x Ta Round $x$ half away from zero to the nearest integer
|
||||||
.It Fn CEIL x Ta Round $x$ up to the nearest integer
|
.It Fn CEIL x Ta Round $x$ up to the nearest integer
|
||||||
.It Fn FLOOR x Ta Round $x$ down to the nearest integer
|
.It Fn FLOOR x Ta Round $x$ down to the nearest integer
|
||||||
.It Fn SIN x Ta Sine of $x$
|
.It Fn SIN x Ta Sine of $x$
|
||||||
@@ -507,9 +532,7 @@ will produce a nonsensical (but technically correct) result:
|
|||||||
The
|
The
|
||||||
.Ic FMOD
|
.Ic FMOD
|
||||||
function
|
function
|
||||||
is used to get the remainder of the corresponding fixed-point division, so that
|
is used to get the remainder of the corresponding fixed-point division.
|
||||||
.Ql MUL(DIV(x, y), y) + FMOD(x, y) == x
|
|
||||||
is always true.
|
|
||||||
The result has the same sign as the
|
The result has the same sign as the
|
||||||
.Em dividend ;
|
.Em dividend ;
|
||||||
this is the opposite of how the integer modulo operator
|
this is the opposite of how the integer modulo operator
|
||||||
@@ -532,14 +555,15 @@ These functions are useful for automatic generation of various tables.
|
|||||||
For example:
|
For example:
|
||||||
.Bd -literal -offset indent
|
.Bd -literal -offset indent
|
||||||
; Generate a table of 128 sine values
|
; Generate a table of 128 sine values
|
||||||
; from sin(0.0) to sin(0.5) excluded,
|
; from sin(0.0) included to sin(0.5) excluded,
|
||||||
; with amplitude scaled from [-1.0, 1.0] to [0.0, 128.0].
|
; with amplitude scaled from [-1.0, 1.0] to [0.0, 128.0],
|
||||||
|
; then divided by 1.0 to round down to integer values.
|
||||||
FOR angle, 0.0, 0.5, 0.5 / 128
|
FOR angle, 0.0, 0.5, 0.5 / 128
|
||||||
db MUL(SIN(angle) + 1.0, 128.0 / 2) >> 16
|
db MUL(SIN(angle) + 1.0, 128.0 / 2) / 1.0
|
||||||
ENDR
|
ENDR
|
||||||
.Ed
|
.Ed
|
||||||
.Ss String expressions
|
.Ss String expressions
|
||||||
The most basic string expression is any number of characters contained in double quotes
|
The most basic string expression is a string literal: any number of characters contained in double quotes
|
||||||
.Pq Ql \&"for instance" .
|
.Pq Ql \&"for instance" .
|
||||||
The backslash character
|
The backslash character
|
||||||
.Ql \e
|
.Ql \e
|
||||||
@@ -560,14 +584,14 @@ There are a number of escape sequences you can use within a string:
|
|||||||
.It Ql \e0 Ta Null Pq ASCII $00
|
.It Ql \e0 Ta Null Pq ASCII $00
|
||||||
.El
|
.El
|
||||||
.Pp
|
.Pp
|
||||||
Multi-line strings are contained in triple quotes
|
Multi-line string literals are contained in triple quotes
|
||||||
.Pq Ql \&"\&"\&"for instance""" .
|
.Pq Ql \&"\&"\&"for instance""" .
|
||||||
Escape sequences work the same way in multi-line strings; however, literal newline characters will be included as-is, without needing to escape them with
|
Escape sequences work the same way in multi-line strings; however, literal newline characters will be included as-is, without needing to escape them with
|
||||||
.Ql \er
|
.Ql \er
|
||||||
or
|
or
|
||||||
.Ql \en .
|
.Ql \en .
|
||||||
.Pp
|
.Pp
|
||||||
Raw strings are prefixed by a hash
|
Raw string literals are prefixed by a hash
|
||||||
.Sq # .
|
.Sq # .
|
||||||
Inside them, backslashes and braces are treated like regular characters, so they will not be expanded as macro arguments, interpolated symbols, or escape sequences.
|
Inside them, backslashes and braces are treated like regular characters, so they will not be expanded as macro arguments, interpolated symbols, or escape sequences.
|
||||||
For example, the raw string
|
For example, the raw string
|
||||||
@@ -599,7 +623,7 @@ and
|
|||||||
is equivalent to
|
is equivalent to
|
||||||
.Ql STRCMP("str", \&"ing") != 0 .
|
.Ql STRCMP("str", \&"ing") != 0 .
|
||||||
.Pp
|
.Pp
|
||||||
The following functions operate on string expressions, and return strings themselves.
|
The following functions operate on string expressions, and return strings themselves:
|
||||||
.Bl -column "STRSLICE(str, start, stop)"
|
.Bl -column "STRSLICE(str, start, stop)"
|
||||||
.It Sy Name Ta Sy Operation
|
.It Sy Name Ta Sy Operation
|
||||||
.It Fn STRCAT strs... Ta Concatenates Ar strs .
|
.It Fn STRCAT strs... Ta Concatenates Ar strs .
|
||||||
@@ -610,7 +634,7 @@ in uppercase.
|
|||||||
.Pq Ql A-Z
|
.Pq Ql A-Z
|
||||||
in lowercase.
|
in lowercase.
|
||||||
.It Fn STRSLICE str start stop Ta Returns a substring of Ar str No starting at Ar start No and ending at Ar stop No (exclusive). If Ar stop No is not specified, the substring continues to the end of Ar str .
|
.It Fn STRSLICE str start stop Ta Returns a substring of Ar str No starting at Ar start No and ending at Ar stop No (exclusive). If Ar stop No is not specified, the substring continues to the end of Ar str .
|
||||||
.It Fn STRRPL str old new Ta Returns Ar str No with each non-overlapping occurrence of the substring Ar old No replaced with Ar new .
|
.It Fn STRRPL str old new Ta Returns Ar str No with each occurrence of the substring Ar old No replaced with Ar new .
|
||||||
.It Fn STRFMT fmt args... Ta Returns the string Ar fmt No with each
|
.It Fn STRFMT fmt args... Ta Returns the string Ar fmt No with each
|
||||||
.Ql %spec
|
.Ql %spec
|
||||||
pattern replaced by interpolating the format
|
pattern replaced by interpolating the format
|
||||||
@@ -620,11 +644,15 @@ with its corresponding argument in
|
|||||||
.Ar args
|
.Ar args
|
||||||
.Pq So %% Sc is replaced by the So % Sc character .
|
.Pq So %% Sc is replaced by the So % Sc character .
|
||||||
.It Fn STRCHAR str idx Ta Returns the substring of Ar str No for the charmap entry at Ar idx No with the current charmap . Pq Ar idx No counts charmap entries, not characters.
|
.It Fn STRCHAR str idx Ta Returns the substring of Ar str No for the charmap entry at Ar idx No with the current charmap . Pq Ar idx No counts charmap entries, not characters.
|
||||||
|
.El
|
||||||
|
.Pp
|
||||||
|
The following functions take varying operands, and return strings:
|
||||||
|
.Bl -column "READFILE(name, max)"
|
||||||
.It Fn REVCHAR vals... Ta Returns the string that is mapped to Ar vals No with the current charmap. If there is no unique charmap entry for Ar vals Ns , an error occurs.
|
.It Fn REVCHAR vals... Ta Returns the string that is mapped to Ar vals No with the current charmap. If there is no unique charmap entry for Ar vals Ns , an error occurs.
|
||||||
.It Fn READFILE name max Ta Returns the contents of the file Ar name No as a string. Reads up to Ar max No bytes, or the entire contents if Ar max No is not specified. If the file isn't found in the current directory, the include-path list passed to Xr rgbasm 1 Ap s Fl I No option on the command line will be searched.
|
.It Fn READFILE name max Ta Returns the contents of the file Ar name No as a string. Reads up to Ar max No bytes, or the entire contents if Ar max No is not specified. If the file isn't found in the current directory, the include-path list passed to Xr rgbasm 1 Ap s Fl I No option on the command line will be searched.
|
||||||
.El
|
.El
|
||||||
.Pp
|
.Pp
|
||||||
The following functions operate on string expressions, but return integers.
|
The following functions operate on string expressions, but return integers:
|
||||||
.Bl -column "STRRFIND(str, sub)"
|
.Bl -column "STRRFIND(str, sub)"
|
||||||
.It Sy Name Ta Sy Operation
|
.It Sy Name Ta Sy Operation
|
||||||
.It Fn STRLEN str Ta Returns the number of characters in Ar str .
|
.It Fn STRLEN str Ta Returns the number of characters in Ar str .
|
||||||
@@ -668,7 +696,8 @@ and
|
|||||||
being equivalent to
|
being equivalent to
|
||||||
.Ql dw 50, 53, $20ac .
|
.Ql dw 50, 53, $20ac .
|
||||||
.Pp
|
.Pp
|
||||||
Any characters in a string without defined mappings will be copied directly, using the source file's encoding of characters to bytes.
|
Character mappings are matched greedily, so the longest applicable one will be mapped in a string.
|
||||||
|
Any characters in the string without defined mappings will be copied directly, using the source file's encoding of characters to bytes.
|
||||||
.Pp
|
.Pp
|
||||||
It is possible to create multiple character maps and then switch between them as desired.
|
It is possible to create multiple character maps and then switch between them as desired.
|
||||||
This can be used to encode debug information in ASCII and use a different encoding for other purposes, for example.
|
This can be used to encode debug information in ASCII and use a different encoding for other purposes, for example.
|
||||||
@@ -741,7 +770,7 @@ The result is not constant, since only RGBLINK can compute its value.
|
|||||||
.El
|
.El
|
||||||
.Sh SECTIONS
|
.Sh SECTIONS
|
||||||
Before you can start writing code, you must define a section.
|
Before you can start writing code, you must define a section.
|
||||||
This tells the assembler what kind of information follows and, if it is code, where to put it.
|
This tells the assembler what kind of information follows and where to put it.
|
||||||
.Pp
|
.Pp
|
||||||
.Dl SECTION Ar name , type
|
.Dl SECTION Ar name , type
|
||||||
.Dl SECTION Ar name , type , options
|
.Dl SECTION Ar name , type , options
|
||||||
@@ -749,9 +778,9 @@ This tells the assembler what kind of information follows and, if it is code, wh
|
|||||||
.Dl SECTION Ar name , type Ns Bo Ar addr Bc , Ar options
|
.Dl SECTION Ar name , type Ns Bo Ar addr Bc , Ar options
|
||||||
.Pp
|
.Pp
|
||||||
.Ar name
|
.Ar name
|
||||||
is a string enclosed in double quotes, and can be a new name or the name of an existing section.
|
is a string enclosed in double quotes, which is the name of the section.
|
||||||
If the type doesn't match, an error occurs.
|
If the type doesn't match, an error occurs.
|
||||||
All other sections must have a unique name, even in different source files, or the linker will treat it as an error.
|
Each section must have a unique name, even across different source files, or the linker will treat it as an error.
|
||||||
.Pp
|
.Pp
|
||||||
Possible section
|
Possible section
|
||||||
.Ar type Ns s
|
.Ar type Ns s
|
||||||
@@ -776,8 +805,6 @@ can range from
|
|||||||
.Ad $4000
|
.Ad $4000
|
||||||
to
|
to
|
||||||
.Ad $7FFF .
|
.Ad $7FFF .
|
||||||
.Ar bank
|
|
||||||
can range from 1 to 511.
|
|
||||||
Becomes an alias for
|
Becomes an alias for
|
||||||
.Ic ROM0
|
.Ic ROM0
|
||||||
if tiny ROM mode is enabled in the linker.
|
if tiny ROM mode is enabled in the linker.
|
||||||
@@ -797,8 +824,6 @@ can range from
|
|||||||
.Ad $A000
|
.Ad $A000
|
||||||
to
|
to
|
||||||
.Ad $BFFF .
|
.Ad $BFFF .
|
||||||
.Ar bank
|
|
||||||
can range from 0 to 15.
|
|
||||||
.It Ic WRAM0
|
.It Ic WRAM0
|
||||||
A general-purpose RAM section.
|
A general-purpose RAM section.
|
||||||
.Ar addr
|
.Ar addr
|
||||||
@@ -839,12 +864,18 @@ to
|
|||||||
.Ad $FFFE .
|
.Ad $FFFE .
|
||||||
.El
|
.El
|
||||||
.Pp
|
.Pp
|
||||||
Since RGBDS produces ROMs, code and data can only be placed in
|
RGBDS produces ROMs, which means that code and data can only be placed in
|
||||||
.Ic ROM0
|
.Ic ROM0
|
||||||
and
|
and
|
||||||
.Ic ROMX
|
.Ic ROMX
|
||||||
sections.
|
sections.
|
||||||
To put some in RAM, have it stored in ROM, and copy it to RAM.
|
The other RAM section types are for statically allocated labels.
|
||||||
|
If you need code or data in RAM, you will need to copy it from ROM to RAM yourself.
|
||||||
|
See
|
||||||
|
.Sx RAM code
|
||||||
|
for an example of how to conveniently do that with a
|
||||||
|
.Ic LOAD
|
||||||
|
block.
|
||||||
.Pp
|
.Pp
|
||||||
.Ar option Ns s are comma-separated and may include:
|
.Ar option Ns s are comma-separated and may include:
|
||||||
.Bl -tag -width Ds
|
.Bl -tag -width Ds
|
||||||
@@ -901,7 +932,7 @@ creating it if it doesn't already exist.
|
|||||||
It can end up in any ROM bank.
|
It can end up in any ROM bank.
|
||||||
Code and data may follow.
|
Code and data may follow.
|
||||||
.It
|
.It
|
||||||
If it is needed, the the base address of the section can be specified:
|
If it is needed, the base address of the section can be specified:
|
||||||
.Bd -literal -offset indent
|
.Bd -literal -offset indent
|
||||||
SECTION "Cool Stuff", ROMX[$4567]
|
SECTION "Cool Stuff", ROMX[$4567]
|
||||||
.Ed
|
.Ed
|
||||||
@@ -956,13 +987,12 @@ Function:
|
|||||||
ld [wAnswer], a
|
ld [wAnswer], a
|
||||||
.Ed
|
.Ed
|
||||||
.Ss RAM code
|
.Ss RAM code
|
||||||
Sometimes you want to have some code in RAM.
|
Sometimes you want to have some code (or data) in RAM, e.g. for self-modifying code.
|
||||||
But then you can't simply put it in a RAM section, you have to store it in ROM and copy it to RAM at some point.
|
But you can't just put it directly in a RAM section; you have to store it in ROM and copy it to RAM at some point.
|
||||||
.Pp
|
This means that the code will be executed at a different address range than where it's defined, which can be inconvenient for references to labels within that code.
|
||||||
This means the code (or data) will not be stored in the place it gets executed.
|
This situation is what
|
||||||
Luckily,
|
|
||||||
.Ic LOAD
|
.Ic LOAD
|
||||||
blocks are the perfect solution to that.
|
blocks are designed for.
|
||||||
Here's an example of how to use them:
|
Here's an example of how to use them:
|
||||||
.Bd -literal -offset indent
|
.Bd -literal -offset indent
|
||||||
SECTION "LOAD example", ROMX
|
SECTION "LOAD example", ROMX
|
||||||
@@ -1047,7 +1077,7 @@ However, a
|
|||||||
.Ic UNION
|
.Ic UNION
|
||||||
only works within a single file, so it can't be used e.g. to define temporary variables across several files, all of which use the same statically allocated memory.
|
only works within a single file, so it can't be used e.g. to define temporary variables across several files, all of which use the same statically allocated memory.
|
||||||
Unionized sections solve this problem.
|
Unionized sections solve this problem.
|
||||||
To declare an unionized section, add a
|
To declare a unionized section, add a
|
||||||
.Ic UNION
|
.Ic UNION
|
||||||
keyword after the
|
keyword after the
|
||||||
.Ic SECTION
|
.Ic SECTION
|
||||||
@@ -1085,9 +1115,9 @@ Different declarations of the same unionized section are not appended, but inste
|
|||||||
.Sx Allocating overlapping spaces in RAM .
|
.Sx Allocating overlapping spaces in RAM .
|
||||||
Similarly, the size of an unionized section is the largest of all its declarations.
|
Similarly, the size of an unionized section is the largest of all its declarations.
|
||||||
.Ss Section fragments
|
.Ss Section fragments
|
||||||
Section fragments are sections with a small twist: when several of the same name are encountered, they are concatenated instead of producing an error.
|
Section fragments are sections with a small twist: when several fragments with the same name are encountered, they are concatenated into one section instead of producing an error, even across multiple object files.
|
||||||
This works within the same file (paralleling the behavior "plain" sections has in previous versions), but also across object files.
|
This works within the same file (paralleling the behavior "plain" sections has in previous versions), but also across object files.
|
||||||
To declare an section fragment, add a
|
To declare a section fragment, add a
|
||||||
.Ic FRAGMENT
|
.Ic FRAGMENT
|
||||||
keyword after the
|
keyword after the
|
||||||
.Ic SECTION
|
.Ic SECTION
|
||||||
@@ -1556,6 +1586,8 @@ in the C programming language.
|
|||||||
This expansion is disabled in a few contexts:
|
This expansion is disabled in a few contexts:
|
||||||
.Ql DEF(name) ,
|
.Ql DEF(name) ,
|
||||||
.Ql DEF name EQU/=/EQUS/etc ... ,
|
.Ql DEF name EQU/=/EQUS/etc ... ,
|
||||||
|
.Ql REDEF name EQU/=/EQUS/etc ... ,
|
||||||
|
.Ql FOR name, ... ,
|
||||||
.Ql PURGE name ,
|
.Ql PURGE name ,
|
||||||
and
|
and
|
||||||
.Ql MACRO name
|
.Ql MACRO name
|
||||||
@@ -1711,15 +1743,21 @@ Note also that only exported symbols will appear in symbol and map files produce
|
|||||||
.Ss Purging symbols
|
.Ss Purging symbols
|
||||||
.Ic PURGE
|
.Ic PURGE
|
||||||
allows you to completely remove a symbol from the symbol table, as if it had never been defined.
|
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
|
Be
|
||||||
.Em very
|
.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).
|
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).
|
||||||
.Bd -literal -offset indent
|
Purging labels at all is
|
||||||
DEF Kamikaze EQUS "I don't want to live anymore"
|
.Em not
|
||||||
AOLer: DB "Me too lol"
|
recommended.
|
||||||
PURGE Kamikaze, AOLer
|
|
||||||
ASSERT !DEF(Kamikaze) && !DEF(AOLer)
|
|
||||||
.Ed
|
|
||||||
.Pp
|
.Pp
|
||||||
String constants are not expanded within the symbol names.
|
String constants are not expanded within the symbol names.
|
||||||
.Ss Predeclared symbols
|
.Ss Predeclared symbols
|
||||||
@@ -1729,6 +1767,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 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 global label scope
|
||||||
.It Dv .. Ta Ic EQUS Ta The current local 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 _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 _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)
|
.It Dv __ISO_8601_LOCAL__ Ta Ic EQUS Ta ISO 8601 timestamp (local)
|
||||||
@@ -1929,7 +1968,7 @@ and
|
|||||||
.Sq wBonus .
|
.Sq wBonus .
|
||||||
Thus, keep in mind that
|
Thus, keep in mind that
|
||||||
.Ql ld [wHealth], a
|
.Ql ld [wHealth], a
|
||||||
assembles to the exact same thing as
|
assembles to the exact same instruction as
|
||||||
.Ql ld [wName], a .
|
.Ql ld [wName], a .
|
||||||
.Pp
|
.Pp
|
||||||
This whole union's total size is 20 bytes, the size of the largest block (the first one, containing
|
This whole union's total size is 20 bytes, the size of the largest block (the first one, containing
|
||||||
|
|||||||
@@ -1,18 +1,16 @@
|
|||||||
.\" SPDX-License-Identifier: MIT
|
.\" SPDX-License-Identifier: MIT
|
||||||
.\"
|
.\"
|
||||||
.Dd September 30, 2025
|
.Dd October 31, 2025
|
||||||
.Dt RGBDS 5
|
.Dt RGBDS 5
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
.Nm rgbds
|
.Nm rgbds
|
||||||
.Nd object file format documentation
|
.Nd object file format documentation
|
||||||
.Sh DESCRIPTION
|
.Sh DESCRIPTION
|
||||||
This is the description of the object files used by
|
This is the description of the RGB object file format that is output by
|
||||||
.Xr rgbasm 1
|
.Xr rgbasm 1
|
||||||
and
|
and read by
|
||||||
.Xr rgblink 1 .
|
.Xr rgblink 1 .
|
||||||
.Em Please note that the specification is not stable yet.
|
|
||||||
RGBDS is still in active development, and some new features require adding more information to the object file, or modifying some fields, both of which break compatibility with older versions.
|
|
||||||
.Sh FILE STRUCTURE
|
.Sh FILE STRUCTURE
|
||||||
The following types are used:
|
The following types are used:
|
||||||
.Pp
|
.Pp
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
.\" SPDX-License-Identifier: MIT
|
.\" SPDX-License-Identifier: MIT
|
||||||
.\"
|
.\"
|
||||||
.Dd September 30, 2025
|
.Dd October 31, 2025
|
||||||
.Dt RGBDS 7
|
.Dt RGBDS 7
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
@@ -74,4 +74,8 @@ organization.
|
|||||||
2022-05-17: The
|
2022-05-17: The
|
||||||
.Lk https://rgbds.gbdev.io rgbds.gbdev.io
|
.Lk https://rgbds.gbdev.io rgbds.gbdev.io
|
||||||
website for RGBDS documentation and downloads is published.
|
website for RGBDS documentation and downloads is published.
|
||||||
|
.It
|
||||||
|
2025-10-31: RGBDS reaches version 1.0.0 and starts adhering to
|
||||||
|
.Lk https://semver.org/ semantic versioning
|
||||||
|
("semver").
|
||||||
.El
|
.El
|
||||||
|
|||||||
101
man/rgbfix.1
101
man/rgbfix.1
@@ -1,6 +1,6 @@
|
|||||||
.\" SPDX-License-Identifier: MIT
|
.\" SPDX-License-Identifier: MIT
|
||||||
.\"
|
.\"
|
||||||
.Dd September 30, 2025
|
.Dd October 31, 2025
|
||||||
.Dt RGBFIX 1
|
.Dt RGBFIX 1
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
@@ -39,23 +39,67 @@ Developers are advised to fill those fields with 0x00 bytes in their source code
|
|||||||
.Nm ,
|
.Nm ,
|
||||||
and to have already populated whichever fields they don't specify using
|
and to have already populated whichever fields they don't specify using
|
||||||
.Nm .
|
.Nm .
|
||||||
.Pp
|
.Sh ARGUMENTS
|
||||||
The input
|
.Nm
|
||||||
.Ar file
|
accepts the usual short and long options, such as
|
||||||
can be a path to a file, or
|
.Fl V
|
||||||
.Cm \-
|
and
|
||||||
to read from standard input.
|
.Fl -version .
|
||||||
.Pp
|
Options later in the command line override those set earlier, except for when duplicate options are considered an error.
|
||||||
Note that options can be abbreviated as long as the abbreviation is unambiguous:
|
Options can be abbreviated as long as the abbreviation is unambiguous:
|
||||||
.Fl \-verb
|
|
||||||
is
|
|
||||||
.Fl \-verbose ,
|
|
||||||
but
|
|
||||||
.Fl \-ver
|
.Fl \-ver
|
||||||
|
is
|
||||||
|
.Fl \-version ,
|
||||||
|
but
|
||||||
|
.Fl \-v
|
||||||
is invalid because it could also be
|
is invalid because it could also be
|
||||||
.Fl \-version .
|
.Fl \-validate .
|
||||||
Options later in the command line override those set earlier.
|
.Pp
|
||||||
Accepted options are as follows:
|
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
|
.Bl -tag -width Ds
|
||||||
.It Fl C , Fl \-color-only
|
.It Fl C , Fl \-color-only
|
||||||
Set the Game Boy Color\(enonly flag
|
Set the Game Boy Color\(enonly flag
|
||||||
@@ -199,6 +243,23 @@ See the
|
|||||||
section for a list of warnings.
|
section for a list of warnings.
|
||||||
.It Fl w
|
.It Fl w
|
||||||
Disable all warning output, even when turned into errors.
|
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
|
.El
|
||||||
.Sh DIAGNOSTICS
|
.Sh DIAGNOSTICS
|
||||||
Warnings are diagnostic messages that indicate possibly erroneous behavior that does not necessarily compromise the header-fixing process.
|
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=
|
.It Fl Werror=
|
||||||
Make the specified warning or meta warning into an error.
|
Make the specified warning or meta warning into an error.
|
||||||
A warning's name is appended
|
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.
|
and this warning is implicitly enabled and turned into an error.
|
||||||
This can be negated as
|
This can be negated as
|
||||||
.Fl Wno-error=
|
.Fl Wno-error=
|
||||||
@@ -234,10 +295,10 @@ Enables literally every warning.
|
|||||||
.El
|
.El
|
||||||
.Pp
|
.Pp
|
||||||
The following warnings are actual warning flags; with each description, the corresponding warning flag is included.
|
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 Wtruncation
|
.Fl Wobsolete
|
||||||
enables the warning that
|
enables the warning that
|
||||||
.Fl Wno-truncation
|
.Fl Wno-obsolete
|
||||||
disables; and
|
disables; and
|
||||||
.Fl Wall
|
.Fl Wall
|
||||||
enables every warning that
|
enables every warning that
|
||||||
|
|||||||
144
man/rgbgfx.1
144
man/rgbgfx.1
@@ -2,7 +2,7 @@
|
|||||||
.\"
|
.\"
|
||||||
.\" SPDX-License-Identifier: MIT
|
.\" SPDX-License-Identifier: MIT
|
||||||
.\"
|
.\"
|
||||||
.Dd September 30, 2025
|
.Dd October 31, 2025
|
||||||
.Dt RGBGFX 1
|
.Dt RGBGFX 1
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
@@ -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.
|
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.
|
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
|
.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
|
.Fl \-verb
|
||||||
is
|
is
|
||||||
.Fl \-verbose ,
|
.Fl \-verbose ,
|
||||||
@@ -52,26 +58,6 @@ but
|
|||||||
is invalid because it could also be
|
is invalid because it could also be
|
||||||
.Fl \-version .
|
.Fl \-version .
|
||||||
.Pp
|
.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
|
Unless otherwise noted, passing
|
||||||
.Ql -
|
.Ql -
|
||||||
(a single dash) as a file name makes
|
(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
|
pass
|
||||||
.Ql ./-
|
.Ql ./-
|
||||||
instead.
|
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
|
.Pp
|
||||||
The following options are accepted:
|
The following options are accepted:
|
||||||
.Bl -tag -width Ds
|
.Bl -tag -width Ds
|
||||||
@@ -469,69 +487,53 @@ Implies
|
|||||||
.It Fl Z , Fl \-columns
|
.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).
|
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.
|
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
|
.El
|
||||||
.Ss At-files
|
.Ss At-files
|
||||||
In a given project, many images are to be converted with different flags.
|
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
|
.Pp
|
||||||
To avoid these drawbacks,
|
To avoid these drawbacks, you can use
|
||||||
.Nm
|
|
||||||
supports
|
|
||||||
.Dq at-files :
|
.Dq at-files :
|
||||||
any command-line argument that begins with an at sign
|
any command-line argument that begins with an at sign
|
||||||
.Pq Ql @
|
.Pq Ql @
|
||||||
is interpreted as one.
|
is interpreted as one, as documented above.
|
||||||
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.
|
|
||||||
At-files can be stored right next to the corresponding image, for example:
|
At-files can be stored right next to the corresponding image, for example:
|
||||||
.Pp
|
.Pp
|
||||||
.Dl $ rgbgfx -o image.2bpp -t image.tilemap @image.flags image.png
|
.Dl $ rgbgfx -o image.2bpp -t image.tilemap @image.flags image.png
|
||||||
.Pp
|
.Pp
|
||||||
This will read additional flags from file
|
This will read additional flags from the file
|
||||||
.Ql image.flags ,
|
.Ql image.flags ,
|
||||||
which could contains for example
|
which could contain, for example,
|
||||||
.Ql -b 128
|
.Ql -b 128
|
||||||
to specify a base offset for the image's tiles.
|
to specify a base offset for the image's tiles.
|
||||||
The above command could be generated from the following
|
The above command could be generated from the following
|
||||||
.Xr make 1
|
.Xr make 1
|
||||||
rule, for example:
|
rule:
|
||||||
.Bd -literal -offset indent
|
.Bd -literal -offset indent
|
||||||
%.2bpp %.tilemap: %.flags %.png
|
%.2bpp %.tilemap: %.flags %.png
|
||||||
rgbgfx -o $*.2bpp -t $*.tilemap @$*.flags $*.png
|
rgbgfx -o $*.2bpp -t $*.tilemap @$*.flags $*.png
|
||||||
.Ed
|
.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
|
.Sh PALETTE SPECIFICATION FORMATS
|
||||||
The following formats are supported:
|
The following formats are supported:
|
||||||
.Bl -tag -width Ds
|
.Bl -tag -width Ds
|
||||||
@@ -769,7 +771,7 @@ to prevent turning all warnings into errors.
|
|||||||
.It Fl Werror=
|
.It Fl Werror=
|
||||||
Make the specified warning or meta warning into an error.
|
Make the specified warning or meta warning into an error.
|
||||||
A warning's name is appended
|
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.
|
and this warning is implicitly enabled and turned into an error.
|
||||||
This can be negated as
|
This can be negated as
|
||||||
.Fl Wno-error=
|
.Fl Wno-error=
|
||||||
@@ -791,10 +793,10 @@ Enables literally every warning.
|
|||||||
.El
|
.El
|
||||||
.Pp
|
.Pp
|
||||||
The following warnings are actual warning flags; with each description, the corresponding warning flag is included.
|
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 Wtrim-nonempty
|
.Fl Wobsolete
|
||||||
enables the warning that
|
enables the warning that
|
||||||
.Fl Wno-trim-nonempty
|
.Fl Wno-obsolete
|
||||||
disables; and
|
disables; and
|
||||||
.Fl Wall
|
.Fl Wall
|
||||||
enables every warning that
|
enables every warning that
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
.\" SPDX-License-Identifier: MIT
|
.\" SPDX-License-Identifier: MIT
|
||||||
.\"
|
.\"
|
||||||
.Dd September 30, 2025
|
.Dd October 31, 2025
|
||||||
.Dt RGBLINK 1
|
.Dt RGBLINK 1
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
@@ -48,14 +48,14 @@ Also, if your ROM is designed for a monochrome Game Boy, you can make sure that
|
|||||||
option, which implies
|
option, which implies
|
||||||
.Fl w
|
.Fl w
|
||||||
but also prohibits the use of banked VRAM.
|
but also prohibits the use of banked VRAM.
|
||||||
.Pp
|
.Sh ARGUMENTS
|
||||||
The input
|
.Nm
|
||||||
.Ar file
|
accepts the usual short and long options, such as
|
||||||
can be a path to a file, or
|
.Fl V
|
||||||
.Cm \-
|
and
|
||||||
to read from standard input.
|
.Fl -version .
|
||||||
.Pp
|
Options later in the command line override those set earlier, except for when duplicate options are considered an error.
|
||||||
Note that options can be abbreviated as long as the abbreviation is unambiguous:
|
Options can be abbreviated as long as the abbreviation is unambiguous:
|
||||||
.Fl \-verb
|
.Fl \-verb
|
||||||
is
|
is
|
||||||
.Fl \-verbose ,
|
.Fl \-verbose ,
|
||||||
@@ -63,7 +63,52 @@ but
|
|||||||
.Fl \-ver
|
.Fl \-ver
|
||||||
is invalid because it could also be
|
is invalid because it could also be
|
||||||
.Fl \-version .
|
.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
|
.Bl -tag -width Ds
|
||||||
.It Fl B Ar param , Fl \-backtrace Ar param
|
.It Fl B Ar param , Fl \-backtrace Ar param
|
||||||
Configures how location backtraces are printed if warnings or errors occur.
|
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
|
When making a ROM, note that not using this is not a replacement for
|
||||||
.Xr rgbfix 1 Ap s Fl p
|
.Xr rgbfix 1 Ap s Fl p
|
||||||
option!
|
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
|
.El
|
||||||
.Ss Scrambling algorithm
|
.Ss Scrambling algorithm
|
||||||
The default section placement algorithm tries to place sections into as few banks as possible.
|
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=
|
.It Fl Werror=
|
||||||
Make the specified warning or meta warning into an error.
|
Make the specified warning or meta warning into an error.
|
||||||
A warning's name is appended
|
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.
|
and this warning is implicitly enabled and turned into an error.
|
||||||
This can be negated as
|
This can be negated as
|
||||||
.Fl Wno-error=
|
.Fl Wno-error=
|
||||||
@@ -280,7 +342,7 @@ Enables literally every warning.
|
|||||||
.El
|
.El
|
||||||
.Pp
|
.Pp
|
||||||
The following warnings are actual warning flags; with each description, the corresponding warning flag is included.
|
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
|
.Fl Wobsolete
|
||||||
enables the warning that
|
enables the warning that
|
||||||
.Fl Wno-obsolete
|
.Fl Wno-obsolete
|
||||||
@@ -327,10 +389,10 @@ or
|
|||||||
.Fl Wno-truncation
|
.Fl Wno-truncation
|
||||||
disables this warning.
|
disables this warning.
|
||||||
.Fl Wtruncation=1
|
.Fl Wtruncation=1
|
||||||
warns when an N-bit value is 2**N or greater, or less than -2**N.
|
|
||||||
.Fl Wtruncation=2
|
|
||||||
or just
|
or just
|
||||||
.Fl Wtruncation
|
.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.
|
also warns when an N-bit value is less than -2**(N-1), which will not fit in two's complement encoding.
|
||||||
.El
|
.El
|
||||||
.Sh EXAMPLES
|
.Sh EXAMPLES
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
.\" SPDX-License-Identifier: MIT
|
.\" SPDX-License-Identifier: MIT
|
||||||
.\"
|
.\"
|
||||||
.Dd September 30, 2025
|
.Dd October 31, 2025
|
||||||
.Dt RGBLINK 5
|
.Dt RGBLINK 5
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ configure_file(version.cpp _version.cpp ESCAPE_QUOTES)
|
|||||||
|
|
||||||
set(common_src
|
set(common_src
|
||||||
"extern/getopt.cpp"
|
"extern/getopt.cpp"
|
||||||
|
"cli.cpp"
|
||||||
"diagnostics.cpp"
|
"diagnostics.cpp"
|
||||||
"style.cpp"
|
"style.cpp"
|
||||||
"usage.cpp"
|
"usage.cpp"
|
||||||
@@ -92,6 +93,7 @@ set(rgbgfx_src
|
|||||||
"gfx/pal_packing.cpp"
|
"gfx/pal_packing.cpp"
|
||||||
"gfx/pal_sorting.cpp"
|
"gfx/pal_sorting.cpp"
|
||||||
"gfx/pal_spec.cpp"
|
"gfx/pal_spec.cpp"
|
||||||
|
"gfx/palette.cpp"
|
||||||
"gfx/png.cpp"
|
"gfx/png.cpp"
|
||||||
"gfx/process.cpp"
|
"gfx/process.cpp"
|
||||||
"gfx/reverse.cpp"
|
"gfx/reverse.cpp"
|
||||||
|
|||||||
@@ -38,8 +38,8 @@ struct Charmap {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Traverse the trie depth-first to derive the character mappings in definition order
|
// Traverse the trie depth-first to derive the character mappings in definition order
|
||||||
template<typename F>
|
template<typename CallbackFnT>
|
||||||
bool forEachChar(Charmap const &charmap, F callback) {
|
bool forEachChar(Charmap const &charmap, CallbackFnT callback) {
|
||||||
// clang-format off: nested initializers
|
// clang-format off: nested initializers
|
||||||
for (std::stack<std::pair<size_t, std::string>> prefixes({{0, ""}}); !prefixes.empty();) {
|
for (std::stack<std::pair<size_t, std::string>> prefixes({{0, ""}}); !prefixes.empty();) {
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
|
|
||||||
#include "backtrace.hpp"
|
#include "backtrace.hpp"
|
||||||
#include "helpers.hpp"
|
#include "helpers.hpp"
|
||||||
|
#include "itertools.hpp" // reversed
|
||||||
#include "linkdefs.hpp"
|
#include "linkdefs.hpp"
|
||||||
#include "platform.hpp" // strncasecmp
|
#include "platform.hpp" // strncasecmp
|
||||||
#include "verbosity.hpp"
|
#include "verbosity.hpp"
|
||||||
@@ -56,16 +57,6 @@ static std::vector<std::string> includePaths = {""}; // -I
|
|||||||
static std::deque<std::string> preIncludeNames; // -P
|
static std::deque<std::string> preIncludeNames; // -P
|
||||||
static bool failedOnMissingInclude = false;
|
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>;
|
using TraceNode = std::pair<std::string, uint32_t>;
|
||||||
|
|
||||||
static std::vector<TraceNode> backtrace(FileStackNode const &node, uint32_t curLineNo) {
|
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);
|
std::vector<TraceNode> traceNodes = backtrace(*node.parent, node.lineNo);
|
||||||
if (std::holds_alternative<std::vector<uint32_t>>(node.data)) {
|
if (std::holds_alternative<std::vector<uint32_t>>(node.data)) {
|
||||||
assume(!traceNodes.empty()); // REPT nodes use their parent's name
|
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 {
|
} else {
|
||||||
traceNodes.emplace_back(node.name(), curLineNo);
|
traceNodes.emplace_back(node.name(), curLineNo);
|
||||||
}
|
}
|
||||||
@@ -299,9 +295,13 @@ static void
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (macro.src->type == NODE_REPT) {
|
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);
|
fileInfoName.append(macro.name);
|
||||||
|
|
||||||
auto fileInfo = std::make_shared<FileStackNode>(NODE_MACRO, fileInfoName, isQuiet);
|
auto fileInfo = std::make_shared<FileStackNode>(NODE_MACRO, fileInfoName, isQuiet);
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <variant>
|
#include <variant>
|
||||||
@@ -56,7 +57,7 @@ struct Token {
|
|||||||
|
|
||||||
// This map lists all RGBASM keywords which `yylex_NORMAL` lexes as identifiers.
|
// This map lists all RGBASM keywords which `yylex_NORMAL` lexes as identifiers.
|
||||||
// All non-identifier tokens are lexed separately.
|
// All non-identifier tokens are lexed separately.
|
||||||
static UpperMap<int> const keywordDict{
|
static UpperMap<int> const keywords{
|
||||||
{"ADC", T_(SM83_ADC) },
|
{"ADC", T_(SM83_ADC) },
|
||||||
{"ADD", T_(SM83_ADD) },
|
{"ADD", T_(SM83_ADD) },
|
||||||
{"AND", T_(SM83_AND) },
|
{"AND", T_(SM83_AND) },
|
||||||
@@ -715,45 +716,44 @@ int LexerState::peekCharAhead() {
|
|||||||
static std::pair<Symbol const *, std::shared_ptr<std::string>> readInterpolation(size_t depth);
|
static std::pair<Symbol const *, std::shared_ptr<std::string>> readInterpolation(size_t depth);
|
||||||
|
|
||||||
static int peek() {
|
static int peek() {
|
||||||
int c = lexerState->peekChar();
|
for (;;) {
|
||||||
|
int c = lexerState->peekChar();
|
||||||
|
|
||||||
if (lexerState->expansionScanDistance > 0) {
|
if (lexerState->expansionScanDistance > 0) {
|
||||||
return c;
|
|
||||||
}
|
|
||||||
|
|
||||||
++lexerState->expansionScanDistance; // Do not consider again
|
|
||||||
|
|
||||||
if (lexerState->disableExpansions) {
|
|
||||||
return c;
|
|
||||||
} else if (c == '\\') {
|
|
||||||
// If character is a backslash, check for a macro arg
|
|
||||||
++lexerState->expansionScanDistance;
|
|
||||||
if (!isMacroChar(lexerState->peekCharAhead())) {
|
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If character is a macro arg char, do macro arg expansion
|
++lexerState->expansionScanDistance; // Do not consider again
|
||||||
shiftChar();
|
|
||||||
if (std::shared_ptr<std::string> str = readMacroArg(); str) {
|
|
||||||
beginExpansion(str, std::nullopt);
|
|
||||||
|
|
||||||
// Mark the entire macro arg expansion as "painted blue"
|
if (lexerState->disableExpansions) {
|
||||||
// so that macro args can't be recursive
|
return c;
|
||||||
// https://en.wikipedia.org/wiki/Painted_blue
|
} else if (c == '\\') {
|
||||||
lexerState->expansionScanDistance += str->length();
|
// If character is a backslash, check for a macro arg
|
||||||
|
++lexerState->expansionScanDistance;
|
||||||
|
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) {
|
||||||
|
beginExpansion(str, std::nullopt);
|
||||||
|
|
||||||
|
// Mark the entire macro arg expansion as "painted blue"
|
||||||
|
// so that macro args can't be recursive
|
||||||
|
// https://en.wikipedia.org/wiki/Painted_blue
|
||||||
|
lexerState->expansionScanDistance += str->length();
|
||||||
|
}
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
// Continue in the next iteration
|
||||||
|
} else {
|
||||||
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
return peek(); // Tail recursion
|
|
||||||
} 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
|
|
||||||
} else {
|
|
||||||
return c;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -815,8 +815,8 @@ static int nextChar() {
|
|||||||
return peek();
|
return peek();
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename P>
|
template<typename PredicateFnT>
|
||||||
static int skipChars(P predicate) {
|
static int skipChars(PredicateFnT predicate) {
|
||||||
int c = peek();
|
int c = peek();
|
||||||
while (predicate(c)) {
|
while (predicate(c)) {
|
||||||
c = nextChar();
|
c = nextChar();
|
||||||
@@ -1269,21 +1269,27 @@ static uint32_t readGfxConstant() {
|
|||||||
|
|
||||||
static Token readIdentifier(char firstChar, bool raw) {
|
static Token readIdentifier(char firstChar, bool raw) {
|
||||||
std::string identifier(1, firstChar);
|
std::string identifier(1, firstChar);
|
||||||
|
bool keywordBeforeLocal = false;
|
||||||
int tokenType = firstChar == '.' ? T_(LOCAL) : T_(SYMBOL);
|
int tokenType = firstChar == '.' ? T_(LOCAL) : T_(SYMBOL);
|
||||||
|
|
||||||
// Continue reading while the char is in the identifier charset
|
// Continue reading while the char is in the identifier charset
|
||||||
for (int c = peek(); continuesIdentifier(c); c = nextChar()) {
|
for (int c = peek(); continuesIdentifier(c); c = nextChar()) {
|
||||||
identifier += c;
|
|
||||||
|
|
||||||
// If the char was a dot, the identifier is a local label
|
// If the char was a dot, the identifier is a local label
|
||||||
if (c == '.') {
|
if (c == '.') {
|
||||||
|
// Check for a keyword before a non-raw local label
|
||||||
|
if (!raw && tokenType != T_(LOCAL) && keywords.find(identifier) != keywords.end()) {
|
||||||
|
keywordBeforeLocal = true;
|
||||||
|
}
|
||||||
|
|
||||||
tokenType = T_(LOCAL);
|
tokenType = T_(LOCAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
identifier += c;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attempt to check for a keyword if the identifier is not raw or a local label
|
// Check for a keyword if the identifier is not raw and not a local label
|
||||||
if (!raw && tokenType != T_(LOCAL)) {
|
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);
|
return Token(search->second);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1293,6 +1299,14 @@ static Token readIdentifier(char firstChar, bool raw) {
|
|||||||
tokenType = T_(SYMBOL);
|
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);
|
return Token(tokenType, identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1336,7 +1350,7 @@ static std::pair<Symbol const *, std::shared_ptr<std::string>> readInterpolation
|
|||||||
if (identifier.starts_with('#')) {
|
if (identifier.starts_with('#')) {
|
||||||
// Skip a '#' raw symbol prefix, but after expanding any nested interpolations.
|
// Skip a '#' raw symbol prefix, but after expanding any nested interpolations.
|
||||||
identifier.erase(0, 1);
|
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.
|
// Don't allow symbols that alias keywords without a '#' prefix.
|
||||||
error(
|
error(
|
||||||
"Interpolated symbol `%s` is a reserved keyword; add a '#' prefix to use it as a raw "
|
"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);
|
return Token(nextToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (;; lexerState->atLineStart = false) {
|
for (;;) {
|
||||||
int c = bumpChar();
|
int c = bumpChar();
|
||||||
|
|
||||||
switch (c) {
|
switch (c) {
|
||||||
@@ -1692,7 +1706,7 @@ static Token yylex_NORMAL() {
|
|||||||
|
|
||||||
case ' ':
|
case ' ':
|
||||||
case '\t':
|
case '\t':
|
||||||
continue;
|
break;
|
||||||
|
|
||||||
// Handle unambiguous single-char tokens
|
// Handle unambiguous single-char tokens
|
||||||
|
|
||||||
@@ -1744,7 +1758,7 @@ static Token yylex_NORMAL() {
|
|||||||
if (peek() == '*') {
|
if (peek() == '*') {
|
||||||
shiftChar();
|
shiftChar();
|
||||||
discardBlockComment();
|
discardBlockComment();
|
||||||
continue;
|
break;
|
||||||
}
|
}
|
||||||
return oneOrTwo('=', T_(POP_DIVEQ), T_(OP_DIV));
|
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
|
// Macro args were handled by `peek`, and character escapes do not exist
|
||||||
// outside of string literals, so this must be a line continuation.
|
// outside of string literals, so this must be a line continuation.
|
||||||
discardLineContinuation();
|
discardLineContinuation();
|
||||||
continue;
|
break;
|
||||||
|
|
||||||
// Handle raw strings... or fall through if '#' is not followed by '"'
|
// Handle raw strings... or fall through if '#' is not followed by '"'
|
||||||
|
|
||||||
@@ -1903,7 +1917,7 @@ static Token yylex_NORMAL() {
|
|||||||
c = bumpChar();
|
c = bumpChar();
|
||||||
} else if (!startsIdentifier(c)) {
|
} else if (!startsIdentifier(c)) {
|
||||||
reportGarbageCharacters(c);
|
reportGarbageCharacters(c);
|
||||||
continue;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
Token token = readIdentifier(c, raw);
|
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));
|
if (Symbol const *sym = sym_FindExactSymbol(std::get<std::string>(token.value));
|
||||||
sym && sym->type == SYM_EQUS) {
|
sym && sym->type == SYM_EQUS) {
|
||||||
beginExpansion(sym->getEqus(), sym->name);
|
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;
|
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>
|
template<typename CallbackFnT>
|
||||||
static Capture makeCapture(char const *name, F callback) {
|
static Capture makeCapture(char const *name, CallbackFnT callback) {
|
||||||
// Due to parser internals, it reads the EOL after the expression before calling this.
|
// 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.
|
// Thus, we don't need to keep one in the buffer afterwards.
|
||||||
// The following assumption checks that.
|
// The following assumption checks that.
|
||||||
|
|||||||
701
src/asm/main.cpp
701
src/asm/main.cpp
@@ -19,8 +19,8 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "backtrace.hpp"
|
#include "backtrace.hpp"
|
||||||
|
#include "cli.hpp"
|
||||||
#include "diagnostics.hpp"
|
#include "diagnostics.hpp"
|
||||||
#include "extern/getopt.hpp"
|
|
||||||
#include "helpers.hpp"
|
#include "helpers.hpp"
|
||||||
#include "parser.hpp" // Generated from parser.y
|
#include "parser.hpp" // Generated from parser.y
|
||||||
#include "platform.hpp"
|
#include "platform.hpp"
|
||||||
@@ -40,13 +40,17 @@
|
|||||||
|
|
||||||
Options options;
|
Options options;
|
||||||
|
|
||||||
static char const *dependFileName = nullptr; // -M
|
// Flags which must be processed after the option parsing finishes
|
||||||
static std::unordered_map<std::string, std::vector<StateFeature>> stateFileSpecs; // -s
|
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
|
// Short options
|
||||||
static char const *optstring = "B:b:D:Eg:hI:M:o:P:p:Q:r:s:VvW:wX:";
|
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`
|
static int longOpt; // `--color` and variants of `-M`
|
||||||
|
|
||||||
// Equivalent long options
|
// Equivalent long options
|
||||||
@@ -105,134 +109,6 @@ static Usage usage = {
|
|||||||
};
|
};
|
||||||
// clang-format on
|
// 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) {
|
static std::string escapeMakeChars(std::string &str) {
|
||||||
std::string escaped;
|
std::string escaped;
|
||||||
size_t pos = 0;
|
size_t pos = 0;
|
||||||
@@ -295,6 +171,332 @@ static std::vector<StateFeature> parseStateFeatures(char *str) {
|
|||||||
return features;
|
return features;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void parseArg(int ch, char *arg) {
|
||||||
|
switch (ch) {
|
||||||
|
case 'B':
|
||||||
|
if (!trace_ParseTraceDepth(arg)) {
|
||||||
|
fatal("Invalid argument for option '-B'");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'b':
|
||||||
|
if (strlen(arg) == 2) {
|
||||||
|
opt_B(arg);
|
||||||
|
} else {
|
||||||
|
fatal("Must specify exactly 2 characters for option '-b'");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'D': {
|
||||||
|
char *equals = strchr(arg, '=');
|
||||||
|
if (equals) {
|
||||||
|
*equals = '\0';
|
||||||
|
sym_AddString(arg, std::make_shared<std::string>(equals + 1));
|
||||||
|
} else {
|
||||||
|
sym_AddString(arg, std::make_shared<std::string>("1"));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'E':
|
||||||
|
options.exportAll = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'g':
|
||||||
|
if (strlen(arg) == 4) {
|
||||||
|
opt_G(arg);
|
||||||
|
} else {
|
||||||
|
fatal("Must specify exactly 4 characters for option '-g'");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
// LCOV_EXCL_START
|
||||||
|
case 'h':
|
||||||
|
usage.printAndExit(0);
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
|
|
||||||
|
case 'I':
|
||||||
|
fstk_AddIncludePath(arg);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'M':
|
||||||
|
if (localOptions.dependFileName) {
|
||||||
|
warnx(
|
||||||
|
"Overriding dependency file \"%s\"",
|
||||||
|
*localOptions.dependFileName == "-" ? "<stdout>"
|
||||||
|
: localOptions.dependFileName->c_str()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
localOptions.dependFileName = arg;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'o':
|
||||||
|
if (options.objectFileName) {
|
||||||
|
warnx("Overriding output file \"%s\"", options.objectFileName->c_str());
|
||||||
|
}
|
||||||
|
options.objectFileName = arg;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'P':
|
||||||
|
fstk_AddPreIncludeFile(arg);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'p':
|
||||||
|
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");
|
||||||
|
} else {
|
||||||
|
opt_P(*padByte);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'Q': {
|
||||||
|
char const *precisionArg = arg;
|
||||||
|
if (precisionArg[0] == '.') {
|
||||||
|
++precisionArg;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (std::optional<uint64_t> precision = parseWholeNumber(precisionArg); !precision) {
|
||||||
|
fatal("Invalid argument for option '-Q'");
|
||||||
|
} else if (*precision < 1 || *precision > 31) {
|
||||||
|
fatal("Argument for option '-Q' must be between 1 and 31");
|
||||||
|
} else {
|
||||||
|
opt_Q(*precision);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'r':
|
||||||
|
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");
|
||||||
|
} else {
|
||||||
|
options.maxRecursionDepth = *maxDepth;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 's': {
|
||||||
|
// 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(arg);
|
||||||
|
|
||||||
|
if (localOptions.stateFileSpecs.find(name) != localOptions.stateFileSpecs.end()) {
|
||||||
|
warnx("Overriding state file \"%s\"", name);
|
||||||
|
}
|
||||||
|
localOptions.stateFileSpecs.emplace(name, std::move(features));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// LCOV_EXCL_START
|
||||||
|
case 'V':
|
||||||
|
printf("rgbasm %s\n", get_package_version_string());
|
||||||
|
exit(0);
|
||||||
|
|
||||||
|
case 'v':
|
||||||
|
incrementVerbosity();
|
||||||
|
break;
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
|
|
||||||
|
case 'W':
|
||||||
|
opt_W(arg);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'w':
|
||||||
|
warnings.state.warningsEnabled = false;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'X':
|
||||||
|
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);
|
||||||
|
} else {
|
||||||
|
options.maxErrors = *maxErrors;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0: // Long-only options
|
||||||
|
switch (longOpt) {
|
||||||
|
case 'c':
|
||||||
|
if (!style_Parse(arg)) {
|
||||||
|
fatal("Invalid argument for option '--color'");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'C':
|
||||||
|
options.missingIncludeState = GEN_CONTINUE;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'G':
|
||||||
|
options.missingIncludeState = GEN_EXIT;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'P':
|
||||||
|
options.generatePhonyDeps = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'Q':
|
||||||
|
case 'T': {
|
||||||
|
std::string newTarget = arg;
|
||||||
|
if (longOpt == 'Q') {
|
||||||
|
newTarget = escapeMakeChars(newTarget);
|
||||||
|
}
|
||||||
|
if (options.targetFileName) {
|
||||||
|
*options.targetFileName += ' ';
|
||||||
|
*options.targetFileName += newTarget;
|
||||||
|
} else {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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[]) {
|
int main(int argc, char *argv[]) {
|
||||||
// Support SOURCE_DATE_EPOCH for reproducible builds
|
// Support SOURCE_DATE_EPOCH for reproducible builds
|
||||||
// https://reproducible-builds.org/docs/source-date-epoch/
|
// https://reproducible-builds.org/docs/source-date-epoch/
|
||||||
@@ -311,239 +513,54 @@ int main(int argc, char *argv[]) {
|
|||||||
options.maxErrors = 100; // LCOV_EXCL_LINE
|
options.maxErrors = 100; // LCOV_EXCL_LINE
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse CLI options
|
cli_ParseArgs(argc, argv, optstring, longopts, parseArg, usage);
|
||||||
for (int ch; (ch = musl_getopt_long_only(argc, argv, optstring, longopts, nullptr)) != -1;) {
|
|
||||||
switch (ch) {
|
|
||||||
case 'B':
|
|
||||||
if (!trace_ParseTraceDepth(musl_optarg)) {
|
|
||||||
fatal("Invalid argument for option '-B'");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'b':
|
if (!options.targetFileName && options.objectFileName) {
|
||||||
if (strlen(musl_optarg) == 2) {
|
|
||||||
opt_B(musl_optarg);
|
|
||||||
} else {
|
|
||||||
fatal("Must specify exactly 2 characters for option '-b'");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'D': {
|
|
||||||
char *equals = strchr(musl_optarg, '=');
|
|
||||||
if (equals) {
|
|
||||||
*equals = '\0';
|
|
||||||
sym_AddString(musl_optarg, std::make_shared<std::string>(equals + 1));
|
|
||||||
} else {
|
|
||||||
sym_AddString(musl_optarg, std::make_shared<std::string>("1"));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'E':
|
|
||||||
options.exportAll = true;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'g':
|
|
||||||
if (strlen(musl_optarg) == 4) {
|
|
||||||
opt_G(musl_optarg);
|
|
||||||
} else {
|
|
||||||
fatal("Must specify exactly 4 characters for option '-g'");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
// LCOV_EXCL_START
|
|
||||||
case 'h':
|
|
||||||
usage.printAndExit(0);
|
|
||||||
// LCOV_EXCL_STOP
|
|
||||||
|
|
||||||
case 'I':
|
|
||||||
fstk_AddIncludePath(musl_optarg);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'M':
|
|
||||||
if (dependFileName) {
|
|
||||||
warnx(
|
|
||||||
"Overriding dependency file \"%s\"",
|
|
||||||
strcmp(dependFileName, "-") ? dependFileName : "<stdout>"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
dependFileName = musl_optarg;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'o':
|
|
||||||
if (!options.objectFileName.empty()) {
|
|
||||||
warnx("Overriding output file \"%s\"", options.objectFileName.c_str());
|
|
||||||
}
|
|
||||||
options.objectFileName = musl_optarg;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'P':
|
|
||||||
fstk_AddPreIncludeFile(musl_optarg);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'p':
|
|
||||||
if (std::optional<uint64_t> padByte = parseWholeNumber(musl_optarg); !padByte) {
|
|
||||||
fatal("Invalid argument for option '-p'");
|
|
||||||
} else if (*padByte > 0xFF) {
|
|
||||||
fatal("Argument for option '-p' must be between 0 and 0xFF");
|
|
||||||
} else {
|
|
||||||
opt_P(*padByte);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'Q': {
|
|
||||||
char const *precisionArg = musl_optarg;
|
|
||||||
if (precisionArg[0] == '.') {
|
|
||||||
++precisionArg;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (std::optional<uint64_t> precision = parseWholeNumber(precisionArg); !precision) {
|
|
||||||
fatal("Invalid argument for option '-Q'");
|
|
||||||
} else if (*precision < 1 || *precision > 31) {
|
|
||||||
fatal("Argument for option '-Q' must be between 1 and 31");
|
|
||||||
} else {
|
|
||||||
opt_Q(*precision);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'r':
|
|
||||||
if (std::optional<uint64_t> maxDepth = parseWholeNumber(musl_optarg); !maxDepth) {
|
|
||||||
fatal("Invalid argument for option '-r'");
|
|
||||||
} else if (errno == ERANGE) {
|
|
||||||
fatal("Argument for option '-r' is out of range");
|
|
||||||
} else {
|
|
||||||
options.maxRecursionDepth = *maxDepth;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 's': {
|
|
||||||
// Split "<features>:<name>" so `musl_optarg` is "<features>" and `name` is "<name>"
|
|
||||||
char *name = strchr(musl_optarg, ':');
|
|
||||||
if (!name) {
|
|
||||||
fatal("Invalid argument for option '-s'");
|
|
||||||
}
|
|
||||||
*name++ = '\0';
|
|
||||||
|
|
||||||
std::vector<StateFeature> features = parseStateFeatures(musl_optarg);
|
|
||||||
|
|
||||||
if (stateFileSpecs.find(name) != stateFileSpecs.end()) {
|
|
||||||
warnx("Overriding state file \"%s\"", name);
|
|
||||||
}
|
|
||||||
stateFileSpecs.emplace(name, std::move(features));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// LCOV_EXCL_START
|
|
||||||
case 'V':
|
|
||||||
printf("rgbasm %s\n", get_package_version_string());
|
|
||||||
exit(0);
|
|
||||||
|
|
||||||
case 'v':
|
|
||||||
incrementVerbosity();
|
|
||||||
break;
|
|
||||||
// LCOV_EXCL_STOP
|
|
||||||
|
|
||||||
case 'W':
|
|
||||||
opt_W(musl_optarg);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'w':
|
|
||||||
warnings.state.warningsEnabled = false;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'X':
|
|
||||||
if (std::optional<uint64_t> maxErrors = parseWholeNumber(musl_optarg); !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);
|
|
||||||
} else {
|
|
||||||
options.maxErrors = *maxErrors;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 0: // Long-only options
|
|
||||||
switch (longOpt) {
|
|
||||||
case 'c':
|
|
||||||
if (!style_Parse(musl_optarg)) {
|
|
||||||
fatal("Invalid argument for option '--color'");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'C':
|
|
||||||
options.missingIncludeState = GEN_CONTINUE;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'G':
|
|
||||||
options.missingIncludeState = GEN_EXIT;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'P':
|
|
||||||
options.generatePhonyDeps = true;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'Q':
|
|
||||||
case 'T': {
|
|
||||||
std::string newTarget = musl_optarg;
|
|
||||||
if (longOpt == 'Q') {
|
|
||||||
newTarget = escapeMakeChars(newTarget);
|
|
||||||
}
|
|
||||||
if (!options.targetFileName.empty()) {
|
|
||||||
options.targetFileName += ' ';
|
|
||||||
}
|
|
||||||
options.targetFileName += newTarget;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
// LCOV_EXCL_START
|
|
||||||
default:
|
|
||||||
usage.printAndExit(1);
|
|
||||||
// LCOV_EXCL_STOP
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.targetFileName.empty() && !options.objectFileName.empty()) {
|
|
||||||
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)");
|
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 (localOptions.dependFileName) {
|
||||||
|
if (!options.targetFileName) {
|
||||||
if (dependFileName) {
|
|
||||||
if (options.targetFileName.empty()) {
|
|
||||||
fatal("Dependency files can only be created if a target file is specified with either "
|
fatal("Dependency files can only be created if a target file is specified with either "
|
||||||
"'-o', '-MQ' or '-MT'");
|
"'-o', '-MQ' or '-MT'");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strcmp("-", dependFileName)) {
|
if (*localOptions.dependFileName == "-") {
|
||||||
options.dependFile = fopen(dependFileName, "w");
|
options.dependFile = stdout;
|
||||||
|
} else {
|
||||||
|
options.dependFile = fopen(localOptions.dependFileName->c_str(), "w");
|
||||||
if (options.dependFile == nullptr) {
|
if (options.dependFile == nullptr) {
|
||||||
// LCOV_EXCL_START
|
// 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
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
options.dependFile = stdout;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
options.printDep(mainFileName);
|
options.printDep(*localOptions.inputFileName);
|
||||||
|
|
||||||
charmap_New(DEFAULT_CHARMAP_NAME, nullptr);
|
charmap_New(DEFAULT_CHARMAP_NAME, nullptr);
|
||||||
|
|
||||||
// Init lexer and file stack, providing file info
|
// 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`)
|
// Perform parse (`yy::parser` is auto-generated from `parser.y`)
|
||||||
if (yy::parser parser; parser.parse() != 0) {
|
if (yy::parser parser; parser.parse() != 0) {
|
||||||
@@ -569,7 +586,7 @@ int main(int argc, char *argv[]) {
|
|||||||
|
|
||||||
out_WriteObject();
|
out_WriteObject();
|
||||||
|
|
||||||
for (auto const &[name, features] : stateFileSpecs) {
|
for (auto const &[name, features] : localOptions.stateFileSpecs) {
|
||||||
out_WriteState(name, features);
|
out_WriteState(name, features);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -184,30 +184,29 @@ static void writeFileStackNode(FileStackNode const &node, FILE *file) {
|
|||||||
|
|
||||||
putLong(nodeIters.size(), file);
|
putLong(nodeIters.size(), file);
|
||||||
// Iters are stored by decreasing depth, so reverse the order for output
|
// Iters are stored by decreasing depth, so reverse the order for output
|
||||||
for (uint32_t i = nodeIters.size(); i--;) {
|
for (uint32_t iter : reversed(nodeIters)) {
|
||||||
putLong(nodeIters[i], file);
|
putLong(iter, file);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void out_WriteObject() {
|
void out_WriteObject() {
|
||||||
if (options.objectFileName.empty()) {
|
if (!options.objectFileName) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
static FILE *file; // `static` so `sect_ForEach` callback can see it
|
static FILE *file; // `static` so `sect_ForEach` callback can see it
|
||||||
if (options.objectFileName != "-") {
|
char const *objectFileName = options.objectFileName->c_str();
|
||||||
file = fopen(options.objectFileName.c_str(), "wb");
|
if (*options.objectFileName != "-") {
|
||||||
|
file = fopen(objectFileName, "wb");
|
||||||
} else {
|
} else {
|
||||||
options.objectFileName = "<stdout>";
|
objectFileName = "<stdout>";
|
||||||
(void)setmode(STDOUT_FILENO, O_BINARY);
|
(void)setmode(STDOUT_FILENO, O_BINARY);
|
||||||
file = stdout;
|
file = stdout;
|
||||||
}
|
}
|
||||||
if (!file) {
|
if (!file) {
|
||||||
// LCOV_EXCL_START
|
// LCOV_EXCL_START
|
||||||
fatal(
|
fatal("Failed to open object file \"%s\": %s", objectFileName, strerror(errno));
|
||||||
"Failed to open object file \"%s\": %s", options.objectFileName.c_str(), strerror(errno)
|
|
||||||
);
|
|
||||||
// LCOV_EXCL_STOP
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
Defer closeFile{[&] { fclose(file); }};
|
Defer closeFile{[&] { fclose(file); }};
|
||||||
@@ -223,12 +222,10 @@ void out_WriteObject() {
|
|||||||
|
|
||||||
putLong(fileStackNodes.size(), file);
|
putLong(fileStackNodes.size(), file);
|
||||||
for (auto it = fileStackNodes.begin(); it != fileStackNodes.end(); ++it) {
|
for (auto it = fileStackNodes.begin(); it != fileStackNodes.end(); ++it) {
|
||||||
FileStackNode const &node = **it;
|
writeFileStackNode(**it, file);
|
||||||
|
|
||||||
writeFileStackNode(node, file);
|
|
||||||
|
|
||||||
// The list is supposed to have decrementing IDs
|
// 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) {
|
for (Symbol const *sym : objectSymbols) {
|
||||||
|
|||||||
@@ -32,7 +32,6 @@
|
|||||||
|
|
||||||
%code {
|
%code {
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <ctype.h>
|
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
@@ -42,6 +41,7 @@
|
|||||||
|
|
||||||
#include "extern/utf8decoder.hpp"
|
#include "extern/utf8decoder.hpp"
|
||||||
#include "helpers.hpp"
|
#include "helpers.hpp"
|
||||||
|
#include "util.hpp" // toLower, toUpper
|
||||||
|
|
||||||
#include "asm/charmap.hpp"
|
#include "asm/charmap.hpp"
|
||||||
#include "asm/fixpoint.hpp"
|
#include "asm/fixpoint.hpp"
|
||||||
@@ -57,8 +57,10 @@
|
|||||||
|
|
||||||
yy::parser::symbol_type yylex(); // Provided by lexer.cpp
|
yy::parser::symbol_type yylex(); // Provided by lexer.cpp
|
||||||
|
|
||||||
template <typename N, typename S>
|
template<typename NumCallbackFnT, typename StrCallbackFnT>
|
||||||
static auto handleSymbolByType(std::string const &symName, N numCallback, S strCallback) {
|
static auto handleSymbolByType(
|
||||||
|
std::string const &symName, NumCallbackFnT numCallback, StrCallbackFnT strCallback
|
||||||
|
) {
|
||||||
if (Symbol *sym = sym_FindScopedSymbol(symName); sym && sym->type == SYM_EQUS) {
|
if (Symbol *sym = sym_FindScopedSymbol(symName); sym && sym->type == SYM_EQUS) {
|
||||||
return strCallback(*sym->getEqus());
|
return strCallback(*sym->getEqus());
|
||||||
} else {
|
} else {
|
||||||
@@ -453,12 +455,13 @@ endofline: NEWLINE | EOB | EOL;
|
|||||||
// and to avoid causing some grammar conflicts (token reducing is finicky).
|
// and to avoid causing some grammar conflicts (token reducing is finicky).
|
||||||
// This is DEFINITELY one of the more FRAGILE parts of the codebase, handle with care.
|
// This is DEFINITELY one of the more FRAGILE parts of the codebase, handle with care.
|
||||||
line_directive:
|
line_directive:
|
||||||
def_macro
|
macro_def
|
||||||
| rept
|
| rept
|
||||||
| for
|
| for
|
||||||
| break
|
| break
|
||||||
| include
|
| include
|
||||||
| if
|
| if
|
||||||
|
| endc
|
||||||
// It's important that all of these require being at line start for `skipIfBlock`
|
// It's important that all of these require being at line start for `skipIfBlock`
|
||||||
| elif
|
| elif
|
||||||
| else
|
| else
|
||||||
@@ -487,12 +490,12 @@ else:
|
|||||||
plain_directive:
|
plain_directive:
|
||||||
label
|
label
|
||||||
| label data
|
| label data
|
||||||
| label macro
|
| label macro_invocation
|
||||||
| label directive
|
| label directive
|
||||||
;
|
;
|
||||||
|
|
||||||
endc:
|
endc:
|
||||||
POP_ENDC {
|
POP_ENDC endofline {
|
||||||
act_Endc();
|
act_Endc();
|
||||||
}
|
}
|
||||||
;
|
;
|
||||||
@@ -543,7 +546,7 @@ label:
|
|||||||
}
|
}
|
||||||
;
|
;
|
||||||
|
|
||||||
macro:
|
macro_invocation:
|
||||||
SYMBOL {
|
SYMBOL {
|
||||||
// Parsing 'macro_args' will restore the lexer's normal mode
|
// Parsing 'macro_args' will restore the lexer's normal mode
|
||||||
lexer_SetMode(LEXER_RAW);
|
lexer_SetMode(LEXER_RAW);
|
||||||
@@ -569,8 +572,7 @@ macro_args:
|
|||||||
;
|
;
|
||||||
|
|
||||||
directive:
|
directive:
|
||||||
endc
|
print
|
||||||
| print
|
|
||||||
| println
|
| println
|
||||||
| export
|
| export
|
||||||
| export_def
|
| export_def
|
||||||
@@ -851,7 +853,7 @@ break:
|
|||||||
}
|
}
|
||||||
;
|
;
|
||||||
|
|
||||||
def_macro:
|
macro_def:
|
||||||
POP_MACRO {
|
POP_MACRO {
|
||||||
lexer_ToggleStringExpansion(false);
|
lexer_ToggleStringExpansion(false);
|
||||||
} maybe_quiet SYMBOL {
|
} maybe_quiet SYMBOL {
|
||||||
@@ -1599,11 +1601,11 @@ string_literal:
|
|||||||
}
|
}
|
||||||
| OP_STRUPR LPAREN string RPAREN {
|
| OP_STRUPR LPAREN string RPAREN {
|
||||||
$$ = std::move($3);
|
$$ = std::move($3);
|
||||||
std::transform(RANGE($$), $$.begin(), [](char c) { return toupper(c); });
|
std::transform(RANGE($$), $$.begin(), toUpper);
|
||||||
}
|
}
|
||||||
| OP_STRLWR LPAREN string RPAREN {
|
| OP_STRLWR LPAREN string RPAREN {
|
||||||
$$ = std::move($3);
|
$$ = 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 {
|
| OP_STRRPL LPAREN string COMMA string COMMA string RPAREN {
|
||||||
$$ = act_StringReplace($3, $5, $7);
|
$$ = act_StringReplace($3, $5, $7);
|
||||||
|
|||||||
@@ -120,9 +120,8 @@ Section *sect_FindSectionByName(std::string const &name) {
|
|||||||
++nbSectErrors; \
|
++nbSectErrors; \
|
||||||
} while (0)
|
} while (0)
|
||||||
|
|
||||||
static unsigned int mergeSectUnion(
|
static unsigned int
|
||||||
Section §, SectionType type, uint32_t org, uint8_t alignment, uint16_t alignOffset
|
mergeSectUnion(Section §, uint32_t org, uint8_t alignment, uint16_t alignOffset) {
|
||||||
) {
|
|
||||||
unsigned int nbSectErrors = 0;
|
unsigned int nbSectErrors = 0;
|
||||||
|
|
||||||
assume(alignment < 16); // Should be ensured by the caller
|
assume(alignment < 16); // Should be ensured by the caller
|
||||||
@@ -133,12 +132,6 @@ static unsigned int mergeSectUnion(
|
|||||||
uint32_t sectAlignSize = 1u << sect.align;
|
uint32_t sectAlignSize = 1u << sect.align;
|
||||||
uint32_t sectAlignMask = sectAlignSize - 1;
|
uint32_t sectAlignMask = sectAlignSize - 1;
|
||||||
|
|
||||||
// Unionized sections only need "compatible" constraints, and they end up with the strictest
|
|
||||||
// combination of both.
|
|
||||||
if (sectTypeHasData(type)) {
|
|
||||||
sectError("Cannot declare ROM sections as `UNION`");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (org != UINT32_MAX) {
|
if (org != UINT32_MAX) {
|
||||||
// If both are fixed, they must be the same
|
// If both are fixed, they must be the same
|
||||||
if (sect.org != UINT32_MAX && sect.org != org) {
|
if (sect.org != UINT32_MAX && sect.org != org) {
|
||||||
@@ -266,12 +259,10 @@ static void mergeSections(
|
|||||||
} else {
|
} else {
|
||||||
switch (mod) {
|
switch (mod) {
|
||||||
case SECTION_UNION:
|
case SECTION_UNION:
|
||||||
case SECTION_FRAGMENT:
|
case SECTION_FRAGMENT: {
|
||||||
nbSectErrors += mod == SECTION_UNION
|
unsigned int (*merge)(Section &, uint32_t, uint8_t, uint16_t) =
|
||||||
? mergeSectUnion(sect, type, org, alignment, alignOffset)
|
mod == SECTION_UNION ? mergeSectUnion : mergeFragments;
|
||||||
: mergeFragments(sect, org, alignment, alignOffset);
|
nbSectErrors += merge(sect, org, alignment, alignOffset);
|
||||||
|
|
||||||
// Common checks
|
|
||||||
|
|
||||||
// If the section's bank is unspecified, override it
|
// If the section's bank is unspecified, override it
|
||||||
if (sect.bank == UINT32_MAX) {
|
if (sect.bank == UINT32_MAX) {
|
||||||
@@ -282,6 +273,7 @@ static void mergeSections(
|
|||||||
sectError("Section already declared with different bank %" PRIu32, sect.bank);
|
sectError("Section already declared with different bank %" PRIu32, sect.bank);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case SECTION_NORMAL:
|
case SECTION_NORMAL:
|
||||||
errorNoTrace([&]() {
|
errorNoTrace([&]() {
|
||||||
@@ -513,6 +505,11 @@ void sect_NewSection(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mod == SECTION_UNION && sectTypeHasData(type)) {
|
||||||
|
error("Cannot declare ROM sections as `UNION`");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (currentLoadSection) {
|
if (currentLoadSection) {
|
||||||
sect_EndLoadSection("SECTION");
|
sect_EndLoadSection("SECTION");
|
||||||
}
|
}
|
||||||
@@ -1121,12 +1118,12 @@ std::string sect_PushSectionFragmentLiteral() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This section has data (ROM0 or ROMX), so it cannot be a UNION
|
||||||
|
assume(currentSection->modifier != SECTION_UNION);
|
||||||
|
|
||||||
if (currentLoadSection) {
|
if (currentLoadSection) {
|
||||||
fatal("`LOAD` blocks cannot contain fragment literals");
|
fatal("`LOAD` blocks cannot contain fragment literals");
|
||||||
}
|
}
|
||||||
if (currentSection->modifier == SECTION_UNION) {
|
|
||||||
fatal("`SECTION UNION` cannot contain fragment literals");
|
|
||||||
}
|
|
||||||
|
|
||||||
// A section containing a fragment literal has to become a fragment too
|
// A section containing a fragment literal has to become a fragment too
|
||||||
currentSection->modifier = SECTION_FRAGMENT;
|
currentSection->modifier = SECTION_FRAGMENT;
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ static Symbol const *localScope = nullptr; // Current section's local label sco
|
|||||||
|
|
||||||
static Symbol *PCSymbol;
|
static Symbol *PCSymbol;
|
||||||
static Symbol *NARGSymbol;
|
static Symbol *NARGSymbol;
|
||||||
|
static Symbol *SCOPESymbol;
|
||||||
static Symbol *globalScopeSymbol;
|
static Symbol *globalScopeSymbol;
|
||||||
static Symbol *localScopeSymbol;
|
static Symbol *localScopeSymbol;
|
||||||
static Symbol *RSSymbol;
|
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() {
|
static std::shared_ptr<std::string> globalScopeCallback() {
|
||||||
if (!globalScope) {
|
if (!globalScope) {
|
||||||
error("`.` has no value outside of a label scope");
|
error("`.` has no value outside of a label scope");
|
||||||
@@ -296,6 +310,10 @@ Symbol *sym_FindScopedValidSymbol(std::string const &symName) {
|
|||||||
if (sym == localScopeSymbol && !localScope) {
|
if (sym == localScopeSymbol && !localScope) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
// `__SCOPE__` has no value outside of a section
|
||||||
|
if (sym == SCOPESymbol && !sect_GetSymbolSection()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
return sym;
|
return sym;
|
||||||
}
|
}
|
||||||
@@ -683,6 +701,11 @@ void sym_Init(time_t now) {
|
|||||||
localScopeSymbol->data = localScopeCallback;
|
localScopeSymbol->data = localScopeCallback;
|
||||||
localScopeSymbol->isBuiltin = true;
|
localScopeSymbol->isBuiltin = true;
|
||||||
|
|
||||||
|
SCOPESymbol = &createSymbol("__SCOPE__"s);
|
||||||
|
SCOPESymbol->type = SYM_EQUS;
|
||||||
|
SCOPESymbol->data = SCOPECallback;
|
||||||
|
SCOPESymbol->isBuiltin = true;
|
||||||
|
|
||||||
RSSymbol = sym_AddVar("_RS"s, 0);
|
RSSymbol = sym_AddVar("_RS"s, 0);
|
||||||
RSSymbol->isBuiltin = true;
|
RSSymbol->isBuiltin = true;
|
||||||
|
|
||||||
|
|||||||
@@ -52,8 +52,8 @@ Diagnostics<WarningLevel, WarningID> warnings = {
|
|||||||
},
|
},
|
||||||
.paramWarnings = {
|
.paramWarnings = {
|
||||||
{WARNING_NUMERIC_STRING_1, WARNING_NUMERIC_STRING_2, 1},
|
{WARNING_NUMERIC_STRING_1, WARNING_NUMERIC_STRING_2, 1},
|
||||||
{WARNING_PURGE_1, WARNING_PURGE_2, 1},
|
{WARNING_PURGE_1, WARNING_PURGE_2, 2},
|
||||||
{WARNING_TRUNCATION_1, WARNING_TRUNCATION_2, 2},
|
{WARNING_TRUNCATION_1, WARNING_TRUNCATION_2, 1},
|
||||||
{WARNING_UNMAPPED_CHAR_1, WARNING_UNMAPPED_CHAR_2, 1},
|
{WARNING_UNMAPPED_CHAR_1, WARNING_UNMAPPED_CHAR_2, 1},
|
||||||
},
|
},
|
||||||
.state = DiagnosticsState<WarningID>(),
|
.state = DiagnosticsState<WarningID>(),
|
||||||
|
|||||||
157
src/cli.cpp
Normal file
157
src/cli.cpp
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
#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)) {
|
||||||
|
int errnum = errno;
|
||||||
|
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(errnum));
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
299
src/extern/getopt.cpp
vendored
299
src/extern/getopt.cpp
vendored
@@ -11,27 +11,24 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <wchar.h>
|
#include <wchar.h>
|
||||||
|
|
||||||
|
#include "style.hpp"
|
||||||
|
|
||||||
char *musl_optarg;
|
char *musl_optarg;
|
||||||
int musl_optind = 1, musl_opterr = 1, musl_optopt;
|
int musl_optind = 1, musl_optopt;
|
||||||
int musl_optreset = 0;
|
|
||||||
static int musl_optpos;
|
static int musl_optpos;
|
||||||
|
|
||||||
static void musl_getopt_msg(char const *a, char const *b, char const *c, size_t l) {
|
static void musl_getopt_msg(char const *msg, char const *param) {
|
||||||
FILE *f = stderr;
|
style_Set(stderr, STYLE_RED, true);
|
||||||
|
fputs("error: ", stderr);
|
||||||
if (fputs(a, f) >= 0 && fwrite(b, strlen(b), 1, f) && fwrite(c, 1, l, f) == l) {
|
style_Reset(stderr);
|
||||||
putc('\n', f);
|
fputs(msg, stderr);
|
||||||
}
|
fputs(param, stderr);
|
||||||
|
putc('\n', stderr);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int getopt(int argc, char *argv[], char const *optstring) {
|
static int musl_getopt(int argc, char *argv[], char const *optstring) {
|
||||||
int i;
|
if (!musl_optind) {
|
||||||
wchar_t c, d;
|
|
||||||
int k, l;
|
|
||||||
char *optchar;
|
|
||||||
|
|
||||||
if (!musl_optind || musl_optreset) {
|
|
||||||
musl_optreset = 0;
|
|
||||||
musl_optpos = 0;
|
musl_optpos = 0;
|
||||||
musl_optind = 1;
|
musl_optind = 1;
|
||||||
}
|
}
|
||||||
@@ -40,7 +37,9 @@ static int getopt(int argc, char *argv[], char const *optstring) {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (argv[musl_optind][0] != '-') {
|
char *argi = argv[musl_optind];
|
||||||
|
|
||||||
|
if (argi[0] != '-') {
|
||||||
if (optstring[0] == '-') {
|
if (optstring[0] == '-') {
|
||||||
musl_optarg = argv[musl_optind++];
|
musl_optarg = argv[musl_optind++];
|
||||||
return 1;
|
return 1;
|
||||||
@@ -48,26 +47,28 @@ static int getopt(int argc, char *argv[], char const *optstring) {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!argv[musl_optind][1]) {
|
if (!argi[1]) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (argv[musl_optind][1] == '-' && !argv[musl_optind][2]) {
|
if (argi[1] == '-' && !argi[2]) {
|
||||||
return musl_optind++, -1;
|
++musl_optind;
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!musl_optpos) {
|
if (!musl_optpos) {
|
||||||
++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) {
|
if (k < 0) {
|
||||||
k = 1;
|
k = 1;
|
||||||
c = 0xFFFD; // replacement char
|
c = 0xFFFD; // replacement char
|
||||||
}
|
}
|
||||||
optchar = argv[musl_optind] + musl_optpos;
|
char *optchar = argi + musl_optpos;
|
||||||
musl_optpos += k;
|
musl_optpos += k;
|
||||||
|
|
||||||
if (!argv[musl_optind][musl_optpos]) {
|
if (!argi[musl_optpos]) {
|
||||||
++musl_optind;
|
++musl_optind;
|
||||||
musl_optpos = 0;
|
musl_optpos = 0;
|
||||||
}
|
}
|
||||||
@@ -76,8 +77,9 @@ static int getopt(int argc, char *argv[], char const *optstring) {
|
|||||||
++optstring;
|
++optstring;
|
||||||
}
|
}
|
||||||
|
|
||||||
i = 0;
|
int i = 0;
|
||||||
d = 0;
|
wchar_t d = 0;
|
||||||
|
int l;
|
||||||
do {
|
do {
|
||||||
l = mbtowc(&d, optstring + i, MB_LEN_MAX);
|
l = mbtowc(&d, optstring + i, MB_LEN_MAX);
|
||||||
if (l > 0) {
|
if (l > 0) {
|
||||||
@@ -89,8 +91,8 @@ static int getopt(int argc, char *argv[], char const *optstring) {
|
|||||||
|
|
||||||
if (d != c || c == ':') {
|
if (d != c || c == ':') {
|
||||||
musl_optopt = c;
|
musl_optopt = c;
|
||||||
if (optstring[0] != ':' && musl_opterr) {
|
if (optstring[0] != ':') {
|
||||||
musl_getopt_msg(argv[0], ": unrecognized option: ", optchar, k);
|
musl_getopt_msg("unrecognized option: ", optchar);
|
||||||
}
|
}
|
||||||
return '?';
|
return '?';
|
||||||
}
|
}
|
||||||
@@ -105,9 +107,7 @@ static int getopt(int argc, char *argv[], char const *optstring) {
|
|||||||
if (optstring[0] == ':') {
|
if (optstring[0] == ':') {
|
||||||
return ':';
|
return ':';
|
||||||
}
|
}
|
||||||
if (musl_opterr) {
|
musl_getopt_msg("option requires an argument: ", optchar);
|
||||||
musl_getopt_msg(argv[0], ": option requires an argument: ", optchar, k);
|
|
||||||
}
|
|
||||||
return '?';
|
return '?';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -116,25 +116,106 @@ static int getopt(int argc, char *argv[], char const *optstring) {
|
|||||||
|
|
||||||
static void permute(char **argv, int dest, int src) {
|
static void permute(char **argv, int dest, int src) {
|
||||||
char *tmp = argv[src];
|
char *tmp = argv[src];
|
||||||
int i;
|
for (int i = src; i > dest; --i) {
|
||||||
|
|
||||||
for (i = src; i > dest; --i) {
|
|
||||||
argv[i] = argv[i - 1];
|
argv[i] = argv[i - 1];
|
||||||
}
|
}
|
||||||
argv[dest] = tmp;
|
argv[dest] = tmp;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int musl_getopt_long_core(
|
static int
|
||||||
int argc, char **argv, char const *optstring, option const *longopts, int *idx, int longonly
|
musl_getopt_long_core(int argc, char **argv, char const *optstring, option const *longopts) {
|
||||||
);
|
musl_optarg = 0;
|
||||||
|
if (char *argi = argv[musl_optind];
|
||||||
|
!longopts || argi[0] != '-'
|
||||||
|
|| ((!argi[1] || argi[1] == '-') && (argi[1] != '-' || !argi[2]))) {
|
||||||
|
return musl_getopt(argc, argv, optstring);
|
||||||
|
}
|
||||||
|
|
||||||
static int musl_getopt_long(
|
bool colon = optstring[optstring[0] == '+' || optstring[0] == '-'] == ':';
|
||||||
int argc, char **argv, char const *optstring, option const *longopts, int *idx, int longonly
|
int i = 0, cnt = 0, match = 0;
|
||||||
) {
|
char *arg = 0, *opt, *start = argv[musl_optind] + 1;
|
||||||
int ret, skipped, resumed;
|
|
||||||
|
|
||||||
if (!musl_optind || musl_optreset) {
|
for (; longopts[i].name; ++i) {
|
||||||
musl_optreset = 0;
|
char const *name = longopts[i].name;
|
||||||
|
opt = start;
|
||||||
|
if (*opt == '-') {
|
||||||
|
++opt;
|
||||||
|
}
|
||||||
|
while (*opt && *opt != '=' && *opt == *name) {
|
||||||
|
++name;
|
||||||
|
++opt;
|
||||||
|
}
|
||||||
|
if (*opt && *opt != '=') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
arg = opt;
|
||||||
|
match = i;
|
||||||
|
if (!*name) {
|
||||||
|
cnt = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
++cnt;
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
if (j == l) {
|
||||||
|
++cnt;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (cnt == 1) {
|
||||||
|
i = match;
|
||||||
|
opt = arg;
|
||||||
|
++musl_optind;
|
||||||
|
if (*opt == '=') {
|
||||||
|
if (!longopts[i].has_arg) {
|
||||||
|
musl_optopt = longopts[i].val;
|
||||||
|
if (colon) {
|
||||||
|
return '?';
|
||||||
|
}
|
||||||
|
musl_getopt_msg("option does not take an argument: ", longopts[i].name);
|
||||||
|
return '?';
|
||||||
|
}
|
||||||
|
musl_optarg = opt + 1;
|
||||||
|
} else if (longopts[i].has_arg == required_argument) {
|
||||||
|
musl_optarg = argv[musl_optind];
|
||||||
|
if (!musl_optarg) {
|
||||||
|
musl_optopt = longopts[i].val;
|
||||||
|
if (colon) {
|
||||||
|
return ':';
|
||||||
|
}
|
||||||
|
musl_getopt_msg("option requires an argument: ", longopts[i].name);
|
||||||
|
return '?';
|
||||||
|
}
|
||||||
|
++musl_optind;
|
||||||
|
}
|
||||||
|
if (longopts[i].flag) {
|
||||||
|
*longopts[i].flag = longopts[i].val;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return longopts[i].val;
|
||||||
|
}
|
||||||
|
if (argv[musl_optind][1] == '-') {
|
||||||
|
musl_optopt = 0;
|
||||||
|
if (!colon) {
|
||||||
|
musl_getopt_msg(
|
||||||
|
cnt ? "option is ambiguous: " : "unrecognized option: ", argv[musl_optind] + 2
|
||||||
|
);
|
||||||
|
}
|
||||||
|
++musl_optind;
|
||||||
|
return '?';
|
||||||
|
}
|
||||||
|
return musl_getopt(argc, argv, optstring);
|
||||||
|
}
|
||||||
|
|
||||||
|
int musl_getopt_long_only(int argc, char **argv, char const *optstring, option const *longopts) {
|
||||||
|
if (!musl_optind) {
|
||||||
musl_optpos = 0;
|
musl_optpos = 0;
|
||||||
musl_optind = 1;
|
musl_optind = 1;
|
||||||
}
|
}
|
||||||
@@ -143,10 +224,10 @@ static int musl_getopt_long(
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
skipped = musl_optind;
|
int skipped = musl_optind;
|
||||||
if (optstring[0] != '+' && optstring[0] != '-') {
|
if (optstring[0] != '+' && optstring[0] != '-') {
|
||||||
int i;
|
int i = musl_optind;
|
||||||
for (i = musl_optind;; ++i) {
|
for (;; ++i) {
|
||||||
if (i >= argc || !argv[i]) {
|
if (i >= argc || !argv[i]) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@@ -156,134 +237,14 @@ static int musl_getopt_long(
|
|||||||
}
|
}
|
||||||
musl_optind = i;
|
musl_optind = i;
|
||||||
}
|
}
|
||||||
resumed = musl_optind;
|
int resumed = musl_optind;
|
||||||
ret = musl_getopt_long_core(argc, argv, optstring, longopts, idx, longonly);
|
int ret = musl_getopt_long_core(argc, argv, optstring, longopts);
|
||||||
if (resumed > skipped) {
|
if (resumed > skipped) {
|
||||||
int i, cnt = musl_optind - resumed;
|
int cnt = musl_optind - resumed;
|
||||||
|
for (int i = 0; i < cnt; ++i) {
|
||||||
for (i = 0; i < cnt; ++i) {
|
|
||||||
permute(argv, skipped, musl_optind - 1);
|
permute(argv, skipped, musl_optind - 1);
|
||||||
}
|
}
|
||||||
musl_optind = skipped + cnt;
|
musl_optind = skipped + cnt;
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int musl_getopt_long_core(
|
|
||||||
int argc, char **argv, char const *optstring, option const *longopts, int *idx, int longonly
|
|
||||||
) {
|
|
||||||
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;
|
|
||||||
char *arg = 0, *opt, *start = argv[musl_optind] + 1;
|
|
||||||
|
|
||||||
for (cnt = i = 0; longopts[i].name; ++i) {
|
|
||||||
char const *name = longopts[i].name;
|
|
||||||
|
|
||||||
opt = start;
|
|
||||||
if (*opt == '-') {
|
|
||||||
++opt;
|
|
||||||
}
|
|
||||||
while (*opt && *opt != '=' && *opt == *name) {
|
|
||||||
++name;
|
|
||||||
++opt;
|
|
||||||
}
|
|
||||||
if (*opt && *opt != '=') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
arg = opt;
|
|
||||||
match = i;
|
|
||||||
if (!*name) {
|
|
||||||
cnt = 1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
++cnt;
|
|
||||||
}
|
|
||||||
if (cnt == 1 && longonly && 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;
|
|
||||||
}
|
|
||||||
if (j == l) {
|
|
||||||
++cnt;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (cnt == 1) {
|
|
||||||
i = match;
|
|
||||||
opt = arg;
|
|
||||||
++musl_optind;
|
|
||||||
if (*opt == '=') {
|
|
||||||
if (!longopts[i].has_arg) {
|
|
||||||
musl_optopt = longopts[i].val;
|
|
||||||
if (colon || !musl_opterr) {
|
|
||||||
return '?';
|
|
||||||
}
|
|
||||||
musl_getopt_msg(
|
|
||||||
argv[0],
|
|
||||||
": option does not take an argument: ",
|
|
||||||
longopts[i].name,
|
|
||||||
strlen(longopts[i].name)
|
|
||||||
);
|
|
||||||
return '?';
|
|
||||||
}
|
|
||||||
musl_optarg = opt + 1;
|
|
||||||
} else if (longopts[i].has_arg == required_argument) {
|
|
||||||
musl_optarg = argv[musl_optind];
|
|
||||||
if (!musl_optarg) {
|
|
||||||
musl_optopt = longopts[i].val;
|
|
||||||
if (colon) {
|
|
||||||
return ':';
|
|
||||||
}
|
|
||||||
if (!musl_opterr) {
|
|
||||||
return '?';
|
|
||||||
}
|
|
||||||
musl_getopt_msg(
|
|
||||||
argv[0],
|
|
||||||
": option requires an argument: ",
|
|
||||||
longopts[i].name,
|
|
||||||
strlen(longopts[i].name)
|
|
||||||
);
|
|
||||||
return '?';
|
|
||||||
}
|
|
||||||
++musl_optind;
|
|
||||||
}
|
|
||||||
if (idx) {
|
|
||||||
*idx = i;
|
|
||||||
}
|
|
||||||
if (longopts[i].flag) {
|
|
||||||
*longopts[i].flag = longopts[i].val;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return longopts[i].val;
|
|
||||||
}
|
|
||||||
if (argv[musl_optind][1] == '-') {
|
|
||||||
musl_optopt = 0;
|
|
||||||
if (!colon && musl_opterr) {
|
|
||||||
musl_getopt_msg(
|
|
||||||
argv[0],
|
|
||||||
cnt ? ": option is ambiguous: " : ": unrecognized option: ",
|
|
||||||
argv[musl_optind] + 2,
|
|
||||||
strlen(argv[musl_optind] + 2)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
++musl_optind;
|
|
||||||
return '?';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 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);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -141,7 +141,11 @@ static void
|
|||||||
|
|
||||||
if (options.title) {
|
if (options.title) {
|
||||||
overwriteBytes(
|
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(
|
overwriteBytes(
|
||||||
rom0,
|
rom0,
|
||||||
0x13F,
|
0x13F,
|
||||||
reinterpret_cast<uint8_t const *>(options.gameID),
|
reinterpret_cast<uint8_t const *>(options.gameID->c_str()),
|
||||||
options.gameIDLen,
|
options.gameIDLen,
|
||||||
"manufacturer code"
|
"manufacturer code"
|
||||||
);
|
);
|
||||||
@@ -163,7 +167,7 @@ static void
|
|||||||
overwriteBytes(
|
overwriteBytes(
|
||||||
rom0,
|
rom0,
|
||||||
0x144,
|
0x144,
|
||||||
reinterpret_cast<uint8_t const *>(options.newLicensee),
|
reinterpret_cast<uint8_t const *>(options.newLicensee->c_str()),
|
||||||
options.newLicenseeLen,
|
options.newLicenseeLen,
|
||||||
"new licensee code"
|
"new licensee code"
|
||||||
);
|
);
|
||||||
|
|||||||
389
src/fix/main.cpp
389
src/fix/main.cpp
@@ -12,8 +12,8 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "cli.hpp"
|
||||||
#include "diagnostics.hpp"
|
#include "diagnostics.hpp"
|
||||||
#include "extern/getopt.hpp"
|
|
||||||
#include "helpers.hpp"
|
#include "helpers.hpp"
|
||||||
#include "platform.hpp"
|
#include "platform.hpp"
|
||||||
#include "style.hpp"
|
#include "style.hpp"
|
||||||
@@ -27,10 +27,16 @@
|
|||||||
|
|
||||||
Options options;
|
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
|
// Short options
|
||||||
static char const *optstring = "Ccf:hi:jk:L:l:m:n:Oo:p:r:st:VvW:w";
|
static char const *optstring = "Ccf:hi:jk:L:l:m:n:Oo:p:r:st:VvW:w";
|
||||||
|
|
||||||
// Variables for the long-only options
|
// Long-only option variable
|
||||||
static int longOpt; // `--color`
|
static int longOpt; // `--color`
|
||||||
|
|
||||||
// Equivalent long options
|
// Equivalent long options
|
||||||
@@ -91,13 +97,191 @@ static Usage usage = {
|
|||||||
};
|
};
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
static void parseByte(uint16_t &output, char name) {
|
static uint16_t parseByte(char const *input, char name) {
|
||||||
if (std::optional<uint64_t> value = parseWholeNumber(musl_optarg); !value) {
|
if (std::optional<uint64_t> value = parseWholeNumber(input); !value) {
|
||||||
fatal("Invalid argument for option '-%c'", name);
|
fatal("Invalid argument for option '-%c'", name);
|
||||||
} else if (*value > 0xFF) {
|
} else if (*value > 0xFF) {
|
||||||
fatal("Argument for option '-%c' must be between 0 and 0xFF", name);
|
fatal("Argument for option '-%c' must be between 0 and 0xFF", name);
|
||||||
} else {
|
} 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() {
|
static void initLogo() {
|
||||||
if (options.logoFilename) {
|
if (options.logoFilename) {
|
||||||
FILE *logoFile;
|
FILE *logoFile;
|
||||||
if (strcmp(options.logoFilename, "-")) {
|
char const *logoFilename = options.logoFilename->c_str();
|
||||||
logoFile = fopen(options.logoFilename, "rb");
|
if (*options.logoFilename != "-") {
|
||||||
|
logoFile = fopen(logoFilename, "rb");
|
||||||
} else {
|
} else {
|
||||||
// LCOV_EXCL_START
|
// LCOV_EXCL_START
|
||||||
options.logoFilename = "<stdin>";
|
logoFilename = "<stdin>";
|
||||||
(void)setmode(STDIN_FILENO, O_BINARY);
|
(void)setmode(STDIN_FILENO, O_BINARY);
|
||||||
logoFile = stdin;
|
logoFile = stdin;
|
||||||
// LCOV_EXCL_STOP
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
if (!logoFile) {
|
if (!logoFile) {
|
||||||
// LCOV_EXCL_START
|
// 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
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
Defer closeLogo{[&] { fclose(logoFile); }};
|
Defer closeLogo{[&] { fclose(logoFile); }};
|
||||||
@@ -129,7 +314,7 @@ static void initLogo() {
|
|||||||
uint8_t logoBpp[sizeof(options.logo)];
|
uint8_t logoBpp[sizeof(options.logo)];
|
||||||
if (size_t nbRead = fread(logoBpp, 1, sizeof(logoBpp), logoFile);
|
if (size_t nbRead = fread(logoBpp, 1, sizeof(logoBpp), logoFile);
|
||||||
nbRead != sizeof(options.logo) || fgetc(logoFile) != EOF || ferror(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) {
|
auto highs = [&logoBpp](size_t i) {
|
||||||
return (logoBpp[i * 2] & 0xF0) | ((logoBpp[i * 2 + 1] & 0xF0) >> 4);
|
return (logoBpp[i * 2] & 0xF0) | ((logoBpp[i * 2 + 1] & 0xF0) >> 4);
|
||||||
@@ -161,176 +346,7 @@ static void initLogo() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
char const *outputFilename = nullptr;
|
cli_ParseArgs(argc, argv, optstring, longopts, parseArg, usage);
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((options.cartridgeType & 0xFF00) == TPP1 && !options.japanese) {
|
if ((options.cartridgeType & 0xFF00) == TPP1 && !options.japanese) {
|
||||||
warning(
|
warning(
|
||||||
@@ -382,19 +398,20 @@ int main(int argc, char *argv[]) {
|
|||||||
|
|
||||||
initLogo();
|
initLogo();
|
||||||
|
|
||||||
argv += musl_optind;
|
if (localOptions.inputFileNames.empty()) {
|
||||||
if (!*argv) {
|
|
||||||
usage.printAndExit("No input file specified (pass \"-\" to read from standard input)");
|
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");
|
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;
|
bool failed = warnings.nbErrors > 0;
|
||||||
do {
|
for (std::string const &inputFileName : localOptions.inputFileNames) {
|
||||||
failed |= fix_ProcessFile(*argv, outputFilename);
|
failed |= fix_ProcessFile(inputFileName.c_str(), outputFileName);
|
||||||
} while (*++argv);
|
}
|
||||||
|
|
||||||
return failed;
|
return failed;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
|
|
||||||
#include "helpers.hpp" // unreachable_
|
#include "helpers.hpp" // unreachable_
|
||||||
#include "platform.hpp" // strcasecmp
|
#include "platform.hpp" // strcasecmp
|
||||||
#include "util.hpp" // isBlankSpace, isLower, isDigit
|
#include "util.hpp"
|
||||||
|
|
||||||
#include "fix/warning.hpp"
|
#include "fix/warning.hpp"
|
||||||
|
|
||||||
@@ -103,12 +103,10 @@ static void skipMBCSpace(char const *&ptr) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static char normalizeMBCChar(char c) {
|
static char normalizeMBCChar(char c) {
|
||||||
if (isLower(c)) {
|
if (c == '_') {
|
||||||
c = c - 'a' + 'A'; // Uppercase for comparison with `mbc_Name`s
|
return ' '; // Treat underscores as spaces
|
||||||
} else if (c == '_') {
|
|
||||||
c = ' '; // Treat underscores as spaces
|
|
||||||
}
|
}
|
||||||
return c;
|
return toUpper(c); // Uppercase for comparison with `mbc_Name`s
|
||||||
}
|
}
|
||||||
|
|
||||||
[[noreturn]]
|
[[noreturn]]
|
||||||
|
|||||||
842
src/gfx/main.cpp
842
src/gfx/main.cpp
@@ -2,8 +2,6 @@
|
|||||||
|
|
||||||
#include "gfx/main.hpp"
|
#include "gfx/main.hpp"
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <errno.h>
|
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
#include <ios>
|
#include <ios>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
@@ -12,11 +10,12 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "cli.hpp"
|
||||||
#include "diagnostics.hpp"
|
#include "diagnostics.hpp"
|
||||||
#include "extern/getopt.hpp"
|
|
||||||
#include "file.hpp"
|
#include "file.hpp"
|
||||||
#include "helpers.hpp"
|
#include "helpers.hpp"
|
||||||
#include "platform.hpp"
|
#include "platform.hpp"
|
||||||
@@ -36,22 +35,23 @@ using namespace std::literals::string_view_literals;
|
|||||||
|
|
||||||
Options options;
|
Options options;
|
||||||
|
|
||||||
|
// Flags which must be processed after the option parsing finishes
|
||||||
static struct LocalOptions {
|
static struct LocalOptions {
|
||||||
char const *externalPalSpec;
|
std::optional<std::string> externalPalSpec; // -c
|
||||||
bool autoAttrmap;
|
bool autoAttrmap; // -A
|
||||||
bool autoTilemap;
|
bool autoTilemap; // -T
|
||||||
bool autoPalettes;
|
bool autoPalettes; // -P
|
||||||
bool autoPalmap;
|
bool autoPalmap; // -Q
|
||||||
bool groupOutputs;
|
bool groupOutputs; // -O
|
||||||
bool reverse;
|
bool reverse; // -r
|
||||||
|
|
||||||
bool autoAny() const { return autoAttrmap || autoTilemap || autoPalettes || autoPalmap; }
|
bool autoAny() const { return autoAttrmap || autoTilemap || autoPalettes || autoPalmap; }
|
||||||
} localOptions;
|
} localOptions;
|
||||||
|
|
||||||
// Short options
|
// Short options
|
||||||
static char const *optstring = "-Aa:B:b:Cc:d:hi:L:l:mN:n:Oo:Pp:Qq:r:s:Tt:U:uVvW:wXx:YZ";
|
static char const *optstring = "Aa:B:b:Cc:d:hi:L:l:mN:n:Oo:Pp:Qq:r:s:Tt:U:uVvW:wXx:YZ";
|
||||||
|
|
||||||
// Variables for the long-only options
|
// Long-only option variable
|
||||||
static int longOpt; // `--color`
|
static int longOpt; // `--color`
|
||||||
|
|
||||||
// Equivalent long options
|
// Equivalent long options
|
||||||
@@ -138,418 +138,339 @@ static void skipBlankSpace(char const *&arg) {
|
|||||||
arg += strspn(arg, " \t");
|
arg += strspn(arg, " \t");
|
||||||
}
|
}
|
||||||
|
|
||||||
static void registerInput(char const *arg) {
|
static void parseArg(int ch, char *arg) {
|
||||||
if (!options.input.empty()) {
|
char const *argPtr = arg; // Make a copy for scanning
|
||||||
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`.
|
switch (ch) {
|
||||||
static std::vector<size_t> readAtFile(std::string const &path, std::vector<char> &argPool) {
|
case 'A':
|
||||||
File file;
|
localOptions.autoAttrmap = true;
|
||||||
if (!file.open(path, std::ios_base::in)) {
|
break;
|
||||||
fatal("Error reading at-file \"%s\": %s", file.c_str(path), strerror(errno));
|
|
||||||
|
case 'a':
|
||||||
|
localOptions.autoAttrmap = false;
|
||||||
|
if (!options.attrmap.empty()) {
|
||||||
|
warnx("Overriding attrmap file \"%s\"", options.attrmap.c_str());
|
||||||
|
}
|
||||||
|
options.attrmap = arg;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'B':
|
||||||
|
parseBackgroundPalSpec(arg);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'b': {
|
||||||
|
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 (*argPtr == '\0') {
|
||||||
|
options.baseTileIDs[1] = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
skipBlankSpace(argPtr);
|
||||||
|
if (*argPtr != ',') {
|
||||||
|
error("Base tile IDs must be one or two comma-separated numbers, not \"%s\"", arg);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
++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 (*argPtr != '\0') {
|
||||||
|
error("Base tile IDs must be one or two comma-separated numbers, not \"%s\"", arg);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (std::vector<size_t> argvOfs;;) {
|
case 'C':
|
||||||
int c = file->sbumpc();
|
options.useColorCurve = true;
|
||||||
|
break;
|
||||||
|
|
||||||
// First, discard any leading blank space
|
case 'c':
|
||||||
while (isBlankSpace(c)) {
|
localOptions.externalPalSpec = std::nullopt; // Allow overriding a previous pal spec
|
||||||
c = file->sbumpc();
|
if (arg[0] == '#') {
|
||||||
|
options.palSpecType = Options::EXPLICIT;
|
||||||
|
parseInlinePalSpec(arg);
|
||||||
|
} else if (strcasecmp(arg, "embedded") == 0) {
|
||||||
|
// Use PLTE, error out if missing
|
||||||
|
options.palSpecType = Options::EMBEDDED;
|
||||||
|
} else if (strcasecmp(arg, "auto") == 0) {
|
||||||
|
options.palSpecType = Options::NO_SPEC;
|
||||||
|
} else if (strcasecmp(arg, "dmg") == 0) {
|
||||||
|
options.palSpecType = Options::DMG;
|
||||||
|
parseDmgPalSpec(0xE4); // Same darkest-first order as `sortGrayscale`
|
||||||
|
} else if (strncasecmp(arg, "dmg=", literal_strlen("dmg=")) == 0) {
|
||||||
|
options.palSpecType = Options::DMG;
|
||||||
|
parseDmgPalSpec(&arg[literal_strlen("dmg=")]);
|
||||||
|
} else {
|
||||||
|
options.palSpecType = Options::EXPLICIT;
|
||||||
|
localOptions.externalPalSpec = arg;
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
// If it's a comment, discard everything until EOL
|
case 'd':
|
||||||
if (c == '#') {
|
options.bitDepth = readNumber(argPtr, "Bit depth", 2);
|
||||||
c = file->sbumpc();
|
if (*argPtr != '\0') {
|
||||||
while (c != EOF && !isNewline(c)) {
|
error("Bit depth ('-b') argument must be a valid number, not \"%s\"", arg);
|
||||||
c = file->sbumpc();
|
} else if (options.bitDepth != 1 && options.bitDepth != 2) {
|
||||||
}
|
error("Bit depth must be 1 or 2, not %" PRIu8, options.bitDepth);
|
||||||
|
options.bitDepth = 2;
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
if (c == EOF) {
|
// LCOV_EXCL_START
|
||||||
return argvOfs;
|
case 'h':
|
||||||
} else if (isNewline(c)) {
|
usage.printAndExit(0);
|
||||||
continue; // Start processing the next line
|
// LCOV_EXCL_STOP
|
||||||
|
|
||||||
|
case 'i':
|
||||||
|
if (!options.inputTileset.empty()) {
|
||||||
|
warnx("Overriding input tileset file \"%s\"", options.inputTileset.c_str());
|
||||||
}
|
}
|
||||||
|
options.inputTileset = arg;
|
||||||
|
break;
|
||||||
|
|
||||||
// Alright, now we can parse the line
|
case 'L':
|
||||||
do {
|
options.inputSlice.left = readNumber(argPtr, "Input slice left coordinate");
|
||||||
argvOfs.push_back(argPool.size());
|
if (options.inputSlice.left > INT16_MAX) {
|
||||||
|
error("Input slice left coordinate is out of range!");
|
||||||
// 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
|
|
||||||
|
|
||||||
switch (ch) {
|
|
||||||
case 'A':
|
|
||||||
localOptions.autoAttrmap = true;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'a':
|
|
||||||
localOptions.autoAttrmap = false;
|
|
||||||
if (!options.attrmap.empty()) {
|
|
||||||
warnx("Overriding attrmap file \"%s\"", options.attrmap.c_str());
|
|
||||||
}
|
|
||||||
options.attrmap = musl_optarg;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'B':
|
|
||||||
parseBackgroundPalSpec(musl_optarg);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'b': {
|
|
||||||
uint16_t number = readNumber(arg, "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') {
|
|
||||||
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
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
++arg; // Skip comma
|
|
||||||
skipBlankSpace(arg);
|
|
||||||
number = readNumber(arg, "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
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
skipBlankSpace(argPtr);
|
||||||
case 'C':
|
if (*argPtr != ',') {
|
||||||
options.useColorCurve = true;
|
error("Missing comma after left coordinate in \"%s\"", arg);
|
||||||
break;
|
|
||||||
|
|
||||||
case 'c':
|
|
||||||
localOptions.externalPalSpec = nullptr; // Allow overriding a previous pal spec
|
|
||||||
if (musl_optarg[0] == '#') {
|
|
||||||
options.palSpecType = Options::EXPLICIT;
|
|
||||||
parseInlinePalSpec(musl_optarg);
|
|
||||||
} else if (strcasecmp(musl_optarg, "embedded") == 0) {
|
|
||||||
// Use PLTE, error out if missing
|
|
||||||
options.palSpecType = Options::EMBEDDED;
|
|
||||||
} else if (strcasecmp(musl_optarg, "auto") == 0) {
|
|
||||||
options.palSpecType = Options::NO_SPEC;
|
|
||||||
} else if (strcasecmp(musl_optarg, "dmg") == 0) {
|
|
||||||
options.palSpecType = Options::DMG;
|
|
||||||
parseDmgPalSpec(0xE4); // Same darkest-first order as `sortGrayscale`
|
|
||||||
} else if (strncasecmp(musl_optarg, "dmg=", literal_strlen("dmg=")) == 0) {
|
|
||||||
options.palSpecType = Options::DMG;
|
|
||||||
parseDmgPalSpec(&musl_optarg[literal_strlen("dmg=")]);
|
|
||||||
} else {
|
|
||||||
options.palSpecType = Options::EXPLICIT;
|
|
||||||
localOptions.externalPalSpec = musl_optarg;
|
|
||||||
}
|
|
||||||
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);
|
|
||||||
} else if (options.bitDepth != 1 && options.bitDepth != 2) {
|
|
||||||
error("Bit depth must be 1 or 2, not %" PRIu8, options.bitDepth);
|
|
||||||
options.bitDepth = 2;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
// LCOV_EXCL_START
|
|
||||||
case 'h':
|
|
||||||
usage.printAndExit(0);
|
|
||||||
// LCOV_EXCL_STOP
|
|
||||||
|
|
||||||
case 'i':
|
|
||||||
if (!options.inputTileset.empty()) {
|
|
||||||
warnx("Overriding input tileset file \"%s\"", options.inputTileset.c_str());
|
|
||||||
}
|
|
||||||
options.inputTileset = musl_optarg;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'L':
|
|
||||||
options.inputSlice.left = readNumber(arg, "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);
|
|
||||||
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);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
++arg;
|
|
||||||
skipBlankSpace(arg);
|
|
||||||
options.inputSlice.width = readNumber(arg, "Input slice width");
|
|
||||||
skipBlankSpace(arg);
|
|
||||||
if (options.inputSlice.width == 0) {
|
|
||||||
error("Input slice width may not be 0!");
|
|
||||||
}
|
|
||||||
if (*arg != ',') {
|
|
||||||
error("Missing comma after width in \"%s\"", musl_optarg);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
++arg;
|
|
||||||
skipBlankSpace(arg);
|
|
||||||
options.inputSlice.height = readNumber(arg, "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);
|
|
||||||
}
|
|
||||||
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);
|
|
||||||
} else if (number >= 256) {
|
|
||||||
error("Base palette ID must be below 256");
|
|
||||||
} else {
|
|
||||||
options.basePalID = number;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
++argPtr;
|
||||||
case 'm':
|
skipBlankSpace(argPtr);
|
||||||
options.allowMirroringX = true; // Imply `-X`
|
options.inputSlice.top = readNumber(argPtr, "Input slice upper coordinate");
|
||||||
options.allowMirroringY = true; // Imply `-Y`
|
skipBlankSpace(argPtr);
|
||||||
[[fallthrough]]; // Imply `-u`
|
if (*argPtr != ':') {
|
||||||
|
error("Missing colon after upper coordinate in \"%s\"", arg);
|
||||||
case 'u':
|
|
||||||
options.allowDedup = true;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'N':
|
|
||||||
options.maxNbTiles[0] = readNumber(arg, "Number of tiles in bank 0", 256);
|
|
||||||
if (options.maxNbTiles[0] > 256) {
|
|
||||||
error("Bank 0 cannot contain more than 256 tiles");
|
|
||||||
}
|
|
||||||
if (*arg == '\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
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
++arg; // Skip comma
|
|
||||||
skipBlankSpace(arg);
|
|
||||||
options.maxNbTiles[1] = readNumber(arg, "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
|
|
||||||
);
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
if (number > 256) {
|
|
||||||
error("Number of palettes ('-n') must not exceed 256!");
|
|
||||||
} else if (number == 0) {
|
|
||||||
error("Number of palettes ('-n') may not be 0!");
|
|
||||||
} else {
|
|
||||||
options.nbPalettes = number;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
++argPtr;
|
||||||
case 'O':
|
skipBlankSpace(argPtr);
|
||||||
localOptions.groupOutputs = true;
|
options.inputSlice.width = readNumber(argPtr, "Input slice width");
|
||||||
break;
|
skipBlankSpace(argPtr);
|
||||||
|
if (options.inputSlice.width == 0) {
|
||||||
case 'o':
|
error("Input slice width may not be 0!");
|
||||||
if (!options.output.empty()) {
|
|
||||||
warnx("Overriding tile data file %s", options.output.c_str());
|
|
||||||
}
|
|
||||||
options.output = musl_optarg;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'P':
|
|
||||||
localOptions.autoPalettes = true;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'p':
|
|
||||||
localOptions.autoPalettes = false;
|
|
||||||
if (!options.palettes.empty()) {
|
|
||||||
warnx("Overriding palettes file %s", options.palettes.c_str());
|
|
||||||
}
|
|
||||||
options.palettes = musl_optarg;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'Q':
|
|
||||||
localOptions.autoPalmap = true;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'q':
|
|
||||||
localOptions.autoPalmap = false;
|
|
||||||
if (!options.palmap.empty()) {
|
|
||||||
warnx("Overriding palette map file %s", options.palmap.c_str());
|
|
||||||
}
|
|
||||||
options.palmap = musl_optarg;
|
|
||||||
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
|
|
||||||
);
|
|
||||||
}
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
if (options.nbColorsPerPal > 4) {
|
|
||||||
error("Palette size ('-s') must not exceed 4!");
|
|
||||||
} else if (options.nbColorsPerPal == 0) {
|
|
||||||
error("Palette size ('-s') may not be 0!");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'T':
|
|
||||||
localOptions.autoTilemap = true;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 't':
|
|
||||||
localOptions.autoTilemap = false;
|
|
||||||
if (!options.tilemap.empty()) {
|
|
||||||
warnx("Overriding tilemap file %s", options.tilemap.c_str());
|
|
||||||
}
|
|
||||||
options.tilemap = musl_optarg;
|
|
||||||
break;
|
|
||||||
|
|
||||||
// LCOV_EXCL_START
|
|
||||||
case 'V':
|
|
||||||
printf("rgbgfx %s\n", get_package_version_string());
|
|
||||||
exit(0);
|
|
||||||
|
|
||||||
case 'v':
|
|
||||||
incrementVerbosity();
|
|
||||||
break;
|
|
||||||
// LCOV_EXCL_STOP
|
|
||||||
|
|
||||||
case 'W':
|
|
||||||
warnings.processWarningFlag(musl_optarg);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'w':
|
|
||||||
warnings.state.warningsEnabled = false;
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'X':
|
|
||||||
options.allowMirroringX = true;
|
|
||||||
options.allowDedup = true; // Imply `-u`
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'Y':
|
|
||||||
options.allowMirroringY = true;
|
|
||||||
options.allowDedup = true; // Imply `-u`
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'Z':
|
|
||||||
options.columnMajor = true;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 0: // Long-only options
|
|
||||||
if (longOpt == 'c' && !style_Parse(musl_optarg)) {
|
|
||||||
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];
|
|
||||||
} else {
|
|
||||||
registerInput(musl_optarg);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
// LCOV_EXCL_START
|
|
||||||
default:
|
|
||||||
usage.printAndExit(1);
|
|
||||||
// LCOV_EXCL_STOP
|
|
||||||
}
|
}
|
||||||
|
if (*argPtr != ',') {
|
||||||
|
error("Missing comma after width in \"%s\"", arg);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
++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 (*argPtr != '\0') {
|
||||||
|
error("Unexpected extra characters after slice spec in \"%s\"", arg);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'l': {
|
||||||
|
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 {
|
||||||
|
options.basePalID = number;
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return nullptr; // Done processing this argv
|
case 'm':
|
||||||
|
options.allowMirroringX = true; // Imply `-X`
|
||||||
|
options.allowMirroringY = true; // Imply `-Y`
|
||||||
|
[[fallthrough]]; // Imply `-u`
|
||||||
|
|
||||||
|
case 'u':
|
||||||
|
options.allowDedup = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'N':
|
||||||
|
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 (*argPtr == '\0') {
|
||||||
|
options.maxNbTiles[1] = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
skipBlankSpace(argPtr);
|
||||||
|
if (*argPtr != ',') {
|
||||||
|
error("Bank capacity must be one or two comma-separated numbers, not \"%s\"", arg);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
++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 (*argPtr != '\0') {
|
||||||
|
error("Bank capacity must be one or two comma-separated numbers, not \"%s\"", arg);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'n': {
|
||||||
|
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!");
|
||||||
|
} else if (number == 0) {
|
||||||
|
error("Number of palettes ('-n') may not be 0!");
|
||||||
|
} else {
|
||||||
|
options.nbPalettes = number;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'O':
|
||||||
|
localOptions.groupOutputs = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'o':
|
||||||
|
if (!options.output.empty()) {
|
||||||
|
warnx("Overriding tile data file %s", options.output.c_str());
|
||||||
|
}
|
||||||
|
options.output = arg;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'P':
|
||||||
|
localOptions.autoPalettes = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'p':
|
||||||
|
localOptions.autoPalettes = false;
|
||||||
|
if (!options.palettes.empty()) {
|
||||||
|
warnx("Overriding palettes file %s", options.palettes.c_str());
|
||||||
|
}
|
||||||
|
options.palettes = arg;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'Q':
|
||||||
|
localOptions.autoPalmap = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'q':
|
||||||
|
localOptions.autoPalmap = false;
|
||||||
|
if (!options.palmap.empty()) {
|
||||||
|
warnx("Overriding palette map file %s", options.palmap.c_str());
|
||||||
|
}
|
||||||
|
options.palmap = arg;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'r':
|
||||||
|
localOptions.reverse = true;
|
||||||
|
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(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!");
|
||||||
|
} else if (options.nbColorsPerPal == 0) {
|
||||||
|
error("Palette size ('-s') may not be 0!");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'T':
|
||||||
|
localOptions.autoTilemap = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 't':
|
||||||
|
localOptions.autoTilemap = false;
|
||||||
|
if (!options.tilemap.empty()) {
|
||||||
|
warnx("Overriding tilemap file %s", options.tilemap.c_str());
|
||||||
|
}
|
||||||
|
options.tilemap = arg;
|
||||||
|
break;
|
||||||
|
|
||||||
|
// LCOV_EXCL_START
|
||||||
|
case 'V':
|
||||||
|
printf("rgbgfx %s\n", get_package_version_string());
|
||||||
|
exit(0);
|
||||||
|
|
||||||
|
case 'v':
|
||||||
|
incrementVerbosity();
|
||||||
|
break;
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
|
|
||||||
|
case 'W':
|
||||||
|
warnings.processWarningFlag(arg);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'w':
|
||||||
|
warnings.state.warningsEnabled = false;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'x':
|
||||||
|
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;
|
||||||
|
|
||||||
|
case 'X':
|
||||||
|
options.allowMirroringX = true;
|
||||||
|
options.allowDedup = true; // Imply `-u`
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'Y':
|
||||||
|
options.allowMirroringY = true;
|
||||||
|
options.allowDedup = true; // Imply `-u`
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'Z':
|
||||||
|
options.columnMajor = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0: // Long-only options
|
||||||
|
if (longOpt == 'c' && !style_Parse(arg)) {
|
||||||
|
fatal("Invalid argument for option '--color'");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
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 {
|
||||||
|
options.input = arg;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
// LCOV_EXCL_START
|
||||||
|
default:
|
||||||
|
usage.printAndExit(1);
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// LCOV_EXCL_START
|
// LCOV_EXCL_START
|
||||||
@@ -718,70 +639,7 @@ static void replaceExtension(std::string &path, char const *extension) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
struct AtFileStackEntry {
|
cli_ParseArgs(argc, argv, optstring, longopts, parseArg, usage);
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.nbColorsPerPal == 0) {
|
if (options.nbColorsPerPal == 0) {
|
||||||
options.nbColorsPerPal = 1u << options.bitDepth;
|
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
|
// Execute deferred external pal spec parsing, now that all other params are known
|
||||||
if (localOptions.externalPalSpec) {
|
if (localOptions.externalPalSpec) {
|
||||||
parseExternalPalSpec(localOptions.externalPalSpec);
|
parseExternalPalSpec(localOptions.externalPalSpec->c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
verboseOutputConfig(); // LCOV_EXCL_LINE
|
verboseOutputConfig(); // LCOV_EXCL_LINE
|
||||||
@@ -848,47 +706,3 @@ int main(int argc, char *argv[]) {
|
|||||||
requireZeroErrors();
|
requireZeroErrors();
|
||||||
return 0;
|
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();
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
// Template class for both const and non-const iterators over the non-empty `_assigned` slots
|
// 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 {
|
class AssignedSetsIter {
|
||||||
public:
|
public:
|
||||||
friend class AssignedSets;
|
friend class AssignedSets;
|
||||||
@@ -84,7 +84,7 @@ private:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
Constness<decltype(_assigned)> *_array = nullptr;
|
Constness<decltype(_assigned)> *_array = nullptr;
|
||||||
I _iter{};
|
IteratorT _iter{};
|
||||||
|
|
||||||
AssignedSetsIter(decltype(_array) array, decltype(_iter) &&iter)
|
AssignedSetsIter(decltype(_array) array, decltype(_iter) &&iter)
|
||||||
: _array(array), _iter(iter) {}
|
: _array(array), _iter(iter) {}
|
||||||
@@ -164,11 +164,11 @@ public:
|
|||||||
size_t nbColorSets() const { return std::distance(RANGE(*this)); }
|
size_t nbColorSets() const { return std::distance(RANGE(*this)); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
template<typename I>
|
template<typename IteratorT>
|
||||||
static void addUniqueColors(
|
static void addUniqueColors(
|
||||||
std::unordered_set<uint16_t> &colors,
|
std::unordered_set<uint16_t> &colors,
|
||||||
I iter,
|
IteratorT iter,
|
||||||
I const &end,
|
IteratorT const &end,
|
||||||
std::vector<ColorSet> const &colorSets
|
std::vector<ColorSet> const &colorSets
|
||||||
) {
|
) {
|
||||||
for (; iter != end; ++iter) {
|
for (; iter != end; ++iter) {
|
||||||
@@ -177,27 +177,31 @@ private:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This function should stay private because it returns a reference to a unique object
|
public:
|
||||||
std::unordered_set<uint16_t> &uniqueColors() const {
|
// Returns the set of distinct colors
|
||||||
// We check for *distinct* colors by stuffing them into a `set`; this should be
|
std::unordered_set<uint16_t> uniqueColors() const {
|
||||||
// faster than "back-checking" on every element (O(n^2))
|
std::unordered_set<uint16_t> colors;
|
||||||
static std::unordered_set<uint16_t> colors;
|
|
||||||
|
|
||||||
colors.clear();
|
|
||||||
addUniqueColors(colors, RANGE(*this), *_colorSets);
|
addUniqueColors(colors, RANGE(*this), *_colorSets);
|
||||||
return colors;
|
return colors;
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
|
||||||
// Returns the number of distinct colors
|
// Returns the number of distinct colors
|
||||||
size_t volume() const { return uniqueColors().size(); }
|
size_t volume() const { return uniqueColors().size(); }
|
||||||
|
|
||||||
bool canFit(ColorSet const &colorSet) const {
|
bool canFit(ColorSet const &colorSet) const {
|
||||||
std::unordered_set<uint16_t> &colors = uniqueColors();
|
std::unordered_set<uint16_t> colors = uniqueColors();
|
||||||
colors.insert(RANGE(colorSet));
|
colors.insert(RANGE(colorSet));
|
||||||
return colors.size() <= options.maxOpaqueColors();
|
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 `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.
|
// the reciprocal of the "multiplicity" of the color across "our" color sets.
|
||||||
// However, literally computing the reciprocals would involve floating-point division, which
|
// 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;
|
// 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.
|
// it's a measure of how much this color set would "cost" to introduce.
|
||||||
uint32_t relSizeOf(ColorSet const &colorSet) const {
|
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;
|
uint32_t relSize = 0;
|
||||||
for (uint16_t color : colorSet) {
|
for (uint16_t color : colorSet) {
|
||||||
// How many of our color sets does this color also belong to?
|
uint32_t n = multiplicity(color);
|
||||||
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();
|
|
||||||
});
|
|
||||||
// We increase the denominator by 1 here; the reference code does this,
|
// 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
|
// 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
|
// (that is, if the color is not found in any color set), and adding 1 still seems
|
||||||
// to preserve the paper's reasoning.
|
// to preserve the paper's reasoning.
|
||||||
//
|
//
|
||||||
// The scale factor should ensure integer divisions only.
|
// The scale factor should ensure integer divisions only.
|
||||||
assume(scaleFactor % (multiplicity + 1) == 0);
|
assume(scaleFactor % (n + 1) == 0);
|
||||||
relSize += scaleFactor / (multiplicity + 1);
|
relSize += scaleFactor / (n + 1);
|
||||||
}
|
}
|
||||||
return relSize;
|
return relSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Computes the "relative size" of a set of color sets on this palette
|
// Computes the "relative size" of a set of color sets on this palette
|
||||||
template<typename I>
|
template<typename IteratorT>
|
||||||
size_t combinedVolume(I &&begin, I const &end, std::vector<ColorSet> const &colorSets) const {
|
size_t combinedVolume(
|
||||||
std::unordered_set<uint16_t> &colors = uniqueColors();
|
IteratorT &&begin, IteratorT const &end, std::vector<ColorSet> const &colorSets
|
||||||
addUniqueColors(colors, std::forward<I>(begin), end, colorSets);
|
) const {
|
||||||
|
std::unordered_set<uint16_t> colors = uniqueColors();
|
||||||
|
addUniqueColors(colors, std::forward<IteratorT>(begin), end, colorSets);
|
||||||
return colors.size();
|
return colors.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Computes the "relative size" of a set of colors on this palette
|
// Computes the "relative size" of a set of colors on this palette
|
||||||
template<typename I>
|
template<typename IteratorT>
|
||||||
size_t combinedVolume(I &&begin, I &&end) const {
|
size_t combinedVolume(IteratorT &&begin, IteratorT &&end) const {
|
||||||
std::unordered_set<uint16_t> &colors = uniqueColors();
|
std::unordered_set<uint16_t> colors = uniqueColors();
|
||||||
colors.insert(std::forward<I>(begin), std::forward<I>(end));
|
colors.insert(std::forward<IteratorT>(begin), std::forward<IteratorT>(end));
|
||||||
return colors.size();
|
return colors.size();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
#include "helpers.hpp"
|
#include "helpers.hpp"
|
||||||
#include "verbosity.hpp"
|
#include "verbosity.hpp"
|
||||||
|
|
||||||
#include "gfx/main.hpp"
|
#include "gfx/palette.hpp"
|
||||||
#include "gfx/rgba.hpp"
|
#include "gfx/rgba.hpp"
|
||||||
|
|
||||||
void sortIndexed(std::vector<Palette> &palettes, std::vector<Rgba> const &embPal) {
|
void sortIndexed(std::vector<Palette> &palettes, std::vector<Rgba> const &embPal) {
|
||||||
|
|||||||
@@ -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(
|
static void warnExtraColors(
|
||||||
char const *kind, char const *filename, uint16_t nbColors, uint16_t maxNbColors
|
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
|
// Parses the initial part of a string_view, advancing the "read index" as it does
|
||||||
template<typename U> // Should be uint*_t
|
template<typename UintT> // Should be uint*_t
|
||||||
static std::optional<U> parseDec(std::string const &str, size_t &n) {
|
static std::optional<UintT> parseDec(std::string const &str, size_t &n) {
|
||||||
uintmax_t value = 0;
|
uintmax_t value = 0;
|
||||||
auto result = std::from_chars(str.data() + n, str.data() + str.length(), value);
|
auto result = std::from_chars(str.data() + n, str.data() + str.length(), value);
|
||||||
if (static_cast<bool>(result.ec)) {
|
if (static_cast<bool>(result.ec)) {
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
n = result.ptr - str.data();
|
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) {
|
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) {
|
static void parsePSPFile(char const *filename, std::filebuf &file) {
|
||||||
// https://www.selapa.net/swatches/colors/fileformats.php#psp_pal
|
// 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;
|
std::string line;
|
||||||
if (!readLine(file, line) || line != "JASC-PAL") {
|
if (!readLine(file, line) || line != "JASC-PAL") {
|
||||||
error("File \"%s\" is not a valid PSP palette file", filename);
|
error("File \"%s\" is not a valid PSP palette file", filename);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
line.clear();
|
requireLine();
|
||||||
requireLine("PSP", filename, file, line);
|
|
||||||
if (line != "0100") {
|
if (line != "0100") {
|
||||||
error("Unsupported PSP palette file version \"%s\"", line.c_str());
|
error("Unsupported PSP palette file version \"%s\"", line.c_str());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
line.clear();
|
requireLine();
|
||||||
requireLine("PSP", filename, file, line);
|
|
||||||
size_t n = 0;
|
size_t n = 0;
|
||||||
std::optional<uint16_t> nbColors = parseDec<uint16_t>(line, n);
|
std::optional<uint16_t> nbColors = parseDec<uint16_t>(line, n);
|
||||||
if (!nbColors || n != line.length()) {
|
if (!nbColors || n != line.length()) {
|
||||||
@@ -284,8 +283,7 @@ static void parsePSPFile(char const *filename, std::filebuf &file) {
|
|||||||
options.palSpec.clear();
|
options.palSpec.clear();
|
||||||
|
|
||||||
for (uint16_t i = 0; i < *nbColors; ++i) {
|
for (uint16_t i = 0; i < *nbColors; ++i) {
|
||||||
line.clear();
|
requireLine();
|
||||||
requireLine("PSP", filename, file, line);
|
|
||||||
|
|
||||||
n = 0;
|
n = 0;
|
||||||
std::optional<Rgba> color = parseColor(line, n, i + 1);
|
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;
|
options.palSpec.back()[i % options.nbColorsPerPal] = *color;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#undef requireLine
|
||||||
}
|
}
|
||||||
|
|
||||||
static void parseGPLFile(char const *filename, std::filebuf &file) {
|
static void parseGPLFile(char const *filename, std::filebuf &file) {
|
||||||
|
|||||||
55
src/gfx/palette.cpp
Normal file
55
src/gfx/palette.cpp
Normal 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();
|
||||||
|
}
|
||||||
@@ -31,15 +31,19 @@ struct Input {
|
|||||||
|
|
||||||
[[noreturn]]
|
[[noreturn]]
|
||||||
static void handleError(png_structp png, char const *msg) {
|
static void handleError(png_structp png, char const *msg) {
|
||||||
Input const &input = *reinterpret_cast<Input *>(png_get_error_ptr(png));
|
fatal(
|
||||||
|
"libpng error while reading PNG image (\"%s\"): %s",
|
||||||
fatal("Error reading PNG image (\"%s\"): %s", input.filename, msg);
|
reinterpret_cast<Input *>(png_get_error_ptr(png))->filename,
|
||||||
|
msg
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void handleWarning(png_structp png, char const *msg) {
|
static void handleWarning(png_structp png, char const *msg) {
|
||||||
Input const &input = *reinterpret_cast<Input *>(png_get_error_ptr(png));
|
warnx(
|
||||||
|
"libpng found while reading PNG image (\"%s\"): %s",
|
||||||
warnx("In PNG image (\"%s\"): %s", input.filename, msg);
|
reinterpret_cast<Input *>(png_get_error_ptr(png))->filename,
|
||||||
|
msg
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void readData(png_structp png, png_bytep data, size_t length) {
|
static void readData(png_structp png, png_bytep data, size_t length) {
|
||||||
|
|||||||
@@ -26,9 +26,11 @@
|
|||||||
#include "verbosity.hpp"
|
#include "verbosity.hpp"
|
||||||
|
|
||||||
#include "gfx/color_set.hpp"
|
#include "gfx/color_set.hpp"
|
||||||
|
#include "gfx/flip.hpp"
|
||||||
#include "gfx/main.hpp"
|
#include "gfx/main.hpp"
|
||||||
#include "gfx/pal_packing.hpp"
|
#include "gfx/pal_packing.hpp"
|
||||||
#include "gfx/pal_sorting.hpp"
|
#include "gfx/pal_sorting.hpp"
|
||||||
|
#include "gfx/palette.hpp"
|
||||||
#include "gfx/png.hpp"
|
#include "gfx/png.hpp"
|
||||||
#include "gfx/rgba.hpp"
|
#include "gfx/rgba.hpp"
|
||||||
#include "gfx/warning.hpp"
|
#include "gfx/warning.hpp"
|
||||||
@@ -695,12 +697,6 @@ static void outputUnoptimizedMaps(
|
|||||||
uint8_t tileID = 0;
|
uint8_t tileID = 0;
|
||||||
uint8_t bank = 0;
|
uint8_t bank = 0;
|
||||||
for (AttrmapEntry const &attr : attrmap) {
|
for (AttrmapEntry const &attr : attrmap) {
|
||||||
if (tileID == options.maxNbTiles[bank]) {
|
|
||||||
assume(bank == 0);
|
|
||||||
bank = 1;
|
|
||||||
tileID = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tilemapOutput.has_value()) {
|
if (tilemapOutput.has_value()) {
|
||||||
(*tilemapOutput)
|
(*tilemapOutput)
|
||||||
->sputc((attr.isBackgroundTile() ? 0 : tileID) + options.baseTileIDs[bank]);
|
->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.
|
// 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;
|
++tileID;
|
||||||
|
} else {
|
||||||
|
assume(bank == 0);
|
||||||
|
bank = 1;
|
||||||
|
tileID = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
#include "helpers.hpp" // assume
|
#include "helpers.hpp" // assume
|
||||||
#include "verbosity.hpp"
|
#include "verbosity.hpp"
|
||||||
|
|
||||||
|
#include "gfx/flip.hpp"
|
||||||
#include "gfx/main.hpp"
|
#include "gfx/main.hpp"
|
||||||
#include "gfx/rgba.hpp"
|
#include "gfx/rgba.hpp"
|
||||||
#include "gfx/warning.hpp"
|
#include "gfx/warning.hpp"
|
||||||
@@ -61,16 +62,16 @@ static std::vector<uint8_t> readInto(std::string const &path) {
|
|||||||
[[noreturn]]
|
[[noreturn]]
|
||||||
static void pngError(png_structp png, char const *msg) {
|
static void pngError(png_structp png, char const *msg) {
|
||||||
fatal(
|
fatal(
|
||||||
"Error writing reversed image (\"%s\"): %s",
|
"libpng error while writing reversed image (\"%s\"): %s",
|
||||||
static_cast<char const *>(png_get_error_ptr(png)),
|
reinterpret_cast<char const *>(png_get_error_ptr(png)),
|
||||||
msg
|
msg
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void pngWarning(png_structp png, char const *msg) {
|
static void pngWarning(png_structp png, char const *msg) {
|
||||||
warnx(
|
warnx(
|
||||||
"While writing reversed image (\"%s\"): %s",
|
"libpng found while writing reversed image (\"%s\"): %s",
|
||||||
static_cast<char const *>(png_get_error_ptr(png)),
|
reinterpret_cast<char const *>(png_get_error_ptr(png)),
|
||||||
msg
|
msg
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,10 +50,9 @@ uint16_t Rgba::cgbColor() const {
|
|||||||
g = reverse_curve[g];
|
g = reverse_curve[g];
|
||||||
b = reverse_curve[b];
|
b = reverse_curve[b];
|
||||||
} else {
|
} else {
|
||||||
constexpr auto _8to5 = [](uint8_t c) -> uint8_t { return (c * 31 + 127) / 255; };
|
r >>= 3;
|
||||||
r = _8to5(r);
|
g >>= 3;
|
||||||
g = _8to5(g);
|
b >>= 3;
|
||||||
b = _8to5(b);
|
|
||||||
}
|
}
|
||||||
return r | g << 5 | b << 10;
|
return r | g << 5 | b << 10;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,9 +53,9 @@ static void initFreeSpace() {
|
|||||||
static void assignSection(Section §ion, MemoryLocation const &location) {
|
static void assignSection(Section §ion, MemoryLocation const &location) {
|
||||||
// Propagate the assigned location to all UNIONs/FRAGMENTs
|
// Propagate the assigned location to all UNIONs/FRAGMENTs
|
||||||
// so `jr` patches in them will have the correct offset
|
// so `jr` patches in them will have the correct offset
|
||||||
for (Section *piece = §ion; piece != nullptr; piece = piece->nextPiece.get()) {
|
for (Section &piece : section.pieces()) {
|
||||||
piece->org = location.address;
|
piece.org = location.address;
|
||||||
piece->bank = location.bank;
|
piece.bank = location.bank;
|
||||||
}
|
}
|
||||||
out_AddSection(section);
|
out_AddSection(section);
|
||||||
}
|
}
|
||||||
@@ -118,139 +118,127 @@ static MemoryLocation getStartLocation(Section const §ion) {
|
|||||||
static std::optional<size_t> getPlacement(Section const §ion, MemoryLocation &location) {
|
static std::optional<size_t> getPlacement(Section const §ion, MemoryLocation &location) {
|
||||||
SectionTypeInfo const &typeInfo = sectionTypeInfo[section.type];
|
SectionTypeInfo const &typeInfo = sectionTypeInfo[section.type];
|
||||||
|
|
||||||
// Switch to the beginning of the next bank
|
for (;;) {
|
||||||
std::deque<FreeSpace> &bankMem = memory[section.type][location.bank - typeInfo.firstBank];
|
// Switch to the beginning of the next bank
|
||||||
size_t spaceIdx = 0;
|
std::deque<FreeSpace> &bankMem = memory[section.type][location.bank - typeInfo.firstBank];
|
||||||
|
size_t spaceIdx = 0;
|
||||||
|
|
||||||
if (spaceIdx < bankMem.size()) {
|
if (spaceIdx < bankMem.size()) {
|
||||||
location.address = bankMem[spaceIdx].address;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process locations in that bank
|
|
||||||
while (spaceIdx < bankMem.size()) {
|
|
||||||
// If that location is OK, return it
|
|
||||||
if (isLocationSuitable(section, bankMem[spaceIdx], location)) {
|
|
||||||
return spaceIdx;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
} else if (section.isAlignFixed) {
|
|
||||||
// Move to next aligned location
|
|
||||||
// Move back to alignment boundary
|
|
||||||
location.address -= section.alignOfs;
|
|
||||||
// Ensure we're there (e.g. on first check)
|
|
||||||
location.address &= ~section.alignMask;
|
|
||||||
// Go to next align boundary and add offset
|
|
||||||
location.address += section.alignMask + 1 + section.alignOfs;
|
|
||||||
} else if (++spaceIdx < bankMem.size()) {
|
|
||||||
// Any location is fine, so, next free block
|
|
||||||
location.address = bankMem[spaceIdx].address;
|
location.address = bankMem[spaceIdx].address;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If that location is past the current block's end,
|
// Process locations in that bank
|
||||||
// go forwards until that is no longer the case.
|
while (spaceIdx < bankMem.size()) {
|
||||||
while (spaceIdx < bankMem.size()
|
// If that location is OK, return it
|
||||||
&& location.address >= bankMem[spaceIdx].address + bankMem[spaceIdx].size) {
|
if (isLocationSuitable(section, bankMem[spaceIdx], location)) {
|
||||||
++spaceIdx;
|
return spaceIdx;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 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
|
||||||
|
location.address -= section.alignOfs;
|
||||||
|
// Ensure we're there (e.g. on first check)
|
||||||
|
location.address &= ~section.alignMask;
|
||||||
|
// Go to next align boundary and add offset
|
||||||
|
location.address += section.alignMask + 1 + section.alignOfs;
|
||||||
|
} else if (++spaceIdx < bankMem.size()) {
|
||||||
|
// Any location is fine, so, next free block
|
||||||
|
location.address = bankMem[spaceIdx].address;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If that location is past the current block's end,
|
||||||
|
// go forwards until that is no longer the case.
|
||||||
|
while (spaceIdx < bankMem.size()
|
||||||
|
&& location.address >= bankMem[spaceIdx].address + bankMem[spaceIdx].size) {
|
||||||
|
++spaceIdx;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try again with the new location/free space combo
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try again with the new location/free space combo
|
// Try again in the next bank, if one is available.
|
||||||
|
// Try scrambled banks in descending order until no bank in the scrambled range is
|
||||||
|
// available. Otherwise, try in ascending order.
|
||||||
|
if (section.isBankFixed) {
|
||||||
|
return std::nullopt;
|
||||||
|
} else if (options.scrambleROMX && section.type == SECTTYPE_ROMX
|
||||||
|
&& location.bank <= options.scrambleROMX) {
|
||||||
|
if (location.bank > typeInfo.firstBank) {
|
||||||
|
--location.bank;
|
||||||
|
} else if (options.scrambleROMX < typeInfo.lastBank) {
|
||||||
|
location.bank = options.scrambleROMX + 1;
|
||||||
|
} else {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
} else if (options.scrambleWRAMX && section.type == SECTTYPE_WRAMX
|
||||||
|
&& location.bank <= options.scrambleWRAMX) {
|
||||||
|
if (location.bank > typeInfo.firstBank) {
|
||||||
|
--location.bank;
|
||||||
|
} else if (options.scrambleWRAMX < typeInfo.lastBank) {
|
||||||
|
location.bank = options.scrambleWRAMX + 1;
|
||||||
|
} else {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
} else if (options.scrambleSRAM && section.type == SECTTYPE_SRAM
|
||||||
|
&& location.bank <= options.scrambleSRAM) {
|
||||||
|
if (location.bank > typeInfo.firstBank) {
|
||||||
|
--location.bank;
|
||||||
|
} else if (options.scrambleSRAM < typeInfo.lastBank) {
|
||||||
|
location.bank = options.scrambleSRAM + 1;
|
||||||
|
} else {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
} else if (location.bank < typeInfo.lastBank) {
|
||||||
|
++location.bank;
|
||||||
|
} else {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try again in the next iteration.
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try again in the next bank, if one is available.
|
|
||||||
// Try scrambled banks in descending order until no bank in the scrambled range is
|
|
||||||
// available. Otherwise, try in ascending order.
|
|
||||||
if (section.isBankFixed) {
|
|
||||||
return std::nullopt;
|
|
||||||
} else if (options.scrambleROMX && section.type == SECTTYPE_ROMX
|
|
||||||
&& location.bank <= options.scrambleROMX) {
|
|
||||||
if (location.bank > typeInfo.firstBank) {
|
|
||||||
--location.bank;
|
|
||||||
} else if (options.scrambleROMX < typeInfo.lastBank) {
|
|
||||||
location.bank = options.scrambleROMX + 1;
|
|
||||||
} else {
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
} else if (options.scrambleWRAMX && section.type == SECTTYPE_WRAMX
|
|
||||||
&& location.bank <= options.scrambleWRAMX) {
|
|
||||||
if (location.bank > typeInfo.firstBank) {
|
|
||||||
--location.bank;
|
|
||||||
} else if (options.scrambleWRAMX < typeInfo.lastBank) {
|
|
||||||
location.bank = options.scrambleWRAMX + 1;
|
|
||||||
} else {
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
} else if (options.scrambleSRAM && section.type == SECTTYPE_SRAM
|
|
||||||
&& location.bank <= options.scrambleSRAM) {
|
|
||||||
if (location.bank > typeInfo.firstBank) {
|
|
||||||
--location.bank;
|
|
||||||
} else if (options.scrambleSRAM < typeInfo.lastBank) {
|
|
||||||
location.bank = options.scrambleSRAM + 1;
|
|
||||||
} else {
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
} else if (location.bank < typeInfo.lastBank) {
|
|
||||||
++location.bank;
|
|
||||||
} else {
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
return getPlacement(section, location); // Tail recursion
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::string getSectionDescription(Section const §ion) {
|
static std::string getSectionDescription(Section const §ion) {
|
||||||
std::string where;
|
std::string description =
|
||||||
|
"\"" + section.name + "\" (" + sectionTypeInfo[section.type].name + " section) ";
|
||||||
char bank[8], addr[8], mask[8], offset[8];
|
|
||||||
if (section.isBankFixed && sectTypeBanks(section.type) != 1) {
|
if (section.isBankFixed && sectTypeBanks(section.type) != 1) {
|
||||||
|
char bank[8];
|
||||||
snprintf(bank, sizeof(bank), "%02" PRIx32, section.bank);
|
snprintf(bank, sizeof(bank), "%02" PRIx32, section.bank);
|
||||||
}
|
|
||||||
if (section.isAddressFixed) {
|
|
||||||
snprintf(addr, sizeof(addr), "%04" PRIx16, section.org);
|
|
||||||
}
|
|
||||||
if (section.isAlignFixed) {
|
|
||||||
snprintf(mask, sizeof(mask), "%" PRIx16, static_cast<uint16_t>(~section.alignMask));
|
|
||||||
snprintf(offset, sizeof(offset), "%" PRIx16, section.alignOfs);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (section.isBankFixed && sectTypeBanks(section.type) != 1) {
|
|
||||||
if (section.isAddressFixed) {
|
if (section.isAddressFixed) {
|
||||||
where = "at $";
|
char addr[8];
|
||||||
where += bank;
|
snprintf(addr, sizeof(addr), "%04" PRIx16, section.org);
|
||||||
where += ":";
|
description = description + "at $" + bank + ":" + addr;
|
||||||
where += addr;
|
|
||||||
} else if (section.isAlignFixed) {
|
} else if (section.isAlignFixed) {
|
||||||
where = "in bank $";
|
char mask[8];
|
||||||
where += bank;
|
snprintf(mask, sizeof(mask), "%" PRIx16, static_cast<uint16_t>(~section.alignMask));
|
||||||
where += " with align mask $";
|
description = description + "in bank $" + bank + " with align mask $" + mask;
|
||||||
where += mask;
|
|
||||||
} else {
|
} else {
|
||||||
where = "in bank $";
|
description = description + "in bank $" + bank;
|
||||||
where += bank;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (section.isAddressFixed) {
|
if (section.isAddressFixed) {
|
||||||
where = "at address $";
|
char addr[8];
|
||||||
where += addr;
|
snprintf(addr, sizeof(addr), "%04" PRIx16, section.org);
|
||||||
|
description = description + "at address $" + addr;
|
||||||
} else if (section.isAlignFixed) {
|
} else if (section.isAlignFixed) {
|
||||||
where = "with align mask $";
|
char mask[8], offset[8];
|
||||||
where += mask;
|
snprintf(mask, sizeof(mask), "%" PRIx16, static_cast<uint16_t>(~section.alignMask));
|
||||||
where += " and offset $";
|
snprintf(offset, sizeof(offset), "%" PRIx16, section.alignOfs);
|
||||||
where += offset;
|
description = description + "with align mask $" + mask + " and offset $" + offset;
|
||||||
} else {
|
} else {
|
||||||
where = "anywhere";
|
description = description + "anywhere";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return description;
|
||||||
return where;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Places a section in a suitable location, or error out if it fails to.
|
// Places a section in a suitable location, or error out if it fails to.
|
||||||
@@ -309,19 +297,11 @@ static void placeSection(Section §ion) {
|
|||||||
|
|
||||||
if (!section.isBankFixed || !section.isAddressFixed) {
|
if (!section.isBankFixed || !section.isAddressFixed) {
|
||||||
// If a section failed to go to several places, nothing we can report
|
// If a section failed to go to several places, nothing we can report
|
||||||
fatal(
|
fatal("Unable to place %s", getSectionDescription(section).c_str());
|
||||||
"Unable to place \"%s\" (%s section) %s",
|
|
||||||
section.name.c_str(),
|
|
||||||
sectionTypeInfo[section.type].name.c_str(),
|
|
||||||
getSectionDescription(section).c_str()
|
|
||||||
);
|
|
||||||
} else if (section.org + section.size > sectTypeEndAddr(section.type) + 1) {
|
} else if (section.org + section.size > sectTypeEndAddr(section.type) + 1) {
|
||||||
// If the section just can't fit the bank, report that
|
// If the section just can't fit the bank, report that
|
||||||
fatal(
|
fatal(
|
||||||
"Unable to place \"%s\" (%s section) %s: section runs past end of region ($%04x > "
|
"Unable to place %s: section runs past end of region ($%04x > $%04x)",
|
||||||
"$%04x)",
|
|
||||||
section.name.c_str(),
|
|
||||||
sectionTypeInfo[section.type].name.c_str(),
|
|
||||||
getSectionDescription(section).c_str(),
|
getSectionDescription(section).c_str(),
|
||||||
section.org + section.size,
|
section.org + section.size,
|
||||||
sectTypeEndAddr(section.type) + 1
|
sectTypeEndAddr(section.type) + 1
|
||||||
@@ -329,9 +309,7 @@ static void placeSection(Section §ion) {
|
|||||||
} else {
|
} else {
|
||||||
// Otherwise there is overlap with another section
|
// Otherwise there is overlap with another section
|
||||||
fatal(
|
fatal(
|
||||||
"Unable to place \"%s\" (%s section) %s: section overlaps with \"%s\"",
|
"Unable to place %s: section overlaps with \"%s\"",
|
||||||
section.name.c_str(),
|
|
||||||
sectionTypeInfo[section.type].name.c_str(),
|
|
||||||
getSectionDescription(section).c_str(),
|
getSectionDescription(section).c_str(),
|
||||||
out_OverlappingSection(section)->name.c_str()
|
out_OverlappingSection(section)->name.c_str()
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -35,12 +35,12 @@ static std::vector<TraceNode> backtrace(FileStackNode const &node, uint32_t curL
|
|||||||
std::vector<TraceNode> traceNodes = backtrace(*node.parent, node.lineNo);
|
std::vector<TraceNode> traceNodes = backtrace(*node.parent, node.lineNo);
|
||||||
if (std::holds_alternative<std::vector<uint32_t>>(node.data)) {
|
if (std::holds_alternative<std::vector<uint32_t>>(node.data)) {
|
||||||
assume(!traceNodes.empty()); // REPT nodes use their parent's name
|
assume(!traceNodes.empty()); // REPT nodes use their parent's name
|
||||||
std::string reptChain = traceNodes.back().first;
|
std::string reptName = traceNodes.back().first;
|
||||||
for (uint32_t iter : node.iters()) {
|
if (std::vector<uint32_t> const &nodeIters = node.iters(); !nodeIters.empty()) {
|
||||||
reptChain.append("::REPT~");
|
reptName.append(NODE_SEPARATOR REPT_NODE_PREFIX);
|
||||||
reptChain.append(std::to_string(iter));
|
reptName.append(std::to_string(nodeIters.back()));
|
||||||
}
|
}
|
||||||
traceNodes.emplace_back(reptChain, curLineNo);
|
traceNodes.emplace_back(reptName, curLineNo);
|
||||||
} else {
|
} else {
|
||||||
traceNodes.emplace_back(node.name(), curLineNo);
|
traceNodes.emplace_back(node.name(), curLineNo);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -251,8 +251,8 @@ void layout_PlaceSection(std::string const &name, bool isOptional) {
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// SDCC areas don't have a type assigned yet, so the linker script gives them one.
|
// 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()) {
|
for (Section &piece : section->pieces()) {
|
||||||
piece->type = activeType;
|
piece.type = activeType;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (section->type != activeType) {
|
} else if (section->type != activeType) {
|
||||||
|
|||||||
@@ -307,10 +307,10 @@ yy::parser::symbol_type yylex() {
|
|||||||
// Not marking as unreachable; this will generate a warning if any codepath forgets to return.
|
// 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));
|
if (LexerStackEntry &newContext = lexerStack.emplace_back(std::string(linkerScriptName));
|
||||||
!newContext.file.open(newContext.path, std::ios_base::in)) {
|
!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();
|
lexerStack.clear();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,8 +13,8 @@
|
|||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#include "backtrace.hpp"
|
#include "backtrace.hpp"
|
||||||
|
#include "cli.hpp"
|
||||||
#include "diagnostics.hpp"
|
#include "diagnostics.hpp"
|
||||||
#include "extern/getopt.hpp"
|
|
||||||
#include "linkdefs.hpp"
|
#include "linkdefs.hpp"
|
||||||
#include "script.hpp" // Generated from script.y
|
#include "script.hpp" // Generated from script.y
|
||||||
#include "style.hpp"
|
#include "style.hpp"
|
||||||
@@ -33,12 +33,16 @@
|
|||||||
|
|
||||||
Options options;
|
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
|
// Short options
|
||||||
static char const *optstring = "B:dhl:m:Mn:O:o:p:S:tVvW:wx";
|
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`
|
static int longOpt; // `--color`
|
||||||
|
|
||||||
// Equivalent long options
|
// Equivalent long options
|
||||||
@@ -90,97 +94,6 @@ static Usage usage = {
|
|||||||
};
|
};
|
||||||
// clang-format on
|
// 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) {
|
static size_t skipBlankSpace(char const *str) {
|
||||||
return strspn(str, " \t");
|
return strspn(str, " \t");
|
||||||
}
|
}
|
||||||
@@ -292,124 +205,221 @@ static void parseScrambleSpec(char *spec) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
static void parseArg(int ch, char *arg) {
|
||||||
// Parse CLI options
|
switch (ch) {
|
||||||
for (int ch; (ch = musl_getopt_long_only(argc, argv, optstring, longopts, nullptr)) != -1;) {
|
case 'B':
|
||||||
switch (ch) {
|
if (!trace_ParseTraceDepth(arg)) {
|
||||||
case 'B':
|
fatal("Invalid argument for option '-B'");
|
||||||
if (!trace_ParseTraceDepth(musl_optarg)) {
|
|
||||||
fatal("Invalid argument for option '-B'");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'd':
|
|
||||||
options.isDmgMode = true;
|
|
||||||
options.isWRAM0Mode = true;
|
|
||||||
break;
|
|
||||||
|
|
||||||
// LCOV_EXCL_START
|
|
||||||
case 'h':
|
|
||||||
usage.printAndExit(0);
|
|
||||||
// LCOV_EXCL_STOP
|
|
||||||
|
|
||||||
case 'l':
|
|
||||||
if (linkerScriptName) {
|
|
||||||
warnx("Overriding linker script file \"%s\"", linkerScriptName);
|
|
||||||
}
|
|
||||||
linkerScriptName = musl_optarg;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'M':
|
|
||||||
options.noSymInMap = true;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'm':
|
|
||||||
if (options.mapFileName) {
|
|
||||||
warnx("Overriding map file \"%s\"", options.mapFileName);
|
|
||||||
}
|
|
||||||
options.mapFileName = musl_optarg;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'n':
|
|
||||||
if (options.symFileName) {
|
|
||||||
warnx("Overriding sym file \"%s\"", options.symFileName);
|
|
||||||
}
|
|
||||||
options.symFileName = musl_optarg;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'O':
|
|
||||||
if (options.overlayFileName) {
|
|
||||||
warnx("Overriding overlay file \"%s\"", options.overlayFileName);
|
|
||||||
}
|
|
||||||
options.overlayFileName = musl_optarg;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'o':
|
|
||||||
if (options.outputFileName) {
|
|
||||||
warnx("Overriding output file \"%s\"", options.outputFileName);
|
|
||||||
}
|
|
||||||
options.outputFileName = musl_optarg;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'p':
|
|
||||||
if (std::optional<uint64_t> value = parseWholeNumber(musl_optarg); !value) {
|
|
||||||
fatal("Invalid argument for option '-p'");
|
|
||||||
} else if (*value > 0xFF) {
|
|
||||||
fatal("Argument for option '-p' must be between 0 and 0xFF");
|
|
||||||
} else {
|
|
||||||
options.padValue = *value;
|
|
||||||
options.hasPadValue = true;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'S':
|
|
||||||
parseScrambleSpec(musl_optarg);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 't':
|
|
||||||
options.is32kMode = true;
|
|
||||||
break;
|
|
||||||
|
|
||||||
// LCOV_EXCL_START
|
|
||||||
case 'V':
|
|
||||||
printf("rgblink %s\n", get_package_version_string());
|
|
||||||
exit(0);
|
|
||||||
|
|
||||||
case 'v':
|
|
||||||
incrementVerbosity();
|
|
||||||
break;
|
|
||||||
// LCOV_EXCL_STOP
|
|
||||||
|
|
||||||
case 'W':
|
|
||||||
warnings.processWarningFlag(musl_optarg);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'w':
|
|
||||||
options.isWRAM0Mode = true;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'x':
|
|
||||||
options.disablePadding = true;
|
|
||||||
// implies tiny mode
|
|
||||||
options.is32kMode = true;
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'd':
|
||||||
|
options.isDmgMode = true;
|
||||||
|
options.isWRAM0Mode = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
// LCOV_EXCL_START
|
||||||
|
case 'h':
|
||||||
|
usage.printAndExit(0);
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
|
|
||||||
|
case 'l':
|
||||||
|
if (localOptions.linkerScriptName) {
|
||||||
|
warnx("Overriding linker script file \"%s\"", localOptions.linkerScriptName->c_str());
|
||||||
|
}
|
||||||
|
localOptions.linkerScriptName = arg;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'M':
|
||||||
|
options.noSymInMap = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'm':
|
||||||
|
if (options.mapFileName) {
|
||||||
|
warnx("Overriding map file \"%s\"", options.mapFileName->c_str());
|
||||||
|
}
|
||||||
|
options.mapFileName = arg;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'n':
|
||||||
|
if (options.symFileName) {
|
||||||
|
warnx("Overriding sym file \"%s\"", options.symFileName->c_str());
|
||||||
|
}
|
||||||
|
options.symFileName = arg;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'O':
|
||||||
|
if (options.overlayFileName) {
|
||||||
|
warnx("Overriding overlay file \"%s\"", options.overlayFileName->c_str());
|
||||||
|
}
|
||||||
|
options.overlayFileName = arg;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'o':
|
||||||
|
if (options.outputFileName) {
|
||||||
|
warnx("Overriding output file \"%s\"", options.outputFileName->c_str());
|
||||||
|
}
|
||||||
|
options.outputFileName = arg;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'p':
|
||||||
|
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");
|
||||||
|
} else {
|
||||||
|
options.padValue = *value;
|
||||||
|
options.hasPadValue = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'S':
|
||||||
|
parseScrambleSpec(arg);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 't':
|
||||||
|
options.is32kMode = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
// LCOV_EXCL_START
|
||||||
|
case 'V':
|
||||||
|
printf("rgblink %s\n", get_package_version_string());
|
||||||
|
exit(0);
|
||||||
|
|
||||||
|
case 'v':
|
||||||
|
incrementVerbosity();
|
||||||
|
break;
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
|
|
||||||
|
case 'W':
|
||||||
|
warnings.processWarningFlag(arg);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'w':
|
||||||
|
options.isWRAM0Mode = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'x':
|
||||||
|
options.disablePadding = true;
|
||||||
|
// implies tiny mode
|
||||||
|
options.is32kMode = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0: // Long-only options
|
||||||
|
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)");
|
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,
|
// Read all object files first,
|
||||||
obj_Setup(argc - musl_optind);
|
size_t nbFiles = localOptions.inputFileNames.size();
|
||||||
for (int i = musl_optind; i < argc; ++i) {
|
obj_Setup(nbFiles);
|
||||||
obj_ReadFile(argv[i], argc - i - 1);
|
for (size_t i = 0; i < nbFiles; ++i) {
|
||||||
|
obj_ReadFile(localOptions.inputFileNames[i], nbFiles - i - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// apply the linker script's modifications,
|
// apply the linker script's modifications,
|
||||||
if (linkerScriptName) {
|
if (localOptions.linkerScriptName) {
|
||||||
verbosePrint(VERB_NOTICE, "Reading linker script...\n");
|
verbosePrint(VERB_NOTICE, "Reading linker script...\n");
|
||||||
|
|
||||||
if (lexer_Init(linkerScriptName)) {
|
if (lexer_Init(*localOptions.linkerScriptName)) {
|
||||||
if (yy::parser parser; parser.parse() != 0) {
|
if (yy::parser parser; parser.parse() != 0) {
|
||||||
// Exited due to YYABORT or YYNOMEM
|
// Exited due to YYABORT or YYNOMEM
|
||||||
fatal("Unrecoverable error while reading linker script"); // LCOV_EXCL_LINE
|
fatal("Unrecoverable error while reading linker script"); // LCOV_EXCL_LINE
|
||||||
|
|||||||
@@ -405,9 +405,10 @@ static void readAssertion(
|
|||||||
tryReadString(assert.message, file, "%s: Cannot read assertion's message: %s", fileName);
|
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;
|
FILE *file;
|
||||||
if (strcmp(fileName, "-")) {
|
char const *fileName = filePath.c_str();
|
||||||
|
if (filePath != "-") {
|
||||||
file = fopen(fileName, "rb");
|
file = fopen(fileName, "rb");
|
||||||
} else {
|
} else {
|
||||||
fileName = "<stdin>";
|
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);
|
tryReadLong(nbNodes, file, "%s: Cannot read number of nodes: %s", fileName);
|
||||||
nodes[fileID].resize(nbNodes);
|
nodes[fileID].resize(nbNodes);
|
||||||
verbosePrint(VERB_INFO, "Reading %u nodes...\n", nbNodes);
|
verbosePrint(VERB_INFO, "Reading %u nodes...\n", nbNodes);
|
||||||
for (uint32_t i = nbNodes; i--;) {
|
for (uint32_t nodeID = nbNodes; nodeID--;) {
|
||||||
readFileStackNode(file, nodes[fileID], i, fileName);
|
readFileStackNode(file, nodes[fileID], nodeID, fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
// This file's symbols, kept to link sections to them
|
// 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);
|
nodes.resize(nbFiles);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -208,15 +208,16 @@ static void
|
|||||||
|
|
||||||
static void writeROM() {
|
static void writeROM() {
|
||||||
if (options.outputFileName) {
|
if (options.outputFileName) {
|
||||||
if (strcmp(options.outputFileName, "-")) {
|
char const *outputFileName = options.outputFileName->c_str();
|
||||||
outputFile = fopen(options.outputFileName, "wb");
|
if (*options.outputFileName != "-") {
|
||||||
|
outputFile = fopen(outputFileName, "wb");
|
||||||
} else {
|
} else {
|
||||||
options.outputFileName = "<stdout>";
|
outputFileName = "<stdout>";
|
||||||
(void)setmode(STDOUT_FILENO, O_BINARY);
|
(void)setmode(STDOUT_FILENO, O_BINARY);
|
||||||
outputFile = stdout;
|
outputFile = stdout;
|
||||||
}
|
}
|
||||||
if (!outputFile) {
|
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{[&] {
|
Defer closeOutputFile{[&] {
|
||||||
@@ -226,17 +227,16 @@ static void writeROM() {
|
|||||||
}};
|
}};
|
||||||
|
|
||||||
if (options.overlayFileName) {
|
if (options.overlayFileName) {
|
||||||
if (strcmp(options.overlayFileName, "-")) {
|
char const *overlayFileName = options.overlayFileName->c_str();
|
||||||
overlayFile = fopen(options.overlayFileName, "rb");
|
if (*options.overlayFileName != "-") {
|
||||||
|
overlayFile = fopen(overlayFileName, "rb");
|
||||||
} else {
|
} else {
|
||||||
options.overlayFileName = "<stdin>";
|
overlayFileName = "<stdin>";
|
||||||
(void)setmode(STDIN_FILENO, O_BINARY);
|
(void)setmode(STDIN_FILENO, O_BINARY);
|
||||||
overlayFile = stdin;
|
overlayFile = stdin;
|
||||||
}
|
}
|
||||||
if (!overlayFile) {
|
if (!overlayFile) {
|
||||||
fatal(
|
fatal("Failed to open overlay file \"%s\": %s", overlayFileName, strerror(errno));
|
||||||
"Failed to open overlay file \"%s\": %s", options.overlayFileName, strerror(errno)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Defer closeOverlayFile{[&] {
|
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);
|
< std::tie(sym2.addr, sym2_local, sym2.parentAddr, sym2_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename F>
|
template<typename CallbackFnT>
|
||||||
static void forEachSortedSection(SortedSections const &bankSections, F callback) {
|
static void forEachSortedSection(SortedSections const &bankSections, CallbackFnT callback) {
|
||||||
for (Section const *sect : bankSections.zeroLenSections) {
|
for (Section const *sect : bankSections.zeroLenSections) {
|
||||||
for (; sect != nullptr; sect = sect->nextPiece.get()) {
|
for (Section const &piece : sect->pieces()) {
|
||||||
callback(*sect);
|
callback(piece);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (Section const *sect : bankSections.sections) {
|
for (Section const *sect : bankSections.sections) {
|
||||||
for (; sect != nullptr; sect = sect->nextPiece.get()) {
|
for (Section const &piece : sect->pieces()) {
|
||||||
callback(*sect);
|
callback(piece);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -415,8 +415,8 @@ static void writeSectionName(std::string const &name, FILE *file) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename F>
|
template<typename CallbackFnT>
|
||||||
uint16_t forEachSection(SortedSections const §List, F callback) {
|
uint16_t forEachSection(SortedSections const §List, CallbackFnT callback) {
|
||||||
uint16_t used = 0;
|
uint16_t used = 0;
|
||||||
auto section = sectList.sections.begin();
|
auto section = sectList.sections.begin();
|
||||||
auto zeroLenSection = sectList.zeroLenSections.begin();
|
auto zeroLenSection = sectList.zeroLenSections.begin();
|
||||||
@@ -433,31 +433,32 @@ uint16_t forEachSection(SortedSections const §List, F callback) {
|
|||||||
return used;
|
return used;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void writeMapSymbols(Section const *sect) {
|
static void writeMapSymbols(Section const §) {
|
||||||
uint16_t org = sect->org;
|
bool announced = true;
|
||||||
|
for (Section const &piece : sect.pieces()) {
|
||||||
for (bool announced = true; sect != nullptr; sect = sect->nextPiece.get(), announced = false) {
|
for (Symbol *sym : piece.symbols) {
|
||||||
for (Symbol *sym : sect->symbols) {
|
|
||||||
// Don't output symbols that begin with an illegal character
|
// Don't output symbols that begin with an illegal character
|
||||||
if (sym->name.empty() || !startsIdentifier(sym->name[0])) {
|
if (sym->name.empty() || !startsIdentifier(sym->name[0])) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// Announce this "piece" before its contents
|
// Announce this "piece" before its contents
|
||||||
if (!announced) {
|
if (!announced) {
|
||||||
if (sect->modifier == SECTION_UNION) {
|
assume(sect.modifier == piece.modifier);
|
||||||
|
if (sect.modifier == SECTION_UNION) {
|
||||||
fputs("\t ; Next union\n", mapFile);
|
fputs("\t ; Next union\n", mapFile);
|
||||||
} else if (sect->modifier == SECTION_FRAGMENT) {
|
} else if (sect.modifier == SECTION_FRAGMENT) {
|
||||||
fputs("\t ; Next fragment\n", mapFile);
|
fputs("\t ; Next fragment\n", mapFile);
|
||||||
}
|
}
|
||||||
announced = true;
|
announced = true;
|
||||||
}
|
}
|
||||||
assume(std::holds_alternative<Label>(sym->data));
|
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 ..."
|
// Space matches "\tSECTION: $xxxx ..."
|
||||||
fprintf(mapFile, "\t $%04" PRIx32 " = ", address);
|
fprintf(mapFile, "\t $%04" PRIx32 " = ", address);
|
||||||
writeSymName(sym->name, mapFile);
|
writeSymName(sym->name, mapFile);
|
||||||
putc('\n', mapFile);
|
putc('\n', mapFile);
|
||||||
}
|
}
|
||||||
|
announced = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -487,7 +488,7 @@ static void writeMapBank(SortedSections const §List, SectionType type, uint3
|
|||||||
|
|
||||||
if (!options.noSymInMap) {
|
if (!options.noSymInMap) {
|
||||||
// Also print symbols in the following "pieces"
|
// Also print symbols in the following "pieces"
|
||||||
writeMapSymbols(§);
|
writeMapSymbols(sect);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -547,15 +548,16 @@ static void writeSym() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strcmp(options.symFileName, "-")) {
|
char const *symFileName = options.symFileName->c_str();
|
||||||
symFile = fopen(options.symFileName, "w");
|
if (*options.symFileName != "-") {
|
||||||
|
symFile = fopen(symFileName, "w");
|
||||||
} else {
|
} else {
|
||||||
options.symFileName = "<stdout>";
|
symFileName = "<stdout>";
|
||||||
(void)setmode(STDOUT_FILENO, O_TEXT); // May have been set to O_BINARY previously
|
(void)setmode(STDOUT_FILENO, O_TEXT); // May have been set to O_BINARY previously
|
||||||
symFile = stdout;
|
symFile = stdout;
|
||||||
}
|
}
|
||||||
if (!symFile) {
|
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); }};
|
Defer closeSymFile{[&] { fclose(symFile); }};
|
||||||
|
|
||||||
@@ -597,15 +599,16 @@ static void writeMap() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strcmp(options.mapFileName, "-")) {
|
char const *mapFileName = options.mapFileName->c_str();
|
||||||
mapFile = fopen(options.mapFileName, "w");
|
if (*options.mapFileName != "-") {
|
||||||
|
mapFile = fopen(mapFileName, "w");
|
||||||
} else {
|
} else {
|
||||||
options.mapFileName = "<stdout>";
|
mapFileName = "<stdout>";
|
||||||
(void)setmode(STDOUT_FILENO, O_TEXT); // May have been set to O_BINARY previously
|
(void)setmode(STDOUT_FILENO, O_TEXT); // May have been set to O_BINARY previously
|
||||||
mapFile = stdout;
|
mapFile = stdout;
|
||||||
}
|
}
|
||||||
if (!mapFile) {
|
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); }};
|
Defer closeMapFile{[&] { fclose(mapFile); }};
|
||||||
|
|
||||||
|
|||||||
@@ -586,8 +586,8 @@ static void applyPatches(Section §ion) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Section *piece = §ion; piece != nullptr; piece = piece->nextPiece.get()) {
|
for (Section &piece : section.pieces()) {
|
||||||
applyFilePatches(*piece, section);
|
applyFilePatches(piece, section);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -138,23 +138,30 @@ void sdobj_ReadFile(FileStackNode const &src, FILE *file, std::vector<Symbol> &f
|
|||||||
line.reserve(256);
|
line.reserve(256);
|
||||||
char const *token;
|
char const *token;
|
||||||
|
|
||||||
#define getToken(ptr, ...) \
|
#define expectEol(lineType) \
|
||||||
do { \
|
|
||||||
token = strtok((ptr), delim); \
|
|
||||||
if (!token) { \
|
|
||||||
fatalAt(where, __VA_ARGS__); \
|
|
||||||
} \
|
|
||||||
} while (0)
|
|
||||||
#define expectEol(...) \
|
|
||||||
do { \
|
do { \
|
||||||
token = strtok(nullptr, delim); \
|
token = strtok(nullptr, delim); \
|
||||||
if (token) { \
|
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)
|
} while (0)
|
||||||
#define expectToken(expected, lineType) \
|
#define expectToken(expected, lineType) \
|
||||||
do { \
|
do { \
|
||||||
getToken(nullptr, "'%c' line is too short", (lineType)); \
|
expectNext(nullptr, lineType); \
|
||||||
if (strcasecmp(token, (expected)) != 0) { \
|
if (strcasecmp(token, (expected)) != 0) { \
|
||||||
fatalAt( \
|
fatalAt( \
|
||||||
where, \
|
where, \
|
||||||
@@ -223,12 +230,15 @@ void sdobj_ReadFile(FileStackNode const &src, FILE *file, std::vector<Symbol> &f
|
|||||||
}
|
}
|
||||||
// Expected format: "A areas S global symbols"
|
// 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);
|
uint32_t expectedNbAreas = readInt(where, token, numberBase);
|
||||||
|
|
||||||
expectToken("areas", 'H');
|
expectToken("areas", 'H');
|
||||||
|
|
||||||
getToken(nullptr, "'H' line is too short");
|
expectNext(nullptr, 'H');
|
||||||
uint32_t expectedNbSymbols = readInt(where, token, numberBase);
|
uint32_t expectedNbSymbols = readInt(where, token, numberBase);
|
||||||
fileSymbols.reserve(expectedNbSymbols);
|
fileSymbols.reserve(expectedNbSymbols);
|
||||||
|
|
||||||
@@ -236,7 +246,7 @@ void sdobj_ReadFile(FileStackNode const &src, FILE *file, std::vector<Symbol> &f
|
|||||||
|
|
||||||
expectToken("symbols", 'H');
|
expectToken("symbols", 'H');
|
||||||
|
|
||||||
expectEol("'H' line is too long");
|
expectEol('H');
|
||||||
|
|
||||||
// Now, let's parse the rest of the lines as they come!
|
// 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->src = where.src;
|
||||||
curSection->lineNo = where.lineNo;
|
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
|
assume(strlen(token) != 0); // This should be impossible, tokens are non-empty
|
||||||
// The following is required for fragment offsets to be reliably predicted
|
// The following is required for fragment offsets to be reliably predicted
|
||||||
for (FileSection &entry : fileSections) {
|
for (FileSection &entry : fileSections) {
|
||||||
@@ -279,7 +289,7 @@ void sdobj_ReadFile(FileStackNode const &src, FILE *file, std::vector<Symbol> &f
|
|||||||
|
|
||||||
expectToken("size", 'A');
|
expectToken("size", 'A');
|
||||||
|
|
||||||
getToken(nullptr, "'A' line is too short");
|
expectNext(nullptr, 'A');
|
||||||
|
|
||||||
uint32_t tmp = readInt(where, token, numberBase);
|
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');
|
expectToken("flags", 'A');
|
||||||
|
|
||||||
getToken(nullptr, "'A' line is too short");
|
expectNext(nullptr, 'A');
|
||||||
tmp = readInt(where, token, numberBase);
|
tmp = readInt(where, token, numberBase);
|
||||||
if (tmp & (1 << AREA_PAGING)) {
|
if (tmp & (1 << AREA_PAGING)) {
|
||||||
fatalAt(where, "Paging is not supported");
|
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');
|
expectToken("addr", 'A');
|
||||||
|
|
||||||
getToken(nullptr, "'A' line is too short");
|
expectNext(nullptr, 'A');
|
||||||
tmp = readInt(where, token, numberBase);
|
tmp = readInt(where, token, numberBase);
|
||||||
curSection->org = tmp; // Truncation keeps the address portion only
|
curSection->org = tmp; // Truncation keeps the address portion only
|
||||||
curSection->bank = tmp >> 16;
|
curSection->bank = tmp >> 16;
|
||||||
|
|
||||||
expectEol("'A' line is too long");
|
expectEol('A');
|
||||||
|
|
||||||
// Init the rest of the members
|
// Init the rest of the members
|
||||||
curSection->offset = 0;
|
curSection->offset = 0;
|
||||||
@@ -366,10 +376,10 @@ void sdobj_ReadFile(FileStackNode const &src, FILE *file, std::vector<Symbol> &f
|
|||||||
symbol.src = where.src;
|
symbol.src = where.src;
|
||||||
symbol.lineNo = where.lineNo;
|
symbol.lineNo = where.lineNo;
|
||||||
|
|
||||||
getToken(line.data(), "'S' line is too short");
|
expectNext(line.data(), 'S');
|
||||||
symbol.name = token;
|
symbol.name = token;
|
||||||
|
|
||||||
getToken(nullptr, "'S' line is too short");
|
expectNext(nullptr, 'S');
|
||||||
|
|
||||||
if (int32_t value = readInt(where, &token[3], numberBase); !fileSections.empty()) {
|
if (int32_t value = readInt(where, &token[3], numberBase); !fileSections.empty()) {
|
||||||
// Symbols in sections are labels; their value is an offset
|
// 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);
|
fileSections.back().section->symbols.push_back(&symbol);
|
||||||
}
|
}
|
||||||
|
|
||||||
expectEol("'S' line is too long");
|
expectEol('S');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -467,13 +477,13 @@ void sdobj_ReadFile(FileStackNode const &src, FILE *file, std::vector<Symbol> &f
|
|||||||
}
|
}
|
||||||
|
|
||||||
// First two bytes are ignored
|
// First two bytes are ignored
|
||||||
getToken(line.data(), "'R' line is too short");
|
expectNext(line.data(), 'R');
|
||||||
getToken(nullptr, "'R' line is too short");
|
expectNext(nullptr, 'R');
|
||||||
uint16_t areaIdx;
|
uint16_t areaIdx;
|
||||||
|
|
||||||
getToken(nullptr, "'R' line is too short");
|
expectNext(nullptr, 'R');
|
||||||
areaIdx = readByte(where, token, numberBase);
|
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;
|
areaIdx |= static_cast<uint16_t>(readByte(where, token, numberBase)) << 8;
|
||||||
if (areaIdx >= fileSections.size()) {
|
if (areaIdx >= fileSections.size()) {
|
||||||
fatalAt(
|
fatalAt(
|
||||||
@@ -537,12 +547,12 @@ void sdobj_ReadFile(FileStackNode const &src, FILE *file, std::vector<Symbol> &f
|
|||||||
uint16_t flags = readByte(where, token, numberBase);
|
uint16_t flags = readByte(where, token, numberBase);
|
||||||
|
|
||||||
if ((flags & 0xF0) == 0xF0) {
|
if ((flags & 0xF0) == 0xF0) {
|
||||||
getToken(nullptr, "Incomplete relocation");
|
expectRelocation();
|
||||||
flags = (flags & 0x0F)
|
flags = (flags & 0x0F)
|
||||||
| static_cast<uint16_t>(readByte(where, token, numberBase)) << 4;
|
| static_cast<uint16_t>(readByte(where, token, numberBase)) << 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
getToken(nullptr, "Incomplete relocation");
|
expectRelocation();
|
||||||
uint8_t offset = readByte(where, token, numberBase);
|
uint8_t offset = readByte(where, token, numberBase);
|
||||||
|
|
||||||
if (offset < addrSize) {
|
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);
|
uint16_t idx = readByte(where, token, numberBase);
|
||||||
|
|
||||||
getToken(nullptr, "Incomplete relocation");
|
expectRelocation();
|
||||||
idx |= static_cast<uint16_t>(readByte(where, token, numberBase));
|
idx |= static_cast<uint16_t>(readByte(where, token, numberBase));
|
||||||
|
|
||||||
// Loudly fail on unknown flags
|
// Loudly fail on unknown flags
|
||||||
@@ -819,8 +829,9 @@ void sdobj_ReadFile(FileStackNode const &src, FILE *file, std::vector<Symbol> &f
|
|||||||
}
|
}
|
||||||
|
|
||||||
#undef expectEol
|
#undef expectEol
|
||||||
|
#undef expectNext
|
||||||
|
#undef expectRelocation
|
||||||
#undef expectToken
|
#undef expectToken
|
||||||
#undef getToken
|
|
||||||
|
|
||||||
if (!data.empty()) {
|
if (!data.empty()) {
|
||||||
warningAt(where, "Last 'T' line had no 'R' line (ignored)");
|
warningAt(where, "Last 'T' line had no 'R' line (ignored)");
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ Diagnostics<WarningLevel, WarningID> warnings = {
|
|||||||
{"truncation", LEVEL_EVERYTHING},
|
{"truncation", LEVEL_EVERYTHING},
|
||||||
},
|
},
|
||||||
.paramWarnings = {
|
.paramWarnings = {
|
||||||
{WARNING_TRUNCATION_1, WARNING_TRUNCATION_2, 2},
|
{WARNING_TRUNCATION_1, WARNING_TRUNCATION_2, 1},
|
||||||
},
|
},
|
||||||
.state = DiagnosticsState<WarningID>(),
|
.state = DiagnosticsState<WarningID>(),
|
||||||
.nbErrors = 0,
|
.nbErrors = 0,
|
||||||
|
|||||||
@@ -58,6 +58,14 @@ bool isAlphanumeric(int c) {
|
|||||||
return isLetter(c) || isDigit(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) {
|
bool startsIdentifier(int c) {
|
||||||
// This returns false for anonymous labels, which internally start with a '!',
|
// This returns false for anonymous labels, which internally start with a '!',
|
||||||
// and for section fragment literal labels, which internally start with a '$'.
|
// and for section fragment literal labels, which internally start with a '$'.
|
||||||
|
|||||||
@@ -24,6 +24,15 @@ void incrementVerbosity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void printVerbosely(char const *fmt, ...) {
|
||||||
|
va_list args;
|
||||||
|
style_Set(stderr, STYLE_MAGENTA, false);
|
||||||
|
va_start(args, fmt);
|
||||||
|
vfprintf(stderr, fmt, args);
|
||||||
|
va_end(args);
|
||||||
|
style_Reset(stderr);
|
||||||
|
}
|
||||||
|
|
||||||
void printVVVVVVerbosity() {
|
void printVVVVVVerbosity() {
|
||||||
if (!checkVerbosity(VERB_VVVVVV)) {
|
if (!checkVerbosity(VERB_VVVVVV)) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -14,7 +14,13 @@
|
|||||||
#if !defined(NDEBUG) && defined(__SANITIZE_ADDRESS__) && !defined(__APPLE__)
|
#if !defined(NDEBUG) && defined(__SANITIZE_ADDRESS__) && !defined(__APPLE__)
|
||||||
extern "C" {
|
extern "C" {
|
||||||
char const *__asan_default_options(void) {
|
char const *__asan_default_options(void) {
|
||||||
return "detect_leaks=1";
|
return "detect_leaks=1"
|
||||||
|
":detect_stack_use_after_return=1"
|
||||||
|
":detect_invalid_pointer_pairs=2"
|
||||||
|
":check_initialization_order=1"
|
||||||
|
":strict_init_order=1"
|
||||||
|
":strict_string_checks=1"
|
||||||
|
":print_legend=0";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
3
test/asm/abort-on-missing-incbin-start.asm
Normal file
3
test/asm/abort-on-missing-incbin-start.asm
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
section "test", rom0
|
||||||
|
incbin "incbin-mg-noexist.bin", 2
|
||||||
|
println "never reached"
|
||||||
1
test/asm/abort-on-missing-incbin-start.flags
Normal file
1
test/asm/abort-on-missing-incbin-start.flags
Normal file
@@ -0,0 +1 @@
|
|||||||
|
-MG
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
section "test", rom0
|
section "test", rom0
|
||||||
incbin "incbin-mg-noexist.bin", 2
|
incbin "incbin-mg-noexist.bin"
|
||||||
println "never reached"
|
println "never reached"
|
||||||
|
|||||||
@@ -12,4 +12,5 @@ error: syntax error, unexpected PRINTLN, expecting end of line
|
|||||||
at code-after-endm-endr-endc.asm(23)
|
at code-after-endm-endr-endc.asm(23)
|
||||||
error: syntax error, unexpected PRINTLN, expecting end of line or end of buffer or end of fragment literal
|
error: syntax error, unexpected PRINTLN, expecting end of line or end of buffer or end of fragment literal
|
||||||
at code-after-endm-endr-endc.asm(25)
|
at code-after-endm-endr-endc.asm(25)
|
||||||
Assembly aborted with 7 errors!
|
FATAL: Ended block with 2 unterminated conditionals (`IF`/`ELIF`/`ELSE` blocks)
|
||||||
|
at code-after-endm-endr-endc.asm(28)
|
||||||
|
|||||||
10
test/asm/const-low.asm
Normal file
10
test/asm/const-low.asm
Normal 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
5
test/asm/const-low.err
Normal 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!
|
||||||
@@ -1 +0,0 @@
|
|||||||
-M /dev/null -MG
|
|
||||||
0
test/asm/errors-after-missing-include/a.err
Normal file
0
test/asm/errors-after-missing-include/a.err
Normal file
2
test/asm/errors-after-missing-include/a.out
Normal file
2
test/asm/errors-after-missing-include/a.out
Normal 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
2
test/asm/flag-Q.asm
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
section "test", rom0
|
||||||
|
dl 3.14159
|
||||||
1
test/asm/flag-Q.flags
Normal file
1
test/asm/flag-Q.flags
Normal file
@@ -0,0 +1 @@
|
|||||||
|
-Q .24
|
||||||
1
test/asm/flag-Q.out.bin
Normal file
1
test/asm/flag-Q.out.bin
Normal file
@@ -0,0 +1 @@
|
|||||||
|
>?$
|
||||||
4
test/asm/flag-p.asm
Normal file
4
test/asm/flag-p.asm
Normal 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
1
test/asm/flag-p.flags
Normal file
@@ -0,0 +1 @@
|
|||||||
|
-p 0x42
|
||||||
1
test/asm/flag-p.out.bin
Normal file
1
test/asm/flag-p.out.bin
Normal file
@@ -0,0 +1 @@
|
|||||||
|
BBB
|
||||||
@@ -1,2 +1,6 @@
|
|||||||
FATAL: `SECTION UNION` cannot contain fragment literals
|
error: Cannot declare ROM sections as `UNION`
|
||||||
|
at fragment-literal-in-union.asm(1)
|
||||||
|
error: Cannot output data outside of a `SECTION`
|
||||||
|
at fragment-literal-in-union.asm(2)
|
||||||
|
FATAL: Cannot output fragment literals outside of a `SECTION`
|
||||||
at fragment-literal-in-union.asm(3)
|
at fragment-literal-in-union.asm(3)
|
||||||
|
|||||||
4
test/asm/incbin.asm
Normal file
4
test/asm/incbin.asm
Normal 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
1
test/asm/incbin.out.bin
Normal file
@@ -0,0 +1 @@
|
|||||||
|
QÇÀíð+)W-N^wY7Fõ×¾Ô cʃÅ.YzÎö»+áƒåÑ‚‚'挮‡g}©!°XX6¨"]|Ó6`<60>93ãQu/·]Æ@üäˆ-–baüDð$ºsFí$!B(ìx„
©äÒÑy¯«¯Ü½œ»–{@O°›Á½ Û{@O°›Á½ ÛÎö»+áƒåÑ‚‚
|
||||||
3
test/asm/keyword-global.asm
Normal file
3
test/asm/keyword-global.asm
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
section "test", rom0
|
||||||
|
#call.local1
|
||||||
|
call.local2
|
||||||
3
test/asm/keyword-global.err
Normal file
3
test/asm/keyword-global.err
Normal 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!
|
||||||
7
test/asm/label-before-endc.asm
Normal file
7
test/asm/label-before-endc.asm
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
section "test", rom0
|
||||||
|
if 1
|
||||||
|
println "one"
|
||||||
|
label0: endc
|
||||||
|
if 2
|
||||||
|
println "two"
|
||||||
|
label1: endc
|
||||||
6
test/asm/label-before-endc.err
Normal file
6
test/asm/label-before-endc.err
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
error: syntax error, unexpected ENDC
|
||||||
|
at label-before-endc.asm(4)
|
||||||
|
error: syntax error, unexpected ENDC
|
||||||
|
at label-before-endc.asm(7)
|
||||||
|
FATAL: Ended block with 2 unterminated conditionals (`IF`/`ELIF`/`ELSE` blocks)
|
||||||
|
at label-before-endc.asm(8)
|
||||||
2
test/asm/label-before-endc.out
Normal file
2
test/asm/label-before-endc.out
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
one
|
||||||
|
two
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
section "test", rom0
|
||||||
|
incbin "data.bin"
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
-M - -MT preserve$dollars$$ -MQ escape$dollars$$
|
-M - -MT preserve$dollars$$ -MQ escape$dollars$$ -MP
|
||||||
|
|||||||
@@ -1 +1,3 @@
|
|||||||
preserve$dollars$$ escape$$dollars$$$$: make-deps.asm
|
preserve$dollars$$ escape$$dollars$$$$: make-deps.asm
|
||||||
|
preserve$dollars$$ escape$$dollars$$$$: data.bin
|
||||||
|
data.bin:
|
||||||
|
|||||||
@@ -9,10 +9,10 @@ warning: Line 5 [-Wuser]
|
|||||||
warning: Line 8 [-Wuser]
|
warning: Line 8 [-Wuser]
|
||||||
at rept-line-no.asm(8)
|
at rept-line-no.asm(8)
|
||||||
warning: Line 12 [-Wuser]
|
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]
|
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]
|
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]
|
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)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
warning: round 1 [-Wuser]
|
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::REPT~1(12)
|
||||||
<- rept-macro-fstack-trace.asm::outer::REPT~1::inner(11)
|
<- rept-macro-fstack-trace.asm::outer::REPT~1::inner(11)
|
||||||
<- rept-macro-fstack-trace.asm::outer::REPT~1(13)
|
<- 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::REPT~1(18)
|
||||||
<- rept-macro-fstack-trace.asm(17)
|
<- rept-macro-fstack-trace.asm(17)
|
||||||
warning: round 2 [-Wuser]
|
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::REPT~1(12)
|
||||||
<- rept-macro-fstack-trace.asm::outer::REPT~1::inner(11)
|
<- rept-macro-fstack-trace.asm::outer::REPT~1::inner(11)
|
||||||
<- rept-macro-fstack-trace.asm::outer::REPT~1(13)
|
<- 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::REPT~1(18)
|
||||||
<- rept-macro-fstack-trace.asm(17)
|
<- rept-macro-fstack-trace.asm(17)
|
||||||
warning: round 3 [-Wuser]
|
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::REPT~2(12)
|
||||||
<- rept-macro-fstack-trace.asm::outer::REPT~1::inner(11)
|
<- rept-macro-fstack-trace.asm::outer::REPT~1::inner(11)
|
||||||
<- rept-macro-fstack-trace.asm::outer::REPT~1(13)
|
<- 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::REPT~1(18)
|
||||||
<- rept-macro-fstack-trace.asm(17)
|
<- rept-macro-fstack-trace.asm(17)
|
||||||
warning: round 4 [-Wuser]
|
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::REPT~2(12)
|
||||||
<- rept-macro-fstack-trace.asm::outer::REPT~1::inner(11)
|
<- rept-macro-fstack-trace.asm::outer::REPT~1::inner(11)
|
||||||
<- rept-macro-fstack-trace.asm::outer::REPT~1(13)
|
<- 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::REPT~1(18)
|
||||||
<- rept-macro-fstack-trace.asm(17)
|
<- rept-macro-fstack-trace.asm(17)
|
||||||
warning: round 5 [-Wuser]
|
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::REPT~1(12)
|
||||||
<- rept-macro-fstack-trace.asm::outer::REPT~1::inner(11)
|
<- rept-macro-fstack-trace.asm::outer::REPT~1::inner(11)
|
||||||
<- rept-macro-fstack-trace.asm::outer::REPT~2(13)
|
<- 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::REPT~1(18)
|
||||||
<- rept-macro-fstack-trace.asm(17)
|
<- rept-macro-fstack-trace.asm(17)
|
||||||
warning: round 6 [-Wuser]
|
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::REPT~1(12)
|
||||||
<- rept-macro-fstack-trace.asm::outer::REPT~1::inner(11)
|
<- rept-macro-fstack-trace.asm::outer::REPT~1::inner(11)
|
||||||
<- rept-macro-fstack-trace.asm::outer::REPT~2(13)
|
<- 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::REPT~1(18)
|
||||||
<- rept-macro-fstack-trace.asm(17)
|
<- rept-macro-fstack-trace.asm(17)
|
||||||
warning: round 7 [-Wuser]
|
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::REPT~2(12)
|
||||||
<- rept-macro-fstack-trace.asm::outer::REPT~1::inner(11)
|
<- rept-macro-fstack-trace.asm::outer::REPT~1::inner(11)
|
||||||
<- rept-macro-fstack-trace.asm::outer::REPT~2(13)
|
<- 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::REPT~1(18)
|
||||||
<- rept-macro-fstack-trace.asm(17)
|
<- rept-macro-fstack-trace.asm(17)
|
||||||
warning: round 8 [-Wuser]
|
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::REPT~2(12)
|
||||||
<- rept-macro-fstack-trace.asm::outer::REPT~1::inner(11)
|
<- rept-macro-fstack-trace.asm::outer::REPT~1::inner(11)
|
||||||
<- rept-macro-fstack-trace.asm::outer::REPT~2(13)
|
<- 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::REPT~1(18)
|
||||||
<- rept-macro-fstack-trace.asm(17)
|
<- rept-macro-fstack-trace.asm(17)
|
||||||
warning: round 9 [-Wuser]
|
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::REPT~1(12)
|
||||||
<- rept-macro-fstack-trace.asm::outer::REPT~3::inner(11)
|
<- rept-macro-fstack-trace.asm::outer::REPT~3::inner(11)
|
||||||
<- rept-macro-fstack-trace.asm::outer::REPT~3(13)
|
<- 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::REPT~1(18)
|
||||||
<- rept-macro-fstack-trace.asm(17)
|
<- rept-macro-fstack-trace.asm(17)
|
||||||
warning: round 10 [-Wuser]
|
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::REPT~1(12)
|
||||||
<- rept-macro-fstack-trace.asm::outer::REPT~3::inner(11)
|
<- rept-macro-fstack-trace.asm::outer::REPT~3::inner(11)
|
||||||
<- rept-macro-fstack-trace.asm::outer::REPT~3(13)
|
<- 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::REPT~1(18)
|
||||||
<- rept-macro-fstack-trace.asm(17)
|
<- rept-macro-fstack-trace.asm(17)
|
||||||
warning: round 11 [-Wuser]
|
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::REPT~2(12)
|
||||||
<- rept-macro-fstack-trace.asm::outer::REPT~3::inner(11)
|
<- rept-macro-fstack-trace.asm::outer::REPT~3::inner(11)
|
||||||
<- rept-macro-fstack-trace.asm::outer::REPT~3(13)
|
<- 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::REPT~1(18)
|
||||||
<- rept-macro-fstack-trace.asm(17)
|
<- rept-macro-fstack-trace.asm(17)
|
||||||
warning: round 12 [-Wuser]
|
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::REPT~2(12)
|
||||||
<- rept-macro-fstack-trace.asm::outer::REPT~3::inner(11)
|
<- rept-macro-fstack-trace.asm::outer::REPT~3::inner(11)
|
||||||
<- rept-macro-fstack-trace.asm::outer::REPT~3(13)
|
<- 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::REPT~1(18)
|
||||||
<- rept-macro-fstack-trace.asm(17)
|
<- rept-macro-fstack-trace.asm(17)
|
||||||
warning: round 13 [-Wuser]
|
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::REPT~1(12)
|
||||||
<- rept-macro-fstack-trace.asm::outer::REPT~3::inner(11)
|
<- rept-macro-fstack-trace.asm::outer::REPT~3::inner(11)
|
||||||
<- rept-macro-fstack-trace.asm::outer::REPT~4(13)
|
<- 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::REPT~1(18)
|
||||||
<- rept-macro-fstack-trace.asm(17)
|
<- rept-macro-fstack-trace.asm(17)
|
||||||
warning: round 14 [-Wuser]
|
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::REPT~1(12)
|
||||||
<- rept-macro-fstack-trace.asm::outer::REPT~3::inner(11)
|
<- rept-macro-fstack-trace.asm::outer::REPT~3::inner(11)
|
||||||
<- rept-macro-fstack-trace.asm::outer::REPT~4(13)
|
<- 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::REPT~1(18)
|
||||||
<- rept-macro-fstack-trace.asm(17)
|
<- rept-macro-fstack-trace.asm(17)
|
||||||
warning: round 15 [-Wuser]
|
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::REPT~2(12)
|
||||||
<- rept-macro-fstack-trace.asm::outer::REPT~3::inner(11)
|
<- rept-macro-fstack-trace.asm::outer::REPT~3::inner(11)
|
||||||
<- rept-macro-fstack-trace.asm::outer::REPT~4(13)
|
<- 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::REPT~1(18)
|
||||||
<- rept-macro-fstack-trace.asm(17)
|
<- rept-macro-fstack-trace.asm(17)
|
||||||
warning: round 16 [-Wuser]
|
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::REPT~2(12)
|
||||||
<- rept-macro-fstack-trace.asm::outer::REPT~3::inner(11)
|
<- rept-macro-fstack-trace.asm::outer::REPT~3::inner(11)
|
||||||
<- rept-macro-fstack-trace.asm::outer::REPT~4(13)
|
<- 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::REPT~1(18)
|
||||||
<- rept-macro-fstack-trace.asm(17)
|
<- rept-macro-fstack-trace.asm(17)
|
||||||
warning: round 17 [-Wuser]
|
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::REPT~1(23)
|
||||||
<- rept-macro-fstack-trace.asm::foo(22)
|
<- 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::REPT~1(33)
|
||||||
<- rept-macro-fstack-trace.asm::REPT~1::REPT~1::bar(32)
|
<- 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::REPT~1(30)
|
||||||
<- rept-macro-fstack-trace.asm(29)
|
<- rept-macro-fstack-trace.asm(29)
|
||||||
warning: round 18 [-Wuser]
|
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::REPT~1(23)
|
||||||
<- rept-macro-fstack-trace.asm::foo(22)
|
<- 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::REPT~1(33)
|
||||||
<- rept-macro-fstack-trace.asm::REPT~1::REPT~1::bar(32)
|
<- 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::REPT~1(30)
|
||||||
<- rept-macro-fstack-trace.asm(29)
|
<- rept-macro-fstack-trace.asm(29)
|
||||||
warning: round 19 [-Wuser]
|
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::REPT~1(23)
|
||||||
<- rept-macro-fstack-trace.asm::foo(22)
|
<- 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::REPT~2(33)
|
||||||
<- rept-macro-fstack-trace.asm::REPT~1::REPT~1::bar(32)
|
<- 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::REPT~1(30)
|
||||||
<- rept-macro-fstack-trace.asm(29)
|
<- rept-macro-fstack-trace.asm(29)
|
||||||
warning: round 20 [-Wuser]
|
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::REPT~1(23)
|
||||||
<- rept-macro-fstack-trace.asm::foo(22)
|
<- 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::REPT~2(33)
|
||||||
<- rept-macro-fstack-trace.asm::REPT~1::REPT~1::bar(32)
|
<- 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::REPT~1(30)
|
||||||
<- rept-macro-fstack-trace.asm(29)
|
<- rept-macro-fstack-trace.asm(29)
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user