mirror of
https://github.com/gbdev/rgbds.git
synced 2025-11-20 18:22:07 +00:00
Compare commits
128 Commits
v0.8.0
...
v0.9.0-rc1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
37b64ca51f | ||
|
|
deb3ac3452 | ||
|
|
dd20012e88 | ||
|
|
91fbece1ad | ||
|
|
0597ff82e3 | ||
|
|
9ef2e43bf7 | ||
|
|
197f6cb0ba | ||
|
|
9b3d19c3f2 | ||
|
|
02439b18c0 | ||
|
|
122ef95d9c | ||
|
|
bfb96b038d | ||
|
|
1adf68d018 | ||
|
|
750e69c5a6 | ||
|
|
6e83a14143 | ||
|
|
cff05435ad | ||
|
|
3b59e8963e | ||
|
|
56af572bfd | ||
|
|
6bc2446966 | ||
|
|
155e7287db | ||
|
|
1dcc000572 | ||
|
|
8cd0e66297 | ||
|
|
5f07095f6d | ||
|
|
11f0e88b30 | ||
|
|
d917df406d | ||
|
|
323028d9f2 | ||
|
|
7960a10228 | ||
|
|
068ad93427 | ||
|
|
610f04beeb | ||
|
|
e289387b09 | ||
|
|
80d37f9988 | ||
|
|
1283b0b6a6 | ||
|
|
a098213053 | ||
|
|
6b8d33529e | ||
|
|
2fb76ce584 | ||
|
|
7330c2c606 | ||
|
|
c07371c9fc | ||
|
|
15f0871683 | ||
|
|
26fcff831d | ||
|
|
3a5ff35928 | ||
|
|
38a90890fb | ||
|
|
de84e3ea8b | ||
|
|
b77ba1d87d | ||
|
|
4ce6c9f4a5 | ||
|
|
8286d9c462 | ||
|
|
fbe28e0def | ||
|
|
23272f028f | ||
|
|
77129b9e80 | ||
|
|
44332ff4be | ||
|
|
81ab133566 | ||
|
|
0e8a17ce82 | ||
|
|
7bc9a24bf0 | ||
|
|
b438c83bda | ||
|
|
82e81ab1da | ||
|
|
57c3d74b9e | ||
|
|
7d98b9a900 | ||
|
|
8f47fb494b | ||
|
|
8c96293b11 | ||
|
|
57f122a04e | ||
|
|
356367bfd3 | ||
|
|
b7290366cb | ||
|
|
731715ff36 | ||
|
|
7cf4156003 | ||
|
|
ee3a93a442 | ||
|
|
96a0481cba | ||
|
|
86140b5b2f | ||
|
|
369296693c | ||
|
|
47b2e0e912 | ||
|
|
28733fe312 | ||
|
|
e45a422da3 | ||
|
|
9fd4ba90cc | ||
|
|
e548ecc6fa | ||
|
|
00b0914436 | ||
|
|
60c03ec1e3 | ||
|
|
379aa8c267 | ||
|
|
589cea47f6 | ||
|
|
68a6abd00e | ||
|
|
c42e856efb | ||
|
|
b987e5669f | ||
|
|
718066c2cf | ||
|
|
2d530dbcd6 | ||
|
|
a9140e6bb4 | ||
|
|
fb6f861a08 | ||
|
|
0adff57e2c | ||
|
|
784e828219 | ||
|
|
b20b2dd28c | ||
|
|
2a85009b6b | ||
|
|
809b364958 | ||
|
|
60f9e86361 | ||
|
|
817dcfdc70 | ||
|
|
a3f9952b9e | ||
|
|
0cd79c33ef | ||
|
|
747427e801 | ||
|
|
6b09838739 | ||
|
|
0f1137c6ec | ||
|
|
1ad9383042 | ||
|
|
5b486e1d87 | ||
|
|
e93190d491 | ||
|
|
7435630d6a | ||
|
|
fc8707886c | ||
|
|
bb480b761c | ||
|
|
2706f94788 | ||
|
|
2f8f99bd94 | ||
|
|
f304e1dd7f | ||
|
|
c5e6a815fa | ||
|
|
d4231f9efa | ||
|
|
e4ffcf7153 | ||
|
|
1d194b68ca | ||
|
|
9a5b3f0902 | ||
|
|
436580a649 | ||
|
|
8af9e9d465 | ||
|
|
98bca79df4 | ||
|
|
dae4219acd | ||
|
|
3d1f5386c2 | ||
|
|
1f8f28cac8 | ||
|
|
8e60d1f0b8 | ||
|
|
d8aceaea4a | ||
|
|
a23b4732e3 | ||
|
|
3bd35a8848 | ||
|
|
41046c287f | ||
|
|
f4d0f01f91 | ||
|
|
0ed846c773 | ||
|
|
4e0f794c23 | ||
|
|
6a65cbc9ed | ||
|
|
92abe24894 | ||
|
|
13a8895fca | ||
|
|
e179ba5fd3 | ||
|
|
1d89d75381 | ||
|
|
c0904228f2 |
12
.gitattributes
vendored
12
.gitattributes
vendored
@@ -4,3 +4,15 @@
|
||||
|
||||
# Flags also need Unix line endings (see https://github.com/gbdev/rgbds/issues/955)
|
||||
*.flags text eol=lf
|
||||
|
||||
# Binary files need exact bytes
|
||||
*.bin binary
|
||||
*.gb binary
|
||||
*.1bpp binary
|
||||
*.2bpp binary
|
||||
*.pal binary
|
||||
*.attrmap binary
|
||||
*.tilemap binary
|
||||
*.palmap binary
|
||||
*.patch binary
|
||||
*.png binary
|
||||
|
||||
27
.github/scripts/build_libpng.sh
vendored
Executable file
27
.github/scripts/build_libpng.sh
vendored
Executable file
@@ -0,0 +1,27 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
pngver=1.6.43
|
||||
|
||||
## Grab sources and check them
|
||||
|
||||
curl -LOJ "http://prdownloads.sourceforge.net/libpng/libpng-$pngver.tar.xz?download"
|
||||
# Brew doesn't provide any sha256sum, so we're making do with `sha2` instead.
|
||||
if [ "$(sha2 -q -256 libpng-$pngver.tar.xz)" != 6a5ca0652392a2d7c9db2ae5b40210843c0bbc081cbd410825ab00cc59f14a6c ]; then
|
||||
sha2 -256 libpng-$pngver.tar.xz
|
||||
echo Checksum mismatch! Aborting. >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
## Extract sources and patch them
|
||||
|
||||
tar -xvf libpng-$pngver.tar.xz
|
||||
|
||||
## Start building!
|
||||
|
||||
mkdir -p build
|
||||
cd build
|
||||
../libpng-$pngver/configure --disable-shared --enable-static \
|
||||
CFLAGS="-O3 -flto -DNDEBUG -mmacosx-version-min=10.9 -arch x86_64 -arch arm64 -fno-exceptions"
|
||||
make -kj
|
||||
make install prefix="$PWD/../libpng-staging"
|
||||
10
.github/scripts/install_deps.sh
vendored
10
.github/scripts/install_deps.sh
vendored
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
set -euo pipefail
|
||||
|
||||
case "${1%-*}" in
|
||||
ubuntu)
|
||||
@@ -7,9 +7,11 @@ case "${1%-*}" in
|
||||
sudo apt-get install -yq bison libpng-dev pkg-config
|
||||
;;
|
||||
macos)
|
||||
brew install bison libpng pkg-config md5sha1sum
|
||||
# For the version check below exclusively, re-do this before building
|
||||
export PATH="/usr/local/opt/bison/bin:$PATH"
|
||||
brew install bison sha2 md5sha1sum
|
||||
# Export `bison` to allow using the version we install from Homebrew,
|
||||
# instead of the outdated one preinstalled on macOS (which doesn't even support `-Wall`...)
|
||||
export PATH="/opt/homebrew/opt/bison/bin:$PATH"
|
||||
printf 'PATH=%s\n' "$PATH" >>"$GITHUB_ENV" # Make it available to later CI steps too
|
||||
;;
|
||||
*)
|
||||
echo "WARNING: Cannot install deps for OS '$1'"
|
||||
|
||||
8
.github/scripts/mingw-w64-libpng-dev.sh
vendored
8
.github/scripts/mingw-w64-libpng-dev.sh
vendored
@@ -1,5 +1,5 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
pngver=1.6.43
|
||||
arch="$1"
|
||||
@@ -27,7 +27,7 @@ cd build
|
||||
--prefix="/usr/$arch" \
|
||||
--enable-shared --disable-static \
|
||||
CPPFLAGS="-D_FORTIFY_SOURCE=2" \
|
||||
CFLAGS="-O2 -pipe -fno-plt -fexceptions --param=ssp-buffer-size=4" \
|
||||
CFLAGS="-O2 -pipe -fno-plt -fno-exceptions --param=ssp-buffer-size=4" \
|
||||
LDFLAGS="-Wl,-O1,--sort-common,--as-needed -fstack-protector"
|
||||
make -kj
|
||||
make install
|
||||
sudo make install
|
||||
|
||||
18
.github/workflows/create-release-artifacts.yml
vendored
18
.github/workflows/create-release-artifacts.yml
vendored
@@ -67,7 +67,7 @@ jobs:
|
||||
path: rgbds-${{ env.version }}-win${{ matrix.bits }}.zip
|
||||
|
||||
macos:
|
||||
runs-on: macos-12
|
||||
runs-on: macos-14
|
||||
steps:
|
||||
- name: Get version from tag
|
||||
shell: bash
|
||||
@@ -78,21 +78,23 @@ jobs:
|
||||
- name: Install deps
|
||||
shell: bash
|
||||
run: |
|
||||
./.github/scripts/install_deps.sh macos-latest
|
||||
./.github/scripts/install_deps.sh macos
|
||||
- name: Build libpng
|
||||
run: |
|
||||
./.github/scripts/build_libpng.sh
|
||||
# We force linking libpng statically; the other libs are provided by macOS itself
|
||||
- name: Build binaries
|
||||
run: |
|
||||
export PATH="/usr/local/opt/bison/bin:$PATH"
|
||||
make -kj CXXFLAGS="-O3 -flto -DNDEBUG -mmacosx-version-min=10.9" PKG_CONFIG="pkg-config --static" PNGLDLIBS="$(pkg-config --static --libs-only-L libpng | cut -c 3-)/libpng.a $(pkg-config --static --libs-only-l libpng | sed s/-lpng[0-9]*//g)" Q=
|
||||
make -kj CXXFLAGS="-O3 -flto -DNDEBUG -mmacosx-version-min=10.9 -arch x86_64 -arch arm64" PNGCFLAGS="-I libpng-staging/include" PNGLDLIBS="libpng-staging/lib/libpng.a -lz" Q=
|
||||
strip rgb{asm,link,fix,gfx}
|
||||
- name: Package binaries
|
||||
run: |
|
||||
zip --junk-paths rgbds-${{ env.version }}-macos-x86_64.zip rgb{asm,link,fix,gfx} man/* .github/scripts/install.sh
|
||||
zip --junk-paths rgbds-${{ env.version }}-macos.zip rgb{asm,link,fix,gfx} man/* .github/scripts/install.sh
|
||||
- name: Upload macOS binaries
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: macos
|
||||
path: rgbds-${{ env.version }}-macos-x86_64.zip
|
||||
path: rgbds-${{ env.version }}-macos.zip
|
||||
|
||||
linux:
|
||||
runs-on: ubuntu-20.04 # Oldest supported, for best glibc compatibility.
|
||||
@@ -123,6 +125,8 @@ jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [windows, macos, linux]
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Get version from tag
|
||||
shell: bash
|
||||
@@ -147,7 +151,7 @@ jobs:
|
||||
files: |
|
||||
win32/rgbds-${{ env.version }}-win32.zip
|
||||
win64/rgbds-${{ env.version }}-win64.zip
|
||||
macos/rgbds-${{ env.version }}-macos-x86_64.zip
|
||||
macos/rgbds-${{ env.version }}-macos.zip
|
||||
linux/rgbds-${{ env.version }}-linux-x86_64.tar.xz
|
||||
rgbds-${{ env.version }}.tar.gz
|
||||
fail_on_unmatched_files: true
|
||||
|
||||
39
.github/workflows/testing.yml
vendored
39
.github/workflows/testing.yml
vendored
@@ -7,12 +7,12 @@ jobs:
|
||||
unix:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-20.04, ubuntu-22.04, macos-12]
|
||||
os: [ubuntu-20.04, ubuntu-22.04, macos-14]
|
||||
cxx: [g++, clang++]
|
||||
buildsys: [make, cmake]
|
||||
exclude:
|
||||
# Don't use `g++` on macOS; it's just an alias to `clang++`.
|
||||
- os: macos-12
|
||||
- os: macos-14
|
||||
cxx: g++
|
||||
fail-fast: false
|
||||
runs-on: ${{ matrix.os }}
|
||||
@@ -22,18 +22,14 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
./.github/scripts/install_deps.sh ${{ matrix.os }}
|
||||
# Export `bison` to allow using the version we install from Homebrew,
|
||||
# instead of the outdated 2.3 one preinstalled on macOS.
|
||||
- name: Build & install using Make
|
||||
if: matrix.buildsys == 'make'
|
||||
run: |
|
||||
export PATH="/usr/local/opt/bison/bin:$PATH"
|
||||
make develop -kj Q= CXX=${{ matrix.cxx }}
|
||||
sudo make install -j Q=
|
||||
- name: Build & install using CMake
|
||||
if: matrix.buildsys == 'cmake'
|
||||
run: |
|
||||
export PATH="/usr/local/opt/bison/bin:$PATH"
|
||||
cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_COMPILER=${{ matrix.cxx }} -DSANITIZERS=ON -DMORE_WARNINGS=ON
|
||||
cmake --build build -j --verbose
|
||||
cp build/src/rgb{asm,link,fix,gfx} .
|
||||
@@ -74,28 +70,22 @@ jobs:
|
||||
- name: Run tests
|
||||
shell: bash
|
||||
run: |
|
||||
test/run-tests.sh
|
||||
CXX=${{ matrix.cxx }} test/run-tests.sh
|
||||
|
||||
macos-static:
|
||||
strategy:
|
||||
matrix:
|
||||
# Don't run on macOS 11; our setup makes clang segfault (YES).
|
||||
os: [macos-12]
|
||||
fail-fast: false
|
||||
runs-on: ${{ matrix.os }}
|
||||
runs-on: macos-14
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install deps
|
||||
shell: bash
|
||||
run: |
|
||||
./.github/scripts/install_deps.sh ${{ matrix.os }}
|
||||
# Export `bison` to allow using the version we install from Homebrew,
|
||||
# instead of the outdated one preinstalled on macOS (which doesn't
|
||||
# even support `-Wall`...).
|
||||
./.github/scripts/install_deps.sh macos
|
||||
- name: Build libpng
|
||||
run: |
|
||||
./.github/scripts/build_libpng.sh
|
||||
- name: Build & install
|
||||
run: |
|
||||
export PATH="/usr/local/opt/bison/bin:$PATH"
|
||||
make -kj CXXFLAGS="-O3 -flto -DNDEBUG -mmacosx-version-min=10.9" PKG_CONFIG="pkg-config --static" PNGLDLIBS="$(pkg-config --static --libs-only-L libpng | cut -c 3-)/libpng.a $(pkg-config --static --libs-only-l libpng | sed s/-lpng[0-9]*//g)" Q=
|
||||
make -kj CXXFLAGS="-O3 -flto -DNDEBUG -mmacosx-version-min=10.9 -arch x86_64 -arch arm64" PNGCFLAGS="-I libpng-staging/include" PNGLDLIBS="libpng-staging/lib/libpng.a -lz" Q=
|
||||
- name: Package binaries
|
||||
run: |
|
||||
mkdir bins
|
||||
@@ -103,7 +93,7 @@ jobs:
|
||||
- name: Upload binaries
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: rgbds-canary-${{ matrix.os }}-${{ matrix.buildsys }}
|
||||
name: rgbds-canary-macos-static
|
||||
path: bins
|
||||
- name: Compute test dependency cache params
|
||||
id: test-deps-cache-params
|
||||
@@ -127,7 +117,7 @@ jobs:
|
||||
- name: Install test dependency dependencies
|
||||
shell: bash
|
||||
run: |
|
||||
test/fetch-test-deps.sh --get-deps ${{ matrix.os }}
|
||||
test/fetch-test-deps.sh --get-deps macos
|
||||
- name: Run tests
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -243,13 +233,13 @@ jobs:
|
||||
- name: Install deps
|
||||
shell: bash
|
||||
run: |
|
||||
./.github/scripts/install_deps.sh ${{ matrix.os }}
|
||||
./.github/scripts/install_deps.sh ubuntu
|
||||
- name: Install MinGW
|
||||
run: | # dpkg-dev is apparently required for pkg-config for cross-building
|
||||
sudo apt-get install g++-mingw-w64-${{ matrix.arch }}-win32 mingw-w64-tools libz-mingw-w64-dev dpkg-dev
|
||||
- name: Install libpng dev headers for MinGW
|
||||
run: |
|
||||
sudo ./.github/scripts/mingw-w64-libpng-dev.sh ${{ matrix.triplet }}
|
||||
./.github/scripts/mingw-w64-libpng-dev.sh ${{ matrix.triplet }}
|
||||
- name: Cross-build Windows binaries
|
||||
run: |
|
||||
make mingw${{ matrix.bits }} -kj Q=
|
||||
@@ -273,7 +263,6 @@ jobs:
|
||||
path: |
|
||||
test/gfx/randtilegen.exe
|
||||
test/gfx/rgbgfx_test.exe
|
||||
test/link/unmangle.exe
|
||||
|
||||
windows-mingw-testing:
|
||||
needs: windows-mingw-build
|
||||
@@ -294,7 +283,7 @@ jobs:
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: testing-programs-mingw-win${{ matrix.bits }}
|
||||
path: test
|
||||
path: test/gfx
|
||||
- name: Extract binaries
|
||||
shell: bash
|
||||
run: |
|
||||
|
||||
@@ -60,11 +60,11 @@ else()
|
||||
add_compile_options(-Werror -Wextra
|
||||
-Walloc-zero -Wcast-align -Wcast-qual -Wduplicated-branches -Wduplicated-cond
|
||||
-Wfloat-equal -Wlogical-op -Wnull-dereference -Wshift-overflow=2
|
||||
-Wstringop-overflow=4 -Wstrict-overflow=5 -Wundef -Wuninitialized -Wunused
|
||||
-Wshadow # TODO: -Wshadow=compatible-local ?
|
||||
-Wstringop-overflow=4 -Wundef -Wuninitialized -Wunused
|
||||
-Wshadow # TODO: -Wshadow=compatible-local?
|
||||
-Wformat=2 -Wformat-overflow=2 -Wformat-truncation=1
|
||||
-Wno-format-nonliteral # We have a couple of "dynamic" prints
|
||||
# We do some range checks that are always false on some platforms (GCC, Clang)
|
||||
-Wno-format-nonliteral -Wno-strict-overflow
|
||||
-Wno-unused-but-set-variable # bison's `yynerrs_` is incremented but unused
|
||||
-Wno-type-limits -Wno-tautological-constant-out-of-range-compare
|
||||
-Wvla # MSVC does not support VLAs
|
||||
-Wno-unknown-warning-option) # Clang shouldn't diagnose unknown warnings
|
||||
|
||||
@@ -117,25 +117,45 @@ The object file will be linked with and without said flag, respectively; and in
|
||||
|
||||
### RGBFIX
|
||||
|
||||
Each `.bin` file corresponds to one test, and **must** be accompanied by a `.flags` file and a `.err` file.
|
||||
|
||||
The `.flags` file is a text file whose first line contains flags to pass to RGBFIX.
|
||||
Each `.flags` file corresponds to one test.
|
||||
Each one is a text file whose first line contains flags to pass to RGBFIX.
|
||||
(There may be more lines, which will be ignored; they can serve as comments to explain what the test is about.)
|
||||
|
||||
RGBFIX will be invoked on the `.bin` file, and its error output must match the contents of the `.err` file.
|
||||
(If no errors ought to be printed, then the `.err` file should just be empty.)
|
||||
RGBFIX will be invoked on the `.bin` file if it exists, or else on default-input.bin.
|
||||
|
||||
If no `.err` file exists, RGBFIX is simply expected to be able to process the file normally.
|
||||
If one *does* exist, RGBFIX's return status is ignored, but its output **must** match the `.err` file's contents.
|
||||
|
||||
Additionally, if a `.gb` file exists, the output of RGBFIX must match the `.gb`.
|
||||
|
||||
### RGBGFX
|
||||
|
||||
There are three kinds of test.
|
||||
|
||||
#### Simple tests
|
||||
|
||||
Each `.png` file corresponds to one test.
|
||||
RGBGFX will be invoked on the file.
|
||||
If a `.flags` file exists, it will be used as part of the RGBGFX invocation (<code>@<var><file></var>.flags</code>).
|
||||
|
||||
If `.out.1bpp`, `.out.2bpp`, `.out.pal`, `.out.tilemap`, `.out.attrmap`, or `.out.palmap` files exist, RGBGFX will create the corresponding kind of output, which must match the file's contents.
|
||||
Multiple kinds of output may be tested for the same input.
|
||||
|
||||
If no `.err` file exists, RGBGFX is simply expected to be able to process the file normally.
|
||||
If one *does* exist, RGBGFX's return status is ignored, but its output **must** match the `.err` file's contents.
|
||||
|
||||
#### Reverse tests
|
||||
|
||||
Each `.1bpp` or `.2bpp` file corresponds to one test.
|
||||
RGBGFX will be invoked on the file with `-r 1` for reverse mode, then invoked on the output without `-r 1`.
|
||||
The round-trip output must match the input file's contents.
|
||||
If a `.flags` file exists, it will be used as part of the RGBGFX invocation (<code>@<var><file></var>.flags</code>).
|
||||
|
||||
#### Random seed tests
|
||||
|
||||
Each `seed*.bin` file corresponds to one test.
|
||||
Each one is a binary RNG file which is passed to the `rgbgfx_test` program.
|
||||
|
||||
### Downstream projects
|
||||
|
||||
1. Make sure the downstream project supports <code>make <var><target></var> RGBDS=<var><path/to/RGBDS/></var></code>.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
FROM debian:11-slim
|
||||
LABEL org.opencontainers.image.source=https://github.com/gbdev/rgbds
|
||||
ARG version=0.8.0
|
||||
ARG version=0.9.0-rc1
|
||||
WORKDIR /rgbds
|
||||
|
||||
COPY . .
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 1997-2023, Carsten Sørensen and RGBDS contributors.
|
||||
Copyright (c) 1997-2024, Carsten Sørensen and RGBDS contributors.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to
|
||||
|
||||
19
Makefile
19
Makefile
@@ -128,10 +128,7 @@ test/gfx/randtilegen: test/gfx/randtilegen.cpp
|
||||
$Q${CXX} ${REALLDFLAGS} ${PNGLDFLAGS} -o $@ $^ ${REALCXXFLAGS} ${PNGCFLAGS} ${PNGLDLIBS}
|
||||
|
||||
test/gfx/rgbgfx_test: test/gfx/rgbgfx_test.cpp
|
||||
$Q${CXX} ${REALLDFLAGS} ${PNGLDFLAGS} -o $@ $^ ${REALCXXFLAGS} ${PNGLDLIBS}
|
||||
|
||||
test/link/unmangle: test/link/unmangle.cpp
|
||||
$Q${CXX} ${REALLDFLAGS} -o $@ $^ ${REALCXXFLAGS}
|
||||
$Q${CXX} ${REALLDFLAGS} ${PNGLDFLAGS} -o $@ $^ ${REALCXXFLAGS} ${PNGCFLAGS} ${PNGLDLIBS}
|
||||
|
||||
# Rules to process files
|
||||
|
||||
@@ -180,7 +177,7 @@ clean:
|
||||
$Q${RM} rgbshim.sh
|
||||
$Q${RM} src/asm/parser.cpp src/asm/parser.hpp src/asm/stack.hh
|
||||
$Q${RM} src/link/script.cpp src/link/script.hpp src/link/stack.hh
|
||||
$Q${RM} test/gfx/randtilegen test/gfx/rgbgfx_test test/link/unmangle
|
||||
$Q${RM} test/gfx/randtilegen test/gfx/rgbgfx_test
|
||||
|
||||
# Target used to install the binaries and man pages.
|
||||
|
||||
@@ -208,12 +205,10 @@ develop:
|
||||
$Q${MAKE} WARNFLAGS="${WARNFLAGS} -Werror -Wextra \
|
||||
-Walloc-zero -Wcast-align -Wcast-qual -Wduplicated-branches -Wduplicated-cond \
|
||||
-Wfloat-equal -Wlogical-op -Wnull-dereference -Wshift-overflow=2 \
|
||||
-Wstringop-overflow=4 -Wstrict-overflow=5 -Wundef -Wuninitialized -Wunused \
|
||||
-Wshadow \
|
||||
-Wstringop-overflow=4 -Wundef -Wuninitialized -Wunused -Wshadow \
|
||||
-Wformat=2 -Wformat-overflow=2 -Wformat-truncation=1 \
|
||||
-Wno-format-nonliteral -Wno-strict-overflow -Wno-unused-but-set-variable \
|
||||
-Wno-type-limits -Wno-tautological-constant-out-of-range-compare \
|
||||
-Wvla \
|
||||
-Wno-type-limits -Wno-tautological-constant-out-of-range-compare -Wvla \
|
||||
-D_GLIBCXX_ASSERTIONS \
|
||||
-fsanitize=shift -fsanitize=integer-divide-by-zero \
|
||||
-fsanitize=unreachable -fsanitize=vla-bound \
|
||||
@@ -226,7 +221,7 @@ develop:
|
||||
|
||||
debug:
|
||||
$Qenv ${MAKE} \
|
||||
CXXFLAGS="-ggdb3 -Og -fno-omit-frame-pointer -fno-optimize-sibling-calls"
|
||||
CXXFLAGS="-ggdb3 -O0 -fno-omit-frame-pointer -fno-optimize-sibling-calls"
|
||||
|
||||
# This target is used during development in order to more easily profile with callgrind.
|
||||
|
||||
@@ -253,13 +248,13 @@ iwyu:
|
||||
# install instructions instead.
|
||||
|
||||
mingw32:
|
||||
$Q${MAKE} all test/gfx/randtilegen test/gfx/rgbgfx_test test/link/unmangle \
|
||||
$Q${MAKE} all test/gfx/randtilegen test/gfx/rgbgfx_test \
|
||||
CXX=i686-w64-mingw32-g++ \
|
||||
CXXFLAGS="-O3 -flto -DNDEBUG -static-libgcc -static-libstdc++" \
|
||||
PKG_CONFIG="PKG_CONFIG_SYSROOT_DIR=/usr/i686-w64-mingw32 pkg-config"
|
||||
|
||||
mingw64:
|
||||
$Q${MAKE} all test/gfx/randtilegen test/gfx/rgbgfx_test test/link/unmangle \
|
||||
$Q${MAKE} all test/gfx/randtilegen test/gfx/rgbgfx_test \
|
||||
CXX=x86_64-w64-mingw32-g++ \
|
||||
PKG_CONFIG="PKG_CONFIG_SYSROOT_DIR=/usr/x86_64-w64-mingw32 pkg-config"
|
||||
|
||||
|
||||
@@ -38,8 +38,7 @@ GitHub.
|
||||
4. GitHub Actions will run the [create-release-docs.yml](.github/workflows/create-release-docs.yml)
|
||||
workflow to add the release documentation to [rgbds-www](https://github.com/gbdev/rgbds-www).
|
||||
|
||||
For a release candidate, which creates a prerelease, you will have to
|
||||
take these steps yourself.
|
||||
This is not done automatically for prereleases; if you want to do this manually,
|
||||
|
||||
1. Clone [rgbds-www](https://github.com/gbdev/rgbds-www). You can use
|
||||
`git clone https://github.com/gbdev/rgbds-www.git`.
|
||||
@@ -47,7 +46,7 @@ GitHub.
|
||||
2. Make sure that you have installed `groff` and `mandoc`. You will
|
||||
need `mandoc` 1.14.5 or later to support `-O toc`.
|
||||
|
||||
3. Run <code>.github/actions/get-pages.sh -r <i><path/to/rgbds-www></i> <i><tag></i></code>.
|
||||
3. Inside of the `man` directory, run <code><i><path/to/rgbds-www></i>/maintainer/man_to_html.sh <i><tag></i> *</code> then <code><i><path/to/rgbds-www></i>/maintainer/new_release.sh <i><tag></i></code>.
|
||||
This will render the RGBDS documentation as HTML and PDF and copy it to
|
||||
`rgbds-www`.
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ _rgbasm_completions() {
|
||||
[p]="pad-value:unk"
|
||||
[Q]="q-precision:unk"
|
||||
[r]="recursion-depth:unk"
|
||||
[s]="state:unk"
|
||||
[W]="warning:warning"
|
||||
[X]="max-errors:unk"
|
||||
)
|
||||
@@ -186,6 +187,7 @@ _rgbasm_completions() {
|
||||
nested-comment
|
||||
numeric-string
|
||||
obsolete
|
||||
purge
|
||||
shift
|
||||
shift-amount
|
||||
truncation
|
||||
|
||||
@@ -13,11 +13,15 @@ _rgbgfx_completions() {
|
||||
[O]="group-outputs:normal"
|
||||
[u]="unique-tiles:normal"
|
||||
[v]="verbose:normal"
|
||||
[X]="mirror-x:normal"
|
||||
[Y]="mirror-y:normal"
|
||||
[Z]="columns:normal"
|
||||
[a]="attr-map:glob-*.attrmap"
|
||||
[A]="auto-attr-map:normal"
|
||||
[b]="base-tiles:unk"
|
||||
[c]="colors:unk"
|
||||
[d]="depth:unk"
|
||||
[i]="input-tileset:glob-*.2bpp"
|
||||
[L]="slice:unk"
|
||||
[N]="nb-tiles:unk"
|
||||
[n]="nb-palettes:unk"
|
||||
|
||||
25
contrib/view_palettes.sh
Executable file
25
contrib/view_palettes.sh
Executable file
@@ -0,0 +1,25 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
if [[ $# -ne 2 ]]; then
|
||||
cat <<EOF >&2
|
||||
Usage: $0 <palettes.pal> <output.png>
|
||||
EOF
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TMP=$(mktemp -d)
|
||||
readonly TMP
|
||||
trap 'rm -rf "$TMP"' EXIT
|
||||
|
||||
tile() { for i in {0..7}; do printf "$1"; done }
|
||||
{ tile '\x00\x00' && tile '\xFF\x00' && tile '\x00\xFF' && tile '\xFF\xFF'; } >"$TMP/tmp.2bpp"
|
||||
|
||||
NB_BYTES=$(wc -c <"$1")
|
||||
(( NB_PALS = NB_BYTES / 8 ))
|
||||
for (( i = 0; i < NB_PALS; i++ )); do
|
||||
printf '\0\1\2\3' >>"$TMP/tmp.tilemap"
|
||||
printf $(printf '\\x%x' $i{,,,}) >> "$TMP/tmp.palmap"
|
||||
done
|
||||
|
||||
"${RGBGFX:-${RGBDS+$RGBDS/}rgbgfx}" -r 4 "$2" -o "$TMP/tmp.2bpp" -OTQ -p "$1" -n "$NB_PALS"
|
||||
@@ -21,6 +21,7 @@ _rgbasm_warnings() {
|
||||
'nested-comment:Warn on "/*" inside block comments'
|
||||
'numeric-string:Warn when a multi-character string is treated as a number'
|
||||
'obsolete:Warn when using deprecated features'
|
||||
'purge:Warn when purging exported symbols or labels'
|
||||
'shift:Warn when shifting negative values'
|
||||
'shift-amount:Warn when a shift'\''s operand it negative or \> 32'
|
||||
'truncation:Warn when implicit truncation loses bits'
|
||||
@@ -28,7 +29,7 @@ _rgbasm_warnings() {
|
||||
'user:Warn when executing the WARN built-in'
|
||||
)
|
||||
# TODO: handle `no-` and `error=` somehow?
|
||||
# TODO: handle `=0|1|2` levels for `numeric-string`, `truncation`, and `unmapped-char`?
|
||||
# TODO: handle `=0|1|2` levels for `numeric-string`, `purge`, `truncation`, and `unmapped-char`?
|
||||
_describe warning warnings
|
||||
}
|
||||
|
||||
@@ -44,7 +45,7 @@ local args=(
|
||||
'*'{-D,--define}'+[Define a string symbol]:name + value (default 1):'
|
||||
'(-g --gfx-chars)'{-g,--gfx-chars}'+[Change chars for gfx constants]:chars spec:'
|
||||
'(-I --include)'{-I,--include}'+[Add an include directory]:include path:_files -/'
|
||||
'(-M --dependfile)'{-M,--dependfile}"+[List deps in make format]:output file:_files -g '*.{d,mk}'"
|
||||
'(-M --dependfile)'{-M,--dependfile}"+[Write deps in make format]:output file:_files -g '*.{d,mk}'"
|
||||
-MG'[Assume missing files should be generated]'
|
||||
-MP'[Add phony targets to all deps]'
|
||||
'*'-MT"+[Add a target to the rules]:target:_files -g '*.{d,mk,o}'"
|
||||
@@ -54,6 +55,7 @@ local args=(
|
||||
'(-p --pad-value)'{-p,--pad-value}'+[Set padding byte]:padding byte:'
|
||||
'(-Q --q-precision)'{-Q,--q-precision}'+[Set fixed-point precision]:precision:'
|
||||
'(-r --recursion-depth)'{-r,--recursion-depth}'+[Set maximum recursion depth]:depth:'
|
||||
'(-s --state)'{-s,--state}"+[Write features of final state]:state file:_files -g '*.dump.asm'"
|
||||
'(-W --warning)'{-W,--warning}'+[Toggle warning flags]:warning flag:_rgbasm_warnings'
|
||||
'(-X --max-errors)'{-X,--max-errors}'+[Set maximum errors before aborting]:maximum errors:'
|
||||
|
||||
|
||||
@@ -22,11 +22,15 @@ local args=(
|
||||
'(-t --tilemap -T --auto-tilemap)'{-T,--auto-tilemap}'[Shortcut for -t <file>.tilemap]'
|
||||
'(-u --unique-tiles)'{-u,--unique-tiles}'[Eliminate redundant tiles]'
|
||||
{-v,--verbose}'[Enable verbose output]'
|
||||
'(-X --mirror-x)'{-X,--mirror-x}'[Eliminate horizontally mirrored tiles from output]'
|
||||
'(-Y --mirror-y)'{-Y,--mirror-y}'[Eliminate vertically mirrored tiles from output]'
|
||||
'(-Z --columns)'{-Z,--columns}'[Read the image in column-major order]'
|
||||
|
||||
'(-a --attr-map -A --auto-attr-map)'{-a,--attr-map}'+[Generate a map of tile attributes (mirroring)]:attrmap file:_files'
|
||||
'(-b --base-tiles)'{-b,--base-tiles}'+[Base tile IDs for tile map output]:base tile IDs:'
|
||||
'(-c --colors)'{-c,--colors}'+[Specify color palettes]:palette spec:'
|
||||
'(-d --depth)'{-d,--depth}'+[Set bit depth]:bit depth:_depths'
|
||||
'(-i --input-tileset)'{-i,--input-tileset}'+[Use specific tiles]:tileset file:_files -g "*.2bpp"'
|
||||
'(-L --slice)'{-L,--slice}'+[Only process a portion of the image]:input slice:'
|
||||
'(-N --nb-tiles)'{-N,--nb-tiles}'+[Limit number of tiles]:tile count:'
|
||||
'(-n --nb-palettes)'{-n,--nb-palettes}'+[Limit number of palettes]:palette count:'
|
||||
|
||||
@@ -10,13 +10,17 @@
|
||||
|
||||
#define DEFAULT_CHARMAP_NAME "main"
|
||||
|
||||
bool charmap_ForEach(
|
||||
void (*mapFunc)(std::string const &),
|
||||
void (*charFunc)(std::string const &, std::vector<int32_t>)
|
||||
);
|
||||
void charmap_New(std::string const &name, std::string const *baseName);
|
||||
void charmap_Set(std::string const &name);
|
||||
void charmap_Push();
|
||||
void charmap_Pop();
|
||||
void charmap_Add(std::string const &mapping, uint8_t value);
|
||||
void charmap_Add(std::string const &mapping, std::vector<int32_t> &&value);
|
||||
bool charmap_HasChar(std::string const &input);
|
||||
void charmap_Convert(std::string const &input, std::vector<uint8_t> &output);
|
||||
size_t charmap_ConvertNext(std::string_view &input, std::vector<uint8_t> *output);
|
||||
std::vector<int32_t> charmap_Convert(std::string const &input);
|
||||
size_t charmap_ConvertNext(std::string_view &input, std::vector<int32_t> *output);
|
||||
|
||||
#endif // RGBDS_ASM_CHARMAP_HPP
|
||||
|
||||
@@ -9,10 +9,11 @@
|
||||
|
||||
enum FormatState {
|
||||
FORMAT_SIGN, // expects '+' or ' ' (optional)
|
||||
FORMAT_PREFIX, // expects '#' (optional)
|
||||
FORMAT_EXACT, // expects '#' (optional)
|
||||
FORMAT_ALIGN, // expects '-' (optional)
|
||||
FORMAT_WIDTH, // expects '0'-'9', max 255 (optional) (leading '0' indicates pad)
|
||||
FORMAT_FRAC, // got '.', expects '0'-'9', max 255 (optional)
|
||||
FORMAT_PREC, // got 'q', expects '0'-'9', range 1-31 (optional)
|
||||
FORMAT_DONE, // got [duXxbofs] (required)
|
||||
FORMAT_INVALID, // got unexpected character
|
||||
};
|
||||
@@ -20,12 +21,14 @@ enum FormatState {
|
||||
class FormatSpec {
|
||||
FormatState state;
|
||||
int sign;
|
||||
bool prefix;
|
||||
bool exact;
|
||||
bool alignLeft;
|
||||
bool padZero;
|
||||
size_t width;
|
||||
bool hasFrac;
|
||||
size_t fracWidth;
|
||||
bool hasPrec;
|
||||
size_t precision;
|
||||
int type;
|
||||
bool valid;
|
||||
|
||||
|
||||
@@ -10,16 +10,16 @@
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include "either.hpp"
|
||||
#include "linkdefs.hpp"
|
||||
|
||||
#include "asm/lexer.hpp"
|
||||
|
||||
struct FileStackNode {
|
||||
FileStackNodeType type;
|
||||
std::variant<
|
||||
Either<
|
||||
std::vector<uint32_t>, // NODE_REPT
|
||||
std::string // NODE_FILE, NODE_MACRO
|
||||
>
|
||||
@@ -34,13 +34,13 @@ struct FileStackNode {
|
||||
uint32_t ID = -1;
|
||||
|
||||
// REPT iteration counts since last named node, in reverse depth order
|
||||
std::vector<uint32_t> &iters();
|
||||
std::vector<uint32_t> const &iters() const;
|
||||
std::vector<uint32_t> &iters() { return data.get<std::vector<uint32_t>>(); }
|
||||
std::vector<uint32_t> const &iters() const { return data.get<std::vector<uint32_t>>(); }
|
||||
// File name for files, file::macro name for macros
|
||||
std::string &name();
|
||||
std::string const &name() const;
|
||||
std::string &name() { return data.get<std::string>(); }
|
||||
std::string const &name() const { return data.get<std::string>(); }
|
||||
|
||||
FileStackNode(FileStackNodeType type_, std::variant<std::vector<uint32_t>, std::string> data_)
|
||||
FileStackNode(FileStackNodeType type_, Either<std::vector<uint32_t>, std::string> data_)
|
||||
: type(type_), data(data_){};
|
||||
|
||||
std::string const &dump(uint32_t curLineNo) const;
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
#include <optional>
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include "either.hpp"
|
||||
#include "platform.hpp" // SSIZE_MAX
|
||||
|
||||
// This value is a compromise between `LexerState` allocation performance when `mmap` works, and
|
||||
@@ -98,7 +98,7 @@ struct LexerState {
|
||||
bool expandStrings;
|
||||
std::deque<Expansion> expansions; // Front is the innermost current expansion
|
||||
|
||||
std::variant<std::monostate, ViewedContent, BufferedContent> content;
|
||||
Either<ViewedContent, BufferedContent> content;
|
||||
|
||||
~LexerState();
|
||||
|
||||
@@ -131,6 +131,7 @@ static inline void lexer_SetGfxDigits(char const digits[4]) {
|
||||
gfxDigits[3] = digits[3];
|
||||
}
|
||||
|
||||
bool lexer_AtTopLevel();
|
||||
void lexer_RestartRept(uint32_t lineNo);
|
||||
void lexer_Init();
|
||||
void lexer_SetMode(LexerMode mode);
|
||||
|
||||
@@ -6,13 +6,24 @@
|
||||
#include <memory>
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "linkdefs.hpp"
|
||||
|
||||
struct Expression;
|
||||
struct FileStackNode;
|
||||
|
||||
extern std::string objectName;
|
||||
enum StateFeature {
|
||||
STATE_EQU,
|
||||
STATE_VAR,
|
||||
STATE_EQUS,
|
||||
STATE_CHAR,
|
||||
STATE_MACRO,
|
||||
NB_STATE_FEATURES
|
||||
};
|
||||
|
||||
extern std::string objectFileName;
|
||||
|
||||
void out_RegisterNode(std::shared_ptr<FileStackNode> node);
|
||||
void out_SetFileName(std::string const &name);
|
||||
@@ -21,5 +32,6 @@ void out_CreateAssert(
|
||||
AssertionType type, Expression const &expr, std::string const &message, uint32_t ofs
|
||||
);
|
||||
void out_WriteObject();
|
||||
void out_WriteState(std::string name, std::vector<StateFeature> const &features);
|
||||
|
||||
#endif // RGBDS_ASM_OUTPUT_HPP
|
||||
|
||||
@@ -5,18 +5,19 @@
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include "either.hpp"
|
||||
#include "linkdefs.hpp"
|
||||
|
||||
struct Symbol;
|
||||
|
||||
struct Expression {
|
||||
std::variant<
|
||||
int32_t, // If the expression's value is known, it's here
|
||||
std::string // Why the expression is not known, if it isn't
|
||||
> data = 0;
|
||||
Either<
|
||||
int32_t, // If the expression's value is known, it's here
|
||||
std::string // Why the expression is not known, if it isn't
|
||||
>
|
||||
data = 0;
|
||||
bool isSymbol = false; // Whether the expression represents a symbol suitable for const diffing
|
||||
std::vector<uint8_t> rpn{}; // Bytes serializing the RPN expression
|
||||
uint32_t rpnPatchSize = 0; // Size the expression will take in the object file
|
||||
@@ -30,8 +31,12 @@ struct Expression {
|
||||
|
||||
Expression &operator=(Expression &&) = default;
|
||||
|
||||
bool isKnown() const { return std::holds_alternative<int32_t>(data); }
|
||||
int32_t value() const;
|
||||
bool isKnown() const {
|
||||
return data.holds<int32_t>();
|
||||
}
|
||||
int32_t value() const {
|
||||
return data.get<int32_t>();
|
||||
}
|
||||
|
||||
int32_t getConstVal() const;
|
||||
Symbol const *symbolOf() const;
|
||||
@@ -45,11 +50,7 @@ struct Expression {
|
||||
void makeStartOfSection(std::string const §Name);
|
||||
void makeSizeOfSectionType(SectionType type);
|
||||
void makeStartOfSectionType(SectionType type);
|
||||
void makeHigh();
|
||||
void makeLow();
|
||||
void makeNeg();
|
||||
void makeNot();
|
||||
void makeLogicNot();
|
||||
void makeUnaryOp(RPNCommand op, Expression &&src);
|
||||
void makeBinaryOp(RPNCommand op, Expression &&src1, Expression const &src2);
|
||||
|
||||
void makeCheckHRAM();
|
||||
@@ -63,4 +64,6 @@ private:
|
||||
uint8_t *reserveSpace(uint32_t size, uint32_t patchSize);
|
||||
};
|
||||
|
||||
bool checkNBit(int32_t v, uint8_t n, char const *name);
|
||||
|
||||
#endif // RGBDS_ASM_RPN_HPP
|
||||
|
||||
@@ -78,15 +78,17 @@ uint32_t sect_GetOutputOffset();
|
||||
uint32_t sect_GetAlignBytes(uint8_t alignment, uint16_t offset);
|
||||
void sect_AlignPC(uint8_t alignment, uint16_t offset);
|
||||
|
||||
void sect_CheckSizes();
|
||||
|
||||
void sect_StartUnion();
|
||||
void sect_NextUnionMember();
|
||||
void sect_EndUnion();
|
||||
void sect_CheckUnionClosed();
|
||||
|
||||
void sect_AbsByte(uint8_t b);
|
||||
void sect_AbsByteGroup(uint8_t const *s, size_t length);
|
||||
void sect_AbsWordGroup(uint8_t const *s, size_t length);
|
||||
void sect_AbsLongGroup(uint8_t const *s, size_t length);
|
||||
void sect_ConstByte(uint8_t byte);
|
||||
void sect_ByteString(std::vector<int32_t> const &string);
|
||||
void sect_WordString(std::vector<int32_t> const &string);
|
||||
void sect_LongString(std::vector<int32_t> const &string);
|
||||
void sect_Skip(uint32_t skip, bool ds);
|
||||
void sect_RelByte(Expression &expr, uint32_t pcShift);
|
||||
void sect_RelBytes(uint32_t n, std::vector<Expression> &exprs);
|
||||
|
||||
@@ -4,12 +4,12 @@
|
||||
#define RGBDS_ASM_SYMBOL_HPP
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <time.h>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
|
||||
#include "asm/lexer.hpp"
|
||||
@@ -37,14 +37,16 @@ struct Symbol {
|
||||
uint32_t fileLine; // Line where the symbol was defined
|
||||
|
||||
std::variant<
|
||||
int32_t, // If isNumeric()
|
||||
int32_t (*)(), // If isNumeric() and has a callback
|
||||
ContentSpan, // For SYM_MACRO
|
||||
std::shared_ptr<std::string> // For SYM_EQUS
|
||||
int32_t, // If isNumeric()
|
||||
int32_t (*)(), // If isNumeric() via a callback
|
||||
ContentSpan, // For SYM_MACRO
|
||||
std::shared_ptr<std::string>, // For SYM_EQUS
|
||||
std::shared_ptr<std::string> (*)() // For SYM_EQUS via a callback
|
||||
>
|
||||
data;
|
||||
|
||||
uint32_t ID; // ID of the symbol in the object file (-1 if none)
|
||||
uint32_t ID; // ID of the symbol in the object file (-1 if none)
|
||||
uint32_t defIndex; // Ordering of the symbol in the state file
|
||||
|
||||
bool isDefined() const { return type != SYM_REF; }
|
||||
bool isNumeric() const { return type == SYM_LABEL || type == SYM_EQU || type == SYM_VAR; }
|
||||
@@ -67,7 +69,7 @@ struct Symbol {
|
||||
uint32_t getConstantValue() const;
|
||||
};
|
||||
|
||||
void sym_ForEach(void (*func)(Symbol &));
|
||||
void sym_ForEach(void (*callback)(Symbol &));
|
||||
|
||||
void sym_SetExportAll(bool set);
|
||||
Symbol *sym_AddLocalLabel(std::string const &symName);
|
||||
@@ -78,7 +80,6 @@ void sym_Export(std::string const &symName);
|
||||
Symbol *sym_AddEqu(std::string const &symName, int32_t value);
|
||||
Symbol *sym_RedefEqu(std::string const &symName, int32_t value);
|
||||
Symbol *sym_AddVar(std::string const &symName, int32_t value);
|
||||
uint32_t sym_GetPCValue();
|
||||
int32_t sym_GetRSValue();
|
||||
void sym_SetRSValue(int32_t value);
|
||||
uint32_t sym_GetConstantValue(std::string const &symName);
|
||||
@@ -94,10 +95,13 @@ Symbol *sym_Ref(std::string const &symName);
|
||||
Symbol *sym_AddString(std::string const &symName, std::shared_ptr<std::string> value);
|
||||
Symbol *sym_RedefString(std::string const &symName, std::shared_ptr<std::string> value);
|
||||
void sym_Purge(std::string const &symName);
|
||||
bool sym_IsPurgedExact(std::string const &symName);
|
||||
bool sym_IsPurgedScoped(std::string const &symName);
|
||||
void sym_Init(time_t now);
|
||||
|
||||
// Functions to save and restore the current symbol scope.
|
||||
std::optional<std::string> const &sym_GetCurrentSymbolScope();
|
||||
void sym_SetCurrentSymbolScope(std::optional<std::string> const &newScope);
|
||||
// Functions to save and restore the current label scopes.
|
||||
std::pair<Symbol const *, Symbol const *> sym_GetCurrentLabelScopes();
|
||||
void sym_SetCurrentLabelScopes(std::pair<Symbol const *, Symbol const *> newScopes);
|
||||
void sym_ResetCurrentLabelScopes();
|
||||
|
||||
#endif // RGBDS_ASM_SYMBOL_HPP
|
||||
|
||||
@@ -31,6 +31,9 @@ enum WarningID {
|
||||
// Treating string as number may lose some bits
|
||||
WARNING_NUMERIC_STRING_1 = PARAM_WARNINGS_START,
|
||||
WARNING_NUMERIC_STRING_2,
|
||||
// Purging an exported symbol or label
|
||||
WARNING_PURGE_1,
|
||||
WARNING_PURGE_2,
|
||||
// Implicit truncation loses some bits
|
||||
WARNING_TRUNCATION_1,
|
||||
WARNING_TRUNCATION_2,
|
||||
|
||||
176
include/either.hpp
Normal file
176
include/either.hpp
Normal file
@@ -0,0 +1,176 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
#ifndef RGBDS_EITHER_HPP
|
||||
#define RGBDS_EITHER_HPP
|
||||
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include "helpers.hpp" // assume
|
||||
|
||||
template<typename T1, typename T2>
|
||||
union Either {
|
||||
typedef T1 type1;
|
||||
typedef T2 type2;
|
||||
|
||||
private:
|
||||
template<typename T, unsigned V>
|
||||
struct Field {
|
||||
constexpr static unsigned tag_value = V;
|
||||
|
||||
unsigned tag = tag_value;
|
||||
T value;
|
||||
|
||||
Field() : value() {}
|
||||
Field(T &value_) : value(value_) {}
|
||||
Field(T const &value_) : value(value_) {}
|
||||
Field(T &&value_) : value(std::move(value_)) {}
|
||||
};
|
||||
|
||||
// The `_tag` unifies with the first `tag` member of each `struct`.
|
||||
constexpr static unsigned nulltag = 0;
|
||||
unsigned _tag = nulltag;
|
||||
Field<T1, 1> _t1;
|
||||
Field<T2, 2> _t2;
|
||||
|
||||
// Value accessors; the function parameters are dummies for overload resolution.
|
||||
// Only used to implement `field()` below.
|
||||
auto &pick(T1 *) { return _t1; }
|
||||
auto const &pick(T1 *) const { return _t1; }
|
||||
auto &pick(T2 *) { return _t2; }
|
||||
auto const &pick(T2 *) const { return _t2; }
|
||||
|
||||
// Generic field accessors; for internal use only.
|
||||
template<typename T>
|
||||
auto &field() {
|
||||
return pick((T *)nullptr);
|
||||
}
|
||||
template<typename T>
|
||||
auto const &field() const {
|
||||
return pick((T *)nullptr);
|
||||
}
|
||||
|
||||
public:
|
||||
// Equivalent of `std::monostate` for `std::variant`s.
|
||||
Either() : _tag() {}
|
||||
// These constructors cannot be generic over the value type, because that would prevent
|
||||
// constructible values from being inferred, e.g. a `const char *` string literal for an
|
||||
// `std::string` field value.
|
||||
Either(T1 &value) : _t1(value) {}
|
||||
Either(T2 &value) : _t2(value) {}
|
||||
Either(T1 const &value) : _t1(value) {}
|
||||
Either(T2 const &value) : _t2(value) {}
|
||||
Either(T1 &&value) : _t1(std::move(value)) {}
|
||||
Either(T2 &&value) : _t2(std::move(value)) {}
|
||||
|
||||
// Destructor manually calls the appropriate value destructor.
|
||||
~Either() {
|
||||
if (_tag == _t1.tag_value) {
|
||||
_t1.value.~T1();
|
||||
} else if (_tag == _t2.tag_value) {
|
||||
_t2.value.~T2();
|
||||
}
|
||||
}
|
||||
|
||||
// Copy assignment operators for each possible value.
|
||||
Either &operator=(T1 const &value) {
|
||||
_t1.tag = _t1.tag_value;
|
||||
new (&_t1.value) T1(value);
|
||||
return *this;
|
||||
}
|
||||
Either &operator=(T2 const &value) {
|
||||
_t2.tag = _t2.tag_value;
|
||||
new (&_t2.value) T2(value);
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Move assignment operators for each possible value.
|
||||
Either &operator=(T1 &&value) {
|
||||
_t1.tag = _t1.tag_value;
|
||||
new (&_t1.value) T1(std::move(value));
|
||||
return *this;
|
||||
}
|
||||
Either &operator=(T2 &&value) {
|
||||
_t2.tag = _t2.tag_value;
|
||||
new (&_t2.value) T2(std::move(value));
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Copy assignment operator from another `Either`.
|
||||
Either &operator=(Either other) {
|
||||
if (other._tag == other._t1.tag_value) {
|
||||
*this = other._t1.value;
|
||||
} else if (other._tag == other._t2.tag_value) {
|
||||
*this = other._t2.value;
|
||||
} else {
|
||||
_tag = nulltag;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Copy constructor from another `Either`; implemented in terms of value assignment operators.
|
||||
Either(Either const &other) {
|
||||
if (other._tag == other._t1.tag_value) {
|
||||
*this = other._t1.value;
|
||||
} else if (other._tag == other._t2.tag_value) {
|
||||
*this = other._t2.value;
|
||||
} else {
|
||||
_tag = nulltag;
|
||||
}
|
||||
}
|
||||
|
||||
// Move constructor from another `Either`; implemented in terms of value assignment operators.
|
||||
Either(Either &&other) {
|
||||
if (other._tag == other._t1.tag_value) {
|
||||
*this = std::move(other._t1.value);
|
||||
} else if (other._tag == other._t2.tag_value) {
|
||||
*this = std::move(other._t2.value);
|
||||
} else {
|
||||
_tag = nulltag;
|
||||
}
|
||||
}
|
||||
|
||||
// Equivalent of `.emplace<T>()` for `std::variant`s.
|
||||
template<typename T, typename... Args>
|
||||
void emplace(Args &&...args) {
|
||||
this->~Either();
|
||||
if constexpr (std::is_same_v<T, T1>) {
|
||||
_t1.tag = _t1.tag_value;
|
||||
new (&_t1.value) T1(std::forward<Args>(args)...);
|
||||
} else if constexpr (std::is_same_v<T, T2>) {
|
||||
_t2.tag = _t2.tag_value;
|
||||
new (&_t2.value) T2(std::forward<Args>(args)...);
|
||||
} else {
|
||||
_tag = nulltag;
|
||||
}
|
||||
}
|
||||
|
||||
// Equivalent of `std::holds_alternative<std::monostate>()` for `std::variant`s.
|
||||
bool empty() const { return _tag == nulltag; }
|
||||
|
||||
// Equivalent of `std::holds_alternative<T>()` for `std::variant`s.
|
||||
template<typename T>
|
||||
bool holds() const {
|
||||
if constexpr (std::is_same_v<T, T1>) {
|
||||
return _tag == _t1.tag_value;
|
||||
} else if constexpr (std::is_same_v<T, T2>) {
|
||||
return _tag == _t2.tag_value;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Equivalent of `std::get<T>()` for `std::variant`s.
|
||||
template<typename T>
|
||||
auto &get() {
|
||||
assume(holds<T>());
|
||||
return field<T>().value;
|
||||
}
|
||||
template<typename T>
|
||||
auto const &get() const {
|
||||
assume(holds<T>());
|
||||
return field<T>().value;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // RGBDS_EITHER_HPP
|
||||
@@ -10,8 +10,8 @@
|
||||
#include <streambuf>
|
||||
#include <string.h>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
|
||||
#include "either.hpp"
|
||||
#include "helpers.hpp" // assume
|
||||
#include "platform.hpp"
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
class File {
|
||||
// Construct a `std::streambuf *` by default, since it's probably lighter than a `filebuf`.
|
||||
std::variant<std::streambuf *, std::filebuf> _file;
|
||||
Either<std::streambuf *, std::filebuf> _file;
|
||||
|
||||
public:
|
||||
File() {}
|
||||
@@ -31,7 +31,8 @@ public:
|
||||
*/
|
||||
File *open(std::string const &path, std::ios_base::openmode mode) {
|
||||
if (path != "-") {
|
||||
return _file.emplace<std::filebuf>().open(path, mode) ? this : nullptr;
|
||||
_file.emplace<std::filebuf>();
|
||||
return _file.get<std::filebuf>().open(path, mode) ? this : nullptr;
|
||||
} else if (mode & std::ios_base::in) {
|
||||
assume(!(mode & std::ios_base::out));
|
||||
_file.emplace<std::streambuf *>(std::cin.rdbuf());
|
||||
@@ -49,8 +50,8 @@ public:
|
||||
return this;
|
||||
}
|
||||
std::streambuf &operator*() {
|
||||
auto *file = std::get_if<std::filebuf>(&_file);
|
||||
return file ? *file : *std::get<std::streambuf *>(_file);
|
||||
return _file.holds<std::filebuf>() ? _file.get<std::filebuf>()
|
||||
: *_file.get<std::streambuf *>();
|
||||
}
|
||||
std::streambuf const &operator*() const {
|
||||
// The non-`const` version does not perform any modifications, so it's okay.
|
||||
@@ -63,22 +64,23 @@ public:
|
||||
}
|
||||
|
||||
File *close() {
|
||||
if (auto *file = std::get_if<std::filebuf>(&_file); file) {
|
||||
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 (file->close() != nullptr) {
|
||||
if (fileBuf.close() != nullptr) {
|
||||
return this;
|
||||
}
|
||||
} else if (std::get<std::streambuf *>(_file) != nullptr) {
|
||||
} else if (_file.get<std::streambuf *>() != nullptr) {
|
||||
return this;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
char const *c_str(std::string const &path) const {
|
||||
return std::holds_alternative<std::filebuf>(_file) ? path.c_str()
|
||||
: std::get<std::streambuf *>(_file) == std::cin.rdbuf() ? "<stdin>"
|
||||
: "<stdout>";
|
||||
return _file.holds<std::filebuf>() ? path.c_str()
|
||||
: _file.get<std::streambuf *>() == std::cin.rdbuf() ? "<stdin>"
|
||||
: "<stdout>";
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -13,14 +13,12 @@
|
||||
#include "gfx/rgba.hpp"
|
||||
|
||||
struct Options {
|
||||
uint16_t reversedWidth = 0; // -r, in tiles
|
||||
bool reverse() const { return reversedWidth != 0; }
|
||||
|
||||
bool useColorCurve = false; // -C
|
||||
bool allowMirroring = false; // -m
|
||||
bool allowDedup = false; // -u
|
||||
bool columnMajor = false; // -Z, previously -h
|
||||
uint8_t verbosity = 0; // -v
|
||||
bool useColorCurve = false; // -C
|
||||
bool allowDedup = false; // -u
|
||||
bool allowMirroringX = false; // -X, -m
|
||||
bool allowMirroringY = false; // -Y, -m
|
||||
bool columnMajor = false; // -Z
|
||||
uint8_t verbosity = 0; // -v
|
||||
|
||||
std::string attrmap{}; // -a, -A
|
||||
std::array<uint8_t, 2> baseTileIDs{0, 0}; // -b
|
||||
@@ -30,7 +28,8 @@ struct Options {
|
||||
EMBEDDED,
|
||||
} palSpecType = NO_SPEC; // -c
|
||||
std::vector<std::array<std::optional<Rgba>, 4>> palSpec{};
|
||||
uint8_t bitDepth = 2; // -d
|
||||
uint8_t bitDepth = 2; // -d
|
||||
std::string inputTileset{}; // -i
|
||||
struct {
|
||||
uint16_t left;
|
||||
uint16_t top;
|
||||
@@ -38,10 +37,11 @@ struct Options {
|
||||
uint16_t height;
|
||||
} inputSlice{0, 0, 0, 0}; // -L (margins in clockwise order, like CSS)
|
||||
std::array<uint16_t, 2> maxNbTiles{UINT16_MAX, 0}; // -N
|
||||
uint8_t nbPalettes = 8; // -n
|
||||
uint16_t nbPalettes = 8; // -n
|
||||
std::string output{}; // -o
|
||||
std::string palettes{}; // -p, -P
|
||||
std::string palmap{}; // -q, -Q
|
||||
uint16_t reversedWidth = 0; // -r, in tiles
|
||||
uint8_t nbColorsPerPal = 0; // -s; 0 means "auto" = 1 << bitDepth;
|
||||
std::string tilemap{}; // -t, -T
|
||||
uint64_t trim = 0; // -x
|
||||
@@ -67,6 +67,10 @@ extern Options options;
|
||||
* Prints the error count, and exits with failure
|
||||
*/
|
||||
[[noreturn]] void giveUp();
|
||||
/*
|
||||
* If any error has been emitted thus far, calls `giveUp()`.
|
||||
*/
|
||||
void requireZeroErrors();
|
||||
/*
|
||||
* Prints a warning, and does not change the error count
|
||||
*/
|
||||
|
||||
@@ -6,14 +6,14 @@
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include "either.hpp"
|
||||
#include "linkdefs.hpp"
|
||||
|
||||
// Variables related to CLI options
|
||||
extern bool isDmgMode;
|
||||
extern char *linkerScriptName;
|
||||
extern char const *linkerScriptName;
|
||||
extern char const *mapFileName;
|
||||
extern bool noSymInMap;
|
||||
extern char const *symFileName;
|
||||
@@ -38,8 +38,7 @@ extern bool disablePadding;
|
||||
|
||||
struct FileStackNode {
|
||||
FileStackNodeType type;
|
||||
std::variant<
|
||||
std::monostate, // Default constructed; `.type` and `.data` must be set manually
|
||||
Either<
|
||||
std::vector<uint32_t>, // NODE_REPT
|
||||
std::string // NODE_FILE, NODE_MACRO
|
||||
>
|
||||
@@ -50,11 +49,11 @@ struct FileStackNode {
|
||||
uint32_t lineNo;
|
||||
|
||||
// REPT iteration counts since last named node, in reverse depth order
|
||||
std::vector<uint32_t> &iters();
|
||||
std::vector<uint32_t> const &iters() const;
|
||||
std::vector<uint32_t> &iters() { return data.get<std::vector<uint32_t>>(); }
|
||||
std::vector<uint32_t> const &iters() const { return data.get<std::vector<uint32_t>>(); }
|
||||
// File name for files, file::macro name for macros
|
||||
std::string &name();
|
||||
std::string const &name() const;
|
||||
std::string &name() { return data.get<std::string>(); }
|
||||
std::string const &name() const { return data.get<std::string>(); }
|
||||
|
||||
std::string const &dump(uint32_t curLineNo) const;
|
||||
};
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#ifndef RGBDS_LINK_OUTPUT_HPP
|
||||
#define RGBDS_LINK_OUTPUT_HPP
|
||||
|
||||
#include "link/section.hpp"
|
||||
struct Section;
|
||||
|
||||
/*
|
||||
* Registers a section for output.
|
||||
|
||||
@@ -46,6 +46,8 @@ struct Section {
|
||||
bool isAlignFixed;
|
||||
uint16_t alignMask;
|
||||
uint16_t alignOfs;
|
||||
FileStackNode const *src;
|
||||
int32_t lineNo;
|
||||
std::vector<uint8_t> data; // Array of size `size`, or 0 if `type` does not have data
|
||||
std::vector<Patch> patches;
|
||||
// Extra info computed during linking
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
|
||||
#include "either.hpp"
|
||||
#include "linkdefs.hpp"
|
||||
|
||||
struct FileStackNode;
|
||||
@@ -25,19 +25,20 @@ struct Symbol {
|
||||
// Info contained in the object files
|
||||
std::string name;
|
||||
ExportLevel type;
|
||||
char const *objFileName;
|
||||
FileStackNode const *src;
|
||||
int32_t lineNo;
|
||||
std::variant<
|
||||
Either<
|
||||
int32_t, // Constants just have a numeric value
|
||||
Label // Label values refer to an offset within a specific section
|
||||
>
|
||||
data;
|
||||
|
||||
Label &label();
|
||||
Label const &label() const;
|
||||
Label &label() { return data.get<Label>(); }
|
||||
Label const &label() const { return data.get<Label>(); }
|
||||
};
|
||||
|
||||
void sym_ForEach(void (*callback)(Symbol &));
|
||||
|
||||
void sym_AddSymbol(Symbol &symbol);
|
||||
|
||||
/*
|
||||
@@ -47,4 +48,6 @@ void sym_AddSymbol(Symbol &symbol);
|
||||
*/
|
||||
Symbol *sym_GetSymbol(std::string const &name);
|
||||
|
||||
void sym_DumpLocalAliasedSymbols(std::string const &name);
|
||||
|
||||
#endif // RGBDS_LINK_SYMBOL_HPP
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#include "helpers.hpp" // assume
|
||||
|
||||
#define RGBDS_OBJECT_VERSION_STRING "RGB9"
|
||||
#define RGBDS_OBJECT_REV 10U
|
||||
#define RGBDS_OBJECT_REV 11U
|
||||
|
||||
enum AssertionType { ASSERT_WARN, ASSERT_ERROR, ASSERT_FATAL };
|
||||
|
||||
@@ -53,6 +53,11 @@ enum RPNCommand {
|
||||
RPN_HRAM = 0x60,
|
||||
RPN_RST = 0x61,
|
||||
|
||||
RPN_HIGH = 0x70,
|
||||
RPN_LOW = 0x71,
|
||||
RPN_BITWIDTH = 0x72,
|
||||
RPN_TZCOUNT = 0x73,
|
||||
|
||||
RPN_CONST = 0x80,
|
||||
RPN_SYM = 0x81
|
||||
};
|
||||
|
||||
@@ -12,6 +12,6 @@ char const *printChar(int c);
|
||||
/*
|
||||
* @return The number of bytes read, or 0 if invalid data was found
|
||||
*/
|
||||
size_t readUTF8Char(std::vector<uint8_t> *dest, char const *src);
|
||||
size_t readUTF8Char(std::vector<int32_t> *dest, char const *src);
|
||||
|
||||
#endif // RGBDS_UTIL_HPP
|
||||
|
||||
@@ -6,8 +6,9 @@
|
||||
extern "C" {
|
||||
|
||||
#define PACKAGE_VERSION_MAJOR 0
|
||||
#define PACKAGE_VERSION_MINOR 8
|
||||
#define PACKAGE_VERSION_MINOR 9
|
||||
#define PACKAGE_VERSION_PATCH 0
|
||||
#define PACKAGE_VERSION_RC 1
|
||||
|
||||
char const *get_package_version_string();
|
||||
}
|
||||
|
||||
22
man/gbz80.7
22
man/gbz80.7
@@ -1,6 +1,6 @@
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd December 22, 2023
|
||||
.Dd September 18, 2024
|
||||
.Dt GBZ80 7
|
||||
.Os
|
||||
.Sh NAME
|
||||
@@ -11,16 +11,28 @@ This is the list of opcodes supported by
|
||||
.Xr rgbasm 1 ,
|
||||
including a short description, the number of bytes needed to encode them and the number of CPU cycles at 1MHz (or 2MHz in GBC dual speed mode) needed to complete them.
|
||||
.Pp
|
||||
Note: All arithmetic/logic operations that use register
|
||||
Note: All arithmetic/logic instructions that use register
|
||||
.Sy A
|
||||
as destination can omit the destination as it is assumed to be register
|
||||
as a destination can omit the destination, since it is assumed to be register
|
||||
.Sy A
|
||||
by default.
|
||||
The following two lines have the same effect:
|
||||
So the following two lines have the same effect:
|
||||
.Bd -literal -offset indent
|
||||
OR A,B
|
||||
OR B
|
||||
.Ed
|
||||
.Pp
|
||||
Furthermore, the
|
||||
.Sy CPL
|
||||
instruction can take an optional
|
||||
.Sy A
|
||||
destination, since it can only be register
|
||||
.Sy A .
|
||||
So the following two lines have the same effect:
|
||||
.Bd -literal -offset indent
|
||||
CPL
|
||||
CPL A
|
||||
.Ed
|
||||
.Sh LEGEND
|
||||
List of abbreviations used in this document.
|
||||
.Bl -tag -width Ds
|
||||
@@ -57,8 +69,6 @@ Execute if Z is not set.
|
||||
Execute if C is set.
|
||||
.It Sy NC
|
||||
Execute if C is not set.
|
||||
.It Sy ! cc
|
||||
Negates a condition code.
|
||||
.El
|
||||
.It Ar vec
|
||||
One of the
|
||||
|
||||
64
man/rgbasm.1
64
man/rgbasm.1
@@ -1,6 +1,6 @@
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd December 22, 2023
|
||||
.Dd September 18, 2024
|
||||
.Dt RGBASM 1
|
||||
.Os
|
||||
.Sh NAME
|
||||
@@ -8,7 +8,7 @@
|
||||
.Nd Game Boy assembler
|
||||
.Sh SYNOPSIS
|
||||
.Nm
|
||||
.Op Fl EHhLlVvw
|
||||
.Op Fl EVvw
|
||||
.Op Fl b Ar chars
|
||||
.Op Fl D Ar name Ns Op = Ns Ar value
|
||||
.Op Fl g Ar chars
|
||||
@@ -23,6 +23,7 @@
|
||||
.Op Fl p Ar pad_value
|
||||
.Op Fl Q Ar fix_precision
|
||||
.Op Fl r Ar recursion_depth
|
||||
.Op Fl s Ar features Ns : Ns Ar state_file
|
||||
.Op Fl W Ar warning
|
||||
.Op Fl X Ar max_errors
|
||||
.Ar asmfile
|
||||
@@ -82,7 +83,7 @@ first looks up the provided path from its working directory; if this fails, it t
|
||||
.Dq include path
|
||||
directories, in the order they were provided.
|
||||
.It Fl M Ar depend_file , Fl \-dependfile Ar depend_file
|
||||
Print
|
||||
Write
|
||||
.Xr make 1
|
||||
dependencies to
|
||||
.Ar depend_file .
|
||||
@@ -143,8 +144,45 @@ The argument may start with a
|
||||
to match the Q notation, for example,
|
||||
.Ql Fl Q Ar .16 .
|
||||
.It Fl r Ar recursion_depth , Fl \-recursion-depth Ar recursion_depth
|
||||
Specifies the recursion depth past which RGBASM will assume being in an infinite loop.
|
||||
Specifies the recursion depth past which
|
||||
.Nm
|
||||
will assume being in an infinite loop.
|
||||
The default is 64.
|
||||
.It Fl s Ar features Ns : Ns Ar state_file , Fl \-state Ar features Ns : Ns Ar state_file
|
||||
Write the specified
|
||||
.Ar features
|
||||
to
|
||||
.Ar state_file ,
|
||||
based on the final state of
|
||||
.Nm
|
||||
at the end of its input.
|
||||
The expected
|
||||
.Ar features
|
||||
are a comma-separated subset of the following:
|
||||
.Bl -tag -width Ds
|
||||
.It Cm equ
|
||||
Write all numeric constants as
|
||||
.Ql Ic def Ar name Ic equ Ar value .
|
||||
.It Cm var
|
||||
Write all variables as
|
||||
.Ql Ic def Ar name Ic = Ar value .
|
||||
.It Cm equs
|
||||
Write all string constants as
|
||||
.Ql Ic def Ar name Ic equs Qq Ar value .
|
||||
.It Cm char
|
||||
Write all characters as
|
||||
.Ql Ic charmap Ar name , Ar value .
|
||||
.It Cm macro
|
||||
Write all macros as
|
||||
.Ql Ic macro Ar name No ... Ic endm .
|
||||
.It Cm all
|
||||
Acts like
|
||||
.Cm equ,var,equs,char,macro .
|
||||
.El
|
||||
.Pp
|
||||
This flag may be specified multiple times with different feature subsets to write them to different files (see
|
||||
.Sx EXAMPLES
|
||||
below).
|
||||
.It Fl V , Fl \-version
|
||||
Print the version of the program and exit.
|
||||
.It Fl v , Fl \-verbose
|
||||
@@ -271,6 +309,18 @@ or just
|
||||
warns about strings longer than four characters, since four or fewer characters fit within a 32-bit integer.
|
||||
.Fl Wnumeric-string=2
|
||||
warns about any multi-character string.
|
||||
.It Fl Wpurge=
|
||||
Warn when purging symbols which are likely to have been necessary.
|
||||
.Fl Wpurge=0
|
||||
or
|
||||
.Fl Wno-purge
|
||||
disables this warning.
|
||||
.Fl Wpurge=1
|
||||
or just
|
||||
.Fl Wpurge
|
||||
warns when purging any exported symbol (regardless of type).
|
||||
.Fl Wpurge=2
|
||||
also warns when purging any label (even if not exported).
|
||||
.It Fl Wshift
|
||||
Warn when shifting right a negative value.
|
||||
Use a division by 2**N instead.
|
||||
@@ -327,6 +377,12 @@ The resulting object file is not yet a usable ROM image\(emit must first be run
|
||||
.Xr rgblink 1
|
||||
and then
|
||||
.Xr rgbfix 1 .
|
||||
.Pp
|
||||
Writing the final assembler state to a file:
|
||||
.Dl $ rgbasm -s all:state.dump.asm foo.asm
|
||||
.Pp
|
||||
Or to multiple files:
|
||||
.Dl $ rgbasm -s equ,var:numbers.dump.asm -s equs:strings.dump.asm foo.asm
|
||||
.Sh BUGS
|
||||
Please report bugs on
|
||||
.Lk https://github.com/gbdev/rgbds/issues GitHub .
|
||||
|
||||
1135
man/rgbasm.5
1135
man/rgbasm.5
File diff suppressed because it is too large
Load Diff
14
man/rgbds.5
14
man/rgbds.5
@@ -1,6 +1,6 @@
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd December 22, 2023
|
||||
.Dd September 18, 2024
|
||||
.Dt RGBDS 5
|
||||
.Os
|
||||
.Sh NAME
|
||||
@@ -145,6 +145,10 @@ If the symbol belongs to a section, this is the offset within that symbol's sect
|
||||
.Bl -tag -width Ds -compact
|
||||
.It Cm STRING Ar Name
|
||||
The section's name.
|
||||
.It Cm LONG Ar NodeID
|
||||
Context in which the section was defined.
|
||||
.It Cm LONG Ar LineNo
|
||||
Line number in the context at which the section was defined.
|
||||
.It Cm LONG Ar Size
|
||||
The section's size, in bytes.
|
||||
.It Cm BYTE Ar Type
|
||||
@@ -391,6 +395,14 @@ The value is then ORed with $C7
|
||||
.It Li $80 Ta Integer literal; followed by the
|
||||
.Cm LONG
|
||||
integer.
|
||||
.It Li $70 Ta Cm HIGH
|
||||
byte.
|
||||
.It Li $71 Ta Cm LOW
|
||||
byte.
|
||||
.It Li $72 Ta Cm BITWIDTH
|
||||
value.
|
||||
.It Li $73 Ta Cm TZCOUNT
|
||||
value.
|
||||
.It Li $81 Ta A symbol's value; followed by the symbol's
|
||||
.Cm LONG
|
||||
ID.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd December 22, 2023
|
||||
.Dd September 18, 2024
|
||||
.Dt RGBDS 7
|
||||
.Os
|
||||
.Sh NAME
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd December 22, 2023
|
||||
.Dd September 18, 2024
|
||||
.Dt RGBFIX 1
|
||||
.Os
|
||||
.Sh NAME
|
||||
|
||||
79
man/rgbgfx.1
79
man/rgbgfx.1
@@ -2,7 +2,7 @@
|
||||
.\"
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd December 22, 2023
|
||||
.Dd September 18, 2024
|
||||
.Dt RGBGFX 1
|
||||
.Os
|
||||
.Sh NAME
|
||||
@@ -14,15 +14,16 @@
|
||||
.Op Fl v Op Fl v No ...
|
||||
.Op Fl a Ar attrmap | Fl A
|
||||
.Op Fl b Ar base_ids
|
||||
.Op Fl c Ar color_spec
|
||||
.Op Fl c Ar pal_spec
|
||||
.Op Fl d Ar depth
|
||||
.Op Fl i Ar input_tiles
|
||||
.Op Fl L Ar slice
|
||||
.Op Fl N Ar nb_tiles
|
||||
.Op Fl n Ar nb_pals
|
||||
.Op Fl o Ar out_file
|
||||
.Op Fl p Ar pal_file | Fl P
|
||||
.Op Fl q Ar pal_map | Fl Q
|
||||
.Op Fl r Ar stride
|
||||
.Op Fl r Ar width
|
||||
.Op Fl s Ar nb_colors
|
||||
.Op Fl t Ar tilemap | Fl T
|
||||
.Op Fl x Ar quantity
|
||||
@@ -111,16 +112,16 @@ Both default to 0.
|
||||
When generating palettes, use a color curve mimicking the Game Boy Color's screen.
|
||||
The resulting colors may look closer to the input image's
|
||||
.Sy on hardware and accurate emulators .
|
||||
.It Fl c Ar color_spec , Fl \-colors Ar color_spec
|
||||
.It Fl c Ar pal_spec , Fl \-colors Ar pal_spec
|
||||
Use the specified color palettes instead of having
|
||||
.Nm
|
||||
automatically determine some.
|
||||
.Ar color_spec
|
||||
.Ar pal_spec
|
||||
can be one of the following:
|
||||
.Bl -tag -width Ds
|
||||
.It Sy inline palette spec
|
||||
If
|
||||
.Ar color_spec
|
||||
.Ar pal_spec
|
||||
begins with a hash character
|
||||
.Ql # ,
|
||||
it is treated as an inline palette specification.
|
||||
@@ -139,7 +140,7 @@ See
|
||||
for an example of an inline palette specification.
|
||||
.It Sy embedded palette spec
|
||||
If
|
||||
.Ar color_spec
|
||||
.Ar pal_spec
|
||||
is the case-insensitive word
|
||||
.Cm embedded ,
|
||||
then the first four colors of the input PNG's embedded palette are used.
|
||||
@@ -147,7 +148,7 @@ It is an error if the PNG is not indexed, or if colors other than these 4 are us
|
||||
.Pq This is different from the default behavior of indexed PNGs, as then unused entries in the embedded palette are ignored, whereas they are not with Fl c Cm embedded .
|
||||
.It Sy external palette spec
|
||||
Otherwise,
|
||||
.Ar color_spec
|
||||
.Ar pal_spec
|
||||
is assumed to be an external palette specification.
|
||||
The expected format is
|
||||
.Ql format:path ,
|
||||
@@ -164,6 +165,57 @@ for a list of formats and their descriptions.
|
||||
.It Fl d Ar depth , Fl \-depth Ar depth
|
||||
Set the bit depth of the output tile data, in bits per pixel (bpp), either 1 or 2 (the default).
|
||||
This changes how tile data is output, and the maximum number of colors per palette (2 and 4 respectively).
|
||||
.It Fl i Ar input_tiles , Fl \-input-tileset Ar input_tiles
|
||||
Use the specified input tiles in addition to having
|
||||
.Nm
|
||||
automatically determine some.
|
||||
The input tiles will always be first in the
|
||||
.Fl o
|
||||
image output, and will always get the first IDs in the
|
||||
.Fl t
|
||||
tilemap output.
|
||||
.Ar input_tiles
|
||||
must contain 1bpp or 2bpp tile data
|
||||
.Pq whichever matches the Fl d No option used here ,
|
||||
as could be previously generated with the
|
||||
.Fl o
|
||||
option.
|
||||
.Pp
|
||||
If the
|
||||
.Fl o
|
||||
option is also specified, then the input tiles will be assigned the first tile IDs, and any tiles from the input image that are not in the input tileset will be assigned subsequent IDs.
|
||||
But if the
|
||||
.Fl o
|
||||
option is
|
||||
.Em not
|
||||
specified, then the tile map can
|
||||
.Em only
|
||||
use tiles from the input tileset.
|
||||
Using
|
||||
.Fl o
|
||||
with
|
||||
.Fl i
|
||||
is useful if you want to precisely control the tile IDs of its tile map.
|
||||
Using
|
||||
.Fl i
|
||||
alone is more useful if you want several images to use a subset of shared tiles.
|
||||
.Pp
|
||||
If the image will use more than one color palette, it is
|
||||
.Em strongly
|
||||
advised to generate the palette set along with the input tile data, and pass
|
||||
.Fl c Cm gbc: Ns Ar input_palette
|
||||
along with
|
||||
.Fl i Ar input_tiles .
|
||||
This is because
|
||||
.Nm
|
||||
might not generate the same palette set for this image as it did for its input tileset.
|
||||
.Pp
|
||||
See
|
||||
.Sx EXAMPLES
|
||||
for examples of how to use this option.
|
||||
.Pp
|
||||
This option is ignored in
|
||||
.Sx REVERSE MODE .
|
||||
.It Fl L Ar slice , Fl \-slice Ar slice
|
||||
Only process a given rectangle of the image.
|
||||
This is useful for example if the input image is a sheet of some sort, and you want to convert each cel individually.
|
||||
@@ -246,6 +298,9 @@ below for details.
|
||||
.Pp
|
||||
.Ar width
|
||||
is the width of the image to generate, in tiles.
|
||||
.Fl r 0
|
||||
chooses a width to make the image as square as possible.
|
||||
This is useful if you do not know the original width.
|
||||
.It Fl s Ar nb_colors , Fl \-palette-size Ar nb_colors
|
||||
Specify how many colors each palette contains, including the transparent one if any.
|
||||
.Ar nb_colors
|
||||
@@ -634,7 +689,13 @@ without needing an input image.
|
||||
.Pp
|
||||
.Dl $ rgbgfx -c '#fff,#ff0,#f80,#000' -p colors.pal
|
||||
.Pp
|
||||
TODO: more examples.
|
||||
The following will convert two level images using the same tileset, and error out if any of them contain tiles not in the tileset.
|
||||
.Pp
|
||||
.Bd -literal -offset Ds
|
||||
$ rgbgfx tileset.png -o tileset.2bpp -O -P
|
||||
$ rgbgfx level1.png -i tileset.2bpp -c gbc:tileset.pal -t level1.tilemap -a level1.attrmap
|
||||
$ rgbgfx level2.png -i tileset.2bpp -c gbc:tileset.pal -t level2.tilemap -a level2.attrmap
|
||||
.Ed
|
||||
.Sh BUGS
|
||||
Please report bugs and mistakes in this man page on
|
||||
.Lk https://github.com/gbdev/rgbds/issues GitHub .
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd December 22, 2023
|
||||
.Dd September 18, 2024
|
||||
.Dt RGBLINK 1
|
||||
.Os
|
||||
.Sh NAME
|
||||
@@ -78,7 +78,9 @@ If specified, the map file will not list symbols, only sections.
|
||||
.It Fl m Ar map_file , Fl \-map Ar map_file
|
||||
Write a map file to the given filename, listing how sections and symbols were assigned.
|
||||
.It Fl n Ar sym_file , Fl \-sym Ar sym_file
|
||||
Write a symbol file to the given filename, listing the address of all exported symbols.
|
||||
Write a symbol file to the given filename, listing all visible labels and exported numeric constants.
|
||||
Labels output their bank and address, numeric constants output their value, following
|
||||
.Lk https://rgbds.gbdev.io/sym/ this specification .
|
||||
Several external programs can use this information, for example to help debugging ROMs.
|
||||
.It Fl O Ar overlay_file , Fl \-overlay Ar overlay_file
|
||||
If specified, sections will be overlaid "on top" of the ROM image
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd December 22, 2023
|
||||
.Dd September 18, 2024
|
||||
.Dt RGBLINK 5
|
||||
.Os
|
||||
.Sh NAME
|
||||
@@ -86,6 +86,22 @@ If the bank has never been active thus far, the
|
||||
.Dq current address
|
||||
defaults to the beginning of the bank
|
||||
.Pq e.g. Ad $4000 No for Ic ROMX No sections .
|
||||
.Pp
|
||||
Instead of giving a bank number, the keyword
|
||||
.Ic FLOATING
|
||||
can be used instead; this sets the type of the subsequent sections without binding them to a particular bank.
|
||||
(If the type only allows a single bank, e.g.
|
||||
.Ic ROM0 ,
|
||||
then
|
||||
.Ic FLOATING
|
||||
is valid but redundant and has no effect.)
|
||||
Since no particular section is active, the
|
||||
.Dq current address
|
||||
is made floating (as if by a
|
||||
.Ql Ic FLOATING
|
||||
directive), and
|
||||
.Ic ORG
|
||||
is not allowed.
|
||||
.It Changing the current address
|
||||
A bank must be active for any of these directives to be used.
|
||||
.Pp
|
||||
@@ -104,6 +120,7 @@ causes all sections between it and the next
|
||||
.Ic ORG
|
||||
or bank specification to be placed at addresses automatically determined by
|
||||
.Nm .
|
||||
.Pq It is, however, compatible with Ic ALIGN No below.
|
||||
.Pp
|
||||
.Ql Ic ALIGN Ar addr , Ar offset
|
||||
increases the
|
||||
|
||||
@@ -2,12 +2,16 @@
|
||||
|
||||
#include "asm/charmap.hpp"
|
||||
|
||||
#include <deque>
|
||||
#include <map>
|
||||
#include <stack>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
|
||||
#include "helpers.hpp"
|
||||
#include "util.hpp"
|
||||
|
||||
#include "asm/warning.hpp"
|
||||
@@ -16,10 +20,11 @@
|
||||
// Essentially a tree, where each nodes stores a single character's worth of info:
|
||||
// whether there exists a mapping that ends at the current character,
|
||||
struct CharmapNode {
|
||||
bool isTerminal; // Whether there exists a mapping that ends here
|
||||
uint8_t value; // If the above is true, its corresponding value
|
||||
// This MUST be indexes and not pointers, because pointers get invalidated by reallocation!
|
||||
std::vector<int32_t> value; // The mapped value, if there exists a mapping that ends here
|
||||
// These MUST be indexes and not pointers, because pointers get invalidated by reallocation!
|
||||
size_t next[256]; // Indexes of where to go next, 0 = nowhere
|
||||
|
||||
bool isTerminal() const { return !value.empty(); }
|
||||
};
|
||||
|
||||
struct Charmap {
|
||||
@@ -27,47 +32,71 @@ struct Charmap {
|
||||
std::vector<CharmapNode> nodes; // first node is reserved for the root node
|
||||
};
|
||||
|
||||
static std::unordered_map<std::string, Charmap> charmaps;
|
||||
static std::deque<Charmap> charmapList;
|
||||
static std::unordered_map<std::string, size_t> charmapMap; // Indexes into `charmapList`
|
||||
|
||||
static Charmap *currentCharmap;
|
||||
std::stack<Charmap *> charmapStack;
|
||||
|
||||
bool charmap_ForEach(
|
||||
void (*mapFunc)(std::string const &),
|
||||
void (*charFunc)(std::string const &, std::vector<int32_t>)
|
||||
) {
|
||||
for (Charmap const &charmap : charmapList) {
|
||||
// Traverse the trie depth-first to derive the character mappings in definition order
|
||||
std::map<size_t, std::string> mappings;
|
||||
for (std::stack<std::pair<size_t, std::string>> prefixes({{0, ""}}); !prefixes.empty();) {
|
||||
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 + (char)c});
|
||||
}
|
||||
}
|
||||
mapFunc(charmap.name);
|
||||
for (auto [nodeIdx, mapping] : mappings)
|
||||
charFunc(mapping, charmap.nodes[nodeIdx].value);
|
||||
}
|
||||
return !charmapList.empty();
|
||||
}
|
||||
|
||||
void charmap_New(std::string const &name, std::string const *baseName) {
|
||||
Charmap *base = nullptr;
|
||||
size_t baseIdx = (size_t)-1;
|
||||
|
||||
if (baseName != nullptr) {
|
||||
auto search = charmaps.find(*baseName);
|
||||
|
||||
if (search == charmaps.end())
|
||||
if (auto search = charmapMap.find(*baseName); search == charmapMap.end())
|
||||
error("Base charmap '%s' doesn't exist\n", baseName->c_str());
|
||||
else
|
||||
base = &search->second;
|
||||
baseIdx = search->second;
|
||||
}
|
||||
|
||||
if (charmaps.find(name) != charmaps.end()) {
|
||||
if (charmapMap.find(name) != charmapMap.end()) {
|
||||
error("Charmap '%s' already exists\n", name.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
// Init the new charmap's fields
|
||||
Charmap &charmap = charmaps[name];
|
||||
charmapMap[name] = charmapList.size();
|
||||
Charmap &charmap = charmapList.emplace_back();
|
||||
|
||||
if (base)
|
||||
charmap.nodes = base->nodes; // Copies `base->nodes`
|
||||
if (baseIdx != (size_t)-1)
|
||||
charmap.nodes = charmapList[baseIdx].nodes; // Copies `charmapList[baseIdx].nodes`
|
||||
else
|
||||
charmap.nodes.emplace_back(); // Zero-init the root node
|
||||
|
||||
charmap.name = name;
|
||||
|
||||
currentCharmap = &charmap;
|
||||
}
|
||||
|
||||
void charmap_Set(std::string const &name) {
|
||||
auto search = charmaps.find(name);
|
||||
|
||||
if (search == charmaps.end())
|
||||
if (auto search = charmapMap.find(name); search == charmapMap.end())
|
||||
error("Charmap '%s' doesn't exist\n", name.c_str());
|
||||
else
|
||||
currentCharmap = &search->second;
|
||||
currentCharmap = &charmapList[search->second];
|
||||
}
|
||||
|
||||
void charmap_Push() {
|
||||
@@ -84,7 +113,12 @@ void charmap_Pop() {
|
||||
charmapStack.pop();
|
||||
}
|
||||
|
||||
void charmap_Add(std::string const &mapping, uint8_t value) {
|
||||
void charmap_Add(std::string const &mapping, std::vector<int32_t> &&value) {
|
||||
if (mapping.empty()) {
|
||||
error("Cannot map an empty string\n");
|
||||
return;
|
||||
}
|
||||
|
||||
Charmap &charmap = *currentCharmap;
|
||||
size_t nodeIdx = 0;
|
||||
|
||||
@@ -106,11 +140,10 @@ void charmap_Add(std::string const &mapping, uint8_t value) {
|
||||
|
||||
CharmapNode &node = charmap.nodes[nodeIdx];
|
||||
|
||||
if (node.isTerminal)
|
||||
if (node.isTerminal())
|
||||
warning(WARNING_CHARMAP_REDEF, "Overriding charmap mapping\n");
|
||||
|
||||
node.isTerminal = true;
|
||||
node.value = value;
|
||||
std::swap(node.value, value);
|
||||
}
|
||||
|
||||
bool charmap_HasChar(std::string const &input) {
|
||||
@@ -124,16 +157,17 @@ bool charmap_HasChar(std::string const &input) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return charmap.nodes[nodeIdx].isTerminal;
|
||||
return charmap.nodes[nodeIdx].isTerminal();
|
||||
}
|
||||
|
||||
void charmap_Convert(std::string const &input, std::vector<uint8_t> &output) {
|
||||
std::string_view inputView = input;
|
||||
while (charmap_ConvertNext(inputView, &output))
|
||||
std::vector<int32_t> charmap_Convert(std::string const &input) {
|
||||
std::vector<int32_t> output;
|
||||
for (std::string_view inputView = input; charmap_ConvertNext(inputView, &output);)
|
||||
;
|
||||
return output;
|
||||
}
|
||||
|
||||
size_t charmap_ConvertNext(std::string_view &input, std::vector<uint8_t> *output) {
|
||||
size_t charmap_ConvertNext(std::string_view &input, std::vector<int32_t> *output) {
|
||||
// The goal is to match the longest mapping possible.
|
||||
// For that, advance through the trie with each character read.
|
||||
// If that would lead to a dead end, rewind characters until the last match, and output.
|
||||
@@ -151,7 +185,7 @@ size_t charmap_ConvertNext(std::string_view &input, std::vector<uint8_t> *output
|
||||
|
||||
inputIdx++; // Consume that char
|
||||
|
||||
if (charmap.nodes[nodeIdx].isTerminal) {
|
||||
if (charmap.nodes[nodeIdx].isTerminal()) {
|
||||
matchIdx = nodeIdx; // This node matches, register it
|
||||
rewindDistance = 0; // If no longer match is found, rewind here
|
||||
} else {
|
||||
@@ -165,11 +199,12 @@ size_t charmap_ConvertNext(std::string_view &input, std::vector<uint8_t> *output
|
||||
|
||||
size_t matchLen = 0;
|
||||
if (matchIdx) { // A match was found, use it
|
||||
std::vector<int32_t> const &value = charmap.nodes[matchIdx].value;
|
||||
|
||||
if (output)
|
||||
output->push_back(charmap.nodes[matchIdx].value);
|
||||
|
||||
matchLen = 1;
|
||||
output->insert(output->end(), RANGE(value));
|
||||
|
||||
matchLen = value.size();
|
||||
} else if (inputIdx < input.length()) { // No match found, but there is some input left
|
||||
int firstChar = input[inputIdx];
|
||||
// This will write the codepoint's value to `output`, little-endian
|
||||
|
||||
@@ -22,16 +22,16 @@ void FormatSpec::useCharacter(int c) {
|
||||
case '+':
|
||||
if (state > FORMAT_SIGN)
|
||||
goto invalid;
|
||||
state = FORMAT_PREFIX;
|
||||
state = FORMAT_EXACT;
|
||||
sign = c;
|
||||
break;
|
||||
|
||||
// prefix
|
||||
// exact
|
||||
case '#':
|
||||
if (state > FORMAT_PREFIX)
|
||||
if (state > FORMAT_EXACT)
|
||||
goto invalid;
|
||||
state = FORMAT_ALIGN;
|
||||
prefix = true;
|
||||
exact = true;
|
||||
break;
|
||||
|
||||
// align
|
||||
@@ -42,7 +42,7 @@ void FormatSpec::useCharacter(int c) {
|
||||
alignLeft = true;
|
||||
break;
|
||||
|
||||
// pad and width
|
||||
// pad, width, and prec values
|
||||
case '0':
|
||||
if (state < FORMAT_WIDTH)
|
||||
padZero = true;
|
||||
@@ -63,11 +63,14 @@ void FormatSpec::useCharacter(int c) {
|
||||
width = width * 10 + (c - '0');
|
||||
} else if (state == FORMAT_FRAC) {
|
||||
fracWidth = fracWidth * 10 + (c - '0');
|
||||
} else if (state == FORMAT_PREC) {
|
||||
precision = precision * 10 + (c - '0');
|
||||
} else {
|
||||
goto invalid;
|
||||
}
|
||||
break;
|
||||
|
||||
// width
|
||||
case '.':
|
||||
if (state > FORMAT_WIDTH)
|
||||
goto invalid;
|
||||
@@ -75,6 +78,14 @@ void FormatSpec::useCharacter(int c) {
|
||||
hasFrac = true;
|
||||
break;
|
||||
|
||||
// prec
|
||||
case 'q':
|
||||
if (state > FORMAT_PREC)
|
||||
goto invalid;
|
||||
state = FORMAT_PREC;
|
||||
hasPrec = true;
|
||||
break;
|
||||
|
||||
// type
|
||||
case 'd':
|
||||
case 'u':
|
||||
@@ -103,6 +114,36 @@ void FormatSpec::finishCharacters() {
|
||||
state = FORMAT_INVALID;
|
||||
}
|
||||
|
||||
static std::string escapeString(std::string const &str) {
|
||||
std::string escaped;
|
||||
for (char c : str) {
|
||||
// Escape characters that need escaping
|
||||
switch (c) {
|
||||
case '\n':
|
||||
escaped += "\\n";
|
||||
break;
|
||||
case '\r':
|
||||
escaped += "\\r";
|
||||
break;
|
||||
case '\t':
|
||||
escaped += "\\t";
|
||||
break;
|
||||
case '\0':
|
||||
escaped += "\\0";
|
||||
break;
|
||||
case '\\':
|
||||
case '"':
|
||||
case '{':
|
||||
escaped += '\\';
|
||||
[[fallthrough]];
|
||||
default:
|
||||
escaped += c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return escaped;
|
||||
}
|
||||
|
||||
void FormatSpec::appendString(std::string &str, std::string const &value) const {
|
||||
int useType = type;
|
||||
if (isEmpty()) {
|
||||
@@ -112,42 +153,45 @@ void FormatSpec::appendString(std::string &str, std::string const &value) const
|
||||
|
||||
if (sign)
|
||||
error("Formatting string with sign flag '%c'\n", sign);
|
||||
if (prefix)
|
||||
error("Formatting string with prefix flag '#'\n");
|
||||
if (padZero)
|
||||
error("Formatting string with padding flag '0'\n");
|
||||
if (hasFrac)
|
||||
error("Formatting string with fractional width\n");
|
||||
if (hasPrec)
|
||||
error("Formatting string with fractional precision\n");
|
||||
if (useType != 's')
|
||||
error("Formatting string as type '%c'\n", useType);
|
||||
|
||||
size_t valueLen = value.length();
|
||||
std::string useValue = exact ? escapeString(value) : value;
|
||||
size_t valueLen = useValue.length();
|
||||
size_t totalLen = width > valueLen ? width : valueLen;
|
||||
size_t padLen = totalLen - valueLen;
|
||||
|
||||
str.reserve(str.length() + totalLen);
|
||||
if (alignLeft) {
|
||||
str.append(value);
|
||||
str.append(useValue);
|
||||
str.append(padLen, ' ');
|
||||
} else {
|
||||
str.append(padLen, ' ');
|
||||
str.append(value);
|
||||
str.append(useValue);
|
||||
}
|
||||
}
|
||||
|
||||
void FormatSpec::appendNumber(std::string &str, uint32_t value) const {
|
||||
int useType = type;
|
||||
bool usePrefix = prefix;
|
||||
bool useExact = exact;
|
||||
if (isEmpty()) {
|
||||
// No format was specified; default to uppercase $hex
|
||||
useType = 'X';
|
||||
usePrefix = true;
|
||||
useExact = true;
|
||||
}
|
||||
|
||||
if (useType != 'X' && useType != 'x' && useType != 'b' && useType != 'o' && usePrefix)
|
||||
error("Formatting type '%c' with prefix flag '#'\n", useType);
|
||||
if (useType != 'X' && useType != 'x' && useType != 'b' && useType != 'o' && useType != 'f' && useExact)
|
||||
error("Formatting type '%c' with exact flag '#'\n", useType);
|
||||
if (useType != 'f' && hasFrac)
|
||||
error("Formatting type '%c' with fractional width\n", useType);
|
||||
if (useType != 'f' && hasPrec)
|
||||
error("Formatting type '%c' with fractional precision\n", useType);
|
||||
if (useType == 's')
|
||||
error("Formatting number as type 's'\n");
|
||||
|
||||
@@ -161,7 +205,7 @@ void FormatSpec::appendNumber(std::string &str, uint32_t value) const {
|
||||
}
|
||||
}
|
||||
|
||||
char prefixChar = !usePrefix ? 0
|
||||
char prefixChar = !useExact ? 0
|
||||
: useType == 'X' ? '$'
|
||||
: useType == 'x' ? '$'
|
||||
: useType == 'b' ? '%'
|
||||
@@ -188,14 +232,27 @@ void FormatSpec::appendNumber(std::string &str, uint32_t value) const {
|
||||
|
||||
// Default fractional width (C++'s is 6 for "%f"; here 5 is enough for Q16.16)
|
||||
size_t useFracWidth = hasFrac ? fracWidth : 5;
|
||||
|
||||
if (useFracWidth > 255) {
|
||||
error("Fractional width %zu too long, limiting to 255\n", useFracWidth);
|
||||
useFracWidth = 255;
|
||||
}
|
||||
|
||||
double fval = fabs(value / fix_PrecisionFactor());
|
||||
snprintf(valueBuf, sizeof(valueBuf), "%.*f", (int)useFracWidth, fval);
|
||||
size_t defaultPrec = fix_Precision();
|
||||
size_t usePrec = hasPrec ? precision : defaultPrec;
|
||||
if (usePrec < 1 || usePrec > 31) {
|
||||
error(
|
||||
"Fixed-point constant precision %zu invalid, defaulting to %zu\n",
|
||||
usePrec,
|
||||
defaultPrec
|
||||
);
|
||||
usePrec = defaultPrec;
|
||||
}
|
||||
|
||||
double fval = fabs(value / pow(2.0, usePrec));
|
||||
if (useExact)
|
||||
snprintf(valueBuf, sizeof(valueBuf), "%.*fq%zu", (int)useFracWidth, fval, usePrec);
|
||||
else
|
||||
snprintf(valueBuf, sizeof(valueBuf), "%.*f", (int)useFracWidth, fval);
|
||||
} else if (useType == 'd') {
|
||||
// Decimal numbers may be formatted with a '-' sign by `snprintf`, so `abs` prevents that,
|
||||
// with a special case for `INT32_MIN` since `labs(INT32_MIN)` is UB. The sign will be
|
||||
@@ -203,7 +260,7 @@ void FormatSpec::appendNumber(std::string &str, uint32_t value) const {
|
||||
uint32_t uval = value != (uint32_t)INT32_MIN ? labs((int32_t)value) : value;
|
||||
snprintf(valueBuf, sizeof(valueBuf), "%" PRIu32, uval);
|
||||
} else {
|
||||
char const *spec = useType == 'u' ? "%" PRIu32
|
||||
char const *spec = useType == 'u' ? "%" PRIu32
|
||||
: useType == 'X' ? "%" PRIX32
|
||||
: useType == 'x' ? "%" PRIx32
|
||||
: useType == 'o' ? "%" PRIo32
|
||||
|
||||
@@ -48,28 +48,8 @@ static std::vector<std::string> includePaths = {""};
|
||||
|
||||
static std::string preIncludeName;
|
||||
|
||||
std::vector<uint32_t> &FileStackNode::iters() {
|
||||
assume(std::holds_alternative<std::vector<uint32_t>>(data));
|
||||
return std::get<std::vector<uint32_t>>(data);
|
||||
}
|
||||
|
||||
std::vector<uint32_t> const &FileStackNode::iters() const {
|
||||
assume(std::holds_alternative<std::vector<uint32_t>>(data));
|
||||
return std::get<std::vector<uint32_t>>(data);
|
||||
}
|
||||
|
||||
std::string &FileStackNode::name() {
|
||||
assume(std::holds_alternative<std::string>(data));
|
||||
return std::get<std::string>(data);
|
||||
}
|
||||
|
||||
std::string const &FileStackNode::name() const {
|
||||
assume(std::holds_alternative<std::string>(data));
|
||||
return std::get<std::string>(data);
|
||||
}
|
||||
|
||||
std::string const &FileStackNode::dump(uint32_t curLineNo) const {
|
||||
if (std::holds_alternative<std::vector<uint32_t>>(data)) {
|
||||
if (data.holds<std::vector<uint32_t>>()) {
|
||||
assume(parent); // REPT nodes use their parent's name
|
||||
std::string const &lastName = parent->dump(lineNo);
|
||||
fputs(" -> ", stderr);
|
||||
@@ -93,7 +73,7 @@ std::string const &FileStackNode::dump(uint32_t curLineNo) const {
|
||||
}
|
||||
|
||||
void fstk_DumpCurrent() {
|
||||
if (contextStack.empty()) {
|
||||
if (lexer_AtTopLevel()) {
|
||||
fputs("at top level", stderr);
|
||||
return;
|
||||
}
|
||||
@@ -333,7 +313,10 @@ void fstk_RunMacro(std::string const ¯oName, std::shared_ptr<MacroArgs> macr
|
||||
Symbol *macro = sym_FindExactSymbol(macroName);
|
||||
|
||||
if (!macro) {
|
||||
error("Macro \"%s\" not defined\n", macroName.c_str());
|
||||
if (sym_IsPurgedExact(macroName))
|
||||
error("Macro \"%s\" not defined; it was purged\n", macroName.c_str());
|
||||
else
|
||||
error("Macro \"%s\" not defined\n", macroName.c_str());
|
||||
return;
|
||||
}
|
||||
if (macro->type != SYM_MACRO) {
|
||||
|
||||
@@ -106,10 +106,10 @@ using namespace std::literals;
|
||||
|
||||
struct Token {
|
||||
int type;
|
||||
std::variant<std::monostate, uint32_t, std::string> value;
|
||||
Either<uint32_t, std::string> value;
|
||||
|
||||
Token() : type(T_(NUMBER)), value(std::monostate{}) {}
|
||||
Token(int type_) : type(type_), value(std::monostate{}) {}
|
||||
Token() : type(T_(NUMBER)), value() {}
|
||||
Token(int type_) : type(type_), value() {}
|
||||
Token(int type_, uint32_t value_) : type(type_), value(value_) {}
|
||||
Token(int type_, std::string const &value_) : type(type_), value(value_) {}
|
||||
Token(int type_, std::string &&value_) : type(type_), value(value_) {}
|
||||
@@ -212,7 +212,7 @@ static std::unordered_map<std::string, int, CaseInsensitive, CaseInsensitive> ke
|
||||
|
||||
{"FRAGMENT", T_(POP_FRAGMENT) },
|
||||
{"BANK", T_(OP_BANK) },
|
||||
{"ALIGN", T_(OP_ALIGN) },
|
||||
{"ALIGN", T_(POP_ALIGN) },
|
||||
|
||||
{"SIZEOF", T_(OP_SIZEOF) },
|
||||
{"STARTOF", T_(OP_STARTOF) },
|
||||
@@ -237,6 +237,9 @@ static std::unordered_map<std::string, int, CaseInsensitive, CaseInsensitive> ke
|
||||
{"LOW", T_(OP_LOW) },
|
||||
{"ISCONST", T_(OP_ISCONST) },
|
||||
|
||||
{"BITWIDTH", T_(OP_BITWIDTH) },
|
||||
{"TZCOUNT", T_(OP_TZCOUNT) },
|
||||
|
||||
{"STRCMP", T_(OP_STRCMP) },
|
||||
{"STRIN", T_(OP_STRIN) },
|
||||
{"STRRIN", T_(OP_STRRIN) },
|
||||
@@ -324,8 +327,6 @@ static std::unordered_map<std::string, int, CaseInsensitive, CaseInsensitive> ke
|
||||
{"POPO", T_(POP_POPO) },
|
||||
|
||||
{"OPT", T_(POP_OPT) },
|
||||
|
||||
{".", T_(PERIOD) },
|
||||
};
|
||||
|
||||
static bool isWhitespace(int c) {
|
||||
@@ -335,6 +336,10 @@ static bool isWhitespace(int c) {
|
||||
static LexerState *lexerState = nullptr;
|
||||
static LexerState *lexerStateEOL = nullptr;
|
||||
|
||||
bool lexer_AtTopLevel() {
|
||||
return lexerState == nullptr;
|
||||
}
|
||||
|
||||
void LexerState::clear(uint32_t lineNo_) {
|
||||
mode = LEXER_NORMAL;
|
||||
atLineStart = true; // yylex() will init colNo due to this
|
||||
@@ -370,7 +375,7 @@ void lexer_IncIFDepth() {
|
||||
|
||||
void lexer_DecIFDepth() {
|
||||
if (lexerState->ifStack.empty())
|
||||
fatalerror("Found ENDC outside an IF construct\n");
|
||||
fatalerror("Found ENDC outside of an IF construct\n");
|
||||
|
||||
lexerState->ifStack.pop_front();
|
||||
}
|
||||
@@ -461,8 +466,8 @@ void LexerState::setViewAsNextState(char const *name, ContentSpan const &span, u
|
||||
}
|
||||
|
||||
void lexer_RestartRept(uint32_t lineNo) {
|
||||
if (auto *view = std::get_if<ViewedContent>(&lexerState->content); view) {
|
||||
view->offset = 0;
|
||||
if (lexerState->content.holds<ViewedContent>()) {
|
||||
lexerState->content.get<ViewedContent>().offset = 0;
|
||||
}
|
||||
lexerState->clear(lineNo);
|
||||
}
|
||||
@@ -593,7 +598,16 @@ static uint32_t readBracketedMacroArgNum() {
|
||||
|
||||
if (c >= '0' && c <= '9') {
|
||||
num = readNumber(10, 0);
|
||||
} else if (startsIdentifier(c)) {
|
||||
} else if (startsIdentifier(c) || c == '#') {
|
||||
if (c == '#') {
|
||||
shiftChar();
|
||||
c = peek();
|
||||
if (!startsIdentifier(c)) {
|
||||
error("Empty raw symbol in bracketed macro argument\n");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
std::string symName;
|
||||
|
||||
for (; continuesIdentifier(c); c = peek()) {
|
||||
@@ -604,7 +618,10 @@ static uint32_t readBracketedMacroArgNum() {
|
||||
Symbol const *sym = sym_FindScopedValidSymbol(symName);
|
||||
|
||||
if (!sym) {
|
||||
error("Bracketed symbol \"%s\" does not exist\n", symName.c_str());
|
||||
if (sym_IsPurgedScoped(symName))
|
||||
error("Bracketed symbol \"%s\" does not exist; it was purged\n", symName.c_str());
|
||||
else
|
||||
error("Bracketed symbol \"%s\" does not exist\n", symName.c_str());
|
||||
num = 0;
|
||||
symbolError = true;
|
||||
} else if (!sym->isNumeric()) {
|
||||
@@ -643,10 +660,13 @@ static std::shared_ptr<std::string> readMacroArg(char name) {
|
||||
return str;
|
||||
} else if (name == '#') {
|
||||
MacroArgs *macroArgs = fstk_GetCurrentMacroArgs();
|
||||
auto str = macroArgs ? macroArgs->getAllArgs() : nullptr;
|
||||
if (!str) {
|
||||
if (!macroArgs) {
|
||||
error("'\\#' cannot be used outside of a macro\n");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto str = macroArgs->getAllArgs();
|
||||
assume(str); // '\#' should always be defined (at least as an empty string)
|
||||
return str;
|
||||
} else if (name == '<') {
|
||||
uint32_t num = readBracketedMacroArgNum();
|
||||
@@ -666,9 +686,6 @@ static std::shared_ptr<std::string> readMacroArg(char name) {
|
||||
error("Macro argument '\\<%" PRIu32 ">' not defined\n", num);
|
||||
}
|
||||
return str;
|
||||
} else if (name == '0') {
|
||||
error("Invalid macro argument '\\0'\n");
|
||||
return nullptr;
|
||||
} else {
|
||||
assume(name >= '1' && name <= '9');
|
||||
|
||||
@@ -693,12 +710,12 @@ int LexerState::peekChar() {
|
||||
return (uint8_t)(*exp.contents)[exp.offset];
|
||||
}
|
||||
|
||||
if (auto *view = std::get_if<ViewedContent>(&content); view) {
|
||||
if (view->offset < view->span.size)
|
||||
return (uint8_t)view->span.ptr[view->offset];
|
||||
if (content.holds<ViewedContent>()) {
|
||||
auto &view = content.get<ViewedContent>();
|
||||
if (view.offset < view.span.size)
|
||||
return (uint8_t)view.span.ptr[view.offset];
|
||||
} else {
|
||||
assume(std::holds_alternative<BufferedContent>(content));
|
||||
auto &cbuf = std::get<BufferedContent>(content);
|
||||
auto &cbuf = content.get<BufferedContent>();
|
||||
if (cbuf.size == 0)
|
||||
cbuf.refill();
|
||||
assume(cbuf.offset < LEXER_BUF_SIZE);
|
||||
@@ -723,12 +740,12 @@ int LexerState::peekCharAhead() {
|
||||
distance -= exp.size() - exp.offset;
|
||||
}
|
||||
|
||||
if (auto *view = std::get_if<ViewedContent>(&content); view) {
|
||||
if (view->offset + distance < view->span.size)
|
||||
return (uint8_t)view->span.ptr[view->offset + distance];
|
||||
if (content.holds<ViewedContent>()) {
|
||||
auto &view = content.get<ViewedContent>();
|
||||
if (view.offset + distance < view.span.size)
|
||||
return (uint8_t)view.span.ptr[view.offset + distance];
|
||||
} else {
|
||||
assume(std::holds_alternative<BufferedContent>(content));
|
||||
auto &cbuf = std::get<BufferedContent>(content);
|
||||
auto &cbuf = content.get<BufferedContent>();
|
||||
assume(distance < LEXER_BUF_SIZE);
|
||||
if (cbuf.size <= distance)
|
||||
cbuf.refill();
|
||||
@@ -812,12 +829,10 @@ restart:
|
||||
} else {
|
||||
// Advance within the file contents
|
||||
lexerState->colNo++;
|
||||
if (auto *view = std::get_if<ViewedContent>(&lexerState->content); view) {
|
||||
view->offset++;
|
||||
if (lexerState->content.holds<ViewedContent>()) {
|
||||
lexerState->content.get<ViewedContent>().offset++;
|
||||
} else {
|
||||
assume(std::holds_alternative<BufferedContent>(lexerState->content));
|
||||
auto &cbuf = std::get<BufferedContent>(lexerState->content);
|
||||
cbuf.advance();
|
||||
lexerState->content.get<BufferedContent>().advance();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1134,11 +1149,10 @@ static bool startsIdentifier(int c) {
|
||||
}
|
||||
|
||||
static bool continuesIdentifier(int c) {
|
||||
return startsIdentifier(c) || (c <= '9' && c >= '0') || c == '#' || c == '@';
|
||||
return startsIdentifier(c) || (c <= '9' && c >= '0') || c == '#' || c == '$' || c == '@';
|
||||
}
|
||||
|
||||
static Token readIdentifier(char firstChar) {
|
||||
// Lex while checking for a keyword
|
||||
static Token readIdentifier(char firstChar, bool raw) {
|
||||
std::string identifier(1, firstChar);
|
||||
int tokenType = firstChar == '.' ? T_(LOCAL_ID) : T_(ID);
|
||||
|
||||
@@ -1154,9 +1168,17 @@ static Token readIdentifier(char firstChar) {
|
||||
tokenType = T_(LOCAL_ID);
|
||||
}
|
||||
|
||||
// Attempt to check for a keyword
|
||||
auto search = keywordDict.find(identifier.c_str());
|
||||
return search != keywordDict.end() ? Token(search->second) : Token(tokenType, identifier);
|
||||
// Attempt to check for a keyword if the identifier is not raw
|
||||
if (!raw) {
|
||||
if (auto search = keywordDict.find(identifier); search != keywordDict.end())
|
||||
return Token(search->second);
|
||||
}
|
||||
|
||||
// Label scopes `.` and `..` are the only nonlocal identifiers that start with a dot
|
||||
if (identifier.find_first_not_of('.') == identifier.npos)
|
||||
tokenType = T_(ID);
|
||||
|
||||
return Token(tokenType, identifier);
|
||||
}
|
||||
|
||||
// Functions to read strings
|
||||
@@ -1206,10 +1228,26 @@ static std::shared_ptr<std::string> readInterpolation(size_t depth) {
|
||||
// Don't return before `lexerState->disableInterpolation` is reset!
|
||||
lexerState->disableInterpolation = disableInterpolation;
|
||||
|
||||
if (fmtBuf.starts_with('#')) {
|
||||
// Skip a '#' raw identifier prefix, but after expanding any nested interpolations.
|
||||
fmtBuf.erase(0, 1);
|
||||
} else if (keywordDict.find(fmtBuf) != keywordDict.end()) {
|
||||
// Don't allow symbols that alias keywords without a '#' prefix.
|
||||
error(
|
||||
"Interpolated symbol \"%s\" is a reserved keyword; add a '#' prefix to use it as a raw "
|
||||
"symbol\n",
|
||||
fmtBuf.c_str()
|
||||
);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Symbol const *sym = sym_FindScopedValidSymbol(fmtBuf);
|
||||
|
||||
if (!sym) {
|
||||
error("Interpolated symbol \"%s\" does not exist\n", fmtBuf.c_str());
|
||||
if (!sym || !sym->isDefined()) {
|
||||
if (sym_IsPurgedScoped(fmtBuf))
|
||||
error("Interpolated symbol \"%s\" does not exist; it was purged\n", fmtBuf.c_str());
|
||||
else
|
||||
error("Interpolated symbol \"%s\" does not exist\n", fmtBuf.c_str());
|
||||
} else if (sym->type == SYM_EQUS) {
|
||||
auto buf = std::make_shared<std::string>();
|
||||
fmt.appendString(*buf, *sym->getEqus());
|
||||
@@ -1219,7 +1257,7 @@ static std::shared_ptr<std::string> readInterpolation(size_t depth) {
|
||||
fmt.appendNumber(*buf, sym->getConstantValue());
|
||||
return buf;
|
||||
} else {
|
||||
error("Only numerical and string symbols can be interpolated\n");
|
||||
error("Interpolated symbol \"%s\" is not a numeric or string symbol\n", fmtBuf.c_str());
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
@@ -1228,14 +1266,6 @@ static void appendEscapedString(std::string &str, std::string const &escape) {
|
||||
for (char c : escape) {
|
||||
// Escape characters that need escaping
|
||||
switch (c) {
|
||||
case '\\':
|
||||
case '"':
|
||||
case '{':
|
||||
str += '\\';
|
||||
[[fallthrough]];
|
||||
default:
|
||||
str += c;
|
||||
break;
|
||||
case '\n':
|
||||
str += "\\n";
|
||||
break;
|
||||
@@ -1248,6 +1278,14 @@ static void appendEscapedString(std::string &str, std::string const &escape) {
|
||||
case '\0':
|
||||
str += "\\0";
|
||||
break;
|
||||
case '\\':
|
||||
case '"':
|
||||
case '{':
|
||||
str += '\\';
|
||||
[[fallthrough]];
|
||||
default:
|
||||
str += c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1777,8 +1815,13 @@ static Token yylex_NORMAL() {
|
||||
// Handle identifiers... or report garbage characters
|
||||
|
||||
default:
|
||||
bool raw = c == '#';
|
||||
if (raw && startsIdentifier(peek())) {
|
||||
c = nextChar();
|
||||
}
|
||||
|
||||
if (startsIdentifier(c)) {
|
||||
Token token = readIdentifier(c);
|
||||
Token token = readIdentifier(c, raw);
|
||||
|
||||
// An ELIF after a taken IF needs to not evaluate its condition
|
||||
if (token.type == T_(POP_ELIF) && lexerState->lastToken == T_(NEWLINE)
|
||||
@@ -1790,12 +1833,12 @@ static Token yylex_NORMAL() {
|
||||
return token;
|
||||
|
||||
// `token` is either an `ID` or a `LOCAL_ID`, and both have a `std::string` value.
|
||||
assume(std::holds_alternative<std::string>(token.value));
|
||||
assume(token.value.holds<std::string>());
|
||||
|
||||
// Local symbols cannot be string expansions
|
||||
if (token.type == T_(ID) && lexerState->expandStrings) {
|
||||
// Attempt string expansion
|
||||
Symbol const *sym = sym_FindExactSymbol(std::get<std::string>(token.value));
|
||||
Symbol const *sym = sym_FindExactSymbol(token.value.get<std::string>());
|
||||
|
||||
if (sym && sym->type == SYM_EQUS) {
|
||||
std::shared_ptr<std::string> str = sym->getEqus();
|
||||
@@ -2013,7 +2056,7 @@ static Token skipIfBlock(bool toEndc) {
|
||||
|
||||
if (startsIdentifier(c)) {
|
||||
shiftChar();
|
||||
switch (Token token = readIdentifier(c); token.type) {
|
||||
switch (Token token = readIdentifier(c, false); token.type) {
|
||||
case T_(POP_IF):
|
||||
lexer_IncIFDepth();
|
||||
break;
|
||||
@@ -2099,7 +2142,7 @@ static Token yylex_SKIP_TO_ENDR() {
|
||||
|
||||
if (startsIdentifier(c)) {
|
||||
shiftChar();
|
||||
switch (readIdentifier(c).type) {
|
||||
switch (readIdentifier(c, false).type) {
|
||||
case T_(POP_FOR):
|
||||
case T_(POP_REPT):
|
||||
depth++;
|
||||
@@ -2170,17 +2213,22 @@ yy::parser::symbol_type yylex() {
|
||||
Token token = lexerModeFuncs[lexerState->mode]();
|
||||
|
||||
// Captures end at their buffer's boundary no matter what
|
||||
if (token.type == T_(YYEOF) && !lexerState->capturing)
|
||||
token = Token(T_(EOB));
|
||||
if (token.type == T_(YYEOF) && !lexerState->capturing) {
|
||||
// Doing `token = Token(T_(EOB));` here would be valid but redundant, because YYEOF and EOB
|
||||
// both have the same empty value. Furthermore, g++ 11.4.0 was giving a false-positive
|
||||
// '-Wmaybe-uninitialized' warning for `Token::value.Either<...>::_tag` that way.
|
||||
// (This was on a developer's local machine; GitHub Actions CI's g++ was not warning.)
|
||||
token.type = T_(EOB);
|
||||
}
|
||||
lexerState->lastToken = token.type;
|
||||
lexerState->atLineStart = token.type == T_(NEWLINE) || token.type == T_(EOB);
|
||||
|
||||
if (auto *numValue = std::get_if<uint32_t>(&token.value); numValue) {
|
||||
return yy::parser::symbol_type(token.type, *numValue);
|
||||
} else if (auto *strValue = std::get_if<std::string>(&token.value); strValue) {
|
||||
return yy::parser::symbol_type(token.type, *strValue);
|
||||
if (token.value.holds<uint32_t>()) {
|
||||
return yy::parser::symbol_type(token.type, token.value.get<uint32_t>());
|
||||
} else if (token.value.holds<std::string>()) {
|
||||
return yy::parser::symbol_type(token.type, token.value.get<std::string>());
|
||||
} else {
|
||||
assume(std::holds_alternative<std::monostate>(token.value));
|
||||
assume(token.value.empty());
|
||||
return yy::parser::symbol_type(token.type);
|
||||
}
|
||||
}
|
||||
@@ -2196,10 +2244,10 @@ static Capture startCapture() {
|
||||
lexerState->captureSize = 0;
|
||||
|
||||
uint32_t lineNo = lexer_GetLineNo();
|
||||
if (auto *view = std::get_if<ViewedContent>(&lexerState->content);
|
||||
view && lexerState->expansions.empty()) {
|
||||
if (lexerState->content.holds<ViewedContent>() && lexerState->expansions.empty()) {
|
||||
auto &view = lexerState->content.get<ViewedContent>();
|
||||
return {
|
||||
.lineNo = lineNo, .span = {.ptr = view->makeSharedContentPtr(), .size = 0}
|
||||
.lineNo = lineNo, .span = {.ptr = view.makeSharedContentPtr(), .size = 0}
|
||||
};
|
||||
} else {
|
||||
assume(lexerState->captureBuf == nullptr);
|
||||
@@ -2241,7 +2289,7 @@ Capture lexer_CaptureRept() {
|
||||
} while (isWhitespace(c));
|
||||
// Now, try to match `REPT`, `FOR` or `ENDR` as a **whole** identifier
|
||||
if (startsIdentifier(c)) {
|
||||
switch (readIdentifier(c).type) {
|
||||
switch (readIdentifier(c, false).type) {
|
||||
case T_(POP_REPT):
|
||||
case T_(POP_FOR):
|
||||
depth++;
|
||||
@@ -2294,7 +2342,7 @@ Capture lexer_CaptureMacro() {
|
||||
} while (isWhitespace(c));
|
||||
// Now, try to match `ENDM` as a **whole** identifier
|
||||
if (startsIdentifier(c)) {
|
||||
switch (readIdentifier(c).type) {
|
||||
switch (readIdentifier(c, false).type) {
|
||||
case T_(POP_ENDM):
|
||||
endCapture(capture);
|
||||
// The ENDM has been captured, but we don't want it!
|
||||
|
||||
144
src/asm/main.cpp
144
src/asm/main.cpp
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "asm/main.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits.h>
|
||||
#include <memory>
|
||||
#include <stdlib.h>
|
||||
@@ -21,14 +22,13 @@
|
||||
#include "asm/symbol.hpp"
|
||||
#include "asm/warning.hpp"
|
||||
|
||||
FILE *dependFile = nullptr;
|
||||
bool generatedMissingIncludes = false;
|
||||
FILE *dependFile = nullptr; // -M
|
||||
bool generatedMissingIncludes = false; // -MG
|
||||
bool generatePhonyDeps = false; // -MP
|
||||
std::string targetFileName; // -MQ, -MT
|
||||
bool failedOnMissingInclude = false;
|
||||
bool generatePhonyDeps = false;
|
||||
std::string targetFileName;
|
||||
|
||||
bool verbose;
|
||||
bool warnings; // True to enable warnings, false to disable them.
|
||||
bool verbose = false; // -v
|
||||
bool warnings = true; // -w
|
||||
|
||||
// Escapes Make-special chars from a string
|
||||
static std::string make_escape(std::string &str) {
|
||||
@@ -48,7 +48,7 @@ static std::string make_escape(std::string &str) {
|
||||
}
|
||||
|
||||
// Short options
|
||||
static char const *optstring = "b:D:Eg:I:M:o:P:p:Q:r:VvW:wX:";
|
||||
static char const *optstring = "b:D:Eg:I:M:o:P:p:Q:r:s:VvW:wX:";
|
||||
|
||||
// Variables for the long-only options
|
||||
static int depType; // Variants of `-M`
|
||||
@@ -62,27 +62,28 @@ static int depType; // Variants of `-M`
|
||||
// This is because long opt matching, even to a single char, is prioritized
|
||||
// over short opt matching
|
||||
static option const longopts[] = {
|
||||
{"binary-digits", required_argument, nullptr, 'b'},
|
||||
{"define", required_argument, nullptr, 'D'},
|
||||
{"export-all", no_argument, nullptr, 'E'},
|
||||
{"gfx-chars", required_argument, nullptr, 'g'},
|
||||
{"include", required_argument, nullptr, 'I'},
|
||||
{"dependfile", required_argument, nullptr, 'M'},
|
||||
{"MG", no_argument, &depType, 'G'},
|
||||
{"MP", no_argument, &depType, 'P'},
|
||||
{"MT", required_argument, &depType, 'T'},
|
||||
{"warning", required_argument, nullptr, 'W'},
|
||||
{"MQ", required_argument, &depType, 'Q'},
|
||||
{"output", required_argument, nullptr, 'o'},
|
||||
{"preinclude", required_argument, nullptr, 'P'},
|
||||
{"pad-value", required_argument, nullptr, 'p'},
|
||||
{"q-precision", required_argument, nullptr, 'Q'},
|
||||
{"recursion-depth", required_argument, nullptr, 'r'},
|
||||
{"version", no_argument, nullptr, 'V'},
|
||||
{"verbose", no_argument, nullptr, 'v'},
|
||||
{"warning", required_argument, nullptr, 'W'},
|
||||
{"max-errors", required_argument, nullptr, 'X'},
|
||||
{nullptr, no_argument, nullptr, 0 }
|
||||
{"binary-digits", required_argument, nullptr, 'b'},
|
||||
{"define", required_argument, nullptr, 'D'},
|
||||
{"export-all", no_argument, nullptr, 'E'},
|
||||
{"gfx-chars", required_argument, nullptr, 'g'},
|
||||
{"include", required_argument, nullptr, 'I'},
|
||||
{"dependfile", required_argument, nullptr, 'M'},
|
||||
{"MG", no_argument, &depType, 'G'},
|
||||
{"MP", no_argument, &depType, 'P'},
|
||||
{"MT", required_argument, &depType, 'T'},
|
||||
{"warning", required_argument, nullptr, 'W'},
|
||||
{"MQ", required_argument, &depType, 'Q'},
|
||||
{"output", required_argument, nullptr, 'o'},
|
||||
{"preinclude", required_argument, nullptr, 'P'},
|
||||
{"pad-value", required_argument, nullptr, 'p'},
|
||||
{"q-precision", required_argument, nullptr, 'Q'},
|
||||
{"recursion-depth", required_argument, nullptr, 'r'},
|
||||
{"state", required_argument, nullptr, 's'},
|
||||
{"version", no_argument, nullptr, 'V'},
|
||||
{"verbose", no_argument, nullptr, 'v'},
|
||||
{"warning", required_argument, nullptr, 'W'},
|
||||
{"max-errors", required_argument, nullptr, 'X'},
|
||||
{nullptr, no_argument, nullptr, 0 }
|
||||
};
|
||||
|
||||
static void printUsage() {
|
||||
@@ -90,14 +91,16 @@ static void printUsage() {
|
||||
"Usage: rgbasm [-EVvw] [-b chars] [-D name[=value]] [-g chars] [-I path]\n"
|
||||
" [-M depend_file] [-MG] [-MP] [-MT target_file] [-MQ target_file]\n"
|
||||
" [-o out_file] [-P include_file] [-p pad_value] [-Q precision]\n"
|
||||
" [-r depth] [-W warning] [-X max_errors] <file>\n"
|
||||
" [-r depth] [-s features:state_file] [-W warning] [-X max_errors]\n"
|
||||
" <file>\n"
|
||||
"Useful options:\n"
|
||||
" -E, --export-all export all labels\n"
|
||||
" -M, --dependfile <path> set the output dependency file\n"
|
||||
" -o, --output <path> set the output object file\n"
|
||||
" -p, --pad-value <value> set the value to use for `ds'\n"
|
||||
" -V, --version print RGBASM version and exit\n"
|
||||
" -W, --warning <warning> enable or disable warnings\n"
|
||||
" -E, --export-all export all labels\n"
|
||||
" -M, --dependfile <path> set the output dependency file\n"
|
||||
" -o, --output <path> set the output object file\n"
|
||||
" -p, --pad-value <value> set the value to use for `ds'\n"
|
||||
" -s, --state <features>:<path> set an output state file\n"
|
||||
" -V, --version print RGBASM version and exit\n"
|
||||
" -W, --warning <warning> enable or disable warnings\n"
|
||||
"\n"
|
||||
"For help, use `man rgbasm' or go to https://rgbds.gbdev.io/docs/\n",
|
||||
stderr
|
||||
@@ -124,11 +127,10 @@ int main(int argc, char *argv[]) {
|
||||
opt_G("0123");
|
||||
opt_P(0);
|
||||
opt_Q(16);
|
||||
verbose = false;
|
||||
warnings = true;
|
||||
sym_SetExportAll(false);
|
||||
uint32_t maxDepth = DEFAULT_MAX_DEPTH;
|
||||
char const *dependFileName = nullptr;
|
||||
std::unordered_map<std::string, std::vector<StateFeature>> stateFileSpecs;
|
||||
std::string newTarget;
|
||||
// Maximum of 100 errors only applies if rgbasm is printing errors to a terminal.
|
||||
if (isatty(STDERR_FILENO))
|
||||
@@ -230,6 +232,58 @@ int main(int argc, char *argv[]) {
|
||||
errx("Invalid argument for option 'r'");
|
||||
break;
|
||||
|
||||
case 's': {
|
||||
// Split "<features>:<name>" so `musl_optarg` is "<features>" and `name` is "<name>"
|
||||
char *name = strchr(musl_optarg, ':');
|
||||
if (!name)
|
||||
errx("Invalid argument for option 's'");
|
||||
*name++ = '\0';
|
||||
|
||||
std::vector<StateFeature> features;
|
||||
for (char *feature = musl_optarg; feature;) {
|
||||
// Split "<feature>,<rest>" so `feature` is "<feature>" and `next` is "<rest>"
|
||||
char *next = strchr(feature, ',');
|
||||
if (next)
|
||||
*next++ = '\0';
|
||||
// Trim whitespace from the beginning of `feature`...
|
||||
feature += strspn(feature, " \t");
|
||||
// ...and from the end
|
||||
if (char *end = strpbrk(feature, " \t"); end)
|
||||
*end = '\0';
|
||||
// A feature must be specified
|
||||
if (*feature == '\0')
|
||||
errx("Empty feature for option 's'");
|
||||
// Parse the `feature` and update the `features` list
|
||||
if (!strcasecmp(feature, "all")) {
|
||||
if (!features.empty())
|
||||
warnx("Redundant feature before \"%s\" for option 's'", feature);
|
||||
features.assign({STATE_EQU, STATE_VAR, STATE_EQUS, STATE_CHAR, STATE_MACRO});
|
||||
} else {
|
||||
StateFeature value = !strcasecmp(feature, "equ") ? STATE_EQU
|
||||
: !strcasecmp(feature, "var") ? STATE_VAR
|
||||
: !strcasecmp(feature, "equs") ? STATE_EQUS
|
||||
: !strcasecmp(feature, "char") ? STATE_CHAR
|
||||
: !strcasecmp(feature, "macro") ? STATE_MACRO
|
||||
: NB_STATE_FEATURES;
|
||||
if (value == NB_STATE_FEATURES) {
|
||||
errx("Invalid feature for option 's': \"%s\"", feature);
|
||||
} else if (std::find(RANGE(features), value) != features.end()) {
|
||||
warnx("Ignoring duplicate feature for option 's': \"%s\"", feature);
|
||||
} else {
|
||||
features.push_back(value);
|
||||
}
|
||||
}
|
||||
feature = next;
|
||||
}
|
||||
|
||||
if (stateFileSpecs.find(name) != stateFileSpecs.end())
|
||||
warnx("Overriding state filename %s", name);
|
||||
if (verbose)
|
||||
printf("State filename %s\n", name);
|
||||
stateFileSpecs.emplace(name, std::move(features));
|
||||
break;
|
||||
}
|
||||
|
||||
case 'V':
|
||||
printf("rgbasm %s\n", get_package_version_string());
|
||||
exit(0);
|
||||
@@ -284,14 +338,13 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
// Unrecognized options
|
||||
default:
|
||||
fprintf(stderr, "FATAL: unknown option '%c'\n", ch);
|
||||
printUsage();
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (targetFileName.empty() && !objectName.empty())
|
||||
targetFileName = objectName;
|
||||
if (targetFileName.empty() && !objectFileName.empty())
|
||||
targetFileName = objectFileName;
|
||||
|
||||
if (argc == musl_optind) {
|
||||
fputs(
|
||||
@@ -328,6 +381,7 @@ int main(int argc, char *argv[]) {
|
||||
nbErrors = 1;
|
||||
|
||||
sect_CheckUnionClosed();
|
||||
sect_CheckSizes();
|
||||
|
||||
if (nbErrors != 0)
|
||||
errx("Assembly aborted (%u error%s)!", nbErrors, nbErrors == 1 ? "" : "s");
|
||||
@@ -336,8 +390,10 @@ int main(int argc, char *argv[]) {
|
||||
if (failedOnMissingInclude)
|
||||
return 0;
|
||||
|
||||
// If no path specified, don't write file
|
||||
if (!objectName.empty())
|
||||
out_WriteObject();
|
||||
out_WriteObject();
|
||||
|
||||
for (auto [name, features] : stateFileSpecs)
|
||||
out_WriteState(name, features);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
#include "asm/fixpoint.hpp"
|
||||
#include "asm/fstack.hpp"
|
||||
#include "asm/lexer.hpp"
|
||||
#include "asm/main.hpp"
|
||||
#include "asm/section.hpp"
|
||||
#include "asm/warning.hpp"
|
||||
|
||||
|
||||
@@ -2,17 +2,18 @@
|
||||
|
||||
#include "asm/output.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <deque>
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "error.hpp"
|
||||
#include "helpers.hpp" // assume, Defer
|
||||
|
||||
#include "asm/charmap.hpp"
|
||||
#include "asm/fstack.hpp"
|
||||
#include "asm/lexer.hpp"
|
||||
#include "asm/main.hpp"
|
||||
@@ -27,7 +28,7 @@ struct Assertion {
|
||||
std::string message;
|
||||
};
|
||||
|
||||
std::string objectName;
|
||||
std::string objectFileName;
|
||||
|
||||
// List of symbols to put in the object file
|
||||
static std::vector<Symbol *> objectSymbols;
|
||||
@@ -36,8 +37,7 @@ static std::deque<Assertion> assertions;
|
||||
|
||||
static std::deque<std::shared_ptr<FileStackNode>> fileStackNodes;
|
||||
|
||||
// Write a long to a file (little-endian)
|
||||
static void putlong(uint32_t n, FILE *file) {
|
||||
static void putLong(uint32_t n, FILE *file) {
|
||||
uint8_t bytes[] = {
|
||||
(uint8_t)n,
|
||||
(uint8_t)(n >> 8),
|
||||
@@ -47,8 +47,7 @@ static void putlong(uint32_t n, FILE *file) {
|
||||
fwrite(bytes, 1, sizeof(bytes), file);
|
||||
}
|
||||
|
||||
// Write a NUL-terminated string to a file
|
||||
static void putstring(std::string const &s, FILE *file) {
|
||||
static void putString(std::string const &s, FILE *file) {
|
||||
fputs(s.c_str(), file);
|
||||
putc('\0', file);
|
||||
}
|
||||
@@ -72,57 +71,60 @@ static uint32_t getSectIDIfAny(Section *sect) {
|
||||
fatalerror("Unknown section '%s'\n", sect->name.c_str());
|
||||
}
|
||||
|
||||
// Write a patch to a file
|
||||
static void writepatch(Patch const &patch, FILE *file) {
|
||||
static void writePatch(Patch const &patch, FILE *file) {
|
||||
assume(patch.src->ID != (uint32_t)-1);
|
||||
putlong(patch.src->ID, file);
|
||||
putlong(patch.lineNo, file);
|
||||
putlong(patch.offset, file);
|
||||
putlong(getSectIDIfAny(patch.pcSection), file);
|
||||
putlong(patch.pcOffset, file);
|
||||
|
||||
putLong(patch.src->ID, file);
|
||||
putLong(patch.lineNo, file);
|
||||
putLong(patch.offset, file);
|
||||
putLong(getSectIDIfAny(patch.pcSection), file);
|
||||
putLong(patch.pcOffset, file);
|
||||
putc(patch.type, file);
|
||||
putlong(patch.rpn.size(), file);
|
||||
putLong(patch.rpn.size(), file);
|
||||
fwrite(patch.rpn.data(), 1, patch.rpn.size(), file);
|
||||
}
|
||||
|
||||
// Write a section to a file
|
||||
static void writesection(Section const §, FILE *file) {
|
||||
putstring(sect.name, file);
|
||||
static void writeSection(Section const §, FILE *file) {
|
||||
assume(sect.src->ID != (uint32_t)-1);
|
||||
|
||||
putlong(sect.size, file);
|
||||
putString(sect.name, file);
|
||||
|
||||
putLong(sect.src->ID, file);
|
||||
putLong(sect.fileLine, file);
|
||||
|
||||
putLong(sect.size, file);
|
||||
|
||||
bool isUnion = sect.modifier == SECTION_UNION;
|
||||
bool isFragment = sect.modifier == SECTION_FRAGMENT;
|
||||
|
||||
putc(sect.type | isUnion << 7 | isFragment << 6, file);
|
||||
|
||||
putlong(sect.org, file);
|
||||
putlong(sect.bank, file);
|
||||
putLong(sect.org, file);
|
||||
putLong(sect.bank, file);
|
||||
putc(sect.align, file);
|
||||
putlong(sect.alignOfs, file);
|
||||
putLong(sect.alignOfs, file);
|
||||
|
||||
if (sect_HasData(sect.type)) {
|
||||
fwrite(sect.data.data(), 1, sect.size, file);
|
||||
putlong(sect.patches.size(), file);
|
||||
putLong(sect.patches.size(), file);
|
||||
|
||||
for (Patch const &patch : sect.patches)
|
||||
writepatch(patch, file);
|
||||
writePatch(patch, file);
|
||||
}
|
||||
}
|
||||
|
||||
// Write a symbol to a file
|
||||
static void writesymbol(Symbol const &sym, FILE *file) {
|
||||
putstring(sym.name, file);
|
||||
static void writeSymbol(Symbol const &sym, FILE *file) {
|
||||
putString(sym.name, file);
|
||||
if (!sym.isDefined()) {
|
||||
putc(SYMTYPE_IMPORT, file);
|
||||
} else {
|
||||
assume(sym.src->ID != (uint32_t)-1);
|
||||
|
||||
putc(sym.isExported ? SYMTYPE_EXPORT : SYMTYPE_LOCAL, file);
|
||||
putlong(sym.src->ID, file);
|
||||
putlong(sym.fileLine, file);
|
||||
putlong(getSectIDIfAny(sym.getSection()), file);
|
||||
putlong(sym.getOutputValue(), file);
|
||||
putLong(sym.src->ID, file);
|
||||
putLong(sym.fileLine, file);
|
||||
putLong(getSectIDIfAny(sym.getSection()), file);
|
||||
putLong(sym.getOutputValue(), file);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,7 +137,7 @@ static void registerUnregisteredSymbol(Symbol &sym) {
|
||||
}
|
||||
}
|
||||
|
||||
static void writerpn(std::vector<uint8_t> &rpnexpr, std::vector<uint8_t> const &rpn) {
|
||||
static void writeRpn(std::vector<uint8_t> &rpnexpr, std::vector<uint8_t> const &rpn) {
|
||||
std::string symName;
|
||||
size_t rpnptr = 0;
|
||||
|
||||
@@ -233,7 +235,7 @@ static void writerpn(std::vector<uint8_t> &rpnexpr, std::vector<uint8_t> const &
|
||||
}
|
||||
}
|
||||
|
||||
static void initpatch(Patch &patch, uint32_t type, Expression const &expr, uint32_t ofs) {
|
||||
static void initPatch(Patch &patch, uint32_t type, Expression const &expr, uint32_t ofs) {
|
||||
patch.type = type;
|
||||
patch.src = fstk_GetFileStack();
|
||||
// All patches are assumed to eventually be written, so the file stack node is registered
|
||||
@@ -254,16 +256,15 @@ static void initpatch(Patch &patch, uint32_t type, Expression const &expr, uint3
|
||||
patch.rpn[4] = val >> 24;
|
||||
} else {
|
||||
patch.rpn.resize(expr.rpnPatchSize);
|
||||
writerpn(patch.rpn, expr.rpn);
|
||||
writeRpn(patch.rpn, expr.rpn);
|
||||
}
|
||||
}
|
||||
|
||||
// Create a new patch (includes the rpn expr)
|
||||
void out_CreatePatch(uint32_t type, Expression const &expr, uint32_t ofs, uint32_t pcShift) {
|
||||
// Add the patch to the list
|
||||
Patch &patch = currentSection->patches.emplace_front();
|
||||
|
||||
initpatch(patch, type, expr, ofs);
|
||||
initPatch(patch, type, expr, ofs);
|
||||
|
||||
// If the patch had a quantity of bytes output before it,
|
||||
// PC is not at the patch's location, but at the location
|
||||
@@ -271,60 +272,61 @@ void out_CreatePatch(uint32_t type, Expression const &expr, uint32_t ofs, uint32
|
||||
patch.pcOffset -= pcShift;
|
||||
}
|
||||
|
||||
// Creates an assert that will be written to the object file
|
||||
void out_CreateAssert(
|
||||
AssertionType type, Expression const &expr, std::string const &message, uint32_t ofs
|
||||
) {
|
||||
Assertion &assertion = assertions.emplace_front();
|
||||
|
||||
initpatch(assertion.patch, type, expr, ofs);
|
||||
initPatch(assertion.patch, type, expr, ofs);
|
||||
assertion.message = message;
|
||||
}
|
||||
|
||||
static void writeassert(Assertion &assert, FILE *file) {
|
||||
writepatch(assert.patch, file);
|
||||
putstring(assert.message, file);
|
||||
static void writeAssert(Assertion &assert, FILE *file) {
|
||||
writePatch(assert.patch, file);
|
||||
putString(assert.message, file);
|
||||
}
|
||||
|
||||
static void writeFileStackNode(FileStackNode const &node, FILE *file) {
|
||||
putlong(node.parent ? node.parent->ID : (uint32_t)-1, file);
|
||||
putlong(node.lineNo, file);
|
||||
putLong(node.parent ? node.parent->ID : (uint32_t)-1, file);
|
||||
putLong(node.lineNo, file);
|
||||
putc(node.type, file);
|
||||
if (node.type != NODE_REPT) {
|
||||
putstring(node.name(), file);
|
||||
putString(node.name(), file);
|
||||
} else {
|
||||
std::vector<uint32_t> const &nodeIters = node.iters();
|
||||
|
||||
putlong(nodeIters.size(), file);
|
||||
putLong(nodeIters.size(), file);
|
||||
// Iters are stored by decreasing depth, so reverse the order for output
|
||||
for (uint32_t i = nodeIters.size(); i--;)
|
||||
putlong(nodeIters[i], file);
|
||||
putLong(nodeIters[i], file);
|
||||
}
|
||||
}
|
||||
|
||||
// Write an object file
|
||||
void out_WriteObject() {
|
||||
if (objectFileName.empty())
|
||||
return;
|
||||
|
||||
FILE *file;
|
||||
if (objectName != "-") {
|
||||
file = fopen(objectName.c_str(), "wb");
|
||||
if (objectFileName != "-") {
|
||||
file = fopen(objectFileName.c_str(), "wb");
|
||||
} else {
|
||||
objectName = "<stdout>";
|
||||
objectFileName = "<stdout>";
|
||||
file = fdopen(STDOUT_FILENO, "wb");
|
||||
}
|
||||
if (!file)
|
||||
err("Failed to open object file '%s'", objectName.c_str());
|
||||
err("Failed to open object file '%s'", objectFileName.c_str());
|
||||
Defer closeFile{[&] { fclose(file); }};
|
||||
|
||||
// Also write symbols that weren't written above
|
||||
sym_ForEach(registerUnregisteredSymbol);
|
||||
|
||||
fprintf(file, RGBDS_OBJECT_VERSION_STRING);
|
||||
putlong(RGBDS_OBJECT_REV, file);
|
||||
putLong(RGBDS_OBJECT_REV, file);
|
||||
|
||||
putlong(objectSymbols.size(), file);
|
||||
putlong(sectionList.size(), file);
|
||||
putLong(objectSymbols.size(), file);
|
||||
putLong(sectionList.size(), file);
|
||||
|
||||
putlong(fileStackNodes.size(), file);
|
||||
putLong(fileStackNodes.size(), file);
|
||||
for (auto it = fileStackNodes.begin(); it != fileStackNodes.end(); it++) {
|
||||
FileStackNode const &node = **it;
|
||||
|
||||
@@ -341,22 +343,191 @@ void out_WriteObject() {
|
||||
}
|
||||
|
||||
for (Symbol const *sym : objectSymbols)
|
||||
writesymbol(*sym, file);
|
||||
writeSymbol(*sym, file);
|
||||
|
||||
for (auto it = sectionList.rbegin(); it != sectionList.rend(); it++)
|
||||
writesection(*it, file);
|
||||
writeSection(*it, file);
|
||||
|
||||
putlong(assertions.size(), file);
|
||||
putLong(assertions.size(), file);
|
||||
|
||||
for (Assertion &assert : assertions)
|
||||
writeassert(assert, file);
|
||||
writeAssert(assert, file);
|
||||
}
|
||||
|
||||
// Set the object filename
|
||||
void out_SetFileName(std::string const &name) {
|
||||
if (!objectName.empty())
|
||||
warnx("Overriding output filename %s", objectName.c_str());
|
||||
objectName = name;
|
||||
if (!objectFileName.empty())
|
||||
warnx("Overriding output filename %s", objectFileName.c_str());
|
||||
objectFileName = name;
|
||||
if (verbose)
|
||||
printf("Output filename %s\n", objectName.c_str());
|
||||
printf("Output filename %s\n", objectFileName.c_str());
|
||||
}
|
||||
|
||||
static void dumpString(std::string const &escape, FILE *file) {
|
||||
for (char c : escape) {
|
||||
// Escape characters that need escaping
|
||||
switch (c) {
|
||||
case '\n':
|
||||
fputs("\\n", file);
|
||||
break;
|
||||
case '\r':
|
||||
fputs("\\r", file);
|
||||
break;
|
||||
case '\t':
|
||||
fputs("\\t", file);
|
||||
break;
|
||||
case '\0':
|
||||
fputs("\\0", file);
|
||||
break;
|
||||
case '\\':
|
||||
case '"':
|
||||
case '{':
|
||||
putc('\\', file);
|
||||
[[fallthrough]];
|
||||
default:
|
||||
putc(c, file);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool dumpEquConstants(FILE *file) {
|
||||
static std::vector<Symbol *> equConstants; // `static` so `sym_ForEach` callback can see it
|
||||
equConstants.clear();
|
||||
|
||||
sym_ForEach([](Symbol &sym) {
|
||||
if (!sym.isBuiltin && sym.type == SYM_EQU)
|
||||
equConstants.push_back(&sym);
|
||||
});
|
||||
// Constants are ordered by file, then by definition order
|
||||
std::sort(RANGE(equConstants), [](Symbol *sym1, Symbol *sym2) -> bool {
|
||||
return sym1->defIndex < sym2->defIndex;
|
||||
});
|
||||
|
||||
for (Symbol const *sym : equConstants) {
|
||||
uint32_t value = static_cast<uint32_t>(sym->getOutputValue());
|
||||
fprintf(file, "def %s equ $%" PRIx32 "\n", sym->name.c_str(), value);
|
||||
}
|
||||
|
||||
return !equConstants.empty();
|
||||
}
|
||||
|
||||
static bool dumpVariables(FILE *file) {
|
||||
static std::vector<Symbol *> variables; // `static` so `sym_ForEach` callback can see it
|
||||
variables.clear();
|
||||
|
||||
sym_ForEach([](Symbol &sym) {
|
||||
if (!sym.isBuiltin && sym.type == SYM_VAR)
|
||||
variables.push_back(&sym);
|
||||
});
|
||||
// Variables are ordered by file, then by definition order
|
||||
std::sort(RANGE(variables), [](Symbol *sym1, Symbol *sym2) -> bool {
|
||||
return sym1->defIndex < sym2->defIndex;
|
||||
});
|
||||
|
||||
for (Symbol const *sym : variables) {
|
||||
uint32_t value = static_cast<uint32_t>(sym->getOutputValue());
|
||||
fprintf(file, "def %s = $%" PRIx32 "\n", sym->name.c_str(), value);
|
||||
}
|
||||
|
||||
return !variables.empty();
|
||||
}
|
||||
|
||||
static bool dumpEqusConstants(FILE *file) {
|
||||
static std::vector<Symbol *> equsConstants; // `static` so `sym_ForEach` callback can see it
|
||||
equsConstants.clear();
|
||||
|
||||
sym_ForEach([](Symbol &sym) {
|
||||
if (!sym.isBuiltin && sym.type == SYM_EQUS)
|
||||
equsConstants.push_back(&sym);
|
||||
});
|
||||
// Constants are ordered by file, then by definition order
|
||||
std::sort(RANGE(equsConstants), [](Symbol *sym1, Symbol *sym2) -> bool {
|
||||
return sym1->defIndex < sym2->defIndex;
|
||||
});
|
||||
|
||||
for (Symbol const *sym : equsConstants) {
|
||||
fprintf(file, "def %s equs \"", sym->name.c_str());
|
||||
dumpString(*sym->getEqus(), file);
|
||||
fputs("\"\n", file);
|
||||
}
|
||||
|
||||
return !equsConstants.empty();
|
||||
}
|
||||
|
||||
static bool dumpCharmaps(FILE *file) {
|
||||
static FILE *charmapFile; // `static` so `charmap_ForEach` callbacks can see it
|
||||
charmapFile = file;
|
||||
|
||||
// Characters are ordered by charmap, then by definition order
|
||||
return charmap_ForEach(
|
||||
[](std::string const &name) { fprintf(charmapFile, "newcharmap %s\n", name.c_str()); },
|
||||
[](std::string const &mapping, std::vector<int32_t> value) {
|
||||
fputs("charmap \"", charmapFile);
|
||||
dumpString(mapping, charmapFile);
|
||||
putc('"', charmapFile);
|
||||
for (int32_t v : value)
|
||||
fprintf(charmapFile, ", $%" PRIx32, v);
|
||||
putc('\n', charmapFile);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
static bool dumpMacros(FILE *file) {
|
||||
static std::vector<Symbol *> macros; // `static` so `sym_ForEach` callback can see it
|
||||
macros.clear();
|
||||
|
||||
sym_ForEach([](Symbol &sym) {
|
||||
if (!sym.isBuiltin && sym.type == SYM_MACRO)
|
||||
macros.push_back(&sym);
|
||||
});
|
||||
// Macros are ordered by file, then by definition order
|
||||
std::sort(RANGE(macros), [](Symbol *sym1, Symbol *sym2) -> bool {
|
||||
return sym1->defIndex < sym2->defIndex;
|
||||
});
|
||||
|
||||
for (Symbol const *sym : macros) {
|
||||
auto const &body = sym->getMacro();
|
||||
fprintf(file, "macro %s\n", sym->name.c_str());
|
||||
fwrite(body.ptr.get(), 1, body.size, file);
|
||||
fputs("endm\n", file);
|
||||
}
|
||||
|
||||
return !macros.empty();
|
||||
}
|
||||
|
||||
void out_WriteState(std::string name, std::vector<StateFeature> const &features) {
|
||||
// State files may include macro bodies, which may contain arbitrary characters,
|
||||
// so output as binary to preserve them.
|
||||
FILE *file;
|
||||
if (name != "-") {
|
||||
file = fopen(name.c_str(), "wb");
|
||||
} else {
|
||||
name = "<stdout>";
|
||||
file = fdopen(STDOUT_FILENO, "wb");
|
||||
}
|
||||
if (!file)
|
||||
err("Failed to open state file '%s'", name.c_str());
|
||||
Defer closeFile{[&] { fclose(file); }};
|
||||
|
||||
static char const *dumpHeadings[NB_STATE_FEATURES] = {
|
||||
"Numeric constants",
|
||||
"Variables",
|
||||
"String constants",
|
||||
"Character maps",
|
||||
"Macros",
|
||||
};
|
||||
static bool (* const dumpFuncs[NB_STATE_FEATURES])(FILE *) = {
|
||||
dumpEquConstants,
|
||||
dumpVariables,
|
||||
dumpEqusConstants,
|
||||
dumpCharmaps,
|
||||
dumpMacros,
|
||||
};
|
||||
|
||||
fputs("; File generated by rgbasm\n", file);
|
||||
for (StateFeature feature : features) {
|
||||
fprintf(file, "\n; %s\n", dumpHeadings[feature]);
|
||||
if (!dumpFuncs[feature](file))
|
||||
fprintf(file, "; No values\n");
|
||||
}
|
||||
}
|
||||
|
||||
908
src/asm/parser.y
908
src/asm/parser.y
File diff suppressed because it is too large
Load Diff
225
src/asm/rpn.cpp
225
src/asm/rpn.cpp
@@ -9,7 +9,7 @@
|
||||
#include <string.h>
|
||||
#include <string_view>
|
||||
|
||||
#include "helpers.hpp" // assume
|
||||
#include "helpers.hpp" // assume, clz, ctz
|
||||
#include "opmath.hpp"
|
||||
|
||||
#include "asm/output.hpp"
|
||||
@@ -19,11 +19,6 @@
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
int32_t Expression::value() const {
|
||||
assume(std::holds_alternative<int32_t>(data));
|
||||
return std::get<int32_t>(data);
|
||||
}
|
||||
|
||||
void Expression::clear() {
|
||||
data = 0;
|
||||
isSymbol = false;
|
||||
@@ -44,7 +39,7 @@ uint8_t *Expression::reserveSpace(uint32_t size, uint32_t patchSize) {
|
||||
|
||||
int32_t Expression::getConstVal() const {
|
||||
if (!isKnown()) {
|
||||
error("Expected constant expression: %s\n", std::get<std::string>(data).c_str());
|
||||
error("Expected constant expression: %s\n", data.get<std::string>().c_str());
|
||||
return 0;
|
||||
}
|
||||
return value();
|
||||
@@ -76,12 +71,14 @@ void Expression::makeNumber(uint32_t value) {
|
||||
void Expression::makeSymbol(std::string const &symName) {
|
||||
clear();
|
||||
if (Symbol *sym = sym_FindScopedSymbol(symName); sym_IsPC(sym) && !sect_GetSymbolSection()) {
|
||||
error("PC has no value outside a section\n");
|
||||
error("PC has no value outside of a section\n");
|
||||
data = 0;
|
||||
} else if (!sym || !sym->isConstant()) {
|
||||
isSymbol = true;
|
||||
|
||||
data = sym_IsPC(sym) ? "PC is not constant at assembly time"
|
||||
: sym_IsPurgedScoped(symName)
|
||||
? "'"s + symName + "' is not constant at assembly time; it was purged"
|
||||
: "'"s + symName + "' is not constant at assembly time";
|
||||
sym = sym_Ref(symName);
|
||||
|
||||
@@ -101,7 +98,7 @@ void Expression::makeBankSymbol(std::string const &symName) {
|
||||
if (Symbol const *sym = sym_FindScopedSymbol(symName); sym_IsPC(sym)) {
|
||||
// The @ symbol is treated differently.
|
||||
if (!currentSection) {
|
||||
error("PC has no bank outside a section\n");
|
||||
error("PC has no bank outside of a section\n");
|
||||
data = 1;
|
||||
} else if (currentSection->bank == (uint32_t)-1) {
|
||||
data = "Current section's bank is not known";
|
||||
@@ -122,7 +119,9 @@ void Expression::makeBankSymbol(std::string const &symName) {
|
||||
// Symbol's section is known and bank is fixed
|
||||
data = (int32_t)sym->getSection()->bank;
|
||||
} else {
|
||||
data = "\""s + symName + "\"'s bank is not known";
|
||||
data = sym_IsPurgedScoped(symName)
|
||||
? "\""s + symName + "\"'s bank is not known; it was purged"
|
||||
: "\""s + symName + "\"'s bank is not known";
|
||||
|
||||
size_t nameLen = sym->name.length() + 1; // Room for NUL!
|
||||
|
||||
@@ -207,12 +206,57 @@ static bool tryConstNonzero(Expression const &lhs, Expression const &rhs) {
|
||||
return expr.isKnown() && expr.value() != 0;
|
||||
}
|
||||
|
||||
static bool tryConstLogNot(Expression const &expr) {
|
||||
Symbol const *sym = expr.symbolOf();
|
||||
if (!sym || !sym->getSection() || !sym->isDefined())
|
||||
return false;
|
||||
|
||||
assume(sym->isNumeric());
|
||||
|
||||
Section const § = *sym->getSection();
|
||||
int32_t unknownBits = (1 << 16) - (1 << sect.align);
|
||||
|
||||
// `sym->getValue()` attempts to add the section's address, but that's "-1"
|
||||
// because the section is floating (otherwise we wouldn't be here)
|
||||
assume(sect.org == (uint32_t)-1);
|
||||
int32_t symbolOfs = sym->getValue() + 1;
|
||||
|
||||
int32_t knownBits = (symbolOfs + sect.alignOfs) & ~unknownBits;
|
||||
return knownBits != 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Attempts to compute a constant binary AND from non-constant operands
|
||||
* Attempts to compute a constant LOW() from non-constant argument
|
||||
* This is possible if the argument is a symbol belonging to an `ALIGN[8]` section.
|
||||
*
|
||||
* @return The constant `LOW(expr)` result if it can be computed, or -1 otherwise.
|
||||
*/
|
||||
static int32_t tryConstLow(Expression const &expr) {
|
||||
Symbol const *sym = expr.symbolOf();
|
||||
if (!sym || !sym->getSection() || !sym->isDefined())
|
||||
return -1;
|
||||
|
||||
assume(sym->isNumeric());
|
||||
|
||||
// The low byte must not cover any unknown bits
|
||||
Section const § = *sym->getSection();
|
||||
if (sect.align < 8)
|
||||
return -1;
|
||||
|
||||
// `sym->getValue()` attempts to add the section's address, but that's "-1"
|
||||
// because the section is floating (otherwise we wouldn't be here)
|
||||
assume(sect.org == (uint32_t)-1);
|
||||
int32_t symbolOfs = sym->getValue() + 1;
|
||||
|
||||
return (symbolOfs + sect.alignOfs) & 0xFF;
|
||||
}
|
||||
|
||||
/*
|
||||
* Attempts to compute a constant binary AND with one non-constant operands
|
||||
* This is possible if one operand is a symbol belonging to an `ALIGN[N]` section, and the other is
|
||||
* a constant that only keeps (some of) the lower N bits.
|
||||
*
|
||||
* @return The constant result if it can be computed, or -1 otherwise.
|
||||
* @return The constant `lhs & rhs` result if it can be computed, or -1 otherwise.
|
||||
*/
|
||||
static int32_t tryConstMask(Expression const &lhs, Expression const &rhs) {
|
||||
Symbol const *lhsSymbol = lhs.symbolOf();
|
||||
@@ -227,16 +271,17 @@ static int32_t tryConstMask(Expression const &lhs, Expression const &rhs) {
|
||||
Symbol const &sym = lhsIsSymbol ? *lhsSymbol : *rhsSymbol;
|
||||
Expression const &expr = lhsIsSymbol ? rhs : lhs; // Opposite side of `sym`
|
||||
|
||||
if (!sym.isDefined() || !expr.isKnown())
|
||||
return -1;
|
||||
|
||||
assume(sym.isNumeric());
|
||||
|
||||
if (!expr.isKnown())
|
||||
return -1;
|
||||
// We can now safely use `expr.value()`
|
||||
Section const § = *sym.getSection();
|
||||
int32_t unknownBits = (1 << 16) - (1 << sect.align); // The max alignment is 16
|
||||
int32_t mask = expr.value();
|
||||
|
||||
// The mask must ignore all unknown bits
|
||||
if ((expr.value() & unknownBits) != 0)
|
||||
// The mask must not cover any unknown bits
|
||||
Section const § = *sym.getSection();
|
||||
if (int32_t unknownBits = (1 << 16) - (1 << sect.align); (unknownBits & mask) != 0)
|
||||
return -1;
|
||||
|
||||
// `sym.getValue()` attempts to add the section's address, but that's "-1"
|
||||
@@ -244,61 +289,88 @@ static int32_t tryConstMask(Expression const &lhs, Expression const &rhs) {
|
||||
assume(sect.org == (uint32_t)-1);
|
||||
int32_t symbolOfs = sym.getValue() + 1;
|
||||
|
||||
return (symbolOfs + sect.alignOfs) & ~unknownBits;
|
||||
return (symbolOfs + sect.alignOfs) & mask;
|
||||
}
|
||||
|
||||
void Expression::makeHigh() {
|
||||
isSymbol = false;
|
||||
if (isKnown()) {
|
||||
data = (int32_t)((uint32_t)value() >> 8 & 0xFF);
|
||||
void Expression::makeUnaryOp(RPNCommand op, Expression &&src) {
|
||||
clear();
|
||||
// First, check if the expression is known
|
||||
if (src.isKnown()) {
|
||||
// If the expressions is known, just compute the value
|
||||
int32_t val = src.value();
|
||||
|
||||
switch (op) {
|
||||
case RPN_NEG:
|
||||
data = (int32_t) - (uint32_t)val;
|
||||
break;
|
||||
case RPN_NOT:
|
||||
data = ~val;
|
||||
break;
|
||||
case RPN_LOGNOT:
|
||||
data = !val;
|
||||
break;
|
||||
case RPN_HIGH:
|
||||
data = (int32_t)((uint32_t)val >> 8 & 0xFF);
|
||||
break;
|
||||
case RPN_LOW:
|
||||
data = val & 0xFF;
|
||||
break;
|
||||
case RPN_BITWIDTH:
|
||||
data = val != 0 ? 32 - clz((uint32_t)val) : 0;
|
||||
break;
|
||||
case RPN_TZCOUNT:
|
||||
data = val != 0 ? ctz((uint32_t)val) : 32;
|
||||
break;
|
||||
|
||||
case RPN_LOGOR:
|
||||
case RPN_LOGAND:
|
||||
case RPN_LOGEQ:
|
||||
case RPN_LOGGT:
|
||||
case RPN_LOGLT:
|
||||
case RPN_LOGGE:
|
||||
case RPN_LOGLE:
|
||||
case RPN_LOGNE:
|
||||
case RPN_ADD:
|
||||
case RPN_SUB:
|
||||
case RPN_XOR:
|
||||
case RPN_OR:
|
||||
case RPN_AND:
|
||||
case RPN_SHL:
|
||||
case RPN_SHR:
|
||||
case RPN_USHR:
|
||||
case RPN_MUL:
|
||||
case RPN_DIV:
|
||||
case RPN_MOD:
|
||||
case RPN_EXP:
|
||||
case RPN_BANK_SYM:
|
||||
case RPN_BANK_SECT:
|
||||
case RPN_BANK_SELF:
|
||||
case RPN_SIZEOF_SECT:
|
||||
case RPN_STARTOF_SECT:
|
||||
case RPN_SIZEOF_SECTTYPE:
|
||||
case RPN_STARTOF_SECTTYPE:
|
||||
case RPN_HRAM:
|
||||
case RPN_RST:
|
||||
case RPN_CONST:
|
||||
case RPN_SYM:
|
||||
fatalerror("%d is not an unary operator\n", op);
|
||||
}
|
||||
} else if (op == RPN_LOGNOT && tryConstLogNot(src)) {
|
||||
data = 0;
|
||||
} else if (int32_t constVal; op == RPN_LOW && (constVal = tryConstLow(src)) != -1) {
|
||||
data = constVal;
|
||||
} else {
|
||||
uint8_t bytes[] = {RPN_CONST, 8, 0, 0, 0, RPN_SHR, RPN_CONST, 0xFF, 0, 0, 0, RPN_AND};
|
||||
|
||||
memcpy(reserveSpace(sizeof(bytes)), bytes, sizeof(bytes));
|
||||
}
|
||||
}
|
||||
|
||||
void Expression::makeLow() {
|
||||
isSymbol = false;
|
||||
if (isKnown()) {
|
||||
data = value() & 0xFF;
|
||||
} else {
|
||||
uint8_t bytes[] = {RPN_CONST, 0xFF, 0, 0, 0, RPN_AND};
|
||||
|
||||
memcpy(reserveSpace(sizeof(bytes)), bytes, sizeof(bytes));
|
||||
}
|
||||
}
|
||||
|
||||
void Expression::makeNeg() {
|
||||
isSymbol = false;
|
||||
if (isKnown()) {
|
||||
data = (int32_t) - (uint32_t)value();
|
||||
} else {
|
||||
*reserveSpace(1) = RPN_NEG;
|
||||
}
|
||||
}
|
||||
|
||||
void Expression::makeNot() {
|
||||
isSymbol = false;
|
||||
if (isKnown()) {
|
||||
data = ~value();
|
||||
} else {
|
||||
*reserveSpace(1) = RPN_NOT;
|
||||
}
|
||||
}
|
||||
|
||||
void Expression::makeLogicNot() {
|
||||
isSymbol = false;
|
||||
if (isKnown()) {
|
||||
data = !value();
|
||||
} else {
|
||||
*reserveSpace(1) = RPN_LOGNOT;
|
||||
// If it's not known, just reuse its RPN buffer and append the operator
|
||||
rpnPatchSize = src.rpnPatchSize;
|
||||
std::swap(rpn, src.rpn);
|
||||
data = std::move(src.data);
|
||||
*reserveSpace(1) = op;
|
||||
}
|
||||
}
|
||||
|
||||
void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const &src2) {
|
||||
clear();
|
||||
// First, check if the expression is known
|
||||
// First, check if the expressions are known
|
||||
if (src1.isKnown() && src2.isKnown()) {
|
||||
// If both expressions are known, just compute the value
|
||||
int32_t lval = src1.value(), rval = src2.value();
|
||||
@@ -426,6 +498,10 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const
|
||||
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);
|
||||
@@ -516,13 +592,22 @@ void Expression::makeCheckRST() {
|
||||
|
||||
// Checks that an RPN expression's value fits within N bits (signed or unsigned)
|
||||
void Expression::checkNBit(uint8_t n) const {
|
||||
if (isKnown())
|
||||
::checkNBit(value(), n, "Expression");
|
||||
}
|
||||
|
||||
bool checkNBit(int32_t v, uint8_t n, char const *name) {
|
||||
assume(n != 0); // That doesn't make sense
|
||||
assume(n < CHAR_BIT * sizeof(int)); // Otherwise `1 << n` is UB
|
||||
|
||||
if (isKnown()) {
|
||||
if (int32_t val = value(); val < -(1 << n) || val >= 1 << n)
|
||||
warning(WARNING_TRUNCATION_1, "Expression must be %u-bit\n", n);
|
||||
else if (val < -(1 << (n - 1)))
|
||||
warning(WARNING_TRUNCATION_2, "Expression must be %u-bit\n", n);
|
||||
if (v < -(1 << n) || v >= 1 << n) {
|
||||
warning(WARNING_TRUNCATION_1, "%s must be %u-bit\n", name, n);
|
||||
return false;
|
||||
}
|
||||
if (v < -(1 << (n - 1))) {
|
||||
warning(WARNING_TRUNCATION_2, "%s must be %u-bit\n", name, n);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <utility>
|
||||
|
||||
#include "helpers.hpp"
|
||||
|
||||
@@ -31,7 +32,7 @@ struct UnionStackEntry {
|
||||
struct SectionStackEntry {
|
||||
Section *section;
|
||||
Section *loadSection;
|
||||
std::optional<std::string> scope; // Section's symbol scope
|
||||
std::pair<Symbol const *, Symbol const *> labelScopes;
|
||||
uint32_t offset;
|
||||
int32_t loadOffset;
|
||||
std::stack<UnionStackEntry> unionStack;
|
||||
@@ -44,11 +45,11 @@ std::unordered_map<std::string, size_t> sectionMap; // Indexes into `sectionList
|
||||
uint32_t curOffset; // Offset into the current section (see sect_GetSymbolOffset)
|
||||
Section *currentSection = nullptr;
|
||||
static Section *currentLoadSection = nullptr;
|
||||
std::optional<std::string> currentLoadScope = std::nullopt;
|
||||
static std::pair<Symbol const *, Symbol const *> currentLoadLabelScopes = {nullptr, nullptr};
|
||||
int32_t loadOffset; // Offset into the LOAD section's parent (see sect_GetOutputOffset)
|
||||
|
||||
// A quick check to see if we have an initialized section
|
||||
[[nodiscard]] static bool checksection() {
|
||||
[[nodiscard]] static bool requireSection() {
|
||||
if (currentSection)
|
||||
return true;
|
||||
|
||||
@@ -58,8 +59,8 @@ int32_t loadOffset; // Offset into the LOAD section's parent (see sect_GetOutput
|
||||
|
||||
// A quick check to see if we have an initialized section that can contain
|
||||
// this much initialized data
|
||||
[[nodiscard]] static bool checkcodesection() {
|
||||
if (!checksection())
|
||||
[[nodiscard]] static bool requireCodeSection() {
|
||||
if (!requireSection())
|
||||
return false;
|
||||
|
||||
if (sect_HasData(currentSection->type))
|
||||
@@ -72,41 +73,17 @@ int32_t loadOffset; // Offset into the LOAD section's parent (see sect_GetOutput
|
||||
return false;
|
||||
}
|
||||
|
||||
[[nodiscard]] static bool checkSectionSize(Section const §, uint32_t size) {
|
||||
uint32_t maxSize = sectionTypeInfo[sect.type].size;
|
||||
|
||||
// If the new size is reasonable, keep going
|
||||
if (size <= maxSize)
|
||||
return true;
|
||||
|
||||
error(
|
||||
"Section '%s' grew too big (max size = 0x%" PRIX32 " bytes, reached 0x%" PRIX32 ").\n",
|
||||
sect.name.c_str(),
|
||||
maxSize,
|
||||
size
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if the section has grown too much.
|
||||
[[nodiscard]] static bool reserveSpace(uint32_t delta_size) {
|
||||
// This check is here to trap broken code that generates sections that are too big and to
|
||||
// prevent the assembler from generating huge object files or trying to allocate too much
|
||||
// memory.
|
||||
// A check at the linking stage is still necessary.
|
||||
|
||||
// If the section has already overflowed, skip the check to avoid erroring out ad nauseam
|
||||
if (currentSection->size != UINT32_MAX
|
||||
&& !checkSectionSize(*currentSection, curOffset + loadOffset + delta_size))
|
||||
// Mark the section as overflowed, to avoid repeating the error
|
||||
currentSection->size = UINT32_MAX;
|
||||
|
||||
if (currentLoadSection && currentLoadSection->size != UINT32_MAX
|
||||
&& !checkSectionSize(*currentLoadSection, curOffset + delta_size))
|
||||
currentLoadSection->size = UINT32_MAX;
|
||||
|
||||
return currentSection->size != UINT32_MAX
|
||||
&& (!currentLoadSection || currentLoadSection->size != UINT32_MAX);
|
||||
void sect_CheckSizes() {
|
||||
for (Section const § : sectionList) {
|
||||
if (uint32_t maxSize = sectionTypeInfo[sect.type].size; sect.size > maxSize)
|
||||
error(
|
||||
"Section '%s' grew too big (max size = 0x%" PRIX32 " bytes, reached 0x%" PRIX32
|
||||
").\n",
|
||||
sect.name.c_str(),
|
||||
maxSize,
|
||||
sect.size
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Section *sect_FindSectionByName(std::string const &name) {
|
||||
@@ -312,6 +289,8 @@ static Section *createSection(
|
||||
sect.align = alignment;
|
||||
sect.alignOfs = alignOffset;
|
||||
|
||||
out_RegisterNode(sect.src);
|
||||
|
||||
// It is only needed to allocate memory for ROM sections.
|
||||
if (sect_HasData(type))
|
||||
sect.data.resize(sectionTypeInfo[type].size);
|
||||
@@ -417,7 +396,7 @@ static void changeSection() {
|
||||
if (!currentUnionStack.empty())
|
||||
fatalerror("Cannot change the section within a UNION\n");
|
||||
|
||||
sym_SetCurrentSymbolScope(std::nullopt);
|
||||
sym_ResetCurrentLabelScopes();
|
||||
}
|
||||
|
||||
bool Section::isSizeKnown() const {
|
||||
@@ -475,7 +454,7 @@ void sect_SetLoadSection(
|
||||
// Therefore, any interactions are NOT TESTED, so lift either of those restrictions at
|
||||
// your own peril! ^^
|
||||
|
||||
if (!checkcodesection())
|
||||
if (!requireCodeSection())
|
||||
return;
|
||||
|
||||
if (currentLoadSection) {
|
||||
@@ -495,7 +474,7 @@ void sect_SetLoadSection(
|
||||
|
||||
Section *sect = getSection(name, type, org, attrs, mod);
|
||||
|
||||
currentLoadScope = sym_GetCurrentSymbolScope();
|
||||
currentLoadLabelScopes = sym_GetCurrentLabelScopes();
|
||||
changeSection();
|
||||
loadOffset = curOffset - (mod == SECTION_UNION ? 0 : sect->size);
|
||||
curOffset -= loadOffset;
|
||||
@@ -512,7 +491,7 @@ void sect_EndLoadSection() {
|
||||
curOffset += loadOffset;
|
||||
loadOffset = 0;
|
||||
currentLoadSection = nullptr;
|
||||
sym_SetCurrentSymbolScope(currentLoadScope);
|
||||
sym_SetCurrentLabelScopes(currentLoadLabelScopes);
|
||||
}
|
||||
|
||||
Section *sect_GetSymbolSection() {
|
||||
@@ -549,7 +528,7 @@ uint32_t sect_GetAlignBytes(uint8_t alignment, uint16_t offset) {
|
||||
}
|
||||
|
||||
void sect_AlignPC(uint8_t alignment, uint16_t offset) {
|
||||
if (!checksection())
|
||||
if (!requireSection())
|
||||
return;
|
||||
|
||||
Section *sect = sect_GetSymbolSection();
|
||||
@@ -561,7 +540,8 @@ void sect_AlignPC(uint8_t alignment, uint16_t offset) {
|
||||
"Section's fixed address fails required alignment (PC = $%04" PRIx32 ")\n",
|
||||
sect->org + curOffset
|
||||
);
|
||||
} else if (sect->align != 0 && (((sect->alignOfs + curOffset) % (1u << sect->align)) - offset) % alignSize) {
|
||||
} else if (sect->align != 0
|
||||
&& (((sect->alignOfs + curOffset) % (1u << sect->align)) - offset) % alignSize) {
|
||||
error(
|
||||
"Section's alignment fails required alignment (offset from section start = $%04" PRIx32
|
||||
")\n",
|
||||
@@ -583,28 +563,31 @@ void sect_AlignPC(uint8_t alignment, uint16_t offset) {
|
||||
}
|
||||
|
||||
static void growSection(uint32_t growth) {
|
||||
if (growth > 0 && curOffset > UINT32_MAX - growth)
|
||||
fatalerror("Section size would overflow internal counter\n");
|
||||
curOffset += growth;
|
||||
if (curOffset + loadOffset > currentSection->size)
|
||||
currentSection->size = curOffset + loadOffset;
|
||||
if (uint32_t outOffset = sect_GetOutputOffset(); outOffset > currentSection->size)
|
||||
currentSection->size = outOffset;
|
||||
if (currentLoadSection && curOffset > currentLoadSection->size)
|
||||
currentLoadSection->size = curOffset;
|
||||
}
|
||||
|
||||
static void writebyte(uint8_t byte) {
|
||||
currentSection->data[sect_GetOutputOffset()] = byte;
|
||||
static void writeByte(uint8_t byte) {
|
||||
if (uint32_t index = sect_GetOutputOffset(); index < currentSection->data.size())
|
||||
currentSection->data[index] = byte;
|
||||
growSection(1);
|
||||
}
|
||||
|
||||
static void writeword(uint16_t b) {
|
||||
writebyte(b & 0xFF);
|
||||
writebyte(b >> 8);
|
||||
static void writeWord(uint16_t value) {
|
||||
writeByte(value & 0xFF);
|
||||
writeByte(value >> 8);
|
||||
}
|
||||
|
||||
static void writelong(uint32_t b) {
|
||||
writebyte(b & 0xFF);
|
||||
writebyte(b >> 8);
|
||||
writebyte(b >> 16);
|
||||
writebyte(b >> 24);
|
||||
static void writeLong(uint32_t value) {
|
||||
writeByte(value & 0xFF);
|
||||
writeByte(value >> 8);
|
||||
writeByte(value >> 16);
|
||||
writeByte(value >> 24);
|
||||
}
|
||||
|
||||
static void createPatch(PatchType type, Expression const &expr, uint32_t pcShift) {
|
||||
@@ -661,51 +644,54 @@ void sect_CheckUnionClosed() {
|
||||
error("Unterminated UNION construct\n");
|
||||
}
|
||||
|
||||
// Output an absolute byte
|
||||
void sect_AbsByte(uint8_t b) {
|
||||
if (!checkcodesection())
|
||||
return;
|
||||
if (!reserveSpace(1))
|
||||
// Output a constant byte
|
||||
void sect_ConstByte(uint8_t byte) {
|
||||
if (!requireCodeSection())
|
||||
return;
|
||||
|
||||
writebyte(b);
|
||||
writeByte(byte);
|
||||
}
|
||||
|
||||
void sect_AbsByteGroup(uint8_t const *s, size_t length) {
|
||||
if (!checkcodesection())
|
||||
return;
|
||||
if (!reserveSpace(length))
|
||||
// Output a string's character units as bytes
|
||||
void sect_ByteString(std::vector<int32_t> const &string) {
|
||||
if (!requireCodeSection())
|
||||
return;
|
||||
|
||||
while (length--)
|
||||
writebyte(*s++);
|
||||
for (int32_t unit : string) {
|
||||
if (!checkNBit(unit, 8, "All character units"))
|
||||
break;
|
||||
}
|
||||
|
||||
for (int32_t unit : string)
|
||||
writeByte(static_cast<uint8_t>(unit));
|
||||
}
|
||||
|
||||
void sect_AbsWordGroup(uint8_t const *s, size_t length) {
|
||||
if (!checkcodesection())
|
||||
return;
|
||||
if (!reserveSpace(length * 2))
|
||||
// Output a string's character units as words
|
||||
void sect_WordString(std::vector<int32_t> const &string) {
|
||||
if (!requireCodeSection())
|
||||
return;
|
||||
|
||||
while (length--)
|
||||
writeword(*s++);
|
||||
for (int32_t unit : string) {
|
||||
if (!checkNBit(unit, 16, "All character units"))
|
||||
break;
|
||||
}
|
||||
|
||||
for (int32_t unit : string)
|
||||
writeWord(static_cast<uint16_t>(unit));
|
||||
}
|
||||
|
||||
void sect_AbsLongGroup(uint8_t const *s, size_t length) {
|
||||
if (!checkcodesection())
|
||||
return;
|
||||
if (!reserveSpace(length * 4))
|
||||
// Output a string's character units as longs
|
||||
void sect_LongString(std::vector<int32_t> const &string) {
|
||||
if (!requireCodeSection())
|
||||
return;
|
||||
|
||||
while (length--)
|
||||
writelong(*s++);
|
||||
for (int32_t unit : string)
|
||||
writeLong(static_cast<uint32_t>(unit));
|
||||
}
|
||||
|
||||
// Skip this many bytes
|
||||
void sect_Skip(uint32_t skip, bool ds) {
|
||||
if (!checksection())
|
||||
return;
|
||||
if (!reserveSpace(skip))
|
||||
if (!requireSection())
|
||||
return;
|
||||
|
||||
if (!sect_HasData(currentSection->type)) {
|
||||
@@ -721,32 +707,26 @@ void sect_Skip(uint32_t skip, bool ds) {
|
||||
);
|
||||
// We know we're in a code SECTION
|
||||
while (skip--)
|
||||
writebyte(fillByte);
|
||||
writeByte(fillByte);
|
||||
}
|
||||
}
|
||||
|
||||
// Output a relocatable byte. Checking will be done to see if it
|
||||
// is an absolute value in disguise.
|
||||
// Output a byte that can be relocatable or constant
|
||||
void sect_RelByte(Expression &expr, uint32_t pcShift) {
|
||||
if (!checkcodesection())
|
||||
return;
|
||||
if (!reserveSpace(1))
|
||||
if (!requireCodeSection())
|
||||
return;
|
||||
|
||||
if (!expr.isKnown()) {
|
||||
createPatch(PATCHTYPE_BYTE, expr, pcShift);
|
||||
writebyte(0);
|
||||
writeByte(0);
|
||||
} else {
|
||||
writebyte(expr.value());
|
||||
writeByte(expr.value());
|
||||
}
|
||||
}
|
||||
|
||||
// Output several copies of a relocatable byte. Checking will be done to see if
|
||||
// it is an absolute value in disguise.
|
||||
// Output several bytes that can be relocatable or constant
|
||||
void sect_RelBytes(uint32_t n, std::vector<Expression> &exprs) {
|
||||
if (!checkcodesection())
|
||||
return;
|
||||
if (!reserveSpace(n))
|
||||
if (!requireCodeSection())
|
||||
return;
|
||||
|
||||
for (uint32_t i = 0; i < n; i++) {
|
||||
@@ -754,57 +734,47 @@ void sect_RelBytes(uint32_t n, std::vector<Expression> &exprs) {
|
||||
|
||||
if (!expr.isKnown()) {
|
||||
createPatch(PATCHTYPE_BYTE, expr, i);
|
||||
writebyte(0);
|
||||
writeByte(0);
|
||||
} else {
|
||||
writebyte(expr.value());
|
||||
writeByte(expr.value());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Output a relocatable word. Checking will be done to see if
|
||||
// it's an absolute value in disguise.
|
||||
// Output a word that can be relocatable or constant
|
||||
void sect_RelWord(Expression &expr, uint32_t pcShift) {
|
||||
if (!checkcodesection())
|
||||
return;
|
||||
if (!reserveSpace(2))
|
||||
if (!requireCodeSection())
|
||||
return;
|
||||
|
||||
if (!expr.isKnown()) {
|
||||
createPatch(PATCHTYPE_WORD, expr, pcShift);
|
||||
writeword(0);
|
||||
writeWord(0);
|
||||
} else {
|
||||
writeword(expr.value());
|
||||
writeWord(expr.value());
|
||||
}
|
||||
}
|
||||
|
||||
// Output a relocatable longword. Checking will be done to see if
|
||||
// is an absolute value in disguise.
|
||||
// Output a long that can be relocatable or constant
|
||||
void sect_RelLong(Expression &expr, uint32_t pcShift) {
|
||||
if (!checkcodesection())
|
||||
return;
|
||||
if (!reserveSpace(2))
|
||||
if (!requireCodeSection())
|
||||
return;
|
||||
|
||||
if (!expr.isKnown()) {
|
||||
createPatch(PATCHTYPE_LONG, expr, pcShift);
|
||||
writelong(0);
|
||||
writeLong(0);
|
||||
} else {
|
||||
writelong(expr.value());
|
||||
writeLong(expr.value());
|
||||
}
|
||||
}
|
||||
|
||||
// Output a PC-relative relocatable byte. Checking will be done to see if it
|
||||
// is an absolute value in disguise.
|
||||
// Output a PC-relative byte that can be relocatable or constant
|
||||
void sect_PCRelByte(Expression &expr, uint32_t pcShift) {
|
||||
if (!checkcodesection())
|
||||
if (!requireCodeSection())
|
||||
return;
|
||||
if (!reserveSpace(1))
|
||||
return;
|
||||
Symbol const *pc = sym_GetPC();
|
||||
|
||||
if (!expr.isDiffConstant(pc)) {
|
||||
if (Symbol const *pc = sym_GetPC(); !expr.isDiffConstant(pc)) {
|
||||
createPatch(PATCHTYPE_JR, expr, pcShift);
|
||||
writebyte(0);
|
||||
writeByte(0);
|
||||
} else {
|
||||
Symbol const *sym = expr.symbolOf();
|
||||
// The offset wraps (jump from ROM to HRAM, for example)
|
||||
@@ -822,9 +792,9 @@ void sect_PCRelByte(Expression &expr, uint32_t pcShift) {
|
||||
"; use jp instead\n",
|
||||
offset
|
||||
);
|
||||
writebyte(0);
|
||||
writeByte(0);
|
||||
} else {
|
||||
writebyte(offset);
|
||||
writeByte(offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -835,7 +805,7 @@ void sect_BinaryFile(std::string const &name, int32_t startPos) {
|
||||
error("Start position cannot be negative (%" PRId32 ")\n", startPos);
|
||||
startPos = 0;
|
||||
}
|
||||
if (!checkcodesection())
|
||||
if (!requireCodeSection())
|
||||
return;
|
||||
|
||||
FILE *file = nullptr;
|
||||
@@ -853,35 +823,31 @@ void sect_BinaryFile(std::string const &name, int32_t startPos) {
|
||||
}
|
||||
Defer closeFile{[&] { fclose(file); }};
|
||||
|
||||
int32_t fsize = -1;
|
||||
|
||||
if (fseek(file, 0, SEEK_END) != -1) {
|
||||
fsize = ftell(file);
|
||||
|
||||
if (startPos > fsize) {
|
||||
error("Specified start position is greater than length of file\n");
|
||||
if (startPos > ftell(file)) {
|
||||
error("Specified start position is greater than length of file '%s'\n", name.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
// The file is seekable; skip to the specified start position
|
||||
fseek(file, startPos, SEEK_SET);
|
||||
|
||||
if (!reserveSpace(fsize - startPos))
|
||||
return;
|
||||
} else {
|
||||
if (errno != ESPIPE)
|
||||
error(
|
||||
"Error determining size of INCBIN file '%s': %s\n", name.c_str(), strerror(errno)
|
||||
);
|
||||
// The file isn't seekable, so we'll just skip bytes
|
||||
while (startPos--)
|
||||
(void)fgetc(file);
|
||||
// The file isn't seekable, so we'll just skip bytes one at a time
|
||||
while (startPos--) {
|
||||
if (fgetc(file) == EOF) {
|
||||
error(
|
||||
"Specified start position is greater than length of file '%s'\n", name.c_str()
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int byte; (byte = fgetc(file)) != EOF;) {
|
||||
if (fsize == -1)
|
||||
growSection(1);
|
||||
writebyte(byte);
|
||||
}
|
||||
for (int byte; (byte = fgetc(file)) != EOF;)
|
||||
writeByte(byte);
|
||||
|
||||
if (ferror(file))
|
||||
error("Error reading INCBIN file '%s': %s\n", name.c_str(), strerror(errno));
|
||||
@@ -893,18 +859,14 @@ void sect_BinaryFileSlice(std::string const &name, int32_t startPos, int32_t len
|
||||
error("Start position cannot be negative (%" PRId32 ")\n", startPos);
|
||||
startPos = 0;
|
||||
}
|
||||
|
||||
if (length < 0) {
|
||||
error("Number of bytes to read cannot be negative (%" PRId32 ")\n", length);
|
||||
length = 0;
|
||||
}
|
||||
|
||||
if (!checkcodesection())
|
||||
if (!requireCodeSection())
|
||||
return;
|
||||
if (length == 0) // Don't even bother with 0-byte slices
|
||||
return;
|
||||
if (!reserveSpace(length))
|
||||
return;
|
||||
|
||||
FILE *file = nullptr;
|
||||
if (std::optional<std::string> fullPath = fstk_FindFile(name); fullPath)
|
||||
@@ -922,44 +884,49 @@ void sect_BinaryFileSlice(std::string const &name, int32_t startPos, int32_t len
|
||||
Defer closeFile{[&] { fclose(file); }};
|
||||
|
||||
if (fseek(file, 0, SEEK_END) != -1) {
|
||||
int32_t fsize = ftell(file);
|
||||
|
||||
if (startPos > fsize) {
|
||||
error("Specified start position is greater than length of file\n");
|
||||
if (int32_t fsize = ftell(file); startPos > fsize) {
|
||||
error("Specified start position is greater than length of file '%s'\n", name.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
if ((startPos + length) > fsize) {
|
||||
} else if (startPos + length > fsize) {
|
||||
error(
|
||||
"Specified range in INCBIN is out of bounds (%" PRIu32 " + %" PRIu32 " > %" PRIu32
|
||||
")\n",
|
||||
"Specified range in INCBIN file '%s' is out of bounds (%" PRIu32 " + %" PRIu32
|
||||
" > %" PRIu32 ")\n",
|
||||
name.c_str(),
|
||||
startPos,
|
||||
length,
|
||||
fsize
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// The file is seekable; skip to the specified start position
|
||||
fseek(file, startPos, SEEK_SET);
|
||||
} else {
|
||||
if (errno != ESPIPE)
|
||||
error(
|
||||
"Error determining size of INCBIN file '%s': %s\n", name.c_str(), strerror(errno)
|
||||
);
|
||||
// The file isn't seekable, so we'll just skip bytes
|
||||
while (startPos--)
|
||||
(void)fgetc(file);
|
||||
// The file isn't seekable, so we'll just skip bytes one at a time
|
||||
while (startPos--) {
|
||||
if (fgetc(file) == EOF) {
|
||||
error(
|
||||
"Specified start position is greater than length of file '%s'\n", name.c_str()
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (length--) {
|
||||
int byte = fgetc(file);
|
||||
|
||||
if (byte != EOF) {
|
||||
writebyte(byte);
|
||||
if (int byte = fgetc(file); byte != EOF) {
|
||||
writeByte(byte);
|
||||
} else if (ferror(file)) {
|
||||
error("Error reading INCBIN file '%s': %s\n", name.c_str(), strerror(errno));
|
||||
} else {
|
||||
error("Premature end of file (%" PRId32 " bytes left to read)\n", length + 1);
|
||||
error(
|
||||
"Premature end of INCBIN file '%s' (%" PRId32 " bytes left to read)\n",
|
||||
name.c_str(),
|
||||
length + 1
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -969,7 +936,7 @@ void sect_PushSection() {
|
||||
sectionStack.push_front({
|
||||
.section = currentSection,
|
||||
.loadSection = currentLoadSection,
|
||||
.scope = sym_GetCurrentSymbolScope(),
|
||||
.labelScopes = sym_GetCurrentLabelScopes(),
|
||||
.offset = curOffset,
|
||||
.loadOffset = loadOffset,
|
||||
.unionStack = {},
|
||||
@@ -978,7 +945,7 @@ void sect_PushSection() {
|
||||
// Reset the section scope
|
||||
currentSection = nullptr;
|
||||
currentLoadSection = nullptr;
|
||||
sym_SetCurrentSymbolScope(std::nullopt);
|
||||
sym_ResetCurrentLabelScopes();
|
||||
std::swap(currentUnionStack, sectionStack.front().unionStack);
|
||||
}
|
||||
|
||||
@@ -995,7 +962,7 @@ void sect_PopSection() {
|
||||
changeSection();
|
||||
currentSection = entry.section;
|
||||
currentLoadSection = entry.loadSection;
|
||||
sym_SetCurrentSymbolScope(entry.scope);
|
||||
sym_SetCurrentLabelScopes(entry.labelScopes);
|
||||
curOffset = entry.offset;
|
||||
loadOffset = entry.loadOffset;
|
||||
std::swap(currentUnionStack, entry.unionStack);
|
||||
@@ -1013,5 +980,5 @@ void sect_EndSection() {
|
||||
|
||||
// Reset the section scope
|
||||
currentSection = nullptr;
|
||||
sym_SetCurrentSymbolScope(std::nullopt);
|
||||
sym_ResetCurrentLabelScopes();
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
#include "error.hpp"
|
||||
#include "helpers.hpp" // assume
|
||||
@@ -19,11 +20,15 @@
|
||||
using namespace std::literals;
|
||||
|
||||
std::unordered_map<std::string, Symbol> symbols;
|
||||
std::unordered_set<std::string> purgedSymbols;
|
||||
|
||||
static std::optional<std::string> labelScope = std::nullopt; // Current section's label scope
|
||||
static Symbol const *globalScope = nullptr; // Current section's global label scope
|
||||
static Symbol const *localScope = nullptr; // Current section's local label scope
|
||||
static Symbol *PCSymbol;
|
||||
static Symbol *_NARGSymbol;
|
||||
static Symbol *_RSSymbol;
|
||||
static Symbol *NARGSymbol;
|
||||
static Symbol *globalScopeSymbol;
|
||||
static Symbol *localScopeSymbol;
|
||||
static Symbol *RSSymbol;
|
||||
static char savedTIME[256];
|
||||
static char savedDATE[256];
|
||||
static char savedTIMESTAMP_ISO8601_LOCAL[256];
|
||||
@@ -39,19 +44,33 @@ void sym_ForEach(void (*callback)(Symbol &)) {
|
||||
callback(it.second);
|
||||
}
|
||||
|
||||
static int32_t Callback_NARG() {
|
||||
static int32_t NARGCallback() {
|
||||
if (MacroArgs const *macroArgs = fstk_GetCurrentMacroArgs(); macroArgs) {
|
||||
return macroArgs->nbArgs();
|
||||
} else {
|
||||
error("_NARG does not make sense outside of a macro\n");
|
||||
error("_NARG has no value outside of a macro\n");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int32_t CallbackPC() {
|
||||
Section const *section = sect_GetSymbolSection();
|
||||
static std::shared_ptr<std::string> globalScopeCallback() {
|
||||
if (!globalScope) {
|
||||
error("\".\" has no value outside of a label scope\n");
|
||||
return std::make_shared<std::string>("");
|
||||
}
|
||||
return std::make_shared<std::string>(globalScope->name);
|
||||
}
|
||||
|
||||
return section ? section->org + sect_GetSymbolOffset() : 0;
|
||||
static std::shared_ptr<std::string> localScopeCallback() {
|
||||
if (!localScope) {
|
||||
error("\"..\" has no value outside of a local label scope\n");
|
||||
return std::make_shared<std::string>("");
|
||||
}
|
||||
return std::make_shared<std::string>(localScope->name);
|
||||
}
|
||||
|
||||
static int32_t PCCallback() {
|
||||
return sect_GetSymbolSection()->org + sect_GetSymbolOffset();
|
||||
}
|
||||
|
||||
int32_t Symbol::getValue() const {
|
||||
@@ -78,7 +97,12 @@ ContentSpan const &Symbol::getMacro() const {
|
||||
}
|
||||
|
||||
std::shared_ptr<std::string> Symbol::getEqus() const {
|
||||
assume(std::holds_alternative<std::shared_ptr<std::string>>(data));
|
||||
assume(
|
||||
std::holds_alternative<std::shared_ptr<std::string>>(data)
|
||||
|| std::holds_alternative<std::shared_ptr<std::string> (*)()>(data)
|
||||
);
|
||||
if (auto *callback = std::get_if<std::shared_ptr<std::string> (*)()>(&data); callback)
|
||||
return (*callback)();
|
||||
return std::get<std::shared_ptr<std::string>>(data);
|
||||
}
|
||||
|
||||
@@ -93,7 +117,6 @@ static void dumpFilename(Symbol const &sym) {
|
||||
}
|
||||
}
|
||||
|
||||
// Update a symbol's definition filename and line
|
||||
static void updateSymbolFilename(Symbol &sym) {
|
||||
std::shared_ptr<FileStackNode> oldSrc = std::move(sym.src);
|
||||
sym.src = fstk_GetFileStack();
|
||||
@@ -104,8 +127,40 @@ static void updateSymbolFilename(Symbol &sym) {
|
||||
out_RegisterNode(sym.src);
|
||||
}
|
||||
|
||||
// Create a new symbol by name
|
||||
static void alreadyDefinedError(Symbol const &sym, char const *asType) {
|
||||
if (sym.isBuiltin && !sym_FindScopedValidSymbol(sym.name)) {
|
||||
// `DEF()` would return false, so we should not claim the symbol is already defined
|
||||
error("'%s' is reserved for a built-in symbol\n", sym.name.c_str());
|
||||
} else {
|
||||
error("'%s' already defined", sym.name.c_str());
|
||||
if (asType)
|
||||
fprintf(stderr, " as %s", asType);
|
||||
fputs(" at ", stderr);
|
||||
dumpFilename(sym);
|
||||
}
|
||||
}
|
||||
|
||||
static void redefinedError(Symbol const &sym) {
|
||||
assume(sym.isBuiltin);
|
||||
if (!sym_FindScopedValidSymbol(sym.name)) {
|
||||
// `DEF()` would return false, so we should not imply the symbol is already defined
|
||||
error("'%s' is reserved for a built-in symbol\n", sym.name.c_str());
|
||||
} else {
|
||||
error("Built-in symbol '%s' cannot be redefined\n", sym.name.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
static void assumeAlreadyExpanded(std::string const &symName) {
|
||||
// Either the symbol name is `Global.local` or entirely '.'s (for scopes `.` and `..`),
|
||||
// but cannot be unqualified `.local`
|
||||
assume(!symName.starts_with('.') || symName.find_first_not_of('.') == symName.npos);
|
||||
}
|
||||
|
||||
static Symbol &createSymbol(std::string const &symName) {
|
||||
assumeAlreadyExpanded(symName);
|
||||
|
||||
static uint32_t nextDefIndex = 0;
|
||||
|
||||
Symbol &sym = symbols[symName];
|
||||
|
||||
sym.name = symName;
|
||||
@@ -115,39 +170,77 @@ static Symbol &createSymbol(std::string const &symName) {
|
||||
sym.src = fstk_GetFileStack();
|
||||
sym.fileLine = sym.src ? lexer_GetLineNo() : 0;
|
||||
sym.ID = -1;
|
||||
sym.defIndex = nextDefIndex++;
|
||||
|
||||
return sym;
|
||||
}
|
||||
|
||||
static bool isAutoScoped(std::string const &symName) {
|
||||
// `globalScope` should be global if it's defined
|
||||
assume(!globalScope || globalScope->name.find('.') == std::string::npos);
|
||||
// `localScope` should be qualified local if it's defined
|
||||
assume(!localScope || localScope->name.find('.') != std::string::npos);
|
||||
|
||||
size_t dotPos = symName.find('.');
|
||||
|
||||
// If there are no dots, it's not a local label
|
||||
if (dotPos == std::string::npos)
|
||||
return false;
|
||||
|
||||
// Label scopes `.` and `..` are the only nonlocal identifiers that start with a dot
|
||||
if (dotPos == 0 && symName.find_first_not_of('.') == symName.npos)
|
||||
return false;
|
||||
|
||||
// Check for nothing after the dot
|
||||
if (dotPos == symName.length() - 1)
|
||||
fatalerror("'%s' is a nonsensical reference to an empty local label\n", symName.c_str());
|
||||
|
||||
// Check for more than one dot
|
||||
if (symName.find('.', dotPos + 1) != std::string::npos)
|
||||
fatalerror("'%s' is a nonsensical reference to a nested local label\n", symName.c_str());
|
||||
|
||||
// Check for already-qualified local label
|
||||
if (dotPos > 0)
|
||||
return false;
|
||||
|
||||
// Check for unqualifiable local label
|
||||
if (!globalScope)
|
||||
fatalerror("Unqualified local label '%s' in main scope\n", symName.c_str());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Symbol *sym_FindExactSymbol(std::string const &symName) {
|
||||
assumeAlreadyExpanded(symName);
|
||||
|
||||
auto search = symbols.find(symName);
|
||||
return search != symbols.end() ? &search->second : nullptr;
|
||||
}
|
||||
|
||||
Symbol *sym_FindScopedSymbol(std::string const &symName) {
|
||||
if (size_t dotPos = symName.find('.'); dotPos != std::string::npos) {
|
||||
if (symName.find('.', dotPos + 1) != std::string::npos)
|
||||
fatalerror(
|
||||
"'%s' is a nonsensical reference to a nested local symbol\n", symName.c_str()
|
||||
);
|
||||
// If auto-scoped local label, expand the name
|
||||
if (dotPos == 0 && labelScope)
|
||||
return sym_FindExactSymbol(*labelScope + symName);
|
||||
}
|
||||
return sym_FindExactSymbol(symName);
|
||||
return sym_FindExactSymbol(isAutoScoped(symName) ? globalScope->name + symName : symName);
|
||||
}
|
||||
|
||||
Symbol *sym_FindScopedValidSymbol(std::string const &symName) {
|
||||
Symbol *sym = sym_FindScopedSymbol(symName);
|
||||
|
||||
// `@` has no value outside a section
|
||||
// `@` has no value outside of a section
|
||||
if (sym_IsPC(sym) && !sect_GetSymbolSection()) {
|
||||
return nullptr;
|
||||
}
|
||||
// `_NARG` has no value outside a macro
|
||||
if (sym == _NARGSymbol && !fstk_GetCurrentMacroArgs()) {
|
||||
// `_NARG` has no value outside of a macro
|
||||
if (sym == NARGSymbol && !fstk_GetCurrentMacroArgs()) {
|
||||
return nullptr;
|
||||
}
|
||||
// `.` has no value outside of a global label scope
|
||||
if (sym == globalScopeSymbol && !globalScope) {
|
||||
return nullptr;
|
||||
}
|
||||
// `..` has no value outside of a local label scope
|
||||
if (sym == localScopeSymbol && !localScope) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return sym;
|
||||
}
|
||||
|
||||
@@ -155,89 +248,105 @@ Symbol const *sym_GetPC() {
|
||||
return PCSymbol;
|
||||
}
|
||||
|
||||
// Purge a symbol
|
||||
void sym_Purge(std::string const &symName) {
|
||||
Symbol *sym = sym_FindScopedValidSymbol(symName);
|
||||
|
||||
if (!sym) {
|
||||
error("'%s' not defined\n", symName.c_str());
|
||||
if (sym_IsPurgedScoped(symName))
|
||||
error("'%s' was already purged\n", symName.c_str());
|
||||
else
|
||||
error("'%s' not defined\n", symName.c_str());
|
||||
} else if (sym->isBuiltin) {
|
||||
error("Built-in symbol '%s' cannot be purged\n", symName.c_str());
|
||||
} else if (sym->ID != (uint32_t)-1) {
|
||||
error("Symbol \"%s\" is referenced and thus cannot be purged\n", symName.c_str());
|
||||
} else {
|
||||
// Do not keep a reference to the label's name after purging it
|
||||
if (sym->name == labelScope)
|
||||
labelScope = std::nullopt;
|
||||
if (sym->isExported)
|
||||
warning(WARNING_PURGE_1, "Purging an exported symbol \"%s\"\n", symName.c_str());
|
||||
else if (sym->isLabel())
|
||||
warning(WARNING_PURGE_2, "Purging a label \"%s\"\n", symName.c_str());
|
||||
// Do not keep a reference to the label after purging it
|
||||
if (sym == globalScope)
|
||||
globalScope = nullptr;
|
||||
if (sym == localScope)
|
||||
localScope = nullptr;
|
||||
purgedSymbols.emplace(sym->name);
|
||||
symbols.erase(sym->name);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t sym_GetPCValue() {
|
||||
Section const *sect = sect_GetSymbolSection();
|
||||
bool sym_IsPurgedExact(std::string const &symName) {
|
||||
assumeAlreadyExpanded(symName);
|
||||
|
||||
if (!sect)
|
||||
error("PC has no value outside a section\n");
|
||||
else if (sect->org == (uint32_t)-1)
|
||||
error("Expected constant PC but section is not fixed\n");
|
||||
else
|
||||
return CallbackPC();
|
||||
return 0;
|
||||
return purgedSymbols.find(symName) != purgedSymbols.end();
|
||||
}
|
||||
|
||||
bool sym_IsPurgedScoped(std::string const &symName) {
|
||||
return sym_IsPurgedExact(isAutoScoped(symName) ? globalScope->name + symName : symName);
|
||||
}
|
||||
|
||||
int32_t sym_GetRSValue() {
|
||||
return _RSSymbol->getOutputValue();
|
||||
return RSSymbol->getOutputValue();
|
||||
}
|
||||
|
||||
void sym_SetRSValue(int32_t value) {
|
||||
updateSymbolFilename(*_RSSymbol);
|
||||
_RSSymbol->data = value;
|
||||
updateSymbolFilename(*RSSymbol);
|
||||
RSSymbol->data = value;
|
||||
}
|
||||
|
||||
// Return a constant symbol's value, assuming it's defined
|
||||
uint32_t Symbol::getConstantValue() const {
|
||||
if (sym_IsPC(this))
|
||||
return sym_GetPCValue();
|
||||
|
||||
if (isConstant())
|
||||
return getValue();
|
||||
|
||||
error("\"%s\" does not have a constant value\n", name.c_str());
|
||||
if (sym_IsPC(this)) {
|
||||
if (!getSection())
|
||||
error("PC has no value outside of a section\n");
|
||||
else
|
||||
error("PC does not have a constant value; the current section is not fixed\n");
|
||||
} else {
|
||||
error("\"%s\" does not have a constant value\n", name.c_str());
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Return a constant symbol's value
|
||||
uint32_t sym_GetConstantValue(std::string const &symName) {
|
||||
if (Symbol const *sym = sym_FindScopedSymbol(symName); sym)
|
||||
return sym->getConstantValue();
|
||||
|
||||
error("'%s' not defined\n", symName.c_str());
|
||||
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::optional<std::string> const &sym_GetCurrentSymbolScope() {
|
||||
return labelScope;
|
||||
std::pair<Symbol const *, Symbol const *> sym_GetCurrentLabelScopes() {
|
||||
return {globalScope, localScope};
|
||||
}
|
||||
|
||||
void sym_SetCurrentSymbolScope(std::optional<std::string> const &newScope) {
|
||||
labelScope = newScope;
|
||||
void sym_SetCurrentLabelScopes(std::pair<Symbol const *, Symbol const *> newScopes) {
|
||||
globalScope = std::get<0>(newScopes);
|
||||
localScope = std::get<1>(newScopes);
|
||||
|
||||
// `globalScope` should be global if it's defined
|
||||
assume(!globalScope || globalScope->name.find('.') == std::string::npos);
|
||||
// `localScope` should be qualified local if it's defined
|
||||
assume(!localScope || localScope->name.find('.') != std::string::npos);
|
||||
}
|
||||
|
||||
void sym_ResetCurrentLabelScopes() {
|
||||
globalScope = nullptr;
|
||||
localScope = nullptr;
|
||||
}
|
||||
|
||||
/*
|
||||
* Create a symbol that will be non-relocatable and ensure that it
|
||||
* hasn't already been defined or referenced in a context that would
|
||||
* require that it be relocatable
|
||||
* @param symName The name of the symbol to create
|
||||
* @param numeric If false, the symbol may not have been referenced earlier
|
||||
*/
|
||||
static Symbol *createNonrelocSymbol(std::string const &symName, bool numeric) {
|
||||
Symbol *sym = sym_FindExactSymbol(symName);
|
||||
|
||||
if (!sym) {
|
||||
sym = &createSymbol(symName);
|
||||
purgedSymbols.erase(sym->name);
|
||||
} else if (sym->isDefined()) {
|
||||
error("'%s' already defined at ", symName.c_str());
|
||||
dumpFilename(*sym);
|
||||
alreadyDefinedError(*sym, nullptr);
|
||||
return nullptr; // Don't allow overriding the symbol, that'd be bad!
|
||||
} else if (!numeric) {
|
||||
// The symbol has already been referenced, but it's not allowed
|
||||
@@ -249,7 +358,6 @@ static Symbol *createNonrelocSymbol(std::string const &symName, bool numeric) {
|
||||
return sym;
|
||||
}
|
||||
|
||||
// Add an equated symbol
|
||||
Symbol *sym_AddEqu(std::string const &symName, int32_t value) {
|
||||
Symbol *sym = createNonrelocSymbol(symName, true);
|
||||
|
||||
@@ -269,11 +377,10 @@ Symbol *sym_RedefEqu(std::string const &symName, int32_t value) {
|
||||
return sym_AddEqu(symName, value);
|
||||
|
||||
if (sym->isDefined() && sym->type != SYM_EQU) {
|
||||
error("'%s' already defined as non-EQU at ", symName.c_str());
|
||||
dumpFilename(*sym);
|
||||
alreadyDefinedError(*sym, "non-EQU");
|
||||
return nullptr;
|
||||
} else if (sym->isBuiltin) {
|
||||
error("Built-in symbol '%s' cannot be redefined\n", symName.c_str());
|
||||
redefinedError(*sym);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -284,18 +391,6 @@ Symbol *sym_RedefEqu(std::string const &symName, int32_t value) {
|
||||
return sym;
|
||||
}
|
||||
|
||||
/*
|
||||
* Add a string equated symbol.
|
||||
*
|
||||
* If the desired symbol is a string it needs to be passed to this function with
|
||||
* quotes inside the string, like sym_AddString("name"s, "\"test\"), or the
|
||||
* assembler won't be able to use it with DB and similar. This is equivalent to
|
||||
* ``` name EQUS "\"test\"" ```
|
||||
*
|
||||
* If the desired symbol is a register or a number, just the terminator quotes
|
||||
* of the string are enough: sym_AddString("M_PI"s, "3.1415"). This is the same
|
||||
* as ``` M_PI EQUS "3.1415" ```
|
||||
*/
|
||||
Symbol *sym_AddString(std::string const &symName, std::shared_ptr<std::string> str) {
|
||||
Symbol *sym = createNonrelocSymbol(symName, false);
|
||||
|
||||
@@ -314,14 +409,15 @@ Symbol *sym_RedefString(std::string const &symName, std::shared_ptr<std::string>
|
||||
return sym_AddString(symName, str);
|
||||
|
||||
if (sym->type != SYM_EQUS) {
|
||||
if (sym->isDefined())
|
||||
error("'%s' already defined as non-EQUS at ", symName.c_str());
|
||||
else
|
||||
if (sym->isDefined()) {
|
||||
alreadyDefinedError(*sym, "non-EQUS");
|
||||
} else {
|
||||
error("'%s' already referenced at ", symName.c_str());
|
||||
dumpFilename(*sym);
|
||||
dumpFilename(*sym);
|
||||
}
|
||||
return nullptr;
|
||||
} else if (sym->isBuiltin) {
|
||||
error("Built-in symbol '%s' cannot be redefined\n", symName.c_str());
|
||||
redefinedError(*sym);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -331,19 +427,13 @@ Symbol *sym_RedefString(std::string const &symName, std::shared_ptr<std::string>
|
||||
return sym;
|
||||
}
|
||||
|
||||
// Alter a mutable symbol's value
|
||||
Symbol *sym_AddVar(std::string const &symName, int32_t value) {
|
||||
Symbol *sym = sym_FindExactSymbol(symName);
|
||||
|
||||
if (!sym) {
|
||||
sym = &createSymbol(symName);
|
||||
} else if (sym->isDefined() && sym->type != SYM_VAR) {
|
||||
error(
|
||||
"'%s' already defined as %s at ",
|
||||
symName.c_str(),
|
||||
sym->type == SYM_LABEL ? "label" : "constant"
|
||||
);
|
||||
dumpFilename(*sym);
|
||||
alreadyDefinedError(*sym, sym->type == SYM_LABEL ? "label" : "constant");
|
||||
return sym;
|
||||
} else {
|
||||
updateSymbolFilename(*sym);
|
||||
@@ -355,20 +445,15 @@ Symbol *sym_AddVar(std::string const &symName, int32_t value) {
|
||||
return sym;
|
||||
}
|
||||
|
||||
/*
|
||||
* Add a label (aka "relocatable symbol")
|
||||
* @param symName The label's full name (so `.name` is invalid)
|
||||
* @return The created symbol
|
||||
*/
|
||||
static Symbol *addLabel(std::string const &symName) {
|
||||
assume(!symName.starts_with('.')); // The symbol name must have been expanded prior
|
||||
assumeAlreadyExpanded(symName);
|
||||
|
||||
Symbol *sym = sym_FindExactSymbol(symName);
|
||||
|
||||
if (!sym) {
|
||||
sym = &createSymbol(symName);
|
||||
} else if (sym->isDefined()) {
|
||||
error("'%s' already defined at ", symName.c_str());
|
||||
dumpFilename(*sym);
|
||||
alreadyDefinedError(*sym, nullptr);
|
||||
return nullptr;
|
||||
} else {
|
||||
updateSymbolFilename(*sym);
|
||||
@@ -387,46 +472,35 @@ static Symbol *addLabel(std::string const &symName) {
|
||||
return sym;
|
||||
}
|
||||
|
||||
// Add a local (`.name` or `Parent.name`) relocatable symbol
|
||||
Symbol *sym_AddLocalLabel(std::string const &symName) {
|
||||
// Assuming no dots in `labelScope` if defined
|
||||
assume(!labelScope.has_value() || labelScope->find('.') == std::string::npos);
|
||||
// The symbol name should be local, qualified or not
|
||||
assume(symName.find('.') != std::string::npos);
|
||||
|
||||
size_t dotPos = symName.find('.');
|
||||
Symbol *sym = addLabel(isAutoScoped(symName) ? globalScope->name + symName : symName);
|
||||
|
||||
assume(dotPos != std::string::npos); // There should be at least one dot in `symName`
|
||||
if (sym)
|
||||
localScope = sym;
|
||||
|
||||
// Check for something after the dot
|
||||
if (dotPos == symName.length() - 1) {
|
||||
fatalerror("'%s' is a nonsensical reference to an empty local label\n", symName.c_str());
|
||||
}
|
||||
// Check for more than one dot
|
||||
if (symName.find('.', dotPos + 1) != std::string::npos)
|
||||
fatalerror("'%s' is a nonsensical reference to a nested local label\n", symName.c_str());
|
||||
|
||||
if (dotPos == 0) {
|
||||
if (!labelScope.has_value()) {
|
||||
error("Unqualified local label '%s' in main scope\n", symName.c_str());
|
||||
return nullptr;
|
||||
}
|
||||
return addLabel(*labelScope + symName);
|
||||
}
|
||||
return addLabel(symName);
|
||||
return sym;
|
||||
}
|
||||
|
||||
// Add a relocatable symbol
|
||||
Symbol *sym_AddLabel(std::string const &symName) {
|
||||
// The symbol name should be global
|
||||
assume(symName.find('.') == std::string::npos);
|
||||
|
||||
Symbol *sym = addLabel(symName);
|
||||
|
||||
// Set the symbol as the new scope
|
||||
if (sym)
|
||||
labelScope = sym->name;
|
||||
if (sym) {
|
||||
globalScope = sym;
|
||||
// A new global scope resets the local scope
|
||||
localScope = nullptr;
|
||||
}
|
||||
|
||||
return sym;
|
||||
}
|
||||
|
||||
static uint32_t anonLabelID = 0;
|
||||
|
||||
// Add an anonymous label
|
||||
Symbol *sym_AddAnonLabel() {
|
||||
if (anonLabelID == UINT32_MAX) {
|
||||
error("Only %" PRIu32 " anonymous labels can be created!", anonLabelID);
|
||||
@@ -438,7 +512,6 @@ Symbol *sym_AddAnonLabel() {
|
||||
return addLabel(anon);
|
||||
}
|
||||
|
||||
// Write an anonymous label's name to a buffer
|
||||
std::string sym_MakeAnonLabelName(uint32_t ofs, bool neg) {
|
||||
uint32_t id = 0;
|
||||
|
||||
@@ -471,7 +544,6 @@ std::string sym_MakeAnonLabelName(uint32_t ofs, bool neg) {
|
||||
return anon;
|
||||
}
|
||||
|
||||
// Export a symbol
|
||||
void sym_Export(std::string const &symName) {
|
||||
if (symName.starts_with('!')) {
|
||||
error("Anonymous labels cannot be exported\n");
|
||||
@@ -486,7 +558,6 @@ void sym_Export(std::string const &symName) {
|
||||
sym->isExported = true;
|
||||
}
|
||||
|
||||
// Add a macro definition
|
||||
Symbol *sym_AddMacro(std::string const &symName, int32_t defLineNo, ContentSpan const &span) {
|
||||
Symbol *sym = createNonrelocSymbol(symName, false);
|
||||
|
||||
@@ -510,16 +581,7 @@ Symbol *sym_Ref(std::string const &symName) {
|
||||
Symbol *sym = sym_FindScopedSymbol(symName);
|
||||
|
||||
if (!sym) {
|
||||
if (symName.starts_with('.')) {
|
||||
if (!labelScope.has_value())
|
||||
fatalerror("Local label reference '%s' in main scope\n", symName.c_str());
|
||||
std::string fullName = *labelScope + symName;
|
||||
|
||||
sym = &createSymbol(fullName);
|
||||
} else {
|
||||
sym = &createSymbol(symName);
|
||||
}
|
||||
|
||||
sym = &createSymbol(isAutoScoped(symName) ? globalScope->name + symName : symName);
|
||||
sym->type = SYM_REF;
|
||||
}
|
||||
|
||||
@@ -535,16 +597,26 @@ void sym_SetExportAll(bool set) {
|
||||
void sym_Init(time_t now) {
|
||||
PCSymbol = &createSymbol("@"s);
|
||||
PCSymbol->type = SYM_LABEL;
|
||||
PCSymbol->data = CallbackPC;
|
||||
PCSymbol->data = PCCallback;
|
||||
PCSymbol->isBuiltin = true;
|
||||
|
||||
_NARGSymbol = &createSymbol("_NARG"s);
|
||||
_NARGSymbol->type = SYM_EQU;
|
||||
_NARGSymbol->data = Callback_NARG;
|
||||
_NARGSymbol->isBuiltin = true;
|
||||
NARGSymbol = &createSymbol("_NARG"s);
|
||||
NARGSymbol->type = SYM_EQU;
|
||||
NARGSymbol->data = NARGCallback;
|
||||
NARGSymbol->isBuiltin = true;
|
||||
|
||||
_RSSymbol = sym_AddVar("_RS"s, 0);
|
||||
_RSSymbol->isBuiltin = true;
|
||||
globalScopeSymbol = &createSymbol("."s);
|
||||
globalScopeSymbol->type = SYM_EQUS;
|
||||
globalScopeSymbol->data = globalScopeCallback;
|
||||
globalScopeSymbol->isBuiltin = true;
|
||||
|
||||
localScopeSymbol = &createSymbol(".."s);
|
||||
localScopeSymbol->type = SYM_EQUS;
|
||||
localScopeSymbol->data = localScopeCallback;
|
||||
localScopeSymbol->isBuiltin = true;
|
||||
|
||||
RSSymbol = sym_AddVar("_RS"s, 0);
|
||||
RSSymbol->isBuiltin = true;
|
||||
|
||||
sym_AddString("__RGBDS_VERSION__"s, std::make_shared<std::string>(get_package_version_string()))
|
||||
->isBuiltin = true;
|
||||
|
||||
@@ -37,10 +37,12 @@ static WarningState const defaultWarnings[ARRAY_SIZE(warningStates)] = {
|
||||
WARNING_DISABLED, // WARNING_SHIFT_AMOUNT
|
||||
WARNING_ENABLED, // WARNING_USER
|
||||
|
||||
WARNING_ENABLED, // WARNING_NUMERIC_STRING_1
|
||||
WARNING_DISABLED, // WARNING_NUMERIC_STRING_1
|
||||
WARNING_DISABLED, // WARNING_NUMERIC_STRING_2
|
||||
WARNING_ENABLED, // WARNING_TRUNCATION_1
|
||||
WARNING_DISABLED, // WARNING_TRUNCATION_2
|
||||
WARNING_ENABLED, // WARNING_PURGE_1
|
||||
WARNING_DISABLED, // WARNING_PURGE_2
|
||||
WARNING_ENABLED, // WARNING_UNMAPPED_CHAR_1
|
||||
WARNING_DISABLED, // WARNING_UNMAPPED_CHAR_2
|
||||
};
|
||||
@@ -87,6 +89,8 @@ static char const * const warningFlags[NB_WARNINGS] = {
|
||||
// Parametric warnings
|
||||
"numeric-string",
|
||||
"numeric-string",
|
||||
"purge",
|
||||
"purge",
|
||||
"truncation",
|
||||
"truncation",
|
||||
"unmapped-char",
|
||||
@@ -104,6 +108,7 @@ static const struct {
|
||||
uint8_t defaultLevel;
|
||||
} paramWarnings[] = {
|
||||
{"numeric-string", 2, 1},
|
||||
{"purge", 2, 1},
|
||||
{"truncation", 2, 2},
|
||||
{"unmapped-char", 2, 1},
|
||||
};
|
||||
@@ -114,7 +119,10 @@ static bool tryProcessParamWarning(char const *flag, uint8_t param, WarningState
|
||||
for (size_t i = 0; i < ARRAY_SIZE(paramWarnings); i++) {
|
||||
uint8_t maxParam = paramWarnings[i].nbLevels;
|
||||
|
||||
if (!strcmp(paramWarnings[i].name, flag)) { // Match!
|
||||
if (!strcmp(flag, paramWarnings[i].name)) { // Match!
|
||||
if (!strcmp(flag, "numeric-string"))
|
||||
warning(WARNING_OBSOLETE, "Warning flag \"numeric-string\" is deprecated\n");
|
||||
|
||||
// If making the warning an error but param is 0, set to the maximum
|
||||
// This accommodates `-Werror=flag`, but also `-Werror=flag=0`, which is
|
||||
// thus filtered out by the caller.
|
||||
@@ -158,7 +166,8 @@ static uint8_t const _wallCommands[] = {
|
||||
WARNING_LARGE_CONSTANT,
|
||||
WARNING_NESTED_COMMENT,
|
||||
WARNING_OBSOLETE,
|
||||
WARNING_NUMERIC_STRING_1,
|
||||
WARNING_PURGE_1,
|
||||
WARNING_PURGE_2,
|
||||
WARNING_UNMAPPED_CHAR_1,
|
||||
META_WARNING_DONE,
|
||||
};
|
||||
@@ -169,7 +178,8 @@ static uint8_t const _wextraCommands[] = {
|
||||
WARNING_MACRO_SHIFT,
|
||||
WARNING_NESTED_COMMENT,
|
||||
WARNING_OBSOLETE,
|
||||
WARNING_NUMERIC_STRING_2,
|
||||
WARNING_PURGE_1,
|
||||
WARNING_PURGE_2,
|
||||
WARNING_TRUNCATION_1,
|
||||
WARNING_TRUNCATION_2,
|
||||
WARNING_UNMAPPED_CHAR_1,
|
||||
@@ -189,10 +199,10 @@ static uint8_t const _weverythingCommands[] = {
|
||||
WARNING_MACRO_SHIFT,
|
||||
WARNING_NESTED_COMMENT,
|
||||
WARNING_OBSOLETE,
|
||||
WARNING_PURGE_1,
|
||||
WARNING_PURGE_2,
|
||||
WARNING_SHIFT,
|
||||
WARNING_SHIFT_AMOUNT,
|
||||
WARNING_NUMERIC_STRING_1,
|
||||
WARNING_NUMERIC_STRING_2,
|
||||
WARNING_TRUNCATION_1,
|
||||
WARNING_TRUNCATION_2,
|
||||
WARNING_UNMAPPED_CHAR_1,
|
||||
|
||||
@@ -5,7 +5,7 @@ OUTPUT_CPP="${1:?}"
|
||||
INPUT_Y="${2:?}"
|
||||
|
||||
BISON_MAJOR=$(bison -V | sed -E 's/^.+ ([0-9]+)\..*$/\1/g;q')
|
||||
BISON_MINOR=$(bison -V | sed -E 's/^.+ [0-9]+\.([0-9]+)\..*$/\1/g;q')
|
||||
BISON_MINOR=$(bison -V | sed -E 's/^.+ [0-9]+\.([0-9]+)(\..*)?$/\1/g;q')
|
||||
|
||||
if [ "$BISON_MAJOR" -lt 3 ]; then
|
||||
echo "Bison $BISON_MAJOR.$BISON_MINOR is not supported" 1>&2
|
||||
|
||||
@@ -155,6 +155,7 @@ enum MbcType {
|
||||
MBC_BAD, // Specified MBC does not exist / syntax error
|
||||
MBC_WRONG_FEATURES, // MBC incompatible with specified features
|
||||
MBC_BAD_RANGE, // MBC number out of range
|
||||
MBC_BAD_TPP1, // Invalid TPP1 major or minor revision numbers
|
||||
};
|
||||
|
||||
static void printAcceptedMBCNames() {
|
||||
@@ -170,7 +171,7 @@ static void printAcceptedMBCNames() {
|
||||
fputs("\tMBC6 ($20)\n", stderr);
|
||||
fputs("\tMBC7+SENSOR+RUMBLE+RAM+BATTERY ($22)\n", stderr);
|
||||
fputs("\tPOCKET_CAMERA ($FC)\n", stderr);
|
||||
fputs("\tBANDAI_TAMA5 ($FD)\n", stderr);
|
||||
fputs("\tBANDAI_TAMA5 ($FD) [aka TAMA5]\n", stderr);
|
||||
fputs("\tHUC3 ($FE)\n", stderr);
|
||||
fputs("\tHUC1+RAM+BATTERY ($FF)\n", stderr);
|
||||
|
||||
@@ -332,12 +333,12 @@ static MbcType parseMBC(char const *name) {
|
||||
|
||||
if (endptr == ptr) {
|
||||
report("error: Failed to parse TPP1 major revision number\n");
|
||||
return MBC_BAD;
|
||||
return MBC_BAD_TPP1;
|
||||
}
|
||||
ptr = endptr;
|
||||
if (val != 1) {
|
||||
report("error: RGBFIX only supports TPP1 versions 1.0\n");
|
||||
return MBC_BAD;
|
||||
report("error: RGBFIX only supports TPP1 version 1.0\n");
|
||||
return MBC_BAD_TPP1;
|
||||
}
|
||||
tpp1Rev[0] = val;
|
||||
tryReadSlice(".");
|
||||
@@ -345,12 +346,12 @@ static MbcType parseMBC(char const *name) {
|
||||
val = strtoul(ptr, &endptr, 10);
|
||||
if (endptr == ptr) {
|
||||
report("error: Failed to parse TPP1 minor revision number\n");
|
||||
return MBC_BAD;
|
||||
return MBC_BAD_TPP1;
|
||||
}
|
||||
ptr = endptr;
|
||||
if (val > 0xFF) {
|
||||
report("error: TPP1 minor revision number must be 8-bit\n");
|
||||
return MBC_BAD;
|
||||
return MBC_BAD_TPP1;
|
||||
}
|
||||
tpp1Rev[1] = val;
|
||||
mbc = TPP1;
|
||||
@@ -663,6 +664,7 @@ static char const *mbcName(MbcType type) {
|
||||
case MBC_BAD:
|
||||
case MBC_WRONG_FEATURES:
|
||||
case MBC_BAD_RANGE:
|
||||
case MBC_BAD_TPP1:
|
||||
unreachable_();
|
||||
}
|
||||
|
||||
@@ -686,6 +688,7 @@ static bool hasRAM(MbcType type) {
|
||||
case MBC_BAD:
|
||||
case MBC_WRONG_FEATURES:
|
||||
case MBC_BAD_RANGE:
|
||||
case MBC_BAD_TPP1:
|
||||
return false;
|
||||
|
||||
case ROM_RAM:
|
||||
@@ -1324,8 +1327,8 @@ int main(int argc, char *argv[]) {
|
||||
} else if (cartridgeType == ROM_RAM || cartridgeType == ROM_RAM_BATTERY) {
|
||||
fprintf(
|
||||
stderr,
|
||||
"warning: ROM+RAM / ROM+RAM+BATTERY are under-specified and poorly "
|
||||
"supported\n"
|
||||
"warning: MBC \"%s\" is under-specified and poorly supported\n",
|
||||
musl_optarg
|
||||
);
|
||||
}
|
||||
break;
|
||||
@@ -1372,7 +1375,6 @@ int main(int argc, char *argv[]) {
|
||||
break;
|
||||
|
||||
default:
|
||||
fprintf(stderr, "FATAL: unknown option '%c'\n", ch);
|
||||
printUsage();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
133
src/gfx/main.cpp
133
src/gfx/main.cpp
@@ -13,6 +13,7 @@
|
||||
#include <string.h>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
#include "extern/getopt.hpp"
|
||||
#include "file.hpp"
|
||||
@@ -35,6 +36,7 @@ static struct LocalOptions {
|
||||
bool autoPalettes;
|
||||
bool autoPalmap;
|
||||
bool groupOutputs;
|
||||
bool reverse;
|
||||
} localOptions;
|
||||
|
||||
static uintmax_t nbErrors;
|
||||
@@ -44,6 +46,12 @@ static uintmax_t nbErrors;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
void requireZeroErrors() {
|
||||
if (nbErrors != 0) {
|
||||
giveUp();
|
||||
}
|
||||
}
|
||||
|
||||
void warning(char const *fmt, ...) {
|
||||
va_list ap;
|
||||
|
||||
@@ -100,7 +108,7 @@ void Options::verbosePrint(uint8_t level, char const *fmt, ...) const {
|
||||
}
|
||||
|
||||
// Short options
|
||||
static char const *optstring = "-Aa:b:Cc:Dd:FfhL:mN:n:Oo:Pp:Qq:r:s:Tt:U:uVvx:Z";
|
||||
static char const *optstring = "-Aa:b:Cc:Dd:Ffhi:L:mN:n:Oo:Pp:Qq:r:s:Tt:U:uVvx:Z";
|
||||
|
||||
/*
|
||||
* Equivalent long options
|
||||
@@ -113,40 +121,44 @@ static char const *optstring = "-Aa:b:Cc:Dd:FfhL:mN:n:Oo:Pp:Qq:r:s:Tt:U:uVvx:Z";
|
||||
* over short opt matching
|
||||
*/
|
||||
static option const longopts[] = {
|
||||
{"auto-attr-map", no_argument, nullptr, 'A' },
|
||||
{"attr-map", required_argument, nullptr, 'a' },
|
||||
{"base-tiles", required_argument, nullptr, 'b' },
|
||||
{"color-curve", no_argument, nullptr, 'C' },
|
||||
{"colors", required_argument, nullptr, 'c' },
|
||||
{"depth", required_argument, nullptr, 'd' },
|
||||
{"slice", required_argument, nullptr, 'L' },
|
||||
{"mirror-tiles", no_argument, nullptr, 'm' },
|
||||
{"nb-tiles", required_argument, nullptr, 'N' },
|
||||
{"nb-palettes", required_argument, nullptr, 'n' },
|
||||
{"group-outputs", no_argument, nullptr, 'O' },
|
||||
{"output", required_argument, nullptr, 'o' },
|
||||
{"auto-palette", no_argument, nullptr, 'P' },
|
||||
{"palette", required_argument, nullptr, 'p' },
|
||||
{"auto-palette-map", no_argument, nullptr, 'Q' },
|
||||
{"palette-map", required_argument, nullptr, 'q' },
|
||||
{"reverse", required_argument, nullptr, 'r' },
|
||||
{"auto-tilemap", no_argument, nullptr, 'T' },
|
||||
{"tilemap", required_argument, nullptr, 't' },
|
||||
{"unit-size", required_argument, nullptr, 'U' },
|
||||
{"unique-tiles", no_argument, nullptr, 'u' },
|
||||
{"version", no_argument, nullptr, 'V' },
|
||||
{"verbose", no_argument, nullptr, 'v' },
|
||||
{"trim-end", required_argument, nullptr, 'x' },
|
||||
{"columns", no_argument, nullptr, 'Z' },
|
||||
{nullptr, no_argument, nullptr, 0 }
|
||||
{"auto-attr-map", no_argument, nullptr, 'A'},
|
||||
{"attr-map", required_argument, nullptr, 'a'},
|
||||
{"base-tiles", required_argument, nullptr, 'b'},
|
||||
{"color-curve", no_argument, nullptr, 'C'},
|
||||
{"colors", required_argument, nullptr, 'c'},
|
||||
{"depth", required_argument, nullptr, 'd'},
|
||||
{"input-tileset", required_argument, nullptr, 'i'},
|
||||
{"slice", required_argument, nullptr, 'L'},
|
||||
{"mirror-tiles", no_argument, nullptr, 'm'},
|
||||
{"nb-tiles", required_argument, nullptr, 'N'},
|
||||
{"nb-palettes", required_argument, nullptr, 'n'},
|
||||
{"group-outputs", no_argument, nullptr, 'O'},
|
||||
{"output", required_argument, nullptr, 'o'},
|
||||
{"auto-palette", no_argument, nullptr, 'P'},
|
||||
{"palette", required_argument, nullptr, 'p'},
|
||||
{"auto-palette-map", no_argument, nullptr, 'Q'},
|
||||
{"palette-map", required_argument, nullptr, 'q'},
|
||||
{"reverse", required_argument, nullptr, 'r'},
|
||||
{"auto-tilemap", no_argument, nullptr, 'T'},
|
||||
{"tilemap", required_argument, nullptr, 't'},
|
||||
{"unit-size", required_argument, nullptr, 'U'},
|
||||
{"unique-tiles", no_argument, nullptr, 'u'},
|
||||
{"version", no_argument, nullptr, 'V'},
|
||||
{"verbose", no_argument, nullptr, 'v'},
|
||||
{"mirror-x", no_argument, nullptr, 'X'},
|
||||
{"trim-end", required_argument, nullptr, 'x'},
|
||||
{"mirror-y", no_argument, nullptr, 'Y'},
|
||||
{"columns", no_argument, nullptr, 'Z'},
|
||||
{nullptr, no_argument, nullptr, 0 }
|
||||
};
|
||||
|
||||
static void printUsage() {
|
||||
fputs(
|
||||
"Usage: rgbgfx [-r stride] [-CmOuVZ] [-v [-v ...]] [-a <attr_map> | -A]\n"
|
||||
" [-b <base_ids>] [-c <colors>] [-d <depth>] [-L <slice>] [-N <nb_tiles>]\n"
|
||||
" [-n <nb_pals>] [-o <out_file>] [-p <pal_file> | -P] [-q <pal_map> | -Q]\n"
|
||||
" [-s <nb_colors>] [-t <tile_map> | -T] [-x <nb_tiles>] <file>\n"
|
||||
"Usage: rgbgfx [-r stride] [-CmOuVXYZ] [-v [-v ...]] [-a <attr_map> | -A]\n"
|
||||
" [-b <base_ids>] [-c <colors>] [-d <depth>] [-i <tileset_file>]\n"
|
||||
" [-L <slice>] [-N <nb_tiles>] [-n <nb_pals>] [-o <out_file>]\n"
|
||||
" [-p <pal_file> | -P] [-q <pal_map> | -Q] [-s <nb_colors>]\n"
|
||||
" [-t <tile_map> | -T] [-x <nb_tiles>] <file>\n"
|
||||
"Useful options:\n"
|
||||
" -m, --mirror-tiles optimize out mirrored tiles\n"
|
||||
" -o, --output <path> output the tile data to this path\n"
|
||||
@@ -417,6 +429,11 @@ static char *parseArgv(int argc, char *argv[]) {
|
||||
options.bitDepth = 2;
|
||||
}
|
||||
break;
|
||||
case 'i':
|
||||
if (!options.inputTileset.empty())
|
||||
warning("Overriding input tileset file %s", options.inputTileset.c_str());
|
||||
options.inputTileset = musl_optarg;
|
||||
break;
|
||||
case 'L':
|
||||
options.inputSlice.left = parseNumber(arg, "Input slice left coordinate");
|
||||
if (options.inputSlice.left > INT16_MAX) {
|
||||
@@ -458,8 +475,9 @@ static char *parseArgv(int argc, char *argv[]) {
|
||||
}
|
||||
break;
|
||||
case 'm':
|
||||
options.allowMirroring = true;
|
||||
[[fallthrough]]; // Imply `-u`
|
||||
options.allowMirroringX = true; // Imply `-X`
|
||||
options.allowMirroringY = true; // Imply `-Y`
|
||||
[[fallthrough]]; // Imply `-u`
|
||||
case 'u':
|
||||
options.allowDedup = true;
|
||||
break;
|
||||
@@ -534,13 +552,11 @@ static char *parseArgv(int argc, char *argv[]) {
|
||||
options.palmap = musl_optarg;
|
||||
break;
|
||||
case 'r':
|
||||
localOptions.reverse = true;
|
||||
options.reversedWidth = parseNumber(arg, "Reversed image stride");
|
||||
if (*arg != '\0') {
|
||||
error("Reversed image stride (-r) must be a valid number, not \"%s\"", musl_optarg);
|
||||
}
|
||||
if (options.reversedWidth == 0) {
|
||||
error("Reversed image stride (-r) may not be 0!");
|
||||
}
|
||||
break;
|
||||
case 's':
|
||||
options.nbColorsPerPal = parseNumber(arg, "Number of colors per palette", 4);
|
||||
@@ -576,6 +592,14 @@ static char *parseArgv(int argc, char *argv[]) {
|
||||
error("Tile trim (-x) argument must be a valid number, not \"%s\"", musl_optarg);
|
||||
}
|
||||
break;
|
||||
case 'X':
|
||||
options.allowMirroringX = true;
|
||||
options.allowDedup = true; // Imply `-u`
|
||||
break;
|
||||
case 'Y':
|
||||
options.allowMirroringY = true;
|
||||
options.allowDedup = true; // Imply `-u`
|
||||
break;
|
||||
case 'Z':
|
||||
options.columnMajor = true;
|
||||
break;
|
||||
@@ -588,7 +612,6 @@ static char *parseArgv(int argc, char *argv[]) {
|
||||
}
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "FATAL: unknown option '%c'\n", ch);
|
||||
printUsage();
|
||||
exit(1);
|
||||
}
|
||||
@@ -601,7 +624,6 @@ int main(int argc, char *argv[]) {
|
||||
struct AtFileStackEntry {
|
||||
int parentInd; // Saved offset into parent argv
|
||||
std::vector<char *> argv; // This context's arg pointer vec
|
||||
std::vector<char> argPool;
|
||||
|
||||
AtFileStackEntry(int parentInd_, std::vector<char *> argv_)
|
||||
: parentInd(parentInd_), argv(argv_) {}
|
||||
@@ -610,18 +632,24 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
int curArgc = argc;
|
||||
char **curArgv = argv;
|
||||
std::vector<std::vector<char>> argPools;
|
||||
for (;;) {
|
||||
char *atFileName = parseArgv(curArgc, curArgv);
|
||||
if (atFileName) {
|
||||
// We need to allocate a new arg pool for each at-file, so as not to invalidate pointers
|
||||
// previous at-files may have generated to their own arg pools.
|
||||
// But for the same reason, the arg pool must also outlive the at-file's stack entry!
|
||||
auto &argPool = argPools.emplace_back();
|
||||
|
||||
// Copy `argv[0]` for error reporting, and because option parsing skips it
|
||||
AtFileStackEntry &stackEntry =
|
||||
atFileStack.emplace_back(musl_optind, std::vector{atFileName});
|
||||
// It would be nice to compute the char pointers on the fly, but reallocs don't allow
|
||||
// that; so we must compute the offsets after the pool is fixed
|
||||
auto offsets = readAtFile(&musl_optarg[1], stackEntry.argPool);
|
||||
auto offsets = readAtFile(&musl_optarg[1], argPool);
|
||||
stackEntry.argv.reserve(offsets.size() + 2); // Avoid a bunch of reallocs
|
||||
for (size_t ofs : offsets) {
|
||||
stackEntry.argv.push_back(&stackEntry.argPool.data()[ofs]);
|
||||
stackEntry.argv.push_back(&argPool.data()[ofs]);
|
||||
}
|
||||
stackEntry.argv.push_back(nullptr); // Don't forget the arg vector terminator!
|
||||
|
||||
@@ -747,16 +775,18 @@ int main(int argc, char *argv[]) {
|
||||
fputs("Options:\n", stderr);
|
||||
if (options.columnMajor)
|
||||
fputs("\tVisit image in column-major order\n", stderr);
|
||||
if (options.allowMirroring)
|
||||
fputs("\tAllow mirroring tiles\n", stderr);
|
||||
if (options.allowDedup)
|
||||
fputs("\tAllow deduplicating tiles\n", stderr);
|
||||
if (options.allowMirroringX)
|
||||
fputs("\tAllow deduplicating horizontally mirrored tiles\n", stderr);
|
||||
if (options.allowMirroringY)
|
||||
fputs("\tAllow deduplicating vertically mirrored tiles\n", stderr);
|
||||
if (options.useColorCurve)
|
||||
fputs("\tUse color curve\n", stderr);
|
||||
fprintf(stderr, "\tBit depth: %" PRIu8 "bpp\n", options.bitDepth);
|
||||
if (options.trim != 0)
|
||||
fprintf(stderr, "\tTrim the last %" PRIu64 " tiles\n", options.trim);
|
||||
fprintf(stderr, "\tMaximum %" PRIu8 " palettes\n", options.nbPalettes);
|
||||
fprintf(stderr, "\tMaximum %" PRIu16 " palettes\n", options.nbPalettes);
|
||||
fprintf(stderr, "\tPalettes contain %" PRIu8 " colors\n", options.nbColorsPerPal);
|
||||
fprintf(stderr, "\t%s palette spec\n", [] {
|
||||
switch (options.palSpecType) {
|
||||
@@ -780,7 +810,7 @@ int main(int argc, char *argv[]) {
|
||||
fputs("#none, ", stderr);
|
||||
}
|
||||
}
|
||||
fputc('\n', stderr);
|
||||
putc('\n', stderr);
|
||||
}
|
||||
fputs("\t]\n", stderr);
|
||||
}
|
||||
@@ -818,18 +848,17 @@ int main(int argc, char *argv[]) {
|
||||
fputs("Ready.\n", stderr);
|
||||
}
|
||||
|
||||
// Do not do anything if option parsing went wrong
|
||||
if (nbErrors) {
|
||||
giveUp();
|
||||
}
|
||||
// Do not do anything if option parsing went wrong.
|
||||
requireZeroErrors();
|
||||
|
||||
if (!options.input.empty()) {
|
||||
if (options.reverse()) {
|
||||
if (localOptions.reverse) {
|
||||
reverse();
|
||||
} else {
|
||||
process();
|
||||
}
|
||||
} else if (!options.palettes.empty() && options.palSpecType == Options::EXPLICIT && !options.reverse()) {
|
||||
} else if (!options.palettes.empty() && options.palSpecType == Options::EXPLICIT
|
||||
&& !localOptions.reverse) {
|
||||
processPalettes();
|
||||
} else {
|
||||
fputs("FATAL: No input image specified\n", stderr);
|
||||
@@ -837,9 +866,7 @@ int main(int argc, char *argv[]) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (nbErrors) {
|
||||
giveUp();
|
||||
}
|
||||
requireZeroErrors();
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace packing {
|
||||
* A reference to a proto-palette, and attached attributes for sorting purposes
|
||||
*/
|
||||
struct ProtoPalAttrs {
|
||||
size_t const protoPalIndex;
|
||||
size_t protoPalIndex;
|
||||
/*
|
||||
* Pages from which we are banned (to prevent infinite loops)
|
||||
* This is dynamic because we wish not to hard-cap the amount of palettes
|
||||
@@ -359,11 +359,19 @@ std::tuple<DefaultInitVec<size_t>, size_t>
|
||||
);
|
||||
|
||||
// Sort the proto-palettes by size, which improves the packing algorithm's efficiency
|
||||
auto const indexOfLargestProtoPalFirst = [&protoPalettes](size_t left, size_t right) {
|
||||
ProtoPalette const &lhs = protoPalettes[left];
|
||||
ProtoPalette const &rhs = protoPalettes[right];
|
||||
return lhs.size() > rhs.size(); // We want the proto-pals to be sorted *largest first*!
|
||||
};
|
||||
DefaultInitVec<size_t> sortedProtoPalIDs(protoPalettes.size());
|
||||
sortedProtoPalIDs.clear();
|
||||
for (size_t i = 0; i < protoPalettes.size(); ++i) {
|
||||
sortedProtoPalIDs.insert(std::lower_bound(RANGE(sortedProtoPalIDs), i), i);
|
||||
sortedProtoPalIDs.insert(
|
||||
std::lower_bound(RANGE(sortedProtoPalIDs), i, indexOfLargestProtoPalFirst), i
|
||||
);
|
||||
}
|
||||
|
||||
// Begin with all proto-palettes queued up for insertion
|
||||
std::queue<ProtoPalAttrs> queue(std::deque<ProtoPalAttrs>(RANGE(sortedProtoPalIDs)));
|
||||
// Begin with no pages
|
||||
@@ -385,16 +393,18 @@ std::tuple<DefaultInitVec<size_t>, size_t>
|
||||
continue;
|
||||
}
|
||||
|
||||
double relSize = assignments[i].relSizeOf(protoPal);
|
||||
options.verbosePrint(
|
||||
Options::VERB_DEBUG,
|
||||
"%zu/%zu: Rel size: %f (size = %zu)\n",
|
||||
i + 1,
|
||||
assignments.size(),
|
||||
assignments[i].relSizeOf(protoPal),
|
||||
relSize,
|
||||
protoPal.size()
|
||||
);
|
||||
if (assignments[i].relSizeOf(protoPal) < bestRelSize) {
|
||||
if (relSize < bestRelSize) {
|
||||
bestPalIndex = i;
|
||||
bestRelSize = relSize;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -447,17 +457,25 @@ std::tuple<DefaultInitVec<size_t>, size_t>
|
||||
}
|
||||
|
||||
// Deal with palettes still overloaded, by emptying them
|
||||
auto const &largestProtoPalFirst =
|
||||
[&protoPalettes](ProtoPalAttrs const &lhs, ProtoPalAttrs const &rhs) {
|
||||
return protoPalettes[lhs.protoPalIndex].size()
|
||||
> protoPalettes[rhs.protoPalIndex].size();
|
||||
};
|
||||
std::vector<ProtoPalAttrs> overloadQueue{};
|
||||
for (AssignedProtos &pal : assignments) {
|
||||
if (pal.volume() > options.maxOpaqueColors()) {
|
||||
for (ProtoPalAttrs &attrs : pal) {
|
||||
queue.emplace(std::move(attrs));
|
||||
overloadQueue.emplace(
|
||||
std::lower_bound(RANGE(overloadQueue), attrs, largestProtoPalFirst),
|
||||
std::move(attrs)
|
||||
);
|
||||
}
|
||||
pal.clear();
|
||||
}
|
||||
}
|
||||
// Place back any proto-palettes now in the queue via first-fit
|
||||
while (!queue.empty()) {
|
||||
ProtoPalAttrs const &attrs = queue.front();
|
||||
for (ProtoPalAttrs const &attrs : overloadQueue) {
|
||||
ProtoPalette const &protoPal = protoPalettes[attrs.protoPalIndex];
|
||||
auto iter = std::find_if(RANGE(assignments), [&protoPal](AssignedProtos const &pal) {
|
||||
return pal.canFit(protoPal);
|
||||
@@ -479,7 +497,6 @@ std::tuple<DefaultInitVec<size_t>, size_t>
|
||||
);
|
||||
iter->assign(std::move(attrs));
|
||||
}
|
||||
queue.pop();
|
||||
}
|
||||
|
||||
if (options.verbosity >= Options::VERB_INTERM) {
|
||||
|
||||
@@ -176,7 +176,9 @@ void parseInlinePalSpec(char const * const rawArg) {
|
||||
* Returns whether the magic was correctly read.
|
||||
*/
|
||||
template<size_t n>
|
||||
static bool readMagic(std::filebuf &file, char const *magic) {
|
||||
[[gnu::warn_unused_result]] // Ignoring failure to match is a bad idea.
|
||||
static bool
|
||||
readMagic(std::filebuf &file, char const *magic) {
|
||||
assume(strlen(magic) == n);
|
||||
|
||||
char magicBuf[n];
|
||||
@@ -203,37 +205,49 @@ static T readLE(U const *bytes) {
|
||||
|
||||
/*
|
||||
* **Appends** the first line read from `file` to the end of the provided `buffer`.
|
||||
*
|
||||
* @return true if a line was read.
|
||||
*/
|
||||
static void readLine(std::filebuf &file, std::string &buffer) {
|
||||
[[gnu::warn_unused_result]] // Ignoring EOF is a bad idea.
|
||||
static bool
|
||||
readLine(std::filebuf &file, std::string &buffer) {
|
||||
// TODO: maybe this can be optimized to bulk reads?
|
||||
for (;;) {
|
||||
auto c = file.sbumpc();
|
||||
if (c == std::filebuf::traits_type::eof()) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
if (c == '\n') {
|
||||
// Discard a trailing CRLF
|
||||
if (!buffer.empty() && buffer.back() == '\r') {
|
||||
buffer.pop_back();
|
||||
}
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
buffer.push_back(c);
|
||||
}
|
||||
}
|
||||
|
||||
#define requireLine(kind, file, buffer) \
|
||||
do { \
|
||||
if (!readLine(file, buffer)) { \
|
||||
error(kind " palette file is shorter than expected"); \
|
||||
return; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
/*
|
||||
* Parses the initial part of a string_view, advancing the "read index" as it does
|
||||
*/
|
||||
template<typename U> // Should be uint*_t
|
||||
static std::optional<U> parseDec(std::string const &str, std::string::size_type &n) {
|
||||
uintmax_t value = 0;
|
||||
auto result = std::from_chars(str.data(), str.data() + str.size(), value);
|
||||
if ((bool)result.ec) {
|
||||
auto result = std::from_chars(str.data() + n, str.data() + str.size(), value);
|
||||
if (static_cast<bool>(result.ec)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
n += result.ptr - str.data();
|
||||
n = result.ptr - str.data();
|
||||
return std::optional<U>{value};
|
||||
}
|
||||
|
||||
@@ -272,21 +286,20 @@ static void parsePSPFile(std::filebuf &file) {
|
||||
// https://www.selapa.net/swatches/colors/fileformats.php#psp_pal
|
||||
|
||||
std::string line;
|
||||
readLine(file, line);
|
||||
if (line != "JASC-PAL") {
|
||||
if (!readLine(file, line) || line != "JASC-PAL") {
|
||||
error("Palette file does not appear to be a PSP palette file");
|
||||
return;
|
||||
}
|
||||
|
||||
line.clear();
|
||||
readLine(file, line);
|
||||
requireLine("PSP", file, line);
|
||||
if (line != "0100") {
|
||||
error("Unsupported PSP palette file version \"%s\"", line.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
line.clear();
|
||||
readLine(file, line);
|
||||
requireLine("PSP", file, line);
|
||||
std::string::size_type n = 0;
|
||||
std::optional<uint16_t> nbColors = parseDec<uint16_t>(line, n);
|
||||
if (!nbColors || n != line.length()) {
|
||||
@@ -309,7 +322,7 @@ static void parsePSPFile(std::filebuf &file) {
|
||||
|
||||
for (uint16_t i = 0; i < *nbColors; ++i) {
|
||||
line.clear();
|
||||
readLine(file, line);
|
||||
requireLine("PSP", file, line);
|
||||
|
||||
n = 0;
|
||||
std::optional<Rgba> color = parseColor(line, n, i + 1);
|
||||
@@ -336,39 +349,45 @@ static void parseGPLFile(std::filebuf &file) {
|
||||
// https://gitlab.gnome.org/GNOME/gimp/-/blob/gimp-2-10/app/core/gimppalette-load.c#L39
|
||||
|
||||
std::string line;
|
||||
readLine(file, line);
|
||||
if (!line.starts_with("GIMP Palette")) {
|
||||
if (!readLine(file, line) || !line.starts_with("GIMP Palette")) {
|
||||
error("Palette file does not appear to be a GPL palette file");
|
||||
return;
|
||||
}
|
||||
|
||||
uint16_t nbColors = 0;
|
||||
uint16_t maxNbColors = options.nbColorsPerPal * options.nbPalettes;
|
||||
uint16_t const maxNbColors = options.nbColorsPerPal * options.nbPalettes;
|
||||
|
||||
for (;;) {
|
||||
line.clear();
|
||||
readLine(file, line);
|
||||
if (!line.length()) {
|
||||
if (!readLine(file, line)) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (line.starts_with("#") || line.starts_with("Name:") || line.starts_with("Column:")) {
|
||||
if (line.starts_with("Name:") || line.starts_with("Columns:")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string::size_type n = 0;
|
||||
skipWhitespace(line, n);
|
||||
// Skip empty lines, or lines that contain just a comment.
|
||||
if (line.length() == n || line[n] == '#') {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::optional<Rgba> color = parseColor(line, n, nbColors + 1);
|
||||
if (!color) {
|
||||
return;
|
||||
}
|
||||
// Ignore anything following the three components
|
||||
// (sometimes it's a comment, sometimes it's the color in CSS hex format, sometimes there's
|
||||
// nothing...).
|
||||
|
||||
++nbColors;
|
||||
if (nbColors < maxNbColors) {
|
||||
if (nbColors % options.nbColorsPerPal == 1) {
|
||||
if (nbColors % options.nbColorsPerPal == 0) {
|
||||
options.palSpec.emplace_back();
|
||||
}
|
||||
options.palSpec.back()[nbColors % options.nbColorsPerPal] = *color;
|
||||
}
|
||||
++nbColors;
|
||||
}
|
||||
|
||||
if (nbColors > maxNbColors) {
|
||||
@@ -385,14 +404,17 @@ static void parseHEXFile(std::filebuf &file) {
|
||||
// https://lospec.com/palette-list/tag/gbc
|
||||
|
||||
uint16_t nbColors = 0;
|
||||
uint16_t maxNbColors = options.nbColorsPerPal * options.nbPalettes;
|
||||
uint16_t const maxNbColors = options.nbColorsPerPal * options.nbPalettes;
|
||||
|
||||
for (;;) {
|
||||
std::string line;
|
||||
readLine(file, line);
|
||||
if (!line.length()) {
|
||||
if (!readLine(file, line)) {
|
||||
break;
|
||||
}
|
||||
// Ignore empty lines.
|
||||
if (line.length() == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line.length() != 6
|
||||
|| line.find_first_not_of("0123456789ABCDEFabcdef"sv) != std::string::npos) {
|
||||
@@ -407,13 +429,13 @@ static void parseHEXFile(std::filebuf &file) {
|
||||
Rgba color =
|
||||
Rgba(toHex(line[0], line[1]), toHex(line[2], line[3]), toHex(line[4], line[5]), 0xFF);
|
||||
|
||||
++nbColors;
|
||||
if (nbColors < maxNbColors) {
|
||||
if (nbColors % options.nbColorsPerPal == 1) {
|
||||
if (nbColors % options.nbColorsPerPal == 0) {
|
||||
options.palSpec.emplace_back();
|
||||
}
|
||||
options.palSpec.back()[nbColors % options.nbColorsPerPal] = color;
|
||||
}
|
||||
++nbColors;
|
||||
}
|
||||
|
||||
if (nbColors > maxNbColors) {
|
||||
|
||||
@@ -627,8 +627,7 @@ static std::tuple<DefaultInitVec<size_t>, std::vector<Palette>>
|
||||
static char buf[sizeof(", $XXXX, $XXXX, $XXXX, $XXXX")];
|
||||
char *ptr = buf;
|
||||
for (uint16_t cgbColor : list) {
|
||||
sprintf(ptr, ", $%04x", cgbColor);
|
||||
ptr += QUOTEDSTRLEN(", $XXXX");
|
||||
ptr += snprintf(ptr, sizeof(", $XXXX"), ", $%04x", cgbColor);
|
||||
}
|
||||
return &buf[QUOTEDSTRLEN(", ")];
|
||||
};
|
||||
@@ -683,7 +682,7 @@ static void outputPalettes(std::vector<Palette> const &palettes) {
|
||||
// If the palette generation is wrong, other (dependee) operations are likely to be
|
||||
// nonsensical, so fatal-error outright
|
||||
fatal(
|
||||
"Generated %zu palettes, over the maximum of %" PRIu8,
|
||||
"Generated %zu palettes, over the maximum of %" PRIu16,
|
||||
palettes.size(),
|
||||
options.nbPalettes
|
||||
);
|
||||
@@ -692,7 +691,7 @@ static void outputPalettes(std::vector<Palette> const &palettes) {
|
||||
if (!options.palettes.empty()) {
|
||||
File output;
|
||||
if (!output.open(options.palettes, std::ios_base::out | std::ios_base::binary)) {
|
||||
fatal("Failed to open \"%s\": %s", output.c_str(options.palettes), strerror(errno));
|
||||
fatal("Failed to create \"%s\": %s", output.c_str(options.palettes), strerror(errno));
|
||||
}
|
||||
|
||||
for (Palette const &palette : palettes) {
|
||||
@@ -706,6 +705,17 @@ static void outputPalettes(std::vector<Palette> const &palettes) {
|
||||
}
|
||||
}
|
||||
|
||||
static void hashBitplanes(uint16_t bitplanes, uint16_t &hash) {
|
||||
hash ^= bitplanes;
|
||||
if (options.allowMirroringX) {
|
||||
// Count the line itself as mirrored, which ensures the same hash as the tile's horizontal
|
||||
// flip; vertical mirroring is already taken care of because the symmetric line will be
|
||||
// XOR'd the same way. (This can trivially create some collisions, but real-world tile data
|
||||
// generally doesn't trigger them.)
|
||||
hash ^= flipTable[bitplanes >> 8] << 8 | flipTable[bitplanes & 0xFF];
|
||||
}
|
||||
}
|
||||
|
||||
class TileData {
|
||||
std::array<uint8_t, 16> _data;
|
||||
// The hash is a bit lax: it's the XOR of all lines, and every other nibble is identical
|
||||
@@ -736,23 +746,23 @@ public:
|
||||
return row;
|
||||
}
|
||||
|
||||
TileData(std::array<uint8_t, 16> &&raw) : _data(raw), _hash(0) {
|
||||
for (uint8_t y = 0; y < 8; ++y) {
|
||||
uint16_t bitplanes = _data[y * 2] | _data[y * 2 + 1] << 8;
|
||||
hashBitplanes(bitplanes, _hash);
|
||||
}
|
||||
}
|
||||
|
||||
TileData(Png::TilesVisitor::Tile const &tile, Palette const &palette) : _hash(0) {
|
||||
size_t writeIndex = 0;
|
||||
for (uint32_t y = 0; y < 8; ++y) {
|
||||
uint16_t bitplanes = rowBitplanes(tile, palette, y);
|
||||
hashBitplanes(bitplanes, _hash);
|
||||
|
||||
_data[writeIndex++] = bitplanes & 0xFF;
|
||||
if (options.bitDepth == 2) {
|
||||
_data[writeIndex++] = bitplanes >> 8;
|
||||
}
|
||||
|
||||
// Update the hash
|
||||
_hash ^= bitplanes;
|
||||
if (options.allowMirroring) {
|
||||
// Count the line itself as mirrorred; vertical mirroring is
|
||||
// already taken care of because the symmetric line will be XOR'd
|
||||
// the same way. (...which is a problem, but probably benign.)
|
||||
_hash ^= flipTable[bitplanes >> 8] << 8 | flipTable[bitplanes & 0xFF];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -773,15 +783,17 @@ public:
|
||||
return MatchType::EXACT;
|
||||
}
|
||||
|
||||
if (!options.allowMirroring) {
|
||||
return MatchType::NOPE;
|
||||
// Check if we have horizontal mirroring, which scans the array forward again
|
||||
if (options.allowMirroringX
|
||||
&& std::equal(RANGE(_data), other._data.begin(), [](uint8_t lhs, uint8_t rhs) {
|
||||
return lhs == flipTable[rhs];
|
||||
})) {
|
||||
return MatchType::HFLIP;
|
||||
}
|
||||
|
||||
// Check if we have horizontal mirroring, which scans the array forward again
|
||||
if (std::equal(RANGE(_data), other._data.begin(), [](uint8_t lhs, uint8_t rhs) {
|
||||
return lhs == flipTable[rhs];
|
||||
})) {
|
||||
return MatchType::HFLIP;
|
||||
// The remaining possibilities for matching all require vertical mirroring
|
||||
if (!options.allowMirroringY) {
|
||||
return MatchType::NOPE;
|
||||
}
|
||||
|
||||
// Check if we have vertical or vertical+horizontal mirroring, for which we have to read
|
||||
@@ -803,8 +815,16 @@ public:
|
||||
}
|
||||
|
||||
// If we have both (i.e. we have symmetry), default to vflip only
|
||||
assume(hasVFlip || hasVHFlip);
|
||||
return hasVFlip ? MatchType::VFLIP : MatchType::VHFLIP;
|
||||
if (hasVFlip) {
|
||||
return MatchType::VFLIP;
|
||||
}
|
||||
|
||||
// If we allow both and have both, then use both
|
||||
if (options.allowMirroringX && hasVHFlip) {
|
||||
return MatchType::VHFLIP;
|
||||
}
|
||||
|
||||
return MatchType::NOPE;
|
||||
}
|
||||
friend bool operator==(TileData const &lhs, TileData const &rhs) {
|
||||
return lhs.tryMatching(rhs) != MatchType::NOPE;
|
||||
@@ -826,7 +846,7 @@ static void outputTileData(
|
||||
) {
|
||||
File output;
|
||||
if (!output.open(options.output, std::ios_base::out | std::ios_base::binary)) {
|
||||
fatal("Failed to open \"%s\": %s", output.c_str(options.output), strerror(errno));
|
||||
fatal("Failed to create \"%s\": %s", output.c_str(options.output), strerror(errno));
|
||||
}
|
||||
|
||||
uint16_t widthTiles = options.inputSlice.width ? options.inputSlice.width : png.getWidth() / 8;
|
||||
@@ -865,7 +885,7 @@ static void outputMaps(
|
||||
if (!path.empty()) {
|
||||
file.emplace();
|
||||
if (!file->open(path, std::ios_base::out | std::ios_base::binary)) {
|
||||
fatal("Failed to open \"%s\": %s", file->c_str(options.tilemap), strerror(errno));
|
||||
fatal("Failed to create \"%s\": %s", file->c_str(options.tilemap), strerror(errno));
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -913,12 +933,10 @@ struct UniqueTiles {
|
||||
/*
|
||||
* Adds a tile to the collection, and returns its ID
|
||||
*/
|
||||
std::tuple<uint16_t, TileData::MatchType>
|
||||
addTile(Png::TilesVisitor::Tile const &tile, Palette const &palette) {
|
||||
TileData newTile(tile, palette);
|
||||
std::tuple<uint16_t, TileData::MatchType> addTile(TileData newTile) {
|
||||
auto [tileData, inserted] = tileset.insert(newTile);
|
||||
|
||||
TileData::MatchType matchType = TileData::EXACT;
|
||||
TileData::MatchType matchType = TileData::NOPE;
|
||||
if (inserted) {
|
||||
// Give the new tile the next available unique ID
|
||||
tileData->tileID = static_cast<uint16_t>(tiles.size());
|
||||
@@ -953,8 +971,57 @@ static UniqueTiles dedupTiles(
|
||||
// by caching the full tile data anyway, so we might as well.)
|
||||
UniqueTiles tiles;
|
||||
|
||||
if (!options.inputTileset.empty()) {
|
||||
File inputTileset;
|
||||
if (!inputTileset.open(options.inputTileset, std::ios::in | std::ios::binary)) {
|
||||
fatal("Failed to open \"%s\": %s", options.inputTileset.c_str(), strerror(errno));
|
||||
}
|
||||
|
||||
std::array<uint8_t, 16> tile;
|
||||
size_t const tileSize = options.bitDepth * 8;
|
||||
for (;;) {
|
||||
// It's okay to cast between character types.
|
||||
size_t len = inputTileset->sgetn(reinterpret_cast<char *>(tile.data()), tileSize);
|
||||
if (len == 0) { // EOF!
|
||||
break;
|
||||
} else if (len != tileSize) {
|
||||
fatal(
|
||||
"\"%s\" does not contain a multiple of %zu bytes; is it actually tile data?",
|
||||
options.inputTileset.c_str(),
|
||||
tileSize
|
||||
);
|
||||
} else if (len == 8) {
|
||||
// Expand the tile data to 2bpp.
|
||||
for (size_t i = 8; i--;) {
|
||||
tile[i * 2 + 1] = 0;
|
||||
tile[i * 2] = tile[i];
|
||||
}
|
||||
}
|
||||
|
||||
auto [tileID, matchType] = tiles.addTile(std::move(tile));
|
||||
|
||||
if (matchType != TileData::NOPE) {
|
||||
error(
|
||||
"The input tileset's tile #%hu was deduplicated; please check that your "
|
||||
"deduplication flags (`-u`, `-m`) are consistent with what was used to "
|
||||
"generate the input tileset",
|
||||
tileID
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto [tile, attr] : zip(png.visitAsTiles(), attrmap)) {
|
||||
auto [tileID, matchType] = tiles.addTile(tile, palettes[mappings[attr.protoPaletteID]]);
|
||||
auto [tileID, matchType] = tiles.addTile({tile, palettes[mappings[attr.protoPaletteID]]});
|
||||
|
||||
if (matchType == TileData::NOPE && options.output.empty()) {
|
||||
error(
|
||||
"Tile at (%" PRIu32 ", %" PRIu32
|
||||
") is not within the input tileset, and `-o` was not given!",
|
||||
tile.x,
|
||||
tile.y
|
||||
);
|
||||
}
|
||||
|
||||
attr.xFlip = matchType == TileData::HFLIP || matchType == TileData::VHFLIP;
|
||||
attr.yFlip = matchType == TileData::VFLIP || matchType == TileData::VHFLIP;
|
||||
@@ -1176,6 +1243,12 @@ continue_visiting_tiles:;
|
||||
);
|
||||
}
|
||||
|
||||
// I currently cannot figure out useful semantics for this combination of flags.
|
||||
if (!options.inputTileset.empty()) {
|
||||
fatal("Input tilesets are not supported without `-u`\nPlease consider explaining your "
|
||||
"use case to RGBDS' developers!");
|
||||
}
|
||||
|
||||
if (!options.output.empty()) {
|
||||
options.verbosePrint(Options::VERB_LOG_ACT, "Generating unoptimized tile data...\n");
|
||||
unoptimized::outputTileData(png, attrmap, palettes, mappings);
|
||||
|
||||
@@ -46,7 +46,7 @@ ProtoPalette::ComparisonResult ProtoPalette::compare(ProtoPalette const &other)
|
||||
auto ours = _colorIndices.begin(), theirs = other._colorIndices.begin();
|
||||
bool weBigger = true, theyBigger = true;
|
||||
|
||||
while (ours != _colorIndices.end() && theirs != other._colorIndices.end()) {
|
||||
while (ours != end() && theirs != other.end()) {
|
||||
if (*ours == *theirs) {
|
||||
++ours;
|
||||
++theirs;
|
||||
@@ -58,8 +58,8 @@ ProtoPalette::ComparisonResult ProtoPalette::compare(ProtoPalette const &other)
|
||||
weBigger = false;
|
||||
}
|
||||
}
|
||||
weBigger &= theirs == other._colorIndices.end();
|
||||
theyBigger &= ours == _colorIndices.end();
|
||||
weBigger &= theirs == other.end();
|
||||
theyBigger &= ours == end();
|
||||
|
||||
return theyBigger ? THEY_BIGGER : (weBigger ? WE_BIGGER : NEITHER);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <array>
|
||||
#include <errno.h>
|
||||
#include <inttypes.h>
|
||||
#include <math.h>
|
||||
#include <optional>
|
||||
#include <png.h>
|
||||
#include <string.h>
|
||||
@@ -14,7 +15,6 @@
|
||||
#include "defaultinitalloc.hpp"
|
||||
#include "file.hpp"
|
||||
#include "helpers.hpp" // assume
|
||||
#include "itertools.hpp"
|
||||
|
||||
#include "gfx/main.hpp"
|
||||
|
||||
@@ -65,16 +65,36 @@ static void pngWarning(png_structp png, char const *msg) {
|
||||
);
|
||||
}
|
||||
|
||||
void writePng(png_structp png, png_bytep data, size_t length) {
|
||||
static void writePng(png_structp png, png_bytep data, size_t length) {
|
||||
auto &pngFile = *static_cast<File *>(png_get_io_ptr(png));
|
||||
pngFile->sputn(reinterpret_cast<char *>(data), length);
|
||||
}
|
||||
|
||||
void flushPng(png_structp png) {
|
||||
static void flushPng(png_structp png) {
|
||||
auto &pngFile = *static_cast<File *>(png_get_io_ptr(png));
|
||||
pngFile->pubsync();
|
||||
}
|
||||
|
||||
static void printColor(std::optional<Rgba> const &color) {
|
||||
if (color) {
|
||||
fprintf(stderr, "#%08x", color->toCSS());
|
||||
} else {
|
||||
fputs("<none> ", stderr);
|
||||
}
|
||||
}
|
||||
|
||||
static void printPalette(std::array<std::optional<Rgba>, 4> const &palette) {
|
||||
putc('[', stderr);
|
||||
printColor(palette[0]);
|
||||
fputs(", ", stderr);
|
||||
printColor(palette[1]);
|
||||
fputs(", ", stderr);
|
||||
printColor(palette[2]);
|
||||
fputs(", ", stderr);
|
||||
printColor(palette[3]);
|
||||
putc(']', stderr);
|
||||
}
|
||||
|
||||
void reverse() {
|
||||
options.verbosePrint(Options::VERB_CFG, "Using libpng %s\n", png_get_libpng_ver(nullptr));
|
||||
|
||||
@@ -117,36 +137,57 @@ void reverse() {
|
||||
}
|
||||
|
||||
// By default, assume tiles are not deduplicated, and add the (allegedly) trimmed tiles
|
||||
size_t nbTileInstances = tiles.size() / tileSize + options.trim; // Image size in tiles
|
||||
options.verbosePrint(Options::VERB_INTERM, "Read %zu tiles.\n", nbTileInstances);
|
||||
size_t const nbTiles = tiles.size() / tileSize;
|
||||
options.verbosePrint(Options::VERB_INTERM, "Read %zu tiles.\n", nbTiles);
|
||||
size_t mapSize = nbTiles + options.trim; // Image size in tiles
|
||||
std::optional<DefaultInitVec<uint8_t>> tilemap;
|
||||
if (!options.tilemap.empty()) {
|
||||
tilemap = readInto(options.tilemap);
|
||||
nbTileInstances = tilemap->size();
|
||||
options.verbosePrint(Options::VERB_INTERM, "Read %zu tilemap entries.\n", nbTileInstances);
|
||||
mapSize = tilemap->size();
|
||||
options.verbosePrint(Options::VERB_INTERM, "Read %zu tilemap entries.\n", mapSize);
|
||||
}
|
||||
|
||||
if (nbTileInstances == 0) {
|
||||
if (mapSize == 0) {
|
||||
fatal("Cannot generate empty image");
|
||||
}
|
||||
if (nbTileInstances > options.maxNbTiles[0] + options.maxNbTiles[1]) {
|
||||
if (mapSize > options.maxNbTiles[0] + options.maxNbTiles[1]) {
|
||||
warning(
|
||||
"Read %zu tiles, more than the limit of %" PRIu16 " + %" PRIu16,
|
||||
nbTileInstances,
|
||||
"Total number of tiles (%zu) is more than the limit of %" PRIu16 " + %" PRIu16,
|
||||
mapSize,
|
||||
options.maxNbTiles[0],
|
||||
options.maxNbTiles[1]
|
||||
);
|
||||
}
|
||||
|
||||
size_t width = options.reversedWidth, height; // In tiles
|
||||
if (nbTileInstances % width != 0) {
|
||||
if (width == 0) {
|
||||
// Pick the smallest width that will result in a landscape-aspect rectangular image.
|
||||
// Thus a prime number of tiles will result in a horizontal row.
|
||||
// This avoids redundancy with `-r 1` which results in a vertical column.
|
||||
width = (size_t)ceil(sqrt(mapSize));
|
||||
for (; width < mapSize; ++width) {
|
||||
if (mapSize % width == 0)
|
||||
break;
|
||||
}
|
||||
options.verbosePrint(Options::VERB_INTERM, "Picked reversing width of %zu tiles\n", width);
|
||||
}
|
||||
if (mapSize % width != 0) {
|
||||
if (options.trim == 0 && !tilemap) {
|
||||
fatal(
|
||||
"Total number of tiles (%zu) cannot be divided by image width (%zu tiles)\n"
|
||||
"(To proceed anyway with this image width, try passing `-x %zu`)",
|
||||
mapSize,
|
||||
width,
|
||||
width - mapSize % width
|
||||
);
|
||||
}
|
||||
fatal(
|
||||
"Total number of tiles read (%zu) cannot be divided by image width (%zu tiles)",
|
||||
nbTileInstances,
|
||||
"Total number of tiles (%zu) cannot be divided by image width (%zu tiles)",
|
||||
mapSize,
|
||||
width
|
||||
);
|
||||
}
|
||||
height = nbTileInstances / width;
|
||||
height = mapSize / width;
|
||||
|
||||
options.verbosePrint(
|
||||
Options::VERB_INTERM, "Reversed image dimensions: %zux%zu tiles\n", width, height
|
||||
@@ -156,7 +197,7 @@ void reverse() {
|
||||
|
||||
std::vector<std::array<std::optional<Rgba>, 4>> palettes{
|
||||
{Rgba(0xFFFFFFFF), Rgba(0xAAAAAAFF), Rgba(0x555555FF), Rgba(0x000000FF)}
|
||||
};
|
||||
};
|
||||
// If a palette file is used as input, it overrides the default colors.
|
||||
if (!options.palettes.empty()) {
|
||||
File file;
|
||||
@@ -191,7 +232,7 @@ void reverse() {
|
||||
|
||||
if (palettes.size() > options.nbPalettes) {
|
||||
warning(
|
||||
"Read %zu palettes, more than the specified limit of %" PRIu8,
|
||||
"Read %zu palettes, more than the specified limit of %" PRIu16,
|
||||
palettes.size(),
|
||||
options.nbPalettes
|
||||
);
|
||||
@@ -199,6 +240,20 @@ void reverse() {
|
||||
|
||||
if (options.palSpecType == Options::EXPLICIT && palettes != options.palSpec) {
|
||||
warning("Colors in the palette file do not match those specified with `-c`!");
|
||||
// This spacing aligns "...versus with `-c`" above the column of `-c` palettes
|
||||
fputs("Colors specified in the palette file: ...versus with `-c`:\n", stderr);
|
||||
for (size_t i = 0; i < palettes.size() && i < options.palSpec.size(); ++i) {
|
||||
if (i < palettes.size()) {
|
||||
printPalette(palettes[i]);
|
||||
} else {
|
||||
fputs(" ", stderr);
|
||||
}
|
||||
if (i < options.palSpec.size()) {
|
||||
fputs(" ", stderr);
|
||||
printPalette(options.palSpec[i]);
|
||||
}
|
||||
putc('\n', stderr);
|
||||
}
|
||||
}
|
||||
} else if (options.palSpecType == Options::EMBEDDED) {
|
||||
warning("An embedded palette was requested, but no palette file was specified; ignoring "
|
||||
@@ -208,13 +263,14 @@ void reverse() {
|
||||
}
|
||||
|
||||
std::optional<DefaultInitVec<uint8_t>> attrmap;
|
||||
uint16_t nbTilesInBank[2] = {0, 0}; // Only used if there is an attrmap.
|
||||
if (!options.attrmap.empty()) {
|
||||
attrmap = readInto(options.attrmap);
|
||||
if (attrmap->size() != nbTileInstances) {
|
||||
if (attrmap->size() != mapSize) {
|
||||
fatal(
|
||||
"Attribute map size (%zu tiles) doesn't match image's (%zu)",
|
||||
attrmap->size(),
|
||||
nbTileInstances
|
||||
mapSize
|
||||
);
|
||||
}
|
||||
|
||||
@@ -222,57 +278,118 @@ void reverse() {
|
||||
// We do this now for two reasons:
|
||||
// 1. Checking those during the main loop is harmful to optimization, and
|
||||
// 2. It clutters the code more, and it's not in great shape to begin with
|
||||
bool bad = false;
|
||||
for (auto attr : *attrmap) {
|
||||
for (size_t index = 0; index < mapSize; ++index) {
|
||||
uint8_t attr = (*attrmap)[index];
|
||||
size_t tx = index % width, ty = index / width;
|
||||
|
||||
if ((attr & 0b111) > palettes.size()) {
|
||||
error(
|
||||
"Referencing palette %u, but there are only %zu!", attr & 0b111, palettes.size()
|
||||
"Attribute map references palette #%u at (%zu, %zu), but there are only %zu!",
|
||||
attr & 0b111,
|
||||
tx,
|
||||
ty,
|
||||
palettes.size()
|
||||
);
|
||||
bad = true;
|
||||
}
|
||||
if (attr & 0x08 && !tilemap) {
|
||||
warning("Tile in bank 1 but no tilemap specified; ignoring the bank bit");
|
||||
|
||||
bool bank = attr & 0b1000;
|
||||
|
||||
if (!tilemap) {
|
||||
if (bank) {
|
||||
warning(
|
||||
"Attribute map assigns tile at (%zu, %zu) to bank 1, but no tilemap "
|
||||
"specified; "
|
||||
"ignoring the bank bit",
|
||||
tx,
|
||||
ty
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (uint8_t tileOfs = (*tilemap)[index] - options.baseTileIDs[bank];
|
||||
tileOfs >= nbTilesInBank[bank]) {
|
||||
nbTilesInBank[bank] = tileOfs + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (bad) {
|
||||
giveUp();
|
||||
|
||||
options.verbosePrint(
|
||||
Options::VERB_INTERM,
|
||||
"Number of tiles in bank {0: %" PRIu16 ", 1: %" PRIu16 "}\n",
|
||||
nbTilesInBank[0],
|
||||
nbTilesInBank[1]
|
||||
);
|
||||
|
||||
for (int bank = 0; bank < 2; ++bank) {
|
||||
if (nbTilesInBank[bank] > options.maxNbTiles[bank]) {
|
||||
error(
|
||||
"Bank %d contains %" PRIu16 " tiles, but the specified limit is %" PRIu16,
|
||||
bank,
|
||||
nbTilesInBank[bank],
|
||||
options.maxNbTiles[bank]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (nbTilesInBank[0] + nbTilesInBank[1] > nbTiles) {
|
||||
fatal(
|
||||
"The tilemap references %" PRIu16 " tiles in bank 0 and %" PRIu16
|
||||
" in bank 1, but only %zu have been read in total",
|
||||
nbTilesInBank[0],
|
||||
nbTilesInBank[1],
|
||||
nbTiles
|
||||
);
|
||||
}
|
||||
|
||||
requireZeroErrors();
|
||||
}
|
||||
|
||||
if (tilemap) {
|
||||
if (attrmap) {
|
||||
for (auto [id, attr] : zip(*tilemap, *attrmap)) {
|
||||
bool bank = attr & 1 << 3;
|
||||
if (id >= options.maxNbTiles[bank]) {
|
||||
warning(
|
||||
"Tile #%" PRIu8 " was referenced, but the limit for bank %u is %" PRIu16,
|
||||
id,
|
||||
for (size_t index = 0; index < mapSize; ++index) {
|
||||
size_t tx = index % width, ty = index / width;
|
||||
uint8_t tileID = (*tilemap)[index];
|
||||
uint8_t attr = (*attrmap)[index];
|
||||
bool bank = attr & 0x08;
|
||||
|
||||
if (uint8_t tileOfs = tileID - options.baseTileIDs[bank];
|
||||
tileOfs >= options.maxNbTiles[bank]) {
|
||||
error(
|
||||
"Tilemap references tile #%" PRIu8
|
||||
" at (%zu, %zu), but the limit for bank %u is %" PRIu16,
|
||||
tileID,
|
||||
tx,
|
||||
ty,
|
||||
bank,
|
||||
options.maxNbTiles[bank]
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (auto id : *tilemap) {
|
||||
if (id >= options.maxNbTiles[0]) {
|
||||
warning(
|
||||
"Tile #%" PRIu8 " was referenced, but the limit is %" PRIu16,
|
||||
id,
|
||||
options.maxNbTiles[0]
|
||||
size_t const limit = std::min<size_t>(nbTiles, options.maxNbTiles[0]);
|
||||
for (size_t index = 0; index < mapSize; ++index) {
|
||||
if (uint8_t tileID = (*tilemap)[index];
|
||||
static_cast<uint8_t>(tileID - options.baseTileIDs[0]) >= limit) {
|
||||
size_t tx = index % width, ty = index / width;
|
||||
error(
|
||||
"Tilemap references tile #%" PRIu8 " at (%zu, %zu), but the limit is %zu",
|
||||
tileID,
|
||||
tx,
|
||||
ty,
|
||||
limit
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
requireZeroErrors();
|
||||
}
|
||||
|
||||
std::optional<DefaultInitVec<uint8_t>> palmap;
|
||||
if (!options.palmap.empty()) {
|
||||
palmap = readInto(options.palmap);
|
||||
if (palmap->size() != nbTileInstances) {
|
||||
if (palmap->size() != mapSize) {
|
||||
fatal(
|
||||
"Palette map size (%zu tiles) doesn't match image's (%zu)",
|
||||
palmap->size(),
|
||||
nbTileInstances
|
||||
"Palette map size (%zu tiles) doesn't match image size (%zu)", palmap->size(), mapSize
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -300,7 +417,7 @@ void reverse() {
|
||||
png_set_IHDR(
|
||||
png,
|
||||
pngInfo,
|
||||
options.reversedWidth * 8,
|
||||
width * 8,
|
||||
height * 8,
|
||||
8,
|
||||
PNG_COLOR_TYPE_RGB_ALPHA,
|
||||
@@ -317,8 +434,8 @@ void reverse() {
|
||||
sbitChunk.alpha = 1;
|
||||
png_set_sBIT(png, pngInfo, &sbitChunk);
|
||||
|
||||
constexpr uint8_t SIZEOF_PIXEL = 4; // Each pixel is 4 bytes (RGBA @ 8 bits/component)
|
||||
size_t const SIZEOF_ROW = options.reversedWidth * 8 * SIZEOF_PIXEL;
|
||||
constexpr uint8_t SIZEOF_TILE = 4 * 8; // 4 bytes/pixel (RGBA @ 8 bits/channel) * 8 pixels/tile
|
||||
size_t const SIZEOF_ROW = width * SIZEOF_TILE;
|
||||
std::vector<uint8_t> tileRow(8 * SIZEOF_ROW, 0xFF); // Data for 8 rows of pixels
|
||||
uint8_t * const rowPtrs[8] = {
|
||||
&tileRow.data()[0 * SIZEOF_ROW],
|
||||
@@ -335,15 +452,15 @@ void reverse() {
|
||||
for (size_t tx = 0; tx < width; ++tx) {
|
||||
size_t index = options.columnMajor ? ty + tx * height : ty * width + tx;
|
||||
// By default, a tile is unflipped, in bank 0, and uses palette #0
|
||||
uint8_t attribute = attrmap.has_value() ? (*attrmap)[index] : 0x00;
|
||||
bool bank = attribute & 0x08;
|
||||
uint8_t attribute = attrmap ? (*attrmap)[index] : 0b0000;
|
||||
bool bank = attribute & 0b1000;
|
||||
// Get the tile ID at this location
|
||||
size_t tileID = index;
|
||||
if (tilemap.has_value()) {
|
||||
tileID =
|
||||
(*tilemap)[index] - options.baseTileIDs[bank] + bank * options.maxNbTiles[0];
|
||||
}
|
||||
assume(tileID < nbTileInstances); // Should have been checked earlier
|
||||
size_t tileOfs =
|
||||
tilemap ? static_cast<uint8_t>((*tilemap)[index] - options.baseTileIDs[bank])
|
||||
+ (bank ? nbTilesInBank[0] : 0)
|
||||
: index;
|
||||
// This should have been enforced by the earlier checking.
|
||||
assume(tileOfs < nbTiles + options.trim);
|
||||
size_t palID = palmap ? (*palmap)[index] : attribute & 0b111;
|
||||
assume(palID < palettes.size()); // Should be ensured on data read
|
||||
|
||||
@@ -366,9 +483,8 @@ void reverse() {
|
||||
0x00,
|
||||
0x00,
|
||||
};
|
||||
uint8_t const *tileData = tileID > nbTileInstances - options.trim
|
||||
? trimmedTile.data()
|
||||
: &tiles[tileID * tileSize];
|
||||
uint8_t const *tileData =
|
||||
tileOfs >= nbTiles ? trimmedTile.data() : &tiles[tileOfs * tileSize];
|
||||
auto const &palette = palettes[palID];
|
||||
for (uint8_t y = 0; y < 8; ++y) {
|
||||
// If vertically mirrored, fetch the bytes from the other end
|
||||
@@ -379,7 +495,7 @@ void reverse() {
|
||||
bitplane0 = flipTable[bitplane0];
|
||||
bitplane1 = flipTable[bitplane1];
|
||||
}
|
||||
uint8_t *ptr = &rowPtrs[y][tx * 8 * SIZEOF_PIXEL];
|
||||
uint8_t *ptr = &rowPtrs[y][tx * SIZEOF_TILE];
|
||||
for (uint8_t x = 0; x < 8; ++x) {
|
||||
uint8_t bit0 = bitplane0 & 0x80, bit1 = bitplane1 & 0x80;
|
||||
Rgba const &pixel = *palette[bit0 >> 7 | bit1 >> 6];
|
||||
|
||||
@@ -128,46 +128,47 @@ static ssize_t getPlacement(Section const §ion, MemoryLocation &location) {
|
||||
std::deque<FreeSpace> &bankMem = memory[section.type][location.bank - typeInfo.firstBank];
|
||||
size_t spaceIdx = 0;
|
||||
|
||||
if (spaceIdx < bankMem.size())
|
||||
if (spaceIdx < bankMem.size()) {
|
||||
location.address = bankMem[spaceIdx].address;
|
||||
|
||||
// Process locations in that bank
|
||||
while (spaceIdx < bankMem.size()) {
|
||||
// If that location is OK, return it
|
||||
if (isLocationSuitable(section, bankMem[spaceIdx], location))
|
||||
return spaceIdx;
|
||||
// Process locations in that bank
|
||||
while (spaceIdx < bankMem.size()) {
|
||||
// If that location is OK, return it
|
||||
if (isLocationSuitable(section, bankMem[spaceIdx], location))
|
||||
return spaceIdx;
|
||||
|
||||
// Go to the next *possible* location
|
||||
if (section.isAddressFixed) {
|
||||
// If the address is fixed, there can be only
|
||||
// one candidate block per bank; if we already
|
||||
// reached it, give up.
|
||||
if (location.address < section.org)
|
||||
location.address = section.org;
|
||||
else
|
||||
break; // Try again in next bank
|
||||
} else if (section.isAlignFixed) {
|
||||
// Move to next aligned location
|
||||
// Move back to alignment boundary
|
||||
location.address -= section.alignOfs;
|
||||
// Ensure we're there (e.g. on first check)
|
||||
location.address &= ~section.alignMask;
|
||||
// Go to next align boundary and add offset
|
||||
location.address += section.alignMask + 1 + section.alignOfs;
|
||||
} else {
|
||||
// Any location is fine, so, next free block
|
||||
spaceIdx++;
|
||||
if (spaceIdx < bankMem.size())
|
||||
location.address = bankMem[spaceIdx].address;
|
||||
// Go to the next *possible* location
|
||||
if (section.isAddressFixed) {
|
||||
// If the address is fixed, there can be only
|
||||
// one candidate block per bank; if we already
|
||||
// reached it, give up.
|
||||
if (location.address < section.org)
|
||||
location.address = section.org;
|
||||
else
|
||||
break; // Try again in next bank
|
||||
} else if (section.isAlignFixed) {
|
||||
// Move to next aligned location
|
||||
// Move back to alignment boundary
|
||||
location.address -= section.alignOfs;
|
||||
// Ensure we're there (e.g. on first check)
|
||||
location.address &= ~section.alignMask;
|
||||
// Go to next align boundary and add offset
|
||||
location.address += section.alignMask + 1 + section.alignOfs;
|
||||
} else {
|
||||
// Any location is fine, so, next free block
|
||||
spaceIdx++;
|
||||
if (spaceIdx < bankMem.size())
|
||||
location.address = bankMem[spaceIdx].address;
|
||||
}
|
||||
|
||||
// If that location is past the current block's end,
|
||||
// go forwards until that is no longer the case.
|
||||
while (spaceIdx < bankMem.size()
|
||||
&& location.address >= bankMem[spaceIdx].address + bankMem[spaceIdx].size)
|
||||
spaceIdx++;
|
||||
|
||||
// Try again with the new location/free space combo
|
||||
}
|
||||
|
||||
// If that location is past the current block's end,
|
||||
// go forwards until that is no longer the case.
|
||||
while (spaceIdx < bankMem.size()
|
||||
&& location.address >= bankMem[spaceIdx].address + bankMem[spaceIdx].size)
|
||||
spaceIdx++;
|
||||
|
||||
// Try again with the new location/free space combo
|
||||
}
|
||||
|
||||
// Try again in the next bank, if one is available.
|
||||
@@ -235,8 +236,9 @@ static void placeSection(Section §ion) {
|
||||
assignSection(section, location);
|
||||
|
||||
// Update the free space
|
||||
uint16_t sectionEnd = section.org + section.size;
|
||||
bool noLeftSpace = freeSpace.address == section.org;
|
||||
bool noRightSpace = freeSpace.address + freeSpace.size == section.org + section.size;
|
||||
bool noRightSpace = freeSpace.address + freeSpace.size == sectionEnd;
|
||||
if (noLeftSpace && noRightSpace) {
|
||||
// The free space is entirely deleted
|
||||
bankMem.erase(bankMem.begin() + spaceIdx);
|
||||
@@ -245,12 +247,11 @@ static void placeSection(Section §ion) {
|
||||
// Append the new space after the original one
|
||||
bankMem.insert(
|
||||
bankMem.begin() + spaceIdx + 1,
|
||||
{.address = (uint16_t)(section.org + section.size),
|
||||
.size =
|
||||
(uint16_t)(freeSpace.address + freeSpace.size - section.org - section.size)}
|
||||
{.address = sectionEnd,
|
||||
.size = (uint16_t)(freeSpace.address + freeSpace.size - sectionEnd)}
|
||||
);
|
||||
// **`freeSpace` cannot be reused from this point on**, because `bankMem.insert`
|
||||
// invalidates all references to itself!
|
||||
// **`freeSpace` cannot be reused from this point on, because `bankMem.insert`
|
||||
// invalidates all references to itself!**
|
||||
|
||||
// Resize the original space (address is unmodified)
|
||||
bankMem[spaceIdx].size = section.org - bankMem[spaceIdx].address;
|
||||
|
||||
@@ -24,14 +24,14 @@
|
||||
#include "link/section.hpp"
|
||||
#include "link/symbol.hpp"
|
||||
|
||||
bool isDmgMode; // -d
|
||||
char *linkerScriptName; // -l
|
||||
char const *mapFileName; // -m
|
||||
bool noSymInMap; // -M
|
||||
char const *symFileName; // -n
|
||||
char const *overlayFileName; // -O
|
||||
char const *outputFileName; // -o
|
||||
uint8_t padValue; // -p
|
||||
bool isDmgMode; // -d
|
||||
char const *linkerScriptName; // -l
|
||||
char const *mapFileName; // -m
|
||||
bool noSymInMap; // -M
|
||||
char const *symFileName; // -n
|
||||
char const *overlayFileName; // -O
|
||||
char const *outputFileName; // -o
|
||||
uint8_t padValue; // -p
|
||||
bool hasPadValue = false;
|
||||
// Setting these three to 0 disables the functionality
|
||||
uint16_t scrambleROMX = 0; // -S
|
||||
@@ -46,28 +46,8 @@ FILE *linkerScript;
|
||||
|
||||
static uint32_t nbErrors = 0;
|
||||
|
||||
std::vector<uint32_t> &FileStackNode::iters() {
|
||||
assume(std::holds_alternative<std::vector<uint32_t>>(data));
|
||||
return std::get<std::vector<uint32_t>>(data);
|
||||
}
|
||||
|
||||
std::vector<uint32_t> const &FileStackNode::iters() const {
|
||||
assume(std::holds_alternative<std::vector<uint32_t>>(data));
|
||||
return std::get<std::vector<uint32_t>>(data);
|
||||
}
|
||||
|
||||
std::string &FileStackNode::name() {
|
||||
assume(std::holds_alternative<std::string>(data));
|
||||
return std::get<std::string>(data);
|
||||
}
|
||||
|
||||
std::string const &FileStackNode::name() const {
|
||||
assume(std::holds_alternative<std::string>(data));
|
||||
return std::get<std::string>(data);
|
||||
}
|
||||
|
||||
std::string const &FileStackNode::dump(uint32_t curLineNo) const {
|
||||
if (std::holds_alternative<std::vector<uint32_t>>(data)) {
|
||||
if (data.holds<std::vector<uint32_t>>()) {
|
||||
assume(parent); // REPT nodes use their parent's name
|
||||
std::string const &lastName = parent->dump(lineNo);
|
||||
fputs(" -> ", stderr);
|
||||
@@ -349,7 +329,7 @@ int main(int argc, char *argv[]) {
|
||||
break;
|
||||
case 'l':
|
||||
if (linkerScriptName)
|
||||
warnx("Overriding linker script %s", musl_optarg);
|
||||
warnx("Overriding linker script %s", linkerScriptName);
|
||||
linkerScriptName = musl_optarg;
|
||||
break;
|
||||
case 'M':
|
||||
@@ -357,22 +337,22 @@ int main(int argc, char *argv[]) {
|
||||
break;
|
||||
case 'm':
|
||||
if (mapFileName)
|
||||
warnx("Overriding map file %s", musl_optarg);
|
||||
warnx("Overriding map file %s", mapFileName);
|
||||
mapFileName = musl_optarg;
|
||||
break;
|
||||
case 'n':
|
||||
if (symFileName)
|
||||
warnx("Overriding sym file %s", musl_optarg);
|
||||
warnx("Overriding sym file %s", symFileName);
|
||||
symFileName = musl_optarg;
|
||||
break;
|
||||
case 'O':
|
||||
if (overlayFileName)
|
||||
warnx("Overriding overlay file %s", musl_optarg);
|
||||
warnx("Overriding overlay file %s", overlayFileName);
|
||||
overlayFileName = musl_optarg;
|
||||
break;
|
||||
case 'o':
|
||||
if (outputFileName)
|
||||
warnx("Overriding output file %s", musl_optarg);
|
||||
warnx("Overriding output file %s", outputFileName);
|
||||
outputFileName = musl_optarg;
|
||||
break;
|
||||
case 'p': {
|
||||
@@ -412,7 +392,6 @@ int main(int argc, char *argv[]) {
|
||||
is32kMode = true;
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "FATAL: unknown option '%c'\n", ch);
|
||||
printUsage();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ static std::vector<std::vector<FileStackNode>> nodes;
|
||||
// Helper functions for reading object files
|
||||
|
||||
// Internal, DO NOT USE.
|
||||
// For helper wrapper macros defined below, such as `tryReadlong`
|
||||
// For helper wrapper macros defined below, such as `tryReadLong`
|
||||
#define tryRead(func, type, errval, vartype, var, file, ...) \
|
||||
do { \
|
||||
FILE *tmpFile = file; \
|
||||
@@ -48,7 +48,7 @@ static std::vector<std::vector<FileStackNode>> nodes;
|
||||
* @param file The file to read from. This will read 4 bytes from the file.
|
||||
* @return The value read, cast to a int64_t, or -1 on failure.
|
||||
*/
|
||||
static int64_t readlong(FILE *file) {
|
||||
static int64_t readLong(FILE *file) {
|
||||
uint32_t value = 0;
|
||||
|
||||
// Read the little-endian value byte by byte
|
||||
@@ -76,8 +76,8 @@ static int64_t readlong(FILE *file) {
|
||||
* @param ... A format string and related arguments; note that an extra string
|
||||
* argument is provided, the reason for failure
|
||||
*/
|
||||
#define tryReadlong(var, file, ...) \
|
||||
tryRead(readlong, int64_t, INT64_MAX, long, var, file, __VA_ARGS__)
|
||||
#define tryReadLong(var, file, ...) \
|
||||
tryRead(readLong, int64_t, INT64_MAX, long, var, file, __VA_ARGS__)
|
||||
|
||||
// There is no `readbyte`, just use `fgetc` or `getc`.
|
||||
|
||||
@@ -99,7 +99,7 @@ static int64_t readlong(FILE *file) {
|
||||
* @param ... A format string and related arguments; note that an extra string
|
||||
* argument is provided, the reason for failure
|
||||
*/
|
||||
#define tryReadstring(var, file, ...) \
|
||||
#define tryReadString(var, file, ...) \
|
||||
do { \
|
||||
FILE *tmpFile = file; \
|
||||
std::string &tmpVal = var; \
|
||||
@@ -127,9 +127,9 @@ static void readFileStackNode(
|
||||
FileStackNode &node = fileNodes[i];
|
||||
uint32_t parentID;
|
||||
|
||||
tryReadlong(parentID, file, "%s: Cannot read node #%" PRIu32 "'s parent ID: %s", fileName, i);
|
||||
tryReadLong(parentID, file, "%s: Cannot read node #%" PRIu32 "'s parent ID: %s", fileName, i);
|
||||
node.parent = parentID != (uint32_t)-1 ? &fileNodes[parentID] : nullptr;
|
||||
tryReadlong(
|
||||
tryReadLong(
|
||||
node.lineNo, file, "%s: Cannot read node #%" PRIu32 "'s line number: %s", fileName, i
|
||||
);
|
||||
tryGetc(
|
||||
@@ -144,17 +144,17 @@ static void readFileStackNode(
|
||||
case NODE_FILE:
|
||||
case NODE_MACRO:
|
||||
node.data = "";
|
||||
tryReadstring(
|
||||
tryReadString(
|
||||
node.name(), file, "%s: Cannot read node #%" PRIu32 "'s file name: %s", fileName, i
|
||||
);
|
||||
break;
|
||||
|
||||
uint32_t depth;
|
||||
case NODE_REPT:
|
||||
tryReadlong(depth, file, "%s: Cannot read node #%" PRIu32 "'s rept depth: %s", fileName, i);
|
||||
tryReadLong(depth, file, "%s: Cannot read node #%" PRIu32 "'s rept depth: %s", fileName, i);
|
||||
node.data = std::vector<uint32_t>(depth);
|
||||
for (uint32_t k = 0; k < depth; k++)
|
||||
tryReadlong(
|
||||
tryReadLong(
|
||||
node.iters()[k],
|
||||
file,
|
||||
"%s: Cannot read node #%" PRIu32 "'s iter #%" PRIu32 ": %s",
|
||||
@@ -182,7 +182,7 @@ static void readFileStackNode(
|
||||
static void readSymbol(
|
||||
FILE *file, Symbol &symbol, char const *fileName, std::vector<FileStackNode> const &fileNodes
|
||||
) {
|
||||
tryReadstring(symbol.name, file, "%s: Cannot read symbol name: %s", fileName);
|
||||
tryReadString(symbol.name, file, "%s: Cannot read symbol name: %s", fileName);
|
||||
tryGetc(
|
||||
ExportLevel,
|
||||
symbol.type,
|
||||
@@ -193,13 +193,12 @@ static void readSymbol(
|
||||
);
|
||||
// If the symbol is defined in this file, read its definition
|
||||
if (symbol.type != SYMTYPE_IMPORT) {
|
||||
symbol.objFileName = fileName;
|
||||
uint32_t nodeID;
|
||||
tryReadlong(
|
||||
tryReadLong(
|
||||
nodeID, file, "%s: Cannot read \"%s\"'s node ID: %s", fileName, symbol.name.c_str()
|
||||
);
|
||||
symbol.src = &fileNodes[nodeID];
|
||||
tryReadlong(
|
||||
tryReadLong(
|
||||
symbol.lineNo,
|
||||
file,
|
||||
"%s: Cannot read \"%s\"'s line number: %s",
|
||||
@@ -207,14 +206,14 @@ static void readSymbol(
|
||||
symbol.name.c_str()
|
||||
);
|
||||
int32_t sectionID, value;
|
||||
tryReadlong(
|
||||
tryReadLong(
|
||||
sectionID,
|
||||
file,
|
||||
"%s: Cannot read \"%s\"'s section ID: %s",
|
||||
fileName,
|
||||
symbol.name.c_str()
|
||||
);
|
||||
tryReadlong(
|
||||
tryReadLong(
|
||||
value, file, "%s: Cannot read \"%s\"'s value: %s", fileName, symbol.name.c_str()
|
||||
);
|
||||
if (sectionID == -1) {
|
||||
@@ -250,7 +249,7 @@ static void readPatch(
|
||||
uint32_t nodeID, rpnSize;
|
||||
PatchType type;
|
||||
|
||||
tryReadlong(
|
||||
tryReadLong(
|
||||
nodeID,
|
||||
file,
|
||||
"%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s node ID: %s",
|
||||
@@ -259,7 +258,7 @@ static void readPatch(
|
||||
i
|
||||
);
|
||||
patch.src = &fileNodes[nodeID];
|
||||
tryReadlong(
|
||||
tryReadLong(
|
||||
patch.lineNo,
|
||||
file,
|
||||
"%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s line number: %s",
|
||||
@@ -267,7 +266,7 @@ static void readPatch(
|
||||
sectName.c_str(),
|
||||
i
|
||||
);
|
||||
tryReadlong(
|
||||
tryReadLong(
|
||||
patch.offset,
|
||||
file,
|
||||
"%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s offset: %s",
|
||||
@@ -275,7 +274,7 @@ static void readPatch(
|
||||
sectName.c_str(),
|
||||
i
|
||||
);
|
||||
tryReadlong(
|
||||
tryReadLong(
|
||||
patch.pcSectionID,
|
||||
file,
|
||||
"%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s PC offset: %s",
|
||||
@@ -283,7 +282,7 @@ static void readPatch(
|
||||
sectName.c_str(),
|
||||
i
|
||||
);
|
||||
tryReadlong(
|
||||
tryReadLong(
|
||||
patch.pcOffset,
|
||||
file,
|
||||
"%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s PC offset: %s",
|
||||
@@ -301,7 +300,7 @@ static void readPatch(
|
||||
i
|
||||
);
|
||||
patch.type = type;
|
||||
tryReadlong(
|
||||
tryReadLong(
|
||||
rpnSize,
|
||||
file,
|
||||
"%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s RPN size: %s",
|
||||
@@ -345,8 +344,20 @@ static void readSection(
|
||||
int32_t tmp;
|
||||
uint8_t byte;
|
||||
|
||||
tryReadstring(section.name, file, "%s: Cannot read section name: %s", fileName);
|
||||
tryReadlong(tmp, file, "%s: Cannot read \"%s\"'s' size: %s", fileName, section.name.c_str());
|
||||
tryReadString(section.name, file, "%s: Cannot read section name: %s", fileName);
|
||||
uint32_t nodeID;
|
||||
tryReadLong(
|
||||
nodeID, file, "%s: Cannot read \"%s\"'s node ID: %s", fileName, section.name.c_str()
|
||||
);
|
||||
section.src = &fileNodes[nodeID];
|
||||
tryReadLong(
|
||||
section.lineNo,
|
||||
file,
|
||||
"%s: Cannot read \"%s\"'s line number: %s",
|
||||
fileName,
|
||||
section.name.c_str()
|
||||
);
|
||||
tryReadLong(tmp, file, "%s: Cannot read \"%s\"'s' size: %s", fileName, section.name.c_str());
|
||||
if (tmp < 0 || tmp > UINT16_MAX)
|
||||
errx("\"%s\"'s section size (%" PRId32 ") is invalid", section.name.c_str(), tmp);
|
||||
section.size = tmp;
|
||||
@@ -354,21 +365,25 @@ static void readSection(
|
||||
tryGetc(
|
||||
uint8_t, byte, file, "%s: Cannot read \"%s\"'s type: %s", fileName, section.name.c_str()
|
||||
);
|
||||
section.type = (SectionType)(byte & 0x3F);
|
||||
if (uint8_t type = byte & 0x3F; type >= SECTTYPE_INVALID) {
|
||||
errx("\"%s\" has unknown section type 0x%02x", section.name.c_str(), type);
|
||||
} else {
|
||||
section.type = SectionType(type);
|
||||
}
|
||||
if (byte >> 7)
|
||||
section.modifier = SECTION_UNION;
|
||||
else if (byte >> 6)
|
||||
section.modifier = SECTION_FRAGMENT;
|
||||
else
|
||||
section.modifier = SECTION_NORMAL;
|
||||
tryReadlong(tmp, file, "%s: Cannot read \"%s\"'s org: %s", fileName, section.name.c_str());
|
||||
tryReadLong(tmp, file, "%s: Cannot read \"%s\"'s org: %s", fileName, section.name.c_str());
|
||||
section.isAddressFixed = tmp >= 0;
|
||||
if (tmp > UINT16_MAX) {
|
||||
error(nullptr, 0, "\"%s\"'s org is too large (%" PRId32 ")", section.name.c_str(), tmp);
|
||||
tmp = UINT16_MAX;
|
||||
}
|
||||
section.org = tmp;
|
||||
tryReadlong(tmp, file, "%s: Cannot read \"%s\"'s bank: %s", fileName, section.name.c_str());
|
||||
tryReadLong(tmp, file, "%s: Cannot read \"%s\"'s bank: %s", fileName, section.name.c_str());
|
||||
section.isBankFixed = tmp >= 0;
|
||||
section.bank = tmp;
|
||||
tryGetc(
|
||||
@@ -383,7 +398,7 @@ static void readSection(
|
||||
byte = 16;
|
||||
section.isAlignFixed = byte != 0;
|
||||
section.alignMask = (1 << byte) - 1;
|
||||
tryReadlong(
|
||||
tryReadLong(
|
||||
tmp, file, "%s: Cannot read \"%s\"'s alignment offset: %s", fileName, section.name.c_str()
|
||||
);
|
||||
if (tmp > UINT16_MAX) {
|
||||
@@ -413,7 +428,7 @@ static void readSection(
|
||||
|
||||
uint32_t nbPatches;
|
||||
|
||||
tryReadlong(
|
||||
tryReadLong(
|
||||
nbPatches,
|
||||
file,
|
||||
"%s: Cannot read \"%s\"'s number of patches: %s",
|
||||
@@ -466,7 +481,7 @@ static void readAssertion(
|
||||
|
||||
assertName += std::to_string(i);
|
||||
readPatch(file, assert.patch, fileName, assertName, 0, fileNodes);
|
||||
tryReadstring(assert.message, file, "%s: Cannot read assertion's message: %s", fileName);
|
||||
tryReadString(assert.message, file, "%s: Cannot read assertion's message: %s", fileName);
|
||||
}
|
||||
|
||||
void obj_ReadFile(char const *fileName, unsigned int fileID) {
|
||||
@@ -499,7 +514,7 @@ void obj_ReadFile(char const *fileName, unsigned int fileID) {
|
||||
// object file. It's better than nothing.
|
||||
nodes[fileID].push_back({
|
||||
.type = NODE_FILE,
|
||||
.data = fileName,
|
||||
.data = Either<std::vector<uint32_t>, std::string>(fileName),
|
||||
.parent = nullptr,
|
||||
.lineNo = 0,
|
||||
});
|
||||
@@ -521,7 +536,7 @@ void obj_ReadFile(char const *fileName, unsigned int fileID) {
|
||||
|
||||
uint32_t revNum;
|
||||
|
||||
tryReadlong(revNum, file, "%s: Cannot read revision number: %s", fileName);
|
||||
tryReadLong(revNum, file, "%s: Cannot read revision number: %s", fileName);
|
||||
if (revNum != RGBDS_OBJECT_REV)
|
||||
errx(
|
||||
"%s: Unsupported object file for rgblink %s; try rebuilding \"%s\"%s"
|
||||
@@ -538,12 +553,12 @@ void obj_ReadFile(char const *fileName, unsigned int fileID) {
|
||||
uint32_t nbSymbols;
|
||||
uint32_t nbSections;
|
||||
|
||||
tryReadlong(nbSymbols, file, "%s: Cannot read number of symbols: %s", fileName);
|
||||
tryReadlong(nbSections, file, "%s: Cannot read number of sections: %s", fileName);
|
||||
tryReadLong(nbSymbols, file, "%s: Cannot read number of symbols: %s", fileName);
|
||||
tryReadLong(nbSections, file, "%s: Cannot read number of sections: %s", fileName);
|
||||
|
||||
nbSectionsToAssign += nbSections;
|
||||
|
||||
tryReadlong(nbNodes, file, "%s: Cannot read number of nodes: %s", fileName);
|
||||
tryReadLong(nbNodes, file, "%s: Cannot read number of nodes: %s", fileName);
|
||||
nodes[fileID].resize(nbNodes);
|
||||
verbosePrint("Reading %u nodes...\n", nbNodes);
|
||||
for (uint32_t i = nbNodes; i--;)
|
||||
@@ -560,10 +575,9 @@ void obj_ReadFile(char const *fileName, unsigned int fileID) {
|
||||
|
||||
readSymbol(file, symbol, fileName, nodes[fileID]);
|
||||
|
||||
if (symbol.type == SYMTYPE_EXPORT)
|
||||
sym_AddSymbol(symbol);
|
||||
if (auto *label = std::get_if<Label>(&symbol.data); label)
|
||||
nbSymPerSect[label->sectionID]++;
|
||||
sym_AddSymbol(symbol);
|
||||
if (symbol.data.holds<Label>())
|
||||
nbSymPerSect[symbol.data.get<Label>().sectionID]++;
|
||||
}
|
||||
|
||||
// This file's sections, stored in a table to link symbols to them
|
||||
@@ -581,7 +595,7 @@ void obj_ReadFile(char const *fileName, unsigned int fileID) {
|
||||
|
||||
uint32_t nbAsserts;
|
||||
|
||||
tryReadlong(nbAsserts, file, "%s: Cannot read number of assertions: %s", fileName);
|
||||
tryReadLong(nbAsserts, file, "%s: Cannot read number of assertions: %s", fileName);
|
||||
verbosePrint("Reading %" PRIu32 " assertions...\n", nbAsserts);
|
||||
for (uint32_t i = 0; i < nbAsserts; i++) {
|
||||
Assertion &assertion = assertions.emplace_front();
|
||||
@@ -601,12 +615,11 @@ void obj_ReadFile(char const *fileName, unsigned int fileID) {
|
||||
|
||||
// Give symbols' section pointers to their sections
|
||||
for (uint32_t i = 0; i < nbSymbols; i++) {
|
||||
if (auto *label = std::get_if<Label>(&fileSymbols[i].data); label) {
|
||||
Section *section = fileSections[label->sectionID].get();
|
||||
|
||||
label->section = section;
|
||||
if (fileSymbols[i].data.holds<Label>()) {
|
||||
Label &label = fileSymbols[i].data.get<Label>();
|
||||
label.section = fileSections[label.sectionID].get();
|
||||
// Give the section a pointer to the symbol as well
|
||||
linkSymToSect(fileSymbols[i], *section);
|
||||
linkSymToSect(fileSymbols[i], *label.section);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -618,13 +631,16 @@ void obj_ReadFile(char const *fileName, unsigned int fileID) {
|
||||
// This has to run **after** all the `sect_AddSection()` calls,
|
||||
// so that `sect_GetSection()` will work
|
||||
for (uint32_t i = 0; i < nbSymbols; i++) {
|
||||
if (auto *label = std::get_if<Label>(&fileSymbols[i].data); label) {
|
||||
if (Section *section = label->section; section->modifier != SECTION_NORMAL) {
|
||||
if (section->modifier == SECTION_FRAGMENT)
|
||||
if (fileSymbols[i].data.holds<Label>()) {
|
||||
Label &label = fileSymbols[i].data.get<Label>();
|
||||
if (Section *section = label.section; section->modifier != SECTION_NORMAL) {
|
||||
if (section->modifier == SECTION_FRAGMENT) {
|
||||
// Add the fragment's offset to the symbol's
|
||||
label->offset += section->offset;
|
||||
// (`section->offset` is computed by `sect_AddSection`)
|
||||
label.offset += section->offset;
|
||||
}
|
||||
// Associate the symbol with the main section, not the "component" one
|
||||
label->section = sect_GetSection(section->name);
|
||||
label.section = sect_GetSection(section->name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "platform.hpp"
|
||||
|
||||
#include "link/main.hpp"
|
||||
#include "link/section.hpp"
|
||||
#include "link/symbol.hpp"
|
||||
|
||||
#define BANK_SIZE 0x4000
|
||||
@@ -558,6 +559,25 @@ static void writeSym() {
|
||||
for (uint32_t bank = 0; bank < sections[type].size(); bank++)
|
||||
writeSymBank(sections[type][bank], type, bank);
|
||||
}
|
||||
|
||||
// Output the exported numeric constants
|
||||
static std::vector<Symbol *> constants; // `static` so `sym_ForEach` callback can see it
|
||||
constants.clear();
|
||||
sym_ForEach([](Symbol &sym) {
|
||||
// Symbols are already limited to the exported ones
|
||||
if (sym.data.holds<int32_t>())
|
||||
constants.push_back(&sym);
|
||||
});
|
||||
// Numeric constants are ordered by value, then by name
|
||||
std::sort(RANGE(constants), [](Symbol *sym1, Symbol *sym2) -> bool {
|
||||
int32_t val1 = sym1->data.get<int32_t>(), val2 = sym2->data.get<int32_t>();
|
||||
return val1 != val2 ? val1 < val2 : sym1->name < sym2->name;
|
||||
});
|
||||
for (Symbol *sym : constants) {
|
||||
int32_t val = sym->data.get<int32_t>();
|
||||
int width = val < 0x100 ? 2 : val < 0x10000 ? 4 : 8;
|
||||
fprintf(symFile, "%0*" PRIx32 " %s\n", width, val, sym->name.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
// Writes the map file, if applicable.
|
||||
|
||||
@@ -5,10 +5,9 @@
|
||||
#include <deque>
|
||||
#include <inttypes.h>
|
||||
#include <stdint.h>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include "helpers.hpp" // assume
|
||||
#include "helpers.hpp" // assume, clz, ctz
|
||||
#include "linkdefs.hpp"
|
||||
#include "opmath.hpp"
|
||||
|
||||
@@ -139,6 +138,22 @@ static int32_t computeRPNExpr(Patch const &patch, std::vector<Symbol> const &fil
|
||||
}
|
||||
break;
|
||||
|
||||
case RPN_HIGH:
|
||||
value = (popRPN(patch) >> 8) & 0xFF;
|
||||
break;
|
||||
case RPN_LOW:
|
||||
value = popRPN(patch) & 0xFF;
|
||||
break;
|
||||
|
||||
case RPN_BITWIDTH:
|
||||
value = popRPN(patch);
|
||||
value = value != 0 ? 32 - clz((uint32_t)value) : 0;
|
||||
break;
|
||||
case RPN_TZCOUNT:
|
||||
value = popRPN(patch);
|
||||
value = value != 0 ? ctz((uint32_t)value) : 32;
|
||||
break;
|
||||
|
||||
case RPN_OR:
|
||||
value = popRPN(patch) | popRPN(patch);
|
||||
break;
|
||||
@@ -214,8 +229,8 @@ static int32_t computeRPNExpr(Patch const &patch, std::vector<Symbol> const &fil
|
||||
);
|
||||
isError = true;
|
||||
value = 1;
|
||||
} else if (auto *label = std::get_if<Label>(&symbol->data); label) {
|
||||
value = label->section->bank;
|
||||
} else if (symbol->data.holds<Label>()) {
|
||||
value = symbol->data.get<Label>().section->bank;
|
||||
} else {
|
||||
error(
|
||||
patch.src,
|
||||
@@ -252,7 +267,7 @@ static int32_t computeRPNExpr(Patch const &patch, std::vector<Symbol> const &fil
|
||||
|
||||
case RPN_BANK_SELF:
|
||||
if (!patch.pcSection) {
|
||||
error(patch.src, patch.lineNo, "PC has no bank outside a section");
|
||||
error(patch.src, patch.lineNo, "PC has no bank outside of a section");
|
||||
isError = true;
|
||||
value = 1;
|
||||
} else {
|
||||
@@ -359,7 +374,7 @@ static int32_t computeRPNExpr(Patch const &patch, std::vector<Symbol> const &fil
|
||||
|
||||
if (value == -1) { // PC
|
||||
if (!patch.pcSection) {
|
||||
error(patch.src, patch.lineNo, "PC has no value outside a section");
|
||||
error(patch.src, patch.lineNo, "PC has no value outside of a section");
|
||||
value = 0;
|
||||
isError = true;
|
||||
} else {
|
||||
@@ -373,12 +388,13 @@ static int32_t computeRPNExpr(Patch const &patch, std::vector<Symbol> const &fil
|
||||
"Unknown symbol \"%s\"",
|
||||
fileSymbols[value].name.c_str()
|
||||
);
|
||||
sym_DumpLocalAliasedSymbols(fileSymbols[value].name);
|
||||
isError = true;
|
||||
} else if (auto *label = std::get_if<Label>(&symbol->data); label) {
|
||||
value = label->section->org + label->offset;
|
||||
} else if (symbol->data.holds<Label>()) {
|
||||
Label const &label = symbol->data.get<Label>();
|
||||
value = label.section->org + label.offset;
|
||||
} else {
|
||||
assume(std::holds_alternative<int32_t>(symbol->data));
|
||||
value = std::get<int32_t>(symbol->data);
|
||||
value = symbol->data.get<int32_t>();
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -450,8 +466,28 @@ static void applyFilePatches(Section §ion, Section &dataSection) {
|
||||
int32_t value = computeRPNExpr(patch, *section.fileSymbols);
|
||||
uint16_t offset = patch.offset + section.offset;
|
||||
|
||||
// `jr` is quite unlike the others...
|
||||
if (patch.type == PATCHTYPE_JR) {
|
||||
struct {
|
||||
uint8_t size;
|
||||
int32_t min;
|
||||
int32_t max;
|
||||
} const types[PATCHTYPE_INVALID] = {
|
||||
{1, -128, 255 }, // PATCHTYPE_BYTE
|
||||
{2, -32768, 65536 }, // PATCHTYPE_WORD
|
||||
{4, INT32_MIN, INT32_MAX}, // PATCHTYPE_LONG
|
||||
{1, 0, 0 }, // PATCHTYPE_JR
|
||||
};
|
||||
auto const &type = types[patch.type];
|
||||
|
||||
if (dataSection.data.size() < offset + type.size) {
|
||||
error(
|
||||
patch.src,
|
||||
patch.lineNo,
|
||||
"Patch would write %zu bytes past the end of section \"%s\" (%zu bytes long)",
|
||||
offset + type.size - dataSection.data.size(),
|
||||
dataSection.name.c_str(),
|
||||
dataSection.data.size()
|
||||
);
|
||||
} else if (patch.type == PATCHTYPE_JR) { // `jr` is quite unlike the others...
|
||||
// Offset is relative to the byte *after* the operand
|
||||
// PC as operand to `jr` is lower than reference PC by 2
|
||||
uint16_t address = patch.pcSection->org + patch.pcOffset + 2;
|
||||
@@ -468,26 +504,16 @@ static void applyFilePatches(Section §ion, Section &dataSection) {
|
||||
dataSection.data[offset] = jumpOffset & 0xFF;
|
||||
} else {
|
||||
// Patch a certain number of bytes
|
||||
struct {
|
||||
uint8_t size;
|
||||
int32_t min;
|
||||
int32_t max;
|
||||
} const types[PATCHTYPE_INVALID] = {
|
||||
{1, -128, 255 }, // PATCHTYPE_BYTE
|
||||
{2, -32768, 65536 }, // PATCHTYPE_WORD
|
||||
{4, INT32_MIN, INT32_MAX}, // PATCHTYPE_LONG
|
||||
};
|
||||
|
||||
if (!isError && (value < types[patch.type].min || value > types[patch.type].max))
|
||||
if (!isError && (value < type.min || value > type.max))
|
||||
error(
|
||||
patch.src,
|
||||
patch.lineNo,
|
||||
"Value %" PRId32 "%s is not %u-bit",
|
||||
value,
|
||||
value < 0 ? " (maybe negative?)" : "",
|
||||
types[patch.type].size * 8U
|
||||
type.size * 8U
|
||||
);
|
||||
for (uint8_t i = 0; i < types[patch.type].size; i++) {
|
||||
for (uint8_t i = 0; i < type.size; i++) {
|
||||
dataSection.data[offset + i] = value & 0xFF;
|
||||
value >>= 8;
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
static void includeFile(std::string &&path);
|
||||
static void incLineNo();
|
||||
|
||||
static void setFloatingSectionType(SectionType type);
|
||||
static void setSectionType(SectionType type);
|
||||
static void setSectionType(SectionType type, uint32_t bank);
|
||||
static void setAddr(uint32_t addr);
|
||||
@@ -51,6 +52,8 @@
|
||||
};
|
||||
}
|
||||
|
||||
/******************** Tokens and data types ********************/
|
||||
|
||||
%token YYEOF 0 "end of file"
|
||||
%token newline
|
||||
%token COMMA ","
|
||||
@@ -78,6 +81,8 @@
|
||||
|
||||
%%
|
||||
|
||||
/******************** Parser rules ********************/
|
||||
|
||||
lines:
|
||||
%empty
|
||||
| line lines
|
||||
@@ -102,11 +107,14 @@ line:
|
||||
|
||||
directive:
|
||||
sect_type {
|
||||
setSectionType($1);
|
||||
setSectionType($1);
|
||||
}
|
||||
| sect_type number {
|
||||
setSectionType($1, $2);
|
||||
}
|
||||
| sect_type FLOATING {
|
||||
setFloatingSectionType($1);
|
||||
}
|
||||
| FLOATING {
|
||||
makeAddrFloating();
|
||||
}
|
||||
@@ -147,7 +155,7 @@ optional:
|
||||
context.lineNo __VA_OPT__(, ) __VA_ARGS__ \
|
||||
)
|
||||
|
||||
// Lexer.
|
||||
/******************** Lexer ********************/
|
||||
|
||||
struct LexerStackEntry {
|
||||
std::filebuf file;
|
||||
@@ -233,7 +241,7 @@ yy::parser::symbol_type yylex() {
|
||||
}
|
||||
// Then, skip a comment if applicable.
|
||||
if (c == ';') {
|
||||
while (!isNewline(c)) {
|
||||
while (c != EOF && !isNewline(c)) {
|
||||
c = context.file.sbumpc();
|
||||
}
|
||||
}
|
||||
@@ -366,7 +374,7 @@ yy::parser::symbol_type yylex() {
|
||||
// Not marking as unreachable; this will generate a warning if any codepath forgets to return.
|
||||
}
|
||||
|
||||
// Semantic actions.
|
||||
/******************** Semantic actions ********************/
|
||||
|
||||
static std::array<std::vector<uint16_t>, SECTTYPE_INVALID> curAddr;
|
||||
static SectionType activeType; // Index into curAddr
|
||||
@@ -384,6 +392,20 @@ static void setActiveTypeAndIdx(SectionType type, uint32_t idx) {
|
||||
}
|
||||
}
|
||||
|
||||
static void setFloatingSectionType(SectionType type) {
|
||||
if (nbbanks(type) == 1) {
|
||||
setActiveTypeAndIdx(type, 0); // There is only a single bank anyway, so just set the index to 0.
|
||||
} else {
|
||||
activeType = type;
|
||||
activeBankIdx = UINT32_MAX;
|
||||
// Force PC to be floating for this kind of section.
|
||||
// Because we wouldn't know how to index into `curAddr[activeType]`!
|
||||
isPcFloating = true;
|
||||
floatingAlignMask = 0;
|
||||
floatingAlignOffset = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void setSectionType(SectionType type) {
|
||||
auto const &context = lexerStack.back();
|
||||
|
||||
@@ -429,6 +451,10 @@ static void setAddr(uint32_t addr) {
|
||||
scriptError(context, "Cannot set the current address: no memory region is active");
|
||||
return;
|
||||
}
|
||||
if (activeBankIdx == UINT32_MAX) {
|
||||
scriptError(context, "Cannot set the current address: the bank is floating");
|
||||
return;
|
||||
}
|
||||
|
||||
auto &pc = curAddr[activeType][activeBankIdx];
|
||||
auto const &typeInfo = sectionTypeInfo[activeType];
|
||||
@@ -590,9 +616,28 @@ static void placeSection(std::string const &name, bool isOptional) {
|
||||
assume(section->offset == 0);
|
||||
// Check that the linker script doesn't contradict what the code says.
|
||||
if (section->type == SECTTYPE_INVALID) {
|
||||
// SDCC areas don't have a type assigned yet, so the linker script is used to give them one.
|
||||
for (Section *fragment = section; fragment; fragment = fragment->nextu.get()) {
|
||||
fragment->type = activeType;
|
||||
// A section that has data must get assigned a type that requires data.
|
||||
if (!sect_HasData(activeType) && !section->data.empty()) {
|
||||
scriptError(
|
||||
context,
|
||||
"\"%s\" is specified to be a %s section, but it contains data",
|
||||
name.c_str(),
|
||||
typeInfo.name.c_str()
|
||||
);
|
||||
} else if (sect_HasData(activeType) && section->data.empty() && section->size != 0) {
|
||||
// A section that lacks data can only be assigned to a type that requires data
|
||||
// if it's empty.
|
||||
scriptError(
|
||||
context,
|
||||
"\"%s\" is specified to be a %s section, but it doesn't contain data",
|
||||
name.c_str(),
|
||||
typeInfo.name.c_str()
|
||||
);
|
||||
} else {
|
||||
// SDCC areas don't have a type assigned yet, so the linker script is used to give them one.
|
||||
for (Section *fragment = section; fragment; fragment = fragment->nextu.get()) {
|
||||
fragment->type = activeType;
|
||||
}
|
||||
}
|
||||
} else if (section->type != activeType) {
|
||||
scriptError(
|
||||
@@ -604,20 +649,24 @@ static void placeSection(std::string const &name, bool isOptional) {
|
||||
);
|
||||
}
|
||||
|
||||
uint32_t bank = activeBankIdx + typeInfo.firstBank;
|
||||
if (section->isBankFixed && bank != section->bank) {
|
||||
scriptError(
|
||||
context,
|
||||
"The linker script places section \"%s\" in %s bank %" PRIu32
|
||||
", but it was already defined in bank %" PRIu32,
|
||||
name.c_str(),
|
||||
sectionTypeInfo[section->type].name.c_str(),
|
||||
bank,
|
||||
section->bank
|
||||
);
|
||||
if (activeBankIdx == UINT32_MAX) {
|
||||
section->isBankFixed = false;
|
||||
} else {
|
||||
uint32_t bank = activeBankIdx + typeInfo.firstBank;
|
||||
if (section->isBankFixed && bank != section->bank) {
|
||||
scriptError(
|
||||
context,
|
||||
"The linker script places section \"%s\" in %s bank %" PRIu32
|
||||
", but it was already defined in bank %" PRIu32,
|
||||
name.c_str(),
|
||||
sectionTypeInfo[section->type].name.c_str(),
|
||||
bank,
|
||||
section->bank
|
||||
);
|
||||
}
|
||||
section->isBankFixed = true;
|
||||
section->bank = bank;
|
||||
}
|
||||
section->isBankFixed = true;
|
||||
section->bank = bank;
|
||||
|
||||
if (!isPcFloating) {
|
||||
uint16_t &org = curAddr[activeType][activeBankIdx];
|
||||
@@ -677,7 +726,7 @@ static void placeSection(std::string const &name, bool isOptional) {
|
||||
}
|
||||
}
|
||||
|
||||
// External API.
|
||||
/******************** External API ********************/
|
||||
|
||||
void script_ProcessScript(char const *path) {
|
||||
activeType = SECTTYPE_INVALID;
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <tuple>
|
||||
#include <variant>
|
||||
|
||||
#include "helpers.hpp" // assume
|
||||
#include "linkdefs.hpp"
|
||||
@@ -37,6 +36,7 @@ static int
|
||||
retry:
|
||||
++lineNo;
|
||||
int firstChar = getc(file);
|
||||
lineBuf.clear();
|
||||
|
||||
switch (firstChar) {
|
||||
case EOF:
|
||||
@@ -136,7 +136,8 @@ enum RelocFlags {
|
||||
};
|
||||
|
||||
void sdobj_ReadFile(FileStackNode const &where, FILE *file, std::vector<Symbol> &fileSymbols) {
|
||||
std::vector<char> line(256);
|
||||
std::vector<char> line;
|
||||
line.reserve(256);
|
||||
char const *token;
|
||||
|
||||
#define getToken(ptr, ...) \
|
||||
@@ -221,6 +222,7 @@ void sdobj_ReadFile(FileStackNode const &where, FILE *file, std::vector<Symbol>
|
||||
|
||||
getToken(nullptr, "'H' line is too short");
|
||||
uint32_t expectedNbSymbols = parseNumber(where, lineNo, token, numberType);
|
||||
fileSymbols.reserve(expectedNbSymbols);
|
||||
|
||||
expectToken("global", 'H');
|
||||
|
||||
@@ -254,6 +256,9 @@ void sdobj_ReadFile(FileStackNode const &where, FILE *file, std::vector<Symbol>
|
||||
);
|
||||
std::unique_ptr<Section> curSection = std::make_unique<Section>();
|
||||
|
||||
curSection->src = &where;
|
||||
curSection->lineNo = lineNo;
|
||||
|
||||
getToken(line.data(), "'A' line is too short");
|
||||
assume(strlen(token) != 0); // This should be impossible, tokens are non-empty
|
||||
// The following is required for fragment offsets to be reliably predicted
|
||||
@@ -341,17 +346,18 @@ void sdobj_ReadFile(FileStackNode const &where, FILE *file, std::vector<Symbol>
|
||||
}
|
||||
|
||||
case 'S': {
|
||||
if (fileSymbols.size() == expectedNbSymbols)
|
||||
warning(
|
||||
if (fileSymbols.size() == expectedNbSymbols) {
|
||||
error(
|
||||
&where,
|
||||
lineNo,
|
||||
"Got more 'S' lines than the expected %" PRIu32,
|
||||
expectedNbSymbols
|
||||
);
|
||||
break; // Refuse processing the line further.
|
||||
}
|
||||
Symbol &symbol = fileSymbols.emplace_back();
|
||||
|
||||
// Init other members
|
||||
symbol.objFileName = where.name().c_str();
|
||||
symbol.src = &where;
|
||||
symbol.lineNo = lineNo;
|
||||
|
||||
@@ -378,6 +384,7 @@ void sdobj_ReadFile(FileStackNode const &where, FILE *file, std::vector<Symbol>
|
||||
// Expected format: /[DR]ef[0-9A-F]+/i
|
||||
if (token[0] == 'R' || token[0] == 'r') {
|
||||
symbol.type = SYMTYPE_IMPORT;
|
||||
sym_AddSymbol(symbol);
|
||||
// TODO: hard error if the rest is not zero
|
||||
} else if (token[0] != 'D' && token[0] != 'd') {
|
||||
fatal(&where, lineNo, "'S' line is neither \"Def\" nor \"Ref\"");
|
||||
@@ -390,10 +397,11 @@ void sdobj_ReadFile(FileStackNode const &where, FILE *file, std::vector<Symbol>
|
||||
// The same symbol can only be defined twice if neither
|
||||
// definition is in a floating section
|
||||
auto checkSymbol = [](Symbol const &sym) -> std::tuple<Section *, int32_t> {
|
||||
if (auto *label = std::get_if<Label>(&sym.data); label)
|
||||
return {label->section, label->offset};
|
||||
assume(std::holds_alternative<int32_t>(sym.data));
|
||||
return {nullptr, std::get<int32_t>(sym.data)};
|
||||
if (sym.data.holds<Label>()) {
|
||||
Label const &label = sym.data.get<Label>();
|
||||
return {label.section, label.offset};
|
||||
}
|
||||
return {nullptr, sym.data.get<int32_t>()};
|
||||
};
|
||||
auto [symbolSection, symbolValue] = checkSymbol(symbol);
|
||||
auto [otherSection, otherValue] = checkSymbol(*other);
|
||||
@@ -402,16 +410,17 @@ void sdobj_ReadFile(FileStackNode const &where, FILE *file, std::vector<Symbol>
|
||||
|| (symbolSection && !symbolSection->isAddressFixed)) {
|
||||
sym_AddSymbol(symbol); // This will error out
|
||||
} else if (otherValue != symbolValue) {
|
||||
error(
|
||||
&where,
|
||||
lineNo,
|
||||
"Definition of \"%s\" conflicts with definition in %s (%" PRId32
|
||||
" != %" PRId32 ")",
|
||||
fprintf(
|
||||
stderr,
|
||||
"error: \"%s\" is defined as %" PRId32 " at ",
|
||||
symbol.name.c_str(),
|
||||
other->objFileName,
|
||||
symbolValue,
|
||||
otherValue
|
||||
symbolValue
|
||||
);
|
||||
symbol.src->dump(symbol.lineNo);
|
||||
fprintf(stderr, ", but as %" PRId32 " at ", otherValue);
|
||||
other->src->dump(other->lineNo);
|
||||
putc('\n', stderr);
|
||||
exit(1);
|
||||
}
|
||||
} else {
|
||||
// Add a new definition
|
||||
@@ -823,7 +832,37 @@ void sdobj_ReadFile(FileStackNode const &where, FILE *file, std::vector<Symbol>
|
||||
std::unique_ptr<Section> §ion = entry.section;
|
||||
|
||||
// RAM sections can have a size, but don't get any data (they shouldn't have any)
|
||||
if (entry.writeIndex != section->size && entry.writeIndex != 0)
|
||||
if (section->type != SECTTYPE_INVALID) {
|
||||
auto const &typeInfo = sectionTypeInfo[section->type];
|
||||
// Otherwise, how would the type already be known at this point?
|
||||
assume(section->isAddressFixed);
|
||||
|
||||
if (!sect_HasData(section->type)) {
|
||||
if (!section->data.empty()) {
|
||||
fatal(
|
||||
&where,
|
||||
lineNo,
|
||||
"\"%s\" is implicitly defined as a %s section (being at address $%04" PRIx16
|
||||
"), but it has data! (Was a bad `__at()` value used?)",
|
||||
section->name.c_str(),
|
||||
typeInfo.name.c_str(),
|
||||
section->org
|
||||
);
|
||||
}
|
||||
} else if (section->size != 0 && section->data.empty()) {
|
||||
fatal(
|
||||
&where,
|
||||
lineNo,
|
||||
"\"%s\" is implicitly defined as a %s section (being at address $%04" PRIx16
|
||||
"), but it doesn't have any data! (Was a bad `__at()` value used?)",
|
||||
section->name.c_str(),
|
||||
typeInfo.name.c_str(),
|
||||
section->org
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (entry.writeIndex != 0 && entry.writeIndex != section->size) {
|
||||
fatal(
|
||||
&where,
|
||||
lineNo,
|
||||
@@ -832,14 +871,26 @@ void sdobj_ReadFile(FileStackNode const &where, FILE *file, std::vector<Symbol>
|
||||
entry.writeIndex,
|
||||
section->size
|
||||
);
|
||||
|
||||
if (section->modifier == SECTION_FRAGMENT) {
|
||||
// Add the fragment's offset to all of its symbols
|
||||
for (Symbol *symbol : section->symbols)
|
||||
symbol->label().offset += section->offset;
|
||||
}
|
||||
|
||||
// Calling `sect_AddSection` invalidates the contents of `fileSections`!
|
||||
sect_AddSection(std::move(section));
|
||||
}
|
||||
|
||||
// Fix symbols' section pointers to component sections
|
||||
// This has to run **after** all the `sect_AddSection()` calls,
|
||||
// so that `sect_GetSection()` will work
|
||||
for (Symbol &sym : fileSymbols) {
|
||||
if (sym.data.holds<Label>()) {
|
||||
Label &label = sym.data.get<Label>();
|
||||
if (Section *section = label.section; section->modifier != SECTION_NORMAL) {
|
||||
if (section->modifier == SECTION_FRAGMENT) {
|
||||
// Add the fragment's offset to the symbol's
|
||||
// (`section->offset` is computed by `sect_AddSection`)
|
||||
label.offset += section->offset;
|
||||
}
|
||||
// Associate the symbol with the main section, not the "component" one
|
||||
label.section = sect_GetSection(section->name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,54 +18,91 @@ void sect_ForEach(void (*callback)(Section &)) {
|
||||
callback(*it->get());
|
||||
}
|
||||
|
||||
static void checkSectUnionCompat(Section &target, Section &other) {
|
||||
if (other.isAddressFixed) {
|
||||
if (target.isAddressFixed) {
|
||||
if (target.org != other.org)
|
||||
errx(
|
||||
"Section \"%s\" is defined with conflicting addresses $%04" PRIx16
|
||||
" and $%04" PRIx16,
|
||||
other.name.c_str(),
|
||||
target.org,
|
||||
other.org
|
||||
);
|
||||
} else if (target.isAlignFixed) {
|
||||
if ((other.org - target.alignOfs) & target.alignMask)
|
||||
errx(
|
||||
"Section \"%s\" is defined with conflicting %d-byte alignment (offset %" PRIu16
|
||||
") and address $%04" PRIx16,
|
||||
other.name.c_str(),
|
||||
target.alignMask + 1,
|
||||
target.alignOfs,
|
||||
other.org
|
||||
);
|
||||
static void checkAgainstFixedAddress(Section const &target, Section const &other, uint16_t org) {
|
||||
if (target.isAddressFixed) {
|
||||
if (target.org != org) {
|
||||
fprintf(
|
||||
stderr,
|
||||
"error: Section \"%s\" is defined with address $%04" PRIx16 " at ",
|
||||
target.name.c_str(),
|
||||
target.org
|
||||
);
|
||||
target.src->dump(target.lineNo);
|
||||
fprintf(stderr, ", but with address $%04" PRIx16 " at ", other.org);
|
||||
other.src->dump(other.lineNo);
|
||||
putc('\n', stderr);
|
||||
exit(1);
|
||||
}
|
||||
target.isAddressFixed = true;
|
||||
target.org = other.org;
|
||||
|
||||
} else if (other.isAlignFixed) {
|
||||
if (target.isAddressFixed) {
|
||||
if ((target.org - other.alignOfs) & other.alignMask)
|
||||
errx(
|
||||
"Section \"%s\" is defined with conflicting address $%04" PRIx16
|
||||
" and %d-byte alignment (offset %" PRIu16 ")",
|
||||
other.name.c_str(),
|
||||
target.org,
|
||||
other.alignMask + 1,
|
||||
other.alignOfs
|
||||
);
|
||||
} else if (target.isAlignFixed
|
||||
&& (other.alignMask & target.alignOfs) != (target.alignMask & other.alignOfs)) {
|
||||
errx(
|
||||
"Section \"%s\" is defined with conflicting %d-byte alignment (offset %" PRIu16
|
||||
") and %d-byte alignment (offset %" PRIu16 ")",
|
||||
other.name.c_str(),
|
||||
} else if (target.isAlignFixed) {
|
||||
if ((org - target.alignOfs) & target.alignMask) {
|
||||
fprintf(
|
||||
stderr,
|
||||
"error: Section \"%s\" is defined with %d-byte alignment (offset %" PRIu16 ") at ",
|
||||
target.name.c_str(),
|
||||
target.alignMask + 1,
|
||||
target.alignOfs,
|
||||
target.alignOfs
|
||||
);
|
||||
target.src->dump(target.lineNo);
|
||||
fprintf(stderr, ", but with address $%04" PRIx16 " at ", other.org);
|
||||
other.src->dump(other.lineNo);
|
||||
putc('\n', stderr);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool checkAgainstFixedAlign(Section const &target, Section const &other, int32_t ofs) {
|
||||
if (target.isAddressFixed) {
|
||||
if ((target.org - ofs) & other.alignMask) {
|
||||
fprintf(
|
||||
stderr,
|
||||
"error: Section \"%s\" is defined with address $%04" PRIx16 " at ",
|
||||
target.name.c_str(),
|
||||
target.org
|
||||
);
|
||||
target.src->dump(target.lineNo);
|
||||
fprintf(
|
||||
stderr,
|
||||
", but with %d-byte alignment (offset %" PRIu16 ") at ",
|
||||
other.alignMask + 1,
|
||||
other.alignOfs
|
||||
);
|
||||
} else if (!target.isAlignFixed || (other.alignMask > target.alignMask)) {
|
||||
other.src->dump(other.lineNo);
|
||||
putc('\n', stderr);
|
||||
exit(1);
|
||||
}
|
||||
return false;
|
||||
} else if (target.isAlignFixed
|
||||
&& (other.alignMask & target.alignOfs) != (target.alignMask & ofs)) {
|
||||
fprintf(
|
||||
stderr,
|
||||
"error: Section \"%s\" is defined with %d-byte alignment (offset %" PRIu16 ") at ",
|
||||
target.name.c_str(),
|
||||
target.alignMask + 1,
|
||||
target.alignOfs
|
||||
);
|
||||
target.src->dump(target.lineNo);
|
||||
fprintf(
|
||||
stderr,
|
||||
", but with %d-byte alignment (offset %" PRIu16 ") at ",
|
||||
other.alignMask + 1,
|
||||
other.alignOfs
|
||||
);
|
||||
other.src->dump(other.lineNo);
|
||||
putc('\n', stderr);
|
||||
exit(1);
|
||||
} else {
|
||||
return !target.isAlignFixed || (other.alignMask > target.alignMask);
|
||||
}
|
||||
}
|
||||
|
||||
static void checkSectUnionCompat(Section &target, Section &other) {
|
||||
if (other.isAddressFixed) {
|
||||
checkAgainstFixedAddress(target, other, other.org);
|
||||
target.isAddressFixed = true;
|
||||
target.org = other.org;
|
||||
} else if (other.isAlignFixed) {
|
||||
if (checkAgainstFixedAlign(target, other, other.alignOfs)) {
|
||||
target.isAlignFixed = true;
|
||||
target.alignMask = other.alignMask;
|
||||
}
|
||||
@@ -75,60 +112,14 @@ static void checkSectUnionCompat(Section &target, Section &other) {
|
||||
static void checkFragmentCompat(Section &target, Section &other) {
|
||||
if (other.isAddressFixed) {
|
||||
uint16_t org = other.org - target.size;
|
||||
|
||||
if (target.isAddressFixed) {
|
||||
if (target.org != org)
|
||||
errx(
|
||||
"Section \"%s\" is defined with conflicting addresses $%04" PRIx16
|
||||
" and $%04" PRIx16,
|
||||
other.name.c_str(),
|
||||
target.org,
|
||||
other.org
|
||||
);
|
||||
|
||||
} else if (target.isAlignFixed) {
|
||||
if ((org - target.alignOfs) & target.alignMask)
|
||||
errx(
|
||||
"Section \"%s\" is defined with conflicting %d-byte alignment (offset %" PRIu16
|
||||
") and address $%04" PRIx16,
|
||||
other.name.c_str(),
|
||||
target.alignMask + 1,
|
||||
target.alignOfs,
|
||||
other.org
|
||||
);
|
||||
}
|
||||
checkAgainstFixedAddress(target, other, org);
|
||||
target.isAddressFixed = true;
|
||||
target.org = org;
|
||||
|
||||
} else if (other.isAlignFixed) {
|
||||
int32_t ofs = (other.alignOfs - target.size) % (other.alignMask + 1);
|
||||
|
||||
if (ofs < 0)
|
||||
ofs += other.alignMask + 1;
|
||||
|
||||
if (target.isAddressFixed) {
|
||||
if ((target.org - ofs) & other.alignMask)
|
||||
errx(
|
||||
"Section \"%s\" is defined with conflicting address $%04" PRIx16
|
||||
" and %d-byte alignment (offset %" PRIu16 ")",
|
||||
other.name.c_str(),
|
||||
target.org,
|
||||
other.alignMask + 1,
|
||||
other.alignOfs
|
||||
);
|
||||
|
||||
} else if (target.isAlignFixed && (other.alignMask & target.alignOfs) != (target.alignMask & ofs)) {
|
||||
errx(
|
||||
"Section \"%s\" is defined with conflicting %d-byte alignment (offset %" PRIu16
|
||||
") and %d-byte alignment (offset %" PRIu16 ")",
|
||||
other.name.c_str(),
|
||||
target.alignMask + 1,
|
||||
target.alignOfs,
|
||||
other.alignMask + 1,
|
||||
other.alignOfs
|
||||
);
|
||||
|
||||
} else if (!target.isAlignFixed || (other.alignMask > target.alignMask)) {
|
||||
if (checkAgainstFixedAlign(target, other, ofs)) {
|
||||
target.isAlignFixed = true;
|
||||
target.alignMask = other.alignMask;
|
||||
target.alignOfs = ofs;
|
||||
@@ -139,25 +130,36 @@ static void checkFragmentCompat(Section &target, Section &other) {
|
||||
static void mergeSections(Section &target, std::unique_ptr<Section> &&other, SectionModifier mod) {
|
||||
// Common checks
|
||||
|
||||
if (target.type != other->type)
|
||||
errx(
|
||||
"Section \"%s\" is defined with conflicting types %s and %s",
|
||||
other->name.c_str(),
|
||||
sectionTypeInfo[target.type].name.c_str(),
|
||||
sectionTypeInfo[other->type].name.c_str()
|
||||
if (target.type != other->type) {
|
||||
fprintf(
|
||||
stderr,
|
||||
"error: Section \"%s\" is defined with type %s at ",
|
||||
target.name.c_str(),
|
||||
sectionTypeInfo[target.type].name.c_str()
|
||||
);
|
||||
target.src->dump(target.lineNo);
|
||||
fprintf(stderr, ", but with type %s at ", sectionTypeInfo[other->type].name.c_str());
|
||||
other->src->dump(other->lineNo);
|
||||
putc('\n', stderr);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (other->isBankFixed) {
|
||||
if (!target.isBankFixed) {
|
||||
target.isBankFixed = true;
|
||||
target.bank = other->bank;
|
||||
} else if (target.bank != other->bank) {
|
||||
errx(
|
||||
"Section \"%s\" is defined with conflicting banks %" PRIu32 " and %" PRIu32,
|
||||
other->name.c_str(),
|
||||
target.bank,
|
||||
other->bank
|
||||
fprintf(
|
||||
stderr,
|
||||
"error: Section \"%s\" is defined with bank %" PRIu32 " at ",
|
||||
target.name.c_str(),
|
||||
target.bank
|
||||
);
|
||||
target.src->dump(target.lineNo);
|
||||
fprintf(stderr, ", but with bank %" PRIu32 " at ", other->bank);
|
||||
other->src->dump(other->lineNo);
|
||||
putc('\n', stderr);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,17 +199,28 @@ static void mergeSections(Section &target, std::unique_ptr<Section> &&other, Sec
|
||||
void sect_AddSection(std::unique_ptr<Section> &§ion) {
|
||||
// Check if the section already exists
|
||||
if (Section *other = sect_GetSection(section->name); other) {
|
||||
if (section->modifier != other->modifier)
|
||||
errx(
|
||||
"Section \"%s\" defined as %s and %s",
|
||||
if (section->modifier != other->modifier) {
|
||||
fprintf(
|
||||
stderr,
|
||||
"error: Section \"%s\" is defined as %s at ",
|
||||
section->name.c_str(),
|
||||
sectionModNames[section->modifier],
|
||||
sectionModNames[other->modifier]
|
||||
sectionModNames[section->modifier]
|
||||
);
|
||||
else if (section->modifier == SECTION_NORMAL)
|
||||
errx("Section name \"%s\" is already in use", section->name.c_str());
|
||||
else
|
||||
section->src->dump(section->lineNo);
|
||||
fprintf(stderr, ", but as %s at ", sectionModNames[other->modifier]);
|
||||
other->src->dump(other->lineNo);
|
||||
putc('\n', stderr);
|
||||
exit(1);
|
||||
} else if (section->modifier == SECTION_NORMAL) {
|
||||
fprintf(stderr, "error: Section \"%s\" is defined at ", section->name.c_str());
|
||||
section->src->dump(section->lineNo);
|
||||
fputs(", but also at ", stderr);
|
||||
other->src->dump(other->lineNo);
|
||||
putc('\n', stderr);
|
||||
exit(1);
|
||||
} else {
|
||||
mergeSections(*other, std::move(section), section->modifier);
|
||||
}
|
||||
} else if (section->modifier == SECTION_UNION && sect_HasData(section->type)) {
|
||||
errx(
|
||||
"Section \"%s\" is of type %s, which cannot be unionized",
|
||||
@@ -229,7 +242,14 @@ Section *sect_GetSection(std::string const &name) {
|
||||
static void doSanityChecks(Section §ion) {
|
||||
// Sanity check the section's type
|
||||
if (section.type < 0 || section.type >= SECTTYPE_INVALID) {
|
||||
error(nullptr, 0, "Section \"%s\" has an invalid type", section.name.c_str());
|
||||
// This is trapped early in RGBDS objects (because then the format is not parseable),
|
||||
// which leaves SDAS objects.
|
||||
error(
|
||||
nullptr,
|
||||
0,
|
||||
"Section \"%s\" has not been assigned a type by a linker script",
|
||||
section.name.c_str()
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
|
||||
#include "link/symbol.hpp"
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdlib.h>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "helpers.hpp" // assume
|
||||
|
||||
@@ -11,29 +13,42 @@
|
||||
#include "link/section.hpp"
|
||||
|
||||
std::unordered_map<std::string, Symbol *> symbols;
|
||||
std::unordered_map<std::string, std::vector<Symbol *>> localSymbols;
|
||||
|
||||
Label &Symbol::label() {
|
||||
assume(std::holds_alternative<Label>(data));
|
||||
return std::get<Label>(data);
|
||||
}
|
||||
|
||||
Label const &Symbol::label() const {
|
||||
assume(std::holds_alternative<Label>(data));
|
||||
return std::get<Label>(data);
|
||||
void sym_ForEach(void (*callback)(Symbol &)) {
|
||||
for (auto &it : symbols)
|
||||
callback(*it.second);
|
||||
}
|
||||
|
||||
void sym_AddSymbol(Symbol &symbol) {
|
||||
if (symbol.type != SYMTYPE_EXPORT) {
|
||||
if (symbol.type != SYMTYPE_IMPORT)
|
||||
localSymbols[symbol.name].push_back(&symbol);
|
||||
return;
|
||||
}
|
||||
|
||||
Symbol *other = sym_GetSymbol(symbol.name);
|
||||
auto *symValue = std::get_if<int32_t>(&symbol.data);
|
||||
auto *otherValue = other ? std::get_if<int32_t>(&other->data) : nullptr;
|
||||
int32_t *symValue = symbol.data.holds<int32_t>() ? &symbol.data.get<int32_t>() : nullptr;
|
||||
int32_t *otherValue =
|
||||
other && other->data.holds<int32_t>() ? &other->data.get<int32_t>() : nullptr;
|
||||
|
||||
// Check if the symbol already exists with a different value
|
||||
if (other && !(symValue && otherValue && *symValue == *otherValue)) {
|
||||
fprintf(stderr, "error: \"%s\" both in %s from ", symbol.name.c_str(), symbol.objFileName);
|
||||
fprintf(stderr, "error: \"%s\" is defined as ", symbol.name.c_str());
|
||||
if (symValue)
|
||||
fprintf(stderr, "%" PRId32, *symValue);
|
||||
else
|
||||
fputs("a label", stderr);
|
||||
fputs(" at ", stderr);
|
||||
symbol.src->dump(symbol.lineNo);
|
||||
fprintf(stderr, " and in %s from ", other->objFileName);
|
||||
fputs(", but as ", stderr);
|
||||
if (otherValue)
|
||||
fprintf(stderr, "%" PRId32, *otherValue);
|
||||
else
|
||||
fputs("another label", stderr);
|
||||
fputs(" at ", stderr);
|
||||
other->src->dump(other->lineNo);
|
||||
fputc('\n', stderr);
|
||||
putc('\n', stderr);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
@@ -45,3 +60,30 @@ Symbol *sym_GetSymbol(std::string const &name) {
|
||||
auto search = symbols.find(name);
|
||||
return search != symbols.end() ? search->second : nullptr;
|
||||
}
|
||||
|
||||
void sym_DumpLocalAliasedSymbols(std::string const &name) {
|
||||
std::vector<Symbol *> const &locals = localSymbols[name];
|
||||
int count = 0;
|
||||
for (Symbol *local : locals) {
|
||||
if (count++ == 3) {
|
||||
size_t remaining = locals.size() - 3;
|
||||
bool plural = remaining != 1;
|
||||
fprintf(
|
||||
stderr,
|
||||
" ...and %zu more symbol%s with that name %s defined but not exported\n",
|
||||
remaining,
|
||||
plural ? "s" : "",
|
||||
plural ? "are" : "is"
|
||||
);
|
||||
break;
|
||||
}
|
||||
fprintf(
|
||||
stderr,
|
||||
" A %s with that name is defined but not exported at ",
|
||||
local->data.holds<Label>() ? "label" : "constant"
|
||||
);
|
||||
assume(local->src);
|
||||
local->src->dump(local->lineNo);
|
||||
putc('\n', stderr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,13 +53,12 @@ char const *printChar(int c) {
|
||||
return buf;
|
||||
}
|
||||
|
||||
size_t readUTF8Char(std::vector<uint8_t> *dest, char const *src) {
|
||||
uint32_t state = 0;
|
||||
uint32_t codep;
|
||||
size_t readUTF8Char(std::vector<int32_t> *dest, char const *src) {
|
||||
uint32_t state = 0, codepoint;
|
||||
size_t i = 0;
|
||||
|
||||
for (;;) {
|
||||
if (decode(&state, &codep, src[i]) == 1)
|
||||
if (decode(&state, &codepoint, src[i]) == 1)
|
||||
return 0;
|
||||
|
||||
if (dest)
|
||||
|
||||
@@ -2,18 +2,12 @@
|
||||
|
||||
add_executable(randtilegen gfx/randtilegen.cpp)
|
||||
add_executable(rgbgfx_test gfx/rgbgfx_test.cpp)
|
||||
add_executable(unmangle link/unmangle.cpp)
|
||||
|
||||
install(TARGETS randtilegen rgbgfx_test
|
||||
DESTINATION ${rgbds_SOURCE_DIR}/test/gfx
|
||||
COMPONENT "Test support programs"
|
||||
EXCLUDE_FROM_ALL
|
||||
)
|
||||
install(TARGETS unmangle
|
||||
DESTINATION ${rgbds_SOURCE_DIR}/test/link
|
||||
COMPONENT "Test support programs"
|
||||
EXCLUDE_FROM_ALL
|
||||
)
|
||||
|
||||
foreach(TARGET randtilegen rgbgfx_test)
|
||||
if(LIBPNG_FOUND) # pkg-config
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
|
||||
SECTION "You lost the game", ROM0[17]
|
||||
SECTION "You lost the game", ROM0, ALIGN[17, 99]
|
||||
ALIGN 17, 99
|
||||
|
||||
5
test/asm/align-large.err
Normal file
5
test/asm/align-large.err
Normal file
@@ -0,0 +1,5 @@
|
||||
error: align-large.asm(1):
|
||||
Alignment must be between 0 and 16, not 17
|
||||
error: align-large.asm(2):
|
||||
Alignment must be between 0 and 16, not 17
|
||||
error: Assembly aborted (2 errors)!
|
||||
@@ -3,7 +3,7 @@ error: anon-label-bad.asm(2):
|
||||
error: anon-label-bad.asm(6):
|
||||
Reference to anonymous label 2 before, when only 1 has been created so far
|
||||
error: anon-label-bad.asm(9):
|
||||
syntax error, unexpected anonymous label, expecting label or identifier or local identifier
|
||||
syntax error, unexpected anonymous label
|
||||
error: anon-label-bad.asm(10):
|
||||
syntax error, unexpected anonymous label, expecting label or identifier or local identifier
|
||||
error: anon-label-bad.asm(22):
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
error: assert-nosect-bank.asm(1):
|
||||
PC has no bank outside a section
|
||||
PC has no bank outside of a section
|
||||
error: Assembly aborted (1 error)!
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
error: assert@-no-sect.asm(1):
|
||||
PC has no value outside a section
|
||||
PC has no value outside of a section
|
||||
error: Assembly aborted (1 error)!
|
||||
|
||||
11
test/asm/bit-functions.asm
Normal file
11
test/asm/bit-functions.asm
Normal file
@@ -0,0 +1,11 @@
|
||||
assert BITWIDTH(0) == 0
|
||||
assert BITWIDTH(42) == 6
|
||||
assert BITWIDTH(-1) == 32
|
||||
assert BITWIDTH($80000000) == 32
|
||||
|
||||
assert TZCOUNT(0) == 32
|
||||
assert TZCOUNT(42) == 1
|
||||
assert TZCOUNT(-1) == 0
|
||||
assert TZCOUNT($80000000) == 31
|
||||
|
||||
assert TZCOUNT(1.0) == 16
|
||||
@@ -3,5 +3,5 @@ error: bracketed-symbols.asm(16):
|
||||
error: bracketed-symbols.asm(20):
|
||||
"Label" does not have a constant value
|
||||
error: bracketed-symbols.asm(21):
|
||||
Expected constant PC but section is not fixed
|
||||
PC does not have a constant value; the current section is not fixed
|
||||
error: Assembly aborted (3 errors)!
|
||||
|
||||
33
test/asm/builtin-reserved.asm
Normal file
33
test/asm/builtin-reserved.asm
Normal file
@@ -0,0 +1,33 @@
|
||||
ASSERT !DEF(_NARG)
|
||||
|
||||
PURGE _NARG
|
||||
|
||||
DEF _NARG EQU 12
|
||||
REDEF _NARG EQU 34
|
||||
|
||||
DEF _NARG = 56
|
||||
REDEF _NARG = 78
|
||||
|
||||
DEF _NARG EQUS "hello"
|
||||
REDEF _NARG EQUS "world"
|
||||
|
||||
SECTION "_NARG", ROM0
|
||||
_NARG:
|
||||
ENDSECTION
|
||||
|
||||
ASSERT !DEF(.)
|
||||
|
||||
PURGE .
|
||||
|
||||
DEF . EQU 12
|
||||
REDEF . EQU 34
|
||||
|
||||
DEF . = 56
|
||||
REDEF . = 78
|
||||
|
||||
DEF . EQUS "hello"
|
||||
REDEF . EQUS "world"
|
||||
|
||||
SECTION ".", ROM0
|
||||
.:
|
||||
ENDSECTION
|
||||
33
test/asm/builtin-reserved.err
Normal file
33
test/asm/builtin-reserved.err
Normal file
@@ -0,0 +1,33 @@
|
||||
error: builtin-reserved.asm(3):
|
||||
'_NARG' not defined
|
||||
error: builtin-reserved.asm(5):
|
||||
'_NARG' is reserved for a built-in symbol
|
||||
error: builtin-reserved.asm(6):
|
||||
'_NARG' is reserved for a built-in symbol
|
||||
error: builtin-reserved.asm(8):
|
||||
'_NARG' is reserved for a built-in symbol
|
||||
error: builtin-reserved.asm(9):
|
||||
'_NARG' is reserved for a built-in symbol
|
||||
error: builtin-reserved.asm(11):
|
||||
'_NARG' is reserved for a built-in symbol
|
||||
error: builtin-reserved.asm(12):
|
||||
'_NARG' is reserved for a built-in symbol
|
||||
error: builtin-reserved.asm(15):
|
||||
'_NARG' is reserved for a built-in symbol
|
||||
error: builtin-reserved.asm(20):
|
||||
'.' not defined
|
||||
error: builtin-reserved.asm(22):
|
||||
'.' is reserved for a built-in symbol
|
||||
error: builtin-reserved.asm(23):
|
||||
'.' is reserved for a built-in symbol
|
||||
error: builtin-reserved.asm(25):
|
||||
'.' is reserved for a built-in symbol
|
||||
error: builtin-reserved.asm(26):
|
||||
'.' is reserved for a built-in symbol
|
||||
error: builtin-reserved.asm(28):
|
||||
'.' is reserved for a built-in symbol
|
||||
error: builtin-reserved.asm(29):
|
||||
'.' is reserved for a built-in symbol
|
||||
error: builtin-reserved.asm(32):
|
||||
"." has no value outside of a label scope
|
||||
error: Assembly aborted (16 errors)!
|
||||
2
test/asm/charmap-empty.asm
Normal file
2
test/asm/charmap-empty.asm
Normal file
@@ -0,0 +1,2 @@
|
||||
charmap "", 1
|
||||
charmap "nonempty", ; nothing
|
||||
5
test/asm/charmap-empty.err
Normal file
5
test/asm/charmap-empty.err
Normal file
@@ -0,0 +1,5 @@
|
||||
error: charmap-empty.asm(1):
|
||||
Cannot map an empty string
|
||||
error: charmap-empty.asm(2):
|
||||
syntax error, unexpected newline
|
||||
error: Assembly aborted (2 errors)!
|
||||
@@ -1,5 +1,5 @@
|
||||
error: const-and.asm(2):
|
||||
PC has no value outside a section
|
||||
PC has no value outside of a section
|
||||
error: const-and.asm(11):
|
||||
Expected constant expression: 'Aligned' is not constant at assembly time
|
||||
error: const-and.asm(17):
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
$0
|
||||
$A
|
||||
$A
|
||||
$2
|
||||
$0
|
||||
$A
|
||||
$0
|
||||
|
||||
3
test/asm/const-not.asm
Normal file
3
test/asm/const-not.asm
Normal file
@@ -0,0 +1,3 @@
|
||||
section "test", rom0, align[8]
|
||||
ds 42
|
||||
assert !@
|
||||
3
test/asm/const-not.err
Normal file
3
test/asm/const-not.err
Normal file
@@ -0,0 +1,3 @@
|
||||
error: const-not.asm(3):
|
||||
Assertion failed
|
||||
error: Assembly aborted (1 error)!
|
||||
6
test/asm/const-unknown.asm
Normal file
6
test/asm/const-unknown.asm
Normal file
@@ -0,0 +1,6 @@
|
||||
SECTION "test", ROMX
|
||||
; Foo is unknown so none of these should warn
|
||||
assert warn, Foo & $8000
|
||||
assert warn, !Foo
|
||||
assert warn, LOW(Foo)
|
||||
assert warn, !(Foo & LOW(Foo))
|
||||
@@ -1,6 +1,6 @@
|
||||
warning: db-dw-dl-string.asm(15): [-Wnumeric-string]
|
||||
Treating 4-character string as a number
|
||||
warning: db-dw-dl-string.asm(16): [-Wnumeric-string]
|
||||
Treating 4-character string as a number
|
||||
warning: db-dw-dl-string.asm(17): [-Wnumeric-string]
|
||||
Treating 4-character string as a number
|
||||
warning: db-dw-dl-string.asm(15): [-Wobsolete]
|
||||
Treating multi-unit strings as numbers is deprecated
|
||||
warning: db-dw-dl-string.asm(16): [-Wobsolete]
|
||||
Treating multi-unit strings as numbers is deprecated
|
||||
warning: db-dw-dl-string.asm(17): [-Wobsolete]
|
||||
Treating multi-unit strings as numbers is deprecated
|
||||
|
||||
21
test/asm/destination-a.asm
Normal file
21
test/asm/destination-a.asm
Normal file
@@ -0,0 +1,21 @@
|
||||
SECTION "destination optional", ROM0[0]
|
||||
|
||||
MACRO both
|
||||
REDEF op EQUS "\1"
|
||||
SHIFT
|
||||
{op} \#
|
||||
if _NARG
|
||||
{op} a, \#
|
||||
else
|
||||
{op} a
|
||||
endc
|
||||
ENDM
|
||||
|
||||
both cpl
|
||||
both add, b
|
||||
both adc, 42
|
||||
both sub, 69
|
||||
both sbc, c
|
||||
both and, d
|
||||
both or, %1010
|
||||
both xor, $80
|
||||
3
test/asm/destination-a.out.bin
Normal file
3
test/asm/destination-a.out.bin
Normal file
@@ -0,0 +1,3 @@
|
||||
//<2F><><EFBFBD>*<2A>*<2A>E<EFBFBD>E<EFBFBD><45><EFBFBD><EFBFBD><EFBFBD>
|
||||
<EFBFBD>
|
||||
<EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
3
test/asm/double-purge.asm
Normal file
3
test/asm/double-purge.asm
Normal file
@@ -0,0 +1,3 @@
|
||||
def n equ 42
|
||||
purge n
|
||||
purge n
|
||||
3
test/asm/double-purge.err
Normal file
3
test/asm/double-purge.err
Normal file
@@ -0,0 +1,3 @@
|
||||
error: double-purge.asm(3):
|
||||
'n' was already purged
|
||||
error: Assembly aborted (1 error)!
|
||||
3
test/asm/empty-local-purged.asm
Normal file
3
test/asm/empty-local-purged.asm
Normal file
@@ -0,0 +1,3 @@
|
||||
SECTION "Test", ROM0
|
||||
|
||||
PURGE .test
|
||||
2
test/asm/empty-local-purged.err
Normal file
2
test/asm/empty-local-purged.err
Normal file
@@ -0,0 +1,2 @@
|
||||
FATAL: empty-local-purged.asm(3):
|
||||
Unqualified local label '.test' in main scope
|
||||
3
test/asm/empty-local-referenced.asm
Normal file
3
test/asm/empty-local-referenced.asm
Normal file
@@ -0,0 +1,3 @@
|
||||
SECTION "Test", ROM0
|
||||
|
||||
dw Referenced.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user