mirror of
https://github.com/gbdev/rgbds.git
synced 2025-11-20 10:12:06 +00:00
Compare commits
134 Commits
v0.9.3
...
d1829ed923
| 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 |
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
|
#!/bin/bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
pngver=1.6.45
|
pngver=1.6.50
|
||||||
|
|
||||||
## Grab sources and check them
|
## Grab sources and check them
|
||||||
|
|
||||||
curl -LOJ "http://prdownloads.sourceforge.net/libpng/libpng-$pngver.tar.xz?download"
|
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.
|
# 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
|
sha2 -256 libpng-$pngver.tar.xz
|
||||||
echo Checksum mismatch! Aborting. >&2
|
echo Checksum mismatch! Aborting. >&2
|
||||||
exit 1
|
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://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/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
|
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 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
|
#!/bin/bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
pngver=1.6.45
|
pngver=1.6.50
|
||||||
arch="$1"
|
arch="$1"
|
||||||
|
|
||||||
## Grab sources and check them
|
## Grab sources and check them
|
||||||
|
|
||||||
wget http://downloads.sourceforge.net/project/libpng/libpng16/$pngver/libpng-$pngver.tar.xz
|
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
|
echo 4df396518620a7aa3651443e87d1b2862e4e88cad135a8b93423e01706232307 libpng-$pngver.tar.xz | sha256sum -c -
|
||||||
sha256sum -c .github/scripts/mingw-w64-libpng-dev.sha256sums
|
|
||||||
|
|
||||||
## Extract sources and patch them
|
## Extract sources and patch them
|
||||||
|
|
||||||
tar -xf libpng-$pngver.tar.xz
|
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!
|
## Start building!
|
||||||
|
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
f7caa3b55f003ce23d6a087b1c2a643262647bfd1a1b31afdc9b18eabf1bbc7e libpng-1.6.45-apng.patch.gz
|
|
||||||
926485350139ffb51ef69760db35f78846c805fef3d59bfdcb2fba704663f370 libpng-1.6.45.tar.xz
|
|
||||||
1
.github/workflows/build-container.yml
vendored
1
.github/workflows/build-container.yml
vendored
@@ -37,6 +37,7 @@ jobs:
|
|||||||
TAG_NAME=${GITHUB_REF#refs/tags/}
|
TAG_NAME=${GITHUB_REF#refs/tags/}
|
||||||
sed -i "2i LABEL org.opencontainers.image.description=\"RGBDS container image for the release version $TAG_NAME\"" Dockerfile
|
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 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:$TAG_NAME
|
||||||
docker push ghcr.io/gbdev/rgbds:latest
|
docker push ghcr.io/gbdev/rgbds:latest
|
||||||
|
|
||||||
|
|||||||
2
.github/workflows/checkdiff.yml
vendored
2
.github/workflows/checkdiff.yml
vendored
@@ -1,4 +1,4 @@
|
|||||||
name: Code coverage checking
|
name: Diff completeness check
|
||||||
on: pull_request
|
on: pull_request
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|||||||
2
.github/workflows/checkformat.yml
vendored
2
.github/workflows/checkformat.yml
vendored
@@ -9,4 +9,4 @@ jobs:
|
|||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
- name: Check format
|
- name: Check format
|
||||||
run: |
|
run: |
|
||||||
./contrib/checkformat.bash
|
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
|
||||||
19
.github/workflows/testing.yml
vendored
19
.github/workflows/testing.yml
vendored
@@ -69,7 +69,7 @@ jobs:
|
|||||||
- name: Run tests
|
- name: Run tests
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
CXX=${{ matrix.cxx }} test/run-tests.sh
|
CXX=${{ matrix.cxx }} test/run-tests.sh --os ${{ matrix.os }}
|
||||||
|
|
||||||
macos-static:
|
macos-static:
|
||||||
runs-on: macos-14
|
runs-on: macos-14
|
||||||
@@ -121,13 +121,13 @@ jobs:
|
|||||||
- name: Run tests
|
- name: Run tests
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
test/run-tests.sh
|
test/run-tests.sh --os macos
|
||||||
|
|
||||||
windows:
|
windows:
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
bits: [32, 64]
|
bits: [32, 64]
|
||||||
os: [windows-2019, windows-2022]
|
os: [windows-2022, windows-2025]
|
||||||
include:
|
include:
|
||||||
- bits: 32
|
- bits: 32
|
||||||
arch: x86
|
arch: x86
|
||||||
@@ -149,9 +149,10 @@ jobs:
|
|||||||
path: |
|
path: |
|
||||||
zbuild
|
zbuild
|
||||||
pngbuild
|
pngbuild
|
||||||
key: ${{ matrix.arch }}-${{ hashFiles('zlib/**', 'libpng/**') }}
|
key: ${{ matrix.os }}-${{ matrix.arch }}-${{ hashFiles('zlib/**', 'libpng/**') }}
|
||||||
- name: Build zlib
|
- name: Build zlib
|
||||||
if: steps.cache.outputs.cache-hit != 'true'
|
if: steps.cache.outputs.cache-hit != 'true'
|
||||||
|
shell: bash
|
||||||
run: | # BUILD_SHARED_LIBS causes the output DLL to be correctly called `zlib1.dll`
|
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 -S zlib -B zbuild -A ${{ matrix.platform }} -Wno-dev -DCMAKE_INSTALL_PREFIX=install_dir -DBUILD_SHARED_LIBS=ON
|
||||||
cmake --build zbuild --config Release -j
|
cmake --build zbuild --config Release -j
|
||||||
@@ -212,7 +213,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
cp bins/* .
|
cp bins/* .
|
||||||
cp bins/*.dll test/gfx
|
cp bins/*.dll test/gfx
|
||||||
test/run-tests.sh
|
test/run-tests.sh --os ${{ matrix.os }}
|
||||||
|
|
||||||
windows-mingw-build:
|
windows-mingw-build:
|
||||||
strategy:
|
strategy:
|
||||||
@@ -270,7 +271,7 @@ jobs:
|
|||||||
needs: windows-mingw-build
|
needs: windows-mingw-build
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [windows-2019, windows-2022]
|
os: [windows-2022, windows-2025]
|
||||||
bits: [32, 64]
|
bits: [32, 64]
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
@@ -319,7 +320,7 @@ jobs:
|
|||||||
- name: Run tests
|
- name: Run tests
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
test/run-tests.sh
|
test/run-tests.sh --os ${{ matrix.os }}
|
||||||
|
|
||||||
cygwin:
|
cygwin:
|
||||||
strategy:
|
strategy:
|
||||||
@@ -331,7 +332,7 @@ jobs:
|
|||||||
- bits: 64
|
- bits: 64
|
||||||
arch: x86_64
|
arch: x86_64
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
runs-on: windows-2019
|
runs-on: windows-2022
|
||||||
timeout-minutes: 30
|
timeout-minutes: 30
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
@@ -382,7 +383,7 @@ jobs:
|
|||||||
git \
|
git \
|
||||||
png
|
png
|
||||||
run: | # FreeBSD `c++` compiler does not support `make develop` sanitizers ASan or UBSan
|
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 --build build -j4 --verbose
|
||||||
cmake --install build --verbose
|
cmake --install build --verbose
|
||||||
cmake --build build --target test
|
cmake --build build --target test
|
||||||
|
|||||||
@@ -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.
|
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.
|
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.
|
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.
|
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
|
#### Variant tests
|
||||||
|
|
||||||
These allow testing RGBLINK's `-d`, `-t`, and `-w` flags.
|
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.
|
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
|
### 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.
|
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 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`.
|
Additionally, if a `.gb` file exists, the output of RGBFIX must match the `.gb`.
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
FROM debian:12-slim
|
FROM debian:12-slim
|
||||||
LABEL org.opencontainers.image.source=https://github.com/gbdev/rgbds
|
LABEL org.opencontainers.image.source=https://github.com/gbdev/rgbds
|
||||||
ARG version=0.9.3
|
ARG version=0.9.4
|
||||||
WORKDIR /rgbds
|
WORKDIR /rgbds
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|||||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
|||||||
The MIT License
|
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
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to
|
of this software and associated documentation files (the "Software"), to
|
||||||
|
|||||||
65
Makefile
65
Makefile
@@ -3,7 +3,7 @@
|
|||||||
.SUFFIXES:
|
.SUFFIXES:
|
||||||
.SUFFIXES: .cpp .y .o
|
.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
|
# User-defined variables
|
||||||
|
|
||||||
@@ -49,7 +49,14 @@ BASE_REF := origin/master
|
|||||||
|
|
||||||
all: rgbasm rgblink rgbfix rgbgfx
|
all: rgbasm rgblink rgbfix rgbgfx
|
||||||
|
|
||||||
|
common_obj := \
|
||||||
|
src/extern/getopt.o \
|
||||||
|
src/diagnostics.o \
|
||||||
|
src/usage.o
|
||||||
|
|
||||||
rgbasm_obj := \
|
rgbasm_obj := \
|
||||||
|
${common_obj} \
|
||||||
|
src/asm/actions.o \
|
||||||
src/asm/charmap.o \
|
src/asm/charmap.o \
|
||||||
src/asm/fixpoint.o \
|
src/asm/fixpoint.o \
|
||||||
src/asm/format.o \
|
src/asm/format.o \
|
||||||
@@ -64,9 +71,7 @@ rgbasm_obj := \
|
|||||||
src/asm/section.o \
|
src/asm/section.o \
|
||||||
src/asm/symbol.o \
|
src/asm/symbol.o \
|
||||||
src/asm/warning.o \
|
src/asm/warning.o \
|
||||||
src/extern/getopt.o \
|
|
||||||
src/extern/utf8decoder.o \
|
src/extern/utf8decoder.o \
|
||||||
src/error.o \
|
|
||||||
src/linkdefs.o \
|
src/linkdefs.o \
|
||||||
src/opmath.o \
|
src/opmath.o \
|
||||||
src/util.o
|
src/util.o
|
||||||
@@ -74,7 +79,10 @@ rgbasm_obj := \
|
|||||||
src/asm/lexer.o src/asm/main.o: src/asm/parser.hpp
|
src/asm/lexer.o src/asm/main.o: src/asm/parser.hpp
|
||||||
|
|
||||||
rgblink_obj := \
|
rgblink_obj := \
|
||||||
|
${common_obj} \
|
||||||
src/link/assign.o \
|
src/link/assign.o \
|
||||||
|
src/link/lexer.o \
|
||||||
|
src/link/layout.o \
|
||||||
src/link/main.o \
|
src/link/main.o \
|
||||||
src/link/object.o \
|
src/link/object.o \
|
||||||
src/link/output.o \
|
src/link/output.o \
|
||||||
@@ -83,34 +91,36 @@ rgblink_obj := \
|
|||||||
src/link/sdas_obj.o \
|
src/link/sdas_obj.o \
|
||||||
src/link/section.o \
|
src/link/section.o \
|
||||||
src/link/symbol.o \
|
src/link/symbol.o \
|
||||||
src/extern/getopt.o \
|
src/link/warning.o \
|
||||||
src/extern/utf8decoder.o \
|
src/extern/utf8decoder.o \
|
||||||
src/error.o \
|
|
||||||
src/linkdefs.o \
|
src/linkdefs.o \
|
||||||
src/opmath.o \
|
src/opmath.o \
|
||||||
src/util.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 := \
|
rgbfix_obj := \
|
||||||
|
${common_obj} \
|
||||||
src/fix/main.o \
|
src/fix/main.o \
|
||||||
src/extern/getopt.o \
|
src/fix/mbc.o \
|
||||||
src/error.o
|
src/fix/warning.o
|
||||||
|
|
||||||
rgbgfx_obj := \
|
rgbgfx_obj := \
|
||||||
|
${common_obj} \
|
||||||
|
src/gfx/color_set.o \
|
||||||
src/gfx/main.o \
|
src/gfx/main.o \
|
||||||
src/gfx/pal_packing.o \
|
src/gfx/pal_packing.o \
|
||||||
src/gfx/pal_sorting.o \
|
src/gfx/pal_sorting.o \
|
||||||
src/gfx/pal_spec.o \
|
src/gfx/pal_spec.o \
|
||||||
|
src/gfx/png.o \
|
||||||
src/gfx/process.o \
|
src/gfx/process.o \
|
||||||
src/gfx/proto_palette.o \
|
|
||||||
src/gfx/reverse.o \
|
src/gfx/reverse.o \
|
||||||
src/gfx/rgba.o \
|
src/gfx/rgba.o \
|
||||||
src/extern/getopt.o \
|
src/gfx/warning.o \
|
||||||
src/error.o
|
src/util.o
|
||||||
|
|
||||||
rgbasm: ${rgbasm_obj}
|
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}
|
rgblink: ${rgblink_obj}
|
||||||
$Q${CXX} ${REALLDFLAGS} -o $@ ${rgblink_obj} ${REALCXXFLAGS} src/version.cpp
|
$Q${CXX} ${REALLDFLAGS} -o $@ ${rgblink_obj} ${REALCXXFLAGS} src/version.cpp
|
||||||
@@ -142,6 +152,8 @@ src/link/script.hpp: src/link/script.cpp
|
|||||||
$Qtouch $@
|
$Qtouch $@
|
||||||
|
|
||||||
# Only RGBGFX uses libpng (POSIX make doesn't support pattern rules to cover all these)
|
# 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
|
src/gfx/main.o: src/gfx/main.cpp
|
||||||
$Q${CXX} ${REALCXXFLAGS} ${PNGCFLAGS} -c -o $@ $<
|
$Q${CXX} ${REALCXXFLAGS} ${PNGCFLAGS} -c -o $@ $<
|
||||||
src/gfx/pal_packing.o: src/gfx/pal_packing.cpp
|
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 $@ $<
|
$Q${CXX} ${REALCXXFLAGS} ${PNGCFLAGS} -c -o $@ $<
|
||||||
src/gfx/pal_spec.o: src/gfx/pal_spec.cpp
|
src/gfx/pal_spec.o: src/gfx/pal_spec.cpp
|
||||||
$Q${CXX} ${REALCXXFLAGS} ${PNGCFLAGS} -c -o $@ $<
|
$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 $@ $<
|
$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 $@ $<
|
$Q${CXX} ${REALCXXFLAGS} ${PNGCFLAGS} -c -o $@ $<
|
||||||
src/gfx/reverse.o: src/gfx/reverse.cpp
|
src/gfx/reverse.o: src/gfx/reverse.cpp
|
||||||
$Q${CXX} ${REALCXXFLAGS} ${PNGCFLAGS} -c -o $@ $<
|
$Q${CXX} ${REALCXXFLAGS} ${PNGCFLAGS} -c -o $@ $<
|
||||||
@@ -163,7 +175,6 @@ src/gfx/rgba.o: src/gfx/rgba.cpp
|
|||||||
$Q${CXX} ${REALCXXFLAGS} -c -o $@ $<
|
$Q${CXX} ${REALCXXFLAGS} -c -o $@ $<
|
||||||
|
|
||||||
# Target used to remove all files generated by other Makefile targets
|
# Target used to remove all files generated by other Makefile targets
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
$Q${RM} rgbasm rgbasm.exe
|
$Q${RM} rgbasm rgbasm.exe
|
||||||
$Q${RM} rgblink rgblink.exe
|
$Q${RM} rgblink rgblink.exe
|
||||||
@@ -177,7 +188,6 @@ clean:
|
|||||||
$Q${RM} test/gfx/randtilegen test/gfx/rgbgfx_test
|
$Q${RM} test/gfx/randtilegen test/gfx/rgbgfx_test
|
||||||
|
|
||||||
# Target used to install the binaries and man pages.
|
# Target used to install the binaries and man pages.
|
||||||
|
|
||||||
install: all
|
install: all
|
||||||
$Qinstall -d ${DESTDIR}${bindir}/ ${DESTDIR}${mandir}/man1/ ${DESTDIR}${mandir}/man5/ ${DESTDIR}${mandir}/man7/
|
$Qinstall -d ${DESTDIR}${bindir}/ ${DESTDIR}${mandir}/man1/ ${DESTDIR}${mandir}/man5/ ${DESTDIR}${mandir}/man7/
|
||||||
$Qinstall ${STRIP} -m ${BINMODE} rgbasm ${DESTDIR}${bindir}/rgbasm${SUFFIX}
|
$Qinstall ${STRIP} -m ${BINMODE} rgbasm ${DESTDIR}${bindir}/rgbasm${SUFFIX}
|
||||||
@@ -189,15 +199,13 @@ install: all
|
|||||||
$Qinstall -m ${MANMODE} man/rgbds.7 man/gbz80.7 ${DESTDIR}${mandir}/man7/
|
$Qinstall -m ${MANMODE} man/rgbds.7 man/gbz80.7 ${DESTDIR}${mandir}/man7/
|
||||||
|
|
||||||
# Target used to check for suspiciously missing changed files.
|
# Target used to check for suspiciously missing changed files.
|
||||||
|
|
||||||
checkdiff:
|
checkdiff:
|
||||||
$Qcontrib/checkdiff.bash `git merge-base HEAD ${BASE_REF}`
|
$Qcontrib/checkdiff.bash `git merge-base HEAD ${BASE_REF}`
|
||||||
|
|
||||||
# This target is used during development in order to prevent adding new issues
|
# Target used in development to prevent adding new issues to the source code.
|
||||||
# to the source code. All warnings are treated as errors in order to block the
|
# All warnings are treated as errors to block the compilation and make the
|
||||||
# compilation and make the continous integration infrastructure return failure.
|
# continous integration infrastructure return failure.
|
||||||
# The rationale for some of the flags is documented in the CMakeLists.
|
# The rationale for some of the flags is documented in the CMakeLists.
|
||||||
|
|
||||||
develop:
|
develop:
|
||||||
$Q${MAKE} WARNFLAGS="${WARNFLAGS} -Werror -Wextra \
|
$Q${MAKE} WARNFLAGS="${WARNFLAGS} -Werror -Wextra \
|
||||||
-Walloc-zero -Wcast-align -Wcast-qual -Wduplicated-branches -Wduplicated-cond \
|
-Walloc-zero -Wcast-align -Wcast-qual -Wduplicated-branches -Wduplicated-cond \
|
||||||
@@ -210,26 +218,27 @@ develop:
|
|||||||
-fsanitize=float-divide-by-zero" \
|
-fsanitize=float-divide-by-zero" \
|
||||||
CXXFLAGS="-ggdb3 -Og -fno-omit-frame-pointer -fno-optimize-sibling-calls"
|
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:
|
debug:
|
||||||
$Qenv ${MAKE} \
|
$Qenv ${MAKE} \
|
||||||
CXXFLAGS="-ggdb3 -O0 -fno-omit-frame-pointer -fno-optimize-sibling-calls"
|
CXXFLAGS="-ggdb3 -O0 -fno-omit-frame-pointer -fno-optimize-sibling-calls"
|
||||||
|
|
||||||
# This target is used during development in order to more easily profile with callgrind.
|
# Target used in development to profile with callgrind.
|
||||||
|
|
||||||
profile:
|
profile:
|
||||||
$Qenv ${MAKE} \
|
$Qenv ${MAKE} \
|
||||||
CXXFLAGS="-ggdb3 -O3 -fno-omit-frame-pointer -fno-optimize-sibling-calls"
|
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:
|
coverage:
|
||||||
$Qenv ${MAKE} \
|
$Qenv ${MAKE} \
|
||||||
CXXFLAGS="-ggdb3 -Og --coverage -fno-omit-frame-pointer -fno-optimize-sibling-calls"
|
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:
|
iwyu:
|
||||||
$Qenv ${MAKE} \
|
$Qenv ${MAKE} \
|
||||||
CXX="include-what-you-use" \
|
CXX="include-what-you-use" \
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ for the Game Boy and Game Boy Color. It consists of:
|
|||||||
- RGBASM (assembler)
|
- RGBASM (assembler)
|
||||||
- RGBLINK (linker)
|
- RGBLINK (linker)
|
||||||
- RGBFIX (checksum/header fixer)
|
- 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
|
This is a fork of the original RGBDS which aims to make the programs more like
|
||||||
other UNIX tools.
|
other UNIX tools.
|
||||||
@@ -93,6 +93,7 @@ The RGBDS source code file structure is as follows:
|
|||||||
│ └── run-tests.sh
|
│ └── run-tests.sh
|
||||||
├── .clang-format
|
├── .clang-format
|
||||||
├── CMakeLists.txt
|
├── CMakeLists.txt
|
||||||
|
├── compile_flags.txt
|
||||||
├── Dockerfile
|
├── Dockerfile
|
||||||
├── Makefile
|
├── Makefile
|
||||||
└── README.md
|
└── README.md
|
||||||
@@ -118,6 +119,8 @@ The RGBDS source code file structure is as follows:
|
|||||||
modify the behavior of RGBDS.
|
modify the behavior of RGBDS.
|
||||||
- `.clang-format` - code style for automated C++ formatting with
|
- `.clang-format` - code style for automated C++ formatting with
|
||||||
[`clang-format`](https://clang.llvm.org/docs/ClangFormat.html).
|
[`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.
|
- `Dockerfile` - defines how to build RGBDS with Docker.
|
||||||
|
|
||||||
## 3. History
|
## 3. History
|
||||||
@@ -140,7 +143,7 @@ The RGBDS source code file structure is as follows:
|
|||||||
- 2010-09-25: Sørensen continues development of
|
- 2010-09-25: Sørensen continues development of
|
||||||
[ASMotor](https://github.com/asmotor/asmotor) to this day.
|
[ASMotor](https://github.com/asmotor/asmotor) to this day.
|
||||||
- 2015-01-18: stag019 begins implementing [RGBGFX](https://github.com/stag019/rgbgfx),
|
- 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
|
- 2016-09-05: RGBGFX is
|
||||||
[integrated](https://github.com/gbdev/rgbds/commit/c3c31138ddbd8680d4e67957e387f2816798a71b)
|
[integrated](https://github.com/gbdev/rgbds/commit/c3c31138ddbd8680d4e67957e387f2816798a71b)
|
||||||
into Bentley's repository.
|
into Bentley's repository.
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ GitHub.
|
|||||||
|
|
||||||
6. Click the "Publish release" button to publish it!
|
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.
|
8. Update the following related projects.
|
||||||
|
|
||||||
@@ -77,9 +77,7 @@ GitHub.
|
|||||||
to list the new release.
|
to list the new release.
|
||||||
2. [rgbds-live](https://github.com/gbdev/rgbds-live): update the `rgbds` submodule (and
|
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)
|
[patches/rgbds.patch](https://github.com/gbdev/rgbds-live/blob/master/patches/rgbds.patch)
|
||||||
if necessary) to use the new release, and
|
if necessary) to use the new release.
|
||||||
[index.html](https://github.com/gbdev/rgbds-live/blob/master/index.html)
|
|
||||||
to link to the new manual version.
|
|
||||||
3. [rgbobj](https://github.com/gbdev/rgbobj) and [rgbds-obj](https://github.com/gbdev/rgbds-obj):
|
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.
|
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.
|
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
|
# Empty long opt = it doesn't exit
|
||||||
# See the `state` variable below for info about `state_after`
|
# See the `state` variable below for info about `state_after`
|
||||||
declare -A opts=(
|
declare -A opts=(
|
||||||
[V]="version:normal"
|
|
||||||
[h]="help:normal"
|
[h]="help:normal"
|
||||||
[E]="export-all:normal"
|
[V]="version:normal"
|
||||||
[v]="verbose:normal"
|
[W]="warning:warning"
|
||||||
[w]=":normal"
|
[w]=":normal"
|
||||||
[b]="binary-digits:unk"
|
[b]="binary-digits:unk"
|
||||||
[D]="define:unk"
|
[D]="define:unk"
|
||||||
|
[E]="export-all:normal"
|
||||||
[g]="gfx-chars:unk"
|
[g]="gfx-chars:unk"
|
||||||
[I]="include:dir"
|
[I]="include:dir"
|
||||||
[M]="dependfile:glob-*.mk *.d"
|
[M]="dependfile:glob-*.mk *.d"
|
||||||
@@ -40,7 +40,7 @@ _rgbasm_completions() {
|
|||||||
[Q]="q-precision:unk"
|
[Q]="q-precision:unk"
|
||||||
[r]="recursion-depth:unk"
|
[r]="recursion-depth:unk"
|
||||||
[s]="state:unk"
|
[s]="state:unk"
|
||||||
[W]="warning:warning"
|
[v]="verbose:normal"
|
||||||
[X]="max-errors:unk"
|
[X]="max-errors:unk"
|
||||||
)
|
)
|
||||||
# Parse command-line up to current word
|
# Parse command-line up to current word
|
||||||
@@ -155,7 +155,7 @@ _rgbasm_completions() {
|
|||||||
parse_short_opt "$cur_word"
|
parse_short_opt "$cur_word"
|
||||||
|
|
||||||
if [[ "$state" = 'normal' ]]; then
|
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
|
return 0
|
||||||
elif [[ "$optlen" = "${#cur_word}" && "$state" != "warning" ]]; then
|
elif [[ "$optlen" = "${#cur_word}" && "$state" != "warning" ]]; then
|
||||||
# This short option group only awaits its argument!
|
# This short option group only awaits its argument!
|
||||||
|
|||||||
@@ -7,15 +7,15 @@ _rgbfix_completions() {
|
|||||||
# Empty long opt = it doesn't exit
|
# Empty long opt = it doesn't exit
|
||||||
# See the `state` variable below for info about `state_after`
|
# See the `state` variable below for info about `state_after`
|
||||||
declare -A opts=(
|
declare -A opts=(
|
||||||
[V]="version:normal"
|
|
||||||
[h]="help:normal"
|
[h]="help:normal"
|
||||||
[j]="non-japanese:normal"
|
[V]="version:normal"
|
||||||
[s]="sgb-compatible:normal"
|
[W]="warning:warning"
|
||||||
[v]="validate:normal"
|
[w]=":normal"
|
||||||
[C]="color-only:normal"
|
[C]="color-only:normal"
|
||||||
[c]="color-compatible:normal"
|
[c]="color-compatible:normal"
|
||||||
[f]="fix-spec:fix-spec"
|
[f]="fix-spec:fix-spec"
|
||||||
[i]="game-id:unk"
|
[i]="game-id:unk"
|
||||||
|
[j]="non-japanese:normal"
|
||||||
[k]="new-licensee:unk"
|
[k]="new-licensee:unk"
|
||||||
[L]="custom-logo:glob-*.1bpp"
|
[L]="custom-logo:glob-*.1bpp"
|
||||||
[l]="old-licensee:unk"
|
[l]="old-licensee:unk"
|
||||||
@@ -24,7 +24,9 @@ _rgbfix_completions() {
|
|||||||
[o]="output:glob-*.gb *.gbc *.sgb"
|
[o]="output:glob-*.gb *.gbc *.sgb"
|
||||||
[p]="pad-value:unk"
|
[p]="pad-value:unk"
|
||||||
[r]="ram-size:unk"
|
[r]="ram-size:unk"
|
||||||
|
[s]="sgb-compatible:normal"
|
||||||
[t]="title:unk"
|
[t]="title:unk"
|
||||||
|
[v]="validate:normal"
|
||||||
)
|
)
|
||||||
# Parse command-line up to current word
|
# Parse command-line up to current word
|
||||||
local opt_ena=true
|
local opt_ena=true
|
||||||
@@ -140,6 +142,16 @@ _rgbfix_completions() {
|
|||||||
case "$state" in
|
case "$state" in
|
||||||
unk) # Return with no replies: no idea what to complete!
|
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)
|
fix-spec)
|
||||||
COMPREPLY=( "${cur_word}"{l,h,g,L,H,G} )
|
COMPREPLY=( "${cur_word}"{l,h,g,L,H,G} )
|
||||||
;;
|
;;
|
||||||
|
|||||||
@@ -7,36 +7,38 @@ _rgbgfx_completions() {
|
|||||||
# Empty long opt = it doesn't exit
|
# Empty long opt = it doesn't exit
|
||||||
# See the `state` variable below for info about `state_after`
|
# See the `state` variable below for info about `state_after`
|
||||||
declare -A opts=(
|
declare -A opts=(
|
||||||
[V]="version:normal"
|
|
||||||
[h]="help:normal"
|
[h]="help:normal"
|
||||||
[C]="color-curve:normal"
|
[V]="version:normal"
|
||||||
[m]="mirror-tiles:normal"
|
[W]="warning:warning"
|
||||||
[O]="group-outputs:normal"
|
[w]=":normal"
|
||||||
[u]="unique-tiles:normal"
|
|
||||||
[v]="verbose:normal"
|
|
||||||
[X]="mirror-x:normal"
|
|
||||||
[Y]="mirror-y:normal"
|
|
||||||
[Z]="columns:normal"
|
|
||||||
[a]="attr-map:glob-*.attrmap"
|
|
||||||
[A]="auto-attr-map:normal"
|
[A]="auto-attr-map:normal"
|
||||||
|
[a]="attr-map:glob-*.attrmap"
|
||||||
[B]="background-color:unk"
|
[B]="background-color:unk"
|
||||||
[b]="base-tiles:unk"
|
[b]="base-tiles:unk"
|
||||||
|
[C]="color-curve:normal"
|
||||||
[c]="colors:unk"
|
[c]="colors:unk"
|
||||||
[d]="depth:unk"
|
[d]="depth:unk"
|
||||||
[i]="input-tileset:glob-*.2bpp"
|
[i]="input-tileset:glob-*.2bpp"
|
||||||
[L]="slice:unk"
|
[L]="slice:unk"
|
||||||
|
[m]="mirror-tiles:normal"
|
||||||
[N]="nb-tiles:unk"
|
[N]="nb-tiles:unk"
|
||||||
[n]="nb-palettes:unk"
|
[n]="nb-palettes:unk"
|
||||||
|
[O]="group-outputs:normal"
|
||||||
[o]="output:glob-*.2bpp"
|
[o]="output:glob-*.2bpp"
|
||||||
[p]="palette:glob-*.pal"
|
|
||||||
[P]="auto-palette:normal"
|
[P]="auto-palette:normal"
|
||||||
[q]="palette-map:glob-*.palmap"
|
[p]="palette:glob-*.pal"
|
||||||
[Q]="auto-palette-map:normal"
|
[Q]="auto-palette-map:normal"
|
||||||
|
[q]="palette-map:glob-*.palmap"
|
||||||
[r]="reverse:unk"
|
[r]="reverse:unk"
|
||||||
[s]="palette-size:unk"
|
[s]="palette-size:unk"
|
||||||
[t]="tilemap:glob-*.tilemap"
|
|
||||||
[T]="auto-tilemap:normal"
|
[T]="auto-tilemap:normal"
|
||||||
|
[t]="tilemap:glob-*.tilemap"
|
||||||
|
[u]="unique-tiles:normal"
|
||||||
|
[v]="verbose:normal"
|
||||||
|
[X]="mirror-x:normal"
|
||||||
[x]="trim-end:unk"
|
[x]="trim-end:unk"
|
||||||
|
[Y]="mirror-y:normal"
|
||||||
|
[Z]="columns:normal"
|
||||||
)
|
)
|
||||||
# Parse command-line up to current word
|
# Parse command-line up to current word
|
||||||
local opt_ena=true
|
local opt_ena=true
|
||||||
@@ -152,6 +154,14 @@ _rgbgfx_completions() {
|
|||||||
case "$state" in
|
case "$state" in
|
||||||
unk) # Return with no replies: no idea what to complete!
|
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...
|
normal) # Acts like a glob...
|
||||||
state="glob-*.png"
|
state="glob-*.png"
|
||||||
;&
|
;&
|
||||||
|
|||||||
@@ -7,20 +7,21 @@ _rgblink_completions() {
|
|||||||
# Empty long opt = it doesn't exit
|
# Empty long opt = it doesn't exit
|
||||||
# See the `state` variable below for info about `state_after`
|
# See the `state` variable below for info about `state_after`
|
||||||
declare -A opts=(
|
declare -A opts=(
|
||||||
[V]="version:normal"
|
|
||||||
[h]="help:normal"
|
[h]="help:normal"
|
||||||
[d]="dmg:normal"
|
[V]="version:normal"
|
||||||
[t]="tiny:normal"
|
[W]="warning:warning"
|
||||||
[v]="verbose:normal"
|
|
||||||
[w]="wramx:normal"
|
|
||||||
[x]="nopad:normal"
|
|
||||||
[l]="linkerscript:glob-*"
|
|
||||||
[M]="no-sym-in-map:normal"
|
[M]="no-sym-in-map:normal"
|
||||||
|
[d]="dmg:normal"
|
||||||
|
[l]="linkerscript:glob-*"
|
||||||
[m]="map:glob-*.map"
|
[m]="map:glob-*.map"
|
||||||
[n]="sym:glob-*.sym"
|
[n]="sym:glob-*.sym"
|
||||||
[O]="overlay:glob-*.gb *.gbc *.sgb"
|
[O]="overlay:glob-*.gb *.gbc *.sgb"
|
||||||
[o]="output:glob-*.gb *.gbc *.sgb"
|
[o]="output:glob-*.gb *.gbc *.sgb"
|
||||||
[p]="pad:unk"
|
[p]="pad:unk"
|
||||||
|
[t]="tiny:normal"
|
||||||
|
[v]="verbose:normal"
|
||||||
|
[w]="wramx:normal"
|
||||||
|
[x]="nopad:normal"
|
||||||
)
|
)
|
||||||
# Parse command-line up to current word
|
# Parse command-line up to current word
|
||||||
local opt_ena=true
|
local opt_ena=true
|
||||||
@@ -136,6 +137,18 @@ _rgblink_completions() {
|
|||||||
case "$state" in
|
case "$state" in
|
||||||
unk) # Return with no replies: no idea what to complete!
|
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...
|
normal) # Acts like a glob...
|
||||||
state="glob-*.o *.obj"
|
state="glob-*.o *.obj"
|
||||||
;&
|
;&
|
||||||
|
|||||||
@@ -26,11 +26,23 @@ dependency include/linkdefs.hpp man/rgbds.5 \
|
|||||||
dependency src/asm/parser.y man/rgbasm.5 \
|
dependency src/asm/parser.y man/rgbasm.5 \
|
||||||
"Was the rgbasm grammar changed?"
|
"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 \
|
dependency src/link/script.y man/rgblink.5 \
|
||||||
"Was the linker script grammar changed?"
|
"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?"
|
"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 \
|
dependency src/asm/object.cpp include/linkdefs.hpp \
|
||||||
"Should the object file revision be bumped?"
|
"Should the object file revision be bumped?"
|
||||||
|
|||||||
@@ -11,5 +11,5 @@ if ! git diff-index --quiet HEAD --; then
|
|||||||
git diff-index --name-only HEAD --
|
git diff-index --name-only HEAD --
|
||||||
echo
|
echo
|
||||||
git diff HEAD --
|
git diff HEAD --
|
||||||
return 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -7,20 +7,29 @@ make coverage -j
|
|||||||
# Run the tests
|
# Run the tests
|
||||||
pushd test
|
pushd test
|
||||||
./fetch-test-deps.sh
|
./fetch-test-deps.sh
|
||||||
./run-tests.sh
|
if [[ $# -eq 0 ]]; then
|
||||||
|
./run-tests.sh
|
||||||
|
else
|
||||||
|
./run-tests.sh --os "$1"
|
||||||
|
fi
|
||||||
popd
|
popd
|
||||||
|
|
||||||
# Generate coverage logs
|
# Generate coverage logs
|
||||||
gcov src/**/*.cpp
|
gcov src/**/*.cpp
|
||||||
mkdir -p coverage
|
mkdir -p coverage
|
||||||
|
|
||||||
# Generate coverage report
|
# Generate coverage report, excluding Bison-generated files
|
||||||
lcov -c --no-external -d . -o coverage/coverage.info
|
COVERAGE_INFO=coverage/coverage.info
|
||||||
genhtml -f -s -o coverage/ 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
|
# Check whether running from coverage.yml workflow
|
||||||
if [ "$(uname)" == "Darwin" ]; then
|
if [ "$1" != "ubuntu-ci" ]; then
|
||||||
open coverage/index.html
|
# Open report in web browser
|
||||||
else
|
if [ "$(uname)" == "Darwin" ]; then
|
||||||
xdg-open coverage/index.html
|
open coverage/index.html
|
||||||
|
else
|
||||||
|
xdg-open coverage/index.html
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -23,15 +23,13 @@ _rgbasm_warnings() {
|
|||||||
'obsolete:Warn when using deprecated features'
|
'obsolete:Warn when using deprecated features'
|
||||||
'purge:Warn when purging exported symbols or labels'
|
'purge:Warn when purging exported symbols or labels'
|
||||||
'shift:Warn when shifting negative values'
|
'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'
|
'truncation:Warn when implicit truncation loses bits'
|
||||||
'unmapped-char:Warn on unmapped character'
|
'unmapped-char:Warn on unmapped character'
|
||||||
'unmatched-directive:Warn on unmatched directive pair'
|
'unmatched-directive:Warn on unmatched directive pair'
|
||||||
'unterminated-load:Warn on LOAD without ENDL'
|
'unterminated-load:Warn on LOAD without ENDL'
|
||||||
'user:Warn when executing the WARN built-in'
|
'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
|
_describe warning warnings
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,9 +46,10 @@ local args=(
|
|||||||
'*'{-D,--define}'+[Define a string symbol]:name + value (default 1):'
|
'*'{-D,--define}'+[Define a string symbol]:name + value (default 1):'
|
||||||
'(-g --gfx-chars)'{-g,--gfx-chars}'+[Change chars for gfx constants]:chars spec:'
|
'(-g --gfx-chars)'{-g,--gfx-chars}'+[Change chars for gfx constants]:chars spec:'
|
||||||
'(-I --include)'{-I,--include}'+[Add an include directory]:include path:_files -/'
|
'(-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}'"
|
'(-M --dependfile)'{-M,--dependfile}"+[Write dependencies in Makefile format]:output file:_files -g '*.{d,mk}'"
|
||||||
-MG'[Assume missing files should be generated]'
|
-MC'[Continue after missing dependencies]'
|
||||||
-MP'[Add phony targets to all deps]'
|
-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}'"
|
'*'-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}'"
|
'*'-MQ"+[Add a target to the rules]:target:_files -g '*.{d,mk,o}'"
|
||||||
'(-o --output)'{-o,--output}'+[Output file]:output file:_files'
|
'(-o --output)'{-o,--output}'+[Output file]:output file:_files'
|
||||||
|
|||||||
@@ -34,6 +34,21 @@ _mbc_names() {
|
|||||||
_describe "MBC name" 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=(
|
local args=(
|
||||||
# Arguments are listed here in the same order as in the manual, except for the version and help
|
# 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]'
|
'(- : * options)'{-V,--version}'[Print version number and exit]'
|
||||||
@@ -45,6 +60,7 @@ local args=(
|
|||||||
'(-O --overwrite)'{-O,--overwrite}'[Allow overwriting non-zero bytes]'
|
'(-O --overwrite)'{-O,--overwrite}'[Allow overwriting non-zero bytes]'
|
||||||
'(-s --sgb-compatible)'{-s,--sgb-compatible}'[Set the SGB flag]'
|
'(-s --sgb-compatible)'{-s,--sgb-compatible}'[Set the SGB flag]'
|
||||||
'(-f --fix-spec -v --validate)'{-v,--validate}'[Shorthand for -f lhg]'
|
'(-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:'
|
'(-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:'
|
'(-i --game-id)'{-i,--game-id}'+[Set game ID string]:4-char game ID:'
|
||||||
@@ -57,6 +73,7 @@ local args=(
|
|||||||
'(-p --pad-value)'{-p,--pad-value}'+[Pad to next valid size using this byte as padding]:padding byte:'
|
'(-p --pad-value)'{-p,--pad-value}'+[Pad to next valid size using this byte as padding]:padding byte:'
|
||||||
'(-r --ram-size)'{-r,--ram-size}'+[Set RAM size]:ram size byte:'
|
'(-r --ram-size)'{-r,--ram-size}'+[Set RAM size]:ram size byte:'
|
||||||
'(-t --title)'{-t,--title}'+[Set title string]:11-char title string:'
|
'(-t --title)'{-t,--title}'+[Set title string]:11-char title string:'
|
||||||
|
'(-W --warning)'{-W,--warning}'+[Toggle warning flags]:warning flag:_rgbfix_warnings'
|
||||||
|
|
||||||
'*'":ROM files:_files -g '*.{gb,sgb,gbc}'"
|
'*'":ROM files:_files -g '*.{gb,sgb,gbc}'"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -9,6 +9,19 @@ _depths() {
|
|||||||
_describe 'bit depth' 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=(
|
local args=(
|
||||||
# Arguments are listed here in the same order as in the manual, except for the version and help
|
# 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]'
|
'(- : * options)'{-V,--version}'[Print version number and exit]'
|
||||||
@@ -22,7 +35,8 @@ local args=(
|
|||||||
'(-q --palette-map -Q --auto-palette-map)'{-Q,--auto-palette-map}'[Shortcut for -p <file>.palmap]'
|
'(-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]'
|
'(-t --tilemap -T --auto-tilemap)'{-T,--auto-tilemap}'[Shortcut for -t <file>.tilemap]'
|
||||||
'(-u --unique-tiles)'{-u,--unique-tiles}'[Eliminate redundant tiles]'
|
'(-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]'
|
'(-X --mirror-x)'{-X,--mirror-x}'[Eliminate horizontally mirrored tiles from output]'
|
||||||
'(-Y --mirror-y)'{-Y,--mirror-y}'[Eliminate vertically 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]'
|
'(-Z --columns)'{-Z,--columns}'[Read the image in column-major order]'
|
||||||
@@ -42,6 +56,7 @@ local args=(
|
|||||||
'(-r --reverse)'{-r,--reverse}'+[Yield an image from binary data]:image width (in tiles):'
|
'(-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:'
|
'(-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'
|
'(-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:'
|
'(-x --trim-end)'{-x,--trim-end}'+[Trim end of output by this many tiles]:tile count:'
|
||||||
|
|
||||||
":input png file:_files -g '*.png'"
|
":input png file:_files -g '*.png'"
|
||||||
|
|||||||
@@ -1,5 +1,22 @@
|
|||||||
#compdef rgblink
|
#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=(
|
local args=(
|
||||||
# Arguments are listed here in the same order as in the manual, except for the version and help
|
# 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]'
|
'(- : * 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}'"
|
'(-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:'
|
'(-p --pad-value)'{-p,--pad-value}'+[Set padding byte]:padding byte:'
|
||||||
'(-S --scramble)'{-s,--scramble}'+[Activate scrambling]:scramble spec'
|
'(-S --scramble)'{-s,--scramble}'+[Activate scrambling]:scramble spec'
|
||||||
|
'(-W --warning)'{-W,--warning}'+[Toggle warning flags]:warning flag:_rgblink_warnings'
|
||||||
|
|
||||||
'*'":object files:_files -g '*.o'"
|
'*'":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
|
||||||
@@ -5,9 +5,6 @@
|
|||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
extern uint8_t fixPrecision;
|
|
||||||
|
|
||||||
uint8_t fix_Precision();
|
|
||||||
int32_t fix_Sin(int32_t i, int32_t q);
|
int32_t fix_Sin(int32_t i, int32_t q);
|
||||||
int32_t fix_Cos(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);
|
int32_t fix_Tan(int32_t i, int32_t q);
|
||||||
|
|||||||
@@ -10,16 +10,16 @@
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <variant>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "either.hpp"
|
|
||||||
#include "linkdefs.hpp"
|
#include "linkdefs.hpp"
|
||||||
|
|
||||||
#include "asm/lexer.hpp"
|
#include "asm/lexer.hpp"
|
||||||
|
|
||||||
struct FileStackNode {
|
struct FileStackNode {
|
||||||
FileStackNodeType type;
|
FileStackNodeType type;
|
||||||
Either<
|
std::variant<
|
||||||
std::vector<uint32_t>, // NODE_REPT
|
std::vector<uint32_t>, // NODE_REPT
|
||||||
std::string // NODE_FILE, NODE_MACRO
|
std::string // NODE_FILE, NODE_MACRO
|
||||||
>
|
>
|
||||||
@@ -34,33 +34,34 @@ struct FileStackNode {
|
|||||||
uint32_t ID = UINT32_MAX;
|
uint32_t ID = UINT32_MAX;
|
||||||
|
|
||||||
// REPT iteration counts since last named node, in reverse depth order
|
// 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> &iters() { return std::get<std::vector<uint32_t>>(data); }
|
||||||
std::vector<uint32_t> const &iters() const { return data.get<std::vector<uint32_t>>(); }
|
std::vector<uint32_t> const &iters() const { return std::get<std::vector<uint32_t>>(data); }
|
||||||
// File name for files, file::macro name for macros
|
// File name for files, file::macro name for macros
|
||||||
std::string &name() { return data.get<std::string>(); }
|
std::string &name() { return std::get<std::string>(data); }
|
||||||
std::string const &name() const { return data.get<std::string>(); }
|
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_) {}
|
: type(type_), data(data_) {}
|
||||||
|
|
||||||
std::string const &dump(uint32_t curLineNo) const;
|
std::string const &dump(uint32_t curLineNo) const;
|
||||||
|
std::string reptChain() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern size_t maxRecursionDepth;
|
|
||||||
|
|
||||||
struct MacroArgs;
|
struct MacroArgs;
|
||||||
|
|
||||||
void fstk_DumpCurrent();
|
bool fstk_DumpCurrent();
|
||||||
std::shared_ptr<FileStackNode> fstk_GetFileStack();
|
std::shared_ptr<FileStackNode> fstk_GetFileStack();
|
||||||
std::shared_ptr<std::string> fstk_GetUniqueIDStr();
|
std::shared_ptr<std::string> fstk_GetUniqueIDStr();
|
||||||
MacroArgs *fstk_GetCurrentMacroArgs();
|
MacroArgs *fstk_GetCurrentMacroArgs();
|
||||||
|
|
||||||
void fstk_AddIncludePath(std::string const &path);
|
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);
|
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();
|
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_RunMacro(std::string const ¯oName, std::shared_ptr<MacroArgs> macroArgs);
|
||||||
void fstk_RunRept(uint32_t count, int32_t reptLineNo, ContentSpan const &span);
|
void fstk_RunRept(uint32_t count, int32_t reptLineNo, ContentSpan const &span);
|
||||||
void fstk_RunFor(
|
void fstk_RunFor(
|
||||||
@@ -74,6 +75,6 @@ void fstk_RunFor(
|
|||||||
bool fstk_Break();
|
bool fstk_Break();
|
||||||
|
|
||||||
void fstk_NewRecursionDepth(size_t newDepth);
|
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
|
#endif // RGBDS_ASM_FSTACK_HPP
|
||||||
|
|||||||
@@ -8,13 +8,13 @@
|
|||||||
#include <optional>
|
#include <optional>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <variant>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "either.hpp"
|
|
||||||
#include "platform.hpp" // SSIZE_MAX
|
#include "platform.hpp" // SSIZE_MAX
|
||||||
|
|
||||||
// This value is a compromise between `LexerState` allocation performance when `mmap` works, and
|
// This value is a compromise between `LexerState` allocation performance when reading the entire
|
||||||
// buffering performance when it doesn't/can't (e.g. when piping a file into RGBASM).
|
// 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;
|
static constexpr size_t LEXER_BUF_SIZE = 64;
|
||||||
// The buffer needs to be large enough for the maximum `lexerState->peek()` lookahead distance
|
// 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");
|
static_assert(LEXER_BUF_SIZE > 1, "Lexer buffer size is too small");
|
||||||
@@ -84,6 +84,7 @@ struct LexerState {
|
|||||||
bool atLineStart;
|
bool atLineStart;
|
||||||
uint32_t lineNo;
|
uint32_t lineNo;
|
||||||
int lastToken;
|
int lastToken;
|
||||||
|
int nextToken;
|
||||||
|
|
||||||
std::deque<IfStackEntry> ifStack;
|
std::deque<IfStackEntry> ifStack;
|
||||||
|
|
||||||
@@ -91,13 +92,12 @@ struct LexerState {
|
|||||||
size_t captureSize; // Amount of text captured
|
size_t captureSize; // Amount of text captured
|
||||||
std::shared_ptr<std::vector<char>> captureBuf; // Buffer to send the captured text to if set
|
std::shared_ptr<std::vector<char>> captureBuf; // Buffer to send the captured text to if set
|
||||||
|
|
||||||
bool disableMacroArgs;
|
bool disableExpansions;
|
||||||
bool disableInterpolation;
|
size_t expansionScanDistance; // Max distance already scanned for expansions
|
||||||
size_t macroArgScanDistance; // Max distance already scanned for macro args
|
|
||||||
bool expandStrings;
|
bool expandStrings;
|
||||||
std::deque<Expansion> expansions; // Front is the innermost current expansion
|
std::deque<Expansion> expansions; // Front is the innermost current expansion
|
||||||
|
|
||||||
Either<ViewedContent, BufferedContent> content;
|
std::variant<std::monostate, ViewedContent, BufferedContent> content;
|
||||||
|
|
||||||
~LexerState();
|
~LexerState();
|
||||||
|
|
||||||
@@ -109,21 +109,17 @@ struct LexerState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void setAsCurrentState();
|
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 setViewAsNextState(char const *name, ContentSpan const &span, uint32_t lineNo_);
|
||||||
|
|
||||||
void clear(uint32_t lineNo_);
|
void clear(uint32_t lineNo_);
|
||||||
};
|
};
|
||||||
|
|
||||||
extern char binDigits[2];
|
|
||||||
extern char gfxDigits[4];
|
|
||||||
|
|
||||||
void lexer_SetBinDigits(char const digits[2]);
|
void lexer_SetBinDigits(char const digits[2]);
|
||||||
void lexer_SetGfxDigits(char const digits[4]);
|
void lexer_SetGfxDigits(char const digits[4]);
|
||||||
|
|
||||||
bool lexer_AtTopLevel();
|
bool lexer_AtTopLevel();
|
||||||
void lexer_RestartRept(uint32_t lineNo);
|
void lexer_RestartRept(uint32_t lineNo);
|
||||||
void lexer_Init();
|
|
||||||
void lexer_SetMode(LexerMode mode);
|
void lexer_SetMode(LexerMode mode);
|
||||||
void lexer_ToggleStringExpansion(bool enable);
|
void lexer_ToggleStringExpansion(bool enable);
|
||||||
|
|
||||||
|
|||||||
@@ -3,16 +3,50 @@
|
|||||||
#ifndef RGBDS_ASM_MAIN_HPP
|
#ifndef RGBDS_ASM_MAIN_HPP
|
||||||
#define RGBDS_ASM_MAIN_HPP
|
#define RGBDS_ASM_MAIN_HPP
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
extern bool verbose;
|
enum MissingInclude {
|
||||||
extern bool warnings; // True to enable warnings, false to disable them.
|
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;
|
struct Options {
|
||||||
extern std::string targetFileName;
|
uint8_t fixPrecision = 16; // -Q
|
||||||
extern bool generatedMissingIncludes;
|
size_t maxRecursionDepth = 64; // -r
|
||||||
extern bool failedOnMissingInclude;
|
char binDigits[2] = {'0', '1'}; // -b
|
||||||
extern bool generatePhonyDeps;
|
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
|
#endif // RGBDS_ASM_MAIN_HPP
|
||||||
|
|||||||
@@ -5,10 +5,10 @@
|
|||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
void opt_B(char const chars[2]);
|
void opt_B(char const binDigits[2]);
|
||||||
void opt_G(char const chars[4]);
|
void opt_G(char const gfxDigits[4]);
|
||||||
void opt_P(uint8_t padByte);
|
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_W(char const *flag);
|
||||||
void opt_Parse(char const *option);
|
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 };
|
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_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_CreatePatch(uint32_t type, Expression const &expr, uint32_t ofs, uint32_t pcShift);
|
||||||
void out_CreateAssert(
|
void out_CreateAssert(
|
||||||
AssertionType type, Expression const &expr, std::string const &message, uint32_t ofs
|
AssertionType type, Expression const &expr, std::string const &message, uint32_t ofs
|
||||||
|
|||||||
@@ -5,15 +5,15 @@
|
|||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <variant>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "either.hpp"
|
|
||||||
#include "linkdefs.hpp"
|
#include "linkdefs.hpp"
|
||||||
|
|
||||||
struct Symbol;
|
struct Symbol;
|
||||||
|
|
||||||
struct Expression {
|
struct Expression {
|
||||||
Either<
|
std::variant<
|
||||||
int32_t, // If the expression's value is known, it's here
|
int32_t, // If the expression's value is known, it's here
|
||||||
std::string // Why the expression is not known, if it isn't
|
std::string // Why the expression is not known, if it isn't
|
||||||
>
|
>
|
||||||
@@ -22,8 +22,8 @@ struct Expression {
|
|||||||
std::vector<uint8_t> rpn{}; // Bytes serializing the RPN expression
|
std::vector<uint8_t> rpn{}; // Bytes serializing the RPN expression
|
||||||
uint32_t rpnPatchSize = 0; // Size the expression will take in the object file
|
uint32_t rpnPatchSize = 0; // Size the expression will take in the object file
|
||||||
|
|
||||||
bool isKnown() const { return data.holds<int32_t>(); }
|
bool isKnown() const { return std::holds_alternative<int32_t>(data); }
|
||||||
int32_t value() const { return data.get<int32_t>(); }
|
int32_t value() const { return std::get<int32_t>(data); }
|
||||||
|
|
||||||
int32_t getConstVal() const;
|
int32_t getConstVal() const;
|
||||||
Symbol const *symbolOf() const;
|
Symbol const *symbolOf() const;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
#include <deque>
|
#include <deque>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
@@ -12,8 +13,6 @@
|
|||||||
|
|
||||||
#include "linkdefs.hpp"
|
#include "linkdefs.hpp"
|
||||||
|
|
||||||
extern uint8_t fillByte;
|
|
||||||
|
|
||||||
struct Expression;
|
struct Expression;
|
||||||
struct FileStackNode;
|
struct FileStackNode;
|
||||||
struct Section;
|
struct Section;
|
||||||
@@ -42,6 +41,7 @@ struct Section {
|
|||||||
std::deque<Patch> patches;
|
std::deque<Patch> patches;
|
||||||
std::vector<uint8_t> data;
|
std::vector<uint8_t> data;
|
||||||
|
|
||||||
|
uint32_t getID() const; // ID of the section in the object file (`UINT32_MAX` if none)
|
||||||
bool isSizeKnown() const;
|
bool isSizeKnown() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -51,9 +51,8 @@ struct SectionSpec {
|
|||||||
uint16_t alignOfs;
|
uint16_t alignOfs;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern std::deque<Section> sectionList;
|
size_t sect_CountSections();
|
||||||
extern std::unordered_map<std::string, size_t> sectionMap; // Indexes into `sectionList`
|
void sect_ForEach(void (*callback)(Section &));
|
||||||
extern Section *currentSection;
|
|
||||||
|
|
||||||
Section *sect_FindSectionByName(std::string const &name);
|
Section *sect_FindSectionByName(std::string const &name);
|
||||||
void sect_NewSection(
|
void sect_NewSection(
|
||||||
@@ -76,6 +75,10 @@ void sect_CheckLoadClosed();
|
|||||||
Section *sect_GetSymbolSection();
|
Section *sect_GetSymbolSection();
|
||||||
uint32_t sect_GetSymbolOffset();
|
uint32_t sect_GetSymbolOffset();
|
||||||
uint32_t sect_GetOutputOffset();
|
uint32_t sect_GetOutputOffset();
|
||||||
|
std::optional<uint32_t> sect_GetOutputBank();
|
||||||
|
|
||||||
|
Patch *sect_AddOutputPatch();
|
||||||
|
|
||||||
uint32_t sect_GetAlignBytes(uint8_t alignment, uint16_t offset);
|
uint32_t sect_GetAlignBytes(uint8_t alignment, uint16_t offset);
|
||||||
void sect_AlignPC(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_CheckUnionClosed();
|
||||||
|
|
||||||
void sect_ConstByte(uint8_t byte);
|
void sect_ConstByte(uint8_t byte);
|
||||||
void sect_ByteString(std::vector<int32_t> const &string);
|
void sect_ByteString(std::vector<int32_t> const &str);
|
||||||
void sect_WordString(std::vector<int32_t> const &string);
|
void sect_WordString(std::vector<int32_t> const &str);
|
||||||
void sect_LongString(std::vector<int32_t> const &string);
|
void sect_LongString(std::vector<int32_t> const &str);
|
||||||
void sect_Skip(uint32_t skip, bool ds);
|
void sect_Skip(uint32_t skip, bool ds);
|
||||||
void sect_RelByte(Expression const &expr, uint32_t pcShift);
|
void sect_RelByte(Expression const &expr, uint32_t pcShift);
|
||||||
void sect_RelBytes(uint32_t n, std::vector<Expression> const &exprs);
|
void sect_RelBytes(uint32_t n, std::vector<Expression> const &exprs);
|
||||||
void sect_RelWord(Expression const &expr, uint32_t pcShift);
|
void sect_RelWord(Expression const &expr, uint32_t pcShift);
|
||||||
void sect_RelLong(Expression const &expr, uint32_t pcShift);
|
void sect_RelLong(Expression const &expr, uint32_t pcShift);
|
||||||
void sect_PCRelByte(Expression const &expr, uint32_t pcShift);
|
void sect_PCRelByte(Expression const &expr, uint32_t pcShift);
|
||||||
void sect_BinaryFile(std::string const &name, int32_t startPos);
|
bool sect_BinaryFile(std::string const &name, uint32_t startPos);
|
||||||
void sect_BinaryFileSlice(std::string const &name, int32_t startPos, int32_t length);
|
bool sect_BinaryFileSlice(std::string const &name, uint32_t startPos, uint32_t length);
|
||||||
|
|
||||||
void sect_EndSection();
|
void sect_EndSection();
|
||||||
void sect_PushSection();
|
void sect_PushSection();
|
||||||
void sect_PopSection();
|
void sect_PopSection();
|
||||||
void sect_CheckStack();
|
void sect_CheckStack();
|
||||||
|
|
||||||
|
std::string sect_PushSectionFragmentLiteral();
|
||||||
|
|
||||||
#endif // RGBDS_ASM_SECTION_HPP
|
#endif // RGBDS_ASM_SECTION_HPP
|
||||||
|
|||||||
@@ -24,8 +24,8 @@ enum SymbolType {
|
|||||||
SYM_REF // Forward reference to a label
|
SYM_REF // Forward reference to a label
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Symbol; // For the `sym_IsPC` forward declaration
|
struct Symbol; // Forward declaration for `sym_IsPC`
|
||||||
bool sym_IsPC(Symbol const *sym); // For the inline `getSection` method
|
bool sym_IsPC(Symbol const *sym); // Forward declaration for `getSection`
|
||||||
|
|
||||||
struct Symbol {
|
struct Symbol {
|
||||||
std::string name;
|
std::string name;
|
||||||
|
|||||||
@@ -3,7 +3,16 @@
|
|||||||
#ifndef RGBDS_ASM_WARNING_HPP
|
#ifndef RGBDS_ASM_WARNING_HPP
|
||||||
#define 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 {
|
enum WarningID {
|
||||||
WARNING_ASSERT, // Assertions
|
WARNING_ASSERT, // Assertions
|
||||||
@@ -43,24 +52,7 @@ enum WarningID {
|
|||||||
NB_WARNINGS,
|
NB_WARNINGS,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum WarningAbled { WARNING_DEFAULT, WARNING_ENABLED, WARNING_DISABLED };
|
extern Diagnostics<WarningLevel, WarningID> warnings;
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
// Used to warn the user about problems that don't prevent the generation of
|
// Used to warn the user about problems that don't prevent the generation of
|
||||||
// valid code.
|
// 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,
|
// It is also used when the assembler goes into an invalid state (for example,
|
||||||
// when it fails to allocate memory).
|
// when it fails to allocate memory).
|
||||||
[[gnu::format(printf, 1, 2), noreturn]]
|
[[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
|
// 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
|
// 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)]]
|
[[gnu::format(printf, 1, 2)]]
|
||||||
void error(char const *fmt, ...);
|
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
|
#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; // LCOV_EXCL_LINE
|
|
||||||
}
|
|
||||||
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>
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#define UTF8_ACCEPT 0
|
||||||
|
#define UTF8_REJECT 12
|
||||||
|
|
||||||
uint32_t decode(uint32_t *state, uint32_t *codep, uint8_t byte);
|
uint32_t decode(uint32_t *state, uint32_t *codep, uint8_t byte);
|
||||||
|
|
||||||
#endif // RGBDS_EXTERN_UTF8DECODER_HPP
|
#endif // RGBDS_EXTERN_UTF8DECODER_HPP
|
||||||
|
|||||||
@@ -10,15 +10,13 @@
|
|||||||
#include <streambuf>
|
#include <streambuf>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <variant>
|
||||||
|
|
||||||
#include "either.hpp"
|
|
||||||
#include "helpers.hpp" // assume
|
#include "helpers.hpp" // assume
|
||||||
#include "platform.hpp"
|
#include "platform.hpp"
|
||||||
|
|
||||||
#include "gfx/main.hpp"
|
|
||||||
|
|
||||||
class File {
|
class File {
|
||||||
Either<std::streambuf *, std::filebuf> _file;
|
std::variant<std::streambuf *, std::filebuf> _file;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
File() : _file(nullptr) {}
|
File() : _file(nullptr) {}
|
||||||
@@ -27,17 +25,12 @@ public:
|
|||||||
// Returns `nullptr` on error, and a non-null pointer otherwise.
|
// Returns `nullptr` on error, and a non-null pointer otherwise.
|
||||||
File *open(std::string const &path, std::ios_base::openmode mode) {
|
File *open(std::string const &path, std::ios_base::openmode mode) {
|
||||||
if (path != "-") {
|
if (path != "-") {
|
||||||
_file.emplace<std::filebuf>();
|
return _file.emplace<std::filebuf>().open(path, mode) ? this : nullptr;
|
||||||
return _file.get<std::filebuf>().open(path, mode) ? this : nullptr;
|
|
||||||
} else if (mode & std::ios_base::in) {
|
} else if (mode & std::ios_base::in) {
|
||||||
assume(!(mode & std::ios_base::out));
|
assume(!(mode & std::ios_base::out));
|
||||||
_file.emplace<std::streambuf *>(std::cin.rdbuf());
|
_file.emplace<std::streambuf *>(std::cin.rdbuf());
|
||||||
if (setmode(STDIN_FILENO, (mode & std::ios_base::binary) ? O_BINARY : O_TEXT) == -1) {
|
if (setmode(STDIN_FILENO, (mode & std::ios_base::binary) ? O_BINARY : O_TEXT) == -1) {
|
||||||
fatal(
|
return nullptr;
|
||||||
"Failed to set stdin to %s mode: %s",
|
|
||||||
mode & std::ios_base::binary ? "binary" : "text",
|
|
||||||
strerror(errno)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
assume(mode & std::ios_base::out);
|
assume(mode & std::ios_base::out);
|
||||||
@@ -46,8 +39,8 @@ public:
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
std::streambuf &operator*() {
|
std::streambuf &operator*() {
|
||||||
return _file.holds<std::filebuf>() ? _file.get<std::filebuf>()
|
return std::holds_alternative<std::filebuf>(_file) ? std::get<std::filebuf>(_file)
|
||||||
: *_file.get<std::streambuf *>();
|
: *std::get<std::streambuf *>(_file);
|
||||||
}
|
}
|
||||||
std::streambuf const &operator*() const {
|
std::streambuf const &operator*() const {
|
||||||
// The non-`const` version does not perform any modifications, so it's okay.
|
// The non-`const` version does not perform any modifications, so it's okay.
|
||||||
@@ -60,9 +53,9 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
char const *c_str(std::string const &path) const {
|
char const *c_str(std::string const &path) const {
|
||||||
return _file.holds<std::filebuf>() ? path.c_str()
|
return std::holds_alternative<std::filebuf>(_file) ? path.c_str()
|
||||||
: _file.get<std::streambuf *>() == std::cin.rdbuf() ? "<stdin>"
|
: std::get<std::streambuf *>(_file) == std::cin.rdbuf() ? "<stdin>"
|
||||||
: "<stdout>";
|
: "<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
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef RGBDS_GFX_PROTO_PALETTE_HPP
|
#ifndef RGBDS_GFX_COLOR_SET_HPP
|
||||||
#define RGBDS_GFX_PROTO_PALETTE_HPP
|
#define RGBDS_GFX_COLOR_SET_HPP
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
class ProtoPalette {
|
class ColorSet {
|
||||||
public:
|
public:
|
||||||
static constexpr size_t capacity = 4;
|
static constexpr size_t capacity = 4;
|
||||||
|
|
||||||
@@ -26,7 +26,7 @@ public:
|
|||||||
WE_BIGGER,
|
WE_BIGGER,
|
||||||
THEY_BIGGER = -1,
|
THEY_BIGGER = -1,
|
||||||
};
|
};
|
||||||
ComparisonResult compare(ProtoPalette const &other) const;
|
ComparisonResult compare(ColorSet const &other) const;
|
||||||
|
|
||||||
size_t size() const;
|
size_t size() const;
|
||||||
bool empty() const;
|
bool empty() const;
|
||||||
@@ -35,4 +35,4 @@ public:
|
|||||||
decltype(_colorIndices)::const_iterator end() const;
|
decltype(_colorIndices)::const_iterator end() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // RGBDS_GFX_PROTO_PALETTE_HPP
|
#endif // RGBDS_GFX_COLOR_SET_HPP
|
||||||
@@ -40,7 +40,10 @@ struct Options {
|
|||||||
uint16_t top;
|
uint16_t top;
|
||||||
uint16_t width;
|
uint16_t width;
|
||||||
uint16_t height;
|
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)
|
} 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
|
std::array<uint16_t, 2> maxNbTiles{UINT16_MAX, 0}; // -N
|
||||||
uint16_t nbPalettes = 8; // -n
|
uint16_t nbPalettes = 8; // -n
|
||||||
std::string output{}; // -o
|
std::string output{}; // -o
|
||||||
@@ -68,6 +71,8 @@ struct Options {
|
|||||||
mutable bool hasTransparentPixels = false;
|
mutable bool hasTransparentPixels = false;
|
||||||
uint8_t maxOpaqueColors() const { return nbColorsPerPal - hasTransparentPixels; }
|
uint8_t maxOpaqueColors() const { return nbColorsPerPal - hasTransparentPixels; }
|
||||||
|
|
||||||
|
uint16_t maxNbColors() const { return nbColorsPerPal * nbPalettes; }
|
||||||
|
|
||||||
uint8_t dmgColors[4] = {};
|
uint8_t dmgColors[4] = {};
|
||||||
uint8_t dmgValue(uint8_t i) const {
|
uint8_t dmgValue(uint8_t i) const {
|
||||||
assume(i < 4);
|
assume(i < 4);
|
||||||
@@ -77,25 +82,6 @@ struct Options {
|
|||||||
|
|
||||||
extern Options options;
|
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 {
|
struct Palette {
|
||||||
// An array of 4 GBC-native (RGB555) colors
|
// An array of 4 GBC-native (RGB555) colors
|
||||||
std::array<uint16_t, 4> colors{UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX};
|
std::array<uint16_t, 4> colors{UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX};
|
||||||
@@ -114,9 +100,9 @@ struct Palette {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Flipping tends to happen fairly often, so take a bite out of dcache to speed it up
|
// 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{};
|
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.
|
// To flip all the bits, we'll flip both nibbles, then each nibble half, etc.
|
||||||
uint16_t byte = i;
|
uint16_t byte = i;
|
||||||
byte = (byte & 0b0000'1111) << 4 | (byte & 0b1111'0000) >> 4;
|
byte = (byte & 0b0000'1111) << 4 | (byte & 0b1111'0000) >> 4;
|
||||||
|
|||||||
@@ -3,16 +3,14 @@
|
|||||||
#ifndef RGBDS_GFX_PAL_PACKING_HPP
|
#ifndef RGBDS_GFX_PAL_PACKING_HPP
|
||||||
#define RGBDS_GFX_PAL_PACKING_HPP
|
#define RGBDS_GFX_PAL_PACKING_HPP
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
#include <tuple>
|
#include <tuple>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "defaultinitvec.hpp"
|
|
||||||
|
|
||||||
struct Palette;
|
struct Palette;
|
||||||
class ProtoPalette;
|
class ColorSet;
|
||||||
|
|
||||||
// Returns which palette each proto-palette maps to, and how many palettes are necessary
|
// Returns which palette each color set maps to, and how many palettes are necessary
|
||||||
std::tuple<DefaultInitVec<size_t>, size_t>
|
std::tuple<std::vector<size_t>, size_t> overloadAndRemove(std::vector<ColorSet> const &colorSets);
|
||||||
overloadAndRemove(std::vector<ProtoPalette> const &protoPalettes);
|
|
||||||
|
|
||||||
#endif // RGBDS_GFX_PAL_PACKING_HPP
|
#endif // RGBDS_GFX_PAL_PACKING_HPP
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <png.h>
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "gfx/rgba.hpp"
|
#include "gfx/rgba.hpp"
|
||||||
@@ -16,13 +15,7 @@ static constexpr size_t NB_COLOR_SLOTS = (1 << (5 * 3)) + 1;
|
|||||||
|
|
||||||
struct Palette;
|
struct Palette;
|
||||||
|
|
||||||
void sortIndexed(
|
void sortIndexed(std::vector<Palette> &palettes, std::vector<Rgba> const &embPal);
|
||||||
std::vector<Palette> &palettes,
|
|
||||||
int palSize,
|
|
||||||
png_color const *palRGB,
|
|
||||||
int palAlphaSize,
|
|
||||||
png_byte *palAlpha
|
|
||||||
);
|
|
||||||
void sortGrayscale(
|
void sortGrayscale(
|
||||||
std::vector<Palette> &palettes, std::array<std::optional<Rgba>, NB_COLOR_SLOTS> const &colors
|
std::vector<Palette> &palettes, std::array<std::optional<Rgba>, NB_COLOR_SLOTS> const &colors
|
||||||
);
|
);
|
||||||
|
|||||||
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,23 +17,25 @@ struct Rgba {
|
|||||||
explicit constexpr Rgba(uint32_t rgba = 0)
|
explicit constexpr Rgba(uint32_t rgba = 0)
|
||||||
: red(rgba >> 24), green(rgba >> 16), blue(rgba >> 8), alpha(rgba) {}
|
: red(rgba >> 24), green(rgba >> 16), blue(rgba >> 8), alpha(rgba) {}
|
||||||
|
|
||||||
static constexpr Rgba fromCGBColor(uint16_t cgbColor) {
|
static constexpr Rgba fromCGBColor(uint16_t color) {
|
||||||
constexpr auto _5to8 = [](uint8_t fiveBpp) -> uint8_t {
|
constexpr auto _5to8 = [](uint8_t channel) -> uint8_t {
|
||||||
fiveBpp &= 0b11111; // For caller's convenience
|
channel &= 0b11111; // For caller's convenience
|
||||||
return fiveBpp << 3 | fiveBpp >> 2;
|
return channel << 3 | channel >> 2;
|
||||||
};
|
};
|
||||||
return {
|
return {
|
||||||
_5to8(cgbColor),
|
_5to8(color),
|
||||||
_5to8(cgbColor >> 5),
|
_5to8(color >> 5),
|
||||||
_5to8(cgbColor >> 10),
|
_5to8(color >> 10),
|
||||||
static_cast<uint8_t>(cgbColor & 0x8000 ? 0x00 : 0xFF),
|
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
|
// Returns this RGBA as a 32-bit number that can be printed in hex (`%08x`) to yield its CSS
|
||||||
// representation
|
// representation
|
||||||
uint32_t toCSS() const {
|
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);
|
return shl(red, 24) | shl(green, 16) | shl(blue, 8) | shl(alpha, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
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
|
#ifdef _MSC_VER
|
||||||
#define assume(x) __assume(x)
|
#define assume(x) __assume(x)
|
||||||
#else
|
#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) \
|
#define assume(x) \
|
||||||
do { \
|
do { \
|
||||||
if (!(x)) { \
|
if (!(x)) { \
|
||||||
@@ -71,7 +71,7 @@ static inline int ctz(unsigned int x) {
|
|||||||
|
|
||||||
while (!(x & 1)) {
|
while (!(x & 1)) {
|
||||||
x >>= 1;
|
x >>= 1;
|
||||||
cnt++;
|
++cnt;
|
||||||
}
|
}
|
||||||
return cnt;
|
return cnt;
|
||||||
}
|
}
|
||||||
@@ -81,7 +81,7 @@ static inline int clz(unsigned int x) {
|
|||||||
|
|
||||||
while (x <= UINT_MAX / 2) {
|
while (x <= UINT_MAX / 2) {
|
||||||
x <<= 1;
|
x <<= 1;
|
||||||
cnt++;
|
++cnt;
|
||||||
}
|
}
|
||||||
return cnt;
|
return cnt;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class EnumSeq {
|
|||||||
return *this;
|
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; }
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,10 +3,6 @@
|
|||||||
#ifndef RGBDS_LINK_ASSIGN_HPP
|
#ifndef RGBDS_LINK_ASSIGN_HPP
|
||||||
#define 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
|
// Assigns all sections a slice of the address space
|
||||||
void assign_AssignSections();
|
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 <stdint.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <variant>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "either.hpp"
|
|
||||||
#include "linkdefs.hpp"
|
#include "linkdefs.hpp"
|
||||||
|
|
||||||
// Variables related to CLI options
|
struct Options {
|
||||||
extern bool isDmgMode;
|
bool isDmgMode; // -d
|
||||||
extern char const *linkerScriptName;
|
char const *mapFileName; // -m
|
||||||
extern char const *mapFileName;
|
bool noSymInMap; // -M
|
||||||
extern bool noSymInMap;
|
char const *symFileName; // -n
|
||||||
extern char const *symFileName;
|
char const *overlayFileName; // -O
|
||||||
extern char const *overlayFileName;
|
char const *outputFileName; // -o
|
||||||
extern char const *outputFileName;
|
uint8_t padValue; // -p
|
||||||
extern uint8_t padValue;
|
bool hasPadValue = false;
|
||||||
extern bool hasPadValue;
|
// Setting these three to 0 disables the functionality
|
||||||
extern uint16_t scrambleROMX;
|
uint16_t scrambleROMX; // -S
|
||||||
extern uint8_t scrambleWRAMX;
|
uint16_t scrambleWRAMX;
|
||||||
extern uint8_t scrambleSRAM;
|
uint16_t scrambleSRAM;
|
||||||
extern bool is32kMode;
|
bool is32kMode; // -t
|
||||||
extern bool beVerbose;
|
bool beVerbose; // -v
|
||||||
extern bool isWRAM0Mode;
|
bool isWRAM0Mode; // -w
|
||||||
extern bool disablePadding;
|
bool disablePadding; // -x
|
||||||
|
};
|
||||||
|
|
||||||
|
extern Options options;
|
||||||
|
|
||||||
// Helper macro for printing verbose-mode messages
|
|
||||||
#define verbosePrint(...) \
|
#define verbosePrint(...) \
|
||||||
do { \
|
do { \
|
||||||
if (beVerbose) { \
|
if (options.beVerbose) { \
|
||||||
fprintf(stderr, __VA_ARGS__); \
|
fprintf(stderr, __VA_ARGS__); \
|
||||||
} \
|
} \
|
||||||
} while (0)
|
} while (0)
|
||||||
|
|
||||||
struct FileStackNode {
|
struct FileStackNode {
|
||||||
FileStackNodeType type;
|
FileStackNodeType type;
|
||||||
Either<
|
std::variant<
|
||||||
|
std::monostate, // Default constructed; `.type` and `.data` must be set manually
|
||||||
std::vector<uint32_t>, // NODE_REPT
|
std::vector<uint32_t>, // NODE_REPT
|
||||||
std::string // NODE_FILE, NODE_MACRO
|
std::string // NODE_FILE, NODE_MACRO
|
||||||
>
|
>
|
||||||
@@ -50,20 +53,13 @@ struct FileStackNode {
|
|||||||
uint32_t lineNo;
|
uint32_t lineNo;
|
||||||
|
|
||||||
// REPT iteration counts since last named node, in reverse depth order
|
// 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> &iters() { return std::get<std::vector<uint32_t>>(data); }
|
||||||
std::vector<uint32_t> const &iters() const { return data.get<std::vector<uint32_t>>(); }
|
std::vector<uint32_t> const &iters() const { return std::get<std::vector<uint32_t>>(data); }
|
||||||
// File name for files, file::macro name for macros
|
// File name for files, file::macro name for macros
|
||||||
std::string &name() { return data.get<std::string>(); }
|
std::string &name() { return std::get<std::string>(data); }
|
||||||
std::string const &name() const { return data.get<std::string>(); }
|
std::string const &name() const { return std::get<std::string>(data); }
|
||||||
|
|
||||||
std::string const &dump(uint32_t curLineNo) const;
|
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
|
#endif // RGBDS_LINK_MAIN_HPP
|
||||||
|
|||||||
@@ -3,6 +3,23 @@
|
|||||||
#ifndef RGBDS_LINK_PATCH_HPP
|
#ifndef RGBDS_LINK_PATCH_HPP
|
||||||
#define 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
|
// Checks all assertions
|
||||||
void patch_CheckAssertions();
|
void patch_CheckAssertions();
|
||||||
|
|
||||||
|
|||||||
@@ -56,15 +56,6 @@ struct Section {
|
|||||||
std::unique_ptr<Section> nextu; // The next "component" of this unionized sect
|
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.
|
// Execute a callback for each section currently registered.
|
||||||
// This is to avoid exposing the data structure in which sections are stored.
|
// This is to avoid exposing the data structure in which sections are stored.
|
||||||
void sect_ForEach(void (*callback)(Section &));
|
void sect_ForEach(void (*callback)(Section &));
|
||||||
|
|||||||
@@ -7,8 +7,8 @@
|
|||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <variant>
|
||||||
|
|
||||||
#include "either.hpp"
|
|
||||||
#include "linkdefs.hpp"
|
#include "linkdefs.hpp"
|
||||||
|
|
||||||
struct FileStackNode;
|
struct FileStackNode;
|
||||||
@@ -27,14 +27,14 @@ struct Symbol {
|
|||||||
ExportLevel type;
|
ExportLevel type;
|
||||||
FileStackNode const *src;
|
FileStackNode const *src;
|
||||||
int32_t lineNo;
|
int32_t lineNo;
|
||||||
Either<
|
std::variant<
|
||||||
int32_t, // Constants just have a numeric value
|
int32_t, // Constants just have a numeric value
|
||||||
Label // Label values refer to an offset within a specific section
|
Label // Label values refer to an offset within a specific section
|
||||||
>
|
>
|
||||||
data;
|
data;
|
||||||
|
|
||||||
Label &label() { return data.get<Label>(); }
|
Label &label() { return std::get<Label>(data); }
|
||||||
Label const &label() const { return data.get<Label>(); }
|
Label const &label() const { return std::get<Label>(data); }
|
||||||
};
|
};
|
||||||
|
|
||||||
void sym_ForEach(void (*callback)(Symbol &));
|
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
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
// platform-specific hacks
|
|
||||||
|
|
||||||
#ifndef RGBDS_PLATFORM_HPP
|
#ifndef RGBDS_PLATFORM_HPP
|
||||||
#define 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,9 +3,23 @@
|
|||||||
#ifndef RGBDS_UTIL_HPP
|
#ifndef RGBDS_UTIL_HPP
|
||||||
#define RGBDS_UTIL_HPP
|
#define RGBDS_UTIL_HPP
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
#include "helpers.hpp"
|
||||||
|
|
||||||
bool startsIdentifier(int c);
|
bool startsIdentifier(int c);
|
||||||
bool continuesIdentifier(int c);
|
bool continuesIdentifier(int c);
|
||||||
|
|
||||||
char const *printChar(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
|
#endif // RGBDS_UTIL_HPP
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
#define PACKAGE_VERSION_MAJOR 0
|
#define PACKAGE_VERSION_MAJOR 0
|
||||||
#define PACKAGE_VERSION_MINOR 9
|
#define PACKAGE_VERSION_MINOR 9
|
||||||
#define PACKAGE_VERSION_PATCH 3
|
#define PACKAGE_VERSION_PATCH 4
|
||||||
|
|
||||||
char const *get_package_version_string();
|
char const *get_package_version_string();
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
.\" SPDX-License-Identifier: MIT
|
.\" SPDX-License-Identifier: MIT
|
||||||
.\"
|
.\"
|
||||||
.Dd June 30, 2025
|
.Dd July 31, 2025
|
||||||
.Dt GBZ80 7
|
.Dt GBZ80 7
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
.\"
|
.\"
|
||||||
.\" SPDX-License-Identifier: MIT
|
.\" SPDX-License-Identifier: MIT
|
||||||
.\"
|
.\"
|
||||||
.Dd June 30, 2025
|
.Dd July 31, 2025
|
||||||
.Dt RGBASM-OLD 5
|
.Dt RGBASM-OLD 5
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
@@ -99,6 +99,23 @@ in RAM sections, allowing labeled space allocations to overlap.
|
|||||||
.Pp
|
.Pp
|
||||||
Instead, use
|
Instead, use
|
||||||
.Ic UNION .
|
.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__
|
.Ss __FILE__ and __LINE__
|
||||||
Deprecated in 0.6.0, removed in 0.7.0.
|
Deprecated in 0.6.0, removed in 0.7.0.
|
||||||
.Pp
|
.Pp
|
||||||
@@ -301,6 +318,11 @@ Supported in ASMotor, removed in RGBDS.
|
|||||||
.Pp
|
.Pp
|
||||||
Instead, use
|
Instead, use
|
||||||
.Ql LD HL, SP + e8 .
|
.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
|
.Ss rgbasm -i
|
||||||
Deprecated in 0.6.0, removed in 0.8.0.
|
Deprecated in 0.6.0, removed in 0.8.0.
|
||||||
.Pp
|
.Pp
|
||||||
@@ -356,6 +378,36 @@ because $16384$ turns = $16384 tau$ radians = $32768 pi$ radians, and $sin ( 327
|
|||||||
.EQ
|
.EQ
|
||||||
delim off
|
delim off
|
||||||
.EN
|
.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
|
.Ss ** operator associativity
|
||||||
Changed in 0.9.0.
|
Changed in 0.9.0.
|
||||||
.Pp
|
.Pp
|
||||||
|
|||||||
47
man/rgbasm.1
47
man/rgbasm.1
@@ -1,6 +1,6 @@
|
|||||||
.\" SPDX-License-Identifier: MIT
|
.\" SPDX-License-Identifier: MIT
|
||||||
.\"
|
.\"
|
||||||
.Dd June 30, 2025
|
.Dd July 31, 2025
|
||||||
.Dt RGBASM 1
|
.Dt RGBASM 1
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
@@ -15,6 +15,7 @@
|
|||||||
.Op Fl I Ar path
|
.Op Fl I Ar path
|
||||||
.Op Fl M Ar depend_file
|
.Op Fl M Ar depend_file
|
||||||
.Op Fl MG
|
.Op Fl MG
|
||||||
|
.Op Fl MC
|
||||||
.Op Fl MP
|
.Op Fl MP
|
||||||
.Op Fl MT Ar target_file
|
.Op Fl MT Ar target_file
|
||||||
.Op Fl MQ Ar target_file
|
.Op Fl MQ Ar target_file
|
||||||
@@ -99,11 +100,12 @@ Add a new
|
|||||||
.Dq include path ;
|
.Dq include path ;
|
||||||
.Ar path
|
.Ar path
|
||||||
must point to a directory.
|
must point to a directory.
|
||||||
When a
|
When any
|
||||||
.Ic INCLUDE
|
.Ic INCLUDE
|
||||||
.Pq including the implicit one from Fl P
|
.Pq including the implicit one from Fl P ,
|
||||||
|
.Ic INCBIN ,
|
||||||
or
|
or
|
||||||
.Ic INCBIN
|
.Ic READFILE
|
||||||
is attempted,
|
is attempted,
|
||||||
.Nm
|
.Nm
|
||||||
first looks up the provided path from its working directory; if this fails, it tries again from each of the
|
first looks up the provided path from its working directory; if this fails, it tries again from each of the
|
||||||
@@ -119,17 +121,33 @@ To be used in conjunction with
|
|||||||
.Fl M .
|
.Fl M .
|
||||||
This makes
|
This makes
|
||||||
.Nm
|
.Nm
|
||||||
assume that missing files are auto-generated: when
|
assume that missing files are auto-generated: when any
|
||||||
.Ic INCLUDE
|
.Ic INCLUDE
|
||||||
.Pq including the implicit one from Fl P
|
.Pq including the implicit one from Fl P ,
|
||||||
|
.Ic INCBIN ,
|
||||||
or
|
or
|
||||||
.Ic INCBIN
|
.Ic READFILE
|
||||||
is attempted on a non-existent file, it is added as a dependency, then
|
is attempted on a non-existent file, it is added as a dependency, then
|
||||||
.Nm
|
.Nm
|
||||||
exits normally instead of erroring out.
|
exits normally or continues processing (depending on whether
|
||||||
This feature is used in automatic updating of makefiles.
|
.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
|
.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
|
This prevents
|
||||||
.Xr make 1
|
.Xr make 1
|
||||||
from erroring out when dependency files are deleted.
|
from erroring out when dependency files are deleted.
|
||||||
@@ -158,6 +176,7 @@ This acts as if a
|
|||||||
.Ql Ic INCLUDE Qq Ar include_file
|
.Ql Ic INCLUDE Qq Ar include_file
|
||||||
was read before the input
|
was read before the input
|
||||||
.Ar asmfile .
|
.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
|
.It Fl p Ar pad_value , Fl \-pad-value Ar pad_value
|
||||||
Use this as the value for
|
Use this as the value for
|
||||||
.Ic DS
|
.Ic DS
|
||||||
@@ -269,7 +288,7 @@ Enables literally every warning.
|
|||||||
.El
|
.El
|
||||||
.Pp
|
.Pp
|
||||||
The following warnings are actual warning flags; with each description, the corresponding warning flag is included.
|
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
|
.Fl Wcharmap-redef
|
||||||
enables the warning that
|
enables the warning that
|
||||||
.Fl Wno-charmap-redef
|
.Fl Wno-charmap-redef
|
||||||
@@ -336,11 +355,7 @@ Block comments cannot be nested, so the first
|
|||||||
.Ql */
|
.Ql */
|
||||||
will end the whole comment.
|
will end the whole comment.
|
||||||
.It Fl Wno-obsolete
|
.It Fl Wno-obsolete
|
||||||
Warn when obsolete constructs such as the
|
Warn when obsolete features are encountered, which have been deprecated and may later be removed.
|
||||||
.Ic _PI
|
|
||||||
constant or
|
|
||||||
.Ic PRINTT
|
|
||||||
directive are encountered.
|
|
||||||
.It Fl Wnumeric-string=
|
.It Fl Wnumeric-string=
|
||||||
Warn when a multi-character string is treated as a number.
|
Warn when a multi-character string is treated as a number.
|
||||||
.Fl Wnumeric-string=0
|
.Fl Wnumeric-string=0
|
||||||
|
|||||||
146
man/rgbasm.5
146
man/rgbasm.5
@@ -2,7 +2,7 @@
|
|||||||
.\"
|
.\"
|
||||||
.\" SPDX-License-Identifier: MIT
|
.\" SPDX-License-Identifier: MIT
|
||||||
.\"
|
.\"
|
||||||
.Dd June 30, 2025
|
.Dd July 31, 2025
|
||||||
.Dt RGBASM 5
|
.Dt RGBASM 5
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
@@ -280,7 +280,7 @@ There are a number of numeric formats.
|
|||||||
.It Binary Ta Li % , 0b , 0B Ta 01
|
.It Binary Ta Li % , 0b , 0B Ta 01
|
||||||
.It Fixed-point Ta none Ta 01234.56789
|
.It Fixed-point Ta none Ta 01234.56789
|
||||||
.It Precise fixed-point Ta none Ta 12.34q8
|
.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
|
.It Game Boy graphics Ta Li \` Ta 0123
|
||||||
.El
|
.El
|
||||||
.Pp
|
.Pp
|
||||||
@@ -293,11 +293,14 @@ or
|
|||||||
The "character constant" form yields the value the character maps to in the current charmap.
|
The "character constant" form yields the value the character maps to in the current charmap.
|
||||||
For example, by default
|
For example, by default
|
||||||
.Pq refer to Xr ascii 7
|
.Pq refer to Xr ascii 7
|
||||||
.Sq \(dqA\(dq
|
.Sq 'A'
|
||||||
yields 65.
|
yields 65.
|
||||||
|
A character constant must represent a single value, so it cannot include multiple characters, or characters which map to multiple values.
|
||||||
See
|
See
|
||||||
.Sx Character maps
|
.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
|
.Pp
|
||||||
The last one, Game Boy graphics, is quite interesting and useful.
|
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.
|
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"
|
.Bl -column -offset indent "Sequence"
|
||||||
.It Sy Sequence Ta Sy Meaning
|
.It Sy Sequence Ta Sy Meaning
|
||||||
.It Ql \e\e Ta Backslash Pq escapes the escape character itself
|
.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 Open curly brace Pq does not start interpolation
|
||||||
.It Ql \e} Ta Close curly brace Pq does not end interpolation
|
.It Ql \e} Ta Close curly brace Pq does not end interpolation
|
||||||
.It Ql \en Ta Newline Pq ASCII $0A
|
.It Ql \en Ta Newline Pq ASCII $0A
|
||||||
@@ -595,6 +599,7 @@ with its corresponding argument in
|
|||||||
.Pq So %% Sc is replaced by the So % Sc character .
|
.Pq So %% Sc is replaced by the So % Sc character .
|
||||||
.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 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 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
|
.El
|
||||||
.Pp
|
.Pp
|
||||||
The following functions operate on string expressions, but return integers.
|
The following functions operate on string expressions, but return integers.
|
||||||
@@ -604,6 +609,8 @@ The following functions operate on string expressions, but return integers.
|
|||||||
.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 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 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 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 INCHARMAP str Ta Returns 1 if Ar str No has an entry in the current charmap, or 0 otherwise.
|
||||||
.It Fn CHARLEN str Ta Returns the number of charmap entries in Ar str No with the current charmap.
|
.It Fn CHARLEN str Ta Returns the number of charmap entries in Ar str No with the current charmap.
|
||||||
.It Fn 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 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.
|
||||||
@@ -710,6 +717,9 @@ If
|
|||||||
.Ar arg
|
.Ar arg
|
||||||
is a section type keyword, it returns the size of that section type.
|
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.
|
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
|
.It Fn STARTOF arg Ta If
|
||||||
.Ar arg
|
.Ar arg
|
||||||
is a string, this function returns the starting address of the section named
|
is a string, this function returns the starting address of the section named
|
||||||
@@ -1112,6 +1122,120 @@ first, followed by the one from
|
|||||||
and the one from
|
and the one from
|
||||||
.Ql bar.o
|
.Ql bar.o
|
||||||
last.
|
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
|
.Sh SYMBOLS
|
||||||
RGBDS supports several types of symbols:
|
RGBDS supports several types of symbols:
|
||||||
.Bl -hang
|
.Bl -hang
|
||||||
@@ -1691,10 +1815,9 @@ Use
|
|||||||
.Ic INCBIN
|
.Ic INCBIN
|
||||||
to include a raw binary file as it is.
|
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
|
If the file isn't found in the current directory, the include-path list passed to
|
||||||
.Xr rgbasm 1
|
.Xr rgbasm 1 Ap s
|
||||||
(see the
|
|
||||||
.Fl I
|
.Fl I
|
||||||
option) on the command line will be searched.
|
option on the command line will be searched.
|
||||||
.Bd -literal -offset indent
|
.Bd -literal -offset indent
|
||||||
INCBIN "titlepic.bin"
|
INCBIN "titlepic.bin"
|
||||||
INCBIN "sprites/hero.bin"
|
INCBIN "sprites/hero.bin"
|
||||||
@@ -2247,11 +2370,10 @@ block, all of them but the first one are ignored.
|
|||||||
Use
|
Use
|
||||||
.Ic INCLUDE
|
.Ic INCLUDE
|
||||||
to process another assembler file and then return to the current file when done.
|
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
|
.Fl I
|
||||||
option in
|
option on the command line will be searched.
|
||||||
.Xr rgbasm 1 )
|
|
||||||
will be searched.
|
|
||||||
You may nest
|
You may nest
|
||||||
.Ic INCLUDE
|
.Ic INCLUDE
|
||||||
calls infinitely (or until you run out of memory, whichever comes first).
|
calls infinitely (or until you run out of memory, whichever comes first).
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
.\" SPDX-License-Identifier: MIT
|
.\" SPDX-License-Identifier: MIT
|
||||||
.\"
|
.\"
|
||||||
.Dd June 30, 2025
|
.Dd July 31, 2025
|
||||||
.Dt RGBDS 5
|
.Dt RGBDS 5
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
.\" SPDX-License-Identifier: MIT
|
.\" SPDX-License-Identifier: MIT
|
||||||
.\"
|
.\"
|
||||||
.Dd June 30, 2025
|
.Dd July 31, 2025
|
||||||
.Dt RGBDS 7
|
.Dt RGBDS 7
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
@@ -57,7 +57,7 @@ to this day.
|
|||||||
.It
|
.It
|
||||||
2015-01-18:
|
2015-01-18:
|
||||||
.An stag019
|
.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
|
.It
|
||||||
2016-09-05: RGBGFX is integrated into Bentley's repository.
|
2016-09-05: RGBGFX is integrated into Bentley's repository.
|
||||||
.It
|
.It
|
||||||
|
|||||||
96
man/rgbfix.1
96
man/rgbfix.1
@@ -1,6 +1,6 @@
|
|||||||
.\" SPDX-License-Identifier: MIT
|
.\" SPDX-License-Identifier: MIT
|
||||||
.\"
|
.\"
|
||||||
.Dd June 30, 2025
|
.Dd July 31, 2025
|
||||||
.Dt RGBFIX 1
|
.Dt RGBFIX 1
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
.Nd Game Boy header utility and checksum fixer
|
.Nd Game Boy header utility and checksum fixer
|
||||||
.Sh SYNOPSIS
|
.Sh SYNOPSIS
|
||||||
.Nm
|
.Nm
|
||||||
.Op Fl hjOsVv
|
.Op Fl hjOsVvw
|
||||||
.Op Fl C | c
|
.Op Fl C | c
|
||||||
.Op Fl f Ar fix_spec
|
.Op Fl f Ar fix_spec
|
||||||
.Op Fl i Ar game_id
|
.Op Fl i Ar game_id
|
||||||
@@ -21,6 +21,7 @@
|
|||||||
.Op Fl p Ar pad_value
|
.Op Fl p Ar pad_value
|
||||||
.Op Fl r Ar ram_size
|
.Op Fl r Ar ram_size
|
||||||
.Op Fl t Ar title_str
|
.Op Fl t Ar title_str
|
||||||
|
.Op Fl W Ar warning
|
||||||
.Op Ar
|
.Op Ar
|
||||||
.Sh DESCRIPTION
|
.Sh DESCRIPTION
|
||||||
The
|
The
|
||||||
@@ -98,7 +99,7 @@ Print help text for the program and exit.
|
|||||||
Set the game ID string
|
Set the game ID string
|
||||||
.Pq Ad 0x13F Ns \(en Ns Ad 0x142
|
.Pq Ad 0x13F Ns \(en Ns Ad 0x142
|
||||||
to a given string.
|
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
|
.It Fl j , Fl \-non-japanese
|
||||||
Set the non-Japanese region flag
|
Set the non-Japanese region flag
|
||||||
.Pq Ad 0x14A
|
.Pq Ad 0x14A
|
||||||
@@ -107,7 +108,7 @@ to 0x01.
|
|||||||
Set the new licensee string
|
Set the new licensee string
|
||||||
.Pq Ad 0x144 Ns \(en Ns Ad 0x145
|
.Pq Ad 0x144 Ns \(en Ns Ad 0x145
|
||||||
to a given string.
|
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
|
.It Fl L Ar logo_file , Fl \-logo Ar logo_file
|
||||||
Specify a logo file to use instead of the official Nintendo logo.
|
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.
|
The file must be 48 bytes of 1bpp tile data; the source image should be 48 pixels wide and 8 pixels tall.
|
||||||
@@ -124,6 +125,8 @@ to a given value from 0 to 0xFF.
|
|||||||
This value may also be an MBC name.
|
This value may also be an MBC name.
|
||||||
The list of accepted names can be obtained by passing
|
The list of accepted names can be obtained by passing
|
||||||
.Ql Cm help
|
.Ql Cm help
|
||||||
|
or
|
||||||
|
.Ql Cm list
|
||||||
as the argument.
|
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.
|
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
|
There are special considerations to take for the TPP1 mapper; see the
|
||||||
@@ -134,7 +137,8 @@ Set the ROM version
|
|||||||
.Pq Ad 0x14C
|
.Pq Ad 0x14C
|
||||||
to a given value from 0 to 0xFF.
|
to a given value from 0 to 0xFF.
|
||||||
.It Fl O , Fl \-overwrite
|
.It Fl O , Fl \-overwrite
|
||||||
Allow overwriting different non-zero bytes in the header without a warning being emitted.
|
Alias for
|
||||||
|
.Fl Wno-overwrite .
|
||||||
.It Fl o Ar out_file , Fl \-output Ar out_file
|
.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.
|
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.
|
If not specified, the input files are modified in-place, or written to standard output if read from standard input.
|
||||||
@@ -154,15 +158,14 @@ to a given value from 0 to 0xFF.
|
|||||||
Set the SGB flag
|
Set the SGB flag
|
||||||
.Pq Ad 0x146
|
.Pq Ad 0x146
|
||||||
to 0x03.
|
to 0x03.
|
||||||
This flag will be ignored by the SGB unless the old licensee code is 0x33!
|
This flag will be ignored by the SGB unless the old licensee code
|
||||||
If this is given as well as
|
.Pq Fl -l
|
||||||
.Fl l ,
|
is 0x33!
|
||||||
but is not set to 0x33, a warning will be printed.
|
|
||||||
.It Fl t Ar title , Fl \-title Ar title
|
.It Fl t Ar title , Fl \-title Ar title
|
||||||
Set the title string
|
Set the title string
|
||||||
.Pq Ad 0x134 Ns \(en Ns Ad 0x143
|
.Pq Ad 0x134 Ns \(en Ns Ad 0x143
|
||||||
to a given string.
|
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
|
The max length is 11 characters if the game ID
|
||||||
.Pq Fl i
|
.Pq Fl i
|
||||||
is specified, 15 characters if the CGB flag
|
is specified, 15 characters if the CGB flag
|
||||||
@@ -175,6 +178,77 @@ Print the version of the program and exit.
|
|||||||
.It Fl v , Fl \-validate
|
.It Fl v , Fl \-validate
|
||||||
Equivalent to
|
Equivalent to
|
||||||
.Fl f Cm lhg .
|
.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
|
.El
|
||||||
.Sh EXAMPLES
|
.Sh EXAMPLES
|
||||||
Most values in the ROM header do not matter to the actual console, and most are seldom useful anyway.
|
Most values in the ROM header do not matter to the actual console, and most are seldom useful anyway.
|
||||||
@@ -228,7 +302,7 @@ Therefore,
|
|||||||
.Nm
|
.Nm
|
||||||
will ignore the
|
will ignore the
|
||||||
.Ql RAM
|
.Ql RAM
|
||||||
feature on a TPP1 mapper with a warning.
|
feature on a TPP1 mapper.
|
||||||
.Ss Special considerations
|
.Ss Special considerations
|
||||||
TPP1 overwrites the byte at
|
TPP1 overwrites the byte at
|
||||||
.Ad 0x14A ,
|
.Ad 0x14A ,
|
||||||
|
|||||||
87
man/rgbgfx.1
87
man/rgbgfx.1
@@ -2,7 +2,7 @@
|
|||||||
.\"
|
.\"
|
||||||
.\" SPDX-License-Identifier: MIT
|
.\" SPDX-License-Identifier: MIT
|
||||||
.\"
|
.\"
|
||||||
.Dd June 30, 2025
|
.Dd July 31, 2025
|
||||||
.Dt RGBGFX 1
|
.Dt RGBGFX 1
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
.Nd Game Boy graphics converter
|
.Nd Game Boy graphics converter
|
||||||
.Sh SYNOPSIS
|
.Sh SYNOPSIS
|
||||||
.Nm
|
.Nm
|
||||||
.Op Fl CmhOuVXYZ
|
.Op Fl CmhOuVwXYZ
|
||||||
.Op Fl v Op Fl v No ...
|
.Op Fl v Op Fl v No ...
|
||||||
.Op Fl a Ar attrmap | Fl A
|
.Op Fl a Ar attrmap | Fl A
|
||||||
.Op Fl b Ar base_ids
|
.Op Fl b Ar base_ids
|
||||||
@@ -18,6 +18,7 @@
|
|||||||
.Op Fl d Ar depth
|
.Op Fl d Ar depth
|
||||||
.Op Fl i Ar input_tiles
|
.Op Fl i Ar input_tiles
|
||||||
.Op Fl L Ar slice
|
.Op Fl L Ar slice
|
||||||
|
.Op Fl l Ar base_pal
|
||||||
.Op Fl N Ar nb_tiles
|
.Op Fl N Ar nb_tiles
|
||||||
.Op Fl n Ar nb_pals
|
.Op Fl n Ar nb_pals
|
||||||
.Op Fl o Ar out_file
|
.Op Fl o Ar out_file
|
||||||
@@ -26,6 +27,7 @@
|
|||||||
.Op Fl r Ar width
|
.Op Fl r Ar width
|
||||||
.Op Fl s Ar nb_colors
|
.Op Fl s Ar nb_colors
|
||||||
.Op Fl t Ar tilemap | Fl T
|
.Op Fl t Ar tilemap | Fl T
|
||||||
|
.Op Fl W Ar warning
|
||||||
.Op Fl x Ar quantity
|
.Op Fl x Ar quantity
|
||||||
.Ar file
|
.Ar file
|
||||||
.Sh DESCRIPTION
|
.Sh DESCRIPTION
|
||||||
@@ -261,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.
|
The second number pair specifies how many tiles to process horizontally and vertically, respectively.
|
||||||
.Pp
|
.Pp
|
||||||
.Fl L Sy is ignored in reverse mode , No no padding is inserted .
|
.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
|
.It Fl m , Fl \-mirror-tiles
|
||||||
Deduplicate tiles that are horizontally and/or vertically symmetrical mirror images of each other.
|
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.
|
Only one of each unique tile will be saved in the tile data file, with mirror images counting as duplicates.
|
||||||
@@ -387,6 +394,17 @@ Some internal debug printing is enabled.
|
|||||||
The verbosity level does not go past 6.
|
The verbosity level does not go past 6.
|
||||||
.Pp
|
.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.
|
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
|
.It Fl X , Fl \-mirror-x
|
||||||
Deduplicate tiles that are horizontally symmetrical mirror images of each other across the X axis.
|
Deduplicate tiles that are horizontally symmetrical mirror images of each other across the X axis.
|
||||||
Implies
|
Implies
|
||||||
@@ -498,6 +516,9 @@ Useful to force several images to share the same palette.
|
|||||||
Plaintext lines of hexadecimal colors in
|
Plaintext lines of hexadecimal colors in
|
||||||
.Ql rrggbb
|
.Ql rrggbb
|
||||||
format.
|
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
|
.It Cm psp
|
||||||
.Lk https://www.selapa.net/swatches/colors/fileformats.php#psp_pal Paint Shop Pro palette .
|
.Lk https://www.selapa.net/swatches/colors/fileformats.php#psp_pal Paint Shop Pro palette .
|
||||||
.El
|
.El
|
||||||
@@ -702,6 +723,68 @@ assume that tiles were not deduplicated, and should be laid out in the order the
|
|||||||
.Nm
|
.Nm
|
||||||
assumes that no tiles were mirrored.
|
assumes that no tiles were mirrored.
|
||||||
.El
|
.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
|
.Sh EXAMPLES
|
||||||
The following will only validate the
|
The following will only validate the
|
||||||
.Ql tileset.png
|
.Ql tileset.png
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
.\" SPDX-License-Identifier: MIT
|
.\" SPDX-License-Identifier: MIT
|
||||||
.\"
|
.\"
|
||||||
.Dd June 30, 2025
|
.Dd July 31, 2025
|
||||||
.Dt RGBLINK 1
|
.Dt RGBLINK 1
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
@@ -16,6 +16,7 @@
|
|||||||
.Op Fl o Ar out_file
|
.Op Fl o Ar out_file
|
||||||
.Op Fl p Ar pad_value
|
.Op Fl p Ar pad_value
|
||||||
.Op Fl S Ar spec
|
.Op Fl S Ar spec
|
||||||
|
.Op Fl W Ar warning
|
||||||
.Ar
|
.Ar
|
||||||
.Sh DESCRIPTION
|
.Sh DESCRIPTION
|
||||||
The
|
The
|
||||||
@@ -114,6 +115,15 @@ Useful for ROMs that fit in 32 KiB.
|
|||||||
Print the version of the program and exit.
|
Print the version of the program and exit.
|
||||||
.It Fl v , Fl \-verbose
|
.It Fl v , Fl \-verbose
|
||||||
Verbose: enable printing more information to standard error.
|
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
|
.It Fl w , Fl \-wramx
|
||||||
Expand the WRAM0 section size from 4 KiB to the full 8 KiB assigned to WRAM.
|
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.
|
WRAMX sections that are fixed to a bank other than 1 become errors, other WRAMX sections are treated as WRAM0.
|
||||||
@@ -176,6 +186,84 @@ as
|
|||||||
.Ic WRAMX
|
.Ic WRAMX
|
||||||
sections will be treated as
|
sections will be treated as
|
||||||
.Ic WRAM0 .
|
.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
|
.Sh EXAMPLES
|
||||||
All you need for a basic ROM is an object file, which can be made into a ROM image like so:
|
All you need for a basic ROM is an object file, which can be made into a ROM image like so:
|
||||||
.Pp
|
.Pp
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
.\" SPDX-License-Identifier: MIT
|
.\" SPDX-License-Identifier: MIT
|
||||||
.\"
|
.\"
|
||||||
.Dd June 30, 2025
|
.Dd July 31, 2025
|
||||||
.Dt RGBLINK 5
|
.Dt RGBLINK 5
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
@@ -24,18 +24,20 @@ They are simply ignored.
|
|||||||
.Pp
|
.Pp
|
||||||
Keywords are composed of letters and digits (but they can't start with a digit); they are all case-insensitive.
|
Keywords are composed of letters and digits (but they can't start with a digit); they are all case-insensitive.
|
||||||
.Pp
|
.Pp
|
||||||
Numbers can be written in decimal format, or in binary using the
|
Numbers can be written in a number of formats.
|
||||||
.Ql %
|
.Bl -column -offset indent "Hexadecimal" "Possible prefixes"
|
||||||
prefix, or in hexadecimal using the
|
.It Sy Format type Ta Sy Possible prefixes Ta Sy Accepted characters
|
||||||
.Ql $
|
.It Decimal Ta none Ta 0123456789
|
||||||
prefix (hexadecimal digits are case-insensitive).
|
.It Hexadecimal Ta Li $ , 0x , 0X Ta 0123456789ABCDEF
|
||||||
Note that unlike
|
.It Octal Ta Li & , 0o , 0O Ta 01234567
|
||||||
.Xr rgbasm 5 ,
|
.It Binary Ta Li % , 0b , 0B Ta 01
|
||||||
an octal
|
.El
|
||||||
.Ql &
|
.Pp
|
||||||
prefix is not supported, nor are
|
Underscores are also accepted in numbers, except at the beginning of one.
|
||||||
.Ql _
|
This can be useful for grouping digits, like
|
||||||
digit separators.
|
.Ql 1_234
|
||||||
|
or
|
||||||
|
.Ql $ff_80 .
|
||||||
.Pp
|
.Pp
|
||||||
Strings begin with a double quote, and end at the next (non-escaped) double quote.
|
Strings begin with a double quote, and end at the next (non-escaped) double quote.
|
||||||
Strings must not contain literal newline characters.
|
Strings must not contain literal newline characters.
|
||||||
@@ -46,8 +48,9 @@ are supported, specifically
|
|||||||
.Ql \e" ,
|
.Ql \e" ,
|
||||||
.Ql \en ,
|
.Ql \en ,
|
||||||
.Ql \er ,
|
.Ql \er ,
|
||||||
|
.Ql \et ,
|
||||||
and
|
and
|
||||||
.Ql \et .
|
.Ql \e0 .
|
||||||
Other backslash escape sequences in
|
Other backslash escape sequences in
|
||||||
.Xr rgbasm 5
|
.Xr rgbasm 5
|
||||||
are only relevant to assembly code and do not apply in linker scripts.
|
are only relevant to assembly code and do not apply in linker scripts.
|
||||||
|
|||||||
@@ -3,8 +3,9 @@
|
|||||||
configure_file(version.cpp _version.cpp ESCAPE_QUOTES)
|
configure_file(version.cpp _version.cpp ESCAPE_QUOTES)
|
||||||
|
|
||||||
set(common_src
|
set(common_src
|
||||||
"error.cpp"
|
|
||||||
"extern/getopt.cpp"
|
"extern/getopt.cpp"
|
||||||
|
"diagnostics.cpp"
|
||||||
|
"usage.cpp"
|
||||||
"_version.cpp"
|
"_version.cpp"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -34,6 +35,7 @@ BISON_TARGET(LINKER_SCRIPT_PARSER "link/script.y"
|
|||||||
|
|
||||||
set(rgbasm_src
|
set(rgbasm_src
|
||||||
"${BISON_ASM_PARSER_OUTPUT_SOURCE}"
|
"${BISON_ASM_PARSER_OUTPUT_SOURCE}"
|
||||||
|
"asm/actions.cpp"
|
||||||
"asm/charmap.cpp"
|
"asm/charmap.cpp"
|
||||||
"asm/fixpoint.cpp"
|
"asm/fixpoint.cpp"
|
||||||
"asm/format.cpp"
|
"asm/format.cpp"
|
||||||
@@ -53,26 +55,11 @@ set(rgbasm_src
|
|||||||
"util.cpp"
|
"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
|
set(rgblink_src
|
||||||
"${BISON_LINKER_SCRIPT_PARSER_OUTPUT_SOURCE}"
|
"${BISON_LINKER_SCRIPT_PARSER_OUTPUT_SOURCE}"
|
||||||
"link/assign.cpp"
|
"link/assign.cpp"
|
||||||
|
"link/lexer.cpp"
|
||||||
|
"link/layout.cpp"
|
||||||
"link/main.cpp"
|
"link/main.cpp"
|
||||||
"link/object.cpp"
|
"link/object.cpp"
|
||||||
"link/output.cpp"
|
"link/output.cpp"
|
||||||
@@ -80,12 +67,33 @@ set(rgblink_src
|
|||||||
"link/sdas_obj.cpp"
|
"link/sdas_obj.cpp"
|
||||||
"link/section.cpp"
|
"link/section.cpp"
|
||||||
"link/symbol.cpp"
|
"link/symbol.cpp"
|
||||||
|
"link/warning.cpp"
|
||||||
"extern/utf8decoder.cpp"
|
"extern/utf8decoder.cpp"
|
||||||
"linkdefs.cpp"
|
"linkdefs.cpp"
|
||||||
"opmath.cpp"
|
"opmath.cpp"
|
||||||
"util.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")
|
foreach(PROG "asm" "fix" "gfx" "link")
|
||||||
add_executable(rgb${PROG}
|
add_executable(rgb${PROG}
|
||||||
${rgb${PROG}_src}
|
${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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -31,36 +31,34 @@ struct CharmapNode {
|
|||||||
struct Charmap {
|
struct Charmap {
|
||||||
std::string name;
|
std::string name;
|
||||||
std::vector<CharmapNode> nodes; // first node is reserved for the root node
|
std::vector<CharmapNode> nodes; // first node is reserved for the root node
|
||||||
|
};
|
||||||
|
|
||||||
// Traverse the trie depth-first to derive the character mappings in definition order
|
// Traverse the trie depth-first to derive the character mappings in definition order
|
||||||
template<typename F>
|
template<typename F>
|
||||||
bool forEachChar(F callback) const {
|
bool forEachChar(Charmap const &charmap, F callback) {
|
||||||
// clang-format off: nested initializers
|
// clang-format off: nested initializers
|
||||||
for (std::stack<std::pair<size_t, std::string>> prefixes({{0, ""}}); !prefixes.empty();) {
|
for (std::stack<std::pair<size_t, std::string>> prefixes({{0, ""}}); !prefixes.empty();) {
|
||||||
// clang-format on
|
// clang-format on
|
||||||
auto [nodeIdx, mapping] = std::move(prefixes.top());
|
auto [nodeIdx, mapping] = std::move(prefixes.top());
|
||||||
prefixes.pop();
|
prefixes.pop();
|
||||||
CharmapNode const &node = nodes[nodeIdx];
|
CharmapNode const &node = charmap.nodes[nodeIdx];
|
||||||
if (node.isTerminal()) {
|
if (node.isTerminal() && !callback(nodeIdx, mapping)) {
|
||||||
if (!callback(nodeIdx, mapping)) {
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
for (unsigned c = 0; c < std::size(node.next); ++c) {
|
||||||
}
|
if (size_t nextIdx = node.next[c]; nextIdx) {
|
||||||
for (unsigned c = 0; c < std::size(node.next); c++) {
|
prefixes.push({nextIdx, mapping + static_cast<char>(c)});
|
||||||
if (size_t nextIdx = node.next[c]; nextIdx) {
|
|
||||||
prefixes.push({nextIdx, mapping + static_cast<char>(c)});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
};
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
static std::deque<Charmap> charmapList;
|
static std::deque<Charmap> charmapList;
|
||||||
static std::unordered_map<std::string, size_t> charmapMap; // Indexes into `charmapList`
|
static std::unordered_map<std::string, size_t> charmapMap; // Indexes into `charmapList`
|
||||||
|
|
||||||
static Charmap *currentCharmap;
|
static Charmap *currentCharmap;
|
||||||
std::stack<Charmap *> charmapStack;
|
static std::stack<Charmap *> charmapStack;
|
||||||
|
|
||||||
bool charmap_ForEach(
|
bool charmap_ForEach(
|
||||||
void (*mapFunc)(std::string const &),
|
void (*mapFunc)(std::string const &),
|
||||||
@@ -68,7 +66,7 @@ bool charmap_ForEach(
|
|||||||
) {
|
) {
|
||||||
for (Charmap const &charmap : charmapList) {
|
for (Charmap const &charmap : charmapList) {
|
||||||
std::map<size_t, std::string> mappings;
|
std::map<size_t, std::string> mappings;
|
||||||
charmap.forEachChar([&mappings](size_t nodeIdx, std::string const &mapping) {
|
forEachChar(charmap, [&mappings](size_t nodeIdx, std::string const &mapping) {
|
||||||
mappings[nodeIdx] = mapping;
|
mappings[nodeIdx] = mapping;
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
@@ -86,14 +84,14 @@ void charmap_New(std::string const &name, std::string const *baseName) {
|
|||||||
|
|
||||||
if (baseName != nullptr) {
|
if (baseName != nullptr) {
|
||||||
if (auto search = charmapMap.find(*baseName); search == charmapMap.end()) {
|
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 {
|
} else {
|
||||||
baseIdx = search->second;
|
baseIdx = search->second;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (charmapMap.find(name) != charmapMap.end()) {
|
if (charmapMap.find(name) != charmapMap.end()) {
|
||||||
error("Charmap '%s' already exists\n", name.c_str());
|
error("Charmap '%s' already exists", name.c_str());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,7 +112,7 @@ void charmap_New(std::string const &name, std::string const *baseName) {
|
|||||||
|
|
||||||
void charmap_Set(std::string const &name) {
|
void charmap_Set(std::string const &name) {
|
||||||
if (auto search = charmapMap.find(name); search == charmapMap.end()) {
|
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 {
|
} else {
|
||||||
currentCharmap = &charmapList[search->second];
|
currentCharmap = &charmapList[search->second];
|
||||||
}
|
}
|
||||||
@@ -126,7 +124,7 @@ void charmap_Push() {
|
|||||||
|
|
||||||
void charmap_Pop() {
|
void charmap_Pop() {
|
||||||
if (charmapStack.empty()) {
|
if (charmapStack.empty()) {
|
||||||
error("No entries in the charmap stack\n");
|
error("No entries in the charmap stack");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,13 +134,13 @@ void charmap_Pop() {
|
|||||||
|
|
||||||
void charmap_CheckStack() {
|
void charmap_CheckStack() {
|
||||||
if (!charmapStack.empty()) {
|
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) {
|
void charmap_Add(std::string const &mapping, std::vector<int32_t> &&value) {
|
||||||
if (mapping.empty()) {
|
if (mapping.empty()) {
|
||||||
error("Cannot map an empty string\n");
|
error("Cannot map an empty string");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,7 +166,7 @@ void charmap_Add(std::string const &mapping, std::vector<int32_t> &&value) {
|
|||||||
CharmapNode &node = charmap.nodes[nodeIdx];
|
CharmapNode &node = charmap.nodes[nodeIdx];
|
||||||
|
|
||||||
if (node.isTerminal()) {
|
if (node.isTerminal()) {
|
||||||
warning(WARNING_CHARMAP_REDEF, "Overriding charmap mapping\n");
|
warning(WARNING_CHARMAP_REDEF, "Overriding charmap mapping");
|
||||||
}
|
}
|
||||||
|
|
||||||
std::swap(node.value, value);
|
std::swap(node.value, value);
|
||||||
@@ -240,13 +238,13 @@ size_t charmap_ConvertNext(std::string_view &input, std::vector<int32_t> *output
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
inputIdx++; // Consume that char
|
++inputIdx; // Consume that char
|
||||||
|
|
||||||
if (charmap.nodes[nodeIdx].isTerminal()) {
|
if (charmap.nodes[nodeIdx].isTerminal()) {
|
||||||
matchIdx = nodeIdx; // This node matches, register it
|
matchIdx = nodeIdx; // This node matches, register it
|
||||||
rewindDistance = 0; // If no longer match is found, rewind here
|
rewindDistance = 0; // If no longer match is found, rewind here
|
||||||
} else {
|
} else {
|
||||||
rewindDistance++;
|
++rewindDistance;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -266,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
|
} else if (inputIdx < input.length()) { // No match found, but there is some input left
|
||||||
size_t codepointLen = 0;
|
size_t codepointLen = 0;
|
||||||
// This will write the codepoint's value to `output`, little-endian
|
// This will write the codepoint's value to `output`, little-endian
|
||||||
for (uint32_t state = 0, codepoint = 0; inputIdx + codepointLen < input.length();) {
|
for (uint32_t state = UTF8_ACCEPT, codepoint = 0;
|
||||||
if (decode(&state, &codepoint, input[inputIdx + codepointLen]) == 1) {
|
inputIdx + codepointLen < input.length();) {
|
||||||
error("Input string is not valid UTF-8\n");
|
if (decode(&state, &codepoint, input[inputIdx + codepointLen]) == UTF8_REJECT) {
|
||||||
|
error("Input string is not valid UTF-8");
|
||||||
codepointLen = 1;
|
codepointLen = 1;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
codepointLen++;
|
++codepointLen;
|
||||||
if (state == 0) {
|
if (state == UTF8_ACCEPT) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -286,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
|
// Warn if this character is not mapped but any others are
|
||||||
if (int firstChar = input[inputIdx]; charmap.nodes.size() > 1) {
|
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) {
|
} else if (charmap.name != DEFAULT_CHARMAP_NAME) {
|
||||||
warning(
|
warning(
|
||||||
WARNING_UNMAPPED_CHAR_2,
|
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)
|
printChar(firstChar)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -306,7 +305,7 @@ 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) {
|
std::string charmap_Reverse(std::vector<int32_t> const &value, bool &unique) {
|
||||||
Charmap const &charmap = *currentCharmap;
|
Charmap const &charmap = *currentCharmap;
|
||||||
std::string revMapping;
|
std::string revMapping;
|
||||||
unique = charmap.forEachChar([&](size_t nodeIdx, std::string const &mapping) {
|
unique = forEachChar(charmap, [&](size_t nodeIdx, std::string const &mapping) {
|
||||||
if (charmap.nodes[nodeIdx].value == value) {
|
if (charmap.nodes[nodeIdx].value == value) {
|
||||||
if (revMapping.empty()) {
|
if (revMapping.empty()) {
|
||||||
revMapping = mapping;
|
revMapping = mapping;
|
||||||
|
|||||||
@@ -10,12 +10,6 @@
|
|||||||
#define M_PI 3.14159265358979323846
|
#define M_PI 3.14159265358979323846
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
uint8_t fixPrecision;
|
|
||||||
|
|
||||||
uint8_t fix_Precision() {
|
|
||||||
return fixPrecision;
|
|
||||||
}
|
|
||||||
|
|
||||||
static double fix2double(int32_t i, int32_t q) {
|
static double fix2double(int32_t i, int32_t q) {
|
||||||
return i / pow(2.0, q);
|
return i / pow(2.0, q);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include "asm/fixpoint.hpp"
|
#include "asm/fixpoint.hpp"
|
||||||
|
#include "asm/main.hpp" // options
|
||||||
#include "asm/warning.hpp"
|
#include "asm/warning.hpp"
|
||||||
|
|
||||||
void FormatSpec::useCharacter(int c) {
|
void FormatSpec::useCharacter(int c) {
|
||||||
@@ -162,19 +163,19 @@ void FormatSpec::appendString(std::string &str, std::string const &value) const
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (sign) {
|
if (sign) {
|
||||||
error("Formatting string with sign flag '%c'\n", sign);
|
error("Formatting string with sign flag '%c'", sign);
|
||||||
}
|
}
|
||||||
if (padZero) {
|
if (padZero) {
|
||||||
error("Formatting string with padding flag '0'\n");
|
error("Formatting string with padding flag '0'");
|
||||||
}
|
}
|
||||||
if (hasFrac) {
|
if (hasFrac) {
|
||||||
error("Formatting string with fractional width\n");
|
error("Formatting string with fractional width");
|
||||||
}
|
}
|
||||||
if (hasPrec) {
|
if (hasPrec) {
|
||||||
error("Formatting string with fractional precision\n");
|
error("Formatting string with fractional precision");
|
||||||
}
|
}
|
||||||
if (useType != 's') {
|
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;
|
std::string useValue = exact ? escapeString(value) : value;
|
||||||
@@ -203,16 +204,16 @@ void FormatSpec::appendNumber(std::string &str, uint32_t value) const {
|
|||||||
|
|
||||||
if (useType != 'X' && useType != 'x' && useType != 'b' && useType != 'o' && useType != 'f'
|
if (useType != 'X' && useType != 'x' && useType != 'b' && useType != 'o' && useType != 'f'
|
||||||
&& useExact) {
|
&& useExact) {
|
||||||
error("Formatting type '%c' with exact flag '#'\n", useType);
|
error("Formatting type '%c' with exact flag '#'", useType);
|
||||||
}
|
}
|
||||||
if (useType != 'f' && hasFrac) {
|
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) {
|
if (useType != 'f' && hasPrec) {
|
||||||
error("Formatting type '%c' with fractional precision\n", useType);
|
error("Formatting type '%c' with fractional precision", useType);
|
||||||
}
|
}
|
||||||
if (useType == 's') {
|
if (useType == 's') {
|
||||||
error("Formatting number as type 's'\n");
|
error("Formatting number as type 's'");
|
||||||
}
|
}
|
||||||
|
|
||||||
char signChar = sign; // 0 or ' ' or '+'
|
char signChar = sign; // 0 or ' ' or '+'
|
||||||
@@ -254,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)
|
// Default fractional width (C++'s is 6 for "%f"; here 5 is enough for Q16.16)
|
||||||
size_t useFracWidth = hasFrac ? fracWidth : 5;
|
size_t useFracWidth = hasFrac ? fracWidth : 5;
|
||||||
if (useFracWidth > 255) {
|
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;
|
useFracWidth = 255;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t defaultPrec = fix_Precision();
|
size_t defaultPrec = options.fixPrecision;
|
||||||
size_t usePrec = hasPrec ? precision : defaultPrec;
|
size_t usePrec = hasPrec ? precision : defaultPrec;
|
||||||
if (usePrec < 1 || usePrec > 31) {
|
if (usePrec < 1 || usePrec > 31) {
|
||||||
error(
|
error(
|
||||||
"Fixed-point constant precision %zu invalid, defaulting to %zu\n",
|
"Fixed-point constant precision %zu invalid, defaulting to %zu",
|
||||||
usePrec,
|
usePrec,
|
||||||
defaultPrec
|
defaultPrec
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include "asm/fstack.hpp"
|
#include "asm/fstack.hpp"
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
#include <deque>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
@@ -10,7 +11,7 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
#include "error.hpp"
|
#include "diagnostics.hpp"
|
||||||
#include "helpers.hpp"
|
#include "helpers.hpp"
|
||||||
#include "linkdefs.hpp"
|
#include "linkdefs.hpp"
|
||||||
#include "platform.hpp" // S_ISDIR (stat macro)
|
#include "platform.hpp" // S_ISDIR (stat macro)
|
||||||
@@ -41,23 +42,29 @@ struct Context {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static std::stack<Context> contextStack;
|
static std::stack<Context> contextStack;
|
||||||
size_t maxRecursionDepth;
|
|
||||||
|
|
||||||
// The first include path for `fstk_FindFile` to try is none at all
|
// 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 {
|
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
|
assume(parent); // REPT nodes use their parent's name
|
||||||
std::string const &lastName = parent->dump(lineNo);
|
std::string const &lastName = parent->dump(lineNo);
|
||||||
fputs(" -> ", stderr);
|
fputs(" -> ", stderr);
|
||||||
fputs(lastName.c_str(), stderr);
|
fputs(lastName.c_str(), stderr);
|
||||||
std::vector<uint32_t> const &nodeIters = iters();
|
fputs(reptChain().c_str(), stderr);
|
||||||
for (uint32_t i = nodeIters.size(); i--;) {
|
|
||||||
fprintf(stderr, "::REPT~%" PRIu32, nodeIters[i]);
|
|
||||||
}
|
|
||||||
fprintf(stderr, "(%" PRIu32 ")", curLineNo);
|
fprintf(stderr, "(%" PRIu32 ")", curLineNo);
|
||||||
return lastName;
|
return lastName;
|
||||||
} else {
|
} else {
|
||||||
@@ -72,12 +79,13 @@ std::string const &FileStackNode::dump(uint32_t curLineNo) const {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void fstk_DumpCurrent() {
|
bool fstk_DumpCurrent() {
|
||||||
if (lexer_AtTopLevel()) {
|
if (lexer_AtTopLevel()) {
|
||||||
fputs("at top level", stderr);
|
return false;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
assume(!contextStack.empty());
|
||||||
contextStack.top().fileInfo->dump(lexer_GetLineNo());
|
contextStack.top().fileInfo->dump(lexer_GetLineNo());
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<FileStackNode> fstk_GetFileStack() {
|
std::shared_ptr<FileStackNode> fstk_GetFileStack() {
|
||||||
@@ -114,16 +122,9 @@ void fstk_AddIncludePath(std::string const &path) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void fstk_SetPreIncludeFile(std::string const &path) {
|
void fstk_AddPreIncludeFile(std::string const &path) {
|
||||||
if (!preIncludeName.empty()) {
|
preIncludeNames.emplace_front(path);
|
||||||
warnx("Overriding pre-included filename %s", preIncludeName.c_str());
|
verbosePrint("Pre-included filename %s\n", path.c_str()); // LCOV_EXCL_LINE
|
||||||
}
|
|
||||||
preIncludeName = path;
|
|
||||||
// LCOV_EXCL_START
|
|
||||||
if (verbose) {
|
|
||||||
printf("Pre-included filename %s\n", preIncludeName.c_str());
|
|
||||||
}
|
|
||||||
// LCOV_EXCL_STOP
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool isValidFilePath(std::string const &path) {
|
static bool isValidFilePath(std::string const &path) {
|
||||||
@@ -132,11 +133,9 @@ static bool isValidFilePath(std::string const &path) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void printDep(std::string const &path) {
|
static void printDep(std::string const &path) {
|
||||||
if (dependFile) {
|
options.printDep(path);
|
||||||
fprintf(dependFile, "%s: %s\n", targetFileName.c_str(), path.c_str());
|
if (options.dependFile && options.generatePhonyDeps && isValidFilePath(path)) {
|
||||||
if (generatePhonyDeps && isValidFilePath(path)) {
|
fprintf(options.dependFile, "%s:\n", path.c_str());
|
||||||
fprintf(dependFile, "%s:\n", path.c_str());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,7 +148,7 @@ std::optional<std::string> fstk_FindFile(std::string const &path) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
errno = ENOENT;
|
errno = ENOENT;
|
||||||
if (generatedMissingIncludes) {
|
if (options.missingIncludeState != INC_ERROR) {
|
||||||
printDep(path);
|
printDep(path);
|
||||||
}
|
}
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
@@ -159,8 +158,8 @@ bool yywrap() {
|
|||||||
uint32_t ifDepth = lexer_GetIFDepth();
|
uint32_t ifDepth = lexer_GetIFDepth();
|
||||||
|
|
||||||
if (ifDepth != 0) {
|
if (ifDepth != 0) {
|
||||||
fatalerror(
|
fatal(
|
||||||
"Ended block with %" PRIu32 " unterminated IF construct%s\n",
|
"Ended block with %" PRIu32 " unterminated IF construct%s",
|
||||||
ifDepth,
|
ifDepth,
|
||||||
ifDepth == 1 ? "" : "s"
|
ifDepth == 1 ? "" : "s"
|
||||||
);
|
);
|
||||||
@@ -188,11 +187,11 @@ bool yywrap() {
|
|||||||
|
|
||||||
// This error message will refer to the current iteration
|
// This error message will refer to the current iteration
|
||||||
if (sym->type != SYM_VAR) {
|
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
|
// Advance to the next iteration
|
||||||
fileInfoIters.front()++;
|
++fileInfoIters.front();
|
||||||
// If this wasn't the last iteration, wrap instead of popping
|
// If this wasn't the last iteration, wrap instead of popping
|
||||||
if (fileInfoIters.front() <= context.nbReptIters) {
|
if (fileInfoIters.front() <= context.nbReptIters) {
|
||||||
lexer_RestartRept(context.fileInfo->lineNo);
|
lexer_RestartRept(context.fileInfo->lineNo);
|
||||||
@@ -210,12 +209,12 @@ bool yywrap() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void checkRecursionDepth() {
|
static void checkRecursionDepth() {
|
||||||
if (contextStack.size() > maxRecursionDepth) {
|
if (contextStack.size() > options.maxRecursionDepth) {
|
||||||
fatalerror("Recursion limit (%zu) exceeded\n", 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();
|
checkRecursionDepth();
|
||||||
|
|
||||||
std::shared_ptr<std::string> uniqueIDStr = nullptr;
|
std::shared_ptr<std::string> uniqueIDStr = nullptr;
|
||||||
@@ -237,7 +236,7 @@ static bool newFileContext(std::string const &filePath, bool updateStateNow) {
|
|||||||
.macroArgs = macroArgs,
|
.macroArgs = macroArgs,
|
||||||
});
|
});
|
||||||
|
|
||||||
return context.lexerState.setFileAsNextState(filePath, updateStateNow);
|
context.lexerState.setFileAsNextState(filePath, updateStateNow);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void newMacroContext(Symbol const ¯o, std::shared_ptr<MacroArgs> macroArgs) {
|
static void newMacroContext(Symbol const ¯o, std::shared_ptr<MacroArgs> macroArgs) {
|
||||||
@@ -253,11 +252,7 @@ static void newMacroContext(Symbol const ¯o, std::shared_ptr<MacroArgs> macr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (macro.src->type == NODE_REPT) {
|
if (macro.src->type == NODE_REPT) {
|
||||||
std::vector<uint32_t> const &srcIters = macro.src->iters();
|
fileInfoName.append(macro.src->reptChain());
|
||||||
for (uint32_t i = srcIters.size(); i--;) {
|
|
||||||
fileInfoName.append("::REPT~");
|
|
||||||
fileInfoName.append(std::to_string(srcIters[i]));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
fileInfoName.append("::");
|
fileInfoName.append("::");
|
||||||
fileInfoName.append(macro.name);
|
fileInfoName.append(macro.name);
|
||||||
@@ -305,26 +300,34 @@ static Context &newReptContext(int32_t reptLineNo, ContentSpan const &span, uint
|
|||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
void fstk_RunInclude(std::string const &path, bool preInclude) {
|
bool fstk_FileError(std::string const &path, char const *functionName) {
|
||||||
std::optional<std::string> fullPath = fstk_FindFile(path);
|
if (options.missingIncludeState == INC_ERROR) {
|
||||||
|
error("Error opening %s file '%s': %s", functionName, path.c_str(), strerror(errno));
|
||||||
if (!fullPath) {
|
} else {
|
||||||
if (generatedMissingIncludes && !preInclude) {
|
failedOnMissingInclude = true;
|
||||||
// LCOV_EXCL_START
|
// LCOV_EXCL_START
|
||||||
if (verbose) {
|
if (options.missingIncludeState == GEN_EXIT) {
|
||||||
printf("Aborting (-MG) on INCLUDE file '%s' (%s)\n", path.c_str(), strerror(errno));
|
verbosePrint(
|
||||||
}
|
"Aborting (-MG) on %s file '%s' (%s)\n", functionName, path.c_str(), strerror(errno)
|
||||||
// LCOV_EXCL_STOP
|
);
|
||||||
failedOnMissingInclude = true;
|
return true;
|
||||||
} else {
|
|
||||||
error("Unable to open included file '%s': %s\n", path.c_str(), strerror(errno));
|
|
||||||
}
|
}
|
||||||
return;
|
assume(options.missingIncludeState == GEN_CONTINUE);
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (!newFileContext(*fullPath, false)) {
|
bool fstk_FailedOnMissingInclude() {
|
||||||
fatalerror("Failed to set up lexer for file include\n"); // LCOV_EXCL_LINE
|
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) {
|
void fstk_RunMacro(std::string const ¯oName, std::shared_ptr<MacroArgs> macroArgs) {
|
||||||
@@ -332,14 +335,14 @@ void fstk_RunMacro(std::string const ¯oName, std::shared_ptr<MacroArgs> macr
|
|||||||
|
|
||||||
if (!macro) {
|
if (!macro) {
|
||||||
if (sym_IsPurgedExact(macroName)) {
|
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 {
|
} else {
|
||||||
error("Macro \"%s\" not defined\n", macroName.c_str());
|
error("Macro \"%s\" not defined", macroName.c_str());
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (macro->type != SYM_MACRO) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -372,13 +375,11 @@ void fstk_RunFor(
|
|||||||
} else if (step < 0 && stop < start) {
|
} else if (step < 0 && stop < start) {
|
||||||
count = (static_cast<int64_t>(start) - stop - 1) / -static_cast<int64_t>(step) + 1;
|
count = (static_cast<int64_t>(start) - stop - 1) / -static_cast<int64_t>(step) + 1;
|
||||||
} else if (step == 0) {
|
} 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)) {
|
if ((step > 0 && start > stop) || (step < 0 && start < stop)) {
|
||||||
warning(
|
warning(WARNING_BACKWARDS_FOR, "FOR goes backwards from %d to %d by %d", start, stop, step);
|
||||||
WARNING_BACKWARDS_FOR, "FOR goes backwards from %d to %d by %d\n", start, stop, step
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (count == 0) {
|
if (count == 0) {
|
||||||
@@ -394,7 +395,7 @@ void fstk_RunFor(
|
|||||||
|
|
||||||
bool fstk_Break() {
|
bool fstk_Break() {
|
||||||
if (contextStack.top().fileInfo->type != NODE_REPT) {
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -404,19 +405,19 @@ bool fstk_Break() {
|
|||||||
|
|
||||||
void fstk_NewRecursionDepth(size_t newDepth) {
|
void fstk_NewRecursionDepth(size_t newDepth) {
|
||||||
if (contextStack.size() > newDepth + 1) {
|
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) {
|
void fstk_Init(std::string const &mainPath) {
|
||||||
if (!newFileContext(mainPath, true)) {
|
newFileContext(mainPath, true);
|
||||||
fatalerror("Failed to open main file\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
maxRecursionDepth = maxDepth;
|
for (std::string const &name : preIncludeNames) {
|
||||||
|
if (std::optional<std::string> fullPath = fstk_FindFile(name); fullPath) {
|
||||||
if (!preIncludeName.empty()) {
|
newFileContext(*fullPath, false);
|
||||||
fstk_RunInclude(preIncludeName, true);
|
} else {
|
||||||
|
error("Error reading pre-included file '%s': %s", name.c_str(), strerror(errno));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1461
src/asm/lexer.cpp
1461
src/asm/lexer.cpp
File diff suppressed because it is too large
Load Diff
@@ -29,15 +29,15 @@ std::shared_ptr<std::string> MacroArgs::getAllArgs() const {
|
|||||||
|
|
||||||
size_t len = 0;
|
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
|
len += args[i]->length() + 1; // 1 for comma
|
||||||
}
|
}
|
||||||
|
|
||||||
auto str = std::make_shared<std::string>();
|
auto str = std::make_shared<std::string>();
|
||||||
str->reserve(len + 1); // 1 for comma
|
str->reserve(len + 1); // 1 for comma
|
||||||
|
|
||||||
for (uint32_t i = shift; i < nbArgs; i++) {
|
for (uint32_t i = shift; i < nbArgs; ++i) {
|
||||||
auto const &arg = args[i];
|
std::shared_ptr<std::string> const &arg = args[i];
|
||||||
|
|
||||||
str->append(*arg);
|
str->append(*arg);
|
||||||
|
|
||||||
@@ -52,7 +52,7 @@ std::shared_ptr<std::string> MacroArgs::getAllArgs() const {
|
|||||||
|
|
||||||
void MacroArgs::appendArg(std::shared_ptr<std::string> arg) {
|
void MacroArgs::appendArg(std::shared_ptr<std::string> arg) {
|
||||||
if (arg->empty()) {
|
if (arg->empty()) {
|
||||||
warning(WARNING_EMPTY_MACRO_ARG, "Empty macro argument\n");
|
warning(WARNING_EMPTY_MACRO_ARG, "Empty macro argument");
|
||||||
}
|
}
|
||||||
args.push_back(arg);
|
args.push_back(arg);
|
||||||
}
|
}
|
||||||
@@ -60,10 +60,10 @@ void MacroArgs::appendArg(std::shared_ptr<std::string> arg) {
|
|||||||
void MacroArgs::shiftArgs(int32_t count) {
|
void MacroArgs::shiftArgs(int32_t count) {
|
||||||
if (size_t nbArgs = args.size();
|
if (size_t nbArgs = args.size();
|
||||||
count > 0 && (static_cast<uint32_t>(count) > nbArgs || shift > nbArgs - count)) {
|
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;
|
shift = nbArgs;
|
||||||
} else if (count < 0 && shift < static_cast<uint32_t>(-count)) {
|
} 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;
|
shift = 0;
|
||||||
} else {
|
} else {
|
||||||
shift += count;
|
shift += count;
|
||||||
|
|||||||
322
src/asm/main.cpp
322
src/asm/main.cpp
@@ -3,16 +3,20 @@
|
|||||||
#include "asm/main.hpp"
|
#include "asm/main.hpp"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <errno.h>
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <stdarg.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
|
|
||||||
#include "error.hpp"
|
#include "diagnostics.hpp"
|
||||||
#include "extern/getopt.hpp"
|
#include "extern/getopt.hpp"
|
||||||
#include "helpers.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 "version.hpp"
|
||||||
|
|
||||||
#include "asm/charmap.hpp"
|
#include "asm/charmap.hpp"
|
||||||
@@ -22,13 +26,7 @@
|
|||||||
#include "asm/symbol.hpp"
|
#include "asm/symbol.hpp"
|
||||||
#include "asm/warning.hpp"
|
#include "asm/warning.hpp"
|
||||||
|
|
||||||
FILE *dependFile = nullptr; // -M
|
Options options;
|
||||||
bool generatedMissingIncludes = false; // -MG
|
|
||||||
bool generatePhonyDeps = false; // -MP
|
|
||||||
std::string targetFileName; // -MQ, -MT
|
|
||||||
bool failedOnMissingInclude = false;
|
|
||||||
bool verbose = false; // -v
|
|
||||||
bool warnings = true; // -w
|
|
||||||
|
|
||||||
// Escapes Make-special chars from a string
|
// Escapes Make-special chars from a string
|
||||||
static std::string make_escape(std::string &str) {
|
static std::string make_escape(std::string &str) {
|
||||||
@@ -66,14 +64,14 @@ static option const longopts[] = {
|
|||||||
{"define", required_argument, nullptr, 'D'},
|
{"define", required_argument, nullptr, 'D'},
|
||||||
{"export-all", no_argument, nullptr, 'E'},
|
{"export-all", no_argument, nullptr, 'E'},
|
||||||
{"gfx-chars", required_argument, nullptr, 'g'},
|
{"gfx-chars", required_argument, nullptr, 'g'},
|
||||||
|
{"help", no_argument, nullptr, 'h'},
|
||||||
{"include", required_argument, nullptr, 'I'},
|
{"include", required_argument, nullptr, 'I'},
|
||||||
{"dependfile", required_argument, nullptr, 'M'},
|
{"dependfile", required_argument, nullptr, 'M'},
|
||||||
|
{"MC", no_argument, &depType, 'C'},
|
||||||
{"MG", no_argument, &depType, 'G'},
|
{"MG", no_argument, &depType, 'G'},
|
||||||
{"help", no_argument, nullptr, 'h'},
|
|
||||||
{"MP", no_argument, &depType, 'P'},
|
{"MP", no_argument, &depType, 'P'},
|
||||||
{"MT", required_argument, &depType, 'T'},
|
|
||||||
{"warning", required_argument, nullptr, 'W'},
|
|
||||||
{"MQ", required_argument, &depType, 'Q'},
|
{"MQ", required_argument, &depType, 'Q'},
|
||||||
|
{"MT", required_argument, &depType, 'T'},
|
||||||
{"output", required_argument, nullptr, 'o'},
|
{"output", required_argument, nullptr, 'o'},
|
||||||
{"preinclude", required_argument, nullptr, 'P'},
|
{"preinclude", required_argument, nullptr, 'P'},
|
||||||
{"pad-value", required_argument, nullptr, 'p'},
|
{"pad-value", required_argument, nullptr, 'p'},
|
||||||
@@ -87,61 +85,89 @@ static option const longopts[] = {
|
|||||||
{nullptr, no_argument, nullptr, 0 }
|
{nullptr, no_argument, nullptr, 0 }
|
||||||
};
|
};
|
||||||
|
|
||||||
// LCOV_EXCL_START
|
// clang-format off: long string literal
|
||||||
static void printUsage() {
|
static Usage usage(
|
||||||
fputs(
|
"Usage: rgbasm [-EhVvw] [-b chars] [-D name[=value]] [-g chars] [-I path]\n"
|
||||||
"Usage: rgbasm [-EhVvw] [-b chars] [-D name[=value]] [-g chars] [-I path]\n"
|
" [-M depend_file] [-MC] [-MG] [-MP] [-MT target_file] [-MQ target_file]\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"
|
||||||
" [-o out_file] [-P include_file] [-p pad_value] [-Q precision]\n"
|
" [-r depth] [-s features:state_file] [-W warning] [-X max_errors]\n"
|
||||||
" [-r depth] [-s features:state_file] [-W warning] [-X max_errors]\n"
|
" <file>\n"
|
||||||
" <file>\n"
|
"Useful options:\n"
|
||||||
"Useful options:\n"
|
" -E, --export-all export all labels\n"
|
||||||
" -E, --export-all export all labels\n"
|
" -M, --dependfile <path> set the output dependency file\n"
|
||||||
" -M, --dependfile <path> set the output dependency file\n"
|
" -o, --output <path> set the output object file\n"
|
||||||
" -o, --output <path> set the output object file\n"
|
" -p, --pad-value <value> set the value to use for `ds'\n"
|
||||||
" -p, --pad-value <value> set the value to use for `ds'\n"
|
" -s, --state <features>:<path> set an output state file\n"
|
||||||
" -s, --state <features>:<path> set an output state file\n"
|
" -V, --version print RGBASM version and exit\n"
|
||||||
" -V, --version print RGBASM version and exit\n"
|
" -W, --warning <warning> enable or disable warnings\n"
|
||||||
" -W, --warning <warning> enable or disable warnings\n"
|
"\n"
|
||||||
"\n"
|
"For help, use `man rgbasm' or go to https://rgbds.gbdev.io/docs/\n"
|
||||||
"For help, use `man rgbasm' or go to https://rgbds.gbdev.io/docs/\n",
|
);
|
||||||
stderr
|
// 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;
|
||||||
}
|
}
|
||||||
// LCOV_EXCL_STOP
|
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
time_t now = time(nullptr);
|
|
||||||
// Support SOURCE_DATE_EPOCH for reproducible builds
|
// Support SOURCE_DATE_EPOCH for reproducible builds
|
||||||
// https://reproducible-builds.org/docs/source-date-epoch/
|
// https://reproducible-builds.org/docs/source-date-epoch/
|
||||||
|
time_t now = time(nullptr);
|
||||||
if (char const *sourceDateEpoch = getenv("SOURCE_DATE_EPOCH"); sourceDateEpoch) {
|
if (char const *sourceDateEpoch = getenv("SOURCE_DATE_EPOCH"); sourceDateEpoch) {
|
||||||
now = static_cast<time_t>(strtoul(sourceDateEpoch, nullptr, 0));
|
now = static_cast<time_t>(strtoul(sourceDateEpoch, nullptr, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
Defer closeDependFile{[&] {
|
|
||||||
if (dependFile) {
|
|
||||||
fclose(dependFile);
|
|
||||||
}
|
|
||||||
}};
|
|
||||||
|
|
||||||
// Perform some init for below
|
|
||||||
sym_Init(now);
|
sym_Init(now);
|
||||||
|
|
||||||
// Set defaults
|
// Maximum of 100 errors only applies if rgbasm is printing errors to a terminal
|
||||||
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.
|
|
||||||
if (isatty(STDERR_FILENO)) {
|
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;) {
|
for (int ch; (ch = musl_getopt_long_only(argc, argv, optstring, longopts, nullptr)) != -1;) {
|
||||||
switch (ch) {
|
switch (ch) {
|
||||||
char *endptr;
|
char *endptr;
|
||||||
@@ -150,7 +176,7 @@ int main(int argc, char *argv[]) {
|
|||||||
if (strlen(musl_optarg) == 2) {
|
if (strlen(musl_optarg) == 2) {
|
||||||
opt_B(musl_optarg);
|
opt_B(musl_optarg);
|
||||||
} else {
|
} else {
|
||||||
errx("Must specify exactly 2 characters for option 'b'");
|
fatal("Must specify exactly 2 characters for option 'b'");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -173,42 +199,45 @@ int main(int argc, char *argv[]) {
|
|||||||
if (strlen(musl_optarg) == 4) {
|
if (strlen(musl_optarg) == 4) {
|
||||||
opt_G(musl_optarg);
|
opt_G(musl_optarg);
|
||||||
} else {
|
} else {
|
||||||
errx("Must specify exactly 4 characters for option 'g'");
|
fatal("Must specify exactly 4 characters for option 'g'");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'h':
|
case 'h':
|
||||||
// LCOV_EXCL_START
|
usage.printAndExit(0); // LCOV_EXCL_LINE
|
||||||
printUsage();
|
|
||||||
exit(0);
|
|
||||||
// LCOV_EXCL_STOP
|
|
||||||
|
|
||||||
case 'I':
|
case 'I':
|
||||||
fstk_AddIncludePath(musl_optarg);
|
fstk_AddIncludePath(musl_optarg);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'M':
|
case 'M':
|
||||||
if (dependFile) {
|
if (options.dependFile) {
|
||||||
warnx("Overriding dependfile %s", dependFileName);
|
warnx("Overriding dependfile %s", dependFileName);
|
||||||
}
|
}
|
||||||
if (strcmp("-", musl_optarg)) {
|
if (strcmp("-", musl_optarg)) {
|
||||||
dependFile = fopen(musl_optarg, "w");
|
options.dependFile = fopen(musl_optarg, "w");
|
||||||
dependFileName = musl_optarg;
|
dependFileName = musl_optarg;
|
||||||
} else {
|
} else {
|
||||||
dependFile = stdout;
|
options.dependFile = stdout;
|
||||||
dependFileName = "<stdout>";
|
dependFileName = "<stdout>";
|
||||||
}
|
}
|
||||||
if (dependFile == nullptr) {
|
if (options.dependFile == nullptr) {
|
||||||
err("Failed to open dependfile \"%s\"", dependFileName); // LCOV_EXCL_LINE
|
// LCOV_EXCL_START
|
||||||
|
fatal("Failed to open dependfile \"%s\": %s", dependFileName, strerror(errno));
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'o':
|
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;
|
break;
|
||||||
|
|
||||||
case 'P':
|
case 'P':
|
||||||
fstk_SetPreIncludeFile(musl_optarg);
|
fstk_AddPreIncludeFile(musl_optarg);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
unsigned long padByte;
|
unsigned long padByte;
|
||||||
@@ -216,41 +245,40 @@ int main(int argc, char *argv[]) {
|
|||||||
padByte = strtoul(musl_optarg, &endptr, 0);
|
padByte = strtoul(musl_optarg, &endptr, 0);
|
||||||
|
|
||||||
if (musl_optarg[0] == '\0' || *endptr != '\0') {
|
if (musl_optarg[0] == '\0' || *endptr != '\0') {
|
||||||
errx("Invalid argument for option 'p'");
|
fatal("Invalid argument for option 'p'");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (padByte > 0xFF) {
|
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);
|
opt_P(padByte);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
unsigned long precision;
|
case 'Q': {
|
||||||
char const *precisionArg;
|
char const *precisionArg = musl_optarg;
|
||||||
case 'Q':
|
|
||||||
precisionArg = musl_optarg;
|
|
||||||
if (precisionArg[0] == '.') {
|
if (precisionArg[0] == '.') {
|
||||||
precisionArg++;
|
++precisionArg;
|
||||||
}
|
}
|
||||||
precision = strtoul(precisionArg, &endptr, 0);
|
unsigned long precision = strtoul(precisionArg, &endptr, 0);
|
||||||
|
|
||||||
if (musl_optarg[0] == '\0' || *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) {
|
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);
|
opt_Q(precision);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case 'r':
|
case 'r':
|
||||||
maxDepth = strtoul(musl_optarg, &endptr, 0);
|
options.maxRecursionDepth = strtoul(musl_optarg, &endptr, 0);
|
||||||
|
|
||||||
if (musl_optarg[0] == '\0' || *endptr != '\0') {
|
if (musl_optarg[0] == '\0' || *endptr != '\0') {
|
||||||
errx("Invalid argument for option 'r'");
|
fatal("Invalid argument for option 'r'");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -258,59 +286,16 @@ int main(int argc, char *argv[]) {
|
|||||||
// Split "<features>:<name>" so `musl_optarg` is "<features>" and `name` is "<name>"
|
// Split "<features>:<name>" so `musl_optarg` is "<features>" and `name` is "<name>"
|
||||||
char *name = strchr(musl_optarg, ':');
|
char *name = strchr(musl_optarg, ':');
|
||||||
if (!name) {
|
if (!name) {
|
||||||
errx("Invalid argument for option 's'");
|
fatal("Invalid argument for option 's'");
|
||||||
}
|
}
|
||||||
*name++ = '\0';
|
*name++ = '\0';
|
||||||
|
|
||||||
std::vector<StateFeature> features;
|
std::vector<StateFeature> features = parseStateFeatures(musl_optarg);
|
||||||
for (char *feature = musl_optarg; feature;) {
|
|
||||||
// Split "<feature>,<rest>" so `feature` is "<feature>" and `next` is "<rest>"
|
|
||||||
char *next = strchr(feature, ',');
|
|
||||||
if (next) {
|
|
||||||
*next++ = '\0';
|
|
||||||
}
|
|
||||||
// Trim whitespace from the beginning of `feature`...
|
|
||||||
feature += strspn(feature, " \t");
|
|
||||||
// ...and from the end
|
|
||||||
if (char *end = strpbrk(feature, " \t"); end) {
|
|
||||||
*end = '\0';
|
|
||||||
}
|
|
||||||
// A feature must be specified
|
|
||||||
if (*feature == '\0') {
|
|
||||||
errx("Empty feature for option 's'");
|
|
||||||
}
|
|
||||||
// Parse the `feature` and update the `features` list
|
|
||||||
if (!strcasecmp(feature, "all")) {
|
|
||||||
if (!features.empty()) {
|
|
||||||
warnx("Redundant feature before \"%s\" for option 's'", feature);
|
|
||||||
}
|
|
||||||
features.assign({STATE_EQU, STATE_VAR, STATE_EQUS, STATE_CHAR, STATE_MACRO});
|
|
||||||
} else {
|
|
||||||
StateFeature value = !strcasecmp(feature, "equ") ? STATE_EQU
|
|
||||||
: !strcasecmp(feature, "var") ? STATE_VAR
|
|
||||||
: !strcasecmp(feature, "equs") ? STATE_EQUS
|
|
||||||
: !strcasecmp(feature, "char") ? STATE_CHAR
|
|
||||||
: !strcasecmp(feature, "macro") ? STATE_MACRO
|
|
||||||
: NB_STATE_FEATURES;
|
|
||||||
if (value == NB_STATE_FEATURES) {
|
|
||||||
errx("Invalid feature for option 's': \"%s\"", feature);
|
|
||||||
} else if (std::find(RANGE(features), value) != features.end()) {
|
|
||||||
warnx("Ignoring duplicate feature for option 's': \"%s\"", feature);
|
|
||||||
} else {
|
|
||||||
features.push_back(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
feature = next;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stateFileSpecs.find(name) != stateFileSpecs.end()) {
|
if (stateFileSpecs.find(name) != stateFileSpecs.end()) {
|
||||||
warnx("Overriding state filename %s", name);
|
warnx("Overriding state filename %s", name);
|
||||||
}
|
}
|
||||||
// LCOV_EXCL_START
|
verbosePrint("State filename %s\n", name); // LCOV_EXCL_LINE
|
||||||
if (verbose) {
|
|
||||||
printf("State filename %s\n", name);
|
|
||||||
}
|
|
||||||
// LCOV_EXCL_STOP
|
|
||||||
stateFileSpecs.emplace(name, std::move(features));
|
stateFileSpecs.emplace(name, std::move(features));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -321,7 +306,7 @@ int main(int argc, char *argv[]) {
|
|||||||
|
|
||||||
case 'v':
|
case 'v':
|
||||||
// LCOV_EXCL_START
|
// LCOV_EXCL_START
|
||||||
verbose = true;
|
options.verbose = true;
|
||||||
break;
|
break;
|
||||||
// LCOV_EXCL_STOP
|
// LCOV_EXCL_STOP
|
||||||
|
|
||||||
@@ -330,100 +315,93 @@ int main(int argc, char *argv[]) {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'w':
|
case 'w':
|
||||||
warnings = false;
|
warnings.state.warningsEnabled = false;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
unsigned long maxValue;
|
case 'X': {
|
||||||
case 'X':
|
uint64_t maxErrors = strtoul(musl_optarg, &endptr, 0);
|
||||||
maxValue = strtoul(musl_optarg, &endptr, 0);
|
|
||||||
|
|
||||||
if (musl_optarg[0] == '\0' || *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) {
|
if (maxErrors > UINT64_MAX) {
|
||||||
errx("Argument for option 'X' must be between 0 and %u", UINT_MAX);
|
fatal("Argument for option 'X' must be between 0 and %" PRIu64, UINT64_MAX);
|
||||||
}
|
}
|
||||||
|
|
||||||
maxErrors = maxValue;
|
options.maxErrors = maxErrors;
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// Long-only options
|
// Long-only options
|
||||||
case 0:
|
case 0:
|
||||||
switch (depType) {
|
switch (depType) {
|
||||||
|
case 'C':
|
||||||
|
options.missingIncludeState = GEN_CONTINUE;
|
||||||
|
break;
|
||||||
|
|
||||||
case 'G':
|
case 'G':
|
||||||
generatedMissingIncludes = true;
|
options.missingIncludeState = GEN_EXIT;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'P':
|
case 'P':
|
||||||
generatePhonyDeps = true;
|
options.generatePhonyDeps = true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'Q':
|
case 'Q':
|
||||||
case 'T':
|
case 'T': {
|
||||||
newTarget = musl_optarg;
|
std::string newTarget = musl_optarg;
|
||||||
if (depType == 'Q') {
|
if (depType == 'Q') {
|
||||||
newTarget = make_escape(newTarget);
|
newTarget = make_escape(newTarget);
|
||||||
}
|
}
|
||||||
if (!targetFileName.empty()) {
|
if (!options.targetFileName.empty()) {
|
||||||
targetFileName += ' ';
|
options.targetFileName += ' ';
|
||||||
}
|
}
|
||||||
targetFileName += newTarget;
|
options.targetFileName += newTarget;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Unrecognized options
|
// Unrecognized options
|
||||||
default:
|
default:
|
||||||
// LCOV_EXCL_START
|
usage.printAndExit(1); // LCOV_EXCL_LINE
|
||||||
printUsage();
|
|
||||||
exit(1);
|
|
||||||
// LCOV_EXCL_STOP
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (targetFileName.empty() && !objectFileName.empty()) {
|
if (options.targetFileName.empty() && !options.objectFileName.empty()) {
|
||||||
targetFileName = objectFileName;
|
options.targetFileName = options.objectFileName;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (argc == musl_optind) {
|
if (argc == musl_optind) {
|
||||||
fputs(
|
usage.printAndExit("Please specify an input file (pass `-` to read from standard input)");
|
||||||
"FATAL: Please specify an input file (pass `-` to read from standard input)\n", stderr
|
|
||||||
);
|
|
||||||
printUsage();
|
|
||||||
exit(1);
|
|
||||||
} else if (argc != musl_optind + 1) {
|
} else if (argc != musl_optind + 1) {
|
||||||
fputs("FATAL: More than one input file specified\n", stderr);
|
usage.printAndExit("More than one input file specified");
|
||||||
printUsage();
|
|
||||||
exit(1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string mainFileName = argv[musl_optind];
|
std::string mainFileName = argv[musl_optind];
|
||||||
|
|
||||||
if (verbose) {
|
verbosePrint("Assembling %s\n", mainFileName.c_str()); // LCOV_EXCL_LINE
|
||||||
printf("Assembling %s\n", mainFileName.c_str()); // LCOV_EXCL_LINE
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dependFile) {
|
if (options.dependFile && options.targetFileName.empty()) {
|
||||||
if (targetFileName.empty()) {
|
fatal("Dependency files can only be created if a target file is specified with either "
|
||||||
errx("Dependency files can only be created if a target file is specified with either "
|
"-o, -MQ or -MT");
|
||||||
"-o, -MQ or -MT");
|
|
||||||
}
|
|
||||||
|
|
||||||
fprintf(dependFile, "%s: %s\n", targetFileName.c_str(), mainFileName.c_str());
|
|
||||||
}
|
}
|
||||||
|
options.printDep(mainFileName);
|
||||||
|
|
||||||
charmap_New(DEFAULT_CHARMAP_NAME, nullptr);
|
charmap_New(DEFAULT_CHARMAP_NAME, nullptr);
|
||||||
|
|
||||||
// Init lexer and file stack, providing file info
|
// 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`)
|
// Perform parse (`yy::parser` is auto-generated from `parser.y`)
|
||||||
if (yy::parser parser; parser.parse() != 0 && nbErrors == 0) {
|
if (yy::parser parser; parser.parse() != 0) {
|
||||||
nbErrors = 1;
|
if (warnings.nbErrors == 0) {
|
||||||
|
warnings.nbErrors = 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!failedOnMissingInclude) {
|
if (!fstk_FailedOnMissingInclude()) {
|
||||||
sect_CheckUnionClosed();
|
sect_CheckUnionClosed();
|
||||||
sect_CheckLoadClosed();
|
sect_CheckLoadClosed();
|
||||||
sect_CheckSizes();
|
sect_CheckSizes();
|
||||||
@@ -433,12 +411,10 @@ int main(int argc, char *argv[]) {
|
|||||||
sect_CheckStack();
|
sect_CheckStack();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nbErrors != 0) {
|
requireZeroErrors();
|
||||||
errx("Assembly aborted (%u error%s)!", nbErrors, nbErrors == 1 ? "" : "s");
|
|
||||||
}
|
|
||||||
|
|
||||||
// If parse aborted due to missing an include, and `-MG` was given, exit normally
|
// If parse aborted due to missing an include, and `-MG` was given, exit normally
|
||||||
if (failedOnMissingInclude) {
|
if (fstk_FailedOnMissingInclude()) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
102
src/asm/opt.cpp
102
src/asm/opt.cpp
@@ -7,9 +7,12 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "helpers.hpp" // assume
|
||||||
|
|
||||||
#include "asm/fixpoint.hpp"
|
#include "asm/fixpoint.hpp"
|
||||||
#include "asm/fstack.hpp"
|
#include "asm/fstack.hpp"
|
||||||
#include "asm/lexer.hpp"
|
#include "asm/lexer.hpp"
|
||||||
|
#include "asm/main.hpp" // options
|
||||||
#include "asm/section.hpp"
|
#include "asm/section.hpp"
|
||||||
#include "asm/warning.hpp"
|
#include "asm/warning.hpp"
|
||||||
|
|
||||||
@@ -17,37 +20,38 @@ struct OptStackEntry {
|
|||||||
char binDigits[2];
|
char binDigits[2];
|
||||||
char gfxDigits[4];
|
char gfxDigits[4];
|
||||||
uint8_t fixPrecision;
|
uint8_t fixPrecision;
|
||||||
uint8_t fillByte;
|
uint8_t padByte;
|
||||||
bool warningsAreErrors;
|
|
||||||
size_t maxRecursionDepth;
|
size_t maxRecursionDepth;
|
||||||
Diagnostics warningStates;
|
DiagnosticsState<WarningID> warningStates;
|
||||||
};
|
};
|
||||||
|
|
||||||
static std::stack<OptStackEntry> stack;
|
static std::stack<OptStackEntry> stack;
|
||||||
|
|
||||||
void opt_B(char const chars[2]) {
|
void opt_B(char const binDigits[2]) {
|
||||||
lexer_SetBinDigits(chars);
|
lexer_SetBinDigits(binDigits);
|
||||||
}
|
}
|
||||||
|
|
||||||
void opt_G(char const chars[4]) {
|
void opt_G(char const gfxDigits[4]) {
|
||||||
lexer_SetGfxDigits(chars);
|
lexer_SetGfxDigits(gfxDigits);
|
||||||
}
|
}
|
||||||
|
|
||||||
void opt_P(uint8_t padByte) {
|
void opt_P(uint8_t padByte) {
|
||||||
fillByte = padByte;
|
options.padByte = padByte;
|
||||||
}
|
}
|
||||||
|
|
||||||
void opt_Q(uint8_t precision) {
|
void opt_Q(uint8_t fixPrecision) {
|
||||||
fixPrecision = precision;
|
options.fixPrecision = fixPrecision;
|
||||||
}
|
}
|
||||||
|
|
||||||
void opt_R(size_t newDepth) {
|
void opt_R(size_t maxRecursionDepth) {
|
||||||
fstk_NewRecursionDepth(newDepth);
|
fstk_NewRecursionDepth(maxRecursionDepth);
|
||||||
lexer_CheckRecursionDepth();
|
lexer_CheckRecursionDepth();
|
||||||
}
|
}
|
||||||
|
|
||||||
void opt_W(char const *flag) {
|
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) {
|
void opt_Parse(char const *s) {
|
||||||
@@ -56,7 +60,7 @@ void opt_Parse(char const *s) {
|
|||||||
if (strlen(&s[1]) == 2) {
|
if (strlen(&s[1]) == 2) {
|
||||||
opt_B(&s[1]);
|
opt_B(&s[1]);
|
||||||
} else {
|
} else {
|
||||||
error("Must specify exactly 2 characters for option 'b'\n");
|
error("Must specify exactly 2 characters for option 'b'");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -64,7 +68,7 @@ void opt_Parse(char const *s) {
|
|||||||
if (strlen(&s[1]) == 4) {
|
if (strlen(&s[1]) == 4) {
|
||||||
opt_G(&s[1]);
|
opt_G(&s[1]);
|
||||||
} else {
|
} else {
|
||||||
error("Must specify exactly 4 characters for option 'g'\n");
|
error("Must specify exactly 4 characters for option 'g'");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -75,14 +79,14 @@ void opt_Parse(char const *s) {
|
|||||||
|
|
||||||
result = sscanf(&s[1], "%x", &padByte);
|
result = sscanf(&s[1], "%x", &padByte);
|
||||||
if (result != 1) {
|
if (result != 1) {
|
||||||
error("Invalid argument for option 'p'\n");
|
error("Invalid argument for option 'p'");
|
||||||
} else if (padByte > 0xFF) {
|
|
||||||
error("Argument for option 'p' must be between 0 and 0xFF\n");
|
|
||||||
} else {
|
} else {
|
||||||
|
// Two characters cannot be scanned as a hex number greater than 0xFF
|
||||||
|
assume(padByte <= 0xFF);
|
||||||
opt_P(padByte);
|
opt_P(padByte);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
error("Invalid argument for option 'p'\n");
|
error("Invalid argument for option 'p'");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -90,22 +94,22 @@ void opt_Parse(char const *s) {
|
|||||||
case 'Q':
|
case 'Q':
|
||||||
precisionArg = &s[1];
|
precisionArg = &s[1];
|
||||||
if (precisionArg[0] == '.') {
|
if (precisionArg[0] == '.') {
|
||||||
precisionArg++;
|
++precisionArg;
|
||||||
}
|
}
|
||||||
if (strlen(precisionArg) <= 2) {
|
if (strlen(precisionArg) <= 2) {
|
||||||
int result;
|
int result;
|
||||||
unsigned int precision;
|
unsigned int fixPrecision;
|
||||||
|
|
||||||
result = sscanf(precisionArg, "%u", &precision);
|
result = sscanf(precisionArg, "%u", &fixPrecision);
|
||||||
if (result != 1) {
|
if (result != 1) {
|
||||||
error("Invalid argument for option 'Q'\n");
|
error("Invalid argument for option 'Q'");
|
||||||
} else if (precision < 1 || precision > 31) {
|
} else if (fixPrecision < 1 || fixPrecision > 31) {
|
||||||
error("Argument for option 'Q' must be between 1 and 31\n");
|
error("Argument for option 'Q' must be between 1 and 31");
|
||||||
} else {
|
} else {
|
||||||
opt_Q(precision);
|
opt_Q(fixPrecision);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
error("Invalid argument for option 'Q'\n");
|
error("Invalid argument for option 'Q'");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -116,19 +120,19 @@ void opt_Parse(char const *s) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (s[0] == '\0') {
|
if (s[0] == '\0') {
|
||||||
error("Missing argument to option 'r'\n");
|
error("Missing argument to option 'r'");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
char *endptr;
|
char *endptr;
|
||||||
unsigned long newDepth = strtoul(s, &endptr, 10);
|
unsigned long maxRecursionDepth = strtoul(s, &endptr, 10);
|
||||||
|
|
||||||
if (*endptr != '\0') {
|
if (*endptr != '\0') {
|
||||||
error("Invalid argument to option 'r' (\"%s\")\n", s);
|
error("Invalid argument to option 'r' (\"%s\")", s);
|
||||||
} else if (errno == ERANGE) {
|
} 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 {
|
} else {
|
||||||
opt_R(newDepth);
|
opt_R(maxRecursionDepth);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -137,12 +141,12 @@ void opt_Parse(char const *s) {
|
|||||||
if (strlen(&s[1]) > 0) {
|
if (strlen(&s[1]) > 0) {
|
||||||
opt_W(&s[1]);
|
opt_W(&s[1]);
|
||||||
} else {
|
} else {
|
||||||
error("Must specify an argument for option 'W'\n");
|
error("Must specify an argument for option 'W'");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
error("Unknown option '%c'\n", s[0]);
|
error("Unknown option '%c'", s[0]);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -150,26 +154,19 @@ void opt_Parse(char const *s) {
|
|||||||
void opt_Push() {
|
void opt_Push() {
|
||||||
OptStackEntry entry;
|
OptStackEntry entry;
|
||||||
|
|
||||||
// Both of these are pulled from lexer.hpp
|
memcpy(entry.binDigits, options.binDigits, std::size(options.binDigits));
|
||||||
memcpy(entry.binDigits, binDigits, std::size(binDigits));
|
memcpy(entry.gfxDigits, options.gfxDigits, std::size(options.gfxDigits));
|
||||||
memcpy(entry.gfxDigits, gfxDigits, std::size(gfxDigits));
|
entry.padByte = options.padByte;
|
||||||
|
entry.fixPrecision = options.fixPrecision;
|
||||||
entry.fixPrecision = fixPrecision; // Pulled from fixpoint.hpp
|
entry.maxRecursionDepth = options.maxRecursionDepth;
|
||||||
|
entry.warningStates = warnings.state;
|
||||||
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
|
|
||||||
|
|
||||||
stack.push(entry);
|
stack.push(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
void opt_Pop() {
|
void opt_Pop() {
|
||||||
if (stack.empty()) {
|
if (stack.empty()) {
|
||||||
error("No entries in the option stack\n");
|
error("No entries in the option stack");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,17 +175,16 @@ void opt_Pop() {
|
|||||||
|
|
||||||
opt_B(entry.binDigits);
|
opt_B(entry.binDigits);
|
||||||
opt_G(entry.gfxDigits);
|
opt_G(entry.gfxDigits);
|
||||||
opt_P(entry.fillByte);
|
opt_P(entry.padByte);
|
||||||
opt_Q(entry.fixPrecision);
|
opt_Q(entry.fixPrecision);
|
||||||
opt_R(entry.maxRecursionDepth);
|
opt_R(entry.maxRecursionDepth);
|
||||||
|
|
||||||
// opt_W does not apply a whole warning state; it processes one flag string
|
// `opt_W` does not apply a whole warning state; it processes one flag string
|
||||||
warningsAreErrors = entry.warningsAreErrors;
|
warnings.state = entry.warningStates;
|
||||||
warningStates = entry.warningStates;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void opt_CheckStack() {
|
void opt_CheckStack() {
|
||||||
if (!stack.empty()) {
|
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 <algorithm>
|
||||||
#include <deque>
|
#include <deque>
|
||||||
|
#include <errno.h>
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include "error.hpp"
|
#include "diagnostics.hpp"
|
||||||
#include "helpers.hpp" // assume, Defer
|
#include "helpers.hpp" // assume, Defer
|
||||||
#include "platform.hpp"
|
#include "platform.hpp"
|
||||||
|
|
||||||
@@ -29,8 +30,6 @@ struct Assertion {
|
|||||||
std::string message;
|
std::string message;
|
||||||
};
|
};
|
||||||
|
|
||||||
std::string objectFileName;
|
|
||||||
|
|
||||||
// List of symbols to put in the object file
|
// List of symbols to put in the object file
|
||||||
static std::vector<Symbol *> objectSymbols;
|
static std::vector<Symbol *> objectSymbols;
|
||||||
|
|
||||||
@@ -61,27 +60,13 @@ void out_RegisterNode(std::shared_ptr<FileStackNode> node) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return a section's ID, or UINT32_MAX if the section does not exist
|
|
||||||
static uint32_t getSectIDIfAny(Section *sect) {
|
|
||||||
if (!sect) {
|
|
||||||
return UINT32_MAX;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (auto search = sectionMap.find(sect->name); search != sectionMap.end()) {
|
|
||||||
return static_cast<uint32_t>(search->second);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Every section that exists should be in `sectionMap`
|
|
||||||
fatalerror("Unknown section '%s'\n", sect->name.c_str()); // LCOV_EXCL_LINE
|
|
||||||
}
|
|
||||||
|
|
||||||
static void writePatch(Patch const &patch, FILE *file) {
|
static void writePatch(Patch const &patch, FILE *file) {
|
||||||
assume(patch.src->ID != UINT32_MAX);
|
assume(patch.src->ID != UINT32_MAX);
|
||||||
|
|
||||||
putLong(patch.src->ID, file);
|
putLong(patch.src->ID, file);
|
||||||
putLong(patch.lineNo, file);
|
putLong(patch.lineNo, file);
|
||||||
putLong(patch.offset, file);
|
putLong(patch.offset, file);
|
||||||
putLong(getSectIDIfAny(patch.pcSection), file);
|
putLong(patch.pcSection ? patch.pcSection->getID() : UINT32_MAX, file);
|
||||||
putLong(patch.pcOffset, file);
|
putLong(patch.pcOffset, file);
|
||||||
putc(patch.type, file);
|
putc(patch.type, file);
|
||||||
putLong(patch.rpn.size(), file);
|
putLong(patch.rpn.size(), file);
|
||||||
@@ -125,10 +110,12 @@ static void writeSymbol(Symbol const &sym, FILE *file) {
|
|||||||
} else {
|
} else {
|
||||||
assume(sym.src->ID != UINT32_MAX);
|
assume(sym.src->ID != UINT32_MAX);
|
||||||
|
|
||||||
|
Section *symSection = sym.getSection();
|
||||||
|
|
||||||
putc(sym.isExported ? SYMTYPE_EXPORT : SYMTYPE_LOCAL, file);
|
putc(sym.isExported ? SYMTYPE_EXPORT : SYMTYPE_LOCAL, file);
|
||||||
putLong(sym.src->ID, file);
|
putLong(sym.src->ID, file);
|
||||||
putLong(sym.fileLine, file);
|
putLong(sym.fileLine, file);
|
||||||
putLong(getSectIDIfAny(sym.getSection()), file);
|
putLong(symSection ? symSection->getID() : UINT32_MAX, file);
|
||||||
putLong(sym.getOutputValue(), file);
|
putLong(sym.getOutputValue(), file);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -143,12 +130,19 @@ static void registerUnregisteredSymbol(Symbol &sym) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void writeRpn(std::vector<uint8_t> &rpnexpr, std::vector<uint8_t> const &rpn) {
|
static void writeRpn(std::vector<uint8_t> &rpnexpr, std::vector<uint8_t> const &rpn) {
|
||||||
std::string symName;
|
|
||||||
size_t rpnptr = 0;
|
size_t rpnptr = 0;
|
||||||
|
|
||||||
for (size_t offset = 0; offset < rpn.size();) {
|
for (size_t offset = 0; offset < rpn.size();) {
|
||||||
uint8_t rpndata = rpn[offset++];
|
uint8_t rpndata = rpn[offset++];
|
||||||
|
|
||||||
|
auto getSymName = [&]() {
|
||||||
|
std::string symName;
|
||||||
|
for (uint8_t c; (c = rpn[offset++]) != 0;) {
|
||||||
|
symName += c;
|
||||||
|
}
|
||||||
|
return symName;
|
||||||
|
};
|
||||||
|
|
||||||
switch (rpndata) {
|
switch (rpndata) {
|
||||||
Symbol *sym;
|
Symbol *sym;
|
||||||
uint32_t value;
|
uint32_t value;
|
||||||
@@ -163,17 +157,8 @@ static void writeRpn(std::vector<uint8_t> &rpnexpr, std::vector<uint8_t> const &
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case RPN_SYM:
|
case RPN_SYM:
|
||||||
symName.clear();
|
|
||||||
for (;;) {
|
|
||||||
uint8_t c = rpn[offset++];
|
|
||||||
if (c == 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
symName += c;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The symbol name is always written expanded
|
// The symbol name is always written expanded
|
||||||
sym = sym_FindExactSymbol(symName);
|
sym = sym_FindExactSymbol(getSymName());
|
||||||
if (sym->isConstant()) {
|
if (sym->isConstant()) {
|
||||||
rpnexpr[rpnptr++] = RPN_CONST;
|
rpnexpr[rpnptr++] = RPN_CONST;
|
||||||
value = sym->getConstantValue();
|
value = sym->getConstantValue();
|
||||||
@@ -190,17 +175,8 @@ static void writeRpn(std::vector<uint8_t> &rpnexpr, std::vector<uint8_t> const &
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case RPN_BANK_SYM:
|
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
|
// The symbol name is always written expanded
|
||||||
sym = sym_FindExactSymbol(symName);
|
sym = sym_FindExactSymbol(getSymName());
|
||||||
registerUnregisteredSymbol(*sym); // Ensure that `sym->ID` is set
|
registerUnregisteredSymbol(*sym); // Ensure that `sym->ID` is set
|
||||||
value = sym->ID;
|
value = sym->ID;
|
||||||
|
|
||||||
@@ -269,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) {
|
void out_CreatePatch(uint32_t type, Expression const &expr, uint32_t ofs, uint32_t pcShift) {
|
||||||
// Add the patch to the list
|
// 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);
|
initPatch(patch, type, expr, ofs);
|
||||||
|
|
||||||
@@ -311,20 +288,24 @@ static void writeFileStackNode(FileStackNode const &node, FILE *file) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void out_WriteObject() {
|
void out_WriteObject() {
|
||||||
if (objectFileName.empty()) {
|
if (options.objectFileName.empty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
FILE *file;
|
static FILE *file; // `static` so `sect_ForEach` callback can see it
|
||||||
if (objectFileName != "-") {
|
if (options.objectFileName != "-") {
|
||||||
file = fopen(objectFileName.c_str(), "wb");
|
file = fopen(options.objectFileName.c_str(), "wb");
|
||||||
} else {
|
} else {
|
||||||
objectFileName = "<stdout>";
|
options.objectFileName = "<stdout>";
|
||||||
(void)setmode(STDOUT_FILENO, O_BINARY);
|
(void)setmode(STDOUT_FILENO, O_BINARY);
|
||||||
file = stdout;
|
file = stdout;
|
||||||
}
|
}
|
||||||
if (!file) {
|
if (!file) {
|
||||||
err("Failed to open object file '%s'", objectFileName.c_str()); // LCOV_EXCL_LINE
|
// LCOV_EXCL_START
|
||||||
|
fatal(
|
||||||
|
"Failed to open object file '%s': %s", options.objectFileName.c_str(), strerror(errno)
|
||||||
|
);
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
Defer closeFile{[&] { fclose(file); }};
|
Defer closeFile{[&] { fclose(file); }};
|
||||||
|
|
||||||
@@ -335,10 +316,10 @@ void out_WriteObject() {
|
|||||||
putLong(RGBDS_OBJECT_REV, file);
|
putLong(RGBDS_OBJECT_REV, file);
|
||||||
|
|
||||||
putLong(objectSymbols.size(), file);
|
putLong(objectSymbols.size(), file);
|
||||||
putLong(sectionList.size(), file);
|
putLong(sect_CountSections(), file);
|
||||||
|
|
||||||
putLong(fileStackNodes.size(), 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;
|
FileStackNode const &node = **it;
|
||||||
|
|
||||||
writeFileStackNode(node, file);
|
writeFileStackNode(node, file);
|
||||||
@@ -351,9 +332,7 @@ void out_WriteObject() {
|
|||||||
writeSymbol(*sym, file);
|
writeSymbol(*sym, file);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Section const § : sectionList) {
|
sect_ForEach([](Section §) { writeSection(sect, file); });
|
||||||
writeSection(sect, file);
|
|
||||||
}
|
|
||||||
|
|
||||||
putLong(assertions.size(), file);
|
putLong(assertions.size(), file);
|
||||||
|
|
||||||
@@ -362,18 +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;
|
|
||||||
// LCOV_EXCL_START
|
|
||||||
if (verbose) {
|
|
||||||
printf("Output filename %s\n", objectFileName.c_str());
|
|
||||||
}
|
|
||||||
// LCOV_EXCL_STOP
|
|
||||||
}
|
|
||||||
|
|
||||||
static void dumpString(std::string const &escape, FILE *file) {
|
static void dumpString(std::string const &escape, FILE *file) {
|
||||||
for (char c : escape) {
|
for (char c : escape) {
|
||||||
// Escape characters that need escaping
|
// Escape characters that need escaping
|
||||||
@@ -402,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 bool dumpEquConstants(FILE *file) {
|
||||||
static std::vector<Symbol *> equConstants; // `static` so `sym_ForEach` callback can see it
|
static std::vector<Symbol *> equConstants; // `static` so `sym_ForEach` callback can see it
|
||||||
equConstants.clear();
|
equConstants.clear();
|
||||||
@@ -411,10 +383,7 @@ static bool dumpEquConstants(FILE *file) {
|
|||||||
equConstants.push_back(&sym);
|
equConstants.push_back(&sym);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// Constants are ordered by file, then by definition order
|
std::sort(RANGE(equConstants), compareSymbols);
|
||||||
std::sort(RANGE(equConstants), [](Symbol *sym1, Symbol *sym2) -> bool {
|
|
||||||
return sym1->defIndex < sym2->defIndex;
|
|
||||||
});
|
|
||||||
|
|
||||||
for (Symbol const *sym : equConstants) {
|
for (Symbol const *sym : equConstants) {
|
||||||
uint32_t value = static_cast<uint32_t>(sym->getOutputValue());
|
uint32_t value = static_cast<uint32_t>(sym->getOutputValue());
|
||||||
@@ -433,10 +402,7 @@ static bool dumpVariables(FILE *file) {
|
|||||||
variables.push_back(&sym);
|
variables.push_back(&sym);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// Variables are ordered by file, then by definition order
|
std::sort(RANGE(variables), compareSymbols);
|
||||||
std::sort(RANGE(variables), [](Symbol *sym1, Symbol *sym2) -> bool {
|
|
||||||
return sym1->defIndex < sym2->defIndex;
|
|
||||||
});
|
|
||||||
|
|
||||||
for (Symbol const *sym : variables) {
|
for (Symbol const *sym : variables) {
|
||||||
uint32_t value = static_cast<uint32_t>(sym->getOutputValue());
|
uint32_t value = static_cast<uint32_t>(sym->getOutputValue());
|
||||||
@@ -455,10 +421,7 @@ static bool dumpEqusConstants(FILE *file) {
|
|||||||
equsConstants.push_back(&sym);
|
equsConstants.push_back(&sym);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// Constants are ordered by file, then by definition order
|
std::sort(RANGE(equsConstants), compareSymbols);
|
||||||
std::sort(RANGE(equsConstants), [](Symbol *sym1, Symbol *sym2) -> bool {
|
|
||||||
return sym1->defIndex < sym2->defIndex;
|
|
||||||
});
|
|
||||||
|
|
||||||
for (Symbol const *sym : equsConstants) {
|
for (Symbol const *sym : equsConstants) {
|
||||||
fprintf(file, "def %s equs \"", sym->name.c_str());
|
fprintf(file, "def %s equs \"", sym->name.c_str());
|
||||||
@@ -497,13 +460,10 @@ static bool dumpMacros(FILE *file) {
|
|||||||
macros.push_back(&sym);
|
macros.push_back(&sym);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// Macros are ordered by file, then by definition order
|
std::sort(RANGE(macros), compareSymbols);
|
||||||
std::sort(RANGE(macros), [](Symbol *sym1, Symbol *sym2) -> bool {
|
|
||||||
return sym1->defIndex < sym2->defIndex;
|
|
||||||
});
|
|
||||||
|
|
||||||
for (Symbol const *sym : macros) {
|
for (Symbol const *sym : macros) {
|
||||||
auto const &body = sym->getMacro();
|
ContentSpan const &body = sym->getMacro();
|
||||||
fprintf(file, "macro %s\n", sym->name.c_str());
|
fprintf(file, "macro %s\n", sym->name.c_str());
|
||||||
fwrite(body.ptr.get(), 1, body.size, file);
|
fwrite(body.ptr.get(), 1, body.size, file);
|
||||||
fputs("endm\n", file);
|
fputs("endm\n", file);
|
||||||
@@ -524,7 +484,9 @@ void out_WriteState(std::string name, std::vector<StateFeature> const &features)
|
|||||||
file = stdout;
|
file = stdout;
|
||||||
}
|
}
|
||||||
if (!file) {
|
if (!file) {
|
||||||
err("Failed to open state file '%s'", name.c_str()); // LCOV_EXCL_LINE
|
// LCOV_EXCL_START
|
||||||
|
fatal("Failed to open state file '%s': %s", name.c_str(), strerror(errno));
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
Defer closeFile{[&] { fclose(file); }};
|
Defer closeFile{[&] { fclose(file); }};
|
||||||
|
|
||||||
@@ -547,7 +509,7 @@ void out_WriteState(std::string name, std::vector<StateFeature> const &features)
|
|||||||
for (StateFeature feature : features) {
|
for (StateFeature feature : features) {
|
||||||
fprintf(file, "\n; %s\n", dumpHeadings[feature]);
|
fprintf(file, "\n; %s\n", dumpHeadings[feature]);
|
||||||
if (!dumpFuncs[feature](file)) {
|
if (!dumpFuncs[feature](file)) {
|
||||||
fprintf(file, "; No values\n");
|
fputs("; No values\n", file);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
733
src/asm/parser.y
733
src/asm/parser.y
File diff suppressed because it is too large
Load Diff
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
|
#include <optional>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
@@ -39,7 +40,7 @@ uint8_t *Expression::reserveSpace(uint32_t size, uint32_t patchSize) {
|
|||||||
|
|
||||||
int32_t Expression::getConstVal() const {
|
int32_t Expression::getConstVal() const {
|
||||||
if (!isKnown()) {
|
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 0;
|
||||||
}
|
}
|
||||||
return value();
|
return value();
|
||||||
@@ -73,10 +74,10 @@ void Expression::makeNumber(uint32_t value) {
|
|||||||
void Expression::makeSymbol(std::string const &symName) {
|
void Expression::makeSymbol(std::string const &symName) {
|
||||||
clear();
|
clear();
|
||||||
if (Symbol *sym = sym_FindScopedSymbol(symName); sym_IsPC(sym) && !sect_GetSymbolSection()) {
|
if (Symbol *sym = sym_FindScopedSymbol(symName); sym_IsPC(sym) && !sect_GetSymbolSection()) {
|
||||||
error("PC has no value outside of a section\n");
|
error("PC has no value outside of a section");
|
||||||
data = 0;
|
data = 0;
|
||||||
} else if (sym && !sym->isNumeric() && !sym->isLabel()) {
|
} else if (sym && !sym->isNumeric() && !sym->isLabel()) {
|
||||||
error("'%s' is not a numeric symbol\n", symName.c_str());
|
error("'%s' is not a numeric symbol", symName.c_str());
|
||||||
data = 0;
|
data = 0;
|
||||||
} else if (!sym || !sym->isConstant()) {
|
} else if (!sym || !sym->isConstant()) {
|
||||||
isSymbol = true;
|
isSymbol = true;
|
||||||
@@ -102,19 +103,19 @@ void Expression::makeBankSymbol(std::string const &symName) {
|
|||||||
clear();
|
clear();
|
||||||
if (Symbol const *sym = sym_FindScopedSymbol(symName); sym_IsPC(sym)) {
|
if (Symbol const *sym = sym_FindScopedSymbol(symName); sym_IsPC(sym)) {
|
||||||
// The @ symbol is treated differently.
|
// The @ symbol is treated differently.
|
||||||
if (!currentSection) {
|
if (std::optional<uint32_t> outputBank = sect_GetOutputBank(); !outputBank) {
|
||||||
error("PC has no bank outside of a section\n");
|
error("PC has no bank outside of a section");
|
||||||
data = 1;
|
data = 1;
|
||||||
} else if (currentSection->bank == UINT32_MAX) {
|
} else if (*outputBank == UINT32_MAX) {
|
||||||
data = "Current section's bank is not known";
|
data = "Current section's bank is not known";
|
||||||
|
|
||||||
*reserveSpace(1) = RPN_BANK_SELF;
|
*reserveSpace(1) = RPN_BANK_SELF;
|
||||||
} else {
|
} else {
|
||||||
data = static_cast<int32_t>(currentSection->bank);
|
data = static_cast<int32_t>(*outputBank);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
} else if (sym && !sym->isLabel()) {
|
} else if (sym && !sym->isLabel()) {
|
||||||
error("BANK argument must be a label\n");
|
error("BANK argument must be a label");
|
||||||
data = 1;
|
data = 1;
|
||||||
} else {
|
} else {
|
||||||
sym = sym_Ref(symName);
|
sym = sym_Ref(symName);
|
||||||
@@ -325,9 +326,9 @@ void Expression::makeUnaryOp(RPNCommand op, Expression &&src) {
|
|||||||
case RPN_TZCOUNT:
|
case RPN_TZCOUNT:
|
||||||
data = val != 0 ? ctz(uval) : 32;
|
data = val != 0 ? ctz(uval) : 32;
|
||||||
break;
|
break;
|
||||||
|
// LCOV_EXCL_START
|
||||||
default:
|
default:
|
||||||
// `makeUnaryOp` should never be called with a non-unary operator!
|
// `makeUnaryOp` should never be called with a non-unary operator!
|
||||||
// LCOV_EXCL_START
|
|
||||||
unreachable_();
|
unreachable_();
|
||||||
}
|
}
|
||||||
// LCOV_EXCL_STOP
|
// LCOV_EXCL_STOP
|
||||||
@@ -394,43 +395,37 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const
|
|||||||
break;
|
break;
|
||||||
case RPN_SHL:
|
case RPN_SHL:
|
||||||
if (rval < 0) {
|
if (rval < 0) {
|
||||||
warning(
|
warning(WARNING_SHIFT_AMOUNT, "Shifting left by negative amount %" PRId32, rval);
|
||||||
WARNING_SHIFT_AMOUNT, "Shifting left by negative amount %" PRId32 "\n", rval
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rval >= 32) {
|
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);
|
data = op_shift_left(lval, rval);
|
||||||
break;
|
break;
|
||||||
case RPN_SHR:
|
case RPN_SHR:
|
||||||
if (lval < 0) {
|
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) {
|
if (rval < 0) {
|
||||||
warning(
|
warning(WARNING_SHIFT_AMOUNT, "Shifting right by negative amount %" PRId32, rval);
|
||||||
WARNING_SHIFT_AMOUNT, "Shifting right by negative amount %" PRId32 "\n", rval
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rval >= 32) {
|
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);
|
data = op_shift_right(lval, rval);
|
||||||
break;
|
break;
|
||||||
case RPN_USHR:
|
case RPN_USHR:
|
||||||
if (rval < 0) {
|
if (rval < 0) {
|
||||||
warning(
|
warning(WARNING_SHIFT_AMOUNT, "Shifting right by negative amount %" PRId32, rval);
|
||||||
WARNING_SHIFT_AMOUNT, "Shifting right by negative amount %" PRId32 "\n", rval
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rval >= 32) {
|
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);
|
data = op_shift_right_unsigned(lval, rval);
|
||||||
@@ -440,13 +435,13 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const
|
|||||||
break;
|
break;
|
||||||
case RPN_DIV:
|
case RPN_DIV:
|
||||||
if (rval == 0) {
|
if (rval == 0) {
|
||||||
fatalerror("Division by zero\n");
|
fatal("Division by zero");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lval == INT32_MIN && rval == -1) {
|
if (lval == INT32_MIN && rval == -1) {
|
||||||
warning(
|
warning(
|
||||||
WARNING_DIV,
|
WARNING_DIV,
|
||||||
"Division of %" PRId32 " by -1 yields %" PRId32 "\n",
|
"Division of %" PRId32 " by -1 yields %" PRId32,
|
||||||
INT32_MIN,
|
INT32_MIN,
|
||||||
INT32_MIN
|
INT32_MIN
|
||||||
);
|
);
|
||||||
@@ -457,7 +452,7 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const
|
|||||||
break;
|
break;
|
||||||
case RPN_MOD:
|
case RPN_MOD:
|
||||||
if (rval == 0) {
|
if (rval == 0) {
|
||||||
fatalerror("Modulo by zero\n");
|
fatal("Modulo by zero");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lval == INT32_MIN && rval == -1) {
|
if (lval == INT32_MIN && rval == -1) {
|
||||||
@@ -468,14 +463,14 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const
|
|||||||
break;
|
break;
|
||||||
case RPN_EXP:
|
case RPN_EXP:
|
||||||
if (rval < 0) {
|
if (rval < 0) {
|
||||||
fatalerror("Exponentiation by negative power\n");
|
fatal("Exponentiation by negative power");
|
||||||
}
|
}
|
||||||
|
|
||||||
data = op_exponent(lval, rval);
|
data = op_exponent(lval, rval);
|
||||||
break;
|
break;
|
||||||
|
// LCOV_EXCL_START
|
||||||
default:
|
default:
|
||||||
// `makeBinaryOp` should never be called with a non-binary operator!
|
// `makeBinaryOp` should never be called with a non-binary operator!
|
||||||
// LCOV_EXCL_START
|
|
||||||
unreachable_();
|
unreachable_();
|
||||||
}
|
}
|
||||||
// LCOV_EXCL_STOP
|
// LCOV_EXCL_STOP
|
||||||
@@ -551,7 +546,7 @@ bool Expression::makeCheckHRAM() {
|
|||||||
// That range is valid, but deprecated
|
// That range is valid, but deprecated
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
error("Source address $%" PRIx32 " not between $FF00 to $FFFF\n", val);
|
error("Source address $%" PRIx32 " not between $FF00 to $FFFF", val);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -561,7 +556,7 @@ void Expression::makeCheckRST() {
|
|||||||
*reserveSpace(1) = RPN_RST;
|
*reserveSpace(1) = RPN_RST;
|
||||||
} else if (int32_t val = value(); val & ~0x38) {
|
} else if (int32_t val = value(); val & ~0x38) {
|
||||||
// A valid RST address must be masked with 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -575,7 +570,7 @@ void Expression::makeCheckBitIndex(uint8_t mask) {
|
|||||||
} else if (int32_t val = value(); val & ~0x07) {
|
} else if (int32_t val = value(); val & ~0x07) {
|
||||||
// A valid bit index must be masked with 0x07
|
// A valid bit index must be masked with 0x07
|
||||||
static char const *instructions[4] = {"instruction", "BIT", "RES", "SET"};
|
static char const *instructions[4] = {"instruction", "BIT", "RES", "SET"};
|
||||||
error("Invalid bit index %" PRId32 " for %s\n", val, instructions[mask >> 6]);
|
error("Invalid bit index %" PRId32 " for %s", val, instructions[mask >> 6]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -593,20 +588,20 @@ bool checkNBit(int32_t v, uint8_t n, char const *name) {
|
|||||||
if (v < -(1 << n) || v >= 1 << n) {
|
if (v < -(1 << n) || v >= 1 << n) {
|
||||||
warning(
|
warning(
|
||||||
WARNING_TRUNCATION_1,
|
WARNING_TRUNCATION_1,
|
||||||
n == 8 && !name ? "%s must be %u-bit; use LOW() to force 8-bit\n"
|
"%s must be %u-bit%s",
|
||||||
: "%s must be %u-bit\n",
|
|
||||||
name ? name : "Expression",
|
name ? name : "Expression",
|
||||||
n
|
n,
|
||||||
|
n == 8 && !name ? "; use LOW() to force 8-bit" : ""
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (v < -(1 << (n - 1))) {
|
if (v < -(1 << (n - 1))) {
|
||||||
warning(
|
warning(
|
||||||
WARNING_TRUNCATION_2,
|
WARNING_TRUNCATION_2,
|
||||||
n == 8 && !name ? "%s must be %u-bit; use LOW() to force 8-bit\n"
|
"%s must be %u-bit%s",
|
||||||
: "%s must be %u-bit\n",
|
|
||||||
name ? name : "Expression",
|
name ? name : "Expression",
|
||||||
n
|
n,
|
||||||
|
n == 8 && !name ? "; use LOW() to force 8-bit" : ""
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
#include <optional>
|
|
||||||
#include <stack>
|
#include <stack>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
@@ -22,7 +21,7 @@
|
|||||||
#include "asm/symbol.hpp"
|
#include "asm/symbol.hpp"
|
||||||
#include "asm/warning.hpp"
|
#include "asm/warning.hpp"
|
||||||
|
|
||||||
uint8_t fillByte;
|
using namespace std::literals;
|
||||||
|
|
||||||
struct UnionStackEntry {
|
struct UnionStackEntry {
|
||||||
uint32_t start;
|
uint32_t start;
|
||||||
@@ -38,29 +37,30 @@ struct SectionStackEntry {
|
|||||||
std::stack<UnionStackEntry> unionStack;
|
std::stack<UnionStackEntry> unionStack;
|
||||||
};
|
};
|
||||||
|
|
||||||
std::stack<UnionStackEntry> currentUnionStack;
|
static Section *currentSection = nullptr;
|
||||||
std::deque<SectionStackEntry> sectionStack;
|
static std::deque<Section> sectionList;
|
||||||
std::deque<Section> sectionList;
|
static std::unordered_map<std::string, size_t> sectionMap; // Indexes into `sectionList`
|
||||||
std::unordered_map<std::string, size_t> sectionMap; // Indexes into `sectionList`
|
|
||||||
uint32_t curOffset; // Offset into the current section (see sect_GetSymbolOffset)
|
static uint32_t curOffset; // Offset into the current section (see `sect_GetSymbolOffset`)
|
||||||
Section *currentSection = nullptr;
|
|
||||||
|
static std::deque<SectionStackEntry> sectionStack;
|
||||||
|
|
||||||
static Section *currentLoadSection = nullptr;
|
static Section *currentLoadSection = nullptr;
|
||||||
static std::pair<Symbol const *, Symbol const *> currentLoadLabelScopes = {nullptr, 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]]
|
[[nodiscard]]
|
||||||
static bool requireSection() {
|
static bool requireSection() {
|
||||||
if (currentSection) {
|
if (currentSection) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
error("Cannot output data outside of a SECTION\n");
|
error("Cannot output data outside of a SECTION");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// A quick check to see if we have an initialized section that can contain
|
|
||||||
// this much initialized data
|
|
||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
static bool requireCodeSection() {
|
static bool requireCodeSection() {
|
||||||
if (!requireSection()) {
|
if (!requireSection()) {
|
||||||
@@ -72,18 +72,26 @@ static bool requireCodeSection() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
error(
|
error(
|
||||||
"Section '%s' cannot contain code or data (not ROM0 or ROMX)\n",
|
"Section '%s' cannot contain code or data (not ROM0 or ROMX)", currentSection->name.c_str()
|
||||||
currentSection->name.c_str()
|
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size_t sect_CountSections() {
|
||||||
|
return sectionList.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
void sect_ForEach(void (*callback)(Section &)) {
|
||||||
|
for (Section § : sectionList) {
|
||||||
|
callback(sect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void sect_CheckSizes() {
|
void sect_CheckSizes() {
|
||||||
for (Section const § : sectionList) {
|
for (Section const § : sectionList) {
|
||||||
if (uint32_t maxSize = sectionTypeInfo[sect.type].size; sect.size > maxSize) {
|
if (uint32_t maxSize = sectionTypeInfo[sect.type].size; sect.size > maxSize) {
|
||||||
error(
|
error(
|
||||||
"Section '%s' grew too big (max size = 0x%" PRIX32 " bytes, reached 0x%" PRIX32
|
"Section '%s' grew too big (max size = 0x%" PRIX32 " bytes, reached 0x%" PRIX32 ")",
|
||||||
")\n",
|
|
||||||
sect.name.c_str(),
|
sect.name.c_str(),
|
||||||
maxSize,
|
maxSize,
|
||||||
sect.size
|
sect.size
|
||||||
@@ -101,7 +109,7 @@ Section *sect_FindSectionByName(std::string const &name) {
|
|||||||
#define sectError(...) \
|
#define sectError(...) \
|
||||||
do { \
|
do { \
|
||||||
error(__VA_ARGS__); \
|
error(__VA_ARGS__); \
|
||||||
nbSectErrors++; \
|
++nbSectErrors; \
|
||||||
} while (0)
|
} while (0)
|
||||||
|
|
||||||
static unsigned int mergeSectUnion(
|
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
|
// Unionized sections only need "compatible" constraints, and they end up with the strictest
|
||||||
// combination of both.
|
// combination of both.
|
||||||
if (sect_HasData(type)) {
|
if (sect_HasData(type)) {
|
||||||
sectError("Cannot declare ROM sections as UNION\n");
|
sectError("Cannot declare ROM sections as UNION");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (org != UINT32_MAX) {
|
if (org != UINT32_MAX) {
|
||||||
// If both are fixed, they must be the same
|
// If both are fixed, they must be the same
|
||||||
if (sect.org != UINT32_MAX && sect.org != org) {
|
if (sect.org != UINT32_MAX && sect.org != org) {
|
||||||
sectError(
|
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))) {
|
} else if (sect.align != 0 && (mask(sect.align) & (org - sect.alignOfs))) {
|
||||||
sectError(
|
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,
|
1U << sect.align,
|
||||||
sect.alignOfs
|
sect.alignOfs
|
||||||
);
|
);
|
||||||
@@ -138,15 +146,14 @@ static unsigned int mergeSectUnion(
|
|||||||
if (sect.org != UINT32_MAX) {
|
if (sect.org != UINT32_MAX) {
|
||||||
if ((sect.org - alignOffset) & mask(alignment)) {
|
if ((sect.org - alignOffset) & mask(alignment)) {
|
||||||
sectError(
|
sectError(
|
||||||
"Section already declared as fixed at incompatible address $%04" PRIx32 "\n",
|
"Section already declared as fixed at incompatible address $%04" PRIx32,
|
||||||
sect.org
|
sect.org
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// Check if alignment offsets are compatible
|
// Check if alignment offsets are compatible
|
||||||
} else if ((alignOffset & mask(sect.align)) != (sect.alignOfs & mask(alignment))) {
|
} else if ((alignOffset & mask(sect.align)) != (sect.alignOfs & mask(alignment))) {
|
||||||
sectError(
|
sectError(
|
||||||
"Section already declared with incompatible %u"
|
"Section already declared with incompatible %u-byte alignment (offset %" PRIu16 ")",
|
||||||
"-byte alignment (offset %" PRIu16 ")\n",
|
|
||||||
1U << sect.align,
|
1U << sect.align,
|
||||||
sect.alignOfs
|
sect.alignOfs
|
||||||
);
|
);
|
||||||
@@ -174,12 +181,11 @@ static unsigned int
|
|||||||
// If both are fixed, they must be the same
|
// If both are fixed, they must be the same
|
||||||
if (sect.org != UINT32_MAX && sect.org != curOrg) {
|
if (sect.org != UINT32_MAX && sect.org != curOrg) {
|
||||||
sectError(
|
sectError(
|
||||||
"Section already declared as fixed at incompatible address $%04" PRIx32 "\n",
|
"Section already declared as fixed at incompatible address $%04" PRIx32, sect.org
|
||||||
sect.org
|
|
||||||
);
|
);
|
||||||
} else if (sect.align != 0 && (mask(sect.align) & (curOrg - sect.alignOfs))) {
|
} else if (sect.align != 0 && (mask(sect.align) & (curOrg - sect.alignOfs))) {
|
||||||
sectError(
|
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,
|
1U << sect.align,
|
||||||
sect.alignOfs
|
sect.alignOfs
|
||||||
);
|
);
|
||||||
@@ -199,15 +205,14 @@ static unsigned int
|
|||||||
if (sect.org != UINT32_MAX) {
|
if (sect.org != UINT32_MAX) {
|
||||||
if ((sect.org - curOfs) & mask(alignment)) {
|
if ((sect.org - curOfs) & mask(alignment)) {
|
||||||
sectError(
|
sectError(
|
||||||
"Section already declared as fixed at incompatible address $%04" PRIx32 "\n",
|
"Section already declared as fixed at incompatible address $%04" PRIx32,
|
||||||
sect.org
|
sect.org
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// Check if alignment offsets are compatible
|
// Check if alignment offsets are compatible
|
||||||
} else if ((curOfs & mask(sect.align)) != (sect.alignOfs & mask(alignment))) {
|
} else if ((curOfs & mask(sect.align)) != (sect.alignOfs & mask(alignment))) {
|
||||||
sectError(
|
sectError(
|
||||||
"Section already declared with incompatible %u"
|
"Section already declared with incompatible %u-byte alignment (offset %" PRIu16 ")",
|
||||||
"-byte alignment (offset %" PRIu16 ")\n",
|
|
||||||
1U << sect.align,
|
1U << sect.align,
|
||||||
sect.alignOfs
|
sect.alignOfs
|
||||||
);
|
);
|
||||||
@@ -234,12 +239,12 @@ static void mergeSections(
|
|||||||
|
|
||||||
if (type != sect.type) {
|
if (type != sect.type) {
|
||||||
sectError(
|
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) {
|
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 {
|
} else {
|
||||||
switch (mod) {
|
switch (mod) {
|
||||||
case SECTION_UNION:
|
case SECTION_UNION:
|
||||||
@@ -256,21 +261,22 @@ static void mergeSections(
|
|||||||
}
|
}
|
||||||
// If both specify a bank, it must be the same one
|
// If both specify a bank, it must be the same one
|
||||||
else if (bank != UINT32_MAX && sect.bank != bank) {
|
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;
|
break;
|
||||||
|
|
||||||
case SECTION_NORMAL:
|
case SECTION_NORMAL:
|
||||||
sectError("Section already defined previously at ");
|
sectError([&]() {
|
||||||
sect.src->dump(sect.fileLine);
|
fputs("Section already defined previously at ", stderr);
|
||||||
putc('\n', stderr);
|
sect.src->dump(sect.fileLine);
|
||||||
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nbSectErrors) {
|
if (nbSectErrors) {
|
||||||
fatalerror(
|
fatal(
|
||||||
"Cannot create section \"%s\" (%u error%s)\n",
|
"Cannot create section \"%s\" (%u error%s)",
|
||||||
sect.name.c_str(),
|
sect.name.c_str(),
|
||||||
nbSectErrors,
|
nbSectErrors,
|
||||||
nbSectErrors == 1 ? "" : "s"
|
nbSectErrors == 1 ? "" : "s"
|
||||||
@@ -280,7 +286,6 @@ static void mergeSections(
|
|||||||
|
|
||||||
#undef sectError
|
#undef sectError
|
||||||
|
|
||||||
// Create a new section, not yet in the list.
|
|
||||||
static Section *createSection(
|
static Section *createSection(
|
||||||
std::string const &name,
|
std::string const &name,
|
||||||
SectionType type,
|
SectionType type,
|
||||||
@@ -315,7 +320,31 @@ static Section *createSection(
|
|||||||
return §
|
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(
|
static Section *getSection(
|
||||||
std::string const &name,
|
std::string const &name,
|
||||||
SectionType type,
|
SectionType type,
|
||||||
@@ -332,11 +361,11 @@ static Section *getSection(
|
|||||||
if (bank != UINT32_MAX) {
|
if (bank != UINT32_MAX) {
|
||||||
if (type != SECTTYPE_ROMX && type != SECTTYPE_VRAM && type != SECTTYPE_SRAM
|
if (type != SECTTYPE_ROMX && type != SECTTYPE_VRAM && type != SECTTYPE_SRAM
|
||||||
&& type != SECTTYPE_WRAMX) {
|
&& 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
|
} else if (bank < sectionTypeInfo[type].firstBank
|
||||||
|| bank > sectionTypeInfo[type].lastBank) {
|
|| bank > sectionTypeInfo[type].lastBank) {
|
||||||
error(
|
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(),
|
sectionTypeInfo[type].name.c_str(),
|
||||||
bank,
|
bank,
|
||||||
sectionTypeInfo[type].firstBank,
|
sectionTypeInfo[type].firstBank,
|
||||||
@@ -350,7 +379,7 @@ static Section *getSection(
|
|||||||
|
|
||||||
if (alignOffset >= 1 << alignment) {
|
if (alignOffset >= 1 << alignment) {
|
||||||
error(
|
error(
|
||||||
"Alignment offset (%" PRIu16 ") must be smaller than alignment size (%u)\n",
|
"Alignment offset (%" PRIu16 ") must be smaller than alignment size (%u)",
|
||||||
alignOffset,
|
alignOffset,
|
||||||
1U << alignment
|
1U << alignment
|
||||||
);
|
);
|
||||||
@@ -361,7 +390,7 @@ static Section *getSection(
|
|||||||
if (org < sectionTypeInfo[type].startAddr || org > endaddr(type)) {
|
if (org < sectionTypeInfo[type].startAddr || org > endaddr(type)) {
|
||||||
error(
|
error(
|
||||||
"Section \"%s\"'s fixed address $%04" PRIx32 " is outside of range [$%04" PRIx16
|
"Section \"%s\"'s fixed address $%04" PRIx32 " is outside of range [$%04" PRIx16
|
||||||
"; $%04" PRIx16 "]\n",
|
"; $%04" PRIx16 "]",
|
||||||
name.c_str(),
|
name.c_str(),
|
||||||
org,
|
org,
|
||||||
sectionTypeInfo[type].startAddr,
|
sectionTypeInfo[type].startAddr,
|
||||||
@@ -372,7 +401,7 @@ static Section *getSection(
|
|||||||
|
|
||||||
if (alignment != 0) {
|
if (alignment != 0) {
|
||||||
if (alignment > 16) {
|
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;
|
alignment = 16;
|
||||||
}
|
}
|
||||||
// It doesn't make sense to have both alignment and org set
|
// 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 != UINT32_MAX) {
|
||||||
if ((org - alignOffset) & mask) {
|
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
|
alignment = 0; // Ignore it if it's satisfied
|
||||||
} else if (sectionTypeInfo[type].startAddr & mask) {
|
} else if (sectionTypeInfo[type].startAddr & mask) {
|
||||||
error(
|
error(
|
||||||
"Section \"%s\"'s alignment cannot be attained in %s\n",
|
"Section \"%s\"'s alignment cannot be attained in %s",
|
||||||
name.c_str(),
|
name.c_str(),
|
||||||
sectionTypeInfo[type].name.c_str()
|
sectionTypeInfo[type].name.c_str()
|
||||||
);
|
);
|
||||||
@@ -412,15 +441,24 @@ static Section *getSection(
|
|||||||
return sect;
|
return sect;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the current section
|
|
||||||
static void changeSection() {
|
static void changeSection() {
|
||||||
if (!currentUnionStack.empty()) {
|
if (!currentUnionStack.empty()) {
|
||||||
fatalerror("Cannot change the section within a UNION\n");
|
fatal("Cannot change the section within a UNION");
|
||||||
}
|
}
|
||||||
|
|
||||||
sym_ResetCurrentLabelScopes();
|
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 {
|
bool Section::isSizeKnown() const {
|
||||||
// SECTION UNION and SECTION FRAGMENT can still grow
|
// SECTION UNION and SECTION FRAGMENT can still grow
|
||||||
if (modifier != SECTION_NORMAL) {
|
if (modifier != SECTION_NORMAL) {
|
||||||
@@ -442,7 +480,6 @@ bool Section::isSizeKnown() const {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the current section by name and type
|
|
||||||
void sect_NewSection(
|
void sect_NewSection(
|
||||||
std::string const &name,
|
std::string const &name,
|
||||||
SectionType type,
|
SectionType type,
|
||||||
@@ -452,7 +489,7 @@ void sect_NewSection(
|
|||||||
) {
|
) {
|
||||||
for (SectionStackEntry &entry : sectionStack) {
|
for (SectionStackEntry &entry : sectionStack) {
|
||||||
if (entry.section && entry.section->name == name) {
|
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;
|
currentSection = sect;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the current section by name and type
|
|
||||||
void sect_SetLoadSection(
|
void sect_SetLoadSection(
|
||||||
std::string const &name,
|
std::string const &name,
|
||||||
SectionType type,
|
SectionType type,
|
||||||
@@ -486,7 +522,7 @@ void sect_SetLoadSection(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (sect_HasData(type)) {
|
if (sect_HasData(type)) {
|
||||||
error("`LOAD` blocks cannot create a ROM section\n");
|
error("`LOAD` blocks cannot create a ROM section");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -505,13 +541,11 @@ void sect_SetLoadSection(
|
|||||||
|
|
||||||
void sect_EndLoadSection(char const *cause) {
|
void sect_EndLoadSection(char const *cause) {
|
||||||
if (cause) {
|
if (cause) {
|
||||||
warning(
|
warning(WARNING_UNTERMINATED_LOAD, "`LOAD` block without `ENDL` terminated by `%s`", cause);
|
||||||
WARNING_UNTERMINATED_LOAD, "`LOAD` block without `ENDL` terminated by `%s`\n", cause
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!currentLoadSection) {
|
if (!currentLoadSection) {
|
||||||
error("Found `ENDL` outside of a `LOAD` block\n");
|
error("Found `ENDL` outside of a `LOAD` block");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -524,7 +558,7 @@ void sect_EndLoadSection(char const *cause) {
|
|||||||
|
|
||||||
void sect_CheckLoadClosed() {
|
void sect_CheckLoadClosed() {
|
||||||
if (currentLoadSection) {
|
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;
|
return currentLoadSection ? currentLoadSection : currentSection;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The offset into the section above
|
|
||||||
uint32_t sect_GetSymbolOffset() {
|
uint32_t sect_GetSymbolOffset() {
|
||||||
return curOffset;
|
return curOffset;
|
||||||
}
|
}
|
||||||
@@ -541,6 +574,14 @@ uint32_t sect_GetOutputOffset() {
|
|||||||
return curOffset + loadOffset;
|
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
|
// Returns how many bytes need outputting for the specified alignment and offset to succeed
|
||||||
uint32_t sect_GetAlignBytes(uint8_t alignment, uint16_t offset) {
|
uint32_t sect_GetAlignBytes(uint8_t alignment, uint16_t offset) {
|
||||||
Section *sect = sect_GetSymbolSection();
|
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"
|
uint32_t alignSize = 1 << alignment; // Size of an aligned "block"
|
||||||
|
|
||||||
if (sect->org != UINT32_MAX) {
|
if (sect->org != UINT32_MAX) {
|
||||||
if ((sect->org + curOffset - offset) % alignSize) {
|
if (uint32_t actualOffset = (sect->org + curOffset) % alignSize; actualOffset != offset) {
|
||||||
error(
|
error(
|
||||||
"Section's fixed address fails required alignment (PC = $%04" PRIx32 ")\n",
|
"Section is misaligned (at PC = $%04" PRIx32 ", expected ALIGN[%" PRIu32
|
||||||
sect->org + curOffset
|
", %" PRIu32 "], got ALIGN[%" PRIu32 ", %" PRIu32 "])",
|
||||||
|
sect->org + curOffset,
|
||||||
|
alignment,
|
||||||
|
offset,
|
||||||
|
alignment,
|
||||||
|
actualOffset
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else if (sect->align != 0
|
} else {
|
||||||
&& (((sect->alignOfs + curOffset) % (1u << sect->align)) - offset) % alignSize) {
|
if (uint32_t actualOffset = (sect->alignOfs + curOffset) % alignSize,
|
||||||
error(
|
sectAlignSize = 1 << sect->align;
|
||||||
"Section's alignment fails required alignment (offset from section start = $%04" PRIx32
|
sect->align != 0 && actualOffset % sectAlignSize != offset % sectAlignSize) {
|
||||||
")\n",
|
error(
|
||||||
curOffset
|
"Section is misaligned ($%04" PRIx32
|
||||||
);
|
" bytes into the section, expected ALIGN[%" PRIu32 ", %" PRIu32
|
||||||
} else if (alignment >= 16) {
|
"], got ALIGN[%" PRIu32 ", %" PRIu32 "])",
|
||||||
// Treat an alignment large enough as fixing the address.
|
curOffset,
|
||||||
// Note that this also ensures that a section's alignment never becomes 16 or greater.
|
alignment,
|
||||||
if (alignment > 16) {
|
offset,
|
||||||
error("Alignment must be between 0 and 16, not %u\n", alignment);
|
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) {
|
static void growSection(uint32_t growth) {
|
||||||
if (growth > 0 && curOffset > UINT32_MAX - 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;
|
curOffset += growth;
|
||||||
if (uint32_t outOffset = sect_GetOutputOffset(); outOffset > currentSection->size) {
|
if (uint32_t outOffset = sect_GetOutputOffset(); outOffset > currentSection->size) {
|
||||||
@@ -643,11 +697,11 @@ void sect_StartUnion() {
|
|||||||
// your own peril! ^^
|
// your own peril! ^^
|
||||||
|
|
||||||
if (!currentSection) {
|
if (!currentSection) {
|
||||||
error("UNIONs must be inside a SECTION\n");
|
error("UNIONs must be inside a SECTION");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (sect_HasData(currentSection->type)) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -666,7 +720,7 @@ static void endUnionMember() {
|
|||||||
|
|
||||||
void sect_NextUnionMember() {
|
void sect_NextUnionMember() {
|
||||||
if (currentUnionStack.empty()) {
|
if (currentUnionStack.empty()) {
|
||||||
error("Found NEXTU outside of a UNION construct\n");
|
error("Found NEXTU outside of a UNION construct");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
endUnionMember();
|
endUnionMember();
|
||||||
@@ -674,7 +728,7 @@ void sect_NextUnionMember() {
|
|||||||
|
|
||||||
void sect_EndUnion() {
|
void sect_EndUnion() {
|
||||||
if (currentUnionStack.empty()) {
|
if (currentUnionStack.empty()) {
|
||||||
error("Found ENDU outside of a UNION construct\n");
|
error("Found ENDU outside of a UNION construct");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
endUnionMember();
|
endUnionMember();
|
||||||
@@ -684,11 +738,10 @@ void sect_EndUnion() {
|
|||||||
|
|
||||||
void sect_CheckUnionClosed() {
|
void sect_CheckUnionClosed() {
|
||||||
if (!currentUnionStack.empty()) {
|
if (!currentUnionStack.empty()) {
|
||||||
error("Unterminated UNION construct\n");
|
error("Unterminated UNION construct");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Output a constant byte
|
|
||||||
void sect_ConstByte(uint8_t byte) {
|
void sect_ConstByte(uint8_t byte) {
|
||||||
if (!requireCodeSection()) {
|
if (!requireCodeSection()) {
|
||||||
return;
|
return;
|
||||||
@@ -697,52 +750,48 @@ void sect_ConstByte(uint8_t byte) {
|
|||||||
writeByte(byte);
|
writeByte(byte);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Output a string's character units as bytes
|
void sect_ByteString(std::vector<int32_t> const &str) {
|
||||||
void sect_ByteString(std::vector<int32_t> const &string) {
|
|
||||||
if (!requireCodeSection()) {
|
if (!requireCodeSection()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int32_t unit : string) {
|
for (int32_t unit : str) {
|
||||||
if (!checkNBit(unit, 8, "All character units")) {
|
if (!checkNBit(unit, 8, "All character units")) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int32_t unit : string) {
|
for (int32_t unit : str) {
|
||||||
writeByte(static_cast<uint8_t>(unit));
|
writeByte(static_cast<uint8_t>(unit));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Output a string's character units as words
|
void sect_WordString(std::vector<int32_t> const &str) {
|
||||||
void sect_WordString(std::vector<int32_t> const &string) {
|
|
||||||
if (!requireCodeSection()) {
|
if (!requireCodeSection()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int32_t unit : string) {
|
for (int32_t unit : str) {
|
||||||
if (!checkNBit(unit, 16, "All character units")) {
|
if (!checkNBit(unit, 16, "All character units")) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int32_t unit : string) {
|
for (int32_t unit : str) {
|
||||||
writeWord(static_cast<uint16_t>(unit));
|
writeWord(static_cast<uint16_t>(unit));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Output a string's character units as longs
|
void sect_LongString(std::vector<int32_t> const &str) {
|
||||||
void sect_LongString(std::vector<int32_t> const &string) {
|
|
||||||
if (!requireCodeSection()) {
|
if (!requireCodeSection()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int32_t unit : string) {
|
for (int32_t unit : str) {
|
||||||
writeLong(static_cast<uint32_t>(unit));
|
writeLong(static_cast<uint32_t>(unit));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip this many bytes
|
|
||||||
void sect_Skip(uint32_t skip, bool ds) {
|
void sect_Skip(uint32_t skip, bool ds) {
|
||||||
if (!requireSection()) {
|
if (!requireSection()) {
|
||||||
return;
|
return;
|
||||||
@@ -754,7 +803,7 @@ void sect_Skip(uint32_t skip, bool ds) {
|
|||||||
if (!ds) {
|
if (!ds) {
|
||||||
warning(
|
warning(
|
||||||
WARNING_EMPTY_DATA_DIRECTIVE,
|
WARNING_EMPTY_DATA_DIRECTIVE,
|
||||||
"%s directive without data in ROM\n",
|
"%s directive without data in ROM",
|
||||||
(skip == 4) ? "DL"
|
(skip == 4) ? "DL"
|
||||||
: (skip == 2) ? "DW"
|
: (skip == 2) ? "DW"
|
||||||
: "DB"
|
: "DB"
|
||||||
@@ -762,12 +811,11 @@ void sect_Skip(uint32_t skip, bool ds) {
|
|||||||
}
|
}
|
||||||
// We know we're in a code SECTION
|
// We know we're in a code SECTION
|
||||||
while (skip--) {
|
while (skip--) {
|
||||||
writeByte(fillByte);
|
writeByte(options.padByte);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Output a byte that can be relocatable or constant
|
|
||||||
void sect_RelByte(Expression const &expr, uint32_t pcShift) {
|
void sect_RelByte(Expression const &expr, uint32_t pcShift) {
|
||||||
if (!requireCodeSection()) {
|
if (!requireCodeSection()) {
|
||||||
return;
|
return;
|
||||||
@@ -781,13 +829,12 @@ void sect_RelByte(Expression const &expr, uint32_t pcShift) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Output several bytes that can be relocatable or constant
|
|
||||||
void sect_RelBytes(uint32_t n, std::vector<Expression> const &exprs) {
|
void sect_RelBytes(uint32_t n, std::vector<Expression> const &exprs) {
|
||||||
if (!requireCodeSection()) {
|
if (!requireCodeSection()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (uint32_t i = 0; i < n; i++) {
|
for (uint32_t i = 0; i < n; ++i) {
|
||||||
if (Expression const &expr = exprs[i % exprs.size()]; !expr.isKnown()) {
|
if (Expression const &expr = exprs[i % exprs.size()]; !expr.isKnown()) {
|
||||||
createPatch(PATCHTYPE_BYTE, expr, i);
|
createPatch(PATCHTYPE_BYTE, expr, i);
|
||||||
writeByte(0);
|
writeByte(0);
|
||||||
@@ -797,7 +844,6 @@ void sect_RelBytes(uint32_t n, std::vector<Expression> const &exprs) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Output a word that can be relocatable or constant
|
|
||||||
void sect_RelWord(Expression const &expr, uint32_t pcShift) {
|
void sect_RelWord(Expression const &expr, uint32_t pcShift) {
|
||||||
if (!requireCodeSection()) {
|
if (!requireCodeSection()) {
|
||||||
return;
|
return;
|
||||||
@@ -811,7 +857,6 @@ void sect_RelWord(Expression const &expr, uint32_t pcShift) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Output a long that can be relocatable or constant
|
|
||||||
void sect_RelLong(Expression const &expr, uint32_t pcShift) {
|
void sect_RelLong(Expression const &expr, uint32_t pcShift) {
|
||||||
if (!requireCodeSection()) {
|
if (!requireCodeSection()) {
|
||||||
return;
|
return;
|
||||||
@@ -825,7 +870,6 @@ void sect_RelLong(Expression const &expr, uint32_t pcShift) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Output a PC-relative byte that can be relocatable or constant
|
|
||||||
void sect_PCRelByte(Expression const &expr, uint32_t pcShift) {
|
void sect_PCRelByte(Expression const &expr, uint32_t pcShift) {
|
||||||
if (!requireCodeSection()) {
|
if (!requireCodeSection()) {
|
||||||
return;
|
return;
|
||||||
@@ -849,7 +893,7 @@ void sect_PCRelByte(Expression const &expr, uint32_t pcShift) {
|
|||||||
if (offset < -128 || offset > 127) {
|
if (offset < -128 || offset > 127) {
|
||||||
error(
|
error(
|
||||||
"JR target must be between -128 and 127 bytes away, not %" PRId16
|
"JR target must be between -128 and 127 bytes away, not %" PRId16
|
||||||
"; use JP instead\n",
|
"; use JP instead",
|
||||||
offset
|
offset
|
||||||
);
|
);
|
||||||
writeByte(0);
|
writeByte(0);
|
||||||
@@ -859,14 +903,9 @@ void sect_PCRelByte(Expression const &expr, uint32_t pcShift) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Output a binary file
|
bool sect_BinaryFile(std::string const &name, uint32_t startPos) {
|
||||||
void sect_BinaryFile(std::string const &name, int32_t startPos) {
|
|
||||||
if (startPos < 0) {
|
|
||||||
error("Start position cannot be negative (%" PRId32 ")\n", startPos);
|
|
||||||
startPos = 0;
|
|
||||||
}
|
|
||||||
if (!requireCodeSection()) {
|
if (!requireCodeSection()) {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
FILE *file = nullptr;
|
FILE *file = nullptr;
|
||||||
@@ -874,40 +913,26 @@ void sect_BinaryFile(std::string const &name, int32_t startPos) {
|
|||||||
file = fopen(fullPath->c_str(), "rb");
|
file = fopen(fullPath->c_str(), "rb");
|
||||||
}
|
}
|
||||||
if (!file) {
|
if (!file) {
|
||||||
if (generatedMissingIncludes) {
|
return fstk_FileError(name, "INCBIN");
|
||||||
// LCOV_EXCL_START
|
|
||||||
if (verbose) {
|
|
||||||
printf("Aborting (-MG) on INCBIN file '%s' (%s)\n", name.c_str(), strerror(errno));
|
|
||||||
}
|
|
||||||
// LCOV_EXCL_STOP
|
|
||||||
failedOnMissingInclude = true;
|
|
||||||
} else {
|
|
||||||
error("Error opening INCBIN file '%s': %s\n", name.c_str(), strerror(errno));
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
Defer closeFile{[&] { fclose(file); }};
|
Defer closeFile{[&] { fclose(file); }};
|
||||||
|
|
||||||
if (fseek(file, 0, SEEK_END) != -1) {
|
if (fseek(file, 0, SEEK_END) == 0) {
|
||||||
if (startPos > ftell(file)) {
|
if (startPos > ftell(file)) {
|
||||||
error("Specified start position is greater than length of file '%s'\n", name.c_str());
|
error("Specified start position is greater than length of file '%s'", name.c_str());
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
// The file is seekable; skip to the specified start position
|
// The file is seekable; skip to the specified start position
|
||||||
fseek(file, startPos, SEEK_SET);
|
fseek(file, startPos, SEEK_SET);
|
||||||
} else {
|
} else {
|
||||||
if (errno != ESPIPE) {
|
if (errno != ESPIPE) {
|
||||||
error(
|
error("Error determining size of INCBIN file '%s': %s", name.c_str(), strerror(errno));
|
||||||
"Error determining size of INCBIN file '%s': %s\n", name.c_str(), strerror(errno)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
// The file isn't seekable, so we'll just skip bytes one at a time
|
// The file isn't seekable, so we'll just skip bytes one at a time
|
||||||
while (startPos--) {
|
while (startPos--) {
|
||||||
if (fgetc(file) == EOF) {
|
if (fgetc(file) == EOF) {
|
||||||
error(
|
error("Specified start position is greater than length of file '%s'", name.c_str());
|
||||||
"Specified start position is greater than length of file '%s'\n", name.c_str()
|
return false;
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -917,25 +942,17 @@ void sect_BinaryFile(std::string const &name, int32_t startPos) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (ferror(file)) {
|
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
|
bool sect_BinaryFileSlice(std::string const &name, uint32_t startPos, uint32_t length) {
|
||||||
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;
|
|
||||||
}
|
|
||||||
if (!requireCodeSection()) {
|
if (!requireCodeSection()) {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
if (length == 0) { // Don't even bother with 0-byte slices
|
if (length == 0) { // Don't even bother with 0-byte slices
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
FILE *file = nullptr;
|
FILE *file = nullptr;
|
||||||
@@ -943,50 +960,36 @@ void sect_BinaryFileSlice(std::string const &name, int32_t startPos, int32_t len
|
|||||||
file = fopen(fullPath->c_str(), "rb");
|
file = fopen(fullPath->c_str(), "rb");
|
||||||
}
|
}
|
||||||
if (!file) {
|
if (!file) {
|
||||||
if (generatedMissingIncludes) {
|
return fstk_FileError(name, "INCBIN");
|
||||||
// LCOV_EXCL_START
|
|
||||||
if (verbose) {
|
|
||||||
printf("Aborting (-MG) on INCBIN file '%s' (%s)\n", name.c_str(), strerror(errno));
|
|
||||||
}
|
|
||||||
// LCOV_EXCL_STOP
|
|
||||||
failedOnMissingInclude = true;
|
|
||||||
} else {
|
|
||||||
error("Error opening INCBIN file '%s': %s\n", name.c_str(), strerror(errno));
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
Defer closeFile{[&] { fclose(file); }};
|
Defer closeFile{[&] { fclose(file); }};
|
||||||
|
|
||||||
if (fseek(file, 0, SEEK_END) != -1) {
|
if (fseek(file, 0, SEEK_END) == 0) {
|
||||||
if (int32_t fsize = ftell(file); startPos > fsize) {
|
if (long fsize = ftell(file); startPos > fsize) {
|
||||||
error("Specified start position is greater than length of file '%s'\n", name.c_str());
|
error("Specified start position is greater than length of file '%s'", name.c_str());
|
||||||
return;
|
return false;
|
||||||
} else if (startPos + length > fsize) {
|
} else if (startPos + length > fsize) {
|
||||||
error(
|
error(
|
||||||
"Specified range in INCBIN file '%s' is out of bounds (%" PRIu32 " + %" PRIu32
|
"Specified range in INCBIN file '%s' is out of bounds (%" PRIu32 " + %" PRIu32
|
||||||
" > %" PRIu32 ")\n",
|
" > %ld)",
|
||||||
name.c_str(),
|
name.c_str(),
|
||||||
startPos,
|
startPos,
|
||||||
length,
|
length,
|
||||||
fsize
|
fsize
|
||||||
);
|
);
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
// The file is seekable; skip to the specified start position
|
// The file is seekable; skip to the specified start position
|
||||||
fseek(file, startPos, SEEK_SET);
|
fseek(file, startPos, SEEK_SET);
|
||||||
} else {
|
} else {
|
||||||
if (errno != ESPIPE) {
|
if (errno != ESPIPE) {
|
||||||
error(
|
error("Error determining size of INCBIN file '%s': %s", name.c_str(), strerror(errno));
|
||||||
"Error determining size of INCBIN file '%s': %s\n", name.c_str(), strerror(errno)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
// The file isn't seekable, so we'll just skip bytes one at a time
|
// The file isn't seekable, so we'll just skip bytes one at a time
|
||||||
while (startPos--) {
|
while (startPos--) {
|
||||||
if (fgetc(file) == EOF) {
|
if (fgetc(file) == EOF) {
|
||||||
error(
|
error("Specified start position is greater than length of file '%s'", name.c_str());
|
||||||
"Specified start position is greater than length of file '%s'\n", name.c_str()
|
return false;
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -995,18 +998,18 @@ void sect_BinaryFileSlice(std::string const &name, int32_t startPos, int32_t len
|
|||||||
if (int byte = fgetc(file); byte != EOF) {
|
if (int byte = fgetc(file); byte != EOF) {
|
||||||
writeByte(byte);
|
writeByte(byte);
|
||||||
} else if (ferror(file)) {
|
} 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 {
|
} else {
|
||||||
error(
|
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(),
|
name.c_str(),
|
||||||
length + 1
|
length + 1
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Section stack routines
|
|
||||||
void sect_PushSection() {
|
void sect_PushSection() {
|
||||||
sectionStack.push_front({
|
sectionStack.push_front({
|
||||||
.section = currentSection,
|
.section = currentSection,
|
||||||
@@ -1026,7 +1029,7 @@ void sect_PushSection() {
|
|||||||
|
|
||||||
void sect_PopSection() {
|
void sect_PopSection() {
|
||||||
if (sectionStack.empty()) {
|
if (sectionStack.empty()) {
|
||||||
fatalerror("No entries in the section stack\n");
|
fatal("No entries in the section stack");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentLoadSection) {
|
if (currentLoadSection) {
|
||||||
@@ -1047,17 +1050,17 @@ void sect_PopSection() {
|
|||||||
|
|
||||||
void sect_CheckStack() {
|
void sect_CheckStack() {
|
||||||
if (!sectionStack.empty()) {
|
if (!sectionStack.empty()) {
|
||||||
warning(WARNING_UNMATCHED_DIRECTIVE, "`PUSHS` without corresponding `POPS`\n");
|
warning(WARNING_UNMATCHED_DIRECTIVE, "`PUSHS` without corresponding `POPS`");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void sect_EndSection() {
|
void sect_EndSection() {
|
||||||
if (!currentSection) {
|
if (!currentSection) {
|
||||||
fatalerror("Cannot end the section outside of a SECTION\n");
|
fatal("Cannot end the section outside of a SECTION");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!currentUnionStack.empty()) {
|
if (!currentUnionStack.empty()) {
|
||||||
fatalerror("Cannot end the section within a UNION\n");
|
fatal("Cannot end the section within a UNION");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentLoadSection) {
|
if (currentLoadSection) {
|
||||||
@@ -1068,3 +1071,40 @@ void sect_EndSection() {
|
|||||||
currentSection = nullptr;
|
currentSection = nullptr;
|
||||||
sym_ResetCurrentLabelScopes();
|
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++);
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,12 +3,13 @@
|
|||||||
#include "asm/symbol.hpp"
|
#include "asm/symbol.hpp"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <errno.h>
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
|
|
||||||
#include "error.hpp"
|
#include "diagnostics.hpp"
|
||||||
#include "helpers.hpp" // assume
|
#include "helpers.hpp" // assume
|
||||||
#include "util.hpp"
|
#include "util.hpp"
|
||||||
#include "version.hpp"
|
#include "version.hpp"
|
||||||
@@ -21,21 +22,24 @@
|
|||||||
|
|
||||||
using namespace std::literals;
|
using namespace std::literals;
|
||||||
|
|
||||||
std::unordered_map<std::string, Symbol> symbols;
|
static std::unordered_map<std::string, Symbol> symbols;
|
||||||
std::unordered_set<std::string> purgedSymbols;
|
static std::unordered_set<std::string> purgedSymbols;
|
||||||
|
|
||||||
static Symbol const *globalScope = nullptr; // Current section's global label scope
|
static Symbol const *globalScope = nullptr; // Current section's global label scope
|
||||||
static Symbol const *localScope = nullptr; // Current section's local label scope
|
static Symbol const *localScope = nullptr; // Current section's local label scope
|
||||||
|
|
||||||
static Symbol *PCSymbol;
|
static Symbol *PCSymbol;
|
||||||
static Symbol *NARGSymbol;
|
static Symbol *NARGSymbol;
|
||||||
static Symbol *globalScopeSymbol;
|
static Symbol *globalScopeSymbol;
|
||||||
static Symbol *localScopeSymbol;
|
static Symbol *localScopeSymbol;
|
||||||
static Symbol *RSSymbol;
|
static Symbol *RSSymbol;
|
||||||
|
|
||||||
static char savedTIME[256];
|
static char savedTIME[256];
|
||||||
static char savedDATE[256];
|
static char savedDATE[256];
|
||||||
static char savedTIMESTAMP_ISO8601_LOCAL[256];
|
static char savedTIMESTAMP_ISO8601_LOCAL[256];
|
||||||
static char savedTIMESTAMP_ISO8601_UTC[256];
|
static char savedTIMESTAMP_ISO8601_UTC[256];
|
||||||
static bool exportAll;
|
|
||||||
|
static bool exportAll = false; // -E
|
||||||
|
|
||||||
bool sym_IsPC(Symbol const *sym) {
|
bool sym_IsPC(Symbol const *sym) {
|
||||||
return sym == PCSymbol;
|
return sym == PCSymbol;
|
||||||
@@ -51,14 +55,14 @@ static int32_t NARGCallback() {
|
|||||||
if (MacroArgs const *macroArgs = fstk_GetCurrentMacroArgs(); macroArgs) {
|
if (MacroArgs const *macroArgs = fstk_GetCurrentMacroArgs(); macroArgs) {
|
||||||
return macroArgs->nbArgs();
|
return macroArgs->nbArgs();
|
||||||
} else {
|
} else {
|
||||||
error("_NARG has no value outside of a macro\n");
|
error("_NARG has no value outside of a macro");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::shared_ptr<std::string> globalScopeCallback() {
|
static std::shared_ptr<std::string> globalScopeCallback() {
|
||||||
if (!globalScope) {
|
if (!globalScope) {
|
||||||
error("\".\" has no value outside of a label scope\n");
|
error("\".\" has no value outside of a label scope");
|
||||||
return std::make_shared<std::string>("");
|
return std::make_shared<std::string>("");
|
||||||
}
|
}
|
||||||
return std::make_shared<std::string>(globalScope->name);
|
return std::make_shared<std::string>(globalScope->name);
|
||||||
@@ -66,7 +70,7 @@ static std::shared_ptr<std::string> globalScopeCallback() {
|
|||||||
|
|
||||||
static std::shared_ptr<std::string> localScopeCallback() {
|
static std::shared_ptr<std::string> localScopeCallback() {
|
||||||
if (!localScope) {
|
if (!localScope) {
|
||||||
error("\"..\" has no value outside of a local label scope\n");
|
error("\"..\" has no value outside of a local label scope");
|
||||||
return std::make_shared<std::string>("");
|
return std::make_shared<std::string>("");
|
||||||
}
|
}
|
||||||
return std::make_shared<std::string>(localScope->name);
|
return std::make_shared<std::string>(localScope->name);
|
||||||
@@ -111,13 +115,13 @@ std::shared_ptr<std::string> Symbol::getEqus() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void dumpFilename(Symbol const &sym) {
|
static void dumpFilename(Symbol const &sym) {
|
||||||
|
fputs(" at ", stderr);
|
||||||
if (sym.src) {
|
if (sym.src) {
|
||||||
sym.src->dump(sym.fileLine);
|
sym.src->dump(sym.fileLine);
|
||||||
putc('\n', stderr);
|
|
||||||
} else if (sym.isBuiltin) {
|
} else if (sym.isBuiltin) {
|
||||||
fputs("<builtin>\n", stderr);
|
fputs("<builtin>", stderr);
|
||||||
} else {
|
} else {
|
||||||
fputs("<command-line>\n", stderr);
|
fputs("<command-line>", stderr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,23 +144,25 @@ static bool isValidIdentifier(std::string const &s) {
|
|||||||
static void alreadyDefinedError(Symbol const &sym, char const *asType) {
|
static void alreadyDefinedError(Symbol const &sym, char const *asType) {
|
||||||
if (sym.isBuiltin && !sym_FindScopedValidSymbol(sym.name)) {
|
if (sym.isBuiltin && !sym_FindScopedValidSymbol(sym.name)) {
|
||||||
// `DEF()` would return false, so we should not claim the symbol is already defined
|
// `DEF()` would return false, so we should not claim the symbol is already defined
|
||||||
error("'%s' is reserved for a built-in symbol\n", sym.name.c_str());
|
error("'%s' is reserved for a built-in symbol", sym.name.c_str());
|
||||||
} else {
|
} else {
|
||||||
error("'%s' already defined", sym.name.c_str());
|
error([&]() {
|
||||||
if (asType) {
|
fprintf(stderr, "'%s' already defined", sym.name.c_str());
|
||||||
fprintf(stderr, " as %s", asType);
|
if (asType) {
|
||||||
}
|
fprintf(stderr, " as %s", asType);
|
||||||
fputs(" at ", stderr);
|
}
|
||||||
dumpFilename(sym);
|
dumpFilename(sym);
|
||||||
if (sym.type == SYM_EQUS) {
|
if (sym.type != SYM_EQUS) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (std::string const &contents = *sym.getEqus(); isValidIdentifier(contents)) {
|
if (std::string const &contents = *sym.getEqus(); isValidIdentifier(contents)) {
|
||||||
fprintf(
|
fprintf(
|
||||||
stderr,
|
stderr,
|
||||||
" (should it be {interpolated} to define its contents \"%s\"?)\n",
|
"\n (should it be {interpolated} to define its contents \"%s\"?)",
|
||||||
contents.c_str()
|
contents.c_str()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,9 +170,9 @@ static void redefinedError(Symbol const &sym) {
|
|||||||
assume(sym.isBuiltin);
|
assume(sym.isBuiltin);
|
||||||
if (!sym_FindScopedValidSymbol(sym.name)) {
|
if (!sym_FindScopedValidSymbol(sym.name)) {
|
||||||
// `DEF()` would return false, so we should not imply the symbol is already defined
|
// `DEF()` would return false, so we should not imply the symbol is already defined
|
||||||
error("'%s' is reserved for a built-in symbol\n", sym.name.c_str());
|
error("'%s' is reserved for a built-in symbol", sym.name.c_str());
|
||||||
} else {
|
} else {
|
||||||
error("Built-in symbol '%s' cannot be redefined\n", sym.name.c_str());
|
error("Built-in symbol '%s' cannot be redefined", sym.name.c_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -215,12 +221,12 @@ static bool isAutoScoped(std::string const &symName) {
|
|||||||
|
|
||||||
// Check for nothing after the dot
|
// Check for nothing after the dot
|
||||||
if (dotPos == symName.length() - 1) {
|
if (dotPos == symName.length() - 1) {
|
||||||
fatalerror("'%s' is a nonsensical reference to an empty local label\n", symName.c_str());
|
fatal("'%s' is a nonsensical reference to an empty local label", symName.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for more than one dot
|
// Check for more than one dot
|
||||||
if (symName.find('.', dotPos + 1) != std::string::npos) {
|
if (symName.find('.', dotPos + 1) != std::string::npos) {
|
||||||
fatalerror("'%s' is a nonsensical reference to a nested local label\n", symName.c_str());
|
fatal("'%s' is a nonsensical reference to a nested local label", symName.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for already-qualified local label
|
// Check for already-qualified local label
|
||||||
@@ -230,7 +236,7 @@ static bool isAutoScoped(std::string const &symName) {
|
|||||||
|
|
||||||
// Check for unqualifiable local label
|
// Check for unqualifiable local label
|
||||||
if (!globalScope) {
|
if (!globalScope) {
|
||||||
fatalerror("Unqualified local label '%s' in main scope\n", symName.c_str());
|
fatal("Unqualified local label '%s' in main scope", symName.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -279,19 +285,19 @@ void sym_Purge(std::string const &symName) {
|
|||||||
|
|
||||||
if (!sym) {
|
if (!sym) {
|
||||||
if (sym_IsPurgedScoped(symName)) {
|
if (sym_IsPurgedScoped(symName)) {
|
||||||
error("'%s' was already purged\n", symName.c_str());
|
error("'%s' was already purged", symName.c_str());
|
||||||
} else {
|
} else {
|
||||||
error("'%s' not defined\n", symName.c_str());
|
error("'%s' not defined", symName.c_str());
|
||||||
}
|
}
|
||||||
} else if (sym->isBuiltin) {
|
} else if (sym->isBuiltin) {
|
||||||
error("Built-in symbol '%s' cannot be purged\n", symName.c_str());
|
error("Built-in symbol '%s' cannot be purged", symName.c_str());
|
||||||
} else if (sym->ID != UINT32_MAX) {
|
} else if (sym->ID != UINT32_MAX) {
|
||||||
error("Symbol \"%s\" is referenced and thus cannot be purged\n", symName.c_str());
|
error("Symbol \"%s\" is referenced and thus cannot be purged", symName.c_str());
|
||||||
} else {
|
} else {
|
||||||
if (sym->isExported) {
|
if (sym->isExported) {
|
||||||
warning(WARNING_PURGE_1, "Purging an exported symbol \"%s\"\n", symName.c_str());
|
warning(WARNING_PURGE_1, "Purging an exported symbol \"%s\"", symName.c_str());
|
||||||
} else if (sym->isLabel()) {
|
} else if (sym->isLabel()) {
|
||||||
warning(WARNING_PURGE_2, "Purging a label \"%s\"\n", symName.c_str());
|
warning(WARNING_PURGE_2, "Purging a label \"%s\"", symName.c_str());
|
||||||
}
|
}
|
||||||
// Do not keep a reference to the label after purging it
|
// Do not keep a reference to the label after purging it
|
||||||
if (sym == globalScope) {
|
if (sym == globalScope) {
|
||||||
@@ -331,12 +337,12 @@ uint32_t Symbol::getConstantValue() const {
|
|||||||
|
|
||||||
if (sym_IsPC(this)) {
|
if (sym_IsPC(this)) {
|
||||||
if (!getSection()) {
|
if (!getSection()) {
|
||||||
error("PC has no value outside of a section\n");
|
error("PC has no value outside of a section");
|
||||||
} else {
|
} else {
|
||||||
error("PC does not have a constant value; the current section is not fixed\n");
|
error("PC does not have a constant value; the current section is not fixed");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
error("\"%s\" does not have a constant value\n", name.c_str());
|
error("\"%s\" does not have a constant value", name.c_str());
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -371,8 +377,10 @@ static Symbol *createNonrelocSymbol(std::string const &symName, bool numeric) {
|
|||||||
return nullptr; // Don't allow overriding the symbol, that'd be bad!
|
return nullptr; // Don't allow overriding the symbol, that'd be bad!
|
||||||
} else if (!numeric) {
|
} else if (!numeric) {
|
||||||
// The symbol has already been referenced, but it's not allowed
|
// The symbol has already been referenced, but it's not allowed
|
||||||
error("'%s' already referenced at ", symName.c_str());
|
error([&]() {
|
||||||
dumpFilename(*sym);
|
fprintf(stderr, "'%s' already referenced", symName.c_str());
|
||||||
|
dumpFilename(*sym);
|
||||||
|
});
|
||||||
return nullptr; // Don't allow overriding the symbol, that'd be bad!
|
return nullptr; // Don't allow overriding the symbol, that'd be bad!
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -437,8 +445,10 @@ Symbol *sym_RedefString(std::string const &symName, std::shared_ptr<std::string>
|
|||||||
if (sym->isDefined()) {
|
if (sym->isDefined()) {
|
||||||
alreadyDefinedError(*sym, "non-EQUS");
|
alreadyDefinedError(*sym, "non-EQUS");
|
||||||
} else {
|
} else {
|
||||||
error("'%s' already referenced at ", symName.c_str());
|
error([&]() {
|
||||||
dumpFilename(*sym);
|
fprintf(stderr, "'%s' already referenced", symName.c_str());
|
||||||
|
dumpFilename(*sym);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return nullptr;
|
return nullptr;
|
||||||
} else if (sym->isBuiltin) {
|
} else if (sym->isBuiltin) {
|
||||||
@@ -493,7 +503,7 @@ static Symbol *addLabel(std::string const &symName) {
|
|||||||
sym->section = sect_GetSymbolSection();
|
sym->section = sect_GetSymbolSection();
|
||||||
|
|
||||||
if (sym && !sym->section) {
|
if (sym && !sym->section) {
|
||||||
error("Label \"%s\" created outside of a SECTION\n", symName.c_str());
|
error("Label \"%s\" created outside of a SECTION", symName.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
return sym;
|
return sym;
|
||||||
@@ -538,7 +548,7 @@ Symbol *sym_AddAnonLabel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::string anon = sym_MakeAnonLabelName(0, true); // The direction is important!
|
std::string anon = sym_MakeAnonLabelName(0, true); // The direction is important!
|
||||||
anonLabelID++;
|
++anonLabelID;
|
||||||
return addLabel(anon);
|
return addLabel(anon);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -549,7 +559,7 @@ std::string sym_MakeAnonLabelName(uint32_t ofs, bool neg) {
|
|||||||
if (ofs > anonLabelID) {
|
if (ofs > anonLabelID) {
|
||||||
error(
|
error(
|
||||||
"Reference to anonymous label %" PRIu32 " before, when only %" PRIu32
|
"Reference to anonymous label %" PRIu32 " before, when only %" PRIu32
|
||||||
" ha%s been created so far\n",
|
" ha%s been created so far",
|
||||||
ofs,
|
ofs,
|
||||||
anonLabelID,
|
anonLabelID,
|
||||||
anonLabelID == 1 ? "s" : "ve"
|
anonLabelID == 1 ? "s" : "ve"
|
||||||
@@ -558,12 +568,12 @@ std::string sym_MakeAnonLabelName(uint32_t ofs, bool neg) {
|
|||||||
id = anonLabelID - ofs;
|
id = anonLabelID - ofs;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ofs--; // We're referencing symbols that haven't been created yet...
|
// We're referencing symbols that haven't been created yet...
|
||||||
if (ofs > UINT32_MAX - anonLabelID) {
|
if (--ofs > UINT32_MAX - anonLabelID) {
|
||||||
// LCOV_EXCL_START
|
// LCOV_EXCL_START
|
||||||
error(
|
error(
|
||||||
"Reference to anonymous label %" PRIu32 " after, when only %" PRIu32
|
"Reference to anonymous label %" PRIu32 " after, when only %" PRIu32
|
||||||
" may still be created\n",
|
" may still be created",
|
||||||
ofs + 1,
|
ofs + 1,
|
||||||
UINT32_MAX - anonLabelID
|
UINT32_MAX - anonLabelID
|
||||||
);
|
);
|
||||||
@@ -580,7 +590,7 @@ void sym_Export(std::string const &symName) {
|
|||||||
if (symName.starts_with('!')) {
|
if (symName.starts_with('!')) {
|
||||||
// LCOV_EXCL_START
|
// LCOV_EXCL_START
|
||||||
// The parser does not accept anonymous labels for an `EXPORT` directive
|
// The parser does not accept anonymous labels for an `EXPORT` directive
|
||||||
error("Anonymous labels cannot be exported\n");
|
error("Anonymous labels cannot be exported");
|
||||||
return;
|
return;
|
||||||
// LCOV_EXCL_STOP
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
@@ -666,7 +676,7 @@ void sym_Init(time_t now) {
|
|||||||
|
|
||||||
// LCOV_EXCL_START
|
// LCOV_EXCL_START
|
||||||
if (now == static_cast<time_t>(-1)) {
|
if (now == static_cast<time_t>(-1)) {
|
||||||
warn("Failed to determine current time");
|
warnx("Failed to determine current time: %s", strerror(errno));
|
||||||
// Fall back by pretending we are at the Epoch
|
// Fall back by pretending we are at the Epoch
|
||||||
now = 0;
|
now = 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include "error.hpp"
|
#include "diagnostics.hpp"
|
||||||
#include "helpers.hpp"
|
#include "helpers.hpp"
|
||||||
#include "itertools.hpp"
|
#include "itertools.hpp"
|
||||||
|
|
||||||
@@ -17,296 +17,82 @@
|
|||||||
#include "asm/lexer.hpp"
|
#include "asm/lexer.hpp"
|
||||||
#include "asm/main.hpp"
|
#include "asm/main.hpp"
|
||||||
|
|
||||||
unsigned int nbErrors = 0;
|
// clang-format off: nested initializers
|
||||||
unsigned int maxErrors = 0;
|
Diagnostics<WarningLevel, WarningID> warnings = {
|
||||||
|
.metaWarnings = {
|
||||||
Diagnostics warningStates;
|
{"all", LEVEL_ALL },
|
||||||
bool warningsAreErrors;
|
{"extra", LEVEL_EXTRA },
|
||||||
|
{"everything", LEVEL_EVERYTHING},
|
||||||
enum WarningLevel {
|
},
|
||||||
LEVEL_DEFAULT, // Warnings that are enabled by default
|
.warningFlags = {
|
||||||
LEVEL_ALL, // Warnings that probably indicate an error
|
{"assert", LEVEL_DEFAULT },
|
||||||
LEVEL_EXTRA, // Warnings that are less likely to indicate an error
|
{"backwards-for", LEVEL_ALL },
|
||||||
LEVEL_EVERYTHING, // Literally every warning
|
{"builtin-args", LEVEL_ALL },
|
||||||
|
{"charmap-redef", LEVEL_ALL },
|
||||||
|
{"div", LEVEL_EVERYTHING},
|
||||||
|
{"empty-data-directive", LEVEL_ALL },
|
||||||
|
{"empty-macro-arg", LEVEL_EXTRA },
|
||||||
|
{"empty-strrpl", LEVEL_ALL },
|
||||||
|
{"large-constant", LEVEL_ALL },
|
||||||
|
{"macro-shift", LEVEL_EXTRA },
|
||||||
|
{"nested-comment", LEVEL_DEFAULT },
|
||||||
|
{"obsolete", LEVEL_DEFAULT },
|
||||||
|
{"shift", LEVEL_EVERYTHING},
|
||||||
|
{"shift-amount", LEVEL_EVERYTHING},
|
||||||
|
{"unmatched-directive", LEVEL_EXTRA },
|
||||||
|
{"unterminated-load", LEVEL_EXTRA },
|
||||||
|
{"user", LEVEL_DEFAULT },
|
||||||
|
// Parametric warnings
|
||||||
|
{"numeric-string", LEVEL_EVERYTHING},
|
||||||
|
{"numeric-string", LEVEL_EVERYTHING},
|
||||||
|
{"purge", LEVEL_DEFAULT },
|
||||||
|
{"purge", LEVEL_ALL },
|
||||||
|
{"truncation", LEVEL_DEFAULT },
|
||||||
|
{"truncation", LEVEL_EXTRA },
|
||||||
|
{"unmapped-char", LEVEL_DEFAULT },
|
||||||
|
{"unmapped-char", LEVEL_ALL },
|
||||||
|
},
|
||||||
|
.paramWarnings = {
|
||||||
|
{WARNING_NUMERIC_STRING_1, WARNING_NUMERIC_STRING_2, 1},
|
||||||
|
{WARNING_PURGE_1, WARNING_PURGE_2, 1},
|
||||||
|
{WARNING_TRUNCATION_1, WARNING_TRUNCATION_2, 2},
|
||||||
|
{WARNING_UNMAPPED_CHAR_1, WARNING_UNMAPPED_CHAR_2, 1},
|
||||||
|
},
|
||||||
|
.state = DiagnosticsState<WarningID>(),
|
||||||
|
.nbErrors = 0,
|
||||||
};
|
};
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
struct WarningFlag {
|
static void printDiag(
|
||||||
char const *name;
|
|
||||||
WarningLevel level;
|
|
||||||
};
|
|
||||||
|
|
||||||
static WarningFlag const metaWarnings[] = {
|
|
||||||
{"all", LEVEL_ALL },
|
|
||||||
{"extra", LEVEL_EXTRA },
|
|
||||||
{"everything", LEVEL_EVERYTHING},
|
|
||||||
};
|
|
||||||
|
|
||||||
static WarningFlag const warningFlags[NB_WARNINGS] = {
|
|
||||||
{"assert", LEVEL_DEFAULT },
|
|
||||||
{"backwards-for", LEVEL_ALL },
|
|
||||||
{"builtin-args", LEVEL_ALL },
|
|
||||||
{"charmap-redef", LEVEL_ALL },
|
|
||||||
{"div", LEVEL_EVERYTHING},
|
|
||||||
{"empty-data-directive", LEVEL_ALL },
|
|
||||||
{"empty-macro-arg", LEVEL_EXTRA },
|
|
||||||
{"empty-strrpl", LEVEL_ALL },
|
|
||||||
{"large-constant", LEVEL_ALL },
|
|
||||||
{"macro-shift", LEVEL_EXTRA },
|
|
||||||
{"nested-comment", LEVEL_DEFAULT },
|
|
||||||
{"obsolete", LEVEL_DEFAULT },
|
|
||||||
{"shift", LEVEL_EVERYTHING},
|
|
||||||
{"shift-amount", LEVEL_EVERYTHING},
|
|
||||||
{"unmatched-directive", LEVEL_EXTRA },
|
|
||||||
{"unterminated-load", LEVEL_EXTRA },
|
|
||||||
{"user", LEVEL_DEFAULT },
|
|
||||||
// Parametric warnings
|
|
||||||
{"numeric-string", LEVEL_EVERYTHING},
|
|
||||||
{"numeric-string", LEVEL_EVERYTHING},
|
|
||||||
{"purge", LEVEL_DEFAULT },
|
|
||||||
{"purge", LEVEL_ALL },
|
|
||||||
{"truncation", LEVEL_DEFAULT },
|
|
||||||
{"truncation", LEVEL_EXTRA },
|
|
||||||
{"unmapped-char", LEVEL_DEFAULT },
|
|
||||||
{"unmapped-char", LEVEL_ALL },
|
|
||||||
};
|
|
||||||
|
|
||||||
static const struct {
|
|
||||||
WarningID firstID;
|
|
||||||
WarningID lastID;
|
|
||||||
uint8_t defaultLevel;
|
|
||||||
} paramWarnings[] = {
|
|
||||||
{WARNING_NUMERIC_STRING_1, WARNING_NUMERIC_STRING_2, 1},
|
|
||||||
{WARNING_PURGE_1, WARNING_PURGE_2, 1},
|
|
||||||
{WARNING_TRUNCATION_1, WARNING_TRUNCATION_2, 2},
|
|
||||||
{WARNING_UNMAPPED_CHAR_1, WARNING_UNMAPPED_CHAR_2, 1},
|
|
||||||
};
|
|
||||||
|
|
||||||
enum WarningBehavior { DISABLED, ENABLED, ERROR };
|
|
||||||
|
|
||||||
static WarningBehavior getWarningBehavior(WarningID id) {
|
|
||||||
// Check if warnings are globally disabled
|
|
||||||
if (!warnings) {
|
|
||||||
return WarningBehavior::DISABLED;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the state of this warning flag
|
|
||||||
WarningState const &flagState = warningStates.flagStates[id];
|
|
||||||
WarningState const &metaState = warningStates.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 = 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 == LEVEL_DEFAULT) { // enabled by default
|
|
||||||
return enabledBehavior;
|
|
||||||
}
|
|
||||||
|
|
||||||
// No flag enables this warning, explicitly or implicitly
|
|
||||||
return WarningBehavior::DISABLED;
|
|
||||||
}
|
|
||||||
|
|
||||||
void WarningState::update(WarningState other) {
|
|
||||||
if (other.state != WARNING_DEFAULT) {
|
|
||||||
state = other.state;
|
|
||||||
}
|
|
||||||
if (other.error != WARNING_DEFAULT) {
|
|
||||||
error = other.error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void 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
|
|
||||||
warningsAreErrors = true;
|
|
||||||
return;
|
|
||||||
} else if (rootFlag == "no-error") {
|
|
||||||
// `-Wno-error` disables promotion of warnings to errors
|
|
||||||
warningsAreErrors = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for prefixes that affect what the flag does
|
|
||||||
WarningState state;
|
|
||||||
if (rootFlag.starts_with("error=")) {
|
|
||||||
// `-Werror=<flag>` enables the flag as an error
|
|
||||||
state = {.state = WARNING_ENABLED, .error = WARNING_ENABLED};
|
|
||||||
rootFlag.erase(0, literal_strlen("error="));
|
|
||||||
} else if (rootFlag.starts_with("no-error=")) {
|
|
||||||
// `-Wno-error=<flag>` prevents the flag from being an error,
|
|
||||||
// without affecting whether it is enabled
|
|
||||||
state = {.state = WARNING_DEFAULT, .error = WARNING_DISABLED};
|
|
||||||
rootFlag.erase(0, literal_strlen("no-error="));
|
|
||||||
} else if (rootFlag.starts_with("no-")) {
|
|
||||||
// `-Wno-<flag>` disables the flag
|
|
||||||
state = {.state = WARNING_DISABLED, .error = WARNING_DEFAULT};
|
|
||||||
rootFlag.erase(0, literal_strlen("no-"));
|
|
||||||
} else {
|
|
||||||
// `-W<flag>` enables the flag
|
|
||||||
state = {.state = WARNING_ENABLED, .error = WARNING_DEFAULT};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for an `=` parameter to process as a parametric warning
|
|
||||||
// `-Wno-<flag>` and `-Wno-error=<flag>` negation cannot have an `=` parameter, but without a
|
|
||||||
// parameter, the 0 value will apply to all levels of a parametric warning
|
|
||||||
uint8_t param = 0;
|
|
||||||
bool hasParam = false;
|
|
||||||
if (state.state == WARNING_ENABLED) {
|
|
||||||
// First, check if there is an "equals" sign followed by a decimal number
|
|
||||||
// Ignore an equal sign at the very end of the string
|
|
||||||
if (auto equals = rootFlag.find('=');
|
|
||||||
equals != rootFlag.npos && equals != rootFlag.size() - 1) {
|
|
||||||
hasParam = true;
|
|
||||||
|
|
||||||
// Is the rest of the string a decimal number?
|
|
||||||
// We want to avoid `strtoul`'s whitespace and sign, so we parse manually
|
|
||||||
char const *ptr = rootFlag.c_str() + equals + 1;
|
|
||||||
bool warned = false;
|
|
||||||
|
|
||||||
// The `if`'s condition above ensures that this will run at least once
|
|
||||||
do {
|
|
||||||
// If we don't have a digit, bail
|
|
||||||
if (*ptr < '0' || *ptr > '9') {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// Avoid overflowing!
|
|
||||||
if (param > UINT8_MAX - (*ptr - '0')) {
|
|
||||||
if (!warned) {
|
|
||||||
warnx("Invalid warning flag \"%s\": capping parameter at 255", flag);
|
|
||||||
}
|
|
||||||
warned = true; // Only warn once, cap always
|
|
||||||
param = 255;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
param = param * 10 + (*ptr - '0');
|
|
||||||
|
|
||||||
ptr++;
|
|
||||||
} while (*ptr);
|
|
||||||
|
|
||||||
// If we reached the end of the string, truncate it at the '='
|
|
||||||
if (*ptr == '\0') {
|
|
||||||
rootFlag.resize(equals);
|
|
||||||
// `-W<flag>=0` is equivalent to `-Wno-<flag>`
|
|
||||||
if (param == 0) {
|
|
||||||
state.state = WARNING_DISABLED;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 (auto const ¶mWarning : paramWarnings) {
|
|
||||||
WarningID baseID = paramWarning.firstID;
|
|
||||||
uint8_t maxParam = paramWarning.lastID - baseID + 1;
|
|
||||||
assume(paramWarning.defaultLevel <= maxParam);
|
|
||||||
|
|
||||||
if (rootFlag == warningFlags[baseID].name) { // Match!
|
|
||||||
if (rootFlag == "numeric-string") {
|
|
||||||
warning(WARNING_OBSOLETE, "Warning flag \"numeric-string\" is deprecated\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
// If making the warning an error but param is 0, set to the maximum
|
|
||||||
// This accommodates `-Werror=<flag>`, but also `-Werror=<flag>=0`, which is
|
|
||||||
// thus filtered out by the caller.
|
|
||||||
// A param of 0 makes sense for disabling everything, but neither for
|
|
||||||
// enabling nor "erroring". Use the default for those.
|
|
||||||
if (param == 0) {
|
|
||||||
param = paramWarning.defaultLevel;
|
|
||||||
} else if (param > maxParam) {
|
|
||||||
if (param != 255) { // Don't warn if already capped
|
|
||||||
warnx(
|
|
||||||
"Invalid parameter %" PRIu8
|
|
||||||
" for warning flag \"%s\"; capping at maximum %" PRIu8,
|
|
||||||
param,
|
|
||||||
rootFlag.c_str(),
|
|
||||||
maxParam
|
|
||||||
);
|
|
||||||
}
|
|
||||||
param = maxParam;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the first <param> to enabled/error, and disable the rest
|
|
||||||
for (uint8_t ofs = 0; ofs < maxParam; ofs++) {
|
|
||||||
WarningState &warning = warningStates.flagStates[baseID + ofs];
|
|
||||||
if (ofs < param) {
|
|
||||||
warning.update(state);
|
|
||||||
} else {
|
|
||||||
warning.state = WARNING_DISABLED;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to match against a non-parametric warning, unless there was an equals sign
|
|
||||||
if (!hasParam) {
|
|
||||||
// Try to match against a "meta" warning
|
|
||||||
for (WarningFlag const &metaWarning : metaWarnings) {
|
|
||||||
if (rootFlag == metaWarning.name) {
|
|
||||||
// Set each of the warning flags that meets this level
|
|
||||||
for (WarningID id : EnumSeq(NB_WARNINGS)) {
|
|
||||||
if (metaWarning.level >= warningFlags[id].level) {
|
|
||||||
warningStates.metaStates[id].update(state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to match the flag against a "normal" flag
|
|
||||||
for (WarningID id : EnumSeq(NB_PLAIN_WARNINGS)) {
|
|
||||||
if (rootFlag == warningFlags[id].name) {
|
|
||||||
warningStates.flagStates[id].update(state);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
warnx("Unknown warning flag \"%s\"", flag);
|
|
||||||
}
|
|
||||||
|
|
||||||
void printDiag(
|
|
||||||
char const *fmt, va_list args, char const *type, char const *flagfmt, char const *flag
|
char const *fmt, va_list args, char const *type, char const *flagfmt, char const *flag
|
||||||
) {
|
) {
|
||||||
fputs(type, stderr);
|
fputs(type, stderr);
|
||||||
fputs(": ", stderr);
|
fputs(": ", stderr);
|
||||||
fstk_DumpCurrent();
|
if (fstk_DumpCurrent()) {
|
||||||
fprintf(stderr, flagfmt, flag);
|
fprintf(stderr, flagfmt, flag);
|
||||||
fputs("\n ", stderr);
|
fputs("\n ", stderr);
|
||||||
|
}
|
||||||
vfprintf(stderr, fmt, args);
|
vfprintf(stderr, fmt, args);
|
||||||
|
putc('\n', stderr);
|
||||||
|
|
||||||
lexer_DumpStringExpansions();
|
lexer_DumpStringExpansions();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void incrementErrors() {
|
||||||
|
// This intentionally makes 0 act as "unlimited"
|
||||||
|
warnings.incrementErrors();
|
||||||
|
if (warnings.nbErrors == options.maxErrors) {
|
||||||
|
fprintf(
|
||||||
|
stderr,
|
||||||
|
"Assembly aborted after the maximum of %" PRIu64 " error%s! (configure with "
|
||||||
|
"'-X/--max-errors')\n",
|
||||||
|
warnings.nbErrors,
|
||||||
|
warnings.nbErrors == 1 ? "" : "s"
|
||||||
|
);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void error(char const *fmt, ...) {
|
void error(char const *fmt, ...) {
|
||||||
va_list args;
|
va_list args;
|
||||||
|
|
||||||
@@ -314,20 +100,23 @@ void error(char const *fmt, ...) {
|
|||||||
printDiag(fmt, args, "error", ":", nullptr);
|
printDiag(fmt, args, "error", ":", nullptr);
|
||||||
va_end(args);
|
va_end(args);
|
||||||
|
|
||||||
// This intentionally makes 0 act as "unlimited" (or at least "limited to sizeof(unsigned)")
|
incrementErrors();
|
||||||
nbErrors++;
|
}
|
||||||
if (nbErrors == maxErrors) {
|
|
||||||
errx(
|
void error(std::function<void()> callback) {
|
||||||
"The maximum of %u error%s was reached (configure with \"-X/--max-errors\"); assembly "
|
fputs("error: ", stderr);
|
||||||
"aborted!",
|
if (fstk_DumpCurrent()) {
|
||||||
maxErrors,
|
fputs(":\n ", stderr);
|
||||||
maxErrors == 1 ? "" : "s"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
callback();
|
||||||
|
putc('\n', stderr);
|
||||||
|
lexer_DumpStringExpansions();
|
||||||
|
|
||||||
|
incrementErrors();
|
||||||
}
|
}
|
||||||
|
|
||||||
[[noreturn]]
|
[[noreturn]]
|
||||||
void fatalerror(char const *fmt, ...) {
|
void fatal(char const *fmt, ...) {
|
||||||
va_list args;
|
va_list args;
|
||||||
|
|
||||||
va_start(args, fmt);
|
va_start(args, fmt);
|
||||||
@@ -337,13 +126,25 @@ void fatalerror(char const *fmt, ...) {
|
|||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void requireZeroErrors() {
|
||||||
|
if (warnings.nbErrors != 0) {
|
||||||
|
fprintf(
|
||||||
|
stderr,
|
||||||
|
"Assembly aborted with %" PRIu64 " error%s!\n",
|
||||||
|
warnings.nbErrors,
|
||||||
|
warnings.nbErrors == 1 ? "" : "s"
|
||||||
|
);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void warning(WarningID id, char const *fmt, ...) {
|
void warning(WarningID id, char const *fmt, ...) {
|
||||||
char const *flag = warningFlags[id].name;
|
char const *flag = warnings.warningFlags[id].name;
|
||||||
va_list args;
|
va_list args;
|
||||||
|
|
||||||
va_start(args, fmt);
|
va_start(args, fmt);
|
||||||
|
|
||||||
switch (getWarningBehavior(id)) {
|
switch (warnings.getWarningBehavior(id)) {
|
||||||
case WarningBehavior::DISABLED:
|
case WarningBehavior::DISABLED:
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|||||||
81
src/diagnostics.cpp
Normal file
81
src/diagnostics.cpp
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
#include "diagnostics.hpp"
|
||||||
|
|
||||||
|
void warnx(char const *fmt, ...) {
|
||||||
|
va_list ap;
|
||||||
|
fputs("warning: ", stderr);
|
||||||
|
va_start(ap, fmt);
|
||||||
|
vfprintf(stderr, fmt, ap);
|
||||||
|
va_end(ap);
|
||||||
|
putc('\n', stderr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WarningState::update(WarningState other) {
|
||||||
|
if (other.state != WARNING_DEFAULT) {
|
||||||
|
state = other.state;
|
||||||
|
}
|
||||||
|
if (other.error != WARNING_DEFAULT) {
|
||||||
|
error = other.error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<WarningState, std::optional<uint32_t>> getInitialWarningState(std::string &flag) {
|
||||||
|
// Check for prefixes that affect what the flag does
|
||||||
|
WarningState state;
|
||||||
|
if (flag.starts_with("error=")) {
|
||||||
|
// `-Werror=<flag>` enables the flag as an error
|
||||||
|
state = {.state = WARNING_ENABLED, .error = WARNING_ENABLED};
|
||||||
|
flag.erase(0, literal_strlen("error="));
|
||||||
|
} else if (flag.starts_with("no-error=")) {
|
||||||
|
// `-Wno-error=<flag>` prevents the flag from being an error,
|
||||||
|
// without affecting whether it is enabled
|
||||||
|
state = {.state = WARNING_DEFAULT, .error = WARNING_DISABLED};
|
||||||
|
flag.erase(0, literal_strlen("no-error="));
|
||||||
|
} else if (flag.starts_with("no-")) {
|
||||||
|
// `-Wno-<flag>` disables the flag
|
||||||
|
state = {.state = WARNING_DISABLED, .error = WARNING_DEFAULT};
|
||||||
|
flag.erase(0, literal_strlen("no-"));
|
||||||
|
} else {
|
||||||
|
// `-W<flag>` enables the flag
|
||||||
|
state = {.state = WARNING_ENABLED, .error = WARNING_DEFAULT};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if there is an "equals" sign followed by a decimal number
|
||||||
|
// Ignore an equals sign at the very end of the string
|
||||||
|
auto equals = flag.find('=');
|
||||||
|
// `-Wno-<flag>` and `-Wno-error=<flag>` negation cannot have an `=` parameter, but without
|
||||||
|
// one, the 0 value will apply to all levels of a parametric warning
|
||||||
|
if (state.state != WARNING_ENABLED || equals == flag.npos || equals == flag.size() - 1) {
|
||||||
|
return {state, std::nullopt};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is the rest of the string a decimal number?
|
||||||
|
// We want to avoid `strtoul`'s whitespace and sign, so we parse manually
|
||||||
|
char const *ptr = flag.c_str() + equals + 1;
|
||||||
|
uint32_t param = 0;
|
||||||
|
bool overflowed = false;
|
||||||
|
|
||||||
|
for (; *ptr >= '0' && *ptr <= '9'; ++ptr) {
|
||||||
|
if (overflowed) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t c = *ptr - '0';
|
||||||
|
if (param > (UINT32_MAX - c) / 10) {
|
||||||
|
overflowed = true;
|
||||||
|
param = UINT32_MAX;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
param = param * 10 + c;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we reached the end of the string, truncate it at the '='
|
||||||
|
if (*ptr == '\0') {
|
||||||
|
flag.resize(equals);
|
||||||
|
// `-W<flag>=0` is equivalent to `-Wno-<flag>`
|
||||||
|
if (param == 0) {
|
||||||
|
state.state = WARNING_DISABLED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {state, param};
|
||||||
|
}
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
#include "error.hpp"
|
|
||||||
|
|
||||||
#include <errno.h>
|
|
||||||
#include <stdarg.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
static void vwarn(char const *fmt, va_list ap) {
|
|
||||||
char const *error = strerror(errno);
|
|
||||||
|
|
||||||
fprintf(stderr, "warning: ");
|
|
||||||
vfprintf(stderr, fmt, ap);
|
|
||||||
fprintf(stderr, ": %s\n", error);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void vwarnx(char const *fmt, va_list ap) {
|
|
||||||
fprintf(stderr, "warning: ");
|
|
||||||
vfprintf(stderr, fmt, ap);
|
|
||||||
putc('\n', stderr);
|
|
||||||
}
|
|
||||||
|
|
||||||
[[noreturn]]
|
|
||||||
static void verr(char const *fmt, va_list ap) {
|
|
||||||
char const *error = strerror(errno);
|
|
||||||
|
|
||||||
fprintf(stderr, "error: ");
|
|
||||||
vfprintf(stderr, fmt, ap);
|
|
||||||
fprintf(stderr, ": %s\n", error);
|
|
||||||
va_end(ap);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
[[noreturn]]
|
|
||||||
static void verrx(char const *fmt, va_list ap) {
|
|
||||||
fprintf(stderr, "error: ");
|
|
||||||
vfprintf(stderr, fmt, ap);
|
|
||||||
putc('\n', stderr);
|
|
||||||
va_end(ap);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
void warn(char const *fmt, ...) {
|
|
||||||
va_list ap;
|
|
||||||
|
|
||||||
va_start(ap, fmt);
|
|
||||||
vwarn(fmt, ap);
|
|
||||||
va_end(ap);
|
|
||||||
}
|
|
||||||
|
|
||||||
void warnx(char const *fmt, ...) {
|
|
||||||
va_list ap;
|
|
||||||
|
|
||||||
va_start(ap, fmt);
|
|
||||||
vwarnx(fmt, ap);
|
|
||||||
va_end(ap);
|
|
||||||
}
|
|
||||||
|
|
||||||
[[noreturn]]
|
|
||||||
void err(char const *fmt, ...) {
|
|
||||||
va_list ap;
|
|
||||||
|
|
||||||
va_start(ap, fmt);
|
|
||||||
verr(fmt, ap);
|
|
||||||
}
|
|
||||||
|
|
||||||
[[noreturn]]
|
|
||||||
void errx(char const *fmt, ...) {
|
|
||||||
va_list ap;
|
|
||||||
|
|
||||||
va_start(ap, fmt);
|
|
||||||
verrx(fmt, ap);
|
|
||||||
}
|
|
||||||
38
src/extern/getopt.cpp
vendored
38
src/extern/getopt.cpp
vendored
@@ -1,6 +1,6 @@
|
|||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
// This implementation was taken from musl and modified for RGBDS
|
// This implementation was taken from musl and modified for RGBDS.
|
||||||
|
|
||||||
#include "extern/getopt.hpp"
|
#include "extern/getopt.hpp"
|
||||||
|
|
||||||
@@ -57,7 +57,7 @@ static int getopt(int argc, char *argv[], char const *optstring) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!musl_optpos) {
|
if (!musl_optpos) {
|
||||||
musl_optpos++;
|
++musl_optpos;
|
||||||
}
|
}
|
||||||
k = mbtowc(&c, argv[musl_optind] + musl_optpos, MB_LEN_MAX);
|
k = mbtowc(&c, argv[musl_optind] + musl_optpos, MB_LEN_MAX);
|
||||||
if (k < 0) {
|
if (k < 0) {
|
||||||
@@ -68,12 +68,12 @@ static int getopt(int argc, char *argv[], char const *optstring) {
|
|||||||
musl_optpos += k;
|
musl_optpos += k;
|
||||||
|
|
||||||
if (!argv[musl_optind][musl_optpos]) {
|
if (!argv[musl_optind][musl_optpos]) {
|
||||||
musl_optind++;
|
++musl_optind;
|
||||||
musl_optpos = 0;
|
musl_optpos = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (optstring[0] == '-' || optstring[0] == '+') {
|
if (optstring[0] == '-' || optstring[0] == '+') {
|
||||||
optstring++;
|
++optstring;
|
||||||
}
|
}
|
||||||
|
|
||||||
i = 0;
|
i = 0;
|
||||||
@@ -83,7 +83,7 @@ static int getopt(int argc, char *argv[], char const *optstring) {
|
|||||||
if (l > 0) {
|
if (l > 0) {
|
||||||
i += l;
|
i += l;
|
||||||
} else {
|
} else {
|
||||||
i++;
|
++i;
|
||||||
}
|
}
|
||||||
} while (l && d != c);
|
} while (l && d != c);
|
||||||
|
|
||||||
@@ -118,7 +118,7 @@ static void permute(char **argv, int dest, int src) {
|
|||||||
char *tmp = argv[src];
|
char *tmp = argv[src];
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
for (i = src; i > dest; i--) {
|
for (i = src; i > dest; --i) {
|
||||||
argv[i] = argv[i - 1];
|
argv[i] = argv[i - 1];
|
||||||
}
|
}
|
||||||
argv[dest] = tmp;
|
argv[dest] = tmp;
|
||||||
@@ -146,7 +146,7 @@ static int musl_getopt_long(
|
|||||||
skipped = musl_optind;
|
skipped = musl_optind;
|
||||||
if (optstring[0] != '+' && optstring[0] != '-') {
|
if (optstring[0] != '+' && optstring[0] != '-') {
|
||||||
int i;
|
int i;
|
||||||
for (i = musl_optind;; i++) {
|
for (i = musl_optind;; ++i) {
|
||||||
if (i >= argc || !argv[i]) {
|
if (i >= argc || !argv[i]) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@@ -161,7 +161,7 @@ static int musl_getopt_long(
|
|||||||
if (resumed > skipped) {
|
if (resumed > skipped) {
|
||||||
int i, cnt = musl_optind - resumed;
|
int i, cnt = musl_optind - resumed;
|
||||||
|
|
||||||
for (i = 0; i < cnt; i++) {
|
for (i = 0; i < cnt; ++i) {
|
||||||
permute(argv, skipped, musl_optind - 1);
|
permute(argv, skipped, musl_optind - 1);
|
||||||
}
|
}
|
||||||
musl_optind = skipped + cnt;
|
musl_optind = skipped + cnt;
|
||||||
@@ -180,16 +180,16 @@ static int musl_getopt_long_core(
|
|||||||
int i, cnt, match = 0;
|
int i, cnt, match = 0;
|
||||||
char *arg = 0, *opt, *start = argv[musl_optind] + 1;
|
char *arg = 0, *opt, *start = argv[musl_optind] + 1;
|
||||||
|
|
||||||
for (cnt = i = 0; longopts[i].name; i++) {
|
for (cnt = i = 0; longopts[i].name; ++i) {
|
||||||
char const *name = longopts[i].name;
|
char const *name = longopts[i].name;
|
||||||
|
|
||||||
opt = start;
|
opt = start;
|
||||||
if (*opt == '-') {
|
if (*opt == '-') {
|
||||||
opt++;
|
++opt;
|
||||||
}
|
}
|
||||||
while (*opt && *opt != '=' && *opt == *name) {
|
while (*opt && *opt != '=' && *opt == *name) {
|
||||||
name++;
|
++name;
|
||||||
opt++;
|
++opt;
|
||||||
}
|
}
|
||||||
if (*opt && *opt != '=') {
|
if (*opt && *opt != '=') {
|
||||||
continue;
|
continue;
|
||||||
@@ -200,19 +200,19 @@ static int musl_getopt_long_core(
|
|||||||
cnt = 1;
|
cnt = 1;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
cnt++;
|
++cnt;
|
||||||
}
|
}
|
||||||
if (cnt == 1 && longonly && arg - start == mblen(start, MB_LEN_MAX)) {
|
if (cnt == 1 && longonly && arg - start == mblen(start, MB_LEN_MAX)) {
|
||||||
int l = arg - start;
|
int l = arg - start;
|
||||||
|
|
||||||
for (i = 0; optstring[i]; i++) {
|
for (i = 0; optstring[i]; ++i) {
|
||||||
int j = 0;
|
int j = 0;
|
||||||
|
|
||||||
while (j < l && start[j] == optstring[i + j]) {
|
while (j < l && start[j] == optstring[i + j]) {
|
||||||
j++;
|
++j;
|
||||||
}
|
}
|
||||||
if (j == l) {
|
if (j == l) {
|
||||||
cnt++;
|
++cnt;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -220,7 +220,7 @@ static int musl_getopt_long_core(
|
|||||||
if (cnt == 1) {
|
if (cnt == 1) {
|
||||||
i = match;
|
i = match;
|
||||||
opt = arg;
|
opt = arg;
|
||||||
musl_optind++;
|
++musl_optind;
|
||||||
if (*opt == '=') {
|
if (*opt == '=') {
|
||||||
if (!longopts[i].has_arg) {
|
if (!longopts[i].has_arg) {
|
||||||
musl_optopt = longopts[i].val;
|
musl_optopt = longopts[i].val;
|
||||||
@@ -254,7 +254,7 @@ static int musl_getopt_long_core(
|
|||||||
);
|
);
|
||||||
return '?';
|
return '?';
|
||||||
}
|
}
|
||||||
musl_optind++;
|
++musl_optind;
|
||||||
}
|
}
|
||||||
if (idx) {
|
if (idx) {
|
||||||
*idx = i;
|
*idx = i;
|
||||||
@@ -275,7 +275,7 @@ static int musl_getopt_long_core(
|
|||||||
strlen(argv[musl_optind] + 2)
|
strlen(argv[musl_optind] + 2)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
musl_optind++;
|
++musl_optind;
|
||||||
return '?';
|
return '?';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
64
src/extern/utf8decoder.cpp
vendored
64
src/extern/utf8decoder.cpp
vendored
@@ -1,42 +1,48 @@
|
|||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
// UTF-8 decoder: http://bjoern.hoehrmann.de/utf-8/decoder/dfa/
|
// This implementation was taken from
|
||||||
|
// http://bjoern.hoehrmann.de/utf-8/decoder/dfa/
|
||||||
|
// and modified for RGBDS.
|
||||||
|
|
||||||
#include "extern/utf8decoder.hpp"
|
#include "extern/utf8decoder.hpp"
|
||||||
|
|
||||||
|
// clang-format off: vertically align values
|
||||||
static uint8_t const utf8d[] = {
|
static uint8_t const utf8d[] = {
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 00..0f
|
// The first part of the table maps bytes to character classes that
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 10..1f
|
// to reduce the size of the transition table and create bitmasks.
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20..2f
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 00..0f
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 30..3f
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 10..1f
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 40..4f
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20..2f
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 50..5f
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 30..3f
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 60..6f
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 40..4f
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 70..7f
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 50..5f
|
||||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 80..8f
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 60..6f
|
||||||
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, // 90..9f
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 70..7f
|
||||||
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // a0..af
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 80..8f
|
||||||
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // b0..bf
|
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, // 90..9f
|
||||||
8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // c0..cf
|
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // a0..af
|
||||||
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // d0..df
|
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // b0..bf
|
||||||
|
8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // c0..cf
|
||||||
|
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // d0..df
|
||||||
10, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 3, // e0..ef
|
10, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 3, // e0..ef
|
||||||
11, 6, 6, 6, 5, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, // f0..ff
|
11, 6, 6, 6, 5, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, // f0..ff
|
||||||
0, 1, 2, 3, 5, 8, 7, 1, 1, 1, 4, 6, 1, 1, 1, 1, // s0
|
// The second part is a transition table that maps a combination
|
||||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // s1
|
// of a state of the automaton and a character class to a state.
|
||||||
1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, // s1
|
0, 12, 24, 36, 60, 96, 84, 12, 12, 12, 48, 72, // s0
|
||||||
1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, // s3
|
12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, // s1
|
||||||
1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, // s4
|
12, 0, 12, 12, 12, 12, 12, 0, 12, 0, 12, 12, // s2
|
||||||
1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, // s5
|
12, 24, 12, 12, 12, 12, 12, 24, 12, 24, 12, 12, // s3
|
||||||
1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, // s6
|
12, 12, 12, 12, 12, 12, 12, 24, 12, 12, 12, 12, // s4
|
||||||
1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, // s7
|
12, 24, 12, 12, 12, 12, 12, 12, 12, 24, 12, 12, // s5
|
||||||
1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // s8
|
12, 12, 12, 12, 12, 12, 12, 36, 12, 36, 12, 12, // s6
|
||||||
|
12, 36, 12, 12, 12, 12, 12, 36, 12, 36, 12, 12, // s7
|
||||||
|
12, 36, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, // s8
|
||||||
};
|
};
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
uint32_t decode(uint32_t *state, uint32_t *codep, uint8_t byte) {
|
uint32_t decode(uint32_t *state, uint32_t *codep, uint8_t byte) {
|
||||||
uint32_t type = utf8d[byte];
|
uint8_t type = utf8d[byte];
|
||||||
|
*codep = *state != UTF8_ACCEPT ? (byte & 0b111111) | (*codep << 6) : (0xff >> type) & byte;
|
||||||
*codep = (*state != 0) ? (byte & 0x3FU) | (*codep << 6) : (0xFF >> type) & (byte);
|
*state = utf8d[0x100 + *state + type];
|
||||||
|
|
||||||
*state = utf8d[256 + *state * 16 + type];
|
|
||||||
return *state;
|
return *state;
|
||||||
}
|
}
|
||||||
|
|||||||
1123
src/fix/main.cpp
1123
src/fix/main.cpp
File diff suppressed because it is too large
Load Diff
510
src/fix/mbc.cpp
Normal file
510
src/fix/mbc.cpp
Normal file
@@ -0,0 +1,510 @@
|
|||||||
|
#include "fix/mbc.hpp"
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include "helpers.hpp" // unreachable_
|
||||||
|
#include "platform.hpp" // strcasecmp
|
||||||
|
|
||||||
|
#include "fix/warning.hpp"
|
||||||
|
|
||||||
|
// Associate every MBC type with its name and whether it has RAM
|
||||||
|
static std::unordered_map<MbcType, std::pair<char const *, bool>> mbcData{
|
||||||
|
{ROM, {"ROM", false} },
|
||||||
|
{ROM_RAM, {"ROM+RAM", true} },
|
||||||
|
{ROM_RAM_BATTERY, {"ROM+RAM+BATTERY", true} },
|
||||||
|
{MBC1, {"MBC1", false} },
|
||||||
|
{MBC1_RAM, {"MBC1+RAM", true} },
|
||||||
|
{MBC1_RAM_BATTERY, {"MBC1+RAM+BATTERY", true} },
|
||||||
|
// MBC2 technically has RAM, but is not marked as such
|
||||||
|
{MBC2, {"MBC2", false} },
|
||||||
|
{MBC2_BATTERY, {"MBC2+BATTERY", false} },
|
||||||
|
{MMM01, {"MMM01", false} },
|
||||||
|
{MMM01_RAM, {"MMM01+RAM", true} },
|
||||||
|
{MMM01_RAM_BATTERY, {"MMM01+RAM+BATTERY", true} },
|
||||||
|
{MBC3, {"MBC3", false} },
|
||||||
|
{MBC3_TIMER_BATTERY, {"MBC3+TIMER+BATTERY", false} },
|
||||||
|
{MBC3_TIMER_RAM_BATTERY, {"MBC3+TIMER+RAM+BATTERY", true} },
|
||||||
|
{MBC3_RAM, {"MBC3+RAM", true} },
|
||||||
|
{MBC3_RAM_BATTERY, {"MBC3+RAM+BATTERY", true} },
|
||||||
|
{MBC5, {"MBC5", false} },
|
||||||
|
{MBC5_RAM, {"MBC5+RAM", true} },
|
||||||
|
{MBC5_RAM_BATTERY, {"MBC5+RAM+BATTERY", true} },
|
||||||
|
{MBC5_RUMBLE, {"MBC5+RUMBLE", false} },
|
||||||
|
{MBC5_RUMBLE_RAM, {"MBC5+RUMBLE+RAM", true} },
|
||||||
|
{MBC5_RUMBLE_RAM_BATTERY, {"MBC5+RUMBLE+RAM+BATTERY", true} },
|
||||||
|
// MBC6 "Net de Get - Minigame @ 100" has RAM size 3 (32 KiB)
|
||||||
|
{MBC6, {"MBC6", true} },
|
||||||
|
{MBC7_SENSOR_RUMBLE_RAM_BATTERY, {"MBC7+SENSOR+RUMBLE+RAM+BATTERY", true} },
|
||||||
|
{POCKET_CAMERA, {"POCKET CAMERA", true} },
|
||||||
|
// Bandai TAMA5 "Game de Hakken!! Tamagotchi - Osutchi to Mesutchi" has RAM size 0
|
||||||
|
{BANDAI_TAMA5, {"BANDAI TAMA5", false} },
|
||||||
|
{HUC3, {"HUC3", true} },
|
||||||
|
{HUC1_RAM_BATTERY, {"HUC1+RAM+BATTERY", true} },
|
||||||
|
// TPP1 may or may not have RAM, don't use these flags for it
|
||||||
|
{TPP1, {"TPP1", false} },
|
||||||
|
{TPP1_RUMBLE, {"TPP1+RUMBLE", false} },
|
||||||
|
{TPP1_MULTIRUMBLE_RUMBLE, {"TPP1+MULTIRUMBLE", false} },
|
||||||
|
{TPP1_TIMER, {"TPP1+TIMER", false} },
|
||||||
|
{TPP1_TIMER_RUMBLE, {"TPP1+TIMER+RUMBLE", false} },
|
||||||
|
{TPP1_TIMER_MULTIRUMBLE_RUMBLE, {"TPP1+TIMER+MULTIRUMBLE", false} },
|
||||||
|
{TPP1_BATTERY, {"TPP1+BATTERY", false} },
|
||||||
|
{TPP1_BATTERY_RUMBLE, {"TPP1+BATTERY+RUMBLE", false} },
|
||||||
|
{TPP1_BATTERY_MULTIRUMBLE_RUMBLE, {"TPP1+BATTERY+MULTIRUMBLE", false} },
|
||||||
|
{TPP1_BATTERY_TIMER, {"TPP1+BATTERY+TIMER", false} },
|
||||||
|
{TPP1_BATTERY_TIMER_RUMBLE, {"TPP1+BATTERY+TIMER+RUMBLE", false} },
|
||||||
|
{TPP1_BATTERY_TIMER_MULTIRUMBLE_RUMBLE, {"TPP1+BATTERY+TIMER+MULTIRUMBLE", false}},
|
||||||
|
};
|
||||||
|
|
||||||
|
static char const *acceptedMBCNames =
|
||||||
|
"Accepted MBC names:\n"
|
||||||
|
"\tROM ($00) [aka ROM_ONLY]\n"
|
||||||
|
"\tMBC1 ($01), MBC1+RAM ($02), MBC1+RAM+BATTERY ($03)\n"
|
||||||
|
"\tMBC2 ($05), MBC2+BATTERY ($06)\n"
|
||||||
|
"\tROM+RAM ($08) [deprecated], ROM+RAM+BATTERY ($09) [deprecated]\n"
|
||||||
|
"\tMMM01 ($0B), MMM01+RAM ($0C), MMM01+RAM+BATTERY ($0D)\n"
|
||||||
|
"\tMBC3+TIMER+BATTERY ($0F), MBC3+TIMER+RAM+BATTERY ($10)\n"
|
||||||
|
"\tMBC3 ($11), MBC3+RAM ($12), MBC3+RAM+BATTERY ($13)\n"
|
||||||
|
"\tMBC5 ($19), MBC5+RAM ($1A), MBC5+RAM+BATTERY ($1B)\n"
|
||||||
|
"\tMBC5+RUMBLE ($1C), MBC5+RUMBLE+RAM ($1D), MBC5+RUMBLE+RAM+BATTERY ($1E)\n"
|
||||||
|
"\tMBC6 ($20)\n"
|
||||||
|
"\tMBC7+SENSOR+RUMBLE+RAM+BATTERY ($22)\n"
|
||||||
|
"\tPOCKET_CAMERA ($FC)\n"
|
||||||
|
"\tBANDAI_TAMA5 ($FD) [aka TAMA5]\n"
|
||||||
|
"\tHUC3 ($FE)\n"
|
||||||
|
"\tHUC1+RAM+BATTERY ($FF)\n"
|
||||||
|
"\n"
|
||||||
|
"\tTPP1_1.0, TPP1_1.0+RUMBLE, TPP1_1.0+MULTIRUMBLE, TPP1_1.0+TIMER,\n"
|
||||||
|
"\tTPP1_1.0+TIMER+RUMBLE, TPP1_1.0+TIMER+MULTIRUMBLE, TPP1_1.0+BATTERY,\n"
|
||||||
|
"\tTPP1_1.0+BATTERY+RUMBLE, TPP1_1.0+BATTERY+MULTIRUMBLE,\n"
|
||||||
|
"\tTPP1_1.0+BATTERY+TIMER, TPP1_1.0+BATTERY+TIMER+RUMBLE,\n"
|
||||||
|
"\tTPP1_1.0+BATTERY+TIMER+MULTIRUMBLE"; // No trailing newline
|
||||||
|
|
||||||
|
char const *mbc_Name(MbcType type) {
|
||||||
|
auto search = mbcData.find(type);
|
||||||
|
return search != mbcData.end() ? search->second.first : "(unknown)";
|
||||||
|
}
|
||||||
|
|
||||||
|
bool mbc_HasRAM(MbcType type) {
|
||||||
|
auto search = mbcData.find(type);
|
||||||
|
return search != mbcData.end() && search->second.second;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void skipWhitespace(char const *&ptr) {
|
||||||
|
while (*ptr == ' ' || *ptr == '\t') {
|
||||||
|
++ptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void skipMBCSpace(char const *&ptr) {
|
||||||
|
while (*ptr == ' ' || *ptr == '\t' || *ptr == '_') {
|
||||||
|
++ptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static char normalizeMBCChar(char c) {
|
||||||
|
if (c >= 'a' && c <= 'z') { // Uppercase for comparison with `mbc_Name`s
|
||||||
|
c = c - 'a' + 'A';
|
||||||
|
} else if (c == '_') { // Treat underscores as spaces
|
||||||
|
c = ' ';
|
||||||
|
}
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool readMBCSlice(char const *&name, char const *expected) {
|
||||||
|
while (*expected) {
|
||||||
|
// If `name` is too short, the character will be '\0' and this will return `false`
|
||||||
|
if (normalizeMBCChar(*name++) != *expected++) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[noreturn]]
|
||||||
|
static void fatalUnknownMBC(char const *fullName) {
|
||||||
|
fatal("Unknown MBC \"%s\"\n%s", fullName, acceptedMBCNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[noreturn]]
|
||||||
|
static void fatalWrongMBCFeatures(char const *fullName) {
|
||||||
|
fatal("Features incompatible with MBC (\"%s\")\n%s", fullName, acceptedMBCNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
MbcType mbc_ParseName(char const *name, uint8_t &tpp1Major, uint8_t &tpp1Minor) {
|
||||||
|
char const *fullName = name;
|
||||||
|
|
||||||
|
if (!strcasecmp(name, "help") || !strcasecmp(name, "list")) {
|
||||||
|
puts(acceptedMBCNames); // Outputs to stdout and appends a newline
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((name[0] >= '0' && name[0] <= '9') || name[0] == '$') {
|
||||||
|
int base = 0;
|
||||||
|
|
||||||
|
if (name[0] == '$') {
|
||||||
|
++name;
|
||||||
|
base = 16;
|
||||||
|
}
|
||||||
|
// Parse number, and return it as-is (unless it's too large)
|
||||||
|
char *endptr;
|
||||||
|
unsigned long mbc = strtoul(name, &endptr, base);
|
||||||
|
|
||||||
|
if (*endptr) {
|
||||||
|
fatalUnknownMBC(fullName);
|
||||||
|
}
|
||||||
|
if (mbc > 0xFF) {
|
||||||
|
fatal("Specified MBC ID out of range 0-255: %s", fullName);
|
||||||
|
}
|
||||||
|
return static_cast<MbcType>(mbc);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Begin by reading the MBC type:
|
||||||
|
uint16_t mbc;
|
||||||
|
char const *ptr = name;
|
||||||
|
|
||||||
|
skipWhitespace(ptr); // Trim off leading whitespace
|
||||||
|
|
||||||
|
#define tryReadSlice(expected) \
|
||||||
|
do { \
|
||||||
|
if (!readMBCSlice(ptr, expected)) { \
|
||||||
|
fatalUnknownMBC(fullName); \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
switch (*ptr++) {
|
||||||
|
case 'R': // ROM / ROM_ONLY
|
||||||
|
case 'r':
|
||||||
|
tryReadSlice("OM");
|
||||||
|
// Handle optional " ONLY"
|
||||||
|
skipMBCSpace(ptr);
|
||||||
|
if (*ptr == 'O' || *ptr == 'o') {
|
||||||
|
++ptr;
|
||||||
|
tryReadSlice("NLY");
|
||||||
|
}
|
||||||
|
mbc = ROM;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'M': // MBC{1, 2, 3, 5, 6, 7} / MMM01
|
||||||
|
case 'm':
|
||||||
|
switch (*ptr++) {
|
||||||
|
case 'B':
|
||||||
|
case 'b':
|
||||||
|
switch (*ptr++) {
|
||||||
|
case 'C':
|
||||||
|
case 'c':
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
fatalUnknownMBC(fullName);
|
||||||
|
}
|
||||||
|
switch (*ptr++) {
|
||||||
|
case '1':
|
||||||
|
mbc = MBC1;
|
||||||
|
break;
|
||||||
|
case '2':
|
||||||
|
mbc = MBC2;
|
||||||
|
break;
|
||||||
|
case '3':
|
||||||
|
mbc = MBC3;
|
||||||
|
break;
|
||||||
|
case '5':
|
||||||
|
mbc = MBC5;
|
||||||
|
break;
|
||||||
|
case '6':
|
||||||
|
mbc = MBC6;
|
||||||
|
break;
|
||||||
|
case '7':
|
||||||
|
mbc = MBC7_SENSOR_RUMBLE_RAM_BATTERY;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
fatalUnknownMBC(fullName);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'M':
|
||||||
|
case 'm':
|
||||||
|
tryReadSlice("M01");
|
||||||
|
mbc = MMM01;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
fatalUnknownMBC(fullName);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'P': // POCKET_CAMERA
|
||||||
|
case 'p':
|
||||||
|
tryReadSlice("OCKET CAMERA");
|
||||||
|
mbc = POCKET_CAMERA;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'B': // BANDAI_TAMA5
|
||||||
|
case 'b':
|
||||||
|
tryReadSlice("ANDAI TAMA5");
|
||||||
|
mbc = BANDAI_TAMA5;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'T': // TAMA5 / TPP1
|
||||||
|
case 't':
|
||||||
|
switch (*ptr++) {
|
||||||
|
case 'A':
|
||||||
|
tryReadSlice("MA5");
|
||||||
|
mbc = BANDAI_TAMA5;
|
||||||
|
break;
|
||||||
|
case 'P': {
|
||||||
|
tryReadSlice("P1");
|
||||||
|
// Parse version
|
||||||
|
skipMBCSpace(ptr);
|
||||||
|
// Major
|
||||||
|
char *endptr;
|
||||||
|
unsigned long val = strtoul(ptr, &endptr, 10);
|
||||||
|
|
||||||
|
if (endptr == ptr) {
|
||||||
|
fatal("Failed to parse TPP1 major revision number");
|
||||||
|
}
|
||||||
|
ptr = endptr;
|
||||||
|
if (val != 1) {
|
||||||
|
fatal("RGBFIX only supports TPP1 version 1.0");
|
||||||
|
}
|
||||||
|
tpp1Major = val;
|
||||||
|
tryReadSlice(".");
|
||||||
|
// Minor
|
||||||
|
val = strtoul(ptr, &endptr, 10);
|
||||||
|
if (endptr == ptr) {
|
||||||
|
fatal("Failed to parse TPP1 minor revision number");
|
||||||
|
}
|
||||||
|
ptr = endptr;
|
||||||
|
if (val > 0xFF) {
|
||||||
|
fatal("TPP1 minor revision number must be 8-bit");
|
||||||
|
}
|
||||||
|
tpp1Minor = val;
|
||||||
|
mbc = TPP1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
fatalUnknownMBC(fullName);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'H': // HuC{1, 3}
|
||||||
|
case 'h':
|
||||||
|
tryReadSlice("UC");
|
||||||
|
switch (*ptr++) {
|
||||||
|
case '1':
|
||||||
|
mbc = HUC1_RAM_BATTERY;
|
||||||
|
break;
|
||||||
|
case '3':
|
||||||
|
mbc = HUC3;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
fatalUnknownMBC(fullName);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
fatalUnknownMBC(fullName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read "additional features"
|
||||||
|
uint8_t features = 0;
|
||||||
|
// clang-format off: vertically align values
|
||||||
|
static constexpr uint8_t RAM = 1 << 7;
|
||||||
|
static constexpr uint8_t BATTERY = 1 << 6;
|
||||||
|
static constexpr uint8_t TIMER = 1 << 5;
|
||||||
|
static constexpr uint8_t RUMBLE = 1 << 4;
|
||||||
|
static constexpr uint8_t SENSOR = 1 << 3;
|
||||||
|
static constexpr uint8_t MULTIRUMBLE = 1 << 2;
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
skipWhitespace(ptr); // Trim off trailing whitespace
|
||||||
|
|
||||||
|
// If done, start processing "features"
|
||||||
|
if (!*ptr) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// We expect a '+' at this point
|
||||||
|
skipMBCSpace(ptr);
|
||||||
|
if (*ptr++ != '+') {
|
||||||
|
fatalUnknownMBC(fullName);
|
||||||
|
}
|
||||||
|
skipMBCSpace(ptr);
|
||||||
|
|
||||||
|
switch (*ptr++) {
|
||||||
|
case 'B': // BATTERY
|
||||||
|
case 'b':
|
||||||
|
tryReadSlice("ATTERY");
|
||||||
|
features |= BATTERY;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'M':
|
||||||
|
case 'm':
|
||||||
|
tryReadSlice("ULTIRUMBLE");
|
||||||
|
features |= MULTIRUMBLE;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'R': // RAM or RUMBLE
|
||||||
|
case 'r':
|
||||||
|
switch (*ptr++) {
|
||||||
|
case 'U':
|
||||||
|
case 'u':
|
||||||
|
tryReadSlice("MBLE");
|
||||||
|
features |= RUMBLE;
|
||||||
|
break;
|
||||||
|
case 'A':
|
||||||
|
case 'a':
|
||||||
|
tryReadSlice("M");
|
||||||
|
features |= RAM;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
fatalUnknownMBC(fullName);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'S': // SENSOR
|
||||||
|
case 's':
|
||||||
|
tryReadSlice("ENSOR");
|
||||||
|
features |= SENSOR;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'T': // TIMER
|
||||||
|
case 't':
|
||||||
|
tryReadSlice("IMER");
|
||||||
|
features |= TIMER;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
fatalUnknownMBC(fullName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#undef tryReadSlice
|
||||||
|
|
||||||
|
switch (mbc) {
|
||||||
|
case ROM:
|
||||||
|
if (!features) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
mbc = ROM_RAM - 1;
|
||||||
|
static_assert(ROM_RAM + 1 == ROM_RAM_BATTERY, "Enum sanity check failed!");
|
||||||
|
static_assert(MBC1 + 1 == MBC1_RAM, "Enum sanity check failed!");
|
||||||
|
static_assert(MBC1 + 2 == MBC1_RAM_BATTERY, "Enum sanity check failed!");
|
||||||
|
static_assert(MMM01 + 1 == MMM01_RAM, "Enum sanity check failed!");
|
||||||
|
static_assert(MMM01 + 2 == MMM01_RAM_BATTERY, "Enum sanity check failed!");
|
||||||
|
[[fallthrough]];
|
||||||
|
case MBC1:
|
||||||
|
case MMM01:
|
||||||
|
if (features == RAM) {
|
||||||
|
++mbc;
|
||||||
|
} else if (features == (RAM | BATTERY)) {
|
||||||
|
mbc += 2;
|
||||||
|
} else if (features) {
|
||||||
|
fatalWrongMBCFeatures(fullName);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MBC2:
|
||||||
|
if (features == BATTERY) {
|
||||||
|
mbc = MBC2_BATTERY;
|
||||||
|
} else if (features) {
|
||||||
|
fatalWrongMBCFeatures(fullName);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MBC3:
|
||||||
|
// Handle timer, which also requires battery
|
||||||
|
if (features & TIMER) {
|
||||||
|
if (!(features & BATTERY)) {
|
||||||
|
warning(WARNING_MBC, "MBC3+TIMER implies BATTERY");
|
||||||
|
}
|
||||||
|
features &= ~(TIMER | BATTERY); // Reset those bits
|
||||||
|
mbc = MBC3_TIMER_BATTERY;
|
||||||
|
// RAM is handled below
|
||||||
|
}
|
||||||
|
static_assert(MBC3 + 1 == MBC3_RAM, "Enum sanity check failed!");
|
||||||
|
static_assert(MBC3 + 2 == MBC3_RAM_BATTERY, "Enum sanity check failed!");
|
||||||
|
static_assert(
|
||||||
|
MBC3_TIMER_BATTERY + 1 == MBC3_TIMER_RAM_BATTERY, "Enum sanity check failed!"
|
||||||
|
);
|
||||||
|
if (features == RAM) {
|
||||||
|
++mbc;
|
||||||
|
} else if (features == (RAM | BATTERY)) {
|
||||||
|
mbc += 2;
|
||||||
|
} else if (features) {
|
||||||
|
fatalWrongMBCFeatures(fullName);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MBC5:
|
||||||
|
if (features & RUMBLE) {
|
||||||
|
features &= ~RUMBLE;
|
||||||
|
mbc = MBC5_RUMBLE;
|
||||||
|
}
|
||||||
|
static_assert(MBC5 + 1 == MBC5_RAM, "Enum sanity check failed!");
|
||||||
|
static_assert(MBC5 + 2 == MBC5_RAM_BATTERY, "Enum sanity check failed!");
|
||||||
|
static_assert(MBC5_RUMBLE + 1 == MBC5_RUMBLE_RAM, "Enum sanity check failed!");
|
||||||
|
static_assert(MBC5_RUMBLE + 2 == MBC5_RUMBLE_RAM_BATTERY, "Enum sanity check failed!");
|
||||||
|
if (features == RAM) {
|
||||||
|
++mbc;
|
||||||
|
} else if (features == (RAM | BATTERY)) {
|
||||||
|
mbc += 2;
|
||||||
|
} else if (features) {
|
||||||
|
fatalWrongMBCFeatures(fullName);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MBC6:
|
||||||
|
case POCKET_CAMERA:
|
||||||
|
case BANDAI_TAMA5:
|
||||||
|
case HUC3:
|
||||||
|
// No extra features accepted
|
||||||
|
if (features) {
|
||||||
|
fatalWrongMBCFeatures(fullName);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MBC7_SENSOR_RUMBLE_RAM_BATTERY:
|
||||||
|
if (features != (SENSOR | RUMBLE | RAM | BATTERY)) {
|
||||||
|
fatalWrongMBCFeatures(fullName);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case HUC1_RAM_BATTERY:
|
||||||
|
if (features != (RAM | BATTERY)) { // HuC1 expects RAM+BATTERY
|
||||||
|
fatalWrongMBCFeatures(fullName);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TPP1:
|
||||||
|
if (features & RAM) {
|
||||||
|
warning(WARNING_MBC, "TPP1 requests RAM implicitly if given a non-zero RAM size");
|
||||||
|
}
|
||||||
|
if (features & BATTERY) {
|
||||||
|
mbc |= 0x08;
|
||||||
|
}
|
||||||
|
if (features & TIMER) {
|
||||||
|
mbc |= 0x04;
|
||||||
|
}
|
||||||
|
if (features & MULTIRUMBLE) {
|
||||||
|
mbc |= 0x03; // Also set the rumble flag
|
||||||
|
}
|
||||||
|
if (features & RUMBLE) {
|
||||||
|
mbc |= 0x01;
|
||||||
|
}
|
||||||
|
if (features & SENSOR) {
|
||||||
|
fatalWrongMBCFeatures(fullName);
|
||||||
|
}
|
||||||
|
// Multiple rumble speeds imply rumble
|
||||||
|
if (mbc & 0x01) {
|
||||||
|
assume(mbc & 0x02);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
skipWhitespace(ptr); // Trim off trailing whitespace
|
||||||
|
|
||||||
|
// If there is still something past the whitespace, error out
|
||||||
|
if (*ptr) {
|
||||||
|
fatalUnknownMBC(fullName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return static_cast<MbcType>(mbc);
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user