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
set -euo pipefail
pngver=1.6.50
pngver=1.6.53
## Grab sources and check them
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.
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
echo Checksum mismatch! Aborting. >&2
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://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
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
set -euo pipefail
pngver=1.6.50
pngver=1.6.53
arch="$1"
## Grab sources and check them
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

View File

@@ -49,7 +49,7 @@ else()
-fsanitize=float-divide-by-zero)
add_compile_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
set(CMAKE_CXX_FLAGS_DEBUG "-g -Og -fno-omit-frame-pointer -fno-optimize-sibling-calls ${CMAKE_CXX_FLAGS_DEBUG}"
CACHE STRING "" FORCE)

View File

@@ -1,6 +1,6 @@
FROM debian:12-slim
LABEL org.opencontainers.image.source=https://github.com/gbdev/rgbds
ARG version=1.0.0
ARG version=1.0.1
WORKDIR /rgbds
COPY . .

View File

@@ -31,7 +31,7 @@ WARNFLAGS := -Wall -pedantic -Wno-unknown-warning-option -Wno-gnu-zero-variadic-
# Overridable CXXFLAGS
CXXFLAGS ?= -O3 -flto -DNDEBUG
# 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
LDFLAGS ?=
# Non-overridable LDFLAGS
@@ -222,8 +222,8 @@ develop:
-Wformat=2 -Wformat-overflow=2 -Wformat-truncation=1 \
-Wno-format-nonliteral -Wno-strict-overflow -Wno-unused-but-set-variable \
-Wno-type-limits -Wno-tautological-constant-out-of-range-compare -Wvla \
-D_GLIBCXX_ASSERTIONS -fsanitize=address -fsanitize=undefined \
-fsanitize=float-divide-by-zero" \
-D_GLIBCXX_ASSERTIONS -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_DEBUG \
-fsanitize=address -fsanitize=undefined -fsanitize=float-divide-by-zero" \
CXXFLAGS="-ggdb3 -Og -fno-omit-frame-pointer -fno-optimize-sibling-calls"
# Target used in development to debug with gdb.
@@ -254,7 +254,7 @@ tidy: src/asm/parser.hpp src/link/script.hpp
iwyu:
$Qenv ${MAKE} \
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.
# This is not for Windows users!

View File

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

View File

@@ -22,7 +22,7 @@ mkdir -p coverage
COVERAGE_INFO=coverage/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"
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
if [ "$1" != "ubuntu-ci" ]; then

View File

@@ -63,7 +63,7 @@ MacroArgs *fstk_GetCurrentMacroArgs();
void fstk_AddIncludePath(std::string const &path);
void fstk_AddPreIncludeFile(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 yywrap();
@@ -84,6 +84,6 @@ void fstk_RunFor(
bool fstk_Break();
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

View File

@@ -68,6 +68,8 @@ struct Symbol {
uint32_t getConstantValue() const;
};
bool sym_IsDotScope(std::string const &symName);
void sym_ForEach(void (*callback)(Symbol &));
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[];
enum ExportLevel { SYMTYPE_LOCAL, SYMTYPE_IMPORT, SYMTYPE_EXPORT };
enum ExportLevel { SYMTYPE_LOCAL, SYMTYPE_IMPORT, SYMTYPE_EXPORT, SYMTYPE_INVALID };
enum PatchType {
PATCHTYPE_BYTE,

View File

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

View File

@@ -3,17 +3,14 @@
#ifndef RGBDS_VERBOSITY_HPP
#define RGBDS_VERBOSITY_HPP
#include <stdarg.h>
#include <stdio.h>
#include "style.hpp"
// This macro does not evaluate its arguments unless the condition is true.
#define verbosePrint(level, ...) \
do { \
if (checkVerbosity(level)) { \
style_Set(stderr, STYLE_MAGENTA, false); \
fprintf(stderr, __VA_ARGS__); \
style_Reset(stderr); \
printVerbosely(__VA_ARGS__); \
} \
} while (0)
@@ -30,6 +27,9 @@ enum Verbosity {
void incrementVerbosity();
bool checkVerbosity(Verbosity level);
[[gnu::format(printf, 1, 2)]]
void printVerbosely(char const *fmt, ...);
void printVVVVVVerbosity();
#endif // RGBDS_VERBOSITY_HPP

View File

@@ -5,7 +5,7 @@
#define PACKAGE_VERSION_MAJOR 1
#define PACKAGE_VERSION_MINOR 0
#define PACKAGE_VERSION_PATCH 0
#define PACKAGE_VERSION_PATCH 1
// #define PACKAGE_VERSION_RC 1
char const *get_package_version_string();

View File

@@ -1,6 +1,6 @@
.\" SPDX-License-Identifier: MIT
.\"
.Dd October 31, 2025
.Dd January 1, 2026
.Dt GBZ80 7
.Os
.Sh NAME
@@ -11,6 +11,11 @@ This is the list of instructions supported by
.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.
.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
.Sy A
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
.Ar n16 .
.Pp
The address is encoded as a signed 8-bit offset from the address immediately following the
.Ic JR
instruction, so the target address
The target address
.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
and
.Sy 127
@@ -889,6 +896,18 @@ if condition
.Ar cc
is met.
.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
.Pp
Bytes: 2
@@ -992,8 +1011,15 @@ Flags: None affected.
Copy the value in register
.Sy A
into the byte at address
.Ar n16 ,
provided the address is between
.Ar n16 .
.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
and
.Ad $FFFF .
@@ -1043,8 +1069,15 @@ Flags: None affected.
Copy the byte at address
.Ar n16
into register
.Sy A ,
provided the address is between
.Sy A .
.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
and
.Ad $FFFF .

View File

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

View File

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

View File

@@ -2,7 +2,7 @@
.\"
.\" SPDX-License-Identifier: MIT
.\"
.Dd October 31, 2025
.Dd January 1, 2026
.Dt RGBASM 5
.Os
.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:
.Bl -bullet -offset indent
.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
.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
.Pp
Directives are commands to the assembler itself, such as

View File

@@ -1,6 +1,6 @@
.\" SPDX-License-Identifier: MIT
.\"
.Dd October 31, 2025
.Dd January 1, 2026
.Dt RGBDS 5
.Os
.Sh NAME
@@ -92,7 +92,7 @@ If the node is not a REPT node...
.Pp
.Bl -tag -width Ds -compact
.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 .
.El
.It Cm ELSE

View File

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

View File

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

View File

@@ -2,7 +2,7 @@
.\"
.\" SPDX-License-Identifier: MIT
.\"
.Dd October 31, 2025
.Dd January 1, 2026
.Dt RGBGFX 1
.Os
.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.
Both default to 0.
.It Fl C , Fl \-color-curve
When generating palettes, use a color curve mimicking the Game Boy Color's screen.
The resulting colors may look closer to the input image's
.Sy on hardware and accurate emulators .
Modifies the color palettes
.Pq whether they are generated from the input image or taken from an input palette specification
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
Use the specified color palettes instead of having
.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.
.Pp
.Ar slice
must be two number pairs, separated by a colon.
The numbers must be separated by commas; space is allowed around all punctuation.
must be formatted as
.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 second number pair specifies how many tiles to process horizontally and vertically, respectively.
.Pp

View File

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

View File

@@ -1,6 +1,6 @@
.\" SPDX-License-Identifier: MIT
.\"
.Dd October 31, 2025
.Dd January 1, 2026
.Dt RGBLINK 5
.Os
.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");
}
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
// missing file, so return `std::nullopt`, which tells the caller to `YYACCEPT`
return std::nullopt;

View File

@@ -57,44 +57,47 @@ static std::vector<std::string> includePaths = {""}; // -I
static std::deque<std::string> preIncludeNames; // -P
static bool failedOnMissingInclude = false;
using TraceNode = std::pair<std::string, uint32_t>;
static std::vector<TraceNode> backtrace(FileStackNode const &node, uint32_t curLineNo) {
if (node.isQuiet && !tracing.loud) {
if (node.parent) {
void FileStackNode::printBacktrace(uint32_t curLineNo) const {
using TraceItem = std::pair<FileStackNode const *, uint32_t>;
std::vector<TraceItem> items;
for (TraceItem item{this, curLineNo};;) {
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,
// which is more precise than the parent's own line number (since that will be
// 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) {
assume(node.type != NODE_REPT && std::holds_alternative<std::string>(node.data));
return {
{node.name(), curLineNo}
};
}
std::vector<TraceNode> traceNodes = backtrace(*node.parent, node.lineNo);
if (std::holds_alternative<std::vector<uint32_t>>(node.data)) {
assume(!traceNodes.empty()); // REPT nodes use their parent's name
std::string reptName = traceNodes.back().first;
if (std::vector<uint32_t> const &nodeIters = node.iters(); !nodeIters.empty()) {
reptName.append(NODE_SEPARATOR REPT_NODE_PREFIX);
reptName.append(std::to_string(nodeIters.front()));
using TraceNode = std::pair<std::string, uint32_t>;
std::vector<TraceNode> traceNodes;
traceNodes.reserve(items.size());
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;
if (std::vector<uint32_t> const &nodeIters = node->iters(); !nodeIters.empty()) {
reptName.append(NODE_SEPARATOR REPT_NODE_PREFIX);
reptName.append(std::to_string(nodeIters.front()));
}
traceNodes.emplace_back(reptName, itemLineNo);
} else {
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(
backtrace(*this, curLineNo),
traceNodes,
[](TraceNode const &node) { return node.first.c_str(); },
[](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) {
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;
}
@@ -348,17 +355,17 @@ static 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) {
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 {
failedOnMissingInclude = true;
// LCOV_EXCL_START
if (options.missingIncludeState == GEN_EXIT) {
verbosePrint(
VERB_NOTICE,
"Aborting due to '-MG' on `%s` file \"%s\": %s\n",
functionName,
"Aborting due to '-MG' on %s file \"%s\": %s\n",
description,
path.c_str(),
strerror(errno)
);
@@ -379,7 +386,7 @@ bool fstk_RunInclude(std::string const &path, bool isQuiet) {
newFileContext(*fullPath, isQuiet, false);
return false;
}
return fstk_FileError(path, "INCLUDE");
return fstk_FileError(path, "`INCLUDE`");
}
void fstk_RunMacro(
@@ -487,14 +494,16 @@ void fstk_NewRecursionDepth(size_t newDepth) {
options.maxRecursionDepth = newDepth;
}
void fstk_Init(std::string const &mainPath) {
bool fstk_Init(std::string const &mainPath) {
newFileContext(mainPath, false, true);
for (std::string const &name : preIncludeNames) {
if (std::optional<std::string> fullPath = fstk_FindFile(name); fullPath) {
newFileContext(*fullPath, false, false);
} else {
error("Error reading pre-included file \"%s\": %s", name.c_str(), strerror(errno));
} else if (fstk_FileError(name, "pre-included")) {
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
if (identifier.find_first_not_of('.') == identifier.npos) {
if (sym_IsDotScope(identifier)) {
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`!
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
if (isWhitespace(c)) {
return false;
@@ -1645,7 +1649,7 @@ static bool isGarbageCharacter(int c) {
return true;
}
// 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);
}
@@ -1935,11 +1939,12 @@ static Token yylex_NORMAL() {
// `token` is either a `SYMBOL` or a `LOCAL`, and both have a `std::string` 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
if (!raw && token.type == T_(SYMBOL) && lexerState->expandStrings) {
// 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) {
beginExpansion(sym->getEqus(), sym->name);
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`)
// - quiet macro invocations (which are followed by a '?' and use the token `QMACRO`)
// - 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
// 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.
// 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.
if (token.type == T_(SYMBOL)) {
if (token.type == T_(SYMBOL) && !sym_IsDotScope(identifier)) {
c = peek();
token.type = c == ':' ? T_(LABEL) : c == '?' ? T_(QMACRO) : T_(SYMBOL);
}

View File

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

View File

@@ -120,9 +120,8 @@ Section *sect_FindSectionByName(std::string const &name) {
++nbSectErrors; \
} while (0)
static unsigned int mergeSectUnion(
Section &sect, SectionType type, uint32_t org, uint8_t alignment, uint16_t alignOffset
) {
static unsigned int
mergeSectUnion(Section &sect, uint32_t org, uint8_t alignment, uint16_t alignOffset) {
unsigned int nbSectErrors = 0;
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 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 both are fixed, they must be the same
if (sect.org != UINT32_MAX && sect.org != org) {
@@ -266,12 +259,10 @@ static void mergeSections(
} else {
switch (mod) {
case SECTION_UNION:
case SECTION_FRAGMENT:
nbSectErrors += mod == SECTION_UNION
? mergeSectUnion(sect, type, org, alignment, alignOffset)
: mergeFragments(sect, org, alignment, alignOffset);
// Common checks
case SECTION_FRAGMENT: {
unsigned int (*merge)(Section &, uint32_t, uint8_t, uint16_t) =
mod == SECTION_UNION ? mergeSectUnion : mergeFragments;
nbSectErrors += merge(sect, org, alignment, alignOffset);
// If the section's bank is unspecified, override it
if (sect.bank == UINT32_MAX) {
@@ -282,6 +273,7 @@ static void mergeSections(
sectError("Section already declared with different bank %" PRIu32, sect.bank);
}
break;
}
case SECTION_NORMAL:
errorNoTrace([&]() {
@@ -513,6 +505,11 @@ void sect_NewSection(
}
}
if (mod == SECTION_UNION && sectTypeHasData(type)) {
error("Cannot declare ROM sections as `UNION`");
return;
}
if (currentLoadSection) {
sect_EndLoadSection("SECTION");
}
@@ -932,7 +929,7 @@ bool sect_BinaryFile(std::string const &name, uint32_t startPos) {
file = fopen(fullPath->c_str(), "rb");
}
if (!file) {
return fstk_FileError(name, "INCBIN");
return fstk_FileError(name, "`INCBIN`");
}
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");
}
if (!file) {
return fstk_FileError(name, "INCBIN");
return fstk_FileError(name, "`INCBIN`");
}
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) {
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
currentSection->modifier = SECTION_FRAGMENT;

View File

@@ -53,6 +53,12 @@ bool sym_IsPC(Symbol const *sym) {
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 &)) {
for (auto &it : symbols) {
callback(it.second);
@@ -215,8 +221,8 @@ static void redefinedError(Symbol const &sym) {
static void assumeAlreadyExpanded(std::string const &symName) {
// Either the symbol name is `Global.local` or entirely '.'s (for scopes `.` and `..`),
// but cannot be unqualified `.local`
assume(!symName.starts_with('.') || symName.find_first_not_of('.') == symName.npos);
// but cannot be unqualified `.local` or more than two '.'s
assume(!symName.starts_with('.') || sym_IsDotScope(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
if (dotPos == 0 && symName.find_first_not_of('.') == symName.npos) {
if (sym_IsDotScope(symName)) {
return false;
}
@@ -581,7 +587,7 @@ static uint32_t anonLabelID = 0;
Symbol *sym_AddAnonLabel() {
if (anonLabelID == UINT32_MAX) {
// LCOV_EXCL_START
error("Only %" PRIu32 " anonymous labels can be created!", anonLabelID);
error("Only %" PRIu32 " anonymous labels can be created", anonLabelID);
return nullptr;
// LCOV_EXCL_STOP
}

View File

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

View File

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

View File

@@ -19,7 +19,6 @@
#include "style.hpp"
#include "usage.hpp"
#include "util.hpp"
#include "version.hpp"
#include "fix/fix.hpp"
#include "fix/mbc.hpp"
@@ -252,7 +251,7 @@ static void parseArg(int ch, char *arg) {
// LCOV_EXCL_START
case 'V':
printf("rgbfix %s\n", get_package_version_string());
usage.printVersion(false);
exit(0);
case 'v':

View File

@@ -23,7 +23,6 @@
#include "usage.hpp"
#include "util.hpp"
#include "verbosity.hpp"
#include "version.hpp"
#include "gfx/pal_spec.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);
return errVal;
} else if (*number > UINT16_MAX) {
error("%s: the number is too large!", errPrefix);
error("%s: the number is too large", errPrefix);
return errVal;
} else {
return *number;
@@ -240,7 +239,7 @@ static void parseArg(int ch, char *arg) {
case 'L':
options.inputSlice.left = readNumber(argPtr, "Input slice left coordinate");
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;
}
skipBlankSpace(argPtr);
@@ -261,7 +260,7 @@ static void parseArg(int ch, char *arg) {
options.inputSlice.width = readNumber(argPtr, "Input slice width");
skipBlankSpace(argPtr);
if (options.inputSlice.width == 0) {
error("Input slice width may not be 0!");
error("Input slice width may not be 0");
}
if (*argPtr != ',') {
error("Missing comma after width in \"%s\"", arg);
@@ -271,7 +270,7 @@ static void parseArg(int ch, char *arg) {
skipBlankSpace(argPtr);
options.inputSlice.height = readNumber(argPtr, "Input slice height");
if (options.inputSlice.height == 0) {
error("Input slice height may not be 0!");
error("Input slice height may not be 0");
}
if (*argPtr != '\0') {
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);
}
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) {
error("Number of palettes ('-n') may not be 0!");
error("Number of palettes ('-n') may not be 0");
} else {
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);
}
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) {
error("Palette size ('-s') may not be 0!");
error("Palette size ('-s') may not be 0");
}
break;
@@ -409,7 +408,7 @@ static void parseArg(int ch, char *arg) {
// LCOV_EXCL_START
case 'V':
printf("rgbgfx %s\n", get_package_version_string());
usage.printVersion(false);
exit(0);
case 'v':
@@ -481,7 +480,7 @@ static void verboseOutputConfig() {
style_Set(stderr, STYLE_MAGENTA, false);
fprintf(stderr, "rgbgfx %s\n", get_package_version_string());
usage.printVersion(true);
printVVVVVVerbosity();
@@ -609,7 +608,7 @@ static void verboseOutputConfig() {
if (localOptions.reverse) {
fprintf(stderr, "\tReverse image width: %" PRIu16 " tiles\n", options.reversedWidth);
}
fputs("Ready.\n", stderr);
fputs("Ready for conversion\n", stderr);
style_Reset(stderr);
}

View File

@@ -68,7 +68,7 @@ public:
size_t size() const {
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; }
@@ -84,7 +84,13 @@ struct Image {
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]; }
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"
if (colors.size() > options.maxOpaqueColors()) { // Apply the Pigeonhole Principle
verbosePrint(
@@ -93,7 +99,7 @@ struct Image {
colors.size(),
options.maxOpaqueColors()
);
return false;
return {GrayscaleResult::GRAY_TOO_MANY, std::nullopt};
}
uint8_t bins = 0;
for (std::optional<Rgba> const &color : colors) {
@@ -106,7 +112,7 @@ struct Image {
"Found non-gray color #%08x, not using grayscale sorting\n",
color->toCSS()
);
return false;
return {GrayscaleResult::GRAY_NONGRAY, color};
}
uint8_t mask = 1 << color->grayIndex();
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->toCSS()
);
return false;
return {GrayscaleResult::GRAY_CONFLICT, color};
}
bins |= mask;
}
return true;
return {GrayscaleResult::GRAY_OK, std::nullopt};
}
explicit Image(std::string const &path) {
@@ -132,15 +138,15 @@ struct Image {
// Validate input slice
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) {
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) {
error(
"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.top,
options.inputSlice.right(),
@@ -151,8 +157,8 @@ struct Image {
if (options.inputSlice.width % 8 == 0 && options.inputSlice.height % 8 == 0) {
fprintf(
stderr,
"note: Did you mean the slice \"%" PRIu32 ",%" PRIu32 ":%" PRId32 ",%" PRId32
"\"? (width and height are in tiles, not pixels!)\n",
" (Did you mean the slice \"%" PRIu32 ",%" PRIu32 ":%" PRId32 ",%" PRId32
"\"? The width and height are in tiles, not pixels!)\n",
options.inputSlice.left,
options.inputSlice.top,
options.inputSlice.width / 8,
@@ -181,7 +187,7 @@ struct Image {
if (uint32_t css = color.toCSS(); ambiguous.find(css) == ambiguous.end()) {
error(
"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,
Rgba::transparency_threshold,
Rgba::opacity_threshold,
@@ -195,8 +201,8 @@ struct Image {
if (std::pair fused{color.toCSS(), other->toCSS()};
fusions.find(fused) == fusions.end()) {
warnx(
"Fusing colors #%08x and #%08x into Game Boy color $%04x [first seen "
"at x: %" PRIu32 ", y: %" PRIu32 "]",
"Colors #%08x and #%08x both reduce to the same Game Boy color $%04x "
"(first seen at (%" PRIu32 ", %" PRIu32 "))",
fused.first,
fused.second,
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
std::vector<Rgba> const &embPal = image.png.palette;
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
@@ -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'"
);
sortIndexed(palettes, image.png.palette);
} else if (image.isSuitableForGrayscale()) {
} else if (image.isSuitableForGrayscale().first == Image::GRAY_OK) {
sortGrayscale(palettes, image.colors.raw());
} else {
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 (size_t i = 0; i < options.nbColorsPerPal; ++i) {
// 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();
}
}
@@ -821,7 +827,7 @@ static UniqueTiles dedupTiles(
if (inputWithoutOutput && matchType == TileData::NOPE) {
error(
"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.y
);
@@ -939,14 +945,24 @@ void process() {
// LCOV_EXCL_STOP
if (options.palSpecType == Options::DMG) {
char const *prefix =
"Image is not compatible with a DMG palette specification: it contains";
if (options.hasTransparentPixels) {
fatal(
"Image contains transparent pixels, not compatible with a DMG palette specification"
);
fatal("%s transparent pixels", prefix);
}
if (!image.isSuitableForGrayscale()) {
fatal("Image contains too many or non-gray colors, not compatible with a DMG palette "
"specification");
switch (auto const [result, color] = image.isSuitableForGrayscale(); result) {
case Image::GRAY_OK:
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 x = 0; x < 8; ++x) {
if (Rgba color = tile.pixel(x, y);
!color.isTransparent() || !options.hasTransparentPixels) {
color.isOpaque() || !options.hasTransparentPixels) {
tileColors.insert(color.cgbColor());
}
}
@@ -973,7 +989,7 @@ void process() {
if (tileColors.size() > options.maxOpaqueColors()) {
fatal(
"Tile at (%" PRIu32 ", %" PRIu32 ") has %zu colors, more than %" PRIu8 "!",
"Tile at (%" PRIu32 ", %" PRIu32 ") has %zu colors, more than %" PRIu8,
tile.x,
tile.y,
tileColors.size(),
@@ -1001,7 +1017,7 @@ void process() {
continue;
}
fatal(
"Tile (%" PRIu32 ", %" PRIu32 ") contains the background color (#%08x)!",
"Tile (%" PRIu32 ", %" PRIu32 ") contains the background color (#%08x)",
tile.x,
tile.y,
options.bgColor->toCSS()

View File

@@ -112,15 +112,15 @@ void reverse() {
// Check for weird flag combinations
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()) {
warnx("Tile deduplication is enabled, but no tilemap is provided?");
warnx("Tile deduplication is enabled, but no tilemap is provided");
}
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
@@ -149,13 +149,13 @@ void reverse() {
// By default, assume tiles are not deduplicated, and add the (allegedly) trimmed tiles
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
std::optional<std::vector<uint8_t>> tilemap;
if (!options.tilemap.empty()) {
tilemap = readInto(options.tilemap);
mapSize = tilemap->size();
verbosePrint(VERB_INFO, "Read %zu tilemap entries.\n", mapSize);
verbosePrint(VERB_INFO, "Read %zu tilemap entries\n", mapSize);
}
if (mapSize == 0) {
@@ -224,7 +224,7 @@ void reverse() {
break;
} else if (nbRead != buf.size()) {
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,
buf.size()
);
@@ -250,7 +250,7 @@ void reverse() {
}
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
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) {
@@ -272,7 +272,7 @@ void reverse() {
}
} else if (options.palSpecType == Options::EMBEDDED) {
warnx("An embedded palette was requested, but no palette file was specified; ignoring "
"request.");
"request");
} else if (options.palSpecType == Options::EXPLICIT) {
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()) {
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,
tx,
ty,

View File

@@ -36,19 +36,6 @@ struct FreeSpace {
// Table of free space for each bank
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
static void assignSection(Section &section, MemoryLocation const &location) {
// 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];
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
std::deque<FreeSpace> &bankMem = memory[section.type][location.bank - typeInfo.firstBank];
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) {
std::string where;
char bank[8], addr[8], mask[8], offset[8];
std::string description =
"\"" + section.name + "\" (" + sectionTypeInfo[section.type].name + " section) ";
if (section.isBankFixed && sectTypeBanks(section.type) != 1) {
char bank[8];
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) {
where = "at $";
where += bank;
where += ":";
where += addr;
char addr[8];
snprintf(addr, sizeof(addr), "%04" PRIx16, section.org);
description = description + "at $" + bank + ":" + addr;
} else if (section.isAlignFixed) {
where = "in bank $";
where += bank;
where += " with align mask $";
where += mask;
char mask[8];
snprintf(mask, sizeof(mask), "%" PRIx16, static_cast<uint16_t>(~section.alignMask));
description = description + "in bank $" + bank + " with align mask $" + mask;
} else {
where = "in bank $";
where += bank;
description = description + "in bank $" + bank;
}
} else {
if (section.isAddressFixed) {
where = "at address $";
where += addr;
char addr[8];
snprintf(addr, sizeof(addr), "%04" PRIx16, section.org);
description = description + "at address $" + addr;
} else if (section.isAlignFixed) {
where = "with align mask $";
where += mask;
where += " and offset $";
where += offset;
char mask[8], offset[8];
snprintf(mask, sizeof(mask), "%" PRIx16, static_cast<uint16_t>(~section.alignMask));
snprintf(offset, sizeof(offset), "%" PRIx16, section.alignOfs);
description = description + "with align mask $" + mask + " and offset $" + offset;
} else {
where = "anywhere";
description = description + "anywhere";
}
}
return where;
return description;
}
// 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 a section failed to go to several places, nothing we can report
fatal(
"Unable to place \"%s\" (%s section) %s",
section.name.c_str(),
sectionTypeInfo[section.type].name.c_str(),
getSectionDescription(section).c_str()
);
fatal("Unable to place %s", getSectionDescription(section).c_str());
} else if (section.org + section.size > sectTypeEndAddr(section.type) + 1) {
// If the section just can't fit the bank, report that
fatal(
"Unable to place \"%s\" (%s section) %s: section runs past end of region ($%04x > "
"$%04x)",
section.name.c_str(),
sectionTypeInfo[section.type].name.c_str(),
"Unable to place %s: section runs past end of region ($%04x > $%04x)",
getSectionDescription(section).c_str(),
section.org + section.size,
sectTypeEndAddr(section.type) + 1
@@ -330,9 +306,7 @@ static void placeSection(Section &section) {
} else {
// Otherwise there is overlap with another section
fatal(
"Unable to place \"%s\" (%s section) %s: section overlaps with \"%s\"",
section.name.c_str(),
sectionTypeInfo[section.type].name.c_str(),
"Unable to place %s: section overlaps with \"%s\"",
getSectionDescription(section).c_str(),
out_OverlappingSection(section)->name.c_str()
);
@@ -381,36 +355,74 @@ static void categorizeSection(Section &section) {
sections.insert(pos, &section);
}
static std::vector<Section const *> checkOverlayCompat() {
std::vector<Section const *> unfixedSections;
static void checkOverlayCompat() {
auto isFixed = [](uint8_t constraints) {
return (constraints & BANK_CONSTRAINED) && (constraints & ORG_CONSTRAINED);
};
if (!options.overlayFileName) {
return unfixedSections;
std::string unfixedList;
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--;) {
if (((constraints & BANK_CONSTRAINED) && (constraints & ORG_CONSTRAINED))
|| unassignedSections[constraints].empty()) {
if (isFixed(constraints)) {
continue;
}
for (Section *section : unassignedSections[constraints]) {
unfixedSections.push_back(section);
if (unfixedSections.size() == 10) {
return unfixedSections;
for (Section const *section : unassignedSections[constraints]) {
if (nbListed == 10) {
unfixedList += "\n- and ";
unfixedList += std::to_string(nbUnfixedSections - nbListed);
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() {
verbosePrint(VERB_NOTICE, "Beginning assignment...\n");
// Initialize assignment
initFreeSpace();
// Initialize the free space-modelling structs
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
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
if (std::vector<Section const *> unfixedSections = checkOverlayCompat();
!unfixedSections.empty()) {
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()
);
if (options.overlayFileName) {
checkOverlayCompat();
}
// Assign sections in decreasing constraint order

View File

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

View File

@@ -21,7 +21,6 @@
#include "usage.hpp"
#include "util.hpp" // UpperMap, printChar
#include "verbosity.hpp"
#include "version.hpp"
#include "link/assign.hpp"
#include "link/lexer.hpp"
@@ -283,7 +282,7 @@ static void parseArg(int ch, char *arg) {
// LCOV_EXCL_START
case 'V':
printf("rgblink %s\n", get_package_version_string());
usage.printVersion(false);
exit(0);
case 'v':
@@ -330,7 +329,7 @@ static void verboseOutputConfig() {
style_Set(stderr, STYLE_MAGENTA, false);
fprintf(stderr, "rgblink %s\n", get_package_version_string());
usage.printVersion(true);
printVVVVVVerbosity();
@@ -408,7 +407,7 @@ static void verboseOutputConfig() {
}
// -n/--sym
printPath("Output sym file", options.symFileName);
fputs("Ready.\n", stderr);
fputs("Ready for linking\n", stderr);
style_Reset(stderr);
}
@@ -447,11 +446,9 @@ int main(int argc, char *argv[]) {
if (localOptions.linkerScriptName) {
verbosePrint(VERB_NOTICE, "Reading linker script...\n");
if (lexer_Init(*localOptions.linkerScriptName)) {
if (yy::parser parser; parser.parse() != 0) {
// Exited due to YYABORT or YYNOMEM
fatal("Unrecoverable error while reading linker script"); // LCOV_EXCL_LINE
}
if (yy::parser parser; lexer_Init(*localOptions.linkerScriptName) && parser.parse() != 0) {
// Exited due to YYABORT or YYNOMEM
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
@@ -468,4 +465,6 @@ int main(int argc, char *argv[]) {
patch_ApplyPatches();
requireZeroErrors();
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__)
// 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.
#define tryReadString(var, file, ...) \
@@ -100,27 +100,31 @@ static void readFileStackNode(
tryReadLong(
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(
node.lineNo, file, "%s: Cannot read node #%" PRIu32 "'s line number: %s", fileName, nodeID
);
uint8_t type;
tryGetc(uint8_t, type, file, "%s: Cannot read node #%" PRIu32 "'s type: %s", fileName, nodeID);
node.type = static_cast<FileStackNodeType>(type & ~(1 << FSTACKNODE_QUIET_BIT));
node.isQuiet = (type & (1 << FSTACKNODE_QUIET_BIT)) != 0;
switch (node.type) {
tryGetc(type, file, "%s: Cannot read node #%" PRIu32 "'s type: %s", fileName, nodeID);
switch (type & ~(1 << FSTACKNODE_QUIET_BIT)) {
case NODE_FILE:
case NODE_MACRO:
node.type = FileStackNodeType(type);
node.data = "";
tryReadString(
node.name(), file, "%s: Cannot read node #%" PRIu32 "'s file name: %s", fileName, nodeID
);
break;
case NODE_REPT: {
node.type = NODE_REPT;
uint32_t depth;
tryReadLong(
depth, file, "%s: Cannot read node #%" PRIu32 "'s REPT depth: %s", fileName, nodeID
@@ -143,8 +147,13 @@ static void readFileStackNode(
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.
@@ -152,20 +161,25 @@ static void readSymbol(
FILE *file, Symbol &symbol, char const *fileName, std::vector<FileStackNode> const &fileNodes
) {
tryReadString(symbol.name, file, "%s: Cannot read symbol name: %s", fileName);
tryGetc(
ExportLevel,
symbol.type,
file,
"%s: Cannot read `%s`'s type: %s",
fileName,
symbol.name.c_str()
);
uint8_t type;
tryGetc(type, file, "%s: Cannot read `%s`'s type: %s", fileName, symbol.name.c_str());
if (type >= SYMTYPE_INVALID) {
fatal("%s: `%s` has unknown type 0x%02x", fileName, symbol.name.c_str(), type);
} else {
symbol.type = ExportLevel(type);
}
// If the symbol is defined in this file, read its definition
if (symbol.type != SYMTYPE_IMPORT) {
uint32_t nodeID;
tryReadLong(
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];
tryReadLong(
symbol.lineNo,
@@ -212,6 +226,15 @@ static void readPatch(
sectName.c_str(),
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];
tryReadLong(
@@ -247,9 +270,8 @@ static void readPatch(
patchID
);
PatchType type;
uint8_t type;
tryGetc(
PatchType,
type,
file,
"%s: Cannot read \"%s\"'s patch #%" PRIu32 "'s type: %s",
@@ -257,7 +279,17 @@ static void readPatch(
sectName.c_str(),
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;
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.
static void readSection(
FILE *file, Section &section, char const *fileName, std::vector<FileStackNode> const &fileNodes
@@ -296,11 +321,16 @@ static void readSection(
uint8_t byte;
tryReadString(section.name, file, "%s: Cannot read section name: %s", fileName);
uint32_t nodeID;
tryReadLong(
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];
tryReadLong(
section.lineNo,
file,
@@ -310,18 +340,23 @@ static void readSection(
);
tryReadLong(tmp, file, "%s: Cannot read \"%s\"'s' size: %s", fileName, section.name.c_str());
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.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) {
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 {
section.type = SectionType(type);
}
if (byte & (1 << SECTTYPE_UNION_BIT)) {
section.modifier = SECTION_UNION;
} 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());
section.isBankFixed = tmp >= 0;
section.bank = tmp;
tryGetc(
uint8_t,
byte,
file,
"%s: Cannot read \"%s\"'s alignment: %s",
fileName,
section.name.c_str()
);
tryGetc(byte, file, "%s: Cannot read \"%s\"'s alignment: %s", fileName, section.name.c_str());
if (byte > 16) {
byte = 16;
}
@@ -420,19 +448,15 @@ void obj_ReadFile(std::string const &filePath, size_t fileID) {
}
Defer closeFile{[&] { fclose(file); }};
// First, check if the object is a RGBDS object or a SDCC one. If the first byte is 'R',
// we'll assume it's a RGBDS object file, and otherwise, that it's a SDCC object file.
int c = getc(file);
ungetc(c, file); // Guaranteed to work
switch (c) {
// First, check if the object is a RGBDS object, a SDCC one, or neither.
// A single `ungetc` is guaranteed to work.
switch (ungetc(getc(file), file)) {
case EOF:
fatal("File \"%s\" is empty!", fileName);
fatal("File \"%s\" is empty", fileName);
case 'R':
break;
default:
case 'X':
case 'D':
case 'Q': {
// 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
// object file. It's better than nothing.
@@ -450,11 +474,16 @@ void obj_ReadFile(std::string const &filePath, size_t fileID) {
return;
}
// Begin by reading the magic bytes
int matchedElems;
case 'R':
// 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
&& matchedElems != literal_strlen(RGBDS_OBJECT_VERSION_STRING)) {
default:
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]);
sym_AddSymbol(sym);
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();
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;
}
// Give patches' PC section pointers to their sections
for (std::unique_ptr<Section> const &sect : fileSections) {
if (sectTypeHasData(sect->type)) {
for (Patch &patch : sect->patches) {
linkPatchToPCSect(patch, fileSections);
if (!sectTypeHasData(sect->type)) {
continue;
}
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 minNbBanks = targetBank + 1;
if (minNbBanks > maxNbBanks[section.type]) {
if (targetBank >= maxNbBanks[section.type]) {
fatal(
"Section \"%s\" has an invalid bank range (%" PRIu32 " > %" PRIu32 ")",
section.name.c_str(),
@@ -81,16 +79,14 @@ void out_AddSection(Section const &section) {
maxNbBanks[section.type] - 1
);
}
for (uint32_t i = sections[section.type].size(); i < minNbBanks; ++i) {
sections[section.type].emplace_back();
if (sections[section.type].size() <= targetBank) {
sections[section.type].resize(targetBank + 1);
}
// Insert section while keeping the list sorted by increasing org
std::deque<Section const *> &bankSections =
section.size ? sections[section.type][targetBank].sections
: sections[section.type][targetBank].zeroLenSections;
// Insert section while keeping the list sorted by increasing org
auto pos = bankSections.begin();
while (pos != bankSections.end() && (*pos)->org < section.org) {
++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) {
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];
// 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);
break;
case RPN_BANK_SYM:
value = 0;
case RPN_BANK_SYM: {
uint32_t symID = 0;
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(
patch,
"Requested `BANK()` of undefined symbol `%s`",
fileSymbols[value].name.c_str()
fileSymbols[symID].name.c_str()
);
isError = true;
value = 1;
@@ -290,12 +293,13 @@ static int32_t computeRPNExpr(Patch const &patch, std::vector<Symbol> const &fil
errorAt(
patch,
"Requested `BANK()` of non-label symbol `%s`",
fileSymbols[value].name.c_str()
fileSymbols[symID].name.c_str()
);
isError = true;
value = 1;
}
break;
}
case RPN_BANK_SECT: {
// `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;
case RPN_SYM:
value = 0;
case RPN_SYM: {
uint32_t symID = 0;
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) {
value = patch.pcOffset + patch.pcSection->org;
} else {
@@ -434,9 +438,12 @@ static int32_t computeRPNExpr(Patch const &patch, std::vector<Symbol> const &fil
value = 0;
isError = true;
}
} else if (Symbol const *symbol = getSymbol(fileSymbols, value); !symbol) {
errorAt(patch, "Undefined symbol `%s`", fileSymbols[value].name.c_str());
sym_TraceLocalAliasedSymbols(fileSymbols[value].name);
} else if (symID >= fileSymbols.size()) {
fatalAt(patch, "Invalid symbol ID #%" PRIu32, symID);
} 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;
} else if (std::holds_alternative<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;
}
}
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) {
fatalAt(
where,
"Area \"%s\" is larger than the GB address space!?",
"Area \"%s\" is larger than the GB address space",
curSection->name.c_str()
);
}

View File

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

View File

@@ -9,6 +9,7 @@
#include "helpers.hpp"
#include "platform.hpp"
#include "style.hpp"
#include "version.hpp"
#if defined(_MSC_VER) || defined(__MINGW32__)
#define WIN32_LEAN_AND_MEAN // Include less from `windows.h`
@@ -17,6 +18,10 @@
#include <sys/ioctl.h>
#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 {
FILE *file;
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() {
if (!checkVerbosity(VERB_VVVVVV)) {
return;

View File

@@ -14,7 +14,13 @@
#if !defined(NDEBUG) && defined(__SANITIZE_ADDRESS__) && !defined(__APPLE__)
extern "C" {
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

View File

@@ -1,3 +1,3 @@
error: The absolute alignment offset (2) must be less than alignment size (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)
error: Alignment must be between 0 and 16, not 17
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)
error: The absolute alignment offset (20) must be less than alignment size (16)
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`
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
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)
error: syntax error, unexpected ::
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
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
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
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)
error: `BANK` argument must be a label
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)
error: syntax error, unexpected end of buffer
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
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)
error: Bracketed symbol `abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz` does not exist
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)
error: PC does not have a constant value; the current section is not fixed
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)
error: Built-in symbol `__ISO_8601_UTC__` cannot be redefined
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)
error: `.` has no value outside of a label scope
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)
error: Unterminated string
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)
error: Invalid character '\t' in bracketed macro argument
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)
error: Character literals must be a single charmap unit
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)
error: syntax error, unexpected end of line
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`
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)
error: CHARSIZE: No character mapping for "é"
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)
warning: CHARVAL: Index 10 is past the end of the character mapping [-Wbuiltin-args]
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)
and also:
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`
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)
error: Expected constant expression: `Floating` is not constant at assembly time
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)
error: Assertion failed
at const-low.asm(10)
Assembly aborted with 2 errors!
Assembly aborted with 2 errors

View File

@@ -1,3 +1,3 @@
error: Assertion failed
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
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)
error: Section "data" cannot contain code or data (not `ROM0` or `ROMX`)
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)
error: syntax error, unexpected local label, expecting symbol
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)
and also:
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)
error: STRSUB: Incomplete UTF-8 character
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)
error: syntax error, unexpected '+' at the beginning of the line (is it a leftover diff mark?)
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
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)
warning: Expression must be 8-bit; use `LOW()` to force 8-bit [-Wtruncation]
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)
error: Empty raw symbol in bracketed macro argument
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)
error: Found `ENDL` outside of a `LOAD` block
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`
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)
and also:
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