Compare commits
16 Commits
f065243cd2
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1eb4eb3339 | ||
|
|
a3d3e1525a | ||
|
|
3553c9c4da | ||
|
|
5c2c893ced | ||
|
|
0f266d1c66 | ||
|
|
8ab4602ae5 | ||
|
|
04e3a904c2 | ||
|
|
395b03e88e | ||
|
|
fb9fa6038c | ||
|
|
35e5808423 | ||
|
|
558d3ca0fc | ||
|
|
df5162edca | ||
|
|
2519d1e698 | ||
|
|
ca383c91ca | ||
|
|
8bedd710d7 | ||
|
|
efb5a88edb |
@@ -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 . .
|
||||||
|
|||||||
4
Makefile
@@ -222,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.
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include "extern/getopt.hpp" // option
|
#include "extern/getopt.hpp" // option
|
||||||
|
#include "usage.hpp"
|
||||||
|
|
||||||
void cli_ParseArgs(
|
void cli_ParseArgs(
|
||||||
int argc,
|
int argc,
|
||||||
@@ -14,7 +15,7 @@ void cli_ParseArgs(
|
|||||||
char const *shortOpts,
|
char const *shortOpts,
|
||||||
option const *longOpts,
|
option const *longOpts,
|
||||||
void (*parseArg)(int, char *),
|
void (*parseArg)(int, char *),
|
||||||
void (*fatal)(char const *, ...)
|
Usage usage
|
||||||
);
|
);
|
||||||
|
|
||||||
#endif // RGBDS_CLI_HPP
|
#endif // RGBDS_CLI_HPP
|
||||||
|
|||||||
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
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -60,13 +60,4 @@
|
|||||||
#define _POSIX_C_SOURCE 200809L
|
#define _POSIX_C_SOURCE 200809L
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// gcc and clang have their own `musttail` attributes for tail recursion
|
|
||||||
#if defined(__clang__) && __has_cpp_attribute(clang::musttail)
|
|
||||||
#define MUSTTAIL [[clang::musttail]]
|
|
||||||
#elif defined(__GNUC__) && __has_cpp_attribute(gnu::musttail)
|
|
||||||
#define MUSTTAIL [[gnu::musttail]]
|
|
||||||
#else
|
|
||||||
#define MUSTTAIL
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif // RGBDS_PLATFORM_HPP
|
#endif // RGBDS_PLATFORM_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.
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
208
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
|
||||||
@@ -1936,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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -716,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
MUSTTAIL return peek();
|
|
||||||
} 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
MUSTTAIL return peek();
|
|
||||||
} else {
|
|
||||||
return c;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1943,8 +1942,6 @@ 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);
|
||||||
// We cannot do `MUSTTAIL return yylex_NORMAL();` because tail call optimization
|
|
||||||
// requires the return value to be "trivially destructible", and `Token` is not.
|
|
||||||
continue; // Restart, reading from the new buffer
|
continue; // Restart, reading from the new buffer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -296,7 +296,7 @@ static void parseArg(int ch, char *arg) {
|
|||||||
|
|
||||||
// LCOV_EXCL_START
|
// LCOV_EXCL_START
|
||||||
case 'V':
|
case 'V':
|
||||||
printf("rgbasm %s\n", get_package_version_string());
|
printf("%s %s\n", usage.name.c_str(), get_package_version_string());
|
||||||
exit(0);
|
exit(0);
|
||||||
|
|
||||||
case 'v':
|
case 'v':
|
||||||
@@ -381,7 +381,7 @@ static void verboseOutputConfig() {
|
|||||||
|
|
||||||
style_Set(stderr, STYLE_MAGENTA, false);
|
style_Set(stderr, STYLE_MAGENTA, false);
|
||||||
|
|
||||||
fprintf(stderr, "rgbasm %s\n", get_package_version_string());
|
fprintf(stderr, "%s %s\n", usage.name.c_str(), get_package_version_string());
|
||||||
|
|
||||||
printVVVVVVerbosity();
|
printVVVVVVerbosity();
|
||||||
|
|
||||||
@@ -513,7 +513,7 @@ int main(int argc, char *argv[]) {
|
|||||||
options.maxErrors = 100; // LCOV_EXCL_LINE
|
options.maxErrors = 100; // LCOV_EXCL_LINE
|
||||||
}
|
}
|
||||||
|
|
||||||
cli_ParseArgs(argc, argv, optstring, longopts, parseArg, fatal);
|
cli_ParseArgs(argc, argv, optstring, longopts, parseArg, usage);
|
||||||
|
|
||||||
if (!options.targetFileName && options.objectFileName) {
|
if (!options.targetFileName && options.objectFileName) {
|
||||||
options.targetFileName = options.objectFileName;
|
options.targetFileName = options.objectFileName;
|
||||||
|
|||||||
@@ -455,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
|
||||||
@@ -489,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();
|
||||||
}
|
}
|
||||||
;
|
;
|
||||||
@@ -545,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);
|
||||||
@@ -571,8 +572,7 @@ macro_args:
|
|||||||
;
|
;
|
||||||
|
|
||||||
directive:
|
directive:
|
||||||
endc
|
print
|
||||||
| print
|
|
||||||
| println
|
| println
|
||||||
| export
|
| export
|
||||||
| export_def
|
| export_def
|
||||||
@@ -853,7 +853,7 @@ break:
|
|||||||
}
|
}
|
||||||
;
|
;
|
||||||
|
|
||||||
def_macro:
|
macro_def:
|
||||||
POP_MACRO {
|
POP_MACRO {
|
||||||
lexer_ToggleStringExpansion(false);
|
lexer_ToggleStringExpansion(false);
|
||||||
} maybe_quiet SYMBOL {
|
} maybe_quiet SYMBOL {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
24
src/cli.cpp
@@ -2,26 +2,31 @@
|
|||||||
|
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "extern/getopt.hpp"
|
#include "extern/getopt.hpp"
|
||||||
|
#include "style.hpp"
|
||||||
|
#include "usage.hpp"
|
||||||
#include "util.hpp" // isBlankSpace
|
#include "util.hpp" // isBlankSpace
|
||||||
|
|
||||||
using namespace std::literals;
|
using namespace std::literals;
|
||||||
|
|
||||||
// Turn an at-file's contents into an argv that `getopt` can handle, appending them to `argPool`.
|
// Turn an at-file's contents into an argv that `getopt` can handle, appending them to `argPool`.
|
||||||
static std::vector<size_t> readAtFile(
|
static std::vector<size_t>
|
||||||
std::string const &path, std::vector<char> &argPool, void (*fatal)(char const *, ...)
|
readAtFile(std::string const &path, std::vector<char> &argPool, Usage usage) {
|
||||||
) {
|
|
||||||
std::vector<size_t> argvOfs;
|
std::vector<size_t> argvOfs;
|
||||||
|
|
||||||
std::filebuf file;
|
std::filebuf file;
|
||||||
if (!file.open(path, std::ios_base::in)) {
|
if (!file.open(path, std::ios_base::in)) {
|
||||||
std::string msg = "Error reading at-file \""s + path + "\": " + strerror(errno);
|
int errnum = errno;
|
||||||
fatal(msg.c_str());
|
style_Set(stderr, STYLE_RED, true);
|
||||||
return argvOfs; // Since we can't mark the `fatal` function pointer as [[noreturn]]
|
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 (;;) {
|
for (;;) {
|
||||||
@@ -71,7 +76,7 @@ void cli_ParseArgs(
|
|||||||
char const *shortOpts,
|
char const *shortOpts,
|
||||||
option const *longOpts,
|
option const *longOpts,
|
||||||
void (*parseArg)(int, char *),
|
void (*parseArg)(int, char *),
|
||||||
void (*fatal)(char const *, ...)
|
Usage usage
|
||||||
) {
|
) {
|
||||||
struct AtFileStackEntry {
|
struct AtFileStackEntry {
|
||||||
int parentInd; // Saved offset into parent argv
|
int parentInd; // Saved offset into parent argv
|
||||||
@@ -90,8 +95,7 @@ void cli_ParseArgs(
|
|||||||
for (;;) {
|
for (;;) {
|
||||||
char *atFileName = nullptr;
|
char *atFileName = nullptr;
|
||||||
for (int ch;
|
for (int ch;
|
||||||
(ch = musl_getopt_long_only(curArgc, curArgv, optString.c_str(), longOpts, nullptr))
|
(ch = musl_getopt_long_only(curArgc, curArgv, optString.c_str(), longOpts)) != -1;) {
|
||||||
!= -1;) {
|
|
||||||
if (ch == 1 && musl_optarg[0] == '@') {
|
if (ch == 1 && musl_optarg[0] == '@') {
|
||||||
atFileName = &musl_optarg[1];
|
atFileName = &musl_optarg[1];
|
||||||
break;
|
break;
|
||||||
@@ -112,7 +116,7 @@ void cli_ParseArgs(
|
|||||||
|
|
||||||
// It would be nice to compute the char pointers on the fly, but reallocs don't allow
|
// 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
|
// that; so we must compute the offsets after the pool is fixed
|
||||||
std::vector<size_t> offsets = readAtFile(&musl_optarg[1], argPool, fatal);
|
std::vector<size_t> offsets = readAtFile(&musl_optarg[1], argPool, usage);
|
||||||
stackEntry.argv.reserve(offsets.size() + 2); // Avoid a bunch of reallocs
|
stackEntry.argv.reserve(offsets.size() + 2); // Avoid a bunch of reallocs
|
||||||
for (size_t ofs : offsets) {
|
for (size_t ofs : offsets) {
|
||||||
stackEntry.argv.push_back(&argPool.data()[ofs]);
|
stackEntry.argv.push_back(&argPool.data()[ofs]);
|
||||||
|
|||||||
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);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -252,7 +252,7 @@ static void parseArg(int ch, char *arg) {
|
|||||||
|
|
||||||
// LCOV_EXCL_START
|
// LCOV_EXCL_START
|
||||||
case 'V':
|
case 'V':
|
||||||
printf("rgbfix %s\n", get_package_version_string());
|
printf("%s %s\n", usage.name.c_str(), get_package_version_string());
|
||||||
exit(0);
|
exit(0);
|
||||||
|
|
||||||
case 'v':
|
case 'v':
|
||||||
@@ -346,7 +346,7 @@ static void initLogo() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
cli_ParseArgs(argc, argv, optstring, longopts, parseArg, fatal);
|
cli_ParseArgs(argc, argv, optstring, longopts, parseArg, usage);
|
||||||
|
|
||||||
if ((options.cartridgeType & 0xFF00) == TPP1 && !options.japanese) {
|
if ((options.cartridgeType & 0xFF00) == TPP1 && !options.japanese) {
|
||||||
warning(
|
warning(
|
||||||
|
|||||||
@@ -409,7 +409,7 @@ static void parseArg(int ch, char *arg) {
|
|||||||
|
|
||||||
// LCOV_EXCL_START
|
// LCOV_EXCL_START
|
||||||
case 'V':
|
case 'V':
|
||||||
printf("rgbgfx %s\n", get_package_version_string());
|
printf("%s %s\n", usage.name.c_str(), get_package_version_string());
|
||||||
exit(0);
|
exit(0);
|
||||||
|
|
||||||
case 'v':
|
case 'v':
|
||||||
@@ -481,7 +481,7 @@ static void verboseOutputConfig() {
|
|||||||
|
|
||||||
style_Set(stderr, STYLE_MAGENTA, false);
|
style_Set(stderr, STYLE_MAGENTA, false);
|
||||||
|
|
||||||
fprintf(stderr, "rgbgfx %s\n", get_package_version_string());
|
fprintf(stderr, "%s %s\n", usage.name.c_str(), get_package_version_string());
|
||||||
|
|
||||||
printVVVVVVerbosity();
|
printVVVVVVerbosity();
|
||||||
|
|
||||||
@@ -639,7 +639,7 @@ static void replaceExtension(std::string &path, char const *extension) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
cli_ParseArgs(argc, argv, optstring, longopts, parseArg, fatal);
|
cli_ParseArgs(argc, argv, optstring, longopts, parseArg, usage);
|
||||||
|
|
||||||
if (options.nbColorsPerPal == 0) {
|
if (options.nbColorsPerPal == 0) {
|
||||||
options.nbColorsPerPal = 1u << options.bitDepth;
|
options.nbColorsPerPal = 1u << options.bitDepth;
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,6 @@
|
|||||||
#include "helpers.hpp"
|
#include "helpers.hpp"
|
||||||
#include "itertools.hpp"
|
#include "itertools.hpp"
|
||||||
#include "linkdefs.hpp"
|
#include "linkdefs.hpp"
|
||||||
#include "platform.hpp" // MUSTTAIL
|
|
||||||
#include "verbosity.hpp"
|
#include "verbosity.hpp"
|
||||||
|
|
||||||
#include "link/main.hpp"
|
#include "link/main.hpp"
|
||||||
@@ -119,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;
|
|
||||||
}
|
|
||||||
|
|
||||||
MUSTTAIL return getPlacement(section, location);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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.
|
||||||
@@ -310,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
|
||||||
@@ -330,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()
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -283,7 +283,7 @@ static void parseArg(int ch, char *arg) {
|
|||||||
|
|
||||||
// LCOV_EXCL_START
|
// LCOV_EXCL_START
|
||||||
case 'V':
|
case 'V':
|
||||||
printf("rgblink %s\n", get_package_version_string());
|
printf("%s %s\n", usage.name.c_str(), get_package_version_string());
|
||||||
exit(0);
|
exit(0);
|
||||||
|
|
||||||
case 'v':
|
case 'v':
|
||||||
@@ -330,7 +330,7 @@ static void verboseOutputConfig() {
|
|||||||
|
|
||||||
style_Set(stderr, STYLE_MAGENTA, false);
|
style_Set(stderr, STYLE_MAGENTA, false);
|
||||||
|
|
||||||
fprintf(stderr, "rgblink %s\n", get_package_version_string());
|
fprintf(stderr, "%s %s\n", usage.name.c_str(), get_package_version_string());
|
||||||
|
|
||||||
printVVVVVVerbosity();
|
printVVVVVVerbosity();
|
||||||
|
|
||||||
@@ -415,7 +415,7 @@ static void verboseOutputConfig() {
|
|||||||
// LCOV_EXCL_STOP
|
// LCOV_EXCL_STOP
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
cli_ParseArgs(argc, argv, optstring, longopts, parseArg, fatal);
|
cli_ParseArgs(argc, argv, optstring, longopts, parseArg, usage);
|
||||||
|
|
||||||
verboseOutputConfig();
|
verboseOutputConfig();
|
||||||
|
|
||||||
|
|||||||
@@ -420,19 +420,15 @@ void obj_ReadFile(std::string const &filePath, size_t fileID) {
|
|||||||
}
|
}
|
||||||
Defer closeFile{[&] { fclose(file); }};
|
Defer closeFile{[&] { fclose(file); }};
|
||||||
|
|
||||||
// First, check if the object is a RGBDS object or a SDCC one. If the first byte is 'R',
|
// First, check if the object is a RGBDS object, a SDCC one, or neither.
|
||||||
// we'll assume it's a RGBDS object file, and otherwise, that it's a SDCC object file.
|
// A single `ungetc` is guaranteed to work.
|
||||||
int c = getc(file);
|
switch (ungetc(getc(file), file)) {
|
||||||
|
|
||||||
ungetc(c, file); // Guaranteed to work
|
|
||||||
switch (c) {
|
|
||||||
case EOF:
|
case EOF:
|
||||||
fatal("File \"%s\" is empty!", fileName);
|
fatal("File \"%s\" is empty!", fileName);
|
||||||
|
|
||||||
case 'R':
|
case 'X':
|
||||||
break;
|
case 'D':
|
||||||
|
case 'Q': {
|
||||||
default:
|
|
||||||
// This is (probably) a SDCC object file, defer the rest of detection to it.
|
// This is (probably) a SDCC object file, defer the rest of detection to it.
|
||||||
// Since SDCC does not provide line info, everything will be reported as coming from the
|
// Since SDCC does not provide line info, everything will be reported as coming from the
|
||||||
// object file. It's better than nothing.
|
// object file. It's better than nothing.
|
||||||
@@ -450,11 +446,16 @@ void obj_ReadFile(std::string const &filePath, size_t fileID) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Begin by reading the magic bytes
|
case 'R':
|
||||||
int matchedElems;
|
// Check the magic byte signature for a RGB object file.
|
||||||
|
if (char magic[literal_strlen(RGBDS_OBJECT_VERSION_STRING)];
|
||||||
|
fread(magic, 1, sizeof(magic), file) == sizeof(magic)
|
||||||
|
&& !memcmp(magic, RGBDS_OBJECT_VERSION_STRING, sizeof(magic))) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
[[fallthrough]];
|
||||||
|
|
||||||
if (fscanf(file, RGBDS_OBJECT_VERSION_STRING "%n", &matchedElems) == 1
|
default:
|
||||||
&& matchedElems != literal_strlen(RGBDS_OBJECT_VERSION_STRING)) {
|
|
||||||
fatal("%s: Not a RGBDS object file", fileName);
|
fatal("%s: Not a RGBDS object file", fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -437,6 +437,7 @@ static int32_t computeRPNExpr(Patch const &patch, std::vector<Symbol> const &fil
|
|||||||
} else if (Symbol const *symbol = getSymbol(fileSymbols, value); !symbol) {
|
} else if (Symbol const *symbol = getSymbol(fileSymbols, value); !symbol) {
|
||||||
errorAt(patch, "Undefined symbol `%s`", fileSymbols[value].name.c_str());
|
errorAt(patch, "Undefined symbol `%s`", fileSymbols[value].name.c_str());
|
||||||
sym_TraceLocalAliasedSymbols(fileSymbols[value].name);
|
sym_TraceLocalAliasedSymbols(fileSymbols[value].name);
|
||||||
|
value = 0;
|
||||||
isError = true;
|
isError = true;
|
||||||
} else if (std::holds_alternative<Label>(symbol->data)) {
|
} else if (std::holds_alternative<Label>(symbol->data)) {
|
||||||
Label const &label = std::get<Label>(symbol->data);
|
Label const &label = std::get<Label>(symbol->data);
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
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
@@ -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
@@ -0,0 +1,2 @@
|
|||||||
|
one
|
||||||
|
two
|
||||||
2
test/asm/section-union-data.asm
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
SECTION UNION "wat", ROM0
|
||||||
|
db 42
|
||||||
5
test/asm/section-union-data.err
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
error: Cannot declare ROM sections as `UNION`
|
||||||
|
at section-union-data.asm(1)
|
||||||
|
error: Cannot output data outside of a `SECTION`
|
||||||
|
at section-union-data.asm(2)
|
||||||
|
Assembly aborted with 2 errors!
|
||||||
@@ -108,10 +108,10 @@ fi
|
|||||||
|
|
||||||
if "$nonfree"; then
|
if "$nonfree"; then
|
||||||
action github.com pret pokecrystal 2025-09-05 d138ed1bd4db80cf8caa549878600448fedf674e
|
action github.com pret pokecrystal 2025-09-05 d138ed1bd4db80cf8caa549878600448fedf674e
|
||||||
action github.com pret pokered 2025-09-25 628797baffe7ea7dd4b224116d9704c7ae1b9c29
|
action github.com pret pokered 2025-10-04 e3af20b907fad59bcbee7d36ab4912d0cc5f2935
|
||||||
action github.com zladx LADX-Disassembly 2025-09-20 e09ee3259acbdecb89a0eba6cbc438281c174e85
|
action github.com zladx LADX-Disassembly 2025-10-11 f6de3516aaea480d2d3beda357fc76460bb8299d
|
||||||
fi
|
fi
|
||||||
action github.com AntonioND ucity 2025-08-07 d1880a2a112d7c26f16c0fc06a15b6c32fdc9137
|
action github.com AntonioND ucity 2025-08-07 d1880a2a112d7c26f16c0fc06a15b6c32fdc9137
|
||||||
action github.com pinobatch libbet 2025-08-31 e42c0036b18e6e715987b88b4973389b283974c9
|
action github.com pinobatch libbet 2025-08-31 e42c0036b18e6e715987b88b4973389b283974c9
|
||||||
action github.com LIJI32 SameBoy 2025-09-27 91006369a6510c5db029a440691dd4becaa6208b
|
action github.com LIJI32 SameBoy 2025-10-16 4c57a761517d3d11bb8d2ce31037b17d3a85b745
|
||||||
action codeberg.org ISSOtm gb-starter-kit 2025-09-23 6aeb2508ab75c15724b177a1437b939357bc5d6f
|
action codeberg.org ISSOtm gb-starter-kit 2025-09-23 6aeb2508ab75c15724b177a1437b939357bc5d6f
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
error: Failed to fit tile colors [$1527, $15cc, $1a93] in specified palettes
|
error: Failed to fit tile colors [$1527, $15cc, $1ab4] in specified palettes
|
||||||
note: The following palette was specified:
|
note: The following palette was specified:
|
||||||
[$1a93, $15cc]
|
[$1ab4, $15cc]
|
||||||
Conversion aborted after 1 error
|
Conversion aborted after 1 error
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
error: Failed to fit tile colors [$688b, $7f55, $7fff] in specified palettes
|
error: Failed to fit tile colors [$6c8a, $7f55, $7fff] in specified palettes
|
||||||
note: The following palettes were specified:
|
note: The following palettes were specified:
|
||||||
[$5f77, $213d, $41a6, $40ee]
|
[$5f77, $213d, $41a6, $40ee]
|
||||||
[$3f65, $36b3, $262a, $50b0]
|
[$3f65, $36b3, $262a, $50b0]
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
error: Failed to fit tile colors [$688b, $7f55, $7fff] in specified palettes
|
error: Failed to fit tile colors [$6c8a, $7f55, $7fff] in specified palettes
|
||||||
note: The following palettes were specified:
|
note: The following palettes were specified:
|
||||||
[$7fff, $7f55]
|
[$7fff, $7f55]
|
||||||
[$7fff, $688b]
|
[$7fff, $6c8a]
|
||||||
Conversion aborted after 1 error
|
Conversion aborted after 1 error
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
warning: Fusing colors #a9bacbff and #aabbccff into Game Boy color $66f5 [first seen at x: 1, y: 1]
|
warning: Fusing colors #a9b9c9ff and #aabbccff into Game Boy color $66f5 [first seen at x: 1, y: 1]
|
||||||
FATAL: Tile (0, 0) contains the background color (#aabbccff)!
|
FATAL: Tile (0, 0) contains the background color (#aabbccff)!
|
||||||
Conversion aborted after 1 error
|
Conversion aborted after 1 error
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 174 B After Width: | Height: | Size: 177 B |
@@ -1 +1 @@
|
|||||||
媓U<EFBFBD><EFBFBD><EFBFBD>
|
妉U<EFBFBD><EFBFBD><EFBFBD>
|
||||||
|
Before Width: | Height: | Size: 556 B After Width: | Height: | Size: 927 B |
|
Before Width: | Height: | Size: 556 B After Width: | Height: | Size: 927 B |
|
Before Width: | Height: | Size: 556 B After Width: | Height: | Size: 927 B |
|
Before Width: | Height: | Size: 556 B After Width: | Height: | Size: 927 B |
@@ -144,7 +144,7 @@ static void generate_palettes(uint16_t palettes[/* 60 */][4]) {
|
|||||||
* Expand a 5-bit color component to 8 bits with minimal bias
|
* Expand a 5-bit color component to 8 bits with minimal bias
|
||||||
*/
|
*/
|
||||||
static uint8_t _5to8(uint8_t five) {
|
static uint8_t _5to8(uint8_t five) {
|
||||||
return (five * 255 + 15) / 31;
|
return five << 3 | five >> 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Can't mark as `const`, as the array type is otherwise not compatible (augh)
|
// Can't mark as `const`, as the array type is otherwise not compatible (augh)
|
||||||
|
|||||||
3
test/link/rpn-bad-sym-id.asm
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
SECTION "bad", ROM0
|
||||||
|
ld a, FOO * 256
|
||||||
|
ld a, BAR * 256
|
||||||
5
test/link/rpn-bad-sym-id.out
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
error: Undefined symbol `BAR`
|
||||||
|
at rpn-bad-sym-id.asm(3)
|
||||||
|
error: Undefined symbol `FOO`
|
||||||
|
at rpn-bad-sym-id.asm(2)
|
||||||
|
Linking failed with 2 errors
|
||||||
@@ -1,2 +1,2 @@
|
|||||||
section fragment "test", rom0
|
section fragment "test", wram0
|
||||||
db 1
|
w1:: db
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
section union "test", rom0
|
section union "test", wram0
|
||||||
db 2
|
w2:: db
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
IF !DEF(SECOND)
|
|
||||||
def DATA equs "ds 4"
|
|
||||||
ELSE
|
|
||||||
def DATA equs "db $aa, $bb, $cc, $dd"
|
|
||||||
ENDC
|
|
||||||
|
|
||||||
SECTION UNION "overlaid data", ROM0
|
|
||||||
{DATA}
|
|
||||||
|
|
||||||
PURGE DATA
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
FATAL: Section "overlaid data" is of type `ROM0`, which cannot be `UNION`ized
|
|
||||||
Linking aborted with 1 error
|
|
||||||
---
|
|
||||||
error: Cannot declare ROM sections as `UNION`
|
|
||||||
at <stdin>(18)
|
|
||||||
FATAL: Cannot create section "overlaid data" (1 error)
|
|
||||||
at <stdin>(18)
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
IF !DEF(SECOND)
|
|
||||||
def DATA = 1
|
|
||||||
ELSE
|
|
||||||
def DATA = 2
|
|
||||||
ENDC
|
|
||||||
|
|
||||||
SECTION UNION "different data", ROM0
|
|
||||||
db DATA
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
FATAL: Section "different data" is of type `ROM0`, which cannot be `UNION`ized
|
|
||||||
Linking aborted with 1 error
|
|
||||||
---
|
|
||||||
error: Cannot declare ROM sections as `UNION`
|
|
||||||
at <stdin>(16)
|
|
||||||
FATAL: Cannot create section "different data" (1 error)
|
|
||||||
at <stdin>(16)
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
IF !DEF(SECOND)
|
|
||||||
def SIZE = 69
|
|
||||||
ELSE
|
|
||||||
def SIZE = 420
|
|
||||||
ENDC
|
|
||||||
|
|
||||||
SECTION UNION "different section sizes", ROM0
|
|
||||||
ds SIZE
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
FATAL: Section "different section sizes" is of type `ROM0`, which cannot be `UNION`ized
|
|
||||||
Linking aborted with 1 error
|
|
||||||
---
|
|
||||||
error: Cannot declare ROM sections as `UNION`
|
|
||||||
at <stdin>(16)
|
|
||||||
FATAL: Cannot create section "different section sizes" (1 error)
|
|
||||||
at <stdin>(16)
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
IF !DEF(SECOND)
|
|
||||||
def INSTR equs "sbc a"
|
|
||||||
ELSE
|
|
||||||
def INSTR equs "db $9f"
|
|
||||||
ENDC
|
|
||||||
|
|
||||||
SECTION UNION "different syntaxes", ROM0
|
|
||||||
{INSTR}
|
|
||||||
|
|
||||||
PURGE INSTR
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
FATAL: Section "different syntaxes" is of type `ROM0`, which cannot be `UNION`ized
|
|
||||||
Linking aborted with 1 error
|
|
||||||
---
|
|
||||||
error: Cannot declare ROM sections as `UNION`
|
|
||||||
at <stdin>(18)
|
|
||||||
FATAL: Cannot create section "different syntaxes" (1 error)
|
|
||||||
at <stdin>(18)
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
IF !DEF(SECOND)
|
|
||||||
def DATA equs "ds 1\ndb $aa"
|
|
||||||
ELSE
|
|
||||||
def DATA equs "db $bb\nds 1"
|
|
||||||
ENDC
|
|
||||||
|
|
||||||
SECTION UNION "mutually-overlaid data", ROM0
|
|
||||||
{DATA}
|
|
||||||
|
|
||||||
PURGE DATA
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
FATAL: Section "mutually-overlaid data" is of type `ROM0`, which cannot be `UNION`ized
|
|
||||||
Linking aborted with 1 error
|
|
||||||
---
|
|
||||||
error: Cannot declare ROM sections as `UNION`
|
|
||||||
at <stdin>(18)
|
|
||||||
FATAL: Cannot create section "mutually-overlaid data" (1 error)
|
|
||||||
at <stdin>(18)
|
|
||||||