mirror of
https://github.com/gbdev/rgbds.git
synced 2025-11-20 10:12:06 +00:00
Compare commits
134 Commits
v0.9.0-rc1
...
v0.9.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
81ea4ee920 | ||
|
|
29ece2940d | ||
|
|
03452c6d4f | ||
|
|
b35e9d86fb | ||
|
|
e20347e38c | ||
|
|
f61019dd68 | ||
|
|
c19ddc80f0 | ||
|
|
a59867cd78 | ||
|
|
375adc6804 | ||
|
|
44caffe04a | ||
|
|
d54619a453 | ||
|
|
e49291b7cf | ||
|
|
34a9c8e083 | ||
|
|
c4b456b166 | ||
|
|
79401cce8b | ||
|
|
4e44958d26 | ||
|
|
cae31005f8 | ||
|
|
25c9f8f383 | ||
|
|
01c9106b59 | ||
|
|
192fc808c8 | ||
|
|
9c8e327ae2 | ||
|
|
9ebd2a7e8e | ||
|
|
b8b60207f5 | ||
|
|
c5e59f40fd | ||
|
|
a354af3d08 | ||
|
|
20c18256ed | ||
|
|
890528812e | ||
|
|
84f59e14ed | ||
|
|
91d7ce5e09 | ||
|
|
d9654b752f | ||
|
|
157826bf82 | ||
|
|
a5e36f924f | ||
|
|
82f7bdb480 | ||
|
|
056190413e | ||
|
|
c2db23aef0 | ||
|
|
2426068409 | ||
|
|
147a5c9bf3 | ||
|
|
6ae3f040b8 | ||
|
|
e561f63db3 | ||
|
|
af9de812ec | ||
|
|
edc9e07a2d | ||
|
|
382ad17969 | ||
|
|
fac5e35d24 | ||
|
|
a85d6b3b57 | ||
|
|
f23a14afc7 | ||
|
|
f63167dd0f | ||
|
|
0ee4ba95b3 | ||
|
|
727c1f5b50 | ||
|
|
d829fd2ffe | ||
|
|
b13c0f2f8e | ||
|
|
d9773424e4 | ||
|
|
4e2464a69d | ||
|
|
a5f12f66bb | ||
|
|
73ad431b8d | ||
|
|
d88feee1c0 | ||
|
|
5963dc9e0e | ||
|
|
8363f25d47 | ||
|
|
72b2a4d7c0 | ||
|
|
06daf2a9b5 | ||
|
|
ad95d2e06f | ||
|
|
5197e6b79f | ||
|
|
b99ce3845e | ||
|
|
d63955eccd | ||
|
|
2c4fc4cbe8 | ||
|
|
7d3c31b6d8 | ||
|
|
151f83db6d | ||
|
|
22838ce2d8 | ||
|
|
b058bb6e15 | ||
|
|
36b04b5dea | ||
|
|
a7296ecb31 | ||
|
|
92917ceb2f | ||
|
|
c1c5b10082 | ||
|
|
f44de0c7ae | ||
|
|
b18cfe6bdb | ||
|
|
a8ec9228d4 | ||
|
|
c1b85554a8 | ||
|
|
b877c81c32 | ||
|
|
e66da4c8c7 | ||
|
|
573e044b30 | ||
|
|
ceb43c7aa4 | ||
|
|
eae1ecb77e | ||
|
|
c4de6c402b | ||
|
|
0b147c9386 | ||
|
|
6982c8a116 | ||
|
|
d5f39c8dce | ||
|
|
a5d18d62df | ||
|
|
a27f704c25 | ||
|
|
9216485bca | ||
|
|
c33acb905b | ||
|
|
81c3521610 | ||
|
|
e0ee9dc3ad | ||
|
|
cb546f0cd8 | ||
|
|
a60186db2f | ||
|
|
d9f87a5721 | ||
|
|
a7fdb2c3d3 | ||
|
|
5efd303b7f | ||
|
|
0d3980d039 | ||
|
|
ab6244d81c | ||
|
|
7fcf4ba60f | ||
|
|
f048cbbb11 | ||
|
|
4c495c31d9 | ||
|
|
90286ccbbc | ||
|
|
b33aa31944 | ||
|
|
dd6c741143 | ||
|
|
3b3263273c | ||
|
|
bc5a71ff88 | ||
|
|
e623aeb85d | ||
|
|
a2ff653a83 | ||
|
|
a13723c523 | ||
|
|
cf85146353 | ||
|
|
a9e49a09fd | ||
|
|
cbe44fed9b | ||
|
|
c439b8e27f | ||
|
|
86bf289452 | ||
|
|
e1ac7f389d | ||
|
|
d5159f66be | ||
|
|
c7a029a051 | ||
|
|
d5ded84501 | ||
|
|
4cd0dd5314 | ||
|
|
9783671399 | ||
|
|
8037b9e10a | ||
|
|
7c74653aa1 | ||
|
|
22767e36e2 | ||
|
|
6b89938da7 | ||
|
|
15919e550f | ||
|
|
f93587c805 | ||
|
|
a870f7de10 | ||
|
|
6b72067387 | ||
|
|
84c01f064f | ||
|
|
5d3e96662e | ||
|
|
91580043e0 | ||
|
|
3e28e92622 | ||
|
|
d494f73825 | ||
|
|
b03a5b13b7 |
@@ -24,6 +24,7 @@ AttributeMacros:
|
||||
BinPackArguments: false
|
||||
BinPackParameters: false
|
||||
BitFieldColonSpacing: Both
|
||||
BreakAfterAttributes: Always
|
||||
BreakBeforeBinaryOperators: NonAssignment
|
||||
BreakBeforeBraces: Attach
|
||||
BreakBeforeConceptDeclarations: true
|
||||
@@ -54,12 +55,14 @@ IncludeCategories:
|
||||
IndentAccessModifiers: false
|
||||
IndentCaseBlocks: false
|
||||
IndentCaseLabels: false
|
||||
IndentExternBlock: NoIndent
|
||||
IndentExternBlock: Indent
|
||||
IndentGotoLabels: false
|
||||
IndentPPDirectives: BeforeHash
|
||||
IndentRequires: true
|
||||
IndentWidth: 4
|
||||
IndentWrappedFunctionNames: true
|
||||
InsertBraces: true
|
||||
InsertNewlineAtEOF: true
|
||||
# Only support for Javascript as of clang-format 14...
|
||||
# InsertTrailingCommas: Wrapped
|
||||
KeepEmptyLinesAtTheStartOfBlocks: false
|
||||
@@ -71,6 +74,8 @@ PPIndentWidth: -1
|
||||
PointerAlignment: Right
|
||||
QualifierAlignment: Right
|
||||
ReflowComments: true
|
||||
RemoveParentheses: ReturnStatement
|
||||
RemoveSemicolon: true
|
||||
SortIncludes: CaseSensitive
|
||||
SortUsingDeclarations: true
|
||||
SpaceAfterCStyleCast: false
|
||||
|
||||
4
.github/scripts/build_libpng.sh
vendored
4
.github/scripts/build_libpng.sh
vendored
@@ -1,13 +1,13 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
pngver=1.6.43
|
||||
pngver=1.6.45
|
||||
|
||||
## Grab sources and check them
|
||||
|
||||
curl -LOJ "http://prdownloads.sourceforge.net/libpng/libpng-$pngver.tar.xz?download"
|
||||
# Brew doesn't provide any sha256sum, so we're making do with `sha2` instead.
|
||||
if [ "$(sha2 -q -256 libpng-$pngver.tar.xz)" != 6a5ca0652392a2d7c9db2ae5b40210843c0bbc081cbd410825ab00cc59f14a6c ]; then
|
||||
if [ "$(sha2 -q -256 libpng-$pngver.tar.xz)" != 926485350139ffb51ef69760db35f78846c805fef3d59bfdcb2fba704663f370 ]; then
|
||||
sha2 -256 libpng-$pngver.tar.xz
|
||||
echo Checksum mismatch! Aborting. >&2
|
||||
exit 1
|
||||
|
||||
4
.github/scripts/get_win_deps.ps1
vendored
4
.github/scripts/get_win_deps.ps1
vendored
@@ -16,8 +16,8 @@ function getlibrary ([string] $URI, [string] $filename, [string] $hash, [string]
|
||||
}
|
||||
|
||||
getlibrary 'https://www.zlib.net/zlib131.zip' 'zlib.zip' '72af66d44fcc14c22013b46b814d5d2514673dda3d115e64b690c1ad636e7b17' .
|
||||
getlibrary 'https://github.com/glennrp/libpng/archive/refs/tags/v1.6.43.zip' 'libpng.zip' '5e18474a26814ae479e02ca6432da32d19dc6e615551d140c954a68d63b3f192' .
|
||||
getlibrary 'https://github.com/glennrp/libpng/archive/refs/tags/v1.6.45.zip' 'libpng.zip' '1b3d94b2f1d137db1bf1842cb9f03df179772a517f7b86e26351742190632785' .
|
||||
getlibrary 'https://github.com/lexxmark/winflexbison/releases/download/v2.5.25/win_flex_bison-2.5.25.zip' 'winflexbison.zip' '8d324b62be33604b2c45ad1dd34ab93d722534448f55a16ca7292de32b6ac135' install_dir
|
||||
|
||||
Move-Item zlib-1.3.1 zlib
|
||||
Move-Item libpng-1.6.43 libpng
|
||||
Move-Item libpng-1.6.45 libpng
|
||||
|
||||
2
.github/scripts/install.sh
vendored
2
.github/scripts/install.sh
vendored
@@ -3,5 +3,5 @@
|
||||
install -d /usr/local/bin/ /usr/local/share/man/man1/ /usr/local/share/man/man5/ /usr/local/share/man/man7/
|
||||
install -s -m 755 rgbasm rgblink rgbfix rgbgfx /usr/local/bin/
|
||||
install -m 644 rgbasm.1 rgblink.1 rgbfix.1 rgbgfx.1 /usr/local/share/man/man1/
|
||||
install -m 644 rgbds.5 rgbasm.5 rgblink.5 /usr/local/share/man/man5/
|
||||
install -m 644 rgbds.5 rgbasm.5 rgblink.5 rgbasm-old.5 /usr/local/share/man/man5/
|
||||
install -m 644 rgbds.7 gbz80.7 /usr/local/share/man/man7/
|
||||
|
||||
2
.github/scripts/mingw-w64-libpng-dev.sh
vendored
2
.github/scripts/mingw-w64-libpng-dev.sh
vendored
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
pngver=1.6.43
|
||||
pngver=1.6.45
|
||||
arch="$1"
|
||||
|
||||
## Grab sources and check them
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
d6bd2a3f43f17020918a4c1bd81c1a78111b6f759af9c1d3c754f704a1bf0429 libpng-1.6.43-apng.patch.gz
|
||||
6a5ca0652392a2d7c9db2ae5b40210843c0bbc081cbd410825ab00cc59f14a6c libpng-1.6.43.tar.xz
|
||||
f7caa3b55f003ce23d6a087b1c2a643262647bfd1a1b31afdc9b18eabf1bbc7e libpng-1.6.45-apng.patch.gz
|
||||
926485350139ffb51ef69760db35f78846c805fef3d59bfdcb2fba704663f370 libpng-1.6.45.tar.xz
|
||||
|
||||
18
.github/workflows/analysis.yml
vendored
Normal file
18
.github/workflows/analysis.yml
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
name: Static analysis
|
||||
on:
|
||||
- push
|
||||
- pull_request
|
||||
|
||||
jobs:
|
||||
analysis:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
- name: Install deps
|
||||
shell: bash
|
||||
run: |
|
||||
./.github/scripts/install_deps.sh ubuntu-latest
|
||||
- name: Static analysis
|
||||
run: | # Silence warnings with too many false positives (https://stackoverflow.com/a/73913076)
|
||||
make -kj CXX=g++-14 CXXFLAGS="-fanalyzer -fanalyzer-verbosity=0 -Wno-analyzer-use-of-uninitialized-value -DNDEBUG" Q=
|
||||
51
.github/workflows/build-container.yml
vendored
Normal file
51
.github/workflows/build-container.yml
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
name: Build container image
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
publish-docker-image:
|
||||
if: github.repository_owner == 'gbdev'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
packages: write
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push the master container image
|
||||
if: github.ref == 'refs/heads/master'
|
||||
run: |
|
||||
COMMIT_HASH=$(git rev-parse --short HEAD)
|
||||
sed -i "2i LABEL org.opencontainers.image.description=\"RGBDS container image, containing the git version master:$COMMIT_HASH\"" Dockerfile
|
||||
docker build . --tag ghcr.io/gbdev/rgbds:master
|
||||
docker push ghcr.io/gbdev/rgbds:master
|
||||
|
||||
- name: Build and push the version-tagged container image
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
run: |
|
||||
TAG_NAME=${GITHUB_REF#refs/tags/}
|
||||
sed -i "2i LABEL org.opencontainers.image.description=\"RGBDS container image for the release version $TAG_NAME\"" Dockerfile
|
||||
docker build . --tag ghcr.io/gbdev/rgbds:$TAG_NAME
|
||||
docker push ghcr.io/gbdev/rgbds:$TAG_NAME
|
||||
|
||||
- name: Delete untagged container images
|
||||
if: github.repository_owner == 'gbdev'
|
||||
uses: Chizkiyahu/delete-untagged-ghcr-action@v5
|
||||
with:
|
||||
# Requires a personal access token with delete:packages permissions
|
||||
token: ${{ secrets.PAT_TOKEN }}
|
||||
package_name: 'rgbds'
|
||||
untagged_only: true
|
||||
except_untagged_multiplatform: true
|
||||
owner_type: 'org'
|
||||
2
.github/workflows/checkdiff.yml
vendored
2
.github/workflows/checkdiff.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: "Code coverage checking"
|
||||
name: Code coverage checking
|
||||
on: pull_request
|
||||
|
||||
jobs:
|
||||
|
||||
26
.github/workflows/create-release-artifacts.yml
vendored
26
.github/workflows/create-release-artifacts.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: "Create release artifacts"
|
||||
name: Create release artifacts
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
@@ -24,30 +24,32 @@ jobs:
|
||||
run: | # Turn "vX.Y.Z" into "X.Y.Z"
|
||||
VERSION="${{ github.ref_name }}"
|
||||
echo "version=${VERSION#v}" >> $GITHUB_ENV
|
||||
- uses: actions/checkout@v4
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
- name: Install deps
|
||||
run: .github/scripts/get_win_deps.ps1
|
||||
- uses: actions/cache@v4
|
||||
- name: Check libraries cache
|
||||
id: cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
zbuild
|
||||
pngbuild
|
||||
key: ${{ matrix.arch }}-${{ hashFiles('zlib/**', 'libpng/**') }}
|
||||
- name: Build zlib
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
run: | # BUILD_SHARED_LIBS causes the output DLL to be correctly called `zlib1.dll`
|
||||
cmake -S zlib -B zbuild -A ${{ matrix.platform }} -Wno-dev -DCMAKE_INSTALL_PREFIX=install_dir -DBUILD_SHARED_LIBS=ON
|
||||
cmake --build zbuild --config Release -j
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
- name: Install zlib
|
||||
run: |
|
||||
cmake --install zbuild
|
||||
- name: Build libpng
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
shell: bash
|
||||
run: |
|
||||
cmake -S libpng -B pngbuild -A ${{ matrix.platform }} -Wno-dev -DCMAKE_INSTALL_PREFIX=install_dir -DPNG_SHARED=ON -DPNG_STATIC=OFF -DPNG_TESTS=OFF
|
||||
cmake --build pngbuild --config Release -j
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
- name: Install libpng
|
||||
run: |
|
||||
cmake --install pngbuild
|
||||
@@ -74,7 +76,8 @@ jobs:
|
||||
run: | # Turn "refs/tags/vX.Y.Z" into "X.Y.Z"
|
||||
VERSION="${{ github.ref_name }}"
|
||||
echo "version=${VERSION#v}" >> $GITHUB_ENV
|
||||
- uses: actions/checkout@v4
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
- name: Install deps
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -104,7 +107,8 @@ jobs:
|
||||
run: | # Turn "refs/tags/vX.Y.Z" into "X.Y.Z"
|
||||
VERSION="${{ github.ref_name }}"
|
||||
echo "version=${VERSION#v}" >> $GITHUB_ENV
|
||||
- uses: actions/checkout@v4
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
- name: Install deps
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -133,17 +137,19 @@ jobs:
|
||||
run: | # Turn "refs/tags/vX.Y.Z" into "X.Y.Z"
|
||||
VERSION="${{ github.ref_name }}"
|
||||
echo "version=${VERSION#v}" >> $GITHUB_ENV
|
||||
- uses: actions/checkout@v4
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
- name: Package sources
|
||||
run: |
|
||||
make dist Q=
|
||||
ls
|
||||
- uses: actions/download-artifact@v4
|
||||
- name: Download Linux binaries
|
||||
uses: actions/download-artifact@v4
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
body: |
|
||||
Please ensure that the four packages below work properly.
|
||||
Please ensure that the packages below work properly.
|
||||
Once that's done, replace this text with the changelog, un-draft the release, and update the `release` branch.
|
||||
By the way, if you forgot to update `include/version.hpp`, RGBASM's version test is gonna fail in the tag's regression testing! (Use `git push --delete origin <tag>` to delete it)
|
||||
draft: true # Don't publish the release quite yet...
|
||||
|
||||
4
.github/workflows/create-release-docs.yml
vendored
4
.github/workflows/create-release-docs.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: "Create release docs"
|
||||
name: Create release docs
|
||||
on:
|
||||
release:
|
||||
types:
|
||||
@@ -7,7 +7,7 @@ on:
|
||||
jobs:
|
||||
build:
|
||||
if: github.repository_owner == 'gbdev'
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout rgbds@release
|
||||
uses: actions/checkout@v4
|
||||
|
||||
109
.github/workflows/testing.yml
vendored
109
.github/workflows/testing.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: "Regression testing"
|
||||
name: Regression testing
|
||||
on:
|
||||
- push
|
||||
- pull_request
|
||||
@@ -17,7 +17,8 @@ jobs:
|
||||
fail-fast: false
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
- name: Install deps
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -32,9 +33,7 @@ jobs:
|
||||
run: |
|
||||
cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_COMPILER=${{ matrix.cxx }} -DSANITIZERS=ON -DMORE_WARNINGS=ON
|
||||
cmake --build build -j --verbose
|
||||
cp build/src/rgb{asm,link,fix,gfx} .
|
||||
sudo cmake --install build --verbose
|
||||
cmake --install build --verbose --component "Test support programs"
|
||||
- name: Package binaries
|
||||
run: |
|
||||
mkdir bins
|
||||
@@ -58,8 +57,8 @@ jobs:
|
||||
with:
|
||||
path: ${{ fromJSON(steps.test-deps-cache-params.outputs.paths) }}
|
||||
key: ${{ matrix.os }}-${{ steps.test-deps-cache-params.outputs.hash }}
|
||||
- if: steps.test-deps-cache.outputs.cache-hit != 'true'
|
||||
name: Fetch test dependency repositories
|
||||
- name: Fetch test dependency repositories
|
||||
if: steps.test-deps-cache.outputs.cache-hit != 'true'
|
||||
continue-on-error: true
|
||||
run: |
|
||||
test/fetch-test-deps.sh
|
||||
@@ -75,7 +74,8 @@ jobs:
|
||||
macos-static:
|
||||
runs-on: macos-14
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
- name: Install deps
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -109,8 +109,8 @@ jobs:
|
||||
with:
|
||||
path: ${{ fromJSON(steps.test-deps-cache-params.outputs.paths) }}
|
||||
key: ${{ matrix.os }}-${{ steps.test-deps-cache-params.outputs.hash }}
|
||||
- if: steps.test-deps-cache.outputs.cache-hit != 'true'
|
||||
name: Fetch test dependency repositories
|
||||
- name: Fetch test dependency repositories
|
||||
if: steps.test-deps-cache.outputs.cache-hit != 'true'
|
||||
continue-on-error: true
|
||||
run: |
|
||||
test/fetch-test-deps.sh
|
||||
@@ -138,30 +138,32 @@ jobs:
|
||||
fail-fast: false
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
- name: Install deps
|
||||
run: .github/scripts/get_win_deps.ps1
|
||||
- uses: actions/cache@v4
|
||||
- name: Check libraries cache
|
||||
id: cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
zbuild
|
||||
pngbuild
|
||||
key: ${{ matrix.arch }}-${{ hashFiles('zlib/**', 'libpng/**') }}
|
||||
- name: Build zlib
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
run: | # BUILD_SHARED_LIBS causes the output DLL to be correctly called `zlib1.dll`
|
||||
cmake -S zlib -B zbuild -A ${{ matrix.platform }} -Wno-dev -DCMAKE_INSTALL_PREFIX=install_dir -DBUILD_SHARED_LIBS=ON
|
||||
cmake --build zbuild --config Release -j
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
- name: Install zlib
|
||||
run: |
|
||||
cmake --install zbuild
|
||||
- name: Build libpng
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
shell: bash
|
||||
run: |
|
||||
cmake -S libpng -B pngbuild -A ${{ matrix.platform }} -Wno-dev -DCMAKE_INSTALL_PREFIX=install_dir -DPNG_SHARED=ON -DPNG_STATIC=OFF -DPNG_TESTS=OFF
|
||||
cmake --build pngbuild --config Release -j
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
- name: Install libpng
|
||||
run: |
|
||||
cmake --install pngbuild
|
||||
@@ -171,7 +173,6 @@ jobs:
|
||||
cmake -S . -B build -A ${{ matrix.platform }} -DCMAKE_INSTALL_PREFIX=install_dir -DCMAKE_BUILD_TYPE=Release
|
||||
cmake --build build --config Release -j --verbose
|
||||
cmake --install build --verbose --prefix install_dir
|
||||
cmake --install build --verbose --component "Test support programs"
|
||||
- name: Package binaries
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -196,8 +197,8 @@ jobs:
|
||||
with:
|
||||
path: ${{ fromJSON(steps.test-deps-cache-params.outputs.paths) }}
|
||||
key: ${{ matrix.os }}-${{ matrix.bits }}-${{ steps.test-deps-cache-params.outputs.hash }}
|
||||
- if: steps.test-deps-cache.outputs.cache-hit != 'true'
|
||||
name: Fetch test dependency repositories
|
||||
- name: Fetch test dependency repositories
|
||||
if: steps.test-deps-cache.outputs.cache-hit != 'true'
|
||||
shell: bash
|
||||
continue-on-error: true
|
||||
run: |
|
||||
@@ -229,7 +230,8 @@ jobs:
|
||||
env:
|
||||
DIST_DIR: win${{ matrix.bits }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
- name: Install deps
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -273,7 +275,8 @@ jobs:
|
||||
fail-fast: false
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
- name: Retrieve binaries
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
@@ -303,8 +306,8 @@ jobs:
|
||||
with:
|
||||
path: ${{ fromJSON(steps.test-deps-cache-params.outputs.paths) }}
|
||||
key: mingw-${{ matrix.bits }}-${{ steps.test-deps-cache-params.outputs.hash }}
|
||||
- if: steps.test-deps-cache.outputs.cache-hit != 'true'
|
||||
name: Fetch test dependency repositories
|
||||
- name: Fetch test dependency repositories
|
||||
if: steps.test-deps-cache.outputs.cache-hit != 'true'
|
||||
shell: bash
|
||||
continue-on-error: true
|
||||
run: |
|
||||
@@ -317,3 +320,69 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
test/run-tests.sh
|
||||
|
||||
cygwin:
|
||||
strategy:
|
||||
matrix:
|
||||
bits: [32, 64]
|
||||
include:
|
||||
- bits: 32
|
||||
arch: x86
|
||||
- bits: 64
|
||||
arch: x86_64
|
||||
fail-fast: false
|
||||
runs-on: windows-2019
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
fetch-tags: true
|
||||
- name: Setup Cygwin
|
||||
uses: cygwin/cygwin-install-action@v4
|
||||
with:
|
||||
platform: ${{ matrix.arch }}
|
||||
packages: >-
|
||||
bison
|
||||
gcc-g++
|
||||
git
|
||||
libpng-devel
|
||||
make
|
||||
pkg-config
|
||||
- name: Build & install using Make
|
||||
shell: C:\cygwin\bin\env.exe CYGWIN_NOWINPATH=1 CHERE_INVOKING=1 C:\cygwin\bin\bash.exe -o igncr '{0}'
|
||||
run: | # Cygwin does not support `make develop` sanitizers ASan or UBSan
|
||||
make -kj Q=
|
||||
make install -j Q=
|
||||
- name: Run tests
|
||||
shell: C:\cygwin\bin\env.exe CYGWIN_NOWINPATH=1 CHERE_INVOKING=1 C:\cygwin\bin\bash.exe -o igncr '{0}'
|
||||
run: |
|
||||
test/run-tests.sh --only-internal
|
||||
|
||||
freebsd:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
fetch-tags: true
|
||||
- name: Build & test using CMake on FreeBSD
|
||||
uses: vmactions/freebsd-vm@v1
|
||||
with:
|
||||
release: "15.0"
|
||||
usesh: true
|
||||
prepare: |
|
||||
pkg install -y \
|
||||
bash \
|
||||
bison \
|
||||
cmake \
|
||||
git \
|
||||
png
|
||||
run: | # FreeBSD `c++` compiler does not support `make develop` sanitizers ASan or UBSan
|
||||
cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_COMPILER=c++ -DUSE_EXTERNAL_TESTS=OFF
|
||||
cmake --build build -j4 --verbose
|
||||
cmake --install build --verbose
|
||||
cmake --build build --target test
|
||||
|
||||
5
.github/workflows/update-master-docs.yml
vendored
5
.github/workflows/update-master-docs.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: "Update master docs"
|
||||
name: Update master docs
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
@@ -9,6 +9,7 @@ on:
|
||||
- man/rgbds.7
|
||||
- man/rgbasm.1
|
||||
- man/rgbasm.5
|
||||
- man/rgbasm-old.5
|
||||
- man/rgblink.1
|
||||
- man/rgblink.5
|
||||
- man/rgbfix.1
|
||||
@@ -17,7 +18,7 @@ on:
|
||||
jobs:
|
||||
build:
|
||||
if: github.repository_owner == 'gbdev'
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout rgbds@master
|
||||
uses: actions/checkout@v4
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -13,4 +13,5 @@
|
||||
CMakeCache.txt
|
||||
CMakeFiles/
|
||||
cmake_install.cmake
|
||||
build/
|
||||
callgrind.out.*
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
# 3.9 required for LTO checks
|
||||
cmake_minimum_required(VERSION 3.9 FATAL_ERROR)
|
||||
# 3.17 optional for CMAKE_CTEST_ARGUMENTS
|
||||
cmake_minimum_required(VERSION 3.9..3.17 FATAL_ERROR)
|
||||
|
||||
project(rgbds
|
||||
LANGUAGES CXX)
|
||||
|
||||
include(CTest)
|
||||
|
||||
# get real path of source and binary directories
|
||||
get_filename_component(srcdir "${CMAKE_SOURCE_DIR}" REALPATH)
|
||||
get_filename_component(bindir "${CMAKE_BINARY_DIR}" REALPATH)
|
||||
@@ -41,13 +44,9 @@ else()
|
||||
# does not recognize this yet.
|
||||
add_compile_options(-Wno-gnu-zero-variadic-macro-arguments)
|
||||
endif()
|
||||
add_definitions(-D_POSIX_C_SOURCE=200809L)
|
||||
if(SANITIZERS)
|
||||
set(SAN_FLAGS -fsanitize=shift -fsanitize=integer-divide-by-zero
|
||||
-fsanitize=unreachable -fsanitize=vla-bound
|
||||
-fsanitize=signed-integer-overflow -fsanitize=bounds
|
||||
-fsanitize=object-size -fsanitize=bool -fsanitize=enum
|
||||
-fsanitize=alignment -fsanitize=null -fsanitize=address)
|
||||
set(SAN_FLAGS -fsanitize=address -fsanitize=undefined
|
||||
-fsanitize=float-divide-by-zero)
|
||||
add_compile_options(${SAN_FLAGS})
|
||||
add_link_options(${SAN_FLAGS})
|
||||
add_definitions(-D_GLIBCXX_ASSERTIONS)
|
||||
@@ -59,7 +58,7 @@ else()
|
||||
if(MORE_WARNINGS)
|
||||
add_compile_options(-Werror -Wextra
|
||||
-Walloc-zero -Wcast-align -Wcast-qual -Wduplicated-branches -Wduplicated-cond
|
||||
-Wfloat-equal -Wlogical-op -Wnull-dereference -Wshift-overflow=2
|
||||
-Wfloat-equal -Wlogical-op -Wnull-dereference -Wold-style-cast -Wshift-overflow=2
|
||||
-Wstringop-overflow=4 -Wundef -Wuninitialized -Wunused
|
||||
-Wshadow # TODO: -Wshadow=compatible-local?
|
||||
-Wformat=2 -Wformat-overflow=2 -Wformat-truncation=1
|
||||
@@ -76,7 +75,7 @@ endif()
|
||||
|
||||
find_program(GIT git)
|
||||
if(GIT)
|
||||
execute_process(COMMAND ${GIT} --git-dir=.git describe --tags --dirty --always
|
||||
execute_process(COMMAND ${GIT} --git-dir=.git -c safe.directory='*' describe --tags --dirty --always
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||
OUTPUT_VARIABLE GIT_REV OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
ERROR_QUIET)
|
||||
@@ -88,6 +87,8 @@ endif(GIT)
|
||||
find_package(PkgConfig)
|
||||
if(MSVC OR NOT PKG_CONFIG_FOUND)
|
||||
# fallback to find_package
|
||||
# cmake's FindPNG is very fragile; it breaks when multiple versions are installed
|
||||
# this is most evident on macOS but can occur on Linux too
|
||||
find_package(PNG REQUIRED)
|
||||
else()
|
||||
pkg_check_modules(LIBPNG REQUIRED libpng)
|
||||
@@ -99,6 +100,7 @@ set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED True)
|
||||
|
||||
add_subdirectory(src)
|
||||
set(CMAKE_CTEST_ARGUMENTS "--verbose")
|
||||
add_subdirectory(test)
|
||||
|
||||
# By default, build in Release mode; Debug mode must be explicitly requested
|
||||
@@ -121,6 +123,7 @@ set(man1 "man/rgbasm.1"
|
||||
"man/rgbgfx.1"
|
||||
"man/rgblink.1")
|
||||
set(man5 "man/rgbasm.5"
|
||||
"man/rgbasm-old.5"
|
||||
"man/rgblink.5"
|
||||
"man/rgbds.5")
|
||||
set(man7 "man/gbz80.7"
|
||||
|
||||
@@ -174,3 +174,17 @@ Each one is a binary RNG file which is passed to the `rgbgfx_test` program.
|
||||
```sh
|
||||
test_downstream <owner> <repo> <makefile target> <build file> <sha1 hash of build file>
|
||||
```
|
||||
|
||||
## Container images
|
||||
|
||||
The CI will [take care](https://github.com/gbdev/rgbds/blob/master/.github/workflows/build-container.yml) of updating the [rgbds container](https://github.com/gbdev/rgbds/pkgs/container/rgbds) image tagged `master`.
|
||||
|
||||
When a git tag is pushed, the image is also tagged with that tag.
|
||||
|
||||
The image can be built locally and pushed to the GitHub container registry by manually running:
|
||||
|
||||
```bash
|
||||
# e.g. to build and tag as 'master'
|
||||
docker build . --tag ghcr.io/gbdev/rgbds:master
|
||||
docker push ghcr.io/gbdev/rgbds:master
|
||||
```
|
||||
@@ -1,6 +1,6 @@
|
||||
FROM debian:11-slim
|
||||
FROM debian:12-slim
|
||||
LABEL org.opencontainers.image.source=https://github.com/gbdev/rgbds
|
||||
ARG version=0.9.0-rc1
|
||||
ARG version=0.9.1
|
||||
WORKDIR /rgbds
|
||||
|
||||
COPY . .
|
||||
@@ -8,7 +8,7 @@ COPY . .
|
||||
RUN apt-get update && \
|
||||
apt-get install sudo make cmake gcc build-essential -y
|
||||
|
||||
RUN ./.github/scripts/install_deps.sh ubuntu-20.04
|
||||
RUN ./.github/scripts/install_deps.sh ubuntu-22.04
|
||||
RUN make -j CXXFLAGS="-O3 -flto -DNDEBUG -static" PKG_CONFIG="pkg-config --static" Q=
|
||||
|
||||
RUN tar caf rgbds-${version}-linux-x86_64.tar.xz --transform='s#.*/##' rgbasm rgblink rgbfix rgbgfx man/* .github/scripts/install.sh
|
||||
|
||||
25
Makefile
25
Makefile
@@ -24,21 +24,18 @@ PNGLDFLAGS := `${PKG_CONFIG} --libs-only-L libpng`
|
||||
PNGLDLIBS := `${PKG_CONFIG} --libs-only-l libpng`
|
||||
|
||||
# Note: if this comes up empty, `version.cpp` will automatically fall back to last release number
|
||||
VERSION_STRING := `git --git-dir=.git describe --tags --dirty --always 2>/dev/null`
|
||||
VERSION_STRING := `git --git-dir=.git -c safe.directory='*' describe --tags --dirty --always 2>/dev/null`
|
||||
|
||||
WARNFLAGS := -Wall -pedantic -Wno-unknown-warning-option \
|
||||
-Wno-gnu-zero-variadic-macro-arguments
|
||||
WARNFLAGS := -Wall -pedantic -Wno-unknown-warning-option -Wno-gnu-zero-variadic-macro-arguments
|
||||
|
||||
# Overridable CXXFLAGS
|
||||
CXXFLAGS ?= -O3 -flto -DNDEBUG
|
||||
# Non-overridable CXXFLAGS
|
||||
REALCXXFLAGS := ${CXXFLAGS} ${WARNFLAGS} -std=c++2a -I include \
|
||||
-D_POSIX_C_SOURCE=200809L -fno-exceptions -fno-rtti
|
||||
REALCXXFLAGS := ${CXXFLAGS} ${WARNFLAGS} -std=c++2a -I include -fno-exceptions -fno-rtti
|
||||
# Overridable LDFLAGS
|
||||
LDFLAGS ?=
|
||||
# Non-overridable LDFLAGS
|
||||
REALLDFLAGS := ${LDFLAGS} ${WARNFLAGS} \
|
||||
-DBUILD_VERSION_STRING=\"${VERSION_STRING}\"
|
||||
REALLDFLAGS := ${LDFLAGS} ${WARNFLAGS} -DBUILD_VERSION_STRING=\"${VERSION_STRING}\"
|
||||
|
||||
# Wrapper around bison that passes flags depending on what the version supports
|
||||
BISON := src/bison.sh
|
||||
@@ -188,7 +185,7 @@ install: all
|
||||
$Qinstall ${STRIP} -m ${BINMODE} rgbfix ${DESTDIR}${bindir}/rgbfix${SUFFIX}
|
||||
$Qinstall ${STRIP} -m ${BINMODE} rgbgfx ${DESTDIR}${bindir}/rgbgfx${SUFFIX}
|
||||
$Qinstall -m ${MANMODE} man/rgbasm.1 man/rgblink.1 man/rgbfix.1 man/rgbgfx.1 ${DESTDIR}${mandir}/man1/
|
||||
$Qinstall -m ${MANMODE} man/rgbds.5 man/rgbasm.5 man/rgblink.5 ${DESTDIR}${mandir}/man5/
|
||||
$Qinstall -m ${MANMODE} man/rgbds.5 man/rgbasm.5 man/rgbasm-old.5 man/rgblink.5 ${DESTDIR}${mandir}/man5/
|
||||
$Qinstall -m ${MANMODE} man/rgbds.7 man/gbz80.7 ${DESTDIR}${mandir}/man7/
|
||||
|
||||
# Target used to check for suspiciously missing changed files.
|
||||
@@ -204,17 +201,13 @@ checkdiff:
|
||||
develop:
|
||||
$Q${MAKE} WARNFLAGS="${WARNFLAGS} -Werror -Wextra \
|
||||
-Walloc-zero -Wcast-align -Wcast-qual -Wduplicated-branches -Wduplicated-cond \
|
||||
-Wfloat-equal -Wlogical-op -Wnull-dereference -Wshift-overflow=2 \
|
||||
-Wfloat-equal -Wlogical-op -Wnull-dereference -Wold-style-cast -Wshift-overflow=2 \
|
||||
-Wstringop-overflow=4 -Wundef -Wuninitialized -Wunused -Wshadow \
|
||||
-Wformat=2 -Wformat-overflow=2 -Wformat-truncation=1 \
|
||||
-Wno-format-nonliteral -Wno-strict-overflow -Wno-unused-but-set-variable \
|
||||
-Wno-type-limits -Wno-tautological-constant-out-of-range-compare -Wvla \
|
||||
-D_GLIBCXX_ASSERTIONS \
|
||||
-fsanitize=shift -fsanitize=integer-divide-by-zero \
|
||||
-fsanitize=unreachable -fsanitize=vla-bound \
|
||||
-fsanitize=signed-integer-overflow -fsanitize=bounds \
|
||||
-fsanitize=object-size -fsanitize=bool -fsanitize=enum \
|
||||
-fsanitize=alignment -fsanitize=null -fsanitize=address" \
|
||||
-D_GLIBCXX_ASSERTIONS -fsanitize=address -fsanitize=undefined \
|
||||
-fsanitize=float-divide-by-zero" \
|
||||
CXXFLAGS="-ggdb3 -Og -fno-omit-frame-pointer -fno-optimize-sibling-calls"
|
||||
|
||||
# This target is used during development in order to more easily debug with gdb.
|
||||
@@ -272,4 +265,4 @@ wine-shim:
|
||||
|
||||
dist:
|
||||
$Qgit ls-files | sed s~^~$${PWD##*/}/~ \
|
||||
| tar -czf rgbds-`git describe --tags | cut -c 2-`.tar.gz -C .. -T -
|
||||
| tar -czf rgbds-`git -c safe.directory='*' describe --tags | cut -c 2-`.tar.gz -C .. -T -
|
||||
|
||||
@@ -137,9 +137,12 @@ The RGBDS source code file structure is as follows:
|
||||
this version as [rgbds-linux](https://github.com/vegard/rgbds-linux).
|
||||
- 2010-01-12: Anthony J. Bentley [forks](https://github.com/bentley) Nossum's
|
||||
repository. The fork becomes the reference implementation of RGBDS.
|
||||
- 2010-09-25: Sørensen continues development of
|
||||
[ASMotor](https://github.com/asmotor/asmotor) to this day.
|
||||
- 2015-01-18: stag019 begins implementing [RGBGFX](https://github.com/stag019/rgbgfx),
|
||||
a PNG‐to‐Game Boy graphics converter, for eventual integration into RGBDS.
|
||||
- 2016-09-05: RGBGFX is [integrated](https://github.com/gbdev/rgbds/commit/c3c31138ddbd8680d4e67957e387f2816798a71b)
|
||||
- 2016-09-05: RGBGFX is
|
||||
[integrated](https://github.com/gbdev/rgbds/commit/c3c31138ddbd8680d4e67957e387f2816798a71b)
|
||||
into Bentley's repository.
|
||||
- 2017-02-23: Bentley's repository is moved to the [rednex](https://github.com/rednex)
|
||||
organization.
|
||||
|
||||
34
RELEASE.md
34
RELEASE.md
@@ -3,13 +3,16 @@
|
||||
This describes for the maintainers of RGBDS how to publish a new release on
|
||||
GitHub.
|
||||
|
||||
1. Update, commit, and push [include/version.hpp](include/version.hpp) with
|
||||
values for `PACKAGE_VERSION_MAJOR`, `PACKAGE_VERSION_MINOR`,
|
||||
`PACKAGE_VERSION_PATCH`, and `PACKAGE_VERSION_RC`, as well as
|
||||
[Dockerfile](Dockerfile) with a value for `ARG version`. Only define
|
||||
`PACKAGE_VERSION_RC` if you are publishing a release candidate! You can
|
||||
use <code>git commit -m "Release <i><version></i>"</code> and
|
||||
`git push origin master`.
|
||||
1. Update the following files, then commit and push.
|
||||
You can use <code>git commit -m "Release <i><version></i>"</code> and `git push origin master`.
|
||||
|
||||
- [include/version.hpp](include/version.hpp): set appropriate values for `PACKAGE_VERSION_MAJOR`,
|
||||
`PACKAGE_VERSION_MINOR`, `PACKAGE_VERSION_PATCH`, and `PACKAGE_VERSION_RC`.
|
||||
**Only** define `PACKAGE_VERSION_RC` if you are publishing a release candidate!
|
||||
- [Dockerfile](Dockerfile): update `ARG version`.
|
||||
- [test/fetch-test-deps.sh](test/fetch-test-deps.sh): update test dependency commits
|
||||
(preferably, use the latest available).
|
||||
- [man/\*](man/): update dates and authors.
|
||||
|
||||
2. Create a Git tag formatted as <code>v<i><MAJOR></i>.<i><MINOR></i>.<i><PATCH></i></code>,
|
||||
or <code>v<i><MAJOR></i>.<i><MINOR></i>.<i><PATCH></i>-rc<i><RC></i></code>
|
||||
@@ -38,7 +41,9 @@ GitHub.
|
||||
4. GitHub Actions will run the [create-release-docs.yml](.github/workflows/create-release-docs.yml)
|
||||
workflow to add the release documentation to [rgbds-www](https://github.com/gbdev/rgbds-www).
|
||||
|
||||
This is not done automatically for prereleases; if you want to do this manually,
|
||||
This is not done automatically for prereleases, since we do not normally publish documentation
|
||||
for them. If you want to manually publish prerelease documentation, such as for an April Fools
|
||||
joke prerelease,
|
||||
|
||||
1. Clone [rgbds-www](https://github.com/gbdev/rgbds-www). You can use
|
||||
`git clone https://github.com/gbdev/rgbds-www.git`.
|
||||
@@ -52,8 +57,8 @@ GitHub.
|
||||
|
||||
If you do not have `groff` installed, you can change
|
||||
`groff -Tpdf -mdoc -wall` to `mandoc -Tpdf -I os=Linux` in
|
||||
[.github/actions/get-pages.sh](.github/actions/get-pages.sh) and it
|
||||
will suffice.
|
||||
[maintainer/man_to_html.sh](https://github.com/gbdev/rgbds-www/blob/master/maintainer/man_to_html.sh)
|
||||
and it will suffice.
|
||||
|
||||
4. Commit and push the documentation. You can use <code>git commit -m
|
||||
"Create RGBDS <i><tag></i> documentation"</code> and `git push origin master`
|
||||
@@ -64,3 +69,12 @@ GitHub.
|
||||
6. Click the "Publish release" button to publish it!
|
||||
|
||||
7. Update the `release` branch. You can use `git push origin release`.
|
||||
|
||||
8. Update the following related projects.
|
||||
|
||||
- [rgbobj](https://github.com/gbdev/rgbobj) and [rgbds-obj](https://github.com/gbdev/rgbds-obj):
|
||||
make sure that object files created by the latest RGBASM can be parsed and displayed.
|
||||
If the object file revision has been updated, rgbobj will need a corresponding release.
|
||||
- [rgbds-www](https://github.com/gbdev/rgbds-www): update
|
||||
[src/pages/versions.mdx](https://github.com/gbdev/rgbds-www/blob/master/src/pages/versions.mdx)
|
||||
to list the new release.
|
||||
|
||||
@@ -25,6 +25,7 @@ _rgbasm_completions() {
|
||||
# See the `state` variable below for info about `state_after`
|
||||
declare -A opts=(
|
||||
[V]="version:normal"
|
||||
[h]="help:normal"
|
||||
[E]="export-all:normal"
|
||||
[v]="verbose:normal"
|
||||
[w]=":normal"
|
||||
@@ -192,6 +193,8 @@ _rgbasm_completions() {
|
||||
shift-amount
|
||||
truncation
|
||||
unmapped-char
|
||||
unmatched-directive
|
||||
unterminated-load
|
||||
user
|
||||
all
|
||||
extra
|
||||
|
||||
@@ -8,6 +8,7 @@ _rgbfix_completions() {
|
||||
# See the `state` variable below for info about `state_after`
|
||||
declare -A opts=(
|
||||
[V]="version:normal"
|
||||
[h]="help:normal"
|
||||
[j]="non-japanese:normal"
|
||||
[s]="sgb-compatible:normal"
|
||||
[v]="validate:normal"
|
||||
|
||||
@@ -8,6 +8,7 @@ _rgbgfx_completions() {
|
||||
# See the `state` variable below for info about `state_after`
|
||||
declare -A opts=(
|
||||
[V]="version:normal"
|
||||
[h]="help:normal"
|
||||
[C]="color-curve:normal"
|
||||
[m]="mirror-tiles:normal"
|
||||
[O]="group-outputs:normal"
|
||||
|
||||
@@ -8,6 +8,7 @@ _rgblink_completions() {
|
||||
# See the `state` variable below for info about `state_after`
|
||||
declare -A opts=(
|
||||
[V]="version:normal"
|
||||
[h]="help:normal"
|
||||
[d]="dmg:normal"
|
||||
[t]="tiny:normal"
|
||||
[v]="verbose:normal"
|
||||
|
||||
@@ -26,6 +26,8 @@ _rgbasm_warnings() {
|
||||
'shift-amount:Warn when a shift'\''s operand it negative or \> 32'
|
||||
'truncation:Warn when implicit truncation loses bits'
|
||||
'unmapped-char:Warn on unmapped character'
|
||||
'unmatched-directive:Warn on unmatched directive pair'
|
||||
'unterminated-load:Warn on LOAD without ENDL'
|
||||
'user:Warn when executing the WARN built-in'
|
||||
)
|
||||
# TODO: handle `no-` and `error=` somehow?
|
||||
@@ -34,8 +36,9 @@ _rgbasm_warnings() {
|
||||
}
|
||||
|
||||
local args=(
|
||||
# Arguments are listed here in the same order as in the manual, except for the version
|
||||
'(- : * options)'{-V,--version}'[Print version number]'
|
||||
# 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)'{-h,--help}'[Print help text and exit]'
|
||||
|
||||
'(-E --export-all)'{-E,--export-all}'[Export all symbols]'
|
||||
'(-v --verbose)'{-v,--verbose}'[Print additional messages regarding progression]'
|
||||
|
||||
@@ -35,8 +35,9 @@ _mbc_names() {
|
||||
}
|
||||
|
||||
local args=(
|
||||
# Arguments are listed here in the same order as in the manual, except for the version
|
||||
'(- : * options)'{-V,--version}'[Print version number]'
|
||||
# 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)'{-h,--help}'[Print help text and exit]'
|
||||
|
||||
'(-C --color-only -c --color-compatible)'{-C,--color-only}'[Mark ROM as GBC-only]'
|
||||
'(-C --color-only -c --color-compatible)'{-c,--color-compatible}'[Mark ROM as GBC-compatible]'
|
||||
|
||||
@@ -10,8 +10,9 @@ _depths() {
|
||||
}
|
||||
|
||||
local args=(
|
||||
# Arguments are listed here in the same order as in the manual, except for the version
|
||||
'(- : * options)'{-V,--version}'[Print version number]'
|
||||
# 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)'{-h,--help}'[Print help text and exit]'
|
||||
|
||||
'(-a --attr-map -A --auto-attr-map)'{-A,--auto-attr-map}'[Shortcut for -a <file>.attrmap]'
|
||||
'(-C --color-curve)'{-C,--color-curve}'[Generate palettes using GBC color curve]'
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
#compdef rgblink
|
||||
|
||||
local args=(
|
||||
# Arguments are listed here in the same order as in the manual, except for the version
|
||||
'(- : * options)'{-V,--version}'[Print version number]'
|
||||
# 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)'{-h,--help}'[Print help text and exit]'
|
||||
|
||||
'(-d --dmg)'{-d,--dmg}'[Enable DMG mode (-w + no VRAM banking)]'
|
||||
'(-t --tiny)'{-t,--tiny}'[Enable tiny mode, disabling ROM banking]'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_ASM_CHARMAP_HPP
|
||||
#define RGBDS_ASM_CHARMAP_HPP
|
||||
@@ -18,6 +18,7 @@ void charmap_New(std::string const &name, std::string const *baseName);
|
||||
void charmap_Set(std::string const &name);
|
||||
void charmap_Push();
|
||||
void charmap_Pop();
|
||||
void charmap_CheckStack();
|
||||
void charmap_Add(std::string const &mapping, std::vector<int32_t> &&value);
|
||||
bool charmap_HasChar(std::string const &input);
|
||||
std::vector<int32_t> charmap_Convert(std::string const &input);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_ASM_FIXPOINT_HPP
|
||||
#define RGBDS_ASM_FIXPOINT_HPP
|
||||
@@ -8,7 +8,6 @@
|
||||
extern uint8_t fixPrecision;
|
||||
|
||||
uint8_t fix_Precision();
|
||||
double fix_PrecisionFactor();
|
||||
int32_t fix_Sin(int32_t i, int32_t q);
|
||||
int32_t fix_Cos(int32_t i, int32_t q);
|
||||
int32_t fix_Tan(int32_t i, int32_t q);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_ASM_FORMAT_HPP
|
||||
#define RGBDS_ASM_FORMAT_HPP
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// Contains some assembler-wide defines and externs
|
||||
|
||||
@@ -30,8 +30,8 @@ struct FileStackNode {
|
||||
// Meaningless at the root level, but gets written to the object file anyway, so init it
|
||||
uint32_t lineNo = 0;
|
||||
|
||||
// Set only if referenced: ID within the object file, -1 if not output yet
|
||||
uint32_t ID = -1;
|
||||
// Set only if referenced: ID within the object file, `UINT32_MAX` if not output yet
|
||||
uint32_t ID = UINT32_MAX;
|
||||
|
||||
// REPT iteration counts since last named node, in reverse depth order
|
||||
std::vector<uint32_t> &iters() { return data.get<std::vector<uint32_t>>(); }
|
||||
@@ -41,15 +41,11 @@ struct FileStackNode {
|
||||
std::string const &name() const { return data.get<std::string>(); }
|
||||
|
||||
FileStackNode(FileStackNodeType type_, Either<std::vector<uint32_t>, std::string> data_)
|
||||
: type(type_), data(data_){};
|
||||
: type(type_), data(data_) {}
|
||||
|
||||
std::string const &dump(uint32_t curLineNo) const;
|
||||
|
||||
// If true, entering this context generates a new unique ID.
|
||||
bool generatesUniqueID() const { return type == NODE_REPT || type == NODE_MACRO; }
|
||||
};
|
||||
|
||||
#define DEFAULT_MAX_DEPTH 64
|
||||
extern size_t maxRecursionDepth;
|
||||
|
||||
struct MacroArgs;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_ASM_LEXER_HPP
|
||||
#define RGBDS_ASM_LEXER_HPP
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
// This value is a compromise between `LexerState` allocation performance when `mmap` works, and
|
||||
// buffering performance when it doesn't/can't (e.g. when piping a file into RGBASM).
|
||||
#define 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
|
||||
static_assert(LEXER_BUF_SIZE > 1, "Lexer buffer size is too small");
|
||||
// This caps the size of buffer reads, and according to POSIX, passing more than SSIZE_MAX is UB
|
||||
@@ -83,7 +83,6 @@ struct LexerState {
|
||||
LexerMode mode;
|
||||
bool atLineStart;
|
||||
uint32_t lineNo;
|
||||
uint32_t colNo;
|
||||
int lastToken;
|
||||
|
||||
std::deque<IfStackEntry> ifStack;
|
||||
@@ -147,7 +146,6 @@ void lexer_ReachELSEBlock();
|
||||
|
||||
void lexer_CheckRecursionDepth();
|
||||
uint32_t lexer_GetLineNo();
|
||||
uint32_t lexer_GetColNo();
|
||||
void lexer_DumpStringExpansions();
|
||||
|
||||
struct Capture {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_ASM_MACRO_HPP
|
||||
#define RGBDS_ASM_MACRO_HPP
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_ASM_MAIN_HPP
|
||||
#define RGBDS_ASM_MAIN_HPP
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_ASM_OPT_HPP
|
||||
#define RGBDS_ASM_OPT_HPP
|
||||
@@ -14,5 +14,6 @@ void opt_Parse(char const *option);
|
||||
|
||||
void opt_Push();
|
||||
void opt_Pop();
|
||||
void opt_CheckStack();
|
||||
|
||||
#endif // RGBDS_ASM_OPT_HPP
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_ASM_OUTPUT_HPP
|
||||
#define RGBDS_ASM_OUTPUT_HPP
|
||||
@@ -14,14 +14,7 @@
|
||||
struct Expression;
|
||||
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;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_ASM_RPN_HPP
|
||||
#define RGBDS_ASM_RPN_HPP
|
||||
@@ -31,12 +31,8 @@ struct Expression {
|
||||
|
||||
Expression &operator=(Expression &&) = default;
|
||||
|
||||
bool isKnown() const {
|
||||
return data.holds<int32_t>();
|
||||
}
|
||||
int32_t value() const {
|
||||
return data.get<int32_t>();
|
||||
}
|
||||
bool isKnown() const { return data.holds<int32_t>(); }
|
||||
int32_t value() const { return data.get<int32_t>(); }
|
||||
|
||||
int32_t getConstVal() const;
|
||||
Symbol const *symbolOf() const;
|
||||
@@ -53,7 +49,7 @@ struct Expression {
|
||||
void makeUnaryOp(RPNCommand op, Expression &&src);
|
||||
void makeBinaryOp(RPNCommand op, Expression &&src1, Expression const &src2);
|
||||
|
||||
void makeCheckHRAM();
|
||||
bool makeCheckHRAM();
|
||||
void makeCheckRST();
|
||||
|
||||
void checkNBit(uint8_t n) const;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_ASM_SECTION_HPP
|
||||
#define RGBDS_ASM_SECTION_HPP
|
||||
@@ -70,7 +70,8 @@ void sect_SetLoadSection(
|
||||
SectionSpec const &attrs,
|
||||
SectionModifier mod
|
||||
);
|
||||
void sect_EndLoadSection();
|
||||
void sect_EndLoadSection(char const *cause);
|
||||
void sect_CheckLoadClosed();
|
||||
|
||||
Section *sect_GetSymbolSection();
|
||||
uint32_t sect_GetSymbolOffset();
|
||||
@@ -101,5 +102,6 @@ void sect_BinaryFileSlice(std::string const &name, int32_t startPos, int32_t len
|
||||
void sect_EndSection();
|
||||
void sect_PushSection();
|
||||
void sect_PopSection();
|
||||
void sect_CheckStack();
|
||||
|
||||
#endif // RGBDS_ASM_SECTION_HPP
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_ASM_SYMBOL_HPP
|
||||
#define RGBDS_ASM_SYMBOL_HPP
|
||||
@@ -45,7 +45,7 @@ struct Symbol {
|
||||
>
|
||||
data;
|
||||
|
||||
uint32_t ID; // ID of the symbol in the object file (-1 if none)
|
||||
uint32_t ID; // ID of the symbol in the object file (`UINT32_MAX` if none)
|
||||
uint32_t defIndex; // Ordering of the symbol in the state file
|
||||
|
||||
bool isDefined() const { return type != SYM_REF; }
|
||||
@@ -55,7 +55,7 @@ struct Symbol {
|
||||
bool isConstant() const {
|
||||
if (type == SYM_LABEL) {
|
||||
Section const *sect = getSection();
|
||||
return sect && sect->org != (uint32_t)-1;
|
||||
return sect && sect->org != UINT32_MAX;
|
||||
}
|
||||
return type == SYM_EQU || type == SYM_VAR;
|
||||
}
|
||||
|
||||
@@ -1,35 +1,34 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_ASM_WARNING_HPP
|
||||
#define RGBDS_ASM_WARNING_HPP
|
||||
|
||||
extern unsigned int nbErrors, maxErrors;
|
||||
|
||||
enum WarningState { WARNING_DEFAULT, WARNING_DISABLED, WARNING_ENABLED, WARNING_ERROR };
|
||||
|
||||
enum WarningID {
|
||||
WARNING_ASSERT, // Assertions
|
||||
WARNING_BACKWARDS_FOR, // `for` loop with backwards range
|
||||
WARNING_BACKWARDS_FOR, // `FOR` loop with backwards range
|
||||
WARNING_BUILTIN_ARG, // Invalid args to builtins
|
||||
WARNING_CHARMAP_REDEF, // Charmap entry re-definition
|
||||
WARNING_DIV, // Division undefined behavior
|
||||
WARNING_DIV, // Undefined division behavior
|
||||
WARNING_EMPTY_DATA_DIRECTIVE, // `db`, `dw` or `dl` directive without data in ROM
|
||||
WARNING_EMPTY_MACRO_ARG, // Empty macro argument
|
||||
WARNING_EMPTY_STRRPL, // Empty second argument in `STRRPL`
|
||||
WARNING_LARGE_CONSTANT, // Constants too large
|
||||
WARNING_MACRO_SHIFT, // Shift past available arguments in macro
|
||||
WARNING_MACRO_SHIFT, // `SHIFT` past available arguments in macro
|
||||
WARNING_NESTED_COMMENT, // Comment-start delimiter in a block comment
|
||||
WARNING_OBSOLETE, // Obsolete things
|
||||
WARNING_SHIFT, // Shifting undefined behavior
|
||||
WARNING_SHIFT_AMOUNT, // Strange shift amount
|
||||
WARNING_USER, // User warnings
|
||||
WARNING_OBSOLETE, // Obsolete/deprecated things
|
||||
WARNING_SHIFT, // Undefined `SHIFT` behavior
|
||||
WARNING_SHIFT_AMOUNT, // Strange `SHIFT` amount
|
||||
WARNING_UNMATCHED_DIRECTIVE, // `PUSH[C|O|S]` without `POP[C|O|S]`
|
||||
WARNING_UNTERMINATED_LOAD, // `LOAD` without `ENDL`
|
||||
WARNING_USER, // User-defined `WARN`ings
|
||||
|
||||
NB_PLAIN_WARNINGS,
|
||||
|
||||
// Warnings past this point are "parametric" warnings, only mapping to a single flag
|
||||
#define PARAM_WARNINGS_START NB_PLAIN_WARNINGS
|
||||
// Warnings past this point are "parametric" warnings, only mapping to a single flag
|
||||
// Treating string as number may lose some bits
|
||||
WARNING_NUMERIC_STRING_1 = PARAM_WARNINGS_START,
|
||||
WARNING_NUMERIC_STRING_1 = NB_PLAIN_WARNINGS,
|
||||
WARNING_NUMERIC_STRING_2,
|
||||
// Purging an exported symbol or label
|
||||
WARNING_PURGE_1,
|
||||
@@ -41,45 +40,46 @@ enum WarningID {
|
||||
WARNING_UNMAPPED_CHAR_1,
|
||||
WARNING_UNMAPPED_CHAR_2,
|
||||
|
||||
NB_PLAIN_AND_PARAM_WARNINGS,
|
||||
#define NB_PARAM_WARNINGS (NB_PLAIN_AND_PARAM_WARNINGS - PARAM_WARNINGS_START)
|
||||
|
||||
// Warnings past this point are "meta" warnings
|
||||
#define META_WARNINGS_START NB_PLAIN_AND_PARAM_WARNINGS
|
||||
WARNING_ALL = META_WARNINGS_START,
|
||||
WARNING_EXTRA,
|
||||
WARNING_EVERYTHING,
|
||||
|
||||
NB_WARNINGS,
|
||||
#define NB_META_WARNINGS (NB_WARNINGS - META_WARNINGS_START)
|
||||
};
|
||||
|
||||
extern WarningState warningStates[NB_PLAIN_AND_PARAM_WARNINGS];
|
||||
enum WarningAbled { WARNING_DEFAULT, WARNING_ENABLED, WARNING_DISABLED };
|
||||
|
||||
struct WarningState {
|
||||
WarningAbled state;
|
||||
WarningAbled error;
|
||||
|
||||
void update(WarningState other);
|
||||
};
|
||||
|
||||
struct Diagnostics {
|
||||
WarningState flagStates[NB_WARNINGS];
|
||||
WarningState metaStates[NB_WARNINGS];
|
||||
};
|
||||
|
||||
extern Diagnostics warningStates;
|
||||
extern bool warningsAreErrors;
|
||||
|
||||
void processWarningFlag(char const *flag);
|
||||
|
||||
/*
|
||||
* Used to warn the user about problems that don't prevent the generation of
|
||||
* valid code.
|
||||
*/
|
||||
[[gnu::format(printf, 2, 3)]] void warning(WarningID id, char const *fmt, ...);
|
||||
// Used to warn the user about problems that don't prevent the generation of
|
||||
// valid code.
|
||||
[[gnu::format(printf, 2, 3)]]
|
||||
void warning(WarningID id, char const *fmt, ...);
|
||||
|
||||
/*
|
||||
* Used for errors that compromise the whole assembly process by affecting the
|
||||
* following code, potencially making the assembler generate errors caused by
|
||||
* the first one and unrelated to the code that the assembler complains about.
|
||||
* It is also used when the assembler goes into an invalid state (for example,
|
||||
* when it fails to allocate memory).
|
||||
*/
|
||||
[[gnu::format(printf, 1, 2), noreturn]] void fatalerror(char const *fmt, ...);
|
||||
// Used for errors that compromise the whole assembly process by affecting the
|
||||
// following code, potencially making the assembler generate errors caused by
|
||||
// the first one and unrelated to the code that the assembler complains about.
|
||||
// It is also used when the assembler goes into an invalid state (for example,
|
||||
// when it fails to allocate memory).
|
||||
[[gnu::format(printf, 1, 2), noreturn]]
|
||||
void fatalerror(char const *fmt, ...);
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
[[gnu::format(printf, 1, 2)]] void error(char const *fmt, ...);
|
||||
// Used for errors that make it impossible to assemble correctly, but don't
|
||||
// affect the following code. The code will fail to assemble but the user will
|
||||
// get a list of all errors at the end, making it easier to fix all of them at
|
||||
// once.
|
||||
[[gnu::format(printf, 1, 2)]]
|
||||
void error(char const *fmt, ...);
|
||||
|
||||
#endif // RGBDS_ASM_WARNING_HPP
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_DEFAULT_INIT_ALLOC_HPP
|
||||
#define RGBDS_DEFAULT_INIT_ALLOC_HPP
|
||||
@@ -6,14 +6,11 @@
|
||||
#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
|
||||
*/
|
||||
|
||||
// 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>;
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_EITHER_HPP
|
||||
#define RGBDS_EITHER_HPP
|
||||
@@ -43,11 +43,11 @@ private:
|
||||
// Generic field accessors; for internal use only.
|
||||
template<typename T>
|
||||
auto &field() {
|
||||
return pick((T *)nullptr);
|
||||
return pick(static_cast<T *>(nullptr));
|
||||
}
|
||||
template<typename T>
|
||||
auto const &field() const {
|
||||
return pick((T *)nullptr);
|
||||
return pick(static_cast<T *>(nullptr));
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// 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)]] 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, ...);
|
||||
[[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
|
||||
|
||||
16
include/extern/getopt.hpp
vendored
16
include/extern/getopt.hpp
vendored
@@ -1,11 +1,15 @@
|
||||
/* 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
|
||||
|
||||
#ifndef RGBDS_EXTERN_GETOPT_HPP
|
||||
#define RGBDS_EXTERN_GETOPT_HPP
|
||||
|
||||
extern "C" {
|
||||
// clang-format off: vertically align values
|
||||
static constexpr int no_argument = 0;
|
||||
static constexpr int required_argument = 1;
|
||||
static constexpr int optional_argument = 2;
|
||||
// clang-format on
|
||||
|
||||
extern char *musl_optarg;
|
||||
extern int musl_optind, musl_opterr, musl_optopt, musl_optreset;
|
||||
@@ -21,10 +25,4 @@ int musl_getopt_long_only(
|
||||
int argc, char **argv, char const *optstring, option const *longopts, int *idx
|
||||
);
|
||||
|
||||
#define no_argument 0
|
||||
#define required_argument 1
|
||||
#define optional_argument 2
|
||||
|
||||
} // extern "C"
|
||||
|
||||
#endif // RGBDS_EXTERN_GETOPT_HPP
|
||||
|
||||
2
include/extern/utf8decoder.hpp
vendored
2
include/extern/utf8decoder.hpp
vendored
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_EXTERN_UTF8DECODER_HPP
|
||||
#define RGBDS_EXTERN_UTF8DECODER_HPP
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_FILE_HPP
|
||||
#define RGBDS_FILE_HPP
|
||||
@@ -25,10 +25,8 @@ public:
|
||||
File() {}
|
||||
~File() { close(); }
|
||||
|
||||
/**
|
||||
* This should only be called once, and before doing any `->` operations.
|
||||
* Returns `nullptr` on error, and a non-null pointer otherwise.
|
||||
*/
|
||||
// This should only be called once, and before doing any `->` operations.
|
||||
// Returns `nullptr` on error, and a non-null pointer otherwise.
|
||||
File *open(std::string const &path, std::ios_base::openmode mode) {
|
||||
if (path != "-") {
|
||||
_file.emplace<std::filebuf>();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_GFX_MAIN_HPP
|
||||
#define RGBDS_GFX_MAIN_HPP
|
||||
@@ -48,14 +48,17 @@ struct Options {
|
||||
|
||||
std::string input{}; // positional arg
|
||||
|
||||
static constexpr uint8_t VERB_NONE = 0; // Normal, no extra output
|
||||
static constexpr uint8_t VERB_CFG = 1; // Print configuration after parsing options
|
||||
static constexpr uint8_t VERB_LOG_ACT = 2; // Log actions before doing them
|
||||
static constexpr uint8_t VERB_INTERM = 3; // Print some intermediate results
|
||||
static constexpr uint8_t VERB_DEBUG = 4; // Internals are logged
|
||||
static constexpr uint8_t VERB_UNMAPPED = 5; // Unused so far
|
||||
static constexpr uint8_t VERB_VVVVVV = 6; // What, can't I have a little fun?
|
||||
[[gnu::format(printf, 3, 4)]] void verbosePrint(uint8_t level, char const *fmt, ...) const;
|
||||
// clang-format off: vertically align values
|
||||
static constexpr uint8_t VERB_NONE = 0; // Normal, no extra output
|
||||
static constexpr uint8_t VERB_CFG = 1; // Print configuration after parsing options
|
||||
static constexpr uint8_t VERB_LOG_ACT = 2; // Log actions before doing them
|
||||
static constexpr uint8_t VERB_INTERM = 3; // Print some intermediate results
|
||||
static constexpr uint8_t VERB_DEBUG = 4; // Internals are logged
|
||||
static constexpr uint8_t VERB_TRACE = 5; // Step-by-step algorithm details
|
||||
static constexpr uint8_t VERB_VVVVVV = 6; // What, can't I have a little fun?
|
||||
// clang-format on
|
||||
[[gnu::format(printf, 3, 4)]]
|
||||
void verbosePrint(uint8_t level, char const *fmt, ...) const;
|
||||
|
||||
mutable bool hasTransparentPixels = false;
|
||||
uint8_t maxOpaqueColors() const { return nbColorsPerPal - hasTransparentPixels; }
|
||||
@@ -63,32 +66,24 @@ struct 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()`.
|
||||
*/
|
||||
// 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)`.
|
||||
*/
|
||||
// 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, ...);
|
||||
// Prints a fatal error, increments the error count, and gives up
|
||||
[[gnu::format(printf, 1, 2), noreturn]]
|
||||
void fatal(char const *fmt, ...);
|
||||
|
||||
struct Palette {
|
||||
// An array of 4 GBC-native (RGB555) colors
|
||||
@@ -107,20 +102,18 @@ struct Palette {
|
||||
uint8_t size() const;
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
template<typename T, T... i>
|
||||
static constexpr auto flipTable(std::integer_sequence<T, i...>) {
|
||||
return std::array{[](uint8_t byte) {
|
||||
// Flipping tends to happen fairly often, so take a bite out of dcache to speed it up
|
||||
static constexpr auto flipTable = ([]() constexpr {
|
||||
std::array<uint16_t, 256> table{};
|
||||
for (uint16_t i = 0; i < table.size(); i++) {
|
||||
// To flip all the bits, we'll flip both nibbles, then each nibble half, etc.
|
||||
uint16_t byte = i;
|
||||
byte = (byte & 0b0000'1111) << 4 | (byte & 0b1111'0000) >> 4;
|
||||
byte = (byte & 0b0011'0011) << 2 | (byte & 0b1100'1100) >> 2;
|
||||
byte = (byte & 0b0101'0101) << 1 | (byte & 0b1010'1010) >> 1;
|
||||
return byte;
|
||||
}(i)...};
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
// Flipping tends to happen fairly often, so take a bite out of dcache to speed it up
|
||||
static constexpr auto flipTable = detail::flipTable(std::make_integer_sequence<uint16_t, 256>());
|
||||
table[i] = byte;
|
||||
}
|
||||
return table;
|
||||
})();
|
||||
|
||||
#endif // RGBDS_GFX_MAIN_HPP
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_GFX_PAL_PACKING_HPP
|
||||
#define RGBDS_GFX_PAL_PACKING_HPP
|
||||
@@ -6,19 +6,13 @@
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
|
||||
#include "defaultinitalloc.hpp"
|
||||
#include "defaultinitvec.hpp"
|
||||
|
||||
struct Palette;
|
||||
class ProtoPalette;
|
||||
|
||||
namespace packing {
|
||||
|
||||
/*
|
||||
* Returns which palette each proto-palette maps to, and how many palettes are necessary
|
||||
*/
|
||||
// Returns which palette each proto-palette maps to, and how many palettes are necessary
|
||||
std::tuple<DefaultInitVec<size_t>, size_t>
|
||||
overloadAndRemove(std::vector<ProtoPalette> const &protoPalettes);
|
||||
|
||||
} // namespace packing
|
||||
|
||||
#endif // RGBDS_GFX_PAL_PACKING_HPP
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_GFX_PAL_SORTING_HPP
|
||||
#define RGBDS_GFX_PAL_SORTING_HPP
|
||||
@@ -10,22 +10,22 @@
|
||||
|
||||
#include "gfx/rgba.hpp"
|
||||
|
||||
// Allow a slot for every possible CGB color, plus one for transparency
|
||||
// 32 (1 << 5) per channel, times 3 RGB channels = 32768 CGB colors
|
||||
static constexpr size_t NB_COLOR_SLOTS = (1 << (5 * 3)) + 1;
|
||||
|
||||
struct Palette;
|
||||
|
||||
namespace sorting {
|
||||
|
||||
void indexed(
|
||||
void sortIndexed(
|
||||
std::vector<Palette> &palettes,
|
||||
int palSize,
|
||||
png_color const *palRGB,
|
||||
int palAlphaSize,
|
||||
png_byte *palAlpha
|
||||
);
|
||||
void grayscale(
|
||||
std::vector<Palette> &palettes, std::array<std::optional<Rgba>, 0x8001> const &colors
|
||||
void sortGrayscale(
|
||||
std::vector<Palette> &palettes, std::array<std::optional<Rgba>, NB_COLOR_SLOTS> const &colors
|
||||
);
|
||||
void rgb(std::vector<Palette> &palettes);
|
||||
|
||||
} // namespace sorting
|
||||
void sortRgb(std::vector<Palette> &palettes);
|
||||
|
||||
#endif // RGBDS_GFX_PAL_SORTING_HPP
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_GFX_PAL_SPEC_HPP
|
||||
#define RGBDS_GFX_PAL_SPEC_HPP
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_GFX_PROCESS_HPP
|
||||
#define RGBDS_GFX_PROCESS_HPP
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_GFX_PROTO_PALETTE_HPP
|
||||
#define RGBDS_GFX_PROTO_PALETTE_HPP
|
||||
@@ -18,12 +18,8 @@ private:
|
||||
std::array<uint16_t, capacity> _colorIndices{UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX};
|
||||
|
||||
public:
|
||||
/*
|
||||
* Adds the specified color to the set, or **silently drops it** if the set is full.
|
||||
*
|
||||
* Returns whether the color was unique.
|
||||
*/
|
||||
bool add(uint16_t color);
|
||||
// Adds the specified color to the set, or **silently drops it** if the set is full.
|
||||
void add(uint16_t color);
|
||||
|
||||
enum ComparisonResult {
|
||||
NEITHER,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_GFX_REVERSE_HPP
|
||||
#define RGBDS_GFX_REVERSE_HPP
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_GFX_RGBA_HPP
|
||||
#define RGBDS_GFX_RGBA_HPP
|
||||
@@ -13,9 +13,7 @@ struct Rgba {
|
||||
|
||||
constexpr Rgba(uint8_t r, uint8_t g, uint8_t b, uint8_t a)
|
||||
: red(r), green(g), blue(b), alpha(a) {}
|
||||
/*
|
||||
* Constructs the color from a "packed" RGBA representation (0xRRGGBBAA)
|
||||
*/
|
||||
// Constructs the color from a "packed" RGBA representation (0xRRGGBBAA)
|
||||
explicit constexpr Rgba(uint32_t rgba = 0)
|
||||
: red(rgba >> 24), green(rgba >> 16), blue(rgba >> 8), alpha(rgba) {}
|
||||
|
||||
@@ -28,34 +26,28 @@ struct Rgba {
|
||||
_5to8(cgbColor),
|
||||
_5to8(cgbColor >> 5),
|
||||
_5to8(cgbColor >> 10),
|
||||
(uint8_t)(cgbColor & 0x8000 ? 0x00 : 0xFF),
|
||||
static_cast<uint8_t>(cgbColor & 0x8000 ? 0x00 : 0xFF),
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns this RGBA as a 32-bit number that can be printed in hex (`%08x`) to yield its CSS
|
||||
* representation
|
||||
*/
|
||||
// Returns this RGBA as a 32-bit number that can be printed in hex (`%08x`) to yield its CSS
|
||||
// representation
|
||||
uint32_t toCSS() const {
|
||||
auto shl = [](uint8_t val, unsigned shift) { return static_cast<uint32_t>(val) << shift; };
|
||||
return shl(red, 24) | shl(green, 16) | shl(blue, 8) | shl(alpha, 0);
|
||||
}
|
||||
friend bool operator==(Rgba const &lhs, Rgba const &rhs) { return lhs.toCSS() == rhs.toCSS(); }
|
||||
friend bool operator!=(Rgba const &lhs, Rgba const &rhs) { return lhs.toCSS() != rhs.toCSS(); }
|
||||
bool operator==(Rgba const &rhs) const { return toCSS() == rhs.toCSS(); }
|
||||
bool operator!=(Rgba const &rhs) const { return toCSS() != rhs.toCSS(); }
|
||||
|
||||
/*
|
||||
* CGB colors are RGB555, so we use bit 15 to signify that the color is transparent instead
|
||||
* Since the rest of the bits don't matter then, we return 0x8000 exactly.
|
||||
*/
|
||||
// CGB colors are RGB555, so we use bit 15 to signify that the color is transparent instead
|
||||
// Since the rest of the bits don't matter then, we return 0x8000 exactly.
|
||||
static constexpr uint16_t transparent = 0b1'00000'00000'00000;
|
||||
|
||||
static constexpr uint8_t transparency_threshold = 0x10;
|
||||
bool isTransparent() const { return alpha < transparency_threshold; }
|
||||
static constexpr uint8_t opacity_threshold = 0xF0;
|
||||
bool isOpaque() const { return alpha >= opacity_threshold; }
|
||||
/*
|
||||
* Computes the equivalent CGB color, respects the color curve depending on options
|
||||
*/
|
||||
// Computes the equivalent CGB color, respects the color curve depending on options
|
||||
uint16_t cgbColor() const;
|
||||
|
||||
bool isGray() const { return red == green && green == blue; }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_HELPERS_HPP
|
||||
#define RGBDS_HELPERS_HPP
|
||||
@@ -14,7 +14,8 @@
|
||||
#else
|
||||
// This seems to generate similar code to __builtin_unreachable, despite different semantics
|
||||
// Note that executing this is undefined behavior (declared [[noreturn]], but does return)
|
||||
[[noreturn]] static inline void unreachable_() {
|
||||
[[noreturn]]
|
||||
static inline void unreachable_() {
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -26,8 +27,9 @@
|
||||
// `[[gnu::assume()]]` for GCC or compatible also has insufficient support (GCC 13+ only)
|
||||
#define assume(x) \
|
||||
do { \
|
||||
if (!(x)) \
|
||||
if (!(x)) { \
|
||||
unreachable_(); \
|
||||
} \
|
||||
} while (0)
|
||||
#endif
|
||||
#else
|
||||
@@ -93,15 +95,14 @@ static inline int clz(unsigned int x) {
|
||||
#define CAT(x, y) x##y
|
||||
#define EXPAND_AND_CAT(x, y) CAT(x, y)
|
||||
|
||||
// Obtaining the size of an array; `arr` must be an expression, not a type!
|
||||
// (Having two instances of `arr` is OK because the contents of `sizeof` are not evaluated.)
|
||||
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof *(arr))
|
||||
|
||||
// For lack of <ranges>, this adds some more brevity
|
||||
#define RANGE(s) std::begin(s), std::end(s)
|
||||
|
||||
// MSVC does not inline `strlen()` or `.length()` of a constant string, so we use `sizeof`
|
||||
#define QUOTEDSTRLEN(s) (sizeof(s) - 1)
|
||||
// MSVC does not inline `strlen()` or `.length()` of a constant string
|
||||
template<int N>
|
||||
static constexpr int literal_strlen(char const (&)[N]) {
|
||||
return N - 1;
|
||||
}
|
||||
|
||||
// For ad-hoc RAII in place of a `defer` statement or cross-platform `__attribute__((cleanup))`
|
||||
template<typename T>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_ITERTOOLS_HPP
|
||||
#define RGBDS_ITERTOOLS_HPP
|
||||
@@ -6,52 +6,46 @@
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
|
||||
template<typename T>
|
||||
class EnumSeqIterator {
|
||||
T _value;
|
||||
|
||||
public:
|
||||
explicit EnumSeqIterator(T value) : _value(value) {}
|
||||
|
||||
EnumSeqIterator &operator++() {
|
||||
_value = (T)(_value + 1);
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto operator*() const { return _value; }
|
||||
|
||||
friend auto operator==(EnumSeqIterator const &lhs, EnumSeqIterator const &rhs) {
|
||||
return lhs._value == rhs._value;
|
||||
}
|
||||
|
||||
friend auto operator!=(EnumSeqIterator const &lhs, EnumSeqIterator const &rhs) {
|
||||
return lhs._value != rhs._value;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
class EnumSeq {
|
||||
T _start;
|
||||
T _stop;
|
||||
|
||||
class Iterator {
|
||||
T _value;
|
||||
|
||||
public:
|
||||
explicit Iterator(T value) : _value(value) {}
|
||||
|
||||
Iterator &operator++() {
|
||||
_value = static_cast<T>(_value + 1);
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto operator*() const { return _value; }
|
||||
|
||||
bool operator==(Iterator const &rhs) const { return _value == rhs._value; }
|
||||
bool operator!=(Iterator const &rhs) const { return _value != rhs._value; }
|
||||
};
|
||||
|
||||
public:
|
||||
explicit EnumSeq(T stop) : _start((T)0), _stop(stop) {}
|
||||
explicit EnumSeq(T stop) : _start(static_cast<T>(0)), _stop(stop) {}
|
||||
explicit EnumSeq(T start, T stop) : _start(start), _stop(stop) {}
|
||||
|
||||
EnumSeqIterator<T> begin() { return EnumSeqIterator(_start); }
|
||||
EnumSeqIterator<T> end() { return EnumSeqIterator(_stop); }
|
||||
Iterator begin() { return Iterator(_start); }
|
||||
Iterator end() { return Iterator(_stop); }
|
||||
};
|
||||
|
||||
// This is not a fully generic implementation; its current use cases only require for-loop behavior.
|
||||
// We also assume that all iterators have the same length.
|
||||
template<typename... Iters>
|
||||
class Zip {
|
||||
std::tuple<Iters...> _iters;
|
||||
template<typename... Ts>
|
||||
class ZipIterator {
|
||||
std::tuple<Ts...> _iters;
|
||||
|
||||
public:
|
||||
explicit Zip(std::tuple<Iters...> &&iters) : _iters(iters) {}
|
||||
explicit ZipIterator(std::tuple<Ts...> &&iters) : _iters(iters) {}
|
||||
|
||||
Zip &operator++() {
|
||||
ZipIterator &operator++() {
|
||||
std::apply([](auto &&...it) { (++it, ...); }, _iters);
|
||||
return *this;
|
||||
}
|
||||
@@ -62,26 +56,24 @@ public:
|
||||
);
|
||||
}
|
||||
|
||||
friend auto operator==(Zip const &lhs, Zip const &rhs) {
|
||||
return std::get<0>(lhs._iters) == std::get<0>(rhs._iters);
|
||||
bool operator==(ZipIterator const &rhs) const {
|
||||
return std::get<0>(_iters) == std::get<0>(rhs._iters);
|
||||
}
|
||||
|
||||
friend auto operator!=(Zip const &lhs, Zip const &rhs) {
|
||||
return std::get<0>(lhs._iters) != std::get<0>(rhs._iters);
|
||||
bool operator!=(ZipIterator const &rhs) const {
|
||||
return std::get<0>(_iters) != std::get<0>(rhs._iters);
|
||||
}
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
template<typename... Containers>
|
||||
template<typename... Ts>
|
||||
class ZipContainer {
|
||||
std::tuple<Containers...> _containers;
|
||||
std::tuple<Ts...> _containers;
|
||||
|
||||
public:
|
||||
explicit ZipContainer(Containers &&...containers)
|
||||
: _containers(std::forward<Containers>(containers)...) {}
|
||||
explicit ZipContainer(Ts &&...containers) : _containers(std::forward<Ts>(containers)...) {}
|
||||
|
||||
auto begin() {
|
||||
return Zip(std::apply(
|
||||
return ZipIterator(std::apply(
|
||||
[](auto &&...containers) {
|
||||
using std::begin;
|
||||
return std::make_tuple(begin(containers)...);
|
||||
@@ -91,7 +83,7 @@ public:
|
||||
}
|
||||
|
||||
auto end() {
|
||||
return Zip(std::apply(
|
||||
return ZipIterator(std::apply(
|
||||
[](auto &&...containers) {
|
||||
using std::end;
|
||||
return std::make_tuple(end(containers)...);
|
||||
@@ -105,12 +97,11 @@ public:
|
||||
template<typename T>
|
||||
using Holder = std::
|
||||
conditional_t<std::is_lvalue_reference_v<T>, T, std::remove_cv_t<std::remove_reference_t<T>>>;
|
||||
} // namespace detail
|
||||
|
||||
// Does the same number of iterations as the first container's iterator!
|
||||
template<typename... Containers>
|
||||
static constexpr auto zip(Containers &&...cs) {
|
||||
return detail::ZipContainer<detail::Holder<Containers>...>(std::forward<Containers>(cs)...);
|
||||
template<typename... Ts>
|
||||
static constexpr auto zip(Ts &&...cs) {
|
||||
return ZipContainer<Holder<Ts>...>(std::forward<Ts>(cs)...);
|
||||
}
|
||||
|
||||
#endif // RGBDS_ITERTOOLS_HPP
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_LINK_ASSIGN_HPP
|
||||
#define RGBDS_LINK_ASSIGN_HPP
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_LINK_MAIN_HPP
|
||||
#define RGBDS_LINK_MAIN_HPP
|
||||
@@ -32,8 +32,9 @@ extern bool disablePadding;
|
||||
// Helper macro for printing verbose-mode messages
|
||||
#define verbosePrint(...) \
|
||||
do { \
|
||||
if (beVerbose) \
|
||||
if (beVerbose) { \
|
||||
fprintf(stderr, __VA_ARGS__); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
struct FileStackNode {
|
||||
@@ -58,11 +59,11 @@ struct FileStackNode {
|
||||
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, ...);
|
||||
[[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
|
||||
|
||||
@@ -1,19 +1,12 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_LINK_OBJECT_HPP
|
||||
#define RGBDS_LINK_OBJECT_HPP
|
||||
|
||||
/*
|
||||
* Read an object (.o) file, and add its info to the data structures.
|
||||
* @param fileName A path to the object file to be read
|
||||
* @param i The ID of the file
|
||||
*/
|
||||
void obj_ReadFile(char const *fileName, unsigned int i);
|
||||
// Read an object (.o) file, and add its info to the data structures.
|
||||
void obj_ReadFile(char const *fileName, unsigned int fileID);
|
||||
|
||||
/*
|
||||
* Sets up object file reading
|
||||
* @param nbFiles The number of object files that will be read
|
||||
*/
|
||||
// Sets up object file reading
|
||||
void obj_Setup(unsigned int nbFiles);
|
||||
|
||||
#endif // RGBDS_LINK_OBJECT_HPP
|
||||
|
||||
@@ -1,26 +1,17 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_LINK_OUTPUT_HPP
|
||||
#define RGBDS_LINK_OUTPUT_HPP
|
||||
|
||||
struct Section;
|
||||
|
||||
/*
|
||||
* Registers a section for output.
|
||||
* @param section The section to add
|
||||
*/
|
||||
// Registers a section for output.
|
||||
void out_AddSection(Section const §ion);
|
||||
|
||||
/*
|
||||
* Finds an assigned section overlapping another one.
|
||||
* @param section The section that is being overlapped
|
||||
* @return A section overlapping it
|
||||
*/
|
||||
// Finds an assigned section overlapping another one.
|
||||
Section const *out_OverlappingSection(Section const §ion);
|
||||
|
||||
/*
|
||||
* Writes all output (bin, sym, map) files.
|
||||
*/
|
||||
// Writes all output (bin, sym, map) files.
|
||||
void out_WriteFiles();
|
||||
|
||||
#endif // RGBDS_LINK_OUTPUT_HPP
|
||||
|
||||
@@ -1,17 +1,12 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_LINK_PATCH_HPP
|
||||
#define RGBDS_LINK_PATCH_HPP
|
||||
|
||||
/*
|
||||
* Checks all assertions
|
||||
* @return true if assertion failed
|
||||
*/
|
||||
// Checks all assertions
|
||||
void patch_CheckAssertions();
|
||||
|
||||
/*
|
||||
* Applies all SECTIONs' patches to them
|
||||
*/
|
||||
// Applies all SECTIONs' patches to them
|
||||
void patch_ApplyPatches();
|
||||
|
||||
#endif // RGBDS_LINK_PATCH_HPP
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_LINK_SDAS_OBJ_HPP
|
||||
#define RGBDS_LINK_SDAS_OBJ_HPP
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_LINK_SECTION_HPP
|
||||
#define RGBDS_LINK_SECTION_HPP
|
||||
@@ -65,29 +65,17 @@ struct Assertion {
|
||||
|
||||
extern std::deque<Assertion> assertions;
|
||||
|
||||
/*
|
||||
* Execute a callback for each section currently registered.
|
||||
* This is to avoid exposing the data structure in which sections are stored.
|
||||
* @param callback The function to call for each structure.
|
||||
*/
|
||||
// Execute a callback for each section currently registered.
|
||||
// This is to avoid exposing the data structure in which sections are stored.
|
||||
void sect_ForEach(void (*callback)(Section &));
|
||||
|
||||
/*
|
||||
* Registers a section to be processed.
|
||||
* @param section The section to register.
|
||||
*/
|
||||
// Registers a section to be processed.
|
||||
void sect_AddSection(std::unique_ptr<Section> &§ion);
|
||||
|
||||
/*
|
||||
* Finds a section by its name.
|
||||
* @param name The name of the section to look for
|
||||
* @return A pointer to the section, or `nullptr` if it wasn't found
|
||||
*/
|
||||
// Finds a section by its name.
|
||||
Section *sect_GetSection(std::string const &name);
|
||||
|
||||
/*
|
||||
* Checks if all sections meet reasonable criteria, such as max size
|
||||
*/
|
||||
// Checks if all sections meet reasonable criteria, such as max size
|
||||
void sect_DoSanityChecks();
|
||||
|
||||
#endif // RGBDS_LINK_SECTION_HPP
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_LINK_SYMBOL_HPP
|
||||
#define RGBDS_LINK_SYMBOL_HPP
|
||||
@@ -41,11 +41,7 @@ void sym_ForEach(void (*callback)(Symbol &));
|
||||
|
||||
void sym_AddSymbol(Symbol &symbol);
|
||||
|
||||
/*
|
||||
* Finds a symbol in all the defined symbols.
|
||||
* @param name The name of the symbol to look for
|
||||
* @return A pointer to the symbol, or `nullptr` if not found.
|
||||
*/
|
||||
// Finds a symbol in all the defined symbols.
|
||||
Symbol *sym_GetSymbol(std::string const &name);
|
||||
|
||||
void sym_DumpLocalAliasedSymbols(std::string const &name);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_LINKDEFS_HPP
|
||||
#define RGBDS_LINKDEFS_HPP
|
||||
@@ -92,29 +92,19 @@ extern struct SectionTypeInfo {
|
||||
uint32_t lastBank;
|
||||
} sectionTypeInfo[SECTTYPE_INVALID];
|
||||
|
||||
/*
|
||||
* Tells whether a section has data in its object file definition,
|
||||
* depending on type.
|
||||
* @param type The section's type
|
||||
* @return `true` if the section's definition includes data
|
||||
*/
|
||||
// Tells whether a section has data in its object file definition,
|
||||
// depending on type.
|
||||
static inline bool sect_HasData(SectionType type) {
|
||||
assume(type != SECTTYPE_INVALID);
|
||||
return type == SECTTYPE_ROM0 || type == SECTTYPE_ROMX;
|
||||
}
|
||||
|
||||
/*
|
||||
* Computes a memory region's end address (last byte), eg. 0x7FFF
|
||||
* @return The address of the last byte in that memory region
|
||||
*/
|
||||
// Returns a memory region's end address (last byte), e.g. 0x7FFF
|
||||
static inline uint16_t endaddr(SectionType type) {
|
||||
return sectionTypeInfo[type].startAddr + sectionTypeInfo[type].size - 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Computes a memory region's number of banks
|
||||
* @return The number of banks, 1 for regions without banking
|
||||
*/
|
||||
// Returns a memory region's number of banks, or 1 for regions without banking
|
||||
static inline uint32_t nbbanks(SectionType type) {
|
||||
return sectionTypeInfo[type].lastBank - sectionTypeInfo[type].firstBank + 1;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_OP_MATH_HPP
|
||||
#define RGBDS_OP_MATH_HPP
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// platform-specific hacks
|
||||
|
||||
@@ -56,4 +56,9 @@
|
||||
#define setmode(fd, mode) (0)
|
||||
#endif
|
||||
|
||||
// MingGW and Cygwin need POSIX functions which are not standard C explicitly enabled
|
||||
#if defined(__MINGW32__) || defined(__CYGWIN__)
|
||||
#define _POSIX_C_SOURCE 200809L
|
||||
#endif
|
||||
|
||||
#endif // RGBDS_PLATFORM_HPP
|
||||
|
||||
@@ -1,17 +1,8 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_UTIL_HPP
|
||||
#define RGBDS_UTIL_HPP
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <vector>
|
||||
|
||||
char const *printChar(int c);
|
||||
|
||||
/*
|
||||
* @return The number of bytes read, or 0 if invalid data was found
|
||||
*/
|
||||
size_t readUTF8Char(std::vector<int32_t> *dest, char const *src);
|
||||
|
||||
#endif // RGBDS_UTIL_HPP
|
||||
|
||||
@@ -1,16 +1,12 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_VERSION_HPP
|
||||
#define RGBDS_VERSION_HPP
|
||||
|
||||
extern "C" {
|
||||
|
||||
#define PACKAGE_VERSION_MAJOR 0
|
||||
#define PACKAGE_VERSION_MINOR 9
|
||||
#define PACKAGE_VERSION_PATCH 0
|
||||
#define PACKAGE_VERSION_RC 1
|
||||
#define PACKAGE_VERSION_PATCH 1
|
||||
|
||||
char const *get_package_version_string();
|
||||
}
|
||||
|
||||
#endif // RGBDS_VERSION_H
|
||||
|
||||
609
man/gbz80.7
609
man/gbz80.7
File diff suppressed because it is too large
Load Diff
445
man/rgbasm-old.5
Normal file
445
man/rgbasm-old.5
Normal file
@@ -0,0 +1,445 @@
|
||||
'\" e
|
||||
.\"
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd February 2, 2025
|
||||
.Dt RGBASM-OLD 5
|
||||
.Os
|
||||
.Sh NAME
|
||||
.Nm rgbasm-old
|
||||
.Nd obsolete language documentation
|
||||
.Sh DESCRIPTION
|
||||
This is the list of features that have been removed from the
|
||||
.Xr rgbasm 5
|
||||
assembly language over its decades of evolution, along with their modern alternatives.
|
||||
Its goal is to be a reference for backwards incompatibility, when upgrading an old assembly codebase to work with the latest RGBDS release.
|
||||
It does
|
||||
.Em not
|
||||
attempt to list every syntax bug that was ever fixed (with some notable exceptions), nor new reserved keywords that may conflict with old identifiers.
|
||||
.Sh REMOVED
|
||||
These are features which have been completely removed, without any direct alternatives.
|
||||
Usually these features were limiting the addition of other features, or had awkward limits on their own intended effects.
|
||||
.Ss Automatic LD to LDH conversion (rgbasm -l)
|
||||
Deprecated in 0.7.0, removed in 0.8.0.
|
||||
.Pp
|
||||
.Xr rgbasm 1
|
||||
used to automatically treat
|
||||
.Ql LD
|
||||
as
|
||||
.Ql LDH
|
||||
if the address was known to be in the
|
||||
.Ad $FF00-$FFFF
|
||||
range, with the
|
||||
.Fl L
|
||||
flag to opt out.
|
||||
.Xr rgbasm 1
|
||||
0.6.0 added a
|
||||
.Fl l
|
||||
flag to opt in instead.
|
||||
.Pp
|
||||
Instead, use
|
||||
.Ql LDH ,
|
||||
and remove the
|
||||
.Fl L
|
||||
and
|
||||
.Fl l
|
||||
flags from
|
||||
.Xr rgbasm 1 .
|
||||
.Ss Automatic NOP after HALT (rgbasm -H)
|
||||
Deprecated in 0.7.0, removed in 0.8.0.
|
||||
.Pp
|
||||
.Xr rgbasm 1
|
||||
used to automatically insert a
|
||||
.Ql NOP
|
||||
after
|
||||
.Ql HALT ,
|
||||
with the
|
||||
.Fl h
|
||||
flag to opt out.
|
||||
.Xr rgbasm 1
|
||||
0.6.0 added a
|
||||
.Fl H
|
||||
flag to opt in instead.
|
||||
.Pp
|
||||
Instead, use an explicit
|
||||
.Ql NOP
|
||||
after
|
||||
.Ql HALT ,
|
||||
and remove the
|
||||
.Fl h
|
||||
and
|
||||
.Fl H
|
||||
flags from
|
||||
.Xr rgbasm 1 .
|
||||
.Ss Nested macro definitions
|
||||
Removed in 0.4.2.
|
||||
.Pp
|
||||
Instead, put the nested macro definition inside a quoted string (making sure that none of its lines start with
|
||||
.Ic ENDM ) ,
|
||||
then interpolate that string.
|
||||
For example:
|
||||
.Bd -literal -offset indent
|
||||
MACRO outer
|
||||
DEF definition EQUS """
|
||||
MACRO inner
|
||||
println (\e1) - (\e\e1)
|
||||
\enENDM"""
|
||||
{definition}
|
||||
PURGE definition
|
||||
ENDM
|
||||
outer 10
|
||||
inner 3 ; prints 7
|
||||
.Ed
|
||||
.Ss Negative DS
|
||||
Removed in 0.3.2.
|
||||
.Pp
|
||||
This was used to "rewind" the value of
|
||||
.Ic @
|
||||
in RAM sections, allowing labeled space allocations to overlap.
|
||||
.Pp
|
||||
Instead, use
|
||||
.Ic UNION .
|
||||
.Ss __FILE__ and __LINE__
|
||||
Deprecated in 0.6.0, removed in 0.7.0.
|
||||
.Pp
|
||||
Instead, use
|
||||
.Ic WARN
|
||||
or
|
||||
.Ic FAIL
|
||||
to print a complete trace of filenames and line numbers.
|
||||
.Ss _PI
|
||||
Deprecated in 0.5.0, removed in 0.6.0.
|
||||
.Pp
|
||||
Instead, use
|
||||
.Ql 3.141592653 .
|
||||
.Ss Treating multi-character strings as numbers
|
||||
Deprecated in 0.9.0.
|
||||
.Pp
|
||||
Instead, use a multi-value
|
||||
.Ic CHARMAP ,
|
||||
or explicitly combine the values of individual characters.
|
||||
.Ss rgbgfx -f/--fix and -F/--fix-and-save
|
||||
Removed in 0.6.0.
|
||||
.Pp
|
||||
Instead, use
|
||||
.Ql rgbgfx -c/--colors
|
||||
to explicitly specify a color palette.
|
||||
If using
|
||||
.Ql -c embedded ,
|
||||
arrange the PNG's indexed palette in a separate graphics editor.
|
||||
.Ss rgbgfx -D/--debug
|
||||
Removed in 0.6.0.
|
||||
.Sh REPLACED
|
||||
These are features whose syntax has been changed without affecting functionality.
|
||||
They can generally be updated with a single search-and-replace.
|
||||
.Ss Defining constants and variables without DEF
|
||||
Deprecated in 0.7.0, removed in 0.8.0.
|
||||
.Pp
|
||||
.Ic EQU , EQUS , = , RB , RW ,
|
||||
and
|
||||
.Ic RL
|
||||
definitions used to just start with the symbol name, but had to be typed in column 1.
|
||||
.Pp
|
||||
Instead, use
|
||||
.Ic DEF
|
||||
before constant and variable definitions.
|
||||
Note that
|
||||
.Ic EQUS
|
||||
expansion does not occur for the symbol name, so you have to use explicit
|
||||
.Ql {interpolation} .
|
||||
.Ss Defining macros like labels
|
||||
Deprecated in 0.6.0, removed in 0.7.0.
|
||||
.Pp
|
||||
Macros used to be defined as
|
||||
.Ql name: MACRO .
|
||||
.Pp
|
||||
Instead, use
|
||||
.Ql MACRO name .
|
||||
Note that
|
||||
.Ic EQUS
|
||||
expansion does not occur for the macro name, so you have to use explicit
|
||||
.Ql {interpolation} .
|
||||
.Ss Defining variables with SET
|
||||
Deprecated in 0.5.2, removed in 0.6.0.
|
||||
.Pp
|
||||
Variables used to be defined as
|
||||
.Ql name SET value .
|
||||
.Pp
|
||||
Instead, use
|
||||
.Ql DEF name = value .
|
||||
.Ss Global labels without colons
|
||||
Deprecated in 0.4.0, removed in 0.5.0.
|
||||
.Pp
|
||||
Labels used to be definable with just a name, but had to be typed in column 1.
|
||||
.Pp
|
||||
Instead, use explicit colons; for example,
|
||||
.Ql Label:
|
||||
or exported
|
||||
.Ql Label:: .
|
||||
.Ss '\e,' in strings within macro arguments
|
||||
Deprecated in 0.5.0, removed in 0.7.0.
|
||||
.Pp
|
||||
Macro arguments now handle quoted strings and parenthesized expressions as single arguments, so commas inside them are not argument separators and do not need escaping.
|
||||
.Pp
|
||||
Instead, just use commas without backslashes.
|
||||
.Ss '*' comments
|
||||
Deprecated in 0.4.1, removed in 0.5.0.
|
||||
.Pp
|
||||
These comments had to have the
|
||||
.Ql *
|
||||
typed in column 1.
|
||||
.Pp
|
||||
Instead, use
|
||||
.Ql \&;
|
||||
comments.
|
||||
.Ss PRINTT, PRINTI, PRINTV, and PRINTF
|
||||
Deprecated in 0.5.0, removed in 0.6.0.
|
||||
.Pp
|
||||
These directives were each specific to one type of value.
|
||||
.Pp
|
||||
Instead, use
|
||||
.Ic PRINT
|
||||
and
|
||||
.Ic PRINTLN ,
|
||||
with
|
||||
.Ic STRFMT
|
||||
or
|
||||
.Ql {interpolation}
|
||||
for type-specific formatting.
|
||||
.Ss IMPORT and XREF
|
||||
Removed in 0.4.0.
|
||||
.Pp
|
||||
Symbols are now automatically resolved if they were exported from elsewhere.
|
||||
.Pp
|
||||
Instead, just remove these directives.
|
||||
.Ss GLOBAL and XDEF
|
||||
Deprecated in 0.4.2, removed in 0.5.0.
|
||||
.Pp
|
||||
Instead, use
|
||||
.Ic EXPORT .
|
||||
.Ss HOME, CODE, DATA, and BSS
|
||||
Deprecated in 0.3.0, removed in 0.4.0.
|
||||
.Pp
|
||||
Instead of
|
||||
.Ic HOME ,
|
||||
use
|
||||
.Ic ROM0 ;
|
||||
instead of
|
||||
.Ic CODE
|
||||
and
|
||||
.Ic DATA ,
|
||||
use
|
||||
.Ic ROMX ;
|
||||
instead of
|
||||
.Ic BSS ,
|
||||
use
|
||||
.Ic WRAM0 .
|
||||
.Ss JP [HL]
|
||||
Deprecated in 0.3.0, removed in 0.4.0.
|
||||
.Pp
|
||||
Instead, use
|
||||
.Ql JP HL .
|
||||
.Ss LDI A, HL and LDD A, HL
|
||||
Deprecated in 0.3.0, removed in 0.4.0.
|
||||
.Pp
|
||||
Instead, use
|
||||
.Ql LDI A, [HL]
|
||||
and
|
||||
.Ql LDD A, [HL]
|
||||
(or
|
||||
.Ql LD A, [HLI]
|
||||
and
|
||||
.Ql LD A, [HLD] ;
|
||||
or
|
||||
.Ql LD A, [HL+]
|
||||
and
|
||||
.Ql LD A, [HL-] ) .
|
||||
.Ss LDIO
|
||||
Deprecated in 0.9.0.
|
||||
.Pp
|
||||
Instead, use
|
||||
.Ql LDH .
|
||||
.Ss LD [C], A and LD A, [C]
|
||||
Deprecated in 0.9.0.
|
||||
.Pp
|
||||
Instead, use
|
||||
.Ql LDH [C], A
|
||||
and
|
||||
.Ql LDH A, [C] .
|
||||
.Pp
|
||||
Note that
|
||||
.Ql LD [$FF00+C], A
|
||||
and
|
||||
.Ql LD A, [$FF00+C]
|
||||
were also deprecated in 0.9.0, but were
|
||||
.Em undeprecated
|
||||
in 0.9.1.
|
||||
.Ss LDH [n8], A and LDH A, [n8]
|
||||
Deprecated in 0.9.0.
|
||||
.Pp
|
||||
.Ql LDH
|
||||
used to treat "addresses" from
|
||||
.Ad $00
|
||||
to
|
||||
.Ad $FF
|
||||
as if they were the low byte of an address from
|
||||
.Ad $FF00
|
||||
to
|
||||
.Ad $FFFF .
|
||||
.Pp
|
||||
Instead, use
|
||||
.Ql LDH [n16], A
|
||||
and
|
||||
.Ql LDH A, [n16] .
|
||||
.Ss LD HL, [SP + e8]
|
||||
Deprecated in 0.3.0, removed in 0.4.0.
|
||||
.Pp
|
||||
Instead, use
|
||||
.Ql LD HL, SP + e8 .
|
||||
.Ss LDHL SP, e8
|
||||
Supported in ASMotor, removed in RGBDS.
|
||||
.Pp
|
||||
Instead, use
|
||||
.Ql LD HL, SP + e8 .
|
||||
.Ss rgbasm -i
|
||||
Deprecated in 0.6.0, removed in 0.8.0.
|
||||
.Pp
|
||||
Instead, use
|
||||
.Fl I
|
||||
or
|
||||
.Fl -include .
|
||||
.Ss rgbgfx -h
|
||||
Removed in 0.6.0.
|
||||
.Pp
|
||||
Instead, use
|
||||
.Fl Z
|
||||
or
|
||||
.Fl -columns .
|
||||
.Ss rgbgfx --output-*
|
||||
Deprecated in 0.7.0, removed in 0.8.0.
|
||||
.Pp
|
||||
Instead, use
|
||||
.Fl -auto-* .
|
||||
.Sh CHANGED
|
||||
These are breaking changes that did not alter syntax, and so could not practically be deprecated.
|
||||
.Ss Trigonometry function units
|
||||
Changed in 0.6.0.
|
||||
.Pp
|
||||
Instead of dividing a circle into 65536.0 "binary degrees", it is now divided into 1.0 "turns".
|
||||
.Pp
|
||||
For example, previously we had:
|
||||
.EQ
|
||||
delim $$
|
||||
.EN
|
||||
.Bl -bullet -offset indent
|
||||
.It
|
||||
.Ql SIN(0.25) == 0.00002 ,
|
||||
because 0.25 binary degrees = $0.25 / 65536.0$ turns = $0.000004 tau$ radians = $0.000008 pi$ radians, and $sin ( 0.000008 pi ) = 0.00002$
|
||||
.It
|
||||
.Ql SIN(16384.0) == 1.0 ,
|
||||
because 16384.0 binary degrees = $16384.0 / 65536.0$ turns = $0.25 tau$ radians = $pi / 2$ radians, and $sin ( pi / 2 ) = 1$
|
||||
.It
|
||||
.Ql ASIN(1.0) == 16384.0
|
||||
.El
|
||||
.Pp
|
||||
Instead, now we have:
|
||||
.Bl -bullet -offset indent
|
||||
.It
|
||||
.Ql SIN(0.25) == 1.0 ,
|
||||
because $0.25$ turns = $0.25 tau$ radians = $pi / 2$ radians, and $sin ( pi / 2 ) = 1$
|
||||
.It
|
||||
.Ql SIN(16384.0) == 0.0 ,
|
||||
because $16384$ turns = $16384 tau$ radians = $32768 pi$ radians, and $sin ( 32768 pi ) = 0$
|
||||
.It
|
||||
.Ql ASIN(1.0) == 0.25
|
||||
.El
|
||||
.EQ
|
||||
delim off
|
||||
.EN
|
||||
.Ss ** operator associativity
|
||||
Changed in 0.9.0.
|
||||
.Pp
|
||||
Instead of being left-associative,
|
||||
.Ql **
|
||||
is now right-associative.
|
||||
.Pp
|
||||
Previously we had
|
||||
.Ql p ** q ** r == (p ** q) ** r .
|
||||
.Pp
|
||||
Instead, now we have
|
||||
.Ql p ** q ** r == p ** (q ** r) .
|
||||
.Sh BUGS
|
||||
These are misfeatures that may have been possible by mistake.
|
||||
They do not get deprecated, just fixed.
|
||||
.Ss Space between exported labels' colons
|
||||
Fixed in 0.7.0.
|
||||
.Pp
|
||||
Labels with two colons used to ignore a space between them; for example,
|
||||
.Ql Label:\ : .
|
||||
.Pp
|
||||
Instead, use
|
||||
.Ql Label:: .
|
||||
.Ss Space between label and colon
|
||||
Fixed in 0.9.0.
|
||||
.Pp
|
||||
Space between a label and its colon(s) used to be ignored; for example,
|
||||
.Ql Label\ :
|
||||
and
|
||||
.Ql Label\ :: .
|
||||
Now they are treated as invocations of the
|
||||
.Ql Label
|
||||
macro with
|
||||
.Ql \&:
|
||||
and
|
||||
.Ql ::
|
||||
as arguments.
|
||||
.Pp
|
||||
Instead, use
|
||||
.Ql Label:
|
||||
and
|
||||
.Ql Label:: .
|
||||
.Ss ADD r16 with implicit first HL operand
|
||||
Fixed in 0.5.0.
|
||||
.Pp
|
||||
For example,
|
||||
.Ql ADD BC
|
||||
used to be treated as
|
||||
.Ql ADD HL, BC ,
|
||||
and likewise for
|
||||
.Ql DE ,
|
||||
.Ql HL ,
|
||||
and
|
||||
.Ql SP .
|
||||
.Pp
|
||||
Instead, use an explicit first
|
||||
.Ql HL
|
||||
operand.
|
||||
.Ss = instead of SET
|
||||
Fixed in 0.4.0.
|
||||
.Pp
|
||||
The
|
||||
.Ic =
|
||||
operator used to be an alias for the
|
||||
.Ic SET
|
||||
keyword, which included using
|
||||
.Ic =
|
||||
for the
|
||||
.Ic SET
|
||||
.Em instruction .
|
||||
.Pp
|
||||
Instead, just use
|
||||
.Ic SET
|
||||
for the instruction.
|
||||
.Sh SEE ALSO
|
||||
.Xr rgbasm 1 ,
|
||||
.Xr gbz80 7 ,
|
||||
.Xr rgbds 5 ,
|
||||
.Xr rgbds 7
|
||||
.Sh HISTORY
|
||||
.Xr rgbasm 1
|
||||
was originally written by
|
||||
.An Carsten S\(/orensen
|
||||
as part of the ASMotor package, and was later repackaged in RGBDS by
|
||||
.An Justin Lloyd .
|
||||
It is now maintained by a number of contributors at
|
||||
.Lk https://github.com/gbdev/rgbds .
|
||||
52
man/rgbasm.1
52
man/rgbasm.1
@@ -1,6 +1,6 @@
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd September 18, 2024
|
||||
.Dd February 2, 2025
|
||||
.Dt RGBASM 1
|
||||
.Os
|
||||
.Sh NAME
|
||||
@@ -8,7 +8,7 @@
|
||||
.Nd Game Boy assembler
|
||||
.Sh SYNOPSIS
|
||||
.Nm
|
||||
.Op Fl EVvw
|
||||
.Op Fl EhVvw
|
||||
.Op Fl b Ar chars
|
||||
.Op Fl D Ar name Ns Op = Ns Ar value
|
||||
.Op Fl g Ar chars
|
||||
@@ -67,6 +67,8 @@ Export all labels, including unreferenced and local labels.
|
||||
.It Fl g Ar chars , Fl \-gfx-chars Ar chars
|
||||
Change the four characters used for gfx constants.
|
||||
The defaults are 0123.
|
||||
.It Fl h , Fl \-help
|
||||
Print help text for the program and exit.
|
||||
.It Fl I Ar path , Fl \-include Ar path
|
||||
Add a new
|
||||
.Dq include path ;
|
||||
@@ -200,7 +202,7 @@ section for a list of warnings.
|
||||
Disable all warning output, even when turned into errors.
|
||||
.It Fl X Ar max_errors , Fl \-max-errors Ar max_errors
|
||||
If more than this number of errors (not warnings) occur, then abort the assembly process;
|
||||
.Fl X 0
|
||||
.Fl X Ar 0
|
||||
disables this behavior.
|
||||
The default is 100 if
|
||||
.Nm
|
||||
@@ -212,13 +214,19 @@ 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 into an error.
|
||||
Make the specified warning or meta warning into an error.
|
||||
A warning's name is appended
|
||||
.Pq example: Fl Werror=obsolete ,
|
||||
and this warning is implicitly enabled and turned into an error.
|
||||
This is an error if used with a meta warning, such as
|
||||
.Fl Werror=all .
|
||||
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
|
||||
@@ -240,6 +248,10 @@ Note that each of these flag also has a negation (for example,
|
||||
.Fl Wcharmap-redef
|
||||
enables the warning that
|
||||
.Fl Wno-charmap-redef
|
||||
disables; and
|
||||
.Fl Wall
|
||||
enables every warning that
|
||||
.Fl Wno-all
|
||||
disables).
|
||||
Only the non-default flag is listed here.
|
||||
Ignoring the
|
||||
@@ -291,6 +303,13 @@ This warning is enabled by
|
||||
Warn when shifting macro arguments past their limits.
|
||||
This warning is enabled by
|
||||
.Fl Wextra .
|
||||
.It Fl Wno-nested-comment
|
||||
Warn when the block comment start sequence
|
||||
.Ql /*
|
||||
is found inside of a block comment.
|
||||
Block comments cannot be nested, so the first
|
||||
.Ql */
|
||||
will end the whole comment.
|
||||
.It Fl Wno-obsolete
|
||||
Warn when obsolete constructs such as the
|
||||
.Ic _PI
|
||||
@@ -344,7 +363,7 @@ also warns when an N-bit value is less than -2**(N-1), which will not fit in two
|
||||
Warn when a character goes through charmap conversion but has no defined mapping.
|
||||
.Fl Wunmapped-char=0
|
||||
or
|
||||
.Fl Wunmapped-char
|
||||
.Fl Wno-unmapped-char
|
||||
disables this warning.
|
||||
.Fl Wunmapped-char=1
|
||||
or just
|
||||
@@ -353,6 +372,24 @@ only warns if the active charmap is not empty.
|
||||
.Fl Wunmapped-char=2
|
||||
warns if the active charmap is empty, and/or is not the default charmap
|
||||
.Sq main .
|
||||
.It Fl Wunmatched-directive
|
||||
Warn when a
|
||||
.Ic PUSHC , PUSHO ,
|
||||
or
|
||||
.Ic PUSHS
|
||||
directive does not have a corresponding
|
||||
.Ic POPC , POPO ,
|
||||
or
|
||||
.Ic POPS .
|
||||
This warning is enabled by
|
||||
.Fl Wextra .
|
||||
.It Fl Wunterminated-load
|
||||
Warn when a
|
||||
.Ic LOAD
|
||||
block is not terminated by an
|
||||
.Ic ENDL .
|
||||
This warning is enabled by
|
||||
.Fl Wextra .
|
||||
.It Fl Wno-user
|
||||
Warn when the
|
||||
.Ic WARN
|
||||
@@ -392,6 +429,7 @@ Please report bugs on
|
||||
.Xr rgbfix 1 ,
|
||||
.Xr rgbgfx 1 ,
|
||||
.Xr gbz80 7 ,
|
||||
.Xr rgbasm-old 5 ,
|
||||
.Xr rgbds 5 ,
|
||||
.Xr rgbds 7
|
||||
.Sh HISTORY
|
||||
|
||||
244
man/rgbasm.5
244
man/rgbasm.5
@@ -2,7 +2,7 @@
|
||||
.\"
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd September 18, 2024
|
||||
.Dd February 2, 2025
|
||||
.Dt RGBASM 5
|
||||
.Os
|
||||
.Sh NAME
|
||||
@@ -41,7 +41,6 @@ or
|
||||
Labels tie a name to a specific location within a section (see
|
||||
.Sx Labels
|
||||
below).
|
||||
They must come first in the line.
|
||||
.Pp
|
||||
Instructions are assembled into Game Boy opcodes.
|
||||
Multiple instructions on one line can be separated by double colons
|
||||
@@ -254,7 +253,7 @@ Although, for these examples,
|
||||
.Ic STRFMT
|
||||
would be more appropriate; see
|
||||
.Sx String expressions
|
||||
further below.
|
||||
below.
|
||||
.Sh EXPRESSIONS
|
||||
An expression can be composed of many things.
|
||||
Numeric expressions are always evaluated using signed 32-bit math.
|
||||
@@ -268,21 +267,21 @@ This is generally always the case, unless a label is involved, as explained in t
|
||||
section.
|
||||
However, some operators can be constant even with non-constant operands, as explained in
|
||||
.Sx Operators
|
||||
further below.
|
||||
below.
|
||||
.Pp
|
||||
The instructions in the macro-language generally require constant expressions.
|
||||
.Ss Numeric formats
|
||||
There are a number of numeric formats.
|
||||
.Bl -column -offset indent "Precise fixed-point" "Prefix"
|
||||
.It Sy Format type Ta Sy Prefix Ta Sy Accepted characters
|
||||
.It Hexadecimal Ta $ Ta 0123456789ABCDEF
|
||||
.Bl -column -offset indent "Precise fixed-point" "Possible prefixes"
|
||||
.It Sy Format type Ta Sy Possible prefixes Ta Sy Accepted characters
|
||||
.It Decimal Ta none Ta 0123456789
|
||||
.It Octal Ta & Ta 01234567
|
||||
.It Binary Ta % Ta 01
|
||||
.It Hexadecimal Ta Li $ , 0x , 0X Ta 0123456789ABCDEF
|
||||
.It Octal Ta Li & , 0o , 0O Ta 01234567
|
||||
.It Binary Ta Li % , 0b , 0B Ta 01
|
||||
.It Fixed-point Ta none Ta 01234.56789
|
||||
.It Precise fixed-point Ta none Ta 12.34q8
|
||||
.It Character constant Ta none Ta \(dqABYZ\(dq
|
||||
.It Game Boy graphics Ta \` Ta 0123
|
||||
.It Game Boy graphics Ta Li \` Ta 0123
|
||||
.El
|
||||
.Pp
|
||||
Underscores are also accepted in numbers, except at the beginning of one.
|
||||
@@ -310,26 +309,35 @@ is equivalent to
|
||||
.Pp
|
||||
You can also use symbols, which are implicitly replaced with their value.
|
||||
.Ss Operators
|
||||
A great number of operators you can use in expressions are available (listed from highest to lowest precedence):
|
||||
You can use these operators in numeric expressions (listed from highest to lowest precedence):
|
||||
.Bl -column -offset indent "!= == <= >= < >"
|
||||
.It Sy Operator Ta Sy Meaning
|
||||
.It Li \&( \&) Ta Precedence override
|
||||
.It Li \&( \&) Ta Grouping
|
||||
.It Li FUNC() Ta Built-in function call
|
||||
.It Li ** Ta Exponent
|
||||
.It Li ~ + - Ta Unary complement/plus/minus
|
||||
.It Li * / % Ta Multiply/divide/modulo
|
||||
.It Li << Ta Shift left
|
||||
.It Li >> Ta Signed shift right (sign-extension)
|
||||
.It Li >>> Ta Unsigned shift right (zero-extension)
|
||||
.It Li & \&| ^ Ta Binary and/or/xor
|
||||
.It Li + - Ta Add/subtract
|
||||
.It Li != == <= >= < > Ta Comparison
|
||||
.It Li && || Ta Boolean and/or
|
||||
.It Li \&! Ta Unary not
|
||||
.It Li ** Ta Exponentiation
|
||||
.It Li + - ~ \&! Ta Unary plus, minus (negation), complement (bitwise negation), and Boolean negation
|
||||
.It Li * / % Ta Multiplication, division, and modulo (remainder)
|
||||
.It Li << >> >>> Ta Bit shifts (left, sign-extended right, zero-extended right)
|
||||
.It Li & \&| ^ Ta Bitwise AND/OR/XOR
|
||||
.It Li + - Ta Addition and subtraction
|
||||
.It Li == != < > <= >= Ta Comparisons
|
||||
.It Li && Ta Boolean AND
|
||||
.It Li || Ta Boolean OR
|
||||
.El
|
||||
.Pp
|
||||
.Sq **
|
||||
raises a number to a non-negative power. It is the only
|
||||
.Em right-associative
|
||||
operator, meaning that
|
||||
.Ql p ** q ** r
|
||||
is equal to
|
||||
.Ql p ** (q ** r) ,
|
||||
not
|
||||
.Ql (p ** q) ** r .
|
||||
All other binary operators are left-associative.
|
||||
.Pp
|
||||
.Sq ~
|
||||
complements a value by inverting all its bits.
|
||||
complements a value by inverting all 32 of its bits.
|
||||
.Pp
|
||||
.Sq %
|
||||
is used to get the remainder of the corresponding division, so that
|
||||
@@ -379,8 +387,8 @@ Even a non-constant operand with any non-zero bits will return 0.
|
||||
Besides operators, there are also some functions which have more specialized uses.
|
||||
.Bl -column "BITWIDTH(n)"
|
||||
.It Sy Name Ta Sy Operation
|
||||
.It Fn HIGH n Ta Equivalent to Ql Ar n No & $FF .
|
||||
.It Fn LOW n Ta Equivalent to Ql Po Ns Ar n No & $FF00 Pc >> 8 .
|
||||
.It Fn HIGH n Ta Equivalent to Ql Po Ns Ar n No & $FF00 Pc >> 8 .
|
||||
.It Fn LOW n Ta Equivalent to Ql Ar n No & $FF .
|
||||
.EQ
|
||||
delim $$
|
||||
.EN
|
||||
@@ -904,7 +912,7 @@ SECTION "LOAD example", ROMX
|
||||
CopyCode:
|
||||
ld de, RAMCode
|
||||
ld hl, RAMLocation
|
||||
ld c, RAMLocation.end - RAMLocation
|
||||
ld c, RAMCode.end - RAMCode
|
||||
\&.loop
|
||||
ld a, [de]
|
||||
inc de
|
||||
@@ -928,8 +936,8 @@ RAMLocation:
|
||||
|
||||
\&.string
|
||||
db "Hello World!\e0"
|
||||
\&.end
|
||||
ENDL
|
||||
\&.end
|
||||
.Ed
|
||||
.Pp
|
||||
A
|
||||
@@ -939,7 +947,9 @@ block feels similar to a
|
||||
declaration because it creates a new one.
|
||||
All data and code generated within such a block is placed in the current section like usual, but all labels are created as if they were placed in this newly-created section.
|
||||
.Pp
|
||||
In the example above, all of the code and data will end up in the "LOAD example" section.
|
||||
In the example above, all of the code and data will end up in the
|
||||
.Dq LOAD example
|
||||
section.
|
||||
You will notice the
|
||||
.Sq RAMCode
|
||||
and
|
||||
@@ -951,15 +961,30 @@ You cannot nest
|
||||
.Ic LOAD
|
||||
blocks, nor can you change or stop the current section within them.
|
||||
.Pp
|
||||
The current
|
||||
.Ic LOAD
|
||||
block can be ended by using
|
||||
.Ic ENDL .
|
||||
This directive is only necessary if you want to resume writing code in its containing ROM section.
|
||||
Any of
|
||||
.Ic LOAD , SECTION , ENDSECTION ,
|
||||
or
|
||||
.Ic POPS
|
||||
will end the current
|
||||
.Ic LOAD
|
||||
block before performing its own function.
|
||||
.Pp
|
||||
.Ic LOAD
|
||||
blocks can use the
|
||||
.Ic UNION
|
||||
or
|
||||
.Ic FRAGMENT
|
||||
modifiers, as described below.
|
||||
modifiers as described in
|
||||
.Sx Unionized sections
|
||||
below.
|
||||
.Ss Unionized sections
|
||||
When you're tight on RAM, you may want to define overlapping static memory allocations, as explained in the
|
||||
.Sx Unions
|
||||
.Sx Allocating overlapping spaces in RAM
|
||||
section.
|
||||
However, a
|
||||
.Ic UNION
|
||||
@@ -1000,7 +1025,7 @@ or
|
||||
.El
|
||||
.Pp
|
||||
Different declarations of the same unionized section are not appended, but instead overlaid on top of each other, just like
|
||||
.Sx Unions .
|
||||
.Sx Allocating overlapping spaces in RAM .
|
||||
Similarly, the size of an unionized section is the largest of all its declarations.
|
||||
.Ss Section fragments
|
||||
Section fragments are sections with a small twist: when several of the same name are encountered, they are concatenated instead of producing an error.
|
||||
@@ -1123,7 +1148,7 @@ otherwise, it is said to be
|
||||
.Dq exported ,
|
||||
explained in
|
||||
.Sx Exporting and importing symbols
|
||||
further below).
|
||||
below).
|
||||
More than one dot in label names is not allowed.
|
||||
.Pp
|
||||
For convenience, local labels can use a shorthand syntax: when a symbol name starting with a dot is found (for example, inside an expression, or when declaring a label), then the current
|
||||
@@ -1553,47 +1578,6 @@ environment variable if that is defined as a UNIX timestamp.
|
||||
Refer to the spec at
|
||||
.Lk https://reproducible-builds.org/docs/source-date-epoch/ reproducible-builds.org .
|
||||
.Sh DEFINING DATA
|
||||
.Ss Statically allocating space in RAM
|
||||
.Ic DS
|
||||
statically allocates a number of empty bytes.
|
||||
This is the preferred method of allocating space in a RAM section.
|
||||
You can also use
|
||||
.Ic DB , DW
|
||||
and
|
||||
.Ic DL
|
||||
without any arguments instead (see
|
||||
.Sx Defining constant data in ROM
|
||||
below).
|
||||
.Bd -literal -offset indent
|
||||
DS 42 ;\ Allocates 42 bytes
|
||||
.Ed
|
||||
.Pp
|
||||
Empty space in RAM sections will not be initialized.
|
||||
In ROM sections, it will be filled with the value passed to the
|
||||
.Fl p
|
||||
command-line option, except when using overlays with
|
||||
.Fl O .
|
||||
.Pp
|
||||
Instead of an exact number of bytes, you can specify
|
||||
.Ic ALIGN Ns Bq Ar align , offset
|
||||
to allocate however many bytes are required to align the subsequent data.
|
||||
Thus,
|
||||
.Sq Ic DS ALIGN Ns Bo Ar align , offset Bc , No ...
|
||||
is equivalent to
|
||||
.Sq Ic DS Ar n , No ...
|
||||
followed by
|
||||
.Sq Ic ALIGN Ns Bq Ar align , offset ,
|
||||
where
|
||||
.Ar n
|
||||
is the minimum value needed to satisfy the
|
||||
.Ic ALIGN
|
||||
constraint (see
|
||||
.Sx Requesting alignment
|
||||
below).
|
||||
Note that
|
||||
.Ic ALIGN Ns Bq Ar align
|
||||
is a shorthand for
|
||||
.Ic ALIGN Ns Bq Ar align , No 0 .
|
||||
.Ss Defining constant data in ROM
|
||||
.Ic DB
|
||||
defines a list of bytes that will be stored in the final image.
|
||||
@@ -1660,7 +1644,7 @@ can be used in a
|
||||
/
|
||||
.Ic SRAM
|
||||
section.
|
||||
.Ss Including binary files
|
||||
.Ss Including binary data files
|
||||
You probably have some graphics, level data, etc. you'd like to include.
|
||||
Use
|
||||
.Ic INCBIN
|
||||
@@ -1684,7 +1668,48 @@ INCBIN "data.bin", 78, 256
|
||||
.Pp
|
||||
The length argument is optional.
|
||||
If only the start position is specified, the bytes from the start position until the end of the file will be included.
|
||||
.Ss Unions
|
||||
.Ss Statically allocating space in RAM
|
||||
.Ic DS
|
||||
statically allocates a number of empty bytes.
|
||||
This is the preferred method of allocating space in a RAM section.
|
||||
You can also use
|
||||
.Ic DB , DW
|
||||
and
|
||||
.Ic DL
|
||||
without any arguments instead (see
|
||||
.Sx Defining constant data in ROM
|
||||
below).
|
||||
.Bd -literal -offset indent
|
||||
DS 42 ;\ Allocates 42 bytes
|
||||
.Ed
|
||||
.Pp
|
||||
Empty space in RAM sections will not be initialized.
|
||||
In ROM sections, it will be filled with the value passed to the
|
||||
.Fl p
|
||||
command-line option, except when using overlays with
|
||||
.Fl O .
|
||||
.Pp
|
||||
Instead of an exact number of bytes, you can specify
|
||||
.Ic ALIGN Ns Bq Ar align , offset
|
||||
to allocate however many bytes are required to align the subsequent data.
|
||||
Thus,
|
||||
.Sq Ic DS ALIGN Ns Bo Ar align , offset Bc , No ...
|
||||
is equivalent to
|
||||
.Sq Ic DS Ar n , No ...
|
||||
followed by
|
||||
.Sq Ic ALIGN Ns Bq Ar align , offset ,
|
||||
where
|
||||
.Ar n
|
||||
is the minimum value needed to satisfy the
|
||||
.Ic ALIGN
|
||||
constraint (see
|
||||
.Sx Requesting alignment
|
||||
below).
|
||||
Note that
|
||||
.Ic ALIGN Ns Bq Ar align
|
||||
is a shorthand for
|
||||
.Ic ALIGN Ns Bq Ar align , No 0 .
|
||||
.Ss Allocating overlapping spaces in RAM
|
||||
Unions allow multiple static memory allocations to overlap, like unions in C.
|
||||
This does not increase the amount of memory available, but allows re-using the same memory region for different purposes.
|
||||
.Pp
|
||||
@@ -1745,6 +1770,37 @@ Unions may be used in any section, but they may only contain space-allocating di
|
||||
.Ic DS
|
||||
(see
|
||||
.Sx Statically allocating space in RAM ) .
|
||||
.Ss Requesting alignment
|
||||
While
|
||||
.Ic ALIGN
|
||||
as presented in
|
||||
.Sx SECTIONS
|
||||
is often useful as-is, sometimes you instead want a particular piece of data (or code) in the middle of the section to be aligned.
|
||||
This is made easier through the use of mid-section
|
||||
.Ic ALIGN Ar align , offset .
|
||||
It will retroactively alter the section's attributes to ensure that the location the
|
||||
.Ic ALIGN
|
||||
directive is at, has its
|
||||
.Ar align
|
||||
lower bits equal to
|
||||
.Ar offset .
|
||||
.Pp
|
||||
If the constraint cannot be met (for example because the section is fixed at an incompatible address), an error is produced.
|
||||
Note that
|
||||
.Ic ALIGN Ar align
|
||||
is a shorthand for
|
||||
.Ic ALIGN Ar align , No 0 .
|
||||
.Pp
|
||||
There may be times when you don't just want to specify an alignment constraint at the current location, but also skip ahead until the constraint can be satisfied.
|
||||
In that case, you can use
|
||||
.Ic DS ALIGN Ns Bq Ar align , offset
|
||||
to allocate however many bytes are required to align the subsequent data.
|
||||
.Pp
|
||||
If the constraint cannot be met by skipping any amount of space, an error is produced.
|
||||
Note that
|
||||
.Ic ALIGN Ns Bq Ar align
|
||||
is a shorthand for
|
||||
.Ic ALIGN Ns Bq Ar align , No 0 .
|
||||
.Sh THE MACRO LANGUAGE
|
||||
.Ss Invoking macros
|
||||
A macro is invoked by using its name at the beginning of a line, like a directive, followed by any comma-separated arguments.
|
||||
@@ -2283,9 +2339,9 @@ POPO
|
||||
.Pp
|
||||
.Ic OPT
|
||||
can modify the options
|
||||
.Cm b , g , p , Q ,
|
||||
.Cm b , g , p , Q , r ,
|
||||
and
|
||||
.Cm r .
|
||||
.Cm W .
|
||||
.Pp
|
||||
.Ic POPO
|
||||
and
|
||||
@@ -2306,37 +2362,6 @@ PUSHO b.X, g.oOX
|
||||
DW `..ooOOXX
|
||||
POPO
|
||||
.Ed
|
||||
.Ss Requesting alignment
|
||||
While
|
||||
.Ic ALIGN
|
||||
as presented in
|
||||
.Sx SECTIONS
|
||||
is often useful as-is, sometimes you instead want a particular piece of data (or code) in the middle of the section to be aligned.
|
||||
This is made easier through the use of mid-section
|
||||
.Ic ALIGN Ar align , offset .
|
||||
It will alter the section's attributes to ensure that the location the
|
||||
.Ic ALIGN
|
||||
directive is at, has its
|
||||
.Ar align
|
||||
lower bits equal to
|
||||
.Ar offset .
|
||||
.Pp
|
||||
If the constraint cannot be met (for example because the section is fixed at an incompatible address), an error is produced.
|
||||
Note that
|
||||
.Ic ALIGN Ar align
|
||||
is a shorthand for
|
||||
.Ic ALIGN Ar align , No 0 .
|
||||
.Pp
|
||||
There may be times when you don't just want to specify an alignment constraint at the current location, but also skip ahead until the constraint can be satisfied.
|
||||
In that case, you can use
|
||||
.Ic DS ALIGN Ns Bq Ar align , offset
|
||||
to allocate however many bytes are required to align the subsequent data.
|
||||
.Pp
|
||||
If the constraint cannot be met by skipping any amount of space, an error is produced.
|
||||
Note that
|
||||
.Ic ALIGN Ns Bq Ar align
|
||||
is a shorthand for
|
||||
.Ic ALIGN Ns Bq Ar align , No 0 .
|
||||
.Sh SEE ALSO
|
||||
.Xr rgbasm 1 ,
|
||||
.Xr rgblink 1 ,
|
||||
@@ -2344,6 +2369,7 @@ is a shorthand for
|
||||
.Xr rgbfix 1 ,
|
||||
.Xr rgbgfx 1 ,
|
||||
.Xr gbz80 7 ,
|
||||
.Xr rgbasm-old 5 ,
|
||||
.Xr rgbds 5 ,
|
||||
.Xr rgbds 7
|
||||
.Sh HISTORY
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd September 18, 2024
|
||||
.Dd February 2, 2025
|
||||
.Dt RGBDS 5
|
||||
.Os
|
||||
.Sh NAME
|
||||
@@ -254,7 +254,7 @@ Size of the
|
||||
below.
|
||||
.It Cm BYTE Ar RPNExpr Ns Bq RPNSize
|
||||
The patch's value, encoded as a RPN expression
|
||||
.Pq see Sx RPN EXPRESSIONS .
|
||||
.Pq see Sx RPN expressions .
|
||||
.El
|
||||
.It Cm ENDR
|
||||
.El
|
||||
@@ -294,14 +294,14 @@ Size of the
|
||||
below.
|
||||
.It Cm BYTE Ar RPNExpr Ns Bq RPNSize
|
||||
The patch's value, encoded as a RPN expression
|
||||
.Pq see Sx RPN EXPRESSIONS .
|
||||
.Pq see Sx RPN expressions .
|
||||
.It Cm STRING Ar Message
|
||||
The message displayed if the expression evaluates to a non-zero value.
|
||||
If empty, a generic message is displayed instead.
|
||||
.El
|
||||
.It Cm ENDR
|
||||
.El
|
||||
.Ss RPN EXPRESSIONS
|
||||
.Ss RPN expressions
|
||||
Expressions in the object file are stored as RPN, or
|
||||
.Dq Reverse Polish Notation ,
|
||||
which is a notation that allows computing arbitrary expressions with just a simple stack.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd September 18, 2024
|
||||
.Dd February 2, 2025
|
||||
.Dt RGBDS 7
|
||||
.Os
|
||||
.Sh NAME
|
||||
@@ -51,6 +51,10 @@ adapts the code to be more UNIX-like and releases this version as rgbds-linux.
|
||||
forks Nossum's repository.
|
||||
The fork becomes the reference implementation of RGBDS.
|
||||
.It
|
||||
2010-09-25: S\(/orensen continues development of
|
||||
.Lk https://github.com/asmotor/asmotor ASMotor
|
||||
to this day.
|
||||
.It
|
||||
2015-01-18:
|
||||
.An stag019
|
||||
begins implementing RGBGFX, a PNG‐to‐Game Boy graphics converter, for eventual integration into RGBDS.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd September 18, 2024
|
||||
.Dd February 2, 2025
|
||||
.Dt RGBFIX 1
|
||||
.Os
|
||||
.Sh NAME
|
||||
@@ -8,7 +8,7 @@
|
||||
.Nd Game Boy header utility and checksum fixer
|
||||
.Sh SYNOPSIS
|
||||
.Nm
|
||||
.Op Fl jOsVv
|
||||
.Op Fl hjOsVv
|
||||
.Op Fl C | c
|
||||
.Op Fl f Ar fix_spec
|
||||
.Op Fl i Ar game_id
|
||||
@@ -91,6 +91,8 @@ Fix the global checksum
|
||||
.It Cm G
|
||||
Trash the global checksum.
|
||||
.El
|
||||
.It Fl h , Fl \-help
|
||||
Print help text for the program and exit.
|
||||
.It Fl i Ar game_id , Fl \-game-id Ar game_id
|
||||
Set the game ID string
|
||||
.Pq Ad 0x13F Ns \(en Ns Ad 0x142
|
||||
|
||||
19
man/rgbgfx.1
19
man/rgbgfx.1
@@ -2,7 +2,7 @@
|
||||
.\"
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd September 18, 2024
|
||||
.Dd February 2, 2025
|
||||
.Dt RGBGFX 1
|
||||
.Os
|
||||
.Sh NAME
|
||||
@@ -10,7 +10,7 @@
|
||||
.Nd Game Boy graphics converter
|
||||
.Sh SYNOPSIS
|
||||
.Nm
|
||||
.Op Fl CmOuVZ
|
||||
.Op Fl CmhOuVXYZ
|
||||
.Op Fl v Op Fl v No ...
|
||||
.Op Fl a Ar attrmap | Fl A
|
||||
.Op Fl b Ar base_ids
|
||||
@@ -165,6 +165,8 @@ for a list of formats and their descriptions.
|
||||
.It Fl d Ar depth , Fl \-depth Ar depth
|
||||
Set the bit depth of the output tile data, in bits per pixel (bpp), either 1 or 2 (the default).
|
||||
This changes how tile data is output, and the maximum number of colors per palette (2 and 4 respectively).
|
||||
.It Fl h , Fl \-help
|
||||
Print help text for the program and exit.
|
||||
.It Fl i Ar input_tiles , Fl \-input-tileset Ar input_tiles
|
||||
Use the specified input tiles in addition to having
|
||||
.Nm
|
||||
@@ -229,9 +231,8 @@ The second number pair specifies how many tiles to process horizontally and vert
|
||||
.Pp
|
||||
.Fl L Sy is ignored in reverse mode , No no padding is inserted .
|
||||
.It Fl m , Fl \-mirror-tiles
|
||||
Deduplicate tiles that are 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.
|
||||
Tiles are checked for horizontal, vertical, and horizontal-vertical mirroring.
|
||||
Useful with a tile map and attribute map together (see
|
||||
.Fl a
|
||||
and
|
||||
@@ -239,6 +240,8 @@ and
|
||||
to keep track of the duplicated tiles and the dimension(s) mirrored.
|
||||
Implies
|
||||
.Fl u .
|
||||
Equivalent to
|
||||
.Fl XY .
|
||||
.It Fl N Ar nb_tiles , Fl \-nb-tiles Ar nb_tiles
|
||||
Set a maximum number of tiles that can be placed in each VRAM bank.
|
||||
.Ar nb_tiles
|
||||
@@ -353,6 +356,10 @@ Some internal debug printing is enabled.
|
||||
The verbosity level does not go past 6.
|
||||
.Pp
|
||||
Note that verbose output is only intended to be consumed by humans, and may change without notice between RGBDS releases; relying on those for scripts is not advised.
|
||||
.It Fl X , Fl \-mirror-x
|
||||
Deduplicate tiles that are horizontally symmetrical mirror images of each other across the X axis.
|
||||
Implies
|
||||
.Fl u .
|
||||
.It Fl x Ar quantity , Fl \-trim-end Ar quantity
|
||||
Do not output the last
|
||||
.Ar quantity
|
||||
@@ -373,6 +380,10 @@ was enabled, so you probably don't want to use this option in combination with
|
||||
Note also that the tiles that don't get output will not count towards
|
||||
.Fl N Ap s
|
||||
limit.
|
||||
.It Fl Y , Fl \-mirror-y
|
||||
Deduplicate tiles that are vertically symmetrical mirror images of each other across the Y axis.
|
||||
Implies
|
||||
.Fl u .
|
||||
.It Fl Z , Fl \-columns
|
||||
Read squares from the PNG in column-major order (column by column), instead of the default row-major order (line by line).
|
||||
This primarily affects tile map and attribute map output, although it may also change generated tile data and palettes.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd September 18, 2024
|
||||
.Dd February 2, 2025
|
||||
.Dt RGBLINK 1
|
||||
.Os
|
||||
.Sh NAME
|
||||
@@ -8,7 +8,7 @@
|
||||
.Nd Game Boy linker
|
||||
.Sh SYNOPSIS
|
||||
.Nm
|
||||
.Op Fl dMtVvwx
|
||||
.Op Fl dhMtVvwx
|
||||
.Op Fl l Ar linker_script
|
||||
.Op Fl m Ar map_file
|
||||
.Op Fl n Ar sym_file
|
||||
@@ -67,6 +67,8 @@ Enable DMG mode.
|
||||
Prohibit the use of sections that doesn't exist on a DMG, such as VRAM bank 1.
|
||||
This option automatically enables
|
||||
.Fl w .
|
||||
.It Fl h , Fl \-help
|
||||
Print help text for the program and exit.
|
||||
.It Fl l Ar linker_script , Fl \-linkerscript Ar linker_script
|
||||
Specify a linker script file that tells the linker how sections must be placed in the ROM.
|
||||
The attributes assigned in the linker script must be consistent with any assigned in the code.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd September 18, 2024
|
||||
.Dd February 2, 2025
|
||||
.Dt RGBLINK 5
|
||||
.Os
|
||||
.Sh NAME
|
||||
|
||||
@@ -91,10 +91,11 @@ foreach(PROG "asm" "fix" "gfx" "link")
|
||||
${rgb${PROG}_src}
|
||||
${common_src}
|
||||
)
|
||||
if(SUFFIX)
|
||||
set_target_properties(rgb${PROG} PROPERTIES SUFFIX ${SUFFIX})
|
||||
endif()
|
||||
install(TARGETS rgb${PROG} RUNTIME DESTINATION bin)
|
||||
# Required to run tests
|
||||
set_target_properties(rgb${PROG} PROPERTIES
|
||||
# hack for MSVC: no-op generator expression to stop generation of "per-configuration subdirectory"
|
||||
RUNTIME_OUTPUT_DIRECTORY $<1:${CMAKE_SOURCE_DIR}>)
|
||||
endforeach()
|
||||
|
||||
if(LIBPNG_FOUND) # pkg-config
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "asm/charmap.hpp"
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
|
||||
#include "extern/utf8decoder.hpp"
|
||||
#include "helpers.hpp"
|
||||
#include "util.hpp"
|
||||
|
||||
@@ -45,32 +46,39 @@ bool charmap_ForEach(
|
||||
for (Charmap const &charmap : charmapList) {
|
||||
// Traverse the trie depth-first to derive the character mappings in definition order
|
||||
std::map<size_t, std::string> mappings;
|
||||
for (std::stack<std::pair<size_t, std::string>> prefixes({{0, ""}}); !prefixes.empty();) {
|
||||
// clang-format off: nested initializers
|
||||
for (std::stack<std::pair<size_t, std::string>> prefixes({{0, ""}});
|
||||
!prefixes.empty();) {
|
||||
// clang-format on
|
||||
auto [nodeIdx, mapping] = std::move(prefixes.top());
|
||||
prefixes.pop();
|
||||
CharmapNode const &node = charmap.nodes[nodeIdx];
|
||||
if (node.isTerminal())
|
||||
if (node.isTerminal()) {
|
||||
mappings[nodeIdx] = mapping;
|
||||
}
|
||||
for (unsigned c = 0; c < 256; c++) {
|
||||
if (size_t nextIdx = node.next[c]; nextIdx)
|
||||
prefixes.push({nextIdx, mapping + (char)c});
|
||||
if (size_t nextIdx = node.next[c]; nextIdx) {
|
||||
prefixes.push({nextIdx, mapping + static_cast<char>(c)});
|
||||
}
|
||||
}
|
||||
}
|
||||
mapFunc(charmap.name);
|
||||
for (auto [nodeIdx, mapping] : mappings)
|
||||
for (auto [nodeIdx, mapping] : mappings) {
|
||||
charFunc(mapping, charmap.nodes[nodeIdx].value);
|
||||
}
|
||||
}
|
||||
return !charmapList.empty();
|
||||
}
|
||||
|
||||
void charmap_New(std::string const &name, std::string const *baseName) {
|
||||
size_t baseIdx = (size_t)-1;
|
||||
size_t baseIdx = SIZE_MAX;
|
||||
|
||||
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());
|
||||
else
|
||||
} else {
|
||||
baseIdx = search->second;
|
||||
}
|
||||
}
|
||||
|
||||
if (charmapMap.find(name) != charmapMap.end()) {
|
||||
@@ -82,10 +90,11 @@ void charmap_New(std::string const &name, std::string const *baseName) {
|
||||
charmapMap[name] = charmapList.size();
|
||||
Charmap &charmap = charmapList.emplace_back();
|
||||
|
||||
if (baseIdx != (size_t)-1)
|
||||
if (baseIdx != SIZE_MAX) {
|
||||
charmap.nodes = charmapList[baseIdx].nodes; // Copies `charmapList[baseIdx].nodes`
|
||||
else
|
||||
} else {
|
||||
charmap.nodes.emplace_back(); // Zero-init the root node
|
||||
}
|
||||
|
||||
charmap.name = name;
|
||||
|
||||
@@ -93,10 +102,11 @@ void charmap_New(std::string const &name, std::string const *baseName) {
|
||||
}
|
||||
|
||||
void charmap_Set(std::string const &name) {
|
||||
if (auto search = charmapMap.find(name); search == charmapMap.end())
|
||||
if (auto search = charmapMap.find(name); search == charmapMap.end()) {
|
||||
error("Charmap '%s' doesn't exist\n", name.c_str());
|
||||
else
|
||||
} else {
|
||||
currentCharmap = &charmapList[search->second];
|
||||
}
|
||||
}
|
||||
|
||||
void charmap_Push() {
|
||||
@@ -113,6 +123,12 @@ void charmap_Pop() {
|
||||
charmapStack.pop();
|
||||
}
|
||||
|
||||
void charmap_CheckStack() {
|
||||
if (!charmapStack.empty()) {
|
||||
warning(WARNING_UNMATCHED_DIRECTIVE, "`PUSHC` without corresponding `POPC`\n");
|
||||
}
|
||||
}
|
||||
|
||||
void charmap_Add(std::string const &mapping, std::vector<int32_t> &&value) {
|
||||
if (mapping.empty()) {
|
||||
error("Cannot map an empty string\n");
|
||||
@@ -123,7 +139,7 @@ void charmap_Add(std::string const &mapping, std::vector<int32_t> &&value) {
|
||||
size_t nodeIdx = 0;
|
||||
|
||||
for (char c : mapping) {
|
||||
size_t &nextIdxRef = charmap.nodes[nodeIdx].next[(uint8_t)c];
|
||||
size_t &nextIdxRef = charmap.nodes[nodeIdx].next[static_cast<uint8_t>(c)];
|
||||
size_t nextIdx = nextIdxRef;
|
||||
|
||||
if (!nextIdx) {
|
||||
@@ -140,8 +156,9 @@ void charmap_Add(std::string const &mapping, std::vector<int32_t> &&value) {
|
||||
|
||||
CharmapNode &node = charmap.nodes[nodeIdx];
|
||||
|
||||
if (node.isTerminal())
|
||||
if (node.isTerminal()) {
|
||||
warning(WARNING_CHARMAP_REDEF, "Overriding charmap mapping\n");
|
||||
}
|
||||
|
||||
std::swap(node.value, value);
|
||||
}
|
||||
@@ -151,10 +168,11 @@ bool charmap_HasChar(std::string const &input) {
|
||||
size_t nodeIdx = 0;
|
||||
|
||||
for (char c : input) {
|
||||
nodeIdx = charmap.nodes[nodeIdx].next[(uint8_t)c];
|
||||
nodeIdx = charmap.nodes[nodeIdx].next[static_cast<uint8_t>(c)];
|
||||
|
||||
if (!nodeIdx)
|
||||
if (!nodeIdx) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return charmap.nodes[nodeIdx].isTerminal();
|
||||
@@ -162,8 +180,7 @@ bool charmap_HasChar(std::string const &input) {
|
||||
|
||||
std::vector<int32_t> charmap_Convert(std::string const &input) {
|
||||
std::vector<int32_t> output;
|
||||
for (std::string_view inputView = input; charmap_ConvertNext(inputView, &output);)
|
||||
;
|
||||
for (std::string_view inputView = input; charmap_ConvertNext(inputView, &output);) {}
|
||||
return output;
|
||||
}
|
||||
|
||||
@@ -178,10 +195,11 @@ size_t charmap_ConvertNext(std::string_view &input, std::vector<int32_t> *output
|
||||
size_t inputIdx = 0;
|
||||
|
||||
for (size_t nodeIdx = 0; inputIdx < input.length();) {
|
||||
nodeIdx = charmap.nodes[nodeIdx].next[(uint8_t)input[inputIdx]];
|
||||
nodeIdx = charmap.nodes[nodeIdx].next[static_cast<uint8_t>(input[inputIdx])];
|
||||
|
||||
if (!nodeIdx)
|
||||
if (!nodeIdx) {
|
||||
break;
|
||||
}
|
||||
|
||||
inputIdx++; // Consume that char
|
||||
|
||||
@@ -201,27 +219,42 @@ size_t charmap_ConvertNext(std::string_view &input, std::vector<int32_t> *output
|
||||
if (matchIdx) { // A match was found, use it
|
||||
std::vector<int32_t> const &value = charmap.nodes[matchIdx].value;
|
||||
|
||||
if (output)
|
||||
if (output) {
|
||||
output->insert(output->end(), RANGE(value));
|
||||
}
|
||||
|
||||
matchLen = value.size();
|
||||
} else if (inputIdx < input.length()) { // No match found, but there is some input left
|
||||
int firstChar = input[inputIdx];
|
||||
size_t codepointLen = 0;
|
||||
// This will write the codepoint's value to `output`, little-endian
|
||||
size_t codepointLen = readUTF8Char(output, input.data() + inputIdx);
|
||||
for (uint32_t state = 0, codepoint = 0; inputIdx + codepointLen < input.length();) {
|
||||
if (decode(&state, &codepoint, input[inputIdx + codepointLen]) == 1) {
|
||||
error("Input string is not valid UTF-8\n");
|
||||
codepointLen = 1;
|
||||
break;
|
||||
}
|
||||
codepointLen++;
|
||||
if (state == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (codepointLen == 0)
|
||||
error("Input string is not valid UTF-8\n");
|
||||
if (output) {
|
||||
output->insert(
|
||||
output->end(), input.data() + inputIdx, input.data() + inputIdx + codepointLen
|
||||
);
|
||||
}
|
||||
|
||||
// Warn if this character is not mapped but any others are
|
||||
if (charmap.nodes.size() > 1)
|
||||
if (int firstChar = input[inputIdx]; charmap.nodes.size() > 1) {
|
||||
warning(WARNING_UNMAPPED_CHAR_1, "Unmapped character %s\n", printChar(firstChar));
|
||||
else if (charmap.name != DEFAULT_CHARMAP_NAME)
|
||||
} else if (charmap.name != DEFAULT_CHARMAP_NAME) {
|
||||
warning(
|
||||
WARNING_UNMAPPED_CHAR_2,
|
||||
"Unmapped character %s not in " DEFAULT_CHARMAP_NAME " charmap\n",
|
||||
printChar(firstChar)
|
||||
);
|
||||
}
|
||||
|
||||
inputIdx += codepointLen;
|
||||
matchLen = codepointLen;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// Fixed-point math routines
|
||||
|
||||
@@ -16,20 +16,18 @@ uint8_t fix_Precision() {
|
||||
return fixPrecision;
|
||||
}
|
||||
|
||||
double fix_PrecisionFactor() {
|
||||
return pow(2.0, fixPrecision);
|
||||
}
|
||||
|
||||
static double fix2double(int32_t i, int32_t q) {
|
||||
return i / pow(2.0, q);
|
||||
}
|
||||
|
||||
static int32_t double2fix(double d, int32_t q) {
|
||||
if (isnan(d))
|
||||
if (isnan(d)) {
|
||||
return 0;
|
||||
if (isinf(d))
|
||||
}
|
||||
if (isinf(d)) {
|
||||
return d < 0 ? INT32_MIN : INT32_MAX;
|
||||
return (int32_t)round(d * pow(2.0, q));
|
||||
}
|
||||
return static_cast<int32_t>(round(d * pow(2.0, q)));
|
||||
}
|
||||
|
||||
static double turn2rad(double t) {
|
||||
@@ -73,7 +71,12 @@ int32_t fix_Mul(int32_t i, int32_t j, int32_t q) {
|
||||
}
|
||||
|
||||
int32_t fix_Div(int32_t i, int32_t j, int32_t q) {
|
||||
return double2fix(fix2double(i, q) / fix2double(j, q), q);
|
||||
double dividend = fix2double(i, q);
|
||||
double divisor = fix2double(j, q);
|
||||
if (fpclassify(divisor) == FP_ZERO) {
|
||||
return dividend < 0 ? INT32_MIN : dividend > 0 ? INT32_MAX : 0;
|
||||
}
|
||||
return double2fix(dividend / divisor, q);
|
||||
}
|
||||
|
||||
int32_t fix_Mod(int32_t i, int32_t j, int32_t q) {
|
||||
@@ -85,7 +88,11 @@ int32_t fix_Pow(int32_t i, int32_t j, int32_t q) {
|
||||
}
|
||||
|
||||
int32_t fix_Log(int32_t i, int32_t j, int32_t q) {
|
||||
return double2fix(log(fix2double(i, q)) / log(fix2double(j, q)), q);
|
||||
double divisor = log(fix2double(j, q));
|
||||
if (fpclassify(divisor) == FP_ZERO) {
|
||||
return INT32_MAX;
|
||||
}
|
||||
return double2fix(log(fix2double(i, q)) / divisor, q);
|
||||
}
|
||||
|
||||
int32_t fix_Round(int32_t i, int32_t q) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "asm/format.hpp"
|
||||
|
||||
@@ -13,39 +13,44 @@
|
||||
#include "asm/warning.hpp"
|
||||
|
||||
void FormatSpec::useCharacter(int c) {
|
||||
if (state == FORMAT_INVALID)
|
||||
if (state == FORMAT_INVALID) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (c) {
|
||||
// sign
|
||||
case ' ':
|
||||
case '+':
|
||||
if (state > FORMAT_SIGN)
|
||||
if (state > FORMAT_SIGN) {
|
||||
goto invalid;
|
||||
}
|
||||
state = FORMAT_EXACT;
|
||||
sign = c;
|
||||
break;
|
||||
|
||||
// exact
|
||||
case '#':
|
||||
if (state > FORMAT_EXACT)
|
||||
if (state > FORMAT_EXACT) {
|
||||
goto invalid;
|
||||
}
|
||||
state = FORMAT_ALIGN;
|
||||
exact = true;
|
||||
break;
|
||||
|
||||
// align
|
||||
case '-':
|
||||
if (state > FORMAT_ALIGN)
|
||||
if (state > FORMAT_ALIGN) {
|
||||
goto invalid;
|
||||
}
|
||||
state = FORMAT_WIDTH;
|
||||
alignLeft = true;
|
||||
break;
|
||||
|
||||
// pad, width, and prec values
|
||||
case '0':
|
||||
if (state < FORMAT_WIDTH)
|
||||
if (state < FORMAT_WIDTH) {
|
||||
padZero = true;
|
||||
}
|
||||
[[fallthrough]];
|
||||
case '1':
|
||||
case '2':
|
||||
@@ -72,16 +77,18 @@ void FormatSpec::useCharacter(int c) {
|
||||
|
||||
// width
|
||||
case '.':
|
||||
if (state > FORMAT_WIDTH)
|
||||
if (state > FORMAT_WIDTH) {
|
||||
goto invalid;
|
||||
}
|
||||
state = FORMAT_FRAC;
|
||||
hasFrac = true;
|
||||
break;
|
||||
|
||||
// prec
|
||||
case 'q':
|
||||
if (state > FORMAT_PREC)
|
||||
if (state > FORMAT_PREC) {
|
||||
goto invalid;
|
||||
}
|
||||
state = FORMAT_PREC;
|
||||
hasPrec = true;
|
||||
break;
|
||||
@@ -95,8 +102,9 @@ void FormatSpec::useCharacter(int c) {
|
||||
case 'o':
|
||||
case 'f':
|
||||
case 's':
|
||||
if (state >= FORMAT_DONE)
|
||||
if (state >= FORMAT_DONE) {
|
||||
goto invalid;
|
||||
}
|
||||
state = FORMAT_DONE;
|
||||
valid = true;
|
||||
type = c;
|
||||
@@ -110,8 +118,9 @@ invalid:
|
||||
}
|
||||
|
||||
void FormatSpec::finishCharacters() {
|
||||
if (!isValid())
|
||||
if (!isValid()) {
|
||||
state = FORMAT_INVALID;
|
||||
}
|
||||
}
|
||||
|
||||
static std::string escapeString(std::string const &str) {
|
||||
@@ -151,16 +160,21 @@ void FormatSpec::appendString(std::string &str, std::string const &value) const
|
||||
useType = 's';
|
||||
}
|
||||
|
||||
if (sign)
|
||||
if (sign) {
|
||||
error("Formatting string with sign flag '%c'\n", sign);
|
||||
if (padZero)
|
||||
}
|
||||
if (padZero) {
|
||||
error("Formatting string with padding flag '0'\n");
|
||||
if (hasFrac)
|
||||
}
|
||||
if (hasFrac) {
|
||||
error("Formatting string with fractional width\n");
|
||||
if (hasPrec)
|
||||
}
|
||||
if (hasPrec) {
|
||||
error("Formatting string with fractional precision\n");
|
||||
if (useType != 's')
|
||||
}
|
||||
if (useType != 's') {
|
||||
error("Formatting string as type '%c'\n", useType);
|
||||
}
|
||||
|
||||
std::string useValue = exact ? escapeString(value) : value;
|
||||
size_t valueLen = useValue.length();
|
||||
@@ -186,22 +200,28 @@ void FormatSpec::appendNumber(std::string &str, uint32_t value) const {
|
||||
useExact = true;
|
||||
}
|
||||
|
||||
if (useType != 'X' && useType != 'x' && useType != 'b' && useType != 'o' && useType != 'f' && useExact)
|
||||
if (useType != 'X' && useType != 'x' && useType != 'b' && useType != 'o' && useType != 'f'
|
||||
&& useExact) {
|
||||
error("Formatting type '%c' with exact flag '#'\n", useType);
|
||||
if (useType != 'f' && hasFrac)
|
||||
}
|
||||
if (useType != 'f' && hasFrac) {
|
||||
error("Formatting type '%c' with fractional width\n", useType);
|
||||
if (useType != 'f' && hasPrec)
|
||||
}
|
||||
if (useType != 'f' && hasPrec) {
|
||||
error("Formatting type '%c' with fractional precision\n", useType);
|
||||
if (useType == 's')
|
||||
}
|
||||
if (useType == 's') {
|
||||
error("Formatting number as type 's'\n");
|
||||
}
|
||||
|
||||
char signChar = sign; // 0 or ' ' or '+'
|
||||
|
||||
if (useType == 'd' || useType == 'f') {
|
||||
if (int32_t v = value; v < 0) {
|
||||
signChar = '-';
|
||||
if (v != INT32_MIN)
|
||||
if (v != INT32_MIN) {
|
||||
value = -v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,15 +269,17 @@ void FormatSpec::appendNumber(std::string &str, uint32_t value) const {
|
||||
}
|
||||
|
||||
double fval = fabs(value / pow(2.0, usePrec));
|
||||
if (useExact)
|
||||
snprintf(valueBuf, sizeof(valueBuf), "%.*fq%zu", (int)useFracWidth, fval, usePrec);
|
||||
else
|
||||
snprintf(valueBuf, sizeof(valueBuf), "%.*f", (int)useFracWidth, fval);
|
||||
if (int fracWidthArg = static_cast<int>(useFracWidth); useExact) {
|
||||
snprintf(valueBuf, sizeof(valueBuf), "%.*fq%zu", fracWidthArg, fval, usePrec);
|
||||
} else {
|
||||
snprintf(valueBuf, sizeof(valueBuf), "%.*f", fracWidthArg, fval);
|
||||
}
|
||||
} else if (useType == 'd') {
|
||||
// Decimal numbers may be formatted with a '-' sign by `snprintf`, so `abs` prevents that,
|
||||
// with a special case for `INT32_MIN` since `labs(INT32_MIN)` is UB. The sign will be
|
||||
// printed later from `signChar`.
|
||||
uint32_t uval = value != (uint32_t)INT32_MIN ? labs((int32_t)value) : value;
|
||||
uint32_t uval =
|
||||
value != static_cast<uint32_t>(INT32_MIN) ? labs(static_cast<int32_t>(value)) : value;
|
||||
snprintf(valueBuf, sizeof(valueBuf), "%" PRIu32, uval);
|
||||
} else {
|
||||
char const *spec = useType == 'u' ? "%" PRIu32
|
||||
@@ -276,27 +298,33 @@ void FormatSpec::appendNumber(std::string &str, uint32_t value) const {
|
||||
|
||||
str.reserve(str.length() + totalLen);
|
||||
if (alignLeft) {
|
||||
if (signChar)
|
||||
if (signChar) {
|
||||
str += signChar;
|
||||
if (prefixChar)
|
||||
}
|
||||
if (prefixChar) {
|
||||
str += prefixChar;
|
||||
}
|
||||
str.append(valueBuf);
|
||||
str.append(padLen, ' ');
|
||||
} else {
|
||||
if (padZero) {
|
||||
// sign, then prefix, then zero padding
|
||||
if (signChar)
|
||||
if (signChar) {
|
||||
str += signChar;
|
||||
if (prefixChar)
|
||||
}
|
||||
if (prefixChar) {
|
||||
str += prefixChar;
|
||||
}
|
||||
str.append(padLen, '0');
|
||||
} else {
|
||||
// space padding, then sign, then prefix
|
||||
str.append(padLen, ' ');
|
||||
if (signChar)
|
||||
if (signChar) {
|
||||
str += signChar;
|
||||
if (prefixChar)
|
||||
}
|
||||
if (prefixChar) {
|
||||
str += prefixChar;
|
||||
}
|
||||
}
|
||||
str.append(valueBuf);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "asm/fstack.hpp"
|
||||
#include <sys/stat.h>
|
||||
@@ -90,8 +90,9 @@ std::shared_ptr<std::string> fstk_GetUniqueIDStr() {
|
||||
std::shared_ptr<std::string> &str = contextStack.top().uniqueIDStr;
|
||||
|
||||
// If a unique ID is allowed but has not been generated yet, generate one now.
|
||||
if (str && str->empty())
|
||||
if (str && str->empty()) {
|
||||
*str = "_u"s + std::to_string(nextUniqueID++);
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
@@ -103,27 +104,32 @@ MacroArgs *fstk_GetCurrentMacroArgs() {
|
||||
}
|
||||
|
||||
void fstk_AddIncludePath(std::string const &path) {
|
||||
if (path.empty())
|
||||
if (path.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::string &includePath = includePaths.emplace_back(path);
|
||||
if (includePath.back() != '/')
|
||||
if (includePath.back() != '/') {
|
||||
includePath += '/';
|
||||
}
|
||||
}
|
||||
|
||||
void fstk_SetPreIncludeFile(std::string const &path) {
|
||||
if (!preIncludeName.empty())
|
||||
if (!preIncludeName.empty()) {
|
||||
warnx("Overriding pre-included filename %s", preIncludeName.c_str());
|
||||
}
|
||||
preIncludeName = path;
|
||||
if (verbose)
|
||||
if (verbose) {
|
||||
printf("Pre-included filename %s\n", preIncludeName.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
static void printDep(std::string const &path) {
|
||||
if (dependFile) {
|
||||
fprintf(dependFile, "%s: %s\n", targetFileName.c_str(), path.c_str());
|
||||
if (generatePhonyDeps)
|
||||
if (generatePhonyDeps) {
|
||||
fprintf(dependFile, "%s:\n", path.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,20 +147,22 @@ std::optional<std::string> fstk_FindFile(std::string const &path) {
|
||||
}
|
||||
|
||||
errno = ENOENT;
|
||||
if (generatedMissingIncludes)
|
||||
if (generatedMissingIncludes) {
|
||||
printDep(path);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
bool yywrap() {
|
||||
uint32_t ifDepth = lexer_GetIFDepth();
|
||||
|
||||
if (ifDepth != 0)
|
||||
if (ifDepth != 0) {
|
||||
fatalerror(
|
||||
"Ended block with %" PRIu32 " unterminated IF construct%s\n",
|
||||
ifDepth,
|
||||
ifDepth == 1 ? "" : "s"
|
||||
);
|
||||
}
|
||||
|
||||
if (Context &context = contextStack.top(); context.fileInfo->type == NODE_REPT) {
|
||||
// The context is a REPT or FOR block, which may loop
|
||||
@@ -162,7 +170,7 @@ bool yywrap() {
|
||||
// If the node is referenced outside this context, we can't edit it, so duplicate it
|
||||
if (context.fileInfo.use_count() > 1) {
|
||||
context.fileInfo = std::make_shared<FileStackNode>(*context.fileInfo);
|
||||
context.fileInfo->ID = -1; // The copy is not yet registered
|
||||
context.fileInfo->ID = UINT32_MAX; // The copy is not yet registered
|
||||
}
|
||||
|
||||
std::vector<uint32_t> &fileInfoIters = context.fileInfo->iters();
|
||||
@@ -170,13 +178,16 @@ bool yywrap() {
|
||||
// If this is a FOR, update the symbol value
|
||||
if (context.isForLoop && fileInfoIters.front() <= context.nbReptIters) {
|
||||
// Avoid arithmetic overflow runtime error
|
||||
uint32_t forValue = (uint32_t)context.forValue + (uint32_t)context.forStep;
|
||||
context.forValue = forValue <= INT32_MAX ? forValue : -(int32_t)~forValue - 1;
|
||||
uint32_t forValue =
|
||||
static_cast<uint32_t>(context.forValue) + static_cast<uint32_t>(context.forStep);
|
||||
context.forValue =
|
||||
forValue <= INT32_MAX ? forValue : -static_cast<int32_t>(~forValue) - 1;
|
||||
Symbol *sym = sym_AddVar(context.forName, context.forValue);
|
||||
|
||||
// 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");
|
||||
}
|
||||
}
|
||||
// Advance to the next iteration
|
||||
fileInfoIters.front()++;
|
||||
@@ -197,8 +208,9 @@ bool yywrap() {
|
||||
}
|
||||
|
||||
static void checkRecursionDepth() {
|
||||
if (contextStack.size() > maxRecursionDepth)
|
||||
if (contextStack.size() > maxRecursionDepth) {
|
||||
fatalerror("Recursion limit (%zu) exceeded\n", maxRecursionDepth);
|
||||
}
|
||||
}
|
||||
|
||||
static bool newFileContext(std::string const &filePath, bool updateStateNow) {
|
||||
@@ -208,7 +220,7 @@ static bool newFileContext(std::string const &filePath, bool updateStateNow) {
|
||||
std::shared_ptr<MacroArgs> macroArgs = nullptr;
|
||||
|
||||
auto fileInfo =
|
||||
std::make_shared<FileStackNode>(NODE_MACRO, filePath == "-" ? "<stdin>" : filePath);
|
||||
std::make_shared<FileStackNode>(NODE_FILE, filePath == "-" ? "<stdin>" : filePath);
|
||||
if (!contextStack.empty()) {
|
||||
Context &oldContext = contextStack.top();
|
||||
fileInfo->parent = oldContext.fileInfo;
|
||||
@@ -296,8 +308,9 @@ void fstk_RunInclude(std::string const &path, bool preInclude) {
|
||||
|
||||
if (!fullPath) {
|
||||
if (generatedMissingIncludes && !preInclude) {
|
||||
if (verbose)
|
||||
if (verbose) {
|
||||
printf("Aborting (-MG) on INCLUDE file '%s' (%s)\n", path.c_str(), strerror(errno));
|
||||
}
|
||||
failedOnMissingInclude = true;
|
||||
} else {
|
||||
error("Unable to open included file '%s': %s\n", path.c_str(), strerror(errno));
|
||||
@@ -305,18 +318,20 @@ void fstk_RunInclude(std::string const &path, bool preInclude) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!newFileContext(*fullPath, false))
|
||||
if (!newFileContext(*fullPath, false)) {
|
||||
fatalerror("Failed to set up lexer for file include\n");
|
||||
}
|
||||
}
|
||||
|
||||
void fstk_RunMacro(std::string const ¯oName, std::shared_ptr<MacroArgs> macroArgs) {
|
||||
Symbol *macro = sym_FindExactSymbol(macroName);
|
||||
|
||||
if (!macro) {
|
||||
if (sym_IsPurgedExact(macroName))
|
||||
if (sym_IsPurgedExact(macroName)) {
|
||||
error("Macro \"%s\" not defined; it was purged\n", macroName.c_str());
|
||||
else
|
||||
} else {
|
||||
error("Macro \"%s\" not defined\n", macroName.c_str());
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (macro->type != SYM_MACRO) {
|
||||
@@ -328,8 +343,9 @@ void fstk_RunMacro(std::string const ¯oName, std::shared_ptr<MacroArgs> macr
|
||||
}
|
||||
|
||||
void fstk_RunRept(uint32_t count, int32_t reptLineNo, ContentSpan const &span) {
|
||||
if (count == 0)
|
||||
if (count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
newReptContext(reptLineNo, span, count);
|
||||
}
|
||||
@@ -342,24 +358,28 @@ void fstk_RunFor(
|
||||
int32_t reptLineNo,
|
||||
ContentSpan const &span
|
||||
) {
|
||||
if (Symbol *sym = sym_AddVar(symName, start); sym->type != SYM_VAR)
|
||||
if (Symbol *sym = sym_AddVar(symName, start); sym->type != SYM_VAR) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t count = 0;
|
||||
if (step > 0 && start < stop)
|
||||
count = ((int64_t)stop - start - 1) / step + 1;
|
||||
else if (step < 0 && stop < start)
|
||||
count = ((int64_t)start - stop - 1) / -(int64_t)step + 1;
|
||||
else if (step == 0)
|
||||
if (step > 0 && start < stop) {
|
||||
count = (static_cast<int64_t>(stop) - start - 1) / step + 1;
|
||||
} else if (step < 0 && stop < start) {
|
||||
count = (static_cast<int64_t>(start) - stop - 1) / -static_cast<int64_t>(step) + 1;
|
||||
} else if (step == 0) {
|
||||
error("FOR cannot have a step value of 0\n");
|
||||
}
|
||||
|
||||
if ((step > 0 && start > stop) || (step < 0 && start < stop))
|
||||
if ((step > 0 && start > stop) || (step < 0 && start < stop)) {
|
||||
warning(
|
||||
WARNING_BACKWARDS_FOR, "FOR goes backwards from %d to %d by %d\n", start, stop, step
|
||||
);
|
||||
}
|
||||
|
||||
if (count == 0)
|
||||
if (count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
Context &context = newReptContext(reptLineNo, span, count);
|
||||
context.isForLoop = true;
|
||||
@@ -383,17 +403,20 @@ bool fstk_Break() {
|
||||
}
|
||||
|
||||
void fstk_NewRecursionDepth(size_t newDepth) {
|
||||
if (contextStack.size() > newDepth + 1)
|
||||
if (contextStack.size() > newDepth + 1) {
|
||||
fatalerror("Recursion limit (%zu) exceeded\n", newDepth);
|
||||
}
|
||||
maxRecursionDepth = newDepth;
|
||||
}
|
||||
|
||||
void fstk_Init(std::string const &mainPath, size_t maxDepth) {
|
||||
if (!newFileContext(mainPath, true))
|
||||
if (!newFileContext(mainPath, true)) {
|
||||
fatalerror("Failed to open main file\n");
|
||||
}
|
||||
|
||||
maxRecursionDepth = maxDepth;
|
||||
|
||||
if (!preIncludeName.empty())
|
||||
if (!preIncludeName.empty()) {
|
||||
fstk_RunInclude(preIncludeName, true);
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "asm/macro.hpp"
|
||||
|
||||
@@ -6,12 +6,8 @@
|
||||
#include <string.h>
|
||||
#include <string>
|
||||
|
||||
#include "helpers.hpp"
|
||||
|
||||
#include "asm/warning.hpp"
|
||||
|
||||
#define MAXMACROARGS 99999
|
||||
|
||||
std::shared_ptr<std::string> MacroArgs::getArg(uint32_t i) const {
|
||||
uint32_t realIndex = i + shift - 1;
|
||||
|
||||
@@ -21,13 +17,15 @@ std::shared_ptr<std::string> MacroArgs::getArg(uint32_t i) const {
|
||||
std::shared_ptr<std::string> MacroArgs::getAllArgs() const {
|
||||
size_t nbArgs = args.size();
|
||||
|
||||
if (shift >= nbArgs)
|
||||
if (shift >= nbArgs) {
|
||||
return std::make_shared<std::string>("");
|
||||
}
|
||||
|
||||
size_t len = 0;
|
||||
|
||||
for (uint32_t i = shift; i < nbArgs; i++)
|
||||
for (uint32_t i = shift; i < nbArgs; i++) {
|
||||
len += args[i]->length() + 1; // 1 for comma
|
||||
}
|
||||
|
||||
auto str = std::make_shared<std::string>();
|
||||
str->reserve(len + 1); // 1 for comma
|
||||
@@ -38,27 +36,27 @@ std::shared_ptr<std::string> MacroArgs::getAllArgs() const {
|
||||
str->append(*arg);
|
||||
|
||||
// Commas go between args and after a last empty arg
|
||||
if (i < nbArgs - 1 || arg->empty())
|
||||
if (i < nbArgs - 1 || arg->empty()) {
|
||||
str->push_back(','); // no space after comma
|
||||
}
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
void MacroArgs::appendArg(std::shared_ptr<std::string> arg) {
|
||||
if (arg->empty())
|
||||
if (arg->empty()) {
|
||||
warning(WARNING_EMPTY_MACRO_ARG, "Empty macro argument\n");
|
||||
if (args.size() == MAXMACROARGS)
|
||||
error("A maximum of " EXPAND_AND_STR(MAXMACROARGS) " arguments is allowed\n");
|
||||
}
|
||||
args.push_back(arg);
|
||||
}
|
||||
|
||||
void MacroArgs::shiftArgs(int32_t count) {
|
||||
if (size_t nbArgs = args.size();
|
||||
count > 0 && ((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");
|
||||
shift = nbArgs;
|
||||
} else if (count < 0 && shift < (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");
|
||||
shift = 0;
|
||||
} else {
|
||||
|
||||
135
src/asm/main.cpp
135
src/asm/main.cpp
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "asm/main.hpp"
|
||||
|
||||
@@ -37,30 +37,30 @@ static std::string make_escape(std::string &str) {
|
||||
for (;;) {
|
||||
// All dollars needs to be doubled
|
||||
size_t nextPos = str.find("$", pos);
|
||||
if (nextPos == std::string::npos)
|
||||
if (nextPos == std::string::npos) {
|
||||
break;
|
||||
}
|
||||
escaped.append(str, pos, nextPos - pos);
|
||||
escaped.append("$$");
|
||||
pos = nextPos + QUOTEDSTRLEN("$");
|
||||
pos = nextPos + literal_strlen("$");
|
||||
}
|
||||
escaped.append(str, pos, str.length() - pos);
|
||||
return escaped;
|
||||
}
|
||||
|
||||
// Short options
|
||||
static char const *optstring = "b:D:Eg:I:M:o:P:p:Q:r:s:VvW:wX:";
|
||||
static char const *optstring = "b:D:Eg:hI:M:o:P:p:Q:r:s:VvW:wX:";
|
||||
|
||||
// Variables for the long-only options
|
||||
static int depType; // Variants of `-M`
|
||||
|
||||
// Equivalent long options
|
||||
// Please keep in the same order as short opts
|
||||
//
|
||||
// Please keep in the same order as short opts.
|
||||
// Also, make sure long opts don't create ambiguity:
|
||||
// A long opt's name should start with the same letter as its short opt,
|
||||
// except if it doesn't create any ambiguity (`verbose` versus `version`).
|
||||
// This is because long opt matching, even to a single char, is prioritized
|
||||
// over short opt matching
|
||||
// over short opt matching.
|
||||
static option const longopts[] = {
|
||||
{"binary-digits", required_argument, nullptr, 'b'},
|
||||
{"define", required_argument, nullptr, 'D'},
|
||||
@@ -69,6 +69,7 @@ static option const longopts[] = {
|
||||
{"include", required_argument, nullptr, 'I'},
|
||||
{"dependfile", required_argument, nullptr, 'M'},
|
||||
{"MG", no_argument, &depType, 'G'},
|
||||
{"help", no_argument, nullptr, 'h'},
|
||||
{"MP", no_argument, &depType, 'P'},
|
||||
{"MT", required_argument, &depType, 'T'},
|
||||
{"warning", required_argument, nullptr, 'W'},
|
||||
@@ -88,7 +89,7 @@ static option const longopts[] = {
|
||||
|
||||
static void printUsage() {
|
||||
fputs(
|
||||
"Usage: rgbasm [-EVvw] [-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] [-MG] [-MP] [-MT target_file] [-MQ target_file]\n"
|
||||
" [-o out_file] [-P include_file] [-p pad_value] [-Q precision]\n"
|
||||
" [-r depth] [-s features:state_file] [-W warning] [-X max_errors]\n"
|
||||
@@ -111,12 +112,14 @@ int main(int argc, char *argv[]) {
|
||||
time_t now = time(nullptr);
|
||||
// Support SOURCE_DATE_EPOCH for reproducible builds
|
||||
// https://reproducible-builds.org/docs/source-date-epoch/
|
||||
if (char const *sourceDateEpoch = getenv("SOURCE_DATE_EPOCH"); sourceDateEpoch)
|
||||
now = (time_t)strtoul(sourceDateEpoch, nullptr, 0);
|
||||
if (char const *sourceDateEpoch = getenv("SOURCE_DATE_EPOCH"); sourceDateEpoch) {
|
||||
now = static_cast<time_t>(strtoul(sourceDateEpoch, nullptr, 0));
|
||||
}
|
||||
|
||||
Defer closeDependFile{[&] {
|
||||
if (dependFile)
|
||||
if (dependFile) {
|
||||
fclose(dependFile);
|
||||
}
|
||||
}};
|
||||
|
||||
// Perform some init for below
|
||||
@@ -128,23 +131,25 @@ int main(int argc, char *argv[]) {
|
||||
opt_P(0);
|
||||
opt_Q(16);
|
||||
sym_SetExportAll(false);
|
||||
uint32_t maxDepth = DEFAULT_MAX_DEPTH;
|
||||
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;
|
||||
}
|
||||
|
||||
for (int ch; (ch = musl_getopt_long_only(argc, argv, optstring, longopts, nullptr)) != -1;) {
|
||||
switch (ch) {
|
||||
char *endptr;
|
||||
|
||||
case 'b':
|
||||
if (strlen(musl_optarg) == 2)
|
||||
if (strlen(musl_optarg) == 2) {
|
||||
opt_B(musl_optarg);
|
||||
else
|
||||
} else {
|
||||
errx("Must specify exactly 2 characters for option 'b'");
|
||||
}
|
||||
break;
|
||||
|
||||
char *equals;
|
||||
@@ -163,19 +168,25 @@ int main(int argc, char *argv[]) {
|
||||
break;
|
||||
|
||||
case 'g':
|
||||
if (strlen(musl_optarg) == 4)
|
||||
if (strlen(musl_optarg) == 4) {
|
||||
opt_G(musl_optarg);
|
||||
else
|
||||
} else {
|
||||
errx("Must specify exactly 4 characters for option 'g'");
|
||||
}
|
||||
break;
|
||||
|
||||
case 'h':
|
||||
printUsage();
|
||||
exit(0);
|
||||
|
||||
case 'I':
|
||||
fstk_AddIncludePath(musl_optarg);
|
||||
break;
|
||||
|
||||
case 'M':
|
||||
if (dependFile)
|
||||
if (dependFile) {
|
||||
warnx("Overriding dependfile %s", dependFileName);
|
||||
}
|
||||
if (strcmp("-", musl_optarg)) {
|
||||
dependFile = fopen(musl_optarg, "w");
|
||||
dependFileName = musl_optarg;
|
||||
@@ -183,8 +194,9 @@ int main(int argc, char *argv[]) {
|
||||
dependFile = stdout;
|
||||
dependFileName = "<stdout>";
|
||||
}
|
||||
if (dependFile == nullptr)
|
||||
if (dependFile == nullptr) {
|
||||
err("Failed to open dependfile \"%s\"", dependFileName);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'o':
|
||||
@@ -199,11 +211,13 @@ int main(int argc, char *argv[]) {
|
||||
case 'p':
|
||||
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'");
|
||||
}
|
||||
|
||||
if (padByte > 0xFF)
|
||||
if (padByte > 0xFF) {
|
||||
errx("Argument for option 'p' must be between 0 and 0xFF");
|
||||
}
|
||||
|
||||
opt_P(padByte);
|
||||
break;
|
||||
@@ -212,15 +226,18 @@ int main(int argc, char *argv[]) {
|
||||
char const *precisionArg;
|
||||
case 'Q':
|
||||
precisionArg = musl_optarg;
|
||||
if (precisionArg[0] == '.')
|
||||
if (precisionArg[0] == '.') {
|
||||
precisionArg++;
|
||||
}
|
||||
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'");
|
||||
}
|
||||
|
||||
if (precision < 1 || precision > 31)
|
||||
if (precision < 1 || precision > 31) {
|
||||
errx("Argument for option 'Q' must be between 1 and 31");
|
||||
}
|
||||
|
||||
opt_Q(precision);
|
||||
break;
|
||||
@@ -228,35 +245,41 @@ int main(int argc, char *argv[]) {
|
||||
case 'r':
|
||||
maxDepth = 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'");
|
||||
}
|
||||
break;
|
||||
|
||||
case 's': {
|
||||
// Split "<features>:<name>" so `musl_optarg` is "<features>" and `name` is "<name>"
|
||||
char *name = strchr(musl_optarg, ':');
|
||||
if (!name)
|
||||
if (!name) {
|
||||
errx("Invalid argument for option 's'");
|
||||
}
|
||||
*name++ = '\0';
|
||||
|
||||
std::vector<StateFeature> features;
|
||||
for (char *feature = musl_optarg; feature;) {
|
||||
// Split "<feature>,<rest>" so `feature` is "<feature>" and `next` is "<rest>"
|
||||
char *next = strchr(feature, ',');
|
||||
if (next)
|
||||
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)
|
||||
if (char *end = strpbrk(feature, " \t"); end) {
|
||||
*end = '\0';
|
||||
}
|
||||
// A feature must be specified
|
||||
if (*feature == '\0')
|
||||
if (*feature == '\0') {
|
||||
errx("Empty feature for option 's'");
|
||||
}
|
||||
// Parse the `feature` and update the `features` list
|
||||
if (!strcasecmp(feature, "all")) {
|
||||
if (!features.empty())
|
||||
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
|
||||
@@ -276,10 +299,12 @@ int main(int argc, char *argv[]) {
|
||||
feature = next;
|
||||
}
|
||||
|
||||
if (stateFileSpecs.find(name) != stateFileSpecs.end())
|
||||
if (stateFileSpecs.find(name) != stateFileSpecs.end()) {
|
||||
warnx("Overriding state filename %s", name);
|
||||
if (verbose)
|
||||
}
|
||||
if (verbose) {
|
||||
printf("State filename %s\n", name);
|
||||
}
|
||||
stateFileSpecs.emplace(name, std::move(features));
|
||||
break;
|
||||
}
|
||||
@@ -293,7 +318,7 @@ int main(int argc, char *argv[]) {
|
||||
break;
|
||||
|
||||
case 'W':
|
||||
processWarningFlag(musl_optarg);
|
||||
opt_W(musl_optarg);
|
||||
break;
|
||||
|
||||
case 'w':
|
||||
@@ -304,11 +329,13 @@ int main(int argc, char *argv[]) {
|
||||
case 'X':
|
||||
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'");
|
||||
}
|
||||
|
||||
if (maxValue > UINT_MAX)
|
||||
if (maxValue > UINT_MAX) {
|
||||
errx("Argument for option 'X' must be between 0 and %u", UINT_MAX);
|
||||
}
|
||||
|
||||
maxErrors = maxValue;
|
||||
break;
|
||||
@@ -327,10 +354,12 @@ int main(int argc, char *argv[]) {
|
||||
case 'Q':
|
||||
case 'T':
|
||||
newTarget = musl_optarg;
|
||||
if (depType == 'Q')
|
||||
if (depType == 'Q') {
|
||||
newTarget = make_escape(newTarget);
|
||||
if (!targetFileName.empty())
|
||||
}
|
||||
if (!targetFileName.empty()) {
|
||||
targetFileName += ' ';
|
||||
}
|
||||
targetFileName += newTarget;
|
||||
break;
|
||||
}
|
||||
@@ -343,8 +372,9 @@ int main(int argc, char *argv[]) {
|
||||
}
|
||||
}
|
||||
|
||||
if (targetFileName.empty() && !objectFileName.empty())
|
||||
if (targetFileName.empty() && !objectFileName.empty()) {
|
||||
targetFileName = objectFileName;
|
||||
}
|
||||
|
||||
if (argc == musl_optind) {
|
||||
fputs(
|
||||
@@ -360,13 +390,15 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
std::string mainFileName = argv[musl_optind];
|
||||
|
||||
if (verbose)
|
||||
if (verbose) {
|
||||
printf("Assembling %s\n", mainFileName.c_str());
|
||||
}
|
||||
|
||||
if (dependFile) {
|
||||
if (targetFileName.empty())
|
||||
if (targetFileName.empty()) {
|
||||
errx("Dependency files can only be created if a target file is specified with either "
|
||||
"-o, -MQ or -MT");
|
||||
}
|
||||
|
||||
fprintf(dependFile, "%s: %s\n", targetFileName.c_str(), mainFileName.c_str());
|
||||
}
|
||||
@@ -377,23 +409,34 @@ int main(int argc, char *argv[]) {
|
||||
fstk_Init(mainFileName, maxDepth);
|
||||
|
||||
// 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 == 0) {
|
||||
nbErrors = 1;
|
||||
}
|
||||
|
||||
sect_CheckUnionClosed();
|
||||
sect_CheckSizes();
|
||||
if (!failedOnMissingInclude) {
|
||||
sect_CheckUnionClosed();
|
||||
sect_CheckLoadClosed();
|
||||
sect_CheckSizes();
|
||||
|
||||
if (nbErrors != 0)
|
||||
charmap_CheckStack();
|
||||
opt_CheckStack();
|
||||
sect_CheckStack();
|
||||
}
|
||||
|
||||
if (nbErrors != 0) {
|
||||
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 (failedOnMissingInclude)
|
||||
if (failedOnMissingInclude) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
out_WriteObject();
|
||||
|
||||
for (auto [name, features] : stateFileSpecs)
|
||||
for (auto [name, features] : stateFileSpecs) {
|
||||
out_WriteState(name, features);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
@@ -13,8 +13,6 @@
|
||||
#include "asm/section.hpp"
|
||||
#include "asm/warning.hpp"
|
||||
|
||||
static constexpr size_t numWarningStates = sizeof(warningStates);
|
||||
|
||||
struct OptStackEntry {
|
||||
char binary[2];
|
||||
char gbgfx[4];
|
||||
@@ -22,7 +20,7 @@ struct OptStackEntry {
|
||||
uint8_t fillByte;
|
||||
bool warningsAreErrors;
|
||||
size_t maxRecursionDepth;
|
||||
WarningState warningStates[numWarningStates];
|
||||
Diagnostics warningStates;
|
||||
};
|
||||
|
||||
static std::stack<OptStackEntry> stack;
|
||||
@@ -55,17 +53,19 @@ void opt_W(char const *flag) {
|
||||
void opt_Parse(char const *s) {
|
||||
switch (s[0]) {
|
||||
case 'b':
|
||||
if (strlen(&s[1]) == 2)
|
||||
if (strlen(&s[1]) == 2) {
|
||||
opt_B(&s[1]);
|
||||
else
|
||||
} else {
|
||||
error("Must specify exactly 2 characters for option 'b'\n");
|
||||
}
|
||||
break;
|
||||
|
||||
case 'g':
|
||||
if (strlen(&s[1]) == 4)
|
||||
if (strlen(&s[1]) == 4) {
|
||||
opt_G(&s[1]);
|
||||
else
|
||||
} else {
|
||||
error("Must specify exactly 4 characters for option 'g'\n");
|
||||
}
|
||||
break;
|
||||
|
||||
case 'p':
|
||||
@@ -74,12 +74,13 @@ void opt_Parse(char const *s) {
|
||||
unsigned int padByte;
|
||||
|
||||
result = sscanf(&s[1], "%x", &padByte);
|
||||
if (result != 1)
|
||||
if (result != 1) {
|
||||
error("Invalid argument for option 'p'\n");
|
||||
else if (padByte > 0xFF)
|
||||
} else if (padByte > 0xFF) {
|
||||
error("Argument for option 'p' must be between 0 and 0xFF\n");
|
||||
else
|
||||
} else {
|
||||
opt_P(padByte);
|
||||
}
|
||||
} else {
|
||||
error("Invalid argument for option 'p'\n");
|
||||
}
|
||||
@@ -88,19 +89,21 @@ void opt_Parse(char const *s) {
|
||||
char const *precisionArg;
|
||||
case 'Q':
|
||||
precisionArg = &s[1];
|
||||
if (precisionArg[0] == '.')
|
||||
if (precisionArg[0] == '.') {
|
||||
precisionArg++;
|
||||
}
|
||||
if (strlen(precisionArg) <= 2) {
|
||||
int result;
|
||||
unsigned int precision;
|
||||
|
||||
result = sscanf(precisionArg, "%u", &precision);
|
||||
if (result != 1)
|
||||
if (result != 1) {
|
||||
error("Invalid argument for option 'Q'\n");
|
||||
else if (precision < 1 || precision > 31)
|
||||
} else if (precision < 1 || precision > 31) {
|
||||
error("Argument for option 'Q' must be between 1 and 31\n");
|
||||
else
|
||||
} else {
|
||||
opt_Q(precision);
|
||||
}
|
||||
} else {
|
||||
error("Invalid argument for option 'Q'\n");
|
||||
}
|
||||
@@ -108,8 +111,9 @@ void opt_Parse(char const *s) {
|
||||
|
||||
case 'r': {
|
||||
++s; // Skip 'r'
|
||||
while (isblank(*s))
|
||||
while (isblank(*s)) {
|
||||
++s; // Skip leading whitespace
|
||||
}
|
||||
|
||||
if (s[0] == '\0') {
|
||||
error("Missing argument to option 'r'\n");
|
||||
@@ -130,10 +134,11 @@ void opt_Parse(char const *s) {
|
||||
}
|
||||
|
||||
case 'W':
|
||||
if (strlen(&s[1]) > 0)
|
||||
if (strlen(&s[1]) > 0) {
|
||||
opt_W(&s[1]);
|
||||
else
|
||||
} else {
|
||||
error("Must specify an argument for option 'W'\n");
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -160,7 +165,7 @@ void opt_Push() {
|
||||
|
||||
// Both of these pulled from warning.hpp
|
||||
entry.warningsAreErrors = warningsAreErrors;
|
||||
memcpy(entry.warningStates, warningStates, numWarningStates);
|
||||
entry.warningStates = warningStates;
|
||||
|
||||
entry.maxRecursionDepth = maxRecursionDepth; // Pulled from fstack.h
|
||||
|
||||
@@ -184,5 +189,11 @@ void opt_Pop() {
|
||||
|
||||
// opt_W does not apply a whole warning state; it processes one flag string
|
||||
warningsAreErrors = entry.warningsAreErrors;
|
||||
memcpy(warningStates, entry.warningStates, numWarningStates);
|
||||
warningStates = entry.warningStates;
|
||||
}
|
||||
|
||||
void opt_CheckStack() {
|
||||
if (!stack.empty()) {
|
||||
warning(WARNING_UNMATCHED_DIRECTIVE, "`PUSHO` without corresponding `POPO`\n");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "asm/output.hpp"
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
|
||||
#include "error.hpp"
|
||||
#include "helpers.hpp" // assume, Defer
|
||||
#include "platform.hpp"
|
||||
|
||||
#include "asm/charmap.hpp"
|
||||
#include "asm/fstack.hpp"
|
||||
@@ -39,10 +40,10 @@ static std::deque<std::shared_ptr<FileStackNode>> fileStackNodes;
|
||||
|
||||
static void putLong(uint32_t n, FILE *file) {
|
||||
uint8_t bytes[] = {
|
||||
(uint8_t)n,
|
||||
(uint8_t)(n >> 8),
|
||||
(uint8_t)(n >> 16),
|
||||
(uint8_t)(n >> 24),
|
||||
static_cast<uint8_t>(n),
|
||||
static_cast<uint8_t>(n >> 8),
|
||||
static_cast<uint8_t>(n >> 16),
|
||||
static_cast<uint8_t>(n >> 24),
|
||||
};
|
||||
fwrite(bytes, 1, sizeof(bytes), file);
|
||||
}
|
||||
@@ -54,25 +55,27 @@ static void putString(std::string const &s, FILE *file) {
|
||||
|
||||
void out_RegisterNode(std::shared_ptr<FileStackNode> node) {
|
||||
// If node is not already registered, register it (and parents), and give it a unique ID
|
||||
for (; node && node->ID == (uint32_t)-1; node = node->parent) {
|
||||
for (; node && node->ID == UINT32_MAX; node = node->parent) {
|
||||
node->ID = fileStackNodes.size();
|
||||
fileStackNodes.push_front(node);
|
||||
}
|
||||
}
|
||||
|
||||
// Return a section's ID, or -1 if the section is not in the list
|
||||
// Return a section's ID, or UINT32_MAX if the section is not in the list
|
||||
static uint32_t getSectIDIfAny(Section *sect) {
|
||||
if (!sect)
|
||||
return (uint32_t)-1;
|
||||
if (!sect) {
|
||||
return UINT32_MAX;
|
||||
}
|
||||
|
||||
if (auto search = sectionMap.find(sect->name); search != sectionMap.end())
|
||||
return (uint32_t)(sectionMap.size() - search->second - 1);
|
||||
if (auto search = sectionMap.find(sect->name); search != sectionMap.end()) {
|
||||
return static_cast<uint32_t>(search->second);
|
||||
}
|
||||
|
||||
fatalerror("Unknown section '%s'\n", sect->name.c_str());
|
||||
}
|
||||
|
||||
static void writePatch(Patch const &patch, FILE *file) {
|
||||
assume(patch.src->ID != (uint32_t)-1);
|
||||
assume(patch.src->ID != UINT32_MAX);
|
||||
|
||||
putLong(patch.src->ID, file);
|
||||
putLong(patch.lineNo, file);
|
||||
@@ -85,7 +88,7 @@ static void writePatch(Patch const &patch, FILE *file) {
|
||||
}
|
||||
|
||||
static void writeSection(Section const §, FILE *file) {
|
||||
assume(sect.src->ID != (uint32_t)-1);
|
||||
assume(sect.src->ID != UINT32_MAX);
|
||||
|
||||
putString(sect.name, file);
|
||||
|
||||
@@ -108,8 +111,9 @@ static void writeSection(Section const §, FILE *file) {
|
||||
fwrite(sect.data.data(), 1, sect.size, file);
|
||||
putLong(sect.patches.size(), file);
|
||||
|
||||
for (Patch const &patch : sect.patches)
|
||||
for (Patch const &patch : sect.patches) {
|
||||
writePatch(patch, file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,7 +122,7 @@ static void writeSymbol(Symbol const &sym, FILE *file) {
|
||||
if (!sym.isDefined()) {
|
||||
putc(SYMTYPE_IMPORT, file);
|
||||
} else {
|
||||
assume(sym.src->ID != (uint32_t)-1);
|
||||
assume(sym.src->ID != UINT32_MAX);
|
||||
|
||||
putc(sym.isExported ? SYMTYPE_EXPORT : SYMTYPE_LOCAL, file);
|
||||
putLong(sym.src->ID, file);
|
||||
@@ -130,7 +134,7 @@ static void writeSymbol(Symbol const &sym, FILE *file) {
|
||||
|
||||
static void registerUnregisteredSymbol(Symbol &sym) {
|
||||
// Check for `sym.src`, to skip any built-in symbol from rgbasm
|
||||
if (sym.src && sym.ID == (uint32_t)-1 && !sym_IsPC(&sym)) {
|
||||
if (sym.src && sym.ID == UINT32_MAX && !sym_IsPC(&sym)) {
|
||||
sym.ID = objectSymbols.size(); // Set the symbol's ID within the object file
|
||||
objectSymbols.push_back(&sym);
|
||||
out_RegisterNode(sym.src);
|
||||
@@ -161,8 +165,9 @@ static void writeRpn(std::vector<uint8_t> &rpnexpr, std::vector<uint8_t> const &
|
||||
symName.clear();
|
||||
for (;;) {
|
||||
uint8_t c = rpn[offset++];
|
||||
if (c == 0)
|
||||
if (c == 0) {
|
||||
break;
|
||||
}
|
||||
symName += c;
|
||||
}
|
||||
|
||||
@@ -187,8 +192,9 @@ static void writeRpn(std::vector<uint8_t> &rpnexpr, std::vector<uint8_t> const &
|
||||
symName.clear();
|
||||
for (;;) {
|
||||
uint8_t c = rpn[offset++];
|
||||
if (c == 0)
|
||||
if (c == 0) {
|
||||
break;
|
||||
}
|
||||
symName += c;
|
||||
}
|
||||
|
||||
@@ -281,13 +287,13 @@ void out_CreateAssert(
|
||||
assertion.message = message;
|
||||
}
|
||||
|
||||
static void writeAssert(Assertion &assert, FILE *file) {
|
||||
static void writeAssert(Assertion const &assert, FILE *file) {
|
||||
writePatch(assert.patch, file);
|
||||
putString(assert.message, file);
|
||||
}
|
||||
|
||||
static void writeFileStackNode(FileStackNode const &node, FILE *file) {
|
||||
putLong(node.parent ? node.parent->ID : (uint32_t)-1, file);
|
||||
putLong(node.parent ? node.parent->ID : UINT32_MAX, file);
|
||||
putLong(node.lineNo, file);
|
||||
putc(node.type, file);
|
||||
if (node.type != NODE_REPT) {
|
||||
@@ -297,30 +303,34 @@ static void writeFileStackNode(FileStackNode const &node, FILE *file) {
|
||||
|
||||
putLong(nodeIters.size(), file);
|
||||
// Iters are stored by decreasing depth, so reverse the order for output
|
||||
for (uint32_t i = nodeIters.size(); i--;)
|
||||
for (uint32_t i = nodeIters.size(); i--;) {
|
||||
putLong(nodeIters[i], file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void out_WriteObject() {
|
||||
if (objectFileName.empty())
|
||||
if (objectFileName.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
FILE *file;
|
||||
if (objectFileName != "-") {
|
||||
file = fopen(objectFileName.c_str(), "wb");
|
||||
} else {
|
||||
objectFileName = "<stdout>";
|
||||
file = fdopen(STDOUT_FILENO, "wb");
|
||||
(void)setmode(STDOUT_FILENO, O_BINARY);
|
||||
file = stdout;
|
||||
}
|
||||
if (!file)
|
||||
if (!file) {
|
||||
err("Failed to open object file '%s'", objectFileName.c_str());
|
||||
}
|
||||
Defer closeFile{[&] { fclose(file); }};
|
||||
|
||||
// Also write symbols that weren't written above
|
||||
sym_ForEach(registerUnregisteredSymbol);
|
||||
|
||||
fprintf(file, RGBDS_OBJECT_VERSION_STRING);
|
||||
fputs(RGBDS_OBJECT_VERSION_STRING, file);
|
||||
putLong(RGBDS_OBJECT_REV, file);
|
||||
|
||||
putLong(objectSymbols.size(), file);
|
||||
@@ -333,33 +343,39 @@ void out_WriteObject() {
|
||||
writeFileStackNode(node, file);
|
||||
|
||||
// The list is supposed to have decrementing IDs
|
||||
if (it + 1 != fileStackNodes.end() && it[1]->ID != node.ID - 1)
|
||||
if (it + 1 != fileStackNodes.end() && it[1]->ID != node.ID - 1) {
|
||||
fatalerror(
|
||||
"Internal error: fstack node #%" PRIu32 " follows #%" PRIu32
|
||||
". Please report this to the developers!\n",
|
||||
it[1]->ID,
|
||||
node.ID
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
for (Symbol const *sym : objectSymbols)
|
||||
for (Symbol const *sym : objectSymbols) {
|
||||
writeSymbol(*sym, file);
|
||||
}
|
||||
|
||||
for (auto it = sectionList.rbegin(); it != sectionList.rend(); it++)
|
||||
writeSection(*it, file);
|
||||
for (Section const § : sectionList) {
|
||||
writeSection(sect, file);
|
||||
}
|
||||
|
||||
putLong(assertions.size(), file);
|
||||
|
||||
for (Assertion &assert : assertions)
|
||||
for (Assertion const &assert : assertions) {
|
||||
writeAssert(assert, file);
|
||||
}
|
||||
}
|
||||
|
||||
void out_SetFileName(std::string const &name) {
|
||||
if (!objectFileName.empty())
|
||||
if (!objectFileName.empty()) {
|
||||
warnx("Overriding output filename %s", objectFileName.c_str());
|
||||
}
|
||||
objectFileName = name;
|
||||
if (verbose)
|
||||
if (verbose) {
|
||||
printf("Output filename %s\n", objectFileName.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
static void dumpString(std::string const &escape, FILE *file) {
|
||||
@@ -395,8 +411,9 @@ static bool dumpEquConstants(FILE *file) {
|
||||
equConstants.clear();
|
||||
|
||||
sym_ForEach([](Symbol &sym) {
|
||||
if (!sym.isBuiltin && sym.type == SYM_EQU)
|
||||
if (!sym.isBuiltin && sym.type == SYM_EQU) {
|
||||
equConstants.push_back(&sym);
|
||||
}
|
||||
});
|
||||
// Constants are ordered by file, then by definition order
|
||||
std::sort(RANGE(equConstants), [](Symbol *sym1, Symbol *sym2) -> bool {
|
||||
@@ -416,8 +433,9 @@ static bool dumpVariables(FILE *file) {
|
||||
variables.clear();
|
||||
|
||||
sym_ForEach([](Symbol &sym) {
|
||||
if (!sym.isBuiltin && sym.type == SYM_VAR)
|
||||
if (!sym.isBuiltin && sym.type == SYM_VAR) {
|
||||
variables.push_back(&sym);
|
||||
}
|
||||
});
|
||||
// Variables are ordered by file, then by definition order
|
||||
std::sort(RANGE(variables), [](Symbol *sym1, Symbol *sym2) -> bool {
|
||||
@@ -437,8 +455,9 @@ static bool dumpEqusConstants(FILE *file) {
|
||||
equsConstants.clear();
|
||||
|
||||
sym_ForEach([](Symbol &sym) {
|
||||
if (!sym.isBuiltin && sym.type == SYM_EQUS)
|
||||
if (!sym.isBuiltin && sym.type == SYM_EQUS) {
|
||||
equsConstants.push_back(&sym);
|
||||
}
|
||||
});
|
||||
// Constants are ordered by file, then by definition order
|
||||
std::sort(RANGE(equsConstants), [](Symbol *sym1, Symbol *sym2) -> bool {
|
||||
@@ -465,8 +484,9 @@ static bool dumpCharmaps(FILE *file) {
|
||||
fputs("charmap \"", charmapFile);
|
||||
dumpString(mapping, charmapFile);
|
||||
putc('"', charmapFile);
|
||||
for (int32_t v : value)
|
||||
for (int32_t v : value) {
|
||||
fprintf(charmapFile, ", $%" PRIx32, v);
|
||||
}
|
||||
putc('\n', charmapFile);
|
||||
}
|
||||
);
|
||||
@@ -477,8 +497,9 @@ static bool dumpMacros(FILE *file) {
|
||||
macros.clear();
|
||||
|
||||
sym_ForEach([](Symbol &sym) {
|
||||
if (!sym.isBuiltin && sym.type == SYM_MACRO)
|
||||
if (!sym.isBuiltin && sym.type == SYM_MACRO) {
|
||||
macros.push_back(&sym);
|
||||
}
|
||||
});
|
||||
// Macros are ordered by file, then by definition order
|
||||
std::sort(RANGE(macros), [](Symbol *sym1, Symbol *sym2) -> bool {
|
||||
@@ -503,10 +524,12 @@ void out_WriteState(std::string name, std::vector<StateFeature> const &features)
|
||||
file = fopen(name.c_str(), "wb");
|
||||
} else {
|
||||
name = "<stdout>";
|
||||
file = fdopen(STDOUT_FILENO, "wb");
|
||||
(void)setmode(STDOUT_FILENO, O_BINARY);
|
||||
file = stdout;
|
||||
}
|
||||
if (!file)
|
||||
if (!file) {
|
||||
err("Failed to open state file '%s'", name.c_str());
|
||||
}
|
||||
Defer closeFile{[&] { fclose(file); }};
|
||||
|
||||
static char const *dumpHeadings[NB_STATE_FEATURES] = {
|
||||
@@ -527,7 +550,8 @@ void out_WriteState(std::string name, std::vector<StateFeature> const &features)
|
||||
fputs("; File generated by rgbasm\n", file);
|
||||
for (StateFeature feature : features) {
|
||||
fprintf(file, "\n; %s\n", dumpHeadings[feature]);
|
||||
if (!dumpFuncs[feature](file))
|
||||
if (!dumpFuncs[feature](file)) {
|
||||
fprintf(file, "; No values\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
829
src/asm/parser.y
829
src/asm/parser.y
File diff suppressed because it is too large
Load Diff
174
src/asm/rpn.cpp
174
src/asm/rpn.cpp
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "asm/rpn.hpp"
|
||||
|
||||
@@ -46,17 +46,19 @@ int32_t Expression::getConstVal() const {
|
||||
}
|
||||
|
||||
Symbol const *Expression::symbolOf() const {
|
||||
if (!isSymbol)
|
||||
if (!isSymbol) {
|
||||
return nullptr;
|
||||
return sym_FindScopedSymbol((char const *)&rpn[1]);
|
||||
}
|
||||
return sym_FindScopedSymbol(reinterpret_cast<char const *>(&rpn[1]));
|
||||
}
|
||||
|
||||
bool Expression::isDiffConstant(Symbol const *sym) const {
|
||||
// Check if both expressions only refer to a single symbol
|
||||
Symbol const *sym1 = symbolOf();
|
||||
|
||||
if (!sym1 || !sym || sym1->type != SYM_LABEL || sym->type != SYM_LABEL)
|
||||
if (!sym1 || !sym || sym1->type != SYM_LABEL || sym->type != SYM_LABEL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Section const *sect1 = sym1->getSection();
|
||||
Section const *sect2 = sym->getSection();
|
||||
@@ -65,7 +67,7 @@ bool Expression::isDiffConstant(Symbol const *sym) const {
|
||||
|
||||
void Expression::makeNumber(uint32_t value) {
|
||||
clear();
|
||||
data = (int32_t)value;
|
||||
data = static_cast<int32_t>(value);
|
||||
}
|
||||
|
||||
void Expression::makeSymbol(std::string const &symName) {
|
||||
@@ -77,9 +79,9 @@ void Expression::makeSymbol(std::string const &symName) {
|
||||
isSymbol = true;
|
||||
|
||||
data = sym_IsPC(sym) ? "PC is not constant at assembly time"
|
||||
: sym_IsPurgedScoped(symName)
|
||||
? "'"s + symName + "' is not constant at assembly time; it was purged"
|
||||
: "'"s + symName + "' is not constant at assembly time";
|
||||
: sym_IsPurgedScoped(symName)
|
||||
? "'"s + symName + "' is not constant at assembly time; it was purged"
|
||||
: "'"s + symName + "' is not constant at assembly time";
|
||||
sym = sym_Ref(symName);
|
||||
|
||||
size_t nameLen = sym->name.length() + 1; // Don't forget NUL!
|
||||
@@ -89,7 +91,7 @@ void Expression::makeSymbol(std::string const &symName) {
|
||||
*ptr++ = RPN_SYM;
|
||||
memcpy(ptr, sym->name.c_str(), nameLen);
|
||||
} else {
|
||||
data = (int32_t)sym_GetConstantValue(symName);
|
||||
data = static_cast<int32_t>(sym_GetConstantValue(symName));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,12 +102,12 @@ void Expression::makeBankSymbol(std::string const &symName) {
|
||||
if (!currentSection) {
|
||||
error("PC has no bank outside of a section\n");
|
||||
data = 1;
|
||||
} else if (currentSection->bank == (uint32_t)-1) {
|
||||
} else if (currentSection->bank == UINT32_MAX) {
|
||||
data = "Current section's bank is not known";
|
||||
|
||||
*reserveSpace(1) = RPN_BANK_SELF;
|
||||
} else {
|
||||
data = (int32_t)currentSection->bank;
|
||||
data = static_cast<int32_t>(currentSection->bank);
|
||||
}
|
||||
return;
|
||||
} else if (sym && !sym->isLabel()) {
|
||||
@@ -115,13 +117,13 @@ void Expression::makeBankSymbol(std::string const &symName) {
|
||||
sym = sym_Ref(symName);
|
||||
assume(sym); // If the symbol didn't exist, it should have been created
|
||||
|
||||
if (sym->getSection() && sym->getSection()->bank != (uint32_t)-1) {
|
||||
if (sym->getSection() && sym->getSection()->bank != UINT32_MAX) {
|
||||
// Symbol's section is known and bank is fixed
|
||||
data = (int32_t)sym->getSection()->bank;
|
||||
data = static_cast<int32_t>(sym->getSection()->bank);
|
||||
} else {
|
||||
data = sym_IsPurgedScoped(symName)
|
||||
? "\""s + symName + "\"'s bank is not known; it was purged"
|
||||
: "\""s + symName + "\"'s bank is not known";
|
||||
? "\""s + symName + "\"'s bank is not known; it was purged"
|
||||
: "\""s + symName + "\"'s bank is not known";
|
||||
|
||||
size_t nameLen = sym->name.length() + 1; // Room for NUL!
|
||||
|
||||
@@ -135,8 +137,8 @@ void Expression::makeBankSymbol(std::string const &symName) {
|
||||
|
||||
void Expression::makeBankSection(std::string const §Name) {
|
||||
clear();
|
||||
if (Section *sect = sect_FindSectionByName(sectName); sect && sect->bank != (uint32_t)-1) {
|
||||
data = (int32_t)sect->bank;
|
||||
if (Section *sect = sect_FindSectionByName(sectName); sect && sect->bank != UINT32_MAX) {
|
||||
data = static_cast<int32_t>(sect->bank);
|
||||
} else {
|
||||
data = "Section \""s + sectName + "\"'s bank is not known";
|
||||
|
||||
@@ -151,7 +153,7 @@ void Expression::makeBankSection(std::string const §Name) {
|
||||
void Expression::makeSizeOfSection(std::string const §Name) {
|
||||
clear();
|
||||
if (Section *sect = sect_FindSectionByName(sectName); sect && sect->isSizeKnown()) {
|
||||
data = (int32_t)sect->size;
|
||||
data = static_cast<int32_t>(sect->size);
|
||||
} else {
|
||||
data = "Section \""s + sectName + "\"'s size is not known";
|
||||
|
||||
@@ -165,8 +167,8 @@ void Expression::makeSizeOfSection(std::string const §Name) {
|
||||
|
||||
void Expression::makeStartOfSection(std::string const §Name) {
|
||||
clear();
|
||||
if (Section *sect = sect_FindSectionByName(sectName); sect && sect->org != (uint32_t)-1) {
|
||||
data = (int32_t)sect->org;
|
||||
if (Section *sect = sect_FindSectionByName(sectName); sect && sect->org != UINT32_MAX) {
|
||||
data = static_cast<int32_t>(sect->org);
|
||||
} else {
|
||||
data = "Section \""s + sectName + "\"'s start is not known";
|
||||
|
||||
@@ -208,71 +210,68 @@ static bool tryConstNonzero(Expression const &lhs, Expression const &rhs) {
|
||||
|
||||
static bool tryConstLogNot(Expression const &expr) {
|
||||
Symbol const *sym = expr.symbolOf();
|
||||
if (!sym || !sym->getSection() || !sym->isDefined())
|
||||
if (!sym || !sym->getSection() || !sym->isDefined()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
assume(sym->isNumeric());
|
||||
|
||||
Section const § = *sym->getSection();
|
||||
int32_t unknownBits = (1 << 16) - (1 << sect.align);
|
||||
|
||||
// `sym->getValue()` attempts to add the section's address, but that's "-1"
|
||||
// `sym->getValue()` attempts to add the section's address, but that's `UINT32_MAX`
|
||||
// because the section is floating (otherwise we wouldn't be here)
|
||||
assume(sect.org == (uint32_t)-1);
|
||||
assume(sect.org == UINT32_MAX);
|
||||
int32_t symbolOfs = sym->getValue() + 1;
|
||||
|
||||
int32_t knownBits = (symbolOfs + sect.alignOfs) & ~unknownBits;
|
||||
return knownBits != 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Attempts to compute a constant LOW() from non-constant argument
|
||||
* This is possible if the argument is a symbol belonging to an `ALIGN[8]` section.
|
||||
*
|
||||
* @return The constant `LOW(expr)` result if it can be computed, or -1 otherwise.
|
||||
*/
|
||||
// Returns a constant LOW() from non-constant argument, or -1 if it cannot be computed.
|
||||
// This is possible if the argument is a symbol belonging to an `ALIGN[8]` section.
|
||||
static int32_t tryConstLow(Expression const &expr) {
|
||||
Symbol const *sym = expr.symbolOf();
|
||||
if (!sym || !sym->getSection() || !sym->isDefined())
|
||||
if (!sym || !sym->getSection() || !sym->isDefined()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
assume(sym->isNumeric());
|
||||
|
||||
// The low byte must not cover any unknown bits
|
||||
Section const § = *sym->getSection();
|
||||
if (sect.align < 8)
|
||||
if (sect.align < 8) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// `sym->getValue()` attempts to add the section's address, but that's "-1"
|
||||
// `sym->getValue()` attempts to add the section's address, but that's `UINT32_MAX`
|
||||
// because the section is floating (otherwise we wouldn't be here)
|
||||
assume(sect.org == (uint32_t)-1);
|
||||
assume(sect.org == UINT32_MAX);
|
||||
int32_t symbolOfs = sym->getValue() + 1;
|
||||
|
||||
return (symbolOfs + sect.alignOfs) & 0xFF;
|
||||
}
|
||||
|
||||
/*
|
||||
* Attempts to compute a constant binary AND with one non-constant operands
|
||||
* This is possible if one operand is a symbol belonging to an `ALIGN[N]` section, and the other is
|
||||
* a constant that only keeps (some of) the lower N bits.
|
||||
*
|
||||
* @return The constant `lhs & rhs` result if it can be computed, or -1 otherwise.
|
||||
*/
|
||||
// Returns a constant binary AND with one non-constant operand, or -1 if it cannot be computed.
|
||||
// This is possible if one operand is a symbol belonging to an `ALIGN[N]` section, and the other is
|
||||
// a constant that only keeps (some of) the lower N bits.
|
||||
static int32_t tryConstMask(Expression const &lhs, Expression const &rhs) {
|
||||
Symbol const *lhsSymbol = lhs.symbolOf();
|
||||
Symbol const *rhsSymbol = lhsSymbol ? nullptr : rhs.symbolOf();
|
||||
bool lhsIsSymbol = lhsSymbol && lhsSymbol->getSection();
|
||||
bool rhsIsSymbol = rhsSymbol && rhsSymbol->getSection();
|
||||
|
||||
if (!lhsIsSymbol && !rhsIsSymbol)
|
||||
if (!lhsIsSymbol && !rhsIsSymbol) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// If the lhs isn't a symbol, try again the other way around
|
||||
Symbol const &sym = lhsIsSymbol ? *lhsSymbol : *rhsSymbol;
|
||||
Expression const &expr = lhsIsSymbol ? rhs : lhs; // Opposite side of `sym`
|
||||
|
||||
if (!sym.isDefined() || !expr.isKnown())
|
||||
if (!sym.isDefined() || !expr.isKnown()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
assume(sym.isNumeric());
|
||||
|
||||
@@ -281,12 +280,13 @@ static int32_t tryConstMask(Expression const &lhs, Expression const &rhs) {
|
||||
|
||||
// The mask must not cover any unknown bits
|
||||
Section const § = *sym.getSection();
|
||||
if (int32_t unknownBits = (1 << 16) - (1 << sect.align); (unknownBits & mask) != 0)
|
||||
if (int32_t unknownBits = (1 << 16) - (1 << sect.align); (unknownBits & mask) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// `sym.getValue()` attempts to add the section's address, but that's "-1"
|
||||
// `sym.getValue()` attempts to add the section's address, but that's `UINT32_MAX`
|
||||
// because the section is floating (otherwise we wouldn't be here)
|
||||
assume(sect.org == (uint32_t)-1);
|
||||
assume(sect.org == UINT32_MAX);
|
||||
int32_t symbolOfs = sym.getValue() + 1;
|
||||
|
||||
return (symbolOfs + sect.alignOfs) & mask;
|
||||
@@ -298,10 +298,11 @@ void Expression::makeUnaryOp(RPNCommand op, Expression &&src) {
|
||||
if (src.isKnown()) {
|
||||
// If the expressions is known, just compute the value
|
||||
int32_t val = src.value();
|
||||
uint32_t uval = static_cast<uint32_t>(val);
|
||||
|
||||
switch (op) {
|
||||
case RPN_NEG:
|
||||
data = (int32_t) - (uint32_t)val;
|
||||
data = static_cast<int32_t>(-uval);
|
||||
break;
|
||||
case RPN_NOT:
|
||||
data = ~val;
|
||||
@@ -310,16 +311,16 @@ void Expression::makeUnaryOp(RPNCommand op, Expression &&src) {
|
||||
data = !val;
|
||||
break;
|
||||
case RPN_HIGH:
|
||||
data = (int32_t)((uint32_t)val >> 8 & 0xFF);
|
||||
data = static_cast<int32_t>(uval >> 8 & 0xFF);
|
||||
break;
|
||||
case RPN_LOW:
|
||||
data = val & 0xFF;
|
||||
break;
|
||||
case RPN_BITWIDTH:
|
||||
data = val != 0 ? 32 - clz((uint32_t)val) : 0;
|
||||
data = val != 0 ? 32 - clz(uval) : 0;
|
||||
break;
|
||||
case RPN_TZCOUNT:
|
||||
data = val != 0 ? ctz((uint32_t)val) : 32;
|
||||
data = val != 0 ? ctz(uval) : 32;
|
||||
break;
|
||||
|
||||
case RPN_LOGOR:
|
||||
@@ -374,6 +375,7 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const
|
||||
if (src1.isKnown() && src2.isKnown()) {
|
||||
// If both expressions are known, just compute the value
|
||||
int32_t lval = src1.value(), rval = src2.value();
|
||||
uint32_t ulval = static_cast<uint32_t>(lval), urval = static_cast<uint32_t>(rval);
|
||||
|
||||
switch (op) {
|
||||
case RPN_LOGOR:
|
||||
@@ -401,10 +403,10 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const
|
||||
data = lval != rval;
|
||||
break;
|
||||
case RPN_ADD:
|
||||
data = (int32_t)((uint32_t)lval + (uint32_t)rval);
|
||||
data = static_cast<int32_t>(ulval + urval);
|
||||
break;
|
||||
case RPN_SUB:
|
||||
data = (int32_t)((uint32_t)lval - (uint32_t)rval);
|
||||
data = static_cast<int32_t>(ulval - urval);
|
||||
break;
|
||||
case RPN_XOR:
|
||||
data = lval ^ rval;
|
||||
@@ -416,47 +418,55 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const
|
||||
data = lval & rval;
|
||||
break;
|
||||
case RPN_SHL:
|
||||
if (rval < 0)
|
||||
if (rval < 0) {
|
||||
warning(
|
||||
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);
|
||||
}
|
||||
|
||||
data = op_shift_left(lval, rval);
|
||||
break;
|
||||
case RPN_SHR:
|
||||
if (lval < 0)
|
||||
if (lval < 0) {
|
||||
warning(WARNING_SHIFT, "Shifting right negative value %" PRId32 "\n", lval);
|
||||
}
|
||||
|
||||
if (rval < 0)
|
||||
if (rval < 0) {
|
||||
warning(
|
||||
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);
|
||||
}
|
||||
|
||||
data = op_shift_right(lval, rval);
|
||||
break;
|
||||
case RPN_USHR:
|
||||
if (rval < 0)
|
||||
if (rval < 0) {
|
||||
warning(
|
||||
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);
|
||||
}
|
||||
|
||||
data = op_shift_right_unsigned(lval, rval);
|
||||
break;
|
||||
case RPN_MUL:
|
||||
data = (int32_t)((uint32_t)lval * (uint32_t)rval);
|
||||
data = static_cast<int32_t>(ulval * urval);
|
||||
break;
|
||||
case RPN_DIV:
|
||||
if (rval == 0)
|
||||
if (rval == 0) {
|
||||
fatalerror("Division by zero\n");
|
||||
}
|
||||
|
||||
if (lval == INT32_MIN && rval == -1) {
|
||||
warning(
|
||||
@@ -471,17 +481,20 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const
|
||||
}
|
||||
break;
|
||||
case RPN_MOD:
|
||||
if (rval == 0)
|
||||
if (rval == 0) {
|
||||
fatalerror("Modulo by zero\n");
|
||||
}
|
||||
|
||||
if (lval == INT32_MIN && rval == -1)
|
||||
if (lval == INT32_MIN && rval == -1) {
|
||||
data = 0;
|
||||
else
|
||||
} else {
|
||||
data = op_modulo(lval, rval);
|
||||
}
|
||||
break;
|
||||
case RPN_EXP:
|
||||
if (rval < 0)
|
||||
if (rval < 0) {
|
||||
fatalerror("Exponentiation by negative power\n");
|
||||
}
|
||||
|
||||
data = op_exponent(lval, rval);
|
||||
break;
|
||||
@@ -522,10 +535,10 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const
|
||||
uint32_t lval = src1.value();
|
||||
uint8_t bytes[] = {
|
||||
RPN_CONST,
|
||||
(uint8_t)lval,
|
||||
(uint8_t)(lval >> 8),
|
||||
(uint8_t)(lval >> 16),
|
||||
(uint8_t)(lval >> 24),
|
||||
static_cast<uint8_t>(lval),
|
||||
static_cast<uint8_t>(lval >> 8),
|
||||
static_cast<uint8_t>(lval >> 16),
|
||||
static_cast<uint8_t>(lval >> 24),
|
||||
};
|
||||
rpn.clear();
|
||||
rpnPatchSize = 0;
|
||||
@@ -546,10 +559,10 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const
|
||||
uint32_t rval = src2.value();
|
||||
uint8_t bytes[] = {
|
||||
RPN_CONST,
|
||||
(uint8_t)rval,
|
||||
(uint8_t)(rval >> 8),
|
||||
(uint8_t)(rval >> 16),
|
||||
(uint8_t)(rval >> 24),
|
||||
static_cast<uint8_t>(rval),
|
||||
static_cast<uint8_t>(rval >> 8),
|
||||
static_cast<uint8_t>(rval >> 16),
|
||||
static_cast<uint8_t>(rval >> 24),
|
||||
};
|
||||
uint8_t *ptr = reserveSpace(sizeof(bytes) + 1, sizeof(bytes) + 1);
|
||||
memcpy(ptr, bytes, sizeof(bytes));
|
||||
@@ -558,24 +571,29 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const
|
||||
// Copy the right RPN and append the operator
|
||||
uint32_t rightRpnSize = src2.rpn.size();
|
||||
uint8_t *ptr = reserveSpace(rightRpnSize + 1, src2.rpnPatchSize + 1);
|
||||
if (rightRpnSize > 0)
|
||||
if (rightRpnSize > 0) {
|
||||
// If `rightRpnSize == 0`, then `memcpy(ptr, nullptr, rightRpnSize)` would be UB
|
||||
memcpy(ptr, src2.rpn.data(), rightRpnSize);
|
||||
}
|
||||
ptr[rightRpnSize] = op;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Expression::makeCheckHRAM() {
|
||||
bool Expression::makeCheckHRAM() {
|
||||
isSymbol = false;
|
||||
if (!isKnown()) {
|
||||
*reserveSpace(1) = RPN_HRAM;
|
||||
} else if (int32_t val = value(); val >= 0xFF00 && val <= 0xFFFF) {
|
||||
// That range is valid, but only keep the lower byte
|
||||
data = val & 0xFF;
|
||||
} else if (val < 0 || val > 0xFF) {
|
||||
} else if (val >= 0 && val <= 0xFF) {
|
||||
// That range is valid, but deprecated
|
||||
return true;
|
||||
} else {
|
||||
error("Source address $%" PRIx32 " not between $FF00 to $FFFF\n", val);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Expression::makeCheckRST() {
|
||||
@@ -584,16 +602,14 @@ void Expression::makeCheckRST() {
|
||||
} else if (int32_t val = value(); val & ~0x38) {
|
||||
// A valid RST address must be masked with 0x38
|
||||
error("Invalid address $%" PRIx32 " for RST\n", val);
|
||||
} else {
|
||||
// The target is in the "0x38" bits, all other bits are set
|
||||
data = val | 0xC7;
|
||||
}
|
||||
}
|
||||
|
||||
// Checks that an RPN expression's value fits within N bits (signed or unsigned)
|
||||
void Expression::checkNBit(uint8_t n) const {
|
||||
if (isKnown())
|
||||
if (isKnown()) {
|
||||
::checkNBit(value(), n, "Expression");
|
||||
}
|
||||
}
|
||||
|
||||
bool checkNBit(int32_t v, uint8_t n, char const *name) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "asm/section.hpp"
|
||||
|
||||
@@ -49,9 +49,11 @@ static std::pair<Symbol const *, Symbol const *> currentLoadLabelScopes = {nullp
|
||||
int32_t loadOffset; // Offset into the LOAD section's parent (see sect_GetOutputOffset)
|
||||
|
||||
// A quick check to see if we have an initialized section
|
||||
[[nodiscard]] static bool requireSection() {
|
||||
if (currentSection)
|
||||
[[nodiscard]]
|
||||
static bool requireSection() {
|
||||
if (currentSection) {
|
||||
return true;
|
||||
}
|
||||
|
||||
error("Cannot output data outside of a SECTION\n");
|
||||
return false;
|
||||
@@ -59,12 +61,15 @@ int32_t loadOffset; // Offset into the LOAD section's parent (see sect_GetOutput
|
||||
|
||||
// A quick check to see if we have an initialized section that can contain
|
||||
// this much initialized data
|
||||
[[nodiscard]] static bool requireCodeSection() {
|
||||
if (!requireSection())
|
||||
[[nodiscard]]
|
||||
static bool requireCodeSection() {
|
||||
if (!requireSection()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sect_HasData(currentSection->type))
|
||||
if (sect_HasData(currentSection->type)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
error(
|
||||
"Section '%s' cannot contain code or data (not ROM0 or ROMX)\n",
|
||||
@@ -75,14 +80,15 @@ int32_t loadOffset; // Offset into the LOAD section's parent (see sect_GetOutput
|
||||
|
||||
void sect_CheckSizes() {
|
||||
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(
|
||||
"Section '%s' grew too big (max size = 0x%" PRIX32 " bytes, reached 0x%" PRIX32
|
||||
").\n",
|
||||
")\n",
|
||||
sect.name.c_str(),
|
||||
maxSize,
|
||||
sect.size
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,33 +112,36 @@ static unsigned int mergeSectUnion(
|
||||
|
||||
// Unionized sections only need "compatible" constraints, and they end up with the strictest
|
||||
// combination of both.
|
||||
if (sect_HasData(type))
|
||||
if (sect_HasData(type)) {
|
||||
sectError("Cannot declare ROM sections as UNION\n");
|
||||
}
|
||||
|
||||
if (org != (uint32_t)-1) {
|
||||
if (org != UINT32_MAX) {
|
||||
// If both are fixed, they must be the same
|
||||
if (sect.org != (uint32_t)-1 && sect.org != org)
|
||||
if (sect.org != UINT32_MAX && sect.org != org) {
|
||||
sectError(
|
||||
"Section already declared as fixed at different address $%04" PRIx32 "\n", 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(
|
||||
"Section already declared as aligned to %u bytes (offset %" PRIu16 ")\n",
|
||||
1U << sect.align,
|
||||
sect.alignOfs
|
||||
);
|
||||
else
|
||||
} else {
|
||||
// Otherwise, just override
|
||||
sect.org = org;
|
||||
}
|
||||
|
||||
} else if (alignment != 0) {
|
||||
// Make sure any fixed address given is compatible
|
||||
if (sect.org != (uint32_t)-1) {
|
||||
if ((sect.org - alignOffset) & mask(alignment))
|
||||
if (sect.org != UINT32_MAX) {
|
||||
if ((sect.org - alignOffset) & mask(alignment)) {
|
||||
sectError(
|
||||
"Section already declared as fixed at incompatible address $%04" PRIx32 "\n",
|
||||
sect.org
|
||||
);
|
||||
}
|
||||
// Check if alignment offsets are compatible
|
||||
} else if ((alignOffset & mask(sect.align)) != (sect.alignOfs & mask(alignment))) {
|
||||
sectError(
|
||||
@@ -159,38 +168,41 @@ static unsigned int
|
||||
// Fragments only need "compatible" constraints, and they end up with the strictest
|
||||
// combination of both.
|
||||
// The merging is however performed at the *end* of the original section!
|
||||
if (org != (uint32_t)-1) {
|
||||
if (org != UINT32_MAX) {
|
||||
uint16_t curOrg = org - sect.size;
|
||||
|
||||
// If both are fixed, they must be the same
|
||||
if (sect.org != (uint32_t)-1 && sect.org != curOrg)
|
||||
if (sect.org != UINT32_MAX && sect.org != curOrg) {
|
||||
sectError(
|
||||
"Section already declared as fixed at incompatible address $%04" PRIx32 "\n",
|
||||
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(
|
||||
"Section already declared as aligned to %u bytes (offset %" PRIu16 ")\n",
|
||||
1U << sect.align,
|
||||
sect.alignOfs
|
||||
);
|
||||
else
|
||||
} else {
|
||||
// Otherwise, just override
|
||||
sect.org = curOrg;
|
||||
}
|
||||
|
||||
} else if (alignment != 0) {
|
||||
int32_t curOfs = (alignOffset - sect.size) % (1U << alignment);
|
||||
|
||||
if (curOfs < 0)
|
||||
if (curOfs < 0) {
|
||||
curOfs += 1U << alignment;
|
||||
}
|
||||
|
||||
// Make sure any fixed address given is compatible
|
||||
if (sect.org != (uint32_t)-1) {
|
||||
if ((sect.org - curOfs) & mask(alignment))
|
||||
if (sect.org != UINT32_MAX) {
|
||||
if ((sect.org - curOfs) & mask(alignment)) {
|
||||
sectError(
|
||||
"Section already declared as fixed at incompatible address $%04" PRIx32 "\n",
|
||||
sect.org
|
||||
);
|
||||
}
|
||||
// Check if alignment offsets are compatible
|
||||
} else if ((curOfs & mask(sect.align)) != (sect.alignOfs & mask(alignment))) {
|
||||
sectError(
|
||||
@@ -220,13 +232,14 @@ static void mergeSections(
|
||||
) {
|
||||
unsigned int nbSectErrors = 0;
|
||||
|
||||
if (type != sect.type)
|
||||
if (type != sect.type) {
|
||||
sectError(
|
||||
"Section already exists but with type %s\n", sectionTypeInfo[sect.type].name.c_str()
|
||||
);
|
||||
}
|
||||
|
||||
if (sect.modifier != mod) {
|
||||
sectError("Section already declared as %s section\n", sectionModNames[sect.modifier]);
|
||||
sectError("Section already declared as SECTION %s\n", sectionModNames[sect.modifier]);
|
||||
} else {
|
||||
switch (mod) {
|
||||
case SECTION_UNION:
|
||||
@@ -238,11 +251,13 @@ static void mergeSections(
|
||||
// Common checks
|
||||
|
||||
// If the section's bank is unspecified, override it
|
||||
if (sect.bank == (uint32_t)-1)
|
||||
if (sect.bank == UINT32_MAX) {
|
||||
sect.bank = bank;
|
||||
}
|
||||
// If both specify a bank, it must be the same one
|
||||
else if (bank != (uint32_t)-1 && sect.bank != bank)
|
||||
else if (bank != UINT32_MAX && sect.bank != bank) {
|
||||
sectError("Section already declared with different bank %" PRIu32 "\n", sect.bank);
|
||||
}
|
||||
break;
|
||||
|
||||
case SECTION_NORMAL:
|
||||
@@ -253,13 +268,14 @@ static void mergeSections(
|
||||
}
|
||||
}
|
||||
|
||||
if (nbSectErrors)
|
||||
if (nbSectErrors) {
|
||||
fatalerror(
|
||||
"Cannot create section \"%s\" (%u error%s)\n",
|
||||
sect.name.c_str(),
|
||||
nbSectErrors,
|
||||
nbSectErrors == 1 ? "" : "s"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#undef sectError
|
||||
@@ -292,8 +308,9 @@ static Section *createSection(
|
||||
out_RegisterNode(sect.src);
|
||||
|
||||
// It is only needed to allocate memory for ROM sections.
|
||||
if (sect_HasData(type))
|
||||
if (sect_HasData(type)) {
|
||||
sect.data.resize(sectionTypeInfo[type].size);
|
||||
}
|
||||
|
||||
return §
|
||||
}
|
||||
@@ -312,11 +329,12 @@ static Section *getSection(
|
||||
|
||||
// First, validate parameters, and normalize them if applicable
|
||||
|
||||
if (bank != (uint32_t)-1) {
|
||||
if (bank != UINT32_MAX) {
|
||||
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");
|
||||
else if (bank < sectionTypeInfo[type].firstBank || bank > sectionTypeInfo[type].lastBank)
|
||||
} else if (bank < sectionTypeInfo[type].firstBank
|
||||
|| bank > sectionTypeInfo[type].lastBank) {
|
||||
error(
|
||||
"%s bank value $%04" PRIx32 " out of range ($%04" PRIx32 " to $%04" PRIx32 ")\n",
|
||||
sectionTypeInfo[type].name.c_str(),
|
||||
@@ -324,6 +342,7 @@ static Section *getSection(
|
||||
sectionTypeInfo[type].firstBank,
|
||||
sectionTypeInfo[type].lastBank
|
||||
);
|
||||
}
|
||||
} else if (nbbanks(type) == 1) {
|
||||
// If the section type only has a single bank, implicitly force it
|
||||
bank = sectionTypeInfo[type].firstBank;
|
||||
@@ -338,8 +357,8 @@ static Section *getSection(
|
||||
alignOffset = 0;
|
||||
}
|
||||
|
||||
if (org != (uint32_t)-1) {
|
||||
if (org < sectionTypeInfo[type].startAddr || org > endaddr(type))
|
||||
if (org != UINT32_MAX) {
|
||||
if (org < sectionTypeInfo[type].startAddr || org > endaddr(type)) {
|
||||
error(
|
||||
"Section \"%s\"'s fixed address $%04" PRIx32 " is outside of range [$%04" PRIx16
|
||||
"; $%04" PRIx16 "]\n",
|
||||
@@ -348,6 +367,7 @@ static Section *getSection(
|
||||
sectionTypeInfo[type].startAddr,
|
||||
endaddr(type)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (alignment != 0) {
|
||||
@@ -358,9 +378,10 @@ static Section *getSection(
|
||||
// It doesn't make sense to have both alignment and org set
|
||||
uint32_t mask = mask(alignment);
|
||||
|
||||
if (org != (uint32_t)-1) {
|
||||
if ((org - alignOffset) & mask)
|
||||
if (org != UINT32_MAX) {
|
||||
if ((org - alignOffset) & mask) {
|
||||
error("Section \"%s\"'s fixed address doesn't match its alignment\n", name.c_str());
|
||||
}
|
||||
alignment = 0; // Ignore it if it's satisfied
|
||||
} else if (sectionTypeInfo[type].startAddr & mask) {
|
||||
error(
|
||||
@@ -393,25 +414,29 @@ static Section *getSection(
|
||||
|
||||
// Set the current section
|
||||
static void changeSection() {
|
||||
if (!currentUnionStack.empty())
|
||||
if (!currentUnionStack.empty()) {
|
||||
fatalerror("Cannot change the section within a UNION\n");
|
||||
}
|
||||
|
||||
sym_ResetCurrentLabelScopes();
|
||||
}
|
||||
|
||||
bool Section::isSizeKnown() const {
|
||||
// SECTION UNION and SECTION FRAGMENT can still grow
|
||||
if (modifier != SECTION_NORMAL)
|
||||
if (modifier != SECTION_NORMAL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The current section (or current load section if within one) is still growing
|
||||
if (this == currentSection || this == currentLoadSection)
|
||||
if (this == currentSection || this == currentLoadSection) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Any section on the stack is still growing
|
||||
for (SectionStackEntry &entry : sectionStack) {
|
||||
if (entry.section && entry.section->name == name)
|
||||
if (entry.section && entry.section->name == name) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -425,12 +450,14 @@ void sect_NewSection(
|
||||
SectionSpec const &attrs,
|
||||
SectionModifier mod
|
||||
) {
|
||||
if (currentLoadSection)
|
||||
fatalerror("Cannot change the section within a `LOAD` block\n");
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
if (currentLoadSection) {
|
||||
sect_EndLoadSection("SECTION");
|
||||
}
|
||||
|
||||
Section *sect = getSection(name, type, org, attrs, mod);
|
||||
@@ -454,11 +481,7 @@ void sect_SetLoadSection(
|
||||
// Therefore, any interactions are NOT TESTED, so lift either of those restrictions at
|
||||
// your own peril! ^^
|
||||
|
||||
if (!requireCodeSection())
|
||||
return;
|
||||
|
||||
if (currentLoadSection) {
|
||||
error("`LOAD` blocks cannot be nested\n");
|
||||
if (!requireCodeSection()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -467,9 +490,8 @@ void sect_SetLoadSection(
|
||||
return;
|
||||
}
|
||||
|
||||
if (mod == SECTION_FRAGMENT) {
|
||||
error("`LOAD FRAGMENT` is not allowed\n");
|
||||
return;
|
||||
if (currentLoadSection) {
|
||||
sect_EndLoadSection("LOAD");
|
||||
}
|
||||
|
||||
Section *sect = getSection(name, type, org, attrs, mod);
|
||||
@@ -481,7 +503,13 @@ void sect_SetLoadSection(
|
||||
currentLoadSection = sect;
|
||||
}
|
||||
|
||||
void sect_EndLoadSection() {
|
||||
void sect_EndLoadSection(char const *cause) {
|
||||
if (cause) {
|
||||
warning(
|
||||
WARNING_UNTERMINATED_LOAD, "`LOAD` block without `ENDL` terminated by `%s`\n", cause
|
||||
);
|
||||
}
|
||||
|
||||
if (!currentLoadSection) {
|
||||
error("Found `ENDL` outside of a `LOAD` block\n");
|
||||
return;
|
||||
@@ -494,6 +522,12 @@ void sect_EndLoadSection() {
|
||||
sym_SetCurrentLabelScopes(currentLoadLabelScopes);
|
||||
}
|
||||
|
||||
void sect_CheckLoadClosed() {
|
||||
if (currentLoadSection) {
|
||||
warning(WARNING_UNTERMINATED_LOAD, "`LOAD` block without `ENDL` terminated by EOF\n");
|
||||
}
|
||||
}
|
||||
|
||||
Section *sect_GetSymbolSection() {
|
||||
return currentLoadSection ? currentLoadSection : currentSection;
|
||||
}
|
||||
@@ -510,16 +544,18 @@ uint32_t sect_GetOutputOffset() {
|
||||
// Returns how many bytes need outputting for the specified alignment and offset to succeed
|
||||
uint32_t sect_GetAlignBytes(uint8_t alignment, uint16_t offset) {
|
||||
Section *sect = sect_GetSymbolSection();
|
||||
if (!sect)
|
||||
if (!sect) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool isFixed = sect->org != (uint32_t)-1;
|
||||
bool isFixed = sect->org != UINT32_MAX;
|
||||
|
||||
// If the section is not aligned, no bytes are needed
|
||||
// (fixed sections count as being maximally aligned for this purpose)
|
||||
uint8_t curAlignment = isFixed ? 16 : sect->align;
|
||||
if (curAlignment == 0)
|
||||
if (curAlignment == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// We need `(pcValue + curOffset + return value) % (1 << alignment) == offset`
|
||||
uint16_t pcValue = isFixed ? sect->org : sect->alignOfs;
|
||||
@@ -528,18 +564,20 @@ uint32_t sect_GetAlignBytes(uint8_t alignment, uint16_t offset) {
|
||||
}
|
||||
|
||||
void sect_AlignPC(uint8_t alignment, uint16_t offset) {
|
||||
if (!requireSection())
|
||||
if (!requireSection()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Section *sect = sect_GetSymbolSection();
|
||||
uint32_t alignSize = 1 << alignment; // Size of an aligned "block"
|
||||
|
||||
if (sect->org != (uint32_t)-1) {
|
||||
if ((sect->org + curOffset - offset) % alignSize)
|
||||
if (sect->org != UINT32_MAX) {
|
||||
if ((sect->org + curOffset - offset) % alignSize) {
|
||||
error(
|
||||
"Section's fixed address fails required alignment (PC = $%04" PRIx32 ")\n",
|
||||
sect->org + curOffset
|
||||
);
|
||||
}
|
||||
} else if (sect->align != 0
|
||||
&& (((sect->alignOfs + curOffset) % (1u << sect->align)) - offset) % alignSize) {
|
||||
error(
|
||||
@@ -563,18 +601,22 @@ void sect_AlignPC(uint8_t alignment, uint16_t offset) {
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
curOffset += growth;
|
||||
if (uint32_t outOffset = sect_GetOutputOffset(); outOffset > currentSection->size)
|
||||
if (uint32_t outOffset = sect_GetOutputOffset(); outOffset > currentSection->size) {
|
||||
currentSection->size = outOffset;
|
||||
if (currentLoadSection && curOffset > currentLoadSection->size)
|
||||
}
|
||||
if (currentLoadSection && curOffset > currentLoadSection->size) {
|
||||
currentLoadSection->size = curOffset;
|
||||
}
|
||||
}
|
||||
|
||||
static void writeByte(uint8_t byte) {
|
||||
if (uint32_t index = sect_GetOutputOffset(); index < currentSection->data.size())
|
||||
if (uint32_t index = sect_GetOutputOffset(); index < currentSection->data.size()) {
|
||||
currentSection->data[index] = byte;
|
||||
}
|
||||
growSection(1);
|
||||
}
|
||||
|
||||
@@ -616,8 +658,9 @@ static void endUnionMember() {
|
||||
UnionStackEntry &member = currentUnionStack.top();
|
||||
uint32_t memberSize = curOffset - member.start;
|
||||
|
||||
if (memberSize > member.size)
|
||||
if (memberSize > member.size) {
|
||||
member.size = memberSize;
|
||||
}
|
||||
curOffset = member.start;
|
||||
}
|
||||
|
||||
@@ -640,64 +683,75 @@ void sect_EndUnion() {
|
||||
}
|
||||
|
||||
void sect_CheckUnionClosed() {
|
||||
if (!currentUnionStack.empty())
|
||||
if (!currentUnionStack.empty()) {
|
||||
error("Unterminated UNION construct\n");
|
||||
}
|
||||
}
|
||||
|
||||
// Output a constant byte
|
||||
void sect_ConstByte(uint8_t byte) {
|
||||
if (!requireCodeSection())
|
||||
if (!requireCodeSection()) {
|
||||
return;
|
||||
}
|
||||
|
||||
writeByte(byte);
|
||||
}
|
||||
|
||||
// Output a string's character units as bytes
|
||||
void sect_ByteString(std::vector<int32_t> const &string) {
|
||||
if (!requireCodeSection())
|
||||
if (!requireCodeSection()) {
|
||||
return;
|
||||
|
||||
for (int32_t unit : string) {
|
||||
if (!checkNBit(unit, 8, "All character units"))
|
||||
break;
|
||||
}
|
||||
|
||||
for (int32_t unit : string)
|
||||
for (int32_t unit : string) {
|
||||
if (!checkNBit(unit, 8, "All character units")) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (int32_t unit : string) {
|
||||
writeByte(static_cast<uint8_t>(unit));
|
||||
}
|
||||
}
|
||||
|
||||
// Output a string's character units as words
|
||||
void sect_WordString(std::vector<int32_t> const &string) {
|
||||
if (!requireCodeSection())
|
||||
if (!requireCodeSection()) {
|
||||
return;
|
||||
|
||||
for (int32_t unit : string) {
|
||||
if (!checkNBit(unit, 16, "All character units"))
|
||||
break;
|
||||
}
|
||||
|
||||
for (int32_t unit : string)
|
||||
for (int32_t unit : string) {
|
||||
if (!checkNBit(unit, 16, "All character units")) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (int32_t unit : string) {
|
||||
writeWord(static_cast<uint16_t>(unit));
|
||||
}
|
||||
}
|
||||
|
||||
// Output a string's character units as longs
|
||||
void sect_LongString(std::vector<int32_t> const &string) {
|
||||
if (!requireCodeSection())
|
||||
if (!requireCodeSection()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int32_t unit : string)
|
||||
for (int32_t unit : string) {
|
||||
writeLong(static_cast<uint32_t>(unit));
|
||||
}
|
||||
}
|
||||
|
||||
// Skip this many bytes
|
||||
void sect_Skip(uint32_t skip, bool ds) {
|
||||
if (!requireSection())
|
||||
if (!requireSection()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!sect_HasData(currentSection->type)) {
|
||||
growSection(skip);
|
||||
} else {
|
||||
if (!ds)
|
||||
if (!ds) {
|
||||
warning(
|
||||
WARNING_EMPTY_DATA_DIRECTIVE,
|
||||
"%s directive without data in ROM\n",
|
||||
@@ -705,16 +759,19 @@ void sect_Skip(uint32_t skip, bool ds) {
|
||||
: (skip == 2) ? "DW"
|
||||
: "DB"
|
||||
);
|
||||
}
|
||||
// We know we're in a code SECTION
|
||||
while (skip--)
|
||||
while (skip--) {
|
||||
writeByte(fillByte);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Output a byte that can be relocatable or constant
|
||||
void sect_RelByte(Expression &expr, uint32_t pcShift) {
|
||||
if (!requireCodeSection())
|
||||
if (!requireCodeSection()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!expr.isKnown()) {
|
||||
createPatch(PATCHTYPE_BYTE, expr, pcShift);
|
||||
@@ -726,8 +783,9 @@ void sect_RelByte(Expression &expr, uint32_t pcShift) {
|
||||
|
||||
// Output several bytes that can be relocatable or constant
|
||||
void sect_RelBytes(uint32_t n, std::vector<Expression> &exprs) {
|
||||
if (!requireCodeSection())
|
||||
if (!requireCodeSection()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < n; i++) {
|
||||
Expression &expr = exprs[i % exprs.size()];
|
||||
@@ -743,8 +801,9 @@ void sect_RelBytes(uint32_t n, std::vector<Expression> &exprs) {
|
||||
|
||||
// Output a word that can be relocatable or constant
|
||||
void sect_RelWord(Expression &expr, uint32_t pcShift) {
|
||||
if (!requireCodeSection())
|
||||
if (!requireCodeSection()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!expr.isKnown()) {
|
||||
createPatch(PATCHTYPE_WORD, expr, pcShift);
|
||||
@@ -756,8 +815,9 @@ void sect_RelWord(Expression &expr, uint32_t pcShift) {
|
||||
|
||||
// Output a long that can be relocatable or constant
|
||||
void sect_RelLong(Expression &expr, uint32_t pcShift) {
|
||||
if (!requireCodeSection())
|
||||
if (!requireCodeSection()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!expr.isKnown()) {
|
||||
createPatch(PATCHTYPE_LONG, expr, pcShift);
|
||||
@@ -769,8 +829,9 @@ void sect_RelLong(Expression &expr, uint32_t pcShift) {
|
||||
|
||||
// Output a PC-relative byte that can be relocatable or constant
|
||||
void sect_PCRelByte(Expression &expr, uint32_t pcShift) {
|
||||
if (!requireCodeSection())
|
||||
if (!requireCodeSection()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Symbol const *pc = sym_GetPC(); !expr.isDiffConstant(pc)) {
|
||||
createPatch(PATCHTYPE_JR, expr, pcShift);
|
||||
@@ -781,15 +842,16 @@ void sect_PCRelByte(Expression &expr, uint32_t pcShift) {
|
||||
int16_t offset;
|
||||
|
||||
// Offset is relative to the byte *after* the operand
|
||||
if (sym == pc)
|
||||
if (sym == pc) {
|
||||
offset = -2; // PC as operand to `jr` is lower than reference PC by 2
|
||||
else
|
||||
} else {
|
||||
offset = sym->getValue() - (pc->getValue() + 1);
|
||||
}
|
||||
|
||||
if (offset < -128 || offset > 127) {
|
||||
error(
|
||||
"jr target must be between -128 and 127 bytes away, not %" PRId16
|
||||
"; use jp instead\n",
|
||||
"JR target must be between -128 and 127 bytes away, not %" PRId16
|
||||
"; use JP instead\n",
|
||||
offset
|
||||
);
|
||||
writeByte(0);
|
||||
@@ -805,16 +867,19 @@ void sect_BinaryFile(std::string const &name, int32_t startPos) {
|
||||
error("Start position cannot be negative (%" PRId32 ")\n", startPos);
|
||||
startPos = 0;
|
||||
}
|
||||
if (!requireCodeSection())
|
||||
if (!requireCodeSection()) {
|
||||
return;
|
||||
}
|
||||
|
||||
FILE *file = nullptr;
|
||||
if (std::optional<std::string> fullPath = fstk_FindFile(name); fullPath)
|
||||
if (std::optional<std::string> fullPath = fstk_FindFile(name); fullPath) {
|
||||
file = fopen(fullPath->c_str(), "rb");
|
||||
}
|
||||
if (!file) {
|
||||
if (generatedMissingIncludes) {
|
||||
if (verbose)
|
||||
if (verbose) {
|
||||
printf("Aborting (-MG) on INCBIN file '%s' (%s)\n", name.c_str(), strerror(errno));
|
||||
}
|
||||
failedOnMissingInclude = true;
|
||||
} else {
|
||||
error("Error opening INCBIN file '%s': %s\n", name.c_str(), strerror(errno));
|
||||
@@ -831,10 +896,11 @@ void sect_BinaryFile(std::string const &name, int32_t startPos) {
|
||||
// The file is seekable; skip to the specified start position
|
||||
fseek(file, startPos, SEEK_SET);
|
||||
} else {
|
||||
if (errno != ESPIPE)
|
||||
if (errno != ESPIPE) {
|
||||
error(
|
||||
"Error determining size of INCBIN file '%s': %s\n", name.c_str(), strerror(errno)
|
||||
);
|
||||
}
|
||||
// The file isn't seekable, so we'll just skip bytes one at a time
|
||||
while (startPos--) {
|
||||
if (fgetc(file) == EOF) {
|
||||
@@ -846,11 +912,13 @@ void sect_BinaryFile(std::string const &name, int32_t startPos) {
|
||||
}
|
||||
}
|
||||
|
||||
for (int byte; (byte = fgetc(file)) != EOF;)
|
||||
for (int byte; (byte = fgetc(file)) != EOF;) {
|
||||
writeByte(byte);
|
||||
}
|
||||
|
||||
if (ferror(file))
|
||||
if (ferror(file)) {
|
||||
error("Error reading INCBIN file '%s': %s\n", name.c_str(), strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
// Output a slice of a binary file
|
||||
@@ -863,18 +931,22 @@ void sect_BinaryFileSlice(std::string const &name, int32_t startPos, int32_t len
|
||||
error("Number of bytes to read cannot be negative (%" PRId32 ")\n", length);
|
||||
length = 0;
|
||||
}
|
||||
if (!requireCodeSection())
|
||||
if (!requireCodeSection()) {
|
||||
return;
|
||||
if (length == 0) // Don't even bother with 0-byte slices
|
||||
}
|
||||
if (length == 0) { // Don't even bother with 0-byte slices
|
||||
return;
|
||||
}
|
||||
|
||||
FILE *file = nullptr;
|
||||
if (std::optional<std::string> fullPath = fstk_FindFile(name); fullPath)
|
||||
if (std::optional<std::string> fullPath = fstk_FindFile(name); fullPath) {
|
||||
file = fopen(fullPath->c_str(), "rb");
|
||||
}
|
||||
if (!file) {
|
||||
if (generatedMissingIncludes) {
|
||||
if (verbose)
|
||||
if (verbose) {
|
||||
printf("Aborting (-MG) on INCBIN file '%s' (%s)\n", name.c_str(), strerror(errno));
|
||||
}
|
||||
failedOnMissingInclude = true;
|
||||
} else {
|
||||
error("Error opening INCBIN file '%s': %s\n", name.c_str(), strerror(errno));
|
||||
@@ -901,10 +973,11 @@ void sect_BinaryFileSlice(std::string const &name, int32_t startPos, int32_t len
|
||||
// The file is seekable; skip to the specified start position
|
||||
fseek(file, startPos, SEEK_SET);
|
||||
} else {
|
||||
if (errno != ESPIPE)
|
||||
if (errno != ESPIPE) {
|
||||
error(
|
||||
"Error determining size of INCBIN file '%s': %s\n", name.c_str(), strerror(errno)
|
||||
);
|
||||
}
|
||||
// The file isn't seekable, so we'll just skip bytes one at a time
|
||||
while (startPos--) {
|
||||
if (fgetc(file) == EOF) {
|
||||
@@ -950,11 +1023,13 @@ void sect_PushSection() {
|
||||
}
|
||||
|
||||
void sect_PopSection() {
|
||||
if (sectionStack.empty())
|
||||
if (sectionStack.empty()) {
|
||||
fatalerror("No entries in the section stack\n");
|
||||
}
|
||||
|
||||
if (currentLoadSection)
|
||||
fatalerror("Cannot change the section within a `LOAD` block\n");
|
||||
if (currentLoadSection) {
|
||||
sect_EndLoadSection("POPS");
|
||||
}
|
||||
|
||||
SectionStackEntry entry = sectionStack.front();
|
||||
sectionStack.pop_front();
|
||||
@@ -968,15 +1043,24 @@ void sect_PopSection() {
|
||||
std::swap(currentUnionStack, entry.unionStack);
|
||||
}
|
||||
|
||||
void sect_CheckStack() {
|
||||
if (!sectionStack.empty()) {
|
||||
warning(WARNING_UNMATCHED_DIRECTIVE, "`PUSHS` without corresponding `POPS`\n");
|
||||
}
|
||||
}
|
||||
|
||||
void sect_EndSection() {
|
||||
if (!currentSection)
|
||||
if (!currentSection) {
|
||||
fatalerror("Cannot end the section outside of a SECTION\n");
|
||||
}
|
||||
|
||||
if (currentLoadSection)
|
||||
fatalerror("Cannot end the section within a `LOAD` block\n");
|
||||
|
||||
if (!currentUnionStack.empty())
|
||||
if (!currentUnionStack.empty()) {
|
||||
fatalerror("Cannot end the section within a UNION\n");
|
||||
}
|
||||
|
||||
if (currentLoadSection) {
|
||||
sect_EndLoadSection("ENDSECTION");
|
||||
}
|
||||
|
||||
// Reset the section scope
|
||||
currentSection = nullptr;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "asm/symbol.hpp"
|
||||
|
||||
@@ -23,7 +23,7 @@ std::unordered_map<std::string, Symbol> symbols;
|
||||
std::unordered_set<std::string> purgedSymbols;
|
||||
|
||||
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 *NARGSymbol;
|
||||
static Symbol *globalScopeSymbol;
|
||||
@@ -40,8 +40,9 @@ bool sym_IsPC(Symbol const *sym) {
|
||||
}
|
||||
|
||||
void sym_ForEach(void (*callback)(Symbol &)) {
|
||||
for (auto &it : symbols)
|
||||
for (auto &it : symbols) {
|
||||
callback(it.second);
|
||||
}
|
||||
}
|
||||
|
||||
static int32_t NARGCallback() {
|
||||
@@ -92,7 +93,7 @@ int32_t Symbol::getOutputValue() const {
|
||||
}
|
||||
|
||||
ContentSpan const &Symbol::getMacro() const {
|
||||
assume((std::holds_alternative<ContentSpan>(data)));
|
||||
assume(std::holds_alternative<ContentSpan>(data));
|
||||
return std::get<ContentSpan>(data);
|
||||
}
|
||||
|
||||
@@ -101,8 +102,9 @@ std::shared_ptr<std::string> Symbol::getEqus() const {
|
||||
std::holds_alternative<std::shared_ptr<std::string>>(data)
|
||||
|| std::holds_alternative<std::shared_ptr<std::string> (*)()>(data)
|
||||
);
|
||||
if (auto *callback = std::get_if<std::shared_ptr<std::string> (*)()>(&data); callback)
|
||||
if (auto *callback = std::get_if<std::shared_ptr<std::string> (*)()>(&data); callback) {
|
||||
return (*callback)();
|
||||
}
|
||||
return std::get<std::shared_ptr<std::string>>(data);
|
||||
}
|
||||
|
||||
@@ -123,8 +125,9 @@ static void updateSymbolFilename(Symbol &sym) {
|
||||
sym.fileLine = sym.src ? lexer_GetLineNo() : 0;
|
||||
|
||||
// If the old node was registered, ensure the new one is too
|
||||
if (oldSrc && oldSrc->ID != (uint32_t)-1)
|
||||
if (oldSrc && oldSrc->ID != UINT32_MAX) {
|
||||
out_RegisterNode(sym.src);
|
||||
}
|
||||
}
|
||||
|
||||
static void alreadyDefinedError(Symbol const &sym, char const *asType) {
|
||||
@@ -133,8 +136,9 @@ static void alreadyDefinedError(Symbol const &sym, char const *asType) {
|
||||
error("'%s' is reserved for a built-in symbol\n", sym.name.c_str());
|
||||
} else {
|
||||
error("'%s' already defined", sym.name.c_str());
|
||||
if (asType)
|
||||
if (asType) {
|
||||
fprintf(stderr, " as %s", asType);
|
||||
}
|
||||
fputs(" at ", stderr);
|
||||
dumpFilename(sym);
|
||||
}
|
||||
@@ -169,7 +173,7 @@ static Symbol &createSymbol(std::string const &symName) {
|
||||
sym.section = nullptr;
|
||||
sym.src = fstk_GetFileStack();
|
||||
sym.fileLine = sym.src ? lexer_GetLineNo() : 0;
|
||||
sym.ID = -1;
|
||||
sym.ID = UINT32_MAX;
|
||||
sym.defIndex = nextDefIndex++;
|
||||
|
||||
return sym;
|
||||
@@ -184,28 +188,34 @@ static bool isAutoScoped(std::string const &symName) {
|
||||
size_t dotPos = symName.find('.');
|
||||
|
||||
// If there are no dots, it's not a local label
|
||||
if (dotPos == std::string::npos)
|
||||
if (dotPos == std::string::npos) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Label scopes `.` and `..` are the only nonlocal identifiers that start with a dot
|
||||
if (dotPos == 0 && symName.find_first_not_of('.') == symName.npos)
|
||||
if (dotPos == 0 && symName.find_first_not_of('.') == symName.npos) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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());
|
||||
}
|
||||
|
||||
// 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());
|
||||
}
|
||||
|
||||
// Check for already-qualified local label
|
||||
if (dotPos > 0)
|
||||
if (dotPos > 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for unqualifiable local label
|
||||
if (!globalScope)
|
||||
if (!globalScope) {
|
||||
fatalerror("Unqualified local label '%s' in main scope\n", symName.c_str());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -252,24 +262,28 @@ void sym_Purge(std::string const &symName) {
|
||||
Symbol *sym = sym_FindScopedValidSymbol(symName);
|
||||
|
||||
if (!sym) {
|
||||
if (sym_IsPurgedScoped(symName))
|
||||
if (sym_IsPurgedScoped(symName)) {
|
||||
error("'%s' was already purged\n", symName.c_str());
|
||||
else
|
||||
} else {
|
||||
error("'%s' not defined\n", symName.c_str());
|
||||
}
|
||||
} else if (sym->isBuiltin) {
|
||||
error("Built-in symbol '%s' cannot be purged\n", symName.c_str());
|
||||
} else if (sym->ID != (uint32_t)-1) {
|
||||
} else if (sym->ID != UINT32_MAX) {
|
||||
error("Symbol \"%s\" is referenced and thus cannot be purged\n", symName.c_str());
|
||||
} else {
|
||||
if (sym->isExported)
|
||||
if (sym->isExported) {
|
||||
warning(WARNING_PURGE_1, "Purging an exported symbol \"%s\"\n", symName.c_str());
|
||||
else if (sym->isLabel())
|
||||
} else if (sym->isLabel()) {
|
||||
warning(WARNING_PURGE_2, "Purging a label \"%s\"\n", symName.c_str());
|
||||
}
|
||||
// Do not keep a reference to the label after purging it
|
||||
if (sym == globalScope)
|
||||
if (sym == globalScope) {
|
||||
globalScope = nullptr;
|
||||
if (sym == localScope)
|
||||
}
|
||||
if (sym == localScope) {
|
||||
localScope = nullptr;
|
||||
}
|
||||
purgedSymbols.emplace(sym->name);
|
||||
symbols.erase(sym->name);
|
||||
}
|
||||
@@ -295,14 +309,16 @@ void sym_SetRSValue(int32_t value) {
|
||||
}
|
||||
|
||||
uint32_t Symbol::getConstantValue() const {
|
||||
if (isConstant())
|
||||
if (isConstant()) {
|
||||
return getValue();
|
||||
}
|
||||
|
||||
if (sym_IsPC(this)) {
|
||||
if (!getSection())
|
||||
if (!getSection()) {
|
||||
error("PC has no value outside of a section\n");
|
||||
else
|
||||
} else {
|
||||
error("PC does not have a constant value; the current section is not fixed\n");
|
||||
}
|
||||
} else {
|
||||
error("\"%s\" does not have a constant value\n", name.c_str());
|
||||
}
|
||||
@@ -310,13 +326,15 @@ uint32_t Symbol::getConstantValue() const {
|
||||
}
|
||||
|
||||
uint32_t sym_GetConstantValue(std::string const &symName) {
|
||||
if (Symbol const *sym = sym_FindScopedSymbol(symName); sym)
|
||||
if (Symbol const *sym = sym_FindScopedSymbol(symName); sym) {
|
||||
return sym->getConstantValue();
|
||||
}
|
||||
|
||||
if (sym_IsPurgedScoped(symName))
|
||||
if (sym_IsPurgedScoped(symName)) {
|
||||
error("'%s' not defined; it was purged\n", symName.c_str());
|
||||
else
|
||||
} else {
|
||||
error("'%s' not defined\n", symName.c_str());
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -361,8 +379,9 @@ static Symbol *createNonrelocSymbol(std::string const &symName, bool numeric) {
|
||||
Symbol *sym_AddEqu(std::string const &symName, int32_t value) {
|
||||
Symbol *sym = createNonrelocSymbol(symName, true);
|
||||
|
||||
if (!sym)
|
||||
if (!sym) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
sym->type = SYM_EQU;
|
||||
sym->data = value;
|
||||
@@ -373,8 +392,9 @@ Symbol *sym_AddEqu(std::string const &symName, int32_t value) {
|
||||
Symbol *sym_RedefEqu(std::string const &symName, int32_t value) {
|
||||
Symbol *sym = sym_FindExactSymbol(symName);
|
||||
|
||||
if (!sym)
|
||||
if (!sym) {
|
||||
return sym_AddEqu(symName, value);
|
||||
}
|
||||
|
||||
if (sym->isDefined() && sym->type != SYM_EQU) {
|
||||
alreadyDefinedError(*sym, "non-EQU");
|
||||
@@ -394,8 +414,9 @@ Symbol *sym_RedefEqu(std::string const &symName, int32_t value) {
|
||||
Symbol *sym_AddString(std::string const &symName, std::shared_ptr<std::string> str) {
|
||||
Symbol *sym = createNonrelocSymbol(symName, false);
|
||||
|
||||
if (!sym)
|
||||
if (!sym) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
sym->type = SYM_EQUS;
|
||||
sym->data = str;
|
||||
@@ -405,8 +426,9 @@ Symbol *sym_AddString(std::string const &symName, std::shared_ptr<std::string> s
|
||||
Symbol *sym_RedefString(std::string const &symName, std::shared_ptr<std::string> str) {
|
||||
Symbol *sym = sym_FindExactSymbol(symName);
|
||||
|
||||
if (!sym)
|
||||
if (!sym) {
|
||||
return sym_AddString(symName, str);
|
||||
}
|
||||
|
||||
if (sym->type != SYM_EQUS) {
|
||||
if (sym->isDefined()) {
|
||||
@@ -460,14 +482,16 @@ static Symbol *addLabel(std::string const &symName) {
|
||||
}
|
||||
// If the symbol already exists as a ref, just "take over" it
|
||||
sym->type = SYM_LABEL;
|
||||
sym->data = (int32_t)sect_GetSymbolOffset();
|
||||
sym->data = static_cast<int32_t>(sect_GetSymbolOffset());
|
||||
// Don't export anonymous labels
|
||||
if (exportAll && !symName.starts_with('!'))
|
||||
if (exportAll && !symName.starts_with('!')) {
|
||||
sym->isExported = true;
|
||||
}
|
||||
sym->section = sect_GetSymbolSection();
|
||||
|
||||
if (sym && !sym->section)
|
||||
if (sym && !sym->section) {
|
||||
error("Label \"%s\" created outside of a SECTION\n", symName.c_str());
|
||||
}
|
||||
|
||||
return sym;
|
||||
}
|
||||
@@ -478,8 +502,9 @@ Symbol *sym_AddLocalLabel(std::string const &symName) {
|
||||
|
||||
Symbol *sym = addLabel(isAutoScoped(symName) ? globalScope->name + symName : symName);
|
||||
|
||||
if (sym)
|
||||
if (sym) {
|
||||
localScope = sym;
|
||||
}
|
||||
|
||||
return sym;
|
||||
}
|
||||
@@ -516,7 +541,7 @@ std::string sym_MakeAnonLabelName(uint32_t ofs, bool neg) {
|
||||
uint32_t id = 0;
|
||||
|
||||
if (neg) {
|
||||
if (ofs > anonLabelID)
|
||||
if (ofs > anonLabelID) {
|
||||
error(
|
||||
"Reference to anonymous label %" PRIu32 " before, when only %" PRIu32
|
||||
" ha%s been created so far\n",
|
||||
@@ -524,19 +549,21 @@ std::string sym_MakeAnonLabelName(uint32_t ofs, bool neg) {
|
||||
anonLabelID,
|
||||
anonLabelID == 1 ? "s" : "ve"
|
||||
);
|
||||
else
|
||||
} else {
|
||||
id = anonLabelID - ofs;
|
||||
}
|
||||
} else {
|
||||
ofs--; // We're referencing symbols that haven't been created yet...
|
||||
if (ofs > UINT32_MAX - anonLabelID)
|
||||
if (ofs > UINT32_MAX - anonLabelID) {
|
||||
error(
|
||||
"Reference to anonymous label %" PRIu32 " after, when only %" PRIu32
|
||||
" may still be created\n",
|
||||
ofs + 1,
|
||||
UINT32_MAX - anonLabelID
|
||||
);
|
||||
else
|
||||
} else {
|
||||
id = anonLabelID + ofs;
|
||||
}
|
||||
}
|
||||
|
||||
std::string anon("!");
|
||||
@@ -553,16 +580,18 @@ void sym_Export(std::string const &symName) {
|
||||
Symbol *sym = sym_FindScopedSymbol(symName);
|
||||
|
||||
// If the symbol doesn't exist, create a ref that can be purged
|
||||
if (!sym)
|
||||
if (!sym) {
|
||||
sym = sym_Ref(symName);
|
||||
}
|
||||
sym->isExported = true;
|
||||
}
|
||||
|
||||
Symbol *sym_AddMacro(std::string const &symName, int32_t defLineNo, ContentSpan const &span) {
|
||||
Symbol *sym = createNonrelocSymbol(symName, false);
|
||||
|
||||
if (!sym)
|
||||
if (!sym) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
sym->type = SYM_MACRO;
|
||||
sym->data = span;
|
||||
@@ -627,7 +656,7 @@ void sym_Init(time_t now) {
|
||||
sym_AddEqu("__RGBDS_RC__"s, PACKAGE_VERSION_RC)->isBuiltin = true;
|
||||
#endif
|
||||
|
||||
if (now == (time_t)-1) {
|
||||
if (now == static_cast<time_t>(-1)) {
|
||||
warn("Failed to determine current time");
|
||||
// Fall back by pretending we are at the Epoch
|
||||
now = 0;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "asm/warning.hpp"
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
#include <string.h>
|
||||
|
||||
#include "error.hpp"
|
||||
#include "helpers.hpp" // QUOTEDSTRLEN
|
||||
#include "helpers.hpp"
|
||||
#include "itertools.hpp"
|
||||
|
||||
#include "asm/fstack.hpp"
|
||||
@@ -20,276 +20,188 @@
|
||||
unsigned int nbErrors = 0;
|
||||
unsigned int maxErrors = 0;
|
||||
|
||||
static WarningState const defaultWarnings[ARRAY_SIZE(warningStates)] = {
|
||||
WARNING_ENABLED, // WARNING_ASSERT
|
||||
WARNING_DISABLED, // WARNING_BACKWARDS_FOR
|
||||
WARNING_DISABLED, // WARNING_BUILTIN_ARG
|
||||
WARNING_DISABLED, // WARNING_CHARMAP_REDEF
|
||||
WARNING_DISABLED, // WARNING_DIV
|
||||
WARNING_DISABLED, // WARNING_EMPTY_DATA_DIRECTIVE
|
||||
WARNING_DISABLED, // WARNING_EMPTY_MACRO_ARG
|
||||
WARNING_DISABLED, // WARNING_EMPTY_STRRPL
|
||||
WARNING_DISABLED, // WARNING_LARGE_CONSTANT
|
||||
WARNING_DISABLED, // WARNING_MACRO_SHIFT
|
||||
WARNING_ENABLED, // WARNING_NESTED_COMMENT
|
||||
WARNING_ENABLED, // WARNING_OBSOLETE
|
||||
WARNING_DISABLED, // WARNING_SHIFT
|
||||
WARNING_DISABLED, // WARNING_SHIFT_AMOUNT
|
||||
WARNING_ENABLED, // WARNING_USER
|
||||
Diagnostics warningStates;
|
||||
bool warningsAreErrors;
|
||||
|
||||
WARNING_DISABLED, // WARNING_NUMERIC_STRING_1
|
||||
WARNING_DISABLED, // WARNING_NUMERIC_STRING_2
|
||||
WARNING_ENABLED, // WARNING_TRUNCATION_1
|
||||
WARNING_DISABLED, // WARNING_TRUNCATION_2
|
||||
WARNING_ENABLED, // WARNING_PURGE_1
|
||||
WARNING_DISABLED, // WARNING_PURGE_2
|
||||
WARNING_ENABLED, // WARNING_UNMAPPED_CHAR_1
|
||||
WARNING_DISABLED, // WARNING_UNMAPPED_CHAR_2
|
||||
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
|
||||
};
|
||||
|
||||
WarningState warningStates[ARRAY_SIZE(warningStates)];
|
||||
struct WarningFlag {
|
||||
char const *name;
|
||||
WarningLevel level;
|
||||
};
|
||||
|
||||
bool warningsAreErrors; // Set if `-Werror` was specified
|
||||
|
||||
static WarningState warningState(WarningID id) {
|
||||
// Check if warnings are globally disabled
|
||||
if (!warnings)
|
||||
return WARNING_DISABLED;
|
||||
|
||||
// Get the actual state
|
||||
WarningState state = warningStates[id];
|
||||
|
||||
if (state == WARNING_DEFAULT)
|
||||
// The state isn't set, grab its default state
|
||||
state = defaultWarnings[id];
|
||||
|
||||
if (warningsAreErrors && state == WARNING_ENABLED)
|
||||
state = WARNING_ERROR;
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
static char const * const warningFlags[NB_WARNINGS] = {
|
||||
"assert",
|
||||
"backwards-for",
|
||||
"builtin-args",
|
||||
"charmap-redef",
|
||||
"div",
|
||||
"empty-data-directive",
|
||||
"empty-macro-arg",
|
||||
"empty-strrpl",
|
||||
"large-constant",
|
||||
"macro-shift",
|
||||
"nested-comment",
|
||||
"obsolete",
|
||||
"shift",
|
||||
"shift-amount",
|
||||
"user",
|
||||
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",
|
||||
"numeric-string",
|
||||
"purge",
|
||||
"purge",
|
||||
"truncation",
|
||||
"truncation",
|
||||
"unmapped-char",
|
||||
"unmapped-char",
|
||||
|
||||
// Meta warnings
|
||||
"all",
|
||||
"extra",
|
||||
"everything", // Especially useful for testing
|
||||
{"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 {
|
||||
char const *name;
|
||||
uint8_t nbLevels;
|
||||
WarningID firstID;
|
||||
WarningID lastID;
|
||||
uint8_t defaultLevel;
|
||||
} paramWarnings[] = {
|
||||
{"numeric-string", 2, 1},
|
||||
{"purge", 2, 1},
|
||||
{"truncation", 2, 2},
|
||||
{"unmapped-char", 2, 1},
|
||||
{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},
|
||||
};
|
||||
|
||||
static bool tryProcessParamWarning(char const *flag, uint8_t param, WarningState state) {
|
||||
WarningID baseID = PARAM_WARNINGS_START;
|
||||
enum WarningBehavior { DISABLED, ENABLED, ERROR };
|
||||
|
||||
for (size_t i = 0; i < ARRAY_SIZE(paramWarnings); i++) {
|
||||
uint8_t maxParam = paramWarnings[i].nbLevels;
|
||||
|
||||
if (!strcmp(flag, paramWarnings[i].name)) { // Match!
|
||||
if (!strcmp(flag, "numeric-string"))
|
||||
warning(WARNING_OBSOLETE, "Warning flag \"numeric-string\" is deprecated\n");
|
||||
|
||||
// If making the warning an error but param is 0, set to the maximum
|
||||
// This accommodates `-Werror=flag`, but also `-Werror=flag=0`, which is
|
||||
// thus filtered out by the caller.
|
||||
// A param of 0 makes sense for disabling everything, but neither for
|
||||
// enabling nor "erroring". Use the default for those.
|
||||
if (param == 0 && state != WARNING_DISABLED) {
|
||||
param = paramWarnings[i].defaultLevel;
|
||||
} else if (param > maxParam) {
|
||||
if (param != 255) // Don't warn if already capped
|
||||
warnx(
|
||||
"Got parameter %" PRIu8
|
||||
" for warning flag \"%s\", but the maximum is %" PRIu8 "; capping.\n",
|
||||
param,
|
||||
flag,
|
||||
maxParam
|
||||
);
|
||||
param = maxParam;
|
||||
}
|
||||
|
||||
// Set the first <param> to enabled/error, and disable the rest
|
||||
for (uint8_t ofs = 0; ofs < maxParam; ofs++) {
|
||||
warningStates[baseID + ofs] = ofs < param ? state : WARNING_DISABLED;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
baseID = (WarningID)(baseID + maxParam);
|
||||
static WarningBehavior getWarningBehavior(WarningID id) {
|
||||
// Check if warnings are globally disabled
|
||||
if (!warnings) {
|
||||
return WarningBehavior::DISABLED;
|
||||
}
|
||||
return false;
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
enum MetaWarningCommand { META_WARNING_DONE = NB_WARNINGS };
|
||||
|
||||
// Warnings that probably indicate an error
|
||||
static uint8_t const _wallCommands[] = {
|
||||
WARNING_BACKWARDS_FOR,
|
||||
WARNING_BUILTIN_ARG,
|
||||
WARNING_CHARMAP_REDEF,
|
||||
WARNING_EMPTY_DATA_DIRECTIVE,
|
||||
WARNING_EMPTY_STRRPL,
|
||||
WARNING_LARGE_CONSTANT,
|
||||
WARNING_NESTED_COMMENT,
|
||||
WARNING_OBSOLETE,
|
||||
WARNING_PURGE_1,
|
||||
WARNING_PURGE_2,
|
||||
WARNING_UNMAPPED_CHAR_1,
|
||||
META_WARNING_DONE,
|
||||
};
|
||||
|
||||
// Warnings that are less likely to indicate an error
|
||||
static uint8_t const _wextraCommands[] = {
|
||||
WARNING_EMPTY_MACRO_ARG,
|
||||
WARNING_MACRO_SHIFT,
|
||||
WARNING_NESTED_COMMENT,
|
||||
WARNING_OBSOLETE,
|
||||
WARNING_PURGE_1,
|
||||
WARNING_PURGE_2,
|
||||
WARNING_TRUNCATION_1,
|
||||
WARNING_TRUNCATION_2,
|
||||
WARNING_UNMAPPED_CHAR_1,
|
||||
WARNING_UNMAPPED_CHAR_2,
|
||||
META_WARNING_DONE,
|
||||
};
|
||||
|
||||
// Literally everything. Notably useful for testing
|
||||
static uint8_t const _weverythingCommands[] = {
|
||||
WARNING_BACKWARDS_FOR,
|
||||
WARNING_BUILTIN_ARG,
|
||||
WARNING_DIV,
|
||||
WARNING_EMPTY_DATA_DIRECTIVE,
|
||||
WARNING_EMPTY_MACRO_ARG,
|
||||
WARNING_EMPTY_STRRPL,
|
||||
WARNING_LARGE_CONSTANT,
|
||||
WARNING_MACRO_SHIFT,
|
||||
WARNING_NESTED_COMMENT,
|
||||
WARNING_OBSOLETE,
|
||||
WARNING_PURGE_1,
|
||||
WARNING_PURGE_2,
|
||||
WARNING_SHIFT,
|
||||
WARNING_SHIFT_AMOUNT,
|
||||
WARNING_TRUNCATION_1,
|
||||
WARNING_TRUNCATION_2,
|
||||
WARNING_UNMAPPED_CHAR_1,
|
||||
WARNING_UNMAPPED_CHAR_2,
|
||||
// WARNING_USER,
|
||||
META_WARNING_DONE,
|
||||
};
|
||||
|
||||
static uint8_t const *metaWarningCommands[NB_META_WARNINGS] = {
|
||||
_wallCommands,
|
||||
_wextraCommands,
|
||||
_weverythingCommands,
|
||||
};
|
||||
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) {
|
||||
static bool setError = false;
|
||||
std::string rootFlag = flag;
|
||||
|
||||
// First, try to match against a "meta" warning
|
||||
for (WarningID id : EnumSeq(META_WARNINGS_START, NB_WARNINGS)) {
|
||||
// TODO: improve the matching performance?
|
||||
if (!strcmp(flag, warningFlags[id])) {
|
||||
// We got a match!
|
||||
if (setError)
|
||||
errx("Cannot make meta warning \"%s\" into an error", flag);
|
||||
|
||||
for (uint8_t const *ptr = metaWarningCommands[id - META_WARNINGS_START];
|
||||
*ptr != META_WARNING_DONE;
|
||||
ptr++) {
|
||||
// Warning flag, set without override
|
||||
if (warningStates[*ptr] == WARNING_DEFAULT)
|
||||
warningStates[*ptr] = WARNING_ENABLED;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
|
||||
// If it's not a meta warning, specially check against `-Werror`
|
||||
if (!strncmp(flag, "error", QUOTEDSTRLEN("error"))) {
|
||||
char const *errorFlag = flag + QUOTEDSTRLEN("error");
|
||||
|
||||
switch (*errorFlag) {
|
||||
case '\0':
|
||||
// `-Werror`
|
||||
warningsAreErrors = true;
|
||||
return;
|
||||
|
||||
case '=':
|
||||
// `-Werror=XXX`
|
||||
setError = true;
|
||||
processWarningFlag(errorFlag + 1); // Skip the `=`
|
||||
setError = false;
|
||||
return;
|
||||
|
||||
// Otherwise, allow parsing as another flag
|
||||
}
|
||||
// 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};
|
||||
}
|
||||
|
||||
// Well, it's either a normal warning or a mistake
|
||||
|
||||
WarningState state = setError ? WARNING_ERROR
|
||||
// Not an error, then check if this is a negation
|
||||
: strncmp(flag, "no-", QUOTEDSTRLEN("no-")) ? WARNING_ENABLED
|
||||
: WARNING_DISABLED;
|
||||
char const *rootFlag = state == WARNING_DISABLED ? flag + QUOTEDSTRLEN("no-") : flag;
|
||||
|
||||
// Is this a "parametric" warning?
|
||||
if (state != WARNING_DISABLED) { // The `no-` form cannot be parametrized
|
||||
// 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
|
||||
char const *equals = strchr(rootFlag, '=');
|
||||
// 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;
|
||||
|
||||
if (equals && equals[1] != '\0') { // Ignore an equal sign at the very end as well
|
||||
// Is the rest of the string a decimal number?
|
||||
// We want to avoid `strtoul`'s whitespace and sign, so we parse manually
|
||||
uint8_t param = 0;
|
||||
char const *ptr = equals + 1;
|
||||
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')
|
||||
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\n", flag);
|
||||
if (!warned) {
|
||||
warnx("Invalid warning flag \"%s\": capping parameter at 255", flag);
|
||||
}
|
||||
warned = true; // Only warn once, cap always
|
||||
param = 255;
|
||||
continue;
|
||||
@@ -299,40 +211,88 @@ void processWarningFlag(char const *flag) {
|
||||
ptr++;
|
||||
} while (*ptr);
|
||||
|
||||
// If we managed to the end of the string, check that the warning indeed
|
||||
// accepts a parameter
|
||||
// If we reached the end of the string, truncate it at the '='
|
||||
if (*ptr == '\0') {
|
||||
if (setError && param == 0) {
|
||||
warnx("Ignoring nonsensical warning flag \"%s\"\n", flag);
|
||||
return;
|
||||
rootFlag.resize(equals);
|
||||
// `-W<flag>=0` is equivalent to `-Wno-<flag>`
|
||||
if (param == 0) {
|
||||
state.state = WARNING_DISABLED;
|
||||
}
|
||||
|
||||
std::string truncFlag = rootFlag;
|
||||
|
||||
truncFlag.resize(equals - rootFlag); // Truncate the param at the '='
|
||||
if (tryProcessParamWarning(
|
||||
truncFlag.c_str(), param, param == 0 ? WARNING_DISABLED : state
|
||||
))
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Try to match the flag against a "normal" flag
|
||||
for (WarningID id : EnumSeq(NB_PLAIN_WARNINGS)) {
|
||||
if (!strcmp(rootFlag, warningFlags[id])) {
|
||||
// We got a match!
|
||||
warningStates[id] = state;
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
// Lastly, this might be a "parametric" warning without an equals sign
|
||||
// If it is, treat the param as 1 if enabling, or 0 if disabling
|
||||
if (tryProcessParamWarning(rootFlag, 0, state))
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
warnx("Unknown warning `%s`", flag);
|
||||
// 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(
|
||||
@@ -356,16 +316,18 @@ void error(char const *fmt, ...) {
|
||||
|
||||
// This intentionally makes 0 act as "unlimited" (or at least "limited to sizeof(unsigned)")
|
||||
nbErrors++;
|
||||
if (nbErrors == maxErrors)
|
||||
if (nbErrors == maxErrors) {
|
||||
errx(
|
||||
"The maximum of %u error%s was reached (configure with \"-X/--max-errors\"); assembly "
|
||||
"aborted!",
|
||||
maxErrors,
|
||||
maxErrors == 1 ? "" : "s"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
[[noreturn]] void fatalerror(char const *fmt, ...) {
|
||||
[[noreturn]]
|
||||
void fatalerror(char const *fmt, ...) {
|
||||
va_list args;
|
||||
|
||||
va_start(args, fmt);
|
||||
@@ -376,26 +338,22 @@ void error(char const *fmt, ...) {
|
||||
}
|
||||
|
||||
void warning(WarningID id, char const *fmt, ...) {
|
||||
char const *flag = warningFlags[id];
|
||||
char const *flag = warningFlags[id].name;
|
||||
va_list args;
|
||||
|
||||
va_start(args, fmt);
|
||||
|
||||
switch (warningState(id)) {
|
||||
case WARNING_DISABLED:
|
||||
switch (getWarningBehavior(id)) {
|
||||
case WarningBehavior::DISABLED:
|
||||
break;
|
||||
|
||||
case WARNING_ENABLED:
|
||||
case WarningBehavior::ENABLED:
|
||||
printDiag(fmt, args, "warning", ": [-W%s]", flag);
|
||||
break;
|
||||
|
||||
case WARNING_ERROR:
|
||||
case WarningBehavior::ERROR:
|
||||
printDiag(fmt, args, "error", ": [-Werror=%s]", flag);
|
||||
break;
|
||||
|
||||
case WARNING_DEFAULT:
|
||||
unreachable_();
|
||||
// Not reached
|
||||
}
|
||||
|
||||
va_end(args);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "error.hpp"
|
||||
|
||||
@@ -22,7 +22,8 @@ static void vwarnx(char const *fmt, va_list ap) {
|
||||
putc('\n', stderr);
|
||||
}
|
||||
|
||||
[[noreturn]] static void verr(char const *fmt, va_list ap) {
|
||||
[[noreturn]]
|
||||
static void verr(char const *fmt, va_list ap) {
|
||||
char const *error = strerror(errno);
|
||||
|
||||
fprintf(stderr, "error: ");
|
||||
@@ -32,7 +33,8 @@ static void vwarnx(char const *fmt, va_list ap) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
[[noreturn]] static void verrx(char const *fmt, va_list ap) {
|
||||
[[noreturn]]
|
||||
static void verrx(char const *fmt, va_list ap) {
|
||||
fprintf(stderr, "error: ");
|
||||
vfprintf(stderr, fmt, ap);
|
||||
putc('\n', stderr);
|
||||
@@ -56,14 +58,16 @@ void warnx(char const *fmt, ...) {
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
[[noreturn]] void err(char const *fmt, ...) {
|
||||
[[noreturn]]
|
||||
void err(char const *fmt, ...) {
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
verr(fmt, ap);
|
||||
}
|
||||
|
||||
[[noreturn]] void errx(char const *fmt, ...) {
|
||||
[[noreturn]]
|
||||
void errx(char const *fmt, ...) {
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
|
||||
77
src/extern/getopt.cpp
vendored
77
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"
|
||||
|
||||
@@ -19,8 +19,9 @@ static int musl_optpos;
|
||||
static void musl_getopt_msg(char const *a, char const *b, char const *c, size_t l) {
|
||||
FILE *f = stderr;
|
||||
|
||||
if (fputs(a, f) >= 0 && fwrite(b, strlen(b), 1, f) && fwrite(c, 1, l, f) == l)
|
||||
if (fputs(a, f) >= 0 && fwrite(b, strlen(b), 1, f) && fwrite(c, 1, l, f) == l) {
|
||||
putc('\n', f);
|
||||
}
|
||||
}
|
||||
|
||||
static int getopt(int argc, char *argv[], char const *optstring) {
|
||||
@@ -35,8 +36,9 @@ static int getopt(int argc, char *argv[], char const *optstring) {
|
||||
musl_optind = 1;
|
||||
}
|
||||
|
||||
if (musl_optind >= argc || !argv[musl_optind])
|
||||
if (musl_optind >= argc || !argv[musl_optind]) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (argv[musl_optind][0] != '-') {
|
||||
if (optstring[0] == '-') {
|
||||
@@ -46,18 +48,21 @@ static int getopt(int argc, char *argv[], char const *optstring) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!argv[musl_optind][1])
|
||||
if (!argv[musl_optind][1]) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (argv[musl_optind][1] == '-' && !argv[musl_optind][2])
|
||||
if (argv[musl_optind][1] == '-' && !argv[musl_optind][2]) {
|
||||
return musl_optind++, -1;
|
||||
}
|
||||
|
||||
if (!musl_optpos)
|
||||
if (!musl_optpos) {
|
||||
musl_optpos++;
|
||||
}
|
||||
k = mbtowc(&c, argv[musl_optind] + musl_optpos, MB_LEN_MAX);
|
||||
if (k < 0) {
|
||||
k = 1;
|
||||
c = 0xFFFD; /* replacement char */
|
||||
c = 0xFFFD; // replacement char
|
||||
}
|
||||
optchar = argv[musl_optind] + musl_optpos;
|
||||
musl_optpos += k;
|
||||
@@ -67,23 +72,26 @@ static int getopt(int argc, char *argv[], char const *optstring) {
|
||||
musl_optpos = 0;
|
||||
}
|
||||
|
||||
if (optstring[0] == '-' || optstring[0] == '+')
|
||||
if (optstring[0] == '-' || optstring[0] == '+') {
|
||||
optstring++;
|
||||
}
|
||||
|
||||
i = 0;
|
||||
d = 0;
|
||||
do {
|
||||
l = mbtowc(&d, optstring + i, MB_LEN_MAX);
|
||||
if (l > 0)
|
||||
if (l > 0) {
|
||||
i += l;
|
||||
else
|
||||
} else {
|
||||
i++;
|
||||
}
|
||||
} while (l && d != c);
|
||||
|
||||
if (d != c || c == ':') {
|
||||
musl_optopt = c;
|
||||
if (optstring[0] != ':' && musl_opterr)
|
||||
if (optstring[0] != ':' && musl_opterr) {
|
||||
musl_getopt_msg(argv[0], ": unrecognized option: ", optchar, k);
|
||||
}
|
||||
return '?';
|
||||
}
|
||||
if (optstring[i] == ':') {
|
||||
@@ -94,10 +102,12 @@ static int getopt(int argc, char *argv[], char const *optstring) {
|
||||
}
|
||||
if (musl_optind > argc) {
|
||||
musl_optopt = c;
|
||||
if (optstring[0] == ':')
|
||||
if (optstring[0] == ':') {
|
||||
return ':';
|
||||
if (musl_opterr)
|
||||
}
|
||||
if (musl_opterr) {
|
||||
musl_getopt_msg(argv[0], ": option requires an argument: ", optchar, k);
|
||||
}
|
||||
return '?';
|
||||
}
|
||||
}
|
||||
@@ -108,8 +118,9 @@ static void permute(char **argv, int dest, int src) {
|
||||
char *tmp = argv[src];
|
||||
int i;
|
||||
|
||||
for (i = src; i > dest; i--)
|
||||
for (i = src; i > dest; i--) {
|
||||
argv[i] = argv[i - 1];
|
||||
}
|
||||
argv[dest] = tmp;
|
||||
}
|
||||
|
||||
@@ -128,17 +139,20 @@ static int musl_getopt_long(
|
||||
musl_optind = 1;
|
||||
}
|
||||
|
||||
if (musl_optind >= argc || !argv[musl_optind])
|
||||
if (musl_optind >= argc || !argv[musl_optind]) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
skipped = musl_optind;
|
||||
if (optstring[0] != '+' && optstring[0] != '-') {
|
||||
int i;
|
||||
for (i = musl_optind;; i++) {
|
||||
if (i >= argc || !argv[i])
|
||||
if (i >= argc || !argv[i]) {
|
||||
return -1;
|
||||
if (argv[i][0] == '-' && argv[i][1])
|
||||
}
|
||||
if (argv[i][0] == '-' && argv[i][1]) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
musl_optind = i;
|
||||
}
|
||||
@@ -147,8 +161,9 @@ static int musl_getopt_long(
|
||||
if (resumed > skipped) {
|
||||
int i, cnt = musl_optind - resumed;
|
||||
|
||||
for (i = 0; i < cnt; i++)
|
||||
for (i = 0; i < cnt; i++) {
|
||||
permute(argv, skipped, musl_optind - 1);
|
||||
}
|
||||
musl_optind = skipped + cnt;
|
||||
}
|
||||
return ret;
|
||||
@@ -169,14 +184,16 @@ static int musl_getopt_long_core(
|
||||
char const *name = longopts[i].name;
|
||||
|
||||
opt = start;
|
||||
if (*opt == '-')
|
||||
if (*opt == '-') {
|
||||
opt++;
|
||||
}
|
||||
while (*opt && *opt != '=' && *opt == *name) {
|
||||
name++;
|
||||
opt++;
|
||||
}
|
||||
if (*opt && *opt != '=')
|
||||
if (*opt && *opt != '=') {
|
||||
continue;
|
||||
}
|
||||
arg = opt;
|
||||
match = i;
|
||||
if (!*name) {
|
||||
@@ -191,8 +208,9 @@ static int musl_getopt_long_core(
|
||||
for (i = 0; optstring[i]; i++) {
|
||||
int j = 0;
|
||||
|
||||
while (j < l && start[j] == optstring[i + j])
|
||||
while (j < l && start[j] == optstring[i + j]) {
|
||||
j++;
|
||||
}
|
||||
if (j == l) {
|
||||
cnt++;
|
||||
break;
|
||||
@@ -206,8 +224,9 @@ static int musl_getopt_long_core(
|
||||
if (*opt == '=') {
|
||||
if (!longopts[i].has_arg) {
|
||||
musl_optopt = longopts[i].val;
|
||||
if (colon || !musl_opterr)
|
||||
if (colon || !musl_opterr) {
|
||||
return '?';
|
||||
}
|
||||
musl_getopt_msg(
|
||||
argv[0],
|
||||
": option does not take an argument: ",
|
||||
@@ -221,10 +240,12 @@ static int musl_getopt_long_core(
|
||||
musl_optarg = argv[musl_optind];
|
||||
if (!musl_optarg) {
|
||||
musl_optopt = longopts[i].val;
|
||||
if (colon)
|
||||
if (colon) {
|
||||
return ':';
|
||||
if (!musl_opterr)
|
||||
}
|
||||
if (!musl_opterr) {
|
||||
return '?';
|
||||
}
|
||||
musl_getopt_msg(
|
||||
argv[0],
|
||||
": option requires an argument: ",
|
||||
@@ -235,8 +256,9 @@ static int musl_getopt_long_core(
|
||||
}
|
||||
musl_optind++;
|
||||
}
|
||||
if (idx)
|
||||
if (idx) {
|
||||
*idx = i;
|
||||
}
|
||||
if (longopts[i].flag) {
|
||||
*longopts[i].flag = longopts[i].val;
|
||||
return 0;
|
||||
@@ -245,13 +267,14 @@ static int musl_getopt_long_core(
|
||||
}
|
||||
if (argv[musl_optind][1] == '-') {
|
||||
musl_optopt = 0;
|
||||
if (!colon && musl_opterr)
|
||||
if (!colon && musl_opterr) {
|
||||
musl_getopt_msg(
|
||||
argv[0],
|
||||
cnt ? ": option is ambiguous: " : ": unrecognized option: ",
|
||||
argv[musl_optind] + 2,
|
||||
strlen(argv[musl_optind] + 2)
|
||||
);
|
||||
}
|
||||
musl_optind++;
|
||||
return '?';
|
||||
}
|
||||
|
||||
54
src/extern/utf8decoder.cpp
vendored
54
src/extern/utf8decoder.cpp
vendored
@@ -1,35 +1,35 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
/* UTF-8 decoder: http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ */
|
||||
// UTF-8 decoder: http://bjoern.hoehrmann.de/utf-8/decoder/dfa/
|
||||
|
||||
#include "extern/utf8decoder.hpp"
|
||||
|
||||
static uint8_t const utf8d[] = {
|
||||
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, /* 10..1f */
|
||||
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, /* 30..3f */
|
||||
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, /* 50..5f */
|
||||
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, /* 70..7f */
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 80..8f */
|
||||
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, /* 90..9f */
|
||||
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, /* a0..af */
|
||||
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 */
|
||||
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 */
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* s1 */
|
||||
1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, /* s1 */
|
||||
1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, /* s3 */
|
||||
1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, /* s4 */
|
||||
1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, /* s5 */
|
||||
1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, /* s6 */
|
||||
1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, /* s7 */
|
||||
1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* s8 */
|
||||
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, // 10..1f
|
||||
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, // 30..3f
|
||||
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, // 50..5f
|
||||
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, // 70..7f
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 80..8f
|
||||
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, // 90..9f
|
||||
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // a0..af
|
||||
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
|
||||
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
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // s1
|
||||
1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, // s1
|
||||
1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, // s3
|
||||
1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, // s4
|
||||
1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, // s5
|
||||
1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, // s6
|
||||
1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, // s7
|
||||
1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // s8
|
||||
};
|
||||
|
||||
uint32_t decode(uint32_t *state, uint32_t *codep, uint8_t byte) {
|
||||
|
||||
363
src/fix/main.cpp
363
src/fix/main.cpp
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
@@ -17,27 +17,26 @@
|
||||
#include "platform.hpp"
|
||||
#include "version.hpp"
|
||||
|
||||
#define UNSPECIFIED 0x200 // Should not be in byte range
|
||||
static constexpr uint16_t UNSPECIFIED = 0x200;
|
||||
static_assert(UNSPECIFIED > 0xFF, "UNSPECIFIED should not be in byte range!");
|
||||
|
||||
#define BANK_SIZE 0x4000
|
||||
static constexpr off_t BANK_SIZE = 0x4000;
|
||||
|
||||
// Short options
|
||||
static char const *optstring = "Ccf:i:jk:L:l:m:n:Op:r:st:Vv";
|
||||
static char const *optstring = "Ccf:hi:jk:L:l:m:n:Op:r:st:Vv";
|
||||
|
||||
/*
|
||||
* Equivalent long options
|
||||
* Please keep in the same order as short opts
|
||||
*
|
||||
* Also, make sure long opts don't create ambiguity:
|
||||
* A long opt's name should start with the same letter as its short opt,
|
||||
* except if it doesn't create any ambiguity (`verbose` versus `version`).
|
||||
* This is because long opt matching, even to a single char, is prioritized
|
||||
* over short opt matching
|
||||
*/
|
||||
// Equivalent long options
|
||||
// Please keep in the same order as short opts.
|
||||
// Also, make sure long opts don't create ambiguity:
|
||||
// A long opt's name should start with the same letter as its short opt,
|
||||
// except if it doesn't create any ambiguity (`verbose` versus `version`).
|
||||
// This is because long opt matching, even to a single char, is prioritized
|
||||
// over short opt matching.
|
||||
static option const longopts[] = {
|
||||
{"color-only", no_argument, nullptr, 'C'},
|
||||
{"color-compatible", no_argument, nullptr, 'c'},
|
||||
{"fix-spec", required_argument, nullptr, 'f'},
|
||||
{"help", no_argument, nullptr, 'h'},
|
||||
{"game-id", required_argument, nullptr, 'i'},
|
||||
{"non-japanese", no_argument, nullptr, 'j'},
|
||||
{"new-licensee", required_argument, nullptr, 'k'},
|
||||
@@ -57,7 +56,7 @@ static option const longopts[] = {
|
||||
|
||||
static void printUsage() {
|
||||
fputs(
|
||||
"Usage: rgbfix [-jOsVv] [-C | -c] [-f <fix_spec>] [-i <game_id>] [-k <licensee>]\n"
|
||||
"Usage: rgbfix [-hjOsVv] [-C | -c] [-f <fix_spec>] [-i <game_id>] [-k <licensee>]\n"
|
||||
" [-L <logo_file>] [-l <licensee_byte>] [-m <mbc_type>]\n"
|
||||
" [-n <rom_version>] [-p <pad_value>] [-r <ram_size>] [-t <title_str>]\n"
|
||||
" <file> ...\n"
|
||||
@@ -76,15 +75,17 @@ static void printUsage() {
|
||||
|
||||
static uint8_t nbErrors;
|
||||
|
||||
[[gnu::format(printf, 1, 2)]] static void report(char const *fmt, ...) {
|
||||
[[gnu::format(printf, 1, 2)]]
|
||||
static void report(char const *fmt, ...) {
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
vfprintf(stderr, fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
if (nbErrors != UINT8_MAX)
|
||||
if (nbErrors != UINT8_MAX) {
|
||||
nbErrors++;
|
||||
}
|
||||
}
|
||||
|
||||
enum MbcType {
|
||||
@@ -184,23 +185,23 @@ static void printAcceptedMBCNames() {
|
||||
|
||||
static uint8_t tpp1Rev[2];
|
||||
|
||||
/*
|
||||
* @return False on failure
|
||||
*/
|
||||
static bool readMBCSlice(char const *&name, char const *expected) {
|
||||
while (*expected) {
|
||||
char c = *name++;
|
||||
|
||||
if (c == '\0') // Name too short
|
||||
if (c == '\0') { // Name too short
|
||||
return false;
|
||||
}
|
||||
|
||||
if (c >= 'a' && c <= 'z') // Perform the comparison case-insensitive
|
||||
if (c >= 'a' && c <= 'z') { // Perform the comparison case-insensitive
|
||||
c = c - 'a' + 'A';
|
||||
else if (c == '_') // Treat underscores as spaces
|
||||
} else if (c == '_') { // Treat underscores as spaces
|
||||
c = ' ';
|
||||
}
|
||||
|
||||
if (c != *expected++)
|
||||
if (c != *expected++) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -223,11 +224,13 @@ static MbcType parseMBC(char const *name) {
|
||||
char *endptr;
|
||||
unsigned long mbc = strtoul(name, &endptr, base);
|
||||
|
||||
if (*endptr)
|
||||
if (*endptr) {
|
||||
return MBC_BAD;
|
||||
if (mbc > 0xFF)
|
||||
}
|
||||
if (mbc > 0xFF) {
|
||||
return MBC_BAD_RANGE;
|
||||
return (MbcType)mbc;
|
||||
}
|
||||
return static_cast<MbcType>(mbc);
|
||||
|
||||
} else {
|
||||
// Begin by reading the MBC type:
|
||||
@@ -235,13 +238,15 @@ static MbcType parseMBC(char const *name) {
|
||||
char const *ptr = name;
|
||||
|
||||
// Trim off leading whitespace
|
||||
while (*ptr == ' ' || *ptr == '\t')
|
||||
while (*ptr == ' ' || *ptr == '\t') {
|
||||
ptr++;
|
||||
}
|
||||
|
||||
#define tryReadSlice(expected) \
|
||||
do { \
|
||||
if (!readMBCSlice(ptr, expected)) \
|
||||
if (!readMBCSlice(ptr, expected)) { \
|
||||
return MBC_BAD; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
switch (*ptr++) {
|
||||
@@ -249,8 +254,9 @@ static MbcType parseMBC(char const *name) {
|
||||
case 'r':
|
||||
tryReadSlice("OM");
|
||||
// Handle optional " ONLY"
|
||||
while (*ptr == ' ' || *ptr == '\t' || *ptr == '_')
|
||||
while (*ptr == ' ' || *ptr == '\t' || *ptr == '_') {
|
||||
ptr++;
|
||||
}
|
||||
if (*ptr == 'O' || *ptr == 'o') {
|
||||
ptr++;
|
||||
tryReadSlice("NLY");
|
||||
@@ -325,8 +331,9 @@ static MbcType parseMBC(char const *name) {
|
||||
case 'P': {
|
||||
tryReadSlice("P1");
|
||||
// Parse version
|
||||
while (*ptr == ' ' || *ptr == '_')
|
||||
while (*ptr == ' ' || *ptr == '_') {
|
||||
ptr++;
|
||||
}
|
||||
// Major
|
||||
char *endptr;
|
||||
unsigned long val = strtoul(ptr, &endptr, 10);
|
||||
@@ -383,27 +390,33 @@ static MbcType parseMBC(char const *name) {
|
||||
|
||||
// Read "additional features"
|
||||
uint8_t features = 0;
|
||||
#define RAM (1 << 7)
|
||||
#define BATTERY (1 << 6)
|
||||
#define TIMER (1 << 5)
|
||||
#define RUMBLE (1 << 4)
|
||||
#define SENSOR (1 << 3)
|
||||
#define MULTIRUMBLE (1 << 2)
|
||||
// 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 (;;) {
|
||||
// Trim off trailing whitespace
|
||||
while (*ptr == ' ' || *ptr == '\t' || *ptr == '_')
|
||||
while (*ptr == ' ' || *ptr == '\t' || *ptr == '_') {
|
||||
ptr++;
|
||||
}
|
||||
|
||||
// If done, start processing "features"
|
||||
if (!*ptr)
|
||||
if (!*ptr) {
|
||||
break;
|
||||
}
|
||||
// We expect a '+' at this point
|
||||
if (*ptr++ != '+')
|
||||
if (*ptr++ != '+') {
|
||||
return MBC_BAD;
|
||||
}
|
||||
// Trim off leading whitespace
|
||||
while (*ptr == ' ' || *ptr == '\t' || *ptr == '_')
|
||||
while (*ptr == ' ' || *ptr == '\t' || *ptr == '_') {
|
||||
ptr++;
|
||||
}
|
||||
|
||||
switch (*ptr++) {
|
||||
case 'B': // BATTERY
|
||||
@@ -428,8 +441,9 @@ static MbcType parseMBC(char const *name) {
|
||||
break;
|
||||
case 'A':
|
||||
case 'a':
|
||||
if (*ptr != 'M' && *ptr != 'm')
|
||||
if (*ptr != 'M' && *ptr != 'm') {
|
||||
return MBC_BAD;
|
||||
}
|
||||
ptr++;
|
||||
features |= RAM;
|
||||
break;
|
||||
@@ -458,8 +472,9 @@ static MbcType parseMBC(char const *name) {
|
||||
|
||||
switch (mbc) {
|
||||
case ROM:
|
||||
if (!features)
|
||||
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!");
|
||||
@@ -469,26 +484,29 @@ static MbcType parseMBC(char const *name) {
|
||||
[[fallthrough]];
|
||||
case MBC1:
|
||||
case MMM01:
|
||||
if (features == RAM)
|
||||
if (features == RAM) {
|
||||
mbc++;
|
||||
else if (features == (RAM | BATTERY))
|
||||
} else if (features == (RAM | BATTERY)) {
|
||||
mbc += 2;
|
||||
else if (features)
|
||||
} else if (features) {
|
||||
return MBC_WRONG_FEATURES;
|
||||
}
|
||||
break;
|
||||
|
||||
case MBC2:
|
||||
if (features == BATTERY)
|
||||
if (features == BATTERY) {
|
||||
mbc = MBC2_BATTERY;
|
||||
else if (features)
|
||||
} else if (features) {
|
||||
return MBC_WRONG_FEATURES;
|
||||
}
|
||||
break;
|
||||
|
||||
case MBC3:
|
||||
// Handle timer, which also requires battery
|
||||
if (features & TIMER) {
|
||||
if (!(features & BATTERY))
|
||||
if (!(features & BATTERY)) {
|
||||
fprintf(stderr, "warning: MBC3+TIMER implies BATTERY\n");
|
||||
}
|
||||
features &= ~(TIMER | BATTERY); // Reset those bits
|
||||
mbc = MBC3_TIMER_BATTERY;
|
||||
// RAM is handled below
|
||||
@@ -498,12 +516,13 @@ static MbcType parseMBC(char const *name) {
|
||||
static_assert(
|
||||
MBC3_TIMER_BATTERY + 1 == MBC3_TIMER_RAM_BATTERY, "Enum sanity check failed!"
|
||||
);
|
||||
if (features == RAM)
|
||||
if (features == RAM) {
|
||||
mbc++;
|
||||
else if (features == (RAM | BATTERY))
|
||||
} else if (features == (RAM | BATTERY)) {
|
||||
mbc += 2;
|
||||
else if (features)
|
||||
} else if (features) {
|
||||
return MBC_WRONG_FEATURES;
|
||||
}
|
||||
break;
|
||||
|
||||
case MBC5:
|
||||
@@ -515,12 +534,13 @@ static MbcType parseMBC(char const *name) {
|
||||
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)
|
||||
if (features == RAM) {
|
||||
mbc++;
|
||||
else if (features == (RAM | BATTERY))
|
||||
} else if (features == (RAM | BATTERY)) {
|
||||
mbc += 2;
|
||||
else if (features)
|
||||
} else if (features) {
|
||||
return MBC_WRONG_FEATURES;
|
||||
}
|
||||
break;
|
||||
|
||||
case MBC6:
|
||||
@@ -528,47 +548,58 @@ static MbcType parseMBC(char const *name) {
|
||||
case BANDAI_TAMA5:
|
||||
case HUC3:
|
||||
// No extra features accepted
|
||||
if (features)
|
||||
if (features) {
|
||||
return MBC_WRONG_FEATURES;
|
||||
}
|
||||
break;
|
||||
|
||||
case MBC7_SENSOR_RUMBLE_RAM_BATTERY:
|
||||
if (features != (SENSOR | RUMBLE | RAM | BATTERY))
|
||||
if (features != (SENSOR | RUMBLE | RAM | BATTERY)) {
|
||||
return MBC_WRONG_FEATURES;
|
||||
}
|
||||
break;
|
||||
|
||||
case HUC1_RAM_BATTERY:
|
||||
if (features != (RAM | BATTERY)) // HuC1 expects RAM+BATTERY
|
||||
if (features != (RAM | BATTERY)) { // HuC1 expects RAM+BATTERY
|
||||
return MBC_WRONG_FEATURES;
|
||||
}
|
||||
break;
|
||||
|
||||
case TPP1:
|
||||
if (features & RAM)
|
||||
if (features & RAM) {
|
||||
fprintf(
|
||||
stderr, "warning: TPP1 requests RAM implicitly if given a non-zero RAM size"
|
||||
);
|
||||
if (features & BATTERY)
|
||||
}
|
||||
if (features & BATTERY) {
|
||||
mbc |= 0x08;
|
||||
if (features & TIMER)
|
||||
}
|
||||
if (features & TIMER) {
|
||||
mbc |= 0x04;
|
||||
if (features & MULTIRUMBLE)
|
||||
}
|
||||
if (features & MULTIRUMBLE) {
|
||||
mbc |= 0x03; // Also set the rumble flag
|
||||
if (features & RUMBLE)
|
||||
}
|
||||
if (features & RUMBLE) {
|
||||
mbc |= 0x01;
|
||||
if (features & SENSOR)
|
||||
}
|
||||
if (features & SENSOR) {
|
||||
return MBC_WRONG_FEATURES;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Trim off trailing whitespace
|
||||
while (*ptr == ' ' || *ptr == '\t')
|
||||
while (*ptr == ' ' || *ptr == '\t') {
|
||||
ptr++;
|
||||
}
|
||||
|
||||
// If there is still something past the whitespace, error out
|
||||
if (*ptr)
|
||||
if (*ptr) {
|
||||
return MBC_BAD;
|
||||
}
|
||||
|
||||
return (MbcType)mbc;
|
||||
return static_cast<MbcType>(mbc);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -740,12 +771,14 @@ static uint8_t const nintendoLogo[] = {
|
||||
};
|
||||
|
||||
static uint8_t fixSpec = 0;
|
||||
#define FIX_LOGO (1 << 7)
|
||||
#define TRASH_LOGO (1 << 6)
|
||||
#define FIX_HEADER_SUM (1 << 5)
|
||||
#define TRASH_HEADER_SUM (1 << 4)
|
||||
#define FIX_GLOBAL_SUM (1 << 3)
|
||||
#define TRASH_GLOBAL_SUM (1 << 2)
|
||||
// clang-format off: vertically align values
|
||||
static constexpr uint8_t FIX_LOGO = 1 << 7;
|
||||
static constexpr uint8_t TRASH_LOGO = 1 << 6;
|
||||
static constexpr uint8_t FIX_HEADER_SUM = 1 << 5;
|
||||
static constexpr uint8_t TRASH_HEADER_SUM = 1 << 4;
|
||||
static constexpr uint8_t FIX_GLOBAL_SUM = 1 << 3;
|
||||
static constexpr uint8_t TRASH_GLOBAL_SUM = 1 << 2;
|
||||
// clang-format on
|
||||
|
||||
static enum { DMG, BOTH, CGB } model = DMG; // If DMG, byte is left alone
|
||||
static char const *gameID = nullptr;
|
||||
@@ -778,11 +811,13 @@ static ssize_t readBytes(int fd, uint8_t *buf, size_t len) {
|
||||
while (len) {
|
||||
ssize_t ret = read(fd, buf, len);
|
||||
|
||||
if (ret == -1 && errno != EINTR) // Return errors, unless we only were interrupted
|
||||
if (ret == -1 && errno != EINTR) { // Return errors, unless we only were interrupted
|
||||
return -1;
|
||||
}
|
||||
// EOF reached
|
||||
if (ret == 0)
|
||||
if (ret == 0) {
|
||||
return total;
|
||||
}
|
||||
// If anything was read, accumulate it, and continue
|
||||
if (ret != -1) {
|
||||
total += ret;
|
||||
@@ -803,43 +838,30 @@ static ssize_t writeBytes(int fd, uint8_t *buf, size_t len) {
|
||||
while (len) {
|
||||
ssize_t ret = write(fd, buf, len);
|
||||
|
||||
if (ret == -1 && errno != EINTR) // Return errors, unless we only were interrupted
|
||||
if (ret == -1 && errno != EINTR) { // Return errors, unless we only were interrupted
|
||||
return -1;
|
||||
// EOF reached
|
||||
if (ret == 0)
|
||||
return total;
|
||||
// If anything was read, accumulate it, and continue
|
||||
}
|
||||
// If anything was written, accumulate it, and continue
|
||||
if (ret != -1) {
|
||||
total += ret;
|
||||
len -= ret;
|
||||
buf += ret;
|
||||
}
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
/*
|
||||
* @param rom0 A pointer to rom0
|
||||
* @param addr What address to check
|
||||
* @param fixedByte The fixed byte at the address
|
||||
* @param areaName Name to be displayed in the warning message
|
||||
*/
|
||||
static void overwriteByte(uint8_t *rom0, uint16_t addr, uint8_t fixedByte, char const *areaName) {
|
||||
uint8_t origByte = rom0[addr];
|
||||
|
||||
if (!overwriteRom && origByte != 0 && origByte != fixedByte)
|
||||
if (!overwriteRom && origByte != 0 && origByte != fixedByte) {
|
||||
fprintf(stderr, "warning: Overwrote a non-zero byte in the %s\n", areaName);
|
||||
}
|
||||
|
||||
rom0[addr] = fixedByte;
|
||||
}
|
||||
|
||||
/*
|
||||
* @param rom0 A pointer to rom0
|
||||
* @param startAddr What address to begin checking from
|
||||
* @param fixed The fixed bytes at the address
|
||||
* @param size How many bytes to check
|
||||
* @param areaName Name to be displayed in the warning message
|
||||
*/
|
||||
static void overwriteBytes(
|
||||
uint8_t *rom0, uint16_t startAddr, uint8_t const *fixed, uint8_t size, char const *areaName
|
||||
) {
|
||||
@@ -857,18 +879,13 @@ static void overwriteBytes(
|
||||
memcpy(&rom0[startAddr], fixed, size);
|
||||
}
|
||||
|
||||
/*
|
||||
* @param input File descriptor to be used for reading
|
||||
* @param output File descriptor to be used for writing, may be equal to `input`
|
||||
* @param name The file's name, to be displayed for error output
|
||||
* @param fileSize The file's size if known, 0 if not.
|
||||
*/
|
||||
static void processFile(int input, int output, char const *name, off_t fileSize) {
|
||||
// Both of these should be true for seekable files, and neither otherwise
|
||||
if (input == output)
|
||||
if (input == output) {
|
||||
assume(fileSize != 0);
|
||||
else
|
||||
} else {
|
||||
assume(fileSize == 0);
|
||||
}
|
||||
|
||||
uint8_t rom0[BANK_SIZE];
|
||||
ssize_t rom0Len = readBytes(input, rom0, sizeof(rom0));
|
||||
@@ -882,33 +899,45 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
|
||||
report(
|
||||
"FATAL: \"%s\" too short, expected at least %jd ($%jx) bytes, got only %jd\n",
|
||||
name,
|
||||
(intmax_t)headerSize,
|
||||
(intmax_t)headerSize,
|
||||
(intmax_t)rom0Len
|
||||
static_cast<intmax_t>(headerSize),
|
||||
static_cast<intmax_t>(headerSize),
|
||||
static_cast<intmax_t>(rom0Len)
|
||||
);
|
||||
return;
|
||||
}
|
||||
// Accept partial reads if the file contains at least the header
|
||||
|
||||
if (fixSpec & (FIX_LOGO | TRASH_LOGO))
|
||||
if (fixSpec & (FIX_LOGO | TRASH_LOGO)) {
|
||||
overwriteBytes(rom0, 0x0104, logo, sizeof(logo), logoFilename ? "logo" : "Nintendo logo");
|
||||
}
|
||||
|
||||
if (title)
|
||||
overwriteBytes(rom0, 0x134, (uint8_t const *)title, titleLen, "title");
|
||||
if (title) {
|
||||
overwriteBytes(rom0, 0x134, reinterpret_cast<uint8_t const *>(title), titleLen, "title");
|
||||
}
|
||||
|
||||
if (gameID)
|
||||
overwriteBytes(rom0, 0x13F, (uint8_t const *)gameID, gameIDLen, "manufacturer code");
|
||||
|
||||
if (model != DMG)
|
||||
overwriteByte(rom0, 0x143, model == BOTH ? 0x80 : 0xC0, "CGB flag");
|
||||
|
||||
if (newLicensee)
|
||||
if (gameID) {
|
||||
overwriteBytes(
|
||||
rom0, 0x144, (uint8_t const *)newLicensee, newLicenseeLen, "new licensee code"
|
||||
rom0, 0x13F, reinterpret_cast<uint8_t const *>(gameID), gameIDLen, "manufacturer code"
|
||||
);
|
||||
}
|
||||
|
||||
if (sgb)
|
||||
if (model != DMG) {
|
||||
overwriteByte(rom0, 0x143, model == BOTH ? 0x80 : 0xC0, "CGB flag");
|
||||
}
|
||||
|
||||
if (newLicensee) {
|
||||
overwriteBytes(
|
||||
rom0,
|
||||
0x144,
|
||||
reinterpret_cast<uint8_t const *>(newLicensee),
|
||||
newLicenseeLen,
|
||||
"new licensee code"
|
||||
);
|
||||
}
|
||||
|
||||
if (sgb) {
|
||||
overwriteByte(rom0, 0x146, 0x03, "SGB flag");
|
||||
}
|
||||
|
||||
// If a valid MBC was specified...
|
||||
if (cartridgeType < MBC_NONE) {
|
||||
@@ -931,31 +960,36 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
|
||||
|
||||
overwriteBytes(rom0, 0x150, tpp1Rev, sizeof(tpp1Rev), "TPP1 revision number");
|
||||
|
||||
if (ramSize != UNSPECIFIED)
|
||||
if (ramSize != UNSPECIFIED) {
|
||||
overwriteByte(rom0, 0x152, ramSize, "RAM size");
|
||||
}
|
||||
|
||||
overwriteByte(rom0, 0x153, cartridgeType & 0xFF, "TPP1 feature flags");
|
||||
} else {
|
||||
// Regular mappers
|
||||
|
||||
if (ramSize != UNSPECIFIED)
|
||||
if (ramSize != UNSPECIFIED) {
|
||||
overwriteByte(rom0, 0x149, ramSize, "RAM size");
|
||||
}
|
||||
|
||||
if (!japanese)
|
||||
if (!japanese) {
|
||||
overwriteByte(rom0, 0x14A, 0x01, "destination code");
|
||||
}
|
||||
}
|
||||
|
||||
if (oldLicensee != UNSPECIFIED)
|
||||
if (oldLicensee != UNSPECIFIED) {
|
||||
overwriteByte(rom0, 0x14B, oldLicensee, "old licensee code");
|
||||
else if (sgb && rom0[0x14B] != 0x33)
|
||||
} else if (sgb && rom0[0x14B] != 0x33) {
|
||||
fprintf(
|
||||
stderr,
|
||||
"warning: SGB compatibility enabled, but old licensee was 0x%02x, not 0x33\n",
|
||||
rom0[0x14B]
|
||||
);
|
||||
}
|
||||
|
||||
if (romVersion != UNSPECIFIED)
|
||||
if (romVersion != UNSPECIFIED) {
|
||||
overwriteByte(rom0, 0x14C, romVersion, "mask ROM version number");
|
||||
}
|
||||
|
||||
// Remain to be handled the ROM size, and header checksum.
|
||||
// The latter depends on the former, and so will be handled after it.
|
||||
@@ -1003,13 +1037,15 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
|
||||
nbBanks++;
|
||||
|
||||
// Update global checksum, too
|
||||
for (uint16_t i = 0; i < bankLen; i++)
|
||||
for (uint16_t i = 0; i < bankLen; i++) {
|
||||
globalSum += romx[totalRomxLen + i];
|
||||
}
|
||||
totalRomxLen += bankLen;
|
||||
}
|
||||
// Stop when an incomplete bank has been read
|
||||
if (bankLen != BANK_SIZE)
|
||||
if (bankLen != BANK_SIZE) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1036,8 +1072,9 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
|
||||
// Alter number of banks to reflect required value
|
||||
// x&(x-1) is zero iff x is a power of 2, or 0; we know for sure it's non-zero,
|
||||
// so this is true (non-zero) when we don't have a power of 2
|
||||
if (nbBanks & (nbBanks - 1))
|
||||
if (nbBanks & (nbBanks - 1)) {
|
||||
nbBanks = 1 << (CHAR_BIT * sizeof(nbBanks) - clz(nbBanks));
|
||||
}
|
||||
// Write final ROM size
|
||||
rom0[0x148] = ctz(nbBanks / 2);
|
||||
// Alter global checksum based on how many bytes will be added (not counting ROM0)
|
||||
@@ -1048,8 +1085,9 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
|
||||
if (fixSpec & (FIX_HEADER_SUM | TRASH_HEADER_SUM)) {
|
||||
uint8_t sum = 0;
|
||||
|
||||
for (uint16_t i = 0x134; i < 0x14D; i++)
|
||||
for (uint16_t i = 0x134; i < 0x14D; i++) {
|
||||
sum -= rom0[i] + 1;
|
||||
}
|
||||
|
||||
overwriteByte(rom0, 0x14D, fixSpec & TRASH_HEADER_SUM ? ~sum : sum, "header checksum");
|
||||
}
|
||||
@@ -1057,26 +1095,33 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
|
||||
if (fixSpec & (FIX_GLOBAL_SUM | TRASH_GLOBAL_SUM)) {
|
||||
// Computation of the global checksum does not include the checksum bytes
|
||||
assume(rom0Len >= 0x14E);
|
||||
for (uint16_t i = 0; i < 0x14E; i++)
|
||||
for (uint16_t i = 0; i < 0x14E; i++) {
|
||||
globalSum += rom0[i];
|
||||
for (uint16_t i = 0x150; i < rom0Len; i++)
|
||||
}
|
||||
for (uint16_t i = 0x150; i < rom0Len; i++) {
|
||||
globalSum += rom0[i];
|
||||
}
|
||||
// Pipes have already read ROMX and updated globalSum, but not regular files
|
||||
if (input == output) {
|
||||
for (;;) {
|
||||
ssize_t bankLen = readBytes(input, bank, sizeof(bank));
|
||||
|
||||
for (uint16_t i = 0; i < bankLen; i++)
|
||||
for (uint16_t i = 0; i < bankLen; i++) {
|
||||
globalSum += bank[i];
|
||||
if (bankLen != sizeof(bank))
|
||||
}
|
||||
if (bankLen != sizeof(bank)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fixSpec & TRASH_GLOBAL_SUM)
|
||||
if (fixSpec & TRASH_GLOBAL_SUM) {
|
||||
globalSum = ~globalSum;
|
||||
}
|
||||
|
||||
uint8_t bytes[2] = {(uint8_t)(globalSum >> 8), (uint8_t)(globalSum & 0xFF)};
|
||||
uint8_t bytes[2] = {
|
||||
static_cast<uint8_t>(globalSum >> 8), static_cast<uint8_t>(globalSum & 0xFF)
|
||||
};
|
||||
|
||||
overwriteBytes(rom0, 0x14E, bytes, sizeof(bytes), "global checksum");
|
||||
}
|
||||
@@ -1086,14 +1131,15 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
|
||||
// In case the output depends on the input, reset to the beginning of the file, and only
|
||||
// write the header
|
||||
if (input == output) {
|
||||
if (lseek(output, 0, SEEK_SET) == (off_t)-1) {
|
||||
if (lseek(output, 0, SEEK_SET) == static_cast<off_t>(-1)) {
|
||||
report("FATAL: Failed to rewind \"%s\": %s\n", name, strerror(errno));
|
||||
return;
|
||||
}
|
||||
// If modifying the file in-place, we only need to edit the header
|
||||
// However, padding may have modified ROM0 (added padding), so don't in that case
|
||||
if (padValue == UNSPECIFIED)
|
||||
if (padValue == UNSPECIFIED) {
|
||||
rom0Len = headerSize;
|
||||
}
|
||||
}
|
||||
writeLen = writeBytes(output, rom0, rom0Len);
|
||||
|
||||
@@ -1103,9 +1149,9 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
|
||||
} else if (writeLen < rom0Len) {
|
||||
report(
|
||||
"FATAL: Could only write %jd of \"%s\"'s %jd ROM0 bytes\n",
|
||||
(intmax_t)writeLen,
|
||||
static_cast<intmax_t>(writeLen),
|
||||
name,
|
||||
(intmax_t)rom0Len
|
||||
static_cast<intmax_t>(rom0Len)
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -1118,10 +1164,10 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
|
||||
if (writeLen == -1) {
|
||||
report("FATAL: Failed to write \"%s\"'s ROMX: %s\n", name, strerror(errno));
|
||||
return;
|
||||
} else if ((size_t)writeLen < totalRomxLen) {
|
||||
} else if (static_cast<size_t>(writeLen) < totalRomxLen) {
|
||||
report(
|
||||
"FATAL: Could only write %jd of \"%s\"'s %zu ROMX bytes\n",
|
||||
(intmax_t)writeLen,
|
||||
static_cast<intmax_t>(writeLen),
|
||||
name,
|
||||
totalRomxLen
|
||||
);
|
||||
@@ -1132,7 +1178,7 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
|
||||
// Output padding
|
||||
if (padValue != UNSPECIFIED) {
|
||||
if (input == output) {
|
||||
if (lseek(output, 0, SEEK_END) == (off_t)-1) {
|
||||
if (lseek(output, 0, SEEK_END) == static_cast<off_t>(-1)) {
|
||||
report("FATAL: Failed to seek to end of \"%s\": %s\n", name, strerror(errno));
|
||||
return;
|
||||
}
|
||||
@@ -1147,7 +1193,7 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
|
||||
|
||||
// The return value is either -1, or at most `thisLen`,
|
||||
// so it's fine to cast to `size_t`
|
||||
if ((size_t)ret != thisLen) {
|
||||
if (static_cast<size_t>(ret) != thisLen) {
|
||||
report("FATAL: Failed to write \"%s\"'s padding: %s\n", name, strerror(errno));
|
||||
break;
|
||||
}
|
||||
@@ -1160,9 +1206,9 @@ static bool processFilename(char const *name) {
|
||||
nbErrors = 0;
|
||||
|
||||
if (!strcmp(name, "-")) {
|
||||
name = "<stdin>";
|
||||
(void)setmode(STDIN_FILENO, O_BINARY);
|
||||
(void)setmode(STDOUT_FILENO, O_BINARY);
|
||||
name = "<stdin>";
|
||||
processFile(STDIN_FILENO, STDOUT_FILENO, name, 0);
|
||||
} else {
|
||||
// POSIX specifies that the results of O_RDWR on a FIFO are undefined.
|
||||
@@ -1188,7 +1234,7 @@ static bool processFilename(char const *name) {
|
||||
report(
|
||||
"FATAL: \"%s\" too short, expected at least 336 ($150) bytes, got only %jd\n",
|
||||
name,
|
||||
(intmax_t)stat.st_size
|
||||
static_cast<intmax_t>(stat.st_size)
|
||||
);
|
||||
} else {
|
||||
processFile(input, input, name, stat.st_size);
|
||||
@@ -1196,7 +1242,7 @@ static bool processFilename(char const *name) {
|
||||
}
|
||||
}
|
||||
|
||||
if (nbErrors)
|
||||
if (nbErrors) {
|
||||
fprintf(
|
||||
stderr,
|
||||
"Fixing \"%s\" failed with %u error%s\n",
|
||||
@@ -1204,6 +1250,7 @@ static bool processFilename(char const *name) {
|
||||
nbErrors,
|
||||
nbErrors == 1 ? "" : "s"
|
||||
);
|
||||
}
|
||||
return nbErrors;
|
||||
}
|
||||
|
||||
@@ -1253,16 +1300,17 @@ int main(int argc, char *argv[]) {
|
||||
switch (*musl_optarg) {
|
||||
#define OVERRIDE_SPEC(cur, bad, curFlag, badFlag) \
|
||||
case STR(cur)[0]: \
|
||||
if (fixSpec & badFlag) \
|
||||
if (fixSpec & badFlag) { \
|
||||
fprintf(stderr, "warning: '" STR(cur) "' overriding '" STR(bad) "' in fix spec\n"); \
|
||||
} \
|
||||
fixSpec = (fixSpec & ~badFlag) | curFlag; \
|
||||
break
|
||||
#define overrideSpecs(fix, fixFlag, trash, trashFlag) \
|
||||
OVERRIDE_SPEC(fix, trash, fixFlag, trashFlag); \
|
||||
OVERRIDE_SPEC(trash, fix, trashFlag, fixFlag)
|
||||
overrideSpecs(l, FIX_LOGO, L, TRASH_LOGO);
|
||||
overrideSpecs(h, FIX_HEADER_SUM, H, TRASH_HEADER_SUM);
|
||||
overrideSpecs(g, FIX_GLOBAL_SUM, G, TRASH_GLOBAL_SUM);
|
||||
overrideSpecs(l, FIX_LOGO, L, TRASH_LOGO);
|
||||
overrideSpecs(h, FIX_HEADER_SUM, H, TRASH_HEADER_SUM);
|
||||
overrideSpecs(g, FIX_GLOBAL_SUM, G, TRASH_GLOBAL_SUM);
|
||||
#undef OVERRIDE_SPEC
|
||||
#undef overrideSpecs
|
||||
|
||||
@@ -1273,6 +1321,10 @@ int main(int argc, char *argv[]) {
|
||||
}
|
||||
break;
|
||||
|
||||
case 'h':
|
||||
printUsage();
|
||||
exit(0);
|
||||
|
||||
case 'i':
|
||||
gameID = musl_optarg;
|
||||
len = strlen(gameID);
|
||||
@@ -1380,21 +1432,23 @@ int main(int argc, char *argv[]) {
|
||||
}
|
||||
}
|
||||
|
||||
if ((cartridgeType & 0xFF00) == TPP1 && !japanese)
|
||||
if ((cartridgeType & 0xFF00) == TPP1 && !japanese) {
|
||||
fprintf(
|
||||
stderr,
|
||||
"warning: TPP1 overwrites region flag for its identification code, ignoring `-j`\n"
|
||||
);
|
||||
}
|
||||
|
||||
// Check that RAM size is correct for "standard" mappers
|
||||
if (ramSize != UNSPECIFIED && (cartridgeType & 0xFF00) == 0) {
|
||||
if (cartridgeType == ROM_RAM || cartridgeType == ROM_RAM_BATTERY) {
|
||||
if (ramSize != 1)
|
||||
if (ramSize != 1) {
|
||||
fprintf(
|
||||
stderr,
|
||||
"warning: MBC \"%s\" should have 2 KiB of RAM (-r 1)\n",
|
||||
mbcName(cartridgeType)
|
||||
);
|
||||
}
|
||||
} else if (hasRAM(cartridgeType)) {
|
||||
if (!ramSize) {
|
||||
fprintf(
|
||||
@@ -1419,12 +1473,13 @@ int main(int argc, char *argv[]) {
|
||||
}
|
||||
}
|
||||
|
||||
if (sgb && oldLicensee != UNSPECIFIED && oldLicensee != 0x33)
|
||||
if (sgb && oldLicensee != UNSPECIFIED && oldLicensee != 0x33) {
|
||||
fprintf(
|
||||
stderr,
|
||||
"warning: SGB compatibility enabled, but old licensee is 0x%02x, not 0x33\n",
|
||||
oldLicensee
|
||||
);
|
||||
}
|
||||
|
||||
argv += musl_optind;
|
||||
bool failed = nbErrors;
|
||||
@@ -1435,7 +1490,8 @@ int main(int argc, char *argv[]) {
|
||||
logoFile = fopen(logoFilename, "rb");
|
||||
} else {
|
||||
logoFilename = "<stdin>";
|
||||
logoFile = fdopen(STDIN_FILENO, "rb");
|
||||
(void)setmode(STDIN_FILENO, O_BINARY);
|
||||
logoFile = stdin;
|
||||
}
|
||||
if (!logoFile) {
|
||||
fprintf(
|
||||
@@ -1474,8 +1530,9 @@ int main(int argc, char *argv[]) {
|
||||
memcpy(logo, nintendoLogo, sizeof(nintendoLogo));
|
||||
}
|
||||
if (fixSpec & TRASH_LOGO) {
|
||||
for (uint16_t i = 0; i < sizeof(logo); i++)
|
||||
for (uint16_t i = 0; i < sizeof(logo); i++) {
|
||||
logo[i] = 0xFF ^ logo[i];
|
||||
}
|
||||
}
|
||||
|
||||
if (!*argv) {
|
||||
|
||||
115
src/gfx/main.cpp
115
src/gfx/main.cpp
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "gfx/main.hpp"
|
||||
|
||||
@@ -41,7 +41,8 @@ static struct LocalOptions {
|
||||
|
||||
static uintmax_t nbErrors;
|
||||
|
||||
[[noreturn]] void giveUp() {
|
||||
[[noreturn]]
|
||||
void giveUp() {
|
||||
fprintf(stderr, "Conversion aborted after %ju error%s\n", nbErrors, nbErrors == 1 ? "" : "s");
|
||||
exit(1);
|
||||
}
|
||||
@@ -71,18 +72,21 @@ void error(char const *fmt, ...) {
|
||||
va_end(ap);
|
||||
putc('\n', stderr);
|
||||
|
||||
if (nbErrors != std::numeric_limits<decltype(nbErrors)>::max())
|
||||
if (nbErrors != std::numeric_limits<decltype(nbErrors)>::max()) {
|
||||
nbErrors++;
|
||||
}
|
||||
}
|
||||
|
||||
void errorMessage(char const *msg) {
|
||||
fprintf(stderr, "error: %s\n", msg);
|
||||
|
||||
if (nbErrors != std::numeric_limits<decltype(nbErrors)>::max())
|
||||
if (nbErrors != std::numeric_limits<decltype(nbErrors)>::max()) {
|
||||
nbErrors++;
|
||||
}
|
||||
}
|
||||
|
||||
[[noreturn]] void fatal(char const *fmt, ...) {
|
||||
[[noreturn]]
|
||||
void fatal(char const *fmt, ...) {
|
||||
va_list ap;
|
||||
|
||||
fputs("FATAL: ", stderr);
|
||||
@@ -91,8 +95,9 @@ void errorMessage(char const *msg) {
|
||||
va_end(ap);
|
||||
putc('\n', stderr);
|
||||
|
||||
if (nbErrors != std::numeric_limits<decltype(nbErrors)>::max())
|
||||
if (nbErrors != std::numeric_limits<decltype(nbErrors)>::max()) {
|
||||
nbErrors++;
|
||||
}
|
||||
|
||||
giveUp();
|
||||
}
|
||||
@@ -108,18 +113,15 @@ void Options::verbosePrint(uint8_t level, char const *fmt, ...) const {
|
||||
}
|
||||
|
||||
// Short options
|
||||
static char const *optstring = "-Aa:b:Cc:Dd:Ffhi:L:mN:n:Oo:Pp:Qq:r:s:Tt:U:uVvx:Z";
|
||||
static char const *optstring = "-Aa:b:Cc:d:hi:L:mN:n:Oo:Pp:Qq:r:s:Tt:U:uVvXx:YZ";
|
||||
|
||||
/*
|
||||
* Equivalent long options
|
||||
* Please keep in the same order as short opts
|
||||
*
|
||||
* Also, make sure long opts don't create ambiguity:
|
||||
* A long opt's name should start with the same letter as its short opt,
|
||||
* except if it doesn't create any ambiguity (`verbose` versus `version`).
|
||||
* This is because long opt matching, even to a single char, is prioritized
|
||||
* over short opt matching
|
||||
*/
|
||||
// Equivalent long options
|
||||
// Please keep in the same order as short opts.
|
||||
// Also, make sure long opts don't create ambiguity:
|
||||
// A long opt's name should start with the same letter as its short opt,
|
||||
// except if it doesn't create any ambiguity (`verbose` versus `version`).
|
||||
// This is because long opt matching, even to a single char, is prioritized
|
||||
// over short opt matching.
|
||||
static option const longopts[] = {
|
||||
{"auto-attr-map", no_argument, nullptr, 'A'},
|
||||
{"attr-map", required_argument, nullptr, 'a'},
|
||||
@@ -127,6 +129,7 @@ static option const longopts[] = {
|
||||
{"color-curve", no_argument, nullptr, 'C'},
|
||||
{"colors", required_argument, nullptr, 'c'},
|
||||
{"depth", required_argument, nullptr, 'd'},
|
||||
{"help", no_argument, nullptr, 'h'},
|
||||
{"input-tileset", required_argument, nullptr, 'i'},
|
||||
{"slice", required_argument, nullptr, 'L'},
|
||||
{"mirror-tiles", no_argument, nullptr, 'm'},
|
||||
@@ -139,6 +142,7 @@ static option const longopts[] = {
|
||||
{"auto-palette-map", no_argument, nullptr, 'Q'},
|
||||
{"palette-map", required_argument, nullptr, 'q'},
|
||||
{"reverse", required_argument, nullptr, 'r'},
|
||||
{"palette-size", required_argument, nullptr, 's'},
|
||||
{"auto-tilemap", no_argument, nullptr, 'T'},
|
||||
{"tilemap", required_argument, nullptr, 't'},
|
||||
{"unit-size", required_argument, nullptr, 'U'},
|
||||
@@ -154,7 +158,7 @@ static option const longopts[] = {
|
||||
|
||||
static void printUsage() {
|
||||
fputs(
|
||||
"Usage: rgbgfx [-r stride] [-CmOuVXYZ] [-v [-v ...]] [-a <attr_map> | -A]\n"
|
||||
"Usage: rgbgfx [-r stride] [-ChmOuVXYZ] [-v [-v ...]] [-a <attr_map> | -A]\n"
|
||||
" [-b <base_ids>] [-c <colors>] [-d <depth>] [-i <tileset_file>]\n"
|
||||
" [-L <slice>] [-N <nb_tiles>] [-n <nb_pals>] [-o <out_file>]\n"
|
||||
" [-p <pal_file> | -P] [-q <pal_map> | -Q] [-s <nb_colors>]\n"
|
||||
@@ -171,10 +175,8 @@ static void printUsage() {
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
* Parses a number at the beginning of a string, moving the pointer to skip the parsed characters
|
||||
* Returns the provided errVal on error
|
||||
*/
|
||||
// Parses a number at the beginning of a string, moving the pointer to skip the parsed characters.
|
||||
// Returns the provided errVal on error.
|
||||
static uint16_t parseNumber(char *&string, char const *errPrefix, uint16_t errVal = UINT16_MAX) {
|
||||
uint8_t base = 10;
|
||||
if (*string == '\0') {
|
||||
@@ -197,12 +199,10 @@ static uint16_t parseNumber(char *&string, char const *errPrefix, uint16_t errVa
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Turns a digit into its numeric value in the current base, if it has one.
|
||||
* Maximum is inclusive. The string_view is modified to "consume" all digits.
|
||||
* Returns 255 on parse failure (including wrong char for base), in which case
|
||||
* the string_view may be pointing on garbage.
|
||||
*/
|
||||
// Turns a digit into its numeric value in the current base, if it has one.
|
||||
// Maximum is inclusive. The string_view is modified to "consume" all digits.
|
||||
// Returns 255 on parse failure (including wrong char for base), in which case
|
||||
// the string_view may be pointing on garbage.
|
||||
auto charIndex = [&base](unsigned char c) -> uint8_t {
|
||||
unsigned char index = c - '0'; // Use wrapping semantics
|
||||
if (base == 2 && index >= 2) {
|
||||
@@ -270,10 +270,7 @@ static void registerInput(char const *arg) {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Turn an "at-file"'s contents into an argv that `getopt` can handle
|
||||
* @param argPool Argument characters will be appended to this vector, for storage purposes.
|
||||
*/
|
||||
// Turn an at-file's contents into an argv that `getopt` can handle, appending them to `argPool`.
|
||||
static std::vector<size_t> readAtFile(std::string const &path, std::vector<char> &argPool) {
|
||||
File file;
|
||||
if (!file.open(path, std::ios_base::in)) {
|
||||
@@ -345,13 +342,10 @@ static std::vector<size_t> readAtFile(std::string const &path, std::vector<char>
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Parses an arg vector, modifying `options` and `localOptions` as options are read.
|
||||
* The `localOptions` struct is for flags which must be processed after the option parsing finishes.
|
||||
*
|
||||
* Returns `nullptr` if the vector was fully parsed, or a pointer (which is part of the arg vector)
|
||||
* to an "at-file" path if one is encountered.
|
||||
*/
|
||||
// Parses an arg vector, modifying `options` and `localOptions` as options are read.
|
||||
// The `localOptions` struct is for flags which must be processed after the option parsing finishes.
|
||||
// Returns `nullptr` if the vector was fully parsed, or a pointer (which is part of the arg vector)
|
||||
// to an "at-file" path if one is encountered.
|
||||
static char *parseArgv(int argc, char *argv[]) {
|
||||
for (int ch; (ch = musl_getopt_long_only(argc, argv, optstring, longopts, nullptr)) != -1;) {
|
||||
char *arg = musl_optarg; // Make a copy for scanning
|
||||
@@ -362,8 +356,9 @@ static char *parseArgv(int argc, char *argv[]) {
|
||||
break;
|
||||
case 'a':
|
||||
localOptions.autoAttrmap = false;
|
||||
if (!options.attrmap.empty())
|
||||
if (!options.attrmap.empty()) {
|
||||
warning("Overriding attrmap file %s", options.attrmap.c_str());
|
||||
}
|
||||
options.attrmap = musl_optarg;
|
||||
break;
|
||||
case 'b':
|
||||
@@ -429,9 +424,13 @@ static char *parseArgv(int argc, char *argv[]) {
|
||||
options.bitDepth = 2;
|
||||
}
|
||||
break;
|
||||
case 'h':
|
||||
printUsage();
|
||||
exit(0);
|
||||
case 'i':
|
||||
if (!options.inputTileset.empty())
|
||||
if (!options.inputTileset.empty()) {
|
||||
warning("Overriding input tileset file %s", options.inputTileset.c_str());
|
||||
}
|
||||
options.inputTileset = musl_optarg;
|
||||
break;
|
||||
case 'L':
|
||||
@@ -529,8 +528,9 @@ static char *parseArgv(int argc, char *argv[]) {
|
||||
localOptions.groupOutputs = true;
|
||||
break;
|
||||
case 'o':
|
||||
if (!options.output.empty())
|
||||
if (!options.output.empty()) {
|
||||
warning("Overriding tile data file %s", options.output.c_str());
|
||||
}
|
||||
options.output = musl_optarg;
|
||||
break;
|
||||
case 'P':
|
||||
@@ -538,8 +538,9 @@ static char *parseArgv(int argc, char *argv[]) {
|
||||
break;
|
||||
case 'p':
|
||||
localOptions.autoPalettes = false;
|
||||
if (!options.palettes.empty())
|
||||
if (!options.palettes.empty()) {
|
||||
warning("Overriding palettes file %s", options.palettes.c_str());
|
||||
}
|
||||
options.palettes = musl_optarg;
|
||||
break;
|
||||
case 'Q':
|
||||
@@ -547,8 +548,9 @@ static char *parseArgv(int argc, char *argv[]) {
|
||||
break;
|
||||
case 'q':
|
||||
localOptions.autoPalmap = false;
|
||||
if (!options.palmap.empty())
|
||||
if (!options.palmap.empty()) {
|
||||
warning("Overriding palette map file %s", options.palmap.c_str());
|
||||
}
|
||||
options.palmap = musl_optarg;
|
||||
break;
|
||||
case 'r':
|
||||
@@ -574,8 +576,9 @@ static char *parseArgv(int argc, char *argv[]) {
|
||||
break;
|
||||
case 't':
|
||||
localOptions.autoTilemap = false;
|
||||
if (!options.tilemap.empty())
|
||||
if (!options.tilemap.empty()) {
|
||||
warning("Overriding tilemap file %s", options.tilemap.c_str());
|
||||
}
|
||||
options.tilemap = musl_optarg;
|
||||
break;
|
||||
case 'V':
|
||||
@@ -773,19 +776,25 @@ int main(int argc, char *argv[]) {
|
||||
}
|
||||
|
||||
fputs("Options:\n", stderr);
|
||||
if (options.columnMajor)
|
||||
if (options.columnMajor) {
|
||||
fputs("\tVisit image in column-major order\n", stderr);
|
||||
if (options.allowDedup)
|
||||
}
|
||||
if (options.allowDedup) {
|
||||
fputs("\tAllow deduplicating tiles\n", stderr);
|
||||
if (options.allowMirroringX)
|
||||
}
|
||||
if (options.allowMirroringX) {
|
||||
fputs("\tAllow deduplicating horizontally mirrored tiles\n", stderr);
|
||||
if (options.allowMirroringY)
|
||||
}
|
||||
if (options.allowMirroringY) {
|
||||
fputs("\tAllow deduplicating vertically mirrored tiles\n", stderr);
|
||||
if (options.useColorCurve)
|
||||
}
|
||||
if (options.useColorCurve) {
|
||||
fputs("\tUse color curve\n", stderr);
|
||||
}
|
||||
fprintf(stderr, "\tBit depth: %" PRIu8 "bpp\n", options.bitDepth);
|
||||
if (options.trim != 0)
|
||||
if (options.trim != 0) {
|
||||
fprintf(stderr, "\tTrim the last %" PRIu64 " tiles\n", options.trim);
|
||||
}
|
||||
fprintf(stderr, "\tMaximum %" PRIu16 " palettes\n", options.nbPalettes);
|
||||
fprintf(stderr, "\tPalettes contain %" PRIu8 " colors\n", options.nbColorsPerPal);
|
||||
fprintf(stderr, "\t%s palette spec\n", [] {
|
||||
@@ -882,9 +891,7 @@ void Palette::addColor(uint16_t color) {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the ID of the color in the palette, or `size()` if the color is not in
|
||||
*/
|
||||
// Returns the ID of the color in the palette, or `size()` if the color is not in
|
||||
uint8_t Palette::indexOf(uint16_t color) const {
|
||||
return color == Rgba::transparent
|
||||
? 0
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user