Compare commits

..

32 Commits

Author SHA1 Message Date
Rangi
92bfe5d930 Release v1.0.1 2026-01-01 00:43:43 -05:00
Rangi
41fe1d8f25 Correct error message for unconstrained sections with overlay (#1879) 2025-12-28 19:21:53 -05:00
Rangi
63a911e657 Specify -std=c++20 not c++2a (#1877) 2025-12-21 11:44:14 -05:00
Rangi
a9ab248fed Improve some RGBGFX error messages (#1876)
* Improve some RGBGFX error messages

* Fix assertion failure on ambiguous transparent/opaque pixels
2025-12-19 13:00:05 -05:00
Rangi
9b22ff3491 Fix link in man page 2025-12-12 18:25:47 -05:00
Rangi
a4af81f250 Fix two "Using a macro as first argument cancels effect of .Li" man page warnings
These were observed in https://udd.debian.org/lintian/?packages=rgbds
and can be reproduced by running:

    for f in man/*; do
        echo $f
        LC_ALL=C.UTF-8 MANROFFSEQ='' MANWIDTH=120 \
            man --warnings -E UTF-8 -l -Tutf8 -Z "$f" >/dev/null
    done
2025-12-12 12:31:03 -05:00
Rangi
e08de399db Clarify usage versus encoding of jr and ldh (#1875) 2025-12-11 17:46:39 -05:00
Rangi
d4e0ca5f90 Update libpng to 1.6.53 2025-12-11 11:48:18 -05:00
Rangi
2666dcbc26 Remove exclamation marks and periods from error messages (#1874) 2025-12-10 11:50:33 -05:00
Rangi42
27dca5680c Fix formatting typo in man/rgbasm-old.5 2025-12-09 10:33:24 -05:00
Rangi
b0e0dfc56e Handle a missing -P/--preinclude file the same as an INCLUDE (#1873) 2025-12-08 14:39:34 -05:00
Rangi
33475e2c36 Factor out version-printing to usage.cpp (#1870) 2025-12-05 23:04:49 -05:00
Rangi
c8161be23a Add missing SPDX-License-Identifier: MIT comments 2025-12-05 22:41:12 -05:00
Rangi
2c5c453ab8 Refactor FileStackNode::printBacktrace from recursive to iterative
This avoids a potential stack overflow for very long backtraces,
or for corrupt object files with cyclic backtraces
2025-12-05 22:41:12 -05:00
Rangi
c3e245c13e Correct typo in rgbds(5) 2025-12-05 22:39:30 -05:00
Rangi
3631fab63c Fix bug where an object's invalid ROMX bank of 0 could break rgblink (#1868) 2025-12-05 11:21:00 -05:00
Rangi
1c00123b33 Add missing return 0; to rgblink main() 2025-12-04 21:06:46 -05:00
Rangi
131bb97ebc Fix some rgblink object file input bugs found via fuzzing with AFL++ (#1867)
- ID numbers (for fstack nodes, sections, symbols, patches, etc)
  might be too large for their associated collection
- Enum values might be invalid
- Bank values might be out of range for their section types
2025-12-04 20:49:16 -05:00
Rangi
8d6c617875 Use 4 spaces per tab in LCOV coverage report 2025-12-04 17:56:16 -05:00
Rangi42
752e2b3620 Symbol names with more than two '.'s could be defined as constants
Dot-only names could also trip an assertion in `make develop`
when used as labels
2025-12-04 15:15:41 -05:00
Rangi42
ad3188f038 Fix garbage characters at EOF causing an infinite loop 2025-12-04 15:15:41 -05:00
Rangi
a6eb6457d8 Clarify documentation of rgbgfx -C/--color-curve (#1864) 2025-12-04 10:49:23 -05:00
Rangi
0d3276975e Update test dependencies (#1865) 2025-12-04 10:29:40 -05:00
Rangi
d961c697d7 Update libpng to 1.6.51 (#1862) 2025-11-22 19:05:52 -05:00
Rangi
1eb4eb3339 Reuse the usage.name for printing version info 2025-11-18 22:32:45 -05:00
Rangi
a3d3e1525a Fix RGBLINK object type detection 2025-11-18 22:01:43 -05:00
Rangi
3553c9c4da Fix RGBLINK evaluation of undefined RPN symbols
This was the only RPN case to not assign a deliberate value
in all possible branches.

Fixes #1858
2025-11-18 16:40:24 -05:00
Rangi
5c2c893ced Refactor getSectionDescription in src/link/assign.cpp 2025-11-16 17:37:01 -05:00
Rangi
0f266d1c66 Specify more ASan options (#1860) 2025-11-16 17:11:09 -05:00
Rangi
8ab4602ae5 Add -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_DEBUG to develop builds (#1859) 2025-11-16 13:11:08 -05:00
Rangi
04e3a904c2 Avoid calling style_Set/Reset before strerror(errno),
since they may call `isatty` which can change `errno`

Fixes #1857
2025-11-08 12:06:16 -05:00
Rangi
395b03e88e Disallow SECTION UNION for ROM sections
Fixes #1855
2025-11-07 11:38:06 -05:00
271 changed files with 914 additions and 672 deletions

View File

@@ -1,13 +1,13 @@
#!/bin/bash #!/bin/bash
set -euo pipefail set -euo pipefail
pngver=1.6.50 pngver=1.6.53
## Grab sources and check them ## Grab sources and check them
curl -LOJ "http://prdownloads.sourceforge.net/libpng/libpng-$pngver.tar.xz?download" curl -LOJ "http://prdownloads.sourceforge.net/libpng/libpng-$pngver.tar.xz?download"
# Brew doesn't provide any sha256sum, so we're making do with `sha2` instead. # Brew doesn't provide any sha256sum, so we're making do with `sha2` instead.
if [ "$(sha2 -q -256 libpng-$pngver.tar.xz)" != 4df396518620a7aa3651443e87d1b2862e4e88cad135a8b93423e01706232307 ]; then if [ "$(sha2 -q -256 libpng-$pngver.tar.xz)" != 1d3fb8ccc2932d04aa3663e22ef5ef490244370f4e568d7850165068778d98d4 ]; then
sha2 -256 libpng-$pngver.tar.xz sha2 -256 libpng-$pngver.tar.xz
echo Checksum mismatch! Aborting. >&2 echo Checksum mismatch! Aborting. >&2
exit 1 exit 1

View File

@@ -16,8 +16,8 @@ function getlibrary ([string] $URI, [string] $filename, [string] $hash, [string]
} }
getlibrary 'https://www.zlib.net/zlib131.zip' 'zlib.zip' '72af66d44fcc14c22013b46b814d5d2514673dda3d115e64b690c1ad636e7b17' . getlibrary 'https://www.zlib.net/zlib131.zip' 'zlib.zip' '72af66d44fcc14c22013b46b814d5d2514673dda3d115e64b690c1ad636e7b17' .
getlibrary 'https://github.com/pnggroup/libpng/archive/refs/tags/v1.6.50.zip' 'libpng.zip' 'f6bb2544d2cf5465af3a695dee0b7eacff82f11a50aa4672ef0e19df6e16d455' . getlibrary 'https://github.com/pnggroup/libpng/archive/refs/tags/v1.6.53.zip' 'libpng.zip' '9fb99118ec4523d9a9dab652ce7c2472ec76f6ccd69d1aba3ab873bb8cf84b98' .
getlibrary 'https://github.com/lexxmark/winflexbison/releases/download/v2.5.25/win_flex_bison-2.5.25.zip' 'winflexbison.zip' '8d324b62be33604b2c45ad1dd34ab93d722534448f55a16ca7292de32b6ac135' install_dir getlibrary 'https://github.com/lexxmark/winflexbison/releases/download/v2.5.25/win_flex_bison-2.5.25.zip' 'winflexbison.zip' '8d324b62be33604b2c45ad1dd34ab93d722534448f55a16ca7292de32b6ac135' install_dir
Move-Item zlib-1.3.1 zlib Move-Item zlib-1.3.1 zlib
Move-Item libpng-1.6.50 libpng Move-Item libpng-1.6.53 libpng

View File

@@ -1,13 +1,13 @@
#!/bin/bash #!/bin/bash
set -euo pipefail set -euo pipefail
pngver=1.6.50 pngver=1.6.53
arch="$1" arch="$1"
## Grab sources and check them ## Grab sources and check them
wget http://downloads.sourceforge.net/project/libpng/libpng16/$pngver/libpng-$pngver.tar.xz wget http://downloads.sourceforge.net/project/libpng/libpng16/$pngver/libpng-$pngver.tar.xz
echo 4df396518620a7aa3651443e87d1b2862e4e88cad135a8b93423e01706232307 libpng-$pngver.tar.xz | sha256sum -c - echo 1d3fb8ccc2932d04aa3663e22ef5ef490244370f4e568d7850165068778d98d4 libpng-$pngver.tar.xz | sha256sum -c -
## Extract sources and patch them ## Extract sources and patch them

View File

@@ -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)

View File

@@ -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 ARG version=1.0.1
WORKDIR /rgbds WORKDIR /rgbds
COPY . . COPY . .

View File

@@ -31,7 +31,7 @@ WARNFLAGS := -Wall -pedantic -Wno-unknown-warning-option -Wno-gnu-zero-variadic-
# Overridable CXXFLAGS # Overridable CXXFLAGS
CXXFLAGS ?= -O3 -flto -DNDEBUG CXXFLAGS ?= -O3 -flto -DNDEBUG
# Non-overridable CXXFLAGS # Non-overridable CXXFLAGS
REALCXXFLAGS := ${CXXFLAGS} ${WARNFLAGS} -std=c++2a -I include -fno-exceptions -fno-rtti REALCXXFLAGS := ${CXXFLAGS} ${WARNFLAGS} -std=c++20 -I include -fno-exceptions -fno-rtti
# Overridable LDFLAGS # Overridable LDFLAGS
LDFLAGS ?= LDFLAGS ?=
# Non-overridable LDFLAGS # Non-overridable LDFLAGS
@@ -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.
@@ -254,7 +254,7 @@ tidy: src/asm/parser.hpp src/link/script.hpp
iwyu: iwyu:
$Qenv ${MAKE} \ $Qenv ${MAKE} \
CXX="include-what-you-use" \ CXX="include-what-you-use" \
REALCXXFLAGS="-std=c++2a -I include" REALCXXFLAGS="-std=c++20 -I include"
# Targets for the project maintainer to easily create Windows exes. # Targets for the project maintainer to easily create Windows exes.
# This is not for Windows users! # This is not for Windows users!

View File

@@ -1,4 +1,4 @@
-std=c++2a -std=c++20
-I -I
include include
-fno-exceptions -fno-exceptions

View File

@@ -22,7 +22,7 @@ mkdir -p coverage
COVERAGE_INFO=coverage/coverage.info COVERAGE_INFO=coverage/coverage.info
lcov -c --no-external -d . -o "$COVERAGE_INFO" lcov -c --no-external -d . -o "$COVERAGE_INFO"
lcov -r "$COVERAGE_INFO" src/asm/parser.{hpp,cpp} src/link/script.{hpp,cpp} -o "$COVERAGE_INFO" lcov -r "$COVERAGE_INFO" src/asm/parser.{hpp,cpp} src/link/script.{hpp,cpp} -o "$COVERAGE_INFO"
genhtml --dark-mode -f -s -o coverage/ "$COVERAGE_INFO" genhtml --dark-mode --num-spaces 4 -f -s -o coverage/ "$COVERAGE_INFO"
# Check whether running from coverage.yml workflow # Check whether running from coverage.yml workflow
if [ "$1" != "ubuntu-ci" ]; then if [ "$1" != "ubuntu-ci" ]; then

View File

@@ -63,7 +63,7 @@ MacroArgs *fstk_GetCurrentMacroArgs();
void fstk_AddIncludePath(std::string const &path); void fstk_AddIncludePath(std::string const &path);
void fstk_AddPreIncludeFile(std::string const &path); void fstk_AddPreIncludeFile(std::string const &path);
std::optional<std::string> fstk_FindFile(std::string const &path); std::optional<std::string> fstk_FindFile(std::string const &path);
bool fstk_FileError(std::string const &path, char const *functionName); bool fstk_FileError(std::string const &path, char const *description);
bool fstk_FailedOnMissingInclude(); bool fstk_FailedOnMissingInclude();
bool yywrap(); bool yywrap();
@@ -84,6 +84,6 @@ void fstk_RunFor(
bool fstk_Break(); bool fstk_Break();
void fstk_NewRecursionDepth(size_t newDepth); void fstk_NewRecursionDepth(size_t newDepth);
void fstk_Init(std::string const &mainPath); bool fstk_Init(std::string const &mainPath);
#endif // RGBDS_ASM_FSTACK_HPP #endif // RGBDS_ASM_FSTACK_HPP

View File

@@ -68,6 +68,8 @@ struct Symbol {
uint32_t getConstantValue() const; uint32_t getConstantValue() const;
}; };
bool sym_IsDotScope(std::string const &symName);
void sym_ForEach(void (*callback)(Symbol &)); void sym_ForEach(void (*callback)(Symbol &));
Symbol *sym_AddLocalLabel(std::string const &symName); Symbol *sym_AddLocalLabel(std::string const &symName);

View File

@@ -120,7 +120,7 @@ enum SectionModifier { SECTION_NORMAL, SECTION_UNION, SECTION_FRAGMENT };
extern char const * const sectionModNames[]; extern char const * const sectionModNames[];
enum ExportLevel { SYMTYPE_LOCAL, SYMTYPE_IMPORT, SYMTYPE_EXPORT }; enum ExportLevel { SYMTYPE_LOCAL, SYMTYPE_IMPORT, SYMTYPE_EXPORT, SYMTYPE_INVALID };
enum PatchType { enum PatchType {
PATCHTYPE_BYTE, PATCHTYPE_BYTE,

View File

@@ -13,6 +13,8 @@ struct Usage {
std::vector<std::string> flags; std::vector<std::string> flags;
std::vector<std::pair<std::vector<std::string>, std::vector<std::string>>> options; std::vector<std::pair<std::vector<std::string>, std::vector<std::string>>> options;
void printVersion(bool error) const;
[[noreturn]] [[noreturn]]
void printAndExit(int code) const; void printAndExit(int code) const;

View File

@@ -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

View File

@@ -5,7 +5,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 1
// #define PACKAGE_VERSION_RC 1 // #define PACKAGE_VERSION_RC 1
char const *get_package_version_string(); char const *get_package_version_string();

View File

@@ -1,6 +1,6 @@
.\" SPDX-License-Identifier: MIT .\" SPDX-License-Identifier: MIT
.\" .\"
.Dd October 31, 2025 .Dd January 1, 2026
.Dt GBZ80 7 .Dt GBZ80 7
.Os .Os
.Sh NAME .Sh NAME
@@ -11,6 +11,11 @@ This is the list of instructions supported by
.Xr rgbasm 1 , .Xr rgbasm 1 ,
including a short description, the number of bytes needed to encode them and the number of CPU cycles at 1MHz (or 2MHz in GBC double speed mode) needed to complete them. including a short description, the number of bytes needed to encode them and the number of CPU cycles at 1MHz (or 2MHz in GBC double speed mode) needed to complete them.
.Pp .Pp
Instructons are documented according to the syntax accepted by
.Xr rgbasm 1 ,
which does not always match one-to-one with the way instructions are
.Lk https://gbdev.io/gb-opcodes/optables/ encoded .
.Pp
Note: All arithmetic and logic instructions that use register Note: All arithmetic and logic instructions that use register
.Sy A .Sy A
as a destination can omit the destination, since it is assumed to be register as a destination can omit the destination, since it is assumed to be register
@@ -861,11 +866,13 @@ Flags: None affected.
Relative Jump to address Relative Jump to address
.Ar n16 . .Ar n16 .
.Pp .Pp
The address is encoded as a signed 8-bit offset from the address immediately following the The target address
.Ic JR
instruction, so the target address
.Ar n16 .Ar n16
must be between is
.Em encoded
as a signed 8-bit offset from the address immediately following the
.Ic JR
instruction, so it must be between
.Sy -128 .Sy -128
and and
.Sy 127 .Sy 127
@@ -889,6 +896,18 @@ if condition
.Ar cc .Ar cc
is met. is met.
.Pp .Pp
The target address
.Ar n16
is
.Em encoded
as a signed 8-bit offset from the address immediately following the
.Ic JR
instruction, so it must be between
.Sy -128
and
.Sy 127
bytes away.
.Pp
Cycles: 3 taken / 2 untaken Cycles: 3 taken / 2 untaken
.Pp .Pp
Bytes: 2 Bytes: 2
@@ -992,8 +1011,15 @@ Flags: None affected.
Copy the value in register Copy the value in register
.Sy A .Sy A
into the byte at address into the byte at address
.Ar n16 , .Ar n16 .
provided the address is between .Pp
The destination address
.Ar n16
is
.Em encoded
as its 8-bit low byte and assumes a high byte of
.Ad $FF ,
so it must be between
.Ad $FF00 .Ad $FF00
and and
.Ad $FFFF . .Ad $FFFF .
@@ -1043,8 +1069,15 @@ Flags: None affected.
Copy the byte at address Copy the byte at address
.Ar n16 .Ar n16
into register into register
.Sy A , .Sy A .
provided the address is between .Pp
The source address
.Ar n16
is
.Em encoded
as its 8-bit low byte and assumes a high byte of
.Ad $FF ,
so it must be between
.Ad $FF00 .Ad $FF00
and and
.Ad $FFFF . .Ad $FFFF .

View File

@@ -2,7 +2,7 @@
.\" .\"
.\" SPDX-License-Identifier: MIT .\" SPDX-License-Identifier: MIT
.\" .\"
.Dd October 31, 2025 .Dd January 1, 2026
.Dt RGBASM-OLD 5 .Dt RGBASM-OLD 5
.Os .Os
.Sh NAME .Sh NAME
@@ -370,7 +370,7 @@ or
Deprecated in 1.0.0. Deprecated in 1.0.0.
.Pp .Pp
Instead, use Instead, use
.Dl -Wno-overwrite . .Ql -Wno-overwrite .
.Ss rgbgfx -h/--horizontal .Ss rgbgfx -h/--horizontal
Removed in 0.6.0. Removed in 0.6.0.
.Pp .Pp

View File

@@ -1,6 +1,6 @@
.\" SPDX-License-Identifier: MIT .\" SPDX-License-Identifier: MIT
.\" .\"
.Dd October 31, 2025 .Dd January 1, 2026
.Dt RGBASM 1 .Dt RGBASM 1
.Os .Os
.Sh NAME .Sh NAME

View File

@@ -2,7 +2,7 @@
.\" .\"
.\" SPDX-License-Identifier: MIT .\" SPDX-License-Identifier: MIT
.\" .\"
.Dd October 31, 2025 .Dd January 1, 2026
.Dt RGBASM 5 .Dt RGBASM 5
.Os .Os
.Sh NAME .Sh NAME
@@ -30,9 +30,9 @@ The syntax is line-based, just as in any other assembler.
Each line may have components in either of these orders: Each line may have components in either of these orders:
.Bl -bullet -offset indent .Bl -bullet -offset indent
.It .It
.Li Oo Ar label : Oc Oo Ar directive Oc Oo ;\ Ns Ar comment Oc .Oo Ar label : Oc Oo Ar directive Oc Oo ;\ Ns Ar comment Oc
.It .It
.Li Oo Ar label : Oc Oo Ar instruction Oo :: Ar instruction ... Oc Oc Oo ;\ Ns Ar comment Oc .Oo Ar label : Oc Oo Ar instruction Oo :: Ar instruction ... Oc Oc Oo ;\ Ns Ar comment Oc
.El .El
.Pp .Pp
Directives are commands to the assembler itself, such as Directives are commands to the assembler itself, such as

View File

@@ -1,6 +1,6 @@
.\" SPDX-License-Identifier: MIT .\" SPDX-License-Identifier: MIT
.\" .\"
.Dd October 31, 2025 .Dd January 1, 2026
.Dt RGBDS 5 .Dt RGBDS 5
.Os .Os
.Sh NAME .Sh NAME
@@ -92,7 +92,7 @@ If the node is not a REPT node...
.Pp .Pp
.Bl -tag -width Ds -compact .Bl -tag -width Ds -compact
.It Cm STRING Ar Name .It Cm STRING Ar Name
The node's name: either a file name, or the macro's name prefixes by its definition's file name The node's name: either a file name, or the macro's name prefixed by its definition's file name
.Pq e.g. Ql src/includes/defines.asm::error . .Pq e.g. Ql src/includes/defines.asm::error .
.El .El
.It Cm ELSE .It Cm ELSE

View File

@@ -1,6 +1,6 @@
.\" SPDX-License-Identifier: MIT .\" SPDX-License-Identifier: MIT
.\" .\"
.Dd October 31, 2025 .Dd January 1, 2026
.Dt RGBDS 7 .Dt RGBDS 7
.Os .Os
.Sh NAME .Sh NAME

View File

@@ -1,6 +1,6 @@
.\" SPDX-License-Identifier: MIT .\" SPDX-License-Identifier: MIT
.\" .\"
.Dd October 31, 2025 .Dd January 1, 2026
.Dt RGBFIX 1 .Dt RGBFIX 1
.Os .Os
.Sh NAME .Sh NAME

View File

@@ -2,7 +2,7 @@
.\" .\"
.\" SPDX-License-Identifier: MIT .\" SPDX-License-Identifier: MIT
.\" .\"
.Dd October 31, 2025 .Dd January 1, 2026
.Dt RGBGFX 1 .Dt RGBGFX 1
.Os .Os
.Sh NAME .Sh NAME
@@ -143,9 +143,17 @@ Set the base IDs for tile map output.
should be one or two numbers between 0 and 255, separated by a comma; they are for bank 0 and bank 1 respectively. should be one or two numbers between 0 and 255, separated by a comma; they are for bank 0 and bank 1 respectively.
Both default to 0. Both default to 0.
.It Fl C , Fl \-color-curve .It Fl C , Fl \-color-curve
When generating palettes, use a color curve mimicking the Game Boy Color's screen. Modifies the color palettes
The resulting colors may look closer to the input image's .Pq whether they are generated from the input image or taken from an input palette specification
.Sy on hardware and accurate emulators . with a color curve mimicking the Game Boy Color's screen.
This adjusts the
.Em absolute
RGB color values so that the
.Em perceived
colors, when displayed on Game Boy Color hardware
.Pq or an emulator with an accurate display filter ,
will look like the original colors as displayed on a backlit computer screen.
Note that GBC displays can look very different depending on the ambient light and their exact hardware model, so this color curve is only a "best effort".
.It Fl c Ar pal_spec , Fl \-colors Ar pal_spec .It Fl c Ar pal_spec , Fl \-colors Ar pal_spec
Use the specified color palettes instead of having Use the specified color palettes instead of having
.Nm .Nm
@@ -305,8 +313,10 @@ This is useful for example if the input image is a sheet of some sort, and you w
The default is to process the whole image as-is. The default is to process the whole image as-is.
.Pp .Pp
.Ar slice .Ar slice
must be two number pairs, separated by a colon. must be formatted as
The numbers must be separated by commas; space is allowed around all punctuation. .Ql Ar X , Ns Ar Y : Ns Ar W , Ns Ar H :
two comma-separated number pairs, separated by a colon.
Whitespace is allowed around all punctuation.
The first number pair specifies the X and Y coordinates of the top-left pixel that will be processed (anything above it or to its left will be ignored). The first number pair specifies the X and Y coordinates of the top-left pixel that will be processed (anything above it or to its left will be ignored).
The second number pair specifies how many tiles to process horizontally and vertically, respectively. The second number pair specifies how many tiles to process horizontally and vertically, respectively.
.Pp .Pp

View File

@@ -1,6 +1,6 @@
.\" SPDX-License-Identifier: MIT .\" SPDX-License-Identifier: MIT
.\" .\"
.Dd October 31, 2025 .Dd January 1, 2026
.Dt RGBLINK 1 .Dt RGBLINK 1
.Os .Os
.Sh NAME .Sh NAME

View File

@@ -1,6 +1,6 @@
.\" SPDX-License-Identifier: MIT .\" SPDX-License-Identifier: MIT
.\" .\"
.Dd October 31, 2025 .Dd January 1, 2026
.Dt RGBLINK 5 .Dt RGBLINK 5
.Os .Os
.Sh NAME .Sh NAME

View File

@@ -139,7 +139,7 @@ std::optional<std::string> act_ReadFile(std::string const &name, uint32_t maxLen
file = fopen(fullPath->c_str(), "rb"); file = fopen(fullPath->c_str(), "rb");
} }
if (!file) { if (!file) {
if (fstk_FileError(name, "READFILE")) { if (fstk_FileError(name, "`READFILE`")) {
// If `fstk_FileError` returned true due to `-MG`, we should abort due to a // If `fstk_FileError` returned true due to `-MG`, we should abort due to a
// missing file, so return `std::nullopt`, which tells the caller to `YYACCEPT` // missing file, so return `std::nullopt`, which tells the caller to `YYACCEPT`
return std::nullopt; return std::nullopt;

View File

@@ -57,44 +57,47 @@ static std::vector<std::string> includePaths = {""}; // -I
static std::deque<std::string> preIncludeNames; // -P static std::deque<std::string> preIncludeNames; // -P
static bool failedOnMissingInclude = false; static bool failedOnMissingInclude = false;
using TraceNode = std::pair<std::string, uint32_t>; void FileStackNode::printBacktrace(uint32_t curLineNo) const {
using TraceItem = std::pair<FileStackNode const *, uint32_t>;
static std::vector<TraceNode> backtrace(FileStackNode const &node, uint32_t curLineNo) { std::vector<TraceItem> items;
if (node.isQuiet && !tracing.loud) { for (TraceItem item{this, curLineNo};;) {
if (node.parent) { auto &[node, itemLineNo] = item;
bool loud = !node->isQuiet || tracing.loud;
if (loud) {
items.emplace_back(node, itemLineNo);
}
if (!node->parent) {
assume(node->type != NODE_REPT && std::holds_alternative<std::string>(node->data));
break;
}
if (loud || node->type != NODE_REPT) {
// Quiet REPT nodes will pass their interior line number up to their parent, // Quiet REPT nodes will pass their interior line number up to their parent,
// which is more precise than the parent's own line number (since that will be // which is more precise than the parent's own line number (since that will be
// the line number of the "REPT?" or "FOR?" itself). // the line number of the "REPT?" or "FOR?" itself).
return backtrace(*node.parent, node.type == NODE_REPT ? curLineNo : node.lineNo); itemLineNo = node->lineNo;
} }
return {}; // LCOV_EXCL_LINE node = &*node->parent;
} }
if (!node.parent) { using TraceNode = std::pair<std::string, uint32_t>;
assume(node.type != NODE_REPT && std::holds_alternative<std::string>(node.data)); std::vector<TraceNode> traceNodes;
return { traceNodes.reserve(items.size());
{node.name(), curLineNo} for (auto &[node, itemLineNo] : reversed(items)) {
}; if (std::holds_alternative<std::vector<uint32_t>>(node->data)) {
} assume(!traceNodes.empty()); // REPT nodes use their parent's name
std::string reptName = traceNodes.back().first;
std::vector<TraceNode> traceNodes = backtrace(*node.parent, node.lineNo); if (std::vector<uint32_t> const &nodeIters = node->iters(); !nodeIters.empty()) {
if (std::holds_alternative<std::vector<uint32_t>>(node.data)) { reptName.append(NODE_SEPARATOR REPT_NODE_PREFIX);
assume(!traceNodes.empty()); // REPT nodes use their parent's name reptName.append(std::to_string(nodeIters.front()));
std::string reptName = traceNodes.back().first; }
if (std::vector<uint32_t> const &nodeIters = node.iters(); !nodeIters.empty()) { traceNodes.emplace_back(reptName, itemLineNo);
reptName.append(NODE_SEPARATOR REPT_NODE_PREFIX); } else {
reptName.append(std::to_string(nodeIters.front())); traceNodes.emplace_back(node->name(), itemLineNo);
} }
traceNodes.emplace_back(reptName, curLineNo);
} else {
traceNodes.emplace_back(node.name(), curLineNo);
} }
return traceNodes;
}
void FileStackNode::printBacktrace(uint32_t curLineNo) const {
trace_PrintBacktrace( trace_PrintBacktrace(
backtrace(*this, curLineNo), traceNodes,
[](TraceNode const &node) { return node.first.c_str(); }, [](TraceNode const &node) { return node.first.c_str(); },
[](TraceNode const &node) { return node.second; } [](TraceNode const &node) { return node.second; }
); );
@@ -188,10 +191,14 @@ std::optional<std::string> fstk_FindFile(std::string const &path) {
} }
} }
errno = ENOENT;
if (options.missingIncludeState != INC_ERROR) { if (options.missingIncludeState != INC_ERROR) {
printDep(path); printDep(path);
} }
// Set `errno` as if `fopen` had failed on a nonexistent file.
// This allows a subsequent `fstk_FileError` to report correctly with `strerror`.
errno = ENOENT;
return std::nullopt; return std::nullopt;
} }
@@ -348,17 +355,17 @@ static Context &
return context; return context;
} }
bool fstk_FileError(std::string const &path, char const *functionName) { bool fstk_FileError(std::string const &path, char const *description) {
if (options.missingIncludeState == INC_ERROR) { if (options.missingIncludeState == INC_ERROR) {
error("Error opening `%s` file \"%s\": %s", functionName, path.c_str(), strerror(errno)); error("Error opening %s file \"%s\": %s", description, path.c_str(), strerror(errno));
} else { } else {
failedOnMissingInclude = true; failedOnMissingInclude = true;
// LCOV_EXCL_START // LCOV_EXCL_START
if (options.missingIncludeState == GEN_EXIT) { if (options.missingIncludeState == GEN_EXIT) {
verbosePrint( verbosePrint(
VERB_NOTICE, VERB_NOTICE,
"Aborting due to '-MG' on `%s` file \"%s\": %s\n", "Aborting due to '-MG' on %s file \"%s\": %s\n",
functionName, description,
path.c_str(), path.c_str(),
strerror(errno) strerror(errno)
); );
@@ -379,7 +386,7 @@ bool fstk_RunInclude(std::string const &path, bool isQuiet) {
newFileContext(*fullPath, isQuiet, false); newFileContext(*fullPath, isQuiet, false);
return false; return false;
} }
return fstk_FileError(path, "INCLUDE"); return fstk_FileError(path, "`INCLUDE`");
} }
void fstk_RunMacro( void fstk_RunMacro(
@@ -487,14 +494,16 @@ void fstk_NewRecursionDepth(size_t newDepth) {
options.maxRecursionDepth = newDepth; options.maxRecursionDepth = newDepth;
} }
void fstk_Init(std::string const &mainPath) { bool fstk_Init(std::string const &mainPath) {
newFileContext(mainPath, false, true); newFileContext(mainPath, false, true);
for (std::string const &name : preIncludeNames) { for (std::string const &name : preIncludeNames) {
if (std::optional<std::string> fullPath = fstk_FindFile(name); fullPath) { if (std::optional<std::string> fullPath = fstk_FindFile(name); fullPath) {
newFileContext(*fullPath, false, false); newFileContext(*fullPath, false, false);
} else { } else if (fstk_FileError(name, "pre-included")) {
error("Error reading pre-included file \"%s\": %s", name.c_str(), strerror(errno)); return false;
} }
} }
return true;
} }

View File

@@ -1295,7 +1295,7 @@ static Token readIdentifier(char firstChar, bool raw) {
} }
// Label scopes `.` and `..` are the only nonlocal identifiers that start with a dot // Label scopes `.` and `..` are the only nonlocal identifiers that start with a dot
if (identifier.find_first_not_of('.') == identifier.npos) { if (sym_IsDotScope(identifier)) {
tokenType = T_(SYMBOL); tokenType = T_(SYMBOL);
} }
@@ -1636,6 +1636,10 @@ static Token yylex_SKIP_TO_ENDC(); // Forward declaration for `yylex_NORMAL`
// Must stay in sync with the `switch` in `yylex_NORMAL`! // Must stay in sync with the `switch` in `yylex_NORMAL`!
static bool isGarbageCharacter(int c) { static bool isGarbageCharacter(int c) {
// EOF is not garbage (it can't be reported anyway)
if (c == EOF) {
return false;
}
// Whitespace characters are not garbage, even the non-"printable" ones // Whitespace characters are not garbage, even the non-"printable" ones
if (isWhitespace(c)) { if (isWhitespace(c)) {
return false; return false;
@@ -1645,7 +1649,7 @@ static bool isGarbageCharacter(int c) {
return true; return true;
} }
// All other printable characters are not garbage (i.e. `yylex_NORMAL` handles them), and // All other printable characters are not garbage (i.e. `yylex_NORMAL` handles them), and
// all other nonprintable characters are garbage (including '\0' and EOF) // all other nonprintable characters are garbage (including '\0')
return !isPrintable(c); return !isPrintable(c);
} }
@@ -1935,11 +1939,12 @@ static Token yylex_NORMAL() {
// `token` is either a `SYMBOL` or a `LOCAL`, and both have a `std::string` value. // `token` is either a `SYMBOL` or a `LOCAL`, and both have a `std::string` value.
assume(std::holds_alternative<std::string>(token.value)); assume(std::holds_alternative<std::string>(token.value));
std::string const &identifier = std::get<std::string>(token.value);
// Raw symbols and local symbols cannot be string expansions // Raw symbols and local symbols cannot be string expansions
if (!raw && token.type == T_(SYMBOL) && lexerState->expandStrings) { if (!raw && token.type == T_(SYMBOL) && lexerState->expandStrings) {
// Attempt string expansion // Attempt string expansion
if (Symbol const *sym = sym_FindExactSymbol(std::get<std::string>(token.value)); if (Symbol const *sym = sym_FindExactSymbol(identifier);
sym && sym->type == SYM_EQUS) { sym && sym->type == SYM_EQUS) {
beginExpansion(sym->getEqus(), sym->name); beginExpansion(sym->getEqus(), sym->name);
continue; // Restart, reading from the new buffer continue; // Restart, reading from the new buffer
@@ -1950,6 +1955,7 @@ static Token yylex_NORMAL() {
// - label definitions (which are followed by a ':' and use the token `LABEL`) // - label definitions (which are followed by a ':' and use the token `LABEL`)
// - quiet macro invocations (which are followed by a '?' and use the token `QMACRO`) // - quiet macro invocations (which are followed by a '?' and use the token `QMACRO`)
// - regular macro invocations (which use the token `SYMBOL`) // - regular macro invocations (which use the token `SYMBOL`)
// - label scopes "." and ".." (which use the token `SYMBOL` no matter what)
// //
// If we had one `IDENTIFIER` token, the parser would need to perform "lookahead" to // If we had one `IDENTIFIER` token, the parser would need to perform "lookahead" to
// determine which rule applies. But since macros need to enter "raw" mode to parse // determine which rule applies. But since macros need to enter "raw" mode to parse
@@ -1960,7 +1966,7 @@ static Token yylex_NORMAL() {
// one to lex depending on the character *immediately* following the identifier. // one to lex depending on the character *immediately* following the identifier.
// Thus "name:" is a label definition, and "name?" is a quiet macro invocation, but // Thus "name:" is a label definition, and "name?" is a quiet macro invocation, but
// "name :" and "name ?" and just "name" are all regular macro invocations. // "name :" and "name ?" and just "name" are all regular macro invocations.
if (token.type == T_(SYMBOL)) { if (token.type == T_(SYMBOL) && !sym_IsDotScope(identifier)) {
c = peek(); c = peek();
token.type = c == ':' ? T_(LABEL) : c == '?' ? T_(QMACRO) : T_(SYMBOL); token.type = c == ':' ? T_(LABEL) : c == '?' ? T_(QMACRO) : T_(SYMBOL);
} }

View File

@@ -28,7 +28,6 @@
#include "usage.hpp" #include "usage.hpp"
#include "util.hpp" // UpperMap #include "util.hpp" // UpperMap
#include "verbosity.hpp" #include "verbosity.hpp"
#include "version.hpp"
#include "asm/charmap.hpp" #include "asm/charmap.hpp"
#include "asm/fstack.hpp" #include "asm/fstack.hpp"
@@ -296,7 +295,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()); usage.printVersion(false);
exit(0); exit(0);
case 'v': case 'v':
@@ -381,7 +380,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()); usage.printVersion(true);
printVVVVVVerbosity(); printVVVVVVerbosity();
@@ -491,7 +490,7 @@ static void verboseOutputConfig() {
fputs("\tGenerate phony dependencies\n", stderr); fputs("\tGenerate phony dependencies\n", stderr);
} }
} }
fputs("Ready.\n", stderr); fputs("Ready for assembly\n", stderr);
style_Reset(stderr); style_Reset(stderr);
} }
@@ -559,11 +558,8 @@ int main(int argc, char *argv[]) {
charmap_New(DEFAULT_CHARMAP_NAME, nullptr); charmap_New(DEFAULT_CHARMAP_NAME, nullptr);
// Init lexer and file stack, providing file info // Init lexer and file stack, and parse (`yy::parser` is auto-generated from `parser.y`)
fstk_Init(*localOptions.inputFileName); if (yy::parser parser; fstk_Init(*localOptions.inputFileName) && parser.parse() != 0) {
// Perform parse (`yy::parser` is auto-generated from `parser.y`)
if (yy::parser parser; parser.parse() != 0) {
// Exited due to YYABORT or YYNOMEM // Exited due to YYABORT or YYNOMEM
fatal("Unrecoverable error while parsing"); // LCOV_EXCL_LINE fatal("Unrecoverable error while parsing"); // LCOV_EXCL_LINE
} }

View File

@@ -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 &sect, SectionType type, uint32_t org, uint8_t alignment, uint16_t alignOffset mergeSectUnion(Section &sect, 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");
} }
@@ -932,7 +929,7 @@ bool sect_BinaryFile(std::string const &name, uint32_t startPos) {
file = fopen(fullPath->c_str(), "rb"); file = fopen(fullPath->c_str(), "rb");
} }
if (!file) { if (!file) {
return fstk_FileError(name, "INCBIN"); return fstk_FileError(name, "`INCBIN`");
} }
Defer closeFile{[&] { fclose(file); }}; Defer closeFile{[&] { fclose(file); }};
@@ -987,7 +984,7 @@ bool sect_BinaryFileSlice(std::string const &name, uint32_t startPos, uint32_t l
file = fopen(fullPath->c_str(), "rb"); file = fopen(fullPath->c_str(), "rb");
} }
if (!file) { if (!file) {
return fstk_FileError(name, "INCBIN"); return fstk_FileError(name, "`INCBIN`");
} }
Defer closeFile{[&] { fclose(file); }}; Defer closeFile{[&] { fclose(file); }};
@@ -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;

View File

@@ -53,6 +53,12 @@ bool sym_IsPC(Symbol const *sym) {
return sym == PCSymbol; return sym == PCSymbol;
} }
bool sym_IsDotScope(std::string const &symName) {
// Label scopes `.` and `..` are the only nonlocal identifiers that start with a dot.
// Three or more dots are considered a nonsensical local label.
return symName == "." || symName == "..";
}
void sym_ForEach(void (*callback)(Symbol &)) { void sym_ForEach(void (*callback)(Symbol &)) {
for (auto &it : symbols) { for (auto &it : symbols) {
callback(it.second); callback(it.second);
@@ -215,8 +221,8 @@ static void redefinedError(Symbol const &sym) {
static void assumeAlreadyExpanded(std::string const &symName) { static void assumeAlreadyExpanded(std::string const &symName) {
// Either the symbol name is `Global.local` or entirely '.'s (for scopes `.` and `..`), // Either the symbol name is `Global.local` or entirely '.'s (for scopes `.` and `..`),
// but cannot be unqualified `.local` // but cannot be unqualified `.local` or more than two '.'s
assume(!symName.starts_with('.') || symName.find_first_not_of('.') == symName.npos); assume(!symName.starts_with('.') || sym_IsDotScope(symName));
} }
static Symbol &createSymbol(std::string const &symName) { static Symbol &createSymbol(std::string const &symName) {
@@ -253,7 +259,7 @@ static bool isAutoScoped(std::string const &symName) {
} }
// Label scopes `.` and `..` are the only nonlocal identifiers that start with a dot // Label scopes `.` and `..` are the only nonlocal identifiers that start with a dot
if (dotPos == 0 && symName.find_first_not_of('.') == symName.npos) { if (sym_IsDotScope(symName)) {
return false; return false;
} }
@@ -581,7 +587,7 @@ static uint32_t anonLabelID = 0;
Symbol *sym_AddAnonLabel() { Symbol *sym_AddAnonLabel() {
if (anonLabelID == UINT32_MAX) { if (anonLabelID == UINT32_MAX) {
// LCOV_EXCL_START // LCOV_EXCL_START
error("Only %" PRIu32 " anonymous labels can be created!", anonLabelID); error("Only %" PRIu32 " anonymous labels can be created", anonLabelID);
return nullptr; return nullptr;
// LCOV_EXCL_STOP // LCOV_EXCL_STOP
} }

View File

@@ -90,7 +90,7 @@ static void incrementErrors() {
style_Set(stderr, STYLE_RED, true); style_Set(stderr, STYLE_RED, true);
fprintf( fprintf(
stderr, stderr,
"Assembly aborted after the maximum of %" PRIu64 " error%s!", "Assembly aborted after the maximum of %" PRIu64 " error%s",
warnings.nbErrors, warnings.nbErrors,
warnings.nbErrors == 1 ? "" : "s" warnings.nbErrors == 1 ? "" : "s"
); );
@@ -136,7 +136,7 @@ void requireZeroErrors() {
style_Set(stderr, STYLE_RED, true); style_Set(stderr, STYLE_RED, true);
fprintf( fprintf(
stderr, stderr,
"Assembly aborted with %" PRIu64 " error%s!\n", "Assembly aborted with %" PRIu64 " error%s\n",
warnings.nbErrors, warnings.nbErrors,
warnings.nbErrors == 1 ? "" : "s" warnings.nbErrors == 1 ? "" : "s"
); );

View File

@@ -1,3 +1,5 @@
// SPDX-License-Identifier: MIT
#include "cli.hpp" #include "cli.hpp"
#include <errno.h> #include <errno.h>
@@ -21,10 +23,11 @@ static std::vector<size_t>
std::filebuf file; std::filebuf file;
if (!file.open(path, std::ios_base::in)) { if (!file.open(path, std::ios_base::in)) {
int errnum = errno;
style_Set(stderr, STYLE_RED, true); style_Set(stderr, STYLE_RED, true);
fputs("FATAL: ", stderr); fputs("FATAL: ", stderr);
style_Reset(stderr); style_Reset(stderr);
fprintf(stderr, "Failed to open at-file \"%s\": %s\n", path.c_str(), strerror(errno)); fprintf(stderr, "Failed to open at-file \"%s\": %s\n", path.c_str(), strerror(errnum));
usage.printAndExit(1); usage.printAndExit(1);
} }

View File

@@ -19,7 +19,6 @@
#include "style.hpp" #include "style.hpp"
#include "usage.hpp" #include "usage.hpp"
#include "util.hpp" #include "util.hpp"
#include "version.hpp"
#include "fix/fix.hpp" #include "fix/fix.hpp"
#include "fix/mbc.hpp" #include "fix/mbc.hpp"
@@ -252,7 +251,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()); usage.printVersion(false);
exit(0); exit(0);
case 'v': case 'v':

View File

@@ -23,7 +23,6 @@
#include "usage.hpp" #include "usage.hpp"
#include "util.hpp" #include "util.hpp"
#include "verbosity.hpp" #include "verbosity.hpp"
#include "version.hpp"
#include "gfx/pal_spec.hpp" #include "gfx/pal_spec.hpp"
#include "gfx/process.hpp" #include "gfx/process.hpp"
@@ -127,7 +126,7 @@ static uint16_t readNumber(char const *&str, char const *errPrefix, uint16_t err
error("%s: expected number, but found nothing", errPrefix); error("%s: expected number, but found nothing", errPrefix);
return errVal; return errVal;
} else if (*number > UINT16_MAX) { } else if (*number > UINT16_MAX) {
error("%s: the number is too large!", errPrefix); error("%s: the number is too large", errPrefix);
return errVal; return errVal;
} else { } else {
return *number; return *number;
@@ -240,7 +239,7 @@ static void parseArg(int ch, char *arg) {
case 'L': case 'L':
options.inputSlice.left = readNumber(argPtr, "Input slice left coordinate"); options.inputSlice.left = readNumber(argPtr, "Input slice left coordinate");
if (options.inputSlice.left > INT16_MAX) { if (options.inputSlice.left > INT16_MAX) {
error("Input slice left coordinate is out of range!"); error("Input slice left coordinate is out of range");
break; break;
} }
skipBlankSpace(argPtr); skipBlankSpace(argPtr);
@@ -261,7 +260,7 @@ static void parseArg(int ch, char *arg) {
options.inputSlice.width = readNumber(argPtr, "Input slice width"); options.inputSlice.width = readNumber(argPtr, "Input slice width");
skipBlankSpace(argPtr); skipBlankSpace(argPtr);
if (options.inputSlice.width == 0) { if (options.inputSlice.width == 0) {
error("Input slice width may not be 0!"); error("Input slice width may not be 0");
} }
if (*argPtr != ',') { if (*argPtr != ',') {
error("Missing comma after width in \"%s\"", arg); error("Missing comma after width in \"%s\"", arg);
@@ -271,7 +270,7 @@ static void parseArg(int ch, char *arg) {
skipBlankSpace(argPtr); skipBlankSpace(argPtr);
options.inputSlice.height = readNumber(argPtr, "Input slice height"); options.inputSlice.height = readNumber(argPtr, "Input slice height");
if (options.inputSlice.height == 0) { if (options.inputSlice.height == 0) {
error("Input slice height may not be 0!"); error("Input slice height may not be 0");
} }
if (*argPtr != '\0') { if (*argPtr != '\0') {
error("Unexpected extra characters after slice spec in \"%s\"", arg); error("Unexpected extra characters after slice spec in \"%s\"", arg);
@@ -331,9 +330,9 @@ static void parseArg(int ch, char *arg) {
error("Number of palettes ('-n') must be a valid number, not \"%s\"", arg); error("Number of palettes ('-n') must be a valid number, not \"%s\"", arg);
} }
if (number > 256) { if (number > 256) {
error("Number of palettes ('-n') must not exceed 256!"); error("Number of palettes ('-n') must not exceed 256");
} else if (number == 0) { } else if (number == 0) {
error("Number of palettes ('-n') may not be 0!"); error("Number of palettes ('-n') may not be 0");
} else { } else {
options.nbPalettes = number; options.nbPalettes = number;
} }
@@ -389,9 +388,9 @@ static void parseArg(int ch, char *arg) {
error("Palette size ('-s') must be a valid number, not \"%s\"", arg); error("Palette size ('-s') must be a valid number, not \"%s\"", arg);
} }
if (options.nbColorsPerPal > 4) { if (options.nbColorsPerPal > 4) {
error("Palette size ('-s') must not exceed 4!"); error("Palette size ('-s') must not exceed 4");
} else if (options.nbColorsPerPal == 0) { } else if (options.nbColorsPerPal == 0) {
error("Palette size ('-s') may not be 0!"); error("Palette size ('-s') may not be 0");
} }
break; break;
@@ -409,7 +408,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()); usage.printVersion(false);
exit(0); exit(0);
case 'v': case 'v':
@@ -481,7 +480,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()); usage.printVersion(true);
printVVVVVVerbosity(); printVVVVVVerbosity();
@@ -609,7 +608,7 @@ static void verboseOutputConfig() {
if (localOptions.reverse) { if (localOptions.reverse) {
fprintf(stderr, "\tReverse image width: %" PRIu16 " tiles\n", options.reversedWidth); fprintf(stderr, "\tReverse image width: %" PRIu16 " tiles\n", options.reversedWidth);
} }
fputs("Ready.\n", stderr); fputs("Ready for conversion\n", stderr);
style_Reset(stderr); style_Reset(stderr);
} }

View File

@@ -68,7 +68,7 @@ public:
size_t size() const { size_t size() const {
return std::count_if(RANGE(_colors), [](std::optional<Rgba> const &slot) { return std::count_if(RANGE(_colors), [](std::optional<Rgba> const &slot) {
return slot.has_value() && !slot->isTransparent(); return slot.has_value() && slot->isOpaque();
}); });
} }
decltype(_colors) const &raw() const { return _colors; } decltype(_colors) const &raw() const { return _colors; }
@@ -84,7 +84,13 @@ struct Image {
Rgba &pixel(uint32_t x, uint32_t y) { return png.pixels[y * png.width + x]; } Rgba &pixel(uint32_t x, uint32_t y) { return png.pixels[y * png.width + x]; }
Rgba const &pixel(uint32_t x, uint32_t y) const { return png.pixels[y * png.width + x]; } Rgba const &pixel(uint32_t x, uint32_t y) const { return png.pixels[y * png.width + x]; }
bool isSuitableForGrayscale() const { enum GrayscaleResult {
GRAY_OK,
GRAY_TOO_MANY,
GRAY_NONGRAY,
GRAY_CONFLICT,
};
std::pair<GrayscaleResult, std::optional<Rgba>> isSuitableForGrayscale() const {
// Check that all of the grays don't fall into the same "bin" // Check that all of the grays don't fall into the same "bin"
if (colors.size() > options.maxOpaqueColors()) { // Apply the Pigeonhole Principle if (colors.size() > options.maxOpaqueColors()) { // Apply the Pigeonhole Principle
verbosePrint( verbosePrint(
@@ -93,7 +99,7 @@ struct Image {
colors.size(), colors.size(),
options.maxOpaqueColors() options.maxOpaqueColors()
); );
return false; return {GrayscaleResult::GRAY_TOO_MANY, std::nullopt};
} }
uint8_t bins = 0; uint8_t bins = 0;
for (std::optional<Rgba> const &color : colors) { for (std::optional<Rgba> const &color : colors) {
@@ -106,7 +112,7 @@ struct Image {
"Found non-gray color #%08x, not using grayscale sorting\n", "Found non-gray color #%08x, not using grayscale sorting\n",
color->toCSS() color->toCSS()
); );
return false; return {GrayscaleResult::GRAY_NONGRAY, color};
} }
uint8_t mask = 1 << color->grayIndex(); uint8_t mask = 1 << color->grayIndex();
if (bins & mask) { // Two in the same bin! if (bins & mask) { // Two in the same bin!
@@ -115,11 +121,11 @@ struct Image {
"Color #%08x conflicts with another one, not using grayscale sorting\n", "Color #%08x conflicts with another one, not using grayscale sorting\n",
color->toCSS() color->toCSS()
); );
return false; return {GrayscaleResult::GRAY_CONFLICT, color};
} }
bins |= mask; bins |= mask;
} }
return true; return {GrayscaleResult::GRAY_OK, std::nullopt};
} }
explicit Image(std::string const &path) { explicit Image(std::string const &path) {
@@ -132,15 +138,15 @@ struct Image {
// Validate input slice // Validate input slice
if (options.inputSlice.width == 0 && png.width % 8 != 0) { if (options.inputSlice.width == 0 && png.width % 8 != 0) {
fatal("Image width (%" PRIu32 " pixels) is not a multiple of 8!", png.width); fatal("Image width (%" PRIu32 " pixels) is not a multiple of 8", png.width);
} }
if (options.inputSlice.height == 0 && png.height % 8 != 0) { if (options.inputSlice.height == 0 && png.height % 8 != 0) {
fatal("Image height (%" PRIu32 " pixels) is not a multiple of 8!", png.height); fatal("Image height (%" PRIu32 " pixels) is not a multiple of 8", png.height);
} }
if (options.inputSlice.right() > png.width || options.inputSlice.bottom() > png.height) { if (options.inputSlice.right() > png.width || options.inputSlice.bottom() > png.height) {
error( error(
"Image slice ((%" PRIu16 ", %" PRIu16 ") to (%" PRIu32 ", %" PRIu32 "Image slice ((%" PRIu16 ", %" PRIu16 ") to (%" PRIu32 ", %" PRIu32
")) is outside the image bounds (%" PRIu32 "x%" PRIu32 ")!", ")) is outside the image bounds (%" PRIu32 "x%" PRIu32 ")",
options.inputSlice.left, options.inputSlice.left,
options.inputSlice.top, options.inputSlice.top,
options.inputSlice.right(), options.inputSlice.right(),
@@ -151,8 +157,8 @@ struct Image {
if (options.inputSlice.width % 8 == 0 && options.inputSlice.height % 8 == 0) { if (options.inputSlice.width % 8 == 0 && options.inputSlice.height % 8 == 0) {
fprintf( fprintf(
stderr, stderr,
"note: Did you mean the slice \"%" PRIu32 ",%" PRIu32 ":%" PRId32 ",%" PRId32 " (Did you mean the slice \"%" PRIu32 ",%" PRIu32 ":%" PRId32 ",%" PRId32
"\"? (width and height are in tiles, not pixels!)\n", "\"? The width and height are in tiles, not pixels!)\n",
options.inputSlice.left, options.inputSlice.left,
options.inputSlice.top, options.inputSlice.top,
options.inputSlice.width / 8, options.inputSlice.width / 8,
@@ -181,7 +187,7 @@ struct Image {
if (uint32_t css = color.toCSS(); ambiguous.find(css) == ambiguous.end()) { if (uint32_t css = color.toCSS(); ambiguous.find(css) == ambiguous.end()) {
error( error(
"Color #%08x is neither transparent (alpha < %u) nor opaque (alpha >= " "Color #%08x is neither transparent (alpha < %u) nor opaque (alpha >= "
"%u) [first seen at x: %" PRIu32 ", y: %" PRIu32 "]", "%u) (first seen at (%" PRIu32 ", %" PRIu32 "))",
css, css,
Rgba::transparency_threshold, Rgba::transparency_threshold,
Rgba::opacity_threshold, Rgba::opacity_threshold,
@@ -195,8 +201,8 @@ struct Image {
if (std::pair fused{color.toCSS(), other->toCSS()}; if (std::pair fused{color.toCSS(), other->toCSS()};
fusions.find(fused) == fusions.end()) { fusions.find(fused) == fusions.end()) {
warnx( warnx(
"Fusing colors #%08x and #%08x into Game Boy color $%04x [first seen " "Colors #%08x and #%08x both reduce to the same Game Boy color $%04x "
"at x: %" PRIu32 ", y: %" PRIu32 "]", "(first seen at (%" PRIu32 ", %" PRIu32 "))",
fused.first, fused.first,
fused.second, fused.second,
color.cgbColor(), color.cgbColor(),
@@ -319,7 +325,7 @@ static void generatePalSpec(Image const &image) {
// Generate a palette spec from the first few colors in the embedded palette // Generate a palette spec from the first few colors in the embedded palette
std::vector<Rgba> const &embPal = image.png.palette; std::vector<Rgba> const &embPal = image.png.palette;
if (embPal.empty()) { if (embPal.empty()) {
fatal("\"-c embedded\" was given, but the PNG does not have an embedded palette!"); fatal("\"-c embedded\" was given, but the PNG does not have an embedded palette");
} }
// Ignore extraneous colors if they are unused // Ignore extraneous colors if they are unused
@@ -381,7 +387,7 @@ static std::pair<std::vector<size_t>, std::vector<Palette>>
"Sorting palette colors by PNG's embedded PLTE chunk without '-c/--colors embedded'" "Sorting palette colors by PNG's embedded PLTE chunk without '-c/--colors embedded'"
); );
sortIndexed(palettes, image.png.palette); sortIndexed(palettes, image.png.palette);
} else if (image.isSuitableForGrayscale()) { } else if (image.isSuitableForGrayscale().first == Image::GRAY_OK) {
sortGrayscale(palettes, image.colors.raw()); sortGrayscale(palettes, image.colors.raw());
} else { } else {
sortRgb(palettes); sortRgb(palettes);
@@ -396,7 +402,7 @@ static std::pair<std::vector<size_t>, std::vector<Palette>>
for (auto [spec, pal] : zip(options.palSpec, palettes)) { for (auto [spec, pal] : zip(options.palSpec, palettes)) {
for (size_t i = 0; i < options.nbColorsPerPal; ++i) { for (size_t i = 0; i < options.nbColorsPerPal; ++i) {
// If the spec has a gap, there's no need to copy anything. // If the spec has a gap, there's no need to copy anything.
if (spec[i].has_value() && !spec[i]->isTransparent()) { if (spec[i].has_value() && spec[i]->isOpaque()) {
pal[i] = spec[i]->cgbColor(); pal[i] = spec[i]->cgbColor();
} }
} }
@@ -821,7 +827,7 @@ static UniqueTiles dedupTiles(
if (inputWithoutOutput && matchType == TileData::NOPE) { if (inputWithoutOutput && matchType == TileData::NOPE) {
error( error(
"Tile at (%" PRIu32 ", %" PRIu32 "Tile at (%" PRIu32 ", %" PRIu32
") is not within the input tileset, and '-o' was not given!", ") is not within the input tileset, and '-o' was not given",
tile.x, tile.x,
tile.y tile.y
); );
@@ -939,14 +945,24 @@ void process() {
// LCOV_EXCL_STOP // LCOV_EXCL_STOP
if (options.palSpecType == Options::DMG) { if (options.palSpecType == Options::DMG) {
char const *prefix =
"Image is not compatible with a DMG palette specification: it contains";
if (options.hasTransparentPixels) { if (options.hasTransparentPixels) {
fatal( fatal("%s transparent pixels", prefix);
"Image contains transparent pixels, not compatible with a DMG palette specification"
);
} }
if (!image.isSuitableForGrayscale()) { switch (auto const [result, color] = image.isSuitableForGrayscale(); result) {
fatal("Image contains too many or non-gray colors, not compatible with a DMG palette " case Image::GRAY_OK:
"specification"); break;
case Image::GRAY_TOO_MANY:
fatal("%s too many colors (%zu)", prefix, image.colors.size());
case Image::GRAY_NONGRAY:
fatal("%s a non-gray color #%08x", prefix, color->toCSS());
case Image::GRAY_CONFLICT:
fatal(
"%s a color #%08x that reduces to the same gray shade as another one",
prefix,
color->toCSS()
);
} }
} }
@@ -965,7 +981,7 @@ void process() {
for (uint32_t y = 0; y < 8; ++y) { for (uint32_t y = 0; y < 8; ++y) {
for (uint32_t x = 0; x < 8; ++x) { for (uint32_t x = 0; x < 8; ++x) {
if (Rgba color = tile.pixel(x, y); if (Rgba color = tile.pixel(x, y);
!color.isTransparent() || !options.hasTransparentPixels) { color.isOpaque() || !options.hasTransparentPixels) {
tileColors.insert(color.cgbColor()); tileColors.insert(color.cgbColor());
} }
} }
@@ -973,7 +989,7 @@ void process() {
if (tileColors.size() > options.maxOpaqueColors()) { if (tileColors.size() > options.maxOpaqueColors()) {
fatal( fatal(
"Tile at (%" PRIu32 ", %" PRIu32 ") has %zu colors, more than %" PRIu8 "!", "Tile at (%" PRIu32 ", %" PRIu32 ") has %zu colors, more than %" PRIu8,
tile.x, tile.x,
tile.y, tile.y,
tileColors.size(), tileColors.size(),
@@ -1001,7 +1017,7 @@ void process() {
continue; continue;
} }
fatal( fatal(
"Tile (%" PRIu32 ", %" PRIu32 ") contains the background color (#%08x)!", "Tile (%" PRIu32 ", %" PRIu32 ") contains the background color (#%08x)",
tile.x, tile.x,
tile.y, tile.y,
options.bgColor->toCSS() options.bgColor->toCSS()

View File

@@ -112,15 +112,15 @@ void reverse() {
// Check for weird flag combinations // Check for weird flag combinations
if (options.output.empty()) { if (options.output.empty()) {
fatal("Tile data must be provided when reversing an image!"); fatal("Tile data must be provided when reversing an image");
} }
if (options.allowDedup && options.tilemap.empty()) { if (options.allowDedup && options.tilemap.empty()) {
warnx("Tile deduplication is enabled, but no tilemap is provided?"); warnx("Tile deduplication is enabled, but no tilemap is provided");
} }
if (options.useColorCurve) { if (options.useColorCurve) {
warnx("The color curve is not yet supported in reverse mode..."); warnx("The color curve is not yet supported in reverse mode");
} }
if (options.inputSlice.left != 0 || options.inputSlice.top != 0 if (options.inputSlice.left != 0 || options.inputSlice.top != 0
@@ -149,13 +149,13 @@ void reverse() {
// By default, assume tiles are not deduplicated, and add the (allegedly) trimmed tiles // By default, assume tiles are not deduplicated, and add the (allegedly) trimmed tiles
size_t const nbTiles = tiles.size() / tileSize; size_t const nbTiles = tiles.size() / tileSize;
verbosePrint(VERB_INFO, "Read %zu tiles.\n", nbTiles); verbosePrint(VERB_INFO, "Read %zu tiles\n", nbTiles);
size_t mapSize = nbTiles + options.trim; // Image size in tiles size_t mapSize = nbTiles + options.trim; // Image size in tiles
std::optional<std::vector<uint8_t>> tilemap; std::optional<std::vector<uint8_t>> tilemap;
if (!options.tilemap.empty()) { if (!options.tilemap.empty()) {
tilemap = readInto(options.tilemap); tilemap = readInto(options.tilemap);
mapSize = tilemap->size(); mapSize = tilemap->size();
verbosePrint(VERB_INFO, "Read %zu tilemap entries.\n", mapSize); verbosePrint(VERB_INFO, "Read %zu tilemap entries\n", mapSize);
} }
if (mapSize == 0) { if (mapSize == 0) {
@@ -224,7 +224,7 @@ void reverse() {
break; break;
} else if (nbRead != buf.size()) { } else if (nbRead != buf.size()) {
fatal( fatal(
"Palette data size (%zu) is not a multiple of %zu bytes!\n", "Palette data size (%zu) is not a multiple of %zu bytes\n",
palettes.size() * buf.size() + nbRead, palettes.size() * buf.size() + nbRead,
buf.size() buf.size()
); );
@@ -250,7 +250,7 @@ void reverse() {
} }
if (options.palSpecType == Options::EXPLICIT && palettes != options.palSpec) { if (options.palSpecType == Options::EXPLICIT && palettes != options.palSpec) {
warnx("Colors in the palette file do not match those specified with '-c'!"); warnx("Colors in the palette file do not match those specified with '-c'");
// This spacing aligns "...versus with `-c`" above the column of `-c` palettes // This spacing aligns "...versus with `-c`" above the column of `-c` palettes
fputs("Colors specified in the palette file: ...versus with '-c':\n", stderr); fputs("Colors specified in the palette file: ...versus with '-c':\n", stderr);
for (size_t i = 0; i < palettes.size() && i < options.palSpec.size(); ++i) { for (size_t i = 0; i < palettes.size() && i < options.palSpec.size(); ++i) {
@@ -272,7 +272,7 @@ void reverse() {
} }
} else if (options.palSpecType == Options::EMBEDDED) { } else if (options.palSpecType == Options::EMBEDDED) {
warnx("An embedded palette was requested, but no palette file was specified; ignoring " warnx("An embedded palette was requested, but no palette file was specified; ignoring "
"request."); "request");
} else if (options.palSpecType == Options::EXPLICIT) { } else if (options.palSpecType == Options::EXPLICIT) {
palettes = std::move(options.palSpec); // We won't be using it again. palettes = std::move(options.palSpec); // We won't be using it again.
} }
@@ -299,7 +299,8 @@ void reverse() {
if (uint8_t palID = (attr & 0b111) - options.basePalID; palID > palettes.size()) { if (uint8_t palID = (attr & 0b111) - options.basePalID; palID > palettes.size()) {
error( error(
"Attribute map references palette #%u at (%zu, %zu), but there are only %zu!", "Attribute map references palette #%u at (%zu, %zu), but there are only %zu "
"palettes",
palID, palID,
tx, tx,
ty, ty,

View File

@@ -36,19 +36,6 @@ struct FreeSpace {
// Table of free space for each bank // Table of free space for each bank
static std::vector<std::deque<FreeSpace>> memory[SECTTYPE_INVALID]; static std::vector<std::deque<FreeSpace>> memory[SECTTYPE_INVALID];
// Init the free space-modelling structs
static void initFreeSpace() {
for (SectionType type : EnumSeq(SECTTYPE_INVALID)) {
memory[type].resize(sectTypeBanks(type));
for (std::deque<FreeSpace> &bankMem : memory[type]) {
bankMem.push_back({
.address = sectionTypeInfo[type].startAddr,
.size = sectionTypeInfo[type].size,
});
}
}
}
// Assigns a section to a given memory location // Assigns a section to a given memory location
static void assignSection(Section &section, MemoryLocation const &location) { static void assignSection(Section &section, MemoryLocation const &location) {
// Propagate the assigned location to all UNIONs/FRAGMENTs // Propagate the assigned location to all UNIONs/FRAGMENTs
@@ -119,6 +106,16 @@ static std::optional<size_t> getPlacement(Section const &section, MemoryLocation
SectionTypeInfo const &typeInfo = sectionTypeInfo[section.type]; SectionTypeInfo const &typeInfo = sectionTypeInfo[section.type];
for (;;) { for (;;) {
if (location.bank < typeInfo.firstBank
|| location.bank >= memory[section.type].size() + typeInfo.firstBank) {
fatal(
"Invalid bank for %s section \"%s\": %" PRIu32,
sectionTypeInfo[section.type].name.c_str(),
section.name.c_str(),
location.bank
);
}
// Switch to the beginning of the next bank // Switch to the beginning of the next bank
std::deque<FreeSpace> &bankMem = memory[section.type][location.bank - typeInfo.firstBank]; std::deque<FreeSpace> &bankMem = memory[section.type][location.bank - typeInfo.firstBank];
size_t spaceIdx = 0; size_t spaceIdx = 0;
@@ -208,50 +205,37 @@ static std::optional<size_t> getPlacement(Section const &section, MemoryLocation
} }
static std::string getSectionDescription(Section const &section) { static std::string getSectionDescription(Section const &section) {
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 +294,11 @@ static void placeSection(Section &section) {
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 +306,7 @@ static void placeSection(Section &section) {
} 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()
); );
@@ -381,36 +355,74 @@ static void categorizeSection(Section &section) {
sections.insert(pos, &section); sections.insert(pos, &section);
} }
static std::vector<Section const *> checkOverlayCompat() { static void checkOverlayCompat() {
std::vector<Section const *> unfixedSections; auto isFixed = [](uint8_t constraints) {
return (constraints & BANK_CONSTRAINED) && (constraints & ORG_CONSTRAINED);
};
if (!options.overlayFileName) { std::string unfixedList;
return unfixedSections;
size_t nbUnfixedSections = 0;
for (uint8_t constraints = std::size(unassignedSections); constraints--;) {
if (!isFixed(constraints)) {
nbUnfixedSections += unassignedSections[constraints].size();
}
} }
if (nbUnfixedSections == 0) {
return;
}
size_t nbListed = 0;
for (uint8_t constraints = std::size(unassignedSections); constraints--;) { for (uint8_t constraints = std::size(unassignedSections); constraints--;) {
if (((constraints & BANK_CONSTRAINED) && (constraints & ORG_CONSTRAINED)) if (isFixed(constraints)) {
|| unassignedSections[constraints].empty()) {
continue; continue;
} }
for (Section *section : unassignedSections[constraints]) { for (Section const *section : unassignedSections[constraints]) {
unfixedSections.push_back(section); if (nbListed == 10) {
unfixedList += "\n- and ";
if (unfixedSections.size() == 10) { unfixedList += std::to_string(nbUnfixedSections - nbListed);
return unfixedSections; unfixedList += " more";
break;
} }
unfixedList += "\n- \"";
unfixedList += section->name;
unfixedList += "\" (";
if (!(constraints & (BANK_CONSTRAINED | ORG_CONSTRAINED))) {
unfixedList += "bank and address";
} else if (!(constraints & BANK_CONSTRAINED)) {
unfixedList += "bank";
} else {
assume(!(constraints & ORG_CONSTRAINED));
unfixedList += "address";
}
unfixedList += " not specified)";
++nbListed;
} }
} }
return unfixedSections; fatal(
"All sections must be fixed when using an overlay file; %zu %s not:%s",
nbUnfixedSections,
nbUnfixedSections == 1 ? "is" : "are",
unfixedList.c_str()
);
} }
void assign_AssignSections() { void assign_AssignSections() {
verbosePrint(VERB_NOTICE, "Beginning assignment...\n"); verbosePrint(VERB_NOTICE, "Beginning assignment...\n");
// Initialize assignment // Initialize the free space-modelling structs
initFreeSpace(); for (SectionType type : EnumSeq(SECTTYPE_INVALID)) {
memory[type].resize(sectTypeBanks(type));
for (std::deque<FreeSpace> &bankMem : memory[type]) {
bankMem.push_back({
.address = sectionTypeInfo[type].startAddr,
.size = sectionTypeInfo[type].size,
});
}
}
// Generate linked lists of sections to assign // Generate linked lists of sections to assign
static uint64_t nbSectionsToAssign = 0; // `static` so `sect_ForEach` callback can see it static uint64_t nbSectionsToAssign = 0; // `static` so `sect_ForEach` callback can see it
@@ -420,26 +432,8 @@ void assign_AssignSections() {
}); });
// Overlaying requires only fully-constrained sections // Overlaying requires only fully-constrained sections
if (std::vector<Section const *> unfixedSections = checkOverlayCompat(); if (options.overlayFileName) {
!unfixedSections.empty()) { checkOverlayCompat();
size_t nbUnfixedSections = unfixedSections.size();
std::string unfixedList;
for (Section const *section : unfixedSections) {
unfixedList += "\n- \"";
unfixedList += section->name;
unfixedList += '"';
}
if (nbSectionsToAssign > nbUnfixedSections) {
unfixedList += "\n- and ";
unfixedList += std::to_string(nbSectionsToAssign - nbUnfixedSections);
unfixedList += " more";
}
fatal(
"All sections must be fixed when using an overlay file; %" PRIu64 " %s not:%s",
nbSectionsToAssign,
nbSectionsToAssign == 1 ? "is" : "are",
unfixedList.c_str()
);
} }
// Assign sections in decreasing constraint order // Assign sections in decreasing constraint order

View File

@@ -1,3 +1,5 @@
// SPDX-License-Identifier: MIT
#include "link/fstack.hpp" #include "link/fstack.hpp"
#include <stdint.h> #include <stdint.h>
@@ -12,44 +14,47 @@
#include "link/warning.hpp" #include "link/warning.hpp"
using TraceNode = std::pair<std::string, uint32_t>; void FileStackNode::printBacktrace(uint32_t curLineNo) const {
using TraceItem = std::pair<FileStackNode const *, uint32_t>;
static std::vector<TraceNode> backtrace(FileStackNode const &node, uint32_t curLineNo) { std::vector<TraceItem> items;
if (node.isQuiet && !tracing.loud) { for (TraceItem item{this, curLineNo};;) {
if (node.parent) { auto &[node, itemLineNo] = item;
bool loud = !node->isQuiet || tracing.loud;
if (loud) {
items.emplace_back(node, itemLineNo);
}
if (!node->parent) {
assume(node->type != NODE_REPT && std::holds_alternative<std::string>(node->data));
break;
}
if (loud || node->type != NODE_REPT) {
// Quiet REPT nodes will pass their interior line number up to their parent, // Quiet REPT nodes will pass their interior line number up to their parent,
// which is more precise than the parent's own line number (since that will be // which is more precise than the parent's own line number (since that will be
// the line number of the "REPT?" or "FOR?" itself). // the line number of the "REPT?" or "FOR?" itself).
return backtrace(*node.parent, node.type == NODE_REPT ? curLineNo : node.lineNo); itemLineNo = node->lineNo;
} }
return {}; // LCOV_EXCL_LINE node = &*node->parent;
} }
if (!node.parent) { using TraceNode = std::pair<std::string, uint32_t>;
assume(node.type != NODE_REPT && std::holds_alternative<std::string>(node.data)); std::vector<TraceNode> traceNodes;
return { traceNodes.reserve(items.size());
{node.name(), curLineNo} for (auto &[node, itemLineNo] : reversed(items)) {
}; if (std::holds_alternative<std::vector<uint32_t>>(node->data)) {
} assume(!traceNodes.empty()); // REPT nodes use their parent's name
std::string reptName = traceNodes.back().first;
std::vector<TraceNode> traceNodes = backtrace(*node.parent, node.lineNo); if (std::vector<uint32_t> const &nodeIters = node->iters(); !nodeIters.empty()) {
if (std::holds_alternative<std::vector<uint32_t>>(node.data)) { reptName.append(NODE_SEPARATOR REPT_NODE_PREFIX);
assume(!traceNodes.empty()); // REPT nodes use their parent's name reptName.append(std::to_string(nodeIters.back()));
std::string reptName = traceNodes.back().first; }
if (std::vector<uint32_t> const &nodeIters = node.iters(); !nodeIters.empty()) { traceNodes.emplace_back(reptName, itemLineNo);
reptName.append(NODE_SEPARATOR REPT_NODE_PREFIX); } else {
reptName.append(std::to_string(nodeIters.back())); traceNodes.emplace_back(node->name(), itemLineNo);
} }
traceNodes.emplace_back(reptName, curLineNo);
} else {
traceNodes.emplace_back(node.name(), curLineNo);
} }
return traceNodes;
}
void FileStackNode::printBacktrace(uint32_t curLineNo) const {
trace_PrintBacktrace( trace_PrintBacktrace(
backtrace(*this, curLineNo), traceNodes,
[](TraceNode const &node) { return node.first.c_str(); }, [](TraceNode const &node) { return node.first.c_str(); },
[](TraceNode const &node) { return node.second; } [](TraceNode const &node) { return node.second; }
); );

View File

@@ -21,7 +21,6 @@
#include "usage.hpp" #include "usage.hpp"
#include "util.hpp" // UpperMap, printChar #include "util.hpp" // UpperMap, printChar
#include "verbosity.hpp" #include "verbosity.hpp"
#include "version.hpp"
#include "link/assign.hpp" #include "link/assign.hpp"
#include "link/lexer.hpp" #include "link/lexer.hpp"
@@ -283,7 +282,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()); usage.printVersion(false);
exit(0); exit(0);
case 'v': case 'v':
@@ -330,7 +329,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()); usage.printVersion(true);
printVVVVVVerbosity(); printVVVVVVerbosity();
@@ -408,7 +407,7 @@ static void verboseOutputConfig() {
} }
// -n/--sym // -n/--sym
printPath("Output sym file", options.symFileName); printPath("Output sym file", options.symFileName);
fputs("Ready.\n", stderr); fputs("Ready for linking\n", stderr);
style_Reset(stderr); style_Reset(stderr);
} }
@@ -447,11 +446,9 @@ int main(int argc, char *argv[]) {
if (localOptions.linkerScriptName) { if (localOptions.linkerScriptName) {
verbosePrint(VERB_NOTICE, "Reading linker script...\n"); verbosePrint(VERB_NOTICE, "Reading linker script...\n");
if (lexer_Init(*localOptions.linkerScriptName)) { if (yy::parser parser; lexer_Init(*localOptions.linkerScriptName) && parser.parse() != 0) {
if (yy::parser parser; parser.parse() != 0) { // Exited due to YYABORT or YYNOMEM
// Exited due to YYABORT or YYNOMEM fatal("Unrecoverable error while reading linker script"); // LCOV_EXCL_LINE
fatal("Unrecoverable error while reading linker script"); // LCOV_EXCL_LINE
}
} }
// If the linker script produced any errors, some sections may be in an invalid state // If the linker script produced any errors, some sections may be in an invalid state
@@ -468,4 +465,6 @@ int main(int argc, char *argv[]) {
patch_ApplyPatches(); patch_ApplyPatches();
requireZeroErrors(); requireZeroErrors();
out_WriteFiles(); out_WriteFiles();
return 0;
} }

View File

@@ -72,7 +72,7 @@ static int64_t readLong(FILE *file) {
tryRead(readLong, int64_t, INT64_MAX, long, var, file, __VA_ARGS__) tryRead(readLong, int64_t, INT64_MAX, long, var, file, __VA_ARGS__)
// Helper macro to read a byte from a file to a var, or error out if it fails to. // Helper macro to read a byte from a file to a var, or error out if it fails to.
#define tryGetc(type, var, file, ...) tryRead(getc, int, EOF, type, var, file, __VA_ARGS__) #define tryGetc(var, file, ...) tryRead(getc, int, EOF, uint8_t, var, file, __VA_ARGS__)
// Helper macro to read a '\0'-terminated string from a file, or error out if it fails to. // Helper macro to read a '\0'-terminated string from a file, or error out if it fails to.
#define tryReadString(var, file, ...) \ #define tryReadString(var, file, ...) \
@@ -100,27 +100,31 @@ static void readFileStackNode(
tryReadLong( tryReadLong(
parentID, file, "%s: Cannot read node #%" PRIu32 "'s parent ID: %s", fileName, nodeID parentID, file, "%s: Cannot read node #%" PRIu32 "'s parent ID: %s", fileName, nodeID
); );
node.parent = parentID != UINT32_MAX ? &fileNodes[parentID] : nullptr; if (parentID == UINT32_MAX) {
node.parent = nullptr;
} else if (parentID >= fileNodes.size()) {
fatal("%s: Node #%" PRIu32 " has invalid parent ID #%" PRIu32, fileName, nodeID, parentID);
} else {
node.parent = &fileNodes[parentID];
}
tryReadLong( tryReadLong(
node.lineNo, file, "%s: Cannot read node #%" PRIu32 "'s line number: %s", fileName, nodeID node.lineNo, file, "%s: Cannot read node #%" PRIu32 "'s line number: %s", fileName, nodeID
); );
uint8_t type; uint8_t type;
tryGetc(uint8_t, type, file, "%s: Cannot read node #%" PRIu32 "'s type: %s", fileName, nodeID); tryGetc(type, file, "%s: Cannot read node #%" PRIu32 "'s type: %s", fileName, nodeID);
node.type = static_cast<FileStackNodeType>(type & ~(1 << FSTACKNODE_QUIET_BIT)); switch (type & ~(1 << FSTACKNODE_QUIET_BIT)) {
node.isQuiet = (type & (1 << FSTACKNODE_QUIET_BIT)) != 0;
switch (node.type) {
case NODE_FILE: case NODE_FILE:
case NODE_MACRO: case NODE_MACRO:
node.type = FileStackNodeType(type);
node.data = ""; node.data = "";
tryReadString( tryReadString(
node.name(), file, "%s: Cannot read node #%" PRIu32 "'s file name: %s", fileName, nodeID node.name(), file, "%s: Cannot read node #%" PRIu32 "'s file name: %s", fileName, nodeID
); );
break; break;
case NODE_REPT: { case NODE_REPT: {
node.type = NODE_REPT;
uint32_t depth; uint32_t depth;
tryReadLong( tryReadLong(
depth, file, "%s: Cannot read node #%" PRIu32 "'s REPT depth: %s", fileName, nodeID depth, file, "%s: Cannot read node #%" PRIu32 "'s REPT depth: %s", fileName, nodeID
@@ -143,8 +147,13 @@ static void readFileStackNode(
nodeID nodeID
); );
} }
break;
} }
default:
fatal("%s: Node #%" PRIu32 " has unknown type 0x%02x", fileName, nodeID, type);
} }
node.isQuiet = (type & (1 << FSTACKNODE_QUIET_BIT)) != 0;
} }
// Reads a symbol from a file. // Reads a symbol from a file.
@@ -152,20 +161,25 @@ static void readSymbol(
FILE *file, Symbol &symbol, char const *fileName, std::vector<FileStackNode> const &fileNodes FILE *file, Symbol &symbol, char const *fileName, std::vector<FileStackNode> const &fileNodes
) { ) {
tryReadString(symbol.name, file, "%s: Cannot read symbol name: %s", fileName); tryReadString(symbol.name, file, "%s: Cannot read symbol name: %s", fileName);
tryGetc(
ExportLevel, uint8_t type;
symbol.type, tryGetc(type, file, "%s: Cannot read `%s`'s type: %s", fileName, symbol.name.c_str());
file, if (type >= SYMTYPE_INVALID) {
"%s: Cannot read `%s`'s type: %s", fatal("%s: `%s` has unknown type 0x%02x", fileName, symbol.name.c_str(), type);
fileName, } else {
symbol.name.c_str() symbol.type = ExportLevel(type);
); }
// If the symbol is defined in this file, read its definition // If the symbol is defined in this file, read its definition
if (symbol.type != SYMTYPE_IMPORT) { if (symbol.type != SYMTYPE_IMPORT) {
uint32_t nodeID; uint32_t nodeID;
tryReadLong( tryReadLong(
nodeID, file, "%s: Cannot read `%s`'s node ID: %s", fileName, symbol.name.c_str() nodeID, file, "%s: Cannot read `%s`'s node ID: %s", fileName, symbol.name.c_str()
); );
if (nodeID >= fileNodes.size()) {
fatal("%s: `%s` has invalid node ID #%" PRIu32, fileName, symbol.name.c_str(), nodeID);
}
symbol.src = &fileNodes[nodeID]; symbol.src = &fileNodes[nodeID];
tryReadLong( tryReadLong(
symbol.lineNo, symbol.lineNo,
@@ -212,6 +226,15 @@ static void readPatch(
sectName.c_str(), sectName.c_str(),
patchID patchID
); );
if (nodeID >= fileNodes.size()) {
fatal(
"%s: \"%s\"'s patch #%" PRIu32 " has invalid node ID #%" PRIu32,
fileName,
sectName.c_str(),
patchID,
nodeID
);
}
patch.src = &fileNodes[nodeID]; patch.src = &fileNodes[nodeID];
tryReadLong( tryReadLong(
@@ -247,9 +270,8 @@ static void readPatch(
patchID patchID
); );
PatchType type; uint8_t type;
tryGetc( tryGetc(
PatchType,
type, type,
file, file,
"%s: Cannot read \"%s\"'s patch #%" PRIu32 "'s type: %s", "%s: Cannot read \"%s\"'s patch #%" PRIu32 "'s type: %s",
@@ -257,7 +279,17 @@ static void readPatch(
sectName.c_str(), sectName.c_str(),
patchID patchID
); );
patch.type = type; if (type >= PATCHTYPE_INVALID) {
fatal(
"%s: \"%s\"'s patch #%" PRIu32 " has unknown type 0x%02x",
fileName,
sectName.c_str(),
patchID,
type
);
} else {
patch.type = PatchType(type);
}
uint32_t rpnSize; uint32_t rpnSize;
tryReadLong( tryReadLong(
@@ -281,13 +313,6 @@ static void readPatch(
} }
} }
// Sets a patch's `pcSection` from its `pcSectionID`.
static void
linkPatchToPCSect(Patch &patch, std::vector<std::unique_ptr<Section>> const &fileSections) {
patch.pcSection =
patch.pcSectionID != UINT32_MAX ? fileSections[patch.pcSectionID].get() : nullptr;
}
// Reads a section from a file. // Reads a section from a file.
static void readSection( static void readSection(
FILE *file, Section &section, char const *fileName, std::vector<FileStackNode> const &fileNodes FILE *file, Section &section, char const *fileName, std::vector<FileStackNode> const &fileNodes
@@ -296,11 +321,16 @@ static void readSection(
uint8_t byte; uint8_t byte;
tryReadString(section.name, file, "%s: Cannot read section name: %s", fileName); tryReadString(section.name, file, "%s: Cannot read section name: %s", fileName);
uint32_t nodeID; uint32_t nodeID;
tryReadLong( tryReadLong(
nodeID, file, "%s: Cannot read \"%s\"'s node ID: %s", fileName, section.name.c_str() nodeID, file, "%s: Cannot read \"%s\"'s node ID: %s", fileName, section.name.c_str()
); );
if (nodeID >= fileNodes.size()) {
fatal("%s: \"%s\" has invalid node ID #%" PRIu32, fileName, section.name.c_str(), nodeID);
}
section.src = &fileNodes[nodeID]; section.src = &fileNodes[nodeID];
tryReadLong( tryReadLong(
section.lineNo, section.lineNo,
file, file,
@@ -310,18 +340,23 @@ static void readSection(
); );
tryReadLong(tmp, file, "%s: Cannot read \"%s\"'s' size: %s", fileName, section.name.c_str()); tryReadLong(tmp, file, "%s: Cannot read \"%s\"'s' size: %s", fileName, section.name.c_str());
if (tmp < 0 || tmp > UINT16_MAX) { if (tmp < 0 || tmp > UINT16_MAX) {
fatal("\"%s\"'s section size ($%" PRIx32 ") is invalid", section.name.c_str(), tmp); fatal(
"%s: \"%s\"'s section size ($%" PRIx32 ") is invalid",
fileName,
section.name.c_str(),
tmp
);
} }
section.size = tmp; section.size = tmp;
section.offset = 0; section.offset = 0;
tryGetc(
uint8_t, byte, file, "%s: Cannot read \"%s\"'s type: %s", fileName, section.name.c_str() tryGetc(byte, file, "%s: Cannot read \"%s\"'s type: %s", fileName, section.name.c_str());
);
if (uint8_t type = byte & SECTTYPE_TYPE_MASK; type >= SECTTYPE_INVALID) { if (uint8_t type = byte & SECTTYPE_TYPE_MASK; type >= SECTTYPE_INVALID) {
fatal("\"%s\" has unknown section type 0x%02x", section.name.c_str(), type); fatal("%s: \"%s\" has unknown section type 0x%02x", fileName, section.name.c_str(), type);
} else { } else {
section.type = SectionType(type); section.type = SectionType(type);
} }
if (byte & (1 << SECTTYPE_UNION_BIT)) { if (byte & (1 << SECTTYPE_UNION_BIT)) {
section.modifier = SECTION_UNION; section.modifier = SECTION_UNION;
} else if (byte & (1 << SECTTYPE_FRAGMENT_BIT)) { } else if (byte & (1 << SECTTYPE_FRAGMENT_BIT)) {
@@ -339,14 +374,7 @@ static void readSection(
tryReadLong(tmp, file, "%s: Cannot read \"%s\"'s bank: %s", fileName, section.name.c_str()); tryReadLong(tmp, file, "%s: Cannot read \"%s\"'s bank: %s", fileName, section.name.c_str());
section.isBankFixed = tmp >= 0; section.isBankFixed = tmp >= 0;
section.bank = tmp; section.bank = tmp;
tryGetc( tryGetc(byte, file, "%s: Cannot read \"%s\"'s alignment: %s", fileName, section.name.c_str());
uint8_t,
byte,
file,
"%s: Cannot read \"%s\"'s alignment: %s",
fileName,
section.name.c_str()
);
if (byte > 16) { if (byte > 16) {
byte = 16; byte = 16;
} }
@@ -420,19 +448,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 +474,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);
} }
@@ -498,7 +527,16 @@ void obj_ReadFile(std::string const &filePath, size_t fileID) {
readSymbol(file, sym, fileName, nodes[fileID]); readSymbol(file, sym, fileName, nodes[fileID]);
sym_AddSymbol(sym); sym_AddSymbol(sym);
if (std::holds_alternative<Label>(sym.data)) { if (std::holds_alternative<Label>(sym.data)) {
++nbSymPerSect[std::get<Label>(sym.data).sectionID]; int32_t sectionID = std::get<Label>(sym.data).sectionID;
if (sectionID < 0 || static_cast<size_t>(sectionID) >= nbSymPerSect.size()) {
fatal(
"%s: `%s` has invalid section ID #%" PRId32,
fileName,
sym.name.c_str(),
sectionID
);
}
++nbSymPerSect[sectionID];
} }
} }
@@ -521,15 +559,41 @@ void obj_ReadFile(std::string const &filePath, size_t fileID) {
Assertion &assertion = patch_AddAssertion(); Assertion &assertion = patch_AddAssertion();
readAssertion(file, assertion, fileName, i, nodes[fileID]); readAssertion(file, assertion, fileName, i, nodes[fileID]);
linkPatchToPCSect(assertion.patch, fileSections);
if (assertion.patch.pcSectionID == UINT32_MAX) {
assertion.patch.pcSection = nullptr;
} else if (assertion.patch.pcSectionID >= fileSections.size()) {
fatal(
"%s: Assertion #%" PRIu32 "'s patch has invalid section ID #%" PRIu32,
fileName,
i,
assertion.patch.pcSectionID
);
} else {
assertion.patch.pcSection = fileSections[assertion.patch.pcSectionID].get();
}
assertion.fileSymbols = &fileSymbols; assertion.fileSymbols = &fileSymbols;
} }
// Give patches' PC section pointers to their sections // Give patches' PC section pointers to their sections
for (std::unique_ptr<Section> const &sect : fileSections) { for (std::unique_ptr<Section> const &sect : fileSections) {
if (sectTypeHasData(sect->type)) { if (!sectTypeHasData(sect->type)) {
for (Patch &patch : sect->patches) { continue;
linkPatchToPCSect(patch, fileSections); }
for (size_t i = 0; i < sect->patches.size(); ++i) {
if (Patch &patch = sect->patches[i]; patch.pcSectionID == UINT32_MAX) {
patch.pcSection = nullptr;
} else if (patch.pcSectionID >= fileSections.size()) {
fatal(
"%s: \"%s\"'s patch #%zu has invalid section ID #%" PRIu32,
fileName,
sect->name.c_str(),
i,
patch.pcSectionID
);
} else {
patch.pcSection = fileSections[patch.pcSectionID].get();
} }
} }
} }

View File

@@ -71,9 +71,7 @@ void out_AddSection(Section const &section) {
}; };
uint32_t targetBank = section.bank - sectionTypeInfo[section.type].firstBank; uint32_t targetBank = section.bank - sectionTypeInfo[section.type].firstBank;
uint32_t minNbBanks = targetBank + 1; if (targetBank >= maxNbBanks[section.type]) {
if (minNbBanks > maxNbBanks[section.type]) {
fatal( fatal(
"Section \"%s\" has an invalid bank range (%" PRIu32 " > %" PRIu32 ")", "Section \"%s\" has an invalid bank range (%" PRIu32 " > %" PRIu32 ")",
section.name.c_str(), section.name.c_str(),
@@ -81,16 +79,14 @@ void out_AddSection(Section const &section) {
maxNbBanks[section.type] - 1 maxNbBanks[section.type] - 1
); );
} }
if (sections[section.type].size() <= targetBank) {
for (uint32_t i = sections[section.type].size(); i < minNbBanks; ++i) { sections[section.type].resize(targetBank + 1);
sections[section.type].emplace_back();
} }
// Insert section while keeping the list sorted by increasing org
std::deque<Section const *> &bankSections = std::deque<Section const *> &bankSections =
section.size ? sections[section.type][targetBank].sections section.size ? sections[section.type][targetBank].sections
: sections[section.type][targetBank].zeroLenSections; : sections[section.type][targetBank].zeroLenSections;
// Insert section while keeping the list sorted by increasing org
auto pos = bankSections.begin(); auto pos = bankSections.begin();
while (pos != bankSections.end() && (*pos)->org < section.org) { while (pos != bankSections.end() && (*pos)->org < section.org) {
++pos; ++pos;

View File

@@ -78,7 +78,8 @@ static uint32_t getRPNByte(uint8_t const *&expression, int32_t &size, Patch cons
} }
static Symbol const *getSymbol(std::vector<Symbol> const &symbolList, uint32_t index) { static Symbol const *getSymbol(std::vector<Symbol> const &symbolList, uint32_t index) {
assume(index != UINT32_MAX); // PC needs to be handled specially, not here assume(index != UINT32_MAX); // PC needs to be handled specially, not here
assume(index < symbolList.size()); // This needs to be checked before calling
Symbol const &symbol = symbolList[index]; Symbol const &symbol = symbolList[index];
// If the symbol is defined elsewhere... // If the symbol is defined elsewhere...
@@ -270,17 +271,19 @@ static int32_t computeRPNExpr(Patch const &patch, std::vector<Symbol> const &fil
value = op_shift_right_unsigned(popRPN(patch), value); value = op_shift_right_unsigned(popRPN(patch), value);
break; break;
case RPN_BANK_SYM: case RPN_BANK_SYM: {
value = 0; uint32_t symID = 0;
for (uint8_t shift = 0; shift < 32; shift += 8) { for (uint8_t shift = 0; shift < 32; shift += 8) {
value |= getRPNByte(expression, size, patch) << shift; symID |= getRPNByte(expression, size, patch) << shift;
} }
if (Symbol const *symbol = getSymbol(fileSymbols, value); !symbol) { if (symID >= fileSymbols.size()) {
fatalAt(patch, "Requested `BANK()` of invalid symbol ID #%" PRIu32, symID);
} else if (Symbol const *symbol = getSymbol(fileSymbols, symID); !symbol) {
errorAt( errorAt(
patch, patch,
"Requested `BANK()` of undefined symbol `%s`", "Requested `BANK()` of undefined symbol `%s`",
fileSymbols[value].name.c_str() fileSymbols[symID].name.c_str()
); );
isError = true; isError = true;
value = 1; value = 1;
@@ -290,12 +293,13 @@ static int32_t computeRPNExpr(Patch const &patch, std::vector<Symbol> const &fil
errorAt( errorAt(
patch, patch,
"Requested `BANK()` of non-label symbol `%s`", "Requested `BANK()` of non-label symbol `%s`",
fileSymbols[value].name.c_str() fileSymbols[symID].name.c_str()
); );
isError = true; isError = true;
value = 1; value = 1;
} }
break; break;
}
case RPN_BANK_SECT: { case RPN_BANK_SECT: {
// `expression` is not guaranteed to be '\0'-terminated. If it is not, // `expression` is not guaranteed to be '\0'-terminated. If it is not,
@@ -420,13 +424,13 @@ static int32_t computeRPNExpr(Patch const &patch, std::vector<Symbol> const &fil
} }
break; break;
case RPN_SYM: case RPN_SYM: {
value = 0; uint32_t symID = 0;
for (uint8_t shift = 0; shift < 32; shift += 8) { for (uint8_t shift = 0; shift < 32; shift += 8) {
value |= getRPNByte(expression, size, patch) << shift; symID |= getRPNByte(expression, size, patch) << shift;
} }
if (value == -1) { // PC if (symID == UINT32_MAX) { // PC
if (patch.pcSection) { if (patch.pcSection) {
value = patch.pcOffset + patch.pcSection->org; value = patch.pcOffset + patch.pcSection->org;
} else { } else {
@@ -434,9 +438,12 @@ static int32_t computeRPNExpr(Patch const &patch, std::vector<Symbol> const &fil
value = 0; value = 0;
isError = true; isError = true;
} }
} else if (Symbol const *symbol = getSymbol(fileSymbols, value); !symbol) { } else if (symID >= fileSymbols.size()) {
errorAt(patch, "Undefined symbol `%s`", fileSymbols[value].name.c_str()); fatalAt(patch, "Invalid symbol ID #%" PRIu32, symID);
sym_TraceLocalAliasedSymbols(fileSymbols[value].name); } else if (Symbol const *symbol = getSymbol(fileSymbols, symID); !symbol) {
errorAt(patch, "Undefined symbol `%s`", fileSymbols[symID].name.c_str());
sym_TraceLocalAliasedSymbols(fileSymbols[symID].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);
@@ -446,6 +453,7 @@ static int32_t computeRPNExpr(Patch const &patch, std::vector<Symbol> const &fil
} }
break; break;
} }
}
pushRPN(value, isError); pushRPN(value, isError);
} }

View File

@@ -296,7 +296,7 @@ void sdobj_ReadFile(FileStackNode const &src, FILE *file, std::vector<Symbol> &f
if (tmp > UINT16_MAX) { if (tmp > UINT16_MAX) {
fatalAt( fatalAt(
where, where,
"Area \"%s\" is larger than the GB address space!?", "Area \"%s\" is larger than the GB address space",
curSection->name.c_str() curSection->name.c_str()
); );
} }

View File

@@ -80,14 +80,15 @@ void sym_TraceLocalAliasedSymbols(std::string const &name) {
plural ? "are" : "is" plural ? "are" : "is"
); );
int count = 0; size_t nbListed = 0;
for (Symbol *local : locals) { for (Symbol *local : locals) {
assume(local->src); if (nbListed == 3) {
local->src->printBacktrace(local->lineNo); fprintf(stderr, " ...and %zu more\n", locals.size() - nbListed);
if (++count == 3 && locals.size() > 3) {
fprintf(stderr, " ...and %zu more\n", locals.size() - 3);
break; break;
} }
assume(local->src);
local->src->printBacktrace(local->lineNo);
++nbListed;
} }
} }

View File

@@ -9,6 +9,7 @@
#include "helpers.hpp" #include "helpers.hpp"
#include "platform.hpp" #include "platform.hpp"
#include "style.hpp" #include "style.hpp"
#include "version.hpp"
#if defined(_MSC_VER) || defined(__MINGW32__) #if defined(_MSC_VER) || defined(__MINGW32__)
#define WIN32_LEAN_AND_MEAN // Include less from `windows.h` #define WIN32_LEAN_AND_MEAN // Include less from `windows.h`
@@ -17,6 +18,10 @@
#include <sys/ioctl.h> #include <sys/ioctl.h>
#endif #endif
void Usage::printVersion(bool error) const {
fprintf(error ? stderr : stdout, "%s %s\n", name.c_str(), get_package_version_string());
}
void Usage::printAndExit(int code) const { void Usage::printAndExit(int code) const {
FILE *file; FILE *file;
bool isTerminal; bool isTerminal;

View File

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

View File

@@ -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

View File

@@ -1,3 +1,3 @@
error: The absolute alignment offset (2) must be less than alignment size (2) error: The absolute alignment offset (2) must be less than alignment size (2)
at align-large-ofs.asm(2) at align-large-ofs.asm(2)
Assembly aborted with 1 error! Assembly aborted with 1 error

View File

@@ -2,4 +2,4 @@ error: Alignment must be between 0 and 16, not 17
at align-large.asm(1) at align-large.asm(1)
error: Alignment must be between 0 and 16, not 17 error: Alignment must be between 0 and 16, not 17
at align-large.asm(2) at align-large.asm(2)
Assembly aborted with 2 errors! Assembly aborted with 2 errors

View File

@@ -2,4 +2,4 @@ error: The absolute alignment offset (18) must be less than alignment size (16)
at align-offset.asm(4) at align-offset.asm(4)
error: The absolute alignment offset (20) must be less than alignment size (16) error: The absolute alignment offset (20) must be less than alignment size (16)
at align-offset.asm(6) at align-offset.asm(6)
Assembly aborted with 2 errors! Assembly aborted with 2 errors

View File

@@ -1,3 +1,3 @@
error: Cannot output data outside of a `SECTION` error: Cannot output data outside of a `SECTION`
at align-pc-outside-section.asm(1) at align-pc-outside-section.asm(1)
Assembly aborted with 1 error! Assembly aborted with 1 error

View File

@@ -1,3 +1,3 @@
error: Section "X"'s alignment cannot be attained in WRAM0 error: Section "X"'s alignment cannot be attained in WRAM0
at align-unattainable.asm(3) at align-unattainable.asm(3)
Assembly aborted with 1 error! Assembly aborted with 1 error

View File

@@ -8,4 +8,4 @@ error: syntax error, unexpected anonymous label, expecting symbol or label or lo
at anon-label-bad.asm(10) at anon-label-bad.asm(10)
error: syntax error, unexpected :: error: syntax error, unexpected ::
at anon-label-bad.asm(22) at anon-label-bad.asm(22)
Assembly aborted with 5 errors! Assembly aborted with 5 errors

View File

@@ -1,3 +1,3 @@
error: PC has no bank outside of a section error: PC has no bank outside of a section
at assert-nosect-bank.asm(1) at assert-nosect-bank.asm(1)
Assembly aborted with 1 error! Assembly aborted with 1 error

View File

@@ -1,3 +1,3 @@
error: PC has no value outside of a section error: PC has no value outside of a section
at assert@-no-sect.asm(1) at assert@-no-sect.asm(1)
Assembly aborted with 1 error! Assembly aborted with 1 error

View File

@@ -1,3 +1,3 @@
error: Fixed-point precision must be between 1 and 31, not 42 error: Fixed-point precision must be between 1 and 31, not 42
at bad-precision.asm(1) at bad-precision.asm(1)
Assembly aborted with 1 error! Assembly aborted with 1 error

View File

@@ -20,4 +20,4 @@ error: Expected constant expression: `Label_u12`'s bank is not known
at bank.asm::def_sect(8) <- bank.asm(22) at bank.asm::def_sect(8) <- bank.asm(22)
error: `BANK` argument must be a label error: `BANK` argument must be a label
at bank.asm(26) at bank.asm(26)
Assembly aborted with 11 errors! Assembly aborted with 11 errors

View File

@@ -2,4 +2,4 @@ error: Unterminated block comment
at block-comment-termination-error.asm(1) at block-comment-termination-error.asm(1)
error: syntax error, unexpected end of buffer error: syntax error, unexpected end of buffer
at block-comment-termination-error.asm(1) at block-comment-termination-error.asm(1)
Assembly aborted with 2 errors! Assembly aborted with 2 errors

View File

@@ -1,3 +1,3 @@
error: Invalid character '2' after line continuation error: Invalid character '2' after line continuation
at blue-paint-limits.asm::nth(40) <- blue-paint-limits.asm(43) at blue-paint-limits.asm::nth(40) <- blue-paint-limits.asm(43)
Assembly aborted with 1 error! Assembly aborted with 1 error

View File

@@ -10,4 +10,4 @@ error: Macro argument `\<2>` not defined
at bracketed-macro-args.asm::bad(30) <- bracketed-macro-args.asm(33) at bracketed-macro-args.asm::bad(30) <- bracketed-macro-args.asm(33)
error: Bracketed symbol `abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz` does not exist error: Bracketed symbol `abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz` does not exist
at bracketed-macro-args.asm::toolong(36) <- bracketed-macro-args.asm(39) at bracketed-macro-args.asm::toolong(36) <- bracketed-macro-args.asm(39)
Assembly aborted with 6 errors! Assembly aborted with 6 errors

View File

@@ -4,4 +4,4 @@ error: `Label` does not have a constant value
at bracketed-symbols.asm(20) at bracketed-symbols.asm(20)
error: PC does not have a constant value; the current section is not fixed error: PC does not have a constant value; the current section is not fixed
at bracketed-symbols.asm(21) at bracketed-symbols.asm(21)
Assembly aborted with 3 errors! Assembly aborted with 3 errors

View File

@@ -54,4 +54,4 @@ error: Built-in symbol `__ISO_8601_UTC__` cannot be redefined
at builtin-overwrite.asm::tickle(29) <- builtin-overwrite.asm(37) at builtin-overwrite.asm::tickle(29) <- builtin-overwrite.asm(37)
error: Built-in symbol `__ISO_8601_UTC__` cannot be redefined error: Built-in symbol `__ISO_8601_UTC__` cannot be redefined
at builtin-overwrite.asm::tickle(30) <- builtin-overwrite.asm(37) at builtin-overwrite.asm::tickle(30) <- builtin-overwrite.asm(37)
Assembly aborted with 28 errors! Assembly aborted with 28 errors

View File

@@ -30,4 +30,4 @@ error: `.` is reserved for a built-in symbol
at builtin-reserved.asm(29) at builtin-reserved.asm(29)
error: `.` has no value outside of a label scope error: `.` has no value outside of a label scope
at builtin-reserved.asm(32) at builtin-reserved.asm(32)
Assembly aborted with 16 errors! Assembly aborted with 16 errors

View File

@@ -2,4 +2,4 @@ error: Illegal character escape '\' at end of input
at character-escape-at-end.asm(1) at character-escape-at-end.asm(1)
error: Unterminated string error: Unterminated string
at character-escape-at-end.asm(1) at character-escape-at-end.asm(1)
Assembly aborted with 2 errors! Assembly aborted with 2 errors

View File

@@ -4,4 +4,4 @@ error: Invalid character '\' in bracketed macro argument
at character-escapes.asm::m(7) <- character-escapes.asm(10) at character-escapes.asm::m(7) <- character-escapes.asm(10)
error: Invalid character '\t' in bracketed macro argument error: Invalid character '\t' in bracketed macro argument
at character-escapes.asm::m(8) <- character-escapes.asm(10) at character-escapes.asm::m(8) <- character-escapes.asm(10)
Assembly aborted with 3 errors! Assembly aborted with 3 errors

View File

@@ -16,4 +16,4 @@ error: Unterminated character
at character-literals.asm(35) at character-literals.asm(35)
error: Character literals must be a single charmap unit error: Character literals must be a single charmap unit
at character-literals.asm(35) at character-literals.asm(35)
Assembly aborted with 5 errors! Assembly aborted with 5 errors

View File

@@ -2,4 +2,4 @@ error: Cannot map an empty string
at charmap-empty.asm(1) at charmap-empty.asm(1)
error: syntax error, unexpected end of line error: syntax error, unexpected end of line
at charmap-empty.asm(2) at charmap-empty.asm(2)
Assembly aborted with 2 errors! Assembly aborted with 2 errors

View File

@@ -1,3 +1,3 @@
error: Undefined base charmap `eggs` error: Undefined base charmap `eggs`
at charmap-inheritance.asm(26) at charmap-inheritance.asm(26)
Assembly aborted with 1 error! Assembly aborted with 1 error

View File

@@ -6,4 +6,4 @@ error: CHARSIZE: No character mapping for "abcdef"
at charsize.asm(19) at charsize.asm(19)
error: CHARSIZE: No character mapping for "é" error: CHARSIZE: No character mapping for "é"
at charsize.asm(20) at charsize.asm(20)
Assembly aborted with 4 errors! Assembly aborted with 4 errors

View File

@@ -12,4 +12,4 @@ warning: CHARVAL: Index starts at 0 [-Wbuiltin-args]
at charval.asm(28) at charval.asm(28)
warning: CHARVAL: Index 10 is past the end of the character mapping [-Wbuiltin-args] warning: CHARVAL: Index 10 is past the end of the character mapping [-Wbuiltin-args]
at charval.asm(29) at charval.asm(29)
Assembly aborted with 5 errors! Assembly aborted with 5 errors

View File

@@ -2,4 +2,4 @@ error: `FOO` already defined (should it be {interpolated} to define its contents
at command-line-symbols.asm(3) at command-line-symbols.asm(3)
and also: and also:
at <command-line> at <command-line>
Assembly aborted with 1 error! Assembly aborted with 1 error

View File

@@ -1,3 +1,3 @@
error: Expected constant expression: undefined symbol `UnDeFiNeD` error: Expected constant expression: undefined symbol `UnDeFiNeD`
at compound-assignment.asm(35) at compound-assignment.asm(35)
Assembly aborted with 1 error! Assembly aborted with 1 error

View File

@@ -6,4 +6,4 @@ error: Expected constant expression: PC is not constant at assembly time
at const-and.asm(17) at const-and.asm(17)
error: Expected constant expression: `Floating` is not constant at assembly time error: Expected constant expression: `Floating` is not constant at assembly time
at const-and.asm(20) at const-and.asm(20)
Assembly aborted with 4 errors! Assembly aborted with 4 errors

View File

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

View File

@@ -1,3 +1,3 @@
error: Assertion failed error: Assertion failed
at const-not.asm(3) at const-not.asm(3)
Assembly aborted with 1 error! Assembly aborted with 1 error

View File

@@ -1,3 +1,3 @@
error: Expected constant expression: PC is not constant at assembly time error: Expected constant expression: PC is not constant at assembly time
at const-zero.asm::test_or(14) <- const-zero.asm(21) at const-zero.asm::test_or(14) <- const-zero.asm(21)
Assembly aborted with 1 error! Assembly aborted with 1 error

View File

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

View File

@@ -0,0 +1,13 @@
PUSHC
PUSHO
PUSHS
SECTION "test", WRAM0
UNION
INCLUDE "nonexistent1.inc"
WARN "still going!"
INCLUDE "nonexistent2.inc"
WARN "and going!"
ENDU
POPS
POPO
POPC

View File

@@ -0,0 +1,4 @@
warning: still going! [-Wuser]
at continues-after-missing-preinclude/a.asm(7)
warning: and going! [-Wuser]
at continues-after-missing-preinclude/a.asm(9)

View File

@@ -0,0 +1 @@
-MC -P nonexistent-pre.inc

View File

@@ -0,0 +1,4 @@
a.o: continues-after-missing-preinclude/a.asm
a.o: nonexistent-pre.inc
a.o: nonexistent1.inc
a.o: nonexistent2.inc

View File

@@ -2,4 +2,4 @@ error: Section "code" cannot contain code or data (not `ROM0` or `ROMX`)
at data-in-ram.asm(2) at data-in-ram.asm(2)
error: Section "data" cannot contain code or data (not `ROM0` or `ROMX`) error: Section "data" cannot contain code or data (not `ROM0` or `ROMX`)
at data-in-ram.asm(4) at data-in-ram.asm(4)
Assembly aborted with 2 errors! Assembly aborted with 2 errors

View File

@@ -4,4 +4,4 @@ error: syntax error, unexpected local label, expecting symbol
at def-scoped.asm(13) at def-scoped.asm(13)
error: syntax error, unexpected local label, expecting symbol error: syntax error, unexpected local label, expecting symbol
at def-scoped.asm(16) at def-scoped.asm(16)
Assembly aborted with 3 errors! Assembly aborted with 3 errors

View File

@@ -14,4 +14,4 @@ error: `name` already defined (should it be {interpolated} to define its content
at def.asm(42) at def.asm(42)
and also: and also:
at def.asm(40) at def.asm(40)
Assembly aborted with 4 errors! Assembly aborted with 4 errors

View File

@@ -52,4 +52,4 @@ warning: STRSUB: Position 12 is past the end of the string [-Wbuiltin-args]
at deprecated-functions.asm(28) at deprecated-functions.asm(28)
error: STRSUB: Incomplete UTF-8 character error: STRSUB: Incomplete UTF-8 character
at deprecated-functions.asm(28) at deprecated-functions.asm(28)
Assembly aborted with 6 errors! Assembly aborted with 6 errors

View File

@@ -2,4 +2,4 @@ error: syntax error, unexpected '-' at the beginning of the line (is it a leftov
at diff-marks.asm(2) at diff-marks.asm(2)
error: syntax error, unexpected '+' at the beginning of the line (is it a leftover diff mark?) error: syntax error, unexpected '+' at the beginning of the line (is it a leftover diff mark?)
at diff-marks.asm(3) at diff-marks.asm(3)
Assembly aborted with 2 errors! Assembly aborted with 2 errors

View File

@@ -0,0 +1,2 @@
section "test", rom0
def #... equs "sublocal"

View File

@@ -0,0 +1,3 @@
error: syntax error, unexpected local label, expecting symbol
at dots-constant.asm(2)
Assembly aborted with 1 error

View File

@@ -0,0 +1,2 @@
section "test", rom0
println "{...}"

View File

@@ -0,0 +1,2 @@
FATAL: `...` is a nonsensical reference to a nested local label
at dots-interpolate.asm(2)

2
test/asm/dots-label.asm Normal file
View File

@@ -0,0 +1,2 @@
section "test", rom0
#...:

2
test/asm/dots-label.err Normal file
View File

@@ -0,0 +1,2 @@
FATAL: `...` is a nonsensical reference to a nested local label
at dots-label.asm(2)

View File

@@ -0,0 +1,4 @@
MACRO test
println "\<...>"
ENDM
test 1, 2, 3, 4

View File

@@ -0,0 +1,2 @@
FATAL: `...` is a nonsensical reference to a nested local label
at dots-macro-arg.asm::test(2) <- dots-macro-arg.asm(4)

View File

@@ -1,3 +1,3 @@
error: Undefined symbol `n` was already purged error: Undefined symbol `n` was already purged
at double-purge.asm(3) at double-purge.asm(3)
Assembly aborted with 1 error! Assembly aborted with 1 error

View File

@@ -2,4 +2,4 @@ error: Expected constant expression: undefined symbol `unknown`
at ds-bad.asm(3) at ds-bad.asm(3)
warning: Expression must be 8-bit; use `LOW()` to force 8-bit [-Wtruncation] warning: Expression must be 8-bit; use `LOW()` to force 8-bit [-Wtruncation]
at ds-bad.asm(4) at ds-bad.asm(4)
Assembly aborted with 1 error! Assembly aborted with 1 error

View File

@@ -2,4 +2,4 @@ error: Invalid character '?' in bracketed macro argument
at empty-raw-identifier.asm::macro(3) <- empty-raw-identifier.asm(5) at empty-raw-identifier.asm::macro(3) <- empty-raw-identifier.asm(5)
error: Empty raw symbol in bracketed macro argument error: Empty raw symbol in bracketed macro argument
at empty-raw-identifier.asm::macro(3) <- empty-raw-identifier.asm(5) at empty-raw-identifier.asm::macro(3) <- empty-raw-identifier.asm(5)
Assembly aborted with 2 errors! Assembly aborted with 2 errors

View File

@@ -2,4 +2,4 @@ warning: `LOAD` block without `ENDL` terminated by `ENDSECTION` [-Wunterminated-
at endsection-in-load.asm(3) at endsection-in-load.asm(3)
error: Found `ENDL` outside of a `LOAD` block error: Found `ENDL` outside of a `LOAD` block
at endsection-in-load.asm(4) at endsection-in-load.asm(4)
Assembly aborted with 1 error! Assembly aborted with 1 error

View File

@@ -1,3 +1,3 @@
error: Cannot output data outside of a `SECTION` error: Cannot output data outside of a `SECTION`
at endsection.asm(4) at endsection.asm(4)
Assembly aborted with 1 error! Assembly aborted with 1 error

View File

@@ -2,4 +2,4 @@ error: `x` already defined
at error-limit.asm(2) at error-limit.asm(2)
and also: and also:
at error-limit.asm(1) at error-limit.asm(1)
Assembly aborted after the maximum of 1 error! (configure with '-X/--max-errors') Assembly aborted after the maximum of 1 error (configure with '-X/--max-errors')

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