mirror of
https://github.com/gbdev/rgbds.git
synced 2025-11-21 02:32:06 +00:00
Compare commits
39 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1715f85d50 | ||
|
|
c2de0a991a | ||
|
|
2e6e1ccf06 | ||
|
|
081f48404c | ||
|
|
bdac0ce053 | ||
|
|
122d91509f | ||
|
|
7c6f778ae7 | ||
|
|
8cf6c5423a | ||
|
|
56f7222230 | ||
|
|
e45b9625ca | ||
|
|
e0a8eb8aff | ||
|
|
2a5b9b5f98 | ||
|
|
a72843748f | ||
|
|
762e2311d2 | ||
|
|
0b7cda9e0c | ||
|
|
df83bc31d2 | ||
|
|
bc8d99d915 | ||
|
|
c841672059 | ||
|
|
75b605797d | ||
|
|
00d0ae840d | ||
|
|
2cdbb145da | ||
|
|
d8192560b0 | ||
|
|
9b395f3bf1 | ||
|
|
0150eb4bf3 | ||
|
|
632342b254 | ||
|
|
c9060c7f2d | ||
|
|
993879a2ed | ||
|
|
62309d5c87 | ||
|
|
b2e865ee2a | ||
|
|
3feb75f84f | ||
|
|
ad4d9da4cf | ||
|
|
1489854932 | ||
|
|
2aef09c8d9 | ||
|
|
48412e9c56 | ||
|
|
177a3abfac | ||
|
|
4c916b8da8 | ||
|
|
d9d381cb62 | ||
|
|
fbde24ee17 | ||
|
|
91310c9eb6 |
12
.github/workflows/checkformat.yml
vendored
Normal file
12
.github/workflows/checkformat.yml
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
name: Code format checking
|
||||
on: pull_request
|
||||
|
||||
jobs:
|
||||
checkformat:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
- name: Check format
|
||||
run: |
|
||||
./contrib/checkformat.bash
|
||||
@@ -100,7 +100,7 @@ jobs:
|
||||
path: rgbds-${{ env.version }}-macos.zip
|
||||
|
||||
linux:
|
||||
runs-on: ubuntu-20.04 # Oldest supported, for best glibc compatibility.
|
||||
runs-on: ubuntu-22.04 # Oldest supported, for best glibc compatibility.
|
||||
steps:
|
||||
- name: Get version from tag
|
||||
shell: bash
|
||||
@@ -112,7 +112,7 @@ jobs:
|
||||
- name: Install deps
|
||||
shell: bash
|
||||
run: |
|
||||
./.github/scripts/install_deps.sh ubuntu-20.04
|
||||
./.github/scripts/install_deps.sh ubuntu-22.04
|
||||
- name: Build binaries
|
||||
run: |
|
||||
make -kj WARNFLAGS="-Wall -Wextra -pedantic -static" PKG_CONFIG="pkg-config --static" Q=
|
||||
|
||||
2
.github/workflows/testing.yml
vendored
2
.github/workflows/testing.yml
vendored
@@ -7,7 +7,7 @@ jobs:
|
||||
unix:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-20.04, ubuntu-22.04, macos-14]
|
||||
os: [ubuntu-22.04, macos-14]
|
||||
cxx: [g++, clang++]
|
||||
buildsys: [make, cmake]
|
||||
exclude:
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -14,4 +14,5 @@ CMakeCache.txt
|
||||
CMakeFiles/
|
||||
cmake_install.cmake
|
||||
build/
|
||||
*.dSYM/
|
||||
callgrind.out.*
|
||||
|
||||
@@ -51,16 +51,15 @@ else()
|
||||
add_link_options(${SAN_FLAGS})
|
||||
add_definitions(-D_GLIBCXX_ASSERTIONS)
|
||||
# A non-zero optimization level is desired in debug mode, but allow overriding it nonetheless
|
||||
# TODO: this overrides anything previously set... that's a bit sloppy!
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "-g -Og -fno-omit-frame-pointer -fno-optimize-sibling-calls" CACHE STRING "" FORCE)
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "-g -Og -fno-omit-frame-pointer -fno-optimize-sibling-calls ${CMAKE_CXX_FLAGS_DEBUG}"
|
||||
CACHE STRING "" FORCE)
|
||||
endif()
|
||||
|
||||
if(MORE_WARNINGS)
|
||||
add_compile_options(-Werror -Wextra
|
||||
-Walloc-zero -Wcast-align -Wcast-qual -Wduplicated-branches -Wduplicated-cond
|
||||
-Wfloat-equal -Wlogical-op -Wnull-dereference -Wold-style-cast -Wshift-overflow=2
|
||||
-Wstringop-overflow=4 -Wundef -Wuninitialized -Wunused
|
||||
-Wshadow # TODO: -Wshadow=compatible-local?
|
||||
-Wstringop-overflow=4 -Wtrampolines -Wundef -Wuninitialized -Wunused -Wshadow
|
||||
-Wformat=2 -Wformat-overflow=2 -Wformat-truncation=1
|
||||
-Wno-format-nonliteral -Wno-strict-overflow
|
||||
-Wno-unused-but-set-variable # bison's `yynerrs_` is incremented but unused
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
FROM debian:12-slim
|
||||
LABEL org.opencontainers.image.source=https://github.com/gbdev/rgbds
|
||||
ARG version=0.9.1
|
||||
ARG version=0.9.2
|
||||
WORKDIR /rgbds
|
||||
|
||||
COPY . .
|
||||
|
||||
2
Makefile
2
Makefile
@@ -202,7 +202,7 @@ develop:
|
||||
$Q${MAKE} WARNFLAGS="${WARNFLAGS} -Werror -Wextra \
|
||||
-Walloc-zero -Wcast-align -Wcast-qual -Wduplicated-branches -Wduplicated-cond \
|
||||
-Wfloat-equal -Wlogical-op -Wnull-dereference -Wold-style-cast -Wshift-overflow=2 \
|
||||
-Wstringop-overflow=4 -Wundef -Wuninitialized -Wunused -Wshadow \
|
||||
-Wstringop-overflow=4 -Wtrampolines -Wundef -Wuninitialized -Wunused -Wshadow \
|
||||
-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 \
|
||||
|
||||
13
RELEASE.md
13
RELEASE.md
@@ -72,9 +72,14 @@ GitHub.
|
||||
|
||||
8. Update the following related projects.
|
||||
|
||||
- [rgbobj](https://github.com/gbdev/rgbobj) and [rgbds-obj](https://github.com/gbdev/rgbds-obj):
|
||||
make sure that object files created by the latest RGBASM can be parsed and displayed.
|
||||
If the object file revision has been updated, rgbobj will need a corresponding release.
|
||||
- [rgbds-www](https://github.com/gbdev/rgbds-www): update
|
||||
1. [rgbds-www](https://github.com/gbdev/rgbds-www): update
|
||||
[src/pages/versions.mdx](https://github.com/gbdev/rgbds-www/blob/master/src/pages/versions.mdx)
|
||||
to list the new release.
|
||||
2. [rgbds-live](https://github.com/gbdev/rgbds-live): update the `rgbds` submodule (and
|
||||
[patches/rgbds.patch](https://github.com/gbdev/rgbds-live/blob/master/patches/rgbds.patch)
|
||||
if necessary) to use the new release, and
|
||||
[index.html](https://github.com/gbdev/rgbds-live/blob/master/index.html)
|
||||
to link to the new manual version.
|
||||
3. [rgbobj](https://github.com/gbdev/rgbobj) and [rgbds-obj](https://github.com/gbdev/rgbds-obj):
|
||||
make sure that object files created by the latest RGBASM can be parsed and displayed.
|
||||
If the object file revision has been updated, `rgbobj` will need a corresponding release.
|
||||
|
||||
@@ -21,6 +21,7 @@ _rgbfix_completions() {
|
||||
[l]="old-licensee:unk"
|
||||
[m]="mbc-type:mbc"
|
||||
[n]="rom-version:unk"
|
||||
[o]="output:glob-*.gb *.gbc *.sgb"
|
||||
[p]="pad-value:unk"
|
||||
[r]="ram-size:unk"
|
||||
[t]="title:unk"
|
||||
|
||||
@@ -19,6 +19,7 @@ _rgbgfx_completions() {
|
||||
[Z]="columns:normal"
|
||||
[a]="attr-map:glob-*.attrmap"
|
||||
[A]="auto-attr-map:normal"
|
||||
[B]="background-color:unk"
|
||||
[b]="base-tiles:unk"
|
||||
[c]="colors:unk"
|
||||
[d]="depth:unk"
|
||||
|
||||
15
contrib/checkformat.bash
Executable file
15
contrib/checkformat.bash
Executable file
@@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
clang-format --version
|
||||
|
||||
find . -type f \( -iname '*.hpp' -o -iname '*.cpp' \) -exec clang-format -i {} +
|
||||
|
||||
if ! git diff-index --quiet HEAD --; then
|
||||
echo 'Unformatted files:'
|
||||
git diff-index --name-only HEAD --
|
||||
echo
|
||||
git diff HEAD --
|
||||
return 1
|
||||
fi
|
||||
@@ -32,14 +32,15 @@ diff <(xxd "$1") <(xxd "$2") | while read -r LINE; do
|
||||
# Ignore comment lines, only pick matching bank
|
||||
# (The bank regex ignores comments already, make `cut` and `tr` process less lines)
|
||||
grep -Ei "$(printf "^%02x:" $BANK)" "$SYMFILE" |
|
||||
sed "s/$(printf "^%02x:" $BANK)/0x/g" |
|
||||
cut -d ';' -f 1 |
|
||||
tr -d "\r" |
|
||||
sort -g |
|
||||
while read -r SYMADDR SYM; do
|
||||
SYMADDR=$((0x${SYMADDR#*:}))
|
||||
SYMADDR=$(($SYMADDR))
|
||||
if [[ $SYMADDR -le $ADDR ]]; then
|
||||
printf " (%s+0x%x)\n" "$SYM" $((ADDR - SYMADDR))
|
||||
fi
|
||||
# TODO: assumes sorted sym files
|
||||
done | tail -n 1
|
||||
fi)
|
||||
printf "%02x:%04x %s\n" $BANK $ADDR "$EXTRA"
|
||||
|
||||
@@ -53,6 +53,7 @@ local args=(
|
||||
'(-L --logo)'{-L,--logo}'+[Set custom logo]:1bpp image:'
|
||||
'(-m --mbc-type)'{-m,--mbc-type}"+[Set MBC flags]:mbc name:_mbc_names"
|
||||
'(-n --rom-version)'{-n,--rom-version}'+[Set ROM version]:rom version byte:'
|
||||
'(-o --output)'{-o,--output}"+[Output file]:output file:_files -g '*.{gb,sgb,gbc}'"
|
||||
'(-p --pad-value)'{-p,--pad-value}'+[Pad to next valid size using this byte as padding]:padding byte:'
|
||||
'(-r --ram-size)'{-r,--ram-size}'+[Set RAM size]:ram size byte:'
|
||||
'(-t --title)'{-t,--title}'+[Set title string]:11-char title string:'
|
||||
|
||||
@@ -28,6 +28,7 @@ local args=(
|
||||
'(-Z --columns)'{-Z,--columns}'[Read the image in column-major order]'
|
||||
|
||||
'(-a --attr-map -A --auto-attr-map)'{-a,--attr-map}'+[Generate a map of tile attributes (mirroring)]:attrmap file:_files'
|
||||
'(-B --background-color)'{-B,--background-color}'+[Ignore tiles containing only specified color]:color:'
|
||||
'(-b --base-tiles)'{-b,--base-tiles}'+[Base tile IDs for tile map output]:base tile IDs:'
|
||||
'(-c --colors)'{-c,--colors}'+[Specify color palettes]:palette spec:'
|
||||
'(-d --depth)'{-d,--depth}'+[Set bit depth]:bit depth:_depths'
|
||||
|
||||
@@ -20,8 +20,10 @@ void charmap_Push();
|
||||
void charmap_Pop();
|
||||
void charmap_CheckStack();
|
||||
void charmap_Add(std::string const &mapping, std::vector<int32_t> &&value);
|
||||
bool charmap_HasChar(std::string const &input);
|
||||
bool charmap_HasChar(std::string const &mapping);
|
||||
size_t charmap_CharSize(std::string const &mapping);
|
||||
std::vector<int32_t> charmap_Convert(std::string const &input);
|
||||
size_t charmap_ConvertNext(std::string_view &input, std::vector<int32_t> *output);
|
||||
std::string charmap_Reverse(std::vector<int32_t> const &value, bool &unique);
|
||||
|
||||
#endif // RGBDS_ASM_CHARMAP_HPP
|
||||
|
||||
@@ -9,11 +9,11 @@
|
||||
#include <vector>
|
||||
|
||||
struct MacroArgs {
|
||||
unsigned int shift;
|
||||
uint32_t shift;
|
||||
std::vector<std::shared_ptr<std::string>> args;
|
||||
|
||||
uint32_t nbArgs() const { return args.size() - shift; }
|
||||
std::shared_ptr<std::string> getArg(uint32_t i) const;
|
||||
std::shared_ptr<std::string> getArg(int32_t i) const;
|
||||
std::shared_ptr<std::string> getAllArgs() const;
|
||||
|
||||
void appendArg(std::shared_ptr<std::string> arg);
|
||||
|
||||
@@ -22,15 +22,6 @@ struct Expression {
|
||||
std::vector<uint8_t> rpn{}; // Bytes serializing the RPN expression
|
||||
uint32_t rpnPatchSize = 0; // Size the expression will take in the object file
|
||||
|
||||
Expression() = default;
|
||||
Expression(Expression &&) = default;
|
||||
#ifdef _MSC_VER
|
||||
// MSVC and WinFlexBison won't build without this...
|
||||
Expression(Expression const &) = default;
|
||||
#endif
|
||||
|
||||
Expression &operator=(Expression &&) = default;
|
||||
|
||||
bool isKnown() const { return data.holds<int32_t>(); }
|
||||
int32_t value() const { return data.get<int32_t>(); }
|
||||
|
||||
@@ -51,6 +42,7 @@ struct Expression {
|
||||
|
||||
bool makeCheckHRAM();
|
||||
void makeCheckRST();
|
||||
void makeCheckBitIndex(uint8_t mask);
|
||||
|
||||
void checkNBit(uint8_t n) const;
|
||||
|
||||
|
||||
@@ -91,11 +91,11 @@ void sect_ByteString(std::vector<int32_t> const &string);
|
||||
void sect_WordString(std::vector<int32_t> const &string);
|
||||
void sect_LongString(std::vector<int32_t> const &string);
|
||||
void sect_Skip(uint32_t skip, bool ds);
|
||||
void sect_RelByte(Expression &expr, uint32_t pcShift);
|
||||
void sect_RelBytes(uint32_t n, std::vector<Expression> &exprs);
|
||||
void sect_RelWord(Expression &expr, uint32_t pcShift);
|
||||
void sect_RelLong(Expression &expr, uint32_t pcShift);
|
||||
void sect_PCRelByte(Expression &expr, uint32_t pcShift);
|
||||
void sect_RelByte(Expression const &expr, uint32_t pcShift);
|
||||
void sect_RelBytes(uint32_t n, std::vector<Expression> const &exprs);
|
||||
void sect_RelWord(Expression const &expr, uint32_t pcShift);
|
||||
void sect_RelLong(Expression const &expr, uint32_t pcShift);
|
||||
void sect_PCRelByte(Expression const &expr, uint32_t pcShift);
|
||||
void sect_BinaryFile(std::string const &name, int32_t startPos);
|
||||
void sect_BinaryFileSlice(std::string const &name, int32_t startPos, int32_t length);
|
||||
|
||||
|
||||
@@ -82,7 +82,6 @@ Symbol *sym_RedefEqu(std::string const &symName, int32_t value);
|
||||
Symbol *sym_AddVar(std::string const &symName, int32_t value);
|
||||
int32_t sym_GetRSValue();
|
||||
void sym_SetRSValue(int32_t value);
|
||||
uint32_t sym_GetConstantValue(std::string const &symName);
|
||||
// Find a symbol by exact name, bypassing expansion checks
|
||||
Symbol *sym_FindExactSymbol(std::string const &symName);
|
||||
// Find a symbol, possibly scoped, by name
|
||||
|
||||
@@ -103,7 +103,7 @@ public:
|
||||
} else if (other._tag == other._t2.tag_value) {
|
||||
*this = other._t2.value;
|
||||
} else {
|
||||
_tag = nulltag;
|
||||
_tag = nulltag; // LCOV_EXCL_LINE
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -18,12 +18,10 @@
|
||||
#include "gfx/main.hpp"
|
||||
|
||||
class File {
|
||||
// Construct a `std::streambuf *` by default, since it's probably lighter than a `filebuf`.
|
||||
Either<std::streambuf *, std::filebuf> _file;
|
||||
|
||||
public:
|
||||
File() {}
|
||||
~File() { close(); }
|
||||
File() : _file(nullptr) {}
|
||||
|
||||
// This should only be called once, and before doing any `->` operations.
|
||||
// Returns `nullptr` on error, and a non-null pointer otherwise.
|
||||
@@ -61,20 +59,6 @@ public:
|
||||
return const_cast<File *>(this)->operator->();
|
||||
}
|
||||
|
||||
File *close() {
|
||||
if (_file.holds<std::filebuf>()) {
|
||||
// This is called by the destructor, and an explicit `close` shouldn't close twice.
|
||||
std::filebuf fileBuf = std::move(_file.get<std::filebuf>());
|
||||
_file.emplace<std::streambuf *>(nullptr);
|
||||
if (fileBuf.close() != nullptr) {
|
||||
return this;
|
||||
}
|
||||
} else if (_file.get<std::streambuf *>() != nullptr) {
|
||||
return this;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
char const *c_str(std::string const &path) const {
|
||||
return _file.holds<std::filebuf>() ? path.c_str()
|
||||
: _file.get<std::streambuf *>() == std::cin.rdbuf() ? "<stdin>"
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "helpers.hpp"
|
||||
|
||||
#include "gfx/rgba.hpp"
|
||||
|
||||
struct Options {
|
||||
@@ -21,6 +23,7 @@ struct Options {
|
||||
uint8_t verbosity = 0; // -v
|
||||
|
||||
std::string attrmap{}; // -a, -A
|
||||
std::optional<Rgba> bgColor{}; // -B
|
||||
std::array<uint8_t, 2> baseTileIDs{0, 0}; // -b
|
||||
enum {
|
||||
NO_SPEC,
|
||||
@@ -116,4 +119,27 @@ static constexpr auto flipTable = ([]() constexpr {
|
||||
return table;
|
||||
})();
|
||||
|
||||
// Parsing helpers.
|
||||
|
||||
static constexpr uint8_t nibble(char c) {
|
||||
if (c >= 'a') {
|
||||
assume(c <= 'f');
|
||||
return c - 'a' + 10;
|
||||
} else if (c >= 'A') {
|
||||
assume(c <= 'F');
|
||||
return c - 'A' + 10;
|
||||
} else {
|
||||
assume(c >= '0' && c <= '9');
|
||||
return c - '0';
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr uint8_t toHex(char c1, char c2) {
|
||||
return nibble(c1) * 16 + nibble(c2);
|
||||
}
|
||||
|
||||
static constexpr uint8_t singleToHex(char c) {
|
||||
return toHex(c, c);
|
||||
}
|
||||
|
||||
#endif // RGBDS_GFX_MAIN_HPP
|
||||
|
||||
@@ -36,8 +36,8 @@ struct Rgba {
|
||||
auto shl = [](uint8_t val, unsigned shift) { return static_cast<uint32_t>(val) << shift; };
|
||||
return shl(red, 24) | shl(green, 16) | shl(blue, 8) | shl(alpha, 0);
|
||||
}
|
||||
|
||||
bool operator==(Rgba const &rhs) const { return toCSS() == rhs.toCSS(); }
|
||||
bool operator!=(Rgba const &rhs) const { return toCSS() != rhs.toCSS(); }
|
||||
|
||||
// CGB colors are RGB555, so we use bit 15 to signify that the color is transparent instead
|
||||
// Since the rest of the bits don't matter then, we return 0x8000 exactly.
|
||||
|
||||
@@ -25,7 +25,6 @@ class EnumSeq {
|
||||
auto operator*() const { return _value; }
|
||||
|
||||
bool operator==(Iterator const &rhs) const { return _value == rhs._value; }
|
||||
bool operator!=(Iterator const &rhs) const { return _value != rhs._value; }
|
||||
};
|
||||
|
||||
public:
|
||||
@@ -59,10 +58,6 @@ public:
|
||||
bool operator==(ZipIterator const &rhs) const {
|
||||
return std::get<0>(_iters) == std::get<0>(rhs._iters);
|
||||
}
|
||||
|
||||
bool operator!=(ZipIterator const &rhs) const {
|
||||
return std::get<0>(_iters) != std::get<0>(rhs._iters);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename... Ts>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#include "helpers.hpp" // assume
|
||||
|
||||
#define RGBDS_OBJECT_VERSION_STRING "RGB9"
|
||||
#define RGBDS_OBJECT_REV 11U
|
||||
#define RGBDS_OBJECT_REV 12U
|
||||
|
||||
enum AssertionType { ASSERT_WARN, ASSERT_ERROR, ASSERT_FATAL };
|
||||
|
||||
@@ -52,6 +52,7 @@ enum RPNCommand {
|
||||
|
||||
RPN_HRAM = 0x60,
|
||||
RPN_RST = 0x61,
|
||||
RPN_BIT_INDEX = 0x62,
|
||||
|
||||
RPN_HIGH = 0x70,
|
||||
RPN_LOW = 0x71,
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
#define PACKAGE_VERSION_MAJOR 0
|
||||
#define PACKAGE_VERSION_MINOR 9
|
||||
#define PACKAGE_VERSION_PATCH 1
|
||||
#define PACKAGE_VERSION_PATCH 2
|
||||
|
||||
char const *get_package_version_string();
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd February 2, 2025
|
||||
.Dd May 4, 2025
|
||||
.Dt GBZ80 7
|
||||
.Os
|
||||
.Sh NAME
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
.\"
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd February 2, 2025
|
||||
.Dd May 4, 2025
|
||||
.Dt RGBASM-OLD 5
|
||||
.Os
|
||||
.Sh NAME
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd February 2, 2025
|
||||
.Dd May 4, 2025
|
||||
.Dt RGBASM 1
|
||||
.Os
|
||||
.Sh NAME
|
||||
|
||||
57
man/rgbasm.5
57
man/rgbasm.5
@@ -2,7 +2,7 @@
|
||||
.\"
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd February 2, 2025
|
||||
.Dd May 4, 2025
|
||||
.Dt RGBASM 5
|
||||
.Os
|
||||
.Sh NAME
|
||||
@@ -564,22 +564,17 @@ is equivalent to the regular string
|
||||
(Note that this prevents raw strings from including the double quote character.)
|
||||
Raw strings also may be contained in triple quotes for them to be multi-line, so they can include literal newline or quote characters (although still not three quotes in a row).
|
||||
.Pp
|
||||
The following functions operate on string expressions.
|
||||
Most of them return a string, however some of these functions actually return an integer and can be used as part of an integer expression!
|
||||
.Bl -column "STRSUB(str, pos, len)"
|
||||
The following functions operate on string expressions, and return strings themselves.
|
||||
.Bl -column "STRSLICE(str, start, stop)"
|
||||
.It Sy Name Ta Sy Operation
|
||||
.It Fn STRLEN str Ta Returns the number of characters in Ar str .
|
||||
.It Fn STRCAT strs... Ta Concatenates Ar strs .
|
||||
.It Fn STRCMP str1 str2 Ta Returns -1 if Ar str1 No is alphabetically lower than Ar str2 No , zero if they match, 1 if Ar str1 No is greater than Ar str2 .
|
||||
.It Fn STRIN str1 str2 Ta Returns the first position of Ar str2 No in Ar str1 No or zero if it's not present Pq first character is position 1 .
|
||||
.It Fn STRRIN str1 str2 Ta Returns the last position of Ar str2 No in Ar str1 No or zero if it's not present Pq first character is position 1 .
|
||||
.It Fn STRSUB str pos len Ta Returns a substring from Ar str No starting at Ar pos No (first character is position 1, last is position -1) and Ar len No characters long. If Ar len No is not specified the substring continues to the end of Ar str .
|
||||
.It Fn STRUPR str Ta Returns Ar str No with all ASCII letters
|
||||
.Pq Ql a-z
|
||||
in uppercase.
|
||||
.It Fn STRLWR str Ta Returns Ar str No with all ASCII letters
|
||||
.Pq Ql A-Z
|
||||
in lowercase.
|
||||
.It Fn STRSLICE str start stop Ta Returns a substring of Ar str No starting at Ar start No and ending at Ar stop No (exclusive). If Ar stop No is not specified, the substring continues to the end of Ar str Ns .
|
||||
.It Fn STRRPL str old new Ta Returns Ar str No with each non-overlapping occurrence of the substring Ar old No replaced with Ar new .
|
||||
.It Fn STRFMT fmt args... Ta Returns the string Ar fmt No with each
|
||||
.Ql %spec
|
||||
@@ -589,9 +584,35 @@ pattern replaced by interpolating the format
|
||||
with its corresponding argument in
|
||||
.Ar args
|
||||
.Pq So %% Sc is replaced by the So % Sc character .
|
||||
.It Fn INCHARMAP str Ta Returns 1 if Ar str No has an entry in the current charmap, and 0 otherwise .
|
||||
.It Fn STRCHAR str idx Ta Returns the substring of Ar str No for the charmap entry at Ar idx No with the current charmap . Pq Ar idx No counts charmap entries, not characters.
|
||||
.It Fn REVCHAR vals... Ta Returns the string that is mapped to Ar vals No with the current charmap. If there is no unique charmap entry for Ar vals Ns , an error occurs.
|
||||
.El
|
||||
.Pp
|
||||
The following functions operate on string expressions, but return integers.
|
||||
.Bl -column "STRRFIND(str, sub)"
|
||||
.It Sy Name Ta Sy Operation
|
||||
.It Fn STRLEN str Ta Returns the number of characters in Ar str .
|
||||
.It Fn STRCMP str1 str2 Ta Compares Ar str1 No and Ar str2 No according to ASCII ordering of their characters. Returns -1 if Ar str1 No is lower than Ar str2 Ns , 1 if Ar str1 No is greater than Ar str2 Ns , or 0 if they match.
|
||||
.It Fn STRFIND str sub Ta Returns the first index of Ar sub No in Ar str Ns , or -1 if it's not present.
|
||||
.It Fn STRRFIND str sub Ta Returns the last index of Ar sub No in Ar str Ns , or -1 if it's not present.
|
||||
.It Fn INCHARMAP str Ta Returns 1 if Ar str No has an entry in the current charmap, or 0 otherwise .
|
||||
.It Fn CHARLEN str Ta Returns the number of charmap entries in Ar str No with the current charmap .
|
||||
.It Fn CHARSUB str pos Ta Returns the substring for the charmap entry at Ar pos No in Ar str No (first character is position 1, last is position -1) with the current charmap .
|
||||
.It Fn CHARCMP str1 str2 Ta Compares Ar str1 No and Ar str2 No according to their charmap entry values with the current charmap. Returns -1 if Ar str1 No is lower than Ar str2 Ns , 1 if Ar str1 No is greater than Ar str2 Ns , or 0 if they match.
|
||||
.It Fn CHARSIZE char Ta Returns how many values are in the charmap entry for Ar char No with the current charmap.
|
||||
.El
|
||||
.Pp
|
||||
Note that the first character of a string is at index 0, and the last is at index -1.
|
||||
.Pp
|
||||
The following legacy functions are similar to other functions that operate on string expressions, but for historical reasons, they count characters starting from
|
||||
.Em position 1 ,
|
||||
not from index 0!
|
||||
(Position -1 still counts from the last character.)
|
||||
.Bl -column "STRSUB(str, pos, len)"
|
||||
.It Sy Name Ta Sy Operation
|
||||
.It Fn STRSUB str pos len Ta Returns a substring of Ar str No starting at Ar pos No and Ar len No characters long. If Ar len No is not specified, the substring continues to the end of Ar str No .
|
||||
.It Fn STRIN str sub Ta Returns the first position of Ar sub No in Ar str Ns , or 0 if it's not present.
|
||||
.It Fn STRRIN str sub Ta Returns the last position of Ar sub No in Ar str Ns , or 0 if it's not present.
|
||||
.It Fn CHARSUB str pos Ta Returns the substring of Ar str No for the charmap entry at Ar pos No with the current charmap . Pq Ar pos No counts charmap entries, not characters.
|
||||
.El
|
||||
.Ss Character maps
|
||||
When writing text strings that are meant to be displayed on the Game Boy, the character encoding in the ROM may need to be different than the source file encoding.
|
||||
@@ -1052,7 +1073,7 @@ and
|
||||
.Ic WRAMX
|
||||
types are still considered different.
|
||||
.It
|
||||
Different constraints (alignment, bank, etc.) can be specified for each unionized section declaration, but they must all be compatible.
|
||||
Different constraints (alignment, bank, etc.) can be specified for each section fragment declaration, but they must all be compatible.
|
||||
For example, alignment must be compatible with any fixed address, all specified banks must be the same, etc.
|
||||
.It
|
||||
A section fragment may not be unionized; after all, that wouldn't make much sense.
|
||||
@@ -1104,7 +1125,9 @@ Additionally, label names can contain up to a single dot
|
||||
.Ql \&. ,
|
||||
which may not be the first character.
|
||||
.Pp
|
||||
A symbol cannot have the same name as a reserved keyword, unless it is prefixed by a hash
|
||||
A symbol cannot have the same name as a reserved keyword, unless its name is a
|
||||
.Dq raw identifier
|
||||
prefixed by a hash
|
||||
.Sq # .
|
||||
For example,
|
||||
.Ql #load
|
||||
@@ -1279,7 +1302,7 @@ it at the same time.
|
||||
below).
|
||||
.Ss Numeric constants
|
||||
.Ic EQU
|
||||
is used to define immutable numeric symbols.
|
||||
is used to define numeric constant symbols.
|
||||
Unlike
|
||||
.Sq =
|
||||
above, constants defined this way cannot be redefined.
|
||||
@@ -1387,6 +1410,8 @@ This expansion is disabled in a few contexts:
|
||||
and
|
||||
.Ql MACRO name
|
||||
will not expand string constants in their names.
|
||||
Expansion is also disabled if the string constant's name is a raw identifier prefixed by a hash
|
||||
.Sq # .
|
||||
.Bd -literal -offset indent
|
||||
DEF COUNTREG EQUS "[hl+]"
|
||||
ld a, COUNTREG
|
||||
@@ -1852,9 +1877,11 @@ being the second, and so on. Since there are only nine digits, you can only use
|
||||
To use the rest, you put the argument number in angle brackets, like
|
||||
.Ic \e<10> .
|
||||
.Pp
|
||||
This bracketed syntax supports decimal numbers and numeric constant symbols.
|
||||
This bracketed syntax supports decimal numbers and numeric symbols, where negative values count from the last argument.
|
||||
For example,
|
||||
.Ql \e<_NARG>
|
||||
or
|
||||
.Ql \e<-1>
|
||||
will get the last argument.
|
||||
.Pp
|
||||
Other macro arguments and symbol interpolations will also be expanded inside the angle brackets.
|
||||
|
||||
15
man/rgbds.5
15
man/rgbds.5
@@ -1,6 +1,6 @@
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd February 2, 2025
|
||||
.Dd May 4, 2025
|
||||
.Dt RGBDS 5
|
||||
.Os
|
||||
.Sh NAME
|
||||
@@ -388,10 +388,19 @@ The value is then ANDed with $00FF
|
||||
check.
|
||||
Checks if the value is a valid
|
||||
.Ql rst
|
||||
.Pq see Do RST vec Dc in Xr gbz80 7
|
||||
vector, that is one of $00, $08, $10, $18, $20, $28, $30, or $38.
|
||||
vector
|
||||
.Pq see Do RST vec Dc in Xr gbz80 7 ,
|
||||
that is, one of $00, $08, $10, $18, $20, $28, $30, or $38.
|
||||
The value is then ORed with $C7
|
||||
.Pq Ql \&| $C7 .
|
||||
.It Li $62 Ta Ql bit/res/set
|
||||
check; followed by the instruction's
|
||||
.Cm BYTE
|
||||
mask.
|
||||
Checks if the value is a valid bit index
|
||||
.Pq see e.g. Do BIT u3, r8 Dc in Xr gbz80 7 ,
|
||||
that is, from 0 to 7.
|
||||
The value is then ORed with the instruction's mask.
|
||||
.It Li $80 Ta Integer literal; followed by the
|
||||
.Cm LONG
|
||||
integer.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd February 2, 2025
|
||||
.Dd May 4, 2025
|
||||
.Dt RGBDS 7
|
||||
.Os
|
||||
.Sh NAME
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd February 2, 2025
|
||||
.Dd May 4, 2025
|
||||
.Dt RGBFIX 1
|
||||
.Os
|
||||
.Sh NAME
|
||||
@@ -17,6 +17,7 @@
|
||||
.Op Fl l Ar licensee_id
|
||||
.Op Fl m Ar mbc_type
|
||||
.Op Fl n Ar rom_version
|
||||
.Op Fl o Ar out_file
|
||||
.Op Fl p Ar pad_value
|
||||
.Op Fl r Ar ram_size
|
||||
.Op Fl t Ar title_str
|
||||
@@ -134,6 +135,9 @@ Set the ROM version
|
||||
to a given value from 0 to 0xFF.
|
||||
.It Fl O , Fl \-overwrite
|
||||
Allow overwriting different non-zero bytes in the header without a warning being emitted.
|
||||
.It Fl o Ar out_file , Fl \-output Ar out_file
|
||||
Write the modified ROM image to the given file, or '-' to write to standard output.
|
||||
If not specified, the input files are modified in-place, or written to standard output if read from standard input.
|
||||
.It Fl p Ar pad_value , Fl \-pad-value Ar pad_value
|
||||
Pad the ROM image to a valid size with a given pad value from 0 to 255 (0xFF).
|
||||
.Nm
|
||||
|
||||
17
man/rgbgfx.1
17
man/rgbgfx.1
@@ -2,7 +2,7 @@
|
||||
.\"
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd February 2, 2025
|
||||
.Dd May 4, 2025
|
||||
.Dt RGBGFX 1
|
||||
.Os
|
||||
.Sh NAME
|
||||
@@ -103,6 +103,19 @@ and has the same size.
|
||||
Same as
|
||||
.Fl a Ar base_path Ns .attrmap
|
||||
.Pq see Sx Automatic output paths .
|
||||
.It Fl B Ar color , Fl \-background-color Ar color
|
||||
Set a background color to be omitted from output.
|
||||
Colors are accepted in
|
||||
.Ql #rgb
|
||||
or
|
||||
.Ql #rrggbb
|
||||
format, or as
|
||||
.Ql transparent .
|
||||
Input tiles which are entirely the specified background color are ignored and will not be output in tile data file.
|
||||
The tilemap, atrribute map, or palette map files
|
||||
.Em will
|
||||
use placeholder values where background tiles were.
|
||||
If a background color is specified, it cannot be used within tiles which are not ignored.
|
||||
.It Fl b Ar base_ids , Fl \-base-tiles Ar base_ids
|
||||
Set the base IDs for tile map output.
|
||||
.Ar base_ids
|
||||
@@ -126,7 +139,7 @@ begins with a hash character
|
||||
.Ql # ,
|
||||
it is treated as an inline palette specification.
|
||||
It should contain a comma-separated list of hexadecimal colors, each beginning with a hash.
|
||||
Colors are accepted either as
|
||||
Colors are accepted in
|
||||
.Ql #rgb
|
||||
or
|
||||
.Ql #rrggbb
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd February 2, 2025
|
||||
.Dd May 4, 2025
|
||||
.Dt RGBLINK 1
|
||||
.Os
|
||||
.Sh NAME
|
||||
@@ -121,8 +121,8 @@ WRAMX sections that are fixed to a bank other than 1 become errors, other WRAMX
|
||||
Disables padding the end of the final file.
|
||||
This option automatically enables
|
||||
.Fl t .
|
||||
You can use this when not not making a ROM.
|
||||
When making a ROM, be careful that not using this is not a replacement for
|
||||
You can use this to make binary files that are not a ROM.
|
||||
When making a ROM, note that not using this is not a replacement for
|
||||
.Xr rgbfix 1 Ap s Fl p
|
||||
option!
|
||||
.El
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd February 2, 2025
|
||||
.Dd May 4, 2025
|
||||
.Dt RGBLINK 5
|
||||
.Os
|
||||
.Sh NAME
|
||||
@@ -120,7 +120,7 @@ causes all sections between it and the next
|
||||
.Ic ORG
|
||||
or bank specification to be placed at addresses automatically determined by
|
||||
.Nm .
|
||||
.Pq It is, however, compatible with Ic ALIGN No below.
|
||||
.Pq \&It is, however, compatible with Ic ALIGN No below.
|
||||
.Pp
|
||||
.Ql Ic ALIGN Ar addr , Ar offset
|
||||
increases the
|
||||
|
||||
@@ -31,6 +31,29 @@ struct CharmapNode {
|
||||
struct Charmap {
|
||||
std::string name;
|
||||
std::vector<CharmapNode> nodes; // first node is reserved for the root node
|
||||
|
||||
// Traverse the trie depth-first to derive the character mappings in definition order
|
||||
template<typename F>
|
||||
bool forEachChar(F callback) const {
|
||||
// clang-format off: nested initializers
|
||||
for (std::stack<std::pair<size_t, std::string>> prefixes({{0, ""}}); !prefixes.empty();) {
|
||||
// clang-format on
|
||||
auto [nodeIdx, mapping] = std::move(prefixes.top());
|
||||
prefixes.pop();
|
||||
CharmapNode const &node = nodes[nodeIdx];
|
||||
if (node.isTerminal()) {
|
||||
if (!callback(nodeIdx, mapping)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (unsigned c = 0; c < std::size(node.next); c++) {
|
||||
if (size_t nextIdx = node.next[c]; nextIdx) {
|
||||
prefixes.push({nextIdx, mapping + static_cast<char>(c)});
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
static std::deque<Charmap> charmapList;
|
||||
@@ -44,24 +67,12 @@ bool charmap_ForEach(
|
||||
void (*charFunc)(std::string const &, std::vector<int32_t>)
|
||||
) {
|
||||
for (Charmap const &charmap : charmapList) {
|
||||
// Traverse the trie depth-first to derive the character mappings in definition order
|
||||
std::map<size_t, std::string> mappings;
|
||||
// clang-format off: nested initializers
|
||||
for (std::stack<std::pair<size_t, std::string>> prefixes({{0, ""}});
|
||||
!prefixes.empty();) {
|
||||
// clang-format on
|
||||
auto [nodeIdx, mapping] = std::move(prefixes.top());
|
||||
prefixes.pop();
|
||||
CharmapNode const &node = charmap.nodes[nodeIdx];
|
||||
if (node.isTerminal()) {
|
||||
charmap.forEachChar([&mappings](size_t nodeIdx, std::string const &mapping) {
|
||||
mappings[nodeIdx] = mapping;
|
||||
}
|
||||
for (unsigned c = 0; c < 256; c++) {
|
||||
if (size_t nextIdx = node.next[c]; nextIdx) {
|
||||
prefixes.push({nextIdx, mapping + static_cast<char>(c)});
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
mapFunc(charmap.name);
|
||||
for (auto [nodeIdx, mapping] : mappings) {
|
||||
charFunc(mapping, charmap.nodes[nodeIdx].value);
|
||||
@@ -163,11 +174,11 @@ void charmap_Add(std::string const &mapping, std::vector<int32_t> &&value) {
|
||||
std::swap(node.value, value);
|
||||
}
|
||||
|
||||
bool charmap_HasChar(std::string const &input) {
|
||||
bool charmap_HasChar(std::string const &mapping) {
|
||||
Charmap const &charmap = *currentCharmap;
|
||||
size_t nodeIdx = 0;
|
||||
|
||||
for (char c : input) {
|
||||
for (char c : mapping) {
|
||||
nodeIdx = charmap.nodes[nodeIdx].next[static_cast<uint8_t>(c)];
|
||||
|
||||
if (!nodeIdx) {
|
||||
@@ -178,6 +189,22 @@ bool charmap_HasChar(std::string const &input) {
|
||||
return charmap.nodes[nodeIdx].isTerminal();
|
||||
}
|
||||
|
||||
size_t charmap_CharSize(std::string const &mapping) {
|
||||
Charmap const &charmap = *currentCharmap;
|
||||
size_t nodeIdx = 0;
|
||||
|
||||
for (char c : mapping) {
|
||||
nodeIdx = charmap.nodes[nodeIdx].next[static_cast<uint8_t>(c)];
|
||||
|
||||
if (!nodeIdx) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
CharmapNode const &node = charmap.nodes[nodeIdx];
|
||||
return node.isTerminal() ? node.value.size() : 0;
|
||||
}
|
||||
|
||||
std::vector<int32_t> charmap_Convert(std::string const &input) {
|
||||
std::vector<int32_t> output;
|
||||
for (std::string_view inputView = input; charmap_ConvertNext(inputView, &output);) {}
|
||||
@@ -263,3 +290,20 @@ size_t charmap_ConvertNext(std::string_view &input, std::vector<int32_t> *output
|
||||
input = input.substr(inputIdx);
|
||||
return matchLen;
|
||||
}
|
||||
|
||||
std::string charmap_Reverse(std::vector<int32_t> const &value, bool &unique) {
|
||||
Charmap const &charmap = *currentCharmap;
|
||||
std::string revMapping;
|
||||
unique = charmap.forEachChar([&](size_t nodeIdx, std::string const &mapping) {
|
||||
if (charmap.nodes[nodeIdx].value == value) {
|
||||
if (revMapping.empty()) {
|
||||
revMapping = mapping;
|
||||
} else {
|
||||
revMapping.clear();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return revMapping;
|
||||
}
|
||||
|
||||
@@ -22,29 +22,29 @@ void FormatSpec::useCharacter(int c) {
|
||||
case ' ':
|
||||
case '+':
|
||||
if (state > FORMAT_SIGN) {
|
||||
goto invalid;
|
||||
break;
|
||||
}
|
||||
state = FORMAT_EXACT;
|
||||
sign = c;
|
||||
break;
|
||||
return;
|
||||
|
||||
// exact
|
||||
case '#':
|
||||
if (state > FORMAT_EXACT) {
|
||||
goto invalid;
|
||||
break;
|
||||
}
|
||||
state = FORMAT_ALIGN;
|
||||
exact = true;
|
||||
break;
|
||||
return;
|
||||
|
||||
// align
|
||||
case '-':
|
||||
if (state > FORMAT_ALIGN) {
|
||||
goto invalid;
|
||||
break;
|
||||
}
|
||||
state = FORMAT_WIDTH;
|
||||
alignLeft = true;
|
||||
break;
|
||||
return;
|
||||
|
||||
// pad, width, and prec values
|
||||
case '0':
|
||||
@@ -71,27 +71,27 @@ void FormatSpec::useCharacter(int c) {
|
||||
} else if (state == FORMAT_PREC) {
|
||||
precision = precision * 10 + (c - '0');
|
||||
} else {
|
||||
goto invalid;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return;
|
||||
|
||||
// width
|
||||
case '.':
|
||||
if (state > FORMAT_WIDTH) {
|
||||
goto invalid;
|
||||
break;
|
||||
}
|
||||
state = FORMAT_FRAC;
|
||||
hasFrac = true;
|
||||
break;
|
||||
return;
|
||||
|
||||
// prec
|
||||
case 'q':
|
||||
if (state > FORMAT_PREC) {
|
||||
goto invalid;
|
||||
break;
|
||||
}
|
||||
state = FORMAT_PREC;
|
||||
hasPrec = true;
|
||||
break;
|
||||
return;
|
||||
|
||||
// type
|
||||
case 'd':
|
||||
@@ -103,19 +103,20 @@ void FormatSpec::useCharacter(int c) {
|
||||
case 'f':
|
||||
case 's':
|
||||
if (state >= FORMAT_DONE) {
|
||||
goto invalid;
|
||||
break;
|
||||
}
|
||||
state = FORMAT_DONE;
|
||||
valid = true;
|
||||
type = c;
|
||||
break;
|
||||
return;
|
||||
|
||||
default:
|
||||
invalid:
|
||||
break;
|
||||
}
|
||||
|
||||
state = FORMAT_INVALID;
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
void FormatSpec::finishCharacters() {
|
||||
if (!isValid()) {
|
||||
|
||||
@@ -119,9 +119,11 @@ void fstk_SetPreIncludeFile(std::string const &path) {
|
||||
warnx("Overriding pre-included filename %s", preIncludeName.c_str());
|
||||
}
|
||||
preIncludeName = path;
|
||||
// LCOV_EXCL_START
|
||||
if (verbose) {
|
||||
printf("Pre-included filename %s\n", preIncludeName.c_str());
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
static void printDep(std::string const &path) {
|
||||
@@ -308,9 +310,11 @@ void fstk_RunInclude(std::string const &path, bool preInclude) {
|
||||
|
||||
if (!fullPath) {
|
||||
if (generatedMissingIncludes && !preInclude) {
|
||||
// LCOV_EXCL_START
|
||||
if (verbose) {
|
||||
printf("Aborting (-MG) on INCLUDE file '%s' (%s)\n", path.c_str(), strerror(errno));
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
failedOnMissingInclude = true;
|
||||
} else {
|
||||
error("Unable to open included file '%s': %s\n", path.c_str(), strerror(errno));
|
||||
@@ -319,7 +323,7 @@ void fstk_RunInclude(std::string const &path, bool preInclude) {
|
||||
}
|
||||
|
||||
if (!newFileContext(*fullPath, false)) {
|
||||
fatalerror("Failed to set up lexer for file include\n");
|
||||
fatalerror("Failed to set up lexer for file include\n"); // LCOV_EXCL_LINE
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -77,6 +77,7 @@ struct FileUnmapDeleter {
|
||||
|
||||
static char *mapFile(int fd, std::string const &path, size_t size) {
|
||||
void *mappingAddr = mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd, 0);
|
||||
// LCOV_EXCL_START
|
||||
if (mappingAddr == MAP_FAILED && errno == ENOTSUP) {
|
||||
// The implementation may not support MAP_PRIVATE; try again with MAP_SHARED
|
||||
// instead, offering, I believe, weaker guarantees about external modifications to
|
||||
@@ -86,6 +87,7 @@ static char *mapFile(int fd, std::string const &path, size_t size) {
|
||||
}
|
||||
mappingAddr = mmap(nullptr, size, PROT_READ, MAP_SHARED, fd, 0);
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
return mappingAddr != MAP_FAILED ? static_cast<char *>(mappingAddr) : nullptr;
|
||||
}
|
||||
|
||||
@@ -134,11 +136,9 @@ struct CaseInsensitive {
|
||||
}
|
||||
};
|
||||
|
||||
// Identifiers that are also keywords are listed here. This ONLY applies to ones
|
||||
// that would normally be matched as identifiers! Check out `yylex_NORMAL` to
|
||||
// see how this is used.
|
||||
// Tokens / keywords not handled here are handled in `yylex_NORMAL`'s switch.
|
||||
// This assumes that no two keywords have the same name.
|
||||
// This map lists all RGBASM keywords which `yylex_NORMAL` lexes as identifiers
|
||||
// (see `startsIdentifier` and `continuesIdentifier` below). All non-identifier
|
||||
// tokens are lexed separately.
|
||||
static std::unordered_map<std::string, int, CaseInsensitive, CaseInsensitive> keywordDict = {
|
||||
{"ADC", T_(SM83_ADC) },
|
||||
{"ADD", T_(SM83_ADD) },
|
||||
@@ -241,20 +241,27 @@ static std::unordered_map<std::string, int, CaseInsensitive, CaseInsensitive> ke
|
||||
{"BITWIDTH", T_(OP_BITWIDTH) },
|
||||
{"TZCOUNT", T_(OP_TZCOUNT) },
|
||||
|
||||
{"STRCMP", T_(OP_STRCMP) },
|
||||
{"STRIN", T_(OP_STRIN) },
|
||||
{"STRRIN", T_(OP_STRRIN) },
|
||||
{"STRSUB", T_(OP_STRSUB) },
|
||||
{"STRLEN", T_(OP_STRLEN) },
|
||||
{"STRCAT", T_(OP_STRCAT) },
|
||||
{"STRUPR", T_(OP_STRUPR) },
|
||||
{"STRLWR", T_(OP_STRLWR) },
|
||||
{"STRRPL", T_(OP_STRRPL) },
|
||||
{"STRCHAR", T_(OP_STRCHAR) },
|
||||
{"STRCMP", T_(OP_STRCMP) },
|
||||
{"STRFIND", T_(OP_STRFIND) },
|
||||
{"STRFMT", T_(OP_STRFMT) },
|
||||
{"STRIN", T_(OP_STRIN) },
|
||||
{"STRLEN", T_(OP_STRLEN) },
|
||||
{"STRLWR", T_(OP_STRLWR) },
|
||||
{"STRRFIND", T_(OP_STRRFIND) },
|
||||
{"STRRIN", T_(OP_STRRIN) },
|
||||
{"STRRPL", T_(OP_STRRPL) },
|
||||
{"STRSLICE", T_(OP_STRSLICE) },
|
||||
{"STRSUB", T_(OP_STRSUB) },
|
||||
{"STRUPR", T_(OP_STRUPR) },
|
||||
|
||||
{"CHARCMP", T_(OP_CHARCMP) },
|
||||
{"CHARLEN", T_(OP_CHARLEN) },
|
||||
{"CHARSIZE", T_(OP_CHARSIZE) },
|
||||
{"CHARSUB", T_(OP_CHARSUB) },
|
||||
{"INCHARMAP", T_(OP_INCHARMAP) },
|
||||
{"REVCHAR", T_(OP_REVCHAR) },
|
||||
|
||||
{"INCLUDE", T_(POP_INCLUDE) },
|
||||
{"PRINT", T_(POP_PRINT) },
|
||||
@@ -407,21 +414,27 @@ bool LexerState::setFileAsNextState(std::string const &filePath, bool updateStat
|
||||
if (filePath == "-") {
|
||||
path = "<stdin>";
|
||||
content.emplace<BufferedContent>(STDIN_FILENO);
|
||||
// LCOV_EXCL_START
|
||||
if (verbose) {
|
||||
printf("Opening stdin\n");
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
} else {
|
||||
struct stat statBuf;
|
||||
if (stat(filePath.c_str(), &statBuf) != 0) {
|
||||
// LCOV_EXCL_START
|
||||
error("Failed to stat file \"%s\": %s\n", filePath.c_str(), strerror(errno));
|
||||
return false;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
path = filePath;
|
||||
|
||||
int fd = open(path.c_str(), O_RDONLY);
|
||||
if (fd < 0) {
|
||||
// LCOV_EXCL_START
|
||||
error("Failed to open file \"%s\": %s\n", path.c_str(), strerror(errno));
|
||||
return false;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
bool isMmapped = false;
|
||||
@@ -433,9 +446,11 @@ bool LexerState::setFileAsNextState(std::string const &filePath, bool updateStat
|
||||
content.emplace<ViewedContent>(
|
||||
std::shared_ptr<char[]>(mappingAddr, FileUnmapDeleter(size)), size
|
||||
);
|
||||
// LCOV_EXCL_START
|
||||
if (verbose) {
|
||||
printf("File \"%s\" is mmap()ped\n", path.c_str());
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
isMmapped = true;
|
||||
}
|
||||
}
|
||||
@@ -443,6 +458,7 @@ bool LexerState::setFileAsNextState(std::string const &filePath, bool updateStat
|
||||
if (!isMmapped) {
|
||||
// Sometimes mmap() fails or isn't available, so have a fallback
|
||||
content.emplace<BufferedContent>(fd);
|
||||
// LCOV_EXCL_START
|
||||
if (verbose) {
|
||||
if (statBuf.st_size == 0) {
|
||||
printf("File \"%s\" is empty\n", path.c_str());
|
||||
@@ -452,6 +468,7 @@ bool LexerState::setFileAsNextState(std::string const &filePath, bool updateStat
|
||||
);
|
||||
}
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
}
|
||||
|
||||
@@ -547,7 +564,9 @@ size_t BufferedContent::readMore(size_t startIndex, size_t nbChars) {
|
||||
ssize_t nbReadChars = read(fd, &buf[startIndex], nbChars);
|
||||
|
||||
if (nbReadChars == -1) {
|
||||
// LCOV_EXCL_START
|
||||
fatalerror("Error while reading \"%s\": %s\n", lexerState->path.c_str(), strerror(errno));
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
size += nbReadChars;
|
||||
@@ -606,13 +625,24 @@ static uint32_t readBracketedMacroArgNum() {
|
||||
lexerState->disableInterpolation = disableInterpolation;
|
||||
}};
|
||||
|
||||
uint32_t num = 0;
|
||||
int32_t num = 0;
|
||||
int c = peek();
|
||||
bool empty = false;
|
||||
bool symbolError = false;
|
||||
bool negative = c == '-';
|
||||
|
||||
if (negative) {
|
||||
shiftChar();
|
||||
c = peek();
|
||||
}
|
||||
|
||||
if (c >= '0' && c <= '9') {
|
||||
num = readNumber(10, 0);
|
||||
uint32_t n = readNumber(10, 0);
|
||||
if (n > INT32_MAX) {
|
||||
error("Number in bracketed macro argument is too large\n");
|
||||
return 0;
|
||||
}
|
||||
num = negative ? -n : static_cast<int32_t>(n);
|
||||
} else if (startsIdentifier(c) || c == '#') {
|
||||
if (c == '#') {
|
||||
shiftChar();
|
||||
@@ -645,7 +675,7 @@ static uint32_t readBracketedMacroArgNum() {
|
||||
num = 0;
|
||||
symbolError = true;
|
||||
} else {
|
||||
num = sym->getConstantValue();
|
||||
num = static_cast<int32_t>(sym->getConstantValue());
|
||||
}
|
||||
} else {
|
||||
empty = true;
|
||||
@@ -685,7 +715,7 @@ static std::shared_ptr<std::string> readMacroArg(char name) {
|
||||
assume(str); // '\#' should always be defined (at least as an empty string)
|
||||
return str;
|
||||
} else if (name == '<') {
|
||||
uint32_t num = readBracketedMacroArgNum();
|
||||
int32_t num = readBracketedMacroArgNum();
|
||||
if (num == 0) {
|
||||
// The error was already reported by `readBracketedMacroArgNum`.
|
||||
return nullptr;
|
||||
@@ -699,7 +729,7 @@ static std::shared_ptr<std::string> readMacroArg(char name) {
|
||||
|
||||
auto str = macroArgs->getArg(num);
|
||||
if (!str) {
|
||||
error("Macro argument '\\<%" PRIu32 ">' not defined\n", num);
|
||||
error("Macro argument '\\<%" PRId32 ">' not defined\n", num);
|
||||
}
|
||||
return str;
|
||||
} else {
|
||||
@@ -756,7 +786,9 @@ int LexerState::peekCharAhead() {
|
||||
// and `.peekCharAhead()` will continue with its parent
|
||||
assume(exp.offset <= exp.size());
|
||||
if (exp.offset + distance < exp.size()) {
|
||||
return static_cast<uint8_t>((*exp.contents)[exp.offset + distance]);
|
||||
// Macro args can't be recursive, since `peek()` marks them as scanned, so
|
||||
// this is a failsafe that (as far as I can tell) won't ever actually run.
|
||||
return static_cast<uint8_t>((*exp.contents)[exp.offset + distance]); // LCOV_EXCL_LINE
|
||||
}
|
||||
distance -= exp.size() - exp.offset;
|
||||
}
|
||||
@@ -841,14 +873,14 @@ static void shiftChar() {
|
||||
|
||||
lexerState->macroArgScanDistance--;
|
||||
|
||||
restart:
|
||||
for (;;) {
|
||||
if (!lexerState->expansions.empty()) {
|
||||
// Advance within the current expansion
|
||||
if (Expansion &exp = lexerState->expansions.front(); exp.advance()) {
|
||||
// When advancing would go past an expansion's end,
|
||||
// move up to its parent and try again to advance
|
||||
lexerState->expansions.pop_front();
|
||||
goto restart;
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
// Advance within the file contents
|
||||
@@ -858,6 +890,8 @@ restart:
|
||||
lexerState->content.get<BufferedContent>().advance();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static int nextChar() {
|
||||
@@ -1179,7 +1213,7 @@ static uint32_t readGfxConstant() {
|
||||
return bitPlaneUpper << 8 | bitPlaneLower;
|
||||
}
|
||||
|
||||
// Functions to read identifiers & keywords
|
||||
// Functions to read identifiers and keywords
|
||||
|
||||
static bool startsIdentifier(int c) {
|
||||
// Anonymous labels internally start with '!'
|
||||
@@ -1192,18 +1226,18 @@ static bool continuesIdentifier(int c) {
|
||||
|
||||
static Token readIdentifier(char firstChar, bool raw) {
|
||||
std::string identifier(1, firstChar);
|
||||
int tokenType = firstChar == '.' ? T_(LOCAL_ID) : T_(ID);
|
||||
int tokenType = firstChar == '.' ? T_(LOCAL) : T_(SYMBOL);
|
||||
|
||||
// Continue reading while the char is in the symbol charset
|
||||
// Continue reading while the char is in the identifier charset
|
||||
for (int c = peek(); continuesIdentifier(c); c = peek()) {
|
||||
shiftChar();
|
||||
|
||||
// Write the char to the identifier's name
|
||||
identifier += c;
|
||||
|
||||
// If the char was a dot, mark the identifier as local
|
||||
// If the char was a dot, the identifier is a local label
|
||||
if (c == '.') {
|
||||
tokenType = T_(LOCAL_ID);
|
||||
tokenType = T_(LOCAL);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1219,7 +1253,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) {
|
||||
tokenType = T_(ID);
|
||||
tokenType = T_(SYMBOL);
|
||||
}
|
||||
|
||||
return Token(tokenType, identifier);
|
||||
@@ -1276,7 +1310,7 @@ static std::shared_ptr<std::string> readInterpolation(size_t depth) {
|
||||
lexerState->disableInterpolation = disableInterpolation;
|
||||
|
||||
if (fmtBuf.starts_with('#')) {
|
||||
// Skip a '#' raw identifier prefix, but after expanding any nested interpolations.
|
||||
// Skip a '#' raw symbol prefix, but after expanding any nested interpolations.
|
||||
fmtBuf.erase(0, 1);
|
||||
} else if (keywordDict.find(fmtBuf) != keywordDict.end()) {
|
||||
// Don't allow symbols that alias keywords without a '#' prefix.
|
||||
@@ -1318,8 +1352,11 @@ static void appendEscapedString(std::string &str, std::string const &escape) {
|
||||
str += "\\n";
|
||||
break;
|
||||
case '\r':
|
||||
// A literal CR in a string may get treated as a LF, so '\r' is not tested.
|
||||
// LCOV_EXCL_START
|
||||
str += "\\r";
|
||||
break;
|
||||
// LCOV_EXCL_STOP
|
||||
case '\t':
|
||||
str += "\\t";
|
||||
break;
|
||||
@@ -1620,6 +1657,12 @@ static void appendStringLiteral(std::string &str, bool raw) {
|
||||
|
||||
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) {
|
||||
return c != EOF && !continuesIdentifier(c)
|
||||
&& (c == '\0' || !strchr("; \t~[](),+-*/|^=!<>:&%`\"\r\n\\", c));
|
||||
}
|
||||
|
||||
static Token yylex_NORMAL() {
|
||||
for (;;) {
|
||||
int c = nextChar();
|
||||
@@ -1641,7 +1684,7 @@ static Token yylex_NORMAL() {
|
||||
|
||||
case '@': {
|
||||
std::string symName("@");
|
||||
return Token(T_(ID), symName);
|
||||
return Token(T_(SYMBOL), symName);
|
||||
}
|
||||
|
||||
case '[':
|
||||
@@ -1903,15 +1946,15 @@ static Token yylex_NORMAL() {
|
||||
}
|
||||
|
||||
// If a keyword, don't try to expand
|
||||
if (token.type != T_(ID) && token.type != T_(LOCAL_ID)) {
|
||||
if (token.type != T_(SYMBOL) && token.type != T_(LOCAL)) {
|
||||
return token;
|
||||
}
|
||||
|
||||
// `token` is either an `ID` or a `LOCAL_ID`, and both have a `std::string` value.
|
||||
// `token` is either a `SYMBOL` or a `LOCAL`, and both have a `std::string` value.
|
||||
assume(token.value.holds<std::string>());
|
||||
|
||||
// Local symbols cannot be string expansions
|
||||
if (token.type == T_(ID) && lexerState->expandStrings) {
|
||||
// Raw symbols and local symbols cannot be string expansions
|
||||
if (!raw && token.type == T_(SYMBOL) && lexerState->expandStrings) {
|
||||
// Attempt string expansion
|
||||
Symbol const *sym = sym_FindExactSymbol(token.value.get<std::string>());
|
||||
|
||||
@@ -1925,18 +1968,18 @@ static Token yylex_NORMAL() {
|
||||
}
|
||||
|
||||
// This is a "lexer hack"! We need it to distinguish between label definitions
|
||||
// (which start with `LABEL`) and macro invocations (which start with `ID`).
|
||||
// (which start with `LABEL`) and macro invocations (which start with `SYMBOL`).
|
||||
//
|
||||
// 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 their arguments, which may not even be valid tokens in "normal" mode, we
|
||||
// cannot use lookahead to check for the presence of a `COLON`.
|
||||
//
|
||||
// Instead, we have separate `ID` and `LABEL` tokens, lexing as a `LABEL` if a ':'
|
||||
// character *immediately* follows the identifier. Thus, at the beginning of a line,
|
||||
// "Label:" and "mac:" are treated as label definitions, but "Label :" and "mac :"
|
||||
// are treated as macro invocations.
|
||||
if (token.type == T_(ID) && peek() == ':') {
|
||||
// Instead, we have separate `SYMBOL` and `LABEL` tokens, lexing as a `LABEL` if a
|
||||
// ':' character *immediately* follows the identifier. Thus, at the beginning of a
|
||||
// line, "Label:" and "mac:" are treated as label definitions, but "Label :" and
|
||||
// "mac :" are treated as macro invocations.
|
||||
if (token.type == T_(SYMBOL) && peek() == ':') {
|
||||
token.type = T_(LABEL);
|
||||
}
|
||||
|
||||
@@ -1945,10 +1988,21 @@ static Token yylex_NORMAL() {
|
||||
|
||||
// Do not report weird characters when capturing, it'll be done later
|
||||
if (!lexerState->capturing) {
|
||||
// TODO: try to group reportings
|
||||
assume(isGarbageCharacter(c) || c == '#');
|
||||
if (isGarbageCharacter(peek())) {
|
||||
// At least two characters are garbage; group them into one error report
|
||||
std::string garbage = printChar(c);
|
||||
while (isGarbageCharacter(peek())) {
|
||||
c = nextChar();
|
||||
garbage += ", ";
|
||||
garbage += printChar(c);
|
||||
}
|
||||
error("Unknown characters %s\n", garbage.c_str());
|
||||
} else {
|
||||
error("Unknown character %s\n", printChar(c));
|
||||
}
|
||||
}
|
||||
}
|
||||
lexerState->atLineStart = false;
|
||||
}
|
||||
}
|
||||
@@ -2090,7 +2144,7 @@ append:
|
||||
}
|
||||
}
|
||||
|
||||
finish:
|
||||
finish: // Can't `break` out of a nested `for`-`switch`
|
||||
// Trim right whitespace
|
||||
auto rightPos = std::find_if_not(str.rbegin(), str.rend(), isWhitespace);
|
||||
str.resize(rightPos.base() - str.begin());
|
||||
@@ -2158,7 +2212,8 @@ static Token skipIfBlock(bool toEndc) {
|
||||
|
||||
case T_(POP_ELIF):
|
||||
if (lexer_ReachedELSEBlock()) {
|
||||
fatalerror("Found ELIF after an ELSE block\n");
|
||||
// This should be redundant, as the parser handles this error first.
|
||||
fatalerror("Found ELIF after an ELSE block\n"); // LCOV_EXCL_LINE
|
||||
}
|
||||
if (!toEndc && lexer_GetIFDepth() == startingDepth) {
|
||||
return token;
|
||||
@@ -2250,9 +2305,8 @@ static Token yylex_SKIP_TO_ENDR() {
|
||||
|
||||
case T_(POP_ENDR):
|
||||
depth--;
|
||||
if (!depth) {
|
||||
return Token(T_(YYEOF)); // yywrap() will finish the REPT/FOR loop
|
||||
}
|
||||
// `lexer_CaptureRept` has already guaranteed that the `ENDR`s are balanced
|
||||
assume(depth > 0);
|
||||
break;
|
||||
|
||||
case T_(POP_IF):
|
||||
@@ -2390,7 +2444,7 @@ Capture lexer_CaptureRept() {
|
||||
do { // Discard initial whitespace
|
||||
c = nextChar();
|
||||
} while (isWhitespace(c));
|
||||
// Now, try to match `REPT`, `FOR` or `ENDR` as a **whole** identifier
|
||||
// Now, try to match `REPT`, `FOR` or `ENDR` as a **whole** keyword
|
||||
if (startsIdentifier(c)) {
|
||||
switch (readIdentifier(c, false).type) {
|
||||
case T_(POP_REPT):
|
||||
@@ -2443,7 +2497,7 @@ Capture lexer_CaptureMacro() {
|
||||
do { // Discard initial whitespace
|
||||
c = nextChar();
|
||||
} while (isWhitespace(c));
|
||||
// Now, try to match `ENDM` as a **whole** identifier
|
||||
// Now, try to match `ENDM` as a **whole** keyword
|
||||
if (startsIdentifier(c)) {
|
||||
switch (readIdentifier(c, false).type) {
|
||||
case T_(POP_ENDM):
|
||||
|
||||
@@ -8,10 +8,16 @@
|
||||
|
||||
#include "asm/warning.hpp"
|
||||
|
||||
std::shared_ptr<std::string> MacroArgs::getArg(uint32_t i) const {
|
||||
uint32_t realIndex = i + shift - 1;
|
||||
std::shared_ptr<std::string> MacroArgs::getArg(int32_t i) const {
|
||||
// Bracketed macro arguments adjust negative indexes such that -1 is the last argument.
|
||||
if (i < 0) {
|
||||
i += args.size() + 1;
|
||||
}
|
||||
|
||||
return realIndex >= args.size() ? nullptr : args[realIndex];
|
||||
int32_t realIndex = i + shift - 1;
|
||||
|
||||
return realIndex < 0 || static_cast<uint32_t>(realIndex) >= args.size() ? nullptr
|
||||
: args[realIndex];
|
||||
}
|
||||
|
||||
std::shared_ptr<std::string> MacroArgs::getAllArgs() const {
|
||||
|
||||
@@ -87,6 +87,7 @@ static option const longopts[] = {
|
||||
{nullptr, no_argument, nullptr, 0 }
|
||||
};
|
||||
|
||||
// LCOV_EXCL_START
|
||||
static void printUsage() {
|
||||
fputs(
|
||||
"Usage: rgbasm [-EhVvw] [-b chars] [-D name[=value]] [-g chars] [-I path]\n"
|
||||
@@ -107,6 +108,7 @@ static void printUsage() {
|
||||
stderr
|
||||
);
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
time_t now = time(nullptr);
|
||||
@@ -176,8 +178,10 @@ int main(int argc, char *argv[]) {
|
||||
break;
|
||||
|
||||
case 'h':
|
||||
// LCOV_EXCL_START
|
||||
printUsage();
|
||||
exit(0);
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
case 'I':
|
||||
fstk_AddIncludePath(musl_optarg);
|
||||
@@ -195,7 +199,7 @@ int main(int argc, char *argv[]) {
|
||||
dependFileName = "<stdout>";
|
||||
}
|
||||
if (dependFile == nullptr) {
|
||||
err("Failed to open dependfile \"%s\"", dependFileName);
|
||||
err("Failed to open dependfile \"%s\"", dependFileName); // LCOV_EXCL_LINE
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -302,9 +306,11 @@ int main(int argc, char *argv[]) {
|
||||
if (stateFileSpecs.find(name) != stateFileSpecs.end()) {
|
||||
warnx("Overriding state filename %s", name);
|
||||
}
|
||||
// LCOV_EXCL_START
|
||||
if (verbose) {
|
||||
printf("State filename %s\n", name);
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
stateFileSpecs.emplace(name, std::move(features));
|
||||
break;
|
||||
}
|
||||
@@ -314,8 +320,10 @@ int main(int argc, char *argv[]) {
|
||||
exit(0);
|
||||
|
||||
case 'v':
|
||||
// LCOV_EXCL_START
|
||||
verbose = true;
|
||||
break;
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
case 'W':
|
||||
opt_W(musl_optarg);
|
||||
@@ -367,8 +375,10 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
// Unrecognized options
|
||||
default:
|
||||
// LCOV_EXCL_START
|
||||
printUsage();
|
||||
exit(1);
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
}
|
||||
|
||||
@@ -391,7 +401,7 @@ int main(int argc, char *argv[]) {
|
||||
std::string mainFileName = argv[musl_optind];
|
||||
|
||||
if (verbose) {
|
||||
printf("Assembling %s\n", mainFileName.c_str());
|
||||
printf("Assembling %s\n", mainFileName.c_str()); // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
if (dependFile) {
|
||||
|
||||
@@ -61,7 +61,7 @@ void out_RegisterNode(std::shared_ptr<FileStackNode> node) {
|
||||
}
|
||||
}
|
||||
|
||||
// Return a section's ID, or UINT32_MAX if the section is not in the list
|
||||
// Return a section's ID, or UINT32_MAX if the section does not exist
|
||||
static uint32_t getSectIDIfAny(Section *sect) {
|
||||
if (!sect) {
|
||||
return UINT32_MAX;
|
||||
@@ -71,7 +71,8 @@ static uint32_t getSectIDIfAny(Section *sect) {
|
||||
return static_cast<uint32_t>(search->second);
|
||||
}
|
||||
|
||||
fatalerror("Unknown section '%s'\n", sect->name.c_str());
|
||||
// Every section that exists should be in `sectionMap`
|
||||
fatalerror("Unknown section '%s'\n", sect->name.c_str()); // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
static void writePatch(Patch const &patch, FILE *file) {
|
||||
@@ -175,7 +176,7 @@ static void writeRpn(std::vector<uint8_t> &rpnexpr, std::vector<uint8_t> const &
|
||||
sym = sym_FindExactSymbol(symName);
|
||||
if (sym->isConstant()) {
|
||||
rpnexpr[rpnptr++] = RPN_CONST;
|
||||
value = sym_GetConstantValue(symName);
|
||||
value = sym->getConstantValue();
|
||||
} else {
|
||||
rpnexpr[rpnptr++] = RPN_SYM;
|
||||
registerUnregisteredSymbol(*sym); // Ensure that `sym->ID` is set
|
||||
@@ -323,7 +324,7 @@ void out_WriteObject() {
|
||||
file = stdout;
|
||||
}
|
||||
if (!file) {
|
||||
err("Failed to open object file '%s'", objectFileName.c_str());
|
||||
err("Failed to open object file '%s'", objectFileName.c_str()); // LCOV_EXCL_LINE
|
||||
}
|
||||
Defer closeFile{[&] { fclose(file); }};
|
||||
|
||||
@@ -343,14 +344,7 @@ void out_WriteObject() {
|
||||
writeFileStackNode(node, file);
|
||||
|
||||
// The list is supposed to have decrementing IDs
|
||||
if (it + 1 != fileStackNodes.end() && it[1]->ID != node.ID - 1) {
|
||||
fatalerror(
|
||||
"Internal error: fstack node #%" PRIu32 " follows #%" PRIu32
|
||||
". Please report this to the developers!\n",
|
||||
it[1]->ID,
|
||||
node.ID
|
||||
);
|
||||
}
|
||||
assume(it + 1 == fileStackNodes.end() || it[1]->ID == node.ID - 1);
|
||||
}
|
||||
|
||||
for (Symbol const *sym : objectSymbols) {
|
||||
@@ -373,9 +367,11 @@ void out_SetFileName(std::string const &name) {
|
||||
warnx("Overriding output filename %s", objectFileName.c_str());
|
||||
}
|
||||
objectFileName = name;
|
||||
// LCOV_EXCL_START
|
||||
if (verbose) {
|
||||
printf("Output filename %s\n", objectFileName.c_str());
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
static void dumpString(std::string const &escape, FILE *file) {
|
||||
@@ -528,7 +524,7 @@ void out_WriteState(std::string name, std::vector<StateFeature> const &features)
|
||||
file = stdout;
|
||||
}
|
||||
if (!file) {
|
||||
err("Failed to open state file '%s'", name.c_str());
|
||||
err("Failed to open state file '%s'", name.c_str()); // LCOV_EXCL_LINE
|
||||
}
|
||||
Defer closeFile{[&] { fclose(file); }};
|
||||
|
||||
|
||||
557
src/asm/parser.y
557
src/asm/parser.y
@@ -31,15 +31,6 @@
|
||||
struct StrFmtArgList {
|
||||
std::string format;
|
||||
std::vector<Either<uint32_t, std::string>> args;
|
||||
|
||||
StrFmtArgList() = default;
|
||||
StrFmtArgList(StrFmtArgList &&) = default;
|
||||
#ifdef _MSC_VER
|
||||
// MSVC and WinFlexBison won't build without this...
|
||||
StrFmtArgList(StrFmtArgList const &) = default;
|
||||
#endif
|
||||
|
||||
StrFmtArgList &operator=(StrFmtArgList &&) = default;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -73,9 +64,13 @@
|
||||
static uint32_t strToNum(std::vector<int32_t> const &s);
|
||||
static void errorInvalidUTF8Byte(uint8_t byte, char const *functionName);
|
||||
static size_t strlenUTF8(std::string const &str, bool printErrors);
|
||||
static std::string strsliceUTF8(std::string const &str, uint32_t start, uint32_t stop);
|
||||
static std::string strsubUTF8(std::string const &str, uint32_t pos, uint32_t len);
|
||||
static size_t charlenUTF8(std::string const &str);
|
||||
static std::string strcharUTF8(std::string const &str, uint32_t idx);
|
||||
static std::string charsubUTF8(std::string const &str, uint32_t pos);
|
||||
static int32_t charcmp(std::string_view str1, std::string_view str2);
|
||||
static uint32_t adjustNegativeIndex(int32_t idx, size_t len, char const *functionName);
|
||||
static uint32_t adjustNegativePos(int32_t pos, size_t len, char const *functionName);
|
||||
static std::string strrpl(std::string_view str, std::string const &old, std::string const &rep);
|
||||
static std::string strfmt(
|
||||
@@ -85,6 +80,17 @@
|
||||
static void failAssert(AssertionType type);
|
||||
static void failAssertMsg(AssertionType type, std::string const &message);
|
||||
|
||||
template <typename N, typename S>
|
||||
static auto handleSymbolByType(std::string const &symName, N numCallback, S strCallback) {
|
||||
if (Symbol *sym = sym_FindScopedSymbol(symName); sym && sym->type == SYM_EQUS) {
|
||||
return strCallback(*sym->getEqus());
|
||||
} else {
|
||||
Expression expr;
|
||||
expr.makeSymbol(symName);
|
||||
return numCallback(expr);
|
||||
}
|
||||
}
|
||||
|
||||
// The CPU encodes instructions in a logical way, so most instructions actually follow patterns.
|
||||
// These enums thus help with bit twiddling to compute opcodes.
|
||||
enum { REG_B, REG_C, REG_D, REG_E, REG_H, REG_L, REG_HL_IND, REG_A };
|
||||
@@ -93,6 +99,10 @@
|
||||
|
||||
// REG_AF == REG_SP since LD/INC/ADD/DEC allow SP, while PUSH/POP allow AF
|
||||
enum { REG_BC, REG_DE, REG_HL, REG_SP, REG_AF = REG_SP };
|
||||
// Names are not needed for AF or SP
|
||||
static char const *reg_tt_names[] = { "BC", "DE", "HL" };
|
||||
static char const *reg_tt_high_names[] = { "B", "D", "H" };
|
||||
static char const *reg_tt_low_names[] = { "C", "E", "L" };
|
||||
|
||||
// CC_NZ == CC_Z ^ 1, and CC_NC == CC_C ^ 1, so `!` can toggle them
|
||||
enum { CC_NZ, CC_Z, CC_NC, CC_C };
|
||||
@@ -269,7 +279,9 @@
|
||||
%token OP_BANK "BANK"
|
||||
%token OP_BITWIDTH "BITWIDTH"
|
||||
%token OP_CEIL "CEIL"
|
||||
%token OP_CHARCMP "CHARCMP"
|
||||
%token OP_CHARLEN "CHARLEN"
|
||||
%token OP_CHARSIZE "CHARSIZE"
|
||||
%token OP_CHARSUB "CHARSUB"
|
||||
%token OP_COS "COS"
|
||||
%token OP_DEF "DEF"
|
||||
@@ -283,18 +295,23 @@
|
||||
%token OP_LOG "LOG"
|
||||
%token OP_LOW "LOW"
|
||||
%token OP_POW "POW"
|
||||
%token OP_REVCHAR "REVCHAR"
|
||||
%token OP_ROUND "ROUND"
|
||||
%token OP_SIN "SIN"
|
||||
%token OP_SIZEOF "SIZEOF"
|
||||
%token OP_STARTOF "STARTOF"
|
||||
%token OP_STRCAT "STRCAT"
|
||||
%token OP_STRCHAR "STRCHAR"
|
||||
%token OP_STRCMP "STRCMP"
|
||||
%token OP_STRFIND "STRFIND"
|
||||
%token OP_STRFMT "STRFMT"
|
||||
%token OP_STRIN "STRIN"
|
||||
%token OP_STRLEN "STRLEN"
|
||||
%token OP_STRLWR "STRLWR"
|
||||
%token OP_STRRFIND "STRRFIND"
|
||||
%token OP_STRRIN "STRRIN"
|
||||
%token OP_STRRPL "STRRPL"
|
||||
%token OP_STRSLICE "STRSLICE"
|
||||
%token OP_STRSUB "STRSUB"
|
||||
%token OP_STRUPR "STRUPR"
|
||||
%token OP_TAN "TAN"
|
||||
@@ -313,31 +330,27 @@
|
||||
// Literals
|
||||
%token <int32_t> NUMBER "number"
|
||||
%token <std::string> STRING "string"
|
||||
%token <std::string> SYMBOL "symbol"
|
||||
%token <std::string> LABEL "label"
|
||||
%token <std::string> ID "identifier"
|
||||
%token <std::string> LOCAL_ID "local identifier"
|
||||
%token <std::string> LOCAL "local label"
|
||||
%token <std::string> ANON "anonymous label"
|
||||
|
||||
/******************** Data types ********************/
|
||||
|
||||
// The "no_str" types below are to distinguish numeric and string expressions, since many
|
||||
// contexts treat strings differently than numbers, e.g. `db "string"` or `print "string"`.
|
||||
|
||||
// RPN expressions
|
||||
%type <Expression> relocexpr
|
||||
// `relocexpr_no_str` exists because strings usually count as numeric expressions, but some
|
||||
// contexts treat numbers and strings differently, e.g. `db "string"` or `print "string"`.
|
||||
%type <Expression> relocexpr_no_str
|
||||
%type <Expression> reloc_3bit
|
||||
%type <Expression> reloc_8bit
|
||||
%type <Expression> reloc_8bit_no_str
|
||||
%type <Expression> reloc_8bit_offset
|
||||
%type <Expression> reloc_16bit
|
||||
%type <Expression> reloc_16bit_no_str
|
||||
|
||||
// Constant numbers
|
||||
%type <int32_t> iconst
|
||||
%type <int32_t> const_no_str
|
||||
%type <int32_t> uconst
|
||||
// Constant numbers used only in specific contexts
|
||||
%type <int32_t> bit_const
|
||||
%type <int32_t> precision_arg
|
||||
%type <int32_t> rs_uconst
|
||||
%type <int32_t> sect_org
|
||||
@@ -345,6 +358,7 @@
|
||||
|
||||
// Strings
|
||||
%type <std::string> string
|
||||
%type <std::string> string_literal
|
||||
%type <std::string> strcat_args
|
||||
// Strings used for identifiers
|
||||
%type <std::string> def_id
|
||||
@@ -358,8 +372,10 @@
|
||||
%type <std::string> def_rl
|
||||
%type <std::string> def_equs
|
||||
%type <std::string> redef_equs
|
||||
%type <std::string> scoped_id
|
||||
%type <std::string> scoped_anon_id
|
||||
%type <std::string> scoped_sym
|
||||
// `scoped_sym_no_anon` exists because anonymous labels usually count as "scoped symbols", but some
|
||||
// contexts treat anonymous labels and other labels/symbols differently, e.g. `purge` or `export`.
|
||||
%type <std::string> scoped_sym_no_anon
|
||||
|
||||
// SM83 instruction parameters
|
||||
%type <int32_t> reg_r
|
||||
@@ -368,6 +384,8 @@
|
||||
%type <int32_t> reg_ss
|
||||
%type <int32_t> reg_rr
|
||||
%type <int32_t> reg_tt
|
||||
%type <int32_t> reg_tt_no_af
|
||||
%type <int32_t> reg_bc_or_de
|
||||
%type <int32_t> ccode_expr
|
||||
%type <int32_t> ccode
|
||||
%type <Expression> op_a_n
|
||||
@@ -516,7 +534,7 @@ endc:
|
||||
def_id:
|
||||
OP_DEF {
|
||||
lexer_ToggleStringExpansion(false);
|
||||
} ID {
|
||||
} SYMBOL {
|
||||
lexer_ToggleStringExpansion(true);
|
||||
$$ = std::move($3);
|
||||
}
|
||||
@@ -525,61 +543,42 @@ def_id:
|
||||
redef_id:
|
||||
POP_REDEF {
|
||||
lexer_ToggleStringExpansion(false);
|
||||
} ID {
|
||||
} SYMBOL {
|
||||
lexer_ToggleStringExpansion(true);
|
||||
$$ = std::move($3);
|
||||
}
|
||||
;
|
||||
|
||||
// LABEL covers identifiers followed by a double colon (e.g. `call Function::ret`,
|
||||
// to be read as `call Function :: ret`). This should not conflict with anything.
|
||||
scoped_id:
|
||||
ID {
|
||||
$$ = std::move($1);
|
||||
}
|
||||
| LOCAL_ID {
|
||||
$$ = std::move($1);
|
||||
}
|
||||
| LABEL {
|
||||
$$ = std::move($1);
|
||||
}
|
||||
;
|
||||
scoped_sym_no_anon: SYMBOL | LABEL | LOCAL;
|
||||
|
||||
scoped_anon_id:
|
||||
scoped_id {
|
||||
$$ = std::move($1);
|
||||
}
|
||||
| ANON {
|
||||
$$ = std::move($1);
|
||||
}
|
||||
;
|
||||
scoped_sym: scoped_sym_no_anon | ANON;
|
||||
|
||||
label:
|
||||
%empty
|
||||
| COLON {
|
||||
sym_AddAnonLabel();
|
||||
}
|
||||
| LOCAL_ID {
|
||||
sym_AddLocalLabel($1);
|
||||
}
|
||||
| LOCAL_ID COLON {
|
||||
sym_AddLocalLabel($1);
|
||||
}
|
||||
| LABEL COLON {
|
||||
sym_AddLabel($1);
|
||||
}
|
||||
| LOCAL_ID DOUBLE_COLON {
|
||||
sym_AddLocalLabel($1);
|
||||
sym_Export($1);
|
||||
}
|
||||
| LABEL DOUBLE_COLON {
|
||||
sym_AddLabel($1);
|
||||
sym_Export($1);
|
||||
}
|
||||
| LOCAL {
|
||||
sym_AddLocalLabel($1);
|
||||
}
|
||||
| LOCAL COLON {
|
||||
sym_AddLocalLabel($1);
|
||||
}
|
||||
| LOCAL DOUBLE_COLON {
|
||||
sym_AddLocalLabel($1);
|
||||
sym_Export($1);
|
||||
}
|
||||
| COLON {
|
||||
sym_AddAnonLabel();
|
||||
}
|
||||
;
|
||||
|
||||
macro:
|
||||
ID {
|
||||
SYMBOL {
|
||||
// Parsing 'macro_args' will restore the lexer's normal mode
|
||||
lexer_SetMode(LEXER_RAW);
|
||||
} macro_args {
|
||||
@@ -865,7 +864,7 @@ rept:
|
||||
for:
|
||||
POP_FOR {
|
||||
lexer_ToggleStringExpansion(false);
|
||||
} ID {
|
||||
} SYMBOL {
|
||||
lexer_ToggleStringExpansion(true);
|
||||
} COMMA for_args NEWLINE capture_rept endofline {
|
||||
if ($8.span.ptr) {
|
||||
@@ -909,7 +908,7 @@ break:
|
||||
def_macro:
|
||||
POP_MACRO {
|
||||
lexer_ToggleStringExpansion(false);
|
||||
} ID {
|
||||
} SYMBOL {
|
||||
lexer_ToggleStringExpansion(true);
|
||||
} NEWLINE capture_macro endofline {
|
||||
if ($6.span.ptr) {
|
||||
@@ -1099,10 +1098,10 @@ purge:
|
||||
;
|
||||
|
||||
purge_args:
|
||||
scoped_id {
|
||||
scoped_sym_no_anon {
|
||||
$$.push_back($1);
|
||||
}
|
||||
| purge_args COMMA scoped_id {
|
||||
| purge_args COMMA scoped_sym_no_anon {
|
||||
$$ = std::move($1);
|
||||
$$.push_back($3);
|
||||
}
|
||||
@@ -1116,7 +1115,7 @@ export_list:
|
||||
;
|
||||
|
||||
export_list_entry:
|
||||
scoped_id {
|
||||
scoped_sym_no_anon {
|
||||
sym_Export($1);
|
||||
}
|
||||
;
|
||||
@@ -1174,16 +1173,16 @@ charmap_args:
|
||||
;
|
||||
|
||||
newcharmap:
|
||||
POP_NEWCHARMAP ID {
|
||||
POP_NEWCHARMAP SYMBOL {
|
||||
charmap_New($2, nullptr);
|
||||
}
|
||||
| POP_NEWCHARMAP ID COMMA ID {
|
||||
| POP_NEWCHARMAP SYMBOL COMMA SYMBOL {
|
||||
charmap_New($2, &$4);
|
||||
}
|
||||
;
|
||||
|
||||
setcharmap:
|
||||
POP_SETCHARMAP ID {
|
||||
POP_SETCHARMAP SYMBOL {
|
||||
charmap_Set($2);
|
||||
}
|
||||
;
|
||||
@@ -1195,7 +1194,7 @@ pushc:
|
||||
;
|
||||
|
||||
pushc_setcharmap:
|
||||
POP_PUSHC ID {
|
||||
POP_PUSHC SYMBOL {
|
||||
charmap_Push();
|
||||
charmap_Set($2);
|
||||
}
|
||||
@@ -1226,22 +1225,26 @@ print_exprs:
|
||||
;
|
||||
|
||||
print_expr:
|
||||
const_no_str {
|
||||
printf("$%" PRIX32, $1);
|
||||
relocexpr_no_str {
|
||||
printf("$%" PRIX32, $1.getConstVal());
|
||||
}
|
||||
| string {
|
||||
| string_literal {
|
||||
// Allow printing NUL characters
|
||||
fwrite($1.data(), 1, $1.length(), stdout);
|
||||
}
|
||||
| scoped_sym {
|
||||
handleSymbolByType(
|
||||
$1,
|
||||
[](Expression const &expr) { printf("$%" PRIX32, expr.getConstVal()); },
|
||||
[](std::string const &str) { fwrite(str.data(), 1, str.length(), stdout); }
|
||||
);
|
||||
}
|
||||
;
|
||||
|
||||
bit_const:
|
||||
iconst {
|
||||
$$ = $1;
|
||||
if ($$ < 0 || $$ > 7) {
|
||||
::error("Bit number must be between 0 and 7, not %" PRId32 "\n", $$);
|
||||
$$ = 0;
|
||||
}
|
||||
reloc_3bit:
|
||||
relocexpr {
|
||||
$$ = std::move($1);
|
||||
$$.checkNBit(3);
|
||||
}
|
||||
;
|
||||
|
||||
@@ -1251,13 +1254,27 @@ constlist_8bit:
|
||||
;
|
||||
|
||||
constlist_8bit_entry:
|
||||
reloc_8bit_no_str {
|
||||
relocexpr_no_str {
|
||||
$1.checkNBit(8);
|
||||
sect_RelByte($1, 0);
|
||||
}
|
||||
| string {
|
||||
| string_literal {
|
||||
std::vector<int32_t> output = charmap_Convert($1);
|
||||
sect_ByteString(output);
|
||||
}
|
||||
| scoped_sym {
|
||||
handleSymbolByType(
|
||||
$1,
|
||||
[](Expression const &expr) {
|
||||
expr.checkNBit(8);
|
||||
sect_RelByte(expr, 0);
|
||||
},
|
||||
[](std::string const &str) {
|
||||
std::vector<int32_t> output = charmap_Convert(str);
|
||||
sect_ByteString(output);
|
||||
}
|
||||
);
|
||||
}
|
||||
;
|
||||
|
||||
constlist_16bit:
|
||||
@@ -1266,13 +1283,27 @@ constlist_16bit:
|
||||
;
|
||||
|
||||
constlist_16bit_entry:
|
||||
reloc_16bit_no_str {
|
||||
relocexpr_no_str {
|
||||
$1.checkNBit(16);
|
||||
sect_RelWord($1, 0);
|
||||
}
|
||||
| string {
|
||||
| string_literal {
|
||||
std::vector<int32_t> output = charmap_Convert($1);
|
||||
sect_WordString(output);
|
||||
}
|
||||
| scoped_sym {
|
||||
handleSymbolByType(
|
||||
$1,
|
||||
[](Expression const &expr) {
|
||||
expr.checkNBit(16);
|
||||
sect_RelWord(expr, 0);
|
||||
},
|
||||
[](std::string const &str) {
|
||||
std::vector<int32_t> output = charmap_Convert(str);
|
||||
sect_WordString(output);
|
||||
}
|
||||
);
|
||||
}
|
||||
;
|
||||
|
||||
constlist_32bit:
|
||||
@@ -1284,10 +1315,20 @@ constlist_32bit_entry:
|
||||
relocexpr_no_str {
|
||||
sect_RelLong($1, 0);
|
||||
}
|
||||
| string {
|
||||
| string_literal {
|
||||
std::vector<int32_t> output = charmap_Convert($1);
|
||||
sect_LongString(output);
|
||||
}
|
||||
| scoped_sym {
|
||||
handleSymbolByType(
|
||||
$1,
|
||||
[](Expression const &expr) { sect_RelLong(expr, 0); },
|
||||
[](std::string const &str) {
|
||||
std::vector<int32_t> output = charmap_Convert(str);
|
||||
sect_LongString(output);
|
||||
}
|
||||
);
|
||||
}
|
||||
;
|
||||
|
||||
reloc_8bit:
|
||||
@@ -1297,13 +1338,6 @@ reloc_8bit:
|
||||
}
|
||||
;
|
||||
|
||||
reloc_8bit_no_str:
|
||||
relocexpr_no_str {
|
||||
$$ = std::move($1);
|
||||
$$.checkNBit(8);
|
||||
}
|
||||
;
|
||||
|
||||
reloc_8bit_offset:
|
||||
OP_ADD relocexpr {
|
||||
$$ = std::move($2);
|
||||
@@ -1322,28 +1356,30 @@ reloc_16bit:
|
||||
}
|
||||
;
|
||||
|
||||
reloc_16bit_no_str:
|
||||
relocexpr_no_str {
|
||||
$$ = std::move($1);
|
||||
$$.checkNBit(16);
|
||||
}
|
||||
;
|
||||
|
||||
relocexpr:
|
||||
relocexpr_no_str {
|
||||
$$ = std::move($1);
|
||||
}
|
||||
| string {
|
||||
| string_literal {
|
||||
std::vector<int32_t> output = charmap_Convert($1);
|
||||
$$.makeNumber(strToNum(output));
|
||||
}
|
||||
| scoped_sym {
|
||||
$$ = handleSymbolByType(
|
||||
$1,
|
||||
[](Expression const &expr) { return expr; },
|
||||
[](std::string const &str) {
|
||||
std::vector<int32_t> output = charmap_Convert(str);
|
||||
Expression expr;
|
||||
expr.makeNumber(strToNum(output));
|
||||
return expr;
|
||||
}
|
||||
);
|
||||
}
|
||||
;
|
||||
|
||||
relocexpr_no_str:
|
||||
scoped_anon_id {
|
||||
$$.makeSymbol($1);
|
||||
}
|
||||
| NUMBER {
|
||||
NUMBER {
|
||||
$$.makeNumber($1);
|
||||
}
|
||||
| OP_LOGICNOT relocexpr %prec NEG {
|
||||
@@ -1433,11 +1469,11 @@ relocexpr_no_str:
|
||||
| OP_ISCONST LPAREN relocexpr RPAREN {
|
||||
$$.makeNumber($3.isKnown());
|
||||
}
|
||||
| OP_BANK LPAREN scoped_anon_id RPAREN {
|
||||
// '@' is also an ID; it is handled here
|
||||
| OP_BANK LPAREN scoped_sym RPAREN {
|
||||
// '@' is also a SYMBOL; it is handled here
|
||||
$$.makeBankSymbol($3);
|
||||
}
|
||||
| OP_BANK LPAREN string RPAREN {
|
||||
| OP_BANK LPAREN string_literal RPAREN {
|
||||
$$.makeBankSection($3);
|
||||
}
|
||||
| OP_SIZEOF LPAREN string RPAREN {
|
||||
@@ -1454,7 +1490,7 @@ relocexpr_no_str:
|
||||
}
|
||||
| OP_DEF {
|
||||
lexer_ToggleStringExpansion(false);
|
||||
} LPAREN scoped_anon_id RPAREN {
|
||||
} LPAREN scoped_sym RPAREN {
|
||||
$$.makeNumber(sym_FindScopedValidSymbol($4) != nullptr);
|
||||
lexer_ToggleStringExpansion(true);
|
||||
}
|
||||
@@ -1506,14 +1542,20 @@ relocexpr_no_str:
|
||||
| OP_STRCMP LPAREN string COMMA string RPAREN {
|
||||
$$.makeNumber($3.compare($5));
|
||||
}
|
||||
| OP_STRFIND LPAREN string COMMA string RPAREN {
|
||||
size_t pos = $3.find($5);
|
||||
$$.makeNumber(pos != std::string::npos ? pos : -1);
|
||||
}
|
||||
| OP_STRRFIND LPAREN string COMMA string RPAREN {
|
||||
size_t pos = $3.rfind($5);
|
||||
$$.makeNumber(pos != std::string::npos ? pos : -1);
|
||||
}
|
||||
| OP_STRIN LPAREN string COMMA string RPAREN {
|
||||
auto pos = $3.find($5);
|
||||
|
||||
size_t pos = $3.find($5);
|
||||
$$.makeNumber(pos != std::string::npos ? pos + 1 : 0);
|
||||
}
|
||||
| OP_STRRIN LPAREN string COMMA string RPAREN {
|
||||
auto pos = $3.rfind($5);
|
||||
|
||||
size_t pos = $3.rfind($5);
|
||||
$$.makeNumber(pos != std::string::npos ? pos + 1 : 0);
|
||||
}
|
||||
| OP_STRLEN LPAREN string RPAREN {
|
||||
@@ -1525,6 +1567,16 @@ relocexpr_no_str:
|
||||
| OP_INCHARMAP LPAREN string RPAREN {
|
||||
$$.makeNumber(charmap_HasChar($3));
|
||||
}
|
||||
| OP_CHARCMP LPAREN string COMMA string RPAREN {
|
||||
$$.makeNumber(charcmp($3, $5));
|
||||
}
|
||||
| OP_CHARSIZE LPAREN string RPAREN {
|
||||
size_t charSize = charmap_CharSize($3);
|
||||
if (charSize == 0) {
|
||||
::error("CHARSIZE: No character mapping for \"%s\"\n", $3.c_str());
|
||||
}
|
||||
$$.makeNumber(charSize);
|
||||
}
|
||||
| LPAREN relocexpr RPAREN {
|
||||
$$ = std::move($2);
|
||||
}
|
||||
@@ -1545,12 +1597,6 @@ iconst:
|
||||
}
|
||||
;
|
||||
|
||||
const_no_str:
|
||||
relocexpr_no_str {
|
||||
$$ = $1.getConstVal();
|
||||
}
|
||||
;
|
||||
|
||||
precision_arg:
|
||||
%empty {
|
||||
$$ = fix_Precision();
|
||||
@@ -1564,28 +1610,50 @@ precision_arg:
|
||||
}
|
||||
;
|
||||
|
||||
string:
|
||||
string_literal:
|
||||
STRING {
|
||||
$$ = std::move($1);
|
||||
}
|
||||
| OP_STRSLICE LPAREN string COMMA iconst COMMA iconst RPAREN {
|
||||
size_t len = strlenUTF8($3, false);
|
||||
uint32_t start = adjustNegativeIndex($5, len, "STRSLICE");
|
||||
uint32_t stop = adjustNegativeIndex($7, len, "STRSLICE");
|
||||
$$ = strsliceUTF8($3, start, stop);
|
||||
}
|
||||
| OP_STRSLICE LPAREN string COMMA iconst RPAREN {
|
||||
size_t len = strlenUTF8($3, false);
|
||||
uint32_t start = adjustNegativeIndex($5, len, "STRSLICE");
|
||||
$$ = strsliceUTF8($3, start, len - 1);
|
||||
}
|
||||
| OP_STRSUB LPAREN string COMMA iconst COMMA uconst RPAREN {
|
||||
size_t len = strlenUTF8($3, false);
|
||||
uint32_t pos = adjustNegativePos($5, len, "STRSUB");
|
||||
|
||||
$$ = strsubUTF8($3, pos, $7);
|
||||
}
|
||||
| OP_STRSUB LPAREN string COMMA iconst RPAREN {
|
||||
size_t len = strlenUTF8($3, false);
|
||||
uint32_t pos = adjustNegativePos($5, len, "STRSUB");
|
||||
|
||||
$$ = strsubUTF8($3, pos, pos > len ? 0 : len + 1 - pos);
|
||||
}
|
||||
| OP_STRCHAR LPAREN string COMMA iconst RPAREN {
|
||||
size_t len = charlenUTF8($3);
|
||||
uint32_t idx = adjustNegativeIndex($5, len, "STRCHAR");
|
||||
$$ = strcharUTF8($3, idx);
|
||||
}
|
||||
| OP_CHARSUB LPAREN string COMMA iconst RPAREN {
|
||||
size_t len = charlenUTF8($3);
|
||||
uint32_t pos = adjustNegativePos($5, len, "CHARSUB");
|
||||
|
||||
$$ = charsubUTF8($3, pos);
|
||||
}
|
||||
| OP_REVCHAR LPAREN charmap_args RPAREN {
|
||||
bool unique;
|
||||
$$ = charmap_Reverse($3, unique);
|
||||
if (!unique) {
|
||||
::error("REVCHAR: Multiple character mappings to values\n");
|
||||
} else if ($$.empty()) {
|
||||
::error("REVCHAR: No character mapping to values\n");
|
||||
}
|
||||
}
|
||||
| OP_STRCAT LPAREN RPAREN {
|
||||
$$.clear();
|
||||
}
|
||||
@@ -1606,7 +1674,7 @@ string:
|
||||
| OP_STRFMT LPAREN strfmt_args RPAREN {
|
||||
$$ = strfmt($3.format, $3.args);
|
||||
}
|
||||
| POP_SECTION LPAREN scoped_anon_id RPAREN {
|
||||
| POP_SECTION LPAREN scoped_sym RPAREN {
|
||||
Symbol *sym = sym_FindScopedValidSymbol($3);
|
||||
|
||||
if (!sym) {
|
||||
@@ -1627,6 +1695,19 @@ string:
|
||||
}
|
||||
;
|
||||
|
||||
string:
|
||||
string_literal {
|
||||
$$ = std::move($1);
|
||||
}
|
||||
| scoped_sym {
|
||||
if (Symbol *sym = sym_FindScopedSymbol($1); sym && sym->type == SYM_EQUS) {
|
||||
$$ = *sym->getEqus();
|
||||
} else {
|
||||
::error("'%s' is not a string symbol\n", $1.c_str());
|
||||
}
|
||||
}
|
||||
;
|
||||
|
||||
strcat_args:
|
||||
string {
|
||||
$$ = std::move($1);
|
||||
@@ -1647,14 +1728,24 @@ strfmt_args:
|
||||
|
||||
strfmt_va_args:
|
||||
%empty {}
|
||||
| strfmt_va_args COMMA const_no_str {
|
||||
| strfmt_va_args COMMA relocexpr_no_str {
|
||||
$$ = std::move($1);
|
||||
$$.args.push_back(static_cast<uint32_t>($3));
|
||||
$$.args.push_back(static_cast<uint32_t>($3.getConstVal()));
|
||||
}
|
||||
| strfmt_va_args COMMA string {
|
||||
| strfmt_va_args COMMA string_literal {
|
||||
$$ = std::move($1);
|
||||
$$.args.push_back(std::move($3));
|
||||
}
|
||||
| strfmt_va_args COMMA scoped_sym {
|
||||
$$ = std::move($1);
|
||||
handleSymbolByType(
|
||||
$3,
|
||||
[&](Expression const &expr) {
|
||||
$$.args.push_back(static_cast<uint32_t>(expr.getConstVal()));
|
||||
},
|
||||
[&](std::string const &str) { $$.args.push_back(str); }
|
||||
);
|
||||
}
|
||||
;
|
||||
|
||||
section:
|
||||
@@ -1833,9 +1924,15 @@ sm83_and:
|
||||
;
|
||||
|
||||
sm83_bit:
|
||||
SM83_BIT bit_const COMMA reg_r {
|
||||
SM83_BIT reloc_3bit COMMA reg_r {
|
||||
uint8_t mask = static_cast<uint8_t>(0x40 | $4);
|
||||
$2.makeCheckBitIndex(mask);
|
||||
sect_ConstByte(0xCB);
|
||||
sect_ConstByte(0x40 | ($2 << 3) | $4);
|
||||
if (!$2.isKnown()) {
|
||||
sect_RelByte($2, 0);
|
||||
} else {
|
||||
sect_ConstByte(mask | ($2.value() << 3));
|
||||
}
|
||||
}
|
||||
;
|
||||
|
||||
@@ -2024,16 +2121,30 @@ sm83_ld_hl:
|
||||
sect_ConstByte(0xF8);
|
||||
sect_RelByte($5, 1);
|
||||
}
|
||||
| SM83_LD MODE_HL COMMA MODE_SP {
|
||||
::error("LD HL, SP is not a valid instruction; use LD HL, SP + 0\n");
|
||||
}
|
||||
| SM83_LD MODE_HL COMMA reloc_16bit {
|
||||
sect_ConstByte(0x01 | (REG_HL << 4));
|
||||
sect_RelWord($4, 1);
|
||||
}
|
||||
| SM83_LD MODE_HL COMMA reg_tt_no_af {
|
||||
::error(
|
||||
"LD HL, %s is not a valid instruction; use LD H, %s and LD L, %s\n",
|
||||
reg_tt_names[$4],
|
||||
reg_tt_high_names[$4],
|
||||
reg_tt_low_names[$4]
|
||||
);
|
||||
}
|
||||
;
|
||||
|
||||
sm83_ld_sp:
|
||||
SM83_LD MODE_SP COMMA MODE_HL {
|
||||
sect_ConstByte(0xF9);
|
||||
}
|
||||
| SM83_LD MODE_SP COMMA reg_bc_or_de {
|
||||
::error("LD SP, %s is not a valid instruction\n", reg_tt_names[$4]);
|
||||
}
|
||||
| SM83_LD MODE_SP COMMA reloc_16bit {
|
||||
sect_ConstByte(0x01 | (REG_SP << 4));
|
||||
sect_RelWord($4, 1);
|
||||
@@ -2106,13 +2217,20 @@ sm83_ld_a:
|
||||
;
|
||||
|
||||
sm83_ld_ss:
|
||||
SM83_LD MODE_BC COMMA reloc_16bit {
|
||||
sect_ConstByte(0x01 | (REG_BC << 4));
|
||||
SM83_LD reg_bc_or_de COMMA reloc_16bit {
|
||||
sect_ConstByte(0x01 | ($2 << 4));
|
||||
sect_RelWord($4, 1);
|
||||
}
|
||||
| SM83_LD MODE_DE COMMA reloc_16bit {
|
||||
sect_ConstByte(0x01 | (REG_DE << 4));
|
||||
sect_RelWord($4, 1);
|
||||
| SM83_LD reg_bc_or_de COMMA reg_tt_no_af {
|
||||
::error(
|
||||
"LD %s, %s is not a valid instruction; use LD %s, %s and LD %s, %s\n",
|
||||
reg_tt_names[$2],
|
||||
reg_tt_names[$4],
|
||||
reg_tt_high_names[$2],
|
||||
reg_tt_high_names[$4],
|
||||
reg_tt_low_names[$2],
|
||||
reg_tt_low_names[$4]
|
||||
);
|
||||
}
|
||||
// HL is taken care of in sm83_ld_hl
|
||||
// SP is taken care of in sm83_ld_sp
|
||||
@@ -2147,9 +2265,15 @@ sm83_push:
|
||||
;
|
||||
|
||||
sm83_res:
|
||||
SM83_RES bit_const COMMA reg_r {
|
||||
SM83_RES reloc_3bit COMMA reg_r {
|
||||
uint8_t mask = static_cast<uint8_t>(0x80 | $4);
|
||||
$2.makeCheckBitIndex(mask);
|
||||
sect_ConstByte(0xCB);
|
||||
sect_ConstByte(0x80 | ($2 << 3) | $4);
|
||||
if (!$2.isKnown()) {
|
||||
sect_RelByte($2, 0);
|
||||
} else {
|
||||
sect_ConstByte(mask | ($2.value() << 3));
|
||||
}
|
||||
}
|
||||
;
|
||||
|
||||
@@ -2248,9 +2372,15 @@ sm83_scf:
|
||||
;
|
||||
|
||||
sm83_set:
|
||||
SM83_SET bit_const COMMA reg_r {
|
||||
SM83_SET reloc_3bit COMMA reg_r {
|
||||
uint8_t mask = static_cast<uint8_t>(0xC0 | $4);
|
||||
$2.makeCheckBitIndex(mask);
|
||||
sect_ConstByte(0xCB);
|
||||
sect_ConstByte(0xC0 | ($2 << 3) | $4);
|
||||
if (!$2.isKnown()) {
|
||||
sect_RelByte($2, 0);
|
||||
} else {
|
||||
sect_ConstByte(mask | ($2.value() << 3));
|
||||
}
|
||||
}
|
||||
;
|
||||
|
||||
@@ -2429,33 +2559,33 @@ reg_a:
|
||||
;
|
||||
|
||||
reg_tt:
|
||||
MODE_BC {
|
||||
$$ = REG_BC;
|
||||
}
|
||||
| MODE_DE {
|
||||
$$ = REG_DE;
|
||||
}
|
||||
| MODE_HL {
|
||||
$$ = REG_HL;
|
||||
}
|
||||
reg_tt_no_af
|
||||
| MODE_AF {
|
||||
$$ = REG_AF;
|
||||
}
|
||||
;
|
||||
|
||||
reg_ss:
|
||||
reg_tt_no_af
|
||||
| MODE_SP {
|
||||
$$ = REG_SP;
|
||||
}
|
||||
;
|
||||
|
||||
reg_tt_no_af:
|
||||
reg_bc_or_de
|
||||
| MODE_HL {
|
||||
$$ = REG_HL;
|
||||
}
|
||||
;
|
||||
|
||||
reg_bc_or_de:
|
||||
MODE_BC {
|
||||
$$ = REG_BC;
|
||||
}
|
||||
| MODE_DE {
|
||||
$$ = REG_DE;
|
||||
}
|
||||
| MODE_HL {
|
||||
$$ = REG_HL;
|
||||
}
|
||||
| MODE_SP {
|
||||
$$ = REG_SP;
|
||||
}
|
||||
;
|
||||
|
||||
reg_rr:
|
||||
@@ -2554,12 +2684,76 @@ static size_t strlenUTF8(std::string const &str, bool printErrors) {
|
||||
return len;
|
||||
}
|
||||
|
||||
static std::string strsliceUTF8(std::string const &str, uint32_t start, uint32_t stop) {
|
||||
char const *ptr = str.c_str();
|
||||
size_t index = 0;
|
||||
uint32_t state = 0;
|
||||
uint32_t codepoint = 0;
|
||||
uint32_t curIdx = 0;
|
||||
|
||||
// Advance to starting index in source string.
|
||||
while (ptr[index] && curIdx < start) {
|
||||
switch (decode(&state, &codepoint, ptr[index])) {
|
||||
case 1:
|
||||
errorInvalidUTF8Byte(ptr[index], "STRSLICE");
|
||||
state = 0;
|
||||
// fallthrough
|
||||
case 0:
|
||||
curIdx++;
|
||||
break;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
|
||||
// An index 1 past the end of the string is allowed, but will trigger the
|
||||
// "Length too big" warning below if the length is nonzero.
|
||||
if (!ptr[index] && start > curIdx) {
|
||||
warning(
|
||||
WARNING_BUILTIN_ARG,
|
||||
"STRSLICE: Start index %" PRIu32 " is past the end of the string\n",
|
||||
start
|
||||
);
|
||||
}
|
||||
|
||||
size_t startIndex = index;
|
||||
|
||||
// Advance to ending index in source string.
|
||||
while (ptr[index] && curIdx < stop) {
|
||||
switch (decode(&state, &codepoint, ptr[index])) {
|
||||
case 1:
|
||||
errorInvalidUTF8Byte(ptr[index], "STRSLICE");
|
||||
state = 0;
|
||||
// fallthrough
|
||||
case 0:
|
||||
curIdx++;
|
||||
break;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
|
||||
// Check for partial code point.
|
||||
if (state != 0) {
|
||||
error("STRSLICE: Incomplete UTF-8 character\n");
|
||||
curIdx++;
|
||||
}
|
||||
|
||||
if (curIdx < stop) {
|
||||
warning(
|
||||
WARNING_BUILTIN_ARG,
|
||||
"STRSLICE: Stop index %" PRIu32 " is past the end of the string\n",
|
||||
stop
|
||||
);
|
||||
}
|
||||
|
||||
return std::string(ptr + startIndex, ptr + index);
|
||||
}
|
||||
|
||||
static std::string strsubUTF8(std::string const &str, uint32_t pos, uint32_t len) {
|
||||
char const *ptr = str.c_str();
|
||||
size_t index = 0;
|
||||
uint32_t state = 0;
|
||||
uint32_t codepoint = 0;
|
||||
uint32_t curPos = 1; // RGBASM strings are 1-indexed!
|
||||
uint32_t curPos = 1;
|
||||
|
||||
// Advance to starting position in source string.
|
||||
while (ptr[index] && curPos < pos) {
|
||||
@@ -2622,6 +2816,29 @@ static size_t charlenUTF8(std::string const &str) {
|
||||
return len;
|
||||
}
|
||||
|
||||
static std::string strcharUTF8(std::string const &str, uint32_t idx) {
|
||||
std::string_view view = str;
|
||||
size_t charLen = 1;
|
||||
|
||||
// Advance to starting index in source string.
|
||||
for (uint32_t curIdx = 0; charLen && curIdx < idx; curIdx++) {
|
||||
charLen = charmap_ConvertNext(view, nullptr);
|
||||
}
|
||||
|
||||
std::string_view start = view;
|
||||
|
||||
if (!charmap_ConvertNext(view, nullptr)) {
|
||||
warning(
|
||||
WARNING_BUILTIN_ARG,
|
||||
"STRCHAR: Index %" PRIu32 " is past the end of the string\n",
|
||||
idx
|
||||
);
|
||||
}
|
||||
|
||||
start = start.substr(0, start.length() - view.length());
|
||||
return std::string(start);
|
||||
}
|
||||
|
||||
static std::string charsubUTF8(std::string const &str, uint32_t pos) {
|
||||
std::string_view view = str;
|
||||
size_t charLen = 1;
|
||||
@@ -2645,8 +2862,48 @@ static std::string charsubUTF8(std::string const &str, uint32_t pos) {
|
||||
return std::string(start);
|
||||
}
|
||||
|
||||
static int32_t charcmp(std::string_view str1, std::string_view str2) {
|
||||
std::vector<int32_t> seq1, seq2;
|
||||
size_t idx1 = 0, idx2 = 0;
|
||||
for (;;) {
|
||||
if (idx1 >= seq1.size()) {
|
||||
idx1 = 0;
|
||||
seq1.clear();
|
||||
charmap_ConvertNext(str1, &seq1);
|
||||
}
|
||||
if (idx2 >= seq2.size()) {
|
||||
idx2 = 0;
|
||||
seq2.clear();
|
||||
charmap_ConvertNext(str2, &seq2);
|
||||
}
|
||||
if (seq1.empty() != seq2.empty()) {
|
||||
return seq1.empty() ? -1 : 1;
|
||||
} else if (seq1.empty()) {
|
||||
return 0;
|
||||
} else {
|
||||
int32_t value1 = seq1[idx1++], value2 = seq2[idx2++];
|
||||
if (value1 != value2) {
|
||||
return (value1 > value2) - (value1 < value2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static uint32_t adjustNegativeIndex(int32_t idx, size_t len, char const *functionName) {
|
||||
// String functions adjust negative index arguments the same way,
|
||||
// such that position -1 is the last character of a string.
|
||||
if (idx < 0) {
|
||||
idx += len;
|
||||
}
|
||||
if (idx < 0) {
|
||||
warning(WARNING_BUILTIN_ARG, "%s: Index starts at 0\n", functionName);
|
||||
idx = 0;
|
||||
}
|
||||
return static_cast<uint32_t>(idx);
|
||||
}
|
||||
|
||||
static uint32_t adjustNegativePos(int32_t pos, size_t len, char const *functionName) {
|
||||
// STRSUB and CHARSUB adjust negative `pos` arguments the same way,
|
||||
// STRSUB and CHARSUB adjust negative position arguments the same way,
|
||||
// such that position -1 is the last character of a string.
|
||||
if (pos < 0) {
|
||||
pos += len + 1;
|
||||
|
||||
100
src/asm/rpn.cpp
100
src/asm/rpn.cpp
@@ -75,6 +75,9 @@ void Expression::makeSymbol(std::string const &symName) {
|
||||
if (Symbol *sym = sym_FindScopedSymbol(symName); sym_IsPC(sym) && !sect_GetSymbolSection()) {
|
||||
error("PC has no value outside of a section\n");
|
||||
data = 0;
|
||||
} else if (sym && !sym->isNumeric() && !sym->isLabel()) {
|
||||
error("'%s' is not a numeric symbol\n", symName.c_str());
|
||||
data = 0;
|
||||
} else if (!sym || !sym->isConstant()) {
|
||||
isSymbol = true;
|
||||
|
||||
@@ -91,7 +94,7 @@ void Expression::makeSymbol(std::string const &symName) {
|
||||
*ptr++ = RPN_SYM;
|
||||
memcpy(ptr, sym->name.c_str(), nameLen);
|
||||
} else {
|
||||
data = static_cast<int32_t>(sym_GetConstantValue(symName));
|
||||
data = static_cast<int32_t>(sym->getConstantValue());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -322,40 +325,12 @@ void Expression::makeUnaryOp(RPNCommand op, Expression &&src) {
|
||||
case RPN_TZCOUNT:
|
||||
data = val != 0 ? ctz(uval) : 32;
|
||||
break;
|
||||
|
||||
case RPN_LOGOR:
|
||||
case RPN_LOGAND:
|
||||
case RPN_LOGEQ:
|
||||
case RPN_LOGGT:
|
||||
case RPN_LOGLT:
|
||||
case RPN_LOGGE:
|
||||
case RPN_LOGLE:
|
||||
case RPN_LOGNE:
|
||||
case RPN_ADD:
|
||||
case RPN_SUB:
|
||||
case RPN_XOR:
|
||||
case RPN_OR:
|
||||
case RPN_AND:
|
||||
case RPN_SHL:
|
||||
case RPN_SHR:
|
||||
case RPN_USHR:
|
||||
case RPN_MUL:
|
||||
case RPN_DIV:
|
||||
case RPN_MOD:
|
||||
case RPN_EXP:
|
||||
case RPN_BANK_SYM:
|
||||
case RPN_BANK_SECT:
|
||||
case RPN_BANK_SELF:
|
||||
case RPN_SIZEOF_SECT:
|
||||
case RPN_STARTOF_SECT:
|
||||
case RPN_SIZEOF_SECTTYPE:
|
||||
case RPN_STARTOF_SECTTYPE:
|
||||
case RPN_HRAM:
|
||||
case RPN_RST:
|
||||
case RPN_CONST:
|
||||
case RPN_SYM:
|
||||
fatalerror("%d is not an unary operator\n", op);
|
||||
default:
|
||||
// `makeUnaryOp` should never be called with a non-unary operator!
|
||||
// LCOV_EXCL_START
|
||||
unreachable_();
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
} else if (op == RPN_LOGNOT && tryConstLogNot(src)) {
|
||||
data = 0;
|
||||
} else if (int32_t constVal; op == RPN_LOW && (constVal = tryConstLow(src)) != -1) {
|
||||
@@ -498,27 +473,12 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const
|
||||
|
||||
data = op_exponent(lval, rval);
|
||||
break;
|
||||
|
||||
case RPN_NEG:
|
||||
case RPN_NOT:
|
||||
case RPN_LOGNOT:
|
||||
case RPN_BANK_SYM:
|
||||
case RPN_BANK_SECT:
|
||||
case RPN_BANK_SELF:
|
||||
case RPN_SIZEOF_SECT:
|
||||
case RPN_STARTOF_SECT:
|
||||
case RPN_SIZEOF_SECTTYPE:
|
||||
case RPN_STARTOF_SECTTYPE:
|
||||
case RPN_HRAM:
|
||||
case RPN_RST:
|
||||
case RPN_HIGH:
|
||||
case RPN_LOW:
|
||||
case RPN_BITWIDTH:
|
||||
case RPN_TZCOUNT:
|
||||
case RPN_CONST:
|
||||
case RPN_SYM:
|
||||
fatalerror("%d is not a binary operator\n", op);
|
||||
default:
|
||||
// `makeBinaryOp` should never be called with a non-binary operator!
|
||||
// LCOV_EXCL_START
|
||||
unreachable_();
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
} else if (op == RPN_SUB && src1.isDiffConstant(src2.symbolOf())) {
|
||||
data = src1.symbolOf()->getValue() - src2.symbolOf()->getValue();
|
||||
} else if ((op == RPN_LOGAND || op == RPN_AND) && tryConstZero(src1, src2)) {
|
||||
@@ -605,10 +565,24 @@ void Expression::makeCheckRST() {
|
||||
}
|
||||
}
|
||||
|
||||
void Expression::makeCheckBitIndex(uint8_t mask) {
|
||||
assume((mask & 0xC0) != 0x00); // The high two bits must correspond to BIT, RES, or SET
|
||||
|
||||
if (!isKnown()) {
|
||||
uint8_t *ptr = reserveSpace(2);
|
||||
*ptr++ = RPN_BIT_INDEX;
|
||||
*ptr = mask;
|
||||
} else if (int32_t val = value(); val & ~0x07) {
|
||||
// A valid bit index must be masked with 0x07
|
||||
static char const *instructions[4] = {"instruction", "BIT", "RES", "SET"};
|
||||
error("Invalid bit index %" PRId32 " for %s\n", val, instructions[mask >> 6]);
|
||||
}
|
||||
}
|
||||
|
||||
// Checks that an RPN expression's value fits within N bits (signed or unsigned)
|
||||
void Expression::checkNBit(uint8_t n) const {
|
||||
if (isKnown()) {
|
||||
::checkNBit(value(), n, "Expression");
|
||||
::checkNBit(value(), n, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -617,11 +591,23 @@ bool checkNBit(int32_t v, uint8_t n, char const *name) {
|
||||
assume(n < CHAR_BIT * sizeof(int)); // Otherwise `1 << n` is UB
|
||||
|
||||
if (v < -(1 << n) || v >= 1 << n) {
|
||||
warning(WARNING_TRUNCATION_1, "%s must be %u-bit\n", name, n);
|
||||
warning(
|
||||
WARNING_TRUNCATION_1,
|
||||
n == 8 && !name ? "%s must be %u-bit; use LOW() to force 8-bit\n"
|
||||
: "%s must be %u-bit\n",
|
||||
name ? name : "Expression",
|
||||
n
|
||||
);
|
||||
return false;
|
||||
}
|
||||
if (v < -(1 << (n - 1))) {
|
||||
warning(WARNING_TRUNCATION_2, "%s must be %u-bit\n", name, n);
|
||||
warning(
|
||||
WARNING_TRUNCATION_2,
|
||||
n == 8 && !name ? "%s must be %u-bit; use LOW() to force 8-bit\n"
|
||||
: "%s must be %u-bit\n",
|
||||
name ? name : "Expression",
|
||||
n
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -768,7 +768,7 @@ void sect_Skip(uint32_t skip, bool ds) {
|
||||
}
|
||||
|
||||
// Output a byte that can be relocatable or constant
|
||||
void sect_RelByte(Expression &expr, uint32_t pcShift) {
|
||||
void sect_RelByte(Expression const &expr, uint32_t pcShift) {
|
||||
if (!requireCodeSection()) {
|
||||
return;
|
||||
}
|
||||
@@ -782,15 +782,13 @@ void sect_RelByte(Expression &expr, uint32_t pcShift) {
|
||||
}
|
||||
|
||||
// Output several bytes that can be relocatable or constant
|
||||
void sect_RelBytes(uint32_t n, std::vector<Expression> &exprs) {
|
||||
void sect_RelBytes(uint32_t n, std::vector<Expression> const &exprs) {
|
||||
if (!requireCodeSection()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < n; i++) {
|
||||
Expression &expr = exprs[i % exprs.size()];
|
||||
|
||||
if (!expr.isKnown()) {
|
||||
if (Expression const &expr = exprs[i % exprs.size()]; !expr.isKnown()) {
|
||||
createPatch(PATCHTYPE_BYTE, expr, i);
|
||||
writeByte(0);
|
||||
} else {
|
||||
@@ -800,7 +798,7 @@ void sect_RelBytes(uint32_t n, std::vector<Expression> &exprs) {
|
||||
}
|
||||
|
||||
// Output a word that can be relocatable or constant
|
||||
void sect_RelWord(Expression &expr, uint32_t pcShift) {
|
||||
void sect_RelWord(Expression const &expr, uint32_t pcShift) {
|
||||
if (!requireCodeSection()) {
|
||||
return;
|
||||
}
|
||||
@@ -814,7 +812,7 @@ void sect_RelWord(Expression &expr, uint32_t pcShift) {
|
||||
}
|
||||
|
||||
// Output a long that can be relocatable or constant
|
||||
void sect_RelLong(Expression &expr, uint32_t pcShift) {
|
||||
void sect_RelLong(Expression const &expr, uint32_t pcShift) {
|
||||
if (!requireCodeSection()) {
|
||||
return;
|
||||
}
|
||||
@@ -828,7 +826,7 @@ void sect_RelLong(Expression &expr, uint32_t pcShift) {
|
||||
}
|
||||
|
||||
// Output a PC-relative byte that can be relocatable or constant
|
||||
void sect_PCRelByte(Expression &expr, uint32_t pcShift) {
|
||||
void sect_PCRelByte(Expression const &expr, uint32_t pcShift) {
|
||||
if (!requireCodeSection()) {
|
||||
return;
|
||||
}
|
||||
@@ -877,9 +875,11 @@ void sect_BinaryFile(std::string const &name, int32_t startPos) {
|
||||
}
|
||||
if (!file) {
|
||||
if (generatedMissingIncludes) {
|
||||
// LCOV_EXCL_START
|
||||
if (verbose) {
|
||||
printf("Aborting (-MG) on INCBIN file '%s' (%s)\n", name.c_str(), strerror(errno));
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
failedOnMissingInclude = true;
|
||||
} else {
|
||||
error("Error opening INCBIN file '%s': %s\n", name.c_str(), strerror(errno));
|
||||
@@ -944,9 +944,11 @@ void sect_BinaryFileSlice(std::string const &name, int32_t startPos, int32_t len
|
||||
}
|
||||
if (!file) {
|
||||
if (generatedMissingIncludes) {
|
||||
// LCOV_EXCL_START
|
||||
if (verbose) {
|
||||
printf("Aborting (-MG) on INCBIN file '%s' (%s)\n", name.c_str(), strerror(errno));
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
failedOnMissingInclude = true;
|
||||
} else {
|
||||
error("Error opening INCBIN file '%s': %s\n", name.c_str(), strerror(errno));
|
||||
|
||||
@@ -325,19 +325,6 @@ uint32_t Symbol::getConstantValue() const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t sym_GetConstantValue(std::string const &symName) {
|
||||
if (Symbol const *sym = sym_FindScopedSymbol(symName); sym) {
|
||||
return sym->getConstantValue();
|
||||
}
|
||||
|
||||
if (sym_IsPurgedScoped(symName)) {
|
||||
error("'%s' not defined; it was purged\n", symName.c_str());
|
||||
} else {
|
||||
error("'%s' not defined\n", symName.c_str());
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::pair<Symbol const *, Symbol const *> sym_GetCurrentLabelScopes() {
|
||||
return {globalScope, localScope};
|
||||
}
|
||||
@@ -528,8 +515,10 @@ static uint32_t anonLabelID = 0;
|
||||
|
||||
Symbol *sym_AddAnonLabel() {
|
||||
if (anonLabelID == UINT32_MAX) {
|
||||
// LCOV_EXCL_START
|
||||
error("Only %" PRIu32 " anonymous labels can be created!", anonLabelID);
|
||||
return nullptr;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
std::string anon = sym_MakeAnonLabelName(0, true); // The direction is important!
|
||||
@@ -555,6 +544,7 @@ std::string sym_MakeAnonLabelName(uint32_t ofs, bool neg) {
|
||||
} else {
|
||||
ofs--; // We're referencing symbols that haven't been created yet...
|
||||
if (ofs > UINT32_MAX - anonLabelID) {
|
||||
// LCOV_EXCL_START
|
||||
error(
|
||||
"Reference to anonymous label %" PRIu32 " after, when only %" PRIu32
|
||||
" may still be created\n",
|
||||
@@ -562,19 +552,21 @@ std::string sym_MakeAnonLabelName(uint32_t ofs, bool neg) {
|
||||
UINT32_MAX - anonLabelID
|
||||
);
|
||||
} else {
|
||||
// LCOV_EXCL_STOP
|
||||
id = anonLabelID + ofs;
|
||||
}
|
||||
}
|
||||
|
||||
std::string anon("!");
|
||||
anon += std::to_string(id);
|
||||
return anon;
|
||||
return "!"s + std::to_string(id);
|
||||
}
|
||||
|
||||
void sym_Export(std::string const &symName) {
|
||||
if (symName.starts_with('!')) {
|
||||
// LCOV_EXCL_START
|
||||
// The parser does not accept anonymous labels for an `EXPORT` directive
|
||||
error("Anonymous labels cannot be exported\n");
|
||||
return;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
Symbol *sym = sym_FindScopedSymbol(symName);
|
||||
@@ -656,11 +648,13 @@ void sym_Init(time_t now) {
|
||||
sym_AddEqu("__RGBDS_RC__"s, PACKAGE_VERSION_RC)->isBuiltin = true;
|
||||
#endif
|
||||
|
||||
// LCOV_EXCL_START
|
||||
if (now == static_cast<time_t>(-1)) {
|
||||
warn("Failed to determine current time");
|
||||
// Fall back by pretending we are at the Epoch
|
||||
now = 0;
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
tm const *time_local = localtime(&now);
|
||||
|
||||
|
||||
@@ -15,14 +15,17 @@ fi
|
||||
BISON_FLAGS="-Wall -Dparse.lac=full -Dlr.type=ielr"
|
||||
|
||||
# Set some optimization flags on versions that support them
|
||||
if [ "$BISON_MAJOR" -eq 4 ] || [ "$BISON_MAJOR" -eq 3 ] && [ "$BISON_MINOR" -ge 5 ]; then
|
||||
if [ "$BISON_MAJOR" -ge 4 ] || [ "$BISON_MAJOR" -eq 3 ] && [ "$BISON_MINOR" -ge 5 ]; then
|
||||
BISON_FLAGS="$BISON_FLAGS -Dapi.token.raw=true"
|
||||
fi
|
||||
if [ "$BISON_MAJOR" -eq 4 ] || [ "$BISON_MAJOR" -eq 3 ] && [ "$BISON_MINOR" -ge 6 ]; then
|
||||
if [ "$BISON_MAJOR" -ge 4 ] || [ "$BISON_MAJOR" -eq 3 ] && [ "$BISON_MINOR" -ge 6 ]; then
|
||||
BISON_FLAGS="$BISON_FLAGS -Dparse.error=detailed"
|
||||
else
|
||||
BISON_FLAGS="$BISON_FLAGS -Dparse.error=verbose"
|
||||
fi
|
||||
if [ "$BISON_MAJOR" -ge 4 ] || [ "$BISON_MAJOR" -eq 3 ] && [ "$BISON_MINOR" -ge 7 ]; then
|
||||
BISON_FLAGS="$BISON_FLAGS -Wcounterexamples"
|
||||
fi
|
||||
|
||||
# Replace the arguments to this script ($@) with the ones in $BISON_FLAGS
|
||||
eval "set -- $BISON_FLAGS"
|
||||
|
||||
115
src/fix/main.cpp
115
src/fix/main.cpp
@@ -23,7 +23,7 @@ static_assert(UNSPECIFIED > 0xFF, "UNSPECIFIED should not be in byte range!");
|
||||
static constexpr off_t BANK_SIZE = 0x4000;
|
||||
|
||||
// Short options
|
||||
static char const *optstring = "Ccf:hi:jk:L:l:m:n:Op:r:st:Vv";
|
||||
static char const *optstring = "Ccf:hi:jk:L:l:m:n:Oo:p:r:st:Vv";
|
||||
|
||||
// Equivalent long options
|
||||
// Please keep in the same order as short opts.
|
||||
@@ -45,6 +45,7 @@ static option const longopts[] = {
|
||||
{"mbc-type", required_argument, nullptr, 'm'},
|
||||
{"rom-version", required_argument, nullptr, 'n'},
|
||||
{"overwrite", no_argument, nullptr, 'O'},
|
||||
{"output", required_argument, nullptr, 'o'},
|
||||
{"pad-value", required_argument, nullptr, 'p'},
|
||||
{"ram-size", required_argument, nullptr, 'r'},
|
||||
{"sgb-compatible", no_argument, nullptr, 's'},
|
||||
@@ -54,6 +55,7 @@ static option const longopts[] = {
|
||||
{nullptr, no_argument, nullptr, 0 }
|
||||
};
|
||||
|
||||
// LCOV_EXCL_START
|
||||
static void printUsage() {
|
||||
fputs(
|
||||
"Usage: rgbfix [-hjOsVv] [-C | -c] [-f <fix_spec>] [-i <game_id>] [-k <licensee>]\n"
|
||||
@@ -65,6 +67,7 @@ static void printUsage() {
|
||||
" to the man page for a list of values\n"
|
||||
" -p, --pad-value <value> pad to the next valid size using this value\n"
|
||||
" -r, --ram-size <code> set the cart RAM size byte to this value\n"
|
||||
" -o, --output <path> set the output file\n"
|
||||
" -V, --version print RGBFIX version and exit\n"
|
||||
" -v, --validate fix the header logo and both checksums (-f lhg)\n"
|
||||
"\n"
|
||||
@@ -72,6 +75,7 @@ static void printUsage() {
|
||||
stderr
|
||||
);
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
static uint8_t nbErrors;
|
||||
|
||||
@@ -696,10 +700,12 @@ static char const *mbcName(MbcType type) {
|
||||
case MBC_WRONG_FEATURES:
|
||||
case MBC_BAD_RANGE:
|
||||
case MBC_BAD_TPP1:
|
||||
// LCOV_EXCL_START
|
||||
unreachable_();
|
||||
}
|
||||
|
||||
unreachable_();
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
static bool hasRAM(MbcType type) {
|
||||
@@ -713,8 +719,7 @@ static bool hasRAM(MbcType type) {
|
||||
case MBC3_TIMER_BATTERY:
|
||||
case MBC5:
|
||||
case MBC5_RUMBLE:
|
||||
case MBC6: // TODO: not sure
|
||||
case BANDAI_TAMA5: // TODO: not sure
|
||||
case BANDAI_TAMA5: // "Game de Hakken!! Tamagotchi - Osutchi to Mesutchi" has RAM size 0
|
||||
case MBC_NONE:
|
||||
case MBC_BAD:
|
||||
case MBC_WRONG_FEATURES:
|
||||
@@ -735,6 +740,7 @@ static bool hasRAM(MbcType type) {
|
||||
case MBC5_RAM_BATTERY:
|
||||
case MBC5_RUMBLE_RAM:
|
||||
case MBC5_RUMBLE_RAM_BATTERY:
|
||||
case MBC6: // "Net de Get - Minigame @ 100" has RAM size 3 (32 KiB)
|
||||
case MBC7_SENSOR_RUMBLE_RAM_BATTERY:
|
||||
case POCKET_CAMERA:
|
||||
case HUC3:
|
||||
@@ -761,7 +767,7 @@ static bool hasRAM(MbcType type) {
|
||||
break;
|
||||
}
|
||||
|
||||
unreachable_();
|
||||
unreachable_(); // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
static uint8_t const nintendoLogo[] = {
|
||||
@@ -811,8 +817,9 @@ static ssize_t readBytes(int fd, uint8_t *buf, size_t len) {
|
||||
while (len) {
|
||||
ssize_t ret = read(fd, buf, len);
|
||||
|
||||
if (ret == -1 && errno != EINTR) { // Return errors, unless we only were interrupted
|
||||
return -1;
|
||||
// Return errors, unless we only were interrupted
|
||||
if (ret == -1 && errno != EINTR) {
|
||||
return -1; // LCOV_EXCL_LINE
|
||||
}
|
||||
// EOF reached
|
||||
if (ret == 0) {
|
||||
@@ -838,8 +845,9 @@ static ssize_t writeBytes(int fd, uint8_t *buf, size_t len) {
|
||||
while (len) {
|
||||
ssize_t ret = write(fd, buf, len);
|
||||
|
||||
if (ret == -1 && errno != EINTR) { // Return errors, unless we only were interrupted
|
||||
return -1;
|
||||
// Return errors, unless we only were interrupted
|
||||
if (ret == -1 && errno != EINTR) {
|
||||
return -1; // LCOV_EXCL_LINE
|
||||
}
|
||||
// If anything was written, accumulate it, and continue
|
||||
if (ret != -1) {
|
||||
@@ -879,9 +887,9 @@ static void overwriteBytes(
|
||||
memcpy(&rom0[startAddr], fixed, size);
|
||||
}
|
||||
|
||||
static void processFile(int input, int output, char const *name, off_t fileSize) {
|
||||
// Both of these should be true for seekable files, and neither otherwise
|
||||
if (input == output) {
|
||||
static void
|
||||
processFile(int input, int output, char const *name, off_t fileSize, bool expectFileSize) {
|
||||
if (expectFileSize) {
|
||||
assume(fileSize != 0);
|
||||
} else {
|
||||
assume(fileSize == 0);
|
||||
@@ -893,8 +901,10 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
|
||||
ssize_t headerSize = (cartridgeType & 0xFF00) == TPP1 ? 0x154 : 0x150;
|
||||
|
||||
if (rom0Len == -1) {
|
||||
// LCOV_EXCL_START
|
||||
report("FATAL: Failed to read \"%s\"'s header: %s\n", name, strerror(errno));
|
||||
return;
|
||||
// LCOV_EXCL_STOP
|
||||
} else if (rom0Len < headerSize) {
|
||||
report(
|
||||
"FATAL: \"%s\" too short, expected at least %jd ($%jx) bytes, got only %jd\n",
|
||||
@@ -1132,8 +1142,10 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
|
||||
// write the header
|
||||
if (input == output) {
|
||||
if (lseek(output, 0, SEEK_SET) == static_cast<off_t>(-1)) {
|
||||
// LCOV_EXCL_START
|
||||
report("FATAL: Failed to rewind \"%s\": %s\n", name, strerror(errno));
|
||||
return;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
// If modifying the file in-place, we only need to edit the header
|
||||
// However, padding may have modified ROM0 (added padding), so don't in that case
|
||||
@@ -1144,9 +1156,12 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
|
||||
writeLen = writeBytes(output, rom0, rom0Len);
|
||||
|
||||
if (writeLen == -1) {
|
||||
// LCOV_EXCL_START
|
||||
report("FATAL: Failed to write \"%s\"'s ROM0: %s\n", name, strerror(errno));
|
||||
return;
|
||||
// LCOV_EXCL_STOP
|
||||
} else if (writeLen < rom0Len) {
|
||||
// LCOV_EXCL_START
|
||||
report(
|
||||
"FATAL: Could only write %jd of \"%s\"'s %jd ROM0 bytes\n",
|
||||
static_cast<intmax_t>(writeLen),
|
||||
@@ -1154,6 +1169,7 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
|
||||
static_cast<intmax_t>(rom0Len)
|
||||
);
|
||||
return;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
// Output ROMX if it was buffered
|
||||
@@ -1162,9 +1178,12 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
|
||||
// so it's fine to cast to `size_t`
|
||||
writeLen = writeBytes(output, romx.data(), totalRomxLen);
|
||||
if (writeLen == -1) {
|
||||
// LCOV_EXCL_START
|
||||
report("FATAL: Failed to write \"%s\"'s ROMX: %s\n", name, strerror(errno));
|
||||
return;
|
||||
// LCOV_EXCL_STOP
|
||||
} else if (static_cast<size_t>(writeLen) < totalRomxLen) {
|
||||
// LCOV_EXCL_START
|
||||
report(
|
||||
"FATAL: Could only write %jd of \"%s\"'s %zu ROMX bytes\n",
|
||||
static_cast<intmax_t>(writeLen),
|
||||
@@ -1172,6 +1191,7 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
|
||||
totalRomxLen
|
||||
);
|
||||
return;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1179,8 +1199,10 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
|
||||
if (padValue != UNSPECIFIED) {
|
||||
if (input == output) {
|
||||
if (lseek(output, 0, SEEK_END) == static_cast<off_t>(-1)) {
|
||||
// LCOV_EXCL_START
|
||||
report("FATAL: Failed to seek to end of \"%s\": %s\n", name, strerror(errno));
|
||||
return;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
}
|
||||
memset(bank, padValue, sizeof(bank));
|
||||
@@ -1194,40 +1216,73 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
|
||||
// The return value is either -1, or at most `thisLen`,
|
||||
// so it's fine to cast to `size_t`
|
||||
if (static_cast<size_t>(ret) != thisLen) {
|
||||
// LCOV_EXCL_START
|
||||
report("FATAL: Failed to write \"%s\"'s padding: %s\n", name, strerror(errno));
|
||||
break;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
len -= thisLen;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool processFilename(char const *name) {
|
||||
static bool processFilename(char const *name, char const *outputName) {
|
||||
nbErrors = 0;
|
||||
|
||||
if (!strcmp(name, "-")) {
|
||||
bool inputStdin = !strcmp(name, "-");
|
||||
if (inputStdin && !outputName) {
|
||||
outputName = "-";
|
||||
}
|
||||
|
||||
int output = -1;
|
||||
bool openedOutput = false;
|
||||
if (outputName) {
|
||||
if (!strcmp(outputName, "-")) {
|
||||
output = STDOUT_FILENO;
|
||||
(void)setmode(STDOUT_FILENO, O_BINARY);
|
||||
} else {
|
||||
output = open(outputName, O_WRONLY | O_BINARY | O_CREAT, 0600);
|
||||
if (output == -1) {
|
||||
report(
|
||||
"FATAL: Failed to open \"%s\" for writing: %s\n", outputName, strerror(errno)
|
||||
);
|
||||
return true;
|
||||
}
|
||||
openedOutput = true;
|
||||
}
|
||||
}
|
||||
Defer closeOutput{[&] {
|
||||
if (openedOutput) {
|
||||
close(output);
|
||||
}
|
||||
}};
|
||||
|
||||
if (inputStdin) {
|
||||
name = "<stdin>";
|
||||
(void)setmode(STDIN_FILENO, O_BINARY);
|
||||
(void)setmode(STDOUT_FILENO, O_BINARY);
|
||||
processFile(STDIN_FILENO, STDOUT_FILENO, name, 0);
|
||||
processFile(STDIN_FILENO, output, name, 0, false);
|
||||
} else {
|
||||
// POSIX specifies that the results of O_RDWR on a FIFO are undefined.
|
||||
// However, this is necessary to avoid a TOCTTOU, if the file was changed between
|
||||
// `stat()` and `open(O_RDWR)`, which could trigger the UB anyway.
|
||||
// Thus, we're going to hope that either the `open` fails, or it succeeds but IO
|
||||
// operations may fail, all of which we handle.
|
||||
if (int input = open(name, O_RDWR | O_BINARY); input == -1) {
|
||||
if (int input = open(name, (outputName ? O_RDONLY : O_RDWR) | O_BINARY); input == -1) {
|
||||
report("FATAL: Failed to open \"%s\" for reading+writing: %s\n", name, strerror(errno));
|
||||
} else {
|
||||
Defer closeInput{[&] { close(input); }};
|
||||
struct stat stat;
|
||||
if (fstat(input, &stat) == -1) {
|
||||
// LCOV_EXCL_START
|
||||
report("FATAL: Failed to stat \"%s\": %s\n", name, strerror(errno));
|
||||
} else if (!S_ISREG(stat.st_mode)) { // TODO: Do we want to support other types?
|
||||
// LCOV_EXCL_STOP
|
||||
} else if (!S_ISREG(stat.st_mode)) { // We do not support FIFOs or symlinks
|
||||
// LCOV_EXCL_START
|
||||
report(
|
||||
"FATAL: \"%s\" is not a regular file, and thus cannot be modified in-place\n",
|
||||
name
|
||||
);
|
||||
// LCOV_EXCL_STOP
|
||||
} else if (stat.st_size < 0x150) {
|
||||
// This check is in theory redundant with the one in `processFile`, but it
|
||||
// prevents passing a file size of 0, which usually indicates pipes
|
||||
@@ -1237,7 +1292,10 @@ static bool processFilename(char const *name) {
|
||||
static_cast<intmax_t>(stat.st_size)
|
||||
);
|
||||
} else {
|
||||
processFile(input, input, name, stat.st_size);
|
||||
if (!outputName) {
|
||||
output = input;
|
||||
}
|
||||
processFile(input, output, name, stat.st_size, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1281,6 +1339,7 @@ static void parseByte(uint16_t &output, char name) {
|
||||
int main(int argc, char *argv[]) {
|
||||
nbErrors = 0;
|
||||
|
||||
char const *outputFilename = nullptr;
|
||||
for (int ch; (ch = musl_getopt_long_only(argc, argv, optstring, longopts, nullptr)) != -1;) {
|
||||
switch (ch) {
|
||||
size_t len;
|
||||
@@ -1322,8 +1381,10 @@ int main(int argc, char *argv[]) {
|
||||
break;
|
||||
|
||||
case 'h':
|
||||
// LCOV_EXCL_START
|
||||
printUsage();
|
||||
exit(0);
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
case 'i':
|
||||
gameID = musl_optarg;
|
||||
@@ -1393,6 +1454,10 @@ int main(int argc, char *argv[]) {
|
||||
overwriteRom = true;
|
||||
break;
|
||||
|
||||
case 'o':
|
||||
outputFilename = musl_optarg;
|
||||
break;
|
||||
|
||||
case 'p':
|
||||
parseByte(padValue, 'p');
|
||||
break;
|
||||
@@ -1419,16 +1484,20 @@ int main(int argc, char *argv[]) {
|
||||
}
|
||||
|
||||
case 'V':
|
||||
// LCOV_EXCL_START
|
||||
printf("rgbfix %s\n", get_package_version_string());
|
||||
exit(0);
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
case 'v':
|
||||
fixSpec = FIX_LOGO | FIX_HEADER_SUM | FIX_GLOBAL_SUM;
|
||||
break;
|
||||
|
||||
default:
|
||||
// LCOV_EXCL_START
|
||||
printUsage();
|
||||
exit(1);
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1462,7 +1531,7 @@ int main(int argc, char *argv[]) {
|
||||
"warning: RAM size 1 (2 KiB) was specified for MBC \"%s\"\n",
|
||||
mbcName(cartridgeType)
|
||||
);
|
||||
} // TODO: check possible values?
|
||||
}
|
||||
} else if (ramSize) {
|
||||
fprintf(
|
||||
stderr,
|
||||
@@ -1543,8 +1612,14 @@ int main(int argc, char *argv[]) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (outputFilename && argc != musl_optind + 1) {
|
||||
fputs("FATAL: If `-o` is set then only a single input file may be specified\n", stderr);
|
||||
printUsage();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
do {
|
||||
failed |= processFilename(*argv);
|
||||
failed |= processFilename(*argv, outputFilename);
|
||||
} while (*++argv);
|
||||
|
||||
return failed;
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
#include "extern/getopt.hpp"
|
||||
@@ -103,6 +102,7 @@ void fatal(char const *fmt, ...) {
|
||||
}
|
||||
|
||||
void Options::verbosePrint(uint8_t level, char const *fmt, ...) const {
|
||||
// LCOV_EXCL_START
|
||||
if (verbosity >= level) {
|
||||
va_list ap;
|
||||
|
||||
@@ -110,10 +110,11 @@ void Options::verbosePrint(uint8_t level, char const *fmt, ...) const {
|
||||
vfprintf(stderr, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
// Short options
|
||||
static char const *optstring = "-Aa:b:Cc:d:hi:L:mN:n:Oo:Pp:Qq:r:s:Tt:U:uVvXx:YZ";
|
||||
static char const *optstring = "-Aa:B:b:Cc:d:hi:L:mN:n:Oo:Pp:Qq:r:s:Tt:U:uVvXx:YZ";
|
||||
|
||||
// Equivalent long options
|
||||
// Please keep in the same order as short opts.
|
||||
@@ -125,6 +126,7 @@ static char const *optstring = "-Aa:b:Cc:d:hi:L:mN:n:Oo:Pp:Qq:r:s:Tt:U:uVvXx:YZ"
|
||||
static option const longopts[] = {
|
||||
{"auto-attr-map", no_argument, nullptr, 'A'},
|
||||
{"attr-map", required_argument, nullptr, 'a'},
|
||||
{"background-color", required_argument, nullptr, 'B'},
|
||||
{"base-tiles", required_argument, nullptr, 'b'},
|
||||
{"color-curve", no_argument, nullptr, 'C'},
|
||||
{"colors", required_argument, nullptr, 'c'},
|
||||
@@ -156,6 +158,7 @@ static option const longopts[] = {
|
||||
{nullptr, no_argument, nullptr, 0 }
|
||||
};
|
||||
|
||||
// LCOV_EXCL_START
|
||||
static void printUsage() {
|
||||
fputs(
|
||||
"Usage: rgbgfx [-r stride] [-ChmOuVXYZ] [-v [-v ...]] [-a <attr_map> | -A]\n"
|
||||
@@ -174,6 +177,7 @@ static void printUsage() {
|
||||
stderr
|
||||
);
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
// Parses a number at the beginning of a string, moving the pointer to skip the parsed characters.
|
||||
// Returns the provided errVal on error.
|
||||
@@ -279,8 +283,8 @@ static std::vector<size_t> readAtFile(std::string const &path, std::vector<char>
|
||||
|
||||
// We only filter out `EOF`, but calling `isblank()` on anything else is UB!
|
||||
static_assert(
|
||||
std::remove_reference_t<decltype(*file)>::traits_type::eof() == EOF,
|
||||
"isblank(char_traits<...>::eof()) is UB!"
|
||||
std::streambuf::traits_type::eof() == EOF,
|
||||
"isblank(std::streambuf::traits_type::eof()) is UB!"
|
||||
);
|
||||
std::vector<size_t> argvOfs;
|
||||
|
||||
@@ -350,6 +354,7 @@ static char *parseArgv(int argc, char *argv[]) {
|
||||
for (int ch; (ch = musl_getopt_long_only(argc, argv, optstring, longopts, nullptr)) != -1;) {
|
||||
char *arg = musl_optarg; // Make a copy for scanning
|
||||
uint16_t number;
|
||||
size_t size;
|
||||
switch (ch) {
|
||||
case 'A':
|
||||
localOptions.autoAttrmap = true;
|
||||
@@ -361,6 +366,43 @@ static char *parseArgv(int argc, char *argv[]) {
|
||||
}
|
||||
options.attrmap = musl_optarg;
|
||||
break;
|
||||
case 'B':
|
||||
if (strcasecmp(musl_optarg, "transparent") == 0) {
|
||||
options.bgColor = Rgba(0x00, 0x00, 0x00, 0x00);
|
||||
break;
|
||||
}
|
||||
if (musl_optarg[0] != '#') {
|
||||
error("Background color specification must be `#rgb`, `#rrggbb`, or `transparent`");
|
||||
break;
|
||||
}
|
||||
size = strspn(&musl_optarg[1], "0123456789ABCDEFabcdef");
|
||||
switch (size) {
|
||||
case 3:
|
||||
options.bgColor = Rgba(
|
||||
singleToHex(musl_optarg[1]),
|
||||
singleToHex(musl_optarg[2]),
|
||||
singleToHex(musl_optarg[3]),
|
||||
0xFF
|
||||
);
|
||||
break;
|
||||
case 6:
|
||||
options.bgColor = Rgba(
|
||||
toHex(musl_optarg[1], musl_optarg[2]),
|
||||
toHex(musl_optarg[3], musl_optarg[4]),
|
||||
toHex(musl_optarg[5], musl_optarg[6]),
|
||||
0xFF
|
||||
);
|
||||
break;
|
||||
default:
|
||||
error("Unknown background color specification \"%s\"", musl_optarg);
|
||||
}
|
||||
if (musl_optarg[size + 1] != '\0') {
|
||||
error(
|
||||
"Unexpected text \"%s\" after background color specification",
|
||||
&musl_optarg[size + 1]
|
||||
);
|
||||
}
|
||||
break;
|
||||
case 'b':
|
||||
number = parseNumber(arg, "Bank 0 base tile ID", 0);
|
||||
if (number >= 256) {
|
||||
@@ -409,9 +451,9 @@ static char *parseArgv(int argc, char *argv[]) {
|
||||
} else {
|
||||
options.palSpecType = Options::EXPLICIT;
|
||||
// Can't parse the file yet, as "flat" color collections need to know the palette
|
||||
// size to be split; thus, we defer that
|
||||
// TODO: this does not validate the `fmt` part of any external spec but the last
|
||||
// one, but I guess that's okay
|
||||
// size to be split; thus, we defer that.
|
||||
// If a following `-c` overrides a previous one, the `fmt` part of an overridden
|
||||
// external palette spec will not be validated, but I guess that's okay.
|
||||
localOptions.externalPalSpec = musl_optarg;
|
||||
}
|
||||
break;
|
||||
@@ -425,8 +467,10 @@ static char *parseArgv(int argc, char *argv[]) {
|
||||
}
|
||||
break;
|
||||
case 'h':
|
||||
// LCOV_EXCL_START
|
||||
printUsage();
|
||||
exit(0);
|
||||
// LCOV_EXCL_STOP
|
||||
case 'i':
|
||||
if (!options.inputTileset.empty()) {
|
||||
warning("Overriding input tileset file %s", options.inputTileset.c_str());
|
||||
@@ -582,13 +626,17 @@ static char *parseArgv(int argc, char *argv[]) {
|
||||
options.tilemap = musl_optarg;
|
||||
break;
|
||||
case 'V':
|
||||
// LCOV_EXCL_START
|
||||
printf("rgbgfx %s\n", get_package_version_string());
|
||||
exit(0);
|
||||
// LCOV_EXCL_STOP
|
||||
case 'v':
|
||||
// LCOV_EXCL_START
|
||||
if (options.verbosity < Options::VERB_VVVVVV) {
|
||||
++options.verbosity;
|
||||
}
|
||||
break;
|
||||
// LCOV_EXCL_STOP
|
||||
case 'x':
|
||||
options.trim = parseNumber(arg, "Number of tiles to trim", 0);
|
||||
if (*arg != '\0') {
|
||||
@@ -615,8 +663,10 @@ static char *parseArgv(int argc, char *argv[]) {
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// LCOV_EXCL_START
|
||||
printUsage();
|
||||
exit(1);
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
}
|
||||
|
||||
@@ -744,6 +794,7 @@ int main(int argc, char *argv[]) {
|
||||
parseExternalPalSpec(localOptions.externalPalSpec);
|
||||
}
|
||||
|
||||
// LCOV_EXCL_START
|
||||
if (options.verbosity >= Options::VERB_CFG) {
|
||||
fprintf(stderr, "rgbgfx %s\n", get_package_version_string());
|
||||
|
||||
@@ -825,7 +876,7 @@ int main(int argc, char *argv[]) {
|
||||
}
|
||||
fprintf(
|
||||
stderr,
|
||||
"\tInput image slice: %" PRIu32 "x%" PRIu32 " pixels starting at (%" PRIi32 ", %" PRIi32
|
||||
"\tInput image slice: %" PRIu32 "x%" PRIu32 " pixels starting at (%" PRId32 ", %" PRId32
|
||||
")\n",
|
||||
options.inputSlice.width,
|
||||
options.inputSlice.height,
|
||||
@@ -856,6 +907,7 @@ int main(int argc, char *argv[]) {
|
||||
printPath("Output palettes", options.palettes);
|
||||
fputs("Ready.\n", stderr);
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
// Do not do anything if option parsing went wrong.
|
||||
requireZeroErrors();
|
||||
|
||||
@@ -86,8 +86,8 @@ private:
|
||||
public:
|
||||
Iter() = default;
|
||||
|
||||
bool operator==(Iter const &other) const { return _iter == other._iter; }
|
||||
bool operator!=(Iter const &other) const { return !(*this == other); }
|
||||
bool operator==(Iter const &rhs) const { return _iter == rhs._iter; }
|
||||
|
||||
Iter &operator++() {
|
||||
++_iter;
|
||||
skipEmpty();
|
||||
@@ -98,6 +98,7 @@ private:
|
||||
++(*this);
|
||||
return it;
|
||||
}
|
||||
|
||||
reference operator*() const {
|
||||
assume((*_iter).has_value());
|
||||
return **_iter;
|
||||
@@ -167,16 +168,6 @@ private:
|
||||
std::unordered_set<uint16_t> &uniqueColors() const {
|
||||
// We check for *distinct* colors by stuffing them into a `set`; this should be
|
||||
// faster than "back-checking" on every element (O(n²))
|
||||
//
|
||||
// TODO: calc84maniac suggested another approach; try implementing it, see if it
|
||||
// performs better:
|
||||
// > So basically you make a priority queue that takes iterators into each of your sets
|
||||
// > (paired with end iterators so you'll know where to stop), and the comparator tests the
|
||||
// > values pointed to by each iterator
|
||||
// > Then each iteration you pop from the queue,
|
||||
// > optionally add one to your count, increment the iterator and push it back into the
|
||||
// > queue if it didn't reach the end
|
||||
// > And you do this until the priority queue is empty
|
||||
static std::unordered_set<uint16_t> colors;
|
||||
|
||||
colors.clear();
|
||||
@@ -476,7 +467,6 @@ std::tuple<DefaultInitVec<size_t>, size_t>
|
||||
);
|
||||
|
||||
// All efficiencies are identical iff min equals max
|
||||
// TODO: maybe not ideal to re-compute these two?
|
||||
ProtoPalette const &minProtoPal = protoPalettes[minEfficiencyIter->protoPalIndex];
|
||||
ProtoPalette const &maxProtoPal = protoPalettes[maxEfficiencyIter->protoPalIndex];
|
||||
size_t minSize = minProtoPal.size();
|
||||
@@ -558,6 +548,7 @@ std::tuple<DefaultInitVec<size_t>, size_t>
|
||||
}
|
||||
}
|
||||
|
||||
// LCOV_EXCL_START
|
||||
if (options.verbosity >= Options::VERB_INTERM) {
|
||||
for (auto &&assignment : assignments) {
|
||||
fprintf(stderr, "{ ");
|
||||
@@ -570,11 +561,13 @@ std::tuple<DefaultInitVec<size_t>, size_t>
|
||||
fprintf(stderr, "} (volume = %zu)\n", assignment.volume());
|
||||
}
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
// "Decant" the result
|
||||
decant(assignments, protoPalettes);
|
||||
// Note that the result does not contain any empty palettes
|
||||
|
||||
// LCOV_EXCL_START
|
||||
if (options.verbosity >= Options::VERB_INTERM) {
|
||||
for (auto &&assignment : assignments) {
|
||||
fprintf(stderr, "{ ");
|
||||
@@ -587,6 +580,7 @@ std::tuple<DefaultInitVec<size_t>, size_t>
|
||||
fprintf(stderr, "} (volume = %zu)\n", assignment.volume());
|
||||
}
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
DefaultInitVec<size_t> mappings(protoPalettes.size());
|
||||
for (size_t i = 0; i < assignments.size(); ++i) {
|
||||
|
||||
@@ -40,7 +40,7 @@ void sortIndexed(
|
||||
return true;
|
||||
}
|
||||
}
|
||||
unreachable_(); // This should not be possible
|
||||
unreachable_(); // LCOV_EXCL_LINE
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,29 +23,8 @@
|
||||
|
||||
using namespace std::string_view_literals;
|
||||
|
||||
constexpr uint8_t nibble(char c) {
|
||||
if (c >= 'a') {
|
||||
assume(c <= 'f');
|
||||
return c - 'a' + 10;
|
||||
} else if (c >= 'A') {
|
||||
assume(c <= 'F');
|
||||
return c - 'A' + 10;
|
||||
} else {
|
||||
assume(c >= '0' && c <= '9');
|
||||
return c - '0';
|
||||
}
|
||||
}
|
||||
|
||||
constexpr uint8_t toHex(char c1, char c2) {
|
||||
return nibble(c1) * 16 + nibble(c2);
|
||||
}
|
||||
|
||||
constexpr uint8_t singleToHex(char c) {
|
||||
return toHex(c, c);
|
||||
}
|
||||
|
||||
template<typename Str> // Should be std::string or std::string_view
|
||||
static void skipWhitespace(Str const &str, typename Str::size_type &pos) {
|
||||
static void skipWhitespace(Str const &str, size_t &pos) {
|
||||
pos = std::min(str.find_first_not_of(" \t"sv, pos), str.length());
|
||||
}
|
||||
|
||||
@@ -54,9 +33,8 @@ void parseInlinePalSpec(char const * const rawArg) {
|
||||
// Palettes are separated by colons.
|
||||
|
||||
std::string_view arg(rawArg);
|
||||
using size_type = decltype(arg)::size_type;
|
||||
|
||||
auto parseError = [&rawArg, &arg](size_type ofs, size_type len, char const *msg) {
|
||||
auto parseError = [&rawArg, &arg](size_t ofs, size_t len, char const *msg) {
|
||||
(void)arg; // With NDEBUG, `arg` is otherwise not used
|
||||
assume(ofs <= arg.length());
|
||||
assume(len <= arg.length());
|
||||
@@ -80,17 +58,16 @@ void parseInlinePalSpec(char const * const rawArg) {
|
||||
options.palSpec.clear();
|
||||
options.palSpec.emplace_back(); // Value-initialized, not default-init'd, so we get zeros
|
||||
|
||||
size_type n = 0; // Index into the argument
|
||||
// TODO: store max `nbColors` ever reached, and compare against palette size later
|
||||
size_t n = 0; // Index into the argument
|
||||
size_t nbColors = 0; // Number of colors in the current palette
|
||||
for (;;) {
|
||||
++n; // Ignore the '#' (checked either by caller or previous loop iteration)
|
||||
|
||||
std::optional<Rgba> &color = options.palSpec.back()[nbColors];
|
||||
// Check for #none first.
|
||||
if (arg.compare(n, 4, "none"sv) == 0 || arg.compare(n, 4, "NONE"sv) == 0) {
|
||||
// Check for "#none" first.
|
||||
if (strncasecmp(&rawArg[n], "none", literal_strlen("none")) == 0) {
|
||||
color = {};
|
||||
n += 4;
|
||||
n += literal_strlen("none");
|
||||
} else {
|
||||
auto pos = std::min(arg.find_first_not_of("0123456789ABCDEFabcdef"sv, n), arg.length());
|
||||
switch (pos - n) {
|
||||
@@ -194,7 +171,6 @@ static T readLE(U const *bytes) {
|
||||
[[gnu::warn_unused_result]]
|
||||
static bool readLine(std::filebuf &file, std::string &buffer) {
|
||||
assume(buffer.empty());
|
||||
// TODO: maybe this can be optimized to bulk reads?
|
||||
for (;;) {
|
||||
auto c = file.sbumpc();
|
||||
if (c == std::filebuf::traits_type::eof()) {
|
||||
@@ -222,7 +198,7 @@ static bool readLine(std::filebuf &file, std::string &buffer) {
|
||||
|
||||
// Parses the initial part of a string_view, advancing the "read index" as it does
|
||||
template<typename U> // Should be uint*_t
|
||||
static std::optional<U> parseDec(std::string const &str, std::string::size_type &n) {
|
||||
static std::optional<U> parseDec(std::string const &str, size_t &n) {
|
||||
uintmax_t value = 0;
|
||||
auto result = std::from_chars(str.data() + n, str.data() + str.size(), value);
|
||||
if (static_cast<bool>(result.ec)) {
|
||||
@@ -232,8 +208,7 @@ static std::optional<U> parseDec(std::string const &str, std::string::size_type
|
||||
return std::optional<U>{value};
|
||||
}
|
||||
|
||||
static std::optional<Rgba>
|
||||
parseColor(std::string const &str, std::string::size_type &n, uint16_t i) {
|
||||
static std::optional<Rgba> parseColor(std::string const &str, size_t &n, uint16_t i) {
|
||||
std::optional<uint8_t> r = parseDec<uint8_t>(str, n);
|
||||
if (!r) {
|
||||
error("Failed to parse color #%d (\"%s\"): invalid red component", i + 1, str.c_str());
|
||||
@@ -281,7 +256,7 @@ static void parsePSPFile(std::filebuf &file) {
|
||||
|
||||
line.clear();
|
||||
requireLine("PSP", file, line);
|
||||
std::string::size_type n = 0;
|
||||
size_t n = 0;
|
||||
std::optional<uint16_t> nbColors = parseDec<uint16_t>(line, n);
|
||||
if (!nbColors || n != line.length()) {
|
||||
error("Invalid \"number of colors\" line in PSP file (%s)", line.c_str());
|
||||
@@ -347,7 +322,7 @@ static void parseGPLFile(std::filebuf &file) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string::size_type n = 0;
|
||||
size_t n = 0;
|
||||
skipWhitespace(line, n);
|
||||
// Skip empty lines, or lines that contain just a comment.
|
||||
if (line.length() == n || line[n] == '#') {
|
||||
@@ -438,7 +413,6 @@ static void parseACTFile(std::filebuf &file) {
|
||||
uint16_t nbColors = 256;
|
||||
if (len == 772) {
|
||||
nbColors = readBE<uint16_t>(&buf[768]);
|
||||
// TODO: apparently there is a "transparent color index"? What?
|
||||
if (nbColors > 256 || nbColors == 0) {
|
||||
error("Invalid number of colors in ACT file (%" PRIu16 ")", nbColors);
|
||||
return;
|
||||
@@ -549,9 +523,6 @@ static void parseACOFile(std::filebuf &file) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: maybe scan the v2 data instead (if present)
|
||||
// `codecvt` can be used to convert from UTF-16 to UTF-8
|
||||
}
|
||||
|
||||
static void parseGBCFile(std::filebuf &file) {
|
||||
@@ -603,8 +574,7 @@ void parseExternalPalSpec(char const *arg) {
|
||||
std::tuple{"GBC", &parseGBCFile, std::ios::binary},
|
||||
};
|
||||
|
||||
auto iter =
|
||||
std::find_if(RANGE(parsers), [&arg, &ptr](decltype(parsers)::value_type const &parser) {
|
||||
auto iter = std::find_if(RANGE(parsers), [&arg, &ptr](auto const &parser) {
|
||||
return strncasecmp(arg, std::get<0>(parser), ptr - arg) == 0;
|
||||
});
|
||||
if (iter == parsers.end()) {
|
||||
|
||||
@@ -25,6 +25,10 @@
|
||||
#include "gfx/pal_sorting.hpp"
|
||||
#include "gfx/proto_palette.hpp"
|
||||
|
||||
static bool isBgColorTransparent() {
|
||||
return options.bgColor.has_value() && options.bgColor->isTransparent();
|
||||
}
|
||||
|
||||
class ImagePalette {
|
||||
std::array<std::optional<Rgba>, NB_COLOR_SLOTS> _colors;
|
||||
|
||||
@@ -36,9 +40,9 @@ public:
|
||||
// color), then the other color is returned. Otherwise, `nullptr` is returned.
|
||||
[[nodiscard]]
|
||||
Rgba const *registerColor(Rgba const &rgba) {
|
||||
decltype(_colors)::value_type &slot = _colors[rgba.cgbColor()];
|
||||
std::optional<Rgba> &slot = _colors[rgba.cgbColor()];
|
||||
|
||||
if (rgba.cgbColor() == Rgba::transparent) {
|
||||
if (rgba.cgbColor() == Rgba::transparent && !isBgColorTransparent()) {
|
||||
options.hasTransparentPixels = true;
|
||||
}
|
||||
|
||||
@@ -52,7 +56,7 @@ public:
|
||||
}
|
||||
|
||||
size_t size() const {
|
||||
return std::count_if(RANGE(_colors), [](decltype(_colors)::value_type const &slot) {
|
||||
return std::count_if(RANGE(_colors), [](std::optional<Rgba> const &slot) {
|
||||
return slot.has_value() && !slot->isTransparent();
|
||||
});
|
||||
}
|
||||
@@ -193,23 +197,20 @@ public:
|
||||
PNG_LIBPNG_VER_STRING, static_cast<png_voidp>(this), handleError, handleWarning
|
||||
);
|
||||
if (!png) {
|
||||
fatal("Failed to allocate PNG structure: %s", strerror(errno));
|
||||
fatal("Failed to create PNG read structure: %s", strerror(errno)); // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
info = png_create_info_struct(png);
|
||||
if (!info) {
|
||||
// LCOV_EXCL_START
|
||||
png_destroy_read_struct(&png, nullptr, nullptr);
|
||||
fatal("Failed to allocate PNG info structure: %s", strerror(errno));
|
||||
fatal("Failed to create PNG info structure: %s", strerror(errno));
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
png_set_read_fn(png, this, readData);
|
||||
png_set_sig_bytes(png, pngHeader.size());
|
||||
|
||||
// TODO: png_set_crc_action(png, PNG_CRC_ERROR_QUIT, PNG_CRC_WARN_DISCARD);
|
||||
|
||||
// Skipping chunks we don't use should improve performance
|
||||
// TODO: png_set_keep_unknown_chunks(png, ...);
|
||||
|
||||
// Process all chunks up to but not including the image data
|
||||
png_read_info(png, info);
|
||||
|
||||
@@ -288,9 +289,7 @@ public:
|
||||
options.verbosePrint(Options::VERB_INTERM, "No embedded palette\n");
|
||||
}
|
||||
|
||||
// Set up transformations; to turn everything into RGBA888
|
||||
// TODO: it's not necessary to uniformize the pixel data (in theory), and not doing
|
||||
// so *might* improve performance, and should reduce memory usage.
|
||||
// Set up transformations to turn everything into RGBA888 for simplicity of handling
|
||||
|
||||
// Convert grayscale to RGB
|
||||
switch (colorType & ~PNG_COLOR_MASK_ALPHA) {
|
||||
@@ -466,7 +465,6 @@ public:
|
||||
}
|
||||
|
||||
bool operator==(Iterator const &rhs) const { return coords() == rhs.coords(); }
|
||||
bool operator!=(Iterator const &rhs) const { return coords() != rhs.coords(); }
|
||||
};
|
||||
|
||||
public:
|
||||
@@ -518,10 +516,12 @@ struct AttrmapEntry {
|
||||
bool yFlip;
|
||||
bool xFlip;
|
||||
|
||||
static constexpr decltype(protoPaletteID) transparent = SIZE_MAX;
|
||||
static constexpr size_t transparent = static_cast<size_t>(-1);
|
||||
static constexpr size_t background = static_cast<size_t>(-2);
|
||||
|
||||
bool isBackgroundTile() const { return protoPaletteID == background; }
|
||||
size_t getPalID(DefaultInitVec<size_t> const &mappings) const {
|
||||
return protoPaletteID == transparent ? 0 : mappings[protoPaletteID];
|
||||
return mappings[isBackgroundTile() || protoPaletteID == transparent ? 0 : protoPaletteID];
|
||||
}
|
||||
};
|
||||
|
||||
@@ -552,10 +552,10 @@ static void generatePalSpec(Png const &png) {
|
||||
static std::tuple<DefaultInitVec<size_t>, std::vector<Palette>>
|
||||
generatePalettes(std::vector<ProtoPalette> const &protoPalettes, Png const &png) {
|
||||
// Run a "pagination" problem solver
|
||||
// TODO: allow picking one of several solvers?
|
||||
auto [mappings, nbPalettes] = overloadAndRemove(protoPalettes);
|
||||
assume(mappings.size() == protoPalettes.size());
|
||||
|
||||
// LCOV_EXCL_START
|
||||
if (options.verbosity >= Options::VERB_INTERM) {
|
||||
fprintf(
|
||||
stderr,
|
||||
@@ -567,6 +567,7 @@ static std::tuple<DefaultInitVec<size_t>, std::vector<Palette>>
|
||||
fprintf(stderr, "%zu -> %zu\n", i, mappings[i]);
|
||||
}
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
std::vector<Palette> palettes(nbPalettes);
|
||||
// If the image contains at least one transparent pixel, force transparency in the first slot of
|
||||
@@ -601,9 +602,9 @@ static std::tuple<DefaultInitVec<size_t>, std::vector<Palette>>
|
||||
// Convert the palette spec to actual palettes
|
||||
std::vector<Palette> palettes(options.palSpec.size());
|
||||
for (auto [spec, pal] : zip(options.palSpec, palettes)) {
|
||||
for (size_t i = 0; i < options.nbColorsPerPal && (!spec[i] || spec[i]->isOpaque()); ++i) {
|
||||
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]) {
|
||||
if (spec[i].has_value() && !spec[i]->isTransparent()) {
|
||||
pal[i] = spec[i]->cgbColor();
|
||||
}
|
||||
}
|
||||
@@ -654,6 +655,7 @@ static std::tuple<DefaultInitVec<size_t>, std::vector<Palette>>
|
||||
}
|
||||
|
||||
static void outputPalettes(std::vector<Palette> const &palettes) {
|
||||
// LCOV_EXCL_START
|
||||
if (options.verbosity >= Options::VERB_INTERM) {
|
||||
for (auto &&palette : palettes) {
|
||||
fputs("{ ", stderr);
|
||||
@@ -663,6 +665,7 @@ static void outputPalettes(std::vector<Palette> const &palettes) {
|
||||
fputs("}\n", stderr);
|
||||
}
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
if (palettes.size() > options.nbPalettes) {
|
||||
// If the palette generation is wrong, other (dependee) operations are likely to be
|
||||
@@ -677,7 +680,9 @@ static void outputPalettes(std::vector<Palette> const &palettes) {
|
||||
if (!options.palettes.empty()) {
|
||||
File output;
|
||||
if (!output.open(options.palettes, std::ios_base::out | std::ios_base::binary)) {
|
||||
// LCOV_EXCL_START
|
||||
fatal("Failed to create \"%s\": %s", output.c_str(options.palettes), strerror(errno));
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
for (Palette const &palette : palettes) {
|
||||
@@ -830,7 +835,9 @@ static void outputUnoptimizedTileData(
|
||||
) {
|
||||
File output;
|
||||
if (!output.open(options.output, std::ios_base::out | std::ios_base::binary)) {
|
||||
// LCOV_EXCL_START
|
||||
fatal("Failed to create \"%s\": %s", output.c_str(options.output), strerror(errno));
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
uint16_t widthTiles = options.inputSlice.width ? options.inputSlice.width : png.getWidth() / 8;
|
||||
@@ -843,7 +850,9 @@ static void outputUnoptimizedTileData(
|
||||
remainingTiles -= options.trim;
|
||||
|
||||
for (auto [tile, attr] : zip(png.visitAsTiles(), attrmap)) {
|
||||
// If the tile is fully transparent, default to palette 0
|
||||
// Do not emit fully-background tiles.
|
||||
if (!attr.isBackgroundTile()) {
|
||||
// If the tile is fully transparent, this defaults to palette 0.
|
||||
Palette const &palette = palettes[attr.getPalID(mappings)];
|
||||
for (uint32_t y = 0; y < 8; ++y) {
|
||||
uint16_t bitplanes = TileData::rowBitplanes(tile, palette, y);
|
||||
@@ -852,6 +861,7 @@ static void outputUnoptimizedTileData(
|
||||
output->sputc(bitplanes >> 8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
--remainingTiles;
|
||||
if (remainingTiles == 0) {
|
||||
@@ -869,7 +879,9 @@ static void outputUnoptimizedMaps(
|
||||
if (!path.empty()) {
|
||||
file.emplace();
|
||||
if (!file->open(path, std::ios_base::out | std::ios_base::binary)) {
|
||||
// LCOV_EXCL_START
|
||||
fatal("Failed to create \"%s\": %s", file->c_str(options.tilemap), strerror(errno));
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -887,18 +899,23 @@ static void outputUnoptimizedMaps(
|
||||
}
|
||||
|
||||
if (tilemapOutput.has_value()) {
|
||||
(*tilemapOutput)->sputc(tileID + options.baseTileIDs[bank]);
|
||||
(*tilemapOutput)
|
||||
->sputc((attr.isBackgroundTile() ? 0 : tileID) + options.baseTileIDs[bank]);
|
||||
}
|
||||
uint8_t palID = attr.getPalID(mappings);
|
||||
if (attrmapOutput.has_value()) {
|
||||
uint8_t palID = attr.getPalID(mappings) & 7;
|
||||
(*attrmapOutput)->sputc(palID | bank << 3); // The other flags are all 0
|
||||
(*attrmapOutput)->sputc((palID & 7) | bank << 3); // The other flags are all 0
|
||||
}
|
||||
if (palmapOutput.has_value()) {
|
||||
(*palmapOutput)->sputc(attr.getPalID(mappings));
|
||||
(*palmapOutput)->sputc(palID);
|
||||
}
|
||||
|
||||
// Background tiles are skipped in the tile data, so they should be skipped in the maps too.
|
||||
if (!attr.isBackgroundTile()) {
|
||||
++tileID;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct UniqueTiles {
|
||||
std::unordered_set<TileData> tileset;
|
||||
@@ -989,7 +1006,14 @@ static UniqueTiles dedupTiles(
|
||||
|
||||
bool inputWithoutOutput = !options.inputTileset.empty() && options.output.empty();
|
||||
for (auto [tile, attr] : zip(png.visitAsTiles(), attrmap)) {
|
||||
auto [tileID, matchType] = tiles.addTile({tile, palettes[mappings[attr.protoPaletteID]]});
|
||||
if (attr.isBackgroundTile()) {
|
||||
attr.xFlip = false;
|
||||
attr.yFlip = false;
|
||||
attr.bank = 0;
|
||||
attr.tileID = 0;
|
||||
} else {
|
||||
auto [tileID, matchType] =
|
||||
tiles.addTile({tile, palettes[mappings[attr.protoPaletteID]]});
|
||||
|
||||
if (inputWithoutOutput && matchType == TileData::NOPE) {
|
||||
error(
|
||||
@@ -1003,8 +1027,9 @@ static UniqueTiles dedupTiles(
|
||||
attr.xFlip = matchType == TileData::HFLIP || matchType == TileData::VHFLIP;
|
||||
attr.yFlip = matchType == TileData::VFLIP || matchType == TileData::VHFLIP;
|
||||
attr.bank = tileID >= options.maxNbTiles[0];
|
||||
attr.tileID =
|
||||
(attr.bank ? tileID - options.maxNbTiles[0] : tileID) + options.baseTileIDs[attr.bank];
|
||||
attr.tileID = (attr.bank ? tileID - options.maxNbTiles[0] : tileID)
|
||||
+ options.baseTileIDs[attr.bank];
|
||||
}
|
||||
}
|
||||
|
||||
// Copy elision should prevent the contained `unordered_set` from being re-constructed
|
||||
@@ -1014,7 +1039,9 @@ static UniqueTiles dedupTiles(
|
||||
static void outputTileData(UniqueTiles const &tiles) {
|
||||
File output;
|
||||
if (!output.open(options.output, std::ios_base::out | std::ios_base::binary)) {
|
||||
// LCOV_EXCL_START
|
||||
fatal("Failed to create \"%s\": %s", output.c_str(options.output), strerror(errno));
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
uint16_t tileID = 0;
|
||||
@@ -1036,7 +1063,9 @@ static void outputTileData(UniqueTiles const &tiles) {
|
||||
static void outputTilemap(DefaultInitVec<AttrmapEntry> const &attrmap) {
|
||||
File output;
|
||||
if (!output.open(options.tilemap, std::ios_base::out | std::ios_base::binary)) {
|
||||
// LCOV_EXCL_START
|
||||
fatal("Failed to create \"%s\": %s", output.c_str(options.tilemap), strerror(errno));
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
for (AttrmapEntry const &entry : attrmap) {
|
||||
@@ -1049,7 +1078,9 @@ static void outputAttrmap(
|
||||
) {
|
||||
File output;
|
||||
if (!output.open(options.attrmap, std::ios_base::out | std::ios_base::binary)) {
|
||||
// LCOV_EXCL_START
|
||||
fatal("Failed to create \"%s\": %s", output.c_str(options.attrmap), strerror(errno));
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
for (AttrmapEntry const &entry : attrmap) {
|
||||
@@ -1065,7 +1096,9 @@ static void outputPalmap(
|
||||
) {
|
||||
File output;
|
||||
if (!output.open(options.palmap, std::ios_base::out | std::ios_base::binary)) {
|
||||
// LCOV_EXCL_START
|
||||
fatal("Failed to create \"%s\": %s", output.c_str(options.palmap), strerror(errno));
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
for (AttrmapEntry const &entry : attrmap) {
|
||||
@@ -1093,6 +1126,7 @@ void process() {
|
||||
// Now, we have all the image's colors in `colors`
|
||||
// The next step is to order the palette
|
||||
|
||||
// LCOV_EXCL_START
|
||||
if (options.verbosity >= Options::VERB_INTERM) {
|
||||
fputs("Image colors: [ ", stderr);
|
||||
for (auto const &slot : colors) {
|
||||
@@ -1103,6 +1137,7 @@ void process() {
|
||||
}
|
||||
fputs("]\n", stderr);
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
// Now, iterate through the tiles, generating proto-palettes as we go
|
||||
// We do this unconditionally because this performs the image validation (which we want to
|
||||
@@ -1118,7 +1153,8 @@ void process() {
|
||||
std::unordered_set<uint16_t> tileColors;
|
||||
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()) {
|
||||
if (Rgba color = tile.pixel(x, y);
|
||||
!color.isTransparent() || !options.hasTransparentPixels) {
|
||||
tileColors.insert(color.cgbColor());
|
||||
}
|
||||
}
|
||||
@@ -1136,6 +1172,7 @@ void process() {
|
||||
|
||||
if (tileColors.empty()) {
|
||||
// "Empty" proto-palettes screw with the packing process, so discard those
|
||||
assume(!isBgColorTransparent());
|
||||
attrs.protoPaletteID = AttrmapEntry::transparent;
|
||||
continue;
|
||||
}
|
||||
@@ -1145,6 +1182,21 @@ void process() {
|
||||
protoPalette.add(cgbColor);
|
||||
}
|
||||
|
||||
if (options.bgColor.has_value()
|
||||
&& std::find(RANGE(tileColors), options.bgColor->cgbColor()) != tileColors.end()) {
|
||||
if (tileColors.size() == 1) {
|
||||
// The tile contains just the background color, skip it.
|
||||
attrs.protoPaletteID = AttrmapEntry::background;
|
||||
continue;
|
||||
}
|
||||
fatal(
|
||||
"Tile (%" PRIu32 ", %" PRIu32 ") contains the background color (#%08x)!",
|
||||
tile.x,
|
||||
tile.y,
|
||||
options.bgColor->toCSS()
|
||||
);
|
||||
}
|
||||
|
||||
// Insert the proto-palette, making sure to avoid overlaps
|
||||
for (size_t n = 0; n < protoPalettes.size(); ++n) {
|
||||
switch (protoPalette.compare(protoPalettes[n])) {
|
||||
@@ -1152,17 +1204,6 @@ void process() {
|
||||
protoPalettes[n] = protoPalette; // Override them
|
||||
// Remove any other proto-palettes that we encompass
|
||||
// (Example [(0, 1), (0, 2)], inserting (0, 1, 2))
|
||||
//
|
||||
// The following code does its job, except that references to the removed
|
||||
// proto-palettes are not updated, causing issues.
|
||||
// TODO: overlap might not be detrimental to the packing algorithm.
|
||||
// Investigation is necessary, especially if pathological cases are found.
|
||||
//
|
||||
// for (size_t i = protoPalettes.size(); --i != n;) {
|
||||
// if (protoPalette.compare(protoPalettes[i]) == ProtoPalette::WE_BIGGER) {
|
||||
// protoPalettes.erase(protoPalettes.begin() + i);
|
||||
// }
|
||||
// }
|
||||
[[fallthrough]];
|
||||
|
||||
case ProtoPalette::THEY_BIGGER:
|
||||
@@ -1176,7 +1217,7 @@ void process() {
|
||||
}
|
||||
|
||||
attrs.protoPaletteID = protoPalettes.size();
|
||||
if (protoPalettes.size() == AttrmapEntry::transparent) { // Check for overflow
|
||||
if (protoPalettes.size() == AttrmapEntry::background) { // Check for overflow
|
||||
fatal(
|
||||
"Reached %zu proto-palettes... sorry, this image is too much for me to handle :(",
|
||||
AttrmapEntry::transparent
|
||||
@@ -1192,6 +1233,7 @@ continue_visiting_tiles:;
|
||||
protoPalettes.size(),
|
||||
protoPalettes.size() != 1 ? "s" : ""
|
||||
);
|
||||
// LCOV_EXCL_START
|
||||
if (options.verbosity >= Options::VERB_INTERM) {
|
||||
for (auto const &protoPal : protoPalettes) {
|
||||
fputs("[ ", stderr);
|
||||
@@ -1201,6 +1243,7 @@ continue_visiting_tiles:;
|
||||
fputs("]\n", stderr);
|
||||
}
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
if (options.palSpecType == Options::EMBEDDED) {
|
||||
generatePalSpec(png);
|
||||
|
||||
@@ -195,8 +195,6 @@ void reverse() {
|
||||
Options::VERB_INTERM, "Reversed image dimensions: %zux%zu tiles\n", width, height
|
||||
);
|
||||
|
||||
// TODO: `-U` to configure tile size beyond 8x8px ("deduplication units")
|
||||
|
||||
std::vector<std::array<std::optional<Rgba>, 4>> palettes{
|
||||
{Rgba(0xFFFFFFFF), Rgba(0xAAAAAAFF), Rgba(0x555555FF), Rgba(0x000000FF)}
|
||||
};
|
||||
@@ -401,7 +399,9 @@ void reverse() {
|
||||
options.verbosePrint(Options::VERB_LOG_ACT, "Writing image...\n");
|
||||
File pngFile;
|
||||
if (!pngFile.open(options.input, std::ios::out | std::ios::binary)) {
|
||||
// LCOV_EXCL_START
|
||||
fatal("Failed to create \"%s\": %s", pngFile.c_str(options.input), strerror(errno));
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
png_structp png = png_create_write_struct(
|
||||
PNG_LIBPNG_VER_STRING,
|
||||
@@ -410,11 +410,15 @@ void reverse() {
|
||||
pngWarning
|
||||
);
|
||||
if (!png) {
|
||||
// LCOV_EXCL_START
|
||||
fatal("Failed to create PNG write struct: %s", strerror(errno));
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
png_infop pngInfo = png_create_info_struct(png);
|
||||
if (!pngInfo) {
|
||||
fatal("Failed to create PNG info struct: %s", strerror(errno));
|
||||
// LCOV_EXCL_START
|
||||
fatal("Failed to create PNG info structure: %s", strerror(errno));
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
png_set_write_fn(png, &pngFile, writePng, flushPng);
|
||||
|
||||
|
||||
@@ -58,6 +58,7 @@ uint16_t Rgba::cgbColor() const {
|
||||
|
||||
uint8_t Rgba::grayIndex() const {
|
||||
assume(isGray());
|
||||
// Convert from [0; 256[ to [0; maxOpaqueColors[
|
||||
return static_cast<uint16_t>(255 - red) * options.maxOpaqueColors() / 256;
|
||||
// Convert from 0..<256 to hasTransparentPixels..<nbColorsPerPal
|
||||
// Note that `maxOpaqueColors()` already takes `hasTransparentPixels` into account
|
||||
return (255 - red) * options.maxOpaqueColors() / 256 + options.hasTransparentPixels;
|
||||
}
|
||||
|
||||
@@ -424,5 +424,5 @@ max_out:
|
||||
}
|
||||
}
|
||||
|
||||
unreachable_();
|
||||
unreachable_(); // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
@@ -163,6 +163,7 @@ static option const longopts[] = {
|
||||
{nullptr, no_argument, nullptr, 0 }
|
||||
};
|
||||
|
||||
// LCOV_EXCL_START
|
||||
static void printUsage() {
|
||||
fputs(
|
||||
"Usage: rgblink [-dhMtVvwx] [-l script] [-m map_file] [-n sym_file]\n"
|
||||
@@ -181,6 +182,7 @@ static void printUsage() {
|
||||
stderr
|
||||
);
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
enum ScrambledRegion {
|
||||
SCRAMBLE_ROMX,
|
||||
@@ -335,8 +337,10 @@ int main(int argc, char *argv[]) {
|
||||
isWRAM0Mode = true;
|
||||
break;
|
||||
case 'h':
|
||||
// LCOV_EXCL_START
|
||||
printUsage();
|
||||
exit(0);
|
||||
// LCOV_EXCL_STOP
|
||||
case 'l':
|
||||
if (linkerScriptName) {
|
||||
warnx("Overriding linker script %s", linkerScriptName);
|
||||
@@ -393,11 +397,15 @@ int main(int argc, char *argv[]) {
|
||||
is32kMode = true;
|
||||
break;
|
||||
case 'V':
|
||||
// LCOV_EXCL_START
|
||||
printf("rgblink %s\n", get_package_version_string());
|
||||
exit(0);
|
||||
// LCOV_EXCL_STOP
|
||||
case 'v':
|
||||
// LCOV_EXCL_START
|
||||
beVerbose = true;
|
||||
break;
|
||||
// LCOV_EXCL_STOP
|
||||
case 'w':
|
||||
isWRAM0Mode = true;
|
||||
break;
|
||||
@@ -407,8 +415,10 @@ int main(int argc, char *argv[]) {
|
||||
is32kMode = true;
|
||||
break;
|
||||
default:
|
||||
// LCOV_EXCL_START
|
||||
printUsage();
|
||||
exit(1);
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -259,8 +259,13 @@ static void writeROM() {
|
||||
}
|
||||
}
|
||||
|
||||
// Checks whether this character is legal as the first character of a symbol's name in a sym file
|
||||
static bool canStartSymName(char c) {
|
||||
// Checks whether a symbol is legal for a sym file or map file.
|
||||
// Eliminates anonymous labels, which start with a '!'.
|
||||
static bool isLegalSymbol(Symbol const &sym) {
|
||||
if (sym.name.empty()) {
|
||||
return false;
|
||||
}
|
||||
char c = sym.name[0];
|
||||
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_';
|
||||
}
|
||||
|
||||
@@ -271,7 +276,7 @@ static bool isLegalForSymName(char c) {
|
||||
}
|
||||
|
||||
// Prints a symbol's name to a file, assuming that the first character is legal.
|
||||
// Illegal characters are UTF-8-decoded (errors are replaced by U+FFFD) and emitted as `\u`/`\U`.
|
||||
// Illegal characters are UTF-8-decoded (errors are replaced by U+FFFD) and emitted as '\u'/'\U'.
|
||||
static void printSymName(std::string const &name, FILE *file) {
|
||||
for (char const *ptr = name.c_str(); *ptr != '\0';) {
|
||||
char c = *ptr;
|
||||
@@ -368,7 +373,7 @@ static void writeSymBank(SortedSections const &bankSections, SectionType type, u
|
||||
forEachSortedSection(sect, {
|
||||
for (Symbol const *sym : sect->symbols) {
|
||||
// Don't output symbols that begin with an illegal character
|
||||
if (!sym->name.empty() && canStartSymName(sym->name[0])) {
|
||||
if (isLegalSymbol(*sym)) {
|
||||
symList.push_back({
|
||||
.sym = sym,
|
||||
.addr = static_cast<uint16_t>(sym->label().offset + sect->org),
|
||||
@@ -471,11 +476,14 @@ static void writeMapBank(SortedSections const §List, SectionType type, uint3
|
||||
// Also print symbols in the following "pieces"
|
||||
for (uint16_t org = sect->org; sect; sect = sect->nextu.get()) {
|
||||
for (Symbol *sym : sect->symbols) {
|
||||
// Don't output symbols that begin with an illegal character
|
||||
if (isLegalSymbol(*sym)) {
|
||||
// Space matches "\tSECTION: $xxxx ..."
|
||||
fprintf(mapFile, "\t $%04" PRIx32 " = ", sym->label().offset + org);
|
||||
printSymName(sym->name, mapFile);
|
||||
putc('\n', mapFile);
|
||||
}
|
||||
}
|
||||
|
||||
if (sect->nextu) {
|
||||
// Announce the following "piece"
|
||||
|
||||
@@ -363,7 +363,6 @@ static int32_t computeRPNExpr(Patch const &patch, std::vector<Symbol> const &fil
|
||||
case RPN_RST:
|
||||
value = popRPN(patch);
|
||||
// Acceptable values are 0x00, 0x08, 0x10, ..., 0x38
|
||||
// They can be easily checked with a bitmask
|
||||
if (value & ~0x38) {
|
||||
if (!isError) {
|
||||
error(patch.src, patch.lineNo, "Value $%" PRIx32 " is not a RST vector", value);
|
||||
@@ -374,6 +373,21 @@ static int32_t computeRPNExpr(Patch const &patch, std::vector<Symbol> const &fil
|
||||
value |= 0xC7;
|
||||
break;
|
||||
|
||||
case RPN_BIT_INDEX: {
|
||||
value = popRPN(patch);
|
||||
int32_t mask = getRPNByte(expression, size, patch);
|
||||
// Acceptable values are 0 to 7
|
||||
if (value & ~0x07) {
|
||||
if (!isError) {
|
||||
error(patch.src, patch.lineNo, "Value $%" PRIx32 " is not a bit index", value);
|
||||
isError = true;
|
||||
}
|
||||
value = 0;
|
||||
}
|
||||
value = mask | (value << 3);
|
||||
break;
|
||||
}
|
||||
|
||||
case RPN_CONST:
|
||||
value = 0;
|
||||
for (uint8_t shift = 0; shift < 32; shift += 8) {
|
||||
|
||||
@@ -228,7 +228,7 @@ static uint8_t parseHexDigit(int c) {
|
||||
} else if (c >= 'a' && c <= 'f') {
|
||||
return c - 'a' + 10;
|
||||
} else {
|
||||
unreachable_();
|
||||
unreachable_(); // LCOV_EXCL_LINE
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#include <string.h>
|
||||
#include <tuple>
|
||||
|
||||
#include "helpers.hpp" // assume
|
||||
#include "helpers.hpp" // assume, literal_strlen
|
||||
#include "linkdefs.hpp"
|
||||
#include "platform.hpp"
|
||||
|
||||
@@ -34,9 +34,10 @@ static char const *delim = " \f\n\r\t\v"; // Whitespace according to the C and P
|
||||
|
||||
static int
|
||||
nextLine(std::vector<char> &lineBuf, uint32_t &lineNo, FileStackNode const &where, FILE *file) {
|
||||
retry:
|
||||
int firstChar;
|
||||
for (;;) {
|
||||
++lineNo;
|
||||
int firstChar = getc(file);
|
||||
firstChar = getc(file);
|
||||
lineBuf.clear();
|
||||
|
||||
switch (firstChar) {
|
||||
@@ -55,7 +56,9 @@ retry:
|
||||
}
|
||||
[[fallthrough]];
|
||||
case '\n':
|
||||
goto retry;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
@@ -446,7 +449,7 @@ void sdobj_ReadFile(FileStackNode const &where, FILE *file, std::vector<Symbol>
|
||||
// It's fine to keep modifying the symbol after `AddSymbol`, only
|
||||
// the name must not be modified
|
||||
}
|
||||
if (strncasecmp(&token[1], "ef", 2) != 0) {
|
||||
if (strncasecmp(&token[1], "ef", literal_strlen("ef")) != 0) {
|
||||
fatal(&where, lineNo, "'S' line is neither \"Def\" nor \"Ref\"");
|
||||
}
|
||||
|
||||
|
||||
@@ -208,8 +208,10 @@ static void mergeSections(Section &target, std::unique_ptr<Section> &&other) {
|
||||
break;
|
||||
|
||||
case SECTION_NORMAL:
|
||||
// LCOV_EXCL_START
|
||||
unreachable_();
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
// Note that the order in which fragments are stored in the `nextu` list does not
|
||||
// really matter, only that offsets were properly computed above
|
||||
|
||||
@@ -5,7 +5,7 @@ error: anon-label-bad.asm(6):
|
||||
error: anon-label-bad.asm(9):
|
||||
syntax error, unexpected anonymous label
|
||||
error: anon-label-bad.asm(10):
|
||||
syntax error, unexpected anonymous label, expecting label or identifier or local identifier
|
||||
syntax error, unexpected anonymous label, expecting symbol or label or local label
|
||||
error: anon-label-bad.asm(22):
|
||||
syntax error, unexpected ::
|
||||
error: Assembly aborted (5 errors)!
|
||||
|
||||
@@ -7,6 +7,17 @@ FOR V, 1, 100
|
||||
PRINTLN "cont"
|
||||
ENDR
|
||||
WARN "done {d:V}"
|
||||
rept 2
|
||||
break
|
||||
; skips nested code
|
||||
rept 3
|
||||
println "\tinner"
|
||||
endr
|
||||
if 1
|
||||
println "\tconditional"
|
||||
endc
|
||||
println "outer"
|
||||
endr
|
||||
rept 1
|
||||
break
|
||||
; skips invalid code
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
warning: break.asm(9): [-Wuser]
|
||||
done 5
|
||||
warning: break.asm(17): [-Wuser]
|
||||
warning: break.asm(28): [-Wuser]
|
||||
OK
|
||||
error: break.asm(18):
|
||||
error: break.asm(29):
|
||||
BREAK can only be used inside a REPT/FOR block
|
||||
FATAL: break.asm(19) -> break.asm::REPT~1(23):
|
||||
FATAL: break.asm(30) -> break.asm::REPT~1(34):
|
||||
Ended block with 1 unterminated IF construct
|
||||
|
||||
27
test/asm/charcmp.asm
Normal file
27
test/asm/charcmp.asm
Normal file
@@ -0,0 +1,27 @@
|
||||
charmap "a", 1
|
||||
charmap "b", 2
|
||||
charmap "c", 0
|
||||
charmap "w", 3, 2, 1
|
||||
charmap "x", 1, 2
|
||||
charmap "y", 2, 1
|
||||
charmap "z", 1, 2, 3
|
||||
|
||||
macro test
|
||||
println strfmt("\"%#s\" <=> \"%#s\" == %d", \1, \2, charcmp(\1, \2))
|
||||
endm
|
||||
|
||||
test "", ""
|
||||
test "a", "a"
|
||||
test "aa", "aaa"
|
||||
test "aaa", "aa"
|
||||
test "a", "b"
|
||||
test "b", "a"
|
||||
test "", "b"
|
||||
test "c", ""
|
||||
test "abc", "cba"
|
||||
test "cabc", "cxc"
|
||||
test "zy", "abw"
|
||||
test "abab", "xx"
|
||||
test "abab", "ww"
|
||||
test "w", "z"
|
||||
test "xcy", "zw"
|
||||
15
test/asm/charcmp.out
Normal file
15
test/asm/charcmp.out
Normal file
@@ -0,0 +1,15 @@
|
||||
"" <=> "" == 0
|
||||
"a" <=> "a" == 0
|
||||
"aa" <=> "aaa" == -1
|
||||
"aaa" <=> "aa" == 1
|
||||
"a" <=> "b" == -1
|
||||
"b" <=> "a" == 1
|
||||
"" <=> "b" == -1
|
||||
"c" <=> "" == 1
|
||||
"abc" <=> "cba" == 1
|
||||
"cabc" <=> "cxc" == 0
|
||||
"zy" <=> "abw" == 0
|
||||
"abab" <=> "xx" == 0
|
||||
"abab" <=> "ww" == -1
|
||||
"w" <=> "z" == 1
|
||||
"xcy" <=> "zw" == -1
|
||||
@@ -1,28 +0,0 @@
|
||||
opt Wno-unmapped-char
|
||||
charmap "<NULL>", $00
|
||||
charmap "A", $10
|
||||
charmap "B", $20
|
||||
charmap "C", $30
|
||||
charmap "Bold", $88
|
||||
|
||||
SECTION "test", ROM0
|
||||
|
||||
DEF S EQUS "XBold<NULL>ABC"
|
||||
|
||||
assert CHARLEN("{S}") == 6
|
||||
println CHARSUB("{S}", 2)
|
||||
assert !STRCMP(CHARSUB("{S}", 2), "Bold")
|
||||
assert CHARSUB("{S}", -5) == CHARSUB("{S}", CHARLEN("{S}") + 1 - 5)
|
||||
assert CHARSUB("{S}", 2) == "Bold" && "Bold" == $88
|
||||
assert CHARSUB("{S}", 1) == $58 ; ASCII "X"
|
||||
db "{S}"
|
||||
|
||||
newcharmap ascii
|
||||
|
||||
assert CHARLEN("{S}") == 14
|
||||
println CHARSUB("{S}", 2)
|
||||
assert !STRCMP(CHARSUB("{S}", 2), "B")
|
||||
assert CHARSUB("{S}", -5) == CHARSUB("{S}", CHARLEN("{S}") + 1 - 5)
|
||||
assert CHARSUB("{S}", 2) == "B" && "B" == $42 ; ASCII "B"
|
||||
assert CHARSUB("{S}", 1) == $58 ; ASCII "X"
|
||||
db "{S}"
|
||||
34
test/asm/charlen-strchar.asm
Normal file
34
test/asm/charlen-strchar.asm
Normal file
@@ -0,0 +1,34 @@
|
||||
opt Wno-unmapped-char
|
||||
charmap "<NULL>", $00
|
||||
charmap "A", $10
|
||||
charmap "B", $20
|
||||
charmap "C", $30
|
||||
charmap "Bold", $88
|
||||
|
||||
SECTION "test", ROM0
|
||||
|
||||
DEF S EQUS "XBold<NULL>ABC"
|
||||
|
||||
assert CHARLEN("{S}") == 6
|
||||
println STRCHAR("{S}", 1)
|
||||
|
||||
assert !STRCMP(STRCHAR("{S}", 1), "Bold")
|
||||
assert STRCHAR("{S}", -5) == STRCHAR("{S}", CHARLEN("{S}") - 5)
|
||||
assert STRCHAR("{S}", 1) == "Bold" && "Bold" == $88
|
||||
assert STRCHAR("{S}", 0) == $58 ; ASCII "X"
|
||||
db "{S}"
|
||||
|
||||
for n, CHARLEN("{S}")
|
||||
assert STRCHAR("{S}", n) == CHARSUB("{S}", n + 1)
|
||||
assert STRCHAR("{S}", -n - 1) == CHARSUB("{S}", -n - 1)
|
||||
endr
|
||||
|
||||
newcharmap ascii
|
||||
|
||||
assert CHARLEN("{S}") == 14
|
||||
println STRCHAR("{S}", 1)
|
||||
assert !STRCMP(STRCHAR("{S}", 1), "B")
|
||||
assert STRCHAR("{S}", -5) == STRCHAR("{S}", CHARLEN("{S}") - 5)
|
||||
assert STRCHAR("{S}", 1) == "B" && "B" == $42 ; ASCII "B"
|
||||
assert STRCHAR("{S}", 0) == $58 ; ASCII "X"
|
||||
db "{S}"
|
||||
20
test/asm/charsize.asm
Normal file
20
test/asm/charsize.asm
Normal file
@@ -0,0 +1,20 @@
|
||||
charmap "a", 1
|
||||
charmap "b", 2, 3
|
||||
charmap "cdef", 4
|
||||
charmap "ghi", 5, 6, 7, 8, 9
|
||||
charmap "jkl", 123, 456, 789
|
||||
charmap "mno", 123456789
|
||||
charmap "¡Pokémon!", 2, 3
|
||||
|
||||
assert charsize("a") == 1
|
||||
assert charsize("b") == 2
|
||||
assert charsize("cdef") == 1
|
||||
assert charsize("ghi") == 5
|
||||
assert charsize("jkl") == 3
|
||||
assert charsize("mno") == 1
|
||||
assert charsize("¡Pokémon!") == 2
|
||||
|
||||
assert charsize("") == 0
|
||||
assert charsize("hello world") == 0
|
||||
assert charsize("abcdef") == 0
|
||||
assert charsize("é") == 0
|
||||
9
test/asm/charsize.err
Normal file
9
test/asm/charsize.err
Normal file
@@ -0,0 +1,9 @@
|
||||
error: charsize.asm(17):
|
||||
CHARSIZE: No character mapping for ""
|
||||
error: charsize.asm(18):
|
||||
CHARSIZE: No character mapping for "hello world"
|
||||
error: charsize.asm(19):
|
||||
CHARSIZE: No character mapping for "abcdef"
|
||||
error: charsize.asm(20):
|
||||
CHARSIZE: No character mapping for "é"
|
||||
error: Assembly aborted (4 errors)!
|
||||
@@ -15,3 +15,6 @@ SECTION "Unaligned", ROM0
|
||||
|
||||
println @ & 0
|
||||
println @ & 1 ; Nope
|
||||
|
||||
Floating:
|
||||
println Floating & Aligned ; Neither defined
|
||||
|
||||
@@ -4,4 +4,6 @@ error: const-and.asm(11):
|
||||
Expected constant expression: 'Aligned' is not constant at assembly time
|
||||
error: const-and.asm(17):
|
||||
Expected constant expression: PC is not constant at assembly time
|
||||
error: Assembly aborted (3 errors)!
|
||||
error: const-and.asm(20):
|
||||
Expected constant expression: 'Floating' is not constant at assembly time
|
||||
error: Assembly aborted (4 errors)!
|
||||
|
||||
@@ -5,3 +5,4 @@ $0
|
||||
$A
|
||||
$0
|
||||
$0
|
||||
$0
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
error: def-scoped.asm(10):
|
||||
syntax error, unexpected local identifier, expecting identifier
|
||||
syntax error, unexpected local label, expecting symbol
|
||||
error: def-scoped.asm(13):
|
||||
syntax error, unexpected local identifier, expecting identifier
|
||||
syntax error, unexpected local label, expecting symbol
|
||||
error: def-scoped.asm(16):
|
||||
syntax error, unexpected local identifier, expecting identifier
|
||||
syntax error, unexpected local label, expecting symbol
|
||||
error: Assembly aborted (3 errors)!
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
error: ds-bad.asm(3):
|
||||
Expected constant expression: 'unknown' is not constant at assembly time
|
||||
warning: ds-bad.asm(4): [-Wtruncation]
|
||||
Expression must be 8-bit
|
||||
Expression must be 8-bit; use LOW() to force 8-bit
|
||||
error: Assembly aborted (1 error)!
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
error: error-recovery.asm(3):
|
||||
syntax error, unexpected number
|
||||
error: error-recovery.asm(5) -> error-recovery.asm::REPT~1(7):
|
||||
syntax error, unexpected identifier
|
||||
syntax error, unexpected symbol
|
||||
error: Assembly aborted (2 errors)!
|
||||
|
||||
13
test/asm/for-loop-variable.asm
Normal file
13
test/asm/for-loop-variable.asm
Normal file
@@ -0,0 +1,13 @@
|
||||
def x = 4
|
||||
for x, x
|
||||
def x *= 2
|
||||
println x
|
||||
endr
|
||||
println x
|
||||
|
||||
def y equ 5
|
||||
for y, y
|
||||
def y *= 3
|
||||
println y
|
||||
endr
|
||||
println y
|
||||
3
test/asm/for-loop-variable.err
Normal file
3
test/asm/for-loop-variable.err
Normal file
@@ -0,0 +1,3 @@
|
||||
error: for-loop-variable.asm(12):
|
||||
'y' already defined as constant at for-loop-variable.asm(8)
|
||||
error: Assembly aborted (1 error)!
|
||||
6
test/asm/for-loop-variable.out
Normal file
6
test/asm/for-loop-variable.out
Normal file
@@ -0,0 +1,6 @@
|
||||
$0
|
||||
$2
|
||||
$4
|
||||
$6
|
||||
$4
|
||||
$5
|
||||
3
test/asm/garbage_sequence.asm
Normal file
3
test/asm/garbage_sequence.asm
Normal file
@@ -0,0 +1,3 @@
|
||||
assert 1 +# 1 == 2
|
||||
assert 2 '?* 2 == 4
|
||||
assert 3 **?''?##?? 3 == 27
|
||||
11
test/asm/garbage_sequence.err
Normal file
11
test/asm/garbage_sequence.err
Normal file
@@ -0,0 +1,11 @@
|
||||
error: garbage_sequence.asm(1):
|
||||
Unknown character '#'
|
||||
error: garbage_sequence.asm(2):
|
||||
Unknown characters ''', '?'
|
||||
error: garbage_sequence.asm(3):
|
||||
Unknown characters '?', ''', ''', '?'
|
||||
error: garbage_sequence.asm(3):
|
||||
Unknown character '#'
|
||||
error: garbage_sequence.asm(3):
|
||||
Unknown characters '#', '?', '?'
|
||||
error: Assembly aborted (5 errors)!
|
||||
@@ -1 +0,0 @@
|
||||
DEF S EQUS CHARSUB("ABC", 4)
|
||||
@@ -1,2 +0,0 @@
|
||||
warning: invalid-charsub.asm(1): [-Wbuiltin-args]
|
||||
CHARSUB: Position 4 is past the end of the string
|
||||
@@ -6,3 +6,16 @@ SECTION "invalid", ROM0[$10000]
|
||||
ld b, [$4000]
|
||||
bit 8, a
|
||||
rst $40
|
||||
|
||||
ld bc, bc
|
||||
ld de, hl
|
||||
ld hl, de
|
||||
|
||||
ld hl, sp ; no offset!
|
||||
; ld sp, hl is valid
|
||||
|
||||
ld sp, bc
|
||||
ld bc, sp
|
||||
|
||||
ld af, bc
|
||||
ld bc, af
|
||||
|
||||
@@ -10,8 +10,26 @@ error: invalid-instructions.asm(5):
|
||||
syntax error, unexpected bc, expecting hl
|
||||
error: invalid-instructions.asm(6):
|
||||
syntax error, unexpected number, expecting hl
|
||||
warning: invalid-instructions.asm(7): [-Wtruncation]
|
||||
Expression must be 3-bit
|
||||
error: invalid-instructions.asm(7):
|
||||
Bit number must be between 0 and 7, not 8
|
||||
Invalid bit index 8 for BIT
|
||||
error: invalid-instructions.asm(8):
|
||||
Invalid address $40 for RST
|
||||
error: Assembly aborted (8 errors)!
|
||||
error: invalid-instructions.asm(10):
|
||||
LD BC, BC is not a valid instruction; use LD B, B and LD C, C
|
||||
error: invalid-instructions.asm(11):
|
||||
LD DE, HL is not a valid instruction; use LD D, H and LD E, L
|
||||
error: invalid-instructions.asm(12):
|
||||
LD HL, DE is not a valid instruction; use LD H, D and LD L, E
|
||||
error: invalid-instructions.asm(14):
|
||||
LD HL, SP is not a valid instruction; use LD HL, SP + 0
|
||||
error: invalid-instructions.asm(17):
|
||||
LD SP, BC is not a valid instruction
|
||||
error: invalid-instructions.asm(18):
|
||||
syntax error, unexpected sp
|
||||
error: invalid-instructions.asm(20):
|
||||
syntax error, unexpected af
|
||||
error: invalid-instructions.asm(21):
|
||||
syntax error, unexpected af
|
||||
error: Assembly aborted (16 errors)!
|
||||
|
||||
3
test/asm/invalid-strchar-charsub.asm
Normal file
3
test/asm/invalid-strchar-charsub.asm
Normal file
@@ -0,0 +1,3 @@
|
||||
DEF S EQUS STRCHAR("ABC", 3)
|
||||
DEF T EQUS CHARSUB("ABC", 4)
|
||||
DEF U EQUS CHARSUB("ABC", 0)
|
||||
6
test/asm/invalid-strchar-charsub.err
Normal file
6
test/asm/invalid-strchar-charsub.err
Normal file
@@ -0,0 +1,6 @@
|
||||
warning: invalid-strchar-charsub.asm(1): [-Wbuiltin-args]
|
||||
STRCHAR: Index 3 is past the end of the string
|
||||
warning: invalid-strchar-charsub.asm(2): [-Wbuiltin-args]
|
||||
CHARSUB: Position 4 is past the end of the string
|
||||
warning: invalid-strchar-charsub.asm(3): [-Wbuiltin-args]
|
||||
CHARSUB: Position starts at 1
|
||||
@@ -19,7 +19,7 @@ DEF copy EQUS STRSUB("{invalid}", 1)
|
||||
println "\"{#s:invalid}\" == \"{#s:copy}\" ({d:n})"
|
||||
|
||||
DEF mid1 EQUS STRSUB("{invalid}", 5, 2)
|
||||
DEF mid2 EQUS STRSUB("{invalid}", 9, 1)
|
||||
DEF mid2 EQUS STRSLICE("{invalid}", 8, 9)
|
||||
println "\"{#s:mid2}{#s:mid1}\""
|
||||
|
||||
; characters:
|
||||
@@ -36,8 +36,8 @@ DEF n = STRLEN("{invalid}")
|
||||
DEF r = CHARLEN("{invalid}")
|
||||
println "\"{#s:invalid}\": {d:n} == {d:r}"
|
||||
|
||||
REDEF mid1 EQUS CHARSUB("{invalid}", 4)
|
||||
REDEF mid2 EQUS CHARSUB("{invalid}", 7)
|
||||
REDEF mid1 EQUS STRCHAR("{invalid}", 3)
|
||||
REDEF mid2 EQUS STRCHAR("{invalid}", 6)
|
||||
println "\"{#s:mid2}{#s:mid1}\""
|
||||
|
||||
; characters:
|
||||
@@ -53,3 +53,6 @@ println "\"{#s:invalid}\": {d:n} == {d:r}"
|
||||
|
||||
DEF final EQUS STRSUB("{invalid}", 4, 1)
|
||||
println "\"{#s:invalid}\" ends \"{#s:final}\""
|
||||
|
||||
REDEF final EQUS STRSLICE("{invalid}", 3, 4)
|
||||
println "\"{#s:invalid}\" ends \"{#s:final}\""
|
||||
|
||||
@@ -15,9 +15,9 @@ error: invalid-utf-8-strings.asm(17):
|
||||
error: invalid-utf-8-strings.asm(17):
|
||||
STRSUB: Invalid UTF-8 byte 0xA2
|
||||
error: invalid-utf-8-strings.asm(22):
|
||||
STRSUB: Invalid UTF-8 byte 0xA3
|
||||
STRSLICE: Invalid UTF-8 byte 0xA3
|
||||
error: invalid-utf-8-strings.asm(22):
|
||||
STRSUB: Invalid UTF-8 byte 0xA4
|
||||
STRSLICE: Invalid UTF-8 byte 0xA4
|
||||
error: invalid-utf-8-strings.asm(35):
|
||||
STRLEN: Invalid UTF-8 byte 0xFE
|
||||
error: invalid-utf-8-strings.asm(35):
|
||||
@@ -56,4 +56,6 @@ error: invalid-utf-8-strings.asm(50):
|
||||
STRLEN: Incomplete UTF-8 character
|
||||
error: invalid-utf-8-strings.asm(54):
|
||||
STRSUB: Incomplete UTF-8 character
|
||||
error: Assembly aborted (29 errors)!
|
||||
error: invalid-utf-8-strings.asm(57):
|
||||
STRSLICE: Incomplete UTF-8 character
|
||||
error: Assembly aborted (30 errors)!
|
||||
|
||||
@@ -4,3 +4,4 @@
|
||||
"漢<>"
|
||||
"abc<62><63>": 4 == 4
|
||||
"abc<62><63>" ends "<22><>"
|
||||
"abc<62><63>" ends "<22><>"
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
error: invalid-utf-8.asm(6) -> invalid-utf-8.asm::m(4):
|
||||
Unknown character 0xCF
|
||||
error: invalid-utf-8.asm(6) -> invalid-utf-8.asm::m(4):
|
||||
Unknown character 0xD3
|
||||
error: Assembly aborted (2 errors)!
|
||||
Unknown characters 0xCF, 0xD3
|
||||
error: Assembly aborted (1 error)!
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
|
||||
PUSHS
|
||||
|
||||
SECTION "floating", ROM0
|
||||
|
||||
Known: ; This symbol is known to be a label, but its value is not known yet
|
||||
@@ -15,15 +12,14 @@ SECTION "fixed 2", ROM0[69]
|
||||
|
||||
Constant2: ; Same as above
|
||||
|
||||
ENDSECTION ; Ensure we are in neither section
|
||||
|
||||
|
||||
MACRO print_diff
|
||||
PRINTLN (\1) - (\2)
|
||||
PRINTLN (\2) - (\1)
|
||||
ENDM
|
||||
|
||||
POPS ; Ensure we are in neither section
|
||||
|
||||
; TODO: uncomment all that can be, there is seriously room for improvement here
|
||||
; Diffing two constants should work
|
||||
print_diff Constant, Constant2
|
||||
; Diffing two labels in the same SECTION as well
|
||||
|
||||
@@ -1,37 +1,37 @@
|
||||
error: label-diff.asm(28) -> label-diff.asm::print_diff(19):
|
||||
Expected constant expression: 'Known' is not constant at assembly time
|
||||
error: label-diff.asm(28) -> label-diff.asm::print_diff(20):
|
||||
Expected constant expression: 'Known' is not constant at assembly time
|
||||
error: label-diff.asm(30) -> label-diff.asm::print_diff(19):
|
||||
Expected constant expression: 'Unknown' is not constant at assembly time
|
||||
error: label-diff.asm(30) -> label-diff.asm::print_diff(20):
|
||||
Expected constant expression: 'Unknown' is not constant at assembly time
|
||||
error: label-diff.asm(32) -> label-diff.asm::print_diff(19):
|
||||
Expected constant expression: 'Known' is not constant at assembly time
|
||||
error: label-diff.asm(32) -> label-diff.asm::print_diff(20):
|
||||
Expected constant expression: 'Known' is not constant at assembly time
|
||||
error: label-diff.asm(32) -> label-diff.asm::print_diff(21):
|
||||
Expected constant expression: 'Known' is not constant at assembly time
|
||||
Expected constant expression: 'Unknown' is not constant at assembly time
|
||||
error: label-diff.asm(34) -> label-diff.asm::print_diff(19):
|
||||
Expected constant expression: 'Unknown' is not constant at assembly time
|
||||
error: label-diff.asm(34) -> label-diff.asm::print_diff(20):
|
||||
Expected constant expression: 'Unknown' is not constant at assembly time
|
||||
error: label-diff.asm(34) -> label-diff.asm::print_diff(21):
|
||||
Expected constant expression: 'Unknown' is not constant at assembly time
|
||||
error: label-diff.asm(36) -> label-diff.asm::print_diff(20):
|
||||
Expected constant expression: 'Known' is not constant at assembly time
|
||||
error: label-diff.asm(36) -> label-diff.asm::print_diff(21):
|
||||
Expected constant expression: 'Unknown' is not constant at assembly time
|
||||
error: label-diff.asm(38) -> label-diff.asm::print_diff(20):
|
||||
Expected constant expression: 'Unknown' is not constant at assembly time
|
||||
error: label-diff.asm(38) -> label-diff.asm::print_diff(21):
|
||||
Expected constant expression: 'Unknown2' is not constant at assembly time
|
||||
error: label-diff.asm(45) -> label-diff.asm::print_diff(20):
|
||||
error: label-diff.asm(41) -> label-diff.asm::print_diff(19):
|
||||
Expected constant expression: 'Known' is not constant at assembly time
|
||||
error: label-diff.asm(45) -> label-diff.asm::print_diff(21):
|
||||
error: label-diff.asm(41) -> label-diff.asm::print_diff(20):
|
||||
Expected constant expression: 'Known' is not constant at assembly time
|
||||
error: label-diff.asm(47) -> label-diff.asm::print_diff(20):
|
||||
error: label-diff.asm(43) -> label-diff.asm::print_diff(19):
|
||||
Expected constant expression: 'Unknown' is not constant at assembly time
|
||||
error: label-diff.asm(47) -> label-diff.asm::print_diff(21):
|
||||
error: label-diff.asm(43) -> label-diff.asm::print_diff(20):
|
||||
Expected constant expression: 'Unknown' is not constant at assembly time
|
||||
error: label-diff.asm(54) -> label-diff.asm::print_diff(19):
|
||||
Expected constant expression: PC is not constant at assembly time
|
||||
error: label-diff.asm(54) -> label-diff.asm::print_diff(20):
|
||||
Expected constant expression: PC is not constant at assembly time
|
||||
error: label-diff.asm(56) -> label-diff.asm::print_diff(19):
|
||||
Expected constant expression: 'Known' is not constant at assembly time
|
||||
error: label-diff.asm(56) -> label-diff.asm::print_diff(20):
|
||||
Expected constant expression: PC is not constant at assembly time
|
||||
error: label-diff.asm(58) -> label-diff.asm::print_diff(19):
|
||||
Expected constant expression: 'Unknown' is not constant at assembly time
|
||||
error: label-diff.asm(58) -> label-diff.asm::print_diff(20):
|
||||
Expected constant expression: PC is not constant at assembly time
|
||||
error: label-diff.asm(58) -> label-diff.asm::print_diff(21):
|
||||
Expected constant expression: PC is not constant at assembly time
|
||||
error: label-diff.asm(60) -> label-diff.asm::print_diff(20):
|
||||
Expected constant expression: 'Known' is not constant at assembly time
|
||||
error: label-diff.asm(60) -> label-diff.asm::print_diff(21):
|
||||
Expected constant expression: PC is not constant at assembly time
|
||||
error: label-diff.asm(62) -> label-diff.asm::print_diff(20):
|
||||
Expected constant expression: 'Unknown' is not constant at assembly time
|
||||
error: label-diff.asm(62) -> label-diff.asm::print_diff(21):
|
||||
Expected constant expression: PC is not constant at assembly time
|
||||
error: Assembly aborted (18 errors)!
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
error: label-macro-arg.asm(38) -> label-macro-arg.asm::test_char(25):
|
||||
syntax error, unexpected local identifier, expecting identifier
|
||||
syntax error, unexpected local label, expecting symbol
|
||||
while expanding symbol "VAR_DEF"
|
||||
error: label-macro-arg.asm(38) -> label-macro-arg.asm::test_char(26):
|
||||
syntax error, unexpected local identifier, expecting identifier
|
||||
syntax error, unexpected local label, expecting symbol
|
||||
error: label-macro-arg.asm(38) -> label-macro-arg.asm::test_char(29):
|
||||
Interpolated symbol "sizeof_.something" does not exist
|
||||
error: label-macro-arg.asm(39) -> label-macro-arg.asm::test_char(25):
|
||||
syntax error, unexpected label, expecting identifier
|
||||
syntax error, unexpected label, expecting symbol
|
||||
while expanding symbol "VAR_DEF"
|
||||
error: label-macro-arg.asm(39) -> label-macro-arg.asm::test_char(26):
|
||||
syntax error, unexpected label, expecting identifier
|
||||
syntax error, unexpected label, expecting symbol
|
||||
error: label-macro-arg.asm(39) -> label-macro-arg.asm::test_char(29):
|
||||
Invalid format spec 'sizeof_'
|
||||
error: label-macro-arg.asm(39) -> label-macro-arg.asm::test_char(29):
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user