mirror of
https://github.com/gbdev/rgbds.git
synced 2025-11-21 02:32: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
|
BinPackArguments: false
|
||||||
BinPackParameters: false
|
BinPackParameters: false
|
||||||
BitFieldColonSpacing: Both
|
BitFieldColonSpacing: Both
|
||||||
|
BreakAfterAttributes: Always
|
||||||
BreakBeforeBinaryOperators: NonAssignment
|
BreakBeforeBinaryOperators: NonAssignment
|
||||||
BreakBeforeBraces: Attach
|
BreakBeforeBraces: Attach
|
||||||
BreakBeforeConceptDeclarations: true
|
BreakBeforeConceptDeclarations: true
|
||||||
@@ -54,12 +55,14 @@ IncludeCategories:
|
|||||||
IndentAccessModifiers: false
|
IndentAccessModifiers: false
|
||||||
IndentCaseBlocks: false
|
IndentCaseBlocks: false
|
||||||
IndentCaseLabels: false
|
IndentCaseLabels: false
|
||||||
IndentExternBlock: NoIndent
|
IndentExternBlock: Indent
|
||||||
IndentGotoLabels: false
|
IndentGotoLabels: false
|
||||||
IndentPPDirectives: BeforeHash
|
IndentPPDirectives: BeforeHash
|
||||||
IndentRequires: true
|
IndentRequires: true
|
||||||
IndentWidth: 4
|
IndentWidth: 4
|
||||||
IndentWrappedFunctionNames: true
|
IndentWrappedFunctionNames: true
|
||||||
|
InsertBraces: true
|
||||||
|
InsertNewlineAtEOF: true
|
||||||
# Only support for Javascript as of clang-format 14...
|
# Only support for Javascript as of clang-format 14...
|
||||||
# InsertTrailingCommas: Wrapped
|
# InsertTrailingCommas: Wrapped
|
||||||
KeepEmptyLinesAtTheStartOfBlocks: false
|
KeepEmptyLinesAtTheStartOfBlocks: false
|
||||||
@@ -71,6 +74,8 @@ PPIndentWidth: -1
|
|||||||
PointerAlignment: Right
|
PointerAlignment: Right
|
||||||
QualifierAlignment: Right
|
QualifierAlignment: Right
|
||||||
ReflowComments: true
|
ReflowComments: true
|
||||||
|
RemoveParentheses: ReturnStatement
|
||||||
|
RemoveSemicolon: true
|
||||||
SortIncludes: CaseSensitive
|
SortIncludes: CaseSensitive
|
||||||
SortUsingDeclarations: true
|
SortUsingDeclarations: true
|
||||||
SpaceAfterCStyleCast: false
|
SpaceAfterCStyleCast: false
|
||||||
|
|||||||
4
.github/scripts/build_libpng.sh
vendored
4
.github/scripts/build_libpng.sh
vendored
@@ -1,13 +1,13 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
pngver=1.6.43
|
pngver=1.6.45
|
||||||
|
|
||||||
## Grab sources and check them
|
## Grab sources and check them
|
||||||
|
|
||||||
curl -LOJ "http://prdownloads.sourceforge.net/libpng/libpng-$pngver.tar.xz?download"
|
curl -LOJ "http://prdownloads.sourceforge.net/libpng/libpng-$pngver.tar.xz?download"
|
||||||
# Brew doesn't provide any sha256sum, so we're making do with `sha2` instead.
|
# Brew doesn't provide any sha256sum, so we're making do with `sha2` instead.
|
||||||
if [ "$(sha2 -q -256 libpng-$pngver.tar.xz)" != 6a5ca0652392a2d7c9db2ae5b40210843c0bbc081cbd410825ab00cc59f14a6c ]; then
|
if [ "$(sha2 -q -256 libpng-$pngver.tar.xz)" != 926485350139ffb51ef69760db35f78846c805fef3d59bfdcb2fba704663f370 ]; then
|
||||||
sha2 -256 libpng-$pngver.tar.xz
|
sha2 -256 libpng-$pngver.tar.xz
|
||||||
echo Checksum mismatch! Aborting. >&2
|
echo Checksum mismatch! Aborting. >&2
|
||||||
exit 1
|
exit 1
|
||||||
|
|||||||
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://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
|
getlibrary 'https://github.com/lexxmark/winflexbison/releases/download/v2.5.25/win_flex_bison-2.5.25.zip' 'winflexbison.zip' '8d324b62be33604b2c45ad1dd34ab93d722534448f55a16ca7292de32b6ac135' install_dir
|
||||||
|
|
||||||
Move-Item zlib-1.3.1 zlib
|
Move-Item zlib-1.3.1 zlib
|
||||||
Move-Item libpng-1.6.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 -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 -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 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/
|
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
|
#!/bin/bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
pngver=1.6.43
|
pngver=1.6.45
|
||||||
arch="$1"
|
arch="$1"
|
||||||
|
|
||||||
## Grab sources and check them
|
## Grab sources and check them
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
d6bd2a3f43f17020918a4c1bd81c1a78111b6f759af9c1d3c754f704a1bf0429 libpng-1.6.43-apng.patch.gz
|
f7caa3b55f003ce23d6a087b1c2a643262647bfd1a1b31afdc9b18eabf1bbc7e libpng-1.6.45-apng.patch.gz
|
||||||
6a5ca0652392a2d7c9db2ae5b40210843c0bbc081cbd410825ab00cc59f14a6c libpng-1.6.43.tar.xz
|
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
|
on: pull_request
|
||||||
|
|
||||||
jobs:
|
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:
|
on:
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
@@ -24,30 +24,32 @@ jobs:
|
|||||||
run: | # Turn "vX.Y.Z" into "X.Y.Z"
|
run: | # Turn "vX.Y.Z" into "X.Y.Z"
|
||||||
VERSION="${{ github.ref_name }}"
|
VERSION="${{ github.ref_name }}"
|
||||||
echo "version=${VERSION#v}" >> $GITHUB_ENV
|
echo "version=${VERSION#v}" >> $GITHUB_ENV
|
||||||
- uses: actions/checkout@v4
|
- name: Checkout repo
|
||||||
|
uses: actions/checkout@v4
|
||||||
- name: Install deps
|
- name: Install deps
|
||||||
run: .github/scripts/get_win_deps.ps1
|
run: .github/scripts/get_win_deps.ps1
|
||||||
- uses: actions/cache@v4
|
- name: Check libraries cache
|
||||||
id: cache
|
id: cache
|
||||||
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
zbuild
|
zbuild
|
||||||
pngbuild
|
pngbuild
|
||||||
key: ${{ matrix.arch }}-${{ hashFiles('zlib/**', 'libpng/**') }}
|
key: ${{ matrix.arch }}-${{ hashFiles('zlib/**', 'libpng/**') }}
|
||||||
- name: Build zlib
|
- name: Build zlib
|
||||||
|
if: steps.cache.outputs.cache-hit != 'true'
|
||||||
run: | # BUILD_SHARED_LIBS causes the output DLL to be correctly called `zlib1.dll`
|
run: | # BUILD_SHARED_LIBS causes the output DLL to be correctly called `zlib1.dll`
|
||||||
cmake -S zlib -B zbuild -A ${{ matrix.platform }} -Wno-dev -DCMAKE_INSTALL_PREFIX=install_dir -DBUILD_SHARED_LIBS=ON
|
cmake -S zlib -B zbuild -A ${{ matrix.platform }} -Wno-dev -DCMAKE_INSTALL_PREFIX=install_dir -DBUILD_SHARED_LIBS=ON
|
||||||
cmake --build zbuild --config Release -j
|
cmake --build zbuild --config Release -j
|
||||||
if: steps.cache.outputs.cache-hit != 'true'
|
|
||||||
- name: Install zlib
|
- name: Install zlib
|
||||||
run: |
|
run: |
|
||||||
cmake --install zbuild
|
cmake --install zbuild
|
||||||
- name: Build libpng
|
- name: Build libpng
|
||||||
|
if: steps.cache.outputs.cache-hit != 'true'
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
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 -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
|
cmake --build pngbuild --config Release -j
|
||||||
if: steps.cache.outputs.cache-hit != 'true'
|
|
||||||
- name: Install libpng
|
- name: Install libpng
|
||||||
run: |
|
run: |
|
||||||
cmake --install pngbuild
|
cmake --install pngbuild
|
||||||
@@ -74,7 +76,8 @@ jobs:
|
|||||||
run: | # Turn "refs/tags/vX.Y.Z" into "X.Y.Z"
|
run: | # Turn "refs/tags/vX.Y.Z" into "X.Y.Z"
|
||||||
VERSION="${{ github.ref_name }}"
|
VERSION="${{ github.ref_name }}"
|
||||||
echo "version=${VERSION#v}" >> $GITHUB_ENV
|
echo "version=${VERSION#v}" >> $GITHUB_ENV
|
||||||
- uses: actions/checkout@v4
|
- name: Checkout repo
|
||||||
|
uses: actions/checkout@v4
|
||||||
- name: Install deps
|
- name: Install deps
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
@@ -104,7 +107,8 @@ jobs:
|
|||||||
run: | # Turn "refs/tags/vX.Y.Z" into "X.Y.Z"
|
run: | # Turn "refs/tags/vX.Y.Z" into "X.Y.Z"
|
||||||
VERSION="${{ github.ref_name }}"
|
VERSION="${{ github.ref_name }}"
|
||||||
echo "version=${VERSION#v}" >> $GITHUB_ENV
|
echo "version=${VERSION#v}" >> $GITHUB_ENV
|
||||||
- uses: actions/checkout@v4
|
- name: Checkout repo
|
||||||
|
uses: actions/checkout@v4
|
||||||
- name: Install deps
|
- name: Install deps
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
@@ -133,17 +137,19 @@ jobs:
|
|||||||
run: | # Turn "refs/tags/vX.Y.Z" into "X.Y.Z"
|
run: | # Turn "refs/tags/vX.Y.Z" into "X.Y.Z"
|
||||||
VERSION="${{ github.ref_name }}"
|
VERSION="${{ github.ref_name }}"
|
||||||
echo "version=${VERSION#v}" >> $GITHUB_ENV
|
echo "version=${VERSION#v}" >> $GITHUB_ENV
|
||||||
- uses: actions/checkout@v4
|
- name: Checkout repo
|
||||||
|
uses: actions/checkout@v4
|
||||||
- name: Package sources
|
- name: Package sources
|
||||||
run: |
|
run: |
|
||||||
make dist Q=
|
make dist Q=
|
||||||
ls
|
ls
|
||||||
- uses: actions/download-artifact@v4
|
- name: Download Linux binaries
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
- name: Release
|
- name: Release
|
||||||
uses: softprops/action-gh-release@v2
|
uses: softprops/action-gh-release@v2
|
||||||
with:
|
with:
|
||||||
body: |
|
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.
|
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)
|
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...
|
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:
|
on:
|
||||||
release:
|
release:
|
||||||
types:
|
types:
|
||||||
@@ -7,7 +7,7 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
if: github.repository_owner == 'gbdev'
|
if: github.repository_owner == 'gbdev'
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout rgbds@release
|
- name: Checkout rgbds@release
|
||||||
uses: actions/checkout@v4
|
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:
|
on:
|
||||||
- push
|
- push
|
||||||
- pull_request
|
- pull_request
|
||||||
@@ -17,7 +17,8 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- name: Checkout repo
|
||||||
|
uses: actions/checkout@v4
|
||||||
- name: Install deps
|
- name: Install deps
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
@@ -32,9 +33,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_COMPILER=${{ matrix.cxx }} -DSANITIZERS=ON -DMORE_WARNINGS=ON
|
cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_COMPILER=${{ matrix.cxx }} -DSANITIZERS=ON -DMORE_WARNINGS=ON
|
||||||
cmake --build build -j --verbose
|
cmake --build build -j --verbose
|
||||||
cp build/src/rgb{asm,link,fix,gfx} .
|
|
||||||
sudo cmake --install build --verbose
|
sudo cmake --install build --verbose
|
||||||
cmake --install build --verbose --component "Test support programs"
|
|
||||||
- name: Package binaries
|
- name: Package binaries
|
||||||
run: |
|
run: |
|
||||||
mkdir bins
|
mkdir bins
|
||||||
@@ -58,8 +57,8 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
path: ${{ fromJSON(steps.test-deps-cache-params.outputs.paths) }}
|
path: ${{ fromJSON(steps.test-deps-cache-params.outputs.paths) }}
|
||||||
key: ${{ matrix.os }}-${{ steps.test-deps-cache-params.outputs.hash }}
|
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
|
continue-on-error: true
|
||||||
run: |
|
run: |
|
||||||
test/fetch-test-deps.sh
|
test/fetch-test-deps.sh
|
||||||
@@ -75,7 +74,8 @@ jobs:
|
|||||||
macos-static:
|
macos-static:
|
||||||
runs-on: macos-14
|
runs-on: macos-14
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- name: Checkout repo
|
||||||
|
uses: actions/checkout@v4
|
||||||
- name: Install deps
|
- name: Install deps
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
@@ -109,8 +109,8 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
path: ${{ fromJSON(steps.test-deps-cache-params.outputs.paths) }}
|
path: ${{ fromJSON(steps.test-deps-cache-params.outputs.paths) }}
|
||||||
key: ${{ matrix.os }}-${{ steps.test-deps-cache-params.outputs.hash }}
|
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
|
continue-on-error: true
|
||||||
run: |
|
run: |
|
||||||
test/fetch-test-deps.sh
|
test/fetch-test-deps.sh
|
||||||
@@ -138,30 +138,32 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- name: Checkout repo
|
||||||
|
uses: actions/checkout@v4
|
||||||
- name: Install deps
|
- name: Install deps
|
||||||
run: .github/scripts/get_win_deps.ps1
|
run: .github/scripts/get_win_deps.ps1
|
||||||
- uses: actions/cache@v4
|
- name: Check libraries cache
|
||||||
id: cache
|
id: cache
|
||||||
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
zbuild
|
zbuild
|
||||||
pngbuild
|
pngbuild
|
||||||
key: ${{ matrix.arch }}-${{ hashFiles('zlib/**', 'libpng/**') }}
|
key: ${{ matrix.arch }}-${{ hashFiles('zlib/**', 'libpng/**') }}
|
||||||
- name: Build zlib
|
- name: Build zlib
|
||||||
|
if: steps.cache.outputs.cache-hit != 'true'
|
||||||
run: | # BUILD_SHARED_LIBS causes the output DLL to be correctly called `zlib1.dll`
|
run: | # BUILD_SHARED_LIBS causes the output DLL to be correctly called `zlib1.dll`
|
||||||
cmake -S zlib -B zbuild -A ${{ matrix.platform }} -Wno-dev -DCMAKE_INSTALL_PREFIX=install_dir -DBUILD_SHARED_LIBS=ON
|
cmake -S zlib -B zbuild -A ${{ matrix.platform }} -Wno-dev -DCMAKE_INSTALL_PREFIX=install_dir -DBUILD_SHARED_LIBS=ON
|
||||||
cmake --build zbuild --config Release -j
|
cmake --build zbuild --config Release -j
|
||||||
if: steps.cache.outputs.cache-hit != 'true'
|
|
||||||
- name: Install zlib
|
- name: Install zlib
|
||||||
run: |
|
run: |
|
||||||
cmake --install zbuild
|
cmake --install zbuild
|
||||||
- name: Build libpng
|
- name: Build libpng
|
||||||
|
if: steps.cache.outputs.cache-hit != 'true'
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
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 -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
|
cmake --build pngbuild --config Release -j
|
||||||
if: steps.cache.outputs.cache-hit != 'true'
|
|
||||||
- name: Install libpng
|
- name: Install libpng
|
||||||
run: |
|
run: |
|
||||||
cmake --install pngbuild
|
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 -S . -B build -A ${{ matrix.platform }} -DCMAKE_INSTALL_PREFIX=install_dir -DCMAKE_BUILD_TYPE=Release
|
||||||
cmake --build build --config Release -j --verbose
|
cmake --build build --config Release -j --verbose
|
||||||
cmake --install build --verbose --prefix install_dir
|
cmake --install build --verbose --prefix install_dir
|
||||||
cmake --install build --verbose --component "Test support programs"
|
|
||||||
- name: Package binaries
|
- name: Package binaries
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
@@ -196,8 +197,8 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
path: ${{ fromJSON(steps.test-deps-cache-params.outputs.paths) }}
|
path: ${{ fromJSON(steps.test-deps-cache-params.outputs.paths) }}
|
||||||
key: ${{ matrix.os }}-${{ matrix.bits }}-${{ steps.test-deps-cache-params.outputs.hash }}
|
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
|
shell: bash
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
run: |
|
run: |
|
||||||
@@ -229,7 +230,8 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
DIST_DIR: win${{ matrix.bits }}
|
DIST_DIR: win${{ matrix.bits }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- name: Checkout repo
|
||||||
|
uses: actions/checkout@v4
|
||||||
- name: Install deps
|
- name: Install deps
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
@@ -273,7 +275,8 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- name: Checkout repo
|
||||||
|
uses: actions/checkout@v4
|
||||||
- name: Retrieve binaries
|
- name: Retrieve binaries
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
@@ -303,8 +306,8 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
path: ${{ fromJSON(steps.test-deps-cache-params.outputs.paths) }}
|
path: ${{ fromJSON(steps.test-deps-cache-params.outputs.paths) }}
|
||||||
key: mingw-${{ matrix.bits }}-${{ steps.test-deps-cache-params.outputs.hash }}
|
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
|
shell: bash
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
run: |
|
run: |
|
||||||
@@ -317,3 +320,69 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
test/run-tests.sh
|
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:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
@@ -9,6 +9,7 @@ on:
|
|||||||
- man/rgbds.7
|
- man/rgbds.7
|
||||||
- man/rgbasm.1
|
- man/rgbasm.1
|
||||||
- man/rgbasm.5
|
- man/rgbasm.5
|
||||||
|
- man/rgbasm-old.5
|
||||||
- man/rgblink.1
|
- man/rgblink.1
|
||||||
- man/rgblink.5
|
- man/rgblink.5
|
||||||
- man/rgbfix.1
|
- man/rgbfix.1
|
||||||
@@ -17,7 +18,7 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
if: github.repository_owner == 'gbdev'
|
if: github.repository_owner == 'gbdev'
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout rgbds@master
|
- name: Checkout rgbds@master
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -13,4 +13,5 @@
|
|||||||
CMakeCache.txt
|
CMakeCache.txt
|
||||||
CMakeFiles/
|
CMakeFiles/
|
||||||
cmake_install.cmake
|
cmake_install.cmake
|
||||||
|
build/
|
||||||
callgrind.out.*
|
callgrind.out.*
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
# 3.9 required for LTO checks
|
# 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
|
project(rgbds
|
||||||
LANGUAGES CXX)
|
LANGUAGES CXX)
|
||||||
|
|
||||||
|
include(CTest)
|
||||||
|
|
||||||
# get real path of source and binary directories
|
# get real path of source and binary directories
|
||||||
get_filename_component(srcdir "${CMAKE_SOURCE_DIR}" REALPATH)
|
get_filename_component(srcdir "${CMAKE_SOURCE_DIR}" REALPATH)
|
||||||
get_filename_component(bindir "${CMAKE_BINARY_DIR}" REALPATH)
|
get_filename_component(bindir "${CMAKE_BINARY_DIR}" REALPATH)
|
||||||
@@ -41,13 +44,9 @@ else()
|
|||||||
# does not recognize this yet.
|
# does not recognize this yet.
|
||||||
add_compile_options(-Wno-gnu-zero-variadic-macro-arguments)
|
add_compile_options(-Wno-gnu-zero-variadic-macro-arguments)
|
||||||
endif()
|
endif()
|
||||||
add_definitions(-D_POSIX_C_SOURCE=200809L)
|
|
||||||
if(SANITIZERS)
|
if(SANITIZERS)
|
||||||
set(SAN_FLAGS -fsanitize=shift -fsanitize=integer-divide-by-zero
|
set(SAN_FLAGS -fsanitize=address -fsanitize=undefined
|
||||||
-fsanitize=unreachable -fsanitize=vla-bound
|
-fsanitize=float-divide-by-zero)
|
||||||
-fsanitize=signed-integer-overflow -fsanitize=bounds
|
|
||||||
-fsanitize=object-size -fsanitize=bool -fsanitize=enum
|
|
||||||
-fsanitize=alignment -fsanitize=null -fsanitize=address)
|
|
||||||
add_compile_options(${SAN_FLAGS})
|
add_compile_options(${SAN_FLAGS})
|
||||||
add_link_options(${SAN_FLAGS})
|
add_link_options(${SAN_FLAGS})
|
||||||
add_definitions(-D_GLIBCXX_ASSERTIONS)
|
add_definitions(-D_GLIBCXX_ASSERTIONS)
|
||||||
@@ -59,7 +58,7 @@ else()
|
|||||||
if(MORE_WARNINGS)
|
if(MORE_WARNINGS)
|
||||||
add_compile_options(-Werror -Wextra
|
add_compile_options(-Werror -Wextra
|
||||||
-Walloc-zero -Wcast-align -Wcast-qual -Wduplicated-branches -Wduplicated-cond
|
-Walloc-zero -Wcast-align -Wcast-qual -Wduplicated-branches -Wduplicated-cond
|
||||||
-Wfloat-equal -Wlogical-op -Wnull-dereference -Wshift-overflow=2
|
-Wfloat-equal -Wlogical-op -Wnull-dereference -Wold-style-cast -Wshift-overflow=2
|
||||||
-Wstringop-overflow=4 -Wundef -Wuninitialized -Wunused
|
-Wstringop-overflow=4 -Wundef -Wuninitialized -Wunused
|
||||||
-Wshadow # TODO: -Wshadow=compatible-local?
|
-Wshadow # TODO: -Wshadow=compatible-local?
|
||||||
-Wformat=2 -Wformat-overflow=2 -Wformat-truncation=1
|
-Wformat=2 -Wformat-overflow=2 -Wformat-truncation=1
|
||||||
@@ -76,7 +75,7 @@ endif()
|
|||||||
|
|
||||||
find_program(GIT git)
|
find_program(GIT git)
|
||||||
if(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}
|
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||||
OUTPUT_VARIABLE GIT_REV OUTPUT_STRIP_TRAILING_WHITESPACE
|
OUTPUT_VARIABLE GIT_REV OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||||
ERROR_QUIET)
|
ERROR_QUIET)
|
||||||
@@ -88,6 +87,8 @@ endif(GIT)
|
|||||||
find_package(PkgConfig)
|
find_package(PkgConfig)
|
||||||
if(MSVC OR NOT PKG_CONFIG_FOUND)
|
if(MSVC OR NOT PKG_CONFIG_FOUND)
|
||||||
# fallback to find_package
|
# 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)
|
find_package(PNG REQUIRED)
|
||||||
else()
|
else()
|
||||||
pkg_check_modules(LIBPNG REQUIRED libpng)
|
pkg_check_modules(LIBPNG REQUIRED libpng)
|
||||||
@@ -99,6 +100,7 @@ set(CMAKE_CXX_STANDARD 20)
|
|||||||
set(CMAKE_CXX_STANDARD_REQUIRED True)
|
set(CMAKE_CXX_STANDARD_REQUIRED True)
|
||||||
|
|
||||||
add_subdirectory(src)
|
add_subdirectory(src)
|
||||||
|
set(CMAKE_CTEST_ARGUMENTS "--verbose")
|
||||||
add_subdirectory(test)
|
add_subdirectory(test)
|
||||||
|
|
||||||
# By default, build in Release mode; Debug mode must be explicitly requested
|
# 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/rgbgfx.1"
|
||||||
"man/rgblink.1")
|
"man/rgblink.1")
|
||||||
set(man5 "man/rgbasm.5"
|
set(man5 "man/rgbasm.5"
|
||||||
|
"man/rgbasm-old.5"
|
||||||
"man/rgblink.5"
|
"man/rgblink.5"
|
||||||
"man/rgbds.5")
|
"man/rgbds.5")
|
||||||
set(man7 "man/gbz80.7"
|
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
|
```sh
|
||||||
test_downstream <owner> <repo> <makefile target> <build file> <sha1 hash of build file>
|
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
|
LABEL org.opencontainers.image.source=https://github.com/gbdev/rgbds
|
||||||
ARG version=0.9.0-rc1
|
ARG version=0.9.1
|
||||||
WORKDIR /rgbds
|
WORKDIR /rgbds
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
@@ -8,7 +8,7 @@ COPY . .
|
|||||||
RUN apt-get update && \
|
RUN apt-get update && \
|
||||||
apt-get install sudo make cmake gcc build-essential -y
|
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 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
|
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`
|
PNGLDLIBS := `${PKG_CONFIG} --libs-only-l libpng`
|
||||||
|
|
||||||
# Note: if this comes up empty, `version.cpp` will automatically fall back to last release number
|
# Note: if this comes up empty, `version.cpp` will automatically fall back to last release number
|
||||||
VERSION_STRING := `git --git-dir=.git 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 \
|
WARNFLAGS := -Wall -pedantic -Wno-unknown-warning-option -Wno-gnu-zero-variadic-macro-arguments
|
||||||
-Wno-gnu-zero-variadic-macro-arguments
|
|
||||||
|
|
||||||
# Overridable CXXFLAGS
|
# Overridable CXXFLAGS
|
||||||
CXXFLAGS ?= -O3 -flto -DNDEBUG
|
CXXFLAGS ?= -O3 -flto -DNDEBUG
|
||||||
# Non-overridable CXXFLAGS
|
# Non-overridable CXXFLAGS
|
||||||
REALCXXFLAGS := ${CXXFLAGS} ${WARNFLAGS} -std=c++2a -I include \
|
REALCXXFLAGS := ${CXXFLAGS} ${WARNFLAGS} -std=c++2a -I include -fno-exceptions -fno-rtti
|
||||||
-D_POSIX_C_SOURCE=200809L -fno-exceptions -fno-rtti
|
|
||||||
# Overridable LDFLAGS
|
# Overridable LDFLAGS
|
||||||
LDFLAGS ?=
|
LDFLAGS ?=
|
||||||
# Non-overridable LDFLAGS
|
# Non-overridable LDFLAGS
|
||||||
REALLDFLAGS := ${LDFLAGS} ${WARNFLAGS} \
|
REALLDFLAGS := ${LDFLAGS} ${WARNFLAGS} -DBUILD_VERSION_STRING=\"${VERSION_STRING}\"
|
||||||
-DBUILD_VERSION_STRING=\"${VERSION_STRING}\"
|
|
||||||
|
|
||||||
# Wrapper around bison that passes flags depending on what the version supports
|
# Wrapper around bison that passes flags depending on what the version supports
|
||||||
BISON := src/bison.sh
|
BISON := src/bison.sh
|
||||||
@@ -188,7 +185,7 @@ install: all
|
|||||||
$Qinstall ${STRIP} -m ${BINMODE} rgbfix ${DESTDIR}${bindir}/rgbfix${SUFFIX}
|
$Qinstall ${STRIP} -m ${BINMODE} rgbfix ${DESTDIR}${bindir}/rgbfix${SUFFIX}
|
||||||
$Qinstall ${STRIP} -m ${BINMODE} rgbgfx ${DESTDIR}${bindir}/rgbgfx${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/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/
|
$Qinstall -m ${MANMODE} man/rgbds.7 man/gbz80.7 ${DESTDIR}${mandir}/man7/
|
||||||
|
|
||||||
# Target used to check for suspiciously missing changed files.
|
# Target used to check for suspiciously missing changed files.
|
||||||
@@ -204,17 +201,13 @@ checkdiff:
|
|||||||
develop:
|
develop:
|
||||||
$Q${MAKE} WARNFLAGS="${WARNFLAGS} -Werror -Wextra \
|
$Q${MAKE} WARNFLAGS="${WARNFLAGS} -Werror -Wextra \
|
||||||
-Walloc-zero -Wcast-align -Wcast-qual -Wduplicated-branches -Wduplicated-cond \
|
-Walloc-zero -Wcast-align -Wcast-qual -Wduplicated-branches -Wduplicated-cond \
|
||||||
-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 \
|
-Wstringop-overflow=4 -Wundef -Wuninitialized -Wunused -Wshadow \
|
||||||
-Wformat=2 -Wformat-overflow=2 -Wformat-truncation=1 \
|
-Wformat=2 -Wformat-overflow=2 -Wformat-truncation=1 \
|
||||||
-Wno-format-nonliteral -Wno-strict-overflow -Wno-unused-but-set-variable \
|
-Wno-format-nonliteral -Wno-strict-overflow -Wno-unused-but-set-variable \
|
||||||
-Wno-type-limits -Wno-tautological-constant-out-of-range-compare -Wvla \
|
-Wno-type-limits -Wno-tautological-constant-out-of-range-compare -Wvla \
|
||||||
-D_GLIBCXX_ASSERTIONS \
|
-D_GLIBCXX_ASSERTIONS -fsanitize=address -fsanitize=undefined \
|
||||||
-fsanitize=shift -fsanitize=integer-divide-by-zero \
|
-fsanitize=float-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" \
|
|
||||||
CXXFLAGS="-ggdb3 -Og -fno-omit-frame-pointer -fno-optimize-sibling-calls"
|
CXXFLAGS="-ggdb3 -Og -fno-omit-frame-pointer -fno-optimize-sibling-calls"
|
||||||
|
|
||||||
# This target is used during development in order to more easily debug with gdb.
|
# This target is used during development in order to more easily debug with gdb.
|
||||||
@@ -272,4 +265,4 @@ wine-shim:
|
|||||||
|
|
||||||
dist:
|
dist:
|
||||||
$Qgit ls-files | sed s~^~$${PWD##*/}/~ \
|
$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).
|
this version as [rgbds-linux](https://github.com/vegard/rgbds-linux).
|
||||||
- 2010-01-12: Anthony J. Bentley [forks](https://github.com/bentley) Nossum's
|
- 2010-01-12: Anthony J. Bentley [forks](https://github.com/bentley) Nossum's
|
||||||
repository. The fork becomes the reference implementation of RGBDS.
|
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),
|
- 2015-01-18: stag019 begins implementing [RGBGFX](https://github.com/stag019/rgbgfx),
|
||||||
a PNG‐to‐Game Boy graphics converter, for eventual integration into RGBDS.
|
a PNG‐to‐Game Boy graphics converter, for eventual integration into RGBDS.
|
||||||
- 2016-09-05: RGBGFX is [integrated](https://github.com/gbdev/rgbds/commit/c3c31138ddbd8680d4e67957e387f2816798a71b)
|
- 2016-09-05: RGBGFX is
|
||||||
|
[integrated](https://github.com/gbdev/rgbds/commit/c3c31138ddbd8680d4e67957e387f2816798a71b)
|
||||||
into Bentley's repository.
|
into Bentley's repository.
|
||||||
- 2017-02-23: Bentley's repository is moved to the [rednex](https://github.com/rednex)
|
- 2017-02-23: Bentley's repository is moved to the [rednex](https://github.com/rednex)
|
||||||
organization.
|
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
|
This describes for the maintainers of RGBDS how to publish a new release on
|
||||||
GitHub.
|
GitHub.
|
||||||
|
|
||||||
1. Update, commit, and push [include/version.hpp](include/version.hpp) with
|
1. Update the following files, then commit and push.
|
||||||
values for `PACKAGE_VERSION_MAJOR`, `PACKAGE_VERSION_MINOR`,
|
You can use <code>git commit -m "Release <i><version></i>"</code> and `git push origin master`.
|
||||||
`PACKAGE_VERSION_PATCH`, and `PACKAGE_VERSION_RC`, as well as
|
|
||||||
[Dockerfile](Dockerfile) with a value for `ARG version`. Only define
|
- [include/version.hpp](include/version.hpp): set appropriate values for `PACKAGE_VERSION_MAJOR`,
|
||||||
`PACKAGE_VERSION_RC` if you are publishing a release candidate! You can
|
`PACKAGE_VERSION_MINOR`, `PACKAGE_VERSION_PATCH`, and `PACKAGE_VERSION_RC`.
|
||||||
use <code>git commit -m "Release <i><version></i>"</code> and
|
**Only** define `PACKAGE_VERSION_RC` if you are publishing a release candidate!
|
||||||
`git push origin master`.
|
- [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>,
|
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>
|
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)
|
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).
|
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
|
1. Clone [rgbds-www](https://github.com/gbdev/rgbds-www). You can use
|
||||||
`git clone https://github.com/gbdev/rgbds-www.git`.
|
`git clone https://github.com/gbdev/rgbds-www.git`.
|
||||||
@@ -52,8 +57,8 @@ GitHub.
|
|||||||
|
|
||||||
If you do not have `groff` installed, you can change
|
If you do not have `groff` installed, you can change
|
||||||
`groff -Tpdf -mdoc -wall` to `mandoc -Tpdf -I os=Linux` in
|
`groff -Tpdf -mdoc -wall` to `mandoc -Tpdf -I os=Linux` in
|
||||||
[.github/actions/get-pages.sh](.github/actions/get-pages.sh) and it
|
[maintainer/man_to_html.sh](https://github.com/gbdev/rgbds-www/blob/master/maintainer/man_to_html.sh)
|
||||||
will suffice.
|
and it will suffice.
|
||||||
|
|
||||||
4. Commit and push the documentation. You can use <code>git commit -m
|
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`
|
"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!
|
6. Click the "Publish release" button to publish it!
|
||||||
|
|
||||||
7. Update the `release` branch. You can use `git push origin release`.
|
7. Update the `release` branch. You can use `git push origin 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`
|
# See the `state` variable below for info about `state_after`
|
||||||
declare -A opts=(
|
declare -A opts=(
|
||||||
[V]="version:normal"
|
[V]="version:normal"
|
||||||
|
[h]="help:normal"
|
||||||
[E]="export-all:normal"
|
[E]="export-all:normal"
|
||||||
[v]="verbose:normal"
|
[v]="verbose:normal"
|
||||||
[w]=":normal"
|
[w]=":normal"
|
||||||
@@ -192,6 +193,8 @@ _rgbasm_completions() {
|
|||||||
shift-amount
|
shift-amount
|
||||||
truncation
|
truncation
|
||||||
unmapped-char
|
unmapped-char
|
||||||
|
unmatched-directive
|
||||||
|
unterminated-load
|
||||||
user
|
user
|
||||||
all
|
all
|
||||||
extra
|
extra
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ _rgbfix_completions() {
|
|||||||
# See the `state` variable below for info about `state_after`
|
# See the `state` variable below for info about `state_after`
|
||||||
declare -A opts=(
|
declare -A opts=(
|
||||||
[V]="version:normal"
|
[V]="version:normal"
|
||||||
|
[h]="help:normal"
|
||||||
[j]="non-japanese:normal"
|
[j]="non-japanese:normal"
|
||||||
[s]="sgb-compatible:normal"
|
[s]="sgb-compatible:normal"
|
||||||
[v]="validate:normal"
|
[v]="validate:normal"
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ _rgbgfx_completions() {
|
|||||||
# See the `state` variable below for info about `state_after`
|
# See the `state` variable below for info about `state_after`
|
||||||
declare -A opts=(
|
declare -A opts=(
|
||||||
[V]="version:normal"
|
[V]="version:normal"
|
||||||
|
[h]="help:normal"
|
||||||
[C]="color-curve:normal"
|
[C]="color-curve:normal"
|
||||||
[m]="mirror-tiles:normal"
|
[m]="mirror-tiles:normal"
|
||||||
[O]="group-outputs:normal"
|
[O]="group-outputs:normal"
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ _rgblink_completions() {
|
|||||||
# See the `state` variable below for info about `state_after`
|
# See the `state` variable below for info about `state_after`
|
||||||
declare -A opts=(
|
declare -A opts=(
|
||||||
[V]="version:normal"
|
[V]="version:normal"
|
||||||
|
[h]="help:normal"
|
||||||
[d]="dmg:normal"
|
[d]="dmg:normal"
|
||||||
[t]="tiny:normal"
|
[t]="tiny:normal"
|
||||||
[v]="verbose:normal"
|
[v]="verbose:normal"
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ _rgbasm_warnings() {
|
|||||||
'shift-amount:Warn when a shift'\''s operand it negative or \> 32'
|
'shift-amount:Warn when a shift'\''s operand it negative or \> 32'
|
||||||
'truncation:Warn when implicit truncation loses bits'
|
'truncation:Warn when implicit truncation loses bits'
|
||||||
'unmapped-char:Warn on unmapped character'
|
'unmapped-char:Warn on unmapped character'
|
||||||
|
'unmatched-directive:Warn on unmatched directive pair'
|
||||||
|
'unterminated-load:Warn on LOAD without ENDL'
|
||||||
'user:Warn when executing the WARN built-in'
|
'user:Warn when executing the WARN built-in'
|
||||||
)
|
)
|
||||||
# TODO: handle `no-` and `error=` somehow?
|
# TODO: handle `no-` and `error=` somehow?
|
||||||
@@ -34,8 +36,9 @@ _rgbasm_warnings() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
local args=(
|
local args=(
|
||||||
# Arguments are listed here in the same order as in the manual, except for the version
|
# Arguments are listed here in the same order as in the manual, except for the version and help
|
||||||
'(- : * options)'{-V,--version}'[Print version number]'
|
'(- : * 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]'
|
'(-E --export-all)'{-E,--export-all}'[Export all symbols]'
|
||||||
'(-v --verbose)'{-v,--verbose}'[Print additional messages regarding progression]'
|
'(-v --verbose)'{-v,--verbose}'[Print additional messages regarding progression]'
|
||||||
|
|||||||
@@ -35,8 +35,9 @@ _mbc_names() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
local args=(
|
local args=(
|
||||||
# Arguments are listed here in the same order as in the manual, except for the version
|
# Arguments are listed here in the same order as in the manual, except for the version and help
|
||||||
'(- : * options)'{-V,--version}'[Print version number]'
|
'(- : * 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-only}'[Mark ROM as GBC-only]'
|
||||||
'(-C --color-only -c --color-compatible)'{-c,--color-compatible}'[Mark ROM as GBC-compatible]'
|
'(-C --color-only -c --color-compatible)'{-c,--color-compatible}'[Mark ROM as GBC-compatible]'
|
||||||
|
|||||||
@@ -10,8 +10,9 @@ _depths() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
local args=(
|
local args=(
|
||||||
# Arguments are listed here in the same order as in the manual, except for the version
|
# Arguments are listed here in the same order as in the manual, except for the version and help
|
||||||
'(- : * options)'{-V,--version}'[Print version number]'
|
'(- : * 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]'
|
'(-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]'
|
'(-C --color-curve)'{-C,--color-curve}'[Generate palettes using GBC color curve]'
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
#compdef rgblink
|
#compdef rgblink
|
||||||
|
|
||||||
local args=(
|
local args=(
|
||||||
# Arguments are listed here in the same order as in the manual, except for the version
|
# Arguments are listed here in the same order as in the manual, except for the version and help
|
||||||
'(- : * options)'{-V,--version}'[Print version number]'
|
'(- : * 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)]'
|
'(-d --dmg)'{-d,--dmg}'[Enable DMG mode (-w + no VRAM banking)]'
|
||||||
'(-t --tiny)'{-t,--tiny}'[Enable tiny mode, disabling ROM 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
|
#ifndef RGBDS_ASM_CHARMAP_HPP
|
||||||
#define 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_Set(std::string const &name);
|
||||||
void charmap_Push();
|
void charmap_Push();
|
||||||
void charmap_Pop();
|
void charmap_Pop();
|
||||||
|
void charmap_CheckStack();
|
||||||
void charmap_Add(std::string const &mapping, std::vector<int32_t> &&value);
|
void charmap_Add(std::string const &mapping, std::vector<int32_t> &&value);
|
||||||
bool charmap_HasChar(std::string const &input);
|
bool charmap_HasChar(std::string const &input);
|
||||||
std::vector<int32_t> charmap_Convert(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
|
#ifndef RGBDS_ASM_FIXPOINT_HPP
|
||||||
#define RGBDS_ASM_FIXPOINT_HPP
|
#define RGBDS_ASM_FIXPOINT_HPP
|
||||||
@@ -8,7 +8,6 @@
|
|||||||
extern uint8_t fixPrecision;
|
extern uint8_t fixPrecision;
|
||||||
|
|
||||||
uint8_t fix_Precision();
|
uint8_t fix_Precision();
|
||||||
double fix_PrecisionFactor();
|
|
||||||
int32_t fix_Sin(int32_t i, int32_t q);
|
int32_t fix_Sin(int32_t i, int32_t q);
|
||||||
int32_t fix_Cos(int32_t i, int32_t q);
|
int32_t fix_Cos(int32_t i, int32_t q);
|
||||||
int32_t fix_Tan(int32_t i, int32_t q);
|
int32_t fix_Tan(int32_t i, int32_t q);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef RGBDS_ASM_FORMAT_HPP
|
#ifndef RGBDS_ASM_FORMAT_HPP
|
||||||
#define 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
|
// 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
|
// Meaningless at the root level, but gets written to the object file anyway, so init it
|
||||||
uint32_t lineNo = 0;
|
uint32_t lineNo = 0;
|
||||||
|
|
||||||
// Set only if referenced: ID within the object file, -1 if not output yet
|
// Set only if referenced: ID within the object file, `UINT32_MAX` if not output yet
|
||||||
uint32_t ID = -1;
|
uint32_t ID = UINT32_MAX;
|
||||||
|
|
||||||
// REPT iteration counts since last named node, in reverse depth order
|
// REPT iteration counts since last named node, in reverse depth order
|
||||||
std::vector<uint32_t> &iters() { return data.get<std::vector<uint32_t>>(); }
|
std::vector<uint32_t> &iters() { return data.get<std::vector<uint32_t>>(); }
|
||||||
@@ -41,15 +41,11 @@ struct FileStackNode {
|
|||||||
std::string const &name() const { return data.get<std::string>(); }
|
std::string const &name() const { return data.get<std::string>(); }
|
||||||
|
|
||||||
FileStackNode(FileStackNodeType type_, Either<std::vector<uint32_t>, std::string> data_)
|
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;
|
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;
|
extern size_t maxRecursionDepth;
|
||||||
|
|
||||||
struct MacroArgs;
|
struct MacroArgs;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef RGBDS_ASM_LEXER_HPP
|
#ifndef RGBDS_ASM_LEXER_HPP
|
||||||
#define 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
|
// 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).
|
// 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
|
// The buffer needs to be large enough for the maximum `lexerState->peek()` lookahead distance
|
||||||
static_assert(LEXER_BUF_SIZE > 1, "Lexer buffer size is too small");
|
static_assert(LEXER_BUF_SIZE > 1, "Lexer buffer size is too small");
|
||||||
// This caps the size of buffer reads, and according to POSIX, passing more than SSIZE_MAX is UB
|
// 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;
|
LexerMode mode;
|
||||||
bool atLineStart;
|
bool atLineStart;
|
||||||
uint32_t lineNo;
|
uint32_t lineNo;
|
||||||
uint32_t colNo;
|
|
||||||
int lastToken;
|
int lastToken;
|
||||||
|
|
||||||
std::deque<IfStackEntry> ifStack;
|
std::deque<IfStackEntry> ifStack;
|
||||||
@@ -147,7 +146,6 @@ void lexer_ReachELSEBlock();
|
|||||||
|
|
||||||
void lexer_CheckRecursionDepth();
|
void lexer_CheckRecursionDepth();
|
||||||
uint32_t lexer_GetLineNo();
|
uint32_t lexer_GetLineNo();
|
||||||
uint32_t lexer_GetColNo();
|
|
||||||
void lexer_DumpStringExpansions();
|
void lexer_DumpStringExpansions();
|
||||||
|
|
||||||
struct Capture {
|
struct Capture {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef RGBDS_ASM_MACRO_HPP
|
#ifndef RGBDS_ASM_MACRO_HPP
|
||||||
#define 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
|
#ifndef RGBDS_ASM_MAIN_HPP
|
||||||
#define 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
|
#ifndef RGBDS_ASM_OPT_HPP
|
||||||
#define RGBDS_ASM_OPT_HPP
|
#define RGBDS_ASM_OPT_HPP
|
||||||
@@ -14,5 +14,6 @@ void opt_Parse(char const *option);
|
|||||||
|
|
||||||
void opt_Push();
|
void opt_Push();
|
||||||
void opt_Pop();
|
void opt_Pop();
|
||||||
|
void opt_CheckStack();
|
||||||
|
|
||||||
#endif // RGBDS_ASM_OPT_HPP
|
#endif // RGBDS_ASM_OPT_HPP
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef RGBDS_ASM_OUTPUT_HPP
|
#ifndef RGBDS_ASM_OUTPUT_HPP
|
||||||
#define RGBDS_ASM_OUTPUT_HPP
|
#define RGBDS_ASM_OUTPUT_HPP
|
||||||
@@ -14,14 +14,7 @@
|
|||||||
struct Expression;
|
struct Expression;
|
||||||
struct FileStackNode;
|
struct FileStackNode;
|
||||||
|
|
||||||
enum StateFeature {
|
enum StateFeature { STATE_EQU, STATE_VAR, STATE_EQUS, STATE_CHAR, STATE_MACRO, NB_STATE_FEATURES };
|
||||||
STATE_EQU,
|
|
||||||
STATE_VAR,
|
|
||||||
STATE_EQUS,
|
|
||||||
STATE_CHAR,
|
|
||||||
STATE_MACRO,
|
|
||||||
NB_STATE_FEATURES
|
|
||||||
};
|
|
||||||
|
|
||||||
extern std::string objectFileName;
|
extern std::string objectFileName;
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef RGBDS_ASM_RPN_HPP
|
#ifndef RGBDS_ASM_RPN_HPP
|
||||||
#define RGBDS_ASM_RPN_HPP
|
#define RGBDS_ASM_RPN_HPP
|
||||||
@@ -31,12 +31,8 @@ struct Expression {
|
|||||||
|
|
||||||
Expression &operator=(Expression &&) = default;
|
Expression &operator=(Expression &&) = default;
|
||||||
|
|
||||||
bool isKnown() const {
|
bool isKnown() const { return data.holds<int32_t>(); }
|
||||||
return data.holds<int32_t>();
|
int32_t value() const { return data.get<int32_t>(); }
|
||||||
}
|
|
||||||
int32_t value() const {
|
|
||||||
return data.get<int32_t>();
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t getConstVal() const;
|
int32_t getConstVal() const;
|
||||||
Symbol const *symbolOf() const;
|
Symbol const *symbolOf() const;
|
||||||
@@ -53,7 +49,7 @@ struct Expression {
|
|||||||
void makeUnaryOp(RPNCommand op, Expression &&src);
|
void makeUnaryOp(RPNCommand op, Expression &&src);
|
||||||
void makeBinaryOp(RPNCommand op, Expression &&src1, Expression const &src2);
|
void makeBinaryOp(RPNCommand op, Expression &&src1, Expression const &src2);
|
||||||
|
|
||||||
void makeCheckHRAM();
|
bool makeCheckHRAM();
|
||||||
void makeCheckRST();
|
void makeCheckRST();
|
||||||
|
|
||||||
void checkNBit(uint8_t n) const;
|
void checkNBit(uint8_t n) const;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef RGBDS_ASM_SECTION_HPP
|
#ifndef RGBDS_ASM_SECTION_HPP
|
||||||
#define RGBDS_ASM_SECTION_HPP
|
#define RGBDS_ASM_SECTION_HPP
|
||||||
@@ -70,7 +70,8 @@ void sect_SetLoadSection(
|
|||||||
SectionSpec const &attrs,
|
SectionSpec const &attrs,
|
||||||
SectionModifier mod
|
SectionModifier mod
|
||||||
);
|
);
|
||||||
void sect_EndLoadSection();
|
void sect_EndLoadSection(char const *cause);
|
||||||
|
void sect_CheckLoadClosed();
|
||||||
|
|
||||||
Section *sect_GetSymbolSection();
|
Section *sect_GetSymbolSection();
|
||||||
uint32_t sect_GetSymbolOffset();
|
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_EndSection();
|
||||||
void sect_PushSection();
|
void sect_PushSection();
|
||||||
void sect_PopSection();
|
void sect_PopSection();
|
||||||
|
void sect_CheckStack();
|
||||||
|
|
||||||
#endif // RGBDS_ASM_SECTION_HPP
|
#endif // RGBDS_ASM_SECTION_HPP
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef RGBDS_ASM_SYMBOL_HPP
|
#ifndef RGBDS_ASM_SYMBOL_HPP
|
||||||
#define RGBDS_ASM_SYMBOL_HPP
|
#define RGBDS_ASM_SYMBOL_HPP
|
||||||
@@ -45,7 +45,7 @@ struct Symbol {
|
|||||||
>
|
>
|
||||||
data;
|
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
|
uint32_t defIndex; // Ordering of the symbol in the state file
|
||||||
|
|
||||||
bool isDefined() const { return type != SYM_REF; }
|
bool isDefined() const { return type != SYM_REF; }
|
||||||
@@ -55,7 +55,7 @@ struct Symbol {
|
|||||||
bool isConstant() const {
|
bool isConstant() const {
|
||||||
if (type == SYM_LABEL) {
|
if (type == SYM_LABEL) {
|
||||||
Section const *sect = getSection();
|
Section const *sect = getSection();
|
||||||
return sect && sect->org != (uint32_t)-1;
|
return sect && sect->org != UINT32_MAX;
|
||||||
}
|
}
|
||||||
return type == SYM_EQU || type == SYM_VAR;
|
return type == SYM_EQU || type == SYM_VAR;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,35 +1,34 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef RGBDS_ASM_WARNING_HPP
|
#ifndef RGBDS_ASM_WARNING_HPP
|
||||||
#define RGBDS_ASM_WARNING_HPP
|
#define RGBDS_ASM_WARNING_HPP
|
||||||
|
|
||||||
extern unsigned int nbErrors, maxErrors;
|
extern unsigned int nbErrors, maxErrors;
|
||||||
|
|
||||||
enum WarningState { WARNING_DEFAULT, WARNING_DISABLED, WARNING_ENABLED, WARNING_ERROR };
|
|
||||||
|
|
||||||
enum WarningID {
|
enum WarningID {
|
||||||
WARNING_ASSERT, // Assertions
|
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_BUILTIN_ARG, // Invalid args to builtins
|
||||||
WARNING_CHARMAP_REDEF, // Charmap entry re-definition
|
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_DATA_DIRECTIVE, // `db`, `dw` or `dl` directive without data in ROM
|
||||||
WARNING_EMPTY_MACRO_ARG, // Empty macro argument
|
WARNING_EMPTY_MACRO_ARG, // Empty macro argument
|
||||||
WARNING_EMPTY_STRRPL, // Empty second argument in `STRRPL`
|
WARNING_EMPTY_STRRPL, // Empty second argument in `STRRPL`
|
||||||
WARNING_LARGE_CONSTANT, // Constants too large
|
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_NESTED_COMMENT, // Comment-start delimiter in a block comment
|
||||||
WARNING_OBSOLETE, // Obsolete things
|
WARNING_OBSOLETE, // Obsolete/deprecated things
|
||||||
WARNING_SHIFT, // Shifting undefined behavior
|
WARNING_SHIFT, // Undefined `SHIFT` behavior
|
||||||
WARNING_SHIFT_AMOUNT, // Strange shift amount
|
WARNING_SHIFT_AMOUNT, // Strange `SHIFT` amount
|
||||||
WARNING_USER, // User warnings
|
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,
|
NB_PLAIN_WARNINGS,
|
||||||
|
|
||||||
// Warnings past this point are "parametric" warnings, only mapping to a single flag
|
// Warnings past this point are "parametric" warnings, only mapping to a single flag
|
||||||
#define PARAM_WARNINGS_START NB_PLAIN_WARNINGS
|
|
||||||
// Treating string as number may lose some bits
|
// 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,
|
WARNING_NUMERIC_STRING_2,
|
||||||
// Purging an exported symbol or label
|
// Purging an exported symbol or label
|
||||||
WARNING_PURGE_1,
|
WARNING_PURGE_1,
|
||||||
@@ -41,45 +40,46 @@ enum WarningID {
|
|||||||
WARNING_UNMAPPED_CHAR_1,
|
WARNING_UNMAPPED_CHAR_1,
|
||||||
WARNING_UNMAPPED_CHAR_2,
|
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,
|
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;
|
extern bool warningsAreErrors;
|
||||||
|
|
||||||
void processWarningFlag(char const *flag);
|
void processWarningFlag(char const *flag);
|
||||||
|
|
||||||
/*
|
// Used to warn the user about problems that don't prevent the generation of
|
||||||
* Used to warn the user about problems that don't prevent the generation of
|
// valid code.
|
||||||
* valid code.
|
[[gnu::format(printf, 2, 3)]]
|
||||||
*/
|
void warning(WarningID id, char const *fmt, ...);
|
||||||
[[gnu::format(printf, 2, 3)]] void warning(WarningID id, char const *fmt, ...);
|
|
||||||
|
|
||||||
/*
|
// Used for errors that compromise the whole assembly process by affecting the
|
||||||
* Used for errors that compromise the whole assembly process by affecting the
|
// following code, potencially making the assembler generate errors caused by
|
||||||
* following code, potencially making the assembler generate errors caused by
|
// the first one and unrelated to the code that the assembler complains about.
|
||||||
* 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,
|
||||||
* It is also used when the assembler goes into an invalid state (for example,
|
// when it fails to allocate memory).
|
||||||
* when it fails to allocate memory).
|
[[gnu::format(printf, 1, 2), noreturn]]
|
||||||
*/
|
void fatalerror(char const *fmt, ...);
|
||||||
[[gnu::format(printf, 1, 2), noreturn]] void fatalerror(char const *fmt, ...);
|
|
||||||
|
|
||||||
/*
|
// Used for errors that make it impossible to assemble correctly, but don't
|
||||||
* Used for errors that make it impossible to assemble correctly, but don't
|
// affect the following code. The code will fail to assemble but the user will
|
||||||
* affect the following code. The code will fail to assemble but the user will
|
// get a list of all errors at the end, making it easier to fix all of them at
|
||||||
* get a list of all errors at the end, making it easier to fix all of them at
|
// once.
|
||||||
* once.
|
[[gnu::format(printf, 1, 2)]]
|
||||||
*/
|
void error(char const *fmt, ...);
|
||||||
[[gnu::format(printf, 1, 2)]] void error(char const *fmt, ...);
|
|
||||||
|
|
||||||
#endif // RGBDS_ASM_WARNING_HPP
|
#endif // RGBDS_ASM_WARNING_HPP
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef RGBDS_DEFAULT_INIT_ALLOC_HPP
|
#ifndef RGBDS_DEFAULT_INIT_ALLOC_HPP
|
||||||
#define RGBDS_DEFAULT_INIT_ALLOC_HPP
|
#define RGBDS_DEFAULT_INIT_ALLOC_HPP
|
||||||
@@ -6,14 +6,11 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
/*
|
// Allocator adaptor that interposes construct() calls to convert value-initialization
|
||||||
* 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
|
||||||
* (which is what you get with e.g. `vector::resize`) into default-initialization (which does not
|
// zero out non-class types).
|
||||||
* zero out non-class types).
|
// From
|
||||||
* From
|
// https://stackoverflow.com/questions/21028299/is-this-behavior-of-vectorresizesize-type-n-under-c11-and-boost-container/21028912#21028912
|
||||||
* 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>>
|
template<typename T, typename A = std::allocator<T>>
|
||||||
class default_init_allocator : public A {
|
class default_init_allocator : public A {
|
||||||
using a_t = std::allocator_traits<A>;
|
using a_t = std::allocator_traits<A>;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef RGBDS_EITHER_HPP
|
#ifndef RGBDS_EITHER_HPP
|
||||||
#define RGBDS_EITHER_HPP
|
#define RGBDS_EITHER_HPP
|
||||||
@@ -43,11 +43,11 @@ private:
|
|||||||
// Generic field accessors; for internal use only.
|
// Generic field accessors; for internal use only.
|
||||||
template<typename T>
|
template<typename T>
|
||||||
auto &field() {
|
auto &field() {
|
||||||
return pick((T *)nullptr);
|
return pick(static_cast<T *>(nullptr));
|
||||||
}
|
}
|
||||||
template<typename T>
|
template<typename T>
|
||||||
auto const &field() const {
|
auto const &field() const {
|
||||||
return pick((T *)nullptr);
|
return pick(static_cast<T *>(nullptr));
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|||||||
@@ -1,15 +1,18 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef RGBDS_ERROR_HPP
|
#ifndef RGBDS_ERROR_HPP
|
||||||
#define RGBDS_ERROR_HPP
|
#define RGBDS_ERROR_HPP
|
||||||
|
|
||||||
extern "C" {
|
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), noreturn]]
|
||||||
[[gnu::format(printf, 1, 2)]] void warnx(char const *fmt, ...);
|
void err(char const *fmt, ...);
|
||||||
|
[[gnu::format(printf, 1, 2), noreturn]]
|
||||||
[[gnu::format(printf, 1, 2), noreturn]] void err(char const *fmt, ...);
|
void errx(char const *fmt, ...);
|
||||||
[[gnu::format(printf, 1, 2), noreturn]] void errx(char const *fmt, ...);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // RGBDS_ERROR_HPP
|
#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
|
#ifndef RGBDS_EXTERN_GETOPT_HPP
|
||||||
#define 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 char *musl_optarg;
|
||||||
extern int musl_optind, musl_opterr, musl_optopt, musl_optreset;
|
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
|
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
|
#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
|
#ifndef RGBDS_EXTERN_UTF8DECODER_HPP
|
||||||
#define 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
|
#ifndef RGBDS_FILE_HPP
|
||||||
#define RGBDS_FILE_HPP
|
#define RGBDS_FILE_HPP
|
||||||
@@ -25,10 +25,8 @@ public:
|
|||||||
File() {}
|
File() {}
|
||||||
~File() { close(); }
|
~File() { close(); }
|
||||||
|
|
||||||
/**
|
// This should only be called once, and before doing any `->` operations.
|
||||||
* This should only be called once, and before doing any `->` operations.
|
// Returns `nullptr` on error, and a non-null pointer otherwise.
|
||||||
* Returns `nullptr` on error, and a non-null pointer otherwise.
|
|
||||||
*/
|
|
||||||
File *open(std::string const &path, std::ios_base::openmode mode) {
|
File *open(std::string const &path, std::ios_base::openmode mode) {
|
||||||
if (path != "-") {
|
if (path != "-") {
|
||||||
_file.emplace<std::filebuf>();
|
_file.emplace<std::filebuf>();
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef RGBDS_GFX_MAIN_HPP
|
#ifndef RGBDS_GFX_MAIN_HPP
|
||||||
#define RGBDS_GFX_MAIN_HPP
|
#define RGBDS_GFX_MAIN_HPP
|
||||||
@@ -48,14 +48,17 @@ struct Options {
|
|||||||
|
|
||||||
std::string input{}; // positional arg
|
std::string input{}; // positional arg
|
||||||
|
|
||||||
|
// clang-format off: vertically align values
|
||||||
static constexpr uint8_t VERB_NONE = 0; // Normal, no extra output
|
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_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_LOG_ACT = 2; // Log actions before doing them
|
||||||
static constexpr uint8_t VERB_INTERM = 3; // Print some intermediate results
|
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_DEBUG = 4; // Internals are logged
|
||||||
static constexpr uint8_t VERB_UNMAPPED = 5; // Unused so far
|
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?
|
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 on
|
||||||
|
[[gnu::format(printf, 3, 4)]]
|
||||||
|
void verbosePrint(uint8_t level, char const *fmt, ...) const;
|
||||||
|
|
||||||
mutable bool hasTransparentPixels = false;
|
mutable bool hasTransparentPixels = false;
|
||||||
uint8_t maxOpaqueColors() const { return nbColorsPerPal - hasTransparentPixels; }
|
uint8_t maxOpaqueColors() const { return nbColorsPerPal - hasTransparentPixels; }
|
||||||
@@ -63,32 +66,24 @@ struct Options {
|
|||||||
|
|
||||||
extern Options options;
|
extern Options options;
|
||||||
|
|
||||||
/*
|
// Prints the error count, and exits with failure
|
||||||
* Prints the error count, and exits with failure
|
[[noreturn]]
|
||||||
*/
|
void giveUp();
|
||||||
[[noreturn]] void giveUp();
|
// If any error has been emitted thus far, calls `giveUp()`.
|
||||||
/*
|
|
||||||
* If any error has been emitted thus far, calls `giveUp()`.
|
|
||||||
*/
|
|
||||||
void requireZeroErrors();
|
void requireZeroErrors();
|
||||||
/*
|
// Prints a warning, and does not change the error count
|
||||||
* Prints a warning, and does not change the error count
|
[[gnu::format(printf, 1, 2)]]
|
||||||
*/
|
void warning(char const *fmt, ...);
|
||||||
[[gnu::format(printf, 1, 2)]] void warning(char const *fmt, ...);
|
// Prints an error, and increments the error count
|
||||||
/*
|
[[gnu::format(printf, 1, 2)]]
|
||||||
* Prints an error, and increments the error count
|
void error(char const *fmt, ...);
|
||||||
*/
|
// Prints an error, and increments the error count
|
||||||
[[gnu::format(printf, 1, 2)]] void error(char const *fmt, ...);
|
// Does not take format arguments so `format_` and `-Wformat-security` won't complain about
|
||||||
/*
|
// calling `errorMessage(msg)`.
|
||||||
* 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);
|
void errorMessage(char const *msg);
|
||||||
/*
|
// Prints a fatal error, increments the error count, and gives up
|
||||||
* Prints a fatal error, increments the error count, and gives up
|
[[gnu::format(printf, 1, 2), noreturn]]
|
||||||
*/
|
void fatal(char const *fmt, ...);
|
||||||
[[gnu::format(printf, 1, 2), noreturn]] void fatal(char const *fmt, ...);
|
|
||||||
|
|
||||||
struct Palette {
|
struct Palette {
|
||||||
// An array of 4 GBC-native (RGB555) colors
|
// An array of 4 GBC-native (RGB555) colors
|
||||||
@@ -107,20 +102,18 @@ struct Palette {
|
|||||||
uint8_t size() const;
|
uint8_t size() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace detail {
|
// Flipping tends to happen fairly often, so take a bite out of dcache to speed it up
|
||||||
template<typename T, T... i>
|
static constexpr auto flipTable = ([]() constexpr {
|
||||||
static constexpr auto flipTable(std::integer_sequence<T, i...>) {
|
std::array<uint16_t, 256> table{};
|
||||||
return std::array{[](uint8_t byte) {
|
for (uint16_t i = 0; i < table.size(); i++) {
|
||||||
// To flip all the bits, we'll flip both nibbles, then each nibble half, etc.
|
// To flip all the bits, we'll flip both nibbles, then each nibble half, etc.
|
||||||
|
uint16_t byte = i;
|
||||||
byte = (byte & 0b0000'1111) << 4 | (byte & 0b1111'0000) >> 4;
|
byte = (byte & 0b0000'1111) << 4 | (byte & 0b1111'0000) >> 4;
|
||||||
byte = (byte & 0b0011'0011) << 2 | (byte & 0b1100'1100) >> 2;
|
byte = (byte & 0b0011'0011) << 2 | (byte & 0b1100'1100) >> 2;
|
||||||
byte = (byte & 0b0101'0101) << 1 | (byte & 0b1010'1010) >> 1;
|
byte = (byte & 0b0101'0101) << 1 | (byte & 0b1010'1010) >> 1;
|
||||||
return byte;
|
table[i] = byte;
|
||||||
}(i)...};
|
}
|
||||||
}
|
return table;
|
||||||
} // 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>());
|
|
||||||
|
|
||||||
#endif // RGBDS_GFX_MAIN_HPP
|
#endif // RGBDS_GFX_MAIN_HPP
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef RGBDS_GFX_PAL_PACKING_HPP
|
#ifndef RGBDS_GFX_PAL_PACKING_HPP
|
||||||
#define RGBDS_GFX_PAL_PACKING_HPP
|
#define RGBDS_GFX_PAL_PACKING_HPP
|
||||||
@@ -6,19 +6,13 @@
|
|||||||
#include <tuple>
|
#include <tuple>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "defaultinitalloc.hpp"
|
#include "defaultinitvec.hpp"
|
||||||
|
|
||||||
struct Palette;
|
struct Palette;
|
||||||
class ProtoPalette;
|
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>
|
std::tuple<DefaultInitVec<size_t>, size_t>
|
||||||
overloadAndRemove(std::vector<ProtoPalette> const &protoPalettes);
|
overloadAndRemove(std::vector<ProtoPalette> const &protoPalettes);
|
||||||
|
|
||||||
} // namespace packing
|
|
||||||
|
|
||||||
#endif // RGBDS_GFX_PAL_PACKING_HPP
|
#endif // RGBDS_GFX_PAL_PACKING_HPP
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef RGBDS_GFX_PAL_SORTING_HPP
|
#ifndef RGBDS_GFX_PAL_SORTING_HPP
|
||||||
#define RGBDS_GFX_PAL_SORTING_HPP
|
#define RGBDS_GFX_PAL_SORTING_HPP
|
||||||
@@ -10,22 +10,22 @@
|
|||||||
|
|
||||||
#include "gfx/rgba.hpp"
|
#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;
|
struct Palette;
|
||||||
|
|
||||||
namespace sorting {
|
void sortIndexed(
|
||||||
|
|
||||||
void indexed(
|
|
||||||
std::vector<Palette> &palettes,
|
std::vector<Palette> &palettes,
|
||||||
int palSize,
|
int palSize,
|
||||||
png_color const *palRGB,
|
png_color const *palRGB,
|
||||||
int palAlphaSize,
|
int palAlphaSize,
|
||||||
png_byte *palAlpha
|
png_byte *palAlpha
|
||||||
);
|
);
|
||||||
void grayscale(
|
void sortGrayscale(
|
||||||
std::vector<Palette> &palettes, std::array<std::optional<Rgba>, 0x8001> const &colors
|
std::vector<Palette> &palettes, std::array<std::optional<Rgba>, NB_COLOR_SLOTS> const &colors
|
||||||
);
|
);
|
||||||
void rgb(std::vector<Palette> &palettes);
|
void sortRgb(std::vector<Palette> &palettes);
|
||||||
|
|
||||||
} // namespace sorting
|
|
||||||
|
|
||||||
#endif // RGBDS_GFX_PAL_SORTING_HPP
|
#endif // RGBDS_GFX_PAL_SORTING_HPP
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef RGBDS_GFX_PAL_SPEC_HPP
|
#ifndef RGBDS_GFX_PAL_SPEC_HPP
|
||||||
#define 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
|
#ifndef RGBDS_GFX_PROCESS_HPP
|
||||||
#define 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
|
#ifndef RGBDS_GFX_PROTO_PALETTE_HPP
|
||||||
#define 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};
|
std::array<uint16_t, capacity> _colorIndices{UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/*
|
// Adds the specified color to the set, or **silently drops it** if the set is full.
|
||||||
* Adds the specified color to the set, or **silently drops it** if the set is full.
|
void add(uint16_t color);
|
||||||
*
|
|
||||||
* Returns whether the color was unique.
|
|
||||||
*/
|
|
||||||
bool add(uint16_t color);
|
|
||||||
|
|
||||||
enum ComparisonResult {
|
enum ComparisonResult {
|
||||||
NEITHER,
|
NEITHER,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef RGBDS_GFX_REVERSE_HPP
|
#ifndef RGBDS_GFX_REVERSE_HPP
|
||||||
#define 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
|
#ifndef RGBDS_GFX_RGBA_HPP
|
||||||
#define 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)
|
constexpr Rgba(uint8_t r, uint8_t g, uint8_t b, uint8_t a)
|
||||||
: red(r), green(g), blue(b), alpha(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)
|
explicit constexpr Rgba(uint32_t rgba = 0)
|
||||||
: red(rgba >> 24), green(rgba >> 16), blue(rgba >> 8), alpha(rgba) {}
|
: red(rgba >> 24), green(rgba >> 16), blue(rgba >> 8), alpha(rgba) {}
|
||||||
|
|
||||||
@@ -28,34 +26,28 @@ struct Rgba {
|
|||||||
_5to8(cgbColor),
|
_5to8(cgbColor),
|
||||||
_5to8(cgbColor >> 5),
|
_5to8(cgbColor >> 5),
|
||||||
_5to8(cgbColor >> 10),
|
_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
|
||||||
* Returns this RGBA as a 32-bit number that can be printed in hex (`%08x`) to yield its CSS
|
// representation
|
||||||
* representation
|
|
||||||
*/
|
|
||||||
uint32_t toCSS() const {
|
uint32_t toCSS() const {
|
||||||
auto shl = [](uint8_t val, unsigned shift) { return static_cast<uint32_t>(val) << shift; };
|
auto shl = [](uint8_t val, unsigned shift) { return static_cast<uint32_t>(val) << shift; };
|
||||||
return shl(red, 24) | shl(green, 16) | shl(blue, 8) | shl(alpha, 0);
|
return shl(red, 24) | shl(green, 16) | shl(blue, 8) | shl(alpha, 0);
|
||||||
}
|
}
|
||||||
friend bool operator==(Rgba const &lhs, Rgba const &rhs) { return lhs.toCSS() == rhs.toCSS(); }
|
bool operator==(Rgba const &rhs) const { return 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(); }
|
||||||
|
|
||||||
/*
|
// CGB colors are RGB555, so we use bit 15 to signify that the color is transparent instead
|
||||||
* CGB colors are RGB555, so we use bit 15 to signify that the color is transparent instead
|
// Since the rest of the bits don't matter then, we return 0x8000 exactly.
|
||||||
* Since the rest of the bits don't matter then, we return 0x8000 exactly.
|
|
||||||
*/
|
|
||||||
static constexpr uint16_t transparent = 0b1'00000'00000'00000;
|
static constexpr uint16_t transparent = 0b1'00000'00000'00000;
|
||||||
|
|
||||||
static constexpr uint8_t transparency_threshold = 0x10;
|
static constexpr uint8_t transparency_threshold = 0x10;
|
||||||
bool isTransparent() const { return alpha < transparency_threshold; }
|
bool isTransparent() const { return alpha < transparency_threshold; }
|
||||||
static constexpr uint8_t opacity_threshold = 0xF0;
|
static constexpr uint8_t opacity_threshold = 0xF0;
|
||||||
bool isOpaque() const { return alpha >= opacity_threshold; }
|
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;
|
uint16_t cgbColor() const;
|
||||||
|
|
||||||
bool isGray() const { return red == green && green == blue; }
|
bool isGray() const { return red == green && green == blue; }
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef RGBDS_HELPERS_HPP
|
#ifndef RGBDS_HELPERS_HPP
|
||||||
#define RGBDS_HELPERS_HPP
|
#define RGBDS_HELPERS_HPP
|
||||||
@@ -14,7 +14,8 @@
|
|||||||
#else
|
#else
|
||||||
// This seems to generate similar code to __builtin_unreachable, despite different semantics
|
// This seems to generate similar code to __builtin_unreachable, despite different semantics
|
||||||
// Note that executing this is undefined behavior (declared [[noreturn]], but does return)
|
// Note that executing this is undefined behavior (declared [[noreturn]], but does return)
|
||||||
[[noreturn]] static inline void unreachable_() {
|
[[noreturn]]
|
||||||
|
static inline void unreachable_() {
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -26,8 +27,9 @@
|
|||||||
// `[[gnu::assume()]]` for GCC or compatible also has insufficient support (GCC 13+ only)
|
// `[[gnu::assume()]]` for GCC or compatible also has insufficient support (GCC 13+ only)
|
||||||
#define assume(x) \
|
#define assume(x) \
|
||||||
do { \
|
do { \
|
||||||
if (!(x)) \
|
if (!(x)) { \
|
||||||
unreachable_(); \
|
unreachable_(); \
|
||||||
|
} \
|
||||||
} while (0)
|
} while (0)
|
||||||
#endif
|
#endif
|
||||||
#else
|
#else
|
||||||
@@ -93,15 +95,14 @@ static inline int clz(unsigned int x) {
|
|||||||
#define CAT(x, y) x##y
|
#define CAT(x, y) x##y
|
||||||
#define EXPAND_AND_CAT(x, y) CAT(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
|
// For lack of <ranges>, this adds some more brevity
|
||||||
#define RANGE(s) std::begin(s), std::end(s)
|
#define RANGE(s) std::begin(s), std::end(s)
|
||||||
|
|
||||||
// MSVC does not inline `strlen()` or `.length()` of a constant string, so we use `sizeof`
|
// MSVC does not inline `strlen()` or `.length()` of a constant string
|
||||||
#define QUOTEDSTRLEN(s) (sizeof(s) - 1)
|
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))`
|
// For ad-hoc RAII in place of a `defer` statement or cross-platform `__attribute__((cleanup))`
|
||||||
template<typename T>
|
template<typename T>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef RGBDS_ITERTOOLS_HPP
|
#ifndef RGBDS_ITERTOOLS_HPP
|
||||||
#define RGBDS_ITERTOOLS_HPP
|
#define RGBDS_ITERTOOLS_HPP
|
||||||
@@ -7,51 +7,45 @@
|
|||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
class EnumSeqIterator {
|
class EnumSeq {
|
||||||
|
T _start;
|
||||||
|
T _stop;
|
||||||
|
|
||||||
|
class Iterator {
|
||||||
T _value;
|
T _value;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit EnumSeqIterator(T value) : _value(value) {}
|
explicit Iterator(T value) : _value(value) {}
|
||||||
|
|
||||||
EnumSeqIterator &operator++() {
|
Iterator &operator++() {
|
||||||
_value = (T)(_value + 1);
|
_value = static_cast<T>(_value + 1);
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto operator*() const { return _value; }
|
auto operator*() const { return _value; }
|
||||||
|
|
||||||
friend auto operator==(EnumSeqIterator const &lhs, EnumSeqIterator const &rhs) {
|
bool operator==(Iterator const &rhs) const { return _value == rhs._value; }
|
||||||
return lhs._value == rhs._value;
|
bool operator!=(Iterator const &rhs) const { return _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;
|
|
||||||
|
|
||||||
public:
|
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) {}
|
explicit EnumSeq(T start, T stop) : _start(start), _stop(stop) {}
|
||||||
|
|
||||||
EnumSeqIterator<T> begin() { return EnumSeqIterator(_start); }
|
Iterator begin() { return Iterator(_start); }
|
||||||
EnumSeqIterator<T> end() { return EnumSeqIterator(_stop); }
|
Iterator end() { return Iterator(_stop); }
|
||||||
};
|
};
|
||||||
|
|
||||||
// This is not a fully generic implementation; its current use cases only require for-loop behavior.
|
// 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.
|
// We also assume that all iterators have the same length.
|
||||||
template<typename... Iters>
|
template<typename... Ts>
|
||||||
class Zip {
|
class ZipIterator {
|
||||||
std::tuple<Iters...> _iters;
|
std::tuple<Ts...> _iters;
|
||||||
|
|
||||||
public:
|
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);
|
std::apply([](auto &&...it) { (++it, ...); }, _iters);
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
@@ -62,26 +56,24 @@ public:
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
friend auto operator==(Zip const &lhs, Zip const &rhs) {
|
bool operator==(ZipIterator const &rhs) const {
|
||||||
return std::get<0>(lhs._iters) == std::get<0>(rhs._iters);
|
return std::get<0>(_iters) == std::get<0>(rhs._iters);
|
||||||
}
|
}
|
||||||
|
|
||||||
friend auto operator!=(Zip const &lhs, Zip const &rhs) {
|
bool operator!=(ZipIterator const &rhs) const {
|
||||||
return std::get<0>(lhs._iters) != std::get<0>(rhs._iters);
|
return std::get<0>(_iters) != std::get<0>(rhs._iters);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace detail {
|
template<typename... Ts>
|
||||||
template<typename... Containers>
|
|
||||||
class ZipContainer {
|
class ZipContainer {
|
||||||
std::tuple<Containers...> _containers;
|
std::tuple<Ts...> _containers;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit ZipContainer(Containers &&...containers)
|
explicit ZipContainer(Ts &&...containers) : _containers(std::forward<Ts>(containers)...) {}
|
||||||
: _containers(std::forward<Containers>(containers)...) {}
|
|
||||||
|
|
||||||
auto begin() {
|
auto begin() {
|
||||||
return Zip(std::apply(
|
return ZipIterator(std::apply(
|
||||||
[](auto &&...containers) {
|
[](auto &&...containers) {
|
||||||
using std::begin;
|
using std::begin;
|
||||||
return std::make_tuple(begin(containers)...);
|
return std::make_tuple(begin(containers)...);
|
||||||
@@ -91,7 +83,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto end() {
|
auto end() {
|
||||||
return Zip(std::apply(
|
return ZipIterator(std::apply(
|
||||||
[](auto &&...containers) {
|
[](auto &&...containers) {
|
||||||
using std::end;
|
using std::end;
|
||||||
return std::make_tuple(end(containers)...);
|
return std::make_tuple(end(containers)...);
|
||||||
@@ -105,12 +97,11 @@ public:
|
|||||||
template<typename T>
|
template<typename T>
|
||||||
using Holder = std::
|
using Holder = std::
|
||||||
conditional_t<std::is_lvalue_reference_v<T>, T, std::remove_cv_t<std::remove_reference_t<T>>>;
|
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!
|
// Does the same number of iterations as the first container's iterator!
|
||||||
template<typename... Containers>
|
template<typename... Ts>
|
||||||
static constexpr auto zip(Containers &&...cs) {
|
static constexpr auto zip(Ts &&...cs) {
|
||||||
return detail::ZipContainer<detail::Holder<Containers>...>(std::forward<Containers>(cs)...);
|
return ZipContainer<Holder<Ts>...>(std::forward<Ts>(cs)...);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // RGBDS_ITERTOOLS_HPP
|
#endif // RGBDS_ITERTOOLS_HPP
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef RGBDS_LINK_ASSIGN_HPP
|
#ifndef RGBDS_LINK_ASSIGN_HPP
|
||||||
#define 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
|
#ifndef RGBDS_LINK_MAIN_HPP
|
||||||
#define RGBDS_LINK_MAIN_HPP
|
#define RGBDS_LINK_MAIN_HPP
|
||||||
@@ -32,8 +32,9 @@ extern bool disablePadding;
|
|||||||
// Helper macro for printing verbose-mode messages
|
// Helper macro for printing verbose-mode messages
|
||||||
#define verbosePrint(...) \
|
#define verbosePrint(...) \
|
||||||
do { \
|
do { \
|
||||||
if (beVerbose) \
|
if (beVerbose) { \
|
||||||
fprintf(stderr, __VA_ARGS__); \
|
fprintf(stderr, __VA_ARGS__); \
|
||||||
|
} \
|
||||||
} while (0)
|
} while (0)
|
||||||
|
|
||||||
struct FileStackNode {
|
struct FileStackNode {
|
||||||
@@ -58,11 +59,11 @@ struct FileStackNode {
|
|||||||
std::string const &dump(uint32_t curLineNo) const;
|
std::string const &dump(uint32_t curLineNo) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
[[gnu::format(printf, 3, 4)]] void
|
[[gnu::format(printf, 3, 4)]]
|
||||||
warning(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...);
|
void warning(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...);
|
||||||
[[gnu::format(printf, 3, 4)]] void
|
[[gnu::format(printf, 3, 4)]]
|
||||||
error(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...);
|
void error(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...);
|
||||||
[[gnu::format(printf, 3, 4), noreturn]] void
|
[[gnu::format(printf, 3, 4), noreturn]]
|
||||||
fatal(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...);
|
void fatal(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...);
|
||||||
|
|
||||||
#endif // RGBDS_LINK_MAIN_HPP
|
#endif // RGBDS_LINK_MAIN_HPP
|
||||||
|
|||||||
@@ -1,19 +1,12 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef RGBDS_LINK_OBJECT_HPP
|
#ifndef RGBDS_LINK_OBJECT_HPP
|
||||||
#define RGBDS_LINK_OBJECT_HPP
|
#define RGBDS_LINK_OBJECT_HPP
|
||||||
|
|
||||||
/*
|
// Read an object (.o) file, and add its info to the data structures.
|
||||||
* Read an object (.o) file, and add its info to the data structures.
|
void obj_ReadFile(char const *fileName, unsigned int fileID);
|
||||||
* @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);
|
|
||||||
|
|
||||||
/*
|
// Sets up object file reading
|
||||||
* Sets up object file reading
|
|
||||||
* @param nbFiles The number of object files that will be read
|
|
||||||
*/
|
|
||||||
void obj_Setup(unsigned int nbFiles);
|
void obj_Setup(unsigned int nbFiles);
|
||||||
|
|
||||||
#endif // RGBDS_LINK_OBJECT_HPP
|
#endif // RGBDS_LINK_OBJECT_HPP
|
||||||
|
|||||||
@@ -1,26 +1,17 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef RGBDS_LINK_OUTPUT_HPP
|
#ifndef RGBDS_LINK_OUTPUT_HPP
|
||||||
#define RGBDS_LINK_OUTPUT_HPP
|
#define RGBDS_LINK_OUTPUT_HPP
|
||||||
|
|
||||||
struct Section;
|
struct Section;
|
||||||
|
|
||||||
/*
|
// Registers a section for output.
|
||||||
* Registers a section for output.
|
|
||||||
* @param section The section to add
|
|
||||||
*/
|
|
||||||
void out_AddSection(Section const §ion);
|
void out_AddSection(Section const §ion);
|
||||||
|
|
||||||
/*
|
// Finds an assigned section overlapping another one.
|
||||||
* Finds an assigned section overlapping another one.
|
|
||||||
* @param section The section that is being overlapped
|
|
||||||
* @return A section overlapping it
|
|
||||||
*/
|
|
||||||
Section const *out_OverlappingSection(Section const §ion);
|
Section const *out_OverlappingSection(Section const §ion);
|
||||||
|
|
||||||
/*
|
// Writes all output (bin, sym, map) files.
|
||||||
* Writes all output (bin, sym, map) files.
|
|
||||||
*/
|
|
||||||
void out_WriteFiles();
|
void out_WriteFiles();
|
||||||
|
|
||||||
#endif // RGBDS_LINK_OUTPUT_HPP
|
#endif // RGBDS_LINK_OUTPUT_HPP
|
||||||
|
|||||||
@@ -1,17 +1,12 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef RGBDS_LINK_PATCH_HPP
|
#ifndef RGBDS_LINK_PATCH_HPP
|
||||||
#define RGBDS_LINK_PATCH_HPP
|
#define RGBDS_LINK_PATCH_HPP
|
||||||
|
|
||||||
/*
|
// Checks all assertions
|
||||||
* Checks all assertions
|
|
||||||
* @return true if assertion failed
|
|
||||||
*/
|
|
||||||
void patch_CheckAssertions();
|
void patch_CheckAssertions();
|
||||||
|
|
||||||
/*
|
// Applies all SECTIONs' patches to them
|
||||||
* Applies all SECTIONs' patches to them
|
|
||||||
*/
|
|
||||||
void patch_ApplyPatches();
|
void patch_ApplyPatches();
|
||||||
|
|
||||||
#endif // RGBDS_LINK_PATCH_HPP
|
#endif // RGBDS_LINK_PATCH_HPP
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef RGBDS_LINK_SDAS_OBJ_HPP
|
#ifndef RGBDS_LINK_SDAS_OBJ_HPP
|
||||||
#define 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
|
#ifndef RGBDS_LINK_SECTION_HPP
|
||||||
#define RGBDS_LINK_SECTION_HPP
|
#define RGBDS_LINK_SECTION_HPP
|
||||||
@@ -65,29 +65,17 @@ struct Assertion {
|
|||||||
|
|
||||||
extern std::deque<Assertion> assertions;
|
extern std::deque<Assertion> assertions;
|
||||||
|
|
||||||
/*
|
// Execute a callback for each section currently registered.
|
||||||
* Execute a callback for each section currently registered.
|
// This is to avoid exposing the data structure in which sections are stored.
|
||||||
* This is to avoid exposing the data structure in which sections are stored.
|
|
||||||
* @param callback The function to call for each structure.
|
|
||||||
*/
|
|
||||||
void sect_ForEach(void (*callback)(Section &));
|
void sect_ForEach(void (*callback)(Section &));
|
||||||
|
|
||||||
/*
|
// Registers a section to be processed.
|
||||||
* Registers a section to be processed.
|
|
||||||
* @param section The section to register.
|
|
||||||
*/
|
|
||||||
void sect_AddSection(std::unique_ptr<Section> &§ion);
|
void sect_AddSection(std::unique_ptr<Section> &§ion);
|
||||||
|
|
||||||
/*
|
// Finds a section by its name.
|
||||||
* 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
|
|
||||||
*/
|
|
||||||
Section *sect_GetSection(std::string const &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();
|
void sect_DoSanityChecks();
|
||||||
|
|
||||||
#endif // RGBDS_LINK_SECTION_HPP
|
#endif // RGBDS_LINK_SECTION_HPP
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef RGBDS_LINK_SYMBOL_HPP
|
#ifndef RGBDS_LINK_SYMBOL_HPP
|
||||||
#define RGBDS_LINK_SYMBOL_HPP
|
#define RGBDS_LINK_SYMBOL_HPP
|
||||||
@@ -41,11 +41,7 @@ void sym_ForEach(void (*callback)(Symbol &));
|
|||||||
|
|
||||||
void sym_AddSymbol(Symbol &symbol);
|
void sym_AddSymbol(Symbol &symbol);
|
||||||
|
|
||||||
/*
|
// Finds a symbol in all the defined symbols.
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
Symbol *sym_GetSymbol(std::string const &name);
|
Symbol *sym_GetSymbol(std::string const &name);
|
||||||
|
|
||||||
void sym_DumpLocalAliasedSymbols(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
|
#ifndef RGBDS_LINKDEFS_HPP
|
||||||
#define RGBDS_LINKDEFS_HPP
|
#define RGBDS_LINKDEFS_HPP
|
||||||
@@ -92,29 +92,19 @@ extern struct SectionTypeInfo {
|
|||||||
uint32_t lastBank;
|
uint32_t lastBank;
|
||||||
} sectionTypeInfo[SECTTYPE_INVALID];
|
} sectionTypeInfo[SECTTYPE_INVALID];
|
||||||
|
|
||||||
/*
|
// Tells whether a section has data in its object file definition,
|
||||||
* Tells whether a section has data in its object file definition,
|
// depending on type.
|
||||||
* depending on type.
|
|
||||||
* @param type The section's type
|
|
||||||
* @return `true` if the section's definition includes data
|
|
||||||
*/
|
|
||||||
static inline bool sect_HasData(SectionType type) {
|
static inline bool sect_HasData(SectionType type) {
|
||||||
assume(type != SECTTYPE_INVALID);
|
assume(type != SECTTYPE_INVALID);
|
||||||
return type == SECTTYPE_ROM0 || type == SECTTYPE_ROMX;
|
return type == SECTTYPE_ROM0 || type == SECTTYPE_ROMX;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Returns a memory region's end address (last byte), e.g. 0x7FFF
|
||||||
* Computes a memory region's end address (last byte), eg. 0x7FFF
|
|
||||||
* @return The address of the last byte in that memory region
|
|
||||||
*/
|
|
||||||
static inline uint16_t endaddr(SectionType type) {
|
static inline uint16_t endaddr(SectionType type) {
|
||||||
return sectionTypeInfo[type].startAddr + sectionTypeInfo[type].size - 1;
|
return sectionTypeInfo[type].startAddr + sectionTypeInfo[type].size - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Returns a memory region's number of banks, or 1 for regions without banking
|
||||||
* Computes a memory region's number of banks
|
|
||||||
* @return The number of banks, 1 for regions without banking
|
|
||||||
*/
|
|
||||||
static inline uint32_t nbbanks(SectionType type) {
|
static inline uint32_t nbbanks(SectionType type) {
|
||||||
return sectionTypeInfo[type].lastBank - sectionTypeInfo[type].firstBank + 1;
|
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
|
#ifndef RGBDS_OP_MATH_HPP
|
||||||
#define RGBDS_OP_MATH_HPP
|
#define RGBDS_OP_MATH_HPP
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
// platform-specific hacks
|
// platform-specific hacks
|
||||||
|
|
||||||
@@ -56,4 +56,9 @@
|
|||||||
#define setmode(fd, mode) (0)
|
#define setmode(fd, mode) (0)
|
||||||
#endif
|
#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
|
#endif // RGBDS_PLATFORM_HPP
|
||||||
|
|||||||
@@ -1,17 +1,8 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef RGBDS_UTIL_HPP
|
#ifndef RGBDS_UTIL_HPP
|
||||||
#define RGBDS_UTIL_HPP
|
#define RGBDS_UTIL_HPP
|
||||||
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
char const *printChar(int c);
|
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
|
#endif // RGBDS_UTIL_HPP
|
||||||
|
|||||||
@@ -1,16 +1,12 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef RGBDS_VERSION_HPP
|
#ifndef RGBDS_VERSION_HPP
|
||||||
#define RGBDS_VERSION_HPP
|
#define RGBDS_VERSION_HPP
|
||||||
|
|
||||||
extern "C" {
|
|
||||||
|
|
||||||
#define PACKAGE_VERSION_MAJOR 0
|
#define PACKAGE_VERSION_MAJOR 0
|
||||||
#define PACKAGE_VERSION_MINOR 9
|
#define PACKAGE_VERSION_MINOR 9
|
||||||
#define PACKAGE_VERSION_PATCH 0
|
#define PACKAGE_VERSION_PATCH 1
|
||||||
#define PACKAGE_VERSION_RC 1
|
|
||||||
|
|
||||||
char const *get_package_version_string();
|
char const *get_package_version_string();
|
||||||
}
|
|
||||||
|
|
||||||
#endif // RGBDS_VERSION_H
|
#endif // RGBDS_VERSION_H
|
||||||
|
|||||||
607
man/gbz80.7
607
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
|
.\" SPDX-License-Identifier: MIT
|
||||||
.\"
|
.\"
|
||||||
.Dd September 18, 2024
|
.Dd February 2, 2025
|
||||||
.Dt RGBASM 1
|
.Dt RGBASM 1
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
.Nd Game Boy assembler
|
.Nd Game Boy assembler
|
||||||
.Sh SYNOPSIS
|
.Sh SYNOPSIS
|
||||||
.Nm
|
.Nm
|
||||||
.Op Fl EVvw
|
.Op Fl EhVvw
|
||||||
.Op Fl b Ar chars
|
.Op Fl b Ar chars
|
||||||
.Op Fl D Ar name Ns Op = Ns Ar value
|
.Op Fl D Ar name Ns Op = Ns Ar value
|
||||||
.Op Fl g Ar chars
|
.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
|
.It Fl g Ar chars , Fl \-gfx-chars Ar chars
|
||||||
Change the four characters used for gfx constants.
|
Change the four characters used for gfx constants.
|
||||||
The defaults are 0123.
|
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
|
.It Fl I Ar path , Fl \-include Ar path
|
||||||
Add a new
|
Add a new
|
||||||
.Dq include path ;
|
.Dq include path ;
|
||||||
@@ -200,7 +202,7 @@ section for a list of warnings.
|
|||||||
Disable all warning output, even when turned into errors.
|
Disable all warning output, even when turned into errors.
|
||||||
.It Fl X Ar max_errors , Fl \-max-errors Ar max_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;
|
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.
|
disables this behavior.
|
||||||
The default is 100 if
|
The default is 100 if
|
||||||
.Nm
|
.Nm
|
||||||
@@ -212,13 +214,19 @@ The following options alter the way warnings are processed.
|
|||||||
.Bl -tag -width Ds
|
.Bl -tag -width Ds
|
||||||
.It Fl Werror
|
.It Fl Werror
|
||||||
Make all warnings into errors.
|
Make all warnings into errors.
|
||||||
|
This can be negated as
|
||||||
|
.Fl Wno-error
|
||||||
|
to prevent turning all warnings into errors.
|
||||||
.It Fl Werror=
|
.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
|
A warning's name is appended
|
||||||
.Pq example: Fl Werror=obsolete ,
|
.Pq example: Fl Werror=obsolete ,
|
||||||
and this warning is implicitly enabled and turned into an error.
|
and this warning is implicitly enabled and turned into an error.
|
||||||
This is an error if used with a meta warning, such as
|
This can be negated as
|
||||||
.Fl Werror=all .
|
.Fl Wno-error=
|
||||||
|
to prevent turning a specified warning into an error, even if
|
||||||
|
.Fl Werror
|
||||||
|
is in effect.
|
||||||
.El
|
.El
|
||||||
.Pp
|
.Pp
|
||||||
The following warnings are
|
The following warnings are
|
||||||
@@ -240,6 +248,10 @@ Note that each of these flag also has a negation (for example,
|
|||||||
.Fl Wcharmap-redef
|
.Fl Wcharmap-redef
|
||||||
enables the warning that
|
enables the warning that
|
||||||
.Fl Wno-charmap-redef
|
.Fl Wno-charmap-redef
|
||||||
|
disables; and
|
||||||
|
.Fl Wall
|
||||||
|
enables every warning that
|
||||||
|
.Fl Wno-all
|
||||||
disables).
|
disables).
|
||||||
Only the non-default flag is listed here.
|
Only the non-default flag is listed here.
|
||||||
Ignoring the
|
Ignoring the
|
||||||
@@ -291,6 +303,13 @@ This warning is enabled by
|
|||||||
Warn when shifting macro arguments past their limits.
|
Warn when shifting macro arguments past their limits.
|
||||||
This warning is enabled by
|
This warning is enabled by
|
||||||
.Fl Wextra .
|
.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
|
.It Fl Wno-obsolete
|
||||||
Warn when obsolete constructs such as the
|
Warn when obsolete constructs such as the
|
||||||
.Ic _PI
|
.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.
|
Warn when a character goes through charmap conversion but has no defined mapping.
|
||||||
.Fl Wunmapped-char=0
|
.Fl Wunmapped-char=0
|
||||||
or
|
or
|
||||||
.Fl Wunmapped-char
|
.Fl Wno-unmapped-char
|
||||||
disables this warning.
|
disables this warning.
|
||||||
.Fl Wunmapped-char=1
|
.Fl Wunmapped-char=1
|
||||||
or just
|
or just
|
||||||
@@ -353,6 +372,24 @@ only warns if the active charmap is not empty.
|
|||||||
.Fl Wunmapped-char=2
|
.Fl Wunmapped-char=2
|
||||||
warns if the active charmap is empty, and/or is not the default charmap
|
warns if the active charmap is empty, and/or is not the default charmap
|
||||||
.Sq main .
|
.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
|
.It Fl Wno-user
|
||||||
Warn when the
|
Warn when the
|
||||||
.Ic WARN
|
.Ic WARN
|
||||||
@@ -392,6 +429,7 @@ Please report bugs on
|
|||||||
.Xr rgbfix 1 ,
|
.Xr rgbfix 1 ,
|
||||||
.Xr rgbgfx 1 ,
|
.Xr rgbgfx 1 ,
|
||||||
.Xr gbz80 7 ,
|
.Xr gbz80 7 ,
|
||||||
|
.Xr rgbasm-old 5 ,
|
||||||
.Xr rgbds 5 ,
|
.Xr rgbds 5 ,
|
||||||
.Xr rgbds 7
|
.Xr rgbds 7
|
||||||
.Sh HISTORY
|
.Sh HISTORY
|
||||||
|
|||||||
244
man/rgbasm.5
244
man/rgbasm.5
@@ -2,7 +2,7 @@
|
|||||||
.\"
|
.\"
|
||||||
.\" SPDX-License-Identifier: MIT
|
.\" SPDX-License-Identifier: MIT
|
||||||
.\"
|
.\"
|
||||||
.Dd September 18, 2024
|
.Dd February 2, 2025
|
||||||
.Dt RGBASM 5
|
.Dt RGBASM 5
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
@@ -41,7 +41,6 @@ or
|
|||||||
Labels tie a name to a specific location within a section (see
|
Labels tie a name to a specific location within a section (see
|
||||||
.Sx Labels
|
.Sx Labels
|
||||||
below).
|
below).
|
||||||
They must come first in the line.
|
|
||||||
.Pp
|
.Pp
|
||||||
Instructions are assembled into Game Boy opcodes.
|
Instructions are assembled into Game Boy opcodes.
|
||||||
Multiple instructions on one line can be separated by double colons
|
Multiple instructions on one line can be separated by double colons
|
||||||
@@ -254,7 +253,7 @@ Although, for these examples,
|
|||||||
.Ic STRFMT
|
.Ic STRFMT
|
||||||
would be more appropriate; see
|
would be more appropriate; see
|
||||||
.Sx String expressions
|
.Sx String expressions
|
||||||
further below.
|
below.
|
||||||
.Sh EXPRESSIONS
|
.Sh EXPRESSIONS
|
||||||
An expression can be composed of many things.
|
An expression can be composed of many things.
|
||||||
Numeric expressions are always evaluated using signed 32-bit math.
|
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.
|
section.
|
||||||
However, some operators can be constant even with non-constant operands, as explained in
|
However, some operators can be constant even with non-constant operands, as explained in
|
||||||
.Sx Operators
|
.Sx Operators
|
||||||
further below.
|
below.
|
||||||
.Pp
|
.Pp
|
||||||
The instructions in the macro-language generally require constant expressions.
|
The instructions in the macro-language generally require constant expressions.
|
||||||
.Ss Numeric formats
|
.Ss Numeric formats
|
||||||
There are a number of numeric formats.
|
There are a number of numeric formats.
|
||||||
.Bl -column -offset indent "Precise fixed-point" "Prefix"
|
.Bl -column -offset indent "Precise fixed-point" "Possible prefixes"
|
||||||
.It Sy Format type Ta Sy Prefix Ta Sy Accepted characters
|
.It Sy Format type Ta Sy Possible prefixes Ta Sy Accepted characters
|
||||||
.It Hexadecimal Ta $ Ta 0123456789ABCDEF
|
|
||||||
.It Decimal Ta none Ta 0123456789
|
.It Decimal Ta none Ta 0123456789
|
||||||
.It Octal Ta & Ta 01234567
|
.It Hexadecimal Ta Li $ , 0x , 0X Ta 0123456789ABCDEF
|
||||||
.It Binary Ta % Ta 01
|
.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 Fixed-point Ta none Ta 01234.56789
|
||||||
.It Precise fixed-point Ta none Ta 12.34q8
|
.It Precise fixed-point Ta none Ta 12.34q8
|
||||||
.It Character constant Ta none Ta \(dqABYZ\(dq
|
.It Character constant Ta none Ta \(dqABYZ\(dq
|
||||||
.It Game Boy graphics Ta \` Ta 0123
|
.It Game Boy graphics Ta Li \` Ta 0123
|
||||||
.El
|
.El
|
||||||
.Pp
|
.Pp
|
||||||
Underscores are also accepted in numbers, except at the beginning of one.
|
Underscores are also accepted in numbers, except at the beginning of one.
|
||||||
@@ -310,26 +309,35 @@ is equivalent to
|
|||||||
.Pp
|
.Pp
|
||||||
You can also use symbols, which are implicitly replaced with their value.
|
You can also use symbols, which are implicitly replaced with their value.
|
||||||
.Ss Operators
|
.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 "!= == <= >= < >"
|
.Bl -column -offset indent "!= == <= >= < >"
|
||||||
.It Sy Operator Ta Sy Meaning
|
.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 FUNC() Ta Built-in function call
|
||||||
.It Li ** Ta Exponent
|
.It Li ** Ta Exponentiation
|
||||||
.It Li ~ + - Ta Unary complement/plus/minus
|
.It Li + - ~ \&! Ta Unary plus, minus (negation), complement (bitwise negation), and Boolean negation
|
||||||
.It Li * / % Ta Multiply/divide/modulo
|
.It Li * / % Ta Multiplication, division, and modulo (remainder)
|
||||||
.It Li << Ta Shift left
|
.It Li << >> >>> Ta Bit shifts (left, sign-extended right, zero-extended right)
|
||||||
.It Li >> Ta Signed shift right (sign-extension)
|
.It Li & \&| ^ Ta Bitwise AND/OR/XOR
|
||||||
.It Li >>> Ta Unsigned shift right (zero-extension)
|
.It Li + - Ta Addition and subtraction
|
||||||
.It Li & \&| ^ Ta Binary and/or/xor
|
.It Li == != < > <= >= Ta Comparisons
|
||||||
.It Li + - Ta Add/subtract
|
.It Li && Ta Boolean AND
|
||||||
.It Li != == <= >= < > Ta Comparison
|
.It Li || Ta Boolean OR
|
||||||
.It Li && || Ta Boolean and/or
|
|
||||||
.It Li \&! Ta Unary not
|
|
||||||
.El
|
.El
|
||||||
.Pp
|
.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 ~
|
.Sq ~
|
||||||
complements a value by inverting all its bits.
|
complements a value by inverting all 32 of its bits.
|
||||||
.Pp
|
.Pp
|
||||||
.Sq %
|
.Sq %
|
||||||
is used to get the remainder of the corresponding division, so that
|
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.
|
Besides operators, there are also some functions which have more specialized uses.
|
||||||
.Bl -column "BITWIDTH(n)"
|
.Bl -column "BITWIDTH(n)"
|
||||||
.It Sy Name Ta Sy Operation
|
.It Sy Name Ta Sy Operation
|
||||||
.It Fn HIGH n Ta Equivalent to Ql Ar n No & $FF .
|
.It Fn HIGH n Ta Equivalent to Ql Po Ns Ar n No & $FF00 Pc >> 8 .
|
||||||
.It Fn LOW 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
|
.EQ
|
||||||
delim $$
|
delim $$
|
||||||
.EN
|
.EN
|
||||||
@@ -904,7 +912,7 @@ SECTION "LOAD example", ROMX
|
|||||||
CopyCode:
|
CopyCode:
|
||||||
ld de, RAMCode
|
ld de, RAMCode
|
||||||
ld hl, RAMLocation
|
ld hl, RAMLocation
|
||||||
ld c, RAMLocation.end - RAMLocation
|
ld c, RAMCode.end - RAMCode
|
||||||
\&.loop
|
\&.loop
|
||||||
ld a, [de]
|
ld a, [de]
|
||||||
inc de
|
inc de
|
||||||
@@ -928,8 +936,8 @@ RAMLocation:
|
|||||||
|
|
||||||
\&.string
|
\&.string
|
||||||
db "Hello World!\e0"
|
db "Hello World!\e0"
|
||||||
\&.end
|
|
||||||
ENDL
|
ENDL
|
||||||
|
\&.end
|
||||||
.Ed
|
.Ed
|
||||||
.Pp
|
.Pp
|
||||||
A
|
A
|
||||||
@@ -939,7 +947,9 @@ block feels similar to a
|
|||||||
declaration because it creates a new one.
|
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.
|
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
|
.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
|
You will notice the
|
||||||
.Sq RAMCode
|
.Sq RAMCode
|
||||||
and
|
and
|
||||||
@@ -951,15 +961,30 @@ You cannot nest
|
|||||||
.Ic LOAD
|
.Ic LOAD
|
||||||
blocks, nor can you change or stop the current section within them.
|
blocks, nor can you change or stop the current section within them.
|
||||||
.Pp
|
.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
|
.Ic LOAD
|
||||||
blocks can use the
|
blocks can use the
|
||||||
.Ic UNION
|
.Ic UNION
|
||||||
or
|
or
|
||||||
.Ic FRAGMENT
|
.Ic FRAGMENT
|
||||||
modifiers, as described below.
|
modifiers as described in
|
||||||
|
.Sx Unionized sections
|
||||||
|
below.
|
||||||
.Ss Unionized sections
|
.Ss Unionized sections
|
||||||
When you're tight on RAM, you may want to define overlapping static memory allocations, as explained in the
|
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.
|
section.
|
||||||
However, a
|
However, a
|
||||||
.Ic UNION
|
.Ic UNION
|
||||||
@@ -1000,7 +1025,7 @@ or
|
|||||||
.El
|
.El
|
||||||
.Pp
|
.Pp
|
||||||
Different declarations of the same unionized section are not appended, but instead overlaid on top of each other, just like
|
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.
|
Similarly, the size of an unionized section is the largest of all its declarations.
|
||||||
.Ss Section fragments
|
.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.
|
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 ,
|
.Dq exported ,
|
||||||
explained in
|
explained in
|
||||||
.Sx Exporting and importing symbols
|
.Sx Exporting and importing symbols
|
||||||
further below).
|
below).
|
||||||
More than one dot in label names is not allowed.
|
More than one dot in label names is not allowed.
|
||||||
.Pp
|
.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
|
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
|
Refer to the spec at
|
||||||
.Lk https://reproducible-builds.org/docs/source-date-epoch/ reproducible-builds.org .
|
.Lk https://reproducible-builds.org/docs/source-date-epoch/ reproducible-builds.org .
|
||||||
.Sh DEFINING DATA
|
.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
|
.Ss Defining constant data in ROM
|
||||||
.Ic DB
|
.Ic DB
|
||||||
defines a list of bytes that will be stored in the final image.
|
defines a list of bytes that will be stored in the final image.
|
||||||
@@ -1660,7 +1644,7 @@ can be used in a
|
|||||||
/
|
/
|
||||||
.Ic SRAM
|
.Ic SRAM
|
||||||
section.
|
section.
|
||||||
.Ss Including binary files
|
.Ss Including binary data files
|
||||||
You probably have some graphics, level data, etc. you'd like to include.
|
You probably have some graphics, level data, etc. you'd like to include.
|
||||||
Use
|
Use
|
||||||
.Ic INCBIN
|
.Ic INCBIN
|
||||||
@@ -1684,7 +1668,48 @@ INCBIN "data.bin", 78, 256
|
|||||||
.Pp
|
.Pp
|
||||||
The length argument is optional.
|
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.
|
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.
|
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.
|
This does not increase the amount of memory available, but allows re-using the same memory region for different purposes.
|
||||||
.Pp
|
.Pp
|
||||||
@@ -1745,6 +1770,37 @@ Unions may be used in any section, but they may only contain space-allocating di
|
|||||||
.Ic DS
|
.Ic DS
|
||||||
(see
|
(see
|
||||||
.Sx Statically allocating space in RAM ) .
|
.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
|
.Sh THE MACRO LANGUAGE
|
||||||
.Ss Invoking macros
|
.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.
|
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
|
.Pp
|
||||||
.Ic OPT
|
.Ic OPT
|
||||||
can modify the options
|
can modify the options
|
||||||
.Cm b , g , p , Q ,
|
.Cm b , g , p , Q , r ,
|
||||||
and
|
and
|
||||||
.Cm r .
|
.Cm W .
|
||||||
.Pp
|
.Pp
|
||||||
.Ic POPO
|
.Ic POPO
|
||||||
and
|
and
|
||||||
@@ -2306,37 +2362,6 @@ PUSHO b.X, g.oOX
|
|||||||
DW `..ooOOXX
|
DW `..ooOOXX
|
||||||
POPO
|
POPO
|
||||||
.Ed
|
.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
|
.Sh SEE ALSO
|
||||||
.Xr rgbasm 1 ,
|
.Xr rgbasm 1 ,
|
||||||
.Xr rgblink 1 ,
|
.Xr rgblink 1 ,
|
||||||
@@ -2344,6 +2369,7 @@ is a shorthand for
|
|||||||
.Xr rgbfix 1 ,
|
.Xr rgbfix 1 ,
|
||||||
.Xr rgbgfx 1 ,
|
.Xr rgbgfx 1 ,
|
||||||
.Xr gbz80 7 ,
|
.Xr gbz80 7 ,
|
||||||
|
.Xr rgbasm-old 5 ,
|
||||||
.Xr rgbds 5 ,
|
.Xr rgbds 5 ,
|
||||||
.Xr rgbds 7
|
.Xr rgbds 7
|
||||||
.Sh HISTORY
|
.Sh HISTORY
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
.\" SPDX-License-Identifier: MIT
|
.\" SPDX-License-Identifier: MIT
|
||||||
.\"
|
.\"
|
||||||
.Dd September 18, 2024
|
.Dd February 2, 2025
|
||||||
.Dt RGBDS 5
|
.Dt RGBDS 5
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
@@ -254,7 +254,7 @@ Size of the
|
|||||||
below.
|
below.
|
||||||
.It Cm BYTE Ar RPNExpr Ns Bq RPNSize
|
.It Cm BYTE Ar RPNExpr Ns Bq RPNSize
|
||||||
The patch's value, encoded as a RPN expression
|
The patch's value, encoded as a RPN expression
|
||||||
.Pq see Sx RPN EXPRESSIONS .
|
.Pq see Sx RPN expressions .
|
||||||
.El
|
.El
|
||||||
.It Cm ENDR
|
.It Cm ENDR
|
||||||
.El
|
.El
|
||||||
@@ -294,14 +294,14 @@ Size of the
|
|||||||
below.
|
below.
|
||||||
.It Cm BYTE Ar RPNExpr Ns Bq RPNSize
|
.It Cm BYTE Ar RPNExpr Ns Bq RPNSize
|
||||||
The patch's value, encoded as a RPN expression
|
The patch's value, encoded as a RPN expression
|
||||||
.Pq see Sx RPN EXPRESSIONS .
|
.Pq see Sx RPN expressions .
|
||||||
.It Cm STRING Ar Message
|
.It Cm STRING Ar Message
|
||||||
The message displayed if the expression evaluates to a non-zero value.
|
The message displayed if the expression evaluates to a non-zero value.
|
||||||
If empty, a generic message is displayed instead.
|
If empty, a generic message is displayed instead.
|
||||||
.El
|
.El
|
||||||
.It Cm ENDR
|
.It Cm ENDR
|
||||||
.El
|
.El
|
||||||
.Ss RPN EXPRESSIONS
|
.Ss RPN expressions
|
||||||
Expressions in the object file are stored as RPN, or
|
Expressions in the object file are stored as RPN, or
|
||||||
.Dq Reverse Polish Notation ,
|
.Dq Reverse Polish Notation ,
|
||||||
which is a notation that allows computing arbitrary expressions with just a simple stack.
|
which is a notation that allows computing arbitrary expressions with just a simple stack.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
.\" SPDX-License-Identifier: MIT
|
.\" SPDX-License-Identifier: MIT
|
||||||
.\"
|
.\"
|
||||||
.Dd September 18, 2024
|
.Dd February 2, 2025
|
||||||
.Dt RGBDS 7
|
.Dt RGBDS 7
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.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.
|
forks Nossum's repository.
|
||||||
The fork becomes the reference implementation of RGBDS.
|
The fork becomes the reference implementation of RGBDS.
|
||||||
.It
|
.It
|
||||||
|
2010-09-25: S\(/orensen continues development of
|
||||||
|
.Lk https://github.com/asmotor/asmotor ASMotor
|
||||||
|
to this day.
|
||||||
|
.It
|
||||||
2015-01-18:
|
2015-01-18:
|
||||||
.An stag019
|
.An stag019
|
||||||
begins implementing RGBGFX, a PNG‐to‐Game Boy graphics converter, for eventual integration into RGBDS.
|
begins implementing RGBGFX, a PNG‐to‐Game Boy graphics converter, for eventual integration into RGBDS.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
.\" SPDX-License-Identifier: MIT
|
.\" SPDX-License-Identifier: MIT
|
||||||
.\"
|
.\"
|
||||||
.Dd September 18, 2024
|
.Dd February 2, 2025
|
||||||
.Dt RGBFIX 1
|
.Dt RGBFIX 1
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
.Nd Game Boy header utility and checksum fixer
|
.Nd Game Boy header utility and checksum fixer
|
||||||
.Sh SYNOPSIS
|
.Sh SYNOPSIS
|
||||||
.Nm
|
.Nm
|
||||||
.Op Fl jOsVv
|
.Op Fl hjOsVv
|
||||||
.Op Fl C | c
|
.Op Fl C | c
|
||||||
.Op Fl f Ar fix_spec
|
.Op Fl f Ar fix_spec
|
||||||
.Op Fl i Ar game_id
|
.Op Fl i Ar game_id
|
||||||
@@ -91,6 +91,8 @@ Fix the global checksum
|
|||||||
.It Cm G
|
.It Cm G
|
||||||
Trash the global checksum.
|
Trash the global checksum.
|
||||||
.El
|
.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
|
.It Fl i Ar game_id , Fl \-game-id Ar game_id
|
||||||
Set the game ID string
|
Set the game ID string
|
||||||
.Pq Ad 0x13F Ns \(en Ns Ad 0x142
|
.Pq Ad 0x13F Ns \(en Ns Ad 0x142
|
||||||
|
|||||||
19
man/rgbgfx.1
19
man/rgbgfx.1
@@ -2,7 +2,7 @@
|
|||||||
.\"
|
.\"
|
||||||
.\" SPDX-License-Identifier: MIT
|
.\" SPDX-License-Identifier: MIT
|
||||||
.\"
|
.\"
|
||||||
.Dd September 18, 2024
|
.Dd February 2, 2025
|
||||||
.Dt RGBGFX 1
|
.Dt RGBGFX 1
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
.Nd Game Boy graphics converter
|
.Nd Game Boy graphics converter
|
||||||
.Sh SYNOPSIS
|
.Sh SYNOPSIS
|
||||||
.Nm
|
.Nm
|
||||||
.Op Fl CmOuVZ
|
.Op Fl CmhOuVXYZ
|
||||||
.Op Fl v Op Fl v No ...
|
.Op Fl v Op Fl v No ...
|
||||||
.Op Fl a Ar attrmap | Fl A
|
.Op Fl a Ar attrmap | Fl A
|
||||||
.Op Fl b Ar base_ids
|
.Op Fl b Ar base_ids
|
||||||
@@ -165,6 +165,8 @@ for a list of formats and their descriptions.
|
|||||||
.It Fl d Ar depth , Fl \-depth Ar depth
|
.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).
|
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).
|
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
|
.It Fl i Ar input_tiles , Fl \-input-tileset Ar input_tiles
|
||||||
Use the specified input tiles in addition to having
|
Use the specified input tiles in addition to having
|
||||||
.Nm
|
.Nm
|
||||||
@@ -229,9 +231,8 @@ The second number pair specifies how many tiles to process horizontally and vert
|
|||||||
.Pp
|
.Pp
|
||||||
.Fl L Sy is ignored in reverse mode , No no padding is inserted .
|
.Fl L Sy is ignored in reverse mode , No no padding is inserted .
|
||||||
.It Fl m , Fl \-mirror-tiles
|
.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.
|
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
|
Useful with a tile map and attribute map together (see
|
||||||
.Fl a
|
.Fl a
|
||||||
and
|
and
|
||||||
@@ -239,6 +240,8 @@ and
|
|||||||
to keep track of the duplicated tiles and the dimension(s) mirrored.
|
to keep track of the duplicated tiles and the dimension(s) mirrored.
|
||||||
Implies
|
Implies
|
||||||
.Fl u .
|
.Fl u .
|
||||||
|
Equivalent to
|
||||||
|
.Fl XY .
|
||||||
.It Fl N Ar nb_tiles , Fl \-nb-tiles Ar nb_tiles
|
.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.
|
Set a maximum number of tiles that can be placed in each VRAM bank.
|
||||||
.Ar nb_tiles
|
.Ar nb_tiles
|
||||||
@@ -353,6 +356,10 @@ Some internal debug printing is enabled.
|
|||||||
The verbosity level does not go past 6.
|
The verbosity level does not go past 6.
|
||||||
.Pp
|
.Pp
|
||||||
Note that verbose output is only intended to be consumed by humans, and may change without notice between RGBDS releases; relying on those for scripts is not advised.
|
Note that verbose output is only intended to be consumed by humans, and may change without notice between RGBDS releases; relying on those for scripts is not advised.
|
||||||
|
.It Fl 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
|
.It Fl x Ar quantity , Fl \-trim-end Ar quantity
|
||||||
Do not output the last
|
Do not output the last
|
||||||
.Ar quantity
|
.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
|
Note also that the tiles that don't get output will not count towards
|
||||||
.Fl N Ap s
|
.Fl N Ap s
|
||||||
limit.
|
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
|
.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).
|
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.
|
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
|
.\" SPDX-License-Identifier: MIT
|
||||||
.\"
|
.\"
|
||||||
.Dd September 18, 2024
|
.Dd February 2, 2025
|
||||||
.Dt RGBLINK 1
|
.Dt RGBLINK 1
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
.Nd Game Boy linker
|
.Nd Game Boy linker
|
||||||
.Sh SYNOPSIS
|
.Sh SYNOPSIS
|
||||||
.Nm
|
.Nm
|
||||||
.Op Fl dMtVvwx
|
.Op Fl dhMtVvwx
|
||||||
.Op Fl l Ar linker_script
|
.Op Fl l Ar linker_script
|
||||||
.Op Fl m Ar map_file
|
.Op Fl m Ar map_file
|
||||||
.Op Fl n Ar sym_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.
|
Prohibit the use of sections that doesn't exist on a DMG, such as VRAM bank 1.
|
||||||
This option automatically enables
|
This option automatically enables
|
||||||
.Fl w .
|
.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
|
.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.
|
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.
|
The attributes assigned in the linker script must be consistent with any assigned in the code.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
.\" SPDX-License-Identifier: MIT
|
.\" SPDX-License-Identifier: MIT
|
||||||
.\"
|
.\"
|
||||||
.Dd September 18, 2024
|
.Dd February 2, 2025
|
||||||
.Dt RGBLINK 5
|
.Dt RGBLINK 5
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
|
|||||||
@@ -91,10 +91,11 @@ foreach(PROG "asm" "fix" "gfx" "link")
|
|||||||
${rgb${PROG}_src}
|
${rgb${PROG}_src}
|
||||||
${common_src}
|
${common_src}
|
||||||
)
|
)
|
||||||
if(SUFFIX)
|
|
||||||
set_target_properties(rgb${PROG} PROPERTIES SUFFIX ${SUFFIX})
|
|
||||||
endif()
|
|
||||||
install(TARGETS rgb${PROG} RUNTIME DESTINATION bin)
|
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()
|
endforeach()
|
||||||
|
|
||||||
if(LIBPNG_FOUND) # pkg-config
|
if(LIBPNG_FOUND) # pkg-config
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#include "asm/charmap.hpp"
|
#include "asm/charmap.hpp"
|
||||||
|
|
||||||
@@ -11,6 +11,7 @@
|
|||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
|
#include "extern/utf8decoder.hpp"
|
||||||
#include "helpers.hpp"
|
#include "helpers.hpp"
|
||||||
#include "util.hpp"
|
#include "util.hpp"
|
||||||
|
|
||||||
@@ -45,33 +46,40 @@ bool charmap_ForEach(
|
|||||||
for (Charmap const &charmap : charmapList) {
|
for (Charmap const &charmap : charmapList) {
|
||||||
// Traverse the trie depth-first to derive the character mappings in definition order
|
// Traverse the trie depth-first to derive the character mappings in definition order
|
||||||
std::map<size_t, std::string> mappings;
|
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());
|
auto [nodeIdx, mapping] = std::move(prefixes.top());
|
||||||
prefixes.pop();
|
prefixes.pop();
|
||||||
CharmapNode const &node = charmap.nodes[nodeIdx];
|
CharmapNode const &node = charmap.nodes[nodeIdx];
|
||||||
if (node.isTerminal())
|
if (node.isTerminal()) {
|
||||||
mappings[nodeIdx] = mapping;
|
mappings[nodeIdx] = mapping;
|
||||||
|
}
|
||||||
for (unsigned c = 0; c < 256; c++) {
|
for (unsigned c = 0; c < 256; c++) {
|
||||||
if (size_t nextIdx = node.next[c]; nextIdx)
|
if (size_t nextIdx = node.next[c]; nextIdx) {
|
||||||
prefixes.push({nextIdx, mapping + (char)c});
|
prefixes.push({nextIdx, mapping + static_cast<char>(c)});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mapFunc(charmap.name);
|
mapFunc(charmap.name);
|
||||||
for (auto [nodeIdx, mapping] : mappings)
|
for (auto [nodeIdx, mapping] : mappings) {
|
||||||
charFunc(mapping, charmap.nodes[nodeIdx].value);
|
charFunc(mapping, charmap.nodes[nodeIdx].value);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return !charmapList.empty();
|
return !charmapList.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
void charmap_New(std::string const &name, std::string const *baseName) {
|
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 (baseName != nullptr) {
|
||||||
if (auto search = charmapMap.find(*baseName); search == charmapMap.end())
|
if (auto search = charmapMap.find(*baseName); search == charmapMap.end()) {
|
||||||
error("Base charmap '%s' doesn't exist\n", baseName->c_str());
|
error("Base charmap '%s' doesn't exist\n", baseName->c_str());
|
||||||
else
|
} else {
|
||||||
baseIdx = search->second;
|
baseIdx = search->second;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (charmapMap.find(name) != charmapMap.end()) {
|
if (charmapMap.find(name) != charmapMap.end()) {
|
||||||
error("Charmap '%s' already exists\n", name.c_str());
|
error("Charmap '%s' already exists\n", name.c_str());
|
||||||
@@ -82,10 +90,11 @@ void charmap_New(std::string const &name, std::string const *baseName) {
|
|||||||
charmapMap[name] = charmapList.size();
|
charmapMap[name] = charmapList.size();
|
||||||
Charmap &charmap = charmapList.emplace_back();
|
Charmap &charmap = charmapList.emplace_back();
|
||||||
|
|
||||||
if (baseIdx != (size_t)-1)
|
if (baseIdx != SIZE_MAX) {
|
||||||
charmap.nodes = charmapList[baseIdx].nodes; // Copies `charmapList[baseIdx].nodes`
|
charmap.nodes = charmapList[baseIdx].nodes; // Copies `charmapList[baseIdx].nodes`
|
||||||
else
|
} else {
|
||||||
charmap.nodes.emplace_back(); // Zero-init the root node
|
charmap.nodes.emplace_back(); // Zero-init the root node
|
||||||
|
}
|
||||||
|
|
||||||
charmap.name = name;
|
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) {
|
void charmap_Set(std::string const &name) {
|
||||||
if (auto search = charmapMap.find(name); search == charmapMap.end())
|
if (auto search = charmapMap.find(name); search == charmapMap.end()) {
|
||||||
error("Charmap '%s' doesn't exist\n", name.c_str());
|
error("Charmap '%s' doesn't exist\n", name.c_str());
|
||||||
else
|
} else {
|
||||||
currentCharmap = &charmapList[search->second];
|
currentCharmap = &charmapList[search->second];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void charmap_Push() {
|
void charmap_Push() {
|
||||||
@@ -113,6 +123,12 @@ void charmap_Pop() {
|
|||||||
charmapStack.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) {
|
void charmap_Add(std::string const &mapping, std::vector<int32_t> &&value) {
|
||||||
if (mapping.empty()) {
|
if (mapping.empty()) {
|
||||||
error("Cannot map an empty string\n");
|
error("Cannot map an empty string\n");
|
||||||
@@ -123,7 +139,7 @@ void charmap_Add(std::string const &mapping, std::vector<int32_t> &&value) {
|
|||||||
size_t nodeIdx = 0;
|
size_t nodeIdx = 0;
|
||||||
|
|
||||||
for (char c : mapping) {
|
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;
|
size_t nextIdx = nextIdxRef;
|
||||||
|
|
||||||
if (!nextIdx) {
|
if (!nextIdx) {
|
||||||
@@ -140,8 +156,9 @@ void charmap_Add(std::string const &mapping, std::vector<int32_t> &&value) {
|
|||||||
|
|
||||||
CharmapNode &node = charmap.nodes[nodeIdx];
|
CharmapNode &node = charmap.nodes[nodeIdx];
|
||||||
|
|
||||||
if (node.isTerminal())
|
if (node.isTerminal()) {
|
||||||
warning(WARNING_CHARMAP_REDEF, "Overriding charmap mapping\n");
|
warning(WARNING_CHARMAP_REDEF, "Overriding charmap mapping\n");
|
||||||
|
}
|
||||||
|
|
||||||
std::swap(node.value, value);
|
std::swap(node.value, value);
|
||||||
}
|
}
|
||||||
@@ -151,19 +168,19 @@ bool charmap_HasChar(std::string const &input) {
|
|||||||
size_t nodeIdx = 0;
|
size_t nodeIdx = 0;
|
||||||
|
|
||||||
for (char c : input) {
|
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 false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return charmap.nodes[nodeIdx].isTerminal();
|
return charmap.nodes[nodeIdx].isTerminal();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<int32_t> charmap_Convert(std::string const &input) {
|
std::vector<int32_t> charmap_Convert(std::string const &input) {
|
||||||
std::vector<int32_t> output;
|
std::vector<int32_t> output;
|
||||||
for (std::string_view inputView = input; charmap_ConvertNext(inputView, &output);)
|
for (std::string_view inputView = input; charmap_ConvertNext(inputView, &output);) {}
|
||||||
;
|
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,10 +195,11 @@ size_t charmap_ConvertNext(std::string_view &input, std::vector<int32_t> *output
|
|||||||
size_t inputIdx = 0;
|
size_t inputIdx = 0;
|
||||||
|
|
||||||
for (size_t nodeIdx = 0; inputIdx < input.length();) {
|
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;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
inputIdx++; // Consume that char
|
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
|
if (matchIdx) { // A match was found, use it
|
||||||
std::vector<int32_t> const &value = charmap.nodes[matchIdx].value;
|
std::vector<int32_t> const &value = charmap.nodes[matchIdx].value;
|
||||||
|
|
||||||
if (output)
|
if (output) {
|
||||||
output->insert(output->end(), RANGE(value));
|
output->insert(output->end(), RANGE(value));
|
||||||
|
}
|
||||||
|
|
||||||
matchLen = value.size();
|
matchLen = value.size();
|
||||||
} else if (inputIdx < input.length()) { // No match found, but there is some input left
|
} else if (inputIdx < input.length()) { // No match found, but there is some input left
|
||||||
int firstChar = input[inputIdx];
|
size_t codepointLen = 0;
|
||||||
// This will write the codepoint's value to `output`, little-endian
|
// This will write the codepoint's value to `output`, little-endian
|
||||||
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) {
|
||||||
if (codepointLen == 0)
|
|
||||||
error("Input string is not valid UTF-8\n");
|
error("Input string is not valid UTF-8\n");
|
||||||
|
codepointLen = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
codepointLen++;
|
||||||
|
if (state == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (output) {
|
||||||
|
output->insert(
|
||||||
|
output->end(), input.data() + inputIdx, input.data() + inputIdx + codepointLen
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Warn if this character is not mapped but any others are
|
// 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));
|
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(
|
||||||
WARNING_UNMAPPED_CHAR_2,
|
WARNING_UNMAPPED_CHAR_2,
|
||||||
"Unmapped character %s not in " DEFAULT_CHARMAP_NAME " charmap\n",
|
"Unmapped character %s not in " DEFAULT_CHARMAP_NAME " charmap\n",
|
||||||
printChar(firstChar)
|
printChar(firstChar)
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
inputIdx += codepointLen;
|
inputIdx += codepointLen;
|
||||||
matchLen = codepointLen;
|
matchLen = codepointLen;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
// Fixed-point math routines
|
// Fixed-point math routines
|
||||||
|
|
||||||
@@ -16,20 +16,18 @@ uint8_t fix_Precision() {
|
|||||||
return fixPrecision;
|
return fixPrecision;
|
||||||
}
|
}
|
||||||
|
|
||||||
double fix_PrecisionFactor() {
|
|
||||||
return pow(2.0, fixPrecision);
|
|
||||||
}
|
|
||||||
|
|
||||||
static double fix2double(int32_t i, int32_t q) {
|
static double fix2double(int32_t i, int32_t q) {
|
||||||
return i / pow(2.0, q);
|
return i / pow(2.0, q);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int32_t double2fix(double d, int32_t q) {
|
static int32_t double2fix(double d, int32_t q) {
|
||||||
if (isnan(d))
|
if (isnan(d)) {
|
||||||
return 0;
|
return 0;
|
||||||
if (isinf(d))
|
}
|
||||||
|
if (isinf(d)) {
|
||||||
return d < 0 ? INT32_MIN : INT32_MAX;
|
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) {
|
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) {
|
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) {
|
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) {
|
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) {
|
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"
|
#include "asm/format.hpp"
|
||||||
|
|
||||||
@@ -13,39 +13,44 @@
|
|||||||
#include "asm/warning.hpp"
|
#include "asm/warning.hpp"
|
||||||
|
|
||||||
void FormatSpec::useCharacter(int c) {
|
void FormatSpec::useCharacter(int c) {
|
||||||
if (state == FORMAT_INVALID)
|
if (state == FORMAT_INVALID) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
switch (c) {
|
switch (c) {
|
||||||
// sign
|
// sign
|
||||||
case ' ':
|
case ' ':
|
||||||
case '+':
|
case '+':
|
||||||
if (state > FORMAT_SIGN)
|
if (state > FORMAT_SIGN) {
|
||||||
goto invalid;
|
goto invalid;
|
||||||
|
}
|
||||||
state = FORMAT_EXACT;
|
state = FORMAT_EXACT;
|
||||||
sign = c;
|
sign = c;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// exact
|
// exact
|
||||||
case '#':
|
case '#':
|
||||||
if (state > FORMAT_EXACT)
|
if (state > FORMAT_EXACT) {
|
||||||
goto invalid;
|
goto invalid;
|
||||||
|
}
|
||||||
state = FORMAT_ALIGN;
|
state = FORMAT_ALIGN;
|
||||||
exact = true;
|
exact = true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// align
|
// align
|
||||||
case '-':
|
case '-':
|
||||||
if (state > FORMAT_ALIGN)
|
if (state > FORMAT_ALIGN) {
|
||||||
goto invalid;
|
goto invalid;
|
||||||
|
}
|
||||||
state = FORMAT_WIDTH;
|
state = FORMAT_WIDTH;
|
||||||
alignLeft = true;
|
alignLeft = true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// pad, width, and prec values
|
// pad, width, and prec values
|
||||||
case '0':
|
case '0':
|
||||||
if (state < FORMAT_WIDTH)
|
if (state < FORMAT_WIDTH) {
|
||||||
padZero = true;
|
padZero = true;
|
||||||
|
}
|
||||||
[[fallthrough]];
|
[[fallthrough]];
|
||||||
case '1':
|
case '1':
|
||||||
case '2':
|
case '2':
|
||||||
@@ -72,16 +77,18 @@ void FormatSpec::useCharacter(int c) {
|
|||||||
|
|
||||||
// width
|
// width
|
||||||
case '.':
|
case '.':
|
||||||
if (state > FORMAT_WIDTH)
|
if (state > FORMAT_WIDTH) {
|
||||||
goto invalid;
|
goto invalid;
|
||||||
|
}
|
||||||
state = FORMAT_FRAC;
|
state = FORMAT_FRAC;
|
||||||
hasFrac = true;
|
hasFrac = true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// prec
|
// prec
|
||||||
case 'q':
|
case 'q':
|
||||||
if (state > FORMAT_PREC)
|
if (state > FORMAT_PREC) {
|
||||||
goto invalid;
|
goto invalid;
|
||||||
|
}
|
||||||
state = FORMAT_PREC;
|
state = FORMAT_PREC;
|
||||||
hasPrec = true;
|
hasPrec = true;
|
||||||
break;
|
break;
|
||||||
@@ -95,8 +102,9 @@ void FormatSpec::useCharacter(int c) {
|
|||||||
case 'o':
|
case 'o':
|
||||||
case 'f':
|
case 'f':
|
||||||
case 's':
|
case 's':
|
||||||
if (state >= FORMAT_DONE)
|
if (state >= FORMAT_DONE) {
|
||||||
goto invalid;
|
goto invalid;
|
||||||
|
}
|
||||||
state = FORMAT_DONE;
|
state = FORMAT_DONE;
|
||||||
valid = true;
|
valid = true;
|
||||||
type = c;
|
type = c;
|
||||||
@@ -110,8 +118,9 @@ invalid:
|
|||||||
}
|
}
|
||||||
|
|
||||||
void FormatSpec::finishCharacters() {
|
void FormatSpec::finishCharacters() {
|
||||||
if (!isValid())
|
if (!isValid()) {
|
||||||
state = FORMAT_INVALID;
|
state = FORMAT_INVALID;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::string escapeString(std::string const &str) {
|
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';
|
useType = 's';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sign)
|
if (sign) {
|
||||||
error("Formatting string with sign flag '%c'\n", sign);
|
error("Formatting string with sign flag '%c'\n", sign);
|
||||||
if (padZero)
|
}
|
||||||
|
if (padZero) {
|
||||||
error("Formatting string with padding flag '0'\n");
|
error("Formatting string with padding flag '0'\n");
|
||||||
if (hasFrac)
|
}
|
||||||
|
if (hasFrac) {
|
||||||
error("Formatting string with fractional width\n");
|
error("Formatting string with fractional width\n");
|
||||||
if (hasPrec)
|
}
|
||||||
|
if (hasPrec) {
|
||||||
error("Formatting string with fractional precision\n");
|
error("Formatting string with fractional precision\n");
|
||||||
if (useType != 's')
|
}
|
||||||
|
if (useType != 's') {
|
||||||
error("Formatting string as type '%c'\n", useType);
|
error("Formatting string as type '%c'\n", useType);
|
||||||
|
}
|
||||||
|
|
||||||
std::string useValue = exact ? escapeString(value) : value;
|
std::string useValue = exact ? escapeString(value) : value;
|
||||||
size_t valueLen = useValue.length();
|
size_t valueLen = useValue.length();
|
||||||
@@ -186,24 +200,30 @@ void FormatSpec::appendNumber(std::string &str, uint32_t value) const {
|
|||||||
useExact = true;
|
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);
|
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);
|
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);
|
error("Formatting type '%c' with fractional precision\n", useType);
|
||||||
if (useType == 's')
|
}
|
||||||
|
if (useType == 's') {
|
||||||
error("Formatting number as type 's'\n");
|
error("Formatting number as type 's'\n");
|
||||||
|
}
|
||||||
|
|
||||||
char signChar = sign; // 0 or ' ' or '+'
|
char signChar = sign; // 0 or ' ' or '+'
|
||||||
|
|
||||||
if (useType == 'd' || useType == 'f') {
|
if (useType == 'd' || useType == 'f') {
|
||||||
if (int32_t v = value; v < 0) {
|
if (int32_t v = value; v < 0) {
|
||||||
signChar = '-';
|
signChar = '-';
|
||||||
if (v != INT32_MIN)
|
if (v != INT32_MIN) {
|
||||||
value = -v;
|
value = -v;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
char prefixChar = !useExact ? 0
|
char prefixChar = !useExact ? 0
|
||||||
: useType == 'X' ? '$'
|
: useType == 'X' ? '$'
|
||||||
@@ -249,15 +269,17 @@ void FormatSpec::appendNumber(std::string &str, uint32_t value) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
double fval = fabs(value / pow(2.0, usePrec));
|
double fval = fabs(value / pow(2.0, usePrec));
|
||||||
if (useExact)
|
if (int fracWidthArg = static_cast<int>(useFracWidth); useExact) {
|
||||||
snprintf(valueBuf, sizeof(valueBuf), "%.*fq%zu", (int)useFracWidth, fval, usePrec);
|
snprintf(valueBuf, sizeof(valueBuf), "%.*fq%zu", fracWidthArg, fval, usePrec);
|
||||||
else
|
} else {
|
||||||
snprintf(valueBuf, sizeof(valueBuf), "%.*f", (int)useFracWidth, fval);
|
snprintf(valueBuf, sizeof(valueBuf), "%.*f", fracWidthArg, fval);
|
||||||
|
}
|
||||||
} else if (useType == 'd') {
|
} else if (useType == 'd') {
|
||||||
// Decimal numbers may be formatted with a '-' sign by `snprintf`, so `abs` prevents that,
|
// 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
|
// with a special case for `INT32_MIN` since `labs(INT32_MIN)` is UB. The sign will be
|
||||||
// printed later from `signChar`.
|
// 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);
|
snprintf(valueBuf, sizeof(valueBuf), "%" PRIu32, uval);
|
||||||
} else {
|
} else {
|
||||||
char const *spec = useType == 'u' ? "%" PRIu32
|
char const *spec = useType == 'u' ? "%" PRIu32
|
||||||
@@ -276,28 +298,34 @@ void FormatSpec::appendNumber(std::string &str, uint32_t value) const {
|
|||||||
|
|
||||||
str.reserve(str.length() + totalLen);
|
str.reserve(str.length() + totalLen);
|
||||||
if (alignLeft) {
|
if (alignLeft) {
|
||||||
if (signChar)
|
if (signChar) {
|
||||||
str += signChar;
|
str += signChar;
|
||||||
if (prefixChar)
|
}
|
||||||
|
if (prefixChar) {
|
||||||
str += prefixChar;
|
str += prefixChar;
|
||||||
|
}
|
||||||
str.append(valueBuf);
|
str.append(valueBuf);
|
||||||
str.append(padLen, ' ');
|
str.append(padLen, ' ');
|
||||||
} else {
|
} else {
|
||||||
if (padZero) {
|
if (padZero) {
|
||||||
// sign, then prefix, then zero padding
|
// sign, then prefix, then zero padding
|
||||||
if (signChar)
|
if (signChar) {
|
||||||
str += signChar;
|
str += signChar;
|
||||||
if (prefixChar)
|
}
|
||||||
|
if (prefixChar) {
|
||||||
str += prefixChar;
|
str += prefixChar;
|
||||||
|
}
|
||||||
str.append(padLen, '0');
|
str.append(padLen, '0');
|
||||||
} else {
|
} else {
|
||||||
// space padding, then sign, then prefix
|
// space padding, then sign, then prefix
|
||||||
str.append(padLen, ' ');
|
str.append(padLen, ' ');
|
||||||
if (signChar)
|
if (signChar) {
|
||||||
str += signChar;
|
str += signChar;
|
||||||
if (prefixChar)
|
}
|
||||||
|
if (prefixChar) {
|
||||||
str += prefixChar;
|
str += prefixChar;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
str.append(valueBuf);
|
str.append(valueBuf);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#include "asm/fstack.hpp"
|
#include "asm/fstack.hpp"
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
@@ -90,8 +90,9 @@ std::shared_ptr<std::string> fstk_GetUniqueIDStr() {
|
|||||||
std::shared_ptr<std::string> &str = contextStack.top().uniqueIDStr;
|
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 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++);
|
*str = "_u"s + std::to_string(nextUniqueID++);
|
||||||
|
}
|
||||||
|
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
@@ -103,28 +104,33 @@ MacroArgs *fstk_GetCurrentMacroArgs() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void fstk_AddIncludePath(std::string const &path) {
|
void fstk_AddIncludePath(std::string const &path) {
|
||||||
if (path.empty())
|
if (path.empty()) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
std::string &includePath = includePaths.emplace_back(path);
|
std::string &includePath = includePaths.emplace_back(path);
|
||||||
if (includePath.back() != '/')
|
if (includePath.back() != '/') {
|
||||||
includePath += '/';
|
includePath += '/';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void fstk_SetPreIncludeFile(std::string const &path) {
|
void fstk_SetPreIncludeFile(std::string const &path) {
|
||||||
if (!preIncludeName.empty())
|
if (!preIncludeName.empty()) {
|
||||||
warnx("Overriding pre-included filename %s", preIncludeName.c_str());
|
warnx("Overriding pre-included filename %s", preIncludeName.c_str());
|
||||||
|
}
|
||||||
preIncludeName = path;
|
preIncludeName = path;
|
||||||
if (verbose)
|
if (verbose) {
|
||||||
printf("Pre-included filename %s\n", preIncludeName.c_str());
|
printf("Pre-included filename %s\n", preIncludeName.c_str());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void printDep(std::string const &path) {
|
static void printDep(std::string const &path) {
|
||||||
if (dependFile) {
|
if (dependFile) {
|
||||||
fprintf(dependFile, "%s: %s\n", targetFileName.c_str(), path.c_str());
|
fprintf(dependFile, "%s: %s\n", targetFileName.c_str(), path.c_str());
|
||||||
if (generatePhonyDeps)
|
if (generatePhonyDeps) {
|
||||||
fprintf(dependFile, "%s:\n", path.c_str());
|
fprintf(dependFile, "%s:\n", path.c_str());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool isValidFilePath(std::string const &path) {
|
static bool isValidFilePath(std::string const &path) {
|
||||||
@@ -141,20 +147,22 @@ std::optional<std::string> fstk_FindFile(std::string const &path) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
errno = ENOENT;
|
errno = ENOENT;
|
||||||
if (generatedMissingIncludes)
|
if (generatedMissingIncludes) {
|
||||||
printDep(path);
|
printDep(path);
|
||||||
|
}
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool yywrap() {
|
bool yywrap() {
|
||||||
uint32_t ifDepth = lexer_GetIFDepth();
|
uint32_t ifDepth = lexer_GetIFDepth();
|
||||||
|
|
||||||
if (ifDepth != 0)
|
if (ifDepth != 0) {
|
||||||
fatalerror(
|
fatalerror(
|
||||||
"Ended block with %" PRIu32 " unterminated IF construct%s\n",
|
"Ended block with %" PRIu32 " unterminated IF construct%s\n",
|
||||||
ifDepth,
|
ifDepth,
|
||||||
ifDepth == 1 ? "" : "s"
|
ifDepth == 1 ? "" : "s"
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (Context &context = contextStack.top(); context.fileInfo->type == NODE_REPT) {
|
if (Context &context = contextStack.top(); context.fileInfo->type == NODE_REPT) {
|
||||||
// The context is a REPT or FOR block, which may loop
|
// 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 the node is referenced outside this context, we can't edit it, so duplicate it
|
||||||
if (context.fileInfo.use_count() > 1) {
|
if (context.fileInfo.use_count() > 1) {
|
||||||
context.fileInfo = std::make_shared<FileStackNode>(*context.fileInfo);
|
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();
|
std::vector<uint32_t> &fileInfoIters = context.fileInfo->iters();
|
||||||
@@ -170,14 +178,17 @@ bool yywrap() {
|
|||||||
// If this is a FOR, update the symbol value
|
// If this is a FOR, update the symbol value
|
||||||
if (context.isForLoop && fileInfoIters.front() <= context.nbReptIters) {
|
if (context.isForLoop && fileInfoIters.front() <= context.nbReptIters) {
|
||||||
// Avoid arithmetic overflow runtime error
|
// Avoid arithmetic overflow runtime error
|
||||||
uint32_t forValue = (uint32_t)context.forValue + (uint32_t)context.forStep;
|
uint32_t forValue =
|
||||||
context.forValue = forValue <= INT32_MAX ? forValue : -(int32_t)~forValue - 1;
|
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);
|
Symbol *sym = sym_AddVar(context.forName, context.forValue);
|
||||||
|
|
||||||
// This error message will refer to the current iteration
|
// This error message will refer to the current iteration
|
||||||
if (sym->type != SYM_VAR)
|
if (sym->type != SYM_VAR) {
|
||||||
fatalerror("Failed to update FOR symbol value\n");
|
fatalerror("Failed to update FOR symbol value\n");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// Advance to the next iteration
|
// Advance to the next iteration
|
||||||
fileInfoIters.front()++;
|
fileInfoIters.front()++;
|
||||||
// If this wasn't the last iteration, wrap instead of popping
|
// If this wasn't the last iteration, wrap instead of popping
|
||||||
@@ -197,8 +208,9 @@ bool yywrap() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void checkRecursionDepth() {
|
static void checkRecursionDepth() {
|
||||||
if (contextStack.size() > maxRecursionDepth)
|
if (contextStack.size() > maxRecursionDepth) {
|
||||||
fatalerror("Recursion limit (%zu) exceeded\n", maxRecursionDepth);
|
fatalerror("Recursion limit (%zu) exceeded\n", maxRecursionDepth);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool newFileContext(std::string const &filePath, bool updateStateNow) {
|
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;
|
std::shared_ptr<MacroArgs> macroArgs = nullptr;
|
||||||
|
|
||||||
auto fileInfo =
|
auto fileInfo =
|
||||||
std::make_shared<FileStackNode>(NODE_MACRO, filePath == "-" ? "<stdin>" : filePath);
|
std::make_shared<FileStackNode>(NODE_FILE, filePath == "-" ? "<stdin>" : filePath);
|
||||||
if (!contextStack.empty()) {
|
if (!contextStack.empty()) {
|
||||||
Context &oldContext = contextStack.top();
|
Context &oldContext = contextStack.top();
|
||||||
fileInfo->parent = oldContext.fileInfo;
|
fileInfo->parent = oldContext.fileInfo;
|
||||||
@@ -296,8 +308,9 @@ void fstk_RunInclude(std::string const &path, bool preInclude) {
|
|||||||
|
|
||||||
if (!fullPath) {
|
if (!fullPath) {
|
||||||
if (generatedMissingIncludes && !preInclude) {
|
if (generatedMissingIncludes && !preInclude) {
|
||||||
if (verbose)
|
if (verbose) {
|
||||||
printf("Aborting (-MG) on INCLUDE file '%s' (%s)\n", path.c_str(), strerror(errno));
|
printf("Aborting (-MG) on INCLUDE file '%s' (%s)\n", path.c_str(), strerror(errno));
|
||||||
|
}
|
||||||
failedOnMissingInclude = true;
|
failedOnMissingInclude = true;
|
||||||
} else {
|
} else {
|
||||||
error("Unable to open included file '%s': %s\n", path.c_str(), strerror(errno));
|
error("Unable to open included file '%s': %s\n", path.c_str(), strerror(errno));
|
||||||
@@ -305,18 +318,20 @@ void fstk_RunInclude(std::string const &path, bool preInclude) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!newFileContext(*fullPath, false))
|
if (!newFileContext(*fullPath, false)) {
|
||||||
fatalerror("Failed to set up lexer for file include\n");
|
fatalerror("Failed to set up lexer for file include\n");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void fstk_RunMacro(std::string const ¯oName, std::shared_ptr<MacroArgs> macroArgs) {
|
void fstk_RunMacro(std::string const ¯oName, std::shared_ptr<MacroArgs> macroArgs) {
|
||||||
Symbol *macro = sym_FindExactSymbol(macroName);
|
Symbol *macro = sym_FindExactSymbol(macroName);
|
||||||
|
|
||||||
if (!macro) {
|
if (!macro) {
|
||||||
if (sym_IsPurgedExact(macroName))
|
if (sym_IsPurgedExact(macroName)) {
|
||||||
error("Macro \"%s\" not defined; it was purged\n", macroName.c_str());
|
error("Macro \"%s\" not defined; it was purged\n", macroName.c_str());
|
||||||
else
|
} else {
|
||||||
error("Macro \"%s\" not defined\n", macroName.c_str());
|
error("Macro \"%s\" not defined\n", macroName.c_str());
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (macro->type != SYM_MACRO) {
|
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) {
|
void fstk_RunRept(uint32_t count, int32_t reptLineNo, ContentSpan const &span) {
|
||||||
if (count == 0)
|
if (count == 0) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
newReptContext(reptLineNo, span, count);
|
newReptContext(reptLineNo, span, count);
|
||||||
}
|
}
|
||||||
@@ -342,24 +358,28 @@ void fstk_RunFor(
|
|||||||
int32_t reptLineNo,
|
int32_t reptLineNo,
|
||||||
ContentSpan const &span
|
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;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
uint32_t count = 0;
|
uint32_t count = 0;
|
||||||
if (step > 0 && start < stop)
|
if (step > 0 && start < stop) {
|
||||||
count = ((int64_t)stop - start - 1) / step + 1;
|
count = (static_cast<int64_t>(stop) - start - 1) / step + 1;
|
||||||
else if (step < 0 && stop < start)
|
} else if (step < 0 && stop < start) {
|
||||||
count = ((int64_t)start - stop - 1) / -(int64_t)step + 1;
|
count = (static_cast<int64_t>(start) - stop - 1) / -static_cast<int64_t>(step) + 1;
|
||||||
else if (step == 0)
|
} else if (step == 0) {
|
||||||
error("FOR cannot have a step value of 0\n");
|
error("FOR cannot have a step value of 0\n");
|
||||||
|
}
|
||||||
|
|
||||||
if ((step > 0 && start > stop) || (step < 0 && start < stop))
|
if ((step > 0 && start > stop) || (step < 0 && start < stop)) {
|
||||||
warning(
|
warning(
|
||||||
WARNING_BACKWARDS_FOR, "FOR goes backwards from %d to %d by %d\n", start, stop, step
|
WARNING_BACKWARDS_FOR, "FOR goes backwards from %d to %d by %d\n", start, stop, step
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (count == 0)
|
if (count == 0) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Context &context = newReptContext(reptLineNo, span, count);
|
Context &context = newReptContext(reptLineNo, span, count);
|
||||||
context.isForLoop = true;
|
context.isForLoop = true;
|
||||||
@@ -383,17 +403,20 @@ bool fstk_Break() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void fstk_NewRecursionDepth(size_t newDepth) {
|
void fstk_NewRecursionDepth(size_t newDepth) {
|
||||||
if (contextStack.size() > newDepth + 1)
|
if (contextStack.size() > newDepth + 1) {
|
||||||
fatalerror("Recursion limit (%zu) exceeded\n", newDepth);
|
fatalerror("Recursion limit (%zu) exceeded\n", newDepth);
|
||||||
|
}
|
||||||
maxRecursionDepth = newDepth;
|
maxRecursionDepth = newDepth;
|
||||||
}
|
}
|
||||||
|
|
||||||
void fstk_Init(std::string const &mainPath, size_t maxDepth) {
|
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");
|
fatalerror("Failed to open main file\n");
|
||||||
|
}
|
||||||
|
|
||||||
maxRecursionDepth = maxDepth;
|
maxRecursionDepth = maxDepth;
|
||||||
|
|
||||||
if (!preIncludeName.empty())
|
if (!preIncludeName.empty()) {
|
||||||
fstk_RunInclude(preIncludeName, true);
|
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"
|
#include "asm/macro.hpp"
|
||||||
|
|
||||||
@@ -6,12 +6,8 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include "helpers.hpp"
|
|
||||||
|
|
||||||
#include "asm/warning.hpp"
|
#include "asm/warning.hpp"
|
||||||
|
|
||||||
#define MAXMACROARGS 99999
|
|
||||||
|
|
||||||
std::shared_ptr<std::string> MacroArgs::getArg(uint32_t i) const {
|
std::shared_ptr<std::string> MacroArgs::getArg(uint32_t i) const {
|
||||||
uint32_t realIndex = i + shift - 1;
|
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 {
|
std::shared_ptr<std::string> MacroArgs::getAllArgs() const {
|
||||||
size_t nbArgs = args.size();
|
size_t nbArgs = args.size();
|
||||||
|
|
||||||
if (shift >= nbArgs)
|
if (shift >= nbArgs) {
|
||||||
return std::make_shared<std::string>("");
|
return std::make_shared<std::string>("");
|
||||||
|
}
|
||||||
|
|
||||||
size_t len = 0;
|
size_t len = 0;
|
||||||
|
|
||||||
for (uint32_t i = shift; i < nbArgs; i++)
|
for (uint32_t i = shift; i < nbArgs; i++) {
|
||||||
len += args[i]->length() + 1; // 1 for comma
|
len += args[i]->length() + 1; // 1 for comma
|
||||||
|
}
|
||||||
|
|
||||||
auto str = std::make_shared<std::string>();
|
auto str = std::make_shared<std::string>();
|
||||||
str->reserve(len + 1); // 1 for comma
|
str->reserve(len + 1); // 1 for comma
|
||||||
@@ -38,27 +36,27 @@ std::shared_ptr<std::string> MacroArgs::getAllArgs() const {
|
|||||||
str->append(*arg);
|
str->append(*arg);
|
||||||
|
|
||||||
// Commas go between args and after a last empty 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
|
str->push_back(','); // no space after comma
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MacroArgs::appendArg(std::shared_ptr<std::string> arg) {
|
void MacroArgs::appendArg(std::shared_ptr<std::string> arg) {
|
||||||
if (arg->empty())
|
if (arg->empty()) {
|
||||||
warning(WARNING_EMPTY_MACRO_ARG, "Empty macro argument\n");
|
warning(WARNING_EMPTY_MACRO_ARG, "Empty macro argument\n");
|
||||||
if (args.size() == MAXMACROARGS)
|
}
|
||||||
error("A maximum of " EXPAND_AND_STR(MAXMACROARGS) " arguments is allowed\n");
|
|
||||||
args.push_back(arg);
|
args.push_back(arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MacroArgs::shiftArgs(int32_t count) {
|
void MacroArgs::shiftArgs(int32_t count) {
|
||||||
if (size_t nbArgs = args.size();
|
if (size_t nbArgs = args.size();
|
||||||
count > 0 && ((uint32_t)count > nbArgs || shift > nbArgs - count)) {
|
count > 0 && (static_cast<uint32_t>(count) > nbArgs || shift > nbArgs - count)) {
|
||||||
warning(WARNING_MACRO_SHIFT, "Cannot shift macro arguments past their end\n");
|
warning(WARNING_MACRO_SHIFT, "Cannot shift macro arguments past their end\n");
|
||||||
shift = nbArgs;
|
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");
|
warning(WARNING_MACRO_SHIFT, "Cannot shift macro arguments past their beginning\n");
|
||||||
shift = 0;
|
shift = 0;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
131
src/asm/main.cpp
131
src/asm/main.cpp
@@ -1,4 +1,4 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#include "asm/main.hpp"
|
#include "asm/main.hpp"
|
||||||
|
|
||||||
@@ -37,30 +37,30 @@ static std::string make_escape(std::string &str) {
|
|||||||
for (;;) {
|
for (;;) {
|
||||||
// All dollars needs to be doubled
|
// All dollars needs to be doubled
|
||||||
size_t nextPos = str.find("$", pos);
|
size_t nextPos = str.find("$", pos);
|
||||||
if (nextPos == std::string::npos)
|
if (nextPos == std::string::npos) {
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
escaped.append(str, pos, nextPos - pos);
|
escaped.append(str, pos, nextPos - pos);
|
||||||
escaped.append("$$");
|
escaped.append("$$");
|
||||||
pos = nextPos + QUOTEDSTRLEN("$");
|
pos = nextPos + literal_strlen("$");
|
||||||
}
|
}
|
||||||
escaped.append(str, pos, str.length() - pos);
|
escaped.append(str, pos, str.length() - pos);
|
||||||
return escaped;
|
return escaped;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Short options
|
// 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
|
// Variables for the long-only options
|
||||||
static int depType; // Variants of `-M`
|
static int depType; // Variants of `-M`
|
||||||
|
|
||||||
// Equivalent long options
|
// 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:
|
// Also, make sure long opts don't create ambiguity:
|
||||||
// A long opt's name should start with the same letter as its short opt,
|
// 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`).
|
// except if it doesn't create any ambiguity (`verbose` versus `version`).
|
||||||
// This is because long opt matching, even to a single char, is prioritized
|
// 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[] = {
|
static option const longopts[] = {
|
||||||
{"binary-digits", required_argument, nullptr, 'b'},
|
{"binary-digits", required_argument, nullptr, 'b'},
|
||||||
{"define", required_argument, nullptr, 'D'},
|
{"define", required_argument, nullptr, 'D'},
|
||||||
@@ -69,6 +69,7 @@ static option const longopts[] = {
|
|||||||
{"include", required_argument, nullptr, 'I'},
|
{"include", required_argument, nullptr, 'I'},
|
||||||
{"dependfile", required_argument, nullptr, 'M'},
|
{"dependfile", required_argument, nullptr, 'M'},
|
||||||
{"MG", no_argument, &depType, 'G'},
|
{"MG", no_argument, &depType, 'G'},
|
||||||
|
{"help", no_argument, nullptr, 'h'},
|
||||||
{"MP", no_argument, &depType, 'P'},
|
{"MP", no_argument, &depType, 'P'},
|
||||||
{"MT", required_argument, &depType, 'T'},
|
{"MT", required_argument, &depType, 'T'},
|
||||||
{"warning", required_argument, nullptr, 'W'},
|
{"warning", required_argument, nullptr, 'W'},
|
||||||
@@ -88,7 +89,7 @@ static option const longopts[] = {
|
|||||||
|
|
||||||
static void printUsage() {
|
static void printUsage() {
|
||||||
fputs(
|
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"
|
" [-M depend_file] [-MG] [-MP] [-MT target_file] [-MQ target_file]\n"
|
||||||
" [-o out_file] [-P include_file] [-p pad_value] [-Q precision]\n"
|
" [-o out_file] [-P include_file] [-p pad_value] [-Q precision]\n"
|
||||||
" [-r depth] [-s features:state_file] [-W warning] [-X max_errors]\n"
|
" [-r depth] [-s features:state_file] [-W warning] [-X max_errors]\n"
|
||||||
@@ -111,12 +112,14 @@ int main(int argc, char *argv[]) {
|
|||||||
time_t now = time(nullptr);
|
time_t now = time(nullptr);
|
||||||
// Support SOURCE_DATE_EPOCH for reproducible builds
|
// Support SOURCE_DATE_EPOCH for reproducible builds
|
||||||
// https://reproducible-builds.org/docs/source-date-epoch/
|
// https://reproducible-builds.org/docs/source-date-epoch/
|
||||||
if (char const *sourceDateEpoch = getenv("SOURCE_DATE_EPOCH"); sourceDateEpoch)
|
if (char const *sourceDateEpoch = getenv("SOURCE_DATE_EPOCH"); sourceDateEpoch) {
|
||||||
now = (time_t)strtoul(sourceDateEpoch, nullptr, 0);
|
now = static_cast<time_t>(strtoul(sourceDateEpoch, nullptr, 0));
|
||||||
|
}
|
||||||
|
|
||||||
Defer closeDependFile{[&] {
|
Defer closeDependFile{[&] {
|
||||||
if (dependFile)
|
if (dependFile) {
|
||||||
fclose(dependFile);
|
fclose(dependFile);
|
||||||
|
}
|
||||||
}};
|
}};
|
||||||
|
|
||||||
// Perform some init for below
|
// Perform some init for below
|
||||||
@@ -128,23 +131,25 @@ int main(int argc, char *argv[]) {
|
|||||||
opt_P(0);
|
opt_P(0);
|
||||||
opt_Q(16);
|
opt_Q(16);
|
||||||
sym_SetExportAll(false);
|
sym_SetExportAll(false);
|
||||||
uint32_t maxDepth = DEFAULT_MAX_DEPTH;
|
uint32_t maxDepth = 64;
|
||||||
char const *dependFileName = nullptr;
|
char const *dependFileName = nullptr;
|
||||||
std::unordered_map<std::string, std::vector<StateFeature>> stateFileSpecs;
|
std::unordered_map<std::string, std::vector<StateFeature>> stateFileSpecs;
|
||||||
std::string newTarget;
|
std::string newTarget;
|
||||||
// Maximum of 100 errors only applies if rgbasm is printing errors to a terminal.
|
// Maximum of 100 errors only applies if rgbasm is printing errors to a terminal.
|
||||||
if (isatty(STDERR_FILENO))
|
if (isatty(STDERR_FILENO)) {
|
||||||
maxErrors = 100;
|
maxErrors = 100;
|
||||||
|
}
|
||||||
|
|
||||||
for (int ch; (ch = musl_getopt_long_only(argc, argv, optstring, longopts, nullptr)) != -1;) {
|
for (int ch; (ch = musl_getopt_long_only(argc, argv, optstring, longopts, nullptr)) != -1;) {
|
||||||
switch (ch) {
|
switch (ch) {
|
||||||
char *endptr;
|
char *endptr;
|
||||||
|
|
||||||
case 'b':
|
case 'b':
|
||||||
if (strlen(musl_optarg) == 2)
|
if (strlen(musl_optarg) == 2) {
|
||||||
opt_B(musl_optarg);
|
opt_B(musl_optarg);
|
||||||
else
|
} else {
|
||||||
errx("Must specify exactly 2 characters for option 'b'");
|
errx("Must specify exactly 2 characters for option 'b'");
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
char *equals;
|
char *equals;
|
||||||
@@ -163,19 +168,25 @@ int main(int argc, char *argv[]) {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'g':
|
case 'g':
|
||||||
if (strlen(musl_optarg) == 4)
|
if (strlen(musl_optarg) == 4) {
|
||||||
opt_G(musl_optarg);
|
opt_G(musl_optarg);
|
||||||
else
|
} else {
|
||||||
errx("Must specify exactly 4 characters for option 'g'");
|
errx("Must specify exactly 4 characters for option 'g'");
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'h':
|
||||||
|
printUsage();
|
||||||
|
exit(0);
|
||||||
|
|
||||||
case 'I':
|
case 'I':
|
||||||
fstk_AddIncludePath(musl_optarg);
|
fstk_AddIncludePath(musl_optarg);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'M':
|
case 'M':
|
||||||
if (dependFile)
|
if (dependFile) {
|
||||||
warnx("Overriding dependfile %s", dependFileName);
|
warnx("Overriding dependfile %s", dependFileName);
|
||||||
|
}
|
||||||
if (strcmp("-", musl_optarg)) {
|
if (strcmp("-", musl_optarg)) {
|
||||||
dependFile = fopen(musl_optarg, "w");
|
dependFile = fopen(musl_optarg, "w");
|
||||||
dependFileName = musl_optarg;
|
dependFileName = musl_optarg;
|
||||||
@@ -183,8 +194,9 @@ int main(int argc, char *argv[]) {
|
|||||||
dependFile = stdout;
|
dependFile = stdout;
|
||||||
dependFileName = "<stdout>";
|
dependFileName = "<stdout>";
|
||||||
}
|
}
|
||||||
if (dependFile == nullptr)
|
if (dependFile == nullptr) {
|
||||||
err("Failed to open dependfile \"%s\"", dependFileName);
|
err("Failed to open dependfile \"%s\"", dependFileName);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'o':
|
case 'o':
|
||||||
@@ -199,11 +211,13 @@ int main(int argc, char *argv[]) {
|
|||||||
case 'p':
|
case 'p':
|
||||||
padByte = strtoul(musl_optarg, &endptr, 0);
|
padByte = strtoul(musl_optarg, &endptr, 0);
|
||||||
|
|
||||||
if (musl_optarg[0] == '\0' || *endptr != '\0')
|
if (musl_optarg[0] == '\0' || *endptr != '\0') {
|
||||||
errx("Invalid argument for option 'p'");
|
errx("Invalid argument for option 'p'");
|
||||||
|
}
|
||||||
|
|
||||||
if (padByte > 0xFF)
|
if (padByte > 0xFF) {
|
||||||
errx("Argument for option 'p' must be between 0 and 0xFF");
|
errx("Argument for option 'p' must be between 0 and 0xFF");
|
||||||
|
}
|
||||||
|
|
||||||
opt_P(padByte);
|
opt_P(padByte);
|
||||||
break;
|
break;
|
||||||
@@ -212,15 +226,18 @@ int main(int argc, char *argv[]) {
|
|||||||
char const *precisionArg;
|
char const *precisionArg;
|
||||||
case 'Q':
|
case 'Q':
|
||||||
precisionArg = musl_optarg;
|
precisionArg = musl_optarg;
|
||||||
if (precisionArg[0] == '.')
|
if (precisionArg[0] == '.') {
|
||||||
precisionArg++;
|
precisionArg++;
|
||||||
|
}
|
||||||
precision = strtoul(precisionArg, &endptr, 0);
|
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'");
|
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");
|
errx("Argument for option 'Q' must be between 1 and 31");
|
||||||
|
}
|
||||||
|
|
||||||
opt_Q(precision);
|
opt_Q(precision);
|
||||||
break;
|
break;
|
||||||
@@ -228,35 +245,41 @@ int main(int argc, char *argv[]) {
|
|||||||
case 'r':
|
case 'r':
|
||||||
maxDepth = strtoul(musl_optarg, &endptr, 0);
|
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'");
|
errx("Invalid argument for option 'r'");
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 's': {
|
case 's': {
|
||||||
// Split "<features>:<name>" so `musl_optarg` is "<features>" and `name` is "<name>"
|
// Split "<features>:<name>" so `musl_optarg` is "<features>" and `name` is "<name>"
|
||||||
char *name = strchr(musl_optarg, ':');
|
char *name = strchr(musl_optarg, ':');
|
||||||
if (!name)
|
if (!name) {
|
||||||
errx("Invalid argument for option 's'");
|
errx("Invalid argument for option 's'");
|
||||||
|
}
|
||||||
*name++ = '\0';
|
*name++ = '\0';
|
||||||
|
|
||||||
std::vector<StateFeature> features;
|
std::vector<StateFeature> features;
|
||||||
for (char *feature = musl_optarg; feature;) {
|
for (char *feature = musl_optarg; feature;) {
|
||||||
// Split "<feature>,<rest>" so `feature` is "<feature>" and `next` is "<rest>"
|
// Split "<feature>,<rest>" so `feature` is "<feature>" and `next` is "<rest>"
|
||||||
char *next = strchr(feature, ',');
|
char *next = strchr(feature, ',');
|
||||||
if (next)
|
if (next) {
|
||||||
*next++ = '\0';
|
*next++ = '\0';
|
||||||
|
}
|
||||||
// Trim whitespace from the beginning of `feature`...
|
// Trim whitespace from the beginning of `feature`...
|
||||||
feature += strspn(feature, " \t");
|
feature += strspn(feature, " \t");
|
||||||
// ...and from the end
|
// ...and from the end
|
||||||
if (char *end = strpbrk(feature, " \t"); end)
|
if (char *end = strpbrk(feature, " \t"); end) {
|
||||||
*end = '\0';
|
*end = '\0';
|
||||||
|
}
|
||||||
// A feature must be specified
|
// A feature must be specified
|
||||||
if (*feature == '\0')
|
if (*feature == '\0') {
|
||||||
errx("Empty feature for option 's'");
|
errx("Empty feature for option 's'");
|
||||||
|
}
|
||||||
// Parse the `feature` and update the `features` list
|
// Parse the `feature` and update the `features` list
|
||||||
if (!strcasecmp(feature, "all")) {
|
if (!strcasecmp(feature, "all")) {
|
||||||
if (!features.empty())
|
if (!features.empty()) {
|
||||||
warnx("Redundant feature before \"%s\" for option 's'", feature);
|
warnx("Redundant feature before \"%s\" for option 's'", feature);
|
||||||
|
}
|
||||||
features.assign({STATE_EQU, STATE_VAR, STATE_EQUS, STATE_CHAR, STATE_MACRO});
|
features.assign({STATE_EQU, STATE_VAR, STATE_EQUS, STATE_CHAR, STATE_MACRO});
|
||||||
} else {
|
} else {
|
||||||
StateFeature value = !strcasecmp(feature, "equ") ? STATE_EQU
|
StateFeature value = !strcasecmp(feature, "equ") ? STATE_EQU
|
||||||
@@ -276,10 +299,12 @@ int main(int argc, char *argv[]) {
|
|||||||
feature = next;
|
feature = next;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stateFileSpecs.find(name) != stateFileSpecs.end())
|
if (stateFileSpecs.find(name) != stateFileSpecs.end()) {
|
||||||
warnx("Overriding state filename %s", name);
|
warnx("Overriding state filename %s", name);
|
||||||
if (verbose)
|
}
|
||||||
|
if (verbose) {
|
||||||
printf("State filename %s\n", name);
|
printf("State filename %s\n", name);
|
||||||
|
}
|
||||||
stateFileSpecs.emplace(name, std::move(features));
|
stateFileSpecs.emplace(name, std::move(features));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -293,7 +318,7 @@ int main(int argc, char *argv[]) {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'W':
|
case 'W':
|
||||||
processWarningFlag(musl_optarg);
|
opt_W(musl_optarg);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'w':
|
case 'w':
|
||||||
@@ -304,11 +329,13 @@ int main(int argc, char *argv[]) {
|
|||||||
case 'X':
|
case 'X':
|
||||||
maxValue = strtoul(musl_optarg, &endptr, 0);
|
maxValue = strtoul(musl_optarg, &endptr, 0);
|
||||||
|
|
||||||
if (musl_optarg[0] == '\0' || *endptr != '\0')
|
if (musl_optarg[0] == '\0' || *endptr != '\0') {
|
||||||
errx("Invalid argument for option 'X'");
|
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);
|
errx("Argument for option 'X' must be between 0 and %u", UINT_MAX);
|
||||||
|
}
|
||||||
|
|
||||||
maxErrors = maxValue;
|
maxErrors = maxValue;
|
||||||
break;
|
break;
|
||||||
@@ -327,10 +354,12 @@ int main(int argc, char *argv[]) {
|
|||||||
case 'Q':
|
case 'Q':
|
||||||
case 'T':
|
case 'T':
|
||||||
newTarget = musl_optarg;
|
newTarget = musl_optarg;
|
||||||
if (depType == 'Q')
|
if (depType == 'Q') {
|
||||||
newTarget = make_escape(newTarget);
|
newTarget = make_escape(newTarget);
|
||||||
if (!targetFileName.empty())
|
}
|
||||||
|
if (!targetFileName.empty()) {
|
||||||
targetFileName += ' ';
|
targetFileName += ' ';
|
||||||
|
}
|
||||||
targetFileName += newTarget;
|
targetFileName += newTarget;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -343,8 +372,9 @@ int main(int argc, char *argv[]) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (targetFileName.empty() && !objectFileName.empty())
|
if (targetFileName.empty() && !objectFileName.empty()) {
|
||||||
targetFileName = objectFileName;
|
targetFileName = objectFileName;
|
||||||
|
}
|
||||||
|
|
||||||
if (argc == musl_optind) {
|
if (argc == musl_optind) {
|
||||||
fputs(
|
fputs(
|
||||||
@@ -360,13 +390,15 @@ int main(int argc, char *argv[]) {
|
|||||||
|
|
||||||
std::string mainFileName = argv[musl_optind];
|
std::string mainFileName = argv[musl_optind];
|
||||||
|
|
||||||
if (verbose)
|
if (verbose) {
|
||||||
printf("Assembling %s\n", mainFileName.c_str());
|
printf("Assembling %s\n", mainFileName.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
if (dependFile) {
|
if (dependFile) {
|
||||||
if (targetFileName.empty())
|
if (targetFileName.empty()) {
|
||||||
errx("Dependency files can only be created if a target file is specified with either "
|
errx("Dependency files can only be created if a target file is specified with either "
|
||||||
"-o, -MQ or -MT");
|
"-o, -MQ or -MT");
|
||||||
|
}
|
||||||
|
|
||||||
fprintf(dependFile, "%s: %s\n", targetFileName.c_str(), mainFileName.c_str());
|
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);
|
fstk_Init(mainFileName, maxDepth);
|
||||||
|
|
||||||
// Perform parse (`yy::parser` is auto-generated from `parser.y`)
|
// Perform parse (`yy::parser` is auto-generated from `parser.y`)
|
||||||
if (yy::parser parser; parser.parse() != 0 && nbErrors == 0)
|
if (yy::parser parser; parser.parse() != 0 && nbErrors == 0) {
|
||||||
nbErrors = 1;
|
nbErrors = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!failedOnMissingInclude) {
|
||||||
sect_CheckUnionClosed();
|
sect_CheckUnionClosed();
|
||||||
|
sect_CheckLoadClosed();
|
||||||
sect_CheckSizes();
|
sect_CheckSizes();
|
||||||
|
|
||||||
if (nbErrors != 0)
|
charmap_CheckStack();
|
||||||
|
opt_CheckStack();
|
||||||
|
sect_CheckStack();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nbErrors != 0) {
|
||||||
errx("Assembly aborted (%u error%s)!", nbErrors, nbErrors == 1 ? "" : "s");
|
errx("Assembly aborted (%u error%s)!", nbErrors, nbErrors == 1 ? "" : "s");
|
||||||
|
}
|
||||||
|
|
||||||
// If parse aborted due to missing an include, and `-MG` was given, exit normally
|
// If parse aborted due to missing an include, and `-MG` was given, exit normally
|
||||||
if (failedOnMissingInclude)
|
if (failedOnMissingInclude) {
|
||||||
return 0;
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
out_WriteObject();
|
out_WriteObject();
|
||||||
|
|
||||||
for (auto [name, features] : stateFileSpecs)
|
for (auto [name, features] : stateFileSpecs) {
|
||||||
out_WriteState(name, features);
|
out_WriteState(name, features);
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
@@ -13,8 +13,6 @@
|
|||||||
#include "asm/section.hpp"
|
#include "asm/section.hpp"
|
||||||
#include "asm/warning.hpp"
|
#include "asm/warning.hpp"
|
||||||
|
|
||||||
static constexpr size_t numWarningStates = sizeof(warningStates);
|
|
||||||
|
|
||||||
struct OptStackEntry {
|
struct OptStackEntry {
|
||||||
char binary[2];
|
char binary[2];
|
||||||
char gbgfx[4];
|
char gbgfx[4];
|
||||||
@@ -22,7 +20,7 @@ struct OptStackEntry {
|
|||||||
uint8_t fillByte;
|
uint8_t fillByte;
|
||||||
bool warningsAreErrors;
|
bool warningsAreErrors;
|
||||||
size_t maxRecursionDepth;
|
size_t maxRecursionDepth;
|
||||||
WarningState warningStates[numWarningStates];
|
Diagnostics warningStates;
|
||||||
};
|
};
|
||||||
|
|
||||||
static std::stack<OptStackEntry> stack;
|
static std::stack<OptStackEntry> stack;
|
||||||
@@ -55,17 +53,19 @@ void opt_W(char const *flag) {
|
|||||||
void opt_Parse(char const *s) {
|
void opt_Parse(char const *s) {
|
||||||
switch (s[0]) {
|
switch (s[0]) {
|
||||||
case 'b':
|
case 'b':
|
||||||
if (strlen(&s[1]) == 2)
|
if (strlen(&s[1]) == 2) {
|
||||||
opt_B(&s[1]);
|
opt_B(&s[1]);
|
||||||
else
|
} else {
|
||||||
error("Must specify exactly 2 characters for option 'b'\n");
|
error("Must specify exactly 2 characters for option 'b'\n");
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'g':
|
case 'g':
|
||||||
if (strlen(&s[1]) == 4)
|
if (strlen(&s[1]) == 4) {
|
||||||
opt_G(&s[1]);
|
opt_G(&s[1]);
|
||||||
else
|
} else {
|
||||||
error("Must specify exactly 4 characters for option 'g'\n");
|
error("Must specify exactly 4 characters for option 'g'\n");
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'p':
|
case 'p':
|
||||||
@@ -74,12 +74,13 @@ void opt_Parse(char const *s) {
|
|||||||
unsigned int padByte;
|
unsigned int padByte;
|
||||||
|
|
||||||
result = sscanf(&s[1], "%x", &padByte);
|
result = sscanf(&s[1], "%x", &padByte);
|
||||||
if (result != 1)
|
if (result != 1) {
|
||||||
error("Invalid argument for option 'p'\n");
|
error("Invalid argument for option 'p'\n");
|
||||||
else if (padByte > 0xFF)
|
} else if (padByte > 0xFF) {
|
||||||
error("Argument for option 'p' must be between 0 and 0xFF\n");
|
error("Argument for option 'p' must be between 0 and 0xFF\n");
|
||||||
else
|
} else {
|
||||||
opt_P(padByte);
|
opt_P(padByte);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
error("Invalid argument for option 'p'\n");
|
error("Invalid argument for option 'p'\n");
|
||||||
}
|
}
|
||||||
@@ -88,19 +89,21 @@ void opt_Parse(char const *s) {
|
|||||||
char const *precisionArg;
|
char const *precisionArg;
|
||||||
case 'Q':
|
case 'Q':
|
||||||
precisionArg = &s[1];
|
precisionArg = &s[1];
|
||||||
if (precisionArg[0] == '.')
|
if (precisionArg[0] == '.') {
|
||||||
precisionArg++;
|
precisionArg++;
|
||||||
|
}
|
||||||
if (strlen(precisionArg) <= 2) {
|
if (strlen(precisionArg) <= 2) {
|
||||||
int result;
|
int result;
|
||||||
unsigned int precision;
|
unsigned int precision;
|
||||||
|
|
||||||
result = sscanf(precisionArg, "%u", &precision);
|
result = sscanf(precisionArg, "%u", &precision);
|
||||||
if (result != 1)
|
if (result != 1) {
|
||||||
error("Invalid argument for option 'Q'\n");
|
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");
|
error("Argument for option 'Q' must be between 1 and 31\n");
|
||||||
else
|
} else {
|
||||||
opt_Q(precision);
|
opt_Q(precision);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
error("Invalid argument for option 'Q'\n");
|
error("Invalid argument for option 'Q'\n");
|
||||||
}
|
}
|
||||||
@@ -108,8 +111,9 @@ void opt_Parse(char const *s) {
|
|||||||
|
|
||||||
case 'r': {
|
case 'r': {
|
||||||
++s; // Skip 'r'
|
++s; // Skip 'r'
|
||||||
while (isblank(*s))
|
while (isblank(*s)) {
|
||||||
++s; // Skip leading whitespace
|
++s; // Skip leading whitespace
|
||||||
|
}
|
||||||
|
|
||||||
if (s[0] == '\0') {
|
if (s[0] == '\0') {
|
||||||
error("Missing argument to option 'r'\n");
|
error("Missing argument to option 'r'\n");
|
||||||
@@ -130,10 +134,11 @@ void opt_Parse(char const *s) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case 'W':
|
case 'W':
|
||||||
if (strlen(&s[1]) > 0)
|
if (strlen(&s[1]) > 0) {
|
||||||
opt_W(&s[1]);
|
opt_W(&s[1]);
|
||||||
else
|
} else {
|
||||||
error("Must specify an argument for option 'W'\n");
|
error("Must specify an argument for option 'W'\n");
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@@ -160,7 +165,7 @@ void opt_Push() {
|
|||||||
|
|
||||||
// Both of these pulled from warning.hpp
|
// Both of these pulled from warning.hpp
|
||||||
entry.warningsAreErrors = warningsAreErrors;
|
entry.warningsAreErrors = warningsAreErrors;
|
||||||
memcpy(entry.warningStates, warningStates, numWarningStates);
|
entry.warningStates = warningStates;
|
||||||
|
|
||||||
entry.maxRecursionDepth = maxRecursionDepth; // Pulled from fstack.h
|
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
|
// opt_W does not apply a whole warning state; it processes one flag string
|
||||||
warningsAreErrors = entry.warningsAreErrors;
|
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"
|
#include "asm/output.hpp"
|
||||||
|
|
||||||
@@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
#include "error.hpp"
|
#include "error.hpp"
|
||||||
#include "helpers.hpp" // assume, Defer
|
#include "helpers.hpp" // assume, Defer
|
||||||
|
#include "platform.hpp"
|
||||||
|
|
||||||
#include "asm/charmap.hpp"
|
#include "asm/charmap.hpp"
|
||||||
#include "asm/fstack.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) {
|
static void putLong(uint32_t n, FILE *file) {
|
||||||
uint8_t bytes[] = {
|
uint8_t bytes[] = {
|
||||||
(uint8_t)n,
|
static_cast<uint8_t>(n),
|
||||||
(uint8_t)(n >> 8),
|
static_cast<uint8_t>(n >> 8),
|
||||||
(uint8_t)(n >> 16),
|
static_cast<uint8_t>(n >> 16),
|
||||||
(uint8_t)(n >> 24),
|
static_cast<uint8_t>(n >> 24),
|
||||||
};
|
};
|
||||||
fwrite(bytes, 1, sizeof(bytes), file);
|
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) {
|
void out_RegisterNode(std::shared_ptr<FileStackNode> node) {
|
||||||
// If node is not already registered, register it (and parents), and give it a unique ID
|
// 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();
|
node->ID = fileStackNodes.size();
|
||||||
fileStackNodes.push_front(node);
|
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) {
|
static uint32_t getSectIDIfAny(Section *sect) {
|
||||||
if (!sect)
|
if (!sect) {
|
||||||
return (uint32_t)-1;
|
return UINT32_MAX;
|
||||||
|
}
|
||||||
|
|
||||||
if (auto search = sectionMap.find(sect->name); search != sectionMap.end())
|
if (auto search = sectionMap.find(sect->name); search != sectionMap.end()) {
|
||||||
return (uint32_t)(sectionMap.size() - search->second - 1);
|
return static_cast<uint32_t>(search->second);
|
||||||
|
}
|
||||||
|
|
||||||
fatalerror("Unknown section '%s'\n", sect->name.c_str());
|
fatalerror("Unknown section '%s'\n", sect->name.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
static void writePatch(Patch const &patch, FILE *file) {
|
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.src->ID, file);
|
||||||
putLong(patch.lineNo, file);
|
putLong(patch.lineNo, file);
|
||||||
@@ -85,7 +88,7 @@ static void writePatch(Patch const &patch, FILE *file) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void writeSection(Section const §, 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);
|
putString(sect.name, file);
|
||||||
|
|
||||||
@@ -108,9 +111,10 @@ static void writeSection(Section const §, FILE *file) {
|
|||||||
fwrite(sect.data.data(), 1, sect.size, file);
|
fwrite(sect.data.data(), 1, sect.size, file);
|
||||||
putLong(sect.patches.size(), file);
|
putLong(sect.patches.size(), file);
|
||||||
|
|
||||||
for (Patch const &patch : sect.patches)
|
for (Patch const &patch : sect.patches) {
|
||||||
writePatch(patch, file);
|
writePatch(patch, file);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void writeSymbol(Symbol const &sym, FILE *file) {
|
static void writeSymbol(Symbol const &sym, FILE *file) {
|
||||||
@@ -118,7 +122,7 @@ static void writeSymbol(Symbol const &sym, FILE *file) {
|
|||||||
if (!sym.isDefined()) {
|
if (!sym.isDefined()) {
|
||||||
putc(SYMTYPE_IMPORT, file);
|
putc(SYMTYPE_IMPORT, file);
|
||||||
} else {
|
} else {
|
||||||
assume(sym.src->ID != (uint32_t)-1);
|
assume(sym.src->ID != UINT32_MAX);
|
||||||
|
|
||||||
putc(sym.isExported ? SYMTYPE_EXPORT : SYMTYPE_LOCAL, file);
|
putc(sym.isExported ? SYMTYPE_EXPORT : SYMTYPE_LOCAL, file);
|
||||||
putLong(sym.src->ID, file);
|
putLong(sym.src->ID, file);
|
||||||
@@ -130,7 +134,7 @@ static void writeSymbol(Symbol const &sym, FILE *file) {
|
|||||||
|
|
||||||
static void registerUnregisteredSymbol(Symbol &sym) {
|
static void registerUnregisteredSymbol(Symbol &sym) {
|
||||||
// Check for `sym.src`, to skip any built-in symbol from rgbasm
|
// 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
|
sym.ID = objectSymbols.size(); // Set the symbol's ID within the object file
|
||||||
objectSymbols.push_back(&sym);
|
objectSymbols.push_back(&sym);
|
||||||
out_RegisterNode(sym.src);
|
out_RegisterNode(sym.src);
|
||||||
@@ -161,8 +165,9 @@ static void writeRpn(std::vector<uint8_t> &rpnexpr, std::vector<uint8_t> const &
|
|||||||
symName.clear();
|
symName.clear();
|
||||||
for (;;) {
|
for (;;) {
|
||||||
uint8_t c = rpn[offset++];
|
uint8_t c = rpn[offset++];
|
||||||
if (c == 0)
|
if (c == 0) {
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
symName += c;
|
symName += c;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,8 +192,9 @@ static void writeRpn(std::vector<uint8_t> &rpnexpr, std::vector<uint8_t> const &
|
|||||||
symName.clear();
|
symName.clear();
|
||||||
for (;;) {
|
for (;;) {
|
||||||
uint8_t c = rpn[offset++];
|
uint8_t c = rpn[offset++];
|
||||||
if (c == 0)
|
if (c == 0) {
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
symName += c;
|
symName += c;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -281,13 +287,13 @@ void out_CreateAssert(
|
|||||||
assertion.message = message;
|
assertion.message = message;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void writeAssert(Assertion &assert, FILE *file) {
|
static void writeAssert(Assertion const &assert, FILE *file) {
|
||||||
writePatch(assert.patch, file);
|
writePatch(assert.patch, file);
|
||||||
putString(assert.message, file);
|
putString(assert.message, file);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void writeFileStackNode(FileStackNode const &node, FILE *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);
|
putLong(node.lineNo, file);
|
||||||
putc(node.type, file);
|
putc(node.type, file);
|
||||||
if (node.type != NODE_REPT) {
|
if (node.type != NODE_REPT) {
|
||||||
@@ -297,30 +303,34 @@ static void writeFileStackNode(FileStackNode const &node, FILE *file) {
|
|||||||
|
|
||||||
putLong(nodeIters.size(), file);
|
putLong(nodeIters.size(), file);
|
||||||
// Iters are stored by decreasing depth, so reverse the order for output
|
// 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);
|
putLong(nodeIters[i], file);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void out_WriteObject() {
|
void out_WriteObject() {
|
||||||
if (objectFileName.empty())
|
if (objectFileName.empty()) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
FILE *file;
|
FILE *file;
|
||||||
if (objectFileName != "-") {
|
if (objectFileName != "-") {
|
||||||
file = fopen(objectFileName.c_str(), "wb");
|
file = fopen(objectFileName.c_str(), "wb");
|
||||||
} else {
|
} else {
|
||||||
objectFileName = "<stdout>";
|
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());
|
err("Failed to open object file '%s'", objectFileName.c_str());
|
||||||
|
}
|
||||||
Defer closeFile{[&] { fclose(file); }};
|
Defer closeFile{[&] { fclose(file); }};
|
||||||
|
|
||||||
// Also write symbols that weren't written above
|
// Also write symbols that weren't written above
|
||||||
sym_ForEach(registerUnregisteredSymbol);
|
sym_ForEach(registerUnregisteredSymbol);
|
||||||
|
|
||||||
fprintf(file, RGBDS_OBJECT_VERSION_STRING);
|
fputs(RGBDS_OBJECT_VERSION_STRING, file);
|
||||||
putLong(RGBDS_OBJECT_REV, file);
|
putLong(RGBDS_OBJECT_REV, file);
|
||||||
|
|
||||||
putLong(objectSymbols.size(), file);
|
putLong(objectSymbols.size(), file);
|
||||||
@@ -333,7 +343,7 @@ void out_WriteObject() {
|
|||||||
writeFileStackNode(node, file);
|
writeFileStackNode(node, file);
|
||||||
|
|
||||||
// The list is supposed to have decrementing IDs
|
// The list is supposed to have decrementing IDs
|
||||||
if (it + 1 != fileStackNodes.end() && it[1]->ID != node.ID - 1)
|
if (it + 1 != fileStackNodes.end() && it[1]->ID != node.ID - 1) {
|
||||||
fatalerror(
|
fatalerror(
|
||||||
"Internal error: fstack node #%" PRIu32 " follows #%" PRIu32
|
"Internal error: fstack node #%" PRIu32 " follows #%" PRIu32
|
||||||
". Please report this to the developers!\n",
|
". Please report this to the developers!\n",
|
||||||
@@ -341,25 +351,31 @@ void out_WriteObject() {
|
|||||||
node.ID
|
node.ID
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (Symbol const *sym : objectSymbols)
|
for (Symbol const *sym : objectSymbols) {
|
||||||
writeSymbol(*sym, file);
|
writeSymbol(*sym, file);
|
||||||
|
}
|
||||||
|
|
||||||
for (auto it = sectionList.rbegin(); it != sectionList.rend(); it++)
|
for (Section const § : sectionList) {
|
||||||
writeSection(*it, file);
|
writeSection(sect, file);
|
||||||
|
}
|
||||||
|
|
||||||
putLong(assertions.size(), file);
|
putLong(assertions.size(), file);
|
||||||
|
|
||||||
for (Assertion &assert : assertions)
|
for (Assertion const &assert : assertions) {
|
||||||
writeAssert(assert, file);
|
writeAssert(assert, file);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void out_SetFileName(std::string const &name) {
|
void out_SetFileName(std::string const &name) {
|
||||||
if (!objectFileName.empty())
|
if (!objectFileName.empty()) {
|
||||||
warnx("Overriding output filename %s", objectFileName.c_str());
|
warnx("Overriding output filename %s", objectFileName.c_str());
|
||||||
|
}
|
||||||
objectFileName = name;
|
objectFileName = name;
|
||||||
if (verbose)
|
if (verbose) {
|
||||||
printf("Output filename %s\n", objectFileName.c_str());
|
printf("Output filename %s\n", objectFileName.c_str());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void dumpString(std::string const &escape, FILE *file) {
|
static void dumpString(std::string const &escape, FILE *file) {
|
||||||
@@ -395,8 +411,9 @@ static bool dumpEquConstants(FILE *file) {
|
|||||||
equConstants.clear();
|
equConstants.clear();
|
||||||
|
|
||||||
sym_ForEach([](Symbol &sym) {
|
sym_ForEach([](Symbol &sym) {
|
||||||
if (!sym.isBuiltin && sym.type == SYM_EQU)
|
if (!sym.isBuiltin && sym.type == SYM_EQU) {
|
||||||
equConstants.push_back(&sym);
|
equConstants.push_back(&sym);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
// Constants are ordered by file, then by definition order
|
// Constants are ordered by file, then by definition order
|
||||||
std::sort(RANGE(equConstants), [](Symbol *sym1, Symbol *sym2) -> bool {
|
std::sort(RANGE(equConstants), [](Symbol *sym1, Symbol *sym2) -> bool {
|
||||||
@@ -416,8 +433,9 @@ static bool dumpVariables(FILE *file) {
|
|||||||
variables.clear();
|
variables.clear();
|
||||||
|
|
||||||
sym_ForEach([](Symbol &sym) {
|
sym_ForEach([](Symbol &sym) {
|
||||||
if (!sym.isBuiltin && sym.type == SYM_VAR)
|
if (!sym.isBuiltin && sym.type == SYM_VAR) {
|
||||||
variables.push_back(&sym);
|
variables.push_back(&sym);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
// Variables are ordered by file, then by definition order
|
// Variables are ordered by file, then by definition order
|
||||||
std::sort(RANGE(variables), [](Symbol *sym1, Symbol *sym2) -> bool {
|
std::sort(RANGE(variables), [](Symbol *sym1, Symbol *sym2) -> bool {
|
||||||
@@ -437,8 +455,9 @@ static bool dumpEqusConstants(FILE *file) {
|
|||||||
equsConstants.clear();
|
equsConstants.clear();
|
||||||
|
|
||||||
sym_ForEach([](Symbol &sym) {
|
sym_ForEach([](Symbol &sym) {
|
||||||
if (!sym.isBuiltin && sym.type == SYM_EQUS)
|
if (!sym.isBuiltin && sym.type == SYM_EQUS) {
|
||||||
equsConstants.push_back(&sym);
|
equsConstants.push_back(&sym);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
// Constants are ordered by file, then by definition order
|
// Constants are ordered by file, then by definition order
|
||||||
std::sort(RANGE(equsConstants), [](Symbol *sym1, Symbol *sym2) -> bool {
|
std::sort(RANGE(equsConstants), [](Symbol *sym1, Symbol *sym2) -> bool {
|
||||||
@@ -465,8 +484,9 @@ static bool dumpCharmaps(FILE *file) {
|
|||||||
fputs("charmap \"", charmapFile);
|
fputs("charmap \"", charmapFile);
|
||||||
dumpString(mapping, charmapFile);
|
dumpString(mapping, charmapFile);
|
||||||
putc('"', charmapFile);
|
putc('"', charmapFile);
|
||||||
for (int32_t v : value)
|
for (int32_t v : value) {
|
||||||
fprintf(charmapFile, ", $%" PRIx32, v);
|
fprintf(charmapFile, ", $%" PRIx32, v);
|
||||||
|
}
|
||||||
putc('\n', charmapFile);
|
putc('\n', charmapFile);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -477,8 +497,9 @@ static bool dumpMacros(FILE *file) {
|
|||||||
macros.clear();
|
macros.clear();
|
||||||
|
|
||||||
sym_ForEach([](Symbol &sym) {
|
sym_ForEach([](Symbol &sym) {
|
||||||
if (!sym.isBuiltin && sym.type == SYM_MACRO)
|
if (!sym.isBuiltin && sym.type == SYM_MACRO) {
|
||||||
macros.push_back(&sym);
|
macros.push_back(&sym);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
// Macros are ordered by file, then by definition order
|
// Macros are ordered by file, then by definition order
|
||||||
std::sort(RANGE(macros), [](Symbol *sym1, Symbol *sym2) -> bool {
|
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");
|
file = fopen(name.c_str(), "wb");
|
||||||
} else {
|
} else {
|
||||||
name = "<stdout>";
|
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());
|
err("Failed to open state file '%s'", name.c_str());
|
||||||
|
}
|
||||||
Defer closeFile{[&] { fclose(file); }};
|
Defer closeFile{[&] { fclose(file); }};
|
||||||
|
|
||||||
static char const *dumpHeadings[NB_STATE_FEATURES] = {
|
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);
|
fputs("; File generated by rgbasm\n", file);
|
||||||
for (StateFeature feature : features) {
|
for (StateFeature feature : features) {
|
||||||
fprintf(file, "\n; %s\n", dumpHeadings[feature]);
|
fprintf(file, "\n; %s\n", dumpHeadings[feature]);
|
||||||
if (!dumpFuncs[feature](file))
|
if (!dumpFuncs[feature](file)) {
|
||||||
fprintf(file, "; No values\n");
|
fprintf(file, "; No values\n");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
825
src/asm/parser.y
825
src/asm/parser.y
File diff suppressed because it is too large
Load Diff
164
src/asm/rpn.cpp
164
src/asm/rpn.cpp
@@ -1,4 +1,4 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#include "asm/rpn.hpp"
|
#include "asm/rpn.hpp"
|
||||||
|
|
||||||
@@ -46,17 +46,19 @@ int32_t Expression::getConstVal() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Symbol const *Expression::symbolOf() const {
|
Symbol const *Expression::symbolOf() const {
|
||||||
if (!isSymbol)
|
if (!isSymbol) {
|
||||||
return nullptr;
|
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 {
|
bool Expression::isDiffConstant(Symbol const *sym) const {
|
||||||
// Check if both expressions only refer to a single symbol
|
// Check if both expressions only refer to a single symbol
|
||||||
Symbol const *sym1 = symbolOf();
|
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;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
Section const *sect1 = sym1->getSection();
|
Section const *sect1 = sym1->getSection();
|
||||||
Section const *sect2 = sym->getSection();
|
Section const *sect2 = sym->getSection();
|
||||||
@@ -65,7 +67,7 @@ bool Expression::isDiffConstant(Symbol const *sym) const {
|
|||||||
|
|
||||||
void Expression::makeNumber(uint32_t value) {
|
void Expression::makeNumber(uint32_t value) {
|
||||||
clear();
|
clear();
|
||||||
data = (int32_t)value;
|
data = static_cast<int32_t>(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Expression::makeSymbol(std::string const &symName) {
|
void Expression::makeSymbol(std::string const &symName) {
|
||||||
@@ -89,7 +91,7 @@ void Expression::makeSymbol(std::string const &symName) {
|
|||||||
*ptr++ = RPN_SYM;
|
*ptr++ = RPN_SYM;
|
||||||
memcpy(ptr, sym->name.c_str(), nameLen);
|
memcpy(ptr, sym->name.c_str(), nameLen);
|
||||||
} else {
|
} else {
|
||||||
data = (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) {
|
if (!currentSection) {
|
||||||
error("PC has no bank outside of a section\n");
|
error("PC has no bank outside of a section\n");
|
||||||
data = 1;
|
data = 1;
|
||||||
} else if (currentSection->bank == (uint32_t)-1) {
|
} else if (currentSection->bank == UINT32_MAX) {
|
||||||
data = "Current section's bank is not known";
|
data = "Current section's bank is not known";
|
||||||
|
|
||||||
*reserveSpace(1) = RPN_BANK_SELF;
|
*reserveSpace(1) = RPN_BANK_SELF;
|
||||||
} else {
|
} else {
|
||||||
data = (int32_t)currentSection->bank;
|
data = static_cast<int32_t>(currentSection->bank);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
} else if (sym && !sym->isLabel()) {
|
} else if (sym && !sym->isLabel()) {
|
||||||
@@ -115,9 +117,9 @@ void Expression::makeBankSymbol(std::string const &symName) {
|
|||||||
sym = sym_Ref(symName);
|
sym = sym_Ref(symName);
|
||||||
assume(sym); // If the symbol didn't exist, it should have been created
|
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
|
// Symbol's section is known and bank is fixed
|
||||||
data = (int32_t)sym->getSection()->bank;
|
data = static_cast<int32_t>(sym->getSection()->bank);
|
||||||
} else {
|
} else {
|
||||||
data = sym_IsPurgedScoped(symName)
|
data = sym_IsPurgedScoped(symName)
|
||||||
? "\""s + symName + "\"'s bank is not known; it was purged"
|
? "\""s + symName + "\"'s bank is not known; it was purged"
|
||||||
@@ -135,8 +137,8 @@ void Expression::makeBankSymbol(std::string const &symName) {
|
|||||||
|
|
||||||
void Expression::makeBankSection(std::string const §Name) {
|
void Expression::makeBankSection(std::string const §Name) {
|
||||||
clear();
|
clear();
|
||||||
if (Section *sect = sect_FindSectionByName(sectName); sect && sect->bank != (uint32_t)-1) {
|
if (Section *sect = sect_FindSectionByName(sectName); sect && sect->bank != UINT32_MAX) {
|
||||||
data = (int32_t)sect->bank;
|
data = static_cast<int32_t>(sect->bank);
|
||||||
} else {
|
} else {
|
||||||
data = "Section \""s + sectName + "\"'s bank is not known";
|
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) {
|
void Expression::makeSizeOfSection(std::string const §Name) {
|
||||||
clear();
|
clear();
|
||||||
if (Section *sect = sect_FindSectionByName(sectName); sect && sect->isSizeKnown()) {
|
if (Section *sect = sect_FindSectionByName(sectName); sect && sect->isSizeKnown()) {
|
||||||
data = (int32_t)sect->size;
|
data = static_cast<int32_t>(sect->size);
|
||||||
} else {
|
} else {
|
||||||
data = "Section \""s + sectName + "\"'s size is not known";
|
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) {
|
void Expression::makeStartOfSection(std::string const §Name) {
|
||||||
clear();
|
clear();
|
||||||
if (Section *sect = sect_FindSectionByName(sectName); sect && sect->org != (uint32_t)-1) {
|
if (Section *sect = sect_FindSectionByName(sectName); sect && sect->org != UINT32_MAX) {
|
||||||
data = (int32_t)sect->org;
|
data = static_cast<int32_t>(sect->org);
|
||||||
} else {
|
} else {
|
||||||
data = "Section \""s + sectName + "\"'s start is not known";
|
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) {
|
static bool tryConstLogNot(Expression const &expr) {
|
||||||
Symbol const *sym = expr.symbolOf();
|
Symbol const *sym = expr.symbolOf();
|
||||||
if (!sym || !sym->getSection() || !sym->isDefined())
|
if (!sym || !sym->getSection() || !sym->isDefined()) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
assume(sym->isNumeric());
|
assume(sym->isNumeric());
|
||||||
|
|
||||||
Section const § = *sym->getSection();
|
Section const § = *sym->getSection();
|
||||||
int32_t unknownBits = (1 << 16) - (1 << sect.align);
|
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)
|
// 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 symbolOfs = sym->getValue() + 1;
|
||||||
|
|
||||||
int32_t knownBits = (symbolOfs + sect.alignOfs) & ~unknownBits;
|
int32_t knownBits = (symbolOfs + sect.alignOfs) & ~unknownBits;
|
||||||
return knownBits != 0;
|
return knownBits != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Returns a constant LOW() from non-constant argument, or -1 if it cannot be computed.
|
||||||
* 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.
|
||||||
* This is possible if the argument is a symbol belonging to an `ALIGN[8]` section.
|
|
||||||
*
|
|
||||||
* @return The constant `LOW(expr)` result if it can be computed, or -1 otherwise.
|
|
||||||
*/
|
|
||||||
static int32_t tryConstLow(Expression const &expr) {
|
static int32_t tryConstLow(Expression const &expr) {
|
||||||
Symbol const *sym = expr.symbolOf();
|
Symbol const *sym = expr.symbolOf();
|
||||||
if (!sym || !sym->getSection() || !sym->isDefined())
|
if (!sym || !sym->getSection() || !sym->isDefined()) {
|
||||||
return -1;
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
assume(sym->isNumeric());
|
assume(sym->isNumeric());
|
||||||
|
|
||||||
// The low byte must not cover any unknown bits
|
// The low byte must not cover any unknown bits
|
||||||
Section const § = *sym->getSection();
|
Section const § = *sym->getSection();
|
||||||
if (sect.align < 8)
|
if (sect.align < 8) {
|
||||||
return -1;
|
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)
|
// 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 symbolOfs = sym->getValue() + 1;
|
||||||
|
|
||||||
return (symbolOfs + sect.alignOfs) & 0xFF;
|
return (symbolOfs + sect.alignOfs) & 0xFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Returns a constant binary AND with one non-constant operand, or -1 if it cannot be computed.
|
||||||
* 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
|
||||||
* 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.
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
static int32_t tryConstMask(Expression const &lhs, Expression const &rhs) {
|
static int32_t tryConstMask(Expression const &lhs, Expression const &rhs) {
|
||||||
Symbol const *lhsSymbol = lhs.symbolOf();
|
Symbol const *lhsSymbol = lhs.symbolOf();
|
||||||
Symbol const *rhsSymbol = lhsSymbol ? nullptr : rhs.symbolOf();
|
Symbol const *rhsSymbol = lhsSymbol ? nullptr : rhs.symbolOf();
|
||||||
bool lhsIsSymbol = lhsSymbol && lhsSymbol->getSection();
|
bool lhsIsSymbol = lhsSymbol && lhsSymbol->getSection();
|
||||||
bool rhsIsSymbol = rhsSymbol && rhsSymbol->getSection();
|
bool rhsIsSymbol = rhsSymbol && rhsSymbol->getSection();
|
||||||
|
|
||||||
if (!lhsIsSymbol && !rhsIsSymbol)
|
if (!lhsIsSymbol && !rhsIsSymbol) {
|
||||||
return -1;
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
// If the lhs isn't a symbol, try again the other way around
|
// If the lhs isn't a symbol, try again the other way around
|
||||||
Symbol const &sym = lhsIsSymbol ? *lhsSymbol : *rhsSymbol;
|
Symbol const &sym = lhsIsSymbol ? *lhsSymbol : *rhsSymbol;
|
||||||
Expression const &expr = lhsIsSymbol ? rhs : lhs; // Opposite side of `sym`
|
Expression const &expr = lhsIsSymbol ? rhs : lhs; // Opposite side of `sym`
|
||||||
|
|
||||||
if (!sym.isDefined() || !expr.isKnown())
|
if (!sym.isDefined() || !expr.isKnown()) {
|
||||||
return -1;
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
assume(sym.isNumeric());
|
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
|
// The mask must not cover any unknown bits
|
||||||
Section const § = *sym.getSection();
|
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;
|
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)
|
// 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 symbolOfs = sym.getValue() + 1;
|
||||||
|
|
||||||
return (symbolOfs + sect.alignOfs) & mask;
|
return (symbolOfs + sect.alignOfs) & mask;
|
||||||
@@ -298,10 +298,11 @@ void Expression::makeUnaryOp(RPNCommand op, Expression &&src) {
|
|||||||
if (src.isKnown()) {
|
if (src.isKnown()) {
|
||||||
// If the expressions is known, just compute the value
|
// If the expressions is known, just compute the value
|
||||||
int32_t val = src.value();
|
int32_t val = src.value();
|
||||||
|
uint32_t uval = static_cast<uint32_t>(val);
|
||||||
|
|
||||||
switch (op) {
|
switch (op) {
|
||||||
case RPN_NEG:
|
case RPN_NEG:
|
||||||
data = (int32_t) - (uint32_t)val;
|
data = static_cast<int32_t>(-uval);
|
||||||
break;
|
break;
|
||||||
case RPN_NOT:
|
case RPN_NOT:
|
||||||
data = ~val;
|
data = ~val;
|
||||||
@@ -310,16 +311,16 @@ void Expression::makeUnaryOp(RPNCommand op, Expression &&src) {
|
|||||||
data = !val;
|
data = !val;
|
||||||
break;
|
break;
|
||||||
case RPN_HIGH:
|
case RPN_HIGH:
|
||||||
data = (int32_t)((uint32_t)val >> 8 & 0xFF);
|
data = static_cast<int32_t>(uval >> 8 & 0xFF);
|
||||||
break;
|
break;
|
||||||
case RPN_LOW:
|
case RPN_LOW:
|
||||||
data = val & 0xFF;
|
data = val & 0xFF;
|
||||||
break;
|
break;
|
||||||
case RPN_BITWIDTH:
|
case RPN_BITWIDTH:
|
||||||
data = val != 0 ? 32 - clz((uint32_t)val) : 0;
|
data = val != 0 ? 32 - clz(uval) : 0;
|
||||||
break;
|
break;
|
||||||
case RPN_TZCOUNT:
|
case RPN_TZCOUNT:
|
||||||
data = val != 0 ? ctz((uint32_t)val) : 32;
|
data = val != 0 ? ctz(uval) : 32;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case RPN_LOGOR:
|
case RPN_LOGOR:
|
||||||
@@ -374,6 +375,7 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const
|
|||||||
if (src1.isKnown() && src2.isKnown()) {
|
if (src1.isKnown() && src2.isKnown()) {
|
||||||
// If both expressions are known, just compute the value
|
// If both expressions are known, just compute the value
|
||||||
int32_t lval = src1.value(), rval = src2.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) {
|
switch (op) {
|
||||||
case RPN_LOGOR:
|
case RPN_LOGOR:
|
||||||
@@ -401,10 +403,10 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const
|
|||||||
data = lval != rval;
|
data = lval != rval;
|
||||||
break;
|
break;
|
||||||
case RPN_ADD:
|
case RPN_ADD:
|
||||||
data = (int32_t)((uint32_t)lval + (uint32_t)rval);
|
data = static_cast<int32_t>(ulval + urval);
|
||||||
break;
|
break;
|
||||||
case RPN_SUB:
|
case RPN_SUB:
|
||||||
data = (int32_t)((uint32_t)lval - (uint32_t)rval);
|
data = static_cast<int32_t>(ulval - urval);
|
||||||
break;
|
break;
|
||||||
case RPN_XOR:
|
case RPN_XOR:
|
||||||
data = lval ^ rval;
|
data = lval ^ rval;
|
||||||
@@ -416,47 +418,55 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const
|
|||||||
data = lval & rval;
|
data = lval & rval;
|
||||||
break;
|
break;
|
||||||
case RPN_SHL:
|
case RPN_SHL:
|
||||||
if (rval < 0)
|
if (rval < 0) {
|
||||||
warning(
|
warning(
|
||||||
WARNING_SHIFT_AMOUNT, "Shifting left by negative amount %" PRId32 "\n", rval
|
WARNING_SHIFT_AMOUNT, "Shifting left by negative amount %" PRId32 "\n", rval
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (rval >= 32)
|
if (rval >= 32) {
|
||||||
warning(WARNING_SHIFT_AMOUNT, "Shifting left by large amount %" PRId32 "\n", rval);
|
warning(WARNING_SHIFT_AMOUNT, "Shifting left by large amount %" PRId32 "\n", rval);
|
||||||
|
}
|
||||||
|
|
||||||
data = op_shift_left(lval, rval);
|
data = op_shift_left(lval, rval);
|
||||||
break;
|
break;
|
||||||
case RPN_SHR:
|
case RPN_SHR:
|
||||||
if (lval < 0)
|
if (lval < 0) {
|
||||||
warning(WARNING_SHIFT, "Shifting right negative value %" PRId32 "\n", lval);
|
warning(WARNING_SHIFT, "Shifting right negative value %" PRId32 "\n", lval);
|
||||||
|
}
|
||||||
|
|
||||||
if (rval < 0)
|
if (rval < 0) {
|
||||||
warning(
|
warning(
|
||||||
WARNING_SHIFT_AMOUNT, "Shifting right by negative amount %" PRId32 "\n", rval
|
WARNING_SHIFT_AMOUNT, "Shifting right by negative amount %" PRId32 "\n", rval
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (rval >= 32)
|
if (rval >= 32) {
|
||||||
warning(WARNING_SHIFT_AMOUNT, "Shifting right by large amount %" PRId32 "\n", rval);
|
warning(WARNING_SHIFT_AMOUNT, "Shifting right by large amount %" PRId32 "\n", rval);
|
||||||
|
}
|
||||||
|
|
||||||
data = op_shift_right(lval, rval);
|
data = op_shift_right(lval, rval);
|
||||||
break;
|
break;
|
||||||
case RPN_USHR:
|
case RPN_USHR:
|
||||||
if (rval < 0)
|
if (rval < 0) {
|
||||||
warning(
|
warning(
|
||||||
WARNING_SHIFT_AMOUNT, "Shifting right by negative amount %" PRId32 "\n", rval
|
WARNING_SHIFT_AMOUNT, "Shifting right by negative amount %" PRId32 "\n", rval
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (rval >= 32)
|
if (rval >= 32) {
|
||||||
warning(WARNING_SHIFT_AMOUNT, "Shifting right by large amount %" PRId32 "\n", rval);
|
warning(WARNING_SHIFT_AMOUNT, "Shifting right by large amount %" PRId32 "\n", rval);
|
||||||
|
}
|
||||||
|
|
||||||
data = op_shift_right_unsigned(lval, rval);
|
data = op_shift_right_unsigned(lval, rval);
|
||||||
break;
|
break;
|
||||||
case RPN_MUL:
|
case RPN_MUL:
|
||||||
data = (int32_t)((uint32_t)lval * (uint32_t)rval);
|
data = static_cast<int32_t>(ulval * urval);
|
||||||
break;
|
break;
|
||||||
case RPN_DIV:
|
case RPN_DIV:
|
||||||
if (rval == 0)
|
if (rval == 0) {
|
||||||
fatalerror("Division by zero\n");
|
fatalerror("Division by zero\n");
|
||||||
|
}
|
||||||
|
|
||||||
if (lval == INT32_MIN && rval == -1) {
|
if (lval == INT32_MIN && rval == -1) {
|
||||||
warning(
|
warning(
|
||||||
@@ -471,17 +481,20 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case RPN_MOD:
|
case RPN_MOD:
|
||||||
if (rval == 0)
|
if (rval == 0) {
|
||||||
fatalerror("Modulo by zero\n");
|
fatalerror("Modulo by zero\n");
|
||||||
|
}
|
||||||
|
|
||||||
if (lval == INT32_MIN && rval == -1)
|
if (lval == INT32_MIN && rval == -1) {
|
||||||
data = 0;
|
data = 0;
|
||||||
else
|
} else {
|
||||||
data = op_modulo(lval, rval);
|
data = op_modulo(lval, rval);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case RPN_EXP:
|
case RPN_EXP:
|
||||||
if (rval < 0)
|
if (rval < 0) {
|
||||||
fatalerror("Exponentiation by negative power\n");
|
fatalerror("Exponentiation by negative power\n");
|
||||||
|
}
|
||||||
|
|
||||||
data = op_exponent(lval, rval);
|
data = op_exponent(lval, rval);
|
||||||
break;
|
break;
|
||||||
@@ -522,10 +535,10 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const
|
|||||||
uint32_t lval = src1.value();
|
uint32_t lval = src1.value();
|
||||||
uint8_t bytes[] = {
|
uint8_t bytes[] = {
|
||||||
RPN_CONST,
|
RPN_CONST,
|
||||||
(uint8_t)lval,
|
static_cast<uint8_t>(lval),
|
||||||
(uint8_t)(lval >> 8),
|
static_cast<uint8_t>(lval >> 8),
|
||||||
(uint8_t)(lval >> 16),
|
static_cast<uint8_t>(lval >> 16),
|
||||||
(uint8_t)(lval >> 24),
|
static_cast<uint8_t>(lval >> 24),
|
||||||
};
|
};
|
||||||
rpn.clear();
|
rpn.clear();
|
||||||
rpnPatchSize = 0;
|
rpnPatchSize = 0;
|
||||||
@@ -546,10 +559,10 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const
|
|||||||
uint32_t rval = src2.value();
|
uint32_t rval = src2.value();
|
||||||
uint8_t bytes[] = {
|
uint8_t bytes[] = {
|
||||||
RPN_CONST,
|
RPN_CONST,
|
||||||
(uint8_t)rval,
|
static_cast<uint8_t>(rval),
|
||||||
(uint8_t)(rval >> 8),
|
static_cast<uint8_t>(rval >> 8),
|
||||||
(uint8_t)(rval >> 16),
|
static_cast<uint8_t>(rval >> 16),
|
||||||
(uint8_t)(rval >> 24),
|
static_cast<uint8_t>(rval >> 24),
|
||||||
};
|
};
|
||||||
uint8_t *ptr = reserveSpace(sizeof(bytes) + 1, sizeof(bytes) + 1);
|
uint8_t *ptr = reserveSpace(sizeof(bytes) + 1, sizeof(bytes) + 1);
|
||||||
memcpy(ptr, bytes, sizeof(bytes));
|
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
|
// Copy the right RPN and append the operator
|
||||||
uint32_t rightRpnSize = src2.rpn.size();
|
uint32_t rightRpnSize = src2.rpn.size();
|
||||||
uint8_t *ptr = reserveSpace(rightRpnSize + 1, src2.rpnPatchSize + 1);
|
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
|
// If `rightRpnSize == 0`, then `memcpy(ptr, nullptr, rightRpnSize)` would be UB
|
||||||
memcpy(ptr, src2.rpn.data(), rightRpnSize);
|
memcpy(ptr, src2.rpn.data(), rightRpnSize);
|
||||||
|
}
|
||||||
ptr[rightRpnSize] = op;
|
ptr[rightRpnSize] = op;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Expression::makeCheckHRAM() {
|
bool Expression::makeCheckHRAM() {
|
||||||
isSymbol = false;
|
isSymbol = false;
|
||||||
if (!isKnown()) {
|
if (!isKnown()) {
|
||||||
*reserveSpace(1) = RPN_HRAM;
|
*reserveSpace(1) = RPN_HRAM;
|
||||||
} else if (int32_t val = value(); val >= 0xFF00 && val <= 0xFFFF) {
|
} else if (int32_t val = value(); val >= 0xFF00 && val <= 0xFFFF) {
|
||||||
// That range is valid, but only keep the lower byte
|
// That range is valid, but only keep the lower byte
|
||||||
data = val & 0xFF;
|
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);
|
error("Source address $%" PRIx32 " not between $FF00 to $FFFF\n", val);
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Expression::makeCheckRST() {
|
void Expression::makeCheckRST() {
|
||||||
@@ -584,16 +602,14 @@ void Expression::makeCheckRST() {
|
|||||||
} else if (int32_t val = value(); val & ~0x38) {
|
} else if (int32_t val = value(); val & ~0x38) {
|
||||||
// A valid RST address must be masked with 0x38
|
// A valid RST address must be masked with 0x38
|
||||||
error("Invalid address $%" PRIx32 " for RST\n", val);
|
error("Invalid address $%" PRIx32 " for RST\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)
|
// Checks that an RPN expression's value fits within N bits (signed or unsigned)
|
||||||
void Expression::checkNBit(uint8_t n) const {
|
void Expression::checkNBit(uint8_t n) const {
|
||||||
if (isKnown())
|
if (isKnown()) {
|
||||||
::checkNBit(value(), n, "Expression");
|
::checkNBit(value(), n, "Expression");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool checkNBit(int32_t v, uint8_t n, char const *name) {
|
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"
|
#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)
|
int32_t loadOffset; // Offset into the LOAD section's parent (see sect_GetOutputOffset)
|
||||||
|
|
||||||
// A quick check to see if we have an initialized section
|
// A quick check to see if we have an initialized section
|
||||||
[[nodiscard]] static bool requireSection() {
|
[[nodiscard]]
|
||||||
if (currentSection)
|
static bool requireSection() {
|
||||||
|
if (currentSection) {
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
error("Cannot output data outside of a SECTION\n");
|
error("Cannot output data outside of a SECTION\n");
|
||||||
return false;
|
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
|
// A quick check to see if we have an initialized section that can contain
|
||||||
// this much initialized data
|
// this much initialized data
|
||||||
[[nodiscard]] static bool requireCodeSection() {
|
[[nodiscard]]
|
||||||
if (!requireSection())
|
static bool requireCodeSection() {
|
||||||
|
if (!requireSection()) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (sect_HasData(currentSection->type))
|
if (sect_HasData(currentSection->type)) {
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
error(
|
error(
|
||||||
"Section '%s' cannot contain code or data (not ROM0 or ROMX)\n",
|
"Section '%s' cannot contain code or data (not ROM0 or ROMX)\n",
|
||||||
@@ -75,15 +80,16 @@ int32_t loadOffset; // Offset into the LOAD section's parent (see sect_GetOutput
|
|||||||
|
|
||||||
void sect_CheckSizes() {
|
void sect_CheckSizes() {
|
||||||
for (Section const § : sectionList) {
|
for (Section const § : sectionList) {
|
||||||
if (uint32_t maxSize = sectionTypeInfo[sect.type].size; sect.size > maxSize)
|
if (uint32_t maxSize = sectionTypeInfo[sect.type].size; sect.size > maxSize) {
|
||||||
error(
|
error(
|
||||||
"Section '%s' grew too big (max size = 0x%" PRIX32 " bytes, reached 0x%" PRIX32
|
"Section '%s' grew too big (max size = 0x%" PRIX32 " bytes, reached 0x%" PRIX32
|
||||||
").\n",
|
")\n",
|
||||||
sect.name.c_str(),
|
sect.name.c_str(),
|
||||||
maxSize,
|
maxSize,
|
||||||
sect.size
|
sect.size
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Section *sect_FindSectionByName(std::string const &name) {
|
Section *sect_FindSectionByName(std::string const &name) {
|
||||||
@@ -106,33 +112,36 @@ static unsigned int mergeSectUnion(
|
|||||||
|
|
||||||
// Unionized sections only need "compatible" constraints, and they end up with the strictest
|
// Unionized sections only need "compatible" constraints, and they end up with the strictest
|
||||||
// combination of both.
|
// combination of both.
|
||||||
if (sect_HasData(type))
|
if (sect_HasData(type)) {
|
||||||
sectError("Cannot declare ROM sections as UNION\n");
|
sectError("Cannot declare ROM sections as UNION\n");
|
||||||
|
}
|
||||||
|
|
||||||
if (org != (uint32_t)-1) {
|
if (org != UINT32_MAX) {
|
||||||
// If both are fixed, they must be the same
|
// If both are fixed, they must be the same
|
||||||
if (sect.org != (uint32_t)-1 && sect.org != org)
|
if (sect.org != UINT32_MAX && sect.org != org) {
|
||||||
sectError(
|
sectError(
|
||||||
"Section already declared as fixed at different address $%04" PRIx32 "\n", sect.org
|
"Section already declared as fixed at different address $%04" PRIx32 "\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(
|
sectError(
|
||||||
"Section already declared as aligned to %u bytes (offset %" PRIu16 ")\n",
|
"Section already declared as aligned to %u bytes (offset %" PRIu16 ")\n",
|
||||||
1U << sect.align,
|
1U << sect.align,
|
||||||
sect.alignOfs
|
sect.alignOfs
|
||||||
);
|
);
|
||||||
else
|
} else {
|
||||||
// Otherwise, just override
|
// Otherwise, just override
|
||||||
sect.org = org;
|
sect.org = org;
|
||||||
|
}
|
||||||
|
|
||||||
} else if (alignment != 0) {
|
} else if (alignment != 0) {
|
||||||
// Make sure any fixed address given is compatible
|
// Make sure any fixed address given is compatible
|
||||||
if (sect.org != (uint32_t)-1) {
|
if (sect.org != UINT32_MAX) {
|
||||||
if ((sect.org - alignOffset) & mask(alignment))
|
if ((sect.org - alignOffset) & mask(alignment)) {
|
||||||
sectError(
|
sectError(
|
||||||
"Section already declared as fixed at incompatible address $%04" PRIx32 "\n",
|
"Section already declared as fixed at incompatible address $%04" PRIx32 "\n",
|
||||||
sect.org
|
sect.org
|
||||||
);
|
);
|
||||||
|
}
|
||||||
// Check if alignment offsets are compatible
|
// Check if alignment offsets are compatible
|
||||||
} else if ((alignOffset & mask(sect.align)) != (sect.alignOfs & mask(alignment))) {
|
} else if ((alignOffset & mask(sect.align)) != (sect.alignOfs & mask(alignment))) {
|
||||||
sectError(
|
sectError(
|
||||||
@@ -159,38 +168,41 @@ static unsigned int
|
|||||||
// Fragments only need "compatible" constraints, and they end up with the strictest
|
// Fragments only need "compatible" constraints, and they end up with the strictest
|
||||||
// combination of both.
|
// combination of both.
|
||||||
// The merging is however performed at the *end* of the original section!
|
// 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;
|
uint16_t curOrg = org - sect.size;
|
||||||
|
|
||||||
// If both are fixed, they must be the same
|
// 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(
|
sectError(
|
||||||
"Section already declared as fixed at incompatible address $%04" PRIx32 "\n",
|
"Section already declared as fixed at incompatible address $%04" PRIx32 "\n",
|
||||||
sect.org
|
sect.org
|
||||||
);
|
);
|
||||||
else if (sect.align != 0 && (mask(sect.align) & (curOrg - sect.alignOfs)))
|
} else if (sect.align != 0 && (mask(sect.align) & (curOrg - sect.alignOfs))) {
|
||||||
sectError(
|
sectError(
|
||||||
"Section already declared as aligned to %u bytes (offset %" PRIu16 ")\n",
|
"Section already declared as aligned to %u bytes (offset %" PRIu16 ")\n",
|
||||||
1U << sect.align,
|
1U << sect.align,
|
||||||
sect.alignOfs
|
sect.alignOfs
|
||||||
);
|
);
|
||||||
else
|
} else {
|
||||||
// Otherwise, just override
|
// Otherwise, just override
|
||||||
sect.org = curOrg;
|
sect.org = curOrg;
|
||||||
|
}
|
||||||
|
|
||||||
} else if (alignment != 0) {
|
} else if (alignment != 0) {
|
||||||
int32_t curOfs = (alignOffset - sect.size) % (1U << alignment);
|
int32_t curOfs = (alignOffset - sect.size) % (1U << alignment);
|
||||||
|
|
||||||
if (curOfs < 0)
|
if (curOfs < 0) {
|
||||||
curOfs += 1U << alignment;
|
curOfs += 1U << alignment;
|
||||||
|
}
|
||||||
|
|
||||||
// Make sure any fixed address given is compatible
|
// Make sure any fixed address given is compatible
|
||||||
if (sect.org != (uint32_t)-1) {
|
if (sect.org != UINT32_MAX) {
|
||||||
if ((sect.org - curOfs) & mask(alignment))
|
if ((sect.org - curOfs) & mask(alignment)) {
|
||||||
sectError(
|
sectError(
|
||||||
"Section already declared as fixed at incompatible address $%04" PRIx32 "\n",
|
"Section already declared as fixed at incompatible address $%04" PRIx32 "\n",
|
||||||
sect.org
|
sect.org
|
||||||
);
|
);
|
||||||
|
}
|
||||||
// Check if alignment offsets are compatible
|
// Check if alignment offsets are compatible
|
||||||
} else if ((curOfs & mask(sect.align)) != (sect.alignOfs & mask(alignment))) {
|
} else if ((curOfs & mask(sect.align)) != (sect.alignOfs & mask(alignment))) {
|
||||||
sectError(
|
sectError(
|
||||||
@@ -220,13 +232,14 @@ static void mergeSections(
|
|||||||
) {
|
) {
|
||||||
unsigned int nbSectErrors = 0;
|
unsigned int nbSectErrors = 0;
|
||||||
|
|
||||||
if (type != sect.type)
|
if (type != sect.type) {
|
||||||
sectError(
|
sectError(
|
||||||
"Section already exists but with type %s\n", sectionTypeInfo[sect.type].name.c_str()
|
"Section already exists but with type %s\n", sectionTypeInfo[sect.type].name.c_str()
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (sect.modifier != mod) {
|
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 {
|
} else {
|
||||||
switch (mod) {
|
switch (mod) {
|
||||||
case SECTION_UNION:
|
case SECTION_UNION:
|
||||||
@@ -238,11 +251,13 @@ static void mergeSections(
|
|||||||
// Common checks
|
// Common checks
|
||||||
|
|
||||||
// If the section's bank is unspecified, override it
|
// If the section's bank is unspecified, override it
|
||||||
if (sect.bank == (uint32_t)-1)
|
if (sect.bank == UINT32_MAX) {
|
||||||
sect.bank = bank;
|
sect.bank = bank;
|
||||||
|
}
|
||||||
// If both specify a bank, it must be the same one
|
// If both specify a bank, it must be the same one
|
||||||
else if (bank != (uint32_t)-1 && sect.bank != bank)
|
else if (bank != UINT32_MAX && sect.bank != bank) {
|
||||||
sectError("Section already declared with different bank %" PRIu32 "\n", sect.bank);
|
sectError("Section already declared with different bank %" PRIu32 "\n", sect.bank);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SECTION_NORMAL:
|
case SECTION_NORMAL:
|
||||||
@@ -253,13 +268,14 @@ static void mergeSections(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nbSectErrors)
|
if (nbSectErrors) {
|
||||||
fatalerror(
|
fatalerror(
|
||||||
"Cannot create section \"%s\" (%u error%s)\n",
|
"Cannot create section \"%s\" (%u error%s)\n",
|
||||||
sect.name.c_str(),
|
sect.name.c_str(),
|
||||||
nbSectErrors,
|
nbSectErrors,
|
||||||
nbSectErrors == 1 ? "" : "s"
|
nbSectErrors == 1 ? "" : "s"
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#undef sectError
|
#undef sectError
|
||||||
@@ -292,8 +308,9 @@ static Section *createSection(
|
|||||||
out_RegisterNode(sect.src);
|
out_RegisterNode(sect.src);
|
||||||
|
|
||||||
// It is only needed to allocate memory for ROM sections.
|
// It is only needed to allocate memory for ROM sections.
|
||||||
if (sect_HasData(type))
|
if (sect_HasData(type)) {
|
||||||
sect.data.resize(sectionTypeInfo[type].size);
|
sect.data.resize(sectionTypeInfo[type].size);
|
||||||
|
}
|
||||||
|
|
||||||
return §
|
return §
|
||||||
}
|
}
|
||||||
@@ -312,11 +329,12 @@ static Section *getSection(
|
|||||||
|
|
||||||
// First, validate parameters, and normalize them if applicable
|
// 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
|
if (type != SECTTYPE_ROMX && type != SECTTYPE_VRAM && type != SECTTYPE_SRAM
|
||||||
&& type != SECTTYPE_WRAMX)
|
&& type != SECTTYPE_WRAMX) {
|
||||||
error("BANK only allowed for ROMX, WRAMX, SRAM, or VRAM sections\n");
|
error("BANK only allowed for ROMX, WRAMX, SRAM, or VRAM sections\n");
|
||||||
else if (bank < sectionTypeInfo[type].firstBank || bank > sectionTypeInfo[type].lastBank)
|
} else if (bank < sectionTypeInfo[type].firstBank
|
||||||
|
|| bank > sectionTypeInfo[type].lastBank) {
|
||||||
error(
|
error(
|
||||||
"%s bank value $%04" PRIx32 " out of range ($%04" PRIx32 " to $%04" PRIx32 ")\n",
|
"%s bank value $%04" PRIx32 " out of range ($%04" PRIx32 " to $%04" PRIx32 ")\n",
|
||||||
sectionTypeInfo[type].name.c_str(),
|
sectionTypeInfo[type].name.c_str(),
|
||||||
@@ -324,6 +342,7 @@ static Section *getSection(
|
|||||||
sectionTypeInfo[type].firstBank,
|
sectionTypeInfo[type].firstBank,
|
||||||
sectionTypeInfo[type].lastBank
|
sectionTypeInfo[type].lastBank
|
||||||
);
|
);
|
||||||
|
}
|
||||||
} else if (nbbanks(type) == 1) {
|
} else if (nbbanks(type) == 1) {
|
||||||
// If the section type only has a single bank, implicitly force it
|
// If the section type only has a single bank, implicitly force it
|
||||||
bank = sectionTypeInfo[type].firstBank;
|
bank = sectionTypeInfo[type].firstBank;
|
||||||
@@ -338,8 +357,8 @@ static Section *getSection(
|
|||||||
alignOffset = 0;
|
alignOffset = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (org != (uint32_t)-1) {
|
if (org != UINT32_MAX) {
|
||||||
if (org < sectionTypeInfo[type].startAddr || org > endaddr(type))
|
if (org < sectionTypeInfo[type].startAddr || org > endaddr(type)) {
|
||||||
error(
|
error(
|
||||||
"Section \"%s\"'s fixed address $%04" PRIx32 " is outside of range [$%04" PRIx16
|
"Section \"%s\"'s fixed address $%04" PRIx32 " is outside of range [$%04" PRIx16
|
||||||
"; $%04" PRIx16 "]\n",
|
"; $%04" PRIx16 "]\n",
|
||||||
@@ -349,6 +368,7 @@ static Section *getSection(
|
|||||||
endaddr(type)
|
endaddr(type)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (alignment != 0) {
|
if (alignment != 0) {
|
||||||
if (alignment > 16) {
|
if (alignment > 16) {
|
||||||
@@ -358,9 +378,10 @@ static Section *getSection(
|
|||||||
// It doesn't make sense to have both alignment and org set
|
// It doesn't make sense to have both alignment and org set
|
||||||
uint32_t mask = mask(alignment);
|
uint32_t mask = mask(alignment);
|
||||||
|
|
||||||
if (org != (uint32_t)-1) {
|
if (org != UINT32_MAX) {
|
||||||
if ((org - alignOffset) & mask)
|
if ((org - alignOffset) & mask) {
|
||||||
error("Section \"%s\"'s fixed address doesn't match its alignment\n", name.c_str());
|
error("Section \"%s\"'s fixed address doesn't match its alignment\n", name.c_str());
|
||||||
|
}
|
||||||
alignment = 0; // Ignore it if it's satisfied
|
alignment = 0; // Ignore it if it's satisfied
|
||||||
} else if (sectionTypeInfo[type].startAddr & mask) {
|
} else if (sectionTypeInfo[type].startAddr & mask) {
|
||||||
error(
|
error(
|
||||||
@@ -393,26 +414,30 @@ static Section *getSection(
|
|||||||
|
|
||||||
// Set the current section
|
// Set the current section
|
||||||
static void changeSection() {
|
static void changeSection() {
|
||||||
if (!currentUnionStack.empty())
|
if (!currentUnionStack.empty()) {
|
||||||
fatalerror("Cannot change the section within a UNION\n");
|
fatalerror("Cannot change the section within a UNION\n");
|
||||||
|
}
|
||||||
|
|
||||||
sym_ResetCurrentLabelScopes();
|
sym_ResetCurrentLabelScopes();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Section::isSizeKnown() const {
|
bool Section::isSizeKnown() const {
|
||||||
// SECTION UNION and SECTION FRAGMENT can still grow
|
// SECTION UNION and SECTION FRAGMENT can still grow
|
||||||
if (modifier != SECTION_NORMAL)
|
if (modifier != SECTION_NORMAL) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// The current section (or current load section if within one) is still growing
|
// 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;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Any section on the stack is still growing
|
// Any section on the stack is still growing
|
||||||
for (SectionStackEntry &entry : sectionStack) {
|
for (SectionStackEntry &entry : sectionStack) {
|
||||||
if (entry.section && entry.section->name == name)
|
if (entry.section && entry.section->name == name) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -425,13 +450,15 @@ void sect_NewSection(
|
|||||||
SectionSpec const &attrs,
|
SectionSpec const &attrs,
|
||||||
SectionModifier mod
|
SectionModifier mod
|
||||||
) {
|
) {
|
||||||
if (currentLoadSection)
|
|
||||||
fatalerror("Cannot change the section within a `LOAD` block\n");
|
|
||||||
|
|
||||||
for (SectionStackEntry &entry : sectionStack) {
|
for (SectionStackEntry &entry : sectionStack) {
|
||||||
if (entry.section && entry.section->name == name)
|
if (entry.section && entry.section->name == name) {
|
||||||
fatalerror("Section '%s' is already on the stack\n", name.c_str());
|
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);
|
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
|
// Therefore, any interactions are NOT TESTED, so lift either of those restrictions at
|
||||||
// your own peril! ^^
|
// your own peril! ^^
|
||||||
|
|
||||||
if (!requireCodeSection())
|
if (!requireCodeSection()) {
|
||||||
return;
|
|
||||||
|
|
||||||
if (currentLoadSection) {
|
|
||||||
error("`LOAD` blocks cannot be nested\n");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -467,9 +490,8 @@ void sect_SetLoadSection(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mod == SECTION_FRAGMENT) {
|
if (currentLoadSection) {
|
||||||
error("`LOAD FRAGMENT` is not allowed\n");
|
sect_EndLoadSection("LOAD");
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Section *sect = getSection(name, type, org, attrs, mod);
|
Section *sect = getSection(name, type, org, attrs, mod);
|
||||||
@@ -481,7 +503,13 @@ void sect_SetLoadSection(
|
|||||||
currentLoadSection = sect;
|
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) {
|
if (!currentLoadSection) {
|
||||||
error("Found `ENDL` outside of a `LOAD` block\n");
|
error("Found `ENDL` outside of a `LOAD` block\n");
|
||||||
return;
|
return;
|
||||||
@@ -494,6 +522,12 @@ void sect_EndLoadSection() {
|
|||||||
sym_SetCurrentLabelScopes(currentLoadLabelScopes);
|
sym_SetCurrentLabelScopes(currentLoadLabelScopes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void sect_CheckLoadClosed() {
|
||||||
|
if (currentLoadSection) {
|
||||||
|
warning(WARNING_UNTERMINATED_LOAD, "`LOAD` block without `ENDL` terminated by EOF\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Section *sect_GetSymbolSection() {
|
Section *sect_GetSymbolSection() {
|
||||||
return currentLoadSection ? currentLoadSection : currentSection;
|
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
|
// Returns how many bytes need outputting for the specified alignment and offset to succeed
|
||||||
uint32_t sect_GetAlignBytes(uint8_t alignment, uint16_t offset) {
|
uint32_t sect_GetAlignBytes(uint8_t alignment, uint16_t offset) {
|
||||||
Section *sect = sect_GetSymbolSection();
|
Section *sect = sect_GetSymbolSection();
|
||||||
if (!sect)
|
if (!sect) {
|
||||||
return 0;
|
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
|
// If the section is not aligned, no bytes are needed
|
||||||
// (fixed sections count as being maximally aligned for this purpose)
|
// (fixed sections count as being maximally aligned for this purpose)
|
||||||
uint8_t curAlignment = isFixed ? 16 : sect->align;
|
uint8_t curAlignment = isFixed ? 16 : sect->align;
|
||||||
if (curAlignment == 0)
|
if (curAlignment == 0) {
|
||||||
return 0;
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
// We need `(pcValue + curOffset + return value) % (1 << alignment) == offset`
|
// We need `(pcValue + curOffset + return value) % (1 << alignment) == offset`
|
||||||
uint16_t pcValue = isFixed ? sect->org : sect->alignOfs;
|
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) {
|
void sect_AlignPC(uint8_t alignment, uint16_t offset) {
|
||||||
if (!requireSection())
|
if (!requireSection()) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Section *sect = sect_GetSymbolSection();
|
Section *sect = sect_GetSymbolSection();
|
||||||
uint32_t alignSize = 1 << alignment; // Size of an aligned "block"
|
uint32_t alignSize = 1 << alignment; // Size of an aligned "block"
|
||||||
|
|
||||||
if (sect->org != (uint32_t)-1) {
|
if (sect->org != UINT32_MAX) {
|
||||||
if ((sect->org + curOffset - offset) % alignSize)
|
if ((sect->org + curOffset - offset) % alignSize) {
|
||||||
error(
|
error(
|
||||||
"Section's fixed address fails required alignment (PC = $%04" PRIx32 ")\n",
|
"Section's fixed address fails required alignment (PC = $%04" PRIx32 ")\n",
|
||||||
sect->org + curOffset
|
sect->org + curOffset
|
||||||
);
|
);
|
||||||
|
}
|
||||||
} else if (sect->align != 0
|
} else if (sect->align != 0
|
||||||
&& (((sect->alignOfs + curOffset) % (1u << sect->align)) - offset) % alignSize) {
|
&& (((sect->alignOfs + curOffset) % (1u << sect->align)) - offset) % alignSize) {
|
||||||
error(
|
error(
|
||||||
@@ -563,18 +601,22 @@ void sect_AlignPC(uint8_t alignment, uint16_t offset) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void growSection(uint32_t growth) {
|
static void growSection(uint32_t growth) {
|
||||||
if (growth > 0 && curOffset > UINT32_MAX - growth)
|
if (growth > 0 && curOffset > UINT32_MAX - growth) {
|
||||||
fatalerror("Section size would overflow internal counter\n");
|
fatalerror("Section size would overflow internal counter\n");
|
||||||
|
}
|
||||||
curOffset += growth;
|
curOffset += growth;
|
||||||
if (uint32_t outOffset = sect_GetOutputOffset(); outOffset > currentSection->size)
|
if (uint32_t outOffset = sect_GetOutputOffset(); outOffset > currentSection->size) {
|
||||||
currentSection->size = outOffset;
|
currentSection->size = outOffset;
|
||||||
if (currentLoadSection && curOffset > currentLoadSection->size)
|
}
|
||||||
|
if (currentLoadSection && curOffset > currentLoadSection->size) {
|
||||||
currentLoadSection->size = curOffset;
|
currentLoadSection->size = curOffset;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void writeByte(uint8_t byte) {
|
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;
|
currentSection->data[index] = byte;
|
||||||
|
}
|
||||||
growSection(1);
|
growSection(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -616,8 +658,9 @@ static void endUnionMember() {
|
|||||||
UnionStackEntry &member = currentUnionStack.top();
|
UnionStackEntry &member = currentUnionStack.top();
|
||||||
uint32_t memberSize = curOffset - member.start;
|
uint32_t memberSize = curOffset - member.start;
|
||||||
|
|
||||||
if (memberSize > member.size)
|
if (memberSize > member.size) {
|
||||||
member.size = memberSize;
|
member.size = memberSize;
|
||||||
|
}
|
||||||
curOffset = member.start;
|
curOffset = member.start;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -640,64 +683,75 @@ void sect_EndUnion() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void sect_CheckUnionClosed() {
|
void sect_CheckUnionClosed() {
|
||||||
if (!currentUnionStack.empty())
|
if (!currentUnionStack.empty()) {
|
||||||
error("Unterminated UNION construct\n");
|
error("Unterminated UNION construct\n");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Output a constant byte
|
// Output a constant byte
|
||||||
void sect_ConstByte(uint8_t byte) {
|
void sect_ConstByte(uint8_t byte) {
|
||||||
if (!requireCodeSection())
|
if (!requireCodeSection()) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
writeByte(byte);
|
writeByte(byte);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Output a string's character units as bytes
|
// Output a string's character units as bytes
|
||||||
void sect_ByteString(std::vector<int32_t> const &string) {
|
void sect_ByteString(std::vector<int32_t> const &string) {
|
||||||
if (!requireCodeSection())
|
if (!requireCodeSection()) {
|
||||||
return;
|
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));
|
writeByte(static_cast<uint8_t>(unit));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Output a string's character units as words
|
// Output a string's character units as words
|
||||||
void sect_WordString(std::vector<int32_t> const &string) {
|
void sect_WordString(std::vector<int32_t> const &string) {
|
||||||
if (!requireCodeSection())
|
if (!requireCodeSection()) {
|
||||||
return;
|
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));
|
writeWord(static_cast<uint16_t>(unit));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Output a string's character units as longs
|
// Output a string's character units as longs
|
||||||
void sect_LongString(std::vector<int32_t> const &string) {
|
void sect_LongString(std::vector<int32_t> const &string) {
|
||||||
if (!requireCodeSection())
|
if (!requireCodeSection()) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
for (int32_t unit : string)
|
for (int32_t unit : string) {
|
||||||
writeLong(static_cast<uint32_t>(unit));
|
writeLong(static_cast<uint32_t>(unit));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip this many bytes
|
// Skip this many bytes
|
||||||
void sect_Skip(uint32_t skip, bool ds) {
|
void sect_Skip(uint32_t skip, bool ds) {
|
||||||
if (!requireSection())
|
if (!requireSection()) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!sect_HasData(currentSection->type)) {
|
if (!sect_HasData(currentSection->type)) {
|
||||||
growSection(skip);
|
growSection(skip);
|
||||||
} else {
|
} else {
|
||||||
if (!ds)
|
if (!ds) {
|
||||||
warning(
|
warning(
|
||||||
WARNING_EMPTY_DATA_DIRECTIVE,
|
WARNING_EMPTY_DATA_DIRECTIVE,
|
||||||
"%s directive without data in ROM\n",
|
"%s directive without data in ROM\n",
|
||||||
@@ -705,16 +759,19 @@ void sect_Skip(uint32_t skip, bool ds) {
|
|||||||
: (skip == 2) ? "DW"
|
: (skip == 2) ? "DW"
|
||||||
: "DB"
|
: "DB"
|
||||||
);
|
);
|
||||||
|
}
|
||||||
// We know we're in a code SECTION
|
// We know we're in a code SECTION
|
||||||
while (skip--)
|
while (skip--) {
|
||||||
writeByte(fillByte);
|
writeByte(fillByte);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Output a byte that can be relocatable or constant
|
// Output a byte that can be relocatable or constant
|
||||||
void sect_RelByte(Expression &expr, uint32_t pcShift) {
|
void sect_RelByte(Expression &expr, uint32_t pcShift) {
|
||||||
if (!requireCodeSection())
|
if (!requireCodeSection()) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!expr.isKnown()) {
|
if (!expr.isKnown()) {
|
||||||
createPatch(PATCHTYPE_BYTE, expr, pcShift);
|
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
|
// Output several bytes that can be relocatable or constant
|
||||||
void sect_RelBytes(uint32_t n, std::vector<Expression> &exprs) {
|
void sect_RelBytes(uint32_t n, std::vector<Expression> &exprs) {
|
||||||
if (!requireCodeSection())
|
if (!requireCodeSection()) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
for (uint32_t i = 0; i < n; i++) {
|
for (uint32_t i = 0; i < n; i++) {
|
||||||
Expression &expr = exprs[i % exprs.size()];
|
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
|
// Output a word that can be relocatable or constant
|
||||||
void sect_RelWord(Expression &expr, uint32_t pcShift) {
|
void sect_RelWord(Expression &expr, uint32_t pcShift) {
|
||||||
if (!requireCodeSection())
|
if (!requireCodeSection()) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!expr.isKnown()) {
|
if (!expr.isKnown()) {
|
||||||
createPatch(PATCHTYPE_WORD, expr, pcShift);
|
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
|
// Output a long that can be relocatable or constant
|
||||||
void sect_RelLong(Expression &expr, uint32_t pcShift) {
|
void sect_RelLong(Expression &expr, uint32_t pcShift) {
|
||||||
if (!requireCodeSection())
|
if (!requireCodeSection()) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!expr.isKnown()) {
|
if (!expr.isKnown()) {
|
||||||
createPatch(PATCHTYPE_LONG, expr, pcShift);
|
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
|
// Output a PC-relative byte that can be relocatable or constant
|
||||||
void sect_PCRelByte(Expression &expr, uint32_t pcShift) {
|
void sect_PCRelByte(Expression &expr, uint32_t pcShift) {
|
||||||
if (!requireCodeSection())
|
if (!requireCodeSection()) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (Symbol const *pc = sym_GetPC(); !expr.isDiffConstant(pc)) {
|
if (Symbol const *pc = sym_GetPC(); !expr.isDiffConstant(pc)) {
|
||||||
createPatch(PATCHTYPE_JR, expr, pcShift);
|
createPatch(PATCHTYPE_JR, expr, pcShift);
|
||||||
@@ -781,15 +842,16 @@ void sect_PCRelByte(Expression &expr, uint32_t pcShift) {
|
|||||||
int16_t offset;
|
int16_t offset;
|
||||||
|
|
||||||
// Offset is relative to the byte *after* the operand
|
// 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
|
offset = -2; // PC as operand to `jr` is lower than reference PC by 2
|
||||||
else
|
} else {
|
||||||
offset = sym->getValue() - (pc->getValue() + 1);
|
offset = sym->getValue() - (pc->getValue() + 1);
|
||||||
|
}
|
||||||
|
|
||||||
if (offset < -128 || offset > 127) {
|
if (offset < -128 || offset > 127) {
|
||||||
error(
|
error(
|
||||||
"jr target must be between -128 and 127 bytes away, not %" PRId16
|
"JR target must be between -128 and 127 bytes away, not %" PRId16
|
||||||
"; use jp instead\n",
|
"; use JP instead\n",
|
||||||
offset
|
offset
|
||||||
);
|
);
|
||||||
writeByte(0);
|
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);
|
error("Start position cannot be negative (%" PRId32 ")\n", startPos);
|
||||||
startPos = 0;
|
startPos = 0;
|
||||||
}
|
}
|
||||||
if (!requireCodeSection())
|
if (!requireCodeSection()) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
FILE *file = nullptr;
|
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");
|
file = fopen(fullPath->c_str(), "rb");
|
||||||
|
}
|
||||||
if (!file) {
|
if (!file) {
|
||||||
if (generatedMissingIncludes) {
|
if (generatedMissingIncludes) {
|
||||||
if (verbose)
|
if (verbose) {
|
||||||
printf("Aborting (-MG) on INCBIN file '%s' (%s)\n", name.c_str(), strerror(errno));
|
printf("Aborting (-MG) on INCBIN file '%s' (%s)\n", name.c_str(), strerror(errno));
|
||||||
|
}
|
||||||
failedOnMissingInclude = true;
|
failedOnMissingInclude = true;
|
||||||
} else {
|
} else {
|
||||||
error("Error opening INCBIN file '%s': %s\n", name.c_str(), strerror(errno));
|
error("Error opening INCBIN file '%s': %s\n", name.c_str(), strerror(errno));
|
||||||
@@ -831,10 +896,11 @@ void sect_BinaryFile(std::string const &name, int32_t startPos) {
|
|||||||
// The file is seekable; skip to the specified start position
|
// The file is seekable; skip to the specified start position
|
||||||
fseek(file, startPos, SEEK_SET);
|
fseek(file, startPos, SEEK_SET);
|
||||||
} else {
|
} else {
|
||||||
if (errno != ESPIPE)
|
if (errno != ESPIPE) {
|
||||||
error(
|
error(
|
||||||
"Error determining size of INCBIN file '%s': %s\n", name.c_str(), strerror(errno)
|
"Error determining size of INCBIN file '%s': %s\n", name.c_str(), strerror(errno)
|
||||||
);
|
);
|
||||||
|
}
|
||||||
// The file isn't seekable, so we'll just skip bytes one at a time
|
// The file isn't seekable, so we'll just skip bytes one at a time
|
||||||
while (startPos--) {
|
while (startPos--) {
|
||||||
if (fgetc(file) == EOF) {
|
if (fgetc(file) == EOF) {
|
||||||
@@ -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);
|
writeByte(byte);
|
||||||
|
}
|
||||||
|
|
||||||
if (ferror(file))
|
if (ferror(file)) {
|
||||||
error("Error reading INCBIN file '%s': %s\n", name.c_str(), strerror(errno));
|
error("Error reading INCBIN file '%s': %s\n", name.c_str(), strerror(errno));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Output a slice of a binary file
|
// 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);
|
error("Number of bytes to read cannot be negative (%" PRId32 ")\n", length);
|
||||||
length = 0;
|
length = 0;
|
||||||
}
|
}
|
||||||
if (!requireCodeSection())
|
if (!requireCodeSection()) {
|
||||||
return;
|
return;
|
||||||
if (length == 0) // Don't even bother with 0-byte slices
|
}
|
||||||
|
if (length == 0) { // Don't even bother with 0-byte slices
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
FILE *file = nullptr;
|
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");
|
file = fopen(fullPath->c_str(), "rb");
|
||||||
|
}
|
||||||
if (!file) {
|
if (!file) {
|
||||||
if (generatedMissingIncludes) {
|
if (generatedMissingIncludes) {
|
||||||
if (verbose)
|
if (verbose) {
|
||||||
printf("Aborting (-MG) on INCBIN file '%s' (%s)\n", name.c_str(), strerror(errno));
|
printf("Aborting (-MG) on INCBIN file '%s' (%s)\n", name.c_str(), strerror(errno));
|
||||||
|
}
|
||||||
failedOnMissingInclude = true;
|
failedOnMissingInclude = true;
|
||||||
} else {
|
} else {
|
||||||
error("Error opening INCBIN file '%s': %s\n", name.c_str(), strerror(errno));
|
error("Error opening INCBIN file '%s': %s\n", name.c_str(), strerror(errno));
|
||||||
@@ -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
|
// The file is seekable; skip to the specified start position
|
||||||
fseek(file, startPos, SEEK_SET);
|
fseek(file, startPos, SEEK_SET);
|
||||||
} else {
|
} else {
|
||||||
if (errno != ESPIPE)
|
if (errno != ESPIPE) {
|
||||||
error(
|
error(
|
||||||
"Error determining size of INCBIN file '%s': %s\n", name.c_str(), strerror(errno)
|
"Error determining size of INCBIN file '%s': %s\n", name.c_str(), strerror(errno)
|
||||||
);
|
);
|
||||||
|
}
|
||||||
// The file isn't seekable, so we'll just skip bytes one at a time
|
// The file isn't seekable, so we'll just skip bytes one at a time
|
||||||
while (startPos--) {
|
while (startPos--) {
|
||||||
if (fgetc(file) == EOF) {
|
if (fgetc(file) == EOF) {
|
||||||
@@ -950,11 +1023,13 @@ void sect_PushSection() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void sect_PopSection() {
|
void sect_PopSection() {
|
||||||
if (sectionStack.empty())
|
if (sectionStack.empty()) {
|
||||||
fatalerror("No entries in the section stack\n");
|
fatalerror("No entries in the section stack\n");
|
||||||
|
}
|
||||||
|
|
||||||
if (currentLoadSection)
|
if (currentLoadSection) {
|
||||||
fatalerror("Cannot change the section within a `LOAD` block\n");
|
sect_EndLoadSection("POPS");
|
||||||
|
}
|
||||||
|
|
||||||
SectionStackEntry entry = sectionStack.front();
|
SectionStackEntry entry = sectionStack.front();
|
||||||
sectionStack.pop_front();
|
sectionStack.pop_front();
|
||||||
@@ -968,15 +1043,24 @@ void sect_PopSection() {
|
|||||||
std::swap(currentUnionStack, entry.unionStack);
|
std::swap(currentUnionStack, entry.unionStack);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void sect_CheckStack() {
|
||||||
|
if (!sectionStack.empty()) {
|
||||||
|
warning(WARNING_UNMATCHED_DIRECTIVE, "`PUSHS` without corresponding `POPS`\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void sect_EndSection() {
|
void sect_EndSection() {
|
||||||
if (!currentSection)
|
if (!currentSection) {
|
||||||
fatalerror("Cannot end the section outside of a SECTION\n");
|
fatalerror("Cannot end the section outside of a SECTION\n");
|
||||||
|
}
|
||||||
|
|
||||||
if (currentLoadSection)
|
if (!currentUnionStack.empty()) {
|
||||||
fatalerror("Cannot end the section within a `LOAD` block\n");
|
|
||||||
|
|
||||||
if (!currentUnionStack.empty())
|
|
||||||
fatalerror("Cannot end the section within a UNION\n");
|
fatalerror("Cannot end the section within a UNION\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentLoadSection) {
|
||||||
|
sect_EndLoadSection("ENDSECTION");
|
||||||
|
}
|
||||||
|
|
||||||
// Reset the section scope
|
// Reset the section scope
|
||||||
currentSection = nullptr;
|
currentSection = nullptr;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#include "asm/symbol.hpp"
|
#include "asm/symbol.hpp"
|
||||||
|
|
||||||
@@ -40,8 +40,9 @@ bool sym_IsPC(Symbol const *sym) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void sym_ForEach(void (*callback)(Symbol &)) {
|
void sym_ForEach(void (*callback)(Symbol &)) {
|
||||||
for (auto &it : symbols)
|
for (auto &it : symbols) {
|
||||||
callback(it.second);
|
callback(it.second);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static int32_t NARGCallback() {
|
static int32_t NARGCallback() {
|
||||||
@@ -92,7 +93,7 @@ int32_t Symbol::getOutputValue() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ContentSpan const &Symbol::getMacro() const {
|
ContentSpan const &Symbol::getMacro() const {
|
||||||
assume((std::holds_alternative<ContentSpan>(data)));
|
assume(std::holds_alternative<ContentSpan>(data));
|
||||||
return std::get<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)
|
||||||
|| 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 (*callback)();
|
||||||
|
}
|
||||||
return std::get<std::shared_ptr<std::string>>(data);
|
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;
|
sym.fileLine = sym.src ? lexer_GetLineNo() : 0;
|
||||||
|
|
||||||
// If the old node was registered, ensure the new one is too
|
// 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);
|
out_RegisterNode(sym.src);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void alreadyDefinedError(Symbol const &sym, char const *asType) {
|
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());
|
error("'%s' is reserved for a built-in symbol\n", sym.name.c_str());
|
||||||
} else {
|
} else {
|
||||||
error("'%s' already defined", sym.name.c_str());
|
error("'%s' already defined", sym.name.c_str());
|
||||||
if (asType)
|
if (asType) {
|
||||||
fprintf(stderr, " as %s", asType);
|
fprintf(stderr, " as %s", asType);
|
||||||
|
}
|
||||||
fputs(" at ", stderr);
|
fputs(" at ", stderr);
|
||||||
dumpFilename(sym);
|
dumpFilename(sym);
|
||||||
}
|
}
|
||||||
@@ -169,7 +173,7 @@ static Symbol &createSymbol(std::string const &symName) {
|
|||||||
sym.section = nullptr;
|
sym.section = nullptr;
|
||||||
sym.src = fstk_GetFileStack();
|
sym.src = fstk_GetFileStack();
|
||||||
sym.fileLine = sym.src ? lexer_GetLineNo() : 0;
|
sym.fileLine = sym.src ? lexer_GetLineNo() : 0;
|
||||||
sym.ID = -1;
|
sym.ID = UINT32_MAX;
|
||||||
sym.defIndex = nextDefIndex++;
|
sym.defIndex = nextDefIndex++;
|
||||||
|
|
||||||
return sym;
|
return sym;
|
||||||
@@ -184,28 +188,34 @@ static bool isAutoScoped(std::string const &symName) {
|
|||||||
size_t dotPos = symName.find('.');
|
size_t dotPos = symName.find('.');
|
||||||
|
|
||||||
// If there are no dots, it's not a local label
|
// If there are no dots, it's not a local label
|
||||||
if (dotPos == std::string::npos)
|
if (dotPos == std::string::npos) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Label scopes `.` and `..` are the only nonlocal identifiers that start with a dot
|
// Label scopes `.` and `..` are the only nonlocal identifiers that start with a dot
|
||||||
if (dotPos == 0 && symName.find_first_not_of('.') == symName.npos)
|
if (dotPos == 0 && symName.find_first_not_of('.') == symName.npos) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Check for nothing after the dot
|
// Check for nothing after the dot
|
||||||
if (dotPos == symName.length() - 1)
|
if (dotPos == symName.length() - 1) {
|
||||||
fatalerror("'%s' is a nonsensical reference to an empty local label\n", symName.c_str());
|
fatalerror("'%s' is a nonsensical reference to an empty local label\n", symName.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
// Check for more than one dot
|
// Check for more than one dot
|
||||||
if (symName.find('.', dotPos + 1) != std::string::npos)
|
if (symName.find('.', dotPos + 1) != std::string::npos) {
|
||||||
fatalerror("'%s' is a nonsensical reference to a nested local label\n", symName.c_str());
|
fatalerror("'%s' is a nonsensical reference to a nested local label\n", symName.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
// Check for already-qualified local label
|
// Check for already-qualified local label
|
||||||
if (dotPos > 0)
|
if (dotPos > 0) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Check for unqualifiable local label
|
// Check for unqualifiable local label
|
||||||
if (!globalScope)
|
if (!globalScope) {
|
||||||
fatalerror("Unqualified local label '%s' in main scope\n", symName.c_str());
|
fatalerror("Unqualified local label '%s' in main scope\n", symName.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -252,24 +262,28 @@ void sym_Purge(std::string const &symName) {
|
|||||||
Symbol *sym = sym_FindScopedValidSymbol(symName);
|
Symbol *sym = sym_FindScopedValidSymbol(symName);
|
||||||
|
|
||||||
if (!sym) {
|
if (!sym) {
|
||||||
if (sym_IsPurgedScoped(symName))
|
if (sym_IsPurgedScoped(symName)) {
|
||||||
error("'%s' was already purged\n", symName.c_str());
|
error("'%s' was already purged\n", symName.c_str());
|
||||||
else
|
} else {
|
||||||
error("'%s' not defined\n", symName.c_str());
|
error("'%s' not defined\n", symName.c_str());
|
||||||
|
}
|
||||||
} else if (sym->isBuiltin) {
|
} else if (sym->isBuiltin) {
|
||||||
error("Built-in symbol '%s' cannot be purged\n", symName.c_str());
|
error("Built-in symbol '%s' cannot be purged\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());
|
error("Symbol \"%s\" is referenced and thus cannot be purged\n", symName.c_str());
|
||||||
} else {
|
} else {
|
||||||
if (sym->isExported)
|
if (sym->isExported) {
|
||||||
warning(WARNING_PURGE_1, "Purging an exported symbol \"%s\"\n", symName.c_str());
|
warning(WARNING_PURGE_1, "Purging an exported symbol \"%s\"\n", symName.c_str());
|
||||||
else if (sym->isLabel())
|
} else if (sym->isLabel()) {
|
||||||
warning(WARNING_PURGE_2, "Purging a label \"%s\"\n", symName.c_str());
|
warning(WARNING_PURGE_2, "Purging a label \"%s\"\n", symName.c_str());
|
||||||
|
}
|
||||||
// Do not keep a reference to the label after purging it
|
// Do not keep a reference to the label after purging it
|
||||||
if (sym == globalScope)
|
if (sym == globalScope) {
|
||||||
globalScope = nullptr;
|
globalScope = nullptr;
|
||||||
if (sym == localScope)
|
}
|
||||||
|
if (sym == localScope) {
|
||||||
localScope = nullptr;
|
localScope = nullptr;
|
||||||
|
}
|
||||||
purgedSymbols.emplace(sym->name);
|
purgedSymbols.emplace(sym->name);
|
||||||
symbols.erase(sym->name);
|
symbols.erase(sym->name);
|
||||||
}
|
}
|
||||||
@@ -295,14 +309,16 @@ void sym_SetRSValue(int32_t value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
uint32_t Symbol::getConstantValue() const {
|
uint32_t Symbol::getConstantValue() const {
|
||||||
if (isConstant())
|
if (isConstant()) {
|
||||||
return getValue();
|
return getValue();
|
||||||
|
}
|
||||||
|
|
||||||
if (sym_IsPC(this)) {
|
if (sym_IsPC(this)) {
|
||||||
if (!getSection())
|
if (!getSection()) {
|
||||||
error("PC has no value outside of a section\n");
|
error("PC has no value outside of a section\n");
|
||||||
else
|
} else {
|
||||||
error("PC does not have a constant value; the current section is not fixed\n");
|
error("PC does not have a constant value; the current section is not fixed\n");
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
error("\"%s\" does not have a constant value\n", name.c_str());
|
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) {
|
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();
|
return sym->getConstantValue();
|
||||||
|
}
|
||||||
|
|
||||||
if (sym_IsPurgedScoped(symName))
|
if (sym_IsPurgedScoped(symName)) {
|
||||||
error("'%s' not defined; it was purged\n", symName.c_str());
|
error("'%s' not defined; it was purged\n", symName.c_str());
|
||||||
else
|
} else {
|
||||||
error("'%s' not defined\n", symName.c_str());
|
error("'%s' not defined\n", symName.c_str());
|
||||||
|
}
|
||||||
return 0;
|
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_AddEqu(std::string const &symName, int32_t value) {
|
||||||
Symbol *sym = createNonrelocSymbol(symName, true);
|
Symbol *sym = createNonrelocSymbol(symName, true);
|
||||||
|
|
||||||
if (!sym)
|
if (!sym) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
sym->type = SYM_EQU;
|
sym->type = SYM_EQU;
|
||||||
sym->data = value;
|
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_RedefEqu(std::string const &symName, int32_t value) {
|
||||||
Symbol *sym = sym_FindExactSymbol(symName);
|
Symbol *sym = sym_FindExactSymbol(symName);
|
||||||
|
|
||||||
if (!sym)
|
if (!sym) {
|
||||||
return sym_AddEqu(symName, value);
|
return sym_AddEqu(symName, value);
|
||||||
|
}
|
||||||
|
|
||||||
if (sym->isDefined() && sym->type != SYM_EQU) {
|
if (sym->isDefined() && sym->type != SYM_EQU) {
|
||||||
alreadyDefinedError(*sym, "non-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_AddString(std::string const &symName, std::shared_ptr<std::string> str) {
|
||||||
Symbol *sym = createNonrelocSymbol(symName, false);
|
Symbol *sym = createNonrelocSymbol(symName, false);
|
||||||
|
|
||||||
if (!sym)
|
if (!sym) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
sym->type = SYM_EQUS;
|
sym->type = SYM_EQUS;
|
||||||
sym->data = str;
|
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_RedefString(std::string const &symName, std::shared_ptr<std::string> str) {
|
||||||
Symbol *sym = sym_FindExactSymbol(symName);
|
Symbol *sym = sym_FindExactSymbol(symName);
|
||||||
|
|
||||||
if (!sym)
|
if (!sym) {
|
||||||
return sym_AddString(symName, str);
|
return sym_AddString(symName, str);
|
||||||
|
}
|
||||||
|
|
||||||
if (sym->type != SYM_EQUS) {
|
if (sym->type != SYM_EQUS) {
|
||||||
if (sym->isDefined()) {
|
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
|
// If the symbol already exists as a ref, just "take over" it
|
||||||
sym->type = SYM_LABEL;
|
sym->type = SYM_LABEL;
|
||||||
sym->data = (int32_t)sect_GetSymbolOffset();
|
sym->data = static_cast<int32_t>(sect_GetSymbolOffset());
|
||||||
// Don't export anonymous labels
|
// Don't export anonymous labels
|
||||||
if (exportAll && !symName.starts_with('!'))
|
if (exportAll && !symName.starts_with('!')) {
|
||||||
sym->isExported = true;
|
sym->isExported = true;
|
||||||
|
}
|
||||||
sym->section = sect_GetSymbolSection();
|
sym->section = sect_GetSymbolSection();
|
||||||
|
|
||||||
if (sym && !sym->section)
|
if (sym && !sym->section) {
|
||||||
error("Label \"%s\" created outside of a SECTION\n", symName.c_str());
|
error("Label \"%s\" created outside of a SECTION\n", symName.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
return sym;
|
return sym;
|
||||||
}
|
}
|
||||||
@@ -478,8 +502,9 @@ Symbol *sym_AddLocalLabel(std::string const &symName) {
|
|||||||
|
|
||||||
Symbol *sym = addLabel(isAutoScoped(symName) ? globalScope->name + symName : symName);
|
Symbol *sym = addLabel(isAutoScoped(symName) ? globalScope->name + symName : symName);
|
||||||
|
|
||||||
if (sym)
|
if (sym) {
|
||||||
localScope = sym;
|
localScope = sym;
|
||||||
|
}
|
||||||
|
|
||||||
return sym;
|
return sym;
|
||||||
}
|
}
|
||||||
@@ -516,7 +541,7 @@ std::string sym_MakeAnonLabelName(uint32_t ofs, bool neg) {
|
|||||||
uint32_t id = 0;
|
uint32_t id = 0;
|
||||||
|
|
||||||
if (neg) {
|
if (neg) {
|
||||||
if (ofs > anonLabelID)
|
if (ofs > anonLabelID) {
|
||||||
error(
|
error(
|
||||||
"Reference to anonymous label %" PRIu32 " before, when only %" PRIu32
|
"Reference to anonymous label %" PRIu32 " before, when only %" PRIu32
|
||||||
" ha%s been created so far\n",
|
" ha%s been created so far\n",
|
||||||
@@ -524,20 +549,22 @@ std::string sym_MakeAnonLabelName(uint32_t ofs, bool neg) {
|
|||||||
anonLabelID,
|
anonLabelID,
|
||||||
anonLabelID == 1 ? "s" : "ve"
|
anonLabelID == 1 ? "s" : "ve"
|
||||||
);
|
);
|
||||||
else
|
} else {
|
||||||
id = anonLabelID - ofs;
|
id = anonLabelID - ofs;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
ofs--; // We're referencing symbols that haven't been created yet...
|
ofs--; // We're referencing symbols that haven't been created yet...
|
||||||
if (ofs > UINT32_MAX - anonLabelID)
|
if (ofs > UINT32_MAX - anonLabelID) {
|
||||||
error(
|
error(
|
||||||
"Reference to anonymous label %" PRIu32 " after, when only %" PRIu32
|
"Reference to anonymous label %" PRIu32 " after, when only %" PRIu32
|
||||||
" may still be created\n",
|
" may still be created\n",
|
||||||
ofs + 1,
|
ofs + 1,
|
||||||
UINT32_MAX - anonLabelID
|
UINT32_MAX - anonLabelID
|
||||||
);
|
);
|
||||||
else
|
} else {
|
||||||
id = anonLabelID + ofs;
|
id = anonLabelID + ofs;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
std::string anon("!");
|
std::string anon("!");
|
||||||
anon += std::to_string(id);
|
anon += std::to_string(id);
|
||||||
@@ -553,16 +580,18 @@ void sym_Export(std::string const &symName) {
|
|||||||
Symbol *sym = sym_FindScopedSymbol(symName);
|
Symbol *sym = sym_FindScopedSymbol(symName);
|
||||||
|
|
||||||
// If the symbol doesn't exist, create a ref that can be purged
|
// If the symbol doesn't exist, create a ref that can be purged
|
||||||
if (!sym)
|
if (!sym) {
|
||||||
sym = sym_Ref(symName);
|
sym = sym_Ref(symName);
|
||||||
|
}
|
||||||
sym->isExported = true;
|
sym->isExported = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Symbol *sym_AddMacro(std::string const &symName, int32_t defLineNo, ContentSpan const &span) {
|
Symbol *sym_AddMacro(std::string const &symName, int32_t defLineNo, ContentSpan const &span) {
|
||||||
Symbol *sym = createNonrelocSymbol(symName, false);
|
Symbol *sym = createNonrelocSymbol(symName, false);
|
||||||
|
|
||||||
if (!sym)
|
if (!sym) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
sym->type = SYM_MACRO;
|
sym->type = SYM_MACRO;
|
||||||
sym->data = span;
|
sym->data = span;
|
||||||
@@ -627,7 +656,7 @@ void sym_Init(time_t now) {
|
|||||||
sym_AddEqu("__RGBDS_RC__"s, PACKAGE_VERSION_RC)->isBuiltin = true;
|
sym_AddEqu("__RGBDS_RC__"s, PACKAGE_VERSION_RC)->isBuiltin = true;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (now == (time_t)-1) {
|
if (now == static_cast<time_t>(-1)) {
|
||||||
warn("Failed to determine current time");
|
warn("Failed to determine current time");
|
||||||
// Fall back by pretending we are at the Epoch
|
// Fall back by pretending we are at the Epoch
|
||||||
now = 0;
|
now = 0;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#include "asm/warning.hpp"
|
#include "asm/warning.hpp"
|
||||||
|
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include "error.hpp"
|
#include "error.hpp"
|
||||||
#include "helpers.hpp" // QUOTEDSTRLEN
|
#include "helpers.hpp"
|
||||||
#include "itertools.hpp"
|
#include "itertools.hpp"
|
||||||
|
|
||||||
#include "asm/fstack.hpp"
|
#include "asm/fstack.hpp"
|
||||||
@@ -20,276 +20,188 @@
|
|||||||
unsigned int nbErrors = 0;
|
unsigned int nbErrors = 0;
|
||||||
unsigned int maxErrors = 0;
|
unsigned int maxErrors = 0;
|
||||||
|
|
||||||
static WarningState const defaultWarnings[ARRAY_SIZE(warningStates)] = {
|
Diagnostics warningStates;
|
||||||
WARNING_ENABLED, // WARNING_ASSERT
|
bool warningsAreErrors;
|
||||||
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
|
|
||||||
|
|
||||||
WARNING_DISABLED, // WARNING_NUMERIC_STRING_1
|
enum WarningLevel {
|
||||||
WARNING_DISABLED, // WARNING_NUMERIC_STRING_2
|
LEVEL_DEFAULT, // Warnings that are enabled by default
|
||||||
WARNING_ENABLED, // WARNING_TRUNCATION_1
|
LEVEL_ALL, // Warnings that probably indicate an error
|
||||||
WARNING_DISABLED, // WARNING_TRUNCATION_2
|
LEVEL_EXTRA, // Warnings that are less likely to indicate an error
|
||||||
WARNING_ENABLED, // WARNING_PURGE_1
|
LEVEL_EVERYTHING, // Literally every warning
|
||||||
WARNING_DISABLED, // WARNING_PURGE_2
|
|
||||||
WARNING_ENABLED, // WARNING_UNMAPPED_CHAR_1
|
|
||||||
WARNING_DISABLED, // WARNING_UNMAPPED_CHAR_2
|
|
||||||
};
|
};
|
||||||
|
|
||||||
WarningState warningStates[ARRAY_SIZE(warningStates)];
|
struct WarningFlag {
|
||||||
|
char const *name;
|
||||||
|
WarningLevel level;
|
||||||
|
};
|
||||||
|
|
||||||
bool warningsAreErrors; // Set if `-Werror` was specified
|
static WarningFlag const metaWarnings[] = {
|
||||||
|
{"all", LEVEL_ALL },
|
||||||
static WarningState warningState(WarningID id) {
|
{"extra", LEVEL_EXTRA },
|
||||||
// Check if warnings are globally disabled
|
{"everything", LEVEL_EVERYTHING},
|
||||||
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 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
|
// Parametric warnings
|
||||||
"numeric-string",
|
{"numeric-string", LEVEL_EVERYTHING},
|
||||||
"numeric-string",
|
{"numeric-string", LEVEL_EVERYTHING},
|
||||||
"purge",
|
{"purge", LEVEL_DEFAULT },
|
||||||
"purge",
|
{"purge", LEVEL_ALL },
|
||||||
"truncation",
|
{"truncation", LEVEL_DEFAULT },
|
||||||
"truncation",
|
{"truncation", LEVEL_EXTRA },
|
||||||
"unmapped-char",
|
{"unmapped-char", LEVEL_DEFAULT },
|
||||||
"unmapped-char",
|
{"unmapped-char", LEVEL_ALL },
|
||||||
|
|
||||||
// Meta warnings
|
|
||||||
"all",
|
|
||||||
"extra",
|
|
||||||
"everything", // Especially useful for testing
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static const struct {
|
static const struct {
|
||||||
char const *name;
|
WarningID firstID;
|
||||||
uint8_t nbLevels;
|
WarningID lastID;
|
||||||
uint8_t defaultLevel;
|
uint8_t defaultLevel;
|
||||||
} paramWarnings[] = {
|
} paramWarnings[] = {
|
||||||
{"numeric-string", 2, 1},
|
{WARNING_NUMERIC_STRING_1, WARNING_NUMERIC_STRING_2, 1},
|
||||||
{"purge", 2, 1},
|
{WARNING_PURGE_1, WARNING_PURGE_2, 1},
|
||||||
{"truncation", 2, 2},
|
{WARNING_TRUNCATION_1, WARNING_TRUNCATION_2, 2},
|
||||||
{"unmapped-char", 2, 1},
|
{WARNING_UNMAPPED_CHAR_1, WARNING_UNMAPPED_CHAR_2, 1},
|
||||||
};
|
};
|
||||||
|
|
||||||
static bool tryProcessParamWarning(char const *flag, uint8_t param, WarningState state) {
|
enum WarningBehavior { DISABLED, ENABLED, ERROR };
|
||||||
WarningID baseID = PARAM_WARNINGS_START;
|
|
||||||
|
|
||||||
for (size_t i = 0; i < ARRAY_SIZE(paramWarnings); i++) {
|
static WarningBehavior getWarningBehavior(WarningID id) {
|
||||||
uint8_t maxParam = paramWarnings[i].nbLevels;
|
// Check if warnings are globally disabled
|
||||||
|
if (!warnings) {
|
||||||
if (!strcmp(flag, paramWarnings[i].name)) { // Match!
|
return WarningBehavior::DISABLED;
|
||||||
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
|
// Get the state of this warning flag
|
||||||
for (uint8_t ofs = 0; ofs < maxParam; ofs++) {
|
WarningState const &flagState = warningStates.flagStates[id];
|
||||||
warningStates[baseID + ofs] = ofs < param ? state : WARNING_DISABLED;
|
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;
|
||||||
}
|
}
|
||||||
return true;
|
if (flagState.error == WARNING_ENABLED) { // -Werror=<flag>
|
||||||
|
return WarningBehavior::ERROR;
|
||||||
|
}
|
||||||
|
if (flagState.state == WARNING_ENABLED) { // -W<flag>
|
||||||
|
return enabledBehavior;
|
||||||
}
|
}
|
||||||
|
|
||||||
baseID = (WarningID)(baseID + maxParam);
|
// 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;
|
||||||
}
|
}
|
||||||
return false;
|
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 };
|
void WarningState::update(WarningState other) {
|
||||||
|
if (other.state != WARNING_DEFAULT) {
|
||||||
// Warnings that probably indicate an error
|
state = other.state;
|
||||||
static uint8_t const _wallCommands[] = {
|
}
|
||||||
WARNING_BACKWARDS_FOR,
|
if (other.error != WARNING_DEFAULT) {
|
||||||
WARNING_BUILTIN_ARG,
|
error = other.error;
|
||||||
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 processWarningFlag(char const *flag) {
|
void processWarningFlag(char const *flag) {
|
||||||
static bool setError = false;
|
std::string rootFlag = flag;
|
||||||
|
|
||||||
// First, try to match against a "meta" warning
|
// Check for `-Werror` or `-Wno-error` to return early
|
||||||
for (WarningID id : EnumSeq(META_WARNINGS_START, NB_WARNINGS)) {
|
if (rootFlag == "error") {
|
||||||
// TODO: improve the matching performance?
|
// `-Werror` promotes warnings to errors
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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;
|
warningsAreErrors = true;
|
||||||
return;
|
return;
|
||||||
|
} else if (rootFlag == "no-error") {
|
||||||
case '=':
|
// `-Wno-error` disables promotion of warnings to errors
|
||||||
// `-Werror=XXX`
|
warningsAreErrors = false;
|
||||||
setError = true;
|
|
||||||
processWarningFlag(errorFlag + 1); // Skip the `=`
|
|
||||||
setError = false;
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Otherwise, allow parsing as another flag
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Well, it's either a normal warning or a mistake
|
// 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};
|
||||||
|
}
|
||||||
|
|
||||||
WarningState state = setError ? WARNING_ERROR
|
// Check for an `=` parameter to process as a parametric warning
|
||||||
// Not an error, then check if this is a negation
|
// `-Wno-<flag>` and `-Wno-error=<flag>` negation cannot have an `=` parameter, but without a
|
||||||
: strncmp(flag, "no-", QUOTEDSTRLEN("no-")) ? WARNING_ENABLED
|
// parameter, the 0 value will apply to all levels of a parametric warning
|
||||||
: WARNING_DISABLED;
|
uint8_t param = 0;
|
||||||
char const *rootFlag = state == WARNING_DISABLED ? flag + QUOTEDSTRLEN("no-") : flag;
|
bool hasParam = false;
|
||||||
|
if (state.state == WARNING_ENABLED) {
|
||||||
// Is this a "parametric" warning?
|
|
||||||
if (state != WARNING_DISABLED) { // The `no-` form cannot be parametrized
|
|
||||||
// First, check if there is an "equals" sign followed by a decimal number
|
// 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?
|
// Is the rest of the string a decimal number?
|
||||||
// We want to avoid `strtoul`'s whitespace and sign, so we parse manually
|
// We want to avoid `strtoul`'s whitespace and sign, so we parse manually
|
||||||
uint8_t param = 0;
|
char const *ptr = rootFlag.c_str() + equals + 1;
|
||||||
char const *ptr = equals + 1;
|
|
||||||
bool warned = false;
|
bool warned = false;
|
||||||
|
|
||||||
// The `if`'s condition above ensures that this will run at least once
|
// The `if`'s condition above ensures that this will run at least once
|
||||||
do {
|
do {
|
||||||
// If we don't have a digit, bail
|
// If we don't have a digit, bail
|
||||||
if (*ptr < '0' || *ptr > '9')
|
if (*ptr < '0' || *ptr > '9') {
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
// Avoid overflowing!
|
// Avoid overflowing!
|
||||||
if (param > UINT8_MAX - (*ptr - '0')) {
|
if (param > UINT8_MAX - (*ptr - '0')) {
|
||||||
if (!warned)
|
if (!warned) {
|
||||||
warnx("Invalid warning flag \"%s\": capping parameter at 255\n", flag);
|
warnx("Invalid warning flag \"%s\": capping parameter at 255", flag);
|
||||||
|
}
|
||||||
warned = true; // Only warn once, cap always
|
warned = true; // Only warn once, cap always
|
||||||
param = 255;
|
param = 255;
|
||||||
continue;
|
continue;
|
||||||
@@ -299,40 +211,88 @@ void processWarningFlag(char const *flag) {
|
|||||||
ptr++;
|
ptr++;
|
||||||
} while (*ptr);
|
} while (*ptr);
|
||||||
|
|
||||||
// If we managed to the end of the string, check that the warning indeed
|
// If we reached the end of the string, truncate it at the '='
|
||||||
// accepts a parameter
|
|
||||||
if (*ptr == '\0') {
|
if (*ptr == '\0') {
|
||||||
if (setError && param == 0) {
|
rootFlag.resize(equals);
|
||||||
warnx("Ignoring nonsensical warning flag \"%s\"\n", flag);
|
// `-W<flag>=0` is equivalent to `-Wno-<flag>`
|
||||||
return;
|
if (param == 0) {
|
||||||
|
state.state = WARNING_DISABLED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string truncFlag = rootFlag;
|
// Try to match the flag against a parametric warning
|
||||||
|
// If there was an equals sign, it will have set `param`; if not, `param` will be 0, which
|
||||||
|
// applies to all levels
|
||||||
|
for (auto const ¶mWarning : paramWarnings) {
|
||||||
|
WarningID baseID = paramWarning.firstID;
|
||||||
|
uint8_t maxParam = paramWarning.lastID - baseID + 1;
|
||||||
|
assume(paramWarning.defaultLevel <= maxParam);
|
||||||
|
|
||||||
truncFlag.resize(equals - rootFlag); // Truncate the param at the '='
|
if (rootFlag == warningFlags[baseID].name) { // Match!
|
||||||
if (tryProcessParamWarning(
|
if (rootFlag == "numeric-string") {
|
||||||
truncFlag.c_str(), param, param == 0 ? WARNING_DISABLED : state
|
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;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Try to match against a non-parametric warning, unless there was an equals sign
|
||||||
|
if (!hasParam) {
|
||||||
|
// Try to match against a "meta" warning
|
||||||
|
for (WarningFlag const &metaWarning : metaWarnings) {
|
||||||
|
if (rootFlag == metaWarning.name) {
|
||||||
|
// Set each of the warning flags that meets this level
|
||||||
|
for (WarningID id : EnumSeq(NB_WARNINGS)) {
|
||||||
|
if (metaWarning.level >= warningFlags[id].level) {
|
||||||
|
warningStates.metaStates[id].update(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to match the flag against a "normal" flag
|
// Try to match the flag against a "normal" flag
|
||||||
for (WarningID id : EnumSeq(NB_PLAIN_WARNINGS)) {
|
for (WarningID id : EnumSeq(NB_PLAIN_WARNINGS)) {
|
||||||
if (!strcmp(rootFlag, warningFlags[id])) {
|
if (rootFlag == warningFlags[id].name) {
|
||||||
// We got a match!
|
warningStates.flagStates[id].update(state);
|
||||||
warningStates[id] = state;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Lastly, this might be a "parametric" warning without an equals sign
|
warnx("Unknown warning flag \"%s\"", flag);
|
||||||
// If it is, treat the param as 1 if enabling, or 0 if disabling
|
|
||||||
if (tryProcessParamWarning(rootFlag, 0, state))
|
|
||||||
return;
|
|
||||||
|
|
||||||
warnx("Unknown warning `%s`", flag);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void printDiag(
|
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)")
|
// This intentionally makes 0 act as "unlimited" (or at least "limited to sizeof(unsigned)")
|
||||||
nbErrors++;
|
nbErrors++;
|
||||||
if (nbErrors == maxErrors)
|
if (nbErrors == maxErrors) {
|
||||||
errx(
|
errx(
|
||||||
"The maximum of %u error%s was reached (configure with \"-X/--max-errors\"); assembly "
|
"The maximum of %u error%s was reached (configure with \"-X/--max-errors\"); assembly "
|
||||||
"aborted!",
|
"aborted!",
|
||||||
maxErrors,
|
maxErrors,
|
||||||
maxErrors == 1 ? "" : "s"
|
maxErrors == 1 ? "" : "s"
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[[noreturn]] void fatalerror(char const *fmt, ...) {
|
[[noreturn]]
|
||||||
|
void fatalerror(char const *fmt, ...) {
|
||||||
va_list args;
|
va_list args;
|
||||||
|
|
||||||
va_start(args, fmt);
|
va_start(args, fmt);
|
||||||
@@ -376,26 +338,22 @@ void error(char const *fmt, ...) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void warning(WarningID id, 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_list args;
|
||||||
|
|
||||||
va_start(args, fmt);
|
va_start(args, fmt);
|
||||||
|
|
||||||
switch (warningState(id)) {
|
switch (getWarningBehavior(id)) {
|
||||||
case WARNING_DISABLED:
|
case WarningBehavior::DISABLED:
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case WARNING_ENABLED:
|
case WarningBehavior::ENABLED:
|
||||||
printDiag(fmt, args, "warning", ": [-W%s]", flag);
|
printDiag(fmt, args, "warning", ": [-W%s]", flag);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case WARNING_ERROR:
|
case WarningBehavior::ERROR:
|
||||||
printDiag(fmt, args, "error", ": [-Werror=%s]", flag);
|
printDiag(fmt, args, "error", ": [-Werror=%s]", flag);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case WARNING_DEFAULT:
|
|
||||||
unreachable_();
|
|
||||||
// Not reached
|
|
||||||
}
|
}
|
||||||
|
|
||||||
va_end(args);
|
va_end(args);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#include "error.hpp"
|
#include "error.hpp"
|
||||||
|
|
||||||
@@ -22,7 +22,8 @@ static void vwarnx(char const *fmt, va_list ap) {
|
|||||||
putc('\n', stderr);
|
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);
|
char const *error = strerror(errno);
|
||||||
|
|
||||||
fprintf(stderr, "error: ");
|
fprintf(stderr, "error: ");
|
||||||
@@ -32,7 +33,8 @@ static void vwarnx(char const *fmt, va_list ap) {
|
|||||||
exit(1);
|
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: ");
|
fprintf(stderr, "error: ");
|
||||||
vfprintf(stderr, fmt, ap);
|
vfprintf(stderr, fmt, ap);
|
||||||
putc('\n', stderr);
|
putc('\n', stderr);
|
||||||
@@ -56,14 +58,16 @@ void warnx(char const *fmt, ...) {
|
|||||||
va_end(ap);
|
va_end(ap);
|
||||||
}
|
}
|
||||||
|
|
||||||
[[noreturn]] void err(char const *fmt, ...) {
|
[[noreturn]]
|
||||||
|
void err(char const *fmt, ...) {
|
||||||
va_list ap;
|
va_list ap;
|
||||||
|
|
||||||
va_start(ap, fmt);
|
va_start(ap, fmt);
|
||||||
verr(fmt, ap);
|
verr(fmt, ap);
|
||||||
}
|
}
|
||||||
|
|
||||||
[[noreturn]] void errx(char const *fmt, ...) {
|
[[noreturn]]
|
||||||
|
void errx(char const *fmt, ...) {
|
||||||
va_list ap;
|
va_list ap;
|
||||||
|
|
||||||
va_start(ap, fmt);
|
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"
|
#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) {
|
static void musl_getopt_msg(char const *a, char const *b, char const *c, size_t l) {
|
||||||
FILE *f = stderr;
|
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);
|
putc('\n', f);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static int getopt(int argc, char *argv[], char const *optstring) {
|
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;
|
musl_optind = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (musl_optind >= argc || !argv[musl_optind])
|
if (musl_optind >= argc || !argv[musl_optind]) {
|
||||||
return -1;
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
if (argv[musl_optind][0] != '-') {
|
if (argv[musl_optind][0] != '-') {
|
||||||
if (optstring[0] == '-') {
|
if (optstring[0] == '-') {
|
||||||
@@ -46,18 +48,21 @@ static int getopt(int argc, char *argv[], char const *optstring) {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!argv[musl_optind][1])
|
if (!argv[musl_optind][1]) {
|
||||||
return -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;
|
return musl_optind++, -1;
|
||||||
|
}
|
||||||
|
|
||||||
if (!musl_optpos)
|
if (!musl_optpos) {
|
||||||
musl_optpos++;
|
musl_optpos++;
|
||||||
|
}
|
||||||
k = mbtowc(&c, argv[musl_optind] + musl_optpos, MB_LEN_MAX);
|
k = mbtowc(&c, argv[musl_optind] + musl_optpos, MB_LEN_MAX);
|
||||||
if (k < 0) {
|
if (k < 0) {
|
||||||
k = 1;
|
k = 1;
|
||||||
c = 0xFFFD; /* replacement char */
|
c = 0xFFFD; // replacement char
|
||||||
}
|
}
|
||||||
optchar = argv[musl_optind] + musl_optpos;
|
optchar = argv[musl_optind] + musl_optpos;
|
||||||
musl_optpos += k;
|
musl_optpos += k;
|
||||||
@@ -67,23 +72,26 @@ static int getopt(int argc, char *argv[], char const *optstring) {
|
|||||||
musl_optpos = 0;
|
musl_optpos = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (optstring[0] == '-' || optstring[0] == '+')
|
if (optstring[0] == '-' || optstring[0] == '+') {
|
||||||
optstring++;
|
optstring++;
|
||||||
|
}
|
||||||
|
|
||||||
i = 0;
|
i = 0;
|
||||||
d = 0;
|
d = 0;
|
||||||
do {
|
do {
|
||||||
l = mbtowc(&d, optstring + i, MB_LEN_MAX);
|
l = mbtowc(&d, optstring + i, MB_LEN_MAX);
|
||||||
if (l > 0)
|
if (l > 0) {
|
||||||
i += l;
|
i += l;
|
||||||
else
|
} else {
|
||||||
i++;
|
i++;
|
||||||
|
}
|
||||||
} while (l && d != c);
|
} while (l && d != c);
|
||||||
|
|
||||||
if (d != c || c == ':') {
|
if (d != c || c == ':') {
|
||||||
musl_optopt = c;
|
musl_optopt = c;
|
||||||
if (optstring[0] != ':' && musl_opterr)
|
if (optstring[0] != ':' && musl_opterr) {
|
||||||
musl_getopt_msg(argv[0], ": unrecognized option: ", optchar, k);
|
musl_getopt_msg(argv[0], ": unrecognized option: ", optchar, k);
|
||||||
|
}
|
||||||
return '?';
|
return '?';
|
||||||
}
|
}
|
||||||
if (optstring[i] == ':') {
|
if (optstring[i] == ':') {
|
||||||
@@ -94,10 +102,12 @@ static int getopt(int argc, char *argv[], char const *optstring) {
|
|||||||
}
|
}
|
||||||
if (musl_optind > argc) {
|
if (musl_optind > argc) {
|
||||||
musl_optopt = c;
|
musl_optopt = c;
|
||||||
if (optstring[0] == ':')
|
if (optstring[0] == ':') {
|
||||||
return ':';
|
return ':';
|
||||||
if (musl_opterr)
|
}
|
||||||
|
if (musl_opterr) {
|
||||||
musl_getopt_msg(argv[0], ": option requires an argument: ", optchar, k);
|
musl_getopt_msg(argv[0], ": option requires an argument: ", optchar, k);
|
||||||
|
}
|
||||||
return '?';
|
return '?';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -108,8 +118,9 @@ static void permute(char **argv, int dest, int src) {
|
|||||||
char *tmp = argv[src];
|
char *tmp = argv[src];
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
for (i = src; i > dest; i--)
|
for (i = src; i > dest; i--) {
|
||||||
argv[i] = argv[i - 1];
|
argv[i] = argv[i - 1];
|
||||||
|
}
|
||||||
argv[dest] = tmp;
|
argv[dest] = tmp;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,18 +139,21 @@ static int musl_getopt_long(
|
|||||||
musl_optind = 1;
|
musl_optind = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (musl_optind >= argc || !argv[musl_optind])
|
if (musl_optind >= argc || !argv[musl_optind]) {
|
||||||
return -1;
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
skipped = musl_optind;
|
skipped = musl_optind;
|
||||||
if (optstring[0] != '+' && optstring[0] != '-') {
|
if (optstring[0] != '+' && optstring[0] != '-') {
|
||||||
int i;
|
int i;
|
||||||
for (i = musl_optind;; i++) {
|
for (i = musl_optind;; i++) {
|
||||||
if (i >= argc || !argv[i])
|
if (i >= argc || !argv[i]) {
|
||||||
return -1;
|
return -1;
|
||||||
if (argv[i][0] == '-' && argv[i][1])
|
}
|
||||||
|
if (argv[i][0] == '-' && argv[i][1]) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
musl_optind = i;
|
musl_optind = i;
|
||||||
}
|
}
|
||||||
resumed = musl_optind;
|
resumed = musl_optind;
|
||||||
@@ -147,8 +161,9 @@ static int musl_getopt_long(
|
|||||||
if (resumed > skipped) {
|
if (resumed > skipped) {
|
||||||
int i, cnt = musl_optind - resumed;
|
int i, cnt = musl_optind - resumed;
|
||||||
|
|
||||||
for (i = 0; i < cnt; i++)
|
for (i = 0; i < cnt; i++) {
|
||||||
permute(argv, skipped, musl_optind - 1);
|
permute(argv, skipped, musl_optind - 1);
|
||||||
|
}
|
||||||
musl_optind = skipped + cnt;
|
musl_optind = skipped + cnt;
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
@@ -169,14 +184,16 @@ static int musl_getopt_long_core(
|
|||||||
char const *name = longopts[i].name;
|
char const *name = longopts[i].name;
|
||||||
|
|
||||||
opt = start;
|
opt = start;
|
||||||
if (*opt == '-')
|
if (*opt == '-') {
|
||||||
opt++;
|
opt++;
|
||||||
|
}
|
||||||
while (*opt && *opt != '=' && *opt == *name) {
|
while (*opt && *opt != '=' && *opt == *name) {
|
||||||
name++;
|
name++;
|
||||||
opt++;
|
opt++;
|
||||||
}
|
}
|
||||||
if (*opt && *opt != '=')
|
if (*opt && *opt != '=') {
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
arg = opt;
|
arg = opt;
|
||||||
match = i;
|
match = i;
|
||||||
if (!*name) {
|
if (!*name) {
|
||||||
@@ -191,8 +208,9 @@ static int musl_getopt_long_core(
|
|||||||
for (i = 0; optstring[i]; i++) {
|
for (i = 0; optstring[i]; i++) {
|
||||||
int j = 0;
|
int j = 0;
|
||||||
|
|
||||||
while (j < l && start[j] == optstring[i + j])
|
while (j < l && start[j] == optstring[i + j]) {
|
||||||
j++;
|
j++;
|
||||||
|
}
|
||||||
if (j == l) {
|
if (j == l) {
|
||||||
cnt++;
|
cnt++;
|
||||||
break;
|
break;
|
||||||
@@ -206,8 +224,9 @@ static int musl_getopt_long_core(
|
|||||||
if (*opt == '=') {
|
if (*opt == '=') {
|
||||||
if (!longopts[i].has_arg) {
|
if (!longopts[i].has_arg) {
|
||||||
musl_optopt = longopts[i].val;
|
musl_optopt = longopts[i].val;
|
||||||
if (colon || !musl_opterr)
|
if (colon || !musl_opterr) {
|
||||||
return '?';
|
return '?';
|
||||||
|
}
|
||||||
musl_getopt_msg(
|
musl_getopt_msg(
|
||||||
argv[0],
|
argv[0],
|
||||||
": option does not take an argument: ",
|
": option does not take an argument: ",
|
||||||
@@ -221,10 +240,12 @@ static int musl_getopt_long_core(
|
|||||||
musl_optarg = argv[musl_optind];
|
musl_optarg = argv[musl_optind];
|
||||||
if (!musl_optarg) {
|
if (!musl_optarg) {
|
||||||
musl_optopt = longopts[i].val;
|
musl_optopt = longopts[i].val;
|
||||||
if (colon)
|
if (colon) {
|
||||||
return ':';
|
return ':';
|
||||||
if (!musl_opterr)
|
}
|
||||||
|
if (!musl_opterr) {
|
||||||
return '?';
|
return '?';
|
||||||
|
}
|
||||||
musl_getopt_msg(
|
musl_getopt_msg(
|
||||||
argv[0],
|
argv[0],
|
||||||
": option requires an argument: ",
|
": option requires an argument: ",
|
||||||
@@ -235,8 +256,9 @@ static int musl_getopt_long_core(
|
|||||||
}
|
}
|
||||||
musl_optind++;
|
musl_optind++;
|
||||||
}
|
}
|
||||||
if (idx)
|
if (idx) {
|
||||||
*idx = i;
|
*idx = i;
|
||||||
|
}
|
||||||
if (longopts[i].flag) {
|
if (longopts[i].flag) {
|
||||||
*longopts[i].flag = longopts[i].val;
|
*longopts[i].flag = longopts[i].val;
|
||||||
return 0;
|
return 0;
|
||||||
@@ -245,13 +267,14 @@ static int musl_getopt_long_core(
|
|||||||
}
|
}
|
||||||
if (argv[musl_optind][1] == '-') {
|
if (argv[musl_optind][1] == '-') {
|
||||||
musl_optopt = 0;
|
musl_optopt = 0;
|
||||||
if (!colon && musl_opterr)
|
if (!colon && musl_opterr) {
|
||||||
musl_getopt_msg(
|
musl_getopt_msg(
|
||||||
argv[0],
|
argv[0],
|
||||||
cnt ? ": option is ambiguous: " : ": unrecognized option: ",
|
cnt ? ": option is ambiguous: " : ": unrecognized option: ",
|
||||||
argv[musl_optind] + 2,
|
argv[musl_optind] + 2,
|
||||||
strlen(argv[musl_optind] + 2)
|
strlen(argv[musl_optind] + 2)
|
||||||
);
|
);
|
||||||
|
}
|
||||||
musl_optind++;
|
musl_optind++;
|
||||||
return '?';
|
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"
|
#include "extern/utf8decoder.hpp"
|
||||||
|
|
||||||
static uint8_t const utf8d[] = {
|
static uint8_t const utf8d[] = {
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 00..0f */
|
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, // 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, // 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, // 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, // 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, // 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, // 60..6f
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 70..7f */
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 70..7f
|
||||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 80..8f */
|
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 */
|
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, // a0..af
|
||||||
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, /* b0..bf */
|
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 */
|
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 */
|
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // d0..df
|
||||||
10, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 3, /* e0..ef */
|
10, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 3, // e0..ef
|
||||||
11, 6, 6, 6, 5, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, /* f0..ff */
|
11, 6, 6, 6, 5, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, // f0..ff
|
||||||
0, 1, 2, 3, 5, 8, 7, 1, 1, 1, 4, 6, 1, 1, 1, 1, /* s0 */
|
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, 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, 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, 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, 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, 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, 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, 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 */
|
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) {
|
uint32_t decode(uint32_t *state, uint32_t *codep, uint8_t byte) {
|
||||||
|
|||||||
361
src/fix/main.cpp
361
src/fix/main.cpp
@@ -1,4 +1,4 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
@@ -17,27 +17,26 @@
|
|||||||
#include "platform.hpp"
|
#include "platform.hpp"
|
||||||
#include "version.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
|
// 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
|
||||||
* 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,
|
||||||
* Also, make sure long opts don't create ambiguity:
|
// except if it doesn't create any ambiguity (`verbose` versus `version`).
|
||||||
* A long opt's name should start with the same letter as its short opt,
|
// This is because long opt matching, even to a single char, is prioritized
|
||||||
* except if it doesn't create any ambiguity (`verbose` versus `version`).
|
// over short opt matching.
|
||||||
* This is because long opt matching, even to a single char, is prioritized
|
|
||||||
* over short opt matching
|
|
||||||
*/
|
|
||||||
static option const longopts[] = {
|
static option const longopts[] = {
|
||||||
{"color-only", no_argument, nullptr, 'C'},
|
{"color-only", no_argument, nullptr, 'C'},
|
||||||
{"color-compatible", no_argument, nullptr, 'c'},
|
{"color-compatible", no_argument, nullptr, 'c'},
|
||||||
{"fix-spec", required_argument, nullptr, 'f'},
|
{"fix-spec", required_argument, nullptr, 'f'},
|
||||||
|
{"help", no_argument, nullptr, 'h'},
|
||||||
{"game-id", required_argument, nullptr, 'i'},
|
{"game-id", required_argument, nullptr, 'i'},
|
||||||
{"non-japanese", no_argument, nullptr, 'j'},
|
{"non-japanese", no_argument, nullptr, 'j'},
|
||||||
{"new-licensee", required_argument, nullptr, 'k'},
|
{"new-licensee", required_argument, nullptr, 'k'},
|
||||||
@@ -57,7 +56,7 @@ static option const longopts[] = {
|
|||||||
|
|
||||||
static void printUsage() {
|
static void printUsage() {
|
||||||
fputs(
|
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"
|
" [-L <logo_file>] [-l <licensee_byte>] [-m <mbc_type>]\n"
|
||||||
" [-n <rom_version>] [-p <pad_value>] [-r <ram_size>] [-t <title_str>]\n"
|
" [-n <rom_version>] [-p <pad_value>] [-r <ram_size>] [-t <title_str>]\n"
|
||||||
" <file> ...\n"
|
" <file> ...\n"
|
||||||
@@ -76,15 +75,17 @@ static void printUsage() {
|
|||||||
|
|
||||||
static uint8_t nbErrors;
|
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_list ap;
|
||||||
|
|
||||||
va_start(ap, fmt);
|
va_start(ap, fmt);
|
||||||
vfprintf(stderr, fmt, ap);
|
vfprintf(stderr, fmt, ap);
|
||||||
va_end(ap);
|
va_end(ap);
|
||||||
|
|
||||||
if (nbErrors != UINT8_MAX)
|
if (nbErrors != UINT8_MAX) {
|
||||||
nbErrors++;
|
nbErrors++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum MbcType {
|
enum MbcType {
|
||||||
@@ -184,24 +185,24 @@ static void printAcceptedMBCNames() {
|
|||||||
|
|
||||||
static uint8_t tpp1Rev[2];
|
static uint8_t tpp1Rev[2];
|
||||||
|
|
||||||
/*
|
|
||||||
* @return False on failure
|
|
||||||
*/
|
|
||||||
static bool readMBCSlice(char const *&name, char const *expected) {
|
static bool readMBCSlice(char const *&name, char const *expected) {
|
||||||
while (*expected) {
|
while (*expected) {
|
||||||
char c = *name++;
|
char c = *name++;
|
||||||
|
|
||||||
if (c == '\0') // Name too short
|
if (c == '\0') { // Name too short
|
||||||
return false;
|
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';
|
c = c - 'a' + 'A';
|
||||||
else if (c == '_') // Treat underscores as spaces
|
} else if (c == '_') { // Treat underscores as spaces
|
||||||
c = ' ';
|
c = ' ';
|
||||||
|
}
|
||||||
|
|
||||||
if (c != *expected++)
|
if (c != *expected++) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,11 +224,13 @@ static MbcType parseMBC(char const *name) {
|
|||||||
char *endptr;
|
char *endptr;
|
||||||
unsigned long mbc = strtoul(name, &endptr, base);
|
unsigned long mbc = strtoul(name, &endptr, base);
|
||||||
|
|
||||||
if (*endptr)
|
if (*endptr) {
|
||||||
return MBC_BAD;
|
return MBC_BAD;
|
||||||
if (mbc > 0xFF)
|
}
|
||||||
|
if (mbc > 0xFF) {
|
||||||
return MBC_BAD_RANGE;
|
return MBC_BAD_RANGE;
|
||||||
return (MbcType)mbc;
|
}
|
||||||
|
return static_cast<MbcType>(mbc);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// Begin by reading the MBC type:
|
// Begin by reading the MBC type:
|
||||||
@@ -235,13 +238,15 @@ static MbcType parseMBC(char const *name) {
|
|||||||
char const *ptr = name;
|
char const *ptr = name;
|
||||||
|
|
||||||
// Trim off leading whitespace
|
// Trim off leading whitespace
|
||||||
while (*ptr == ' ' || *ptr == '\t')
|
while (*ptr == ' ' || *ptr == '\t') {
|
||||||
ptr++;
|
ptr++;
|
||||||
|
}
|
||||||
|
|
||||||
#define tryReadSlice(expected) \
|
#define tryReadSlice(expected) \
|
||||||
do { \
|
do { \
|
||||||
if (!readMBCSlice(ptr, expected)) \
|
if (!readMBCSlice(ptr, expected)) { \
|
||||||
return MBC_BAD; \
|
return MBC_BAD; \
|
||||||
|
} \
|
||||||
} while (0)
|
} while (0)
|
||||||
|
|
||||||
switch (*ptr++) {
|
switch (*ptr++) {
|
||||||
@@ -249,8 +254,9 @@ static MbcType parseMBC(char const *name) {
|
|||||||
case 'r':
|
case 'r':
|
||||||
tryReadSlice("OM");
|
tryReadSlice("OM");
|
||||||
// Handle optional " ONLY"
|
// Handle optional " ONLY"
|
||||||
while (*ptr == ' ' || *ptr == '\t' || *ptr == '_')
|
while (*ptr == ' ' || *ptr == '\t' || *ptr == '_') {
|
||||||
ptr++;
|
ptr++;
|
||||||
|
}
|
||||||
if (*ptr == 'O' || *ptr == 'o') {
|
if (*ptr == 'O' || *ptr == 'o') {
|
||||||
ptr++;
|
ptr++;
|
||||||
tryReadSlice("NLY");
|
tryReadSlice("NLY");
|
||||||
@@ -325,8 +331,9 @@ static MbcType parseMBC(char const *name) {
|
|||||||
case 'P': {
|
case 'P': {
|
||||||
tryReadSlice("P1");
|
tryReadSlice("P1");
|
||||||
// Parse version
|
// Parse version
|
||||||
while (*ptr == ' ' || *ptr == '_')
|
while (*ptr == ' ' || *ptr == '_') {
|
||||||
ptr++;
|
ptr++;
|
||||||
|
}
|
||||||
// Major
|
// Major
|
||||||
char *endptr;
|
char *endptr;
|
||||||
unsigned long val = strtoul(ptr, &endptr, 10);
|
unsigned long val = strtoul(ptr, &endptr, 10);
|
||||||
@@ -383,27 +390,33 @@ static MbcType parseMBC(char const *name) {
|
|||||||
|
|
||||||
// Read "additional features"
|
// Read "additional features"
|
||||||
uint8_t features = 0;
|
uint8_t features = 0;
|
||||||
#define RAM (1 << 7)
|
// clang-format off: vertically align values
|
||||||
#define BATTERY (1 << 6)
|
static constexpr uint8_t RAM = 1 << 7;
|
||||||
#define TIMER (1 << 5)
|
static constexpr uint8_t BATTERY = 1 << 6;
|
||||||
#define RUMBLE (1 << 4)
|
static constexpr uint8_t TIMER = 1 << 5;
|
||||||
#define SENSOR (1 << 3)
|
static constexpr uint8_t RUMBLE = 1 << 4;
|
||||||
#define MULTIRUMBLE (1 << 2)
|
static constexpr uint8_t SENSOR = 1 << 3;
|
||||||
|
static constexpr uint8_t MULTIRUMBLE = 1 << 2;
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
// Trim off trailing whitespace
|
// Trim off trailing whitespace
|
||||||
while (*ptr == ' ' || *ptr == '\t' || *ptr == '_')
|
while (*ptr == ' ' || *ptr == '\t' || *ptr == '_') {
|
||||||
ptr++;
|
ptr++;
|
||||||
|
}
|
||||||
|
|
||||||
// If done, start processing "features"
|
// If done, start processing "features"
|
||||||
if (!*ptr)
|
if (!*ptr) {
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
// We expect a '+' at this point
|
// We expect a '+' at this point
|
||||||
if (*ptr++ != '+')
|
if (*ptr++ != '+') {
|
||||||
return MBC_BAD;
|
return MBC_BAD;
|
||||||
|
}
|
||||||
// Trim off leading whitespace
|
// Trim off leading whitespace
|
||||||
while (*ptr == ' ' || *ptr == '\t' || *ptr == '_')
|
while (*ptr == ' ' || *ptr == '\t' || *ptr == '_') {
|
||||||
ptr++;
|
ptr++;
|
||||||
|
}
|
||||||
|
|
||||||
switch (*ptr++) {
|
switch (*ptr++) {
|
||||||
case 'B': // BATTERY
|
case 'B': // BATTERY
|
||||||
@@ -428,8 +441,9 @@ static MbcType parseMBC(char const *name) {
|
|||||||
break;
|
break;
|
||||||
case 'A':
|
case 'A':
|
||||||
case 'a':
|
case 'a':
|
||||||
if (*ptr != 'M' && *ptr != 'm')
|
if (*ptr != 'M' && *ptr != 'm') {
|
||||||
return MBC_BAD;
|
return MBC_BAD;
|
||||||
|
}
|
||||||
ptr++;
|
ptr++;
|
||||||
features |= RAM;
|
features |= RAM;
|
||||||
break;
|
break;
|
||||||
@@ -458,8 +472,9 @@ static MbcType parseMBC(char const *name) {
|
|||||||
|
|
||||||
switch (mbc) {
|
switch (mbc) {
|
||||||
case ROM:
|
case ROM:
|
||||||
if (!features)
|
if (!features) {
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
mbc = ROM_RAM - 1;
|
mbc = ROM_RAM - 1;
|
||||||
static_assert(ROM_RAM + 1 == ROM_RAM_BATTERY, "Enum sanity check failed!");
|
static_assert(ROM_RAM + 1 == ROM_RAM_BATTERY, "Enum sanity check failed!");
|
||||||
static_assert(MBC1 + 1 == MBC1_RAM, "Enum sanity check failed!");
|
static_assert(MBC1 + 1 == MBC1_RAM, "Enum sanity check failed!");
|
||||||
@@ -469,26 +484,29 @@ static MbcType parseMBC(char const *name) {
|
|||||||
[[fallthrough]];
|
[[fallthrough]];
|
||||||
case MBC1:
|
case MBC1:
|
||||||
case MMM01:
|
case MMM01:
|
||||||
if (features == RAM)
|
if (features == RAM) {
|
||||||
mbc++;
|
mbc++;
|
||||||
else if (features == (RAM | BATTERY))
|
} else if (features == (RAM | BATTERY)) {
|
||||||
mbc += 2;
|
mbc += 2;
|
||||||
else if (features)
|
} else if (features) {
|
||||||
return MBC_WRONG_FEATURES;
|
return MBC_WRONG_FEATURES;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MBC2:
|
case MBC2:
|
||||||
if (features == BATTERY)
|
if (features == BATTERY) {
|
||||||
mbc = MBC2_BATTERY;
|
mbc = MBC2_BATTERY;
|
||||||
else if (features)
|
} else if (features) {
|
||||||
return MBC_WRONG_FEATURES;
|
return MBC_WRONG_FEATURES;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MBC3:
|
case MBC3:
|
||||||
// Handle timer, which also requires battery
|
// Handle timer, which also requires battery
|
||||||
if (features & TIMER) {
|
if (features & TIMER) {
|
||||||
if (!(features & BATTERY))
|
if (!(features & BATTERY)) {
|
||||||
fprintf(stderr, "warning: MBC3+TIMER implies BATTERY\n");
|
fprintf(stderr, "warning: MBC3+TIMER implies BATTERY\n");
|
||||||
|
}
|
||||||
features &= ~(TIMER | BATTERY); // Reset those bits
|
features &= ~(TIMER | BATTERY); // Reset those bits
|
||||||
mbc = MBC3_TIMER_BATTERY;
|
mbc = MBC3_TIMER_BATTERY;
|
||||||
// RAM is handled below
|
// RAM is handled below
|
||||||
@@ -498,12 +516,13 @@ static MbcType parseMBC(char const *name) {
|
|||||||
static_assert(
|
static_assert(
|
||||||
MBC3_TIMER_BATTERY + 1 == MBC3_TIMER_RAM_BATTERY, "Enum sanity check failed!"
|
MBC3_TIMER_BATTERY + 1 == MBC3_TIMER_RAM_BATTERY, "Enum sanity check failed!"
|
||||||
);
|
);
|
||||||
if (features == RAM)
|
if (features == RAM) {
|
||||||
mbc++;
|
mbc++;
|
||||||
else if (features == (RAM | BATTERY))
|
} else if (features == (RAM | BATTERY)) {
|
||||||
mbc += 2;
|
mbc += 2;
|
||||||
else if (features)
|
} else if (features) {
|
||||||
return MBC_WRONG_FEATURES;
|
return MBC_WRONG_FEATURES;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MBC5:
|
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 + 2 == MBC5_RAM_BATTERY, "Enum sanity check failed!");
|
||||||
static_assert(MBC5_RUMBLE + 1 == MBC5_RUMBLE_RAM, "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!");
|
static_assert(MBC5_RUMBLE + 2 == MBC5_RUMBLE_RAM_BATTERY, "Enum sanity check failed!");
|
||||||
if (features == RAM)
|
if (features == RAM) {
|
||||||
mbc++;
|
mbc++;
|
||||||
else if (features == (RAM | BATTERY))
|
} else if (features == (RAM | BATTERY)) {
|
||||||
mbc += 2;
|
mbc += 2;
|
||||||
else if (features)
|
} else if (features) {
|
||||||
return MBC_WRONG_FEATURES;
|
return MBC_WRONG_FEATURES;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MBC6:
|
case MBC6:
|
||||||
@@ -528,47 +548,58 @@ static MbcType parseMBC(char const *name) {
|
|||||||
case BANDAI_TAMA5:
|
case BANDAI_TAMA5:
|
||||||
case HUC3:
|
case HUC3:
|
||||||
// No extra features accepted
|
// No extra features accepted
|
||||||
if (features)
|
if (features) {
|
||||||
return MBC_WRONG_FEATURES;
|
return MBC_WRONG_FEATURES;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MBC7_SENSOR_RUMBLE_RAM_BATTERY:
|
case MBC7_SENSOR_RUMBLE_RAM_BATTERY:
|
||||||
if (features != (SENSOR | RUMBLE | RAM | BATTERY))
|
if (features != (SENSOR | RUMBLE | RAM | BATTERY)) {
|
||||||
return MBC_WRONG_FEATURES;
|
return MBC_WRONG_FEATURES;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case HUC1_RAM_BATTERY:
|
case HUC1_RAM_BATTERY:
|
||||||
if (features != (RAM | BATTERY)) // HuC1 expects RAM+BATTERY
|
if (features != (RAM | BATTERY)) { // HuC1 expects RAM+BATTERY
|
||||||
return MBC_WRONG_FEATURES;
|
return MBC_WRONG_FEATURES;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case TPP1:
|
case TPP1:
|
||||||
if (features & RAM)
|
if (features & RAM) {
|
||||||
fprintf(
|
fprintf(
|
||||||
stderr, "warning: TPP1 requests RAM implicitly if given a non-zero RAM size"
|
stderr, "warning: TPP1 requests RAM implicitly if given a non-zero RAM size"
|
||||||
);
|
);
|
||||||
if (features & BATTERY)
|
}
|
||||||
|
if (features & BATTERY) {
|
||||||
mbc |= 0x08;
|
mbc |= 0x08;
|
||||||
if (features & TIMER)
|
}
|
||||||
|
if (features & TIMER) {
|
||||||
mbc |= 0x04;
|
mbc |= 0x04;
|
||||||
if (features & MULTIRUMBLE)
|
}
|
||||||
|
if (features & MULTIRUMBLE) {
|
||||||
mbc |= 0x03; // Also set the rumble flag
|
mbc |= 0x03; // Also set the rumble flag
|
||||||
if (features & RUMBLE)
|
}
|
||||||
|
if (features & RUMBLE) {
|
||||||
mbc |= 0x01;
|
mbc |= 0x01;
|
||||||
if (features & SENSOR)
|
}
|
||||||
|
if (features & SENSOR) {
|
||||||
return MBC_WRONG_FEATURES;
|
return MBC_WRONG_FEATURES;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trim off trailing whitespace
|
// Trim off trailing whitespace
|
||||||
while (*ptr == ' ' || *ptr == '\t')
|
while (*ptr == ' ' || *ptr == '\t') {
|
||||||
ptr++;
|
ptr++;
|
||||||
|
}
|
||||||
|
|
||||||
// If there is still something past the whitespace, error out
|
// If there is still something past the whitespace, error out
|
||||||
if (*ptr)
|
if (*ptr) {
|
||||||
return MBC_BAD;
|
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;
|
static uint8_t fixSpec = 0;
|
||||||
#define FIX_LOGO (1 << 7)
|
// clang-format off: vertically align values
|
||||||
#define TRASH_LOGO (1 << 6)
|
static constexpr uint8_t FIX_LOGO = 1 << 7;
|
||||||
#define FIX_HEADER_SUM (1 << 5)
|
static constexpr uint8_t TRASH_LOGO = 1 << 6;
|
||||||
#define TRASH_HEADER_SUM (1 << 4)
|
static constexpr uint8_t FIX_HEADER_SUM = 1 << 5;
|
||||||
#define FIX_GLOBAL_SUM (1 << 3)
|
static constexpr uint8_t TRASH_HEADER_SUM = 1 << 4;
|
||||||
#define TRASH_GLOBAL_SUM (1 << 2)
|
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 enum { DMG, BOTH, CGB } model = DMG; // If DMG, byte is left alone
|
||||||
static char const *gameID = nullptr;
|
static char const *gameID = nullptr;
|
||||||
@@ -778,11 +811,13 @@ static ssize_t readBytes(int fd, uint8_t *buf, size_t len) {
|
|||||||
while (len) {
|
while (len) {
|
||||||
ssize_t ret = read(fd, buf, len);
|
ssize_t ret = read(fd, buf, len);
|
||||||
|
|
||||||
if (ret == -1 && errno != EINTR) // Return errors, unless we only were interrupted
|
if (ret == -1 && errno != EINTR) { // Return errors, unless we only were interrupted
|
||||||
return -1;
|
return -1;
|
||||||
|
}
|
||||||
// EOF reached
|
// EOF reached
|
||||||
if (ret == 0)
|
if (ret == 0) {
|
||||||
return total;
|
return total;
|
||||||
|
}
|
||||||
// If anything was read, accumulate it, and continue
|
// If anything was read, accumulate it, and continue
|
||||||
if (ret != -1) {
|
if (ret != -1) {
|
||||||
total += ret;
|
total += ret;
|
||||||
@@ -803,43 +838,30 @@ static ssize_t writeBytes(int fd, uint8_t *buf, size_t len) {
|
|||||||
while (len) {
|
while (len) {
|
||||||
ssize_t ret = write(fd, buf, len);
|
ssize_t ret = write(fd, buf, len);
|
||||||
|
|
||||||
if (ret == -1 && errno != EINTR) // Return errors, unless we only were interrupted
|
if (ret == -1 && errno != EINTR) { // Return errors, unless we only were interrupted
|
||||||
return -1;
|
return -1;
|
||||||
// EOF reached
|
}
|
||||||
if (ret == 0)
|
// If anything was written, accumulate it, and continue
|
||||||
return total;
|
|
||||||
// If anything was read, accumulate it, and continue
|
|
||||||
if (ret != -1) {
|
if (ret != -1) {
|
||||||
total += ret;
|
total += ret;
|
||||||
len -= ret;
|
len -= ret;
|
||||||
|
buf += ret;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return total;
|
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) {
|
static void overwriteByte(uint8_t *rom0, uint16_t addr, uint8_t fixedByte, char const *areaName) {
|
||||||
uint8_t origByte = rom0[addr];
|
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);
|
fprintf(stderr, "warning: Overwrote a non-zero byte in the %s\n", areaName);
|
||||||
|
}
|
||||||
|
|
||||||
rom0[addr] = fixedByte;
|
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(
|
static void overwriteBytes(
|
||||||
uint8_t *rom0, uint16_t startAddr, uint8_t const *fixed, uint8_t size, char const *areaName
|
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);
|
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) {
|
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
|
// Both of these should be true for seekable files, and neither otherwise
|
||||||
if (input == output)
|
if (input == output) {
|
||||||
assume(fileSize != 0);
|
assume(fileSize != 0);
|
||||||
else
|
} else {
|
||||||
assume(fileSize == 0);
|
assume(fileSize == 0);
|
||||||
|
}
|
||||||
|
|
||||||
uint8_t rom0[BANK_SIZE];
|
uint8_t rom0[BANK_SIZE];
|
||||||
ssize_t rom0Len = readBytes(input, rom0, sizeof(rom0));
|
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(
|
report(
|
||||||
"FATAL: \"%s\" too short, expected at least %jd ($%jx) bytes, got only %jd\n",
|
"FATAL: \"%s\" too short, expected at least %jd ($%jx) bytes, got only %jd\n",
|
||||||
name,
|
name,
|
||||||
(intmax_t)headerSize,
|
static_cast<intmax_t>(headerSize),
|
||||||
(intmax_t)headerSize,
|
static_cast<intmax_t>(headerSize),
|
||||||
(intmax_t)rom0Len
|
static_cast<intmax_t>(rom0Len)
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Accept partial reads if the file contains at least the header
|
// 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");
|
overwriteBytes(rom0, 0x0104, logo, sizeof(logo), logoFilename ? "logo" : "Nintendo logo");
|
||||||
|
}
|
||||||
|
|
||||||
if (title)
|
if (title) {
|
||||||
overwriteBytes(rom0, 0x134, (uint8_t const *)title, titleLen, "title");
|
overwriteBytes(rom0, 0x134, reinterpret_cast<uint8_t const *>(title), titleLen, "title");
|
||||||
|
}
|
||||||
|
|
||||||
if (gameID)
|
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)
|
|
||||||
overwriteBytes(
|
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");
|
overwriteByte(rom0, 0x146, 0x03, "SGB flag");
|
||||||
|
}
|
||||||
|
|
||||||
// If a valid MBC was specified...
|
// If a valid MBC was specified...
|
||||||
if (cartridgeType < MBC_NONE) {
|
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");
|
overwriteBytes(rom0, 0x150, tpp1Rev, sizeof(tpp1Rev), "TPP1 revision number");
|
||||||
|
|
||||||
if (ramSize != UNSPECIFIED)
|
if (ramSize != UNSPECIFIED) {
|
||||||
overwriteByte(rom0, 0x152, ramSize, "RAM size");
|
overwriteByte(rom0, 0x152, ramSize, "RAM size");
|
||||||
|
}
|
||||||
|
|
||||||
overwriteByte(rom0, 0x153, cartridgeType & 0xFF, "TPP1 feature flags");
|
overwriteByte(rom0, 0x153, cartridgeType & 0xFF, "TPP1 feature flags");
|
||||||
} else {
|
} else {
|
||||||
// Regular mappers
|
// Regular mappers
|
||||||
|
|
||||||
if (ramSize != UNSPECIFIED)
|
if (ramSize != UNSPECIFIED) {
|
||||||
overwriteByte(rom0, 0x149, ramSize, "RAM size");
|
overwriteByte(rom0, 0x149, ramSize, "RAM size");
|
||||||
|
|
||||||
if (!japanese)
|
|
||||||
overwriteByte(rom0, 0x14A, 0x01, "destination code");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (oldLicensee != UNSPECIFIED)
|
if (!japanese) {
|
||||||
|
overwriteByte(rom0, 0x14A, 0x01, "destination code");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (oldLicensee != UNSPECIFIED) {
|
||||||
overwriteByte(rom0, 0x14B, oldLicensee, "old licensee code");
|
overwriteByte(rom0, 0x14B, oldLicensee, "old licensee code");
|
||||||
else if (sgb && rom0[0x14B] != 0x33)
|
} else if (sgb && rom0[0x14B] != 0x33) {
|
||||||
fprintf(
|
fprintf(
|
||||||
stderr,
|
stderr,
|
||||||
"warning: SGB compatibility enabled, but old licensee was 0x%02x, not 0x33\n",
|
"warning: SGB compatibility enabled, but old licensee was 0x%02x, not 0x33\n",
|
||||||
rom0[0x14B]
|
rom0[0x14B]
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (romVersion != UNSPECIFIED)
|
if (romVersion != UNSPECIFIED) {
|
||||||
overwriteByte(rom0, 0x14C, romVersion, "mask ROM version number");
|
overwriteByte(rom0, 0x14C, romVersion, "mask ROM version number");
|
||||||
|
}
|
||||||
|
|
||||||
// Remain to be handled the ROM size, and header checksum.
|
// Remain to be handled the ROM size, and header checksum.
|
||||||
// The latter depends on the former, and so will be handled after it.
|
// The latter depends on the former, and so will be handled after it.
|
||||||
@@ -1003,15 +1037,17 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
|
|||||||
nbBanks++;
|
nbBanks++;
|
||||||
|
|
||||||
// Update global checksum, too
|
// Update global checksum, too
|
||||||
for (uint16_t i = 0; i < bankLen; i++)
|
for (uint16_t i = 0; i < bankLen; i++) {
|
||||||
globalSum += romx[totalRomxLen + i];
|
globalSum += romx[totalRomxLen + i];
|
||||||
|
}
|
||||||
totalRomxLen += bankLen;
|
totalRomxLen += bankLen;
|
||||||
}
|
}
|
||||||
// Stop when an incomplete bank has been read
|
// Stop when an incomplete bank has been read
|
||||||
if (bankLen != BANK_SIZE)
|
if (bankLen != BANK_SIZE) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Handle setting the ROM size if padding was requested
|
// Handle setting the ROM size if padding was requested
|
||||||
// Pad to the next valid power of 2. This is because padding is required by flashers, which
|
// Pad to the next valid power of 2. This is because padding is required by flashers, which
|
||||||
@@ -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
|
// 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,
|
// 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
|
// 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));
|
nbBanks = 1 << (CHAR_BIT * sizeof(nbBanks) - clz(nbBanks));
|
||||||
|
}
|
||||||
// Write final ROM size
|
// Write final ROM size
|
||||||
rom0[0x148] = ctz(nbBanks / 2);
|
rom0[0x148] = ctz(nbBanks / 2);
|
||||||
// Alter global checksum based on how many bytes will be added (not counting ROM0)
|
// 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)) {
|
if (fixSpec & (FIX_HEADER_SUM | TRASH_HEADER_SUM)) {
|
||||||
uint8_t sum = 0;
|
uint8_t sum = 0;
|
||||||
|
|
||||||
for (uint16_t i = 0x134; i < 0x14D; i++)
|
for (uint16_t i = 0x134; i < 0x14D; i++) {
|
||||||
sum -= rom0[i] + 1;
|
sum -= rom0[i] + 1;
|
||||||
|
}
|
||||||
|
|
||||||
overwriteByte(rom0, 0x14D, fixSpec & TRASH_HEADER_SUM ? ~sum : sum, "header checksum");
|
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)) {
|
if (fixSpec & (FIX_GLOBAL_SUM | TRASH_GLOBAL_SUM)) {
|
||||||
// Computation of the global checksum does not include the checksum bytes
|
// Computation of the global checksum does not include the checksum bytes
|
||||||
assume(rom0Len >= 0x14E);
|
assume(rom0Len >= 0x14E);
|
||||||
for (uint16_t i = 0; i < 0x14E; i++)
|
for (uint16_t i = 0; i < 0x14E; i++) {
|
||||||
globalSum += rom0[i];
|
globalSum += rom0[i];
|
||||||
for (uint16_t i = 0x150; i < rom0Len; i++)
|
}
|
||||||
|
for (uint16_t i = 0x150; i < rom0Len; i++) {
|
||||||
globalSum += rom0[i];
|
globalSum += rom0[i];
|
||||||
|
}
|
||||||
// Pipes have already read ROMX and updated globalSum, but not regular files
|
// Pipes have already read ROMX and updated globalSum, but not regular files
|
||||||
if (input == output) {
|
if (input == output) {
|
||||||
for (;;) {
|
for (;;) {
|
||||||
ssize_t bankLen = readBytes(input, bank, sizeof(bank));
|
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];
|
globalSum += bank[i];
|
||||||
if (bankLen != sizeof(bank))
|
}
|
||||||
|
if (bankLen != sizeof(bank)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (fixSpec & TRASH_GLOBAL_SUM)
|
if (fixSpec & TRASH_GLOBAL_SUM) {
|
||||||
globalSum = ~globalSum;
|
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");
|
overwriteBytes(rom0, 0x14E, bytes, sizeof(bytes), "global checksum");
|
||||||
}
|
}
|
||||||
@@ -1086,15 +1131,16 @@ 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
|
// In case the output depends on the input, reset to the beginning of the file, and only
|
||||||
// write the header
|
// write the header
|
||||||
if (input == output) {
|
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));
|
report("FATAL: Failed to rewind \"%s\": %s\n", name, strerror(errno));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// If modifying the file in-place, we only need to edit the header
|
// If modifying the file in-place, we only need to edit the header
|
||||||
// However, padding may have modified ROM0 (added padding), so don't in that case
|
// However, padding may have modified ROM0 (added padding), so don't in that case
|
||||||
if (padValue == UNSPECIFIED)
|
if (padValue == UNSPECIFIED) {
|
||||||
rom0Len = headerSize;
|
rom0Len = headerSize;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
writeLen = writeBytes(output, rom0, rom0Len);
|
writeLen = writeBytes(output, rom0, rom0Len);
|
||||||
|
|
||||||
if (writeLen == -1) {
|
if (writeLen == -1) {
|
||||||
@@ -1103,9 +1149,9 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
|
|||||||
} else if (writeLen < rom0Len) {
|
} else if (writeLen < rom0Len) {
|
||||||
report(
|
report(
|
||||||
"FATAL: Could only write %jd of \"%s\"'s %jd ROM0 bytes\n",
|
"FATAL: Could only write %jd of \"%s\"'s %jd ROM0 bytes\n",
|
||||||
(intmax_t)writeLen,
|
static_cast<intmax_t>(writeLen),
|
||||||
name,
|
name,
|
||||||
(intmax_t)rom0Len
|
static_cast<intmax_t>(rom0Len)
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1118,10 +1164,10 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
|
|||||||
if (writeLen == -1) {
|
if (writeLen == -1) {
|
||||||
report("FATAL: Failed to write \"%s\"'s ROMX: %s\n", name, strerror(errno));
|
report("FATAL: Failed to write \"%s\"'s ROMX: %s\n", name, strerror(errno));
|
||||||
return;
|
return;
|
||||||
} else if ((size_t)writeLen < totalRomxLen) {
|
} else if (static_cast<size_t>(writeLen) < totalRomxLen) {
|
||||||
report(
|
report(
|
||||||
"FATAL: Could only write %jd of \"%s\"'s %zu ROMX bytes\n",
|
"FATAL: Could only write %jd of \"%s\"'s %zu ROMX bytes\n",
|
||||||
(intmax_t)writeLen,
|
static_cast<intmax_t>(writeLen),
|
||||||
name,
|
name,
|
||||||
totalRomxLen
|
totalRomxLen
|
||||||
);
|
);
|
||||||
@@ -1132,7 +1178,7 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
|
|||||||
// Output padding
|
// Output padding
|
||||||
if (padValue != UNSPECIFIED) {
|
if (padValue != UNSPECIFIED) {
|
||||||
if (input == output) {
|
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));
|
report("FATAL: Failed to seek to end of \"%s\": %s\n", name, strerror(errno));
|
||||||
return;
|
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`,
|
// The return value is either -1, or at most `thisLen`,
|
||||||
// so it's fine to cast to `size_t`
|
// so it's fine to cast to `size_t`
|
||||||
if ((size_t)ret != thisLen) {
|
if (static_cast<size_t>(ret) != thisLen) {
|
||||||
report("FATAL: Failed to write \"%s\"'s padding: %s\n", name, strerror(errno));
|
report("FATAL: Failed to write \"%s\"'s padding: %s\n", name, strerror(errno));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -1160,9 +1206,9 @@ static bool processFilename(char const *name) {
|
|||||||
nbErrors = 0;
|
nbErrors = 0;
|
||||||
|
|
||||||
if (!strcmp(name, "-")) {
|
if (!strcmp(name, "-")) {
|
||||||
|
name = "<stdin>";
|
||||||
(void)setmode(STDIN_FILENO, O_BINARY);
|
(void)setmode(STDIN_FILENO, O_BINARY);
|
||||||
(void)setmode(STDOUT_FILENO, O_BINARY);
|
(void)setmode(STDOUT_FILENO, O_BINARY);
|
||||||
name = "<stdin>";
|
|
||||||
processFile(STDIN_FILENO, STDOUT_FILENO, name, 0);
|
processFile(STDIN_FILENO, STDOUT_FILENO, name, 0);
|
||||||
} else {
|
} else {
|
||||||
// POSIX specifies that the results of O_RDWR on a FIFO are undefined.
|
// POSIX specifies that the results of O_RDWR on a FIFO are undefined.
|
||||||
@@ -1188,7 +1234,7 @@ static bool processFilename(char const *name) {
|
|||||||
report(
|
report(
|
||||||
"FATAL: \"%s\" too short, expected at least 336 ($150) bytes, got only %jd\n",
|
"FATAL: \"%s\" too short, expected at least 336 ($150) bytes, got only %jd\n",
|
||||||
name,
|
name,
|
||||||
(intmax_t)stat.st_size
|
static_cast<intmax_t>(stat.st_size)
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
processFile(input, input, name, stat.st_size);
|
processFile(input, input, name, stat.st_size);
|
||||||
@@ -1196,7 +1242,7 @@ static bool processFilename(char const *name) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nbErrors)
|
if (nbErrors) {
|
||||||
fprintf(
|
fprintf(
|
||||||
stderr,
|
stderr,
|
||||||
"Fixing \"%s\" failed with %u error%s\n",
|
"Fixing \"%s\" failed with %u error%s\n",
|
||||||
@@ -1204,6 +1250,7 @@ static bool processFilename(char const *name) {
|
|||||||
nbErrors,
|
nbErrors,
|
||||||
nbErrors == 1 ? "" : "s"
|
nbErrors == 1 ? "" : "s"
|
||||||
);
|
);
|
||||||
|
}
|
||||||
return nbErrors;
|
return nbErrors;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1253,8 +1300,9 @@ int main(int argc, char *argv[]) {
|
|||||||
switch (*musl_optarg) {
|
switch (*musl_optarg) {
|
||||||
#define OVERRIDE_SPEC(cur, bad, curFlag, badFlag) \
|
#define OVERRIDE_SPEC(cur, bad, curFlag, badFlag) \
|
||||||
case STR(cur)[0]: \
|
case STR(cur)[0]: \
|
||||||
if (fixSpec & badFlag) \
|
if (fixSpec & badFlag) { \
|
||||||
fprintf(stderr, "warning: '" STR(cur) "' overriding '" STR(bad) "' in fix spec\n"); \
|
fprintf(stderr, "warning: '" STR(cur) "' overriding '" STR(bad) "' in fix spec\n"); \
|
||||||
|
} \
|
||||||
fixSpec = (fixSpec & ~badFlag) | curFlag; \
|
fixSpec = (fixSpec & ~badFlag) | curFlag; \
|
||||||
break
|
break
|
||||||
#define overrideSpecs(fix, fixFlag, trash, trashFlag) \
|
#define overrideSpecs(fix, fixFlag, trash, trashFlag) \
|
||||||
@@ -1273,6 +1321,10 @@ int main(int argc, char *argv[]) {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'h':
|
||||||
|
printUsage();
|
||||||
|
exit(0);
|
||||||
|
|
||||||
case 'i':
|
case 'i':
|
||||||
gameID = musl_optarg;
|
gameID = musl_optarg;
|
||||||
len = strlen(gameID);
|
len = strlen(gameID);
|
||||||
@@ -1380,21 +1432,23 @@ int main(int argc, char *argv[]) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((cartridgeType & 0xFF00) == TPP1 && !japanese)
|
if ((cartridgeType & 0xFF00) == TPP1 && !japanese) {
|
||||||
fprintf(
|
fprintf(
|
||||||
stderr,
|
stderr,
|
||||||
"warning: TPP1 overwrites region flag for its identification code, ignoring `-j`\n"
|
"warning: TPP1 overwrites region flag for its identification code, ignoring `-j`\n"
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Check that RAM size is correct for "standard" mappers
|
// Check that RAM size is correct for "standard" mappers
|
||||||
if (ramSize != UNSPECIFIED && (cartridgeType & 0xFF00) == 0) {
|
if (ramSize != UNSPECIFIED && (cartridgeType & 0xFF00) == 0) {
|
||||||
if (cartridgeType == ROM_RAM || cartridgeType == ROM_RAM_BATTERY) {
|
if (cartridgeType == ROM_RAM || cartridgeType == ROM_RAM_BATTERY) {
|
||||||
if (ramSize != 1)
|
if (ramSize != 1) {
|
||||||
fprintf(
|
fprintf(
|
||||||
stderr,
|
stderr,
|
||||||
"warning: MBC \"%s\" should have 2 KiB of RAM (-r 1)\n",
|
"warning: MBC \"%s\" should have 2 KiB of RAM (-r 1)\n",
|
||||||
mbcName(cartridgeType)
|
mbcName(cartridgeType)
|
||||||
);
|
);
|
||||||
|
}
|
||||||
} else if (hasRAM(cartridgeType)) {
|
} else if (hasRAM(cartridgeType)) {
|
||||||
if (!ramSize) {
|
if (!ramSize) {
|
||||||
fprintf(
|
fprintf(
|
||||||
@@ -1419,12 +1473,13 @@ int main(int argc, char *argv[]) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sgb && oldLicensee != UNSPECIFIED && oldLicensee != 0x33)
|
if (sgb && oldLicensee != UNSPECIFIED && oldLicensee != 0x33) {
|
||||||
fprintf(
|
fprintf(
|
||||||
stderr,
|
stderr,
|
||||||
"warning: SGB compatibility enabled, but old licensee is 0x%02x, not 0x33\n",
|
"warning: SGB compatibility enabled, but old licensee is 0x%02x, not 0x33\n",
|
||||||
oldLicensee
|
oldLicensee
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
argv += musl_optind;
|
argv += musl_optind;
|
||||||
bool failed = nbErrors;
|
bool failed = nbErrors;
|
||||||
@@ -1435,7 +1490,8 @@ int main(int argc, char *argv[]) {
|
|||||||
logoFile = fopen(logoFilename, "rb");
|
logoFile = fopen(logoFilename, "rb");
|
||||||
} else {
|
} else {
|
||||||
logoFilename = "<stdin>";
|
logoFilename = "<stdin>";
|
||||||
logoFile = fdopen(STDIN_FILENO, "rb");
|
(void)setmode(STDIN_FILENO, O_BINARY);
|
||||||
|
logoFile = stdin;
|
||||||
}
|
}
|
||||||
if (!logoFile) {
|
if (!logoFile) {
|
||||||
fprintf(
|
fprintf(
|
||||||
@@ -1474,9 +1530,10 @@ int main(int argc, char *argv[]) {
|
|||||||
memcpy(logo, nintendoLogo, sizeof(nintendoLogo));
|
memcpy(logo, nintendoLogo, sizeof(nintendoLogo));
|
||||||
}
|
}
|
||||||
if (fixSpec & TRASH_LOGO) {
|
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];
|
logo[i] = 0xFF ^ logo[i];
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!*argv) {
|
if (!*argv) {
|
||||||
fputs(
|
fputs(
|
||||||
|
|||||||
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"
|
#include "gfx/main.hpp"
|
||||||
|
|
||||||
@@ -41,7 +41,8 @@ static struct LocalOptions {
|
|||||||
|
|
||||||
static uintmax_t nbErrors;
|
static uintmax_t nbErrors;
|
||||||
|
|
||||||
[[noreturn]] void giveUp() {
|
[[noreturn]]
|
||||||
|
void giveUp() {
|
||||||
fprintf(stderr, "Conversion aborted after %ju error%s\n", nbErrors, nbErrors == 1 ? "" : "s");
|
fprintf(stderr, "Conversion aborted after %ju error%s\n", nbErrors, nbErrors == 1 ? "" : "s");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
@@ -71,18 +72,21 @@ void error(char const *fmt, ...) {
|
|||||||
va_end(ap);
|
va_end(ap);
|
||||||
putc('\n', stderr);
|
putc('\n', stderr);
|
||||||
|
|
||||||
if (nbErrors != std::numeric_limits<decltype(nbErrors)>::max())
|
if (nbErrors != std::numeric_limits<decltype(nbErrors)>::max()) {
|
||||||
nbErrors++;
|
nbErrors++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void errorMessage(char const *msg) {
|
void errorMessage(char const *msg) {
|
||||||
fprintf(stderr, "error: %s\n", msg);
|
fprintf(stderr, "error: %s\n", msg);
|
||||||
|
|
||||||
if (nbErrors != std::numeric_limits<decltype(nbErrors)>::max())
|
if (nbErrors != std::numeric_limits<decltype(nbErrors)>::max()) {
|
||||||
nbErrors++;
|
nbErrors++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[[noreturn]] void fatal(char const *fmt, ...) {
|
[[noreturn]]
|
||||||
|
void fatal(char const *fmt, ...) {
|
||||||
va_list ap;
|
va_list ap;
|
||||||
|
|
||||||
fputs("FATAL: ", stderr);
|
fputs("FATAL: ", stderr);
|
||||||
@@ -91,8 +95,9 @@ void errorMessage(char const *msg) {
|
|||||||
va_end(ap);
|
va_end(ap);
|
||||||
putc('\n', stderr);
|
putc('\n', stderr);
|
||||||
|
|
||||||
if (nbErrors != std::numeric_limits<decltype(nbErrors)>::max())
|
if (nbErrors != std::numeric_limits<decltype(nbErrors)>::max()) {
|
||||||
nbErrors++;
|
nbErrors++;
|
||||||
|
}
|
||||||
|
|
||||||
giveUp();
|
giveUp();
|
||||||
}
|
}
|
||||||
@@ -108,18 +113,15 @@ void Options::verbosePrint(uint8_t level, char const *fmt, ...) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Short options
|
// 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
|
||||||
* 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,
|
||||||
* Also, make sure long opts don't create ambiguity:
|
// except if it doesn't create any ambiguity (`verbose` versus `version`).
|
||||||
* A long opt's name should start with the same letter as its short opt,
|
// This is because long opt matching, even to a single char, is prioritized
|
||||||
* except if it doesn't create any ambiguity (`verbose` versus `version`).
|
// over short opt matching.
|
||||||
* This is because long opt matching, even to a single char, is prioritized
|
|
||||||
* over short opt matching
|
|
||||||
*/
|
|
||||||
static option const longopts[] = {
|
static option const longopts[] = {
|
||||||
{"auto-attr-map", no_argument, nullptr, 'A'},
|
{"auto-attr-map", no_argument, nullptr, 'A'},
|
||||||
{"attr-map", required_argument, nullptr, 'a'},
|
{"attr-map", required_argument, nullptr, 'a'},
|
||||||
@@ -127,6 +129,7 @@ static option const longopts[] = {
|
|||||||
{"color-curve", no_argument, nullptr, 'C'},
|
{"color-curve", no_argument, nullptr, 'C'},
|
||||||
{"colors", required_argument, nullptr, 'c'},
|
{"colors", required_argument, nullptr, 'c'},
|
||||||
{"depth", required_argument, nullptr, 'd'},
|
{"depth", required_argument, nullptr, 'd'},
|
||||||
|
{"help", no_argument, nullptr, 'h'},
|
||||||
{"input-tileset", required_argument, nullptr, 'i'},
|
{"input-tileset", required_argument, nullptr, 'i'},
|
||||||
{"slice", required_argument, nullptr, 'L'},
|
{"slice", required_argument, nullptr, 'L'},
|
||||||
{"mirror-tiles", no_argument, nullptr, 'm'},
|
{"mirror-tiles", no_argument, nullptr, 'm'},
|
||||||
@@ -139,6 +142,7 @@ static option const longopts[] = {
|
|||||||
{"auto-palette-map", no_argument, nullptr, 'Q'},
|
{"auto-palette-map", no_argument, nullptr, 'Q'},
|
||||||
{"palette-map", required_argument, nullptr, 'q'},
|
{"palette-map", required_argument, nullptr, 'q'},
|
||||||
{"reverse", required_argument, nullptr, 'r'},
|
{"reverse", required_argument, nullptr, 'r'},
|
||||||
|
{"palette-size", required_argument, nullptr, 's'},
|
||||||
{"auto-tilemap", no_argument, nullptr, 'T'},
|
{"auto-tilemap", no_argument, nullptr, 'T'},
|
||||||
{"tilemap", required_argument, nullptr, 't'},
|
{"tilemap", required_argument, nullptr, 't'},
|
||||||
{"unit-size", required_argument, nullptr, 'U'},
|
{"unit-size", required_argument, nullptr, 'U'},
|
||||||
@@ -154,7 +158,7 @@ static option const longopts[] = {
|
|||||||
|
|
||||||
static void printUsage() {
|
static void printUsage() {
|
||||||
fputs(
|
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"
|
" [-b <base_ids>] [-c <colors>] [-d <depth>] [-i <tileset_file>]\n"
|
||||||
" [-L <slice>] [-N <nb_tiles>] [-n <nb_pals>] [-o <out_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"
|
" [-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.
|
||||||
* Parses a number at the beginning of a string, moving the pointer to skip the parsed characters
|
// Returns the provided errVal on error.
|
||||||
* Returns the provided errVal on error
|
|
||||||
*/
|
|
||||||
static uint16_t parseNumber(char *&string, char const *errPrefix, uint16_t errVal = UINT16_MAX) {
|
static uint16_t parseNumber(char *&string, char const *errPrefix, uint16_t errVal = UINT16_MAX) {
|
||||||
uint8_t base = 10;
|
uint8_t base = 10;
|
||||||
if (*string == '\0') {
|
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.
|
||||||
* 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.
|
||||||
* 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
|
||||||
* Returns 255 on parse failure (including wrong char for base), in which case
|
// the string_view may be pointing on garbage.
|
||||||
* the string_view may be pointing on garbage.
|
|
||||||
*/
|
|
||||||
auto charIndex = [&base](unsigned char c) -> uint8_t {
|
auto charIndex = [&base](unsigned char c) -> uint8_t {
|
||||||
unsigned char index = c - '0'; // Use wrapping semantics
|
unsigned char index = c - '0'; // Use wrapping semantics
|
||||||
if (base == 2 && index >= 2) {
|
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, appending them to `argPool`.
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
static std::vector<size_t> readAtFile(std::string const &path, std::vector<char> &argPool) {
|
static std::vector<size_t> readAtFile(std::string const &path, std::vector<char> &argPool) {
|
||||||
File file;
|
File file;
|
||||||
if (!file.open(path, std::ios_base::in)) {
|
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.
|
||||||
* 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.
|
||||||
* 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.
|
||||||
* 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[]) {
|
static char *parseArgv(int argc, char *argv[]) {
|
||||||
for (int ch; (ch = musl_getopt_long_only(argc, argv, optstring, longopts, nullptr)) != -1;) {
|
for (int ch; (ch = musl_getopt_long_only(argc, argv, optstring, longopts, nullptr)) != -1;) {
|
||||||
char *arg = musl_optarg; // Make a copy for scanning
|
char *arg = musl_optarg; // Make a copy for scanning
|
||||||
@@ -362,8 +356,9 @@ static char *parseArgv(int argc, char *argv[]) {
|
|||||||
break;
|
break;
|
||||||
case 'a':
|
case 'a':
|
||||||
localOptions.autoAttrmap = false;
|
localOptions.autoAttrmap = false;
|
||||||
if (!options.attrmap.empty())
|
if (!options.attrmap.empty()) {
|
||||||
warning("Overriding attrmap file %s", options.attrmap.c_str());
|
warning("Overriding attrmap file %s", options.attrmap.c_str());
|
||||||
|
}
|
||||||
options.attrmap = musl_optarg;
|
options.attrmap = musl_optarg;
|
||||||
break;
|
break;
|
||||||
case 'b':
|
case 'b':
|
||||||
@@ -429,9 +424,13 @@ static char *parseArgv(int argc, char *argv[]) {
|
|||||||
options.bitDepth = 2;
|
options.bitDepth = 2;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 'h':
|
||||||
|
printUsage();
|
||||||
|
exit(0);
|
||||||
case 'i':
|
case 'i':
|
||||||
if (!options.inputTileset.empty())
|
if (!options.inputTileset.empty()) {
|
||||||
warning("Overriding input tileset file %s", options.inputTileset.c_str());
|
warning("Overriding input tileset file %s", options.inputTileset.c_str());
|
||||||
|
}
|
||||||
options.inputTileset = musl_optarg;
|
options.inputTileset = musl_optarg;
|
||||||
break;
|
break;
|
||||||
case 'L':
|
case 'L':
|
||||||
@@ -529,8 +528,9 @@ static char *parseArgv(int argc, char *argv[]) {
|
|||||||
localOptions.groupOutputs = true;
|
localOptions.groupOutputs = true;
|
||||||
break;
|
break;
|
||||||
case 'o':
|
case 'o':
|
||||||
if (!options.output.empty())
|
if (!options.output.empty()) {
|
||||||
warning("Overriding tile data file %s", options.output.c_str());
|
warning("Overriding tile data file %s", options.output.c_str());
|
||||||
|
}
|
||||||
options.output = musl_optarg;
|
options.output = musl_optarg;
|
||||||
break;
|
break;
|
||||||
case 'P':
|
case 'P':
|
||||||
@@ -538,8 +538,9 @@ static char *parseArgv(int argc, char *argv[]) {
|
|||||||
break;
|
break;
|
||||||
case 'p':
|
case 'p':
|
||||||
localOptions.autoPalettes = false;
|
localOptions.autoPalettes = false;
|
||||||
if (!options.palettes.empty())
|
if (!options.palettes.empty()) {
|
||||||
warning("Overriding palettes file %s", options.palettes.c_str());
|
warning("Overriding palettes file %s", options.palettes.c_str());
|
||||||
|
}
|
||||||
options.palettes = musl_optarg;
|
options.palettes = musl_optarg;
|
||||||
break;
|
break;
|
||||||
case 'Q':
|
case 'Q':
|
||||||
@@ -547,8 +548,9 @@ static char *parseArgv(int argc, char *argv[]) {
|
|||||||
break;
|
break;
|
||||||
case 'q':
|
case 'q':
|
||||||
localOptions.autoPalmap = false;
|
localOptions.autoPalmap = false;
|
||||||
if (!options.palmap.empty())
|
if (!options.palmap.empty()) {
|
||||||
warning("Overriding palette map file %s", options.palmap.c_str());
|
warning("Overriding palette map file %s", options.palmap.c_str());
|
||||||
|
}
|
||||||
options.palmap = musl_optarg;
|
options.palmap = musl_optarg;
|
||||||
break;
|
break;
|
||||||
case 'r':
|
case 'r':
|
||||||
@@ -574,8 +576,9 @@ static char *parseArgv(int argc, char *argv[]) {
|
|||||||
break;
|
break;
|
||||||
case 't':
|
case 't':
|
||||||
localOptions.autoTilemap = false;
|
localOptions.autoTilemap = false;
|
||||||
if (!options.tilemap.empty())
|
if (!options.tilemap.empty()) {
|
||||||
warning("Overriding tilemap file %s", options.tilemap.c_str());
|
warning("Overriding tilemap file %s", options.tilemap.c_str());
|
||||||
|
}
|
||||||
options.tilemap = musl_optarg;
|
options.tilemap = musl_optarg;
|
||||||
break;
|
break;
|
||||||
case 'V':
|
case 'V':
|
||||||
@@ -773,19 +776,25 @@ int main(int argc, char *argv[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fputs("Options:\n", stderr);
|
fputs("Options:\n", stderr);
|
||||||
if (options.columnMajor)
|
if (options.columnMajor) {
|
||||||
fputs("\tVisit image in column-major order\n", stderr);
|
fputs("\tVisit image in column-major order\n", stderr);
|
||||||
if (options.allowDedup)
|
}
|
||||||
|
if (options.allowDedup) {
|
||||||
fputs("\tAllow deduplicating tiles\n", stderr);
|
fputs("\tAllow deduplicating tiles\n", stderr);
|
||||||
if (options.allowMirroringX)
|
}
|
||||||
|
if (options.allowMirroringX) {
|
||||||
fputs("\tAllow deduplicating horizontally mirrored tiles\n", stderr);
|
fputs("\tAllow deduplicating horizontally mirrored tiles\n", stderr);
|
||||||
if (options.allowMirroringY)
|
}
|
||||||
|
if (options.allowMirroringY) {
|
||||||
fputs("\tAllow deduplicating vertically mirrored tiles\n", stderr);
|
fputs("\tAllow deduplicating vertically mirrored tiles\n", stderr);
|
||||||
if (options.useColorCurve)
|
}
|
||||||
|
if (options.useColorCurve) {
|
||||||
fputs("\tUse color curve\n", stderr);
|
fputs("\tUse color curve\n", stderr);
|
||||||
|
}
|
||||||
fprintf(stderr, "\tBit depth: %" PRIu8 "bpp\n", options.bitDepth);
|
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, "\tTrim the last %" PRIu64 " tiles\n", options.trim);
|
||||||
|
}
|
||||||
fprintf(stderr, "\tMaximum %" PRIu16 " palettes\n", options.nbPalettes);
|
fprintf(stderr, "\tMaximum %" PRIu16 " palettes\n", options.nbPalettes);
|
||||||
fprintf(stderr, "\tPalettes contain %" PRIu8 " colors\n", options.nbColorsPerPal);
|
fprintf(stderr, "\tPalettes contain %" PRIu8 " colors\n", options.nbColorsPerPal);
|
||||||
fprintf(stderr, "\t%s palette spec\n", [] {
|
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 {
|
uint8_t Palette::indexOf(uint16_t color) const {
|
||||||
return color == Rgba::transparent
|
return color == Rgba::transparent
|
||||||
? 0
|
? 0
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user