mirror of
https://github.com/gbdev/rgbds.git
synced 2025-11-21 02:32:06 +00:00
Compare commits
193 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d1829ed923 | ||
|
|
6d53bc4121 | ||
|
|
32b5ef5095 | ||
|
|
a36f2b3b7d | ||
|
|
cd4be6aa07 | ||
|
|
499edaecd0 | ||
|
|
225490163e | ||
|
|
d388a60daa | ||
|
|
6d05de9d4d | ||
|
|
5d6e571338 | ||
|
|
308d488833 | ||
|
|
cecbf0aa0e | ||
|
|
d21e6669ce | ||
|
|
2341d1ee50 | ||
|
|
53949761a7 | ||
|
|
f7eb986313 | ||
|
|
75aed1afd5 | ||
|
|
d16751f56a | ||
|
|
8b1a5244f7 | ||
|
|
b2747dfbd8 | ||
|
|
16e16cdf51 | ||
|
|
a353637a90 | ||
|
|
f3cbfcecf4 | ||
|
|
3bc8b1ff7c | ||
|
|
aa46c79db6 | ||
|
|
92acb6e547 | ||
|
|
ac632d9223 | ||
|
|
0df5b7b86d | ||
|
|
87c10988ed | ||
|
|
d6a28a6259 | ||
|
|
c6d0e8de63 | ||
|
|
ded4ef4072 | ||
|
|
1849a35e61 | ||
|
|
18e35053fa | ||
|
|
7e151f16c3 | ||
|
|
2ce4cdbff6 | ||
|
|
eea532ded1 | ||
|
|
c83b87e0a0 | ||
|
|
8d268e8a8a | ||
|
|
ee0f311c10 | ||
|
|
61730be6ce | ||
|
|
5f333d9753 | ||
|
|
d1493a9f96 | ||
|
|
d652212857 | ||
|
|
0cd60ea1e6 | ||
|
|
a0e23ee911 | ||
|
|
ad81c74cda | ||
|
|
9ef32e405c | ||
|
|
89ca6a325c | ||
|
|
9e0e7ef9a1 | ||
|
|
2dc948fefb | ||
|
|
e3a5290dad | ||
|
|
cfe1f60e47 | ||
|
|
0eed237517 | ||
|
|
68ffb01cac | ||
|
|
169ac61e14 | ||
|
|
0681110647 | ||
|
|
8d1b111692 | ||
|
|
2935942667 | ||
|
|
9a4593e823 | ||
|
|
250e08043b | ||
|
|
14f5e16ae8 | ||
|
|
bf69043a1d | ||
|
|
7086b8aeff | ||
|
|
53c39d01d4 | ||
|
|
4a2f9fc744 | ||
|
|
e7d63f5f6b | ||
|
|
b80b30fba1 | ||
|
|
8e84850679 | ||
|
|
e31256c0d4 | ||
|
|
9a9fd6603c | ||
|
|
e99ff5ac45 | ||
|
|
60cec85638 | ||
|
|
39f2ed1339 | ||
|
|
4c8724899b | ||
|
|
0c96234532 | ||
|
|
9dddd87893 | ||
|
|
5eb093f13e | ||
|
|
529989bde5 | ||
|
|
776e37980b | ||
|
|
7f24d46d44 | ||
|
|
cf6e5fec63 | ||
|
|
d8fc25ee43 | ||
|
|
a0eccceb01 | ||
|
|
2720224890 | ||
|
|
8bebab1db0 | ||
|
|
ee29579d3e | ||
|
|
5aec36350b | ||
|
|
1fecf80659 | ||
|
|
b6d77fbb9e | ||
|
|
8a19c5c30a | ||
|
|
0149122cd0 | ||
|
|
35335aadbe | ||
|
|
80df858ee3 | ||
|
|
eafc32fd68 | ||
|
|
2adeda0318 | ||
|
|
21a6d35b8b | ||
|
|
ce78280af3 | ||
|
|
041b86b8dd | ||
|
|
611b0041c4 | ||
|
|
ddb2acb652 | ||
|
|
da133baf17 | ||
|
|
d32b1912ed | ||
|
|
82513e5255 | ||
|
|
f2708ce967 | ||
|
|
01a5c94c7e | ||
|
|
e7a05b1db8 | ||
|
|
510a4aa99d | ||
|
|
3f4e8396aa | ||
|
|
276a200590 | ||
|
|
a40109e4e4 | ||
|
|
34cf959c9d | ||
|
|
bf6875f160 | ||
|
|
44f5b47bf0 | ||
|
|
41ab5dff5a | ||
|
|
5e43ece578 | ||
|
|
9acba4b412 | ||
|
|
8c50839109 | ||
|
|
6736d2ec66 | ||
|
|
6869e4807c | ||
|
|
5de05e2e4b | ||
|
|
fda54fd0c3 | ||
|
|
35962dedc4 | ||
|
|
991b74dd0d | ||
|
|
1a77667409 | ||
|
|
c9765ec158 | ||
|
|
202c91471c | ||
|
|
e14f68d1d7 | ||
|
|
185a3b29e6 | ||
|
|
d7b1569ee6 | ||
|
|
468f1cd912 | ||
|
|
965288de38 | ||
|
|
008920f533 | ||
|
|
20ed6a52ee | ||
|
|
8b85875b67 | ||
|
|
7054d81650 | ||
|
|
5942117ac3 | ||
|
|
e7a3b9d90e | ||
|
|
b13d623ad4 | ||
|
|
37bf9fae01 | ||
|
|
612cf3b7dd | ||
|
|
089e366ddc | ||
|
|
fa9e29e4ce | ||
|
|
fa3d83a3d1 | ||
|
|
804db4e073 | ||
|
|
5d998ef483 | ||
|
|
126b1e5726 | ||
|
|
4f2400c15b | ||
|
|
063d284cbf | ||
|
|
205bf5a11d | ||
|
|
41c94aa448 | ||
|
|
d413870e6d | ||
|
|
e95ac6fb06 | ||
|
|
e1ae92709c | ||
|
|
1715f85d50 | ||
|
|
c2de0a991a | ||
|
|
2e6e1ccf06 | ||
|
|
081f48404c | ||
|
|
bdac0ce053 | ||
|
|
122d91509f | ||
|
|
7c6f778ae7 | ||
|
|
8cf6c5423a | ||
|
|
56f7222230 | ||
|
|
e45b9625ca | ||
|
|
e0a8eb8aff | ||
|
|
2a5b9b5f98 | ||
|
|
a72843748f | ||
|
|
762e2311d2 | ||
|
|
0b7cda9e0c | ||
|
|
df83bc31d2 | ||
|
|
bc8d99d915 | ||
|
|
c841672059 | ||
|
|
75b605797d | ||
|
|
00d0ae840d | ||
|
|
2cdbb145da | ||
|
|
d8192560b0 | ||
|
|
9b395f3bf1 | ||
|
|
0150eb4bf3 | ||
|
|
632342b254 | ||
|
|
c9060c7f2d | ||
|
|
993879a2ed | ||
|
|
62309d5c87 | ||
|
|
b2e865ee2a | ||
|
|
3feb75f84f | ||
|
|
ad4d9da4cf | ||
|
|
1489854932 | ||
|
|
2aef09c8d9 | ||
|
|
48412e9c56 | ||
|
|
177a3abfac | ||
|
|
4c916b8da8 | ||
|
|
d9d381cb62 | ||
|
|
fbde24ee17 | ||
|
|
91310c9eb6 |
6
.clang-tidy
Normal file
6
.clang-tidy
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
Checks: ''
|
||||
WarningsAsErrors: ''
|
||||
HeaderFilterRegex: ''
|
||||
FormatStyle: none
|
||||
SystemHeaders: false
|
||||
4
.github/scripts/build_libpng.sh
vendored
4
.github/scripts/build_libpng.sh
vendored
@@ -1,13 +1,13 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
pngver=1.6.45
|
||||
pngver=1.6.50
|
||||
|
||||
## 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)" != 926485350139ffb51ef69760db35f78846c805fef3d59bfdcb2fba704663f370 ]; then
|
||||
if [ "$(sha2 -q -256 libpng-$pngver.tar.xz)" != 4df396518620a7aa3651443e87d1b2862e4e88cad135a8b93423e01706232307 ]; then
|
||||
sha2 -256 libpng-$pngver.tar.xz
|
||||
echo Checksum mismatch! Aborting. >&2
|
||||
exit 1
|
||||
|
||||
6
.github/scripts/get_win_deps.ps1
vendored
6
.github/scripts/get_win_deps.ps1
vendored
@@ -16,8 +16,8 @@ function getlibrary ([string] $URI, [string] $filename, [string] $hash, [string]
|
||||
}
|
||||
|
||||
getlibrary 'https://www.zlib.net/zlib131.zip' 'zlib.zip' '72af66d44fcc14c22013b46b814d5d2514673dda3d115e64b690c1ad636e7b17' .
|
||||
getlibrary 'https://github.com/glennrp/libpng/archive/refs/tags/v1.6.45.zip' 'libpng.zip' '1b3d94b2f1d137db1bf1842cb9f03df179772a517f7b86e26351742190632785' .
|
||||
getlibrary 'https://github.com/lexxmark/winflexbison/releases/download/v2.5.25/win_flex_bison-2.5.25.zip' 'winflexbison.zip' '8d324b62be33604b2c45ad1dd34ab93d722534448f55a16ca7292de32b6ac135' install_dir
|
||||
getlibrary 'https://github.com/pnggroup/libpng/archive/refs/tags/v1.6.50.zip' 'libpng.zip' 'f6bb2544d2cf5465af3a695dee0b7eacff82f11a50aa4672ef0e19df6e16d455' .
|
||||
getlibrary 'https://github.com/lexxmark/winflexbison/releases/download/v2.5.25/win_flex_bison-2.5.25.zip' 'winflexbison.zip' '8d324b62be33604b2c45ad1dd34ab93d722534448f55a16ca7292de32b6ac135' install_dir
|
||||
|
||||
Move-Item zlib-1.3.1 zlib
|
||||
Move-Item libpng-1.6.45 libpng
|
||||
Move-Item libpng-1.6.50 libpng
|
||||
|
||||
9
.github/scripts/mingw-w64-libpng-dev.sh
vendored
9
.github/scripts/mingw-w64-libpng-dev.sh
vendored
@@ -1,22 +1,17 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
pngver=1.6.45
|
||||
pngver=1.6.50
|
||||
arch="$1"
|
||||
|
||||
## Grab sources and check them
|
||||
|
||||
wget http://downloads.sourceforge.net/project/libpng/libpng16/$pngver/libpng-$pngver.tar.xz
|
||||
wget http://downloads.sourceforge.net/project/apng/libpng/libpng16/libpng-$pngver-apng.patch.gz
|
||||
sha256sum -c .github/scripts/mingw-w64-libpng-dev.sha256sums
|
||||
echo 4df396518620a7aa3651443e87d1b2862e4e88cad135a8b93423e01706232307 libpng-$pngver.tar.xz | sha256sum -c -
|
||||
|
||||
## Extract sources and patch them
|
||||
|
||||
tar -xf libpng-$pngver.tar.xz
|
||||
gunzip libpng-$pngver-apng.patch.gz
|
||||
|
||||
# Patch in apng support
|
||||
env -C libpng-$pngver patch -p0 ../libpng-$pngver-apng.patch
|
||||
|
||||
## Start building!
|
||||
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
f7caa3b55f003ce23d6a087b1c2a643262647bfd1a1b31afdc9b18eabf1bbc7e libpng-1.6.45-apng.patch.gz
|
||||
926485350139ffb51ef69760db35f78846c805fef3d59bfdcb2fba704663f370 libpng-1.6.45.tar.xz
|
||||
2
.github/workflows/build-container.yml
vendored
2
.github/workflows/build-container.yml
vendored
@@ -37,7 +37,9 @@ jobs:
|
||||
TAG_NAME=${GITHUB_REF#refs/tags/}
|
||||
sed -i "2i LABEL org.opencontainers.image.description=\"RGBDS container image for the release version $TAG_NAME\"" Dockerfile
|
||||
docker build . --tag ghcr.io/gbdev/rgbds:$TAG_NAME
|
||||
docker tag ghcr.io/gbdev/rgbds:$TAG_NAME ghcr.io/gbdev/rgbds:latest
|
||||
docker push ghcr.io/gbdev/rgbds:$TAG_NAME
|
||||
docker push ghcr.io/gbdev/rgbds:latest
|
||||
|
||||
- name: Delete untagged container images
|
||||
if: github.repository_owner == 'gbdev'
|
||||
|
||||
4
.github/workflows/checkdiff.yml
vendored
4
.github/workflows/checkdiff.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: Code coverage checking
|
||||
name: Diff completeness check
|
||||
on: pull_request
|
||||
|
||||
jobs:
|
||||
@@ -11,7 +11,7 @@ jobs:
|
||||
cd rgbds
|
||||
git remote add upstream "${{ github.event.pull_request.base.repo.clone_url }}"
|
||||
git fetch upstream
|
||||
- name: Checkdiff
|
||||
- name: Check diff
|
||||
working-directory: rgbds
|
||||
run: |
|
||||
make checkdiff "BASE_REF=${{ github.event.pull_request.base.sha }}" Q= | tee log
|
||||
|
||||
12
.github/workflows/checkformat.yml
vendored
Normal file
12
.github/workflows/checkformat.yml
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
name: Code format checking
|
||||
on: pull_request
|
||||
|
||||
jobs:
|
||||
checkformat:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
- name: Check format
|
||||
run: |
|
||||
contrib/checkformat.bash
|
||||
34
.github/workflows/coverage.yml
vendored
Normal file
34
.github/workflows/coverage.yml
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
name: Code coverage report
|
||||
on:
|
||||
- push
|
||||
- pull_request
|
||||
|
||||
jobs:
|
||||
coverage:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
- name: Install deps
|
||||
shell: bash
|
||||
run: |
|
||||
./.github/scripts/install_deps.sh ubuntu
|
||||
- name: Install LCOV
|
||||
run: |
|
||||
sudo apt-get install lcov
|
||||
- name: Install test dependency dependencies
|
||||
shell: bash
|
||||
run: |
|
||||
test/fetch-test-deps.sh --get-deps ubuntu
|
||||
- name: Generate coverage report
|
||||
run: |
|
||||
contrib/coverage.bash ubuntu-ci
|
||||
- name: Upload coverage report
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: coverage-report
|
||||
# Workaround for keeping the top-level coverage/ directory
|
||||
# https://github.com/actions/upload-artifact/issues/174
|
||||
path: |
|
||||
coverage
|
||||
dummy-file-to-keep-directory-structure.txt
|
||||
26
.github/workflows/create-release-artifacts.yml
vendored
26
.github/workflows/create-release-artifacts.yml
vendored
@@ -61,12 +61,12 @@ jobs:
|
||||
cmake --install build --verbose --prefix install_dir --strip
|
||||
- name: Package binaries
|
||||
run: |
|
||||
Compress-Archive -LiteralPath @("install_dir/bin/rgbasm.exe", "install_dir/bin/rgblink.exe", "install_dir/bin/rgbfix.exe", "install_dir/bin/rgbgfx.exe", "install_dir/bin/zlib1.dll", "install_dir/bin/libpng16.dll") "rgbds-${{ env.version }}-win${{ matrix.bits }}.zip"
|
||||
Compress-Archive -LiteralPath @("install_dir/bin/rgbasm.exe", "install_dir/bin/rgblink.exe", "install_dir/bin/rgbfix.exe", "install_dir/bin/rgbgfx.exe", "install_dir/bin/zlib1.dll", "install_dir/bin/libpng16.dll") "rgbds-win${{ matrix.bits }}.zip"
|
||||
- name: Upload Windows binaries
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: win${{ matrix.bits }}
|
||||
path: rgbds-${{ env.version }}-win${{ matrix.bits }}.zip
|
||||
path: rgbds-win${{ matrix.bits }}.zip
|
||||
|
||||
macos:
|
||||
runs-on: macos-14
|
||||
@@ -92,15 +92,15 @@ jobs:
|
||||
strip rgb{asm,link,fix,gfx}
|
||||
- name: Package binaries
|
||||
run: |
|
||||
zip --junk-paths rgbds-${{ env.version }}-macos.zip rgb{asm,link,fix,gfx} man/* .github/scripts/install.sh
|
||||
zip --junk-paths rgbds-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.zip
|
||||
path: rgbds-macos.zip
|
||||
|
||||
linux:
|
||||
runs-on: ubuntu-20.04 # Oldest supported, for best glibc compatibility.
|
||||
runs-on: ubuntu-22.04 # Oldest supported, for best glibc compatibility.
|
||||
steps:
|
||||
- name: Get version from tag
|
||||
shell: bash
|
||||
@@ -112,19 +112,19 @@ jobs:
|
||||
- name: Install deps
|
||||
shell: bash
|
||||
run: |
|
||||
./.github/scripts/install_deps.sh ubuntu-20.04
|
||||
./.github/scripts/install_deps.sh ubuntu-22.04
|
||||
- name: Build binaries
|
||||
run: |
|
||||
make -kj WARNFLAGS="-Wall -Wextra -pedantic -static" PKG_CONFIG="pkg-config --static" Q=
|
||||
strip rgb{asm,link,fix,gfx}
|
||||
- name: Package binaries
|
||||
run: |
|
||||
tar caf rgbds-${{ env.version }}-linux-x86_64.tar.xz --transform='s#.*/##' rgb{asm,link,fix,gfx} man/* .github/scripts/install.sh
|
||||
tar caf rgbds-linux-x86_64.tar.xz --transform='s#.*/##' rgb{asm,link,fix,gfx} man/* .github/scripts/install.sh
|
||||
- name: Upload Linux binaries
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: linux
|
||||
path: rgbds-${{ env.version }}-linux-x86_64.tar.xz
|
||||
path: rgbds-linux-x86_64.tar.xz
|
||||
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -155,11 +155,11 @@ jobs:
|
||||
draft: true # Don't publish the release quite yet...
|
||||
prerelease: ${{ contains(github.ref, '-rc') }}
|
||||
files: |
|
||||
win32/rgbds-${{ env.version }}-win32.zip
|
||||
win64/rgbds-${{ env.version }}-win64.zip
|
||||
macos/rgbds-${{ env.version }}-macos.zip
|
||||
linux/rgbds-${{ env.version }}-linux-x86_64.tar.xz
|
||||
rgbds-${{ env.version }}.tar.gz
|
||||
win32/rgbds-win32.zip
|
||||
win64/rgbds-win64.zip
|
||||
macos/rgbds-macos.zip
|
||||
linux/rgbds-linux-x86_64.tar.xz
|
||||
rgbds-source.tar.gz
|
||||
fail_on_unmatched_files: true
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
21
.github/workflows/testing.yml
vendored
21
.github/workflows/testing.yml
vendored
@@ -7,7 +7,7 @@ jobs:
|
||||
unix:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-20.04, ubuntu-22.04, macos-14]
|
||||
os: [ubuntu-22.04, macos-14]
|
||||
cxx: [g++, clang++]
|
||||
buildsys: [make, cmake]
|
||||
exclude:
|
||||
@@ -69,7 +69,7 @@ jobs:
|
||||
- name: Run tests
|
||||
shell: bash
|
||||
run: |
|
||||
CXX=${{ matrix.cxx }} test/run-tests.sh
|
||||
CXX=${{ matrix.cxx }} test/run-tests.sh --os ${{ matrix.os }}
|
||||
|
||||
macos-static:
|
||||
runs-on: macos-14
|
||||
@@ -121,13 +121,13 @@ jobs:
|
||||
- name: Run tests
|
||||
shell: bash
|
||||
run: |
|
||||
test/run-tests.sh
|
||||
test/run-tests.sh --os macos
|
||||
|
||||
windows:
|
||||
strategy:
|
||||
matrix:
|
||||
bits: [32, 64]
|
||||
os: [windows-2019, windows-2022]
|
||||
os: [windows-2022, windows-2025]
|
||||
include:
|
||||
- bits: 32
|
||||
arch: x86
|
||||
@@ -149,9 +149,10 @@ jobs:
|
||||
path: |
|
||||
zbuild
|
||||
pngbuild
|
||||
key: ${{ matrix.arch }}-${{ hashFiles('zlib/**', 'libpng/**') }}
|
||||
key: ${{ matrix.os }}-${{ matrix.arch }}-${{ hashFiles('zlib/**', 'libpng/**') }}
|
||||
- name: Build zlib
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
shell: bash
|
||||
run: | # BUILD_SHARED_LIBS causes the output DLL to be correctly called `zlib1.dll`
|
||||
cmake -S zlib -B zbuild -A ${{ matrix.platform }} -Wno-dev -DCMAKE_INSTALL_PREFIX=install_dir -DBUILD_SHARED_LIBS=ON
|
||||
cmake --build zbuild --config Release -j
|
||||
@@ -212,7 +213,7 @@ jobs:
|
||||
run: |
|
||||
cp bins/* .
|
||||
cp bins/*.dll test/gfx
|
||||
test/run-tests.sh
|
||||
test/run-tests.sh --os ${{ matrix.os }}
|
||||
|
||||
windows-mingw-build:
|
||||
strategy:
|
||||
@@ -270,7 +271,7 @@ jobs:
|
||||
needs: windows-mingw-build
|
||||
strategy:
|
||||
matrix:
|
||||
os: [windows-2019, windows-2022]
|
||||
os: [windows-2022, windows-2025]
|
||||
bits: [32, 64]
|
||||
fail-fast: false
|
||||
runs-on: ${{ matrix.os }}
|
||||
@@ -319,7 +320,7 @@ jobs:
|
||||
- name: Run tests
|
||||
shell: bash
|
||||
run: |
|
||||
test/run-tests.sh
|
||||
test/run-tests.sh --os ${{ matrix.os }}
|
||||
|
||||
cygwin:
|
||||
strategy:
|
||||
@@ -331,7 +332,7 @@ jobs:
|
||||
- bits: 64
|
||||
arch: x86_64
|
||||
fail-fast: false
|
||||
runs-on: windows-2019
|
||||
runs-on: windows-2022
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
@@ -382,7 +383,7 @@ jobs:
|
||||
git \
|
||||
png
|
||||
run: | # FreeBSD `c++` compiler does not support `make develop` sanitizers ASan or UBSan
|
||||
cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_COMPILER=c++ -DUSE_EXTERNAL_TESTS=OFF
|
||||
cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_COMPILER=c++ -DUSE_EXTERNAL_TESTS=OFF -DOS=bsd
|
||||
cmake --build build -j4 --verbose
|
||||
cmake --install build --verbose
|
||||
cmake --build build --target test
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -14,4 +14,5 @@ CMakeCache.txt
|
||||
CMakeFiles/
|
||||
cmake_install.cmake
|
||||
build/
|
||||
*.dSYM/
|
||||
callgrind.out.*
|
||||
|
||||
@@ -51,16 +51,15 @@ else()
|
||||
add_link_options(${SAN_FLAGS})
|
||||
add_definitions(-D_GLIBCXX_ASSERTIONS)
|
||||
# A non-zero optimization level is desired in debug mode, but allow overriding it nonetheless
|
||||
# TODO: this overrides anything previously set... that's a bit sloppy!
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "-g -Og -fno-omit-frame-pointer -fno-optimize-sibling-calls" CACHE STRING "" FORCE)
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "-g -Og -fno-omit-frame-pointer -fno-optimize-sibling-calls ${CMAKE_CXX_FLAGS_DEBUG}"
|
||||
CACHE STRING "" FORCE)
|
||||
endif()
|
||||
|
||||
if(MORE_WARNINGS)
|
||||
add_compile_options(-Werror -Wextra
|
||||
-Walloc-zero -Wcast-align -Wcast-qual -Wduplicated-branches -Wduplicated-cond
|
||||
-Wfloat-equal -Wlogical-op -Wnull-dereference -Wold-style-cast -Wshift-overflow=2
|
||||
-Wstringop-overflow=4 -Wundef -Wuninitialized -Wunused
|
||||
-Wshadow # TODO: -Wshadow=compatible-local?
|
||||
-Wstringop-overflow=4 -Wtrampolines -Wundef -Wuninitialized -Wunused -Wshadow
|
||||
-Wformat=2 -Wformat-overflow=2 -Wformat-truncation=1
|
||||
-Wno-format-nonliteral -Wno-strict-overflow
|
||||
-Wno-unused-but-set-variable # bison's `yynerrs_` is incremented but unused
|
||||
|
||||
@@ -97,7 +97,7 @@ All tests begin by assembling the `.asm` file into an object file, which will be
|
||||
|
||||
These simply check that RGBLINK's output matches some expected output.
|
||||
|
||||
A `.out` file **must** exist, and RGBLINK's output must match that file's contents.
|
||||
A `.out` file **must** exist, and RGBLINK's total output must match that file's contents.
|
||||
|
||||
Additionally, if a `.out.bin` file exists, the `.gb` file generated by RGBLINK must match it.
|
||||
|
||||
@@ -106,14 +106,14 @@ Additionally, if a `.out.bin` file exists, the `.gb` file generated by RGBLINK m
|
||||
These allow applying various linker scripts to the same object file.
|
||||
If one or more `.link` files exist, whose names start the same as the `.asm` file, then each of those files correspond to one test.
|
||||
|
||||
Each `.link` linker script **must** be accompanied by a `.out` file, and RGBLINK's output must match that file's contents when passed the corresponding linker script.
|
||||
Each `.link` linker script **must** be accompanied by a `.out` file, and RGBLINK's total output must match that file's contents when passed the corresponding linker script.
|
||||
|
||||
#### Variant tests
|
||||
|
||||
These allow testing RGBLINK's `-d`, `-t`, and `-w` flags.
|
||||
If one or more <code>-<var><flag></var>.out</code> or <code>-no-<var><flag></var>.out</code> files exist, then each of them corresponds to one test.
|
||||
|
||||
The object file will be linked with and without said flag, respectively; and in each case, RGBLINK's output must match the `.out` file's contents.
|
||||
The object file will be linked with and without said flag, respectively; and in each case, RGBLINK's total output must match the `.out` file's contents.
|
||||
|
||||
### RGBFIX
|
||||
|
||||
@@ -123,8 +123,11 @@ Each one is a text file whose first line contains flags to pass to RGBFIX.
|
||||
|
||||
RGBFIX will be invoked on the `.bin` file if it exists, or else on default-input.bin.
|
||||
|
||||
If no `.out` file exist, RGBFIX is not expected to output anything.
|
||||
If one *does* exist, RGBFIX's output **must** match the `.out` file's contents.
|
||||
|
||||
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.
|
||||
If one *does* exist, RGBFIX's return status is ignored, but its error output **must** match the `.err` file's contents.
|
||||
|
||||
Additionally, if a `.gb` file exists, the output of RGBFIX must match the `.gb`.
|
||||
|
||||
|
||||
11
Dockerfile
11
Dockerfile
@@ -1,6 +1,6 @@
|
||||
FROM debian:12-slim
|
||||
LABEL org.opencontainers.image.source=https://github.com/gbdev/rgbds
|
||||
ARG version=0.9.1
|
||||
ARG version=0.9.4
|
||||
WORKDIR /rgbds
|
||||
|
||||
COPY . .
|
||||
@@ -8,7 +8,14 @@ COPY . .
|
||||
RUN apt-get update && \
|
||||
apt-get install sudo make cmake gcc build-essential -y
|
||||
|
||||
# Install dependencies and compile RGBDS
|
||||
RUN ./.github/scripts/install_deps.sh ubuntu-22.04
|
||||
RUN make -j CXXFLAGS="-O3 -flto -DNDEBUG -static" PKG_CONFIG="pkg-config --static" Q=
|
||||
|
||||
RUN tar caf rgbds-${version}-linux-x86_64.tar.xz --transform='s#.*/##' rgbasm rgblink rgbfix rgbgfx man/* .github/scripts/install.sh
|
||||
# Create an archive with the compiled executables and all the necessary to install it,
|
||||
# so it can be copied outside of the container and installed/used in another system
|
||||
RUN tar caf rgbds-linux-x86_64.tar.xz --transform='s#.*/##' rgbasm rgblink rgbfix rgbgfx man/* .github/scripts/install.sh
|
||||
|
||||
# Install RGBDS on the container so all the executables will be available in the PATH
|
||||
RUN cp man/* .
|
||||
RUN ./.github/scripts/install.sh
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 1997-2024, Carsten Sørensen and RGBDS contributors.
|
||||
Copyright (c) 1996-2025, 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
|
||||
|
||||
111
Makefile
111
Makefile
@@ -3,53 +3,60 @@
|
||||
.SUFFIXES:
|
||||
.SUFFIXES: .cpp .y .o
|
||||
|
||||
.PHONY: all clean install checkdiff develop debug profile coverage iwyu mingw32 mingw64 wine-shim dist
|
||||
.PHONY: all clean install checkdiff develop debug profile coverage tidy iwyu mingw32 mingw64 wine-shim dist
|
||||
|
||||
# User-defined variables
|
||||
|
||||
Q := @
|
||||
PREFIX := /usr/local
|
||||
bindir := ${PREFIX}/bin
|
||||
mandir := ${PREFIX}/share/man
|
||||
SUFFIX :=
|
||||
STRIP := -s
|
||||
BINMODE := 755
|
||||
MANMODE := 644
|
||||
Q := @
|
||||
PREFIX := /usr/local
|
||||
bindir := ${PREFIX}/bin
|
||||
mandir := ${PREFIX}/share/man
|
||||
SUFFIX :=
|
||||
STRIP := -s
|
||||
BINMODE := 755
|
||||
MANMODE := 644
|
||||
|
||||
# Other variables
|
||||
|
||||
PKG_CONFIG := pkg-config
|
||||
PNGCFLAGS := `${PKG_CONFIG} --cflags libpng`
|
||||
PNGLDFLAGS := `${PKG_CONFIG} --libs-only-L libpng`
|
||||
PNGLDLIBS := `${PKG_CONFIG} --libs-only-l libpng`
|
||||
PKG_CONFIG := pkg-config
|
||||
PNGCFLAGS := `${PKG_CONFIG} --cflags libpng`
|
||||
PNGLDFLAGS := `${PKG_CONFIG} --libs-only-L libpng`
|
||||
PNGLDLIBS := `${PKG_CONFIG} --libs-only-l libpng`
|
||||
|
||||
# Note: if this comes up empty, `version.cpp` will automatically fall back to last release number
|
||||
VERSION_STRING := `git --git-dir=.git -c safe.directory='*' describe --tags --dirty --always 2>/dev/null`
|
||||
VERSION_STRING := `git --git-dir=.git -c safe.directory='*' describe --tags --dirty --always 2>/dev/null`
|
||||
|
||||
WARNFLAGS := -Wall -pedantic -Wno-unknown-warning-option -Wno-gnu-zero-variadic-macro-arguments
|
||||
WARNFLAGS := -Wall -pedantic -Wno-unknown-warning-option -Wno-gnu-zero-variadic-macro-arguments
|
||||
|
||||
# Overridable CXXFLAGS
|
||||
CXXFLAGS ?= -O3 -flto -DNDEBUG
|
||||
CXXFLAGS ?= -O3 -flto -DNDEBUG
|
||||
# Non-overridable CXXFLAGS
|
||||
REALCXXFLAGS := ${CXXFLAGS} ${WARNFLAGS} -std=c++2a -I include -fno-exceptions -fno-rtti
|
||||
REALCXXFLAGS := ${CXXFLAGS} ${WARNFLAGS} -std=c++2a -I include -fno-exceptions -fno-rtti
|
||||
# Overridable LDFLAGS
|
||||
LDFLAGS ?=
|
||||
LDFLAGS ?=
|
||||
# Non-overridable LDFLAGS
|
||||
REALLDFLAGS := ${LDFLAGS} ${WARNFLAGS} -DBUILD_VERSION_STRING=\"${VERSION_STRING}\"
|
||||
REALLDFLAGS := ${LDFLAGS} ${WARNFLAGS} -DBUILD_VERSION_STRING=\"${VERSION_STRING}\"
|
||||
|
||||
# Wrapper around bison that passes flags depending on what the version supports
|
||||
BISON := src/bison.sh
|
||||
BISON := src/bison.sh
|
||||
|
||||
RM := rm -rf
|
||||
RM := rm -rf
|
||||
|
||||
# Used for checking pull requests
|
||||
BASE_REF := origin/master
|
||||
BASE_REF := origin/master
|
||||
|
||||
# Rules to build the RGBDS binaries
|
||||
|
||||
all: rgbasm rgblink rgbfix rgbgfx
|
||||
|
||||
common_obj := \
|
||||
src/extern/getopt.o \
|
||||
src/diagnostics.o \
|
||||
src/usage.o
|
||||
|
||||
rgbasm_obj := \
|
||||
${common_obj} \
|
||||
src/asm/actions.o \
|
||||
src/asm/charmap.o \
|
||||
src/asm/fixpoint.o \
|
||||
src/asm/format.o \
|
||||
@@ -64,9 +71,7 @@ rgbasm_obj := \
|
||||
src/asm/section.o \
|
||||
src/asm/symbol.o \
|
||||
src/asm/warning.o \
|
||||
src/extern/getopt.o \
|
||||
src/extern/utf8decoder.o \
|
||||
src/error.o \
|
||||
src/linkdefs.o \
|
||||
src/opmath.o \
|
||||
src/util.o
|
||||
@@ -74,7 +79,10 @@ rgbasm_obj := \
|
||||
src/asm/lexer.o src/asm/main.o: src/asm/parser.hpp
|
||||
|
||||
rgblink_obj := \
|
||||
${common_obj} \
|
||||
src/link/assign.o \
|
||||
src/link/lexer.o \
|
||||
src/link/layout.o \
|
||||
src/link/main.o \
|
||||
src/link/object.o \
|
||||
src/link/output.o \
|
||||
@@ -83,34 +91,36 @@ rgblink_obj := \
|
||||
src/link/sdas_obj.o \
|
||||
src/link/section.o \
|
||||
src/link/symbol.o \
|
||||
src/extern/getopt.o \
|
||||
src/link/warning.o \
|
||||
src/extern/utf8decoder.o \
|
||||
src/error.o \
|
||||
src/linkdefs.o \
|
||||
src/opmath.o \
|
||||
src/util.o
|
||||
|
||||
src/link/main.o: src/link/script.hpp
|
||||
src/link/lexer.o src/link/main.o: src/link/script.hpp
|
||||
|
||||
rgbfix_obj := \
|
||||
${common_obj} \
|
||||
src/fix/main.o \
|
||||
src/extern/getopt.o \
|
||||
src/error.o
|
||||
src/fix/mbc.o \
|
||||
src/fix/warning.o
|
||||
|
||||
rgbgfx_obj := \
|
||||
${common_obj} \
|
||||
src/gfx/color_set.o \
|
||||
src/gfx/main.o \
|
||||
src/gfx/pal_packing.o \
|
||||
src/gfx/pal_sorting.o \
|
||||
src/gfx/pal_spec.o \
|
||||
src/gfx/png.o \
|
||||
src/gfx/process.o \
|
||||
src/gfx/proto_palette.o \
|
||||
src/gfx/reverse.o \
|
||||
src/gfx/rgba.o \
|
||||
src/extern/getopt.o \
|
||||
src/error.o
|
||||
src/gfx/warning.o \
|
||||
src/util.o
|
||||
|
||||
rgbasm: ${rgbasm_obj}
|
||||
$Q${CXX} ${REALLDFLAGS} -o $@ ${rgbasm_obj} ${REALCXXFLAGS} src/version.cpp -lm
|
||||
$Q${CXX} ${REALLDFLAGS} -o $@ ${rgbasm_obj} ${REALCXXFLAGS} src/version.cpp
|
||||
|
||||
rgblink: ${rgblink_obj}
|
||||
$Q${CXX} ${REALLDFLAGS} -o $@ ${rgblink_obj} ${REALCXXFLAGS} src/version.cpp
|
||||
@@ -142,6 +152,8 @@ src/link/script.hpp: src/link/script.cpp
|
||||
$Qtouch $@
|
||||
|
||||
# Only RGBGFX uses libpng (POSIX make doesn't support pattern rules to cover all these)
|
||||
src/gfx/color_set.o: src/gfx/color_set.cpp
|
||||
$Q${CXX} ${REALCXXFLAGS} ${PNGCFLAGS} -c -o $@ $<
|
||||
src/gfx/main.o: src/gfx/main.cpp
|
||||
$Q${CXX} ${REALCXXFLAGS} ${PNGCFLAGS} -c -o $@ $<
|
||||
src/gfx/pal_packing.o: src/gfx/pal_packing.cpp
|
||||
@@ -150,9 +162,9 @@ src/gfx/pal_sorting.o: src/gfx/pal_sorting.cpp
|
||||
$Q${CXX} ${REALCXXFLAGS} ${PNGCFLAGS} -c -o $@ $<
|
||||
src/gfx/pal_spec.o: src/gfx/pal_spec.cpp
|
||||
$Q${CXX} ${REALCXXFLAGS} ${PNGCFLAGS} -c -o $@ $<
|
||||
src/gfx/process.o: src/gfx/process.cpp
|
||||
src/gfx/png.o: src/gfx/png.cpp
|
||||
$Q${CXX} ${REALCXXFLAGS} ${PNGCFLAGS} -c -o $@ $<
|
||||
src/gfx/proto_palette.o: src/gfx/proto_palette.cpp
|
||||
src/gfx/process.o: src/gfx/process.cpp
|
||||
$Q${CXX} ${REALCXXFLAGS} ${PNGCFLAGS} -c -o $@ $<
|
||||
src/gfx/reverse.o: src/gfx/reverse.cpp
|
||||
$Q${CXX} ${REALCXXFLAGS} ${PNGCFLAGS} -c -o $@ $<
|
||||
@@ -163,7 +175,6 @@ src/gfx/rgba.o: src/gfx/rgba.cpp
|
||||
$Q${CXX} ${REALCXXFLAGS} -c -o $@ $<
|
||||
|
||||
# Target used to remove all files generated by other Makefile targets
|
||||
|
||||
clean:
|
||||
$Q${RM} rgbasm rgbasm.exe
|
||||
$Q${RM} rgblink rgblink.exe
|
||||
@@ -177,7 +188,6 @@ clean:
|
||||
$Q${RM} test/gfx/randtilegen test/gfx/rgbgfx_test
|
||||
|
||||
# Target used to install the binaries and man pages.
|
||||
|
||||
install: all
|
||||
$Qinstall -d ${DESTDIR}${bindir}/ ${DESTDIR}${mandir}/man1/ ${DESTDIR}${mandir}/man5/ ${DESTDIR}${mandir}/man7/
|
||||
$Qinstall ${STRIP} -m ${BINMODE} rgbasm ${DESTDIR}${bindir}/rgbasm${SUFFIX}
|
||||
@@ -189,20 +199,18 @@ install: all
|
||||
$Qinstall -m ${MANMODE} man/rgbds.7 man/gbz80.7 ${DESTDIR}${mandir}/man7/
|
||||
|
||||
# Target used to check for suspiciously missing changed files.
|
||||
|
||||
checkdiff:
|
||||
$Qcontrib/checkdiff.bash `git merge-base HEAD ${BASE_REF}`
|
||||
|
||||
# This target is used during development in order to prevent adding new issues
|
||||
# to the source code. All warnings are treated as errors in order to block the
|
||||
# compilation and make the continous integration infrastructure return failure.
|
||||
# Target used in development to prevent adding new issues to the source code.
|
||||
# All warnings are treated as errors to block the compilation and make the
|
||||
# continous integration infrastructure return failure.
|
||||
# The rationale for some of the flags is documented in the CMakeLists.
|
||||
|
||||
develop:
|
||||
$Q${MAKE} WARNFLAGS="${WARNFLAGS} -Werror -Wextra \
|
||||
-Walloc-zero -Wcast-align -Wcast-qual -Wduplicated-branches -Wduplicated-cond \
|
||||
-Wfloat-equal -Wlogical-op -Wnull-dereference -Wold-style-cast -Wshift-overflow=2 \
|
||||
-Wstringop-overflow=4 -Wundef -Wuninitialized -Wunused -Wshadow \
|
||||
-Wstringop-overflow=4 -Wtrampolines -Wundef -Wuninitialized -Wunused -Wshadow \
|
||||
-Wformat=2 -Wformat-overflow=2 -Wformat-truncation=1 \
|
||||
-Wno-format-nonliteral -Wno-strict-overflow -Wno-unused-but-set-variable \
|
||||
-Wno-type-limits -Wno-tautological-constant-out-of-range-compare -Wvla \
|
||||
@@ -210,26 +218,27 @@ develop:
|
||||
-fsanitize=float-divide-by-zero" \
|
||||
CXXFLAGS="-ggdb3 -Og -fno-omit-frame-pointer -fno-optimize-sibling-calls"
|
||||
|
||||
# This target is used during development in order to more easily debug with gdb.
|
||||
|
||||
# Target used in development to debug with gdb.
|
||||
debug:
|
||||
$Qenv ${MAKE} \
|
||||
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.
|
||||
|
||||
# Target used in development to profile with callgrind.
|
||||
profile:
|
||||
$Qenv ${MAKE} \
|
||||
CXXFLAGS="-ggdb3 -O3 -fno-omit-frame-pointer -fno-optimize-sibling-calls"
|
||||
|
||||
# This target is used during development in order to inspect code coverage with gcov.
|
||||
|
||||
# Target used in development to inspect code coverage with gcov.
|
||||
coverage:
|
||||
$Qenv ${MAKE} \
|
||||
CXXFLAGS="-ggdb3 -Og --coverage -fno-omit-frame-pointer -fno-optimize-sibling-calls"
|
||||
|
||||
# This target is used during development in order to remove unused `#include` headers.
|
||||
# Target used in development to check code with clang-tidy.
|
||||
# Requires Bison-generated header files to exist.
|
||||
tidy: src/asm/parser.hpp src/link/script.hpp
|
||||
$Qclang-tidy -p . $$(find src -name '*.cpp')
|
||||
|
||||
# Target used in development to remove unused `#include` headers.
|
||||
iwyu:
|
||||
$Qenv ${MAKE} \
|
||||
CXX="include-what-you-use" \
|
||||
@@ -265,4 +274,4 @@ wine-shim:
|
||||
|
||||
dist:
|
||||
$Qgit ls-files | sed s~^~$${PWD##*/}/~ \
|
||||
| tar -czf rgbds-`git -c safe.directory='*' describe --tags | cut -c 2-`.tar.gz -C .. -T -
|
||||
| tar -czf rgbds-source.tar.gz -C .. -T -
|
||||
|
||||
@@ -6,7 +6,7 @@ for the Game Boy and Game Boy Color. It consists of:
|
||||
- RGBASM (assembler)
|
||||
- RGBLINK (linker)
|
||||
- RGBFIX (checksum/header fixer)
|
||||
- RGBGFX (PNG‐to‐Game Boy graphics converter)
|
||||
- RGBGFX (PNG-to-Game Boy graphics converter)
|
||||
|
||||
This is a fork of the original RGBDS which aims to make the programs more like
|
||||
other UNIX tools.
|
||||
@@ -93,6 +93,7 @@ The RGBDS source code file structure is as follows:
|
||||
│ └── run-tests.sh
|
||||
├── .clang-format
|
||||
├── CMakeLists.txt
|
||||
├── compile_flags.txt
|
||||
├── Dockerfile
|
||||
├── Makefile
|
||||
└── README.md
|
||||
@@ -118,6 +119,8 @@ The RGBDS source code file structure is as follows:
|
||||
modify the behavior of RGBDS.
|
||||
- `.clang-format` - code style for automated C++ formatting with
|
||||
[`clang-format`](https://clang.llvm.org/docs/ClangFormat.html).
|
||||
- `compile_flags.txt` - compiler flags for C++ static analysis with
|
||||
[`clang-tidy`](https://clang.llvm.org/extra/clang-tidy/).
|
||||
- `Dockerfile` - defines how to build RGBDS with Docker.
|
||||
|
||||
## 3. History
|
||||
@@ -140,7 +143,7 @@ The RGBDS source code file structure is as follows:
|
||||
- 2010-09-25: Sørensen continues development of
|
||||
[ASMotor](https://github.com/asmotor/asmotor) to this day.
|
||||
- 2015-01-18: stag019 begins implementing [RGBGFX](https://github.com/stag019/rgbgfx),
|
||||
a PNG‐to‐Game Boy graphics converter, for eventual integration into RGBDS.
|
||||
a PNG-to-Game Boy graphics converter, for eventual integration into RGBDS.
|
||||
- 2016-09-05: RGBGFX is
|
||||
[integrated](https://github.com/gbdev/rgbds/commit/c3c31138ddbd8680d4e67957e387f2816798a71b)
|
||||
into Bentley's repository.
|
||||
|
||||
17
RELEASE.md
17
RELEASE.md
@@ -68,13 +68,16 @@ GitHub.
|
||||
|
||||
6. Click the "Publish release" button to publish it!
|
||||
|
||||
7. Update the `release` branch. You can use `git push origin release`.
|
||||
7. Update the `release` branch. You can use `git push origin master:release`.
|
||||
|
||||
8. Update the following related projects.
|
||||
|
||||
- [rgbobj](https://github.com/gbdev/rgbobj) and [rgbds-obj](https://github.com/gbdev/rgbds-obj):
|
||||
make sure that object files created by the latest RGBASM can be parsed and displayed.
|
||||
If the object file revision has been updated, rgbobj will need a corresponding release.
|
||||
- [rgbds-www](https://github.com/gbdev/rgbds-www): update
|
||||
[src/pages/versions.mdx](https://github.com/gbdev/rgbds-www/blob/master/src/pages/versions.mdx)
|
||||
to list the new release.
|
||||
1. [rgbds-www](https://github.com/gbdev/rgbds-www): update
|
||||
[src/pages/versions.mdx](https://github.com/gbdev/rgbds-www/blob/master/src/pages/versions.mdx)
|
||||
to list the new release.
|
||||
2. [rgbds-live](https://github.com/gbdev/rgbds-live): update the `rgbds` submodule (and
|
||||
[patches/rgbds.patch](https://github.com/gbdev/rgbds-live/blob/master/patches/rgbds.patch)
|
||||
if necessary) to use the new release.
|
||||
3. [rgbobj](https://github.com/gbdev/rgbobj) and [rgbds-obj](https://github.com/gbdev/rgbds-obj):
|
||||
make sure that object files created by the latest RGBASM can be parsed and displayed.
|
||||
If the object file revision has been updated, `rgbobj` will need a corresponding release.
|
||||
|
||||
6
compile_flags.txt
Normal file
6
compile_flags.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
-std=c++2a
|
||||
-I
|
||||
include
|
||||
-fno-exceptions
|
||||
-fno-rtti
|
||||
-fno-caret-diagnostics
|
||||
@@ -24,13 +24,13 @@ _rgbasm_completions() {
|
||||
# Empty long opt = it doesn't exit
|
||||
# See the `state` variable below for info about `state_after`
|
||||
declare -A opts=(
|
||||
[V]="version:normal"
|
||||
[h]="help:normal"
|
||||
[E]="export-all:normal"
|
||||
[v]="verbose:normal"
|
||||
[V]="version:normal"
|
||||
[W]="warning:warning"
|
||||
[w]=":normal"
|
||||
[b]="binary-digits:unk"
|
||||
[D]="define:unk"
|
||||
[E]="export-all:normal"
|
||||
[g]="gfx-chars:unk"
|
||||
[I]="include:dir"
|
||||
[M]="dependfile:glob-*.mk *.d"
|
||||
@@ -40,7 +40,7 @@ _rgbasm_completions() {
|
||||
[Q]="q-precision:unk"
|
||||
[r]="recursion-depth:unk"
|
||||
[s]="state:unk"
|
||||
[W]="warning:warning"
|
||||
[v]="verbose:normal"
|
||||
[X]="max-errors:unk"
|
||||
)
|
||||
# Parse command-line up to current word
|
||||
@@ -155,7 +155,7 @@ _rgbasm_completions() {
|
||||
parse_short_opt "$cur_word"
|
||||
|
||||
if [[ "$state" = 'normal' ]]; then
|
||||
mapfile -t COMPREPLY < <(compgen -W "${!opts[*]}" -P "$cur_word" ''; compgen -W '-MG -MP -MQ -MT' "$cur_word")
|
||||
mapfile -t COMPREPLY < <(compgen -W "${!opts[*]}" -P "$cur_word" ''; compgen -W '-MC -MG -MP -MQ -MT' "$cur_word")
|
||||
return 0
|
||||
elif [[ "$optlen" = "${#cur_word}" && "$state" != "warning" ]]; then
|
||||
# This short option group only awaits its argument!
|
||||
|
||||
@@ -7,23 +7,26 @@ _rgbfix_completions() {
|
||||
# Empty long opt = it doesn't exit
|
||||
# See the `state` variable below for info about `state_after`
|
||||
declare -A opts=(
|
||||
[V]="version:normal"
|
||||
[h]="help:normal"
|
||||
[j]="non-japanese:normal"
|
||||
[s]="sgb-compatible:normal"
|
||||
[v]="validate:normal"
|
||||
[V]="version:normal"
|
||||
[W]="warning:warning"
|
||||
[w]=":normal"
|
||||
[C]="color-only:normal"
|
||||
[c]="color-compatible:normal"
|
||||
[f]="fix-spec:fix-spec"
|
||||
[i]="game-id:unk"
|
||||
[j]="non-japanese:normal"
|
||||
[k]="new-licensee:unk"
|
||||
[L]="custom-logo:glob-*.1bpp"
|
||||
[l]="old-licensee:unk"
|
||||
[m]="mbc-type:mbc"
|
||||
[n]="rom-version:unk"
|
||||
[o]="output:glob-*.gb *.gbc *.sgb"
|
||||
[p]="pad-value:unk"
|
||||
[r]="ram-size:unk"
|
||||
[s]="sgb-compatible:normal"
|
||||
[t]="title:unk"
|
||||
[v]="validate:normal"
|
||||
)
|
||||
# Parse command-line up to current word
|
||||
local opt_ena=true
|
||||
@@ -139,6 +142,16 @@ _rgbfix_completions() {
|
||||
case "$state" in
|
||||
unk) # Return with no replies: no idea what to complete!
|
||||
;;
|
||||
warning)
|
||||
mapfile -t COMPREPLY < <(compgen -W "
|
||||
mbc
|
||||
overwrite
|
||||
sgb
|
||||
truncation
|
||||
all
|
||||
everything
|
||||
error" -P "${cur_word:0:$optlen}" -- "${cur_word:$optlen}")
|
||||
;;
|
||||
fix-spec)
|
||||
COMPREPLY=( "${cur_word}"{l,h,g,L,H,G} )
|
||||
;;
|
||||
|
||||
@@ -7,35 +7,38 @@ _rgbgfx_completions() {
|
||||
# Empty long opt = it doesn't exit
|
||||
# See the `state` variable below for info about `state_after`
|
||||
declare -A opts=(
|
||||
[V]="version:normal"
|
||||
[h]="help:normal"
|
||||
[C]="color-curve:normal"
|
||||
[m]="mirror-tiles:normal"
|
||||
[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"
|
||||
[V]="version:normal"
|
||||
[W]="warning:warning"
|
||||
[w]=":normal"
|
||||
[A]="auto-attr-map:normal"
|
||||
[a]="attr-map:glob-*.attrmap"
|
||||
[B]="background-color:unk"
|
||||
[b]="base-tiles:unk"
|
||||
[C]="color-curve:normal"
|
||||
[c]="colors:unk"
|
||||
[d]="depth:unk"
|
||||
[i]="input-tileset:glob-*.2bpp"
|
||||
[L]="slice:unk"
|
||||
[m]="mirror-tiles:normal"
|
||||
[N]="nb-tiles:unk"
|
||||
[n]="nb-palettes:unk"
|
||||
[O]="group-outputs:normal"
|
||||
[o]="output:glob-*.2bpp"
|
||||
[p]="palette:glob-*.pal"
|
||||
[P]="auto-palette:normal"
|
||||
[q]="palette-map:glob-*.palmap"
|
||||
[p]="palette:glob-*.pal"
|
||||
[Q]="auto-palette-map:normal"
|
||||
[q]="palette-map:glob-*.palmap"
|
||||
[r]="reverse:unk"
|
||||
[s]="palette-size:unk"
|
||||
[t]="tilemap:glob-*.tilemap"
|
||||
[T]="auto-tilemap:normal"
|
||||
[t]="tilemap:glob-*.tilemap"
|
||||
[u]="unique-tiles:normal"
|
||||
[v]="verbose:normal"
|
||||
[X]="mirror-x:normal"
|
||||
[x]="trim-end:unk"
|
||||
[Y]="mirror-y:normal"
|
||||
[Z]="columns:normal"
|
||||
)
|
||||
# Parse command-line up to current word
|
||||
local opt_ena=true
|
||||
@@ -151,6 +154,14 @@ _rgbgfx_completions() {
|
||||
case "$state" in
|
||||
unk) # Return with no replies: no idea what to complete!
|
||||
;;
|
||||
warning)
|
||||
mapfile -t COMPREPLY < <(compgen -W "
|
||||
embedded
|
||||
trim-nonempty
|
||||
all
|
||||
everything
|
||||
error" -P "${cur_word:0:$optlen}" -- "${cur_word:$optlen}")
|
||||
;;
|
||||
normal) # Acts like a glob...
|
||||
state="glob-*.png"
|
||||
;&
|
||||
|
||||
@@ -7,20 +7,21 @@ _rgblink_completions() {
|
||||
# Empty long opt = it doesn't exit
|
||||
# See the `state` variable below for info about `state_after`
|
||||
declare -A opts=(
|
||||
[V]="version:normal"
|
||||
[h]="help:normal"
|
||||
[d]="dmg:normal"
|
||||
[t]="tiny:normal"
|
||||
[v]="verbose:normal"
|
||||
[w]="wramx:normal"
|
||||
[x]="nopad:normal"
|
||||
[l]="linkerscript:glob-*"
|
||||
[V]="version:normal"
|
||||
[W]="warning:warning"
|
||||
[M]="no-sym-in-map:normal"
|
||||
[d]="dmg:normal"
|
||||
[l]="linkerscript:glob-*"
|
||||
[m]="map:glob-*.map"
|
||||
[n]="sym:glob-*.sym"
|
||||
[O]="overlay:glob-*.gb *.gbc *.sgb"
|
||||
[o]="output:glob-*.gb *.gbc *.sgb"
|
||||
[p]="pad:unk"
|
||||
[t]="tiny:normal"
|
||||
[v]="verbose:normal"
|
||||
[w]="wramx:normal"
|
||||
[x]="nopad:normal"
|
||||
)
|
||||
# Parse command-line up to current word
|
||||
local opt_ena=true
|
||||
@@ -136,6 +137,18 @@ _rgblink_completions() {
|
||||
case "$state" in
|
||||
unk) # Return with no replies: no idea what to complete!
|
||||
;;
|
||||
warning)
|
||||
mapfile -t COMPREPLY < <(compgen -W "
|
||||
assert
|
||||
div
|
||||
obsolete
|
||||
shift
|
||||
shift-amount
|
||||
truncation
|
||||
all
|
||||
everything
|
||||
error" -P "${cur_word:0:$optlen}" -- "${cur_word:$optlen}")
|
||||
;;
|
||||
normal) # Acts like a glob...
|
||||
state="glob-*.o *.obj"
|
||||
;&
|
||||
|
||||
@@ -26,11 +26,23 @@ dependency include/linkdefs.hpp man/rgbds.5 \
|
||||
dependency src/asm/parser.y man/rgbasm.5 \
|
||||
"Was the rgbasm grammar changed?"
|
||||
|
||||
dependency src/asm/actions.cpp man/rgbasm.5 \
|
||||
"Was the rgbasm grammar changed?"
|
||||
|
||||
dependency src/link/script.y man/rgblink.5 \
|
||||
"Was the linker script grammar changed?"
|
||||
|
||||
dependency include/asm/warning.hpp man/rgbasm.1 \
|
||||
dependency src/link/layout.cpp man/rgblink.5 \
|
||||
"Was the linker script grammar changed?"
|
||||
|
||||
dependency include/asm/warning.hpp man/rgbasm.1 \
|
||||
"Were the rgbasm warnings changed?"
|
||||
dependency include/link/warning.hpp man/rgblink.1 \
|
||||
"Were the rgblink warnings changed?"
|
||||
dependency include/fix/warning.hpp man/rgbfix.1 \
|
||||
"Were the rgbfix warnings changed?"
|
||||
dependency include/gfx/warning.hpp man/rgbgfx.1 \
|
||||
"Were the rgbgfx warnings changed?"
|
||||
|
||||
dependency src/asm/object.cpp include/linkdefs.hpp \
|
||||
"Should the object file revision be bumped?"
|
||||
|
||||
15
contrib/checkformat.bash
Executable file
15
contrib/checkformat.bash
Executable file
@@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
clang-format --version
|
||||
|
||||
find . -type f \( -iname '*.hpp' -o -iname '*.cpp' \) -exec clang-format -i {} +
|
||||
|
||||
if ! git diff-index --quiet HEAD --; then
|
||||
echo 'Unformatted files:'
|
||||
git diff-index --name-only HEAD --
|
||||
echo
|
||||
git diff HEAD --
|
||||
exit 1
|
||||
fi
|
||||
@@ -7,20 +7,29 @@ make coverage -j
|
||||
# Run the tests
|
||||
pushd test
|
||||
./fetch-test-deps.sh
|
||||
./run-tests.sh
|
||||
if [[ $# -eq 0 ]]; then
|
||||
./run-tests.sh
|
||||
else
|
||||
./run-tests.sh --os "$1"
|
||||
fi
|
||||
popd
|
||||
|
||||
# Generate coverage logs
|
||||
gcov src/**/*.cpp
|
||||
mkdir -p coverage
|
||||
|
||||
# Generate coverage report
|
||||
lcov -c --no-external -d . -o coverage/coverage.info
|
||||
genhtml -f -s -o coverage/ coverage/coverage.info
|
||||
# Generate coverage report, excluding Bison-generated files
|
||||
COVERAGE_INFO=coverage/coverage.info
|
||||
lcov -c --no-external -d . -o "$COVERAGE_INFO"
|
||||
lcov -r "$COVERAGE_INFO" src/asm/parser.{hpp,cpp} src/link/script.{hpp,cpp} -o "$COVERAGE_INFO"
|
||||
genhtml --dark-mode -f -s -o coverage/ "$COVERAGE_INFO"
|
||||
|
||||
# Open report in web browser
|
||||
if [ "$(uname)" == "Darwin" ]; then
|
||||
open coverage/index.html
|
||||
else
|
||||
xdg-open coverage/index.html
|
||||
# Check whether running from coverage.yml workflow
|
||||
if [ "$1" != "ubuntu-ci" ]; then
|
||||
# Open report in web browser
|
||||
if [ "$(uname)" == "Darwin" ]; then
|
||||
open coverage/index.html
|
||||
else
|
||||
xdg-open coverage/index.html
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -32,14 +32,15 @@ diff <(xxd "$1") <(xxd "$2") | while read -r LINE; do
|
||||
# Ignore comment lines, only pick matching bank
|
||||
# (The bank regex ignores comments already, make `cut` and `tr` process less lines)
|
||||
grep -Ei "$(printf "^%02x:" $BANK)" "$SYMFILE" |
|
||||
sed "s/$(printf "^%02x:" $BANK)/0x/g" |
|
||||
cut -d ';' -f 1 |
|
||||
tr -d "\r" |
|
||||
sort -g |
|
||||
while read -r SYMADDR SYM; do
|
||||
SYMADDR=$((0x${SYMADDR#*:}))
|
||||
SYMADDR=$(($SYMADDR))
|
||||
if [[ $SYMADDR -le $ADDR ]]; then
|
||||
printf " (%s+0x%x)\n" "$SYM" $((ADDR - SYMADDR))
|
||||
fi
|
||||
# TODO: assumes sorted sym files
|
||||
done | tail -n 1
|
||||
fi)
|
||||
printf "%02x:%04x %s\n" $BANK $ADDR "$EXTRA"
|
||||
|
||||
@@ -23,15 +23,13 @@ _rgbasm_warnings() {
|
||||
'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'
|
||||
'shift-amount:Warn when a shift'\''s operand is negative or \> 32'
|
||||
'truncation:Warn when implicit truncation loses bits'
|
||||
'unmapped-char:Warn on unmapped character'
|
||||
'unmatched-directive:Warn on unmatched directive pair'
|
||||
'unterminated-load:Warn on LOAD without ENDL'
|
||||
'user:Warn when executing the WARN built-in'
|
||||
)
|
||||
# TODO: handle `no-` and `error=` somehow?
|
||||
# TODO: handle `=0|1|2` levels for `numeric-string`, `purge`, `truncation`, and `unmapped-char`?
|
||||
_describe warning warnings
|
||||
}
|
||||
|
||||
@@ -48,9 +46,10 @@ 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}"+[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]'
|
||||
'(-M --dependfile)'{-M,--dependfile}"+[Write dependencies in Makefile format]:output file:_files -g '*.{d,mk}'"
|
||||
-MC'[Continue after missing dependencies]'
|
||||
-MG'[Assume missing dependencies should be generated]'
|
||||
-MP'[Add phony targets to all dependencies]'
|
||||
'*'-MT"+[Add a target to the rules]:target:_files -g '*.{d,mk,o}'"
|
||||
'*'-MQ"+[Add a target to the rules]:target:_files -g '*.{d,mk,o}'"
|
||||
'(-o --output)'{-o,--output}'+[Output file]:output file:_files'
|
||||
|
||||
@@ -34,6 +34,21 @@ _mbc_names() {
|
||||
_describe "MBC name" mbc_names
|
||||
}
|
||||
|
||||
_rgbfix_warnings() {
|
||||
local warnings=(
|
||||
'error:Turn all warnings into errors'
|
||||
|
||||
'all:Enable most warning messages'
|
||||
'everything:Enable literally everything'
|
||||
|
||||
'mbc:Warn about issues with MBC specs'
|
||||
'overwrite:Warn when overwriting non-zero bytes'
|
||||
'sgb:Warn when SGB flag conflicts with old licensee code'
|
||||
'truncation:Warn when values are truncated to fit'
|
||||
)
|
||||
_describe warning warnings
|
||||
}
|
||||
|
||||
local args=(
|
||||
# Arguments are listed here in the same order as in the manual, except for the version and help
|
||||
'(- : * options)'{-V,--version}'[Print version number and exit]'
|
||||
@@ -45,6 +60,7 @@ local args=(
|
||||
'(-O --overwrite)'{-O,--overwrite}'[Allow overwriting non-zero bytes]'
|
||||
'(-s --sgb-compatible)'{-s,--sgb-compatible}'[Set the SGB flag]'
|
||||
'(-f --fix-spec -v --validate)'{-v,--validate}'[Shorthand for -f lhg]'
|
||||
-w'[Disable all warnings]'
|
||||
|
||||
'(-f --fix-spec -v --validate)'{-f,--fix-spec}'+[Fix or trash some header values]:fix spec:'
|
||||
'(-i --game-id)'{-i,--game-id}'+[Set game ID string]:4-char game ID:'
|
||||
@@ -53,9 +69,11 @@ local args=(
|
||||
'(-L --logo)'{-L,--logo}'+[Set custom logo]:1bpp image:'
|
||||
'(-m --mbc-type)'{-m,--mbc-type}"+[Set MBC flags]:mbc name:_mbc_names"
|
||||
'(-n --rom-version)'{-n,--rom-version}'+[Set ROM version]:rom version byte:'
|
||||
'(-o --output)'{-o,--output}"+[Output file]:output file:_files -g '*.{gb,sgb,gbc}'"
|
||||
'(-p --pad-value)'{-p,--pad-value}'+[Pad to next valid size using this byte as padding]:padding byte:'
|
||||
'(-r --ram-size)'{-r,--ram-size}'+[Set RAM size]:ram size byte:'
|
||||
'(-t --title)'{-t,--title}'+[Set title string]:11-char title string:'
|
||||
'(-W --warning)'{-W,--warning}'+[Toggle warning flags]:warning flag:_rgbfix_warnings'
|
||||
|
||||
'*'":ROM files:_files -g '*.{gb,sgb,gbc}'"
|
||||
)
|
||||
|
||||
@@ -9,6 +9,19 @@ _depths() {
|
||||
_describe 'bit depth' depths
|
||||
}
|
||||
|
||||
_rgbgfx_warnings() {
|
||||
local warnings=(
|
||||
'error:Turn all warnings into errors'
|
||||
|
||||
'all:Enable most warning messages'
|
||||
'everything:Enable literally everything'
|
||||
|
||||
'embedded:Warn when using embedded PLTE without "-c embedded"'
|
||||
'trim-nonempty:Warn when "-x" trims nonempty tiles'
|
||||
)
|
||||
_describe warning warnings
|
||||
}
|
||||
|
||||
local args=(
|
||||
# Arguments are listed here in the same order as in the manual, except for the version and help
|
||||
'(- : * options)'{-V,--version}'[Print version number and exit]'
|
||||
@@ -22,12 +35,14 @@ local args=(
|
||||
'(-q --palette-map -Q --auto-palette-map)'{-Q,--auto-palette-map}'[Shortcut for -p <file>.palmap]'
|
||||
'(-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]'
|
||||
'(-v --verbose)'{-v,--verbose}'[Enable verbose output]'
|
||||
-w'[Disable all warnings]'
|
||||
'(-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 --background-color)'{-B,--background-color}'+[Ignore tiles containing only specified color]:color:'
|
||||
'(-b --base-tiles)'{-b,--base-tiles}'+[Base tile IDs for tile map output]:base tile IDs:'
|
||||
'(-c --colors)'{-c,--colors}'+[Specify color palettes]:palette spec:'
|
||||
'(-d --depth)'{-d,--depth}'+[Set bit depth]:bit depth:_depths'
|
||||
@@ -41,6 +56,7 @@ local args=(
|
||||
'(-r --reverse)'{-r,--reverse}'+[Yield an image from binary data]:image width (in tiles):'
|
||||
'(-s --palette-size)'{-s,--palette-size}'+[Limit palette size]:palette size:'
|
||||
'(-t --tilemap -T --auto-tilemap)'{-t,--tilemap}'+[Generate a map of tile indices]:tilemap file:_files'
|
||||
'(-W --warning)'{-W,--warning}'+[Toggle warning flags]:warning flag:_rgbgfx_warnings'
|
||||
'(-x --trim-end)'{-x,--trim-end}'+[Trim end of output by this many tiles]:tile count:'
|
||||
|
||||
":input png file:_files -g '*.png'"
|
||||
|
||||
@@ -1,5 +1,22 @@
|
||||
#compdef rgblink
|
||||
|
||||
_rgblink_warnings() {
|
||||
local warnings=(
|
||||
'error:Turn all warnings into errors'
|
||||
|
||||
'all:Enable most warning messages'
|
||||
'everything:Enable literally everything'
|
||||
|
||||
'assert:Warn when WARN-type asserts fail'
|
||||
'div:Warn when dividing the smallest int by -1'
|
||||
'obsolete:Warn when using deprecated features'
|
||||
'shift:Warn when shifting negative values'
|
||||
'shift-amount:Warn when a shift'\''s operand is negative or \> 32'
|
||||
'truncation:Warn when implicit truncation loses bits'
|
||||
)
|
||||
_describe warning warnings
|
||||
}
|
||||
|
||||
local args=(
|
||||
# Arguments are listed here in the same order as in the manual, except for the version and help
|
||||
'(- : * options)'{-V,--version}'[Print version number and exit]'
|
||||
@@ -19,6 +36,7 @@ local args=(
|
||||
'(-o --output)'{-o,--output}"+[Write ROM image to this file]:rom file:_files -g '*.{gb,sgb,gbc}'"
|
||||
'(-p --pad-value)'{-p,--pad-value}'+[Set padding byte]:padding byte:'
|
||||
'(-S --scramble)'{-s,--scramble}'+[Activate scrambling]:scramble spec'
|
||||
'(-W --warning)'{-W,--warning}'+[Toggle warning flags]:warning flag:_rgblink_warnings'
|
||||
|
||||
'*'":object files:_files -g '*.o'"
|
||||
)
|
||||
|
||||
42
include/asm/actions.hpp
Normal file
42
include/asm/actions.hpp
Normal file
@@ -0,0 +1,42 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_ASM_ACTIONS_HPP
|
||||
#define RGBDS_ASM_ACTIONS_HPP
|
||||
|
||||
#include <optional>
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include "asm/output.hpp" // AssertionType
|
||||
#include "asm/rpn.hpp" // RPNCommand
|
||||
|
||||
std::optional<std::string> act_ReadFile(std::string const &name, uint32_t maxLen);
|
||||
|
||||
uint32_t act_StringToNum(std::vector<int32_t> const &str);
|
||||
|
||||
size_t act_StringLen(std::string const &str, bool printErrors);
|
||||
std::string act_StringSlice(std::string const &str, uint32_t start, uint32_t stop);
|
||||
std::string act_StringSub(std::string const &str, uint32_t pos, uint32_t len);
|
||||
|
||||
size_t act_CharLen(std::string const &str);
|
||||
std::string act_StringChar(std::string const &str, uint32_t idx);
|
||||
std::string act_CharSub(std::string const &str, uint32_t pos);
|
||||
int32_t act_CharCmp(std::string_view str1, std::string_view str2);
|
||||
|
||||
uint32_t act_AdjustNegativeIndex(int32_t idx, size_t len, char const *functionName);
|
||||
uint32_t act_AdjustNegativePos(int32_t pos, size_t len, char const *functionName);
|
||||
|
||||
std::string act_StringReplace(std::string_view str, std::string const &old, std::string const &rep);
|
||||
std::string act_StringFormat(
|
||||
std::string const &spec, std::vector<std::variant<uint32_t, std::string>> const &args
|
||||
);
|
||||
|
||||
void act_CompoundAssignment(std::string const &symName, RPNCommand op, int32_t constValue);
|
||||
|
||||
void act_FailAssert(AssertionType type);
|
||||
void act_FailAssertMsg(AssertionType type, std::string const &message);
|
||||
|
||||
#endif // RGBDS_ASM_ACTIONS_HPP
|
||||
@@ -3,6 +3,7 @@
|
||||
#ifndef RGBDS_ASM_CHARMAP_HPP
|
||||
#define RGBDS_ASM_CHARMAP_HPP
|
||||
|
||||
#include <optional>
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
@@ -20,8 +21,11 @@ void charmap_Push();
|
||||
void charmap_Pop();
|
||||
void charmap_CheckStack();
|
||||
void charmap_Add(std::string const &mapping, std::vector<int32_t> &&value);
|
||||
bool charmap_HasChar(std::string const &input);
|
||||
bool charmap_HasChar(std::string const &mapping);
|
||||
size_t charmap_CharSize(std::string const &mapping);
|
||||
std::optional<int32_t> charmap_CharValue(std::string const &mapping, size_t idx);
|
||||
std::vector<int32_t> charmap_Convert(std::string const &input);
|
||||
size_t charmap_ConvertNext(std::string_view &input, std::vector<int32_t> *output);
|
||||
std::string charmap_Reverse(std::vector<int32_t> const &value, bool &unique);
|
||||
|
||||
#endif // RGBDS_ASM_CHARMAP_HPP
|
||||
|
||||
@@ -5,9 +5,6 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
extern uint8_t fixPrecision;
|
||||
|
||||
uint8_t fix_Precision();
|
||||
int32_t fix_Sin(int32_t i, int32_t q);
|
||||
int32_t fix_Cos(int32_t i, int32_t q);
|
||||
int32_t fix_Tan(int32_t i, int32_t q);
|
||||
|
||||
@@ -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;
|
||||
Either<
|
||||
std::variant<
|
||||
std::vector<uint32_t>, // NODE_REPT
|
||||
std::string // NODE_FILE, NODE_MACRO
|
||||
>
|
||||
@@ -34,33 +34,34 @@ struct FileStackNode {
|
||||
uint32_t ID = UINT32_MAX;
|
||||
|
||||
// REPT iteration counts since last named node, in reverse depth order
|
||||
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>>(); }
|
||||
std::vector<uint32_t> &iters() { return std::get<std::vector<uint32_t>>(data); }
|
||||
std::vector<uint32_t> const &iters() const { return std::get<std::vector<uint32_t>>(data); }
|
||||
// File name for files, file::macro name for macros
|
||||
std::string &name() { return data.get<std::string>(); }
|
||||
std::string const &name() const { return data.get<std::string>(); }
|
||||
std::string &name() { return std::get<std::string>(data); }
|
||||
std::string const &name() const { return std::get<std::string>(data); }
|
||||
|
||||
FileStackNode(FileStackNodeType type_, Either<std::vector<uint32_t>, std::string> data_)
|
||||
FileStackNode(FileStackNodeType type_, std::variant<std::vector<uint32_t>, std::string> data_)
|
||||
: type(type_), data(data_) {}
|
||||
|
||||
std::string const &dump(uint32_t curLineNo) const;
|
||||
std::string reptChain() const;
|
||||
};
|
||||
|
||||
extern size_t maxRecursionDepth;
|
||||
|
||||
struct MacroArgs;
|
||||
|
||||
void fstk_DumpCurrent();
|
||||
bool fstk_DumpCurrent();
|
||||
std::shared_ptr<FileStackNode> fstk_GetFileStack();
|
||||
std::shared_ptr<std::string> fstk_GetUniqueIDStr();
|
||||
MacroArgs *fstk_GetCurrentMacroArgs();
|
||||
|
||||
void fstk_AddIncludePath(std::string const &path);
|
||||
void fstk_SetPreIncludeFile(std::string const &path);
|
||||
void fstk_AddPreIncludeFile(std::string const &path);
|
||||
std::optional<std::string> fstk_FindFile(std::string const &path);
|
||||
bool fstk_FileError(std::string const &path, char const *functionName);
|
||||
bool fstk_FailedOnMissingInclude();
|
||||
|
||||
bool yywrap();
|
||||
void fstk_RunInclude(std::string const &path, bool updateStateNow);
|
||||
bool fstk_RunInclude(std::string const &path);
|
||||
void fstk_RunMacro(std::string const ¯oName, std::shared_ptr<MacroArgs> macroArgs);
|
||||
void fstk_RunRept(uint32_t count, int32_t reptLineNo, ContentSpan const &span);
|
||||
void fstk_RunFor(
|
||||
@@ -71,10 +72,9 @@ void fstk_RunFor(
|
||||
int32_t reptLineNo,
|
||||
ContentSpan const &span
|
||||
);
|
||||
void fstk_StopRept();
|
||||
bool fstk_Break();
|
||||
|
||||
void fstk_NewRecursionDepth(size_t newDepth);
|
||||
void fstk_Init(std::string const &mainPath, size_t maxDepth);
|
||||
void fstk_Init(std::string const &mainPath);
|
||||
|
||||
#endif // RGBDS_ASM_FSTACK_HPP
|
||||
|
||||
@@ -8,13 +8,13 @@
|
||||
#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
|
||||
// buffering performance when it doesn't/can't (e.g. when piping a file into RGBASM).
|
||||
// This value is a compromise between `LexerState` allocation performance when reading the entire
|
||||
// file works, and buffering performance when it doesn't (e.g. when piping a file into RGBASM).
|
||||
static constexpr size_t LEXER_BUF_SIZE = 64;
|
||||
// The buffer needs to be large enough for the maximum `lexerState->peek()` lookahead distance
|
||||
static_assert(LEXER_BUF_SIZE > 1, "Lexer buffer size is too small");
|
||||
@@ -84,6 +84,7 @@ struct LexerState {
|
||||
bool atLineStart;
|
||||
uint32_t lineNo;
|
||||
int lastToken;
|
||||
int nextToken;
|
||||
|
||||
std::deque<IfStackEntry> ifStack;
|
||||
|
||||
@@ -91,13 +92,12 @@ struct LexerState {
|
||||
size_t captureSize; // Amount of text captured
|
||||
std::shared_ptr<std::vector<char>> captureBuf; // Buffer to send the captured text to if set
|
||||
|
||||
bool disableMacroArgs;
|
||||
bool disableInterpolation;
|
||||
size_t macroArgScanDistance; // Max distance already scanned for macro args
|
||||
bool disableExpansions;
|
||||
size_t expansionScanDistance; // Max distance already scanned for expansions
|
||||
bool expandStrings;
|
||||
std::deque<Expansion> expansions; // Front is the innermost current expansion
|
||||
|
||||
Either<ViewedContent, BufferedContent> content;
|
||||
std::variant<std::monostate, ViewedContent, BufferedContent> content;
|
||||
|
||||
~LexerState();
|
||||
|
||||
@@ -109,30 +109,17 @@ struct LexerState {
|
||||
}
|
||||
|
||||
void setAsCurrentState();
|
||||
bool setFileAsNextState(std::string const &filePath, bool updateStateNow);
|
||||
void setFileAsNextState(std::string const &filePath, bool updateStateNow);
|
||||
void setViewAsNextState(char const *name, ContentSpan const &span, uint32_t lineNo_);
|
||||
|
||||
void clear(uint32_t lineNo_);
|
||||
};
|
||||
|
||||
extern char binDigits[2];
|
||||
extern char gfxDigits[4];
|
||||
|
||||
static inline void lexer_SetBinDigits(char const digits[2]) {
|
||||
binDigits[0] = digits[0];
|
||||
binDigits[1] = digits[1];
|
||||
}
|
||||
|
||||
static inline void lexer_SetGfxDigits(char const digits[4]) {
|
||||
gfxDigits[0] = digits[0];
|
||||
gfxDigits[1] = digits[1];
|
||||
gfxDigits[2] = digits[2];
|
||||
gfxDigits[3] = digits[3];
|
||||
}
|
||||
void lexer_SetBinDigits(char const digits[2]);
|
||||
void lexer_SetGfxDigits(char const digits[4]);
|
||||
|
||||
bool lexer_AtTopLevel();
|
||||
void lexer_RestartRept(uint32_t lineNo);
|
||||
void lexer_Init();
|
||||
void lexer_SetMode(LexerMode mode);
|
||||
void lexer_ToggleStringExpansion(bool enable);
|
||||
|
||||
|
||||
@@ -9,11 +9,11 @@
|
||||
#include <vector>
|
||||
|
||||
struct MacroArgs {
|
||||
unsigned int shift;
|
||||
uint32_t shift;
|
||||
std::vector<std::shared_ptr<std::string>> args;
|
||||
|
||||
uint32_t nbArgs() const { return args.size() - shift; }
|
||||
std::shared_ptr<std::string> getArg(uint32_t i) const;
|
||||
std::shared_ptr<std::string> getArg(int32_t i) const;
|
||||
std::shared_ptr<std::string> getAllArgs() const;
|
||||
|
||||
void appendArg(std::shared_ptr<std::string> arg);
|
||||
|
||||
@@ -3,16 +3,50 @@
|
||||
#ifndef RGBDS_ASM_MAIN_HPP
|
||||
#define RGBDS_ASM_MAIN_HPP
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string>
|
||||
|
||||
extern bool verbose;
|
||||
extern bool warnings; // True to enable warnings, false to disable them.
|
||||
enum MissingInclude {
|
||||
INC_ERROR, // A missing included file is an error that halts assembly
|
||||
GEN_EXIT, // A missing included file is assumed to be generated; exit normally
|
||||
GEN_CONTINUE, // A missing included file is assumed to be generated; continue assembling
|
||||
};
|
||||
|
||||
extern FILE *dependFile;
|
||||
extern std::string targetFileName;
|
||||
extern bool generatedMissingIncludes;
|
||||
extern bool failedOnMissingInclude;
|
||||
extern bool generatePhonyDeps;
|
||||
struct Options {
|
||||
uint8_t fixPrecision = 16; // -Q
|
||||
size_t maxRecursionDepth = 64; // -r
|
||||
char binDigits[2] = {'0', '1'}; // -b
|
||||
char gfxDigits[4] = {'0', '1', '2', '3'}; // -g
|
||||
bool verbose = false; // -v
|
||||
FILE *dependFile = nullptr; // -M
|
||||
std::string targetFileName; // -MQ, -MT
|
||||
MissingInclude missingIncludeState = INC_ERROR; // -MC, -MG
|
||||
bool generatePhonyDeps = false; // -MP
|
||||
std::string objectFileName; // -o
|
||||
uint8_t padByte = 0; // -p
|
||||
uint64_t maxErrors = 0; // -X
|
||||
|
||||
~Options() {
|
||||
if (dependFile) {
|
||||
fclose(dependFile);
|
||||
}
|
||||
}
|
||||
|
||||
void printDep(std::string const &depName) {
|
||||
if (dependFile) {
|
||||
fprintf(dependFile, "%s: %s\n", targetFileName.c_str(), depName.c_str());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
extern Options options;
|
||||
|
||||
#define verbosePrint(...) \
|
||||
do { \
|
||||
if (options.verbose) { \
|
||||
fprintf(stderr, __VA_ARGS__); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#endif // RGBDS_ASM_MAIN_HPP
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
void opt_B(char const chars[2]);
|
||||
void opt_G(char const chars[4]);
|
||||
void opt_B(char const binDigits[2]);
|
||||
void opt_G(char const gfxDigits[4]);
|
||||
void opt_P(uint8_t padByte);
|
||||
void opt_Q(uint8_t precision);
|
||||
void opt_Q(uint8_t fixPrecision);
|
||||
void opt_W(char const *flag);
|
||||
void opt_Parse(char const *option);
|
||||
|
||||
|
||||
@@ -16,10 +16,7 @@ struct FileStackNode;
|
||||
|
||||
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);
|
||||
void out_CreatePatch(uint32_t type, Expression const &expr, uint32_t ofs, uint32_t pcShift);
|
||||
void out_CreateAssert(
|
||||
AssertionType type, Expression const &expr, std::string const &message, uint32_t ofs
|
||||
|
||||
@@ -5,15 +5,15 @@
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include "either.hpp"
|
||||
#include "linkdefs.hpp"
|
||||
|
||||
struct Symbol;
|
||||
|
||||
struct Expression {
|
||||
Either<
|
||||
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
|
||||
>
|
||||
@@ -22,17 +22,8 @@ struct Expression {
|
||||
std::vector<uint8_t> rpn{}; // Bytes serializing the RPN expression
|
||||
uint32_t rpnPatchSize = 0; // Size the expression will take in the object file
|
||||
|
||||
Expression() = default;
|
||||
Expression(Expression &&) = default;
|
||||
#ifdef _MSC_VER
|
||||
// MSVC and WinFlexBison won't build without this...
|
||||
Expression(Expression const &) = default;
|
||||
#endif
|
||||
|
||||
Expression &operator=(Expression &&) = default;
|
||||
|
||||
bool isKnown() const { return data.holds<int32_t>(); }
|
||||
int32_t value() const { return data.get<int32_t>(); }
|
||||
bool isKnown() const { return std::holds_alternative<int32_t>(data); }
|
||||
int32_t value() const { return std::get<int32_t>(data); }
|
||||
|
||||
int32_t getConstVal() const;
|
||||
Symbol const *symbolOf() const;
|
||||
@@ -51,6 +42,7 @@ struct Expression {
|
||||
|
||||
bool makeCheckHRAM();
|
||||
void makeCheckRST();
|
||||
void makeCheckBitIndex(uint8_t mask);
|
||||
|
||||
void checkNBit(uint8_t n) const;
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
@@ -12,8 +13,6 @@
|
||||
|
||||
#include "linkdefs.hpp"
|
||||
|
||||
extern uint8_t fillByte;
|
||||
|
||||
struct Expression;
|
||||
struct FileStackNode;
|
||||
struct Section;
|
||||
@@ -42,6 +41,7 @@ struct Section {
|
||||
std::deque<Patch> patches;
|
||||
std::vector<uint8_t> data;
|
||||
|
||||
uint32_t getID() const; // ID of the section in the object file (`UINT32_MAX` if none)
|
||||
bool isSizeKnown() const;
|
||||
};
|
||||
|
||||
@@ -51,9 +51,8 @@ struct SectionSpec {
|
||||
uint16_t alignOfs;
|
||||
};
|
||||
|
||||
extern std::deque<Section> sectionList;
|
||||
extern std::unordered_map<std::string, size_t> sectionMap; // Indexes into `sectionList`
|
||||
extern Section *currentSection;
|
||||
size_t sect_CountSections();
|
||||
void sect_ForEach(void (*callback)(Section &));
|
||||
|
||||
Section *sect_FindSectionByName(std::string const &name);
|
||||
void sect_NewSection(
|
||||
@@ -76,6 +75,10 @@ void sect_CheckLoadClosed();
|
||||
Section *sect_GetSymbolSection();
|
||||
uint32_t sect_GetSymbolOffset();
|
||||
uint32_t sect_GetOutputOffset();
|
||||
std::optional<uint32_t> sect_GetOutputBank();
|
||||
|
||||
Patch *sect_AddOutputPatch();
|
||||
|
||||
uint32_t sect_GetAlignBytes(uint8_t alignment, uint16_t offset);
|
||||
void sect_AlignPC(uint8_t alignment, uint16_t offset);
|
||||
|
||||
@@ -87,21 +90,23 @@ void sect_EndUnion();
|
||||
void sect_CheckUnionClosed();
|
||||
|
||||
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_ByteString(std::vector<int32_t> const &str);
|
||||
void sect_WordString(std::vector<int32_t> const &str);
|
||||
void sect_LongString(std::vector<int32_t> const &str);
|
||||
void sect_Skip(uint32_t skip, bool ds);
|
||||
void sect_RelByte(Expression &expr, uint32_t pcShift);
|
||||
void sect_RelBytes(uint32_t n, std::vector<Expression> &exprs);
|
||||
void sect_RelWord(Expression &expr, uint32_t pcShift);
|
||||
void sect_RelLong(Expression &expr, uint32_t pcShift);
|
||||
void sect_PCRelByte(Expression &expr, uint32_t pcShift);
|
||||
void sect_BinaryFile(std::string const &name, int32_t startPos);
|
||||
void sect_BinaryFileSlice(std::string const &name, int32_t startPos, int32_t length);
|
||||
void sect_RelByte(Expression const &expr, uint32_t pcShift);
|
||||
void sect_RelBytes(uint32_t n, std::vector<Expression> const &exprs);
|
||||
void sect_RelWord(Expression const &expr, uint32_t pcShift);
|
||||
void sect_RelLong(Expression const &expr, uint32_t pcShift);
|
||||
void sect_PCRelByte(Expression const &expr, uint32_t pcShift);
|
||||
bool sect_BinaryFile(std::string const &name, uint32_t startPos);
|
||||
bool sect_BinaryFileSlice(std::string const &name, uint32_t startPos, uint32_t length);
|
||||
|
||||
void sect_EndSection();
|
||||
void sect_PushSection();
|
||||
void sect_PopSection();
|
||||
void sect_CheckStack();
|
||||
|
||||
std::string sect_PushSectionFragmentLiteral();
|
||||
|
||||
#endif // RGBDS_ASM_SECTION_HPP
|
||||
|
||||
@@ -24,8 +24,8 @@ enum SymbolType {
|
||||
SYM_REF // Forward reference to a label
|
||||
};
|
||||
|
||||
struct Symbol; // For the `sym_IsPC` forward declaration
|
||||
bool sym_IsPC(Symbol const *sym); // For the inline `getSection` method
|
||||
struct Symbol; // Forward declaration for `sym_IsPC`
|
||||
bool sym_IsPC(Symbol const *sym); // Forward declaration for `getSection`
|
||||
|
||||
struct Symbol {
|
||||
std::string name;
|
||||
@@ -82,7 +82,6 @@ Symbol *sym_RedefEqu(std::string const &symName, int32_t value);
|
||||
Symbol *sym_AddVar(std::string const &symName, int32_t value);
|
||||
int32_t sym_GetRSValue();
|
||||
void sym_SetRSValue(int32_t value);
|
||||
uint32_t sym_GetConstantValue(std::string const &symName);
|
||||
// Find a symbol by exact name, bypassing expansion checks
|
||||
Symbol *sym_FindExactSymbol(std::string const &symName);
|
||||
// Find a symbol, possibly scoped, by name
|
||||
|
||||
@@ -3,7 +3,16 @@
|
||||
#ifndef RGBDS_ASM_WARNING_HPP
|
||||
#define RGBDS_ASM_WARNING_HPP
|
||||
|
||||
extern unsigned int nbErrors, maxErrors;
|
||||
#include <functional>
|
||||
|
||||
#include "diagnostics.hpp"
|
||||
|
||||
enum WarningLevel {
|
||||
LEVEL_DEFAULT, // Warnings that are enabled by default
|
||||
LEVEL_ALL, // Warnings that probably indicate an error
|
||||
LEVEL_EXTRA, // Warnings that are less likely to indicate an error
|
||||
LEVEL_EVERYTHING, // Literally every warning
|
||||
};
|
||||
|
||||
enum WarningID {
|
||||
WARNING_ASSERT, // Assertions
|
||||
@@ -43,24 +52,7 @@ enum WarningID {
|
||||
NB_WARNINGS,
|
||||
};
|
||||
|
||||
enum WarningAbled { WARNING_DEFAULT, WARNING_ENABLED, WARNING_DISABLED };
|
||||
|
||||
struct WarningState {
|
||||
WarningAbled state;
|
||||
WarningAbled error;
|
||||
|
||||
void update(WarningState other);
|
||||
};
|
||||
|
||||
struct Diagnostics {
|
||||
WarningState flagStates[NB_WARNINGS];
|
||||
WarningState metaStates[NB_WARNINGS];
|
||||
};
|
||||
|
||||
extern Diagnostics warningStates;
|
||||
extern bool warningsAreErrors;
|
||||
|
||||
void processWarningFlag(char const *flag);
|
||||
extern Diagnostics<WarningLevel, WarningID> warnings;
|
||||
|
||||
// Used to warn the user about problems that don't prevent the generation of
|
||||
// valid code.
|
||||
@@ -73,7 +65,7 @@ void warning(WarningID id, char const *fmt, ...);
|
||||
// It is also used when the assembler goes into an invalid state (for example,
|
||||
// when it fails to allocate memory).
|
||||
[[gnu::format(printf, 1, 2), noreturn]]
|
||||
void fatalerror(char const *fmt, ...);
|
||||
void fatal(char const *fmt, ...);
|
||||
|
||||
// Used for errors that make it impossible to assemble correctly, but don't
|
||||
// affect the following code. The code will fail to assemble but the user will
|
||||
@@ -82,4 +74,12 @@ void fatalerror(char const *fmt, ...);
|
||||
[[gnu::format(printf, 1, 2)]]
|
||||
void error(char const *fmt, ...);
|
||||
|
||||
// Used for errors that make it impossible to assemble correctly, but don't
|
||||
// affect the following code. The code will fail to assemble but the user will
|
||||
// get a list of all errors at the end, making it easier to fix all of them at
|
||||
// once.
|
||||
void error(std::function<void()> callback);
|
||||
|
||||
void requireZeroErrors();
|
||||
|
||||
#endif // RGBDS_ASM_WARNING_HPP
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_DEFAULT_INIT_ALLOC_HPP
|
||||
#define RGBDS_DEFAULT_INIT_ALLOC_HPP
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
// Allocator adaptor that interposes construct() calls to convert value-initialization
|
||||
// (which is what you get with e.g. `vector::resize`) into default-initialization (which does not
|
||||
// zero out non-class types).
|
||||
// From
|
||||
// https://stackoverflow.com/questions/21028299/is-this-behavior-of-vectorresizesize-type-n-under-c11-and-boost-container/21028912#21028912
|
||||
template<typename T, typename A = std::allocator<T>>
|
||||
class default_init_allocator : public A {
|
||||
using a_t = std::allocator_traits<A>;
|
||||
public:
|
||||
template<typename U>
|
||||
struct rebind {
|
||||
using other = default_init_allocator<U, typename a_t::template rebind_alloc<U>>;
|
||||
};
|
||||
|
||||
using A::A; // Inherit the allocator's constructors
|
||||
|
||||
template<typename U>
|
||||
void construct(U *ptr) noexcept(std::is_nothrow_default_constructible_v<U>) {
|
||||
::new (static_cast<void *>(ptr)) U;
|
||||
}
|
||||
template<typename U, typename... Args>
|
||||
void construct(U *ptr, Args &&...args) {
|
||||
a_t::construct(static_cast<A &>(*this), ptr, std::forward<Args>(args)...);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
using DefaultInitVec = std::vector<T, default_init_allocator<T>>;
|
||||
|
||||
#endif // RGBDS_DEFAULT_INIT_ALLOC_HPP
|
||||
212
include/diagnostics.hpp
Normal file
212
include/diagnostics.hpp
Normal file
@@ -0,0 +1,212 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_DIAGNOSTICS_HPP
|
||||
#define RGBDS_DIAGNOSTICS_HPP
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <optional>
|
||||
#include <stdarg.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "helpers.hpp"
|
||||
#include "itertools.hpp"
|
||||
|
||||
[[gnu::format(printf, 1, 2)]]
|
||||
void warnx(char const *fmt, ...);
|
||||
|
||||
enum WarningAbled { WARNING_DEFAULT, WARNING_ENABLED, WARNING_DISABLED };
|
||||
|
||||
struct WarningState {
|
||||
WarningAbled state;
|
||||
WarningAbled error;
|
||||
|
||||
void update(WarningState other);
|
||||
};
|
||||
|
||||
std::pair<WarningState, std::optional<uint32_t>> getInitialWarningState(std::string &flag);
|
||||
|
||||
template<typename L>
|
||||
struct WarningFlag {
|
||||
char const *name;
|
||||
L level;
|
||||
};
|
||||
|
||||
enum WarningBehavior { DISABLED, ENABLED, ERROR };
|
||||
|
||||
template<typename W>
|
||||
struct ParamWarning {
|
||||
W firstID;
|
||||
W lastID;
|
||||
uint8_t defaultLevel;
|
||||
};
|
||||
|
||||
template<typename W>
|
||||
struct DiagnosticsState {
|
||||
WarningState flagStates[W::NB_WARNINGS];
|
||||
WarningState metaStates[W::NB_WARNINGS];
|
||||
bool warningsEnabled = true;
|
||||
bool warningsAreErrors = false;
|
||||
};
|
||||
|
||||
template<typename L, typename W>
|
||||
struct Diagnostics {
|
||||
std::vector<WarningFlag<L>> metaWarnings;
|
||||
std::vector<WarningFlag<L>> warningFlags;
|
||||
std::vector<ParamWarning<W>> paramWarnings;
|
||||
DiagnosticsState<W> state;
|
||||
uint64_t nbErrors;
|
||||
|
||||
void incrementErrors() {
|
||||
if (nbErrors != UINT64_MAX) {
|
||||
++nbErrors;
|
||||
}
|
||||
}
|
||||
|
||||
WarningBehavior getWarningBehavior(W id) const;
|
||||
std::string processWarningFlag(char const *flag);
|
||||
};
|
||||
|
||||
template<typename L, typename W>
|
||||
WarningBehavior Diagnostics<L, W>::getWarningBehavior(W id) const {
|
||||
// Check if warnings are globally disabled
|
||||
if (!state.warningsEnabled) {
|
||||
return WarningBehavior::DISABLED;
|
||||
}
|
||||
|
||||
// Get the state of this warning flag
|
||||
WarningState const &flagState = state.flagStates[id];
|
||||
WarningState const &metaState = state.metaStates[id];
|
||||
|
||||
// If subsequent checks determine that the warning flag is enabled, this checks whether it has
|
||||
// -Werror without -Wno-error=<flag> or -Wno-error=<meta>, which makes it into an error
|
||||
bool warningIsError = state.warningsAreErrors && flagState.error != WARNING_DISABLED
|
||||
&& metaState.error != WARNING_DISABLED;
|
||||
WarningBehavior enabledBehavior =
|
||||
warningIsError ? WarningBehavior::ERROR : WarningBehavior::ENABLED;
|
||||
|
||||
// First, check the state of the specific warning flag
|
||||
if (flagState.state == WARNING_DISABLED) { // -Wno-<flag>
|
||||
return WarningBehavior::DISABLED;
|
||||
}
|
||||
if (flagState.error == WARNING_ENABLED) { // -Werror=<flag>
|
||||
return WarningBehavior::ERROR;
|
||||
}
|
||||
if (flagState.state == WARNING_ENABLED) { // -W<flag>
|
||||
return enabledBehavior;
|
||||
}
|
||||
|
||||
// If no flag is specified, check the state of the "meta" flags that affect this warning flag
|
||||
if (metaState.state == WARNING_DISABLED) { // -Wno-<meta>
|
||||
return WarningBehavior::DISABLED;
|
||||
}
|
||||
if (metaState.error == WARNING_ENABLED) { // -Werror=<meta>
|
||||
return WarningBehavior::ERROR;
|
||||
}
|
||||
if (metaState.state == WARNING_ENABLED) { // -W<meta>
|
||||
return enabledBehavior;
|
||||
}
|
||||
|
||||
// If no meta flag is specified, check the default state of this warning flag
|
||||
if (warningFlags[id].level == L::LEVEL_DEFAULT) { // enabled by default
|
||||
return enabledBehavior;
|
||||
}
|
||||
|
||||
// No flag enables this warning, explicitly or implicitly
|
||||
return WarningBehavior::DISABLED;
|
||||
}
|
||||
|
||||
template<typename L, typename W>
|
||||
std::string Diagnostics<L, W>::processWarningFlag(char const *flag) {
|
||||
std::string rootFlag = flag;
|
||||
|
||||
// Check for `-Werror` or `-Wno-error` to return early
|
||||
if (rootFlag == "error") {
|
||||
// `-Werror` promotes warnings to errors
|
||||
state.warningsAreErrors = true;
|
||||
return rootFlag;
|
||||
} else if (rootFlag == "no-error") {
|
||||
// `-Wno-error` disables promotion of warnings to errors
|
||||
state.warningsAreErrors = false;
|
||||
return rootFlag;
|
||||
}
|
||||
|
||||
auto [flagState, param] = getInitialWarningState(rootFlag);
|
||||
|
||||
// Try to match the flag against a parametric warning
|
||||
// If there was an equals sign, it will have set `param`; if not, `param` will be 0,
|
||||
// which applies to all levels
|
||||
for (ParamWarning<W> const ¶mWarning : paramWarnings) {
|
||||
W baseID = paramWarning.firstID;
|
||||
uint8_t maxParam = paramWarning.lastID - baseID + 1;
|
||||
assume(paramWarning.defaultLevel <= maxParam);
|
||||
|
||||
if (rootFlag != warningFlags[baseID].name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 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.
|
||||
// A param of 0 makes sense for disabling everything, but neither for
|
||||
// enabling nor "erroring". Use the default for those.
|
||||
if (!param.has_value() || *param == 0) {
|
||||
param = paramWarning.defaultLevel;
|
||||
} else if (*param > maxParam) {
|
||||
warnx(
|
||||
"Invalid warning flag parameter \"%s=%" PRIu32 "\"; capping at maximum %" PRIu8,
|
||||
rootFlag.c_str(),
|
||||
*param,
|
||||
maxParam
|
||||
);
|
||||
*param = maxParam;
|
||||
}
|
||||
|
||||
// Set the first <param> to enabled/error, and disable the rest
|
||||
for (uint32_t ofs = 0; ofs < maxParam; ++ofs) {
|
||||
if (WarningState &warning = state.flagStates[baseID + ofs]; ofs < *param) {
|
||||
warning.update(flagState);
|
||||
} else {
|
||||
warning.state = WARNING_DISABLED;
|
||||
}
|
||||
}
|
||||
return rootFlag;
|
||||
}
|
||||
|
||||
if (param.has_value()) {
|
||||
warnx("Unknown warning flag parameter \"%s=%" PRIu32 "\"", rootFlag.c_str(), *param);
|
||||
return rootFlag;
|
||||
}
|
||||
|
||||
// Try to match against a "meta" warning
|
||||
for (WarningFlag<L> const &metaWarning : metaWarnings) {
|
||||
if (rootFlag != metaWarning.name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Set each of the warning flags that meets this level
|
||||
for (W id : EnumSeq(W::NB_WARNINGS)) {
|
||||
if (metaWarning.level >= warningFlags[id].level) {
|
||||
state.metaStates[id].update(flagState);
|
||||
}
|
||||
}
|
||||
return rootFlag;
|
||||
}
|
||||
|
||||
// Try to match against a "normal" flag
|
||||
for (W id : EnumSeq(W::NB_PLAIN_WARNINGS)) {
|
||||
if (rootFlag == warningFlags[id].name) {
|
||||
state.flagStates[id].update(flagState);
|
||||
return rootFlag;
|
||||
}
|
||||
}
|
||||
|
||||
warnx("Unknown warning flag \"%s\"", rootFlag.c_str());
|
||||
return rootFlag;
|
||||
}
|
||||
|
||||
#endif // RGBDS_DIAGNOSTICS_HPP
|
||||
@@ -1,176 +0,0 @@
|
||||
// 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(static_cast<T *>(nullptr));
|
||||
}
|
||||
template<typename T>
|
||||
auto const &field() const {
|
||||
return pick(static_cast<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
|
||||
@@ -1,18 +0,0 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_ERROR_HPP
|
||||
#define RGBDS_ERROR_HPP
|
||||
|
||||
extern "C" {
|
||||
[[gnu::format(printf, 1, 2)]]
|
||||
void warn(char const *fmt...);
|
||||
[[gnu::format(printf, 1, 2)]]
|
||||
void warnx(char const *fmt, ...);
|
||||
|
||||
[[gnu::format(printf, 1, 2), noreturn]]
|
||||
void err(char const *fmt, ...);
|
||||
[[gnu::format(printf, 1, 2), noreturn]]
|
||||
void errx(char const *fmt, ...);
|
||||
}
|
||||
|
||||
#endif // RGBDS_ERROR_HPP
|
||||
3
include/extern/utf8decoder.hpp
vendored
3
include/extern/utf8decoder.hpp
vendored
@@ -5,6 +5,9 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define UTF8_ACCEPT 0
|
||||
#define UTF8_REJECT 12
|
||||
|
||||
uint32_t decode(uint32_t *state, uint32_t *codep, uint8_t byte);
|
||||
|
||||
#endif // RGBDS_EXTERN_UTF8DECODER_HPP
|
||||
|
||||
@@ -10,36 +10,27 @@
|
||||
#include <streambuf>
|
||||
#include <string.h>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
|
||||
#include "either.hpp"
|
||||
#include "helpers.hpp" // assume
|
||||
#include "platform.hpp"
|
||||
|
||||
#include "gfx/main.hpp"
|
||||
|
||||
class File {
|
||||
// Construct a `std::streambuf *` by default, since it's probably lighter than a `filebuf`.
|
||||
Either<std::streambuf *, std::filebuf> _file;
|
||||
std::variant<std::streambuf *, std::filebuf> _file;
|
||||
|
||||
public:
|
||||
File() {}
|
||||
~File() { close(); }
|
||||
File() : _file(nullptr) {}
|
||||
|
||||
// This should only be called once, and before doing any `->` operations.
|
||||
// Returns `nullptr` on error, and a non-null pointer otherwise.
|
||||
File *open(std::string const &path, std::ios_base::openmode mode) {
|
||||
if (path != "-") {
|
||||
_file.emplace<std::filebuf>();
|
||||
return _file.get<std::filebuf>().open(path, mode) ? this : nullptr;
|
||||
return _file.emplace<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());
|
||||
if (setmode(STDIN_FILENO, (mode & std::ios_base::binary) ? O_BINARY : O_TEXT) == -1) {
|
||||
fatal(
|
||||
"Failed to set stdin to %s mode: %s",
|
||||
mode & std::ios_base::binary ? "binary" : "text",
|
||||
strerror(errno)
|
||||
);
|
||||
return nullptr;
|
||||
}
|
||||
} else {
|
||||
assume(mode & std::ios_base::out);
|
||||
@@ -48,8 +39,8 @@ public:
|
||||
return this;
|
||||
}
|
||||
std::streambuf &operator*() {
|
||||
return _file.holds<std::filebuf>() ? _file.get<std::filebuf>()
|
||||
: *_file.get<std::streambuf *>();
|
||||
return std::holds_alternative<std::filebuf>(_file) ? std::get<std::filebuf>(_file)
|
||||
: *std::get<std::streambuf *>(_file);
|
||||
}
|
||||
std::streambuf const &operator*() const {
|
||||
// The non-`const` version does not perform any modifications, so it's okay.
|
||||
@@ -61,24 +52,10 @@ public:
|
||||
return const_cast<File *>(this)->operator->();
|
||||
}
|
||||
|
||||
File *close() {
|
||||
if (_file.holds<std::filebuf>()) {
|
||||
// This is called by the destructor, and an explicit `close` shouldn't close twice.
|
||||
std::filebuf fileBuf = std::move(_file.get<std::filebuf>());
|
||||
_file.emplace<std::streambuf *>(nullptr);
|
||||
if (fileBuf.close() != nullptr) {
|
||||
return this;
|
||||
}
|
||||
} else if (_file.get<std::streambuf *>() != nullptr) {
|
||||
return this;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
char const *c_str(std::string const &path) const {
|
||||
return _file.holds<std::filebuf>() ? path.c_str()
|
||||
: _file.get<std::streambuf *>() == std::cin.rdbuf() ? "<stdin>"
|
||||
: "<stdout>";
|
||||
return std::holds_alternative<std::filebuf>(_file) ? path.c_str()
|
||||
: std::get<std::streambuf *>(_file) == std::cin.rdbuf() ? "<stdin>"
|
||||
: "<stdout>";
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
79
include/fix/mbc.hpp
Normal file
79
include/fix/mbc.hpp
Normal file
@@ -0,0 +1,79 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_FIX_MBC_HPP
|
||||
#define RGBDS_FIX_MBC_HPP
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
constexpr uint16_t UNSPECIFIED = 0x200;
|
||||
static_assert(UNSPECIFIED > 0xFF, "UNSPECIFIED should not be in byte range!");
|
||||
|
||||
enum MbcType {
|
||||
ROM = 0x00,
|
||||
ROM_RAM = 0x08,
|
||||
ROM_RAM_BATTERY = 0x09,
|
||||
|
||||
MBC1 = 0x01,
|
||||
MBC1_RAM = 0x02,
|
||||
MBC1_RAM_BATTERY = 0x03,
|
||||
|
||||
MBC2 = 0x05,
|
||||
MBC2_BATTERY = 0x06,
|
||||
|
||||
MMM01 = 0x0B,
|
||||
MMM01_RAM = 0x0C,
|
||||
MMM01_RAM_BATTERY = 0x0D,
|
||||
|
||||
MBC3 = 0x11,
|
||||
MBC3_TIMER_BATTERY = 0x0F,
|
||||
MBC3_TIMER_RAM_BATTERY = 0x10,
|
||||
MBC3_RAM = 0x12,
|
||||
MBC3_RAM_BATTERY = 0x13,
|
||||
|
||||
MBC5 = 0x19,
|
||||
MBC5_RAM = 0x1A,
|
||||
MBC5_RAM_BATTERY = 0x1B,
|
||||
MBC5_RUMBLE = 0x1C,
|
||||
MBC5_RUMBLE_RAM = 0x1D,
|
||||
MBC5_RUMBLE_RAM_BATTERY = 0x1E,
|
||||
|
||||
MBC6 = 0x20,
|
||||
|
||||
MBC7_SENSOR_RUMBLE_RAM_BATTERY = 0x22,
|
||||
|
||||
POCKET_CAMERA = 0xFC,
|
||||
|
||||
BANDAI_TAMA5 = 0xFD,
|
||||
|
||||
HUC3 = 0xFE,
|
||||
|
||||
HUC1_RAM_BATTERY = 0xFF,
|
||||
|
||||
// "Extended" values (still valid, but not directly actionable)
|
||||
|
||||
// A high byte of 0x01 means TPP1, the low byte is the requested features
|
||||
// This does not include SRAM, which is instead implied by a non-zero SRAM size
|
||||
// Note: Multiple rumble speeds imply rumble
|
||||
TPP1 = 0x100,
|
||||
TPP1_RUMBLE = 0x101,
|
||||
TPP1_MULTIRUMBLE_RUMBLE = 0x103,
|
||||
TPP1_TIMER = 0x104,
|
||||
TPP1_TIMER_RUMBLE = 0x105,
|
||||
TPP1_TIMER_MULTIRUMBLE_RUMBLE = 0x107,
|
||||
TPP1_BATTERY = 0x108,
|
||||
TPP1_BATTERY_RUMBLE = 0x109,
|
||||
TPP1_BATTERY_MULTIRUMBLE_RUMBLE = 0x10B,
|
||||
TPP1_BATTERY_TIMER = 0x10C,
|
||||
TPP1_BATTERY_TIMER_RUMBLE = 0x10D,
|
||||
TPP1_BATTERY_TIMER_MULTIRUMBLE_RUMBLE = 0x10F,
|
||||
|
||||
// Error values
|
||||
MBC_NONE = UNSPECIFIED, // No MBC specified, do not act on it
|
||||
};
|
||||
|
||||
bool mbc_HasRAM(MbcType type);
|
||||
char const *mbc_Name(MbcType type);
|
||||
MbcType mbc_ParseName(char const *name, uint8_t &tpp1Major, uint8_t &tpp1Minor);
|
||||
|
||||
#endif // RGBDS_FIX_MBC_HPP
|
||||
41
include/fix/warning.hpp
Normal file
41
include/fix/warning.hpp
Normal file
@@ -0,0 +1,41 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_FIX_WARNING_HPP
|
||||
#define RGBDS_FIX_WARNING_HPP
|
||||
|
||||
#include "diagnostics.hpp"
|
||||
|
||||
enum WarningLevel {
|
||||
LEVEL_DEFAULT, // Warnings that are enabled by default
|
||||
LEVEL_ALL, // Warnings that probably indicate an error
|
||||
LEVEL_EVERYTHING, // Literally every warning
|
||||
};
|
||||
|
||||
enum WarningID {
|
||||
WARNING_MBC, // Issues with MBC specs
|
||||
WARNING_OVERWRITE, // Overwriting non-zero bytes
|
||||
WARNING_SGB, // SGB flag conflicts with old licensee code
|
||||
WARNING_TRUNCATION, // Truncating values to fit
|
||||
|
||||
NB_PLAIN_WARNINGS,
|
||||
|
||||
NB_WARNINGS = NB_PLAIN_WARNINGS,
|
||||
};
|
||||
|
||||
extern Diagnostics<WarningLevel, WarningID> warnings;
|
||||
|
||||
// Warns the user about problems that don't prevent fixing the ROM header
|
||||
[[gnu::format(printf, 2, 3)]]
|
||||
void warning(WarningID id, char const *fmt, ...);
|
||||
|
||||
// Prints an error, and increments the error count
|
||||
[[gnu::format(printf, 1, 2)]]
|
||||
void error(char const *fmt, ...);
|
||||
|
||||
// Prints an error, and exits with failure
|
||||
[[gnu::format(printf, 1, 2), noreturn]]
|
||||
void fatal(char const *fmt, ...);
|
||||
|
||||
uint32_t checkErrors(char const *filename);
|
||||
|
||||
#endif // RGBDS_FIX_WARNING_HPP
|
||||
@@ -1,13 +1,13 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_GFX_PROTO_PALETTE_HPP
|
||||
#define RGBDS_GFX_PROTO_PALETTE_HPP
|
||||
#ifndef RGBDS_GFX_COLOR_SET_HPP
|
||||
#define RGBDS_GFX_COLOR_SET_HPP
|
||||
|
||||
#include <array>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
class ProtoPalette {
|
||||
class ColorSet {
|
||||
public:
|
||||
static constexpr size_t capacity = 4;
|
||||
|
||||
@@ -26,7 +26,7 @@ public:
|
||||
WE_BIGGER,
|
||||
THEY_BIGGER = -1,
|
||||
};
|
||||
ComparisonResult compare(ProtoPalette const &other) const;
|
||||
ComparisonResult compare(ColorSet const &other) const;
|
||||
|
||||
size_t size() const;
|
||||
bool empty() const;
|
||||
@@ -35,4 +35,4 @@ public:
|
||||
decltype(_colorIndices)::const_iterator end() const;
|
||||
};
|
||||
|
||||
#endif // RGBDS_GFX_PROTO_PALETTE_HPP
|
||||
#endif // RGBDS_GFX_COLOR_SET_HPP
|
||||
@@ -10,6 +10,8 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "helpers.hpp"
|
||||
|
||||
#include "gfx/rgba.hpp"
|
||||
|
||||
struct Options {
|
||||
@@ -21,13 +23,16 @@ struct Options {
|
||||
uint8_t verbosity = 0; // -v
|
||||
|
||||
std::string attrmap{}; // -a, -A
|
||||
std::optional<Rgba> bgColor{}; // -B
|
||||
std::array<uint8_t, 2> baseTileIDs{0, 0}; // -b
|
||||
enum {
|
||||
NO_SPEC,
|
||||
EXPLICIT,
|
||||
EMBEDDED,
|
||||
DMG,
|
||||
} palSpecType = NO_SPEC; // -c
|
||||
std::vector<std::array<std::optional<Rgba>, 4>> palSpec{};
|
||||
uint8_t palSpecDmg = 0;
|
||||
uint8_t bitDepth = 2; // -d
|
||||
std::string inputTileset{}; // -i
|
||||
struct {
|
||||
@@ -35,7 +40,10 @@ struct Options {
|
||||
uint16_t top;
|
||||
uint16_t width;
|
||||
uint16_t height;
|
||||
uint32_t right() const { return left + width * 8; }
|
||||
uint32_t bottom() const { return top + height * 8; }
|
||||
} inputSlice{0, 0, 0, 0}; // -L (margins in clockwise order, like CSS)
|
||||
uint8_t basePalID = 0; // -l
|
||||
std::array<uint16_t, 2> maxNbTiles{UINT16_MAX, 0}; // -N
|
||||
uint16_t nbPalettes = 8; // -n
|
||||
std::string output{}; // -o
|
||||
@@ -62,29 +70,18 @@ struct Options {
|
||||
|
||||
mutable bool hasTransparentPixels = false;
|
||||
uint8_t maxOpaqueColors() const { return nbColorsPerPal - hasTransparentPixels; }
|
||||
|
||||
uint16_t maxNbColors() const { return nbColorsPerPal * nbPalettes; }
|
||||
|
||||
uint8_t dmgColors[4] = {};
|
||||
uint8_t dmgValue(uint8_t i) const {
|
||||
assume(i < 4);
|
||||
return (palSpecDmg >> (2 * i)) & 0b11;
|
||||
}
|
||||
};
|
||||
|
||||
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
|
||||
[[gnu::format(printf, 1, 2)]]
|
||||
void warning(char const *fmt, ...);
|
||||
// Prints an error, and increments the error count
|
||||
[[gnu::format(printf, 1, 2)]]
|
||||
void error(char const *fmt, ...);
|
||||
// Prints an error, and increments the error count
|
||||
// Does not take format arguments so `format_` and `-Wformat-security` won't complain about
|
||||
// calling `errorMessage(msg)`.
|
||||
void errorMessage(char const *msg);
|
||||
// Prints a fatal error, increments the error count, and gives up
|
||||
[[gnu::format(printf, 1, 2), noreturn]]
|
||||
void fatal(char const *fmt, ...);
|
||||
|
||||
struct Palette {
|
||||
// An array of 4 GBC-native (RGB555) colors
|
||||
std::array<uint16_t, 4> colors{UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX};
|
||||
@@ -103,9 +100,9 @@ struct Palette {
|
||||
};
|
||||
|
||||
// Flipping tends to happen fairly often, so take a bite out of dcache to speed it up
|
||||
static constexpr auto flipTable = ([]() constexpr {
|
||||
static std::array<uint16_t, 256> flipTable = ([]() constexpr {
|
||||
std::array<uint16_t, 256> table{};
|
||||
for (uint16_t i = 0; i < table.size(); i++) {
|
||||
for (uint16_t i = 0; i < table.size(); ++i) {
|
||||
// To flip all the bits, we'll flip both nibbles, then each nibble half, etc.
|
||||
uint16_t byte = i;
|
||||
byte = (byte & 0b0000'1111) << 4 | (byte & 0b1111'0000) >> 4;
|
||||
|
||||
@@ -3,16 +3,14 @@
|
||||
#ifndef RGBDS_GFX_PAL_PACKING_HPP
|
||||
#define RGBDS_GFX_PAL_PACKING_HPP
|
||||
|
||||
#include <stddef.h>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
|
||||
#include "defaultinitvec.hpp"
|
||||
|
||||
struct Palette;
|
||||
class ProtoPalette;
|
||||
class ColorSet;
|
||||
|
||||
// Returns which palette each proto-palette maps to, and how many palettes are necessary
|
||||
std::tuple<DefaultInitVec<size_t>, size_t>
|
||||
overloadAndRemove(std::vector<ProtoPalette> const &protoPalettes);
|
||||
// Returns which palette each color set maps to, and how many palettes are necessary
|
||||
std::tuple<std::vector<size_t>, size_t> overloadAndRemove(std::vector<ColorSet> const &colorSets);
|
||||
|
||||
#endif // RGBDS_GFX_PAL_PACKING_HPP
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
#include <array>
|
||||
#include <optional>
|
||||
#include <png.h>
|
||||
#include <vector>
|
||||
|
||||
#include "gfx/rgba.hpp"
|
||||
@@ -16,13 +15,7 @@ static constexpr size_t NB_COLOR_SLOTS = (1 << (5 * 3)) + 1;
|
||||
|
||||
struct Palette;
|
||||
|
||||
void sortIndexed(
|
||||
std::vector<Palette> &palettes,
|
||||
int palSize,
|
||||
png_color const *palRGB,
|
||||
int palAlphaSize,
|
||||
png_byte *palAlpha
|
||||
);
|
||||
void sortIndexed(std::vector<Palette> &palettes, std::vector<Rgba> const &embPal);
|
||||
void sortGrayscale(
|
||||
std::vector<Palette> &palettes, std::array<std::optional<Rgba>, NB_COLOR_SLOTS> const &colors
|
||||
);
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
#ifndef RGBDS_GFX_PAL_SPEC_HPP
|
||||
#define RGBDS_GFX_PAL_SPEC_HPP
|
||||
|
||||
void parseInlinePalSpec(char const * const arg);
|
||||
void parseInlinePalSpec(char const * const rawArg);
|
||||
void parseExternalPalSpec(char const *arg);
|
||||
void parseDmgPalSpec(char const * const rawArg);
|
||||
|
||||
void parseBackgroundPalSpec(char const *arg);
|
||||
|
||||
#endif // RGBDS_GFX_PAL_SPEC_HPP
|
||||
|
||||
21
include/gfx/png.hpp
Normal file
21
include/gfx/png.hpp
Normal file
@@ -0,0 +1,21 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_GFX_PNG_HPP
|
||||
#define RGBDS_GFX_PNG_HPP
|
||||
|
||||
#include <fstream>
|
||||
#include <stdint.h>
|
||||
#include <vector>
|
||||
|
||||
#include "gfx/rgba.hpp"
|
||||
|
||||
struct Png {
|
||||
uint32_t width, height;
|
||||
std::vector<Rgba> pixels{};
|
||||
std::vector<Rgba> palette{};
|
||||
|
||||
Png() {}
|
||||
Png(char const *filename, std::streambuf &file);
|
||||
};
|
||||
|
||||
#endif // RGBDS_GFX_PNG_HPP
|
||||
@@ -17,27 +17,29 @@ struct Rgba {
|
||||
explicit constexpr Rgba(uint32_t rgba = 0)
|
||||
: red(rgba >> 24), green(rgba >> 16), blue(rgba >> 8), alpha(rgba) {}
|
||||
|
||||
static constexpr Rgba fromCGBColor(uint16_t cgbColor) {
|
||||
constexpr auto _5to8 = [](uint8_t fiveBpp) -> uint8_t {
|
||||
fiveBpp &= 0b11111; // For caller's convenience
|
||||
return fiveBpp << 3 | fiveBpp >> 2;
|
||||
static constexpr Rgba fromCGBColor(uint16_t color) {
|
||||
constexpr auto _5to8 = [](uint8_t channel) -> uint8_t {
|
||||
channel &= 0b11111; // For caller's convenience
|
||||
return channel << 3 | channel >> 2;
|
||||
};
|
||||
return {
|
||||
_5to8(cgbColor),
|
||||
_5to8(cgbColor >> 5),
|
||||
_5to8(cgbColor >> 10),
|
||||
static_cast<uint8_t>(cgbColor & 0x8000 ? 0x00 : 0xFF),
|
||||
_5to8(color),
|
||||
_5to8(color >> 5),
|
||||
_5to8(color >> 10),
|
||||
static_cast<uint8_t>(color & 0x8000 ? 0x00 : 0xFF),
|
||||
};
|
||||
}
|
||||
|
||||
// Returns this RGBA as a 32-bit number that can be printed in hex (`%08x`) to yield its CSS
|
||||
// representation
|
||||
uint32_t toCSS() const {
|
||||
auto shl = [](uint8_t val, unsigned shift) { return static_cast<uint32_t>(val) << shift; };
|
||||
constexpr auto shl = [](uint8_t val, unsigned shift) {
|
||||
return static_cast<uint32_t>(val) << shift;
|
||||
};
|
||||
return shl(red, 24) | shl(green, 16) | shl(blue, 8) | shl(alpha, 0);
|
||||
}
|
||||
|
||||
bool operator==(Rgba const &rhs) const { return toCSS() == rhs.toCSS(); }
|
||||
bool operator!=(Rgba const &rhs) const { return toCSS() != rhs.toCSS(); }
|
||||
|
||||
// CGB colors are RGB555, so we use bit 15 to signify that the color is transparent instead
|
||||
// Since the rest of the bits don't matter then, we return 0x8000 exactly.
|
||||
|
||||
44
include/gfx/warning.hpp
Normal file
44
include/gfx/warning.hpp
Normal file
@@ -0,0 +1,44 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_GFX_WARNING_HPP
|
||||
#define RGBDS_GFX_WARNING_HPP
|
||||
|
||||
#include "diagnostics.hpp"
|
||||
|
||||
enum WarningLevel {
|
||||
LEVEL_DEFAULT, // Warnings that are enabled by default
|
||||
LEVEL_ALL, // Warnings that probably indicate an error
|
||||
LEVEL_EVERYTHING, // Literally every warning
|
||||
};
|
||||
|
||||
enum WarningID {
|
||||
WARNING_EMBEDDED, // Using an embedded PNG palette without '-c embedded'
|
||||
WARNING_TRIM_NONEMPTY, // '-x' trims nonempty tiles
|
||||
|
||||
NB_PLAIN_WARNINGS,
|
||||
|
||||
NB_WARNINGS = NB_PLAIN_WARNINGS,
|
||||
};
|
||||
|
||||
extern Diagnostics<WarningLevel, WarningID> warnings;
|
||||
|
||||
// Warns the user about problems that don't prevent valid graphics conversion
|
||||
[[gnu::format(printf, 2, 3)]]
|
||||
void warning(WarningID id, char const *fmt, ...);
|
||||
|
||||
// Prints the error count, and exits with failure
|
||||
[[noreturn]]
|
||||
void giveUp();
|
||||
|
||||
// If any error has been emitted thus far, calls `giveUp()`
|
||||
void requireZeroErrors();
|
||||
|
||||
// Prints an error, and increments the error count
|
||||
[[gnu::format(printf, 1, 2)]]
|
||||
void error(char const *fmt, ...);
|
||||
|
||||
// Prints a fatal error, increments the error count, and gives up
|
||||
[[gnu::format(printf, 1, 2), noreturn]]
|
||||
void fatal(char const *fmt, ...);
|
||||
|
||||
#endif // RGBDS_GFX_WARNING_HPP
|
||||
@@ -24,7 +24,7 @@ static inline void unreachable_() {
|
||||
#ifdef _MSC_VER
|
||||
#define assume(x) __assume(x)
|
||||
#else
|
||||
// `[[gnu::assume()]]` for GCC or compatible also has insufficient support (GCC 13+ only)
|
||||
// `[[gnu::assume()]]` for GCC or compatible also has insufficient support (GCC 13+ only)
|
||||
#define assume(x) \
|
||||
do { \
|
||||
if (!(x)) { \
|
||||
@@ -71,7 +71,7 @@ static inline int ctz(unsigned int x) {
|
||||
|
||||
while (!(x & 1)) {
|
||||
x >>= 1;
|
||||
cnt++;
|
||||
++cnt;
|
||||
}
|
||||
return cnt;
|
||||
}
|
||||
@@ -81,7 +81,7 @@ static inline int clz(unsigned int x) {
|
||||
|
||||
while (x <= UINT_MAX / 2) {
|
||||
x <<= 1;
|
||||
cnt++;
|
||||
++cnt;
|
||||
}
|
||||
return cnt;
|
||||
}
|
||||
|
||||
@@ -22,10 +22,9 @@ class EnumSeq {
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto operator*() const { return _value; }
|
||||
T operator*() const { return _value; }
|
||||
|
||||
bool operator==(Iterator const &rhs) const { return _value == rhs._value; }
|
||||
bool operator!=(Iterator const &rhs) const { return _value != rhs._value; }
|
||||
};
|
||||
|
||||
public:
|
||||
@@ -59,10 +58,6 @@ public:
|
||||
bool operator==(ZipIterator const &rhs) const {
|
||||
return std::get<0>(_iters) == std::get<0>(rhs._iters);
|
||||
}
|
||||
|
||||
bool operator!=(ZipIterator const &rhs) const {
|
||||
return std::get<0>(_iters) != std::get<0>(rhs._iters);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename... Ts>
|
||||
|
||||
@@ -3,10 +3,6 @@
|
||||
#ifndef RGBDS_LINK_ASSIGN_HPP
|
||||
#define RGBDS_LINK_ASSIGN_HPP
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
extern uint64_t nbSectionsToAssign;
|
||||
|
||||
// Assigns all sections a slice of the address space
|
||||
void assign_AssignSections();
|
||||
|
||||
|
||||
22
include/link/layout.hpp
Normal file
22
include/link/layout.hpp
Normal file
@@ -0,0 +1,22 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_LINK_LAYOUT_HPP
|
||||
#define RGBDS_LINK_LAYOUT_HPP
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
|
||||
#include "linkdefs.hpp"
|
||||
|
||||
void layout_SetFloatingSectionType(SectionType type);
|
||||
void layout_SetSectionType(SectionType type);
|
||||
void layout_SetSectionType(SectionType type, uint32_t bank);
|
||||
|
||||
void layout_SetAddr(uint32_t addr);
|
||||
void layout_MakeAddrFloating();
|
||||
void layout_AlignTo(uint32_t alignment, uint32_t offset);
|
||||
void layout_Pad(uint32_t length);
|
||||
|
||||
void layout_PlaceSection(std::string const &name, bool isOptional);
|
||||
|
||||
#endif // RGBDS_LINK_LAYOUT_HPP
|
||||
17
include/link/lexer.hpp
Normal file
17
include/link/lexer.hpp
Normal file
@@ -0,0 +1,17 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_LINK_LEXER_HPP
|
||||
#define RGBDS_LINK_LEXER_HPP
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <string>
|
||||
|
||||
[[gnu::format(printf, 1, 2)]]
|
||||
void lexer_Error(char const *fmt, ...);
|
||||
|
||||
void lexer_IncludeFile(std::string &&path);
|
||||
void lexer_IncLineNo();
|
||||
|
||||
bool lexer_Init(char const *linkerScriptName);
|
||||
|
||||
#endif // RGBDS_LINK_LEXER_HPP
|
||||
@@ -6,40 +6,43 @@
|
||||
#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 const *linkerScriptName;
|
||||
extern char const *mapFileName;
|
||||
extern bool noSymInMap;
|
||||
extern char const *symFileName;
|
||||
extern char const *overlayFileName;
|
||||
extern char const *outputFileName;
|
||||
extern uint8_t padValue;
|
||||
extern bool hasPadValue;
|
||||
extern uint16_t scrambleROMX;
|
||||
extern uint8_t scrambleWRAMX;
|
||||
extern uint8_t scrambleSRAM;
|
||||
extern bool is32kMode;
|
||||
extern bool beVerbose;
|
||||
extern bool isWRAM0Mode;
|
||||
extern bool disablePadding;
|
||||
struct Options {
|
||||
bool isDmgMode; // -d
|
||||
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; // -S
|
||||
uint16_t scrambleWRAMX;
|
||||
uint16_t scrambleSRAM;
|
||||
bool is32kMode; // -t
|
||||
bool beVerbose; // -v
|
||||
bool isWRAM0Mode; // -w
|
||||
bool disablePadding; // -x
|
||||
};
|
||||
|
||||
extern Options options;
|
||||
|
||||
// Helper macro for printing verbose-mode messages
|
||||
#define verbosePrint(...) \
|
||||
do { \
|
||||
if (beVerbose) { \
|
||||
if (options.beVerbose) { \
|
||||
fprintf(stderr, __VA_ARGS__); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
struct FileStackNode {
|
||||
FileStackNodeType type;
|
||||
Either<
|
||||
std::variant<
|
||||
std::monostate, // Default constructed; `.type` and `.data` must be set manually
|
||||
std::vector<uint32_t>, // NODE_REPT
|
||||
std::string // NODE_FILE, NODE_MACRO
|
||||
>
|
||||
@@ -50,20 +53,13 @@ struct FileStackNode {
|
||||
uint32_t lineNo;
|
||||
|
||||
// REPT iteration counts since last named node, in reverse depth order
|
||||
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>>(); }
|
||||
std::vector<uint32_t> &iters() { return std::get<std::vector<uint32_t>>(data); }
|
||||
std::vector<uint32_t> const &iters() const { return std::get<std::vector<uint32_t>>(data); }
|
||||
// File name for files, file::macro name for macros
|
||||
std::string &name() { return data.get<std::string>(); }
|
||||
std::string const &name() const { return data.get<std::string>(); }
|
||||
std::string &name() { return std::get<std::string>(data); }
|
||||
std::string const &name() const { return std::get<std::string>(data); }
|
||||
|
||||
std::string const &dump(uint32_t curLineNo) const;
|
||||
};
|
||||
|
||||
[[gnu::format(printf, 3, 4)]]
|
||||
void warning(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...);
|
||||
[[gnu::format(printf, 3, 4)]]
|
||||
void error(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...);
|
||||
[[gnu::format(printf, 3, 4), noreturn]]
|
||||
void fatal(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...);
|
||||
|
||||
#endif // RGBDS_LINK_MAIN_HPP
|
||||
|
||||
@@ -3,6 +3,23 @@
|
||||
#ifndef RGBDS_LINK_PATCH_HPP
|
||||
#define RGBDS_LINK_PATCH_HPP
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "link/section.hpp"
|
||||
|
||||
struct Symbol;
|
||||
|
||||
struct Assertion {
|
||||
Patch patch; // Also used for its `.type`
|
||||
std::string message;
|
||||
// This would be redundant with `patch.pcSection->fileSymbols`, but `section` is sometimes
|
||||
// `nullptr`!
|
||||
std::vector<Symbol> *fileSymbols;
|
||||
};
|
||||
|
||||
Assertion &patch_AddAssertion();
|
||||
|
||||
// Checks all assertions
|
||||
void patch_CheckAssertions();
|
||||
|
||||
|
||||
@@ -56,15 +56,6 @@ struct Section {
|
||||
std::unique_ptr<Section> nextu; // The next "component" of this unionized sect
|
||||
};
|
||||
|
||||
struct Assertion {
|
||||
Patch patch; // Also used for its `.type`
|
||||
std::string message;
|
||||
// This would be redundant with `.section->fileSymbols`, but `section` is sometimes `nullptr`!
|
||||
std::vector<Symbol> *fileSymbols;
|
||||
};
|
||||
|
||||
extern std::deque<Assertion> assertions;
|
||||
|
||||
// Execute a callback for each section currently registered.
|
||||
// This is to avoid exposing the data structure in which sections are stored.
|
||||
void sect_ForEach(void (*callback)(Section &));
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
|
||||
#include "either.hpp"
|
||||
#include "linkdefs.hpp"
|
||||
|
||||
struct FileStackNode;
|
||||
@@ -27,14 +27,14 @@ struct Symbol {
|
||||
ExportLevel type;
|
||||
FileStackNode const *src;
|
||||
int32_t lineNo;
|
||||
Either<
|
||||
std::variant<
|
||||
int32_t, // Constants just have a numeric value
|
||||
Label // Label values refer to an offset within a specific section
|
||||
>
|
||||
data;
|
||||
|
||||
Label &label() { return data.get<Label>(); }
|
||||
Label const &label() const { return data.get<Label>(); }
|
||||
Label &label() { return std::get<Label>(data); }
|
||||
Label const &label() const { return std::get<Label>(data); }
|
||||
};
|
||||
|
||||
void sym_ForEach(void (*callback)(Symbol &));
|
||||
|
||||
61
include/link/warning.hpp
Normal file
61
include/link/warning.hpp
Normal file
@@ -0,0 +1,61 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_LINK_WARNING_HPP
|
||||
#define RGBDS_LINK_WARNING_HPP
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "diagnostics.hpp"
|
||||
|
||||
#define warningAt(where, ...) warning(where.src, where.lineNo, __VA_ARGS__)
|
||||
#define errorAt(where, ...) error(where.src, where.lineNo, __VA_ARGS__)
|
||||
#define fatalAt(where, ...) fatal(where.src, where.lineNo, __VA_ARGS__)
|
||||
|
||||
enum WarningLevel {
|
||||
LEVEL_DEFAULT, // Warnings that are enabled by default
|
||||
LEVEL_ALL, // Warnings that probably indicate an error
|
||||
LEVEL_EVERYTHING, // Literally every warning
|
||||
};
|
||||
|
||||
enum WarningID {
|
||||
WARNING_ASSERT, // Assertions
|
||||
WARNING_DIV, // Undefined division behavior
|
||||
WARNING_OBSOLETE, // Obsolete/deprecated things
|
||||
WARNING_SHIFT, // Undefined `SHIFT` behavior
|
||||
WARNING_SHIFT_AMOUNT, // Strange `SHIFT` amount
|
||||
WARNING_TRUNCATION, // Implicit truncation loses some bits
|
||||
|
||||
NB_PLAIN_WARNINGS,
|
||||
|
||||
NB_WARNINGS = NB_PLAIN_WARNINGS,
|
||||
};
|
||||
|
||||
extern Diagnostics<WarningLevel, WarningID> warnings;
|
||||
|
||||
struct FileStackNode;
|
||||
|
||||
[[gnu::format(printf, 4, 5)]]
|
||||
void warning(FileStackNode const *src, uint32_t lineNo, WarningID id, char const *fmt, ...);
|
||||
[[gnu::format(printf, 3, 4)]]
|
||||
void warning(FileStackNode const *src, uint32_t lineNo, char const *fmt, ...);
|
||||
[[gnu::format(printf, 1, 2)]]
|
||||
void warning(char const *fmt, ...);
|
||||
|
||||
[[gnu::format(printf, 3, 4)]]
|
||||
void error(FileStackNode const *src, uint32_t lineNo, char const *fmt, ...);
|
||||
[[gnu::format(printf, 1, 2)]]
|
||||
void error(char const *fmt, ...);
|
||||
[[gnu::format(printf, 1, 2)]]
|
||||
void errorNoDump(char const *fmt, ...);
|
||||
|
||||
void scriptError(char const *name, uint32_t lineNo, char const *fmt, va_list args);
|
||||
|
||||
[[gnu::format(printf, 3, 4), noreturn]]
|
||||
void fatal(FileStackNode const *src, uint32_t lineNo, char const *fmt, ...);
|
||||
[[gnu::format(printf, 1, 2), noreturn]]
|
||||
void fatal(char const *fmt, ...);
|
||||
|
||||
void requireZeroErrors();
|
||||
|
||||
#endif // RGBDS_LINK_WARNING_HPP
|
||||
@@ -9,7 +9,7 @@
|
||||
#include "helpers.hpp" // assume
|
||||
|
||||
#define RGBDS_OBJECT_VERSION_STRING "RGB9"
|
||||
#define RGBDS_OBJECT_REV 11U
|
||||
#define RGBDS_OBJECT_REV 12U
|
||||
|
||||
enum AssertionType { ASSERT_WARN, ASSERT_ERROR, ASSERT_FATAL };
|
||||
|
||||
@@ -52,6 +52,7 @@ enum RPNCommand {
|
||||
|
||||
RPN_HRAM = 0x60,
|
||||
RPN_RST = 0x61,
|
||||
RPN_BIT_INDEX = 0x62,
|
||||
|
||||
RPN_HIGH = 0x70,
|
||||
RPN_LOW = 0x71,
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// platform-specific hacks
|
||||
|
||||
#ifndef RGBDS_PLATFORM_HPP
|
||||
#define RGBDS_PLATFORM_HPP
|
||||
|
||||
|
||||
21
include/usage.hpp
Normal file
21
include/usage.hpp
Normal file
@@ -0,0 +1,21 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_USAGE_HPP
|
||||
#define RGBDS_USAGE_HPP
|
||||
|
||||
#include <stdarg.h>
|
||||
|
||||
class Usage {
|
||||
char const *usage;
|
||||
|
||||
public:
|
||||
Usage(char const *usage_) : usage(usage_) {}
|
||||
|
||||
[[noreturn]]
|
||||
void printAndExit(int code) const;
|
||||
|
||||
[[gnu::format(printf, 2, 3), noreturn]]
|
||||
void printAndExit(char const *fmt, ...) const;
|
||||
};
|
||||
|
||||
#endif // RGBDS_USAGE_HPP
|
||||
@@ -3,6 +3,23 @@
|
||||
#ifndef RGBDS_UTIL_HPP
|
||||
#define RGBDS_UTIL_HPP
|
||||
|
||||
#include <stddef.h>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "helpers.hpp"
|
||||
|
||||
bool startsIdentifier(int c);
|
||||
bool continuesIdentifier(int c);
|
||||
|
||||
char const *printChar(int c);
|
||||
|
||||
struct Uppercase {
|
||||
size_t operator()(std::string const &str) const;
|
||||
bool operator()(std::string const &str1, std::string const &str2) const;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
using UpperMap = std::unordered_map<std::string, T, Uppercase, Uppercase>;
|
||||
|
||||
#endif // RGBDS_UTIL_HPP
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
#define PACKAGE_VERSION_MAJOR 0
|
||||
#define PACKAGE_VERSION_MINOR 9
|
||||
#define PACKAGE_VERSION_PATCH 1
|
||||
#define PACKAGE_VERSION_PATCH 4
|
||||
|
||||
char const *get_package_version_string();
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd February 2, 2025
|
||||
.Dd July 31, 2025
|
||||
.Dt GBZ80 7
|
||||
.Os
|
||||
.Sh NAME
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
.\"
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd February 2, 2025
|
||||
.Dd July 31, 2025
|
||||
.Dt RGBASM-OLD 5
|
||||
.Os
|
||||
.Sh NAME
|
||||
@@ -99,6 +99,23 @@ in RAM sections, allowing labeled space allocations to overlap.
|
||||
.Pp
|
||||
Instead, use
|
||||
.Ic UNION .
|
||||
.Ss Section-local charmaps
|
||||
Deprecated in 0.3.9, removed in 0.4.0.
|
||||
.Pp
|
||||
Defining a
|
||||
.Ic CHARMAP
|
||||
inside a
|
||||
.Ic SECTION
|
||||
when the current global charmap was the
|
||||
.Sq main
|
||||
one used to only define that character mapping within that
|
||||
.Ic SECTION .
|
||||
.Pp
|
||||
Instead, use
|
||||
.Ic PUSHC
|
||||
and
|
||||
.Ic POPC
|
||||
and switch to a different character mapping for that section.
|
||||
.Ss __FILE__ and __LINE__
|
||||
Deprecated in 0.6.0, removed in 0.7.0.
|
||||
.Pp
|
||||
@@ -301,6 +318,11 @@ Supported in ASMotor, removed in RGBDS.
|
||||
.Pp
|
||||
Instead, use
|
||||
.Ql LD HL, SP + e8 .
|
||||
.Ss OPT z
|
||||
Deprecated in 0.4.0, removed in 0.5.0.
|
||||
.Pp
|
||||
Instead, use
|
||||
.Ic OPT p .
|
||||
.Ss rgbasm -i
|
||||
Deprecated in 0.6.0, removed in 0.8.0.
|
||||
.Pp
|
||||
@@ -356,6 +378,36 @@ because $16384$ turns = $16384 tau$ radians = $32768 pi$ radians, and $sin ( 327
|
||||
.EQ
|
||||
delim off
|
||||
.EN
|
||||
.Ss % operator behavior with negative dividend or divisor
|
||||
Changed in 0.5.0.
|
||||
.Pp
|
||||
Instead of having the same sign as the dividend (a remainder operation),
|
||||
.Ql %
|
||||
has the same sign as the divisor (a modulo operation).
|
||||
.Pp
|
||||
For example, previously we had:
|
||||
.Bl -bullet -offset indent
|
||||
.It
|
||||
.Ql 13 % 10 == 3
|
||||
.It
|
||||
.Ql -13 % 10 == -3
|
||||
.It
|
||||
.Ql 13 % -10 == 3
|
||||
.It
|
||||
.Ql -13 % -10 == -3
|
||||
.El
|
||||
.Pp
|
||||
Instead, now we have:
|
||||
.Bl -bullet -offset indent
|
||||
.It
|
||||
.Ql 13 % 10 == 3
|
||||
.It
|
||||
.Ql -13 % 10 == 7
|
||||
.It
|
||||
.Ql 13 % -10 == -7
|
||||
.It
|
||||
.Ql -13 % -10 == -3
|
||||
.El
|
||||
.Ss ** operator associativity
|
||||
Changed in 0.9.0.
|
||||
.Pp
|
||||
|
||||
80
man/rgbasm.1
80
man/rgbasm.1
@@ -1,6 +1,6 @@
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd February 2, 2025
|
||||
.Dd July 31, 2025
|
||||
.Dt RGBASM 1
|
||||
.Os
|
||||
.Sh NAME
|
||||
@@ -15,6 +15,7 @@
|
||||
.Op Fl I Ar path
|
||||
.Op Fl M Ar depend_file
|
||||
.Op Fl MG
|
||||
.Op Fl MC
|
||||
.Op Fl MP
|
||||
.Op Fl MT Ar target_file
|
||||
.Op Fl MQ Ar target_file
|
||||
@@ -51,8 +52,19 @@ is invalid because it could also be
|
||||
The arguments are as follows:
|
||||
.Bl -tag -width Ds
|
||||
.It Fl b Ar chars , Fl \-binary-digits Ar chars
|
||||
Change the two characters used for binary constants.
|
||||
The defaults are 01.
|
||||
Allow two characters to be used for binary constants in addition to the default
|
||||
.Sq 0
|
||||
and
|
||||
.Sq 1 .
|
||||
Valid characters are numbers other than
|
||||
.Sq 0
|
||||
and
|
||||
.Sq 1 ,
|
||||
letters,
|
||||
.Sq \&. ,
|
||||
.Sq # ,
|
||||
or
|
||||
.Sq @ .
|
||||
.It Fl D Ar name Ns Oo = Ns Ar value Oc , Fl \-define Ar name Ns Oo = Ns Ar value Oc
|
||||
Add a string symbol to the compiled source code.
|
||||
This is equivalent to
|
||||
@@ -65,7 +77,21 @@ is not specified.
|
||||
.It Fl E , Fl \-export-all
|
||||
Export all labels, including unreferenced and local labels.
|
||||
.It Fl g Ar chars , Fl \-gfx-chars Ar chars
|
||||
Change the four characters used for gfx constants.
|
||||
Allow four characters to be used for graphics constants in addition to the default
|
||||
.Sq 0 ,
|
||||
.Sq 1 ,
|
||||
.Sq 2 ,
|
||||
and
|
||||
.Sq 3 .
|
||||
Valid characters are numbers other than
|
||||
.Sq 0
|
||||
to
|
||||
.Sq 3 ,
|
||||
letters,
|
||||
.Sq \&. ,
|
||||
.Sq # ,
|
||||
or
|
||||
.Sq @ .
|
||||
The defaults are 0123.
|
||||
.It Fl h , Fl \-help
|
||||
Print help text for the program and exit.
|
||||
@@ -74,11 +100,12 @@ Add a new
|
||||
.Dq include path ;
|
||||
.Ar path
|
||||
must point to a directory.
|
||||
When a
|
||||
When any
|
||||
.Ic INCLUDE
|
||||
.Pq including the implicit one from Fl P
|
||||
.Pq including the implicit one from Fl P ,
|
||||
.Ic INCBIN ,
|
||||
or
|
||||
.Ic INCBIN
|
||||
.Ic READFILE
|
||||
is attempted,
|
||||
.Nm
|
||||
first looks up the provided path from its working directory; if this fails, it tries again from each of the
|
||||
@@ -94,17 +121,33 @@ To be used in conjunction with
|
||||
.Fl M .
|
||||
This makes
|
||||
.Nm
|
||||
assume that missing files are auto-generated: when
|
||||
assume that missing files are auto-generated: when any
|
||||
.Ic INCLUDE
|
||||
.Pq including the implicit one from Fl P
|
||||
.Pq including the implicit one from Fl P ,
|
||||
.Ic INCBIN ,
|
||||
or
|
||||
.Ic INCBIN
|
||||
.Ic READFILE
|
||||
is attempted on a non-existent file, it is added as a dependency, then
|
||||
.Nm
|
||||
exits normally instead of erroring out.
|
||||
This feature is used in automatic updating of makefiles.
|
||||
exits normally or continues processing (depending on whether
|
||||
.Fl MC
|
||||
was enabled) instead of erroring out.
|
||||
This feature is used in automatic updating of Makefiles.
|
||||
.It Fl MC
|
||||
Implies
|
||||
.Fl MG .
|
||||
This makes
|
||||
.Nm
|
||||
continue processing after a non-existent dependency file, instead of exiting.
|
||||
Note that this is
|
||||
.Em not
|
||||
recommended if any non-existent dependencies would have influenced subsequent processing, e.g. by causing an
|
||||
.Ic IF
|
||||
condition to take a different branch.
|
||||
.It Fl MP
|
||||
When enabled, this causes a phony target to be added for each dependency other than the main file.
|
||||
When enabled, this adds a phony target to the rules emitted by
|
||||
.Fl M
|
||||
for each dependency other than the main file.
|
||||
This prevents
|
||||
.Xr make 1
|
||||
from erroring out when dependency files are deleted.
|
||||
@@ -133,6 +176,7 @@ This acts as if a
|
||||
.Ql Ic INCLUDE Qq Ar include_file
|
||||
was read before the input
|
||||
.Ar asmfile .
|
||||
Multiple files can be pre-included in the order they were provided.
|
||||
.It Fl p Ar pad_value , Fl \-pad-value Ar pad_value
|
||||
Use this as the value for
|
||||
.Ic DS
|
||||
@@ -244,7 +288,7 @@ Enables literally every warning.
|
||||
.El
|
||||
.Pp
|
||||
The following warnings are actual warning flags; with each description, the corresponding warning flag is included.
|
||||
Note that each of these flag also has a negation (for example,
|
||||
Note that each of these flags also has a negation (for example,
|
||||
.Fl Wcharmap-redef
|
||||
enables the warning that
|
||||
.Fl Wno-charmap-redef
|
||||
@@ -275,7 +319,7 @@ This warning is enabled by
|
||||
.Fl Wall .
|
||||
.It Fl Wbuiltin-args
|
||||
Warn about incorrect arguments to built-in functions, such as
|
||||
.Fn STRSUB
|
||||
.Fn STRSLICE
|
||||
with indexes outside of the string's bounds.
|
||||
This warning is enabled by
|
||||
.Fl Wall .
|
||||
@@ -311,11 +355,7 @@ Block comments cannot be nested, so the first
|
||||
.Ql */
|
||||
will end the whole comment.
|
||||
.It Fl Wno-obsolete
|
||||
Warn when obsolete constructs such as the
|
||||
.Ic _PI
|
||||
constant or
|
||||
.Ic PRINTT
|
||||
directive are encountered.
|
||||
Warn when obsolete features are encountered, which have been deprecated and may later be removed.
|
||||
.It Fl Wnumeric-string=
|
||||
Warn when a multi-character string is treated as a number.
|
||||
.Fl Wnumeric-string=0
|
||||
|
||||
223
man/rgbasm.5
223
man/rgbasm.5
@@ -2,7 +2,7 @@
|
||||
.\"
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd February 2, 2025
|
||||
.Dd July 31, 2025
|
||||
.Dt RGBASM 5
|
||||
.Os
|
||||
.Sh NAME
|
||||
@@ -280,7 +280,7 @@ There are a number of numeric formats.
|
||||
.It Binary Ta Li % , 0b , 0B Ta 01
|
||||
.It Fixed-point Ta none Ta 01234.56789
|
||||
.It Precise fixed-point Ta none Ta 12.34q8
|
||||
.It Character constant Ta none Ta \(dqABYZ\(dq
|
||||
.It Character constant Ta none Ta 'ABYZ'
|
||||
.It Game Boy graphics Ta Li \` Ta 0123
|
||||
.El
|
||||
.Pp
|
||||
@@ -293,11 +293,14 @@ or
|
||||
The "character constant" form yields the value the character maps to in the current charmap.
|
||||
For example, by default
|
||||
.Pq refer to Xr ascii 7
|
||||
.Sq \(dqA\(dq
|
||||
.Sq 'A'
|
||||
yields 65.
|
||||
A character constant must represent a single value, so it cannot include multiple characters, or characters which map to multiple values.
|
||||
See
|
||||
.Sx Character maps
|
||||
for information on charmaps.
|
||||
for information on charmaps, and
|
||||
.Sx String expressions
|
||||
for information on escape characters allowed in character constants.
|
||||
.Pp
|
||||
The last one, Game Boy graphics, is quite interesting and useful.
|
||||
After the backtick, 8 digits between 0 and 3 are expected, corresponding to pixel values.
|
||||
@@ -538,7 +541,8 @@ There are a number of escape sequences you can use within a string:
|
||||
.Bl -column -offset indent "Sequence"
|
||||
.It Sy Sequence Ta Sy Meaning
|
||||
.It Ql \e\e Ta Backslash Pq escapes the escape character itself
|
||||
.It Ql \e" Ta Double quote Pq does not terminate the string
|
||||
.It Ql \e" Ta Double quote Pq does not terminate a string
|
||||
.It Ql \e' Ta Single quote Pq does not terminate a character literal
|
||||
.It Ql \e{ Ta Open curly brace Pq does not start interpolation
|
||||
.It Ql \e} Ta Close curly brace Pq does not end interpolation
|
||||
.It Ql \en Ta Newline Pq ASCII $0A
|
||||
@@ -548,7 +552,7 @@ There are a number of escape sequences you can use within a string:
|
||||
.El
|
||||
.Pp
|
||||
Multi-line strings are contained in triple quotes
|
||||
.Pq Ql \&"\&"\&"for instance\&"\&"\&" .
|
||||
.Pq Ql \&"\&"\&"for instance""" .
|
||||
Escape sequences work the same way in multi-line strings; however, literal newline characters will be included as-is, without needing to escape them with
|
||||
.Ql \er
|
||||
or
|
||||
@@ -560,26 +564,30 @@ Inside them, backslashes and braces are treated like regular characters, so they
|
||||
For example, the raw string
|
||||
.Ql #"\et\e1{s}\e"
|
||||
is equivalent to the regular string
|
||||
.Ql "\e\et\e\e1\e{s}\e\e" .
|
||||
.Ql \&"\e\et\e\e1\e{s}\e\e" .
|
||||
(Note that this prevents raw strings from including the double quote character.)
|
||||
Raw strings also may be contained in triple quotes for them to be multi-line, so they can include literal newline or quote characters (although still not three quotes in a row).
|
||||
.Pp
|
||||
The following functions operate on string expressions.
|
||||
Most of them return a string, however some of these functions actually return an integer and can be used as part of an integer expression!
|
||||
.Bl -column "STRSUB(str, pos, len)"
|
||||
You can use the
|
||||
.Sq ++
|
||||
operator to concatenate two strings.
|
||||
.Ql \&"str" ++ \&"ing"
|
||||
is equivalent to
|
||||
.Ql \&"string" ,
|
||||
or to
|
||||
.Ql STRCAT("str", \&"ing") .
|
||||
.Pp
|
||||
The following functions operate on string expressions, and return strings themselves.
|
||||
.Bl -column "STRSLICE(str, start, stop)"
|
||||
.It Sy Name Ta Sy Operation
|
||||
.It Fn STRLEN str Ta Returns the number of characters in Ar str .
|
||||
.It Fn STRCAT strs... Ta Concatenates Ar strs .
|
||||
.It Fn STRCMP str1 str2 Ta Returns -1 if Ar str1 No is alphabetically lower than Ar str2 No , zero if they match, 1 if Ar str1 No is greater than Ar str2 .
|
||||
.It Fn STRIN str1 str2 Ta Returns the first position of Ar str2 No in Ar str1 No or zero if it's not present Pq first character is position 1 .
|
||||
.It Fn STRRIN str1 str2 Ta Returns the last position of Ar str2 No in Ar str1 No or zero if it's not present Pq first character is position 1 .
|
||||
.It Fn STRSUB str pos len Ta Returns a substring from Ar str No starting at Ar pos No (first character is position 1, last is position -1) and Ar len No characters long. If Ar len No is not specified the substring continues to the end of Ar str .
|
||||
.It Fn STRUPR str Ta Returns Ar str No with all ASCII letters
|
||||
.Pq Ql a-z
|
||||
in uppercase.
|
||||
.It Fn STRLWR str Ta Returns Ar str No with all ASCII letters
|
||||
.Pq Ql A-Z
|
||||
in lowercase.
|
||||
.It Fn STRSLICE str start stop Ta Returns a substring of Ar str No starting at Ar start No and ending at Ar stop No (exclusive). If Ar stop No is not specified, the substring continues to the end of Ar str .
|
||||
.It Fn STRRPL str old new Ta Returns Ar str No with each non-overlapping occurrence of the substring Ar old No replaced with Ar new .
|
||||
.It Fn STRFMT fmt args... Ta Returns the string Ar fmt No with each
|
||||
.Ql %spec
|
||||
@@ -589,9 +597,45 @@ pattern replaced by interpolating the format
|
||||
with its corresponding argument in
|
||||
.Ar args
|
||||
.Pq So %% Sc is replaced by the So % Sc character .
|
||||
.It Fn INCHARMAP str Ta Returns 1 if Ar str No has an entry in the current charmap, and 0 otherwise .
|
||||
.It Fn CHARLEN str Ta Returns the number of charmap entries in Ar str No with the current charmap .
|
||||
.It Fn CHARSUB str pos Ta Returns the substring for the charmap entry at Ar pos No in Ar str No (first character is position 1, last is position -1) with the current charmap .
|
||||
.It Fn STRCHAR str idx Ta Returns the substring of Ar str No for the charmap entry at Ar idx No with the current charmap . Pq Ar idx No counts charmap entries, not characters.
|
||||
.It Fn REVCHAR vals... Ta Returns the string that is mapped to Ar vals No with the current charmap. If there is no unique charmap entry for Ar vals Ns , an error occurs.
|
||||
.It Fn READFILE name max Ta Returns the contents of the file Ar name No as a string. Reads up to Ar max No bytes, or the entire contents if Ar max No is not specified. If the file isn't found in the current directory, the include-path list passed to Xr rgbasm 1 Ap s Fl I No option on the command line will be searched.
|
||||
.El
|
||||
.Pp
|
||||
The following functions operate on string expressions, but return integers.
|
||||
.Bl -column "STRRFIND(str, sub)"
|
||||
.It Sy Name Ta Sy Operation
|
||||
.It Fn STRLEN str Ta Returns the number of characters in Ar str .
|
||||
.It Fn STRCMP str1 str2 Ta Compares Ar str1 No and Ar str2 No according to ASCII ordering of their characters. Returns -1 if Ar str1 No is lower than Ar str2 Ns , 1 if Ar str1 No is greater than Ar str2 Ns , or 0 if they match.
|
||||
.It Fn STRFIND str sub Ta Returns the first index of Ar sub No in Ar str Ns , or -1 if it's not present.
|
||||
.It Fn STRRFIND str sub Ta Returns the last index of Ar sub No in Ar str Ns , or -1 if it's not present.
|
||||
.It Fn BYTELEN str Ta Returns the number of bytes in Ar str . Pq Non-ASCII characters can be multiple bytes.
|
||||
.It Fn STRBYTE str idx Ta Returns the byte value at Ar idx No in Ar str .
|
||||
.It Fn INCHARMAP str Ta Returns 1 if Ar str No has an entry in the current charmap, or 0 otherwise.
|
||||
.It Fn CHARLEN str Ta Returns the number of charmap entries in Ar str No with the current charmap.
|
||||
.It Fn CHARCMP str1 str2 Ta Compares Ar str1 No and Ar str2 No according to their charmap entry values with the current charmap. Returns -1 if Ar str1 No is lower than Ar str2 Ns , 1 if Ar str1 No is greater than Ar str2 Ns , or 0 if they match.
|
||||
.It Fn CHARSIZE char Ta Returns how many values are in the charmap entry for Ar char No with the current charmap.
|
||||
.It Fn CHARVAL char idx Ta Returns the value at Ar idx No of the charmap entry for Ar char .
|
||||
.El
|
||||
.Pp
|
||||
Note that indexes count starting from 0 at the beginning, or from -1 at the end.
|
||||
The characters of a string are counted by
|
||||
.Ql STRLEN ;
|
||||
the charmap entries of a string are counted by
|
||||
.Ql CHARLEN ;
|
||||
and the values of a charmap entry are counted by
|
||||
.Ql CHARSIZE .
|
||||
.Pp
|
||||
The following legacy functions are similar to other functions that operate on string expressions, but for historical reasons, they count starting from
|
||||
.Em position 1 ,
|
||||
not from index 0!
|
||||
(Position -1 still counts from the end.)
|
||||
.Bl -column "STRSUB(str, pos, len)"
|
||||
.It Sy Name Ta Sy Operation
|
||||
.It Fn STRSUB str pos len Ta Returns a substring of Ar str No starting at Ar pos No and Ar len No characters long. If Ar len No is not specified, the substring continues to the end of Ar str No .
|
||||
.It Fn STRIN str sub Ta Returns the first position of Ar sub No in Ar str Ns , or 0 if it's not present.
|
||||
.It Fn STRRIN str sub Ta Returns the last position of Ar sub No in Ar str Ns , or 0 if it's not present.
|
||||
.It Fn CHARSUB str pos Ta Returns the substring of Ar str No for the charmap entry at Ar pos No with the current charmap . Pq Ar pos No counts charmap entries, not characters.
|
||||
.El
|
||||
.Ss Character maps
|
||||
When writing text strings that are meant to be displayed on the Game Boy, the character encoding in the ROM may need to be different than the source file encoding.
|
||||
@@ -673,6 +717,9 @@ If
|
||||
.Ar arg
|
||||
is a section type keyword, it returns the size of that section type.
|
||||
The result is not constant, since only RGBLINK can compute its value.
|
||||
If
|
||||
.Ar arg
|
||||
is an 8-bit or 16-bit register, it returns the size of that register.
|
||||
.It Fn STARTOF arg Ta If
|
||||
.Ar arg
|
||||
is a string, this function returns the starting address of the section named
|
||||
@@ -1052,7 +1099,7 @@ and
|
||||
.Ic WRAMX
|
||||
types are still considered different.
|
||||
.It
|
||||
Different constraints (alignment, bank, etc.) can be specified for each unionized section declaration, but they must all be compatible.
|
||||
Different constraints (alignment, bank, etc.) can be specified for each section fragment declaration, but they must all be compatible.
|
||||
For example, alignment must be compatible with any fixed address, all specified banks must be the same, etc.
|
||||
.It
|
||||
A section fragment may not be unionized; after all, that wouldn't make much sense.
|
||||
@@ -1075,6 +1122,120 @@ first, followed by the one from
|
||||
and the one from
|
||||
.Ql bar.o
|
||||
last.
|
||||
.Ss Fragment literals
|
||||
Fragment literals are useful for short blocks of code or data that are only referenced once.
|
||||
They are section fragments created by surrounding instructions or directives with
|
||||
.Ql [[
|
||||
double brackets
|
||||
.Ql ]] ,
|
||||
without a separate
|
||||
.Ic SECTION FRAGMENT
|
||||
declaration.
|
||||
.Pp
|
||||
The content of a fragment literal becomes a
|
||||
.Ic SECTION FRAGMENT ,
|
||||
sharing the same name and bank as its parent ROM section, but without any other constraints.
|
||||
The parent section also becomes a
|
||||
.Ic FRAGMENT
|
||||
if it was not one already, so that it can be merged with its fragment literals.
|
||||
RGBLINK merges the fragments in no particular order.
|
||||
.Pp
|
||||
A fragment literal can take the place of any 16-bit integer constant
|
||||
.Ql n16
|
||||
from the
|
||||
.Xr gbz80 7
|
||||
documentation, as well as a
|
||||
.Ic DW
|
||||
item.
|
||||
The fragment literal then evaluates to its starting address.
|
||||
For example, you can
|
||||
.Ic CALL
|
||||
or
|
||||
.Ic JP
|
||||
to a fragment literal.
|
||||
.Pp
|
||||
This code using named labels:
|
||||
.Bd -literal -offset indent
|
||||
DataTable:
|
||||
dw First
|
||||
dw Second
|
||||
dw Third
|
||||
First: db 1
|
||||
Second: db 4
|
||||
Third: db 9
|
||||
Routine:
|
||||
push hl
|
||||
ld hl, Left
|
||||
jr z, .got_it
|
||||
ld hl, Right
|
||||
\&.got_it
|
||||
call .print
|
||||
pop hl
|
||||
ret
|
||||
\&.print:
|
||||
ld de, $1003
|
||||
ld bc, STARTOF(VRAM)
|
||||
jp Print
|
||||
Left: db "left\e0"
|
||||
Right: db "right\e0"
|
||||
.Ed
|
||||
.Pp
|
||||
is equivalent to this code using fragment literals:
|
||||
.Bd -literal -offset indent
|
||||
DataTable:
|
||||
dw [[ db 1 ]]
|
||||
dw [[ db 4 ]]
|
||||
dw [[ db 9 ]]
|
||||
Routine:
|
||||
push hl
|
||||
ld hl, [[ db "left\e0" ]]
|
||||
jr z, .got_it
|
||||
ld hl, [[ db "right\e0" ]]
|
||||
\&.got_it
|
||||
call [[
|
||||
ld de, $1003
|
||||
ld bc, STARTOF(VRAM)
|
||||
jp Print
|
||||
]]
|
||||
pop hl
|
||||
ret
|
||||
.Ed
|
||||
.Pp
|
||||
The difference is that the example using fragment literals does not declare a particular order for its pieces.
|
||||
.Pp
|
||||
Fragment literals can be arbitrarily nested, so extreme use cases are
|
||||
.Em technically
|
||||
possible.
|
||||
This code using named labels:
|
||||
.Bd -literal -offset indent
|
||||
dw FortyTwo
|
||||
FortyTwo:
|
||||
call Sub1
|
||||
jr Sub2
|
||||
Sub1:
|
||||
ld a, [Twenty]
|
||||
ret
|
||||
Twenty: db 20
|
||||
Sub2:
|
||||
jp Sub3
|
||||
Sub3:
|
||||
call Sub1
|
||||
inc a
|
||||
add a
|
||||
ret
|
||||
.Ed
|
||||
.Pp
|
||||
is equivalent to this code using fragment literals:
|
||||
.Bd -literal -offset indent
|
||||
dw [[
|
||||
call [[
|
||||
Sub1: ld a, [ [[db 20]] ] :: ret
|
||||
]]
|
||||
jr [[
|
||||
jp [[ call Sub1 :: inc a :: add a :: ret ]]
|
||||
]]
|
||||
]]
|
||||
.Ed
|
||||
.Sh SYMBOLS
|
||||
RGBDS supports several types of symbols:
|
||||
.Bl -hang
|
||||
@@ -1104,7 +1265,9 @@ Additionally, label names can contain up to a single dot
|
||||
.Ql \&. ,
|
||||
which may not be the first character.
|
||||
.Pp
|
||||
A symbol cannot have the same name as a reserved keyword, unless it is prefixed by a hash
|
||||
A symbol cannot have the same name as a reserved keyword, unless its name is a
|
||||
.Dq raw identifier
|
||||
prefixed by a hash
|
||||
.Sq # .
|
||||
For example,
|
||||
.Ql #load
|
||||
@@ -1279,7 +1442,7 @@ it at the same time.
|
||||
below).
|
||||
.Ss Numeric constants
|
||||
.Ic EQU
|
||||
is used to define immutable numeric symbols.
|
||||
is used to define numeric constant symbols.
|
||||
Unlike
|
||||
.Sq =
|
||||
above, constants defined this way cannot be redefined.
|
||||
@@ -1387,6 +1550,8 @@ This expansion is disabled in a few contexts:
|
||||
and
|
||||
.Ql MACRO name
|
||||
will not expand string constants in their names.
|
||||
Expansion is also disabled if the string constant's name is a raw identifier prefixed by a hash
|
||||
.Sq # .
|
||||
.Bd -literal -offset indent
|
||||
DEF COUNTREG EQUS "[hl+]"
|
||||
ld a, COUNTREG
|
||||
@@ -1650,10 +1815,9 @@ Use
|
||||
.Ic INCBIN
|
||||
to include a raw binary file as it is.
|
||||
If the file isn't found in the current directory, the include-path list passed to
|
||||
.Xr rgbasm 1
|
||||
(see the
|
||||
.Xr rgbasm 1 Ap s
|
||||
.Fl I
|
||||
option) on the command line will be searched.
|
||||
option on the command line will be searched.
|
||||
.Bd -literal -offset indent
|
||||
INCBIN "titlepic.bin"
|
||||
INCBIN "sprites/hero.bin"
|
||||
@@ -1852,9 +2016,11 @@ being the second, and so on. Since there are only nine digits, you can only use
|
||||
To use the rest, you put the argument number in angle brackets, like
|
||||
.Ic \e<10> .
|
||||
.Pp
|
||||
This bracketed syntax supports decimal numbers and numeric constant symbols.
|
||||
This bracketed syntax supports decimal numbers and numeric symbols, where negative values count from the last argument.
|
||||
For example,
|
||||
.Ql \e<_NARG>
|
||||
or
|
||||
.Ql \e<-1>
|
||||
will get the last argument.
|
||||
.Pp
|
||||
Other macro arguments and symbol interpolations will also be expanded inside the angle brackets.
|
||||
@@ -2204,11 +2370,10 @@ block, all of them but the first one are ignored.
|
||||
Use
|
||||
.Ic INCLUDE
|
||||
to process another assembler file and then return to the current file when done.
|
||||
If the file isn't found in the current directory, the include path list (see the
|
||||
If the file isn't found in the current directory, the include-path list passed to
|
||||
.Xr rgbasm 1 Ap s
|
||||
.Fl I
|
||||
option in
|
||||
.Xr rgbasm 1 )
|
||||
will be searched.
|
||||
option on the command line will be searched.
|
||||
You may nest
|
||||
.Ic INCLUDE
|
||||
calls infinitely (or until you run out of memory, whichever comes first).
|
||||
|
||||
15
man/rgbds.5
15
man/rgbds.5
@@ -1,6 +1,6 @@
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd February 2, 2025
|
||||
.Dd July 31, 2025
|
||||
.Dt RGBDS 5
|
||||
.Os
|
||||
.Sh NAME
|
||||
@@ -388,10 +388,19 @@ The value is then ANDed with $00FF
|
||||
check.
|
||||
Checks if the value is a valid
|
||||
.Ql rst
|
||||
.Pq see Do RST vec Dc in Xr gbz80 7
|
||||
vector, that is one of $00, $08, $10, $18, $20, $28, $30, or $38.
|
||||
vector
|
||||
.Pq see Do RST vec Dc in Xr gbz80 7 ,
|
||||
that is, one of $00, $08, $10, $18, $20, $28, $30, or $38.
|
||||
The value is then ORed with $C7
|
||||
.Pq Ql \&| $C7 .
|
||||
.It Li $62 Ta Ql bit/res/set
|
||||
check; followed by the instruction's
|
||||
.Cm BYTE
|
||||
mask.
|
||||
Checks if the value is a valid bit index
|
||||
.Pq see e.g. Do BIT u3, r8 Dc in Xr gbz80 7 ,
|
||||
that is, from 0 to 7.
|
||||
The value is then ORed with the instruction's mask.
|
||||
.It Li $80 Ta Integer literal; followed by the
|
||||
.Cm LONG
|
||||
integer.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd February 2, 2025
|
||||
.Dd July 31, 2025
|
||||
.Dt RGBDS 7
|
||||
.Os
|
||||
.Sh NAME
|
||||
@@ -57,7 +57,7 @@ to this day.
|
||||
.It
|
||||
2015-01-18:
|
||||
.An stag019
|
||||
begins implementing RGBGFX, a PNG‐to‐Game Boy graphics converter, for eventual integration into RGBDS.
|
||||
begins implementing RGBGFX, a PNG-to-Game Boy graphics converter, for eventual integration into RGBDS.
|
||||
.It
|
||||
2016-09-05: RGBGFX is integrated into Bentley's repository.
|
||||
.It
|
||||
|
||||
100
man/rgbfix.1
100
man/rgbfix.1
@@ -1,6 +1,6 @@
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd February 2, 2025
|
||||
.Dd July 31, 2025
|
||||
.Dt RGBFIX 1
|
||||
.Os
|
||||
.Sh NAME
|
||||
@@ -8,7 +8,7 @@
|
||||
.Nd Game Boy header utility and checksum fixer
|
||||
.Sh SYNOPSIS
|
||||
.Nm
|
||||
.Op Fl hjOsVv
|
||||
.Op Fl hjOsVvw
|
||||
.Op Fl C | c
|
||||
.Op Fl f Ar fix_spec
|
||||
.Op Fl i Ar game_id
|
||||
@@ -17,9 +17,11 @@
|
||||
.Op Fl l Ar licensee_id
|
||||
.Op Fl m Ar mbc_type
|
||||
.Op Fl n Ar rom_version
|
||||
.Op Fl o Ar out_file
|
||||
.Op Fl p Ar pad_value
|
||||
.Op Fl r Ar ram_size
|
||||
.Op Fl t Ar title_str
|
||||
.Op Fl W Ar warning
|
||||
.Op Ar
|
||||
.Sh DESCRIPTION
|
||||
The
|
||||
@@ -97,7 +99,7 @@ Print help text for the program and exit.
|
||||
Set the game ID string
|
||||
.Pq Ad 0x13F Ns \(en Ns Ad 0x142
|
||||
to a given string.
|
||||
If it's longer than 4 chars, it will be truncated, and a warning emitted.
|
||||
If it's longer than 4 characters, it will be truncated.
|
||||
.It Fl j , Fl \-non-japanese
|
||||
Set the non-Japanese region flag
|
||||
.Pq Ad 0x14A
|
||||
@@ -106,7 +108,7 @@ to 0x01.
|
||||
Set the new licensee string
|
||||
.Pq Ad 0x144 Ns \(en Ns Ad 0x145
|
||||
to a given string.
|
||||
If it's longer than 2 chars, it will be truncated, and a warning emitted.
|
||||
If it's longer than 2 characters, it will be truncated.
|
||||
.It Fl L Ar logo_file , Fl \-logo Ar logo_file
|
||||
Specify a logo file to use instead of the official Nintendo logo.
|
||||
The file must be 48 bytes of 1bpp tile data; the source image should be 48 pixels wide and 8 pixels tall.
|
||||
@@ -123,6 +125,8 @@ to a given value from 0 to 0xFF.
|
||||
This value may also be an MBC name.
|
||||
The list of accepted names can be obtained by passing
|
||||
.Ql Cm help
|
||||
or
|
||||
.Ql Cm list
|
||||
as the argument.
|
||||
Any amount of whitespace (space and tabs) is allowed around plus signs, and the order of "components" is free, as long as the MBC name is first.
|
||||
There are special considerations to take for the TPP1 mapper; see the
|
||||
@@ -133,7 +137,11 @@ Set the ROM version
|
||||
.Pq Ad 0x14C
|
||||
to a given value from 0 to 0xFF.
|
||||
.It Fl O , Fl \-overwrite
|
||||
Allow overwriting different non-zero bytes in the header without a warning being emitted.
|
||||
Alias for
|
||||
.Fl Wno-overwrite .
|
||||
.It Fl o Ar out_file , Fl \-output Ar out_file
|
||||
Write the modified ROM image to the given file, or '-' to write to standard output.
|
||||
If not specified, the input files are modified in-place, or written to standard output if read from standard input.
|
||||
.It Fl p Ar pad_value , Fl \-pad-value Ar pad_value
|
||||
Pad the ROM image to a valid size with a given pad value from 0 to 255 (0xFF).
|
||||
.Nm
|
||||
@@ -150,15 +158,14 @@ to a given value from 0 to 0xFF.
|
||||
Set the SGB flag
|
||||
.Pq Ad 0x146
|
||||
to 0x03.
|
||||
This flag will be ignored by the SGB unless the old licensee code is 0x33!
|
||||
If this is given as well as
|
||||
.Fl l ,
|
||||
but is not set to 0x33, a warning will be printed.
|
||||
This flag will be ignored by the SGB unless the old licensee code
|
||||
.Pq Fl -l
|
||||
is 0x33!
|
||||
.It Fl t Ar title , Fl \-title Ar title
|
||||
Set the title string
|
||||
.Pq Ad 0x134 Ns \(en Ns Ad 0x143
|
||||
to a given string.
|
||||
If the title is longer than the max length, it will be truncated, and a warning emitted.
|
||||
If the title is longer than the maximum length, it will be truncated.
|
||||
The max length is 11 characters if the game ID
|
||||
.Pq Fl i
|
||||
is specified, 15 characters if the CGB flag
|
||||
@@ -171,6 +178,77 @@ Print the version of the program and exit.
|
||||
.It Fl v , Fl \-validate
|
||||
Equivalent to
|
||||
.Fl f Cm lhg .
|
||||
.It Fl W Ar warning , Fl \-warning Ar warning
|
||||
Set warning flag
|
||||
.Ar warning .
|
||||
A warning message will be printed if
|
||||
.Ar warning
|
||||
is an unknown warning flag.
|
||||
See the
|
||||
.Sx DIAGNOSTICS
|
||||
section for a list of warnings.
|
||||
.It Fl w
|
||||
Disable all warning output, even when turned into errors.
|
||||
.El
|
||||
.Sh DIAGNOSTICS
|
||||
Warnings are diagnostic messages that indicate possibly erroneous behavior that does not necessarily compromise the header-fixing process.
|
||||
The following options alter the way warnings are processed.
|
||||
.Bl -tag -width Ds
|
||||
.It Fl Werror
|
||||
Make all warnings into errors.
|
||||
This can be negated as
|
||||
.Fl Wno-error
|
||||
to prevent turning all warnings into errors.
|
||||
.It Fl Werror=
|
||||
Make the specified warning or meta warning into an error.
|
||||
A warning's name is appended
|
||||
.Pq example: Fl Werror=overwrite ,
|
||||
and this warning is implicitly enabled and turned into an error.
|
||||
This can be negated as
|
||||
.Fl Wno-error=
|
||||
to prevent turning a specified warning into an error, even if
|
||||
.Fl Werror
|
||||
is in effect.
|
||||
.El
|
||||
.Pp
|
||||
The following warnings are
|
||||
.Dq meta
|
||||
warnings, that enable a collection of other warnings.
|
||||
If a specific warning is toggled via a meta flag and a specific one, the more specific one takes priority.
|
||||
The position on the command-line acts as a tie breaker, the last one taking effect.
|
||||
.Bl -tag -width Ds
|
||||
.It Fl Wall
|
||||
This enables warnings that are likely to indicate an error or undesired behavior, and that can easily be fixed.
|
||||
.It Fl Weverything
|
||||
Enables literally every warning.
|
||||
.El
|
||||
.Pp
|
||||
The following warnings are actual warning flags; with each description, the corresponding warning flag is included.
|
||||
Note that each of these flag also has a negation (for example,
|
||||
.Fl Wtruncation
|
||||
enables the warning that
|
||||
.Fl Wno-truncation
|
||||
disables; and
|
||||
.Fl Wall
|
||||
enables every warning that
|
||||
.Fl Wno-all
|
||||
disables).
|
||||
Only the non-default flag is listed here.
|
||||
Ignoring the
|
||||
.Dq no-
|
||||
prefix, entries are listed alphabetically.
|
||||
.Bl -tag -width Ds
|
||||
.It Fl Wno-mbc
|
||||
Warn when there are inconsistencies with or caveats about the specified MBC type.
|
||||
.It Fl Wno-overwrite
|
||||
Warn when overwriting different non-zero bytes in the header.
|
||||
.It Fl Wno-sgb
|
||||
Warn when the SGB flag
|
||||
.Pq Fl s
|
||||
conflicts with the old licensee code
|
||||
.Pq Fl l .
|
||||
.It Fl Wno-truncation
|
||||
Warn when truncating values to fit the available space.
|
||||
.El
|
||||
.Sh EXAMPLES
|
||||
Most values in the ROM header do not matter to the actual console, and most are seldom useful anyway.
|
||||
@@ -224,7 +302,7 @@ Therefore,
|
||||
.Nm
|
||||
will ignore the
|
||||
.Ql RAM
|
||||
feature on a TPP1 mapper with a warning.
|
||||
feature on a TPP1 mapper.
|
||||
.Ss Special considerations
|
||||
TPP1 overwrites the byte at
|
||||
.Ad 0x14A ,
|
||||
|
||||
122
man/rgbgfx.1
122
man/rgbgfx.1
@@ -2,7 +2,7 @@
|
||||
.\"
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd February 2, 2025
|
||||
.Dd July 31, 2025
|
||||
.Dt RGBGFX 1
|
||||
.Os
|
||||
.Sh NAME
|
||||
@@ -10,7 +10,7 @@
|
||||
.Nd Game Boy graphics converter
|
||||
.Sh SYNOPSIS
|
||||
.Nm
|
||||
.Op Fl CmhOuVXYZ
|
||||
.Op Fl CmhOuVwXYZ
|
||||
.Op Fl v Op Fl v No ...
|
||||
.Op Fl a Ar attrmap | Fl A
|
||||
.Op Fl b Ar base_ids
|
||||
@@ -18,6 +18,7 @@
|
||||
.Op Fl d Ar depth
|
||||
.Op Fl i Ar input_tiles
|
||||
.Op Fl L Ar slice
|
||||
.Op Fl l Ar base_pal
|
||||
.Op Fl N Ar nb_tiles
|
||||
.Op Fl n Ar nb_pals
|
||||
.Op Fl o Ar out_file
|
||||
@@ -26,6 +27,7 @@
|
||||
.Op Fl r Ar width
|
||||
.Op Fl s Ar nb_colors
|
||||
.Op Fl t Ar tilemap | Fl T
|
||||
.Op Fl W Ar warning
|
||||
.Op Fl x Ar quantity
|
||||
.Ar file
|
||||
.Sh DESCRIPTION
|
||||
@@ -103,6 +105,19 @@ and has the same size.
|
||||
Same as
|
||||
.Fl a Ar base_path Ns .attrmap
|
||||
.Pq see Sx Automatic output paths .
|
||||
.It Fl B Ar color , Fl \-background-color Ar color
|
||||
Set a background color to be omitted from output.
|
||||
Colors are accepted in
|
||||
.Ql #rgb
|
||||
or
|
||||
.Ql #rrggbb
|
||||
format, or as
|
||||
.Ql transparent .
|
||||
Input tiles which are entirely the specified background color are ignored and will not be output in tile data file.
|
||||
The tilemap, atrribute map, or palette map files
|
||||
.Em will
|
||||
use placeholder values where background tiles were.
|
||||
If a background color is specified, it cannot be used within tiles which are not ignored.
|
||||
.It Fl b Ar base_ids , Fl \-base-tiles Ar base_ids
|
||||
Set the base IDs for tile map output.
|
||||
.Ar base_ids
|
||||
@@ -126,7 +141,7 @@ begins with a hash character
|
||||
.Ql # ,
|
||||
it is treated as an inline palette specification.
|
||||
It should contain a comma-separated list of hexadecimal colors, each beginning with a hash.
|
||||
Colors are accepted either as
|
||||
Colors are accepted in
|
||||
.Ql #rgb
|
||||
or
|
||||
.Ql #rrggbb
|
||||
@@ -146,6 +161,24 @@ is the case-insensitive word
|
||||
then the first four colors of the input PNG's embedded palette are used.
|
||||
It is an error if the PNG is not indexed, or if colors other than these 4 are used.
|
||||
.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 DMG palette spec
|
||||
If
|
||||
.Ar pal_spec
|
||||
starts with case-insensitive
|
||||
.Cm dmg= ,
|
||||
then the following two-digit hexadecimal number specifies four grayscale DMG color indexes.
|
||||
The number functions like the DMG's $FF47
|
||||
.Sy BGP
|
||||
register
|
||||
(see
|
||||
.Lk https://gbdev.io/pandocs/Palettes.html Pan Docs
|
||||
for more information):
|
||||
the low two bits 0-1 specify which gray shade goes in color index 0,
|
||||
the next two bits 2-3 specify which gray shade goes in color index 1,
|
||||
and so on.
|
||||
Gray shade 0 is the lightest (white), 3 is the darkest (black).
|
||||
The same gray shade cannot go in two color indexes.
|
||||
To specify a DMG palette, the input PNG must have all its colors in shades of gray, without any transparent colors.
|
||||
.It Sy external palette spec
|
||||
Otherwise,
|
||||
.Ar pal_spec
|
||||
@@ -230,6 +263,11 @@ The first number pair specifies the X and Y coordinates of the top-left pixel th
|
||||
The second number pair specifies how many tiles to process horizontally and vertically, respectively.
|
||||
.Pp
|
||||
.Fl L Sy is ignored in reverse mode , No no padding is inserted .
|
||||
.It Fl l Ar base_pal , Fl \-base-palette Ar base_pal
|
||||
Set the base ID for attribute map and palette map output.
|
||||
.Ar base_pal
|
||||
should be a number between 0 and 255.
|
||||
It defaults to 0.
|
||||
.It Fl m , Fl \-mirror-tiles
|
||||
Deduplicate tiles that are horizontally and/or vertically symmetrical mirror images of each other.
|
||||
Only one of each unique tile will be saved in the tile data file, with mirror images counting as duplicates.
|
||||
@@ -356,6 +394,17 @@ Some internal debug printing is enabled.
|
||||
The verbosity level does not go past 6.
|
||||
.Pp
|
||||
Note that verbose output is only intended to be consumed by humans, and may change without notice between RGBDS releases; relying on those for scripts is not advised.
|
||||
.It Fl W Ar warning , Fl \-warning Ar warning
|
||||
Set warning flag
|
||||
.Ar warning .
|
||||
A warning message will be printed if
|
||||
.Ar warning
|
||||
is an unknown warning flag.
|
||||
See the
|
||||
.Sx DIAGNOSTICS
|
||||
section for a list of warnings.
|
||||
.It Fl w
|
||||
Disable all warning output, even when turned into errors.
|
||||
.It Fl X , Fl \-mirror-x
|
||||
Deduplicate tiles that are horizontally symmetrical mirror images of each other across the X axis.
|
||||
Implies
|
||||
@@ -467,6 +516,9 @@ Useful to force several images to share the same palette.
|
||||
Plaintext lines of hexadecimal colors in
|
||||
.Ql rrggbb
|
||||
format.
|
||||
.It Cm png
|
||||
An image of square color swatches, with each row defining the colors for one palette.
|
||||
Color swatches can be any square size.
|
||||
.It Cm psp
|
||||
.Lk https://www.selapa.net/swatches/colors/fileformats.php#psp_pal Paint Shop Pro palette .
|
||||
.El
|
||||
@@ -515,6 +567,8 @@ Otherwise, if the PNG only contains shades of gray, they will be categorized int
|
||||
.Dq bins
|
||||
as there are colors per palette, and the palette is set to these bins.
|
||||
The darkest gray will end up in bin #0, and so on; note that this is the opposite of the RGB method below.
|
||||
This is equivalent to having specified a DMG palette of
|
||||
.Fl c Cm dmg=E4 .
|
||||
If two distinct grays end up in the same bin, the RGB method is used instead.
|
||||
.Pp
|
||||
Be careful that
|
||||
@@ -669,6 +723,68 @@ assume that tiles were not deduplicated, and should be laid out in the order the
|
||||
.Nm
|
||||
assumes that no tiles were mirrored.
|
||||
.El
|
||||
.Sh DIAGNOSTICS
|
||||
Warnings are diagnostic messages that indicate possibly erroneous behavior that does not necessarily compromise the conversion process.
|
||||
The following options alter the way warnings are processed.
|
||||
.Bl -tag -width Ds
|
||||
.It Fl Werror
|
||||
Make all warnings into errors.
|
||||
This can be negated as
|
||||
.Fl Wno-error
|
||||
to prevent turning all warnings into errors.
|
||||
.It Fl Werror=
|
||||
Make the specified warning or meta warning into an error.
|
||||
A warning's name is appended
|
||||
.Pq example: Fl Werror=embedded ,
|
||||
and this warning is implicitly enabled and turned into an error.
|
||||
This can be negated as
|
||||
.Fl Wno-error=
|
||||
to prevent turning a specified warning into an error, even if
|
||||
.Fl Werror
|
||||
is in effect.
|
||||
.El
|
||||
.Pp
|
||||
The following warnings are
|
||||
.Dq meta
|
||||
warnings, that enable a collection of other warnings.
|
||||
If a specific warning is toggled via a meta flag and a specific one, the more specific one takes priority.
|
||||
The position on the command-line acts as a tie breaker, the last one taking effect.
|
||||
.Bl -tag -width Ds
|
||||
.It Fl Wall
|
||||
This enables warnings that are likely to indicate an error or undesired behavior, and that can easily be fixed.
|
||||
.It Fl Weverything
|
||||
Enables literally every warning.
|
||||
.El
|
||||
.Pp
|
||||
The following warnings are actual warning flags; with each description, the corresponding warning flag is included.
|
||||
Note that each of these flag also has a negation (for example,
|
||||
.Fl Wtrim-nonempty
|
||||
enables the warning that
|
||||
.Fl Wno-trim-nonempty
|
||||
disables; and
|
||||
.Fl Wall
|
||||
enables every warning that
|
||||
.Fl Wno-all
|
||||
disables).
|
||||
Only the non-default flag is listed here.
|
||||
Ignoring the
|
||||
.Dq no-
|
||||
prefix, entries are listed alphabetically.
|
||||
.Bl -tag -width Ds
|
||||
.It Fl Wembedded
|
||||
Warn when a generated palette is sorted according to the input PNG's embedded palette but
|
||||
.Fl c Cm embedded
|
||||
was not provided.
|
||||
This warning is enabled by
|
||||
.Fl Weverything .
|
||||
.It Fl Wtrim-nonempty
|
||||
Warn when
|
||||
.Fl x
|
||||
trims a nonempty tile.
|
||||
An "empty" tile uses entirely color 0 of its palette.
|
||||
This warning is enabled by
|
||||
.Fl Wall .
|
||||
.El
|
||||
.Sh EXAMPLES
|
||||
The following will only validate the
|
||||
.Ql tileset.png
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd February 2, 2025
|
||||
.Dd July 31, 2025
|
||||
.Dt RGBLINK 1
|
||||
.Os
|
||||
.Sh NAME
|
||||
@@ -16,6 +16,7 @@
|
||||
.Op Fl o Ar out_file
|
||||
.Op Fl p Ar pad_value
|
||||
.Op Fl S Ar spec
|
||||
.Op Fl W Ar warning
|
||||
.Ar
|
||||
.Sh DESCRIPTION
|
||||
The
|
||||
@@ -114,6 +115,15 @@ Useful for ROMs that fit in 32 KiB.
|
||||
Print the version of the program and exit.
|
||||
.It Fl v , Fl \-verbose
|
||||
Verbose: enable printing more information to standard error.
|
||||
.It Fl W Ar warning , Fl \-warning Ar warning
|
||||
Set warning flag
|
||||
.Ar warning .
|
||||
A warning message will be printed if
|
||||
.Ar warning
|
||||
is an unknown warning flag.
|
||||
See the
|
||||
.Sx DIAGNOSTICS
|
||||
section for a list of warnings.
|
||||
.It Fl w , Fl \-wramx
|
||||
Expand the WRAM0 section size from 4 KiB to the full 8 KiB assigned to WRAM.
|
||||
WRAMX sections that are fixed to a bank other than 1 become errors, other WRAMX sections are treated as WRAM0.
|
||||
@@ -121,8 +131,8 @@ WRAMX sections that are fixed to a bank other than 1 become errors, other WRAMX
|
||||
Disables padding the end of the final file.
|
||||
This option automatically enables
|
||||
.Fl t .
|
||||
You can use this when not not making a ROM.
|
||||
When making a ROM, be careful that not using this is not a replacement for
|
||||
You can use this to make binary files that are not a ROM.
|
||||
When making a ROM, note that not using this is not a replacement for
|
||||
.Xr rgbfix 1 Ap s Fl p
|
||||
option!
|
||||
.El
|
||||
@@ -176,6 +186,84 @@ as
|
||||
.Ic WRAMX
|
||||
sections will be treated as
|
||||
.Ic WRAM0 .
|
||||
.Sh DIAGNOSTICS
|
||||
Warnings are diagnostic messages that indicate possibly erroneous behavior that does not necessarily compromise the linking process.
|
||||
The following options alter the way warnings are processed.
|
||||
.Bl -tag -width Ds
|
||||
.It Fl Werror
|
||||
Make all warnings into errors.
|
||||
This can be negated as
|
||||
.Fl Wno-error
|
||||
to prevent turning all warnings into errors.
|
||||
.It Fl Werror=
|
||||
Make the specified warning or meta warning into an error.
|
||||
A warning's name is appended
|
||||
.Pq example: Fl Werror=assert ,
|
||||
and this warning is implicitly enabled and turned into an error.
|
||||
This can be negated as
|
||||
.Fl Wno-error=
|
||||
to prevent turning a specified warning into an error, even if
|
||||
.Fl Werror
|
||||
is in effect.
|
||||
.El
|
||||
.Pp
|
||||
The following warnings are
|
||||
.Dq meta
|
||||
warnings, that enable a collection of other warnings.
|
||||
If a specific warning is toggled via a meta flag and a specific one, the more specific one takes priority.
|
||||
The position on the command-line acts as a tie breaker, the last one taking effect.
|
||||
.Bl -tag -width Ds
|
||||
.It Fl Wall
|
||||
This enables warnings that are likely to indicate an error or undesired behavior, and that can easily be fixed.
|
||||
.It Fl Weverything
|
||||
Enables literally every warning.
|
||||
.El
|
||||
.Pp
|
||||
The following warnings are actual warning flags; with each description, the corresponding warning flag is included.
|
||||
Note that each of these flag also has a negation (for example,
|
||||
.Fl Wobsolete
|
||||
enables the warning that
|
||||
.Fl Wno-obsolete
|
||||
disables; and
|
||||
.Fl Wall
|
||||
enables every warning that
|
||||
.Fl Wno-all
|
||||
disables).
|
||||
Only the non-default flag is listed here.
|
||||
Ignoring the
|
||||
.Dq no-
|
||||
prefix, entries are listed alphabetically.
|
||||
.Bl -tag -width Ds
|
||||
.It Fl Wno-assert
|
||||
Warn when
|
||||
.Ic WARN Ns No -type
|
||||
assertions fail. (See
|
||||
.Dq Aborting the assembly process
|
||||
in
|
||||
.Xr rgbasm 5
|
||||
for
|
||||
.Ic ASSERT ) .
|
||||
.It Fl Wdiv
|
||||
Warn when dividing the smallest negative integer (-2**31) by -1, which yields itself due to integer overflow.
|
||||
This warning is enabled by
|
||||
.Fl Wall .
|
||||
.It Fl Wno-obsolete
|
||||
Warn when obsolete features are encountered, which have been deprecated and may later be removed.
|
||||
.It Fl Wshift
|
||||
Warn when shifting right a negative value.
|
||||
Use a division by 2**N instead.
|
||||
This warning is enabled by
|
||||
.Fl Wall .
|
||||
.It Fl Wshift-amount
|
||||
Warn when a shift's operand is negative or greater than 32.
|
||||
This warning is enabled by
|
||||
.Fl Wall .
|
||||
.It Fl Wno-truncation
|
||||
Warn when an implicit truncation (for example,
|
||||
.Ic db
|
||||
to an 8-bit value) loses some bits.
|
||||
This occurs when an N-bit value is 2**N or greater, or less than -2**N.
|
||||
.El
|
||||
.Sh EXAMPLES
|
||||
All you need for a basic ROM is an object file, which can be made into a ROM image like so:
|
||||
.Pp
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd February 2, 2025
|
||||
.Dd July 31, 2025
|
||||
.Dt RGBLINK 5
|
||||
.Os
|
||||
.Sh NAME
|
||||
@@ -24,18 +24,20 @@ They are simply ignored.
|
||||
.Pp
|
||||
Keywords are composed of letters and digits (but they can't start with a digit); they are all case-insensitive.
|
||||
.Pp
|
||||
Numbers can be written in decimal format, or in binary using the
|
||||
.Ql %
|
||||
prefix, or in hexadecimal using the
|
||||
.Ql $
|
||||
prefix (hexadecimal digits are case-insensitive).
|
||||
Note that unlike
|
||||
.Xr rgbasm 5 ,
|
||||
an octal
|
||||
.Ql &
|
||||
prefix is not supported, nor are
|
||||
.Ql _
|
||||
digit separators.
|
||||
Numbers can be written in a number of formats.
|
||||
.Bl -column -offset indent "Hexadecimal" "Possible prefixes"
|
||||
.It Sy Format type Ta Sy Possible prefixes Ta Sy Accepted characters
|
||||
.It Decimal Ta none Ta 0123456789
|
||||
.It Hexadecimal Ta Li $ , 0x , 0X Ta 0123456789ABCDEF
|
||||
.It Octal Ta Li & , 0o , 0O Ta 01234567
|
||||
.It Binary Ta Li % , 0b , 0B Ta 01
|
||||
.El
|
||||
.Pp
|
||||
Underscores are also accepted in numbers, except at the beginning of one.
|
||||
This can be useful for grouping digits, like
|
||||
.Ql 1_234
|
||||
or
|
||||
.Ql $ff_80 .
|
||||
.Pp
|
||||
Strings begin with a double quote, and end at the next (non-escaped) double quote.
|
||||
Strings must not contain literal newline characters.
|
||||
@@ -46,8 +48,9 @@ are supported, specifically
|
||||
.Ql \e" ,
|
||||
.Ql \en ,
|
||||
.Ql \er ,
|
||||
.Ql \et ,
|
||||
and
|
||||
.Ql \et .
|
||||
.Ql \e0 .
|
||||
Other backslash escape sequences in
|
||||
.Xr rgbasm 5
|
||||
are only relevant to assembly code and do not apply in linker scripts.
|
||||
@@ -120,7 +123,7 @@ causes all sections between it and the next
|
||||
.Ic ORG
|
||||
or bank specification to be placed at addresses automatically determined by
|
||||
.Nm .
|
||||
.Pq It is, however, compatible with Ic ALIGN No below.
|
||||
.Pq \&It is, however, compatible with Ic ALIGN No below.
|
||||
.Pp
|
||||
.Ql Ic ALIGN Ar addr , Ar offset
|
||||
increases the
|
||||
|
||||
@@ -3,16 +3,17 @@
|
||||
configure_file(version.cpp _version.cpp ESCAPE_QUOTES)
|
||||
|
||||
set(common_src
|
||||
"error.cpp"
|
||||
"extern/getopt.cpp"
|
||||
"diagnostics.cpp"
|
||||
"usage.cpp"
|
||||
"_version.cpp"
|
||||
)
|
||||
|
||||
find_package(BISON 3.0.0 REQUIRED)
|
||||
set(BISON_FLAGS "-Wall -Dparse.lac=full -Dlr.type=ielr")
|
||||
set(BISON_FLAGS "-Wall -Dlr.type=ielr")
|
||||
# Set some optimization flags on versions that support them
|
||||
if(BISON_VERSION VERSION_GREATER_EQUAL "3.5")
|
||||
set(BISON_FLAGS "${BISON_FLAGS} -Dapi.token.raw=true")
|
||||
set(BISON_FLAGS "${BISON_FLAGS} -Dparse.lac=full -Dapi.token.raw=true")
|
||||
endif()
|
||||
if(BISON_VERSION VERSION_GREATER_EQUAL "3.6")
|
||||
set(BISON_FLAGS "${BISON_FLAGS} -Dparse.error=detailed")
|
||||
@@ -34,6 +35,7 @@ BISON_TARGET(LINKER_SCRIPT_PARSER "link/script.y"
|
||||
|
||||
set(rgbasm_src
|
||||
"${BISON_ASM_PARSER_OUTPUT_SOURCE}"
|
||||
"asm/actions.cpp"
|
||||
"asm/charmap.cpp"
|
||||
"asm/fixpoint.cpp"
|
||||
"asm/format.cpp"
|
||||
@@ -53,26 +55,11 @@ set(rgbasm_src
|
||||
"util.cpp"
|
||||
)
|
||||
|
||||
set(rgbfix_src
|
||||
"fix/main.cpp"
|
||||
)
|
||||
|
||||
set(rgbgfx_src
|
||||
"gfx/main.cpp"
|
||||
"gfx/pal_packing.cpp"
|
||||
"gfx/pal_sorting.cpp"
|
||||
"gfx/pal_spec.cpp"
|
||||
"gfx/process.cpp"
|
||||
"gfx/proto_palette.cpp"
|
||||
"gfx/reverse.cpp"
|
||||
"gfx/rgba.cpp"
|
||||
"extern/getopt.cpp"
|
||||
"error.cpp"
|
||||
)
|
||||
|
||||
set(rgblink_src
|
||||
"${BISON_LINKER_SCRIPT_PARSER_OUTPUT_SOURCE}"
|
||||
"link/assign.cpp"
|
||||
"link/lexer.cpp"
|
||||
"link/layout.cpp"
|
||||
"link/main.cpp"
|
||||
"link/object.cpp"
|
||||
"link/output.cpp"
|
||||
@@ -80,12 +67,33 @@ set(rgblink_src
|
||||
"link/sdas_obj.cpp"
|
||||
"link/section.cpp"
|
||||
"link/symbol.cpp"
|
||||
"link/warning.cpp"
|
||||
"extern/utf8decoder.cpp"
|
||||
"linkdefs.cpp"
|
||||
"opmath.cpp"
|
||||
"util.cpp"
|
||||
)
|
||||
|
||||
set(rgbfix_src
|
||||
"fix/main.cpp"
|
||||
"fix/mbc.cpp"
|
||||
"fix/warning.cpp"
|
||||
)
|
||||
|
||||
set(rgbgfx_src
|
||||
"gfx/color_set.cpp"
|
||||
"gfx/main.cpp"
|
||||
"gfx/pal_packing.cpp"
|
||||
"gfx/pal_sorting.cpp"
|
||||
"gfx/pal_spec.cpp"
|
||||
"gfx/png.cpp"
|
||||
"gfx/process.cpp"
|
||||
"gfx/reverse.cpp"
|
||||
"gfx/rgba.cpp"
|
||||
"gfx/warning.cpp"
|
||||
"util.cpp"
|
||||
)
|
||||
|
||||
foreach(PROG "asm" "fix" "gfx" "link")
|
||||
add_executable(rgb${PROG}
|
||||
${rgb${PROG}_src}
|
||||
|
||||
469
src/asm/actions.cpp
Normal file
469
src/asm/actions.cpp
Normal file
@@ -0,0 +1,469 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "asm/actions.hpp"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "extern/utf8decoder.hpp"
|
||||
#include "helpers.hpp"
|
||||
|
||||
#include "asm/charmap.hpp"
|
||||
#include "asm/format.hpp"
|
||||
#include "asm/fstack.hpp"
|
||||
#include "asm/symbol.hpp"
|
||||
#include "asm/warning.hpp"
|
||||
|
||||
std::optional<std::string> act_ReadFile(std::string const &name, uint32_t maxLen) {
|
||||
FILE *file = nullptr;
|
||||
if (std::optional<std::string> fullPath = fstk_FindFile(name); fullPath) {
|
||||
file = fopen(fullPath->c_str(), "rb");
|
||||
}
|
||||
if (!file) {
|
||||
if (fstk_FileError(name, "READFILE")) {
|
||||
// If `fstk_FileError` returned true due to `-MG`, we should abort due to a
|
||||
// missing file, so return `std::nullopt`, which tells the caller to `YYACCEPT`
|
||||
return std::nullopt;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
Defer closeFile{[&] { fclose(file); }};
|
||||
|
||||
size_t readSize = maxLen;
|
||||
if (fseek(file, 0, SEEK_END) == 0) {
|
||||
// If the file is seekable and shorter than the max length,
|
||||
// just read as many bytes as there are
|
||||
if (long fileSize = ftell(file); static_cast<size_t>(fileSize) < readSize) {
|
||||
readSize = fileSize;
|
||||
}
|
||||
fseek(file, 0, SEEK_SET);
|
||||
} else if (errno != ESPIPE) {
|
||||
error("Error determining size of READFILE file '%s': %s", name.c_str(), strerror(errno));
|
||||
}
|
||||
|
||||
std::string contents;
|
||||
contents.resize(readSize);
|
||||
|
||||
if (fread(&contents[0], 1, readSize, file) < readSize || ferror(file)) {
|
||||
error("Error reading READFILE file '%s': %s", name.c_str(), strerror(errno));
|
||||
return "";
|
||||
}
|
||||
|
||||
return contents;
|
||||
}
|
||||
|
||||
uint32_t act_StringToNum(std::vector<int32_t> const &str) {
|
||||
uint32_t length = str.size();
|
||||
|
||||
if (length == 1) {
|
||||
// The string is a single character with a single value,
|
||||
// which can be used directly as a number.
|
||||
return static_cast<uint32_t>(str[0]);
|
||||
}
|
||||
|
||||
warning(WARNING_OBSOLETE, "Treating multi-unit strings as numbers is deprecated");
|
||||
|
||||
for (int32_t v : str) {
|
||||
if (!checkNBit(v, 8, "All character units")) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t r = 0;
|
||||
|
||||
for (uint32_t i = length < 4 ? 0 : length - 4; i < length; ++i) {
|
||||
r <<= 8;
|
||||
r |= static_cast<uint8_t>(str[i]);
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
static void errorInvalidUTF8Byte(uint8_t byte, char const *functionName) {
|
||||
error("%s: Invalid UTF-8 byte 0x%02hhX", functionName, byte);
|
||||
}
|
||||
|
||||
size_t act_StringLen(std::string const &str, bool printErrors) {
|
||||
size_t len = 0;
|
||||
uint32_t state = UTF8_ACCEPT;
|
||||
uint32_t codepoint = 0;
|
||||
|
||||
for (char c : str) {
|
||||
uint8_t byte = static_cast<uint8_t>(c);
|
||||
|
||||
switch (decode(&state, &codepoint, byte)) {
|
||||
case UTF8_REJECT:
|
||||
if (printErrors) {
|
||||
errorInvalidUTF8Byte(byte, "STRLEN");
|
||||
}
|
||||
state = UTF8_ACCEPT;
|
||||
// fallthrough
|
||||
case UTF8_ACCEPT:
|
||||
++len;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for partial code point.
|
||||
if (state != UTF8_ACCEPT) {
|
||||
if (printErrors) {
|
||||
error("STRLEN: Incomplete UTF-8 character");
|
||||
}
|
||||
++len;
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
std::string act_StringSlice(std::string const &str, uint32_t start, uint32_t stop) {
|
||||
size_t strLen = str.length();
|
||||
size_t index = 0;
|
||||
uint32_t state = UTF8_ACCEPT;
|
||||
uint32_t codepoint = 0;
|
||||
uint32_t curIdx = 0;
|
||||
|
||||
// Advance to starting index in source string.
|
||||
while (index < strLen && curIdx < start) {
|
||||
switch (decode(&state, &codepoint, str[index])) {
|
||||
case UTF8_REJECT:
|
||||
errorInvalidUTF8Byte(str[index], "STRSLICE");
|
||||
state = UTF8_ACCEPT;
|
||||
// fallthrough
|
||||
case UTF8_ACCEPT:
|
||||
++curIdx;
|
||||
break;
|
||||
}
|
||||
++index;
|
||||
}
|
||||
|
||||
// An index 1 past the end of the string is allowed, but will trigger the
|
||||
// "Length too big" warning below if the length is nonzero.
|
||||
if (index >= strLen && start > curIdx) {
|
||||
warning(
|
||||
WARNING_BUILTIN_ARG,
|
||||
"STRSLICE: Start index %" PRIu32 " is past the end of the string",
|
||||
start
|
||||
);
|
||||
}
|
||||
|
||||
size_t startIndex = index;
|
||||
|
||||
// Advance to ending index in source string.
|
||||
while (index < strLen && curIdx < stop) {
|
||||
switch (decode(&state, &codepoint, str[index])) {
|
||||
case UTF8_REJECT:
|
||||
errorInvalidUTF8Byte(str[index], "STRSLICE");
|
||||
state = UTF8_ACCEPT;
|
||||
// fallthrough
|
||||
case UTF8_ACCEPT:
|
||||
++curIdx;
|
||||
break;
|
||||
}
|
||||
++index;
|
||||
}
|
||||
|
||||
// Check for partial code point.
|
||||
if (state != UTF8_ACCEPT) {
|
||||
error("STRSLICE: Incomplete UTF-8 character");
|
||||
++curIdx;
|
||||
}
|
||||
|
||||
if (curIdx < stop) {
|
||||
warning(
|
||||
WARNING_BUILTIN_ARG,
|
||||
"STRSLICE: Stop index %" PRIu32 " is past the end of the string",
|
||||
stop
|
||||
);
|
||||
}
|
||||
|
||||
return str.substr(startIndex, index - startIndex);
|
||||
}
|
||||
|
||||
std::string act_StringSub(std::string const &str, uint32_t pos, uint32_t len) {
|
||||
size_t strLen = str.length();
|
||||
size_t index = 0;
|
||||
uint32_t state = UTF8_ACCEPT;
|
||||
uint32_t codepoint = 0;
|
||||
uint32_t curPos = 1;
|
||||
|
||||
// Advance to starting position in source string.
|
||||
while (index < strLen && curPos < pos) {
|
||||
switch (decode(&state, &codepoint, str[index])) {
|
||||
case UTF8_REJECT:
|
||||
errorInvalidUTF8Byte(str[index], "STRSUB");
|
||||
state = UTF8_ACCEPT;
|
||||
// fallthrough
|
||||
case UTF8_ACCEPT:
|
||||
++curPos;
|
||||
break;
|
||||
}
|
||||
++index;
|
||||
}
|
||||
|
||||
// A position 1 past the end of the string is allowed, but will trigger the
|
||||
// "Length too big" warning below if the length is nonzero.
|
||||
if (index >= strLen && pos > curPos) {
|
||||
warning(
|
||||
WARNING_BUILTIN_ARG, "STRSUB: Position %" PRIu32 " is past the end of the string", pos
|
||||
);
|
||||
}
|
||||
|
||||
size_t startIndex = index;
|
||||
uint32_t curLen = 0;
|
||||
|
||||
// Compute the result length in bytes.
|
||||
while (index < strLen && curLen < len) {
|
||||
switch (decode(&state, &codepoint, str[index])) {
|
||||
case UTF8_REJECT:
|
||||
errorInvalidUTF8Byte(str[index], "STRSUB");
|
||||
state = UTF8_ACCEPT;
|
||||
// fallthrough
|
||||
case UTF8_ACCEPT:
|
||||
++curLen;
|
||||
break;
|
||||
}
|
||||
++index;
|
||||
}
|
||||
|
||||
// Check for partial code point.
|
||||
if (state != UTF8_ACCEPT) {
|
||||
error("STRSUB: Incomplete UTF-8 character");
|
||||
++curLen;
|
||||
}
|
||||
|
||||
if (curLen < len) {
|
||||
warning(WARNING_BUILTIN_ARG, "STRSUB: Length too big: %" PRIu32, len);
|
||||
}
|
||||
|
||||
return str.substr(startIndex, index - startIndex);
|
||||
}
|
||||
|
||||
size_t act_CharLen(std::string const &str) {
|
||||
std::string_view view = str;
|
||||
size_t len;
|
||||
|
||||
for (len = 0; charmap_ConvertNext(view, nullptr); ++len) {}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
std::string act_StringChar(std::string const &str, uint32_t idx) {
|
||||
std::string_view view = str;
|
||||
size_t charLen = 1;
|
||||
|
||||
// Advance to starting index in source string.
|
||||
for (uint32_t curIdx = 0; charLen && curIdx < idx; ++curIdx) {
|
||||
charLen = charmap_ConvertNext(view, nullptr);
|
||||
}
|
||||
|
||||
std::string_view start = view;
|
||||
|
||||
if (!charmap_ConvertNext(view, nullptr)) {
|
||||
warning(
|
||||
WARNING_BUILTIN_ARG, "STRCHAR: Index %" PRIu32 " is past the end of the string", idx
|
||||
);
|
||||
}
|
||||
|
||||
start = start.substr(0, start.length() - view.length());
|
||||
return std::string(start);
|
||||
}
|
||||
|
||||
std::string act_CharSub(std::string const &str, uint32_t pos) {
|
||||
std::string_view view = str;
|
||||
size_t charLen = 1;
|
||||
|
||||
// Advance to starting position in source string.
|
||||
for (uint32_t curPos = 1; charLen && curPos < pos; ++curPos) {
|
||||
charLen = charmap_ConvertNext(view, nullptr);
|
||||
}
|
||||
|
||||
std::string_view start = view;
|
||||
|
||||
if (!charmap_ConvertNext(view, nullptr)) {
|
||||
warning(
|
||||
WARNING_BUILTIN_ARG, "CHARSUB: Position %" PRIu32 " is past the end of the string", pos
|
||||
);
|
||||
}
|
||||
|
||||
start = start.substr(0, start.length() - view.length());
|
||||
return std::string(start);
|
||||
}
|
||||
|
||||
int32_t act_CharCmp(std::string_view str1, std::string_view str2) {
|
||||
std::vector<int32_t> seq1, seq2;
|
||||
size_t idx1 = 0, idx2 = 0;
|
||||
for (;;) {
|
||||
if (idx1 >= seq1.size()) {
|
||||
idx1 = 0;
|
||||
seq1.clear();
|
||||
charmap_ConvertNext(str1, &seq1);
|
||||
}
|
||||
if (idx2 >= seq2.size()) {
|
||||
idx2 = 0;
|
||||
seq2.clear();
|
||||
charmap_ConvertNext(str2, &seq2);
|
||||
}
|
||||
if (seq1.empty() != seq2.empty()) {
|
||||
return seq1.empty() ? -1 : 1;
|
||||
} else if (seq1.empty()) {
|
||||
return 0;
|
||||
} else {
|
||||
int32_t value1 = seq1[idx1++], value2 = seq2[idx2++];
|
||||
if (value1 != value2) {
|
||||
return (value1 > value2) - (value1 < value2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t act_AdjustNegativeIndex(int32_t idx, size_t len, char const *functionName) {
|
||||
// String functions adjust negative index arguments the same way,
|
||||
// such that position -1 is the last character of a string.
|
||||
if (idx < 0) {
|
||||
idx += len;
|
||||
}
|
||||
if (idx < 0) {
|
||||
warning(WARNING_BUILTIN_ARG, "%s: Index starts at 0", functionName);
|
||||
idx = 0;
|
||||
}
|
||||
return static_cast<uint32_t>(idx);
|
||||
}
|
||||
|
||||
uint32_t act_AdjustNegativePos(int32_t pos, size_t len, char const *functionName) {
|
||||
// STRSUB and CHARSUB adjust negative position arguments the same way,
|
||||
// such that position -1 is the last character of a string.
|
||||
if (pos < 0) {
|
||||
pos += len + 1;
|
||||
}
|
||||
if (pos < 1) {
|
||||
warning(WARNING_BUILTIN_ARG, "%s: Position starts at 1", functionName);
|
||||
pos = 1;
|
||||
}
|
||||
return static_cast<uint32_t>(pos);
|
||||
}
|
||||
|
||||
std::string
|
||||
act_StringReplace(std::string_view str, std::string const &old, std::string const &rep) {
|
||||
if (old.empty()) {
|
||||
warning(WARNING_EMPTY_STRRPL, "STRRPL: Cannot replace an empty string");
|
||||
return std::string(str);
|
||||
}
|
||||
|
||||
std::string rpl;
|
||||
|
||||
while (!str.empty()) {
|
||||
auto pos = str.find(old);
|
||||
if (pos == str.npos) {
|
||||
rpl.append(str);
|
||||
break;
|
||||
}
|
||||
rpl.append(str, 0, pos);
|
||||
rpl.append(rep);
|
||||
str.remove_prefix(pos + old.size());
|
||||
}
|
||||
|
||||
return rpl;
|
||||
}
|
||||
|
||||
std::string act_StringFormat(
|
||||
std::string const &spec, std::vector<std::variant<uint32_t, std::string>> const &args
|
||||
) {
|
||||
std::string str;
|
||||
size_t argIndex = 0;
|
||||
|
||||
for (size_t i = 0; spec[i] != '\0'; ++i) {
|
||||
int c = spec[i];
|
||||
|
||||
if (c != '%') {
|
||||
str += c;
|
||||
continue;
|
||||
}
|
||||
|
||||
c = spec[++i];
|
||||
|
||||
if (c == '%') {
|
||||
str += c;
|
||||
continue;
|
||||
}
|
||||
|
||||
FormatSpec fmt{};
|
||||
|
||||
while (c != '\0') {
|
||||
fmt.useCharacter(c);
|
||||
if (fmt.isFinished()) {
|
||||
break;
|
||||
}
|
||||
c = spec[++i];
|
||||
}
|
||||
|
||||
if (fmt.isEmpty()) {
|
||||
error("STRFMT: Illegal '%%' at end of format string");
|
||||
str += '%';
|
||||
break;
|
||||
}
|
||||
|
||||
if (!fmt.isValid()) {
|
||||
error("STRFMT: Invalid format spec for argument %zu", argIndex + 1);
|
||||
str += '%';
|
||||
} else if (argIndex >= args.size()) {
|
||||
// Will warn after formatting is done.
|
||||
str += '%';
|
||||
} else if (std::holds_alternative<uint32_t>(args[argIndex])) {
|
||||
fmt.appendNumber(str, std::get<uint32_t>(args[argIndex]));
|
||||
} else {
|
||||
fmt.appendString(str, std::get<std::string>(args[argIndex]));
|
||||
}
|
||||
|
||||
++argIndex;
|
||||
}
|
||||
|
||||
if (argIndex < args.size()) {
|
||||
error("STRFMT: %zu unformatted argument(s)", args.size() - argIndex);
|
||||
} else if (argIndex > args.size()) {
|
||||
error(
|
||||
"STRFMT: Not enough arguments for format spec, got: %zu, need: %zu",
|
||||
args.size(),
|
||||
argIndex
|
||||
);
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
void act_CompoundAssignment(std::string const &symName, RPNCommand op, int32_t constValue) {
|
||||
Expression oldExpr, constExpr, newExpr;
|
||||
int32_t newValue;
|
||||
|
||||
oldExpr.makeSymbol(symName);
|
||||
constExpr.makeNumber(constValue);
|
||||
newExpr.makeBinaryOp(op, std::move(oldExpr), constExpr);
|
||||
newValue = newExpr.getConstVal();
|
||||
sym_AddVar(symName, newValue);
|
||||
}
|
||||
|
||||
void act_FailAssert(AssertionType type) {
|
||||
switch (type) {
|
||||
case ASSERT_FATAL:
|
||||
fatal("Assertion failed");
|
||||
case ASSERT_ERROR:
|
||||
error("Assertion failed");
|
||||
break;
|
||||
case ASSERT_WARN:
|
||||
warning(WARNING_ASSERT, "Assertion failed");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void act_FailAssertMsg(AssertionType type, std::string const &message) {
|
||||
switch (type) {
|
||||
case ASSERT_FATAL:
|
||||
fatal("Assertion failed: %s", message.c_str());
|
||||
case ASSERT_ERROR:
|
||||
error("Assertion failed: %s", message.c_str());
|
||||
break;
|
||||
case ASSERT_WARN:
|
||||
warning(WARNING_ASSERT, "Assertion failed: %s", message.c_str());
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -33,35 +33,44 @@ struct Charmap {
|
||||
std::vector<CharmapNode> nodes; // first node is reserved for the root node
|
||||
};
|
||||
|
||||
// Traverse the trie depth-first to derive the character mappings in definition order
|
||||
template<typename F>
|
||||
bool forEachChar(Charmap const &charmap, F callback) {
|
||||
// clang-format off: nested initializers
|
||||
for (std::stack<std::pair<size_t, std::string>> prefixes({{0, ""}}); !prefixes.empty();) {
|
||||
// clang-format on
|
||||
auto [nodeIdx, mapping] = std::move(prefixes.top());
|
||||
prefixes.pop();
|
||||
CharmapNode const &node = charmap.nodes[nodeIdx];
|
||||
if (node.isTerminal() && !callback(nodeIdx, mapping)) {
|
||||
return false;
|
||||
}
|
||||
for (unsigned c = 0; c < std::size(node.next); ++c) {
|
||||
if (size_t nextIdx = node.next[c]; nextIdx) {
|
||||
prefixes.push({nextIdx, mapping + static_cast<char>(c)});
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static std::deque<Charmap> charmapList;
|
||||
static std::unordered_map<std::string, size_t> charmapMap; // Indexes into `charmapList`
|
||||
|
||||
static Charmap *currentCharmap;
|
||||
std::stack<Charmap *> charmapStack;
|
||||
static 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;
|
||||
// clang-format off: nested initializers
|
||||
for (std::stack<std::pair<size_t, std::string>> prefixes({{0, ""}});
|
||||
!prefixes.empty();) {
|
||||
// clang-format on
|
||||
auto [nodeIdx, mapping] = std::move(prefixes.top());
|
||||
prefixes.pop();
|
||||
CharmapNode const &node = charmap.nodes[nodeIdx];
|
||||
if (node.isTerminal()) {
|
||||
mappings[nodeIdx] = mapping;
|
||||
}
|
||||
for (unsigned c = 0; c < 256; c++) {
|
||||
if (size_t nextIdx = node.next[c]; nextIdx) {
|
||||
prefixes.push({nextIdx, mapping + static_cast<char>(c)});
|
||||
}
|
||||
}
|
||||
}
|
||||
forEachChar(charmap, [&mappings](size_t nodeIdx, std::string const &mapping) {
|
||||
mappings[nodeIdx] = mapping;
|
||||
return true;
|
||||
});
|
||||
|
||||
mapFunc(charmap.name);
|
||||
for (auto [nodeIdx, mapping] : mappings) {
|
||||
charFunc(mapping, charmap.nodes[nodeIdx].value);
|
||||
@@ -75,14 +84,14 @@ void charmap_New(std::string const &name, std::string const *baseName) {
|
||||
|
||||
if (baseName != nullptr) {
|
||||
if (auto search = charmapMap.find(*baseName); search == charmapMap.end()) {
|
||||
error("Base charmap '%s' doesn't exist\n", baseName->c_str());
|
||||
error("Base charmap '%s' doesn't exist", baseName->c_str());
|
||||
} else {
|
||||
baseIdx = search->second;
|
||||
}
|
||||
}
|
||||
|
||||
if (charmapMap.find(name) != charmapMap.end()) {
|
||||
error("Charmap '%s' already exists\n", name.c_str());
|
||||
error("Charmap '%s' already exists", name.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -103,7 +112,7 @@ void charmap_New(std::string const &name, std::string const *baseName) {
|
||||
|
||||
void charmap_Set(std::string const &name) {
|
||||
if (auto search = charmapMap.find(name); search == charmapMap.end()) {
|
||||
error("Charmap '%s' doesn't exist\n", name.c_str());
|
||||
error("Charmap '%s' doesn't exist", name.c_str());
|
||||
} else {
|
||||
currentCharmap = &charmapList[search->second];
|
||||
}
|
||||
@@ -115,7 +124,7 @@ void charmap_Push() {
|
||||
|
||||
void charmap_Pop() {
|
||||
if (charmapStack.empty()) {
|
||||
error("No entries in the charmap stack\n");
|
||||
error("No entries in the charmap stack");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -125,13 +134,13 @@ void charmap_Pop() {
|
||||
|
||||
void charmap_CheckStack() {
|
||||
if (!charmapStack.empty()) {
|
||||
warning(WARNING_UNMATCHED_DIRECTIVE, "`PUSHC` without corresponding `POPC`\n");
|
||||
warning(WARNING_UNMATCHED_DIRECTIVE, "`PUSHC` without corresponding `POPC`");
|
||||
}
|
||||
}
|
||||
|
||||
void charmap_Add(std::string const &mapping, std::vector<int32_t> &&value) {
|
||||
if (mapping.empty()) {
|
||||
error("Cannot map an empty string\n");
|
||||
error("Cannot map an empty string");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -157,17 +166,17 @@ void charmap_Add(std::string const &mapping, std::vector<int32_t> &&value) {
|
||||
CharmapNode &node = charmap.nodes[nodeIdx];
|
||||
|
||||
if (node.isTerminal()) {
|
||||
warning(WARNING_CHARMAP_REDEF, "Overriding charmap mapping\n");
|
||||
warning(WARNING_CHARMAP_REDEF, "Overriding charmap mapping");
|
||||
}
|
||||
|
||||
std::swap(node.value, value);
|
||||
}
|
||||
|
||||
bool charmap_HasChar(std::string const &input) {
|
||||
bool charmap_HasChar(std::string const &mapping) {
|
||||
Charmap const &charmap = *currentCharmap;
|
||||
size_t nodeIdx = 0;
|
||||
|
||||
for (char c : input) {
|
||||
for (char c : mapping) {
|
||||
nodeIdx = charmap.nodes[nodeIdx].next[static_cast<uint8_t>(c)];
|
||||
|
||||
if (!nodeIdx) {
|
||||
@@ -178,6 +187,34 @@ bool charmap_HasChar(std::string const &input) {
|
||||
return charmap.nodes[nodeIdx].isTerminal();
|
||||
}
|
||||
|
||||
static CharmapNode const *charmapEntry(std::string const &mapping) {
|
||||
Charmap const &charmap = *currentCharmap;
|
||||
size_t nodeIdx = 0;
|
||||
|
||||
for (char c : mapping) {
|
||||
nodeIdx = charmap.nodes[nodeIdx].next[static_cast<uint8_t>(c)];
|
||||
|
||||
if (!nodeIdx) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
return &charmap.nodes[nodeIdx];
|
||||
}
|
||||
|
||||
size_t charmap_CharSize(std::string const &mapping) {
|
||||
CharmapNode const *node = charmapEntry(mapping);
|
||||
return node && node->isTerminal() ? node->value.size() : 0;
|
||||
}
|
||||
|
||||
std::optional<int32_t> charmap_CharValue(std::string const &mapping, size_t idx) {
|
||||
if (CharmapNode const *node = charmapEntry(mapping);
|
||||
node && node->isTerminal() && idx < node->value.size()) {
|
||||
return node->value[idx];
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
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);) {}
|
||||
@@ -201,13 +238,13 @@ size_t charmap_ConvertNext(std::string_view &input, std::vector<int32_t> *output
|
||||
break;
|
||||
}
|
||||
|
||||
inputIdx++; // Consume that char
|
||||
++inputIdx; // Consume that char
|
||||
|
||||
if (charmap.nodes[nodeIdx].isTerminal()) {
|
||||
matchIdx = nodeIdx; // This node matches, register it
|
||||
rewindDistance = 0; // If no longer match is found, rewind here
|
||||
} else {
|
||||
rewindDistance++;
|
||||
++rewindDistance;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,14 +264,15 @@ size_t charmap_ConvertNext(std::string_view &input, std::vector<int32_t> *output
|
||||
} else if (inputIdx < input.length()) { // No match found, but there is some input left
|
||||
size_t codepointLen = 0;
|
||||
// This will write the codepoint's value to `output`, little-endian
|
||||
for (uint32_t state = 0, codepoint = 0; inputIdx + codepointLen < input.length();) {
|
||||
if (decode(&state, &codepoint, input[inputIdx + codepointLen]) == 1) {
|
||||
error("Input string is not valid UTF-8\n");
|
||||
for (uint32_t state = UTF8_ACCEPT, codepoint = 0;
|
||||
inputIdx + codepointLen < input.length();) {
|
||||
if (decode(&state, &codepoint, input[inputIdx + codepointLen]) == UTF8_REJECT) {
|
||||
error("Input string is not valid UTF-8");
|
||||
codepointLen = 1;
|
||||
break;
|
||||
}
|
||||
codepointLen++;
|
||||
if (state == 0) {
|
||||
++codepointLen;
|
||||
if (state == UTF8_ACCEPT) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -247,11 +285,11 @@ size_t charmap_ConvertNext(std::string_view &input, std::vector<int32_t> *output
|
||||
|
||||
// Warn if this character is not mapped but any others are
|
||||
if (int firstChar = input[inputIdx]; charmap.nodes.size() > 1) {
|
||||
warning(WARNING_UNMAPPED_CHAR_1, "Unmapped character %s\n", printChar(firstChar));
|
||||
warning(WARNING_UNMAPPED_CHAR_1, "Unmapped character %s", printChar(firstChar));
|
||||
} else if (charmap.name != DEFAULT_CHARMAP_NAME) {
|
||||
warning(
|
||||
WARNING_UNMAPPED_CHAR_2,
|
||||
"Unmapped character %s not in " DEFAULT_CHARMAP_NAME " charmap\n",
|
||||
"Unmapped character %s not in " DEFAULT_CHARMAP_NAME " charmap",
|
||||
printChar(firstChar)
|
||||
);
|
||||
}
|
||||
@@ -263,3 +301,20 @@ size_t charmap_ConvertNext(std::string_view &input, std::vector<int32_t> *output
|
||||
input = input.substr(inputIdx);
|
||||
return matchLen;
|
||||
}
|
||||
|
||||
std::string charmap_Reverse(std::vector<int32_t> const &value, bool &unique) {
|
||||
Charmap const &charmap = *currentCharmap;
|
||||
std::string revMapping;
|
||||
unique = forEachChar(charmap, [&](size_t nodeIdx, std::string const &mapping) {
|
||||
if (charmap.nodes[nodeIdx].value == value) {
|
||||
if (revMapping.empty()) {
|
||||
revMapping = mapping;
|
||||
} else {
|
||||
revMapping.clear();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return revMapping;
|
||||
}
|
||||
|
||||
@@ -10,12 +10,6 @@
|
||||
#define M_PI 3.14159265358979323846
|
||||
#endif
|
||||
|
||||
uint8_t fixPrecision;
|
||||
|
||||
uint8_t fix_Precision() {
|
||||
return fixPrecision;
|
||||
}
|
||||
|
||||
static double fix2double(int32_t i, int32_t q) {
|
||||
return i / pow(2.0, q);
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <string.h>
|
||||
|
||||
#include "asm/fixpoint.hpp"
|
||||
#include "asm/main.hpp" // options
|
||||
#include "asm/warning.hpp"
|
||||
|
||||
void FormatSpec::useCharacter(int c) {
|
||||
@@ -22,29 +23,29 @@ void FormatSpec::useCharacter(int c) {
|
||||
case ' ':
|
||||
case '+':
|
||||
if (state > FORMAT_SIGN) {
|
||||
goto invalid;
|
||||
break;
|
||||
}
|
||||
state = FORMAT_EXACT;
|
||||
sign = c;
|
||||
break;
|
||||
return;
|
||||
|
||||
// exact
|
||||
case '#':
|
||||
if (state > FORMAT_EXACT) {
|
||||
goto invalid;
|
||||
break;
|
||||
}
|
||||
state = FORMAT_ALIGN;
|
||||
exact = true;
|
||||
break;
|
||||
return;
|
||||
|
||||
// align
|
||||
case '-':
|
||||
if (state > FORMAT_ALIGN) {
|
||||
goto invalid;
|
||||
break;
|
||||
}
|
||||
state = FORMAT_WIDTH;
|
||||
alignLeft = true;
|
||||
break;
|
||||
return;
|
||||
|
||||
// pad, width, and prec values
|
||||
case '0':
|
||||
@@ -71,27 +72,27 @@ void FormatSpec::useCharacter(int c) {
|
||||
} else if (state == FORMAT_PREC) {
|
||||
precision = precision * 10 + (c - '0');
|
||||
} else {
|
||||
goto invalid;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
return;
|
||||
|
||||
// width
|
||||
case '.':
|
||||
if (state > FORMAT_WIDTH) {
|
||||
goto invalid;
|
||||
break;
|
||||
}
|
||||
state = FORMAT_FRAC;
|
||||
hasFrac = true;
|
||||
break;
|
||||
return;
|
||||
|
||||
// prec
|
||||
case 'q':
|
||||
if (state > FORMAT_PREC) {
|
||||
goto invalid;
|
||||
break;
|
||||
}
|
||||
state = FORMAT_PREC;
|
||||
hasPrec = true;
|
||||
break;
|
||||
return;
|
||||
|
||||
// type
|
||||
case 'd':
|
||||
@@ -103,18 +104,19 @@ void FormatSpec::useCharacter(int c) {
|
||||
case 'f':
|
||||
case 's':
|
||||
if (state >= FORMAT_DONE) {
|
||||
goto invalid;
|
||||
break;
|
||||
}
|
||||
state = FORMAT_DONE;
|
||||
valid = true;
|
||||
type = c;
|
||||
break;
|
||||
return;
|
||||
|
||||
default:
|
||||
invalid:
|
||||
state = FORMAT_INVALID;
|
||||
valid = false;
|
||||
break;
|
||||
}
|
||||
|
||||
state = FORMAT_INVALID;
|
||||
valid = false;
|
||||
}
|
||||
|
||||
void FormatSpec::finishCharacters() {
|
||||
@@ -161,19 +163,19 @@ void FormatSpec::appendString(std::string &str, std::string const &value) const
|
||||
}
|
||||
|
||||
if (sign) {
|
||||
error("Formatting string with sign flag '%c'\n", sign);
|
||||
error("Formatting string with sign flag '%c'", sign);
|
||||
}
|
||||
if (padZero) {
|
||||
error("Formatting string with padding flag '0'\n");
|
||||
error("Formatting string with padding flag '0'");
|
||||
}
|
||||
if (hasFrac) {
|
||||
error("Formatting string with fractional width\n");
|
||||
error("Formatting string with fractional width");
|
||||
}
|
||||
if (hasPrec) {
|
||||
error("Formatting string with fractional precision\n");
|
||||
error("Formatting string with fractional precision");
|
||||
}
|
||||
if (useType != 's') {
|
||||
error("Formatting string as type '%c'\n", useType);
|
||||
error("Formatting string as type '%c'", useType);
|
||||
}
|
||||
|
||||
std::string useValue = exact ? escapeString(value) : value;
|
||||
@@ -202,16 +204,16 @@ void FormatSpec::appendNumber(std::string &str, uint32_t value) const {
|
||||
|
||||
if (useType != 'X' && useType != 'x' && useType != 'b' && useType != 'o' && useType != 'f'
|
||||
&& useExact) {
|
||||
error("Formatting type '%c' with exact flag '#'\n", useType);
|
||||
error("Formatting type '%c' with exact flag '#'", useType);
|
||||
}
|
||||
if (useType != 'f' && hasFrac) {
|
||||
error("Formatting type '%c' with fractional width\n", useType);
|
||||
error("Formatting type '%c' with fractional width", useType);
|
||||
}
|
||||
if (useType != 'f' && hasPrec) {
|
||||
error("Formatting type '%c' with fractional precision\n", useType);
|
||||
error("Formatting type '%c' with fractional precision", useType);
|
||||
}
|
||||
if (useType == 's') {
|
||||
error("Formatting number as type 's'\n");
|
||||
error("Formatting number as type 's'");
|
||||
}
|
||||
|
||||
char signChar = sign; // 0 or ' ' or '+'
|
||||
@@ -253,15 +255,15 @@ 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);
|
||||
error("Fractional width %zu too long, limiting to 255", useFracWidth);
|
||||
useFracWidth = 255;
|
||||
}
|
||||
|
||||
size_t defaultPrec = fix_Precision();
|
||||
size_t defaultPrec = options.fixPrecision;
|
||||
size_t usePrec = hasPrec ? precision : defaultPrec;
|
||||
if (usePrec < 1 || usePrec > 31) {
|
||||
error(
|
||||
"Fixed-point constant precision %zu invalid, defaulting to %zu\n",
|
||||
"Fixed-point constant precision %zu invalid, defaulting to %zu",
|
||||
usePrec,
|
||||
defaultPrec
|
||||
);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "asm/fstack.hpp"
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <deque>
|
||||
#include <errno.h>
|
||||
#include <inttypes.h>
|
||||
#include <memory>
|
||||
@@ -10,7 +11,7 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "error.hpp"
|
||||
#include "diagnostics.hpp"
|
||||
#include "helpers.hpp"
|
||||
#include "linkdefs.hpp"
|
||||
#include "platform.hpp" // S_ISDIR (stat macro)
|
||||
@@ -41,23 +42,29 @@ struct Context {
|
||||
};
|
||||
|
||||
static std::stack<Context> contextStack;
|
||||
size_t maxRecursionDepth;
|
||||
|
||||
// The first include path for `fstk_FindFile` to try is none at all
|
||||
static std::vector<std::string> includePaths = {""};
|
||||
static std::vector<std::string> includePaths = {""}; // -I
|
||||
static std::deque<std::string> preIncludeNames; // -P
|
||||
static bool failedOnMissingInclude = false;
|
||||
|
||||
static std::string preIncludeName;
|
||||
std::string FileStackNode::reptChain() const {
|
||||
std::string chain;
|
||||
std::vector<uint32_t> const &nodeIters = iters();
|
||||
for (uint32_t i = nodeIters.size(); i--;) {
|
||||
chain.append("::REPT~");
|
||||
chain.append(std::to_string(nodeIters[i]));
|
||||
}
|
||||
return chain;
|
||||
}
|
||||
|
||||
std::string const &FileStackNode::dump(uint32_t curLineNo) const {
|
||||
if (data.holds<std::vector<uint32_t>>()) {
|
||||
if (std::holds_alternative<std::vector<uint32_t>>(data)) {
|
||||
assume(parent); // REPT nodes use their parent's name
|
||||
std::string const &lastName = parent->dump(lineNo);
|
||||
fputs(" -> ", stderr);
|
||||
fputs(lastName.c_str(), stderr);
|
||||
std::vector<uint32_t> const &nodeIters = iters();
|
||||
for (uint32_t i = nodeIters.size(); i--;) {
|
||||
fprintf(stderr, "::REPT~%" PRIu32, nodeIters[i]);
|
||||
}
|
||||
fputs(reptChain().c_str(), stderr);
|
||||
fprintf(stderr, "(%" PRIu32 ")", curLineNo);
|
||||
return lastName;
|
||||
} else {
|
||||
@@ -72,12 +79,13 @@ std::string const &FileStackNode::dump(uint32_t curLineNo) const {
|
||||
}
|
||||
}
|
||||
|
||||
void fstk_DumpCurrent() {
|
||||
bool fstk_DumpCurrent() {
|
||||
if (lexer_AtTopLevel()) {
|
||||
fputs("at top level", stderr);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
assume(!contextStack.empty());
|
||||
contextStack.top().fileInfo->dump(lexer_GetLineNo());
|
||||
return true;
|
||||
}
|
||||
|
||||
std::shared_ptr<FileStackNode> fstk_GetFileStack() {
|
||||
@@ -114,23 +122,9 @@ void fstk_AddIncludePath(std::string const &path) {
|
||||
}
|
||||
}
|
||||
|
||||
void fstk_SetPreIncludeFile(std::string const &path) {
|
||||
if (!preIncludeName.empty()) {
|
||||
warnx("Overriding pre-included filename %s", preIncludeName.c_str());
|
||||
}
|
||||
preIncludeName = path;
|
||||
if (verbose) {
|
||||
printf("Pre-included filename %s\n", preIncludeName.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
static void printDep(std::string const &path) {
|
||||
if (dependFile) {
|
||||
fprintf(dependFile, "%s: %s\n", targetFileName.c_str(), path.c_str());
|
||||
if (generatePhonyDeps) {
|
||||
fprintf(dependFile, "%s:\n", path.c_str());
|
||||
}
|
||||
}
|
||||
void fstk_AddPreIncludeFile(std::string const &path) {
|
||||
preIncludeNames.emplace_front(path);
|
||||
verbosePrint("Pre-included filename %s\n", path.c_str()); // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
static bool isValidFilePath(std::string const &path) {
|
||||
@@ -138,6 +132,13 @@ static bool isValidFilePath(std::string const &path) {
|
||||
return stat(path.c_str(), &statBuf) == 0 && !S_ISDIR(statBuf.st_mode); // Reject directories
|
||||
}
|
||||
|
||||
static void printDep(std::string const &path) {
|
||||
options.printDep(path);
|
||||
if (options.dependFile && options.generatePhonyDeps && isValidFilePath(path)) {
|
||||
fprintf(options.dependFile, "%s:\n", path.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<std::string> fstk_FindFile(std::string const &path) {
|
||||
for (std::string &incPath : includePaths) {
|
||||
if (std::string fullPath = incPath + path; isValidFilePath(fullPath)) {
|
||||
@@ -147,7 +148,7 @@ std::optional<std::string> fstk_FindFile(std::string const &path) {
|
||||
}
|
||||
|
||||
errno = ENOENT;
|
||||
if (generatedMissingIncludes) {
|
||||
if (options.missingIncludeState != INC_ERROR) {
|
||||
printDep(path);
|
||||
}
|
||||
return std::nullopt;
|
||||
@@ -157,8 +158,8 @@ bool yywrap() {
|
||||
uint32_t ifDepth = lexer_GetIFDepth();
|
||||
|
||||
if (ifDepth != 0) {
|
||||
fatalerror(
|
||||
"Ended block with %" PRIu32 " unterminated IF construct%s\n",
|
||||
fatal(
|
||||
"Ended block with %" PRIu32 " unterminated IF construct%s",
|
||||
ifDepth,
|
||||
ifDepth == 1 ? "" : "s"
|
||||
);
|
||||
@@ -186,11 +187,11 @@ bool yywrap() {
|
||||
|
||||
// This error message will refer to the current iteration
|
||||
if (sym->type != SYM_VAR) {
|
||||
fatalerror("Failed to update FOR symbol value\n");
|
||||
fatal("Failed to update FOR symbol value");
|
||||
}
|
||||
}
|
||||
// Advance to the next iteration
|
||||
fileInfoIters.front()++;
|
||||
++fileInfoIters.front();
|
||||
// If this wasn't the last iteration, wrap instead of popping
|
||||
if (fileInfoIters.front() <= context.nbReptIters) {
|
||||
lexer_RestartRept(context.fileInfo->lineNo);
|
||||
@@ -208,12 +209,12 @@ bool yywrap() {
|
||||
}
|
||||
|
||||
static void checkRecursionDepth() {
|
||||
if (contextStack.size() > maxRecursionDepth) {
|
||||
fatalerror("Recursion limit (%zu) exceeded\n", maxRecursionDepth);
|
||||
if (contextStack.size() > options.maxRecursionDepth) {
|
||||
fatal("Recursion limit (%zu) exceeded", options.maxRecursionDepth);
|
||||
}
|
||||
}
|
||||
|
||||
static bool newFileContext(std::string const &filePath, bool updateStateNow) {
|
||||
static void newFileContext(std::string const &filePath, bool updateStateNow) {
|
||||
checkRecursionDepth();
|
||||
|
||||
std::shared_ptr<std::string> uniqueIDStr = nullptr;
|
||||
@@ -235,7 +236,7 @@ static bool newFileContext(std::string const &filePath, bool updateStateNow) {
|
||||
.macroArgs = macroArgs,
|
||||
});
|
||||
|
||||
return context.lexerState.setFileAsNextState(filePath, updateStateNow);
|
||||
context.lexerState.setFileAsNextState(filePath, updateStateNow);
|
||||
}
|
||||
|
||||
static void newMacroContext(Symbol const ¯o, std::shared_ptr<MacroArgs> macroArgs) {
|
||||
@@ -251,11 +252,7 @@ static void newMacroContext(Symbol const ¯o, std::shared_ptr<MacroArgs> macr
|
||||
}
|
||||
}
|
||||
if (macro.src->type == NODE_REPT) {
|
||||
std::vector<uint32_t> const &srcIters = macro.src->iters();
|
||||
for (uint32_t i = srcIters.size(); i--;) {
|
||||
fileInfoName.append("::REPT~");
|
||||
fileInfoName.append(std::to_string(srcIters[i]));
|
||||
}
|
||||
fileInfoName.append(macro.src->reptChain());
|
||||
}
|
||||
fileInfoName.append("::");
|
||||
fileInfoName.append(macro.name);
|
||||
@@ -303,24 +300,34 @@ static Context &newReptContext(int32_t reptLineNo, ContentSpan const &span, uint
|
||||
return context;
|
||||
}
|
||||
|
||||
void fstk_RunInclude(std::string const &path, bool preInclude) {
|
||||
std::optional<std::string> fullPath = fstk_FindFile(path);
|
||||
|
||||
if (!fullPath) {
|
||||
if (generatedMissingIncludes && !preInclude) {
|
||||
if (verbose) {
|
||||
printf("Aborting (-MG) on INCLUDE file '%s' (%s)\n", path.c_str(), strerror(errno));
|
||||
}
|
||||
failedOnMissingInclude = true;
|
||||
} else {
|
||||
error("Unable to open included file '%s': %s\n", path.c_str(), strerror(errno));
|
||||
bool fstk_FileError(std::string const &path, char const *functionName) {
|
||||
if (options.missingIncludeState == INC_ERROR) {
|
||||
error("Error opening %s file '%s': %s", functionName, path.c_str(), strerror(errno));
|
||||
} else {
|
||||
failedOnMissingInclude = true;
|
||||
// LCOV_EXCL_START
|
||||
if (options.missingIncludeState == GEN_EXIT) {
|
||||
verbosePrint(
|
||||
"Aborting (-MG) on %s file '%s' (%s)\n", functionName, path.c_str(), strerror(errno)
|
||||
);
|
||||
return true;
|
||||
}
|
||||
return;
|
||||
assume(options.missingIncludeState == GEN_CONTINUE);
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!newFileContext(*fullPath, false)) {
|
||||
fatalerror("Failed to set up lexer for file include\n");
|
||||
bool fstk_FailedOnMissingInclude() {
|
||||
return failedOnMissingInclude;
|
||||
}
|
||||
|
||||
bool fstk_RunInclude(std::string const &path) {
|
||||
if (std::optional<std::string> fullPath = fstk_FindFile(path); fullPath) {
|
||||
newFileContext(*fullPath, false);
|
||||
return false;
|
||||
}
|
||||
return fstk_FileError(path, "INCLUDE");
|
||||
}
|
||||
|
||||
void fstk_RunMacro(std::string const ¯oName, std::shared_ptr<MacroArgs> macroArgs) {
|
||||
@@ -328,14 +335,14 @@ void fstk_RunMacro(std::string const ¯oName, std::shared_ptr<MacroArgs> macr
|
||||
|
||||
if (!macro) {
|
||||
if (sym_IsPurgedExact(macroName)) {
|
||||
error("Macro \"%s\" not defined; it was purged\n", macroName.c_str());
|
||||
error("Macro \"%s\" not defined; it was purged", macroName.c_str());
|
||||
} else {
|
||||
error("Macro \"%s\" not defined\n", macroName.c_str());
|
||||
error("Macro \"%s\" not defined", macroName.c_str());
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (macro->type != SYM_MACRO) {
|
||||
error("\"%s\" is not a macro\n", macroName.c_str());
|
||||
error("\"%s\" is not a macro", macroName.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -368,13 +375,11 @@ void fstk_RunFor(
|
||||
} else if (step < 0 && stop < start) {
|
||||
count = (static_cast<int64_t>(start) - stop - 1) / -static_cast<int64_t>(step) + 1;
|
||||
} else if (step == 0) {
|
||||
error("FOR cannot have a step value of 0\n");
|
||||
error("FOR cannot have a step value of 0");
|
||||
}
|
||||
|
||||
if ((step > 0 && start > stop) || (step < 0 && start < stop)) {
|
||||
warning(
|
||||
WARNING_BACKWARDS_FOR, "FOR goes backwards from %d to %d by %d\n", start, stop, step
|
||||
);
|
||||
warning(WARNING_BACKWARDS_FOR, "FOR goes backwards from %d to %d by %d", start, stop, step);
|
||||
}
|
||||
|
||||
if (count == 0) {
|
||||
@@ -388,35 +393,31 @@ void fstk_RunFor(
|
||||
context.forName = symName;
|
||||
}
|
||||
|
||||
void fstk_StopRept() {
|
||||
contextStack.top().nbReptIters = 0; // Prevent more iterations
|
||||
}
|
||||
|
||||
bool fstk_Break() {
|
||||
if (contextStack.top().fileInfo->type != NODE_REPT) {
|
||||
error("BREAK can only be used inside a REPT/FOR block\n");
|
||||
error("BREAK can only be used inside a REPT/FOR block");
|
||||
return false;
|
||||
}
|
||||
|
||||
fstk_StopRept();
|
||||
contextStack.top().nbReptIters = 0; // Prevent more iterations
|
||||
return true;
|
||||
}
|
||||
|
||||
void fstk_NewRecursionDepth(size_t newDepth) {
|
||||
if (contextStack.size() > newDepth + 1) {
|
||||
fatalerror("Recursion limit (%zu) exceeded\n", newDepth);
|
||||
fatal("Recursion limit (%zu) exceeded", newDepth);
|
||||
}
|
||||
maxRecursionDepth = newDepth;
|
||||
options.maxRecursionDepth = newDepth;
|
||||
}
|
||||
|
||||
void fstk_Init(std::string const &mainPath, size_t maxDepth) {
|
||||
if (!newFileContext(mainPath, true)) {
|
||||
fatalerror("Failed to open main file\n");
|
||||
}
|
||||
void fstk_Init(std::string const &mainPath) {
|
||||
newFileContext(mainPath, true);
|
||||
|
||||
maxRecursionDepth = maxDepth;
|
||||
|
||||
if (!preIncludeName.empty()) {
|
||||
fstk_RunInclude(preIncludeName, true);
|
||||
for (std::string const &name : preIncludeNames) {
|
||||
if (std::optional<std::string> fullPath = fstk_FindFile(name); fullPath) {
|
||||
newFileContext(*fullPath, false);
|
||||
} else {
|
||||
error("Error reading pre-included file '%s': %s", name.c_str(), strerror(errno));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
1673
src/asm/lexer.cpp
1673
src/asm/lexer.cpp
File diff suppressed because it is too large
Load Diff
@@ -8,10 +8,16 @@
|
||||
|
||||
#include "asm/warning.hpp"
|
||||
|
||||
std::shared_ptr<std::string> MacroArgs::getArg(uint32_t i) const {
|
||||
uint32_t realIndex = i + shift - 1;
|
||||
std::shared_ptr<std::string> MacroArgs::getArg(int32_t i) const {
|
||||
// Bracketed macro arguments adjust negative indexes such that -1 is the last argument.
|
||||
if (i < 0) {
|
||||
i += args.size() + 1;
|
||||
}
|
||||
|
||||
return realIndex >= args.size() ? nullptr : args[realIndex];
|
||||
int32_t realIndex = i + shift - 1;
|
||||
|
||||
return realIndex < 0 || static_cast<uint32_t>(realIndex) >= args.size() ? nullptr
|
||||
: args[realIndex];
|
||||
}
|
||||
|
||||
std::shared_ptr<std::string> MacroArgs::getAllArgs() const {
|
||||
@@ -23,15 +29,15 @@ std::shared_ptr<std::string> MacroArgs::getAllArgs() const {
|
||||
|
||||
size_t len = 0;
|
||||
|
||||
for (uint32_t i = shift; i < nbArgs; i++) {
|
||||
for (uint32_t i = shift; i < nbArgs; ++i) {
|
||||
len += args[i]->length() + 1; // 1 for comma
|
||||
}
|
||||
|
||||
auto str = std::make_shared<std::string>();
|
||||
str->reserve(len + 1); // 1 for comma
|
||||
|
||||
for (uint32_t i = shift; i < nbArgs; i++) {
|
||||
auto const &arg = args[i];
|
||||
for (uint32_t i = shift; i < nbArgs; ++i) {
|
||||
std::shared_ptr<std::string> const &arg = args[i];
|
||||
|
||||
str->append(*arg);
|
||||
|
||||
@@ -46,7 +52,7 @@ std::shared_ptr<std::string> MacroArgs::getAllArgs() const {
|
||||
|
||||
void MacroArgs::appendArg(std::shared_ptr<std::string> arg) {
|
||||
if (arg->empty()) {
|
||||
warning(WARNING_EMPTY_MACRO_ARG, "Empty macro argument\n");
|
||||
warning(WARNING_EMPTY_MACRO_ARG, "Empty macro argument");
|
||||
}
|
||||
args.push_back(arg);
|
||||
}
|
||||
@@ -54,10 +60,10 @@ void MacroArgs::appendArg(std::shared_ptr<std::string> arg) {
|
||||
void MacroArgs::shiftArgs(int32_t count) {
|
||||
if (size_t nbArgs = args.size();
|
||||
count > 0 && (static_cast<uint32_t>(count) > nbArgs || shift > nbArgs - count)) {
|
||||
warning(WARNING_MACRO_SHIFT, "Cannot shift macro arguments past their end\n");
|
||||
warning(WARNING_MACRO_SHIFT, "Cannot shift macro arguments past their end");
|
||||
shift = nbArgs;
|
||||
} else if (count < 0 && shift < static_cast<uint32_t>(-count)) {
|
||||
warning(WARNING_MACRO_SHIFT, "Cannot shift macro arguments past their beginning\n");
|
||||
warning(WARNING_MACRO_SHIFT, "Cannot shift macro arguments past their beginning");
|
||||
shift = 0;
|
||||
} else {
|
||||
shift += count;
|
||||
|
||||
318
src/asm/main.cpp
318
src/asm/main.cpp
@@ -3,16 +3,20 @@
|
||||
#include "asm/main.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include <memory>
|
||||
#include <stdarg.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "error.hpp"
|
||||
#include "diagnostics.hpp"
|
||||
#include "extern/getopt.hpp"
|
||||
#include "helpers.hpp"
|
||||
#include "parser.hpp"
|
||||
#include "parser.hpp" // Generated from parser.y
|
||||
#include "usage.hpp"
|
||||
#include "util.hpp" // UpperMap
|
||||
#include "version.hpp"
|
||||
|
||||
#include "asm/charmap.hpp"
|
||||
@@ -22,13 +26,7 @@
|
||||
#include "asm/symbol.hpp"
|
||||
#include "asm/warning.hpp"
|
||||
|
||||
FILE *dependFile = nullptr; // -M
|
||||
bool generatedMissingIncludes = false; // -MG
|
||||
bool generatePhonyDeps = false; // -MP
|
||||
std::string targetFileName; // -MQ, -MT
|
||||
bool failedOnMissingInclude = false;
|
||||
bool verbose = false; // -v
|
||||
bool warnings = true; // -w
|
||||
Options options;
|
||||
|
||||
// Escapes Make-special chars from a string
|
||||
static std::string make_escape(std::string &str) {
|
||||
@@ -36,7 +34,7 @@ static std::string make_escape(std::string &str) {
|
||||
size_t pos = 0;
|
||||
for (;;) {
|
||||
// All dollars needs to be doubled
|
||||
size_t nextPos = str.find("$", pos);
|
||||
size_t nextPos = str.find('$', pos);
|
||||
if (nextPos == std::string::npos) {
|
||||
break;
|
||||
}
|
||||
@@ -66,14 +64,14 @@ static option const longopts[] = {
|
||||
{"define", required_argument, nullptr, 'D'},
|
||||
{"export-all", no_argument, nullptr, 'E'},
|
||||
{"gfx-chars", required_argument, nullptr, 'g'},
|
||||
{"help", no_argument, nullptr, 'h'},
|
||||
{"include", required_argument, nullptr, 'I'},
|
||||
{"dependfile", required_argument, nullptr, 'M'},
|
||||
{"MC", no_argument, &depType, 'C'},
|
||||
{"MG", no_argument, &depType, 'G'},
|
||||
{"help", no_argument, nullptr, 'h'},
|
||||
{"MP", no_argument, &depType, 'P'},
|
||||
{"MT", required_argument, &depType, 'T'},
|
||||
{"warning", required_argument, nullptr, 'W'},
|
||||
{"MQ", required_argument, &depType, 'Q'},
|
||||
{"MT", required_argument, &depType, 'T'},
|
||||
{"output", required_argument, nullptr, 'o'},
|
||||
{"preinclude", required_argument, nullptr, 'P'},
|
||||
{"pad-value", required_argument, nullptr, 'p'},
|
||||
@@ -87,59 +85,89 @@ static option const longopts[] = {
|
||||
{nullptr, no_argument, nullptr, 0 }
|
||||
};
|
||||
|
||||
static void printUsage() {
|
||||
fputs(
|
||||
"Usage: rgbasm [-EhVvw] [-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] [-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"
|
||||
" -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
|
||||
);
|
||||
// clang-format off: long string literal
|
||||
static Usage usage(
|
||||
"Usage: rgbasm [-EhVvw] [-b chars] [-D name[=value]] [-g chars] [-I path]\n"
|
||||
" [-M depend_file] [-MC] [-MG] [-MP] [-MT target_file] [-MQ target_file]\n"
|
||||
" [-o out_file] [-P include_file] [-p pad_value] [-Q precision]\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"
|
||||
" -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"
|
||||
);
|
||||
// clang-format on
|
||||
|
||||
// Parse a comma-separated string of '-s/--state' features
|
||||
static std::vector<StateFeature> parseStateFeatures(char *str) {
|
||||
std::vector<StateFeature> features;
|
||||
for (char *feature = str; 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') {
|
||||
fatal("Empty feature for option 's'");
|
||||
}
|
||||
// Parse the `feature` and update the `features` list
|
||||
static UpperMap<StateFeature> const featureNames{
|
||||
{"EQU", STATE_EQU },
|
||||
{"VAR", STATE_VAR },
|
||||
{"EQUS", STATE_EQUS },
|
||||
{"CHAR", STATE_CHAR },
|
||||
{"MACRO", STATE_MACRO},
|
||||
};
|
||||
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 if (auto search = featureNames.find(feature); search == featureNames.end()) {
|
||||
fatal("Invalid feature for option 's': \"%s\"", feature);
|
||||
} else if (StateFeature value = search->second;
|
||||
std::find(RANGE(features), value) != features.end()) {
|
||||
warnx("Ignoring duplicate feature for option 's': \"%s\"", feature);
|
||||
} else {
|
||||
features.push_back(value);
|
||||
}
|
||||
feature = next;
|
||||
}
|
||||
return features;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
time_t now = time(nullptr);
|
||||
// Support SOURCE_DATE_EPOCH for reproducible builds
|
||||
// https://reproducible-builds.org/docs/source-date-epoch/
|
||||
time_t now = time(nullptr);
|
||||
if (char const *sourceDateEpoch = getenv("SOURCE_DATE_EPOCH"); sourceDateEpoch) {
|
||||
now = static_cast<time_t>(strtoul(sourceDateEpoch, nullptr, 0));
|
||||
}
|
||||
|
||||
Defer closeDependFile{[&] {
|
||||
if (dependFile) {
|
||||
fclose(dependFile);
|
||||
}
|
||||
}};
|
||||
|
||||
// Perform some init for below
|
||||
sym_Init(now);
|
||||
|
||||
// Set defaults
|
||||
opt_B("01");
|
||||
opt_G("0123");
|
||||
opt_P(0);
|
||||
opt_Q(16);
|
||||
sym_SetExportAll(false);
|
||||
uint32_t maxDepth = 64;
|
||||
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.
|
||||
// Maximum of 100 errors only applies if rgbasm is printing errors to a terminal
|
||||
if (isatty(STDERR_FILENO)) {
|
||||
maxErrors = 100;
|
||||
options.maxErrors = 100;
|
||||
}
|
||||
|
||||
// Local options
|
||||
char const *dependFileName = nullptr; // -M
|
||||
std::unordered_map<std::string, std::vector<StateFeature>> stateFileSpecs; // -s
|
||||
|
||||
for (int ch; (ch = musl_getopt_long_only(argc, argv, optstring, longopts, nullptr)) != -1;) {
|
||||
switch (ch) {
|
||||
char *endptr;
|
||||
@@ -148,7 +176,7 @@ int main(int argc, char *argv[]) {
|
||||
if (strlen(musl_optarg) == 2) {
|
||||
opt_B(musl_optarg);
|
||||
} else {
|
||||
errx("Must specify exactly 2 characters for option 'b'");
|
||||
fatal("Must specify exactly 2 characters for option 'b'");
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -171,40 +199,45 @@ int main(int argc, char *argv[]) {
|
||||
if (strlen(musl_optarg) == 4) {
|
||||
opt_G(musl_optarg);
|
||||
} else {
|
||||
errx("Must specify exactly 4 characters for option 'g'");
|
||||
fatal("Must specify exactly 4 characters for option 'g'");
|
||||
}
|
||||
break;
|
||||
|
||||
case 'h':
|
||||
printUsage();
|
||||
exit(0);
|
||||
usage.printAndExit(0); // LCOV_EXCL_LINE
|
||||
|
||||
case 'I':
|
||||
fstk_AddIncludePath(musl_optarg);
|
||||
break;
|
||||
|
||||
case 'M':
|
||||
if (dependFile) {
|
||||
if (options.dependFile) {
|
||||
warnx("Overriding dependfile %s", dependFileName);
|
||||
}
|
||||
if (strcmp("-", musl_optarg)) {
|
||||
dependFile = fopen(musl_optarg, "w");
|
||||
options.dependFile = fopen(musl_optarg, "w");
|
||||
dependFileName = musl_optarg;
|
||||
} else {
|
||||
dependFile = stdout;
|
||||
options.dependFile = stdout;
|
||||
dependFileName = "<stdout>";
|
||||
}
|
||||
if (dependFile == nullptr) {
|
||||
err("Failed to open dependfile \"%s\"", dependFileName);
|
||||
if (options.dependFile == nullptr) {
|
||||
// LCOV_EXCL_START
|
||||
fatal("Failed to open dependfile \"%s\": %s", dependFileName, strerror(errno));
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
break;
|
||||
|
||||
case 'o':
|
||||
out_SetFileName(musl_optarg);
|
||||
if (!options.objectFileName.empty()) {
|
||||
warnx("Overriding output filename %s", options.objectFileName.c_str());
|
||||
}
|
||||
options.objectFileName = musl_optarg;
|
||||
verbosePrint("Output filename %s\n", options.objectFileName.c_str()); // LCOV_EXCL_LINE
|
||||
break;
|
||||
|
||||
case 'P':
|
||||
fstk_SetPreIncludeFile(musl_optarg);
|
||||
fstk_AddPreIncludeFile(musl_optarg);
|
||||
break;
|
||||
|
||||
unsigned long padByte;
|
||||
@@ -212,41 +245,40 @@ int main(int argc, char *argv[]) {
|
||||
padByte = strtoul(musl_optarg, &endptr, 0);
|
||||
|
||||
if (musl_optarg[0] == '\0' || *endptr != '\0') {
|
||||
errx("Invalid argument for option 'p'");
|
||||
fatal("Invalid argument for option 'p'");
|
||||
}
|
||||
|
||||
if (padByte > 0xFF) {
|
||||
errx("Argument for option 'p' must be between 0 and 0xFF");
|
||||
fatal("Argument for option 'p' must be between 0 and 0xFF");
|
||||
}
|
||||
|
||||
opt_P(padByte);
|
||||
break;
|
||||
|
||||
unsigned long precision;
|
||||
char const *precisionArg;
|
||||
case 'Q':
|
||||
precisionArg = musl_optarg;
|
||||
case 'Q': {
|
||||
char const *precisionArg = musl_optarg;
|
||||
if (precisionArg[0] == '.') {
|
||||
precisionArg++;
|
||||
++precisionArg;
|
||||
}
|
||||
precision = strtoul(precisionArg, &endptr, 0);
|
||||
unsigned long precision = strtoul(precisionArg, &endptr, 0);
|
||||
|
||||
if (musl_optarg[0] == '\0' || *endptr != '\0') {
|
||||
errx("Invalid argument for option 'Q'");
|
||||
fatal("Invalid argument for option 'Q'");
|
||||
}
|
||||
|
||||
if (precision < 1 || precision > 31) {
|
||||
errx("Argument for option 'Q' must be between 1 and 31");
|
||||
fatal("Argument for option 'Q' must be between 1 and 31");
|
||||
}
|
||||
|
||||
opt_Q(precision);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'r':
|
||||
maxDepth = strtoul(musl_optarg, &endptr, 0);
|
||||
options.maxRecursionDepth = strtoul(musl_optarg, &endptr, 0);
|
||||
|
||||
if (musl_optarg[0] == '\0' || *endptr != '\0') {
|
||||
errx("Invalid argument for option 'r'");
|
||||
fatal("Invalid argument for option 'r'");
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -254,57 +286,16 @@ int main(int argc, char *argv[]) {
|
||||
// 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'");
|
||||
fatal("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;
|
||||
}
|
||||
std::vector<StateFeature> features = parseStateFeatures(musl_optarg);
|
||||
|
||||
if (stateFileSpecs.find(name) != stateFileSpecs.end()) {
|
||||
warnx("Overriding state filename %s", name);
|
||||
}
|
||||
if (verbose) {
|
||||
printf("State filename %s\n", name);
|
||||
}
|
||||
verbosePrint("State filename %s\n", name); // LCOV_EXCL_LINE
|
||||
stateFileSpecs.emplace(name, std::move(features));
|
||||
break;
|
||||
}
|
||||
@@ -314,106 +305,103 @@ int main(int argc, char *argv[]) {
|
||||
exit(0);
|
||||
|
||||
case 'v':
|
||||
verbose = true;
|
||||
// LCOV_EXCL_START
|
||||
options.verbose = true;
|
||||
break;
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
case 'W':
|
||||
opt_W(musl_optarg);
|
||||
break;
|
||||
|
||||
case 'w':
|
||||
warnings = false;
|
||||
warnings.state.warningsEnabled = false;
|
||||
break;
|
||||
|
||||
unsigned long maxValue;
|
||||
case 'X':
|
||||
maxValue = strtoul(musl_optarg, &endptr, 0);
|
||||
case 'X': {
|
||||
uint64_t maxErrors = strtoul(musl_optarg, &endptr, 0);
|
||||
|
||||
if (musl_optarg[0] == '\0' || *endptr != '\0') {
|
||||
errx("Invalid argument for option 'X'");
|
||||
fatal("Invalid argument for option 'X'");
|
||||
}
|
||||
|
||||
if (maxValue > UINT_MAX) {
|
||||
errx("Argument for option 'X' must be between 0 and %u", UINT_MAX);
|
||||
if (maxErrors > UINT64_MAX) {
|
||||
fatal("Argument for option 'X' must be between 0 and %" PRIu64, UINT64_MAX);
|
||||
}
|
||||
|
||||
maxErrors = maxValue;
|
||||
options.maxErrors = maxErrors;
|
||||
break;
|
||||
}
|
||||
|
||||
// Long-only options
|
||||
case 0:
|
||||
switch (depType) {
|
||||
case 'C':
|
||||
options.missingIncludeState = GEN_CONTINUE;
|
||||
break;
|
||||
|
||||
case 'G':
|
||||
generatedMissingIncludes = true;
|
||||
options.missingIncludeState = GEN_EXIT;
|
||||
break;
|
||||
|
||||
case 'P':
|
||||
generatePhonyDeps = true;
|
||||
options.generatePhonyDeps = true;
|
||||
break;
|
||||
|
||||
case 'Q':
|
||||
case 'T':
|
||||
newTarget = musl_optarg;
|
||||
case 'T': {
|
||||
std::string newTarget = musl_optarg;
|
||||
if (depType == 'Q') {
|
||||
newTarget = make_escape(newTarget);
|
||||
}
|
||||
if (!targetFileName.empty()) {
|
||||
targetFileName += ' ';
|
||||
if (!options.targetFileName.empty()) {
|
||||
options.targetFileName += ' ';
|
||||
}
|
||||
targetFileName += newTarget;
|
||||
options.targetFileName += newTarget;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
// Unrecognized options
|
||||
default:
|
||||
printUsage();
|
||||
exit(1);
|
||||
usage.printAndExit(1); // LCOV_EXCL_LINE
|
||||
}
|
||||
}
|
||||
|
||||
if (targetFileName.empty() && !objectFileName.empty()) {
|
||||
targetFileName = objectFileName;
|
||||
if (options.targetFileName.empty() && !options.objectFileName.empty()) {
|
||||
options.targetFileName = options.objectFileName;
|
||||
}
|
||||
|
||||
if (argc == musl_optind) {
|
||||
fputs(
|
||||
"FATAL: Please specify an input file (pass `-` to read from standard input)\n", stderr
|
||||
);
|
||||
printUsage();
|
||||
exit(1);
|
||||
usage.printAndExit("Please specify an input file (pass `-` to read from standard input)");
|
||||
} else if (argc != musl_optind + 1) {
|
||||
fputs("FATAL: More than one input file specified\n", stderr);
|
||||
printUsage();
|
||||
exit(1);
|
||||
usage.printAndExit("More than one input file specified");
|
||||
}
|
||||
|
||||
std::string mainFileName = argv[musl_optind];
|
||||
|
||||
if (verbose) {
|
||||
printf("Assembling %s\n", mainFileName.c_str());
|
||||
}
|
||||
verbosePrint("Assembling %s\n", mainFileName.c_str()); // LCOV_EXCL_LINE
|
||||
|
||||
if (dependFile) {
|
||||
if (targetFileName.empty()) {
|
||||
errx("Dependency files can only be created if a target file is specified with either "
|
||||
"-o, -MQ or -MT");
|
||||
}
|
||||
|
||||
fprintf(dependFile, "%s: %s\n", targetFileName.c_str(), mainFileName.c_str());
|
||||
if (options.dependFile && options.targetFileName.empty()) {
|
||||
fatal("Dependency files can only be created if a target file is specified with either "
|
||||
"-o, -MQ or -MT");
|
||||
}
|
||||
options.printDep(mainFileName);
|
||||
|
||||
charmap_New(DEFAULT_CHARMAP_NAME, nullptr);
|
||||
|
||||
// Init lexer and file stack, providing file info
|
||||
fstk_Init(mainFileName, maxDepth);
|
||||
fstk_Init(mainFileName);
|
||||
|
||||
// Perform parse (`yy::parser` is auto-generated from `parser.y`)
|
||||
if (yy::parser parser; parser.parse() != 0 && nbErrors == 0) {
|
||||
nbErrors = 1;
|
||||
if (yy::parser parser; parser.parse() != 0) {
|
||||
if (warnings.nbErrors == 0) {
|
||||
warnings.nbErrors = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (!failedOnMissingInclude) {
|
||||
if (!fstk_FailedOnMissingInclude()) {
|
||||
sect_CheckUnionClosed();
|
||||
sect_CheckLoadClosed();
|
||||
sect_CheckSizes();
|
||||
@@ -423,12 +411,10 @@ int main(int argc, char *argv[]) {
|
||||
sect_CheckStack();
|
||||
}
|
||||
|
||||
if (nbErrors != 0) {
|
||||
errx("Assembly aborted (%u error%s)!", nbErrors, nbErrors == 1 ? "" : "s");
|
||||
}
|
||||
requireZeroErrors();
|
||||
|
||||
// If parse aborted due to missing an include, and `-MG` was given, exit normally
|
||||
if (failedOnMissingInclude) {
|
||||
if (fstk_FailedOnMissingInclude()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
115
src/asm/opt.cpp
115
src/asm/opt.cpp
@@ -7,47 +7,51 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "helpers.hpp" // assume
|
||||
|
||||
#include "asm/fixpoint.hpp"
|
||||
#include "asm/fstack.hpp"
|
||||
#include "asm/lexer.hpp"
|
||||
#include "asm/main.hpp" // options
|
||||
#include "asm/section.hpp"
|
||||
#include "asm/warning.hpp"
|
||||
|
||||
struct OptStackEntry {
|
||||
char binary[2];
|
||||
char gbgfx[4];
|
||||
char binDigits[2];
|
||||
char gfxDigits[4];
|
||||
uint8_t fixPrecision;
|
||||
uint8_t fillByte;
|
||||
bool warningsAreErrors;
|
||||
uint8_t padByte;
|
||||
size_t maxRecursionDepth;
|
||||
Diagnostics warningStates;
|
||||
DiagnosticsState<WarningID> warningStates;
|
||||
};
|
||||
|
||||
static std::stack<OptStackEntry> stack;
|
||||
|
||||
void opt_B(char const chars[2]) {
|
||||
lexer_SetBinDigits(chars);
|
||||
void opt_B(char const binDigits[2]) {
|
||||
lexer_SetBinDigits(binDigits);
|
||||
}
|
||||
|
||||
void opt_G(char const chars[4]) {
|
||||
lexer_SetGfxDigits(chars);
|
||||
void opt_G(char const gfxDigits[4]) {
|
||||
lexer_SetGfxDigits(gfxDigits);
|
||||
}
|
||||
|
||||
void opt_P(uint8_t padByte) {
|
||||
fillByte = padByte;
|
||||
options.padByte = padByte;
|
||||
}
|
||||
|
||||
void opt_Q(uint8_t precision) {
|
||||
fixPrecision = precision;
|
||||
void opt_Q(uint8_t fixPrecision) {
|
||||
options.fixPrecision = fixPrecision;
|
||||
}
|
||||
|
||||
void opt_R(size_t newDepth) {
|
||||
fstk_NewRecursionDepth(newDepth);
|
||||
void opt_R(size_t maxRecursionDepth) {
|
||||
fstk_NewRecursionDepth(maxRecursionDepth);
|
||||
lexer_CheckRecursionDepth();
|
||||
}
|
||||
|
||||
void opt_W(char const *flag) {
|
||||
processWarningFlag(flag);
|
||||
if (warnings.processWarningFlag(flag) == "numeric-string") {
|
||||
warning(WARNING_OBSOLETE, "Warning flag \"numeric-string\" is deprecated");
|
||||
}
|
||||
}
|
||||
|
||||
void opt_Parse(char const *s) {
|
||||
@@ -56,7 +60,7 @@ void opt_Parse(char const *s) {
|
||||
if (strlen(&s[1]) == 2) {
|
||||
opt_B(&s[1]);
|
||||
} else {
|
||||
error("Must specify exactly 2 characters for option 'b'\n");
|
||||
error("Must specify exactly 2 characters for option 'b'");
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -64,7 +68,7 @@ void opt_Parse(char const *s) {
|
||||
if (strlen(&s[1]) == 4) {
|
||||
opt_G(&s[1]);
|
||||
} else {
|
||||
error("Must specify exactly 4 characters for option 'g'\n");
|
||||
error("Must specify exactly 4 characters for option 'g'");
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -75,14 +79,14 @@ void opt_Parse(char const *s) {
|
||||
|
||||
result = sscanf(&s[1], "%x", &padByte);
|
||||
if (result != 1) {
|
||||
error("Invalid argument for option 'p'\n");
|
||||
} else if (padByte > 0xFF) {
|
||||
error("Argument for option 'p' must be between 0 and 0xFF\n");
|
||||
error("Invalid argument for option 'p'");
|
||||
} else {
|
||||
// Two characters cannot be scanned as a hex number greater than 0xFF
|
||||
assume(padByte <= 0xFF);
|
||||
opt_P(padByte);
|
||||
}
|
||||
} else {
|
||||
error("Invalid argument for option 'p'\n");
|
||||
error("Invalid argument for option 'p'");
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -90,22 +94,22 @@ void opt_Parse(char const *s) {
|
||||
case 'Q':
|
||||
precisionArg = &s[1];
|
||||
if (precisionArg[0] == '.') {
|
||||
precisionArg++;
|
||||
++precisionArg;
|
||||
}
|
||||
if (strlen(precisionArg) <= 2) {
|
||||
int result;
|
||||
unsigned int precision;
|
||||
unsigned int fixPrecision;
|
||||
|
||||
result = sscanf(precisionArg, "%u", &precision);
|
||||
result = sscanf(precisionArg, "%u", &fixPrecision);
|
||||
if (result != 1) {
|
||||
error("Invalid argument for option 'Q'\n");
|
||||
} else if (precision < 1 || precision > 31) {
|
||||
error("Argument for option 'Q' must be between 1 and 31\n");
|
||||
error("Invalid argument for option 'Q'");
|
||||
} else if (fixPrecision < 1 || fixPrecision > 31) {
|
||||
error("Argument for option 'Q' must be between 1 and 31");
|
||||
} else {
|
||||
opt_Q(precision);
|
||||
opt_Q(fixPrecision);
|
||||
}
|
||||
} else {
|
||||
error("Invalid argument for option 'Q'\n");
|
||||
error("Invalid argument for option 'Q'");
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -116,19 +120,19 @@ void opt_Parse(char const *s) {
|
||||
}
|
||||
|
||||
if (s[0] == '\0') {
|
||||
error("Missing argument to option 'r'\n");
|
||||
error("Missing argument to option 'r'");
|
||||
break;
|
||||
}
|
||||
|
||||
char *endptr;
|
||||
unsigned long newDepth = strtoul(s, &endptr, 10);
|
||||
unsigned long maxRecursionDepth = strtoul(s, &endptr, 10);
|
||||
|
||||
if (*endptr != '\0') {
|
||||
error("Invalid argument to option 'r' (\"%s\")\n", s);
|
||||
error("Invalid argument to option 'r' (\"%s\")", s);
|
||||
} else if (errno == ERANGE) {
|
||||
error("Argument to 'r' is out of range (\"%s\")\n", s);
|
||||
error("Argument to 'r' is out of range (\"%s\")", s);
|
||||
} else {
|
||||
opt_R(newDepth);
|
||||
opt_R(maxRecursionDepth);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -137,12 +141,12 @@ void opt_Parse(char const *s) {
|
||||
if (strlen(&s[1]) > 0) {
|
||||
opt_W(&s[1]);
|
||||
} else {
|
||||
error("Must specify an argument for option 'W'\n");
|
||||
error("Must specify an argument for option 'W'");
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
error("Unknown option '%c'\n", s[0]);
|
||||
error("Unknown option '%c'", s[0]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -150,50 +154,37 @@ void opt_Parse(char const *s) {
|
||||
void opt_Push() {
|
||||
OptStackEntry entry;
|
||||
|
||||
// Both of these are pulled from lexer.hpp
|
||||
entry.binary[0] = binDigits[0];
|
||||
entry.binary[1] = binDigits[1];
|
||||
|
||||
entry.gbgfx[0] = gfxDigits[0];
|
||||
entry.gbgfx[1] = gfxDigits[1];
|
||||
entry.gbgfx[2] = gfxDigits[2];
|
||||
entry.gbgfx[3] = gfxDigits[3];
|
||||
|
||||
entry.fixPrecision = fixPrecision; // Pulled from fixpoint.hpp
|
||||
|
||||
entry.fillByte = fillByte; // Pulled from section.hpp
|
||||
|
||||
// Both of these pulled from warning.hpp
|
||||
entry.warningsAreErrors = warningsAreErrors;
|
||||
entry.warningStates = warningStates;
|
||||
|
||||
entry.maxRecursionDepth = maxRecursionDepth; // Pulled from fstack.h
|
||||
memcpy(entry.binDigits, options.binDigits, std::size(options.binDigits));
|
||||
memcpy(entry.gfxDigits, options.gfxDigits, std::size(options.gfxDigits));
|
||||
entry.padByte = options.padByte;
|
||||
entry.fixPrecision = options.fixPrecision;
|
||||
entry.maxRecursionDepth = options.maxRecursionDepth;
|
||||
entry.warningStates = warnings.state;
|
||||
|
||||
stack.push(entry);
|
||||
}
|
||||
|
||||
void opt_Pop() {
|
||||
if (stack.empty()) {
|
||||
error("No entries in the option stack\n");
|
||||
error("No entries in the option stack");
|
||||
return;
|
||||
}
|
||||
|
||||
OptStackEntry entry = stack.top();
|
||||
stack.pop();
|
||||
|
||||
opt_B(entry.binary);
|
||||
opt_G(entry.gbgfx);
|
||||
opt_P(entry.fillByte);
|
||||
opt_B(entry.binDigits);
|
||||
opt_G(entry.gfxDigits);
|
||||
opt_P(entry.padByte);
|
||||
opt_Q(entry.fixPrecision);
|
||||
opt_R(entry.maxRecursionDepth);
|
||||
|
||||
// opt_W does not apply a whole warning state; it processes one flag string
|
||||
warningsAreErrors = entry.warningsAreErrors;
|
||||
warningStates = entry.warningStates;
|
||||
// `opt_W` does not apply a whole warning state; it processes one flag string
|
||||
warnings.state = entry.warningStates;
|
||||
}
|
||||
|
||||
void opt_CheckStack() {
|
||||
if (!stack.empty()) {
|
||||
warning(WARNING_UNMATCHED_DIRECTIVE, "`PUSHO` without corresponding `POPO`\n");
|
||||
warning(WARNING_UNMATCHED_DIRECTIVE, "`PUSHO` without corresponding `POPO`");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,13 +4,14 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <deque>
|
||||
#include <errno.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <string>
|
||||
|
||||
#include "error.hpp"
|
||||
#include "diagnostics.hpp"
|
||||
#include "helpers.hpp" // assume, Defer
|
||||
#include "platform.hpp"
|
||||
|
||||
@@ -29,8 +30,6 @@ struct Assertion {
|
||||
std::string message;
|
||||
};
|
||||
|
||||
std::string objectFileName;
|
||||
|
||||
// List of symbols to put in the object file
|
||||
static std::vector<Symbol *> objectSymbols;
|
||||
|
||||
@@ -61,26 +60,13 @@ void out_RegisterNode(std::shared_ptr<FileStackNode> node) {
|
||||
}
|
||||
}
|
||||
|
||||
// Return a section's ID, or UINT32_MAX if the section is not in the list
|
||||
static uint32_t getSectIDIfAny(Section *sect) {
|
||||
if (!sect) {
|
||||
return UINT32_MAX;
|
||||
}
|
||||
|
||||
if (auto search = sectionMap.find(sect->name); search != sectionMap.end()) {
|
||||
return static_cast<uint32_t>(search->second);
|
||||
}
|
||||
|
||||
fatalerror("Unknown section '%s'\n", sect->name.c_str());
|
||||
}
|
||||
|
||||
static void writePatch(Patch const &patch, FILE *file) {
|
||||
assume(patch.src->ID != UINT32_MAX);
|
||||
|
||||
putLong(patch.src->ID, file);
|
||||
putLong(patch.lineNo, file);
|
||||
putLong(patch.offset, file);
|
||||
putLong(getSectIDIfAny(patch.pcSection), file);
|
||||
putLong(patch.pcSection ? patch.pcSection->getID() : UINT32_MAX, file);
|
||||
putLong(patch.pcOffset, file);
|
||||
putc(patch.type, file);
|
||||
putLong(patch.rpn.size(), file);
|
||||
@@ -124,10 +110,12 @@ static void writeSymbol(Symbol const &sym, FILE *file) {
|
||||
} else {
|
||||
assume(sym.src->ID != UINT32_MAX);
|
||||
|
||||
Section *symSection = sym.getSection();
|
||||
|
||||
putc(sym.isExported ? SYMTYPE_EXPORT : SYMTYPE_LOCAL, file);
|
||||
putLong(sym.src->ID, file);
|
||||
putLong(sym.fileLine, file);
|
||||
putLong(getSectIDIfAny(sym.getSection()), file);
|
||||
putLong(symSection ? symSection->getID() : UINT32_MAX, file);
|
||||
putLong(sym.getOutputValue(), file);
|
||||
}
|
||||
}
|
||||
@@ -142,12 +130,19 @@ static void registerUnregisteredSymbol(Symbol &sym) {
|
||||
}
|
||||
|
||||
static void writeRpn(std::vector<uint8_t> &rpnexpr, std::vector<uint8_t> const &rpn) {
|
||||
std::string symName;
|
||||
size_t rpnptr = 0;
|
||||
|
||||
for (size_t offset = 0; offset < rpn.size();) {
|
||||
uint8_t rpndata = rpn[offset++];
|
||||
|
||||
auto getSymName = [&]() {
|
||||
std::string symName;
|
||||
for (uint8_t c; (c = rpn[offset++]) != 0;) {
|
||||
symName += c;
|
||||
}
|
||||
return symName;
|
||||
};
|
||||
|
||||
switch (rpndata) {
|
||||
Symbol *sym;
|
||||
uint32_t value;
|
||||
@@ -162,20 +157,11 @@ static void writeRpn(std::vector<uint8_t> &rpnexpr, std::vector<uint8_t> const &
|
||||
break;
|
||||
|
||||
case RPN_SYM:
|
||||
symName.clear();
|
||||
for (;;) {
|
||||
uint8_t c = rpn[offset++];
|
||||
if (c == 0) {
|
||||
break;
|
||||
}
|
||||
symName += c;
|
||||
}
|
||||
|
||||
// The symbol name is always written expanded
|
||||
sym = sym_FindExactSymbol(symName);
|
||||
sym = sym_FindExactSymbol(getSymName());
|
||||
if (sym->isConstant()) {
|
||||
rpnexpr[rpnptr++] = RPN_CONST;
|
||||
value = sym_GetConstantValue(symName);
|
||||
value = sym->getConstantValue();
|
||||
} else {
|
||||
rpnexpr[rpnptr++] = RPN_SYM;
|
||||
registerUnregisteredSymbol(*sym); // Ensure that `sym->ID` is set
|
||||
@@ -189,17 +175,8 @@ static void writeRpn(std::vector<uint8_t> &rpnexpr, std::vector<uint8_t> const &
|
||||
break;
|
||||
|
||||
case RPN_BANK_SYM:
|
||||
symName.clear();
|
||||
for (;;) {
|
||||
uint8_t c = rpn[offset++];
|
||||
if (c == 0) {
|
||||
break;
|
||||
}
|
||||
symName += c;
|
||||
}
|
||||
|
||||
// The symbol name is always written expanded
|
||||
sym = sym_FindExactSymbol(symName);
|
||||
sym = sym_FindExactSymbol(getSymName());
|
||||
registerUnregisteredSymbol(*sym); // Ensure that `sym->ID` is set
|
||||
value = sym->ID;
|
||||
|
||||
@@ -268,7 +245,8 @@ static void initPatch(Patch &patch, uint32_t type, Expression const &expr, uint3
|
||||
|
||||
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();
|
||||
assume(sect_GetOutputBank().has_value());
|
||||
Patch &patch = *sect_AddOutputPatch();
|
||||
|
||||
initPatch(patch, type, expr, ofs);
|
||||
|
||||
@@ -310,20 +288,24 @@ static void writeFileStackNode(FileStackNode const &node, FILE *file) {
|
||||
}
|
||||
|
||||
void out_WriteObject() {
|
||||
if (objectFileName.empty()) {
|
||||
if (options.objectFileName.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
FILE *file;
|
||||
if (objectFileName != "-") {
|
||||
file = fopen(objectFileName.c_str(), "wb");
|
||||
static FILE *file; // `static` so `sect_ForEach` callback can see it
|
||||
if (options.objectFileName != "-") {
|
||||
file = fopen(options.objectFileName.c_str(), "wb");
|
||||
} else {
|
||||
objectFileName = "<stdout>";
|
||||
options.objectFileName = "<stdout>";
|
||||
(void)setmode(STDOUT_FILENO, O_BINARY);
|
||||
file = stdout;
|
||||
}
|
||||
if (!file) {
|
||||
err("Failed to open object file '%s'", objectFileName.c_str());
|
||||
// LCOV_EXCL_START
|
||||
fatal(
|
||||
"Failed to open object file '%s': %s", options.objectFileName.c_str(), strerror(errno)
|
||||
);
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
Defer closeFile{[&] { fclose(file); }};
|
||||
|
||||
@@ -334,32 +316,23 @@ void out_WriteObject() {
|
||||
putLong(RGBDS_OBJECT_REV, file);
|
||||
|
||||
putLong(objectSymbols.size(), file);
|
||||
putLong(sectionList.size(), file);
|
||||
putLong(sect_CountSections(), file);
|
||||
|
||||
putLong(fileStackNodes.size(), file);
|
||||
for (auto it = fileStackNodes.begin(); it != fileStackNodes.end(); it++) {
|
||||
for (auto it = fileStackNodes.begin(); it != fileStackNodes.end(); ++it) {
|
||||
FileStackNode const &node = **it;
|
||||
|
||||
writeFileStackNode(node, file);
|
||||
|
||||
// The list is supposed to have decrementing IDs
|
||||
if (it + 1 != fileStackNodes.end() && it[1]->ID != node.ID - 1) {
|
||||
fatalerror(
|
||||
"Internal error: fstack node #%" PRIu32 " follows #%" PRIu32
|
||||
". Please report this to the developers!\n",
|
||||
it[1]->ID,
|
||||
node.ID
|
||||
);
|
||||
}
|
||||
assume(it + 1 == fileStackNodes.end() || it[1]->ID == node.ID - 1);
|
||||
}
|
||||
|
||||
for (Symbol const *sym : objectSymbols) {
|
||||
writeSymbol(*sym, file);
|
||||
}
|
||||
|
||||
for (Section const § : sectionList) {
|
||||
writeSection(sect, file);
|
||||
}
|
||||
sect_ForEach([](Section §) { writeSection(sect, file); });
|
||||
|
||||
putLong(assertions.size(), file);
|
||||
|
||||
@@ -368,16 +341,6 @@ void out_WriteObject() {
|
||||
}
|
||||
}
|
||||
|
||||
void out_SetFileName(std::string const &name) {
|
||||
if (!objectFileName.empty()) {
|
||||
warnx("Overriding output filename %s", objectFileName.c_str());
|
||||
}
|
||||
objectFileName = name;
|
||||
if (verbose) {
|
||||
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
|
||||
@@ -406,6 +369,11 @@ static void dumpString(std::string const &escape, FILE *file) {
|
||||
}
|
||||
}
|
||||
|
||||
// Symbols are ordered by file, then by definition order
|
||||
static bool compareSymbols(Symbol const *sym1, Symbol const *sym2) {
|
||||
return sym1->defIndex < sym2->defIndex;
|
||||
}
|
||||
|
||||
static bool dumpEquConstants(FILE *file) {
|
||||
static std::vector<Symbol *> equConstants; // `static` so `sym_ForEach` callback can see it
|
||||
equConstants.clear();
|
||||
@@ -415,10 +383,7 @@ static bool dumpEquConstants(FILE *file) {
|
||||
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;
|
||||
});
|
||||
std::sort(RANGE(equConstants), compareSymbols);
|
||||
|
||||
for (Symbol const *sym : equConstants) {
|
||||
uint32_t value = static_cast<uint32_t>(sym->getOutputValue());
|
||||
@@ -437,10 +402,7 @@ static bool dumpVariables(FILE *file) {
|
||||
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;
|
||||
});
|
||||
std::sort(RANGE(variables), compareSymbols);
|
||||
|
||||
for (Symbol const *sym : variables) {
|
||||
uint32_t value = static_cast<uint32_t>(sym->getOutputValue());
|
||||
@@ -459,10 +421,7 @@ static bool dumpEqusConstants(FILE *file) {
|
||||
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;
|
||||
});
|
||||
std::sort(RANGE(equsConstants), compareSymbols);
|
||||
|
||||
for (Symbol const *sym : equsConstants) {
|
||||
fprintf(file, "def %s equs \"", sym->name.c_str());
|
||||
@@ -501,13 +460,10 @@ static bool dumpMacros(FILE *file) {
|
||||
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;
|
||||
});
|
||||
std::sort(RANGE(macros), compareSymbols);
|
||||
|
||||
for (Symbol const *sym : macros) {
|
||||
auto const &body = sym->getMacro();
|
||||
ContentSpan 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);
|
||||
@@ -528,7 +484,9 @@ void out_WriteState(std::string name, std::vector<StateFeature> const &features)
|
||||
file = stdout;
|
||||
}
|
||||
if (!file) {
|
||||
err("Failed to open state file '%s'", name.c_str());
|
||||
// LCOV_EXCL_START
|
||||
fatal("Failed to open state file '%s': %s", name.c_str(), strerror(errno));
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
Defer closeFile{[&] { fclose(file); }};
|
||||
|
||||
@@ -551,7 +509,7 @@ void out_WriteState(std::string name, std::vector<StateFeature> const &features)
|
||||
for (StateFeature feature : features) {
|
||||
fprintf(file, "\n; %s\n", dumpHeadings[feature]);
|
||||
if (!dumpFuncs[feature](file)) {
|
||||
fprintf(file, "; No values\n");
|
||||
fputs("; No values\n", file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
998
src/asm/parser.y
998
src/asm/parser.y
File diff suppressed because it is too large
Load Diff
147
src/asm/rpn.cpp
147
src/asm/rpn.cpp
@@ -4,6 +4,7 @@
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <limits.h>
|
||||
#include <optional>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@@ -39,7 +40,7 @@ uint8_t *Expression::reserveSpace(uint32_t size, uint32_t patchSize) {
|
||||
|
||||
int32_t Expression::getConstVal() const {
|
||||
if (!isKnown()) {
|
||||
error("Expected constant expression: %s\n", data.get<std::string>().c_str());
|
||||
error("Expected constant expression: %s", std::get<std::string>(data).c_str());
|
||||
return 0;
|
||||
}
|
||||
return value();
|
||||
@@ -73,7 +74,10 @@ 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 of a section\n");
|
||||
error("PC has no value outside of a section");
|
||||
data = 0;
|
||||
} else if (sym && !sym->isNumeric() && !sym->isLabel()) {
|
||||
error("'%s' is not a numeric symbol", symName.c_str());
|
||||
data = 0;
|
||||
} else if (!sym || !sym->isConstant()) {
|
||||
isSymbol = true;
|
||||
@@ -91,7 +95,7 @@ void Expression::makeSymbol(std::string const &symName) {
|
||||
*ptr++ = RPN_SYM;
|
||||
memcpy(ptr, sym->name.c_str(), nameLen);
|
||||
} else {
|
||||
data = static_cast<int32_t>(sym_GetConstantValue(symName));
|
||||
data = static_cast<int32_t>(sym->getConstantValue());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,19 +103,19 @@ void Expression::makeBankSymbol(std::string const &symName) {
|
||||
clear();
|
||||
if (Symbol const *sym = sym_FindScopedSymbol(symName); sym_IsPC(sym)) {
|
||||
// The @ symbol is treated differently.
|
||||
if (!currentSection) {
|
||||
error("PC has no bank outside of a section\n");
|
||||
if (std::optional<uint32_t> outputBank = sect_GetOutputBank(); !outputBank) {
|
||||
error("PC has no bank outside of a section");
|
||||
data = 1;
|
||||
} else if (currentSection->bank == UINT32_MAX) {
|
||||
} else if (*outputBank == UINT32_MAX) {
|
||||
data = "Current section's bank is not known";
|
||||
|
||||
*reserveSpace(1) = RPN_BANK_SELF;
|
||||
} else {
|
||||
data = static_cast<int32_t>(currentSection->bank);
|
||||
data = static_cast<int32_t>(*outputBank);
|
||||
}
|
||||
return;
|
||||
} else if (sym && !sym->isLabel()) {
|
||||
error("BANK argument must be a label\n");
|
||||
error("BANK argument must be a label");
|
||||
data = 1;
|
||||
} else {
|
||||
sym = sym_Ref(symName);
|
||||
@@ -322,40 +326,12 @@ void Expression::makeUnaryOp(RPNCommand op, Expression &&src) {
|
||||
case RPN_TZCOUNT:
|
||||
data = val != 0 ? ctz(uval) : 32;
|
||||
break;
|
||||
|
||||
case RPN_LOGOR:
|
||||
case RPN_LOGAND:
|
||||
case RPN_LOGEQ:
|
||||
case RPN_LOGGT:
|
||||
case RPN_LOGLT:
|
||||
case RPN_LOGGE:
|
||||
case RPN_LOGLE:
|
||||
case RPN_LOGNE:
|
||||
case RPN_ADD:
|
||||
case RPN_SUB:
|
||||
case RPN_XOR:
|
||||
case RPN_OR:
|
||||
case RPN_AND:
|
||||
case RPN_SHL:
|
||||
case RPN_SHR:
|
||||
case RPN_USHR:
|
||||
case RPN_MUL:
|
||||
case RPN_DIV:
|
||||
case RPN_MOD:
|
||||
case RPN_EXP:
|
||||
case RPN_BANK_SYM:
|
||||
case RPN_BANK_SECT:
|
||||
case RPN_BANK_SELF:
|
||||
case RPN_SIZEOF_SECT:
|
||||
case RPN_STARTOF_SECT:
|
||||
case RPN_SIZEOF_SECTTYPE:
|
||||
case RPN_STARTOF_SECTTYPE:
|
||||
case RPN_HRAM:
|
||||
case RPN_RST:
|
||||
case RPN_CONST:
|
||||
case RPN_SYM:
|
||||
fatalerror("%d is not an unary operator\n", op);
|
||||
// LCOV_EXCL_START
|
||||
default:
|
||||
// `makeUnaryOp` should never be called with a non-unary operator!
|
||||
unreachable_();
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
} else if (op == RPN_LOGNOT && tryConstLogNot(src)) {
|
||||
data = 0;
|
||||
} else if (int32_t constVal; op == RPN_LOW && (constVal = tryConstLow(src)) != -1) {
|
||||
@@ -419,43 +395,37 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const
|
||||
break;
|
||||
case RPN_SHL:
|
||||
if (rval < 0) {
|
||||
warning(
|
||||
WARNING_SHIFT_AMOUNT, "Shifting left by negative amount %" PRId32 "\n", rval
|
||||
);
|
||||
warning(WARNING_SHIFT_AMOUNT, "Shifting left by negative amount %" PRId32, rval);
|
||||
}
|
||||
|
||||
if (rval >= 32) {
|
||||
warning(WARNING_SHIFT_AMOUNT, "Shifting left by large amount %" PRId32 "\n", rval);
|
||||
warning(WARNING_SHIFT_AMOUNT, "Shifting left by large amount %" PRId32, rval);
|
||||
}
|
||||
|
||||
data = op_shift_left(lval, rval);
|
||||
break;
|
||||
case RPN_SHR:
|
||||
if (lval < 0) {
|
||||
warning(WARNING_SHIFT, "Shifting right negative value %" PRId32 "\n", lval);
|
||||
warning(WARNING_SHIFT, "Shifting right negative value %" PRId32, lval);
|
||||
}
|
||||
|
||||
if (rval < 0) {
|
||||
warning(
|
||||
WARNING_SHIFT_AMOUNT, "Shifting right by negative amount %" PRId32 "\n", rval
|
||||
);
|
||||
warning(WARNING_SHIFT_AMOUNT, "Shifting right by negative amount %" PRId32, rval);
|
||||
}
|
||||
|
||||
if (rval >= 32) {
|
||||
warning(WARNING_SHIFT_AMOUNT, "Shifting right by large amount %" PRId32 "\n", rval);
|
||||
warning(WARNING_SHIFT_AMOUNT, "Shifting right by large amount %" PRId32, rval);
|
||||
}
|
||||
|
||||
data = op_shift_right(lval, rval);
|
||||
break;
|
||||
case RPN_USHR:
|
||||
if (rval < 0) {
|
||||
warning(
|
||||
WARNING_SHIFT_AMOUNT, "Shifting right by negative amount %" PRId32 "\n", rval
|
||||
);
|
||||
warning(WARNING_SHIFT_AMOUNT, "Shifting right by negative amount %" PRId32, rval);
|
||||
}
|
||||
|
||||
if (rval >= 32) {
|
||||
warning(WARNING_SHIFT_AMOUNT, "Shifting right by large amount %" PRId32 "\n", rval);
|
||||
warning(WARNING_SHIFT_AMOUNT, "Shifting right by large amount %" PRId32, rval);
|
||||
}
|
||||
|
||||
data = op_shift_right_unsigned(lval, rval);
|
||||
@@ -465,13 +435,13 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const
|
||||
break;
|
||||
case RPN_DIV:
|
||||
if (rval == 0) {
|
||||
fatalerror("Division by zero\n");
|
||||
fatal("Division by zero");
|
||||
}
|
||||
|
||||
if (lval == INT32_MIN && rval == -1) {
|
||||
warning(
|
||||
WARNING_DIV,
|
||||
"Division of %" PRId32 " by -1 yields %" PRId32 "\n",
|
||||
"Division of %" PRId32 " by -1 yields %" PRId32,
|
||||
INT32_MIN,
|
||||
INT32_MIN
|
||||
);
|
||||
@@ -482,7 +452,7 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const
|
||||
break;
|
||||
case RPN_MOD:
|
||||
if (rval == 0) {
|
||||
fatalerror("Modulo by zero\n");
|
||||
fatal("Modulo by zero");
|
||||
}
|
||||
|
||||
if (lval == INT32_MIN && rval == -1) {
|
||||
@@ -493,32 +463,17 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const
|
||||
break;
|
||||
case RPN_EXP:
|
||||
if (rval < 0) {
|
||||
fatalerror("Exponentiation by negative power\n");
|
||||
fatal("Exponentiation by negative power");
|
||||
}
|
||||
|
||||
data = op_exponent(lval, rval);
|
||||
break;
|
||||
|
||||
case RPN_NEG:
|
||||
case RPN_NOT:
|
||||
case RPN_LOGNOT:
|
||||
case RPN_BANK_SYM:
|
||||
case RPN_BANK_SECT:
|
||||
case RPN_BANK_SELF:
|
||||
case RPN_SIZEOF_SECT:
|
||||
case RPN_STARTOF_SECT:
|
||||
case RPN_SIZEOF_SECTTYPE:
|
||||
case RPN_STARTOF_SECTTYPE:
|
||||
case RPN_HRAM:
|
||||
case RPN_RST:
|
||||
case RPN_HIGH:
|
||||
case RPN_LOW:
|
||||
case RPN_BITWIDTH:
|
||||
case RPN_TZCOUNT:
|
||||
case RPN_CONST:
|
||||
case RPN_SYM:
|
||||
fatalerror("%d is not a binary operator\n", op);
|
||||
// LCOV_EXCL_START
|
||||
default:
|
||||
// `makeBinaryOp` should never be called with a non-binary operator!
|
||||
unreachable_();
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
} else if (op == RPN_SUB && src1.isDiffConstant(src2.symbolOf())) {
|
||||
data = src1.symbolOf()->getValue() - src2.symbolOf()->getValue();
|
||||
} else if ((op == RPN_LOGAND || op == RPN_AND) && tryConstZero(src1, src2)) {
|
||||
@@ -591,7 +546,7 @@ bool Expression::makeCheckHRAM() {
|
||||
// That range is valid, but deprecated
|
||||
return true;
|
||||
} else {
|
||||
error("Source address $%" PRIx32 " not between $FF00 to $FFFF\n", val);
|
||||
error("Source address $%" PRIx32 " not between $FF00 to $FFFF", val);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -601,14 +556,28 @@ void Expression::makeCheckRST() {
|
||||
*reserveSpace(1) = RPN_RST;
|
||||
} else if (int32_t val = value(); val & ~0x38) {
|
||||
// A valid RST address must be masked with 0x38
|
||||
error("Invalid address $%" PRIx32 " for RST\n", val);
|
||||
error("Invalid address $%" PRIx32 " for RST", val);
|
||||
}
|
||||
}
|
||||
|
||||
void Expression::makeCheckBitIndex(uint8_t mask) {
|
||||
assume((mask & 0xC0) != 0x00); // The high two bits must correspond to BIT, RES, or SET
|
||||
|
||||
if (!isKnown()) {
|
||||
uint8_t *ptr = reserveSpace(2);
|
||||
*ptr++ = RPN_BIT_INDEX;
|
||||
*ptr = mask;
|
||||
} else if (int32_t val = value(); val & ~0x07) {
|
||||
// A valid bit index must be masked with 0x07
|
||||
static char const *instructions[4] = {"instruction", "BIT", "RES", "SET"};
|
||||
error("Invalid bit index %" PRId32 " for %s", val, instructions[mask >> 6]);
|
||||
}
|
||||
}
|
||||
|
||||
// Checks that an RPN expression's value fits within N bits (signed or unsigned)
|
||||
void Expression::checkNBit(uint8_t n) const {
|
||||
if (isKnown()) {
|
||||
::checkNBit(value(), n, "Expression");
|
||||
::checkNBit(value(), n, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -617,11 +586,23 @@ bool checkNBit(int32_t v, uint8_t n, char const *name) {
|
||||
assume(n < CHAR_BIT * sizeof(int)); // Otherwise `1 << n` is UB
|
||||
|
||||
if (v < -(1 << n) || v >= 1 << n) {
|
||||
warning(WARNING_TRUNCATION_1, "%s must be %u-bit\n", name, n);
|
||||
warning(
|
||||
WARNING_TRUNCATION_1,
|
||||
"%s must be %u-bit%s",
|
||||
name ? name : "Expression",
|
||||
n,
|
||||
n == 8 && !name ? "; use LOW() to force 8-bit" : ""
|
||||
);
|
||||
return false;
|
||||
}
|
||||
if (v < -(1 << (n - 1))) {
|
||||
warning(WARNING_TRUNCATION_2, "%s must be %u-bit\n", name, n);
|
||||
warning(
|
||||
WARNING_TRUNCATION_2,
|
||||
"%s must be %u-bit%s",
|
||||
name ? name : "Expression",
|
||||
n,
|
||||
n == 8 && !name ? "; use LOW() to force 8-bit" : ""
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#include <algorithm>
|
||||
#include <errno.h>
|
||||
#include <inttypes.h>
|
||||
#include <optional>
|
||||
#include <stack>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@@ -22,7 +21,7 @@
|
||||
#include "asm/symbol.hpp"
|
||||
#include "asm/warning.hpp"
|
||||
|
||||
uint8_t fillByte;
|
||||
using namespace std::literals;
|
||||
|
||||
struct UnionStackEntry {
|
||||
uint32_t start;
|
||||
@@ -38,29 +37,30 @@ struct SectionStackEntry {
|
||||
std::stack<UnionStackEntry> unionStack;
|
||||
};
|
||||
|
||||
std::stack<UnionStackEntry> currentUnionStack;
|
||||
std::deque<SectionStackEntry> sectionStack;
|
||||
std::deque<Section> sectionList;
|
||||
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 *currentSection = nullptr;
|
||||
static std::deque<Section> sectionList;
|
||||
static std::unordered_map<std::string, size_t> sectionMap; // Indexes into `sectionList`
|
||||
|
||||
static uint32_t curOffset; // Offset into the current section (see `sect_GetSymbolOffset`)
|
||||
|
||||
static std::deque<SectionStackEntry> sectionStack;
|
||||
|
||||
static Section *currentLoadSection = nullptr;
|
||||
static std::pair<Symbol const *, Symbol const *> currentLoadLabelScopes = {nullptr, nullptr};
|
||||
int32_t loadOffset; // Offset into the LOAD section's parent (see sect_GetOutputOffset)
|
||||
static int32_t loadOffset; // Offset into the LOAD section's parent (see sect_GetOutputOffset)
|
||||
|
||||
static std::stack<UnionStackEntry> currentUnionStack;
|
||||
|
||||
// A quick check to see if we have an initialized section
|
||||
[[nodiscard]]
|
||||
static bool requireSection() {
|
||||
if (currentSection) {
|
||||
return true;
|
||||
}
|
||||
|
||||
error("Cannot output data outside of a SECTION\n");
|
||||
error("Cannot output data outside of a SECTION");
|
||||
return false;
|
||||
}
|
||||
|
||||
// A quick check to see if we have an initialized section that can contain
|
||||
// this much initialized data
|
||||
[[nodiscard]]
|
||||
static bool requireCodeSection() {
|
||||
if (!requireSection()) {
|
||||
@@ -72,18 +72,26 @@ static bool requireCodeSection() {
|
||||
}
|
||||
|
||||
error(
|
||||
"Section '%s' cannot contain code or data (not ROM0 or ROMX)\n",
|
||||
currentSection->name.c_str()
|
||||
"Section '%s' cannot contain code or data (not ROM0 or ROMX)", currentSection->name.c_str()
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t sect_CountSections() {
|
||||
return sectionList.size();
|
||||
}
|
||||
|
||||
void sect_ForEach(void (*callback)(Section &)) {
|
||||
for (Section § : sectionList) {
|
||||
callback(sect);
|
||||
}
|
||||
}
|
||||
|
||||
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",
|
||||
"Section '%s' grew too big (max size = 0x%" PRIX32 " bytes, reached 0x%" PRIX32 ")",
|
||||
sect.name.c_str(),
|
||||
maxSize,
|
||||
sect.size
|
||||
@@ -101,7 +109,7 @@ Section *sect_FindSectionByName(std::string const &name) {
|
||||
#define sectError(...) \
|
||||
do { \
|
||||
error(__VA_ARGS__); \
|
||||
nbSectErrors++; \
|
||||
++nbSectErrors; \
|
||||
} while (0)
|
||||
|
||||
static unsigned int mergeSectUnion(
|
||||
@@ -113,18 +121,18 @@ static unsigned int mergeSectUnion(
|
||||
// Unionized sections only need "compatible" constraints, and they end up with the strictest
|
||||
// combination of both.
|
||||
if (sect_HasData(type)) {
|
||||
sectError("Cannot declare ROM sections as UNION\n");
|
||||
sectError("Cannot declare ROM sections as UNION");
|
||||
}
|
||||
|
||||
if (org != UINT32_MAX) {
|
||||
// If both are fixed, they must be the same
|
||||
if (sect.org != UINT32_MAX && sect.org != org) {
|
||||
sectError(
|
||||
"Section already declared as fixed at different address $%04" PRIx32 "\n", sect.org
|
||||
"Section already declared as fixed at different address $%04" PRIx32, sect.org
|
||||
);
|
||||
} else if (sect.align != 0 && (mask(sect.align) & (org - sect.alignOfs))) {
|
||||
sectError(
|
||||
"Section already declared as aligned to %u bytes (offset %" PRIu16 ")\n",
|
||||
"Section already declared as aligned to %u bytes (offset %" PRIu16 ")",
|
||||
1U << sect.align,
|
||||
sect.alignOfs
|
||||
);
|
||||
@@ -138,15 +146,14 @@ static unsigned int mergeSectUnion(
|
||||
if (sect.org != UINT32_MAX) {
|
||||
if ((sect.org - alignOffset) & mask(alignment)) {
|
||||
sectError(
|
||||
"Section already declared as fixed at incompatible address $%04" PRIx32 "\n",
|
||||
"Section already declared as fixed at incompatible address $%04" PRIx32,
|
||||
sect.org
|
||||
);
|
||||
}
|
||||
// Check if alignment offsets are compatible
|
||||
} else if ((alignOffset & mask(sect.align)) != (sect.alignOfs & mask(alignment))) {
|
||||
sectError(
|
||||
"Section already declared with incompatible %u"
|
||||
"-byte alignment (offset %" PRIu16 ")\n",
|
||||
"Section already declared with incompatible %u-byte alignment (offset %" PRIu16 ")",
|
||||
1U << sect.align,
|
||||
sect.alignOfs
|
||||
);
|
||||
@@ -174,12 +181,11 @@ static unsigned int
|
||||
// If both are fixed, they must be the same
|
||||
if (sect.org != UINT32_MAX && sect.org != curOrg) {
|
||||
sectError(
|
||||
"Section already declared as fixed at incompatible address $%04" PRIx32 "\n",
|
||||
sect.org
|
||||
"Section already declared as fixed at incompatible address $%04" PRIx32, sect.org
|
||||
);
|
||||
} else if (sect.align != 0 && (mask(sect.align) & (curOrg - sect.alignOfs))) {
|
||||
sectError(
|
||||
"Section already declared as aligned to %u bytes (offset %" PRIu16 ")\n",
|
||||
"Section already declared as aligned to %u bytes (offset %" PRIu16 ")",
|
||||
1U << sect.align,
|
||||
sect.alignOfs
|
||||
);
|
||||
@@ -199,15 +205,14 @@ static unsigned int
|
||||
if (sect.org != UINT32_MAX) {
|
||||
if ((sect.org - curOfs) & mask(alignment)) {
|
||||
sectError(
|
||||
"Section already declared as fixed at incompatible address $%04" PRIx32 "\n",
|
||||
"Section already declared as fixed at incompatible address $%04" PRIx32,
|
||||
sect.org
|
||||
);
|
||||
}
|
||||
// Check if alignment offsets are compatible
|
||||
} else if ((curOfs & mask(sect.align)) != (sect.alignOfs & mask(alignment))) {
|
||||
sectError(
|
||||
"Section already declared with incompatible %u"
|
||||
"-byte alignment (offset %" PRIu16 ")\n",
|
||||
"Section already declared with incompatible %u-byte alignment (offset %" PRIu16 ")",
|
||||
1U << sect.align,
|
||||
sect.alignOfs
|
||||
);
|
||||
@@ -234,12 +239,12 @@ static void mergeSections(
|
||||
|
||||
if (type != sect.type) {
|
||||
sectError(
|
||||
"Section already exists but with type %s\n", sectionTypeInfo[sect.type].name.c_str()
|
||||
"Section already exists but with type %s", sectionTypeInfo[sect.type].name.c_str()
|
||||
);
|
||||
}
|
||||
|
||||
if (sect.modifier != mod) {
|
||||
sectError("Section already declared as SECTION %s\n", sectionModNames[sect.modifier]);
|
||||
sectError("Section already declared as SECTION %s", sectionModNames[sect.modifier]);
|
||||
} else {
|
||||
switch (mod) {
|
||||
case SECTION_UNION:
|
||||
@@ -256,21 +261,22 @@ static void mergeSections(
|
||||
}
|
||||
// If both specify a bank, it must be the same one
|
||||
else if (bank != UINT32_MAX && sect.bank != bank) {
|
||||
sectError("Section already declared with different bank %" PRIu32 "\n", sect.bank);
|
||||
sectError("Section already declared with different bank %" PRIu32, sect.bank);
|
||||
}
|
||||
break;
|
||||
|
||||
case SECTION_NORMAL:
|
||||
sectError("Section already defined previously at ");
|
||||
sect.src->dump(sect.fileLine);
|
||||
putc('\n', stderr);
|
||||
sectError([&]() {
|
||||
fputs("Section already defined previously at ", stderr);
|
||||
sect.src->dump(sect.fileLine);
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (nbSectErrors) {
|
||||
fatalerror(
|
||||
"Cannot create section \"%s\" (%u error%s)\n",
|
||||
fatal(
|
||||
"Cannot create section \"%s\" (%u error%s)",
|
||||
sect.name.c_str(),
|
||||
nbSectErrors,
|
||||
nbSectErrors == 1 ? "" : "s"
|
||||
@@ -280,7 +286,6 @@ static void mergeSections(
|
||||
|
||||
#undef sectError
|
||||
|
||||
// Create a new section, not yet in the list.
|
||||
static Section *createSection(
|
||||
std::string const &name,
|
||||
SectionType type,
|
||||
@@ -315,7 +320,31 @@ static Section *createSection(
|
||||
return §
|
||||
}
|
||||
|
||||
// Find a section by name and type. If it doesn't exist, create it.
|
||||
static Section *createSectionFragmentLiteral(Section const &parent) {
|
||||
// Add the new section to the list, but do not update the map
|
||||
Section § = sectionList.emplace_back();
|
||||
assume(sectionMap.find(parent.name) != sectionMap.end());
|
||||
|
||||
sect.name = parent.name;
|
||||
sect.type = parent.type;
|
||||
sect.modifier = SECTION_FRAGMENT;
|
||||
sect.src = fstk_GetFileStack();
|
||||
sect.fileLine = lexer_GetLineNo();
|
||||
sect.size = 0;
|
||||
sect.org = UINT32_MAX;
|
||||
sect.bank = parent.bank == 0 ? UINT32_MAX : parent.bank;
|
||||
sect.align = 0;
|
||||
sect.alignOfs = 0;
|
||||
|
||||
out_RegisterNode(sect.src);
|
||||
|
||||
// Section fragment literals must be ROM sections.
|
||||
assume(sect_HasData(sect.type));
|
||||
sect.data.resize(sectionTypeInfo[sect.type].size);
|
||||
|
||||
return §
|
||||
}
|
||||
|
||||
static Section *getSection(
|
||||
std::string const &name,
|
||||
SectionType type,
|
||||
@@ -332,11 +361,11 @@ static Section *getSection(
|
||||
if (bank != UINT32_MAX) {
|
||||
if (type != SECTTYPE_ROMX && type != SECTTYPE_VRAM && type != SECTTYPE_SRAM
|
||||
&& type != SECTTYPE_WRAMX) {
|
||||
error("BANK only allowed for ROMX, WRAMX, SRAM, or VRAM sections\n");
|
||||
error("BANK only allowed for ROMX, WRAMX, SRAM, or VRAM sections");
|
||||
} else if (bank < sectionTypeInfo[type].firstBank
|
||||
|| bank > sectionTypeInfo[type].lastBank) {
|
||||
error(
|
||||
"%s bank value $%04" PRIx32 " out of range ($%04" PRIx32 " to $%04" PRIx32 ")\n",
|
||||
"%s bank value $%04" PRIx32 " out of range ($%04" PRIx32 " to $%04" PRIx32 ")",
|
||||
sectionTypeInfo[type].name.c_str(),
|
||||
bank,
|
||||
sectionTypeInfo[type].firstBank,
|
||||
@@ -350,7 +379,7 @@ static Section *getSection(
|
||||
|
||||
if (alignOffset >= 1 << alignment) {
|
||||
error(
|
||||
"Alignment offset (%" PRIu16 ") must be smaller than alignment size (%u)\n",
|
||||
"Alignment offset (%" PRIu16 ") must be smaller than alignment size (%u)",
|
||||
alignOffset,
|
||||
1U << alignment
|
||||
);
|
||||
@@ -361,7 +390,7 @@ static Section *getSection(
|
||||
if (org < sectionTypeInfo[type].startAddr || org > endaddr(type)) {
|
||||
error(
|
||||
"Section \"%s\"'s fixed address $%04" PRIx32 " is outside of range [$%04" PRIx16
|
||||
"; $%04" PRIx16 "]\n",
|
||||
"; $%04" PRIx16 "]",
|
||||
name.c_str(),
|
||||
org,
|
||||
sectionTypeInfo[type].startAddr,
|
||||
@@ -372,7 +401,7 @@ static Section *getSection(
|
||||
|
||||
if (alignment != 0) {
|
||||
if (alignment > 16) {
|
||||
error("Alignment must be between 0 and 16, not %u\n", alignment);
|
||||
error("Alignment must be between 0 and 16, not %u", alignment);
|
||||
alignment = 16;
|
||||
}
|
||||
// It doesn't make sense to have both alignment and org set
|
||||
@@ -380,12 +409,12 @@ static Section *getSection(
|
||||
|
||||
if (org != UINT32_MAX) {
|
||||
if ((org - alignOffset) & mask) {
|
||||
error("Section \"%s\"'s fixed address doesn't match its alignment\n", name.c_str());
|
||||
error("Section \"%s\"'s fixed address doesn't match its alignment", name.c_str());
|
||||
}
|
||||
alignment = 0; // Ignore it if it's satisfied
|
||||
} else if (sectionTypeInfo[type].startAddr & mask) {
|
||||
error(
|
||||
"Section \"%s\"'s alignment cannot be attained in %s\n",
|
||||
"Section \"%s\"'s alignment cannot be attained in %s",
|
||||
name.c_str(),
|
||||
sectionTypeInfo[type].name.c_str()
|
||||
);
|
||||
@@ -412,15 +441,24 @@ static Section *getSection(
|
||||
return sect;
|
||||
}
|
||||
|
||||
// Set the current section
|
||||
static void changeSection() {
|
||||
if (!currentUnionStack.empty()) {
|
||||
fatalerror("Cannot change the section within a UNION\n");
|
||||
fatal("Cannot change the section within a UNION");
|
||||
}
|
||||
|
||||
sym_ResetCurrentLabelScopes();
|
||||
}
|
||||
|
||||
uint32_t Section::getID() const {
|
||||
// Section fragments share the same name but have different IDs, so search by identity
|
||||
if (auto search =
|
||||
std::find_if(RANGE(sectionList), [this](Section const &s) { return &s == this; });
|
||||
search != sectionList.end()) {
|
||||
return static_cast<uint32_t>(std::distance(sectionList.begin(), search));
|
||||
}
|
||||
return UINT32_MAX; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
bool Section::isSizeKnown() const {
|
||||
// SECTION UNION and SECTION FRAGMENT can still grow
|
||||
if (modifier != SECTION_NORMAL) {
|
||||
@@ -442,7 +480,6 @@ bool Section::isSizeKnown() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Set the current section by name and type
|
||||
void sect_NewSection(
|
||||
std::string const &name,
|
||||
SectionType type,
|
||||
@@ -452,7 +489,7 @@ void sect_NewSection(
|
||||
) {
|
||||
for (SectionStackEntry &entry : sectionStack) {
|
||||
if (entry.section && entry.section->name == name) {
|
||||
fatalerror("Section '%s' is already on the stack\n", name.c_str());
|
||||
fatal("Section '%s' is already on the stack", name.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -468,7 +505,6 @@ void sect_NewSection(
|
||||
currentSection = sect;
|
||||
}
|
||||
|
||||
// Set the current section by name and type
|
||||
void sect_SetLoadSection(
|
||||
std::string const &name,
|
||||
SectionType type,
|
||||
@@ -486,7 +522,7 @@ void sect_SetLoadSection(
|
||||
}
|
||||
|
||||
if (sect_HasData(type)) {
|
||||
error("`LOAD` blocks cannot create a ROM section\n");
|
||||
error("`LOAD` blocks cannot create a ROM section");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -505,13 +541,11 @@ void sect_SetLoadSection(
|
||||
|
||||
void sect_EndLoadSection(char const *cause) {
|
||||
if (cause) {
|
||||
warning(
|
||||
WARNING_UNTERMINATED_LOAD, "`LOAD` block without `ENDL` terminated by `%s`\n", cause
|
||||
);
|
||||
warning(WARNING_UNTERMINATED_LOAD, "`LOAD` block without `ENDL` terminated by `%s`", cause);
|
||||
}
|
||||
|
||||
if (!currentLoadSection) {
|
||||
error("Found `ENDL` outside of a `LOAD` block\n");
|
||||
error("Found `ENDL` outside of a `LOAD` block");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -524,7 +558,7 @@ void sect_EndLoadSection(char const *cause) {
|
||||
|
||||
void sect_CheckLoadClosed() {
|
||||
if (currentLoadSection) {
|
||||
warning(WARNING_UNTERMINATED_LOAD, "`LOAD` block without `ENDL` terminated by EOF\n");
|
||||
warning(WARNING_UNTERMINATED_LOAD, "`LOAD` block without `ENDL` terminated by EOF");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -532,7 +566,6 @@ Section *sect_GetSymbolSection() {
|
||||
return currentLoadSection ? currentLoadSection : currentSection;
|
||||
}
|
||||
|
||||
// The offset into the section above
|
||||
uint32_t sect_GetSymbolOffset() {
|
||||
return curOffset;
|
||||
}
|
||||
@@ -541,6 +574,14 @@ uint32_t sect_GetOutputOffset() {
|
||||
return curOffset + loadOffset;
|
||||
}
|
||||
|
||||
std::optional<uint32_t> sect_GetOutputBank() {
|
||||
return currentSection ? std::optional<uint32_t>(currentSection->bank) : std::nullopt;
|
||||
}
|
||||
|
||||
Patch *sect_AddOutputPatch() {
|
||||
return currentSection ? ¤tSection->patches.emplace_front() : nullptr;
|
||||
}
|
||||
|
||||
// Returns how many bytes need outputting for the specified alignment and offset to succeed
|
||||
uint32_t sect_GetAlignBytes(uint8_t alignment, uint16_t offset) {
|
||||
Section *sect = sect_GetSymbolSection();
|
||||
@@ -572,37 +613,50 @@ void sect_AlignPC(uint8_t alignment, uint16_t offset) {
|
||||
uint32_t alignSize = 1 << alignment; // Size of an aligned "block"
|
||||
|
||||
if (sect->org != UINT32_MAX) {
|
||||
if ((sect->org + curOffset - offset) % alignSize) {
|
||||
if (uint32_t actualOffset = (sect->org + curOffset) % alignSize; actualOffset != offset) {
|
||||
error(
|
||||
"Section's fixed address fails required alignment (PC = $%04" PRIx32 ")\n",
|
||||
sect->org + curOffset
|
||||
"Section is misaligned (at PC = $%04" PRIx32 ", expected ALIGN[%" PRIu32
|
||||
", %" PRIu32 "], got ALIGN[%" PRIu32 ", %" PRIu32 "])",
|
||||
sect->org + curOffset,
|
||||
alignment,
|
||||
offset,
|
||||
alignment,
|
||||
actualOffset
|
||||
);
|
||||
}
|
||||
} 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",
|
||||
curOffset
|
||||
);
|
||||
} else if (alignment >= 16) {
|
||||
// Treat an alignment large enough as fixing the address.
|
||||
// Note that this also ensures that a section's alignment never becomes 16 or greater.
|
||||
if (alignment > 16) {
|
||||
error("Alignment must be between 0 and 16, not %u\n", alignment);
|
||||
} else {
|
||||
if (uint32_t actualOffset = (sect->alignOfs + curOffset) % alignSize,
|
||||
sectAlignSize = 1 << sect->align;
|
||||
sect->align != 0 && actualOffset % sectAlignSize != offset % sectAlignSize) {
|
||||
error(
|
||||
"Section is misaligned ($%04" PRIx32
|
||||
" bytes into the section, expected ALIGN[%" PRIu32 ", %" PRIu32
|
||||
"], got ALIGN[%" PRIu32 ", %" PRIu32 "])",
|
||||
curOffset,
|
||||
alignment,
|
||||
offset,
|
||||
alignment,
|
||||
actualOffset
|
||||
);
|
||||
} else if (alignment >= 16) {
|
||||
// Treat an alignment large enough as fixing the address.
|
||||
// Note that this also ensures that a section's alignment never becomes 16 or greater.
|
||||
if (alignment > 16) {
|
||||
error("Alignment must be between 0 and 16, not %u", alignment);
|
||||
}
|
||||
sect->align = 0; // Reset the alignment, since we're fixing the address.
|
||||
sect->org = offset - curOffset;
|
||||
} else if (alignment > sect->align) {
|
||||
sect->align = alignment;
|
||||
// We need `(sect->alignOfs + curOffset) % alignSize == offset`
|
||||
sect->alignOfs = (offset - curOffset) % alignSize;
|
||||
}
|
||||
sect->align = 0; // Reset the alignment, since we're fixing the address.
|
||||
sect->org = offset - curOffset;
|
||||
} else if (alignment > sect->align) {
|
||||
sect->align = alignment;
|
||||
// We need `(sect->alignOfs + curOffset) % alignSize == offset`
|
||||
sect->alignOfs = (offset - curOffset) % alignSize;
|
||||
}
|
||||
}
|
||||
|
||||
static void growSection(uint32_t growth) {
|
||||
if (growth > 0 && curOffset > UINT32_MAX - growth) {
|
||||
fatalerror("Section size would overflow internal counter\n");
|
||||
fatal("Section size would overflow internal counter");
|
||||
}
|
||||
curOffset += growth;
|
||||
if (uint32_t outOffset = sect_GetOutputOffset(); outOffset > currentSection->size) {
|
||||
@@ -643,11 +697,11 @@ void sect_StartUnion() {
|
||||
// your own peril! ^^
|
||||
|
||||
if (!currentSection) {
|
||||
error("UNIONs must be inside a SECTION\n");
|
||||
error("UNIONs must be inside a SECTION");
|
||||
return;
|
||||
}
|
||||
if (sect_HasData(currentSection->type)) {
|
||||
error("Cannot use UNION inside of ROM0 or ROMX sections\n");
|
||||
error("Cannot use UNION inside of ROM0 or ROMX sections");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -666,7 +720,7 @@ static void endUnionMember() {
|
||||
|
||||
void sect_NextUnionMember() {
|
||||
if (currentUnionStack.empty()) {
|
||||
error("Found NEXTU outside of a UNION construct\n");
|
||||
error("Found NEXTU outside of a UNION construct");
|
||||
return;
|
||||
}
|
||||
endUnionMember();
|
||||
@@ -674,7 +728,7 @@ void sect_NextUnionMember() {
|
||||
|
||||
void sect_EndUnion() {
|
||||
if (currentUnionStack.empty()) {
|
||||
error("Found ENDU outside of a UNION construct\n");
|
||||
error("Found ENDU outside of a UNION construct");
|
||||
return;
|
||||
}
|
||||
endUnionMember();
|
||||
@@ -684,11 +738,10 @@ void sect_EndUnion() {
|
||||
|
||||
void sect_CheckUnionClosed() {
|
||||
if (!currentUnionStack.empty()) {
|
||||
error("Unterminated UNION construct\n");
|
||||
error("Unterminated UNION construct");
|
||||
}
|
||||
}
|
||||
|
||||
// Output a constant byte
|
||||
void sect_ConstByte(uint8_t byte) {
|
||||
if (!requireCodeSection()) {
|
||||
return;
|
||||
@@ -697,52 +750,48 @@ void sect_ConstByte(uint8_t byte) {
|
||||
writeByte(byte);
|
||||
}
|
||||
|
||||
// Output a string's character units as bytes
|
||||
void sect_ByteString(std::vector<int32_t> const &string) {
|
||||
void sect_ByteString(std::vector<int32_t> const &str) {
|
||||
if (!requireCodeSection()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int32_t unit : string) {
|
||||
for (int32_t unit : str) {
|
||||
if (!checkNBit(unit, 8, "All character units")) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (int32_t unit : string) {
|
||||
for (int32_t unit : str) {
|
||||
writeByte(static_cast<uint8_t>(unit));
|
||||
}
|
||||
}
|
||||
|
||||
// Output a string's character units as words
|
||||
void sect_WordString(std::vector<int32_t> const &string) {
|
||||
void sect_WordString(std::vector<int32_t> const &str) {
|
||||
if (!requireCodeSection()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int32_t unit : string) {
|
||||
for (int32_t unit : str) {
|
||||
if (!checkNBit(unit, 16, "All character units")) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (int32_t unit : string) {
|
||||
for (int32_t unit : str) {
|
||||
writeWord(static_cast<uint16_t>(unit));
|
||||
}
|
||||
}
|
||||
|
||||
// Output a string's character units as longs
|
||||
void sect_LongString(std::vector<int32_t> const &string) {
|
||||
void sect_LongString(std::vector<int32_t> const &str) {
|
||||
if (!requireCodeSection()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int32_t unit : string) {
|
||||
for (int32_t unit : str) {
|
||||
writeLong(static_cast<uint32_t>(unit));
|
||||
}
|
||||
}
|
||||
|
||||
// Skip this many bytes
|
||||
void sect_Skip(uint32_t skip, bool ds) {
|
||||
if (!requireSection()) {
|
||||
return;
|
||||
@@ -754,7 +803,7 @@ void sect_Skip(uint32_t skip, bool ds) {
|
||||
if (!ds) {
|
||||
warning(
|
||||
WARNING_EMPTY_DATA_DIRECTIVE,
|
||||
"%s directive without data in ROM\n",
|
||||
"%s directive without data in ROM",
|
||||
(skip == 4) ? "DL"
|
||||
: (skip == 2) ? "DW"
|
||||
: "DB"
|
||||
@@ -762,13 +811,12 @@ void sect_Skip(uint32_t skip, bool ds) {
|
||||
}
|
||||
// We know we're in a code SECTION
|
||||
while (skip--) {
|
||||
writeByte(fillByte);
|
||||
writeByte(options.padByte);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Output a byte that can be relocatable or constant
|
||||
void sect_RelByte(Expression &expr, uint32_t pcShift) {
|
||||
void sect_RelByte(Expression const &expr, uint32_t pcShift) {
|
||||
if (!requireCodeSection()) {
|
||||
return;
|
||||
}
|
||||
@@ -781,16 +829,13 @@ void sect_RelByte(Expression &expr, uint32_t pcShift) {
|
||||
}
|
||||
}
|
||||
|
||||
// Output several bytes that can be relocatable or constant
|
||||
void sect_RelBytes(uint32_t n, std::vector<Expression> &exprs) {
|
||||
void sect_RelBytes(uint32_t n, std::vector<Expression> const &exprs) {
|
||||
if (!requireCodeSection()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < n; i++) {
|
||||
Expression &expr = exprs[i % exprs.size()];
|
||||
|
||||
if (!expr.isKnown()) {
|
||||
for (uint32_t i = 0; i < n; ++i) {
|
||||
if (Expression const &expr = exprs[i % exprs.size()]; !expr.isKnown()) {
|
||||
createPatch(PATCHTYPE_BYTE, expr, i);
|
||||
writeByte(0);
|
||||
} else {
|
||||
@@ -799,8 +844,7 @@ void sect_RelBytes(uint32_t n, std::vector<Expression> &exprs) {
|
||||
}
|
||||
}
|
||||
|
||||
// Output a word that can be relocatable or constant
|
||||
void sect_RelWord(Expression &expr, uint32_t pcShift) {
|
||||
void sect_RelWord(Expression const &expr, uint32_t pcShift) {
|
||||
if (!requireCodeSection()) {
|
||||
return;
|
||||
}
|
||||
@@ -813,8 +857,7 @@ void sect_RelWord(Expression &expr, uint32_t pcShift) {
|
||||
}
|
||||
}
|
||||
|
||||
// Output a long that can be relocatable or constant
|
||||
void sect_RelLong(Expression &expr, uint32_t pcShift) {
|
||||
void sect_RelLong(Expression const &expr, uint32_t pcShift) {
|
||||
if (!requireCodeSection()) {
|
||||
return;
|
||||
}
|
||||
@@ -827,8 +870,7 @@ void sect_RelLong(Expression &expr, uint32_t pcShift) {
|
||||
}
|
||||
}
|
||||
|
||||
// Output a PC-relative byte that can be relocatable or constant
|
||||
void sect_PCRelByte(Expression &expr, uint32_t pcShift) {
|
||||
void sect_PCRelByte(Expression const &expr, uint32_t pcShift) {
|
||||
if (!requireCodeSection()) {
|
||||
return;
|
||||
}
|
||||
@@ -851,7 +893,7 @@ void sect_PCRelByte(Expression &expr, uint32_t pcShift) {
|
||||
if (offset < -128 || offset > 127) {
|
||||
error(
|
||||
"JR target must be between -128 and 127 bytes away, not %" PRId16
|
||||
"; use JP instead\n",
|
||||
"; use JP instead",
|
||||
offset
|
||||
);
|
||||
writeByte(0);
|
||||
@@ -861,14 +903,9 @@ void sect_PCRelByte(Expression &expr, uint32_t pcShift) {
|
||||
}
|
||||
}
|
||||
|
||||
// Output a binary file
|
||||
void sect_BinaryFile(std::string const &name, int32_t startPos) {
|
||||
if (startPos < 0) {
|
||||
error("Start position cannot be negative (%" PRId32 ")\n", startPos);
|
||||
startPos = 0;
|
||||
}
|
||||
bool sect_BinaryFile(std::string const &name, uint32_t startPos) {
|
||||
if (!requireCodeSection()) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
FILE *file = nullptr;
|
||||
@@ -876,38 +913,26 @@ void sect_BinaryFile(std::string const &name, int32_t startPos) {
|
||||
file = fopen(fullPath->c_str(), "rb");
|
||||
}
|
||||
if (!file) {
|
||||
if (generatedMissingIncludes) {
|
||||
if (verbose) {
|
||||
printf("Aborting (-MG) on INCBIN file '%s' (%s)\n", name.c_str(), strerror(errno));
|
||||
}
|
||||
failedOnMissingInclude = true;
|
||||
} else {
|
||||
error("Error opening INCBIN file '%s': %s\n", name.c_str(), strerror(errno));
|
||||
}
|
||||
return;
|
||||
return fstk_FileError(name, "INCBIN");
|
||||
}
|
||||
Defer closeFile{[&] { fclose(file); }};
|
||||
|
||||
if (fseek(file, 0, SEEK_END) != -1) {
|
||||
if (fseek(file, 0, SEEK_END) == 0) {
|
||||
if (startPos > ftell(file)) {
|
||||
error("Specified start position is greater than length of file '%s'\n", name.c_str());
|
||||
return;
|
||||
error("Specified start position is greater than length of file '%s'", name.c_str());
|
||||
return false;
|
||||
}
|
||||
// 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)
|
||||
);
|
||||
error("Error determining size of INCBIN file '%s': %s", name.c_str(), strerror(errno));
|
||||
}
|
||||
// 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;
|
||||
error("Specified start position is greater than length of file '%s'", name.c_str());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -917,25 +942,17 @@ void sect_BinaryFile(std::string const &name, int32_t startPos) {
|
||||
}
|
||||
|
||||
if (ferror(file)) {
|
||||
error("Error reading INCBIN file '%s': %s\n", name.c_str(), strerror(errno));
|
||||
error("Error reading INCBIN file '%s': %s", name.c_str(), strerror(errno));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Output a slice of a binary file
|
||||
void sect_BinaryFileSlice(std::string const &name, int32_t startPos, int32_t length) {
|
||||
if (startPos < 0) {
|
||||
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;
|
||||
}
|
||||
bool sect_BinaryFileSlice(std::string const &name, uint32_t startPos, uint32_t length) {
|
||||
if (!requireCodeSection()) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
if (length == 0) { // Don't even bother with 0-byte slices
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
FILE *file = nullptr;
|
||||
@@ -943,48 +960,36 @@ void sect_BinaryFileSlice(std::string const &name, int32_t startPos, int32_t len
|
||||
file = fopen(fullPath->c_str(), "rb");
|
||||
}
|
||||
if (!file) {
|
||||
if (generatedMissingIncludes) {
|
||||
if (verbose) {
|
||||
printf("Aborting (-MG) on INCBIN file '%s' (%s)\n", name.c_str(), strerror(errno));
|
||||
}
|
||||
failedOnMissingInclude = true;
|
||||
} else {
|
||||
error("Error opening INCBIN file '%s': %s\n", name.c_str(), strerror(errno));
|
||||
}
|
||||
return;
|
||||
return fstk_FileError(name, "INCBIN");
|
||||
}
|
||||
Defer closeFile{[&] { fclose(file); }};
|
||||
|
||||
if (fseek(file, 0, SEEK_END) != -1) {
|
||||
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 (fseek(file, 0, SEEK_END) == 0) {
|
||||
if (long fsize = ftell(file); startPos > fsize) {
|
||||
error("Specified start position is greater than length of file '%s'", name.c_str());
|
||||
return false;
|
||||
} else if (startPos + length > fsize) {
|
||||
error(
|
||||
"Specified range in INCBIN file '%s' is out of bounds (%" PRIu32 " + %" PRIu32
|
||||
" > %" PRIu32 ")\n",
|
||||
" > %ld)",
|
||||
name.c_str(),
|
||||
startPos,
|
||||
length,
|
||||
fsize
|
||||
);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
// 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)
|
||||
);
|
||||
error("Error determining size of INCBIN file '%s': %s", name.c_str(), strerror(errno));
|
||||
}
|
||||
// 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;
|
||||
error("Specified start position is greater than length of file '%s'", name.c_str());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -993,18 +998,18 @@ void sect_BinaryFileSlice(std::string const &name, int32_t startPos, int32_t len
|
||||
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));
|
||||
error("Error reading INCBIN file '%s': %s", name.c_str(), strerror(errno));
|
||||
} else {
|
||||
error(
|
||||
"Premature end of INCBIN file '%s' (%" PRId32 " bytes left to read)\n",
|
||||
"Premature end of INCBIN file '%s' (%" PRId32 " bytes left to read)",
|
||||
name.c_str(),
|
||||
length + 1
|
||||
);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Section stack routines
|
||||
void sect_PushSection() {
|
||||
sectionStack.push_front({
|
||||
.section = currentSection,
|
||||
@@ -1024,7 +1029,7 @@ void sect_PushSection() {
|
||||
|
||||
void sect_PopSection() {
|
||||
if (sectionStack.empty()) {
|
||||
fatalerror("No entries in the section stack\n");
|
||||
fatal("No entries in the section stack");
|
||||
}
|
||||
|
||||
if (currentLoadSection) {
|
||||
@@ -1045,17 +1050,17 @@ void sect_PopSection() {
|
||||
|
||||
void sect_CheckStack() {
|
||||
if (!sectionStack.empty()) {
|
||||
warning(WARNING_UNMATCHED_DIRECTIVE, "`PUSHS` without corresponding `POPS`\n");
|
||||
warning(WARNING_UNMATCHED_DIRECTIVE, "`PUSHS` without corresponding `POPS`");
|
||||
}
|
||||
}
|
||||
|
||||
void sect_EndSection() {
|
||||
if (!currentSection) {
|
||||
fatalerror("Cannot end the section outside of a SECTION\n");
|
||||
fatal("Cannot end the section outside of a SECTION");
|
||||
}
|
||||
|
||||
if (!currentUnionStack.empty()) {
|
||||
fatalerror("Cannot end the section within a UNION\n");
|
||||
fatal("Cannot end the section within a UNION");
|
||||
}
|
||||
|
||||
if (currentLoadSection) {
|
||||
@@ -1066,3 +1071,40 @@ void sect_EndSection() {
|
||||
currentSection = nullptr;
|
||||
sym_ResetCurrentLabelScopes();
|
||||
}
|
||||
|
||||
std::string sect_PushSectionFragmentLiteral() {
|
||||
static uint64_t nextFragmentLiteralID = 0;
|
||||
|
||||
// Like `requireCodeSection` but fatal
|
||||
if (!currentSection) {
|
||||
fatal("Cannot output fragment literals outside of a SECTION");
|
||||
}
|
||||
if (!sect_HasData(currentSection->type)) {
|
||||
fatal(
|
||||
"Section '%s' cannot contain fragment literals (not ROM0 or ROMX)",
|
||||
currentSection->name.c_str()
|
||||
);
|
||||
}
|
||||
|
||||
if (currentLoadSection) {
|
||||
fatal("`LOAD` blocks cannot contain fragment literals");
|
||||
}
|
||||
if (currentSection->modifier == SECTION_UNION) {
|
||||
fatal("`SECTION UNION` cannot contain fragment literals");
|
||||
}
|
||||
|
||||
// A section containing a fragment literal has to become a fragment too
|
||||
currentSection->modifier = SECTION_FRAGMENT;
|
||||
|
||||
Section *parent = currentSection;
|
||||
sect_PushSection(); // Resets `currentSection`
|
||||
|
||||
Section *sect = createSectionFragmentLiteral(*parent);
|
||||
|
||||
changeSection();
|
||||
curOffset = sect->size;
|
||||
currentSection = sect;
|
||||
|
||||
// Return a symbol ID to use for the address of this section fragment
|
||||
return "$"s + std::to_string(nextFragmentLiteralID++);
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user