Compare commits

...

39 Commits

Author SHA1 Message Date
Rangi42
1715f85d50 Release v0.9.2 2025-05-04 10:04:11 -04:00
Rangi42
c2de0a991a Update test dependency 2025-05-03 12:43:57 -04:00
Rangi
2e6e1ccf06 Show specific messages for some more invalid instructions, not just "syntax error" (#1679) 2025-05-03 12:31:00 -04:00
Rangi42
081f48404c Remove a TODO comment about overlapping proto-palettes
The original algorithm makes a simplifying assumption that one
proto-palette does not fully contain another, and we have no
particular reason to violate this condition.
2025-05-03 10:19:33 -04:00
Rangi42
bdac0ce053 Remove unplanned TODO comments in src/gfx/pal_spec.cpp
ACT palette files support a transparent color index, but RGBGFX
cannot apply *one* such transparent color; it would need every
palette's first color to be transparent. Also ACT files tend to
say the first color is transparent anyway, which the user may
not have intended.

ACO palette files can specify version 2 color data, but it's
required to come after version 1 data, and the colors themselves
already exist in the earlier v1 data; v2 just adds UTF-16 names.

Thus, we do not need to be handling these data in those formats.
2025-05-02 21:29:14 -04:00
Rangi
122d91509f Clear some more TODO comments (#1677) 2025-05-02 21:06:34 -04:00
Rangi
7c6f778ae7 Take care of miscellaneous commented TODOs (#1676) 2025-05-02 16:44:12 -04:00
Eldred Habert
8cf6c5423a Implement --background-color (#1508)
Co-authored-by: Rangi42 <sylvie.oukaour+rangi42@gmail.com>
2025-05-01 23:39:52 -04:00
Rangi
56f7222230 Don't output anonymous labels in map files (#1674) 2025-05-01 19:19:25 +02:00
Rangi
e45b9625ca Group sequences of garbage characters (#1672) 2025-04-30 23:31:41 -04:00
Rangi42
e0a8eb8aff Update test dependencies 2025-04-24 09:52:08 -04:00
Rangi
2a5b9b5f98 Fix two RGBGFX bugs (#1671)
* Fix two RGBGFX bugs

* Fix clang-format idempotence

* Update src/gfx/rgba.cpp

Co-authored-by: Eldred Habert <me@eldred.fr>

---------

Co-authored-by: Eldred Habert <me@eldred.fr>
2025-04-24 15:39:14 +02:00
Rangi42
a72843748f Avoid using indirect C++ types 2025-04-23 00:53:20 -04:00
Rangi42
762e2311d2 Add test case for FOR loop variable reusing an existing one 2025-04-22 15:10:50 -04:00
Rangi
0b7cda9e0c Allow negative values to count macro arguments from the end (#1670) 2025-04-20 00:37:50 -04:00
Rangi42
df83bc31d2 Consistently use PRId* not PRIi* 2025-04-19 23:44:34 -04:00
John Millikin
bc8d99d915 Add -o / --output option to rgbfix to write separate output files (#1666) 2025-04-19 23:17:11 -04:00
Rangi42
c841672059 Don't use tabs for alignment 2025-03-31 19:13:46 -04:00
Rangi42
75b605797d Fix rgblink(5) man page syntax error
Copied from https://salsa.debian.org/twolife/rgbds/-/blob/640a5293/debian/patches/groff.patch
2025-03-07 10:37:20 -05:00
Rangi42
00d0ae840d Avoid use of goto in nextLine 2025-02-27 14:28:17 -05:00
Rangi42
2cdbb145da Avoid use of goto in shiftChar 2025-02-27 14:17:01 -05:00
Rangi42
d8192560b0 Avoid use of goto in FormatSpec::useCharacter 2025-02-27 13:45:13 -05:00
Rangi42
9b395f3bf1 Fix double negative 2025-02-23 13:36:55 -05:00
Rangi
0150eb4bf3 Exclude more lines from test coverage (#1663)
These fall into a few categories:
- `_unreachable()`
- Verbose print messages
- Errors that should never practically occur (alloc/read/write failure,
  more than UINT32_MAX anonymous labels, etc)
2025-02-17 04:56:10 -05:00
Rangi
632342b254 Use LCOV_EXCL comments to exclude some lines from test coverage (#1662) 2025-02-16 13:56:55 -05:00
Rangi
c9060c7f2d Increase test coverage (#1661) 2025-02-16 09:29:16 -05:00
Rangi
993879a2ed Derive operator!= from operator== (#1660) 2025-02-15 12:37:42 +01:00
Rangi
62309d5c87 Define operator!= in terms of operator== (#1659) 2025-02-15 11:34:06 +01:00
Rangi
b2e865ee2a Disable EQUS expansion for raw symbols (by parsing them as strings) (#1648) 2025-02-15 10:44:51 +01:00
Rangi
3feb75f84f Implement new string functions (#1655)
`STRFIND`, `STRRFIND`, `STRCHAR`, `STRSLICE`, `CHARCMP`, `CHARSIZE`, and `REVCHAR`
2025-02-14 23:09:45 +01:00
Rangi42
ad4d9da4cf Remove unnecessary default constructor definitions 2025-02-14 18:58:34 +01:00
Rangi42
1489854932 Use more const references when possible 2025-02-14 18:58:34 +01:00
Rangi
2aef09c8d9 Allow the bit/res/set bit index to be determined at link time (#1654)
This increments the object file revision number from 11 to 12
since it adds a new `RPN_BIT_INDEX` command.
2025-02-12 17:14:10 +01:00
Rangi42
48412e9c56 Some miscellaneous refactoring and copy-editing 2025-02-10 16:51:51 +01:00
Rangi
177a3abfac Fix bug where macro names can be treated as numeric symbols (#1653) 2025-02-08 23:03:21 +01:00
Rangi
4c916b8da8 Parser refers to "symbol"s, "label"s, and "local label"s, not "identifier"s (#1652)
This better matches how the lexed tokens are discussed in rgbasm(5)
2025-02-06 18:01:33 +01:00
Rangi42
d9d381cb62 Refactor the parser to have fewer *_no_str intermediate rules 2025-02-04 18:59:11 +01:00
Rangi
fbde24ee17 Add contrib/checkformat.bash to check for clang-formatting (#1646) 2025-02-04 10:40:38 +01:00
Rangi
91310c9eb6 Update the post-release checklist to mention rgbds-live (#1647) 2025-02-04 09:59:03 +01:00
181 changed files with 2009 additions and 849 deletions

View File

@@ -11,7 +11,7 @@ jobs:
cd rgbds cd rgbds
git remote add upstream "${{ github.event.pull_request.base.repo.clone_url }}" git remote add upstream "${{ github.event.pull_request.base.repo.clone_url }}"
git fetch upstream git fetch upstream
- name: Checkdiff - name: Check diff
working-directory: rgbds working-directory: rgbds
run: | run: |
make checkdiff "BASE_REF=${{ github.event.pull_request.base.sha }}" Q= | tee log make checkdiff "BASE_REF=${{ github.event.pull_request.base.sha }}" Q= | tee log

12
.github/workflows/checkformat.yml vendored Normal file
View 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

View File

@@ -100,7 +100,7 @@ jobs:
path: rgbds-${{ env.version }}-macos.zip path: rgbds-${{ env.version }}-macos.zip
linux: linux:
runs-on: ubuntu-20.04 # Oldest supported, for best glibc compatibility. runs-on: ubuntu-22.04 # Oldest supported, for best glibc compatibility.
steps: steps:
- name: Get version from tag - name: Get version from tag
shell: bash shell: bash
@@ -112,7 +112,7 @@ jobs:
- name: Install deps - name: Install deps
shell: bash shell: bash
run: | run: |
./.github/scripts/install_deps.sh ubuntu-20.04 ./.github/scripts/install_deps.sh ubuntu-22.04
- name: Build binaries - name: Build binaries
run: | run: |
make -kj WARNFLAGS="-Wall -Wextra -pedantic -static" PKG_CONFIG="pkg-config --static" Q= make -kj WARNFLAGS="-Wall -Wextra -pedantic -static" PKG_CONFIG="pkg-config --static" Q=

View File

@@ -7,7 +7,7 @@ jobs:
unix: unix:
strategy: strategy:
matrix: matrix:
os: [ubuntu-20.04, ubuntu-22.04, macos-14] os: [ubuntu-22.04, macos-14]
cxx: [g++, clang++] cxx: [g++, clang++]
buildsys: [make, cmake] buildsys: [make, cmake]
exclude: exclude:

1
.gitignore vendored
View File

@@ -14,4 +14,5 @@ CMakeCache.txt
CMakeFiles/ CMakeFiles/
cmake_install.cmake cmake_install.cmake
build/ build/
*.dSYM/
callgrind.out.* callgrind.out.*

View File

@@ -51,16 +51,15 @@ else()
add_link_options(${SAN_FLAGS}) add_link_options(${SAN_FLAGS})
add_definitions(-D_GLIBCXX_ASSERTIONS) add_definitions(-D_GLIBCXX_ASSERTIONS)
# A non-zero optimization level is desired in debug mode, but allow overriding it nonetheless # A non-zero optimization level is desired in debug mode, but allow overriding it nonetheless
# 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 ${CMAKE_CXX_FLAGS_DEBUG}"
set(CMAKE_CXX_FLAGS_DEBUG "-g -Og -fno-omit-frame-pointer -fno-optimize-sibling-calls" CACHE STRING "" FORCE) CACHE STRING "" FORCE)
endif() endif()
if(MORE_WARNINGS) if(MORE_WARNINGS)
add_compile_options(-Werror -Wextra add_compile_options(-Werror -Wextra
-Walloc-zero -Wcast-align -Wcast-qual -Wduplicated-branches -Wduplicated-cond -Walloc-zero -Wcast-align -Wcast-qual -Wduplicated-branches -Wduplicated-cond
-Wfloat-equal -Wlogical-op -Wnull-dereference -Wold-style-cast -Wshift-overflow=2 -Wfloat-equal -Wlogical-op -Wnull-dereference -Wold-style-cast -Wshift-overflow=2
-Wstringop-overflow=4 -Wundef -Wuninitialized -Wunused -Wstringop-overflow=4 -Wtrampolines -Wundef -Wuninitialized -Wunused -Wshadow
-Wshadow # TODO: -Wshadow=compatible-local?
-Wformat=2 -Wformat-overflow=2 -Wformat-truncation=1 -Wformat=2 -Wformat-overflow=2 -Wformat-truncation=1
-Wno-format-nonliteral -Wno-strict-overflow -Wno-format-nonliteral -Wno-strict-overflow
-Wno-unused-but-set-variable # bison's `yynerrs_` is incremented but unused -Wno-unused-but-set-variable # bison's `yynerrs_` is incremented but unused

View File

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

View File

@@ -7,43 +7,43 @@
# User-defined variables # User-defined variables
Q := @ Q := @
PREFIX := /usr/local PREFIX := /usr/local
bindir := ${PREFIX}/bin bindir := ${PREFIX}/bin
mandir := ${PREFIX}/share/man mandir := ${PREFIX}/share/man
SUFFIX := SUFFIX :=
STRIP := -s STRIP := -s
BINMODE := 755 BINMODE := 755
MANMODE := 644 MANMODE := 644
# Other variables # Other variables
PKG_CONFIG := pkg-config PKG_CONFIG := pkg-config
PNGCFLAGS := `${PKG_CONFIG} --cflags libpng` PNGCFLAGS := `${PKG_CONFIG} --cflags libpng`
PNGLDFLAGS := `${PKG_CONFIG} --libs-only-L libpng` PNGLDFLAGS := `${PKG_CONFIG} --libs-only-L libpng`
PNGLDLIBS := `${PKG_CONFIG} --libs-only-l libpng` PNGLDLIBS := `${PKG_CONFIG} --libs-only-l libpng`
# Note: if this comes up empty, `version.cpp` will automatically fall back to last release number # Note: if this comes up empty, `version.cpp` will automatically fall back to last release number
VERSION_STRING := `git --git-dir=.git -c safe.directory='*' describe --tags --dirty --always 2>/dev/null` VERSION_STRING := `git --git-dir=.git -c safe.directory='*' describe --tags --dirty --always 2>/dev/null`
WARNFLAGS := -Wall -pedantic -Wno-unknown-warning-option -Wno-gnu-zero-variadic-macro-arguments WARNFLAGS := -Wall -pedantic -Wno-unknown-warning-option -Wno-gnu-zero-variadic-macro-arguments
# Overridable CXXFLAGS # Overridable CXXFLAGS
CXXFLAGS ?= -O3 -flto -DNDEBUG CXXFLAGS ?= -O3 -flto -DNDEBUG
# Non-overridable CXXFLAGS # Non-overridable CXXFLAGS
REALCXXFLAGS := ${CXXFLAGS} ${WARNFLAGS} -std=c++2a -I include -fno-exceptions -fno-rtti REALCXXFLAGS := ${CXXFLAGS} ${WARNFLAGS} -std=c++2a -I include -fno-exceptions -fno-rtti
# Overridable LDFLAGS # Overridable LDFLAGS
LDFLAGS ?= LDFLAGS ?=
# Non-overridable LDFLAGS # Non-overridable LDFLAGS
REALLDFLAGS := ${LDFLAGS} ${WARNFLAGS} -DBUILD_VERSION_STRING=\"${VERSION_STRING}\" REALLDFLAGS := ${LDFLAGS} ${WARNFLAGS} -DBUILD_VERSION_STRING=\"${VERSION_STRING}\"
# Wrapper around bison that passes flags depending on what the version supports # Wrapper around bison that passes flags depending on what the version supports
BISON := src/bison.sh BISON := src/bison.sh
RM := rm -rf RM := rm -rf
# Used for checking pull requests # Used for checking pull requests
BASE_REF := origin/master BASE_REF := origin/master
# Rules to build the RGBDS binaries # Rules to build the RGBDS binaries
@@ -202,7 +202,7 @@ develop:
$Q${MAKE} WARNFLAGS="${WARNFLAGS} -Werror -Wextra \ $Q${MAKE} WARNFLAGS="${WARNFLAGS} -Werror -Wextra \
-Walloc-zero -Wcast-align -Wcast-qual -Wduplicated-branches -Wduplicated-cond \ -Walloc-zero -Wcast-align -Wcast-qual -Wduplicated-branches -Wduplicated-cond \
-Wfloat-equal -Wlogical-op -Wnull-dereference -Wold-style-cast -Wshift-overflow=2 \ -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 \ -Wformat=2 -Wformat-overflow=2 -Wformat-truncation=1 \
-Wno-format-nonliteral -Wno-strict-overflow -Wno-unused-but-set-variable \ -Wno-format-nonliteral -Wno-strict-overflow -Wno-unused-but-set-variable \
-Wno-type-limits -Wno-tautological-constant-out-of-range-compare -Wvla \ -Wno-type-limits -Wno-tautological-constant-out-of-range-compare -Wvla \

View File

@@ -72,9 +72,14 @@ GitHub.
8. Update the following related projects. 8. Update the following related projects.
- [rgbobj](https://github.com/gbdev/rgbobj) and [rgbds-obj](https://github.com/gbdev/rgbds-obj): 1. [rgbds-www](https://github.com/gbdev/rgbds-www): update
make sure that object files created by the latest RGBASM can be parsed and displayed. [src/pages/versions.mdx](https://github.com/gbdev/rgbds-www/blob/master/src/pages/versions.mdx)
If the object file revision has been updated, rgbobj will need a corresponding release. to list the new release.
- [rgbds-www](https://github.com/gbdev/rgbds-www): update 2. [rgbds-live](https://github.com/gbdev/rgbds-live): update the `rgbds` submodule (and
[src/pages/versions.mdx](https://github.com/gbdev/rgbds-www/blob/master/src/pages/versions.mdx) [patches/rgbds.patch](https://github.com/gbdev/rgbds-live/blob/master/patches/rgbds.patch)
to list the new release. 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.

View File

@@ -21,6 +21,7 @@ _rgbfix_completions() {
[l]="old-licensee:unk" [l]="old-licensee:unk"
[m]="mbc-type:mbc" [m]="mbc-type:mbc"
[n]="rom-version:unk" [n]="rom-version:unk"
[o]="output:glob-*.gb *.gbc *.sgb"
[p]="pad-value:unk" [p]="pad-value:unk"
[r]="ram-size:unk" [r]="ram-size:unk"
[t]="title:unk" [t]="title:unk"

View File

@@ -19,6 +19,7 @@ _rgbgfx_completions() {
[Z]="columns:normal" [Z]="columns:normal"
[a]="attr-map:glob-*.attrmap" [a]="attr-map:glob-*.attrmap"
[A]="auto-attr-map:normal" [A]="auto-attr-map:normal"
[B]="background-color:unk"
[b]="base-tiles:unk" [b]="base-tiles:unk"
[c]="colors:unk" [c]="colors:unk"
[d]="depth:unk" [d]="depth:unk"

15
contrib/checkformat.bash Executable file
View 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

View File

@@ -32,14 +32,15 @@ diff <(xxd "$1") <(xxd "$2") | while read -r LINE; do
# Ignore comment lines, only pick matching bank # Ignore comment lines, only pick matching bank
# (The bank regex ignores comments already, make `cut` and `tr` process less lines) # (The bank regex ignores comments already, make `cut` and `tr` process less lines)
grep -Ei "$(printf "^%02x:" $BANK)" "$SYMFILE" | grep -Ei "$(printf "^%02x:" $BANK)" "$SYMFILE" |
sed "s/$(printf "^%02x:" $BANK)/0x/g" |
cut -d ';' -f 1 | cut -d ';' -f 1 |
tr -d "\r" | tr -d "\r" |
sort -g |
while read -r SYMADDR SYM; do while read -r SYMADDR SYM; do
SYMADDR=$((0x${SYMADDR#*:})) SYMADDR=$(($SYMADDR))
if [[ $SYMADDR -le $ADDR ]]; then if [[ $SYMADDR -le $ADDR ]]; then
printf " (%s+0x%x)\n" "$SYM" $((ADDR - SYMADDR)) printf " (%s+0x%x)\n" "$SYM" $((ADDR - SYMADDR))
fi fi
# TODO: assumes sorted sym files
done | tail -n 1 done | tail -n 1
fi) fi)
printf "%02x:%04x %s\n" $BANK $ADDR "$EXTRA" printf "%02x:%04x %s\n" $BANK $ADDR "$EXTRA"

View File

@@ -53,6 +53,7 @@ local args=(
'(-L --logo)'{-L,--logo}'+[Set custom logo]:1bpp image:' '(-L --logo)'{-L,--logo}'+[Set custom logo]:1bpp image:'
'(-m --mbc-type)'{-m,--mbc-type}"+[Set MBC flags]:mbc name:_mbc_names" '(-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:' '(-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:' '(-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:' '(-r --ram-size)'{-r,--ram-size}'+[Set RAM size]:ram size byte:'
'(-t --title)'{-t,--title}'+[Set title string]:11-char title string:' '(-t --title)'{-t,--title}'+[Set title string]:11-char title string:'

View File

@@ -28,6 +28,7 @@ local args=(
'(-Z --columns)'{-Z,--columns}'[Read the image in column-major order]' '(-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' '(-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:' '(-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:' '(-c --colors)'{-c,--colors}'+[Specify color palettes]:palette spec:'
'(-d --depth)'{-d,--depth}'+[Set bit depth]:bit depth:_depths' '(-d --depth)'{-d,--depth}'+[Set bit depth]:bit depth:_depths'

View File

@@ -20,8 +20,10 @@ void charmap_Push();
void charmap_Pop(); void charmap_Pop();
void charmap_CheckStack(); void charmap_CheckStack();
void charmap_Add(std::string const &mapping, std::vector<int32_t> &&value); 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); std::vector<int32_t> charmap_Convert(std::string const &input);
size_t charmap_ConvertNext(std::string_view &input, std::vector<int32_t> *output); 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 #endif // RGBDS_ASM_CHARMAP_HPP

View File

@@ -9,11 +9,11 @@
#include <vector> #include <vector>
struct MacroArgs { struct MacroArgs {
unsigned int shift; uint32_t shift;
std::vector<std::shared_ptr<std::string>> args; std::vector<std::shared_ptr<std::string>> args;
uint32_t nbArgs() const { return args.size() - shift; } 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; std::shared_ptr<std::string> getAllArgs() const;
void appendArg(std::shared_ptr<std::string> arg); void appendArg(std::shared_ptr<std::string> arg);

View File

@@ -22,15 +22,6 @@ struct Expression {
std::vector<uint8_t> rpn{}; // Bytes serializing the RPN expression std::vector<uint8_t> rpn{}; // Bytes serializing the RPN expression
uint32_t rpnPatchSize = 0; // Size the expression will take in the object file 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>(); } bool isKnown() const { return data.holds<int32_t>(); }
int32_t value() const { return data.get<int32_t>(); } int32_t value() const { return data.get<int32_t>(); }
@@ -51,6 +42,7 @@ struct Expression {
bool makeCheckHRAM(); bool makeCheckHRAM();
void makeCheckRST(); void makeCheckRST();
void makeCheckBitIndex(uint8_t mask);
void checkNBit(uint8_t n) const; void checkNBit(uint8_t n) const;

View File

@@ -91,11 +91,11 @@ void sect_ByteString(std::vector<int32_t> const &string);
void sect_WordString(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_LongString(std::vector<int32_t> const &string);
void sect_Skip(uint32_t skip, bool ds); void sect_Skip(uint32_t skip, bool ds);
void sect_RelByte(Expression &expr, uint32_t pcShift); void sect_RelByte(Expression const &expr, uint32_t pcShift);
void sect_RelBytes(uint32_t n, std::vector<Expression> &exprs); void sect_RelBytes(uint32_t n, std::vector<Expression> const &exprs);
void sect_RelWord(Expression &expr, uint32_t pcShift); void sect_RelWord(Expression const &expr, uint32_t pcShift);
void sect_RelLong(Expression &expr, uint32_t pcShift); void sect_RelLong(Expression const &expr, uint32_t pcShift);
void sect_PCRelByte(Expression &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_BinaryFile(std::string const &name, int32_t startPos);
void sect_BinaryFileSlice(std::string const &name, int32_t startPos, int32_t length); void sect_BinaryFileSlice(std::string const &name, int32_t startPos, int32_t length);

View File

@@ -82,7 +82,6 @@ Symbol *sym_RedefEqu(std::string const &symName, int32_t value);
Symbol *sym_AddVar(std::string const &symName, int32_t value); Symbol *sym_AddVar(std::string const &symName, int32_t value);
int32_t sym_GetRSValue(); int32_t sym_GetRSValue();
void sym_SetRSValue(int32_t value); void sym_SetRSValue(int32_t value);
uint32_t sym_GetConstantValue(std::string const &symName);
// Find a symbol by exact name, bypassing expansion checks // Find a symbol by exact name, bypassing expansion checks
Symbol *sym_FindExactSymbol(std::string const &symName); Symbol *sym_FindExactSymbol(std::string const &symName);
// Find a symbol, possibly scoped, by name // Find a symbol, possibly scoped, by name

View File

@@ -103,7 +103,7 @@ public:
} else if (other._tag == other._t2.tag_value) { } else if (other._tag == other._t2.tag_value) {
*this = other._t2.value; *this = other._t2.value;
} else { } else {
_tag = nulltag; _tag = nulltag; // LCOV_EXCL_LINE
} }
return *this; return *this;
} }

View File

@@ -18,12 +18,10 @@
#include "gfx/main.hpp" #include "gfx/main.hpp"
class File { class File {
// Construct a `std::streambuf *` by default, since it's probably lighter than a `filebuf`.
Either<std::streambuf *, std::filebuf> _file; Either<std::streambuf *, std::filebuf> _file;
public: public:
File() {} File() : _file(nullptr) {}
~File() { close(); }
// This should only be called once, and before doing any `->` operations. // This should only be called once, and before doing any `->` operations.
// Returns `nullptr` on error, and a non-null pointer otherwise. // Returns `nullptr` on error, and a non-null pointer otherwise.
@@ -61,20 +59,6 @@ public:
return const_cast<File *>(this)->operator->(); 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 { char const *c_str(std::string const &path) const {
return _file.holds<std::filebuf>() ? path.c_str() return _file.holds<std::filebuf>() ? path.c_str()
: _file.get<std::streambuf *>() == std::cin.rdbuf() ? "<stdin>" : _file.get<std::streambuf *>() == std::cin.rdbuf() ? "<stdin>"

View File

@@ -10,6 +10,8 @@
#include <utility> #include <utility>
#include <vector> #include <vector>
#include "helpers.hpp"
#include "gfx/rgba.hpp" #include "gfx/rgba.hpp"
struct Options { struct Options {
@@ -21,6 +23,7 @@ struct Options {
uint8_t verbosity = 0; // -v uint8_t verbosity = 0; // -v
std::string attrmap{}; // -a, -A std::string attrmap{}; // -a, -A
std::optional<Rgba> bgColor{}; // -B
std::array<uint8_t, 2> baseTileIDs{0, 0}; // -b std::array<uint8_t, 2> baseTileIDs{0, 0}; // -b
enum { enum {
NO_SPEC, NO_SPEC,
@@ -116,4 +119,27 @@ static constexpr auto flipTable = ([]() constexpr {
return table; 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 #endif // RGBDS_GFX_MAIN_HPP

View File

@@ -36,8 +36,8 @@ struct Rgba {
auto shl = [](uint8_t val, unsigned shift) { return static_cast<uint32_t>(val) << shift; }; 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); 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(); }
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 // 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. // Since the rest of the bits don't matter then, we return 0x8000 exactly.

View File

@@ -25,7 +25,6 @@ class EnumSeq {
auto operator*() const { return _value; } 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; }
bool operator!=(Iterator const &rhs) const { return _value != rhs._value; }
}; };
public: public:
@@ -59,10 +58,6 @@ public:
bool operator==(ZipIterator const &rhs) const { bool operator==(ZipIterator const &rhs) const {
return std::get<0>(_iters) == std::get<0>(rhs._iters); 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> template<typename... Ts>

View File

@@ -9,7 +9,7 @@
#include "helpers.hpp" // assume #include "helpers.hpp" // assume
#define RGBDS_OBJECT_VERSION_STRING "RGB9" #define RGBDS_OBJECT_VERSION_STRING "RGB9"
#define RGBDS_OBJECT_REV 11U #define RGBDS_OBJECT_REV 12U
enum AssertionType { ASSERT_WARN, ASSERT_ERROR, ASSERT_FATAL }; enum AssertionType { ASSERT_WARN, ASSERT_ERROR, ASSERT_FATAL };
@@ -52,6 +52,7 @@ enum RPNCommand {
RPN_HRAM = 0x60, RPN_HRAM = 0x60,
RPN_RST = 0x61, RPN_RST = 0x61,
RPN_BIT_INDEX = 0x62,
RPN_HIGH = 0x70, RPN_HIGH = 0x70,
RPN_LOW = 0x71, RPN_LOW = 0x71,

View File

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

View File

@@ -1,6 +1,6 @@
.\" SPDX-License-Identifier: MIT .\" SPDX-License-Identifier: MIT
.\" .\"
.Dd February 2, 2025 .Dd May 4, 2025
.Dt GBZ80 7 .Dt GBZ80 7
.Os .Os
.Sh NAME .Sh NAME

View File

@@ -2,7 +2,7 @@
.\" .\"
.\" SPDX-License-Identifier: MIT .\" SPDX-License-Identifier: MIT
.\" .\"
.Dd February 2, 2025 .Dd May 4, 2025
.Dt RGBASM-OLD 5 .Dt RGBASM-OLD 5
.Os .Os
.Sh NAME .Sh NAME

View File

@@ -1,6 +1,6 @@
.\" SPDX-License-Identifier: MIT .\" SPDX-License-Identifier: MIT
.\" .\"
.Dd February 2, 2025 .Dd May 4, 2025
.Dt RGBASM 1 .Dt RGBASM 1
.Os .Os
.Sh NAME .Sh NAME

View File

@@ -2,7 +2,7 @@
.\" .\"
.\" SPDX-License-Identifier: MIT .\" SPDX-License-Identifier: MIT
.\" .\"
.Dd February 2, 2025 .Dd May 4, 2025
.Dt RGBASM 5 .Dt RGBASM 5
.Os .Os
.Sh NAME .Sh NAME
@@ -564,22 +564,17 @@ is equivalent to the regular string
(Note that this prevents raw strings from including the double quote character.) (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). 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 .Pp
The following functions operate on string expressions. The following functions operate on string expressions, and return strings themselves.
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 "STRSLICE(str, start, stop)"
.Bl -column "STRSUB(str, pos, len)"
.It Sy Name Ta Sy Operation .It Sy Name Ta Sy Operation
.It Fn STRLEN str Ta Returns the number of characters in Ar str .
.It Fn STRCAT strs... Ta Concatenates Ar strs . .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 .It Fn STRUPR str Ta Returns Ar str No with all ASCII letters
.Pq Ql a-z .Pq Ql a-z
in uppercase. in uppercase.
.It Fn STRLWR str Ta Returns Ar str No with all ASCII letters .It Fn STRLWR str Ta Returns Ar str No with all ASCII letters
.Pq Ql A-Z .Pq Ql A-Z
in lowercase. in lowercase.
.It Fn STRSLICE str start stop Ta Returns a substring of Ar str No starting at Ar start No and ending at Ar stop No (exclusive). If Ar stop No is not specified, the substring continues to the end of Ar str 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 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 .It Fn STRFMT fmt args... Ta Returns the string Ar fmt No with each
.Ql %spec .Ql %spec
@@ -589,9 +584,35 @@ pattern replaced by interpolating the format
with its corresponding argument in with its corresponding argument in
.Ar args .Ar args
.Pq So %% Sc is replaced by the So % Sc character . .Pq So %% Sc is replaced by the So % Sc character .
.It Fn 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 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 .El
.Ss Character maps .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. 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 .Ic WRAMX
types are still considered different. types are still considered different.
.It .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. For example, alignment must be compatible with any fixed address, all specified banks must be the same, etc.
.It .It
A section fragment may not be unionized; after all, that wouldn't make much sense. 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 \&. , .Ql \&. ,
which may not be the first character. which may not be the first character.
.Pp .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 # . .Sq # .
For example, For example,
.Ql #load .Ql #load
@@ -1279,7 +1302,7 @@ it at the same time.
below). below).
.Ss Numeric constants .Ss Numeric constants
.Ic EQU .Ic EQU
is used to define immutable numeric symbols. is used to define numeric constant symbols.
Unlike Unlike
.Sq = .Sq =
above, constants defined this way cannot be redefined. above, constants defined this way cannot be redefined.
@@ -1387,6 +1410,8 @@ This expansion is disabled in a few contexts:
and and
.Ql MACRO name .Ql MACRO name
will not expand string constants in their names. 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 .Bd -literal -offset indent
DEF COUNTREG EQUS "[hl+]" DEF COUNTREG EQUS "[hl+]"
ld a, COUNTREG 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 To use the rest, you put the argument number in angle brackets, like
.Ic \e<10> . .Ic \e<10> .
.Pp .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, For example,
.Ql \e<_NARG> .Ql \e<_NARG>
or
.Ql \e<-1>
will get the last argument. will get the last argument.
.Pp .Pp
Other macro arguments and symbol interpolations will also be expanded inside the angle brackets. Other macro arguments and symbol interpolations will also be expanded inside the angle brackets.

View File

@@ -1,6 +1,6 @@
.\" SPDX-License-Identifier: MIT .\" SPDX-License-Identifier: MIT
.\" .\"
.Dd February 2, 2025 .Dd May 4, 2025
.Dt RGBDS 5 .Dt RGBDS 5
.Os .Os
.Sh NAME .Sh NAME
@@ -388,10 +388,19 @@ The value is then ANDed with $00FF
check. check.
Checks if the value is a valid Checks if the value is a valid
.Ql rst .Ql rst
.Pq see Do RST vec Dc in Xr gbz80 7 vector
vector, that is one of $00, $08, $10, $18, $20, $28, $30, or $38. .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 The value is then ORed with $C7
.Pq Ql \&| $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 .It Li $80 Ta Integer literal; followed by the
.Cm LONG .Cm LONG
integer. integer.

View File

@@ -1,6 +1,6 @@
.\" SPDX-License-Identifier: MIT .\" SPDX-License-Identifier: MIT
.\" .\"
.Dd February 2, 2025 .Dd May 4, 2025
.Dt RGBDS 7 .Dt RGBDS 7
.Os .Os
.Sh NAME .Sh NAME

View File

@@ -1,6 +1,6 @@
.\" SPDX-License-Identifier: MIT .\" SPDX-License-Identifier: MIT
.\" .\"
.Dd February 2, 2025 .Dd May 4, 2025
.Dt RGBFIX 1 .Dt RGBFIX 1
.Os .Os
.Sh NAME .Sh NAME
@@ -17,6 +17,7 @@
.Op Fl l Ar licensee_id .Op Fl l Ar licensee_id
.Op Fl m Ar mbc_type .Op Fl m Ar mbc_type
.Op Fl n Ar rom_version .Op Fl n Ar rom_version
.Op Fl o Ar out_file
.Op Fl p Ar pad_value .Op Fl p Ar pad_value
.Op Fl r Ar ram_size .Op Fl r Ar ram_size
.Op Fl t Ar title_str .Op Fl t Ar title_str
@@ -134,6 +135,9 @@ Set the ROM version
to a given value from 0 to 0xFF. to a given value from 0 to 0xFF.
.It Fl O , Fl \-overwrite .It Fl O , Fl \-overwrite
Allow overwriting different non-zero bytes in the header without a warning being emitted. 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 .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). Pad the ROM image to a valid size with a given pad value from 0 to 255 (0xFF).
.Nm .Nm

View File

@@ -2,7 +2,7 @@
.\" .\"
.\" SPDX-License-Identifier: MIT .\" SPDX-License-Identifier: MIT
.\" .\"
.Dd February 2, 2025 .Dd May 4, 2025
.Dt RGBGFX 1 .Dt RGBGFX 1
.Os .Os
.Sh NAME .Sh NAME
@@ -103,6 +103,19 @@ and has the same size.
Same as Same as
.Fl a Ar base_path Ns .attrmap .Fl a Ar base_path Ns .attrmap
.Pq see Sx Automatic output paths . .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 .It Fl b Ar base_ids , Fl \-base-tiles Ar base_ids
Set the base IDs for tile map output. Set the base IDs for tile map output.
.Ar base_ids .Ar base_ids
@@ -126,7 +139,7 @@ begins with a hash character
.Ql # , .Ql # ,
it is treated as an inline palette specification. it is treated as an inline palette specification.
It should contain a comma-separated list of hexadecimal colors, each beginning with a hash. 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 .Ql #rgb
or or
.Ql #rrggbb .Ql #rrggbb

View File

@@ -1,6 +1,6 @@
.\" SPDX-License-Identifier: MIT .\" SPDX-License-Identifier: MIT
.\" .\"
.Dd February 2, 2025 .Dd May 4, 2025
.Dt RGBLINK 1 .Dt RGBLINK 1
.Os .Os
.Sh NAME .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. Disables padding the end of the final file.
This option automatically enables This option automatically enables
.Fl t . .Fl t .
You can use this when not not making a ROM. You can use this to make binary files that are not a ROM.
When making a ROM, be careful that not using this is not a replacement for When making a ROM, note that not using this is not a replacement for
.Xr rgbfix 1 Ap s Fl p .Xr rgbfix 1 Ap s Fl p
option! option!
.El .El

View File

@@ -1,6 +1,6 @@
.\" SPDX-License-Identifier: MIT .\" SPDX-License-Identifier: MIT
.\" .\"
.Dd February 2, 2025 .Dd May 4, 2025
.Dt RGBLINK 5 .Dt RGBLINK 5
.Os .Os
.Sh NAME .Sh NAME
@@ -120,7 +120,7 @@ causes all sections between it and the next
.Ic ORG .Ic ORG
or bank specification to be placed at addresses automatically determined by or bank specification to be placed at addresses automatically determined by
.Nm . .Nm .
.Pq It is, however, compatible with Ic ALIGN No below. .Pq \&It is, however, compatible with Ic ALIGN No below.
.Pp .Pp
.Ql Ic ALIGN Ar addr , Ar offset .Ql Ic ALIGN Ar addr , Ar offset
increases the increases the

View File

@@ -31,6 +31,29 @@ struct CharmapNode {
struct Charmap { struct Charmap {
std::string name; std::string name;
std::vector<CharmapNode> nodes; // first node is reserved for the root node 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; static std::deque<Charmap> charmapList;
@@ -44,24 +67,12 @@ bool charmap_ForEach(
void (*charFunc)(std::string const &, std::vector<int32_t>) void (*charFunc)(std::string const &, std::vector<int32_t>)
) { ) {
for (Charmap const &charmap : charmapList) { 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; std::map<size_t, std::string> mappings;
// clang-format off: nested initializers charmap.forEachChar([&mappings](size_t nodeIdx, std::string const &mapping) {
for (std::stack<std::pair<size_t, std::string>> prefixes({{0, ""}}); mappings[nodeIdx] = mapping;
!prefixes.empty();) { return true;
// clang-format on });
auto [nodeIdx, mapping] = std::move(prefixes.top());
prefixes.pop();
CharmapNode const &node = charmap.nodes[nodeIdx];
if (node.isTerminal()) {
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)});
}
}
}
mapFunc(charmap.name); mapFunc(charmap.name);
for (auto [nodeIdx, mapping] : mappings) { for (auto [nodeIdx, mapping] : mappings) {
charFunc(mapping, charmap.nodes[nodeIdx].value); 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); std::swap(node.value, value);
} }
bool charmap_HasChar(std::string const &input) { bool charmap_HasChar(std::string const &mapping) {
Charmap const &charmap = *currentCharmap; Charmap const &charmap = *currentCharmap;
size_t nodeIdx = 0; size_t nodeIdx = 0;
for (char c : input) { for (char c : mapping) {
nodeIdx = charmap.nodes[nodeIdx].next[static_cast<uint8_t>(c)]; nodeIdx = charmap.nodes[nodeIdx].next[static_cast<uint8_t>(c)];
if (!nodeIdx) { if (!nodeIdx) {
@@ -178,6 +189,22 @@ bool charmap_HasChar(std::string const &input) {
return charmap.nodes[nodeIdx].isTerminal(); 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> charmap_Convert(std::string const &input) {
std::vector<int32_t> output; std::vector<int32_t> output;
for (std::string_view inputView = input; charmap_ConvertNext(inputView, &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); input = input.substr(inputIdx);
return matchLen; 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;
}

View File

@@ -22,29 +22,29 @@ void FormatSpec::useCharacter(int c) {
case ' ': case ' ':
case '+': case '+':
if (state > FORMAT_SIGN) { if (state > FORMAT_SIGN) {
goto invalid; break;
} }
state = FORMAT_EXACT; state = FORMAT_EXACT;
sign = c; sign = c;
break; return;
// exact // exact
case '#': case '#':
if (state > FORMAT_EXACT) { if (state > FORMAT_EXACT) {
goto invalid; break;
} }
state = FORMAT_ALIGN; state = FORMAT_ALIGN;
exact = true; exact = true;
break; return;
// align // align
case '-': case '-':
if (state > FORMAT_ALIGN) { if (state > FORMAT_ALIGN) {
goto invalid; break;
} }
state = FORMAT_WIDTH; state = FORMAT_WIDTH;
alignLeft = true; alignLeft = true;
break; return;
// pad, width, and prec values // pad, width, and prec values
case '0': case '0':
@@ -71,27 +71,27 @@ void FormatSpec::useCharacter(int c) {
} else if (state == FORMAT_PREC) { } else if (state == FORMAT_PREC) {
precision = precision * 10 + (c - '0'); precision = precision * 10 + (c - '0');
} else { } else {
goto invalid; break;
} }
break; return;
// width // width
case '.': case '.':
if (state > FORMAT_WIDTH) { if (state > FORMAT_WIDTH) {
goto invalid; break;
} }
state = FORMAT_FRAC; state = FORMAT_FRAC;
hasFrac = true; hasFrac = true;
break; return;
// prec // prec
case 'q': case 'q':
if (state > FORMAT_PREC) { if (state > FORMAT_PREC) {
goto invalid; break;
} }
state = FORMAT_PREC; state = FORMAT_PREC;
hasPrec = true; hasPrec = true;
break; return;
// type // type
case 'd': case 'd':
@@ -103,18 +103,19 @@ void FormatSpec::useCharacter(int c) {
case 'f': case 'f':
case 's': case 's':
if (state >= FORMAT_DONE) { if (state >= FORMAT_DONE) {
goto invalid; break;
} }
state = FORMAT_DONE; state = FORMAT_DONE;
valid = true; valid = true;
type = c; type = c;
break; return;
default: default:
invalid: break;
state = FORMAT_INVALID;
valid = false;
} }
state = FORMAT_INVALID;
valid = false;
} }
void FormatSpec::finishCharacters() { void FormatSpec::finishCharacters() {

View File

@@ -119,9 +119,11 @@ void fstk_SetPreIncludeFile(std::string const &path) {
warnx("Overriding pre-included filename %s", preIncludeName.c_str()); warnx("Overriding pre-included filename %s", preIncludeName.c_str());
} }
preIncludeName = path; preIncludeName = path;
// LCOV_EXCL_START
if (verbose) { if (verbose) {
printf("Pre-included filename %s\n", preIncludeName.c_str()); printf("Pre-included filename %s\n", preIncludeName.c_str());
} }
// LCOV_EXCL_STOP
} }
static void printDep(std::string const &path) { static void printDep(std::string const &path) {
@@ -308,9 +310,11 @@ void fstk_RunInclude(std::string const &path, bool preInclude) {
if (!fullPath) { if (!fullPath) {
if (generatedMissingIncludes && !preInclude) { if (generatedMissingIncludes && !preInclude) {
// LCOV_EXCL_START
if (verbose) { if (verbose) {
printf("Aborting (-MG) on INCLUDE file '%s' (%s)\n", path.c_str(), strerror(errno)); printf("Aborting (-MG) on INCLUDE file '%s' (%s)\n", path.c_str(), strerror(errno));
} }
// LCOV_EXCL_STOP
failedOnMissingInclude = true; failedOnMissingInclude = true;
} else { } else {
error("Unable to open included file '%s': %s\n", path.c_str(), strerror(errno)); 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)) { 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
} }
} }

View File

@@ -77,6 +77,7 @@ struct FileUnmapDeleter {
static char *mapFile(int fd, std::string const &path, size_t size) { static char *mapFile(int fd, std::string const &path, size_t size) {
void *mappingAddr = mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd, 0); void *mappingAddr = mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd, 0);
// LCOV_EXCL_START
if (mappingAddr == MAP_FAILED && errno == ENOTSUP) { if (mappingAddr == MAP_FAILED && errno == ENOTSUP) {
// The implementation may not support MAP_PRIVATE; try again with MAP_SHARED // The implementation may not support MAP_PRIVATE; try again with MAP_SHARED
// instead, offering, I believe, weaker guarantees about external modifications to // 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); mappingAddr = mmap(nullptr, size, PROT_READ, MAP_SHARED, fd, 0);
} }
// LCOV_EXCL_STOP
return mappingAddr != MAP_FAILED ? static_cast<char *>(mappingAddr) : nullptr; 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 // This map lists all RGBASM keywords which `yylex_NORMAL` lexes as identifiers
// that would normally be matched as identifiers! Check out `yylex_NORMAL` to // (see `startsIdentifier` and `continuesIdentifier` below). All non-identifier
// see how this is used. // tokens are lexed separately.
// Tokens / keywords not handled here are handled in `yylex_NORMAL`'s switch.
// This assumes that no two keywords have the same name.
static std::unordered_map<std::string, int, CaseInsensitive, CaseInsensitive> keywordDict = { static std::unordered_map<std::string, int, CaseInsensitive, CaseInsensitive> keywordDict = {
{"ADC", T_(SM83_ADC) }, {"ADC", T_(SM83_ADC) },
{"ADD", T_(SM83_ADD) }, {"ADD", T_(SM83_ADD) },
@@ -241,20 +241,27 @@ static std::unordered_map<std::string, int, CaseInsensitive, CaseInsensitive> ke
{"BITWIDTH", T_(OP_BITWIDTH) }, {"BITWIDTH", T_(OP_BITWIDTH) },
{"TZCOUNT", T_(OP_TZCOUNT) }, {"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) }, {"STRCAT", T_(OP_STRCAT) },
{"STRUPR", T_(OP_STRUPR) }, {"STRCHAR", T_(OP_STRCHAR) },
{"STRLWR", T_(OP_STRLWR) }, {"STRCMP", T_(OP_STRCMP) },
{"STRRPL", T_(OP_STRRPL) }, {"STRFIND", T_(OP_STRFIND) },
{"STRFMT", T_(OP_STRFMT) }, {"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) }, {"CHARLEN", T_(OP_CHARLEN) },
{"CHARSIZE", T_(OP_CHARSIZE) },
{"CHARSUB", T_(OP_CHARSUB) }, {"CHARSUB", T_(OP_CHARSUB) },
{"INCHARMAP", T_(OP_INCHARMAP) }, {"INCHARMAP", T_(OP_INCHARMAP) },
{"REVCHAR", T_(OP_REVCHAR) },
{"INCLUDE", T_(POP_INCLUDE) }, {"INCLUDE", T_(POP_INCLUDE) },
{"PRINT", T_(POP_PRINT) }, {"PRINT", T_(POP_PRINT) },
@@ -407,21 +414,27 @@ bool LexerState::setFileAsNextState(std::string const &filePath, bool updateStat
if (filePath == "-") { if (filePath == "-") {
path = "<stdin>"; path = "<stdin>";
content.emplace<BufferedContent>(STDIN_FILENO); content.emplace<BufferedContent>(STDIN_FILENO);
// LCOV_EXCL_START
if (verbose) { if (verbose) {
printf("Opening stdin\n"); printf("Opening stdin\n");
} }
// LCOV_EXCL_STOP
} else { } else {
struct stat statBuf; struct stat statBuf;
if (stat(filePath.c_str(), &statBuf) != 0) { if (stat(filePath.c_str(), &statBuf) != 0) {
// LCOV_EXCL_START
error("Failed to stat file \"%s\": %s\n", filePath.c_str(), strerror(errno)); error("Failed to stat file \"%s\": %s\n", filePath.c_str(), strerror(errno));
return false; return false;
// LCOV_EXCL_STOP
} }
path = filePath; path = filePath;
int fd = open(path.c_str(), O_RDONLY); int fd = open(path.c_str(), O_RDONLY);
if (fd < 0) { if (fd < 0) {
// LCOV_EXCL_START
error("Failed to open file \"%s\": %s\n", path.c_str(), strerror(errno)); error("Failed to open file \"%s\": %s\n", path.c_str(), strerror(errno));
return false; return false;
// LCOV_EXCL_STOP
} }
bool isMmapped = false; bool isMmapped = false;
@@ -433,9 +446,11 @@ bool LexerState::setFileAsNextState(std::string const &filePath, bool updateStat
content.emplace<ViewedContent>( content.emplace<ViewedContent>(
std::shared_ptr<char[]>(mappingAddr, FileUnmapDeleter(size)), size std::shared_ptr<char[]>(mappingAddr, FileUnmapDeleter(size)), size
); );
// LCOV_EXCL_START
if (verbose) { if (verbose) {
printf("File \"%s\" is mmap()ped\n", path.c_str()); printf("File \"%s\" is mmap()ped\n", path.c_str());
} }
// LCOV_EXCL_STOP
isMmapped = true; isMmapped = true;
} }
} }
@@ -443,6 +458,7 @@ bool LexerState::setFileAsNextState(std::string const &filePath, bool updateStat
if (!isMmapped) { if (!isMmapped) {
// Sometimes mmap() fails or isn't available, so have a fallback // Sometimes mmap() fails or isn't available, so have a fallback
content.emplace<BufferedContent>(fd); content.emplace<BufferedContent>(fd);
// LCOV_EXCL_START
if (verbose) { if (verbose) {
if (statBuf.st_size == 0) { if (statBuf.st_size == 0) {
printf("File \"%s\" is empty\n", path.c_str()); 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); ssize_t nbReadChars = read(fd, &buf[startIndex], nbChars);
if (nbReadChars == -1) { if (nbReadChars == -1) {
// LCOV_EXCL_START
fatalerror("Error while reading \"%s\": %s\n", lexerState->path.c_str(), strerror(errno)); fatalerror("Error while reading \"%s\": %s\n", lexerState->path.c_str(), strerror(errno));
// LCOV_EXCL_STOP
} }
size += nbReadChars; size += nbReadChars;
@@ -606,13 +625,24 @@ static uint32_t readBracketedMacroArgNum() {
lexerState->disableInterpolation = disableInterpolation; lexerState->disableInterpolation = disableInterpolation;
}}; }};
uint32_t num = 0; int32_t num = 0;
int c = peek(); int c = peek();
bool empty = false; bool empty = false;
bool symbolError = false; bool symbolError = false;
bool negative = c == '-';
if (negative) {
shiftChar();
c = peek();
}
if (c >= '0' && c <= '9') { 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 == '#') { } else if (startsIdentifier(c) || c == '#') {
if (c == '#') { if (c == '#') {
shiftChar(); shiftChar();
@@ -645,7 +675,7 @@ static uint32_t readBracketedMacroArgNum() {
num = 0; num = 0;
symbolError = true; symbolError = true;
} else { } else {
num = sym->getConstantValue(); num = static_cast<int32_t>(sym->getConstantValue());
} }
} else { } else {
empty = true; 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) assume(str); // '\#' should always be defined (at least as an empty string)
return str; return str;
} else if (name == '<') { } else if (name == '<') {
uint32_t num = readBracketedMacroArgNum(); int32_t num = readBracketedMacroArgNum();
if (num == 0) { if (num == 0) {
// The error was already reported by `readBracketedMacroArgNum`. // The error was already reported by `readBracketedMacroArgNum`.
return nullptr; return nullptr;
@@ -699,7 +729,7 @@ static std::shared_ptr<std::string> readMacroArg(char name) {
auto str = macroArgs->getArg(num); auto str = macroArgs->getArg(num);
if (!str) { if (!str) {
error("Macro argument '\\<%" PRIu32 ">' not defined\n", num); error("Macro argument '\\<%" PRId32 ">' not defined\n", num);
} }
return str; return str;
} else { } else {
@@ -756,7 +786,9 @@ int LexerState::peekCharAhead() {
// and `.peekCharAhead()` will continue with its parent // and `.peekCharAhead()` will continue with its parent
assume(exp.offset <= exp.size()); assume(exp.offset <= exp.size());
if (exp.offset + distance < 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; distance -= exp.size() - exp.offset;
} }
@@ -841,22 +873,24 @@ static void shiftChar() {
lexerState->macroArgScanDistance--; lexerState->macroArgScanDistance--;
restart: for (;;) {
if (!lexerState->expansions.empty()) { if (!lexerState->expansions.empty()) {
// Advance within the current expansion // Advance within the current expansion
if (Expansion &exp = lexerState->expansions.front(); exp.advance()) { if (Expansion &exp = lexerState->expansions.front(); exp.advance()) {
// When advancing would go past an expansion's end, // When advancing would go past an expansion's end,
// move up to its parent and try again to advance // move up to its parent and try again to advance
lexerState->expansions.pop_front(); lexerState->expansions.pop_front();
goto restart; continue;
} }
} else {
// Advance within the file contents
if (lexerState->content.holds<ViewedContent>()) {
lexerState->content.get<ViewedContent>().offset++;
} else { } else {
lexerState->content.get<BufferedContent>().advance(); // Advance within the file contents
if (lexerState->content.holds<ViewedContent>()) {
lexerState->content.get<ViewedContent>().offset++;
} else {
lexerState->content.get<BufferedContent>().advance();
}
} }
return;
} }
} }
@@ -1179,7 +1213,7 @@ static uint32_t readGfxConstant() {
return bitPlaneUpper << 8 | bitPlaneLower; return bitPlaneUpper << 8 | bitPlaneLower;
} }
// Functions to read identifiers & keywords // Functions to read identifiers and keywords
static bool startsIdentifier(int c) { static bool startsIdentifier(int c) {
// Anonymous labels internally start with '!' // Anonymous labels internally start with '!'
@@ -1192,18 +1226,18 @@ static bool continuesIdentifier(int c) {
static Token readIdentifier(char firstChar, bool raw) { static Token readIdentifier(char firstChar, bool raw) {
std::string identifier(1, firstChar); 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()) { for (int c = peek(); continuesIdentifier(c); c = peek()) {
shiftChar(); shiftChar();
// Write the char to the identifier's name // Write the char to the identifier's name
identifier += c; 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 == '.') { 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 // Label scopes `.` and `..` are the only nonlocal identifiers that start with a dot
if (identifier.find_first_not_of('.') == identifier.npos) { if (identifier.find_first_not_of('.') == identifier.npos) {
tokenType = T_(ID); tokenType = T_(SYMBOL);
} }
return Token(tokenType, identifier); return Token(tokenType, identifier);
@@ -1276,7 +1310,7 @@ static std::shared_ptr<std::string> readInterpolation(size_t depth) {
lexerState->disableInterpolation = disableInterpolation; lexerState->disableInterpolation = disableInterpolation;
if (fmtBuf.starts_with('#')) { 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); fmtBuf.erase(0, 1);
} else if (keywordDict.find(fmtBuf) != keywordDict.end()) { } else if (keywordDict.find(fmtBuf) != keywordDict.end()) {
// Don't allow symbols that alias keywords without a '#' prefix. // 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"; str += "\\n";
break; break;
case '\r': case '\r':
// A literal CR in a string may get treated as a LF, so '\r' is not tested.
// LCOV_EXCL_START
str += "\\r"; str += "\\r";
break; break;
// LCOV_EXCL_STOP
case '\t': case '\t':
str += "\\t"; str += "\\t";
break; break;
@@ -1620,6 +1657,12 @@ static void appendStringLiteral(std::string &str, bool raw) {
static Token yylex_SKIP_TO_ENDC(); // forward declaration for yylex_NORMAL 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() { static Token yylex_NORMAL() {
for (;;) { for (;;) {
int c = nextChar(); int c = nextChar();
@@ -1641,7 +1684,7 @@ static Token yylex_NORMAL() {
case '@': { case '@': {
std::string symName("@"); std::string symName("@");
return Token(T_(ID), symName); return Token(T_(SYMBOL), symName);
} }
case '[': case '[':
@@ -1903,15 +1946,15 @@ static Token yylex_NORMAL() {
} }
// If a keyword, don't try to expand // 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; 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>()); assume(token.value.holds<std::string>());
// Local symbols cannot be string expansions // Raw symbols and local symbols cannot be string expansions
if (token.type == T_(ID) && lexerState->expandStrings) { if (!raw && token.type == T_(SYMBOL) && lexerState->expandStrings) {
// Attempt string expansion // Attempt string expansion
Symbol const *sym = sym_FindExactSymbol(token.value.get<std::string>()); 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 // 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" // 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 // 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 // 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`. // 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 ':' // 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, // ':' character *immediately* follows the identifier. Thus, at the beginning of a
// "Label:" and "mac:" are treated as label definitions, but "Label :" and "mac :" // line, "Label:" and "mac:" are treated as label definitions, but "Label :" and
// are treated as macro invocations. // "mac :" are treated as macro invocations.
if (token.type == T_(ID) && peek() == ':') { if (token.type == T_(SYMBOL) && peek() == ':') {
token.type = T_(LABEL); token.type = T_(LABEL);
} }
@@ -1945,8 +1988,19 @@ static Token yylex_NORMAL() {
// Do not report weird characters when capturing, it'll be done later // Do not report weird characters when capturing, it'll be done later
if (!lexerState->capturing) { if (!lexerState->capturing) {
// TODO: try to group reportings assume(isGarbageCharacter(c) || c == '#');
error("Unknown character %s\n", printChar(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; lexerState->atLineStart = false;
@@ -2090,7 +2144,7 @@ append:
} }
} }
finish: finish: // Can't `break` out of a nested `for`-`switch`
// Trim right whitespace // Trim right whitespace
auto rightPos = std::find_if_not(str.rbegin(), str.rend(), isWhitespace); auto rightPos = std::find_if_not(str.rbegin(), str.rend(), isWhitespace);
str.resize(rightPos.base() - str.begin()); str.resize(rightPos.base() - str.begin());
@@ -2158,7 +2212,8 @@ static Token skipIfBlock(bool toEndc) {
case T_(POP_ELIF): case T_(POP_ELIF):
if (lexer_ReachedELSEBlock()) { 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) { if (!toEndc && lexer_GetIFDepth() == startingDepth) {
return token; return token;
@@ -2250,9 +2305,8 @@ static Token yylex_SKIP_TO_ENDR() {
case T_(POP_ENDR): case T_(POP_ENDR):
depth--; depth--;
if (!depth) { // `lexer_CaptureRept` has already guaranteed that the `ENDR`s are balanced
return Token(T_(YYEOF)); // yywrap() will finish the REPT/FOR loop assume(depth > 0);
}
break; break;
case T_(POP_IF): case T_(POP_IF):
@@ -2390,7 +2444,7 @@ Capture lexer_CaptureRept() {
do { // Discard initial whitespace do { // Discard initial whitespace
c = nextChar(); c = nextChar();
} while (isWhitespace(c)); } 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)) { if (startsIdentifier(c)) {
switch (readIdentifier(c, false).type) { switch (readIdentifier(c, false).type) {
case T_(POP_REPT): case T_(POP_REPT):
@@ -2443,7 +2497,7 @@ Capture lexer_CaptureMacro() {
do { // Discard initial whitespace do { // Discard initial whitespace
c = nextChar(); c = nextChar();
} while (isWhitespace(c)); } while (isWhitespace(c));
// Now, try to match `ENDM` as a **whole** identifier // Now, try to match `ENDM` as a **whole** keyword
if (startsIdentifier(c)) { if (startsIdentifier(c)) {
switch (readIdentifier(c, false).type) { switch (readIdentifier(c, false).type) {
case T_(POP_ENDM): case T_(POP_ENDM):

View File

@@ -8,10 +8,16 @@
#include "asm/warning.hpp" #include "asm/warning.hpp"
std::shared_ptr<std::string> MacroArgs::getArg(uint32_t i) const { std::shared_ptr<std::string> MacroArgs::getArg(int32_t i) const {
uint32_t realIndex = i + shift - 1; // 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 { std::shared_ptr<std::string> MacroArgs::getAllArgs() const {

View File

@@ -87,6 +87,7 @@ static option const longopts[] = {
{nullptr, no_argument, nullptr, 0 } {nullptr, no_argument, nullptr, 0 }
}; };
// LCOV_EXCL_START
static void printUsage() { static void printUsage() {
fputs( fputs(
"Usage: rgbasm [-EhVvw] [-b chars] [-D name[=value]] [-g chars] [-I path]\n" "Usage: rgbasm [-EhVvw] [-b chars] [-D name[=value]] [-g chars] [-I path]\n"
@@ -107,6 +108,7 @@ static void printUsage() {
stderr stderr
); );
} }
// LCOV_EXCL_STOP
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
time_t now = time(nullptr); time_t now = time(nullptr);
@@ -176,8 +178,10 @@ int main(int argc, char *argv[]) {
break; break;
case 'h': case 'h':
// LCOV_EXCL_START
printUsage(); printUsage();
exit(0); exit(0);
// LCOV_EXCL_STOP
case 'I': case 'I':
fstk_AddIncludePath(musl_optarg); fstk_AddIncludePath(musl_optarg);
@@ -195,7 +199,7 @@ int main(int argc, char *argv[]) {
dependFileName = "<stdout>"; dependFileName = "<stdout>";
} }
if (dependFile == nullptr) { if (dependFile == nullptr) {
err("Failed to open dependfile \"%s\"", dependFileName); err("Failed to open dependfile \"%s\"", dependFileName); // LCOV_EXCL_LINE
} }
break; break;
@@ -302,9 +306,11 @@ int main(int argc, char *argv[]) {
if (stateFileSpecs.find(name) != stateFileSpecs.end()) { if (stateFileSpecs.find(name) != stateFileSpecs.end()) {
warnx("Overriding state filename %s", name); warnx("Overriding state filename %s", name);
} }
// LCOV_EXCL_START
if (verbose) { if (verbose) {
printf("State filename %s\n", name); printf("State filename %s\n", name);
} }
// LCOV_EXCL_STOP
stateFileSpecs.emplace(name, std::move(features)); stateFileSpecs.emplace(name, std::move(features));
break; break;
} }
@@ -314,8 +320,10 @@ int main(int argc, char *argv[]) {
exit(0); exit(0);
case 'v': case 'v':
// LCOV_EXCL_START
verbose = true; verbose = true;
break; break;
// LCOV_EXCL_STOP
case 'W': case 'W':
opt_W(musl_optarg); opt_W(musl_optarg);
@@ -367,8 +375,10 @@ int main(int argc, char *argv[]) {
// Unrecognized options // Unrecognized options
default: default:
// LCOV_EXCL_START
printUsage(); printUsage();
exit(1); exit(1);
// LCOV_EXCL_STOP
} }
} }
@@ -391,7 +401,7 @@ int main(int argc, char *argv[]) {
std::string mainFileName = argv[musl_optind]; std::string mainFileName = argv[musl_optind];
if (verbose) { if (verbose) {
printf("Assembling %s\n", mainFileName.c_str()); printf("Assembling %s\n", mainFileName.c_str()); // LCOV_EXCL_LINE
} }
if (dependFile) { if (dependFile) {

View File

@@ -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) { static uint32_t getSectIDIfAny(Section *sect) {
if (!sect) { if (!sect) {
return UINT32_MAX; return UINT32_MAX;
@@ -71,7 +71,8 @@ static uint32_t getSectIDIfAny(Section *sect) {
return static_cast<uint32_t>(search->second); 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) { 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); sym = sym_FindExactSymbol(symName);
if (sym->isConstant()) { if (sym->isConstant()) {
rpnexpr[rpnptr++] = RPN_CONST; rpnexpr[rpnptr++] = RPN_CONST;
value = sym_GetConstantValue(symName); value = sym->getConstantValue();
} else { } else {
rpnexpr[rpnptr++] = RPN_SYM; rpnexpr[rpnptr++] = RPN_SYM;
registerUnregisteredSymbol(*sym); // Ensure that `sym->ID` is set registerUnregisteredSymbol(*sym); // Ensure that `sym->ID` is set
@@ -323,7 +324,7 @@ void out_WriteObject() {
file = stdout; file = stdout;
} }
if (!file) { 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); }}; Defer closeFile{[&] { fclose(file); }};
@@ -343,14 +344,7 @@ void out_WriteObject() {
writeFileStackNode(node, file); writeFileStackNode(node, file);
// The list is supposed to have decrementing IDs // The list is supposed to have decrementing IDs
if (it + 1 != fileStackNodes.end() && it[1]->ID != node.ID - 1) { assume(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
);
}
} }
for (Symbol const *sym : objectSymbols) { for (Symbol const *sym : objectSymbols) {
@@ -373,9 +367,11 @@ void out_SetFileName(std::string const &name) {
warnx("Overriding output filename %s", objectFileName.c_str()); warnx("Overriding output filename %s", objectFileName.c_str());
} }
objectFileName = name; objectFileName = name;
// LCOV_EXCL_START
if (verbose) { if (verbose) {
printf("Output filename %s\n", objectFileName.c_str()); printf("Output filename %s\n", objectFileName.c_str());
} }
// LCOV_EXCL_STOP
} }
static void dumpString(std::string const &escape, FILE *file) { 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; file = stdout;
} }
if (!file) { 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); }}; Defer closeFile{[&] { fclose(file); }};

View File

@@ -31,15 +31,6 @@
struct StrFmtArgList { struct StrFmtArgList {
std::string format; std::string format;
std::vector<Either<uint32_t, std::string>> args; 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 uint32_t strToNum(std::vector<int32_t> const &s);
static void errorInvalidUTF8Byte(uint8_t byte, char const *functionName); static void errorInvalidUTF8Byte(uint8_t byte, char const *functionName);
static size_t strlenUTF8(std::string const &str, bool printErrors); 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 std::string strsubUTF8(std::string const &str, uint32_t pos, uint32_t len);
static size_t charlenUTF8(std::string const &str); 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 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 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 strrpl(std::string_view str, std::string const &old, std::string const &rep);
static std::string strfmt( static std::string strfmt(
@@ -85,6 +80,17 @@
static void failAssert(AssertionType type); static void failAssert(AssertionType type);
static void failAssertMsg(AssertionType type, std::string const &message); 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. // The CPU encodes instructions in a logical way, so most instructions actually follow patterns.
// These enums thus help with bit twiddling to compute opcodes. // 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 }; 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 // 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 }; 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 // CC_NZ == CC_Z ^ 1, and CC_NC == CC_C ^ 1, so `!` can toggle them
enum { CC_NZ, CC_Z, CC_NC, CC_C }; enum { CC_NZ, CC_Z, CC_NC, CC_C };
@@ -269,7 +279,9 @@
%token OP_BANK "BANK" %token OP_BANK "BANK"
%token OP_BITWIDTH "BITWIDTH" %token OP_BITWIDTH "BITWIDTH"
%token OP_CEIL "CEIL" %token OP_CEIL "CEIL"
%token OP_CHARCMP "CHARCMP"
%token OP_CHARLEN "CHARLEN" %token OP_CHARLEN "CHARLEN"
%token OP_CHARSIZE "CHARSIZE"
%token OP_CHARSUB "CHARSUB" %token OP_CHARSUB "CHARSUB"
%token OP_COS "COS" %token OP_COS "COS"
%token OP_DEF "DEF" %token OP_DEF "DEF"
@@ -283,18 +295,23 @@
%token OP_LOG "LOG" %token OP_LOG "LOG"
%token OP_LOW "LOW" %token OP_LOW "LOW"
%token OP_POW "POW" %token OP_POW "POW"
%token OP_REVCHAR "REVCHAR"
%token OP_ROUND "ROUND" %token OP_ROUND "ROUND"
%token OP_SIN "SIN" %token OP_SIN "SIN"
%token OP_SIZEOF "SIZEOF" %token OP_SIZEOF "SIZEOF"
%token OP_STARTOF "STARTOF" %token OP_STARTOF "STARTOF"
%token OP_STRCAT "STRCAT" %token OP_STRCAT "STRCAT"
%token OP_STRCHAR "STRCHAR"
%token OP_STRCMP "STRCMP" %token OP_STRCMP "STRCMP"
%token OP_STRFIND "STRFIND"
%token OP_STRFMT "STRFMT" %token OP_STRFMT "STRFMT"
%token OP_STRIN "STRIN" %token OP_STRIN "STRIN"
%token OP_STRLEN "STRLEN" %token OP_STRLEN "STRLEN"
%token OP_STRLWR "STRLWR" %token OP_STRLWR "STRLWR"
%token OP_STRRFIND "STRRFIND"
%token OP_STRRIN "STRRIN" %token OP_STRRIN "STRRIN"
%token OP_STRRPL "STRRPL" %token OP_STRRPL "STRRPL"
%token OP_STRSLICE "STRSLICE"
%token OP_STRSUB "STRSUB" %token OP_STRSUB "STRSUB"
%token OP_STRUPR "STRUPR" %token OP_STRUPR "STRUPR"
%token OP_TAN "TAN" %token OP_TAN "TAN"
@@ -313,31 +330,27 @@
// Literals // Literals
%token <int32_t> NUMBER "number" %token <int32_t> NUMBER "number"
%token <std::string> STRING "string" %token <std::string> STRING "string"
%token <std::string> SYMBOL "symbol"
%token <std::string> LABEL "label" %token <std::string> LABEL "label"
%token <std::string> ID "identifier" %token <std::string> LOCAL "local label"
%token <std::string> LOCAL_ID "local identifier"
%token <std::string> ANON "anonymous label" %token <std::string> ANON "anonymous label"
/******************** Data types ********************/ /******************** 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 // RPN expressions
%type <Expression> relocexpr %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> relocexpr_no_str
%type <Expression> reloc_3bit
%type <Expression> reloc_8bit %type <Expression> reloc_8bit
%type <Expression> reloc_8bit_no_str
%type <Expression> reloc_8bit_offset %type <Expression> reloc_8bit_offset
%type <Expression> reloc_16bit %type <Expression> reloc_16bit
%type <Expression> reloc_16bit_no_str
// Constant numbers // Constant numbers
%type <int32_t> iconst %type <int32_t> iconst
%type <int32_t> const_no_str
%type <int32_t> uconst %type <int32_t> uconst
// Constant numbers used only in specific contexts // Constant numbers used only in specific contexts
%type <int32_t> bit_const
%type <int32_t> precision_arg %type <int32_t> precision_arg
%type <int32_t> rs_uconst %type <int32_t> rs_uconst
%type <int32_t> sect_org %type <int32_t> sect_org
@@ -345,6 +358,7 @@
// Strings // Strings
%type <std::string> string %type <std::string> string
%type <std::string> string_literal
%type <std::string> strcat_args %type <std::string> strcat_args
// Strings used for identifiers // Strings used for identifiers
%type <std::string> def_id %type <std::string> def_id
@@ -358,8 +372,10 @@
%type <std::string> def_rl %type <std::string> def_rl
%type <std::string> def_equs %type <std::string> def_equs
%type <std::string> redef_equs %type <std::string> redef_equs
%type <std::string> scoped_id %type <std::string> scoped_sym
%type <std::string> scoped_anon_id // `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 // SM83 instruction parameters
%type <int32_t> reg_r %type <int32_t> reg_r
@@ -368,6 +384,8 @@
%type <int32_t> reg_ss %type <int32_t> reg_ss
%type <int32_t> reg_rr %type <int32_t> reg_rr
%type <int32_t> reg_tt %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_expr
%type <int32_t> ccode %type <int32_t> ccode
%type <Expression> op_a_n %type <Expression> op_a_n
@@ -516,7 +534,7 @@ endc:
def_id: def_id:
OP_DEF { OP_DEF {
lexer_ToggleStringExpansion(false); lexer_ToggleStringExpansion(false);
} ID { } SYMBOL {
lexer_ToggleStringExpansion(true); lexer_ToggleStringExpansion(true);
$$ = std::move($3); $$ = std::move($3);
} }
@@ -525,61 +543,42 @@ def_id:
redef_id: redef_id:
POP_REDEF { POP_REDEF {
lexer_ToggleStringExpansion(false); lexer_ToggleStringExpansion(false);
} ID { } SYMBOL {
lexer_ToggleStringExpansion(true); lexer_ToggleStringExpansion(true);
$$ = std::move($3); $$ = std::move($3);
} }
; ;
// LABEL covers identifiers followed by a double colon (e.g. `call Function::ret`, scoped_sym_no_anon: SYMBOL | LABEL | LOCAL;
// 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_anon_id: scoped_sym: scoped_sym_no_anon | ANON;
scoped_id {
$$ = std::move($1);
}
| ANON {
$$ = std::move($1);
}
;
label: label:
%empty %empty
| COLON {
sym_AddAnonLabel();
}
| LOCAL_ID {
sym_AddLocalLabel($1);
}
| LOCAL_ID COLON {
sym_AddLocalLabel($1);
}
| LABEL COLON { | LABEL COLON {
sym_AddLabel($1); sym_AddLabel($1);
} }
| LOCAL_ID DOUBLE_COLON {
sym_AddLocalLabel($1);
sym_Export($1);
}
| LABEL DOUBLE_COLON { | LABEL DOUBLE_COLON {
sym_AddLabel($1); sym_AddLabel($1);
sym_Export($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: macro:
ID { SYMBOL {
// Parsing 'macro_args' will restore the lexer's normal mode // Parsing 'macro_args' will restore the lexer's normal mode
lexer_SetMode(LEXER_RAW); lexer_SetMode(LEXER_RAW);
} macro_args { } macro_args {
@@ -865,7 +864,7 @@ rept:
for: for:
POP_FOR { POP_FOR {
lexer_ToggleStringExpansion(false); lexer_ToggleStringExpansion(false);
} ID { } SYMBOL {
lexer_ToggleStringExpansion(true); lexer_ToggleStringExpansion(true);
} COMMA for_args NEWLINE capture_rept endofline { } COMMA for_args NEWLINE capture_rept endofline {
if ($8.span.ptr) { if ($8.span.ptr) {
@@ -909,7 +908,7 @@ break:
def_macro: def_macro:
POP_MACRO { POP_MACRO {
lexer_ToggleStringExpansion(false); lexer_ToggleStringExpansion(false);
} ID { } SYMBOL {
lexer_ToggleStringExpansion(true); lexer_ToggleStringExpansion(true);
} NEWLINE capture_macro endofline { } NEWLINE capture_macro endofline {
if ($6.span.ptr) { if ($6.span.ptr) {
@@ -1099,10 +1098,10 @@ purge:
; ;
purge_args: purge_args:
scoped_id { scoped_sym_no_anon {
$$.push_back($1); $$.push_back($1);
} }
| purge_args COMMA scoped_id { | purge_args COMMA scoped_sym_no_anon {
$$ = std::move($1); $$ = std::move($1);
$$.push_back($3); $$.push_back($3);
} }
@@ -1116,7 +1115,7 @@ export_list:
; ;
export_list_entry: export_list_entry:
scoped_id { scoped_sym_no_anon {
sym_Export($1); sym_Export($1);
} }
; ;
@@ -1174,16 +1173,16 @@ charmap_args:
; ;
newcharmap: newcharmap:
POP_NEWCHARMAP ID { POP_NEWCHARMAP SYMBOL {
charmap_New($2, nullptr); charmap_New($2, nullptr);
} }
| POP_NEWCHARMAP ID COMMA ID { | POP_NEWCHARMAP SYMBOL COMMA SYMBOL {
charmap_New($2, &$4); charmap_New($2, &$4);
} }
; ;
setcharmap: setcharmap:
POP_SETCHARMAP ID { POP_SETCHARMAP SYMBOL {
charmap_Set($2); charmap_Set($2);
} }
; ;
@@ -1195,7 +1194,7 @@ pushc:
; ;
pushc_setcharmap: pushc_setcharmap:
POP_PUSHC ID { POP_PUSHC SYMBOL {
charmap_Push(); charmap_Push();
charmap_Set($2); charmap_Set($2);
} }
@@ -1226,22 +1225,26 @@ print_exprs:
; ;
print_expr: print_expr:
const_no_str { relocexpr_no_str {
printf("$%" PRIX32, $1); printf("$%" PRIX32, $1.getConstVal());
} }
| string { | string_literal {
// Allow printing NUL characters // Allow printing NUL characters
fwrite($1.data(), 1, $1.length(), stdout); 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: reloc_3bit:
iconst { relocexpr {
$$ = $1; $$ = std::move($1);
if ($$ < 0 || $$ > 7) { $$.checkNBit(3);
::error("Bit number must be between 0 and 7, not %" PRId32 "\n", $$);
$$ = 0;
}
} }
; ;
@@ -1251,13 +1254,27 @@ constlist_8bit:
; ;
constlist_8bit_entry: constlist_8bit_entry:
reloc_8bit_no_str { relocexpr_no_str {
$1.checkNBit(8);
sect_RelByte($1, 0); sect_RelByte($1, 0);
} }
| string { | string_literal {
std::vector<int32_t> output = charmap_Convert($1); std::vector<int32_t> output = charmap_Convert($1);
sect_ByteString(output); 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: constlist_16bit:
@@ -1266,13 +1283,27 @@ constlist_16bit:
; ;
constlist_16bit_entry: constlist_16bit_entry:
reloc_16bit_no_str { relocexpr_no_str {
$1.checkNBit(16);
sect_RelWord($1, 0); sect_RelWord($1, 0);
} }
| string { | string_literal {
std::vector<int32_t> output = charmap_Convert($1); std::vector<int32_t> output = charmap_Convert($1);
sect_WordString(output); 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: constlist_32bit:
@@ -1284,10 +1315,20 @@ constlist_32bit_entry:
relocexpr_no_str { relocexpr_no_str {
sect_RelLong($1, 0); sect_RelLong($1, 0);
} }
| string { | string_literal {
std::vector<int32_t> output = charmap_Convert($1); std::vector<int32_t> output = charmap_Convert($1);
sect_LongString(output); 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: reloc_8bit:
@@ -1297,13 +1338,6 @@ reloc_8bit:
} }
; ;
reloc_8bit_no_str:
relocexpr_no_str {
$$ = std::move($1);
$$.checkNBit(8);
}
;
reloc_8bit_offset: reloc_8bit_offset:
OP_ADD relocexpr { OP_ADD relocexpr {
$$ = std::move($2); $$ = std::move($2);
@@ -1322,28 +1356,30 @@ reloc_16bit:
} }
; ;
reloc_16bit_no_str:
relocexpr_no_str {
$$ = std::move($1);
$$.checkNBit(16);
}
;
relocexpr: relocexpr:
relocexpr_no_str { relocexpr_no_str {
$$ = std::move($1); $$ = std::move($1);
} }
| string { | string_literal {
std::vector<int32_t> output = charmap_Convert($1); std::vector<int32_t> output = charmap_Convert($1);
$$.makeNumber(strToNum(output)); $$.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: relocexpr_no_str:
scoped_anon_id { NUMBER {
$$.makeSymbol($1);
}
| NUMBER {
$$.makeNumber($1); $$.makeNumber($1);
} }
| OP_LOGICNOT relocexpr %prec NEG { | OP_LOGICNOT relocexpr %prec NEG {
@@ -1433,11 +1469,11 @@ relocexpr_no_str:
| OP_ISCONST LPAREN relocexpr RPAREN { | OP_ISCONST LPAREN relocexpr RPAREN {
$$.makeNumber($3.isKnown()); $$.makeNumber($3.isKnown());
} }
| OP_BANK LPAREN scoped_anon_id RPAREN { | OP_BANK LPAREN scoped_sym RPAREN {
// '@' is also an ID; it is handled here // '@' is also a SYMBOL; it is handled here
$$.makeBankSymbol($3); $$.makeBankSymbol($3);
} }
| OP_BANK LPAREN string RPAREN { | OP_BANK LPAREN string_literal RPAREN {
$$.makeBankSection($3); $$.makeBankSection($3);
} }
| OP_SIZEOF LPAREN string RPAREN { | OP_SIZEOF LPAREN string RPAREN {
@@ -1454,7 +1490,7 @@ relocexpr_no_str:
} }
| OP_DEF { | OP_DEF {
lexer_ToggleStringExpansion(false); lexer_ToggleStringExpansion(false);
} LPAREN scoped_anon_id RPAREN { } LPAREN scoped_sym RPAREN {
$$.makeNumber(sym_FindScopedValidSymbol($4) != nullptr); $$.makeNumber(sym_FindScopedValidSymbol($4) != nullptr);
lexer_ToggleStringExpansion(true); lexer_ToggleStringExpansion(true);
} }
@@ -1506,14 +1542,20 @@ relocexpr_no_str:
| OP_STRCMP LPAREN string COMMA string RPAREN { | OP_STRCMP LPAREN string COMMA string RPAREN {
$$.makeNumber($3.compare($5)); $$.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 { | 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); $$.makeNumber(pos != std::string::npos ? pos + 1 : 0);
} }
| OP_STRRIN LPAREN string COMMA string RPAREN { | 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); $$.makeNumber(pos != std::string::npos ? pos + 1 : 0);
} }
| OP_STRLEN LPAREN string RPAREN { | OP_STRLEN LPAREN string RPAREN {
@@ -1525,6 +1567,16 @@ relocexpr_no_str:
| OP_INCHARMAP LPAREN string RPAREN { | OP_INCHARMAP LPAREN string RPAREN {
$$.makeNumber(charmap_HasChar($3)); $$.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 { | LPAREN relocexpr RPAREN {
$$ = std::move($2); $$ = std::move($2);
} }
@@ -1545,12 +1597,6 @@ iconst:
} }
; ;
const_no_str:
relocexpr_no_str {
$$ = $1.getConstVal();
}
;
precision_arg: precision_arg:
%empty { %empty {
$$ = fix_Precision(); $$ = fix_Precision();
@@ -1564,28 +1610,50 @@ precision_arg:
} }
; ;
string: string_literal:
STRING { STRING {
$$ = std::move($1); $$ = 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 { | OP_STRSUB LPAREN string COMMA iconst COMMA uconst RPAREN {
size_t len = strlenUTF8($3, false); size_t len = strlenUTF8($3, false);
uint32_t pos = adjustNegativePos($5, len, "STRSUB"); uint32_t pos = adjustNegativePos($5, len, "STRSUB");
$$ = strsubUTF8($3, pos, $7); $$ = strsubUTF8($3, pos, $7);
} }
| OP_STRSUB LPAREN string COMMA iconst RPAREN { | OP_STRSUB LPAREN string COMMA iconst RPAREN {
size_t len = strlenUTF8($3, false); size_t len = strlenUTF8($3, false);
uint32_t pos = adjustNegativePos($5, len, "STRSUB"); uint32_t pos = adjustNegativePos($5, len, "STRSUB");
$$ = strsubUTF8($3, pos, pos > len ? 0 : len + 1 - pos); $$ = 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 { | OP_CHARSUB LPAREN string COMMA iconst RPAREN {
size_t len = charlenUTF8($3); size_t len = charlenUTF8($3);
uint32_t pos = adjustNegativePos($5, len, "CHARSUB"); uint32_t pos = adjustNegativePos($5, len, "CHARSUB");
$$ = charsubUTF8($3, pos); $$ = 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 { | OP_STRCAT LPAREN RPAREN {
$$.clear(); $$.clear();
} }
@@ -1606,7 +1674,7 @@ string:
| OP_STRFMT LPAREN strfmt_args RPAREN { | OP_STRFMT LPAREN strfmt_args RPAREN {
$$ = strfmt($3.format, $3.args); $$ = strfmt($3.format, $3.args);
} }
| POP_SECTION LPAREN scoped_anon_id RPAREN { | POP_SECTION LPAREN scoped_sym RPAREN {
Symbol *sym = sym_FindScopedValidSymbol($3); Symbol *sym = sym_FindScopedValidSymbol($3);
if (!sym) { 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: strcat_args:
string { string {
$$ = std::move($1); $$ = std::move($1);
@@ -1647,14 +1728,24 @@ strfmt_args:
strfmt_va_args: strfmt_va_args:
%empty {} %empty {}
| strfmt_va_args COMMA const_no_str { | strfmt_va_args COMMA relocexpr_no_str {
$$ = std::move($1); $$ = 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); $$ = std::move($1);
$$.args.push_back(std::move($3)); $$.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: section:
@@ -1833,9 +1924,15 @@ sm83_and:
; ;
sm83_bit: 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(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_ConstByte(0xF8);
sect_RelByte($5, 1); 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 { | SM83_LD MODE_HL COMMA reloc_16bit {
sect_ConstByte(0x01 | (REG_HL << 4)); sect_ConstByte(0x01 | (REG_HL << 4));
sect_RelWord($4, 1); 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_sp:
SM83_LD MODE_SP COMMA MODE_HL { SM83_LD MODE_SP COMMA MODE_HL {
sect_ConstByte(0xF9); 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 { | SM83_LD MODE_SP COMMA reloc_16bit {
sect_ConstByte(0x01 | (REG_SP << 4)); sect_ConstByte(0x01 | (REG_SP << 4));
sect_RelWord($4, 1); sect_RelWord($4, 1);
@@ -2106,13 +2217,20 @@ sm83_ld_a:
; ;
sm83_ld_ss: sm83_ld_ss:
SM83_LD MODE_BC COMMA reloc_16bit { SM83_LD reg_bc_or_de COMMA reloc_16bit {
sect_ConstByte(0x01 | (REG_BC << 4)); sect_ConstByte(0x01 | ($2 << 4));
sect_RelWord($4, 1); sect_RelWord($4, 1);
} }
| SM83_LD MODE_DE COMMA reloc_16bit { | SM83_LD reg_bc_or_de COMMA reg_tt_no_af {
sect_ConstByte(0x01 | (REG_DE << 4)); ::error(
sect_RelWord($4, 1); "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 // HL is taken care of in sm83_ld_hl
// SP is taken care of in sm83_ld_sp // SP is taken care of in sm83_ld_sp
@@ -2147,9 +2265,15 @@ sm83_push:
; ;
sm83_res: 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(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:
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(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: reg_tt:
MODE_BC { reg_tt_no_af
$$ = REG_BC;
}
| MODE_DE {
$$ = REG_DE;
}
| MODE_HL {
$$ = REG_HL;
}
| MODE_AF { | MODE_AF {
$$ = REG_AF; $$ = REG_AF;
} }
; ;
reg_ss: 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 { MODE_BC {
$$ = REG_BC; $$ = REG_BC;
} }
| MODE_DE { | MODE_DE {
$$ = REG_DE; $$ = REG_DE;
} }
| MODE_HL {
$$ = REG_HL;
}
| MODE_SP {
$$ = REG_SP;
}
; ;
reg_rr: reg_rr:
@@ -2554,12 +2684,76 @@ static size_t strlenUTF8(std::string const &str, bool printErrors) {
return len; 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) { static std::string strsubUTF8(std::string const &str, uint32_t pos, uint32_t len) {
char const *ptr = str.c_str(); char const *ptr = str.c_str();
size_t index = 0; size_t index = 0;
uint32_t state = 0; uint32_t state = 0;
uint32_t codepoint = 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. // Advance to starting position in source string.
while (ptr[index] && curPos < pos) { while (ptr[index] && curPos < pos) {
@@ -2622,6 +2816,29 @@ static size_t charlenUTF8(std::string const &str) {
return len; 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) { static std::string charsubUTF8(std::string const &str, uint32_t pos) {
std::string_view view = str; std::string_view view = str;
size_t charLen = 1; size_t charLen = 1;
@@ -2645,8 +2862,48 @@ static std::string charsubUTF8(std::string const &str, uint32_t pos) {
return std::string(start); 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) { 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. // such that position -1 is the last character of a string.
if (pos < 0) { if (pos < 0) {
pos += len + 1; pos += len + 1;

View File

@@ -75,6 +75,9 @@ void Expression::makeSymbol(std::string const &symName) {
if (Symbol *sym = sym_FindScopedSymbol(symName); sym_IsPC(sym) && !sect_GetSymbolSection()) { if (Symbol *sym = sym_FindScopedSymbol(symName); sym_IsPC(sym) && !sect_GetSymbolSection()) {
error("PC has no value outside of a section\n"); error("PC has no value outside of a section\n");
data = 0; 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()) { } else if (!sym || !sym->isConstant()) {
isSymbol = true; isSymbol = true;
@@ -91,7 +94,7 @@ void Expression::makeSymbol(std::string const &symName) {
*ptr++ = RPN_SYM; *ptr++ = RPN_SYM;
memcpy(ptr, sym->name.c_str(), nameLen); memcpy(ptr, sym->name.c_str(), nameLen);
} else { } 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: case RPN_TZCOUNT:
data = val != 0 ? ctz(uval) : 32; data = val != 0 ? ctz(uval) : 32;
break; break;
default:
case RPN_LOGOR: // `makeUnaryOp` should never be called with a non-unary operator!
case RPN_LOGAND: // LCOV_EXCL_START
case RPN_LOGEQ: unreachable_();
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);
} }
// LCOV_EXCL_STOP
} else if (op == RPN_LOGNOT && tryConstLogNot(src)) { } else if (op == RPN_LOGNOT && tryConstLogNot(src)) {
data = 0; data = 0;
} else if (int32_t constVal; op == RPN_LOW && (constVal = tryConstLow(src)) != -1) { } 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); data = op_exponent(lval, rval);
break; break;
default:
case RPN_NEG: // `makeBinaryOp` should never be called with a non-binary operator!
case RPN_NOT: // LCOV_EXCL_START
case RPN_LOGNOT: unreachable_();
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);
} }
// LCOV_EXCL_STOP
} else if (op == RPN_SUB && src1.isDiffConstant(src2.symbolOf())) { } else if (op == RPN_SUB && src1.isDiffConstant(src2.symbolOf())) {
data = src1.symbolOf()->getValue() - src2.symbolOf()->getValue(); data = src1.symbolOf()->getValue() - src2.symbolOf()->getValue();
} else if ((op == RPN_LOGAND || op == RPN_AND) && tryConstZero(src1, src2)) { } 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) // Checks that an RPN expression's value fits within N bits (signed or unsigned)
void Expression::checkNBit(uint8_t n) const { void Expression::checkNBit(uint8_t n) const {
if (isKnown()) { 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 assume(n < CHAR_BIT * sizeof(int)); // Otherwise `1 << n` is UB
if (v < -(1 << n) || v >= 1 << n) { 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; return false;
} }
if (v < -(1 << (n - 1))) { 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; return false;
} }

View File

@@ -768,7 +768,7 @@ void sect_Skip(uint32_t skip, bool ds) {
} }
// Output a byte that can be relocatable or constant // 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()) { if (!requireCodeSection()) {
return; return;
} }
@@ -782,15 +782,13 @@ void sect_RelByte(Expression &expr, uint32_t pcShift) {
} }
// Output several bytes that can be relocatable or constant // 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()) { if (!requireCodeSection()) {
return; return;
} }
for (uint32_t i = 0; i < n; i++) { for (uint32_t i = 0; i < n; i++) {
Expression &expr = exprs[i % exprs.size()]; if (Expression const &expr = exprs[i % exprs.size()]; !expr.isKnown()) {
if (!expr.isKnown()) {
createPatch(PATCHTYPE_BYTE, expr, i); createPatch(PATCHTYPE_BYTE, expr, i);
writeByte(0); writeByte(0);
} else { } else {
@@ -800,7 +798,7 @@ void sect_RelBytes(uint32_t n, std::vector<Expression> &exprs) {
} }
// Output a word that can be relocatable or constant // 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()) { if (!requireCodeSection()) {
return; return;
} }
@@ -814,7 +812,7 @@ void sect_RelWord(Expression &expr, uint32_t pcShift) {
} }
// Output a long that can be relocatable or constant // 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()) { if (!requireCodeSection()) {
return; return;
} }
@@ -828,7 +826,7 @@ void sect_RelLong(Expression &expr, uint32_t pcShift) {
} }
// Output a PC-relative byte that can be relocatable or constant // 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()) { if (!requireCodeSection()) {
return; return;
} }
@@ -877,9 +875,11 @@ void sect_BinaryFile(std::string const &name, int32_t startPos) {
} }
if (!file) { if (!file) {
if (generatedMissingIncludes) { if (generatedMissingIncludes) {
// LCOV_EXCL_START
if (verbose) { if (verbose) {
printf("Aborting (-MG) on INCBIN file '%s' (%s)\n", name.c_str(), strerror(errno)); printf("Aborting (-MG) on INCBIN file '%s' (%s)\n", name.c_str(), strerror(errno));
} }
// LCOV_EXCL_STOP
failedOnMissingInclude = true; failedOnMissingInclude = true;
} else { } else {
error("Error opening INCBIN file '%s': %s\n", name.c_str(), strerror(errno)); 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 (!file) {
if (generatedMissingIncludes) { if (generatedMissingIncludes) {
// LCOV_EXCL_START
if (verbose) { if (verbose) {
printf("Aborting (-MG) on INCBIN file '%s' (%s)\n", name.c_str(), strerror(errno)); printf("Aborting (-MG) on INCBIN file '%s' (%s)\n", name.c_str(), strerror(errno));
} }
// LCOV_EXCL_STOP
failedOnMissingInclude = true; failedOnMissingInclude = true;
} else { } else {
error("Error opening INCBIN file '%s': %s\n", name.c_str(), strerror(errno)); error("Error opening INCBIN file '%s': %s\n", name.c_str(), strerror(errno));

View File

@@ -325,19 +325,6 @@ uint32_t Symbol::getConstantValue() const {
return 0; 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() { std::pair<Symbol const *, Symbol const *> sym_GetCurrentLabelScopes() {
return {globalScope, localScope}; return {globalScope, localScope};
} }
@@ -528,8 +515,10 @@ static uint32_t anonLabelID = 0;
Symbol *sym_AddAnonLabel() { Symbol *sym_AddAnonLabel() {
if (anonLabelID == UINT32_MAX) { if (anonLabelID == UINT32_MAX) {
// LCOV_EXCL_START
error("Only %" PRIu32 " anonymous labels can be created!", anonLabelID); error("Only %" PRIu32 " anonymous labels can be created!", anonLabelID);
return nullptr; return nullptr;
// LCOV_EXCL_STOP
} }
std::string anon = sym_MakeAnonLabelName(0, true); // The direction is important! 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 { } else {
ofs--; // We're referencing symbols that haven't been created yet... ofs--; // We're referencing symbols that haven't been created yet...
if (ofs > UINT32_MAX - anonLabelID) { if (ofs > UINT32_MAX - anonLabelID) {
// LCOV_EXCL_START
error( error(
"Reference to anonymous label %" PRIu32 " after, when only %" PRIu32 "Reference to anonymous label %" PRIu32 " after, when only %" PRIu32
" may still be created\n", " may still be created\n",
@@ -562,19 +552,21 @@ std::string sym_MakeAnonLabelName(uint32_t ofs, bool neg) {
UINT32_MAX - anonLabelID UINT32_MAX - anonLabelID
); );
} else { } else {
// LCOV_EXCL_STOP
id = anonLabelID + ofs; id = anonLabelID + ofs;
} }
} }
std::string anon("!"); return "!"s + std::to_string(id);
anon += std::to_string(id);
return anon;
} }
void sym_Export(std::string const &symName) { void sym_Export(std::string const &symName) {
if (symName.starts_with('!')) { 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"); error("Anonymous labels cannot be exported\n");
return; return;
// LCOV_EXCL_STOP
} }
Symbol *sym = sym_FindScopedSymbol(symName); 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; sym_AddEqu("__RGBDS_RC__"s, PACKAGE_VERSION_RC)->isBuiltin = true;
#endif #endif
// LCOV_EXCL_START
if (now == static_cast<time_t>(-1)) { if (now == static_cast<time_t>(-1)) {
warn("Failed to determine current time"); warn("Failed to determine current time");
// Fall back by pretending we are at the Epoch // Fall back by pretending we are at the Epoch
now = 0; now = 0;
} }
// LCOV_EXCL_STOP
tm const *time_local = localtime(&now); tm const *time_local = localtime(&now);

View File

@@ -15,14 +15,17 @@ fi
BISON_FLAGS="-Wall -Dparse.lac=full -Dlr.type=ielr" BISON_FLAGS="-Wall -Dparse.lac=full -Dlr.type=ielr"
# Set some optimization flags on versions that support them # 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" BISON_FLAGS="$BISON_FLAGS -Dapi.token.raw=true"
fi 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" BISON_FLAGS="$BISON_FLAGS -Dparse.error=detailed"
else else
BISON_FLAGS="$BISON_FLAGS -Dparse.error=verbose" BISON_FLAGS="$BISON_FLAGS -Dparse.error=verbose"
fi 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 # Replace the arguments to this script ($@) with the ones in $BISON_FLAGS
eval "set -- $BISON_FLAGS" eval "set -- $BISON_FLAGS"

View File

@@ -23,7 +23,7 @@ static_assert(UNSPECIFIED > 0xFF, "UNSPECIFIED should not be in byte range!");
static constexpr off_t BANK_SIZE = 0x4000; static constexpr off_t BANK_SIZE = 0x4000;
// Short options // 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 // Equivalent long options
// Please keep in the same order as short opts. // Please keep in the same order as short opts.
@@ -45,6 +45,7 @@ static option const longopts[] = {
{"mbc-type", required_argument, nullptr, 'm'}, {"mbc-type", required_argument, nullptr, 'm'},
{"rom-version", required_argument, nullptr, 'n'}, {"rom-version", required_argument, nullptr, 'n'},
{"overwrite", no_argument, nullptr, 'O'}, {"overwrite", no_argument, nullptr, 'O'},
{"output", required_argument, nullptr, 'o'},
{"pad-value", required_argument, nullptr, 'p'}, {"pad-value", required_argument, nullptr, 'p'},
{"ram-size", required_argument, nullptr, 'r'}, {"ram-size", required_argument, nullptr, 'r'},
{"sgb-compatible", no_argument, nullptr, 's'}, {"sgb-compatible", no_argument, nullptr, 's'},
@@ -54,6 +55,7 @@ static option const longopts[] = {
{nullptr, no_argument, nullptr, 0 } {nullptr, no_argument, nullptr, 0 }
}; };
// LCOV_EXCL_START
static void printUsage() { static void printUsage() {
fputs( fputs(
"Usage: rgbfix [-hjOsVv] [-C | -c] [-f <fix_spec>] [-i <game_id>] [-k <licensee>]\n" "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" " to the man page for a list of values\n"
" -p, --pad-value <value> pad to the next valid size using this value\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" " -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, --version print RGBFIX version and exit\n"
" -v, --validate fix the header logo and both checksums (-f lhg)\n" " -v, --validate fix the header logo and both checksums (-f lhg)\n"
"\n" "\n"
@@ -72,6 +75,7 @@ static void printUsage() {
stderr stderr
); );
} }
// LCOV_EXCL_STOP
static uint8_t nbErrors; static uint8_t nbErrors;
@@ -696,10 +700,12 @@ static char const *mbcName(MbcType type) {
case MBC_WRONG_FEATURES: case MBC_WRONG_FEATURES:
case MBC_BAD_RANGE: case MBC_BAD_RANGE:
case MBC_BAD_TPP1: case MBC_BAD_TPP1:
// LCOV_EXCL_START
unreachable_(); unreachable_();
} }
unreachable_(); unreachable_();
// LCOV_EXCL_STOP
} }
static bool hasRAM(MbcType type) { static bool hasRAM(MbcType type) {
@@ -713,8 +719,7 @@ static bool hasRAM(MbcType type) {
case MBC3_TIMER_BATTERY: case MBC3_TIMER_BATTERY:
case MBC5: case MBC5:
case MBC5_RUMBLE: case MBC5_RUMBLE:
case MBC6: // TODO: not sure case BANDAI_TAMA5: // "Game de Hakken!! Tamagotchi - Osutchi to Mesutchi" has RAM size 0
case BANDAI_TAMA5: // TODO: not sure
case MBC_NONE: case MBC_NONE:
case MBC_BAD: case MBC_BAD:
case MBC_WRONG_FEATURES: case MBC_WRONG_FEATURES:
@@ -735,6 +740,7 @@ static bool hasRAM(MbcType type) {
case MBC5_RAM_BATTERY: case MBC5_RAM_BATTERY:
case MBC5_RUMBLE_RAM: case MBC5_RUMBLE_RAM:
case MBC5_RUMBLE_RAM_BATTERY: 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 MBC7_SENSOR_RUMBLE_RAM_BATTERY:
case POCKET_CAMERA: case POCKET_CAMERA:
case HUC3: case HUC3:
@@ -761,7 +767,7 @@ static bool hasRAM(MbcType type) {
break; break;
} }
unreachable_(); unreachable_(); // LCOV_EXCL_LINE
} }
static uint8_t const nintendoLogo[] = { static uint8_t const nintendoLogo[] = {
@@ -811,8 +817,9 @@ static ssize_t readBytes(int fd, uint8_t *buf, size_t len) {
while (len) { while (len) {
ssize_t ret = read(fd, buf, len); ssize_t ret = read(fd, buf, len);
if (ret == -1 && errno != EINTR) { // Return errors, unless we only were interrupted // Return errors, unless we only were interrupted
return -1; if (ret == -1 && errno != EINTR) {
return -1; // LCOV_EXCL_LINE
} }
// EOF reached // EOF reached
if (ret == 0) { if (ret == 0) {
@@ -838,8 +845,9 @@ static ssize_t writeBytes(int fd, uint8_t *buf, size_t len) {
while (len) { while (len) {
ssize_t ret = write(fd, buf, len); ssize_t ret = write(fd, buf, len);
if (ret == -1 && errno != EINTR) { // Return errors, unless we only were interrupted // Return errors, unless we only were interrupted
return -1; if (ret == -1 && errno != EINTR) {
return -1; // LCOV_EXCL_LINE
} }
// If anything was written, accumulate it, and continue // If anything was written, accumulate it, and continue
if (ret != -1) { if (ret != -1) {
@@ -879,9 +887,9 @@ static void overwriteBytes(
memcpy(&rom0[startAddr], fixed, size); memcpy(&rom0[startAddr], fixed, size);
} }
static void processFile(int input, int output, char const *name, off_t fileSize) { static void
// Both of these should be true for seekable files, and neither otherwise processFile(int input, int output, char const *name, off_t fileSize, bool expectFileSize) {
if (input == output) { if (expectFileSize) {
assume(fileSize != 0); assume(fileSize != 0);
} else { } else {
assume(fileSize == 0); 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; ssize_t headerSize = (cartridgeType & 0xFF00) == TPP1 ? 0x154 : 0x150;
if (rom0Len == -1) { if (rom0Len == -1) {
// LCOV_EXCL_START
report("FATAL: Failed to read \"%s\"'s header: %s\n", name, strerror(errno)); report("FATAL: Failed to read \"%s\"'s header: %s\n", name, strerror(errno));
return; return;
// LCOV_EXCL_STOP
} else if (rom0Len < headerSize) { } else if (rom0Len < headerSize) {
report( report(
"FATAL: \"%s\" too short, expected at least %jd ($%jx) bytes, got only %jd\n", "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 // write the header
if (input == output) { if (input == output) {
if (lseek(output, 0, SEEK_SET) == static_cast<off_t>(-1)) { 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)); report("FATAL: Failed to rewind \"%s\": %s\n", name, strerror(errno));
return; return;
// LCOV_EXCL_STOP
} }
// If modifying the file in-place, we only need to edit the header // 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 // 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); writeLen = writeBytes(output, rom0, rom0Len);
if (writeLen == -1) { if (writeLen == -1) {
// LCOV_EXCL_START
report("FATAL: Failed to write \"%s\"'s ROM0: %s\n", name, strerror(errno)); report("FATAL: Failed to write \"%s\"'s ROM0: %s\n", name, strerror(errno));
return; return;
// LCOV_EXCL_STOP
} else if (writeLen < rom0Len) { } else if (writeLen < rom0Len) {
// LCOV_EXCL_START
report( report(
"FATAL: Could only write %jd of \"%s\"'s %jd ROM0 bytes\n", "FATAL: Could only write %jd of \"%s\"'s %jd ROM0 bytes\n",
static_cast<intmax_t>(writeLen), 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) static_cast<intmax_t>(rom0Len)
); );
return; return;
// LCOV_EXCL_STOP
} }
// Output ROMX if it was buffered // 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` // so it's fine to cast to `size_t`
writeLen = writeBytes(output, romx.data(), totalRomxLen); writeLen = writeBytes(output, romx.data(), totalRomxLen);
if (writeLen == -1) { if (writeLen == -1) {
// LCOV_EXCL_START
report("FATAL: Failed to write \"%s\"'s ROMX: %s\n", name, strerror(errno)); report("FATAL: Failed to write \"%s\"'s ROMX: %s\n", name, strerror(errno));
return; return;
// LCOV_EXCL_STOP
} else if (static_cast<size_t>(writeLen) < totalRomxLen) { } else if (static_cast<size_t>(writeLen) < totalRomxLen) {
// LCOV_EXCL_START
report( report(
"FATAL: Could only write %jd of \"%s\"'s %zu ROMX bytes\n", "FATAL: Could only write %jd of \"%s\"'s %zu ROMX bytes\n",
static_cast<intmax_t>(writeLen), static_cast<intmax_t>(writeLen),
@@ -1172,6 +1191,7 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
totalRomxLen totalRomxLen
); );
return; 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 (padValue != UNSPECIFIED) {
if (input == output) { if (input == output) {
if (lseek(output, 0, SEEK_END) == static_cast<off_t>(-1)) { 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)); report("FATAL: Failed to seek to end of \"%s\": %s\n", name, strerror(errno));
return; return;
// LCOV_EXCL_STOP
} }
} }
memset(bank, padValue, sizeof(bank)); 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`, // The return value is either -1, or at most `thisLen`,
// so it's fine to cast to `size_t` // so it's fine to cast to `size_t`
if (static_cast<size_t>(ret) != thisLen) { if (static_cast<size_t>(ret) != thisLen) {
// LCOV_EXCL_START
report("FATAL: Failed to write \"%s\"'s padding: %s\n", name, strerror(errno)); report("FATAL: Failed to write \"%s\"'s padding: %s\n", name, strerror(errno));
break; break;
// LCOV_EXCL_STOP
} }
len -= thisLen; len -= thisLen;
} }
} }
} }
static bool processFilename(char const *name) { static bool processFilename(char const *name, char const *outputName) {
nbErrors = 0; 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>"; name = "<stdin>";
(void)setmode(STDIN_FILENO, O_BINARY); (void)setmode(STDIN_FILENO, O_BINARY);
(void)setmode(STDOUT_FILENO, O_BINARY); processFile(STDIN_FILENO, output, name, 0, false);
processFile(STDIN_FILENO, STDOUT_FILENO, name, 0);
} else { } else {
// POSIX specifies that the results of O_RDWR on a FIFO are undefined. // 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 // 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. // `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 // Thus, we're going to hope that either the `open` fails, or it succeeds but IO
// operations may fail, all of which we handle. // 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)); report("FATAL: Failed to open \"%s\" for reading+writing: %s\n", name, strerror(errno));
} else { } else {
Defer closeInput{[&] { close(input); }}; Defer closeInput{[&] { close(input); }};
struct stat stat; struct stat stat;
if (fstat(input, &stat) == -1) { if (fstat(input, &stat) == -1) {
// LCOV_EXCL_START
report("FATAL: Failed to stat \"%s\": %s\n", name, strerror(errno)); 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( report(
"FATAL: \"%s\" is not a regular file, and thus cannot be modified in-place\n", "FATAL: \"%s\" is not a regular file, and thus cannot be modified in-place\n",
name name
); );
// LCOV_EXCL_STOP
} else if (stat.st_size < 0x150) { } else if (stat.st_size < 0x150) {
// This check is in theory redundant with the one in `processFile`, but it // This check is in theory redundant with the one in `processFile`, but it
// prevents passing a file size of 0, which usually indicates pipes // 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) static_cast<intmax_t>(stat.st_size)
); );
} else { } 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[]) { int main(int argc, char *argv[]) {
nbErrors = 0; nbErrors = 0;
char const *outputFilename = nullptr;
for (int ch; (ch = musl_getopt_long_only(argc, argv, optstring, longopts, nullptr)) != -1;) { for (int ch; (ch = musl_getopt_long_only(argc, argv, optstring, longopts, nullptr)) != -1;) {
switch (ch) { switch (ch) {
size_t len; size_t len;
@@ -1322,8 +1381,10 @@ int main(int argc, char *argv[]) {
break; break;
case 'h': case 'h':
// LCOV_EXCL_START
printUsage(); printUsage();
exit(0); exit(0);
// LCOV_EXCL_STOP
case 'i': case 'i':
gameID = musl_optarg; gameID = musl_optarg;
@@ -1393,6 +1454,10 @@ int main(int argc, char *argv[]) {
overwriteRom = true; overwriteRom = true;
break; break;
case 'o':
outputFilename = musl_optarg;
break;
case 'p': case 'p':
parseByte(padValue, 'p'); parseByte(padValue, 'p');
break; break;
@@ -1419,16 +1484,20 @@ int main(int argc, char *argv[]) {
} }
case 'V': case 'V':
// LCOV_EXCL_START
printf("rgbfix %s\n", get_package_version_string()); printf("rgbfix %s\n", get_package_version_string());
exit(0); exit(0);
// LCOV_EXCL_STOP
case 'v': case 'v':
fixSpec = FIX_LOGO | FIX_HEADER_SUM | FIX_GLOBAL_SUM; fixSpec = FIX_LOGO | FIX_HEADER_SUM | FIX_GLOBAL_SUM;
break; break;
default: default:
// LCOV_EXCL_START
printUsage(); printUsage();
exit(1); 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", "warning: RAM size 1 (2 KiB) was specified for MBC \"%s\"\n",
mbcName(cartridgeType) mbcName(cartridgeType)
); );
} // TODO: check possible values? }
} else if (ramSize) { } else if (ramSize) {
fprintf( fprintf(
stderr, stderr,
@@ -1543,8 +1612,14 @@ int main(int argc, char *argv[]) {
exit(1); 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 { do {
failed |= processFilename(*argv); failed |= processFilename(*argv, outputFilename);
} while (*++argv); } while (*++argv);
return failed; return failed;

View File

@@ -12,7 +12,6 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <string_view> #include <string_view>
#include <type_traits>
#include <vector> #include <vector>
#include "extern/getopt.hpp" #include "extern/getopt.hpp"
@@ -103,6 +102,7 @@ void fatal(char const *fmt, ...) {
} }
void Options::verbosePrint(uint8_t level, char const *fmt, ...) const { void Options::verbosePrint(uint8_t level, char const *fmt, ...) const {
// LCOV_EXCL_START
if (verbosity >= level) { if (verbosity >= level) {
va_list ap; va_list ap;
@@ -110,10 +110,11 @@ void Options::verbosePrint(uint8_t level, char const *fmt, ...) const {
vfprintf(stderr, fmt, ap); vfprintf(stderr, fmt, ap);
va_end(ap); va_end(ap);
} }
// LCOV_EXCL_STOP
} }
// Short options // 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 // Equivalent long options
// Please keep in the same order as short opts. // 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[] = { static option const longopts[] = {
{"auto-attr-map", no_argument, nullptr, 'A'}, {"auto-attr-map", no_argument, nullptr, 'A'},
{"attr-map", required_argument, nullptr, 'a'}, {"attr-map", required_argument, nullptr, 'a'},
{"background-color", required_argument, nullptr, 'B'},
{"base-tiles", required_argument, nullptr, 'b'}, {"base-tiles", required_argument, nullptr, 'b'},
{"color-curve", no_argument, nullptr, 'C'}, {"color-curve", no_argument, nullptr, 'C'},
{"colors", required_argument, nullptr, 'c'}, {"colors", required_argument, nullptr, 'c'},
@@ -156,6 +158,7 @@ static option const longopts[] = {
{nullptr, no_argument, nullptr, 0 } {nullptr, no_argument, nullptr, 0 }
}; };
// LCOV_EXCL_START
static void printUsage() { static void printUsage() {
fputs( fputs(
"Usage: rgbgfx [-r stride] [-ChmOuVXYZ] [-v [-v ...]] [-a <attr_map> | -A]\n" "Usage: rgbgfx [-r stride] [-ChmOuVXYZ] [-v [-v ...]] [-a <attr_map> | -A]\n"
@@ -174,6 +177,7 @@ static void printUsage() {
stderr stderr
); );
} }
// LCOV_EXCL_STOP
// Parses a number at the beginning of a string, moving the pointer to skip the parsed characters. // Parses a number at the beginning of a string, moving the pointer to skip the parsed characters.
// Returns the provided errVal on error. // 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! // We only filter out `EOF`, but calling `isblank()` on anything else is UB!
static_assert( static_assert(
std::remove_reference_t<decltype(*file)>::traits_type::eof() == EOF, std::streambuf::traits_type::eof() == EOF,
"isblank(char_traits<...>::eof()) is UB!" "isblank(std::streambuf::traits_type::eof()) is UB!"
); );
std::vector<size_t> argvOfs; 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;) { for (int ch; (ch = musl_getopt_long_only(argc, argv, optstring, longopts, nullptr)) != -1;) {
char *arg = musl_optarg; // Make a copy for scanning char *arg = musl_optarg; // Make a copy for scanning
uint16_t number; uint16_t number;
size_t size;
switch (ch) { switch (ch) {
case 'A': case 'A':
localOptions.autoAttrmap = true; localOptions.autoAttrmap = true;
@@ -361,6 +366,43 @@ static char *parseArgv(int argc, char *argv[]) {
} }
options.attrmap = musl_optarg; options.attrmap = musl_optarg;
break; 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': case 'b':
number = parseNumber(arg, "Bank 0 base tile ID", 0); number = parseNumber(arg, "Bank 0 base tile ID", 0);
if (number >= 256) { if (number >= 256) {
@@ -409,9 +451,9 @@ static char *parseArgv(int argc, char *argv[]) {
} else { } else {
options.palSpecType = Options::EXPLICIT; options.palSpecType = Options::EXPLICIT;
// Can't parse the file yet, as "flat" color collections need to know the palette // Can't parse the file yet, as "flat" color collections need to know the palette
// size to be split; thus, we defer that // size to be split; thus, we defer that.
// TODO: this does not validate the `fmt` part of any external spec but the last // If a following `-c` overrides a previous one, the `fmt` part of an overridden
// one, but I guess that's okay // external palette spec will not be validated, but I guess that's okay.
localOptions.externalPalSpec = musl_optarg; localOptions.externalPalSpec = musl_optarg;
} }
break; break;
@@ -425,8 +467,10 @@ static char *parseArgv(int argc, char *argv[]) {
} }
break; break;
case 'h': case 'h':
// LCOV_EXCL_START
printUsage(); printUsage();
exit(0); exit(0);
// LCOV_EXCL_STOP
case 'i': case 'i':
if (!options.inputTileset.empty()) { if (!options.inputTileset.empty()) {
warning("Overriding input tileset file %s", options.inputTileset.c_str()); 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; options.tilemap = musl_optarg;
break; break;
case 'V': case 'V':
// LCOV_EXCL_START
printf("rgbgfx %s\n", get_package_version_string()); printf("rgbgfx %s\n", get_package_version_string());
exit(0); exit(0);
// LCOV_EXCL_STOP
case 'v': case 'v':
// LCOV_EXCL_START
if (options.verbosity < Options::VERB_VVVVVV) { if (options.verbosity < Options::VERB_VVVVVV) {
++options.verbosity; ++options.verbosity;
} }
break; break;
// LCOV_EXCL_STOP
case 'x': case 'x':
options.trim = parseNumber(arg, "Number of tiles to trim", 0); options.trim = parseNumber(arg, "Number of tiles to trim", 0);
if (*arg != '\0') { if (*arg != '\0') {
@@ -615,8 +663,10 @@ static char *parseArgv(int argc, char *argv[]) {
} }
break; break;
default: default:
// LCOV_EXCL_START
printUsage(); printUsage();
exit(1); exit(1);
// LCOV_EXCL_STOP
} }
} }
@@ -744,6 +794,7 @@ int main(int argc, char *argv[]) {
parseExternalPalSpec(localOptions.externalPalSpec); parseExternalPalSpec(localOptions.externalPalSpec);
} }
// LCOV_EXCL_START
if (options.verbosity >= Options::VERB_CFG) { if (options.verbosity >= Options::VERB_CFG) {
fprintf(stderr, "rgbgfx %s\n", get_package_version_string()); fprintf(stderr, "rgbgfx %s\n", get_package_version_string());
@@ -825,7 +876,7 @@ int main(int argc, char *argv[]) {
} }
fprintf( fprintf(
stderr, stderr,
"\tInput image slice: %" PRIu32 "x%" PRIu32 " pixels starting at (%" PRIi32 ", %" PRIi32 "\tInput image slice: %" PRIu32 "x%" PRIu32 " pixels starting at (%" PRId32 ", %" PRId32
")\n", ")\n",
options.inputSlice.width, options.inputSlice.width,
options.inputSlice.height, options.inputSlice.height,
@@ -856,6 +907,7 @@ int main(int argc, char *argv[]) {
printPath("Output palettes", options.palettes); printPath("Output palettes", options.palettes);
fputs("Ready.\n", stderr); fputs("Ready.\n", stderr);
} }
// LCOV_EXCL_STOP
// Do not do anything if option parsing went wrong. // Do not do anything if option parsing went wrong.
requireZeroErrors(); requireZeroErrors();

View File

@@ -86,8 +86,8 @@ private:
public: public:
Iter() = default; Iter() = default;
bool operator==(Iter const &other) const { return _iter == other._iter; } bool operator==(Iter const &rhs) const { return _iter == rhs._iter; }
bool operator!=(Iter const &other) const { return !(*this == other); }
Iter &operator++() { Iter &operator++() {
++_iter; ++_iter;
skipEmpty(); skipEmpty();
@@ -98,6 +98,7 @@ private:
++(*this); ++(*this);
return it; return it;
} }
reference operator*() const { reference operator*() const {
assume((*_iter).has_value()); assume((*_iter).has_value());
return **_iter; return **_iter;
@@ -167,16 +168,6 @@ private:
std::unordered_set<uint16_t> &uniqueColors() const { std::unordered_set<uint16_t> &uniqueColors() const {
// We check for *distinct* colors by stuffing them into a `set`; this should be // We check for *distinct* colors by stuffing them into a `set`; this should be
// faster than "back-checking" on every element (O(n²)) // 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; static std::unordered_set<uint16_t> colors;
colors.clear(); colors.clear();
@@ -476,7 +467,6 @@ std::tuple<DefaultInitVec<size_t>, size_t>
); );
// All efficiencies are identical iff min equals max // 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 &minProtoPal = protoPalettes[minEfficiencyIter->protoPalIndex];
ProtoPalette const &maxProtoPal = protoPalettes[maxEfficiencyIter->protoPalIndex]; ProtoPalette const &maxProtoPal = protoPalettes[maxEfficiencyIter->protoPalIndex];
size_t minSize = minProtoPal.size(); 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) { if (options.verbosity >= Options::VERB_INTERM) {
for (auto &&assignment : assignments) { for (auto &&assignment : assignments) {
fprintf(stderr, "{ "); fprintf(stderr, "{ ");
@@ -570,11 +561,13 @@ std::tuple<DefaultInitVec<size_t>, size_t>
fprintf(stderr, "} (volume = %zu)\n", assignment.volume()); fprintf(stderr, "} (volume = %zu)\n", assignment.volume());
} }
} }
// LCOV_EXCL_STOP
// "Decant" the result // "Decant" the result
decant(assignments, protoPalettes); decant(assignments, protoPalettes);
// Note that the result does not contain any empty palettes // Note that the result does not contain any empty palettes
// LCOV_EXCL_START
if (options.verbosity >= Options::VERB_INTERM) { if (options.verbosity >= Options::VERB_INTERM) {
for (auto &&assignment : assignments) { for (auto &&assignment : assignments) {
fprintf(stderr, "{ "); fprintf(stderr, "{ ");
@@ -587,6 +580,7 @@ std::tuple<DefaultInitVec<size_t>, size_t>
fprintf(stderr, "} (volume = %zu)\n", assignment.volume()); fprintf(stderr, "} (volume = %zu)\n", assignment.volume());
} }
} }
// LCOV_EXCL_STOP
DefaultInitVec<size_t> mappings(protoPalettes.size()); DefaultInitVec<size_t> mappings(protoPalettes.size());
for (size_t i = 0; i < assignments.size(); ++i) { for (size_t i = 0; i < assignments.size(); ++i) {

View File

@@ -40,7 +40,7 @@ void sortIndexed(
return true; return true;
} }
} }
unreachable_(); // This should not be possible unreachable_(); // LCOV_EXCL_LINE
}); });
} }
} }

View File

@@ -23,29 +23,8 @@
using namespace std::string_view_literals; 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 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()); 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. // Palettes are separated by colons.
std::string_view arg(rawArg); 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 (void)arg; // With NDEBUG, `arg` is otherwise not used
assume(ofs <= arg.length()); assume(ofs <= arg.length());
assume(len <= arg.length()); assume(len <= arg.length());
@@ -80,17 +58,16 @@ void parseInlinePalSpec(char const * const rawArg) {
options.palSpec.clear(); options.palSpec.clear();
options.palSpec.emplace_back(); // Value-initialized, not default-init'd, so we get zeros options.palSpec.emplace_back(); // Value-initialized, not default-init'd, so we get zeros
size_type n = 0; // Index into the argument size_t n = 0; // Index into the argument
// TODO: store max `nbColors` ever reached, and compare against palette size later
size_t nbColors = 0; // Number of colors in the current palette size_t nbColors = 0; // Number of colors in the current palette
for (;;) { for (;;) {
++n; // Ignore the '#' (checked either by caller or previous loop iteration) ++n; // Ignore the '#' (checked either by caller or previous loop iteration)
std::optional<Rgba> &color = options.palSpec.back()[nbColors]; std::optional<Rgba> &color = options.palSpec.back()[nbColors];
// Check for #none first. // Check for "#none" first.
if (arg.compare(n, 4, "none"sv) == 0 || arg.compare(n, 4, "NONE"sv) == 0) { if (strncasecmp(&rawArg[n], "none", literal_strlen("none")) == 0) {
color = {}; color = {};
n += 4; n += literal_strlen("none");
} else { } else {
auto pos = std::min(arg.find_first_not_of("0123456789ABCDEFabcdef"sv, n), arg.length()); auto pos = std::min(arg.find_first_not_of("0123456789ABCDEFabcdef"sv, n), arg.length());
switch (pos - n) { switch (pos - n) {
@@ -194,7 +171,6 @@ static T readLE(U const *bytes) {
[[gnu::warn_unused_result]] [[gnu::warn_unused_result]]
static bool readLine(std::filebuf &file, std::string &buffer) { static bool readLine(std::filebuf &file, std::string &buffer) {
assume(buffer.empty()); assume(buffer.empty());
// TODO: maybe this can be optimized to bulk reads?
for (;;) { for (;;) {
auto c = file.sbumpc(); auto c = file.sbumpc();
if (c == std::filebuf::traits_type::eof()) { 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 // Parses the initial part of a string_view, advancing the "read index" as it does
template<typename U> // Should be uint*_t 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; uintmax_t value = 0;
auto result = std::from_chars(str.data() + n, str.data() + str.size(), value); auto result = std::from_chars(str.data() + n, str.data() + str.size(), value);
if (static_cast<bool>(result.ec)) { 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}; return std::optional<U>{value};
} }
static std::optional<Rgba> static std::optional<Rgba> parseColor(std::string const &str, size_t &n, uint16_t i) {
parseColor(std::string const &str, std::string::size_type &n, uint16_t i) {
std::optional<uint8_t> r = parseDec<uint8_t>(str, n); std::optional<uint8_t> r = parseDec<uint8_t>(str, n);
if (!r) { if (!r) {
error("Failed to parse color #%d (\"%s\"): invalid red component", i + 1, str.c_str()); 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(); line.clear();
requireLine("PSP", file, line); requireLine("PSP", file, line);
std::string::size_type n = 0; size_t n = 0;
std::optional<uint16_t> nbColors = parseDec<uint16_t>(line, n); std::optional<uint16_t> nbColors = parseDec<uint16_t>(line, n);
if (!nbColors || n != line.length()) { if (!nbColors || n != line.length()) {
error("Invalid \"number of colors\" line in PSP file (%s)", line.c_str()); error("Invalid \"number of colors\" line in PSP file (%s)", line.c_str());
@@ -347,7 +322,7 @@ static void parseGPLFile(std::filebuf &file) {
continue; continue;
} }
std::string::size_type n = 0; size_t n = 0;
skipWhitespace(line, n); skipWhitespace(line, n);
// Skip empty lines, or lines that contain just a comment. // Skip empty lines, or lines that contain just a comment.
if (line.length() == n || line[n] == '#') { if (line.length() == n || line[n] == '#') {
@@ -438,7 +413,6 @@ static void parseACTFile(std::filebuf &file) {
uint16_t nbColors = 256; uint16_t nbColors = 256;
if (len == 772) { if (len == 772) {
nbColors = readBE<uint16_t>(&buf[768]); nbColors = readBE<uint16_t>(&buf[768]);
// TODO: apparently there is a "transparent color index"? What?
if (nbColors > 256 || nbColors == 0) { if (nbColors > 256 || nbColors == 0) {
error("Invalid number of colors in ACT file (%" PRIu16 ")", nbColors); error("Invalid number of colors in ACT file (%" PRIu16 ")", nbColors);
return; return;
@@ -549,9 +523,6 @@ static void parseACOFile(std::filebuf &file) {
return; 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) { static void parseGBCFile(std::filebuf &file) {
@@ -603,10 +574,9 @@ void parseExternalPalSpec(char const *arg) {
std::tuple{"GBC", &parseGBCFile, std::ios::binary}, std::tuple{"GBC", &parseGBCFile, std::ios::binary},
}; };
auto iter = auto iter = std::find_if(RANGE(parsers), [&arg, &ptr](auto const &parser) {
std::find_if(RANGE(parsers), [&arg, &ptr](decltype(parsers)::value_type const &parser) { return strncasecmp(arg, std::get<0>(parser), ptr - arg) == 0;
return strncasecmp(arg, std::get<0>(parser), ptr - arg) == 0; });
});
if (iter == parsers.end()) { if (iter == parsers.end()) {
error( error(
"Unknown external palette format \"%.*s\"", "Unknown external palette format \"%.*s\"",

View File

@@ -25,6 +25,10 @@
#include "gfx/pal_sorting.hpp" #include "gfx/pal_sorting.hpp"
#include "gfx/proto_palette.hpp" #include "gfx/proto_palette.hpp"
static bool isBgColorTransparent() {
return options.bgColor.has_value() && options.bgColor->isTransparent();
}
class ImagePalette { class ImagePalette {
std::array<std::optional<Rgba>, NB_COLOR_SLOTS> _colors; 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. // color), then the other color is returned. Otherwise, `nullptr` is returned.
[[nodiscard]] [[nodiscard]]
Rgba const *registerColor(Rgba const &rgba) { 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; options.hasTransparentPixels = true;
} }
@@ -52,7 +56,7 @@ public:
} }
size_t size() const { 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(); return slot.has_value() && !slot->isTransparent();
}); });
} }
@@ -193,23 +197,20 @@ public:
PNG_LIBPNG_VER_STRING, static_cast<png_voidp>(this), handleError, handleWarning PNG_LIBPNG_VER_STRING, static_cast<png_voidp>(this), handleError, handleWarning
); );
if (!png) { 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); info = png_create_info_struct(png);
if (!info) { if (!info) {
// LCOV_EXCL_START
png_destroy_read_struct(&png, nullptr, nullptr); 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_read_fn(png, this, readData);
png_set_sig_bytes(png, pngHeader.size()); 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 // Process all chunks up to but not including the image data
png_read_info(png, info); png_read_info(png, info);
@@ -288,9 +289,7 @@ public:
options.verbosePrint(Options::VERB_INTERM, "No embedded palette\n"); options.verbosePrint(Options::VERB_INTERM, "No embedded palette\n");
} }
// Set up transformations; to turn everything into RGBA888 // Set up transformations to turn everything into RGBA888 for simplicity of handling
// TODO: it's not necessary to uniformize the pixel data (in theory), and not doing
// so *might* improve performance, and should reduce memory usage.
// Convert grayscale to RGB // Convert grayscale to RGB
switch (colorType & ~PNG_COLOR_MASK_ALPHA) { 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(); }
bool operator!=(Iterator const &rhs) const { return coords() != rhs.coords(); }
}; };
public: public:
@@ -518,10 +516,12 @@ struct AttrmapEntry {
bool yFlip; bool yFlip;
bool xFlip; 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 { 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>> static std::tuple<DefaultInitVec<size_t>, std::vector<Palette>>
generatePalettes(std::vector<ProtoPalette> const &protoPalettes, Png const &png) { generatePalettes(std::vector<ProtoPalette> const &protoPalettes, Png const &png) {
// Run a "pagination" problem solver // Run a "pagination" problem solver
// TODO: allow picking one of several solvers?
auto [mappings, nbPalettes] = overloadAndRemove(protoPalettes); auto [mappings, nbPalettes] = overloadAndRemove(protoPalettes);
assume(mappings.size() == protoPalettes.size()); assume(mappings.size() == protoPalettes.size());
// LCOV_EXCL_START
if (options.verbosity >= Options::VERB_INTERM) { if (options.verbosity >= Options::VERB_INTERM) {
fprintf( fprintf(
stderr, stderr,
@@ -567,6 +567,7 @@ static std::tuple<DefaultInitVec<size_t>, std::vector<Palette>>
fprintf(stderr, "%zu -> %zu\n", i, mappings[i]); fprintf(stderr, "%zu -> %zu\n", i, mappings[i]);
} }
} }
// LCOV_EXCL_STOP
std::vector<Palette> palettes(nbPalettes); std::vector<Palette> palettes(nbPalettes);
// If the image contains at least one transparent pixel, force transparency in the first slot of // 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 // Convert the palette spec to actual palettes
std::vector<Palette> palettes(options.palSpec.size()); std::vector<Palette> palettes(options.palSpec.size());
for (auto [spec, pal] : zip(options.palSpec, palettes)) { 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 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(); 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) { static void outputPalettes(std::vector<Palette> const &palettes) {
// LCOV_EXCL_START
if (options.verbosity >= Options::VERB_INTERM) { if (options.verbosity >= Options::VERB_INTERM) {
for (auto &&palette : palettes) { for (auto &&palette : palettes) {
fputs("{ ", stderr); fputs("{ ", stderr);
@@ -663,6 +665,7 @@ static void outputPalettes(std::vector<Palette> const &palettes) {
fputs("}\n", stderr); fputs("}\n", stderr);
} }
} }
// LCOV_EXCL_STOP
if (palettes.size() > options.nbPalettes) { if (palettes.size() > options.nbPalettes) {
// If the palette generation is wrong, other (dependee) operations are likely to be // 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()) { if (!options.palettes.empty()) {
File output; File output;
if (!output.open(options.palettes, std::ios_base::out | std::ios_base::binary)) { 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)); fatal("Failed to create \"%s\": %s", output.c_str(options.palettes), strerror(errno));
// LCOV_EXCL_STOP
} }
for (Palette const &palette : palettes) { for (Palette const &palette : palettes) {
@@ -830,7 +835,9 @@ static void outputUnoptimizedTileData(
) { ) {
File output; File output;
if (!output.open(options.output, std::ios_base::out | std::ios_base::binary)) { 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)); 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; uint16_t widthTiles = options.inputSlice.width ? options.inputSlice.width : png.getWidth() / 8;
@@ -843,13 +850,16 @@ static void outputUnoptimizedTileData(
remainingTiles -= options.trim; remainingTiles -= options.trim;
for (auto [tile, attr] : zip(png.visitAsTiles(), attrmap)) { for (auto [tile, attr] : zip(png.visitAsTiles(), attrmap)) {
// If the tile is fully transparent, default to palette 0 // Do not emit fully-background tiles.
Palette const &palette = palettes[attr.getPalID(mappings)]; if (!attr.isBackgroundTile()) {
for (uint32_t y = 0; y < 8; ++y) { // If the tile is fully transparent, this defaults to palette 0.
uint16_t bitplanes = TileData::rowBitplanes(tile, palette, y); Palette const &palette = palettes[attr.getPalID(mappings)];
output->sputc(bitplanes & 0xFF); for (uint32_t y = 0; y < 8; ++y) {
if (options.bitDepth == 2) { uint16_t bitplanes = TileData::rowBitplanes(tile, palette, y);
output->sputc(bitplanes >> 8); output->sputc(bitplanes & 0xFF);
if (options.bitDepth == 2) {
output->sputc(bitplanes >> 8);
}
} }
} }
@@ -869,7 +879,9 @@ static void outputUnoptimizedMaps(
if (!path.empty()) { if (!path.empty()) {
file.emplace(); file.emplace();
if (!file->open(path, std::ios_base::out | std::ios_base::binary)) { 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)); fatal("Failed to create \"%s\": %s", file->c_str(options.tilemap), strerror(errno));
// LCOV_EXCL_STOP
} }
} }
}; };
@@ -887,16 +899,21 @@ static void outputUnoptimizedMaps(
} }
if (tilemapOutput.has_value()) { 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()) { if (attrmapOutput.has_value()) {
uint8_t palID = attr.getPalID(mappings) & 7; (*attrmapOutput)->sputc((palID & 7) | bank << 3); // The other flags are all 0
(*attrmapOutput)->sputc(palID | bank << 3); // The other flags are all 0
} }
if (palmapOutput.has_value()) { 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;
} }
++tileID;
} }
} }
@@ -989,22 +1006,30 @@ static UniqueTiles dedupTiles(
bool inputWithoutOutput = !options.inputTileset.empty() && options.output.empty(); bool inputWithoutOutput = !options.inputTileset.empty() && options.output.empty();
for (auto [tile, attr] : zip(png.visitAsTiles(), attrmap)) { 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) { if (inputWithoutOutput && matchType == TileData::NOPE) {
error( error(
"Tile at (%" PRIu32 ", %" PRIu32 "Tile at (%" PRIu32 ", %" PRIu32
") is not within the input tileset, and `-o` was not given!", ") is not within the input tileset, and `-o` was not given!",
tile.x, tile.x,
tile.y tile.y
); );
}
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.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];
} }
// Copy elision should prevent the contained `unordered_set` from being re-constructed // 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) { static void outputTileData(UniqueTiles const &tiles) {
File output; File output;
if (!output.open(options.output, std::ios_base::out | std::ios_base::binary)) { 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)); fatal("Failed to create \"%s\": %s", output.c_str(options.output), strerror(errno));
// LCOV_EXCL_STOP
} }
uint16_t tileID = 0; uint16_t tileID = 0;
@@ -1036,7 +1063,9 @@ static void outputTileData(UniqueTiles const &tiles) {
static void outputTilemap(DefaultInitVec<AttrmapEntry> const &attrmap) { static void outputTilemap(DefaultInitVec<AttrmapEntry> const &attrmap) {
File output; File output;
if (!output.open(options.tilemap, std::ios_base::out | std::ios_base::binary)) { 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)); fatal("Failed to create \"%s\": %s", output.c_str(options.tilemap), strerror(errno));
// LCOV_EXCL_STOP
} }
for (AttrmapEntry const &entry : attrmap) { for (AttrmapEntry const &entry : attrmap) {
@@ -1049,7 +1078,9 @@ static void outputAttrmap(
) { ) {
File output; File output;
if (!output.open(options.attrmap, std::ios_base::out | std::ios_base::binary)) { 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)); fatal("Failed to create \"%s\": %s", output.c_str(options.attrmap), strerror(errno));
// LCOV_EXCL_STOP
} }
for (AttrmapEntry const &entry : attrmap) { for (AttrmapEntry const &entry : attrmap) {
@@ -1065,7 +1096,9 @@ static void outputPalmap(
) { ) {
File output; File output;
if (!output.open(options.palmap, std::ios_base::out | std::ios_base::binary)) { 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)); fatal("Failed to create \"%s\": %s", output.c_str(options.palmap), strerror(errno));
// LCOV_EXCL_STOP
} }
for (AttrmapEntry const &entry : attrmap) { for (AttrmapEntry const &entry : attrmap) {
@@ -1093,6 +1126,7 @@ void process() {
// Now, we have all the image's colors in `colors` // Now, we have all the image's colors in `colors`
// The next step is to order the palette // The next step is to order the palette
// LCOV_EXCL_START
if (options.verbosity >= Options::VERB_INTERM) { if (options.verbosity >= Options::VERB_INTERM) {
fputs("Image colors: [ ", stderr); fputs("Image colors: [ ", stderr);
for (auto const &slot : colors) { for (auto const &slot : colors) {
@@ -1103,6 +1137,7 @@ void process() {
} }
fputs("]\n", stderr); fputs("]\n", stderr);
} }
// LCOV_EXCL_STOP
// Now, iterate through the tiles, generating proto-palettes as we go // 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 // 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; std::unordered_set<uint16_t> tileColors;
for (uint32_t y = 0; y < 8; ++y) { for (uint32_t y = 0; y < 8; ++y) {
for (uint32_t x = 0; x < 8; ++x) { for (uint32_t x = 0; x < 8; ++x) {
if (Rgba color = tile.pixel(x, y); !color.isTransparent()) { if (Rgba color = tile.pixel(x, y);
!color.isTransparent() || !options.hasTransparentPixels) {
tileColors.insert(color.cgbColor()); tileColors.insert(color.cgbColor());
} }
} }
@@ -1136,6 +1172,7 @@ void process() {
if (tileColors.empty()) { if (tileColors.empty()) {
// "Empty" proto-palettes screw with the packing process, so discard those // "Empty" proto-palettes screw with the packing process, so discard those
assume(!isBgColorTransparent());
attrs.protoPaletteID = AttrmapEntry::transparent; attrs.protoPaletteID = AttrmapEntry::transparent;
continue; continue;
} }
@@ -1145,6 +1182,21 @@ void process() {
protoPalette.add(cgbColor); 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 // Insert the proto-palette, making sure to avoid overlaps
for (size_t n = 0; n < protoPalettes.size(); ++n) { for (size_t n = 0; n < protoPalettes.size(); ++n) {
switch (protoPalette.compare(protoPalettes[n])) { switch (protoPalette.compare(protoPalettes[n])) {
@@ -1152,17 +1204,6 @@ void process() {
protoPalettes[n] = protoPalette; // Override them protoPalettes[n] = protoPalette; // Override them
// Remove any other proto-palettes that we encompass // Remove any other proto-palettes that we encompass
// (Example [(0, 1), (0, 2)], inserting (0, 1, 2)) // (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]]; [[fallthrough]];
case ProtoPalette::THEY_BIGGER: case ProtoPalette::THEY_BIGGER:
@@ -1176,7 +1217,7 @@ void process() {
} }
attrs.protoPaletteID = protoPalettes.size(); attrs.protoPaletteID = protoPalettes.size();
if (protoPalettes.size() == AttrmapEntry::transparent) { // Check for overflow if (protoPalettes.size() == AttrmapEntry::background) { // Check for overflow
fatal( fatal(
"Reached %zu proto-palettes... sorry, this image is too much for me to handle :(", "Reached %zu proto-palettes... sorry, this image is too much for me to handle :(",
AttrmapEntry::transparent AttrmapEntry::transparent
@@ -1192,6 +1233,7 @@ continue_visiting_tiles:;
protoPalettes.size(), protoPalettes.size(),
protoPalettes.size() != 1 ? "s" : "" protoPalettes.size() != 1 ? "s" : ""
); );
// LCOV_EXCL_START
if (options.verbosity >= Options::VERB_INTERM) { if (options.verbosity >= Options::VERB_INTERM) {
for (auto const &protoPal : protoPalettes) { for (auto const &protoPal : protoPalettes) {
fputs("[ ", stderr); fputs("[ ", stderr);
@@ -1201,6 +1243,7 @@ continue_visiting_tiles:;
fputs("]\n", stderr); fputs("]\n", stderr);
} }
} }
// LCOV_EXCL_STOP
if (options.palSpecType == Options::EMBEDDED) { if (options.palSpecType == Options::EMBEDDED) {
generatePalSpec(png); generatePalSpec(png);

View File

@@ -195,8 +195,6 @@ void reverse() {
Options::VERB_INTERM, "Reversed image dimensions: %zux%zu tiles\n", width, height 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{ std::vector<std::array<std::optional<Rgba>, 4>> palettes{
{Rgba(0xFFFFFFFF), Rgba(0xAAAAAAFF), Rgba(0x555555FF), Rgba(0x000000FF)} {Rgba(0xFFFFFFFF), Rgba(0xAAAAAAFF), Rgba(0x555555FF), Rgba(0x000000FF)}
}; };
@@ -401,7 +399,9 @@ void reverse() {
options.verbosePrint(Options::VERB_LOG_ACT, "Writing image...\n"); options.verbosePrint(Options::VERB_LOG_ACT, "Writing image...\n");
File pngFile; File pngFile;
if (!pngFile.open(options.input, std::ios::out | std::ios::binary)) { 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)); fatal("Failed to create \"%s\": %s", pngFile.c_str(options.input), strerror(errno));
// LCOV_EXCL_STOP
} }
png_structp png = png_create_write_struct( png_structp png = png_create_write_struct(
PNG_LIBPNG_VER_STRING, PNG_LIBPNG_VER_STRING,
@@ -410,11 +410,15 @@ void reverse() {
pngWarning pngWarning
); );
if (!png) { if (!png) {
// LCOV_EXCL_START
fatal("Failed to create PNG write struct: %s", strerror(errno)); fatal("Failed to create PNG write struct: %s", strerror(errno));
// LCOV_EXCL_STOP
} }
png_infop pngInfo = png_create_info_struct(png); png_infop pngInfo = png_create_info_struct(png);
if (!pngInfo) { 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); png_set_write_fn(png, &pngFile, writePng, flushPng);

View File

@@ -58,6 +58,7 @@ uint16_t Rgba::cgbColor() const {
uint8_t Rgba::grayIndex() const { uint8_t Rgba::grayIndex() const {
assume(isGray()); assume(isGray());
// Convert from [0; 256[ to [0; maxOpaqueColors[ // Convert from 0..<256 to hasTransparentPixels..<nbColorsPerPal
return static_cast<uint16_t>(255 - red) * options.maxOpaqueColors() / 256; // Note that `maxOpaqueColors()` already takes `hasTransparentPixels` into account
return (255 - red) * options.maxOpaqueColors() / 256 + options.hasTransparentPixels;
} }

View File

@@ -424,5 +424,5 @@ max_out:
} }
} }
unreachable_(); unreachable_(); // LCOV_EXCL_LINE
} }

View File

@@ -163,6 +163,7 @@ static option const longopts[] = {
{nullptr, no_argument, nullptr, 0 } {nullptr, no_argument, nullptr, 0 }
}; };
// LCOV_EXCL_START
static void printUsage() { static void printUsage() {
fputs( fputs(
"Usage: rgblink [-dhMtVvwx] [-l script] [-m map_file] [-n sym_file]\n" "Usage: rgblink [-dhMtVvwx] [-l script] [-m map_file] [-n sym_file]\n"
@@ -181,6 +182,7 @@ static void printUsage() {
stderr stderr
); );
} }
// LCOV_EXCL_STOP
enum ScrambledRegion { enum ScrambledRegion {
SCRAMBLE_ROMX, SCRAMBLE_ROMX,
@@ -335,8 +337,10 @@ int main(int argc, char *argv[]) {
isWRAM0Mode = true; isWRAM0Mode = true;
break; break;
case 'h': case 'h':
// LCOV_EXCL_START
printUsage(); printUsage();
exit(0); exit(0);
// LCOV_EXCL_STOP
case 'l': case 'l':
if (linkerScriptName) { if (linkerScriptName) {
warnx("Overriding linker script %s", linkerScriptName); warnx("Overriding linker script %s", linkerScriptName);
@@ -393,11 +397,15 @@ int main(int argc, char *argv[]) {
is32kMode = true; is32kMode = true;
break; break;
case 'V': case 'V':
// LCOV_EXCL_START
printf("rgblink %s\n", get_package_version_string()); printf("rgblink %s\n", get_package_version_string());
exit(0); exit(0);
// LCOV_EXCL_STOP
case 'v': case 'v':
// LCOV_EXCL_START
beVerbose = true; beVerbose = true;
break; break;
// LCOV_EXCL_STOP
case 'w': case 'w':
isWRAM0Mode = true; isWRAM0Mode = true;
break; break;
@@ -407,8 +415,10 @@ int main(int argc, char *argv[]) {
is32kMode = true; is32kMode = true;
break; break;
default: default:
// LCOV_EXCL_START
printUsage(); printUsage();
exit(1); exit(1);
// LCOV_EXCL_STOP
} }
} }

View File

@@ -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 // Checks whether a symbol is legal for a sym file or map file.
static bool canStartSymName(char c) { // 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 == '_'; 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. // 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) { static void printSymName(std::string const &name, FILE *file) {
for (char const *ptr = name.c_str(); *ptr != '\0';) { for (char const *ptr = name.c_str(); *ptr != '\0';) {
char c = *ptr; char c = *ptr;
@@ -368,7 +373,7 @@ static void writeSymBank(SortedSections const &bankSections, SectionType type, u
forEachSortedSection(sect, { forEachSortedSection(sect, {
for (Symbol const *sym : sect->symbols) { for (Symbol const *sym : sect->symbols) {
// Don't output symbols that begin with an illegal character // Don't output symbols that begin with an illegal character
if (!sym->name.empty() && canStartSymName(sym->name[0])) { if (isLegalSymbol(*sym)) {
symList.push_back({ symList.push_back({
.sym = sym, .sym = sym,
.addr = static_cast<uint16_t>(sym->label().offset + sect->org), .addr = static_cast<uint16_t>(sym->label().offset + sect->org),
@@ -471,10 +476,13 @@ static void writeMapBank(SortedSections const &sectList, SectionType type, uint3
// Also print symbols in the following "pieces" // Also print symbols in the following "pieces"
for (uint16_t org = sect->org; sect; sect = sect->nextu.get()) { for (uint16_t org = sect->org; sect; sect = sect->nextu.get()) {
for (Symbol *sym : sect->symbols) { for (Symbol *sym : sect->symbols) {
// Space matches "\tSECTION: $xxxx ..." // Don't output symbols that begin with an illegal character
fprintf(mapFile, "\t $%04" PRIx32 " = ", sym->label().offset + org); if (isLegalSymbol(*sym)) {
printSymName(sym->name, mapFile); // Space matches "\tSECTION: $xxxx ..."
putc('\n', mapFile); fprintf(mapFile, "\t $%04" PRIx32 " = ", sym->label().offset + org);
printSymName(sym->name, mapFile);
putc('\n', mapFile);
}
} }
if (sect->nextu) { if (sect->nextu) {

View File

@@ -363,7 +363,6 @@ static int32_t computeRPNExpr(Patch const &patch, std::vector<Symbol> const &fil
case RPN_RST: case RPN_RST:
value = popRPN(patch); value = popRPN(patch);
// Acceptable values are 0x00, 0x08, 0x10, ..., 0x38 // Acceptable values are 0x00, 0x08, 0x10, ..., 0x38
// They can be easily checked with a bitmask
if (value & ~0x38) { if (value & ~0x38) {
if (!isError) { if (!isError) {
error(patch.src, patch.lineNo, "Value $%" PRIx32 " is not a RST vector", value); 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; value |= 0xC7;
break; 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: case RPN_CONST:
value = 0; value = 0;
for (uint8_t shift = 0; shift < 32; shift += 8) { for (uint8_t shift = 0; shift < 32; shift += 8) {

View File

@@ -228,7 +228,7 @@ static uint8_t parseHexDigit(int c) {
} else if (c >= 'a' && c <= 'f') { } else if (c >= 'a' && c <= 'f') {
return c - 'a' + 10; return c - 'a' + 10;
} else { } else {
unreachable_(); unreachable_(); // LCOV_EXCL_LINE
} }
} }

View File

@@ -9,7 +9,7 @@
#include <string.h> #include <string.h>
#include <tuple> #include <tuple>
#include "helpers.hpp" // assume #include "helpers.hpp" // assume, literal_strlen
#include "linkdefs.hpp" #include "linkdefs.hpp"
#include "platform.hpp" #include "platform.hpp"
@@ -34,28 +34,31 @@ static char const *delim = " \f\n\r\t\v"; // Whitespace according to the C and P
static int static int
nextLine(std::vector<char> &lineBuf, uint32_t &lineNo, FileStackNode const &where, FILE *file) { nextLine(std::vector<char> &lineBuf, uint32_t &lineNo, FileStackNode const &where, FILE *file) {
retry: int firstChar;
++lineNo; for (;;) {
int firstChar = getc(file); ++lineNo;
lineBuf.clear(); firstChar = getc(file);
lineBuf.clear();
switch (firstChar) { switch (firstChar) {
case EOF: case EOF:
return EOF; return EOF;
case ';': case ';':
// Discard comment line // Discard comment line
// TODO: if `;!FILE [...]` on the first line (`lineNo`), return it // TODO: if `;!FILE [...]` on the first line (`lineNo`), return it
do { do {
firstChar = getc(file); firstChar = getc(file);
} while (firstChar != EOF && firstChar != '\r' && firstChar != '\n'); } while (firstChar != EOF && firstChar != '\r' && firstChar != '\n');
[[fallthrough]]; [[fallthrough]];
case '\r': case '\r':
if (firstChar == '\r' && getc(file) != '\n') { if (firstChar == '\r' && getc(file) != '\n') {
consumeLF(where, lineNo, file); consumeLF(where, lineNo, file);
}
[[fallthrough]];
case '\n':
continue;
} }
[[fallthrough]]; break;
case '\n':
goto retry;
} }
for (;;) { 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 // It's fine to keep modifying the symbol after `AddSymbol`, only
// the name must not be modified // 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\""); fatal(&where, lineNo, "'S' line is neither \"Def\" nor \"Ref\"");
} }

View File

@@ -208,8 +208,10 @@ static void mergeSections(Section &target, std::unique_ptr<Section> &&other) {
break; break;
case SECTION_NORMAL: case SECTION_NORMAL:
// LCOV_EXCL_START
unreachable_(); unreachable_();
} }
// LCOV_EXCL_STOP
// Note that the order in which fragments are stored in the `nextu` list does not // Note that the order in which fragments are stored in the `nextu` list does not
// really matter, only that offsets were properly computed above // really matter, only that offsets were properly computed above

View File

@@ -5,7 +5,7 @@ error: anon-label-bad.asm(6):
error: anon-label-bad.asm(9): error: anon-label-bad.asm(9):
syntax error, unexpected anonymous label syntax error, unexpected anonymous label
error: anon-label-bad.asm(10): 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): error: anon-label-bad.asm(22):
syntax error, unexpected :: syntax error, unexpected ::
error: Assembly aborted (5 errors)! error: Assembly aborted (5 errors)!

View File

@@ -7,6 +7,17 @@ FOR V, 1, 100
PRINTLN "cont" PRINTLN "cont"
ENDR ENDR
WARN "done {d:V}" 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 rept 1
break break
; skips invalid code ; skips invalid code

View File

@@ -1,8 +1,8 @@
warning: break.asm(9): [-Wuser] warning: break.asm(9): [-Wuser]
done 5 done 5
warning: break.asm(17): [-Wuser] warning: break.asm(28): [-Wuser]
OK OK
error: break.asm(18): error: break.asm(29):
BREAK can only be used inside a REPT/FOR block 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 Ended block with 1 unterminated IF construct

27
test/asm/charcmp.asm Normal file
View 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
View 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

View File

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

View 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
View 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
View 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)!

View File

@@ -15,3 +15,6 @@ SECTION "Unaligned", ROM0
println @ & 0 println @ & 0
println @ & 1 ; Nope println @ & 1 ; Nope
Floating:
println Floating & Aligned ; Neither defined

View File

@@ -4,4 +4,6 @@ error: const-and.asm(11):
Expected constant expression: 'Aligned' is not constant at assembly time Expected constant expression: 'Aligned' is not constant at assembly time
error: const-and.asm(17): error: const-and.asm(17):
Expected constant expression: PC is not constant at assembly time 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)!

View File

@@ -5,3 +5,4 @@ $0
$A $A
$0 $0
$0 $0
$0

View File

@@ -1,7 +1,7 @@
error: def-scoped.asm(10): error: def-scoped.asm(10):
syntax error, unexpected local identifier, expecting identifier syntax error, unexpected local label, expecting symbol
error: def-scoped.asm(13): error: def-scoped.asm(13):
syntax error, unexpected local identifier, expecting identifier syntax error, unexpected local label, expecting symbol
error: def-scoped.asm(16): error: def-scoped.asm(16):
syntax error, unexpected local identifier, expecting identifier syntax error, unexpected local label, expecting symbol
error: Assembly aborted (3 errors)! error: Assembly aborted (3 errors)!

View File

@@ -1,5 +1,5 @@
error: ds-bad.asm(3): error: ds-bad.asm(3):
Expected constant expression: 'unknown' is not constant at assembly time Expected constant expression: 'unknown' is not constant at assembly time
warning: ds-bad.asm(4): [-Wtruncation] 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)! error: Assembly aborted (1 error)!

View File

@@ -1,5 +1,5 @@
error: error-recovery.asm(3): error: error-recovery.asm(3):
syntax error, unexpected number syntax error, unexpected number
error: error-recovery.asm(5) -> error-recovery.asm::REPT~1(7): error: error-recovery.asm(5) -> error-recovery.asm::REPT~1(7):
syntax error, unexpected identifier syntax error, unexpected symbol
error: Assembly aborted (2 errors)! error: Assembly aborted (2 errors)!

View 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

View 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)!

View File

@@ -0,0 +1,6 @@
$0
$2
$4
$6
$4
$5

View File

@@ -0,0 +1,3 @@
assert 1 +# 1 == 2
assert 2 '?* 2 == 4
assert 3 **?''?##?? 3 == 27

View 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)!

View File

@@ -1 +0,0 @@
DEF S EQUS CHARSUB("ABC", 4)

View File

@@ -1,2 +0,0 @@
warning: invalid-charsub.asm(1): [-Wbuiltin-args]
CHARSUB: Position 4 is past the end of the string

View File

@@ -6,3 +6,16 @@ SECTION "invalid", ROM0[$10000]
ld b, [$4000] ld b, [$4000]
bit 8, a bit 8, a
rst $40 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

View File

@@ -10,8 +10,26 @@ error: invalid-instructions.asm(5):
syntax error, unexpected bc, expecting hl syntax error, unexpected bc, expecting hl
error: invalid-instructions.asm(6): error: invalid-instructions.asm(6):
syntax error, unexpected number, expecting hl syntax error, unexpected number, expecting hl
warning: invalid-instructions.asm(7): [-Wtruncation]
Expression must be 3-bit
error: invalid-instructions.asm(7): 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): error: invalid-instructions.asm(8):
Invalid address $40 for RST 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)!

View File

@@ -0,0 +1,3 @@
DEF S EQUS STRCHAR("ABC", 3)
DEF T EQUS CHARSUB("ABC", 4)
DEF U EQUS CHARSUB("ABC", 0)

View 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

View File

@@ -19,7 +19,7 @@ DEF copy EQUS STRSUB("{invalid}", 1)
println "\"{#s:invalid}\" == \"{#s:copy}\" ({d:n})" println "\"{#s:invalid}\" == \"{#s:copy}\" ({d:n})"
DEF mid1 EQUS STRSUB("{invalid}", 5, 2) 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}\"" println "\"{#s:mid2}{#s:mid1}\""
; characters: ; characters:
@@ -36,8 +36,8 @@ DEF n = STRLEN("{invalid}")
DEF r = CHARLEN("{invalid}") DEF r = CHARLEN("{invalid}")
println "\"{#s:invalid}\": {d:n} == {d:r}" println "\"{#s:invalid}\": {d:n} == {d:r}"
REDEF mid1 EQUS CHARSUB("{invalid}", 4) REDEF mid1 EQUS STRCHAR("{invalid}", 3)
REDEF mid2 EQUS CHARSUB("{invalid}", 7) REDEF mid2 EQUS STRCHAR("{invalid}", 6)
println "\"{#s:mid2}{#s:mid1}\"" println "\"{#s:mid2}{#s:mid1}\""
; characters: ; characters:
@@ -53,3 +53,6 @@ println "\"{#s:invalid}\": {d:n} == {d:r}"
DEF final EQUS STRSUB("{invalid}", 4, 1) DEF final EQUS STRSUB("{invalid}", 4, 1)
println "\"{#s:invalid}\" ends \"{#s:final}\"" println "\"{#s:invalid}\" ends \"{#s:final}\""
REDEF final EQUS STRSLICE("{invalid}", 3, 4)
println "\"{#s:invalid}\" ends \"{#s:final}\""

View File

@@ -15,9 +15,9 @@ error: invalid-utf-8-strings.asm(17):
error: invalid-utf-8-strings.asm(17): error: invalid-utf-8-strings.asm(17):
STRSUB: Invalid UTF-8 byte 0xA2 STRSUB: Invalid UTF-8 byte 0xA2
error: invalid-utf-8-strings.asm(22): 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): 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): error: invalid-utf-8-strings.asm(35):
STRLEN: Invalid UTF-8 byte 0xFE STRLEN: Invalid UTF-8 byte 0xFE
error: invalid-utf-8-strings.asm(35): error: invalid-utf-8-strings.asm(35):
@@ -56,4 +56,6 @@ error: invalid-utf-8-strings.asm(50):
STRLEN: Incomplete UTF-8 character STRLEN: Incomplete UTF-8 character
error: invalid-utf-8-strings.asm(54): error: invalid-utf-8-strings.asm(54):
STRSUB: Incomplete UTF-8 character 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)!

View File

@@ -4,3 +4,4 @@
"漢<>" "漢<>"
"abc<62><63>": 4 == 4 "abc<62><63>": 4 == 4
"abc<62><63>" ends "<22><>" "abc<62><63>" ends "<22><>"
"abc<62><63>" ends "<22><>"

View File

@@ -1,5 +1,3 @@
error: invalid-utf-8.asm(6) -> invalid-utf-8.asm::m(4): error: invalid-utf-8.asm(6) -> invalid-utf-8.asm::m(4):
Unknown character 0xCF Unknown characters 0xCF, 0xD3
error: invalid-utf-8.asm(6) -> invalid-utf-8.asm::m(4): error: Assembly aborted (1 error)!
Unknown character 0xD3
error: Assembly aborted (2 errors)!

View File

@@ -1,6 +1,3 @@
PUSHS
SECTION "floating", ROM0 SECTION "floating", ROM0
Known: ; This symbol is known to be a label, but its value is not known yet 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 Constant2: ; Same as above
ENDSECTION ; Ensure we are in neither section
MACRO print_diff MACRO print_diff
PRINTLN (\1) - (\2) PRINTLN (\1) - (\2)
PRINTLN (\2) - (\1) PRINTLN (\2) - (\1)
ENDM 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 ; Diffing two constants should work
print_diff Constant, Constant2 print_diff Constant, Constant2
; Diffing two labels in the same SECTION as well ; Diffing two labels in the same SECTION as well

View File

@@ -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): error: label-diff.asm(32) -> label-diff.asm::print_diff(20):
Expected constant expression: 'Known' is not constant at assembly time Expected constant expression: 'Unknown' is not constant at assembly time
error: label-diff.asm(32) -> label-diff.asm::print_diff(21): error: label-diff.asm(34) -> label-diff.asm::print_diff(19):
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(20): 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 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 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 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 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 Expected constant expression: 'Unknown' is not constant at assembly time
error: label-diff.asm(58) -> label-diff.asm::print_diff(20): error: label-diff.asm(58) -> label-diff.asm::print_diff(20):
Expected constant expression: PC is not constant at assembly time 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)! error: Assembly aborted (18 errors)!

View File

@@ -1,15 +1,15 @@
error: label-macro-arg.asm(38) -> label-macro-arg.asm::test_char(25): 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" while expanding symbol "VAR_DEF"
error: label-macro-arg.asm(38) -> label-macro-arg.asm::test_char(26): 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): error: label-macro-arg.asm(38) -> label-macro-arg.asm::test_char(29):
Interpolated symbol "sizeof_.something" does not exist Interpolated symbol "sizeof_.something" does not exist
error: label-macro-arg.asm(39) -> label-macro-arg.asm::test_char(25): 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" while expanding symbol "VAR_DEF"
error: label-macro-arg.asm(39) -> label-macro-arg.asm::test_char(26): 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): error: label-macro-arg.asm(39) -> label-macro-arg.asm::test_char(29):
Invalid format spec 'sizeof_' Invalid format spec 'sizeof_'
error: label-macro-arg.asm(39) -> label-macro-arg.asm::test_char(29): error: label-macro-arg.asm(39) -> label-macro-arg.asm::test_char(29):

Binary file not shown.

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