mirror of
https://github.com/gbdev/rgbds.git
synced 2025-11-21 02:32:06 +00:00
Compare commits
106 Commits
v0.6.0-rc1
...
v0.6.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
69a573923f | ||
|
|
7eb4ecea8b | ||
|
|
599ce757a1 | ||
|
|
75a07a90f8 | ||
|
|
ec2d1312ef | ||
|
|
03b6dd9321 | ||
|
|
a16d3d6405 | ||
|
|
3e5cd8ce1a | ||
|
|
6902387991 | ||
|
|
62b4f2b264 | ||
|
|
79748afdc4 | ||
|
|
32cb0558e4 | ||
|
|
92b2ac3c8c | ||
|
|
0e67298dff | ||
|
|
f6d218ed36 | ||
|
|
1a9fc964df | ||
|
|
48248faab0 | ||
|
|
58181c2d73 | ||
|
|
0f86084e08 | ||
|
|
c8e602dec1 | ||
|
|
b168717e91 | ||
|
|
930c2ac328 | ||
|
|
28737d5778 | ||
|
|
12ba057b4f | ||
|
|
0e0876b17f | ||
|
|
b28eea24fc | ||
|
|
a1e59ddc3d | ||
|
|
3fbdba31bf | ||
|
|
d90a7e4302 | ||
|
|
7377a14245 | ||
|
|
e2136d60b2 | ||
|
|
74e40654e6 | ||
|
|
f90857032c | ||
|
|
1653a9a3f2 | ||
|
|
3c049983f1 | ||
|
|
8553b61a94 | ||
|
|
ab12c474d2 | ||
|
|
8ccbd9dc36 | ||
|
|
b8307432b8 | ||
|
|
80a62a8a03 | ||
|
|
bbe28faab4 | ||
|
|
106ad30e5a | ||
|
|
a1107fc5cf | ||
|
|
969412af24 | ||
|
|
c10345f26d | ||
|
|
6fd5c94b27 | ||
|
|
ddb1d0b6aa | ||
|
|
08545643cf | ||
|
|
140c6b169e | ||
|
|
d86d24bdc1 | ||
|
|
a1a919579c | ||
|
|
a47da5f71f | ||
|
|
68ad926279 | ||
|
|
dec4133e84 | ||
|
|
c35cb6ac32 | ||
|
|
023884d2b0 | ||
|
|
3567faf395 | ||
|
|
6502ed3919 | ||
|
|
b1a241233e | ||
|
|
f88968ec20 | ||
|
|
5ad8a8c958 | ||
|
|
2827374505 | ||
|
|
b8385a50e3 | ||
|
|
02923a67f3 | ||
|
|
f5b1990604 | ||
|
|
0794da22bc | ||
|
|
6df75f7af3 | ||
|
|
7ae23e6cdb | ||
|
|
98a6dffbca | ||
|
|
889302a9e2 | ||
|
|
c01317e08d | ||
|
|
a52a00a9ca | ||
|
|
fa13611bbf | ||
|
|
dca24a6d50 | ||
|
|
4363ffcad4 | ||
|
|
14e6a79adc | ||
|
|
7a2ee26792 | ||
|
|
425339ccf6 | ||
|
|
1a1f1365e6 | ||
|
|
f97139461c | ||
|
|
8207dc57b7 | ||
|
|
d29057e747 | ||
|
|
f1b74fa610 | ||
|
|
c7a92d3104 | ||
|
|
0105779789 | ||
|
|
9ef7954670 | ||
|
|
d7d524294b | ||
|
|
12fed4c68e | ||
|
|
3db3421f07 | ||
|
|
92eb0a133b | ||
|
|
b02ccf8f4a | ||
|
|
2e0991f32b | ||
|
|
f3f2c2ca16 | ||
|
|
9ec8186ac6 | ||
|
|
ab9945c1ee | ||
|
|
18e4f132a8 | ||
|
|
828b2adcdf | ||
|
|
1c2965467d | ||
|
|
d243e50390 | ||
|
|
acb33777c6 | ||
|
|
d15916b1bd | ||
|
|
28fcef0ecd | ||
|
|
b53c115ec2 | ||
|
|
6a51e39a5c | ||
|
|
e348f70866 | ||
|
|
43a487f0bf |
4
.gitattributes
vendored
4
.gitattributes
vendored
@@ -1,2 +1,6 @@
|
|||||||
# Shell scripts need Unix line endings (see https://github.com/gbdev/rgbds/issues/841)
|
# Shell scripts need Unix line endings (see https://github.com/gbdev/rgbds/issues/841)
|
||||||
*.sh text eol=lf
|
*.sh text eol=lf
|
||||||
|
*.bash text eol=lf
|
||||||
|
|
||||||
|
# Flags also need Unix line endings (see https://github.com/gbdev/rgbds/issues/955)
|
||||||
|
*.flags text eol=lf
|
||||||
|
|||||||
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
@@ -1,3 +1 @@
|
|||||||
github: avivace
|
|
||||||
patreon: gbdev01
|
|
||||||
open_collective: gbdev
|
open_collective: gbdev
|
||||||
|
|||||||
17
.github/actions/mingw-configure.sh
vendored
17
.github/actions/mingw-configure.sh
vendored
@@ -1,17 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
source mingw-env @TRIPLE@
|
|
||||||
echo LAST IS: $last
|
|
||||||
|
|
||||||
# check if last arg is a path to configure, else use parent
|
|
||||||
for last; do true; done
|
|
||||||
if test -x "${last}/configure"; then
|
|
||||||
config_path="$last"
|
|
||||||
else
|
|
||||||
config_path=".."
|
|
||||||
fi
|
|
||||||
|
|
||||||
${config_path}/configure \
|
|
||||||
--host=@TRIPLE@ --target=@TRIPLE@ --build="$CHOST" \
|
|
||||||
--prefix=/usr/@TRIPLE@ --libdir=/usr/@TRIPLE@/lib --includedir=/usr/@TRIPLE@/include \
|
|
||||||
--enable-shared --enable-static "$@"
|
|
||||||
16
.github/actions/mingw-env.sh
vendored
16
.github/actions/mingw-env.sh
vendored
@@ -1,16 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
_arch=$1
|
|
||||||
|
|
||||||
default_mingw_pp_flags="-D_FORTIFY_SOURCE=2"
|
|
||||||
default_mingw_compiler_flags="$default_mingw_pp_flags -O2 -pipe -fno-plt -fexceptions --param=ssp-buffer-size=4"
|
|
||||||
default_mingw_linker_flags="-Wl,-O1,--sort-common,--as-needed -fstack-protector"
|
|
||||||
|
|
||||||
export CPPFLAGS="${MINGW_CPPFLAGS:-$default_mingw_pp_flags $CPPFLAGS}"
|
|
||||||
export CFLAGS="${MINGW_CFLAGS:-$default_mingw_compiler_flags $CFLAGS}"
|
|
||||||
export CXXFLAGS="${MINGW_CXXFLAGS:-$default_mingw_compiler_flags $CXXFLAGS}"
|
|
||||||
export LDFLAGS="${MINGW_LDFLAGS:-$default_mingw_linker_flags $LDFLAGS}"
|
|
||||||
|
|
||||||
mingw_prefix=/usr/${_arch}
|
|
||||||
export PKG_CONFIG_SYSROOT_DIR="${mingw_prefix}"
|
|
||||||
export PKG_CONFIG_LIBDIR="${mingw_prefix}/lib/pkgconfig:${mingw_prefix}/share/pkgconfig"
|
|
||||||
44
.github/actions/mingw-w64-libpng-dev.sh
vendored
44
.github/actions/mingw-w64-libpng-dev.sh
vendored
@@ -1,44 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# This script was written by ISSOtm while looking at Arch Linux's PKGBUILD for
|
|
||||||
# the corresponding package. (And its dependencies)
|
|
||||||
# https://aur.archlinux.org/packages/mingw-w64-libpng/
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
pngver=1.6.37
|
|
||||||
_apngver=$pngver
|
|
||||||
_arch="$1"
|
|
||||||
|
|
||||||
|
|
||||||
## Install mingw-configure and mingw-env (both build dependencies)
|
|
||||||
|
|
||||||
install -m 755 .github/actions/mingw-env.sh /usr/bin/mingw-env
|
|
||||||
|
|
||||||
sed "s|@TRIPLE@|${_arch}|g" .github/actions/mingw-configure.sh > ${_arch}-configure
|
|
||||||
install -m 755 ${_arch}-configure /usr/bin/
|
|
||||||
|
|
||||||
|
|
||||||
## Grab sources and check them
|
|
||||||
|
|
||||||
wget http://downloads.sourceforge.net/sourceforge/libpng/libpng-$pngver.tar.xz
|
|
||||||
wget http://downloads.sourceforge.net/project/apng/libpng/libpng16/libpng-$_apngver-apng.patch.gz
|
|
||||||
sha256sum -c .github/actions/mingw-w64-libpng-dev.sha256sums
|
|
||||||
|
|
||||||
## Extract sources
|
|
||||||
|
|
||||||
tar -xf libpng-$pngver.tar.xz
|
|
||||||
gunzip libpng-$_apngver-apng.patch.gz
|
|
||||||
|
|
||||||
|
|
||||||
## Start building!
|
|
||||||
|
|
||||||
cd libpng-$pngver
|
|
||||||
# Patch in apng support
|
|
||||||
patch -p0 ../libpng-$_apngver-apng.patch
|
|
||||||
|
|
||||||
mkdir -p build-${_arch}
|
|
||||||
cd build-${_arch}
|
|
||||||
${_arch}-configure LDFLAGS=-static-libgcc
|
|
||||||
make
|
|
||||||
make install
|
|
||||||
23
.github/scripts/get_win_deps.ps1
vendored
Normal file
23
.github/scripts/get_win_deps.ps1
vendored
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
function getlibrary ([string] $URI, [string] $filename, [string] $hash, [string] $destdir) {
|
||||||
|
$wc = New-Object Net.WebClient
|
||||||
|
[string] $downloadhash = $null
|
||||||
|
try {
|
||||||
|
$wc.DownloadFile($URI, $filename)
|
||||||
|
$downloadhash = $(Get-FileHash $filename -Algorithm SHA256).Hash
|
||||||
|
} catch {
|
||||||
|
Write-Host "${filename}: failed to download"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
if ($hash -ne $downloadhash) {
|
||||||
|
Write-Host "${filename}: SHA256 mismatch ($downloadhash)"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
Expand-Archive -DestinationPath $destdir $filename
|
||||||
|
}
|
||||||
|
|
||||||
|
getlibrary 'https://www.zlib.net/zlib1213.zip' 'zlib.zip' 'd233fca7cf68db4c16dc5287af61f3cd01ab62495224c66639ca3da537701e42' .
|
||||||
|
getlibrary 'https://download.sourceforge.net/libpng/lpng1637.zip' 'libpng.zip' '3b4b1cbd0bae6822f749d39b1ccadd6297f05e2b85a83dd2ce6ecd7d09eabdf2' .
|
||||||
|
getlibrary 'https://github.com/lexxmark/winflexbison/releases/download/v2.5.24/win_flex_bison-2.5.24.zip' 'winflexbison.zip' '39c6086ce211d5415500acc5ed2d8939861ca1696aee48909c7f6daf5122b505' install_dir
|
||||||
|
|
||||||
|
Move-Item zlib-1.2.13 zlib
|
||||||
|
Move-Item lpng1637 libpng
|
||||||
6
.github/scripts/install.sh
vendored
Executable file
6
.github/scripts/install.sh
vendored
Executable file
@@ -0,0 +1,6 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
install -d /usr/local/bin/ /usr/local/share/man/man1/ /usr/local/share/man/man5/ /usr/local/share/man/man7/
|
||||||
|
install -s -m 755 rgbasm rgblink rgbfix rgbgfx /usr/local/bin/
|
||||||
|
install -m 644 rgbasm.1 rgblink.1 rgbfix.1 rgbgfx.1 /usr/local/share/man/man1/
|
||||||
|
install -m 644 rgbds.5 rgbasm.5 rgblink.5 /usr/local/share/man/man5/
|
||||||
|
install -m 644 rgbds.7 gbz80.7 /usr/local/share/man/man7/
|
||||||
34
.github/scripts/mingw-w64-libpng-dev.sh
vendored
Executable file
34
.github/scripts/mingw-w64-libpng-dev.sh
vendored
Executable file
@@ -0,0 +1,34 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
pngver=1.6.37
|
||||||
|
arch="$1"
|
||||||
|
|
||||||
|
## Grab sources and check them
|
||||||
|
|
||||||
|
wget http://downloads.sourceforge.net/project/libpng/libpng16/$pngver/libpng-$pngver.tar.xz
|
||||||
|
wget http://downloads.sourceforge.net/project/apng/libpng/libpng16/libpng-$pngver-apng.patch.gz
|
||||||
|
sha256sum -c .github/scripts/mingw-w64-libpng-dev.sha256sums
|
||||||
|
|
||||||
|
## Extract sources and patch them
|
||||||
|
|
||||||
|
tar -xf libpng-$pngver.tar.xz
|
||||||
|
gunzip libpng-$pngver-apng.patch.gz
|
||||||
|
|
||||||
|
# Patch in apng support
|
||||||
|
env -C libpng-$pngver patch -p0 ../libpng-$pngver-apng.patch
|
||||||
|
|
||||||
|
## Start building!
|
||||||
|
|
||||||
|
mkdir -p build
|
||||||
|
cd build
|
||||||
|
../libpng-$pngver/configure \
|
||||||
|
--host="$arch" --target="$arch" \
|
||||||
|
--prefix="/usr/$arch" \
|
||||||
|
--enable-shared --disable-static \
|
||||||
|
CPPFLAGS="-D_FORTIFY_SOURCE=2" \
|
||||||
|
CFLAGS="-O2 -pipe -fno-plt -fexceptions --param=ssp-buffer-size=4" \
|
||||||
|
LDFLAGS="-Wl,-O1,--sort-common,--as-needed -fstack-protector"
|
||||||
|
make -kj
|
||||||
|
make install
|
||||||
161
.github/workflows/create-release-artifacts.yaml
vendored
161
.github/workflows/create-release-artifacts.yaml
vendored
@@ -6,78 +6,108 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
windows:
|
windows:
|
||||||
runs-on: windows-2019
|
runs-on: windows-2022
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
bits: [32, 64]
|
||||||
|
include:
|
||||||
|
- bits: 32
|
||||||
|
arch: x86
|
||||||
|
platform: Win32
|
||||||
|
- bits: 64
|
||||||
|
arch: x86_x64
|
||||||
|
platform: x64
|
||||||
|
fail-fast: false
|
||||||
|
steps:
|
||||||
|
- name: Get version from tag
|
||||||
|
shell: bash
|
||||||
|
run: | # Turn "vX.Y.Z" into "X.Y.Z"
|
||||||
|
VERSION="${{ github.ref_name }}"
|
||||||
|
echo "version=${VERSION#v}" >> $GITHUB_ENV
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- name: Install deps
|
||||||
|
run: .github/scripts/get_win_deps.ps1
|
||||||
|
- uses: actions/cache@v3
|
||||||
|
id: cache
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
zbuild
|
||||||
|
pngbuild
|
||||||
|
key: ${{ matrix.arch }}-${{ hashFiles('zlib/**', 'libpng/**') }}
|
||||||
|
- name: Build zlib
|
||||||
|
run: | # BUILD_SHARED_LIBS causes the output DLL to be correctly called `zlib1.dll`
|
||||||
|
cmake -S zlib -B zbuild -A ${{ matrix.platform }} -Wno-dev -DCMAKE_INSTALL_PREFIX=install_dir -DBUILD_SHARED_LIBS=ON
|
||||||
|
cmake --build zbuild --config Release -j
|
||||||
|
if: steps.cache.outputs.cache-hit != 'true'
|
||||||
|
- name: Install zlib
|
||||||
|
run: |
|
||||||
|
cmake --install zbuild
|
||||||
|
- name: Build libpng
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
cmake -S libpng -B pngbuild -A ${{ matrix.platform }} -Wno-dev -DCMAKE_INSTALL_PREFIX=install_dir -DPNG_SHARED=ON -DPNG_STATIC=OFF -DPNG_TESTS=OFF -DPNG_BUILD_ZLIB=ON -DZLIB_INCLUDE_DIR="$PWD"/install_dir/include -DZLIB_LIBRARY="$PWD"/install_dir/lib/zlib.lib
|
||||||
|
cmake --build pngbuild --config Release -j
|
||||||
|
if: steps.cache.outputs.cache-hit != 'true'
|
||||||
|
- name: Install libpng
|
||||||
|
run: |
|
||||||
|
cmake --install pngbuild
|
||||||
|
- name: Build Windows binaries
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
cmake -S . -B build -A ${{ matrix.platform }} -DCMAKE_INSTALL_PREFIX=install_dir -DCMAKE_BUILD_TYPE=Release -DZLIB_LIBRARY="$PWD"/install_dir/lib/zlib.lib -DZLIB_INCLUDE_DIR="$PWD"/install_dir/include -DPNG_LIBRARY="$PWD"/install_dir/lib/libpng16.lib -DPNG_INCLUDE_DIR="$PWD"/install_dir/include
|
||||||
|
cmake --build build --config Release -j --verbose
|
||||||
|
cmake --install build --verbose --prefix install_dir --strip
|
||||||
|
- name: Package binaries
|
||||||
|
run: |
|
||||||
|
Compress-Archive -LiteralPath @("install_dir/bin/rgbasm.exe", "install_dir/bin/rgblink.exe", "install_dir/bin/rgbfix.exe", "install_dir/bin/rgbgfx.exe", "install_dir/bin/zlib1.dll", "install_dir/bin/libpng16.dll") "rgbds-${{ env.version }}-win${{ matrix.bits }}.zip"
|
||||||
|
- name: Upload Windows binaries
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: win${{ matrix.bits }}
|
||||||
|
path: rgbds-${{ env.version }}-win${{ matrix.bits }}.zip
|
||||||
|
|
||||||
|
macos:
|
||||||
|
runs-on: macos-12
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- name: Get version from tag
|
- name: Get version from tag
|
||||||
shell: bash
|
shell: bash
|
||||||
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 }}"
|
VERSION="${{ github.ref_name }}"
|
||||||
echo "version=${VERSION##*/v}" >> $GITHUB_ENV
|
echo "version=${VERSION#v}" >> $GITHUB_ENV
|
||||||
- name: Get zlib, libpng and bison
|
- uses: actions/checkout@v3
|
||||||
run: | # TODO: use an array
|
- name: Install deps
|
||||||
$wc = New-Object System.Net.WebClient
|
shell: bash
|
||||||
$wc.DownloadFile('https://www.zlib.net/zlib1212.zip', 'zlib.zip')
|
|
||||||
$hash = (Get-FileHash "zlib.zip" -Algorithm SHA256).Hash
|
|
||||||
if ($hash -ne '173e89893dcb8b4a150d7731cd72f0602f1d6b45e60e2a54efdf7f3fc3325fd7') {
|
|
||||||
Write-Host "zlib SHA256 mismatch! ($hash)"
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
$wc.DownloadFile('https://download.sourceforge.net/libpng/lpng1637.zip', 'libpng.zip')
|
|
||||||
$hash = (Get-FileHash "libpng.zip" -Algorithm SHA256).Hash
|
|
||||||
if ($hash -ne '3b4b1cbd0bae6822f749d39b1ccadd6297f05e2b85a83dd2ce6ecd7d09eabdf2') {
|
|
||||||
Write-Host "libpng SHA256 mismatch! ($hash)"
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
$wc.DownloadFile('https://github.com/lexxmark/winflexbison/releases/download/v2.5.24/win_flex_bison-2.5.24.zip', 'winflexbison.zip')
|
|
||||||
$hash = (Get-FileHash "winflexbison.zip" -Algorithm SHA256).Hash
|
|
||||||
if ($hash -ne '39c6086ce211d5415500acc5ed2d8939861ca1696aee48909c7f6daf5122b505') {
|
|
||||||
Write-Host "bison SHA256 mismatch! ($hash)"
|
|
||||||
}
|
|
||||||
Expand-Archive -DestinationPath . "zlib.zip"
|
|
||||||
Expand-Archive -DestinationPath . "libpng.zip"
|
|
||||||
Expand-Archive -DestinationPath install_dir "winflexbison.zip"
|
|
||||||
Move-Item zlib-1.2.12 zlib
|
|
||||||
Move-Item lpng1637 libpng
|
|
||||||
- name: Build 32-bit zlib
|
|
||||||
run: | # BUILD_SHARED_LIBS causes the output DLL to be correctly called `zlib1.dll`
|
|
||||||
cmake -S zlib -B zbuild32 -A Win32 -DCMAKE_INSTALL_PREFIX=install_dir -DBUILD_SHARED_LIBS=ON
|
|
||||||
cmake --build zbuild32 --config Release
|
|
||||||
cmake --install zbuild32
|
|
||||||
- name: Build 32-bit libpng
|
|
||||||
run: |
|
run: |
|
||||||
cmake -S libpng -B pngbuild32 -A Win32 -DCMAKE_INSTALL_PREFIX=install_dir -DPNG_SHARED=ON -DPNG_STATIC=ON -DPNG_TESTS=OFF
|
./.github/scripts/install_deps.sh macos-latest
|
||||||
cmake --build pngbuild32 --config Release
|
# We force linking libpng statically; the other libs are provided by macOS itself
|
||||||
cmake --install pngbuild32
|
- name: Build binaries
|
||||||
- name: Build 32-bit Windows binaries
|
|
||||||
run: |
|
run: |
|
||||||
cmake -S . -B build32 -A Win32 -DCMAKE_INSTALL_PREFIX=install_dir -DCMAKE_BUILD_TYPE=Release
|
export PATH="/usr/local/opt/bison/bin:$PATH"
|
||||||
cmake --build build32 --config Release
|
make -j WARNFLAGS="-Wall -Wextra -mmacosx-version-min=10.9" PKG_CONFIG="pkg-config --static" PNGLDLIBS="$(pkg-config --static --libs-only-L libpng | cut -c 3-)/libpng.a $(pkg-config --static --libs-only-l libpng | sed s/-lpng[0-9]*//g)" Q=
|
||||||
cmake --install build32
|
- name: Package binaries
|
||||||
- name: Package 32-bit binaries
|
|
||||||
run: |
|
run: |
|
||||||
Compress-Archive -LiteralPath @("install_dir/bin/rgbasm.exe", "install_dir/bin/rgblink.exe", "install_dir/bin/rgbfix.exe", "install_dir/bin/rgbgfx.exe", "install_dir/bin/zlib1.dll", "install_dir/bin/libpng16.dll") "rgbds-${{ env.version }}-win32.zip"
|
zip --junk-paths rgbds-${{ env.version }}-macos-x86-64.zip rgb{asm,link,fix,gfx} man/* .github/scripts/install.sh
|
||||||
- name: Build 64-bit zlib
|
- name: Upload macOS binaries
|
||||||
run: | # BUILD_SHARED_LIBS causes the output DLL to be correctly called `zlib1.dll`
|
uses: actions/upload-artifact@v3
|
||||||
cmake -S zlib -B zbuild64 -A x64 -DCMAKE_INSTALL_PREFIX=install_dir -DBUILD_SHARED_LIBS=ON
|
with:
|
||||||
cmake --build zbuild64 --config Release
|
name: macos
|
||||||
cmake --install zbuild64
|
path: rgbds-${{ env.version }}-macos-x86-64.zip
|
||||||
- name: Build 64-bit libpng
|
|
||||||
run: |
|
release:
|
||||||
cmake -S libpng -B pngbuild64 -A x64 -DCMAKE_INSTALL_PREFIX=install_dir -DPNG_SHARED=ON -DPNG_STATIC=ON -DPNG_TESTS=OFF
|
runs-on: ubuntu-latest
|
||||||
cmake --build pngbuild64 --config Release
|
needs: [windows, macos]
|
||||||
cmake --install pngbuild64
|
steps:
|
||||||
- name: Build 64-bit Windows binaries
|
- name: Get version from tag
|
||||||
run: |
|
shell: bash
|
||||||
cmake -S . -B build64 -A x64 -DCMAKE_INSTALL_PREFIX=install_dir -DCMAKE_BUILD_TYPE=Release
|
run: | # Turn "refs/tags/vX.Y.Z" into "X.Y.Z"
|
||||||
cmake --build build64 --config Release
|
VERSION="${{ github.ref_name }}"
|
||||||
cmake --install build64
|
echo "version=${VERSION#v}" >> $GITHUB_ENV
|
||||||
- name: Package 64-bit binaries
|
- uses: actions/checkout@v3
|
||||||
run: |
|
|
||||||
Compress-Archive -LiteralPath @("install_dir/bin/rgbasm.exe", "install_dir/bin/rgblink.exe", "install_dir/bin/rgbfix.exe", "install_dir/bin/rgbgfx.exe", "install_dir/bin/zlib1.dll", "install_dir/bin/libpng16.dll") "rgbds-${{ env.version }}-win64.zip"
|
|
||||||
- name: Package sources
|
- name: Package sources
|
||||||
run: |
|
run: |
|
||||||
make dist
|
make dist Q=
|
||||||
|
ls
|
||||||
|
- uses: actions/download-artifact@v3
|
||||||
- name: Release
|
- name: Release
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v1
|
||||||
with:
|
with:
|
||||||
@@ -88,8 +118,9 @@ jobs:
|
|||||||
draft: true # Don't publish the release quite yet...
|
draft: true # Don't publish the release quite yet...
|
||||||
prerelease: ${{ contains(github.ref, '-rc') }}
|
prerelease: ${{ contains(github.ref, '-rc') }}
|
||||||
files: |
|
files: |
|
||||||
rgbds-${{ env.version }}-win32.zip
|
win32/rgbds-${{ env.version }}-win32.zip
|
||||||
rgbds-${{ env.version }}-win64.zip
|
win64/rgbds-${{ env.version }}-win64.zip
|
||||||
|
macos/rgbds-${{ env.version }}-macos-x86-64.zip
|
||||||
rgbds-${{ env.version }}.tar.gz
|
rgbds-${{ env.version }}.tar.gz
|
||||||
fail_on_unmatched_files: true
|
fail_on_unmatched_files: true
|
||||||
env:
|
env:
|
||||||
|
|||||||
19
.github/workflows/create-release-docs.yml
vendored
19
.github/workflows/create-release-docs.yml
vendored
@@ -7,32 +7,25 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
if: github.repository_owner == 'gbdev'
|
if: github.repository_owner == 'gbdev'
|
||||||
runs-on: ubuntu-18.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout rgbds@release
|
- name: Checkout rgbds@release
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
path: rgbds
|
path: rgbds
|
||||||
- name: Checkout rgbds-www@master
|
- name: Checkout rgbds-www@master
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
repository: ${{ github.repository_owner }}/rgbds-www
|
repository: ${{ github.repository_owner }}/rgbds-www
|
||||||
path: rgbds-www
|
path: rgbds-www
|
||||||
# `-O toc` was added in 1.14.5, but the repos only have 1.14.4
|
- name: Install groff and mandoc
|
||||||
- name: Build and install mandoc + install groff
|
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get -qq update
|
sudo apt-get -qq update
|
||||||
sudo apt-get install -yq groff zlib1g-dev
|
sudo apt-get install -yq groff mandoc
|
||||||
wget 'http://mandoc.bsd.lv/snapshots/mandoc-1.14.6.tar.gz'
|
|
||||||
tar xf mandoc-1.14.6.tar.gz
|
|
||||||
cd mandoc-1.14.6
|
|
||||||
./configure
|
|
||||||
make
|
|
||||||
sudo make install
|
|
||||||
- name: Update pages
|
- name: Update pages
|
||||||
working-directory: rgbds/man
|
working-directory: rgbds/man
|
||||||
run: | # The ref appears to be in the format "refs/tags/<version>", so strip that
|
run: | # The ref appears to be in the format "refs/tags/<version>", so strip that
|
||||||
../../rgbds-www/.github/actions/get-pages.sh ${GITHUB_REF##*/} *
|
../../rgbds-www/maintainer/man_to_html.sh ${GITHUB_REF##*/} *
|
||||||
- name: Push new pages
|
- name: Push new pages
|
||||||
working-directory: rgbds-www
|
working-directory: rgbds-www
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
142
.github/workflows/testing.yml
vendored
142
.github/workflows/testing.yml
vendored
@@ -7,14 +7,14 @@ jobs:
|
|||||||
unix-testing:
|
unix-testing:
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-20.04, ubuntu-18.04, macos-11.0, macos-10.15]
|
os: [ubuntu-20.04, ubuntu-22.04, macos-11, macos-12]
|
||||||
cc: [gcc, clang]
|
cc: [gcc, clang]
|
||||||
buildsys: [make, cmake]
|
buildsys: [make, cmake]
|
||||||
exclude:
|
exclude:
|
||||||
# `gcc` is just an alias to `clang` on macOS, don't bother
|
# `gcc` is just an alias to `clang` on macOS, don't bother
|
||||||
- os: macos-10.15
|
- os: macos-11
|
||||||
cc: gcc
|
cc: gcc
|
||||||
- os: macos-11.0
|
- os: macos-12
|
||||||
cc: gcc
|
cc: gcc
|
||||||
include:
|
include:
|
||||||
- cc: gcc
|
- cc: gcc
|
||||||
@@ -24,11 +24,11 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- name: Install deps
|
- name: Install deps
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
./.github/actions/install_deps.sh ${{ matrix.os }}
|
./.github/scripts/install_deps.sh ${{ matrix.os }}
|
||||||
# The `export` lines are to allow working on macOS...
|
# The `export` lines are to allow working on macOS...
|
||||||
# Apple's base version is severely outdated, not even supporting -Wall,
|
# Apple's base version is severely outdated, not even supporting -Wall,
|
||||||
# but it overrides Homebrew's version nonetheless...
|
# but it overrides Homebrew's version nonetheless...
|
||||||
@@ -55,6 +55,25 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
name: rgbds-canary-${{ matrix.os }}-${{ matrix.cc }}-${{ matrix.buildsys }}
|
name: rgbds-canary-${{ matrix.os }}-${{ matrix.cc }}-${{ matrix.buildsys }}
|
||||||
path: bins
|
path: bins
|
||||||
|
- name: Compute test dependency cache params
|
||||||
|
id: test-deps-cache-params
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
paths=$(test/fetch-test-deps.sh --get-paths)
|
||||||
|
hash=$(test/fetch-test-deps.sh --get-hash)
|
||||||
|
tee -a <<<"paths=\"${paths//,/\\n}\"" $GITHUB_OUTPUT
|
||||||
|
tee -a <<<"hash=${hash%-}" $GITHUB_OUTPUT
|
||||||
|
- name: Check test dependency repositories cache
|
||||||
|
id: test-deps-cache
|
||||||
|
uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: ${{ fromJSON(steps.test-deps-cache-params.outputs.paths) }}
|
||||||
|
key: ${{ matrix.os }}-${{ steps.test-deps-cache-params.outputs.hash }}
|
||||||
|
- if: steps.test-deps-cache.outputs.cache-hit != 'true'
|
||||||
|
name: Fetch test dependency repositories
|
||||||
|
continue-on-error: true
|
||||||
|
run: |
|
||||||
|
test/fetch-test-deps.sh
|
||||||
- name: Test
|
- name: Test
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
@@ -64,6 +83,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
bits: [32, 64]
|
bits: [32, 64]
|
||||||
|
os: [windows-2019, windows-2022]
|
||||||
include:
|
include:
|
||||||
- bits: 32
|
- bits: 32
|
||||||
arch: x86
|
arch: x86
|
||||||
@@ -72,34 +92,11 @@ jobs:
|
|||||||
arch: x86_x64
|
arch: x86_x64
|
||||||
platform: x64
|
platform: x64
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
runs-on: windows-2019
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- name: Get zlib, libpng and bison
|
- name: Install deps
|
||||||
run: | # TODO: use an array; remember to update the versions being downloaded, *and* the paths being extracted! (`Move-Item`)
|
run: .github/scripts/get_win_deps.ps1
|
||||||
$wc = New-Object System.Net.WebClient
|
|
||||||
$wc.DownloadFile('https://www.zlib.net/zlib1212.zip', 'zlib.zip')
|
|
||||||
$hash = (Get-FileHash "zlib.zip" -Algorithm SHA256).Hash
|
|
||||||
if ($hash -ne '173e89893dcb8b4a150d7731cd72f0602f1d6b45e60e2a54efdf7f3fc3325fd7') {
|
|
||||||
Write-Host "zlib SHA256 mismatch! ($hash)"
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
$wc.DownloadFile('https://download.sourceforge.net/libpng/lpng1637.zip', 'libpng.zip')
|
|
||||||
$hash = (Get-FileHash "libpng.zip" -Algorithm SHA256).Hash
|
|
||||||
if ($hash -ne '3b4b1cbd0bae6822f749d39b1ccadd6297f05e2b85a83dd2ce6ecd7d09eabdf2') {
|
|
||||||
Write-Host "libpng SHA256 mismatch! ($hash)"
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
$wc.DownloadFile('https://github.com/lexxmark/winflexbison/releases/download/v2.5.24/win_flex_bison-2.5.24.zip', 'winflexbison.zip')
|
|
||||||
$hash = (Get-FileHash "winflexbison.zip" -Algorithm SHA256).Hash
|
|
||||||
if ($hash -ne '39c6086ce211d5415500acc5ed2d8939861ca1696aee48909c7f6daf5122b505') {
|
|
||||||
Write-Host "bison SHA256 mismatch! ($hash)"
|
|
||||||
}
|
|
||||||
Expand-Archive -DestinationPath . "zlib.zip"
|
|
||||||
Expand-Archive -DestinationPath . "libpng.zip"
|
|
||||||
Expand-Archive -DestinationPath install_dir "winflexbison.zip"
|
|
||||||
Move-Item zlib-1.2.12 zlib
|
|
||||||
Move-Item lpng1637 libpng
|
|
||||||
- uses: actions/cache@v3
|
- uses: actions/cache@v3
|
||||||
id: cache
|
id: cache
|
||||||
with:
|
with:
|
||||||
@@ -109,23 +106,25 @@ jobs:
|
|||||||
key: ${{ matrix.arch }}-${{ hashFiles('zlib/**', 'libpng/**') }}
|
key: ${{ matrix.arch }}-${{ hashFiles('zlib/**', 'libpng/**') }}
|
||||||
- name: Build zlib
|
- name: Build zlib
|
||||||
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 }} -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'
|
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
|
||||||
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
cmake -S libpng -B pngbuild -A ${{ matrix.platform }} -DCMAKE_INSTALL_PREFIX=install_dir -DPNG_SHARED=ON -DPNG_STATIC=ON -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 -DPNG_BUILD_ZLIB=ON -DZLIB_INCLUDE_DIR="$PWD"/install_dir/include -DZLIB_LIBRARY="$PWD"/install_dir/lib/zlib.lib
|
||||||
cmake --build pngbuild --config Release -j
|
cmake --build pngbuild --config Release -j
|
||||||
if: steps.cache.outputs.cache-hit != 'true'
|
if: steps.cache.outputs.cache-hit != 'true'
|
||||||
- name: Install libpng
|
- name: Install libpng
|
||||||
run: |
|
run: |
|
||||||
cmake --install pngbuild
|
cmake --install pngbuild
|
||||||
- name: Build Windows binaries
|
- name: Build Windows binaries
|
||||||
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
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 -DZLIB_LIBRARY="$PWD"/install_dir/lib/zlib.lib -DZLIB_INCLUDE_DIR="$PWD"/install_dir/include -DPNG_LIBRARY="$PWD"/install_dir/lib/libpng16.lib -DPNG_INCLUDE_DIR="$PWD"/install_dir/include
|
||||||
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
|
||||||
- name: Package binaries
|
- name: Package binaries
|
||||||
@@ -138,6 +137,26 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
name: rgbds-canary-win${{ matrix.bits }}
|
name: rgbds-canary-win${{ matrix.bits }}
|
||||||
path: bins
|
path: bins
|
||||||
|
- name: Compute test dependency cache params
|
||||||
|
id: test-deps-cache-params
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
paths=$(test/fetch-test-deps.sh --get-paths)
|
||||||
|
hash=$(test/fetch-test-deps.sh --get-hash)
|
||||||
|
tee -a <<<"paths=\"${paths//,/\\n}\"" $GITHUB_OUTPUT
|
||||||
|
tee -a <<<"hash=${hash%-}" $GITHUB_OUTPUT
|
||||||
|
- name: Check test dependency repositories cache
|
||||||
|
id: test-deps-cache
|
||||||
|
uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: ${{ fromJSON(steps.test-deps-cache-params.outputs.paths) }}
|
||||||
|
key: ${{ matrix.os }}-${{ matrix.bits }}-${{ steps.test-deps-cache-params.outputs.hash }}
|
||||||
|
- if: steps.test-deps-cache.outputs.cache-hit != 'true'
|
||||||
|
name: Fetch test dependency repositories
|
||||||
|
shell: bash
|
||||||
|
continue-on-error: true
|
||||||
|
run: |
|
||||||
|
test/fetch-test-deps.sh
|
||||||
- name: Test
|
- name: Test
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
@@ -149,7 +168,6 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
bits: [32, 64]
|
bits: [32, 64]
|
||||||
os: [ubuntu-18.04]
|
|
||||||
include:
|
include:
|
||||||
- bits: 32
|
- bits: 32
|
||||||
arch: i686
|
arch: i686
|
||||||
@@ -158,36 +176,31 @@ jobs:
|
|||||||
arch: x86-64
|
arch: x86-64
|
||||||
triplet: x86_64-w64-mingw32
|
triplet: x86_64-w64-mingw32
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ubuntu-22.04
|
||||||
env:
|
env:
|
||||||
DIST_DIR: win${{ matrix.bits }}
|
DIST_DIR: win${{ matrix.bits }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- name: Install deps
|
- name: Install deps
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
./.github/actions/install_deps.sh ${{ matrix.os }}
|
./.github/scripts/install_deps.sh ${{ matrix.os }}
|
||||||
- name: Install MinGW
|
- name: Install MinGW
|
||||||
run: |
|
run: | # dpkg-dev is apparently required for pkg-config for cross-building
|
||||||
sudo apt-get install {gcc,g++}-mingw-w64-${{ matrix.arch }} mingw-w64-tools libz-mingw-w64-dev
|
sudo apt-get install {gcc,g++}-mingw-w64-${{ matrix.arch }}-win32 mingw-w64-tools libz-mingw-w64-dev dpkg-dev
|
||||||
- name: Install libpng dev headers for MinGW
|
- name: Install libpng dev headers for MinGW
|
||||||
run: |
|
run: |
|
||||||
sudo ./.github/actions/mingw-w64-libpng-dev.sh ${{ matrix.triplet }}
|
sudo ./.github/scripts/mingw-w64-libpng-dev.sh ${{ matrix.triplet }}
|
||||||
- name: Cross-build Windows binaries
|
- name: Cross-build Windows binaries
|
||||||
run: |
|
run: |
|
||||||
make mingw${{ matrix.bits }} -j Q=
|
make mingw${{ matrix.bits }} -j Q=
|
||||||
- name: Package binaries
|
- name: Package binaries
|
||||||
run: |
|
run: | # DLL dependencies can be figured out using e.g. Dependency Walker
|
||||||
mkdir bins
|
mkdir bins
|
||||||
mv rgbasm bins/rgbasm.exe
|
mv -v rgb{asm,link,fix,gfx}.exe bins/
|
||||||
mv rgblink bins/rgblink.exe
|
cp -v /usr/${{ matrix.triplet }}/lib/zlib1.dll bins
|
||||||
mv rgbfix bins/rgbfix.exe
|
cp -v /usr/${{ matrix.triplet }}/bin/libpng16-16.dll bins
|
||||||
mv rgbgfx bins/rgbgfx.exe
|
[ "${{ matrix.bits }}" -ne 32 ] || cp -v /usr/lib/gcc/${{ matrix.triplet }}/10-win32/lib{gcc_s_dw2-1,ssp-0,stdc++-6}.dll bins
|
||||||
cp /usr/${{ matrix.triplet }}/lib/zlib1.dll bins
|
|
||||||
cp /usr/${{ matrix.triplet }}/bin/libpng16-16.dll bins
|
|
||||||
if [ ${{ matrix.bits }} -eq 32 ]; then cp /usr/lib/gcc/${{ matrix.triplet }}/7.3-win32/lib{gcc_s_sjlj-1,stdc++-6}.dll bins; fi
|
|
||||||
mv test/gfx/randtilegen{,.exe}
|
|
||||||
mv test/gfx/rgbgfx_test{,.exe}
|
|
||||||
- name: Upload Windows binaries
|
- name: Upload Windows binaries
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
@@ -205,11 +218,12 @@ jobs:
|
|||||||
needs: windows-xbuild
|
needs: windows-xbuild
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
|
os: [windows-2019, windows-2022]
|
||||||
bits: [32, 64]
|
bits: [32, 64]
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
runs-on: windows-2019
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- name: Retrieve binaries
|
- name: Retrieve binaries
|
||||||
uses: actions/download-artifact@v3
|
uses: actions/download-artifact@v3
|
||||||
with:
|
with:
|
||||||
@@ -225,6 +239,26 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
cp bins/* .
|
cp bins/* .
|
||||||
cp bins/*.dll test/gfx
|
cp bins/*.dll test/gfx
|
||||||
|
- name: Compute test dependency cache params
|
||||||
|
id: test-deps-cache-params
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
paths=$(test/fetch-test-deps.sh --get-paths)
|
||||||
|
hash=$(test/fetch-test-deps.sh --get-hash)
|
||||||
|
tee -a <<<"paths=\"${paths//,/\\n}\"" $GITHUB_OUTPUT
|
||||||
|
tee -a <<<"hash=${hash%-}" $GITHUB_OUTPUT
|
||||||
|
- name: Check test dependency repositories cache
|
||||||
|
id: test-deps-cache
|
||||||
|
uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: ${{ fromJSON(steps.test-deps-cache-params.outputs.paths) }}
|
||||||
|
key: mingw-${{ matrix.bits }}-${{ steps.test-deps-cache-params.outputs.hash }}
|
||||||
|
- if: steps.test-deps-cache.outputs.cache-hit != 'true'
|
||||||
|
name: Fetch test dependency repositories
|
||||||
|
shell: bash
|
||||||
|
continue-on-error: true
|
||||||
|
run: |
|
||||||
|
test/fetch-test-deps.sh
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
16
.github/workflows/update-master-docs.yml
vendored
16
.github/workflows/update-master-docs.yml
vendored
@@ -17,30 +17,24 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
if: github.repository_owner == 'gbdev'
|
if: github.repository_owner == 'gbdev'
|
||||||
runs-on: ubuntu-18.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout rgbds@master
|
- name: Checkout rgbds@master
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
repository: gbdev/rgbds
|
repository: gbdev/rgbds
|
||||||
ref: master
|
ref: master
|
||||||
path: rgbds
|
path: rgbds
|
||||||
- name: Checkout rgbds-www@master
|
- name: Checkout rgbds-www@master
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
repository: gbdev/rgbds-www
|
repository: gbdev/rgbds-www
|
||||||
ref: master
|
ref: master
|
||||||
path: rgbds-www
|
path: rgbds-www
|
||||||
- name: Build and install mandoc + install groff
|
- name: Install groff and mandoc
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get -qq update
|
sudo apt-get -qq update
|
||||||
sudo apt-get install -yq groff zlib1g-dev
|
sudo apt-get install -yq groff mandoc
|
||||||
wget 'http://mandoc.bsd.lv/snapshots/mandoc-1.14.6.tar.gz'
|
|
||||||
tar xf mandoc-1.14.6.tar.gz
|
|
||||||
cd mandoc-1.14.6
|
|
||||||
./configure
|
|
||||||
make
|
|
||||||
sudo make install
|
|
||||||
- name: Update pages
|
- name: Update pages
|
||||||
working-directory: rgbds/man
|
working-directory: rgbds/man
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ else()
|
|||||||
-fsanitize=alignment -fsanitize=null -fsanitize=address)
|
-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)
|
||||||
# A non-zero optimization level is desired in debug mode, but allow overriding it nonetheless
|
# A non-zero optimization level is desired in debug mode, but allow overriding it nonetheless
|
||||||
# TODO: this overrides anything previously set... that's a bit sloppy!
|
# TODO: this overrides anything previously set... that's a bit sloppy!
|
||||||
set(CMAKE_C_FLAGS_DEBUG "-g -Og -fno-omit-frame-pointer -fno-optimize-sibling-calls" CACHE STRING "" FORCE)
|
set(CMAKE_C_FLAGS_DEBUG "-g -Og -fno-omit-frame-pointer -fno-optimize-sibling-calls" CACHE STRING "" FORCE)
|
||||||
|
|||||||
38
Makefile
38
Makefile
@@ -9,6 +9,8 @@
|
|||||||
.SUFFIXES:
|
.SUFFIXES:
|
||||||
.SUFFIXES: .h .y .c .cpp .o
|
.SUFFIXES: .h .y .c .cpp .o
|
||||||
|
|
||||||
|
.PHONY: all clean install checkcodebase checkpatch checkdiff develop debug mingw32 mingw64 wine-shim dist
|
||||||
|
|
||||||
# User-defined variables
|
# User-defined variables
|
||||||
|
|
||||||
Q := @
|
Q := @
|
||||||
@@ -91,9 +93,11 @@ rgblink_obj := \
|
|||||||
src/link/output.o \
|
src/link/output.o \
|
||||||
src/link/patch.o \
|
src/link/patch.o \
|
||||||
src/link/script.o \
|
src/link/script.o \
|
||||||
|
src/link/sdas_obj.o \
|
||||||
src/link/section.o \
|
src/link/section.o \
|
||||||
src/link/symbol.o \
|
src/link/symbol.o \
|
||||||
src/extern/getopt.o \
|
src/extern/getopt.o \
|
||||||
|
src/extern/utf8decoder.o \
|
||||||
src/error.o \
|
src/error.o \
|
||||||
src/hashmap.o \
|
src/hashmap.o \
|
||||||
src/linkdefs.o \
|
src/linkdefs.o \
|
||||||
@@ -126,7 +130,7 @@ rgbfix: ${rgbfix_obj}
|
|||||||
$Q${CC} ${REALLDFLAGS} -o $@ ${rgbfix_obj} ${REALCFLAGS} src/version.c
|
$Q${CC} ${REALLDFLAGS} -o $@ ${rgbfix_obj} ${REALCFLAGS} src/version.c
|
||||||
|
|
||||||
rgbgfx: ${rgbgfx_obj}
|
rgbgfx: ${rgbgfx_obj}
|
||||||
$Q${CXX} ${REALLDFLAGS} ${PNGLDFLAGS} -o $@ ${rgbgfx_obj} ${REALCXXFLAGS} -x c++ src/version.c ${PNGLDLIBS}
|
$Q${CXX} ${REALLDFLAGS} ${PNGLDFLAGS} -o $@ ${rgbgfx_obj} ${REALCXXFLAGS} ${PNGLDLIBS} -x c++ src/version.c
|
||||||
|
|
||||||
test/gfx/randtilegen: test/gfx/randtilegen.c
|
test/gfx/randtilegen: test/gfx/randtilegen.c
|
||||||
$Q${CC} ${REALLDFLAGS} ${PNGLDFLAGS} -o $@ $^ ${REALCFLAGS} ${PNGCFLAGS} ${PNGLDLIBS}
|
$Q${CC} ${REALLDFLAGS} ${PNGLDFLAGS} -o $@ $^ ${REALCFLAGS} ${PNGCFLAGS} ${PNGLDLIBS}
|
||||||
@@ -179,21 +183,11 @@ clean:
|
|||||||
# Target used to install the binaries and man pages.
|
# Target used to install the binaries and man pages.
|
||||||
|
|
||||||
install: all
|
install: all
|
||||||
$Qmkdir -p ${DESTDIR}${bindir}
|
$Qinstall -d ${DESTDIR}${bindir}/ ${DESTDIR}${mandir}/man1/ ${DESTDIR}${mandir}/man5/ ${DESTDIR}${mandir}/man7/
|
||||||
$Qinstall ${STRIP} -m ${BINMODE} rgbasm ${DESTDIR}${bindir}/rgbasm
|
$Qinstall ${STRIP} -m ${BINMODE} rgbasm rgblink rgbfix rgbgfx ${DESTDIR}${bindir}/
|
||||||
$Qinstall ${STRIP} -m ${BINMODE} rgbfix ${DESTDIR}${bindir}/rgbfix
|
$Qinstall -m ${MANMODE} man/rgbasm.1 man/rgblink.1 man/rgbfix.1 man/rgbgfx.1 ${DESTDIR}${mandir}/man1/
|
||||||
$Qinstall ${STRIP} -m ${BINMODE} rgblink ${DESTDIR}${bindir}/rgblink
|
$Qinstall -m ${MANMODE} man/rgbds.5 man/rgbasm.5 man/rgblink.5 ${DESTDIR}${mandir}/man5/
|
||||||
$Qinstall ${STRIP} -m ${BINMODE} rgbgfx ${DESTDIR}${bindir}/rgbgfx
|
$Qinstall -m ${MANMODE} man/rgbds.7 man/gbz80.7 ${DESTDIR}${mandir}/man7/
|
||||||
$Qmkdir -p ${DESTDIR}${mandir}/man1 ${DESTDIR}${mandir}/man5 ${DESTDIR}${mandir}/man7
|
|
||||||
$Qinstall -m ${MANMODE} man/rgbds.7 ${DESTDIR}${mandir}/man7/rgbds.7
|
|
||||||
$Qinstall -m ${MANMODE} man/gbz80.7 ${DESTDIR}${mandir}/man7/gbz80.7
|
|
||||||
$Qinstall -m ${MANMODE} man/rgbds.5 ${DESTDIR}${mandir}/man5/rgbds.5
|
|
||||||
$Qinstall -m ${MANMODE} man/rgbasm.1 ${DESTDIR}${mandir}/man1/rgbasm.1
|
|
||||||
$Qinstall -m ${MANMODE} man/rgbasm.5 ${DESTDIR}${mandir}/man5/rgbasm.5
|
|
||||||
$Qinstall -m ${MANMODE} man/rgbfix.1 ${DESTDIR}${mandir}/man1/rgbfix.1
|
|
||||||
$Qinstall -m ${MANMODE} man/rgblink.1 ${DESTDIR}${mandir}/man1/rgblink.1
|
|
||||||
$Qinstall -m ${MANMODE} man/rgblink.5 ${DESTDIR}${mandir}/man5/rgblink.5
|
|
||||||
$Qinstall -m ${MANMODE} man/rgbgfx.1 ${DESTDIR}${mandir}/man1/rgbgfx.1
|
|
||||||
|
|
||||||
# Target used to check the coding style of the whole codebase.
|
# Target used to check the coding style of the whole codebase.
|
||||||
# `extern/` is excluded, as it contains external code that should not be patched
|
# `extern/` is excluded, as it contains external code that should not be patched
|
||||||
@@ -240,6 +234,7 @@ develop:
|
|||||||
-Wno-type-limits -Wno-tautological-constant-out-of-range-compare \
|
-Wno-type-limits -Wno-tautological-constant-out-of-range-compare \
|
||||||
-Wvla \
|
-Wvla \
|
||||||
-Wno-unknown-warning-option \
|
-Wno-unknown-warning-option \
|
||||||
|
-D_GLIBCXX_ASSERTIONS \
|
||||||
-fsanitize=shift -fsanitize=integer-divide-by-zero \
|
-fsanitize=shift -fsanitize=integer-divide-by-zero \
|
||||||
-fsanitize=unreachable -fsanitize=vla-bound \
|
-fsanitize=unreachable -fsanitize=vla-bound \
|
||||||
-fsanitize=signed-integer-overflow -fsanitize=bounds \
|
-fsanitize=signed-integer-overflow -fsanitize=bounds \
|
||||||
@@ -248,6 +243,13 @@ develop:
|
|||||||
CFLAGS="-ggdb3 -Og -fno-omit-frame-pointer -fno-optimize-sibling-calls" \
|
CFLAGS="-ggdb3 -Og -fno-omit-frame-pointer -fno-optimize-sibling-calls" \
|
||||||
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.
|
||||||
|
|
||||||
|
debug:
|
||||||
|
$Qenv ${MAKE} \
|
||||||
|
CFLAGS="-ggdb3 -Og -fno-omit-frame-pointer -fno-optimize-sibling-calls" \
|
||||||
|
CXXFLAGS="-ggdb3 -Og -fno-omit-frame-pointer -fno-optimize-sibling-calls"
|
||||||
|
|
||||||
# Targets for the project maintainer to easily create Windows exes.
|
# Targets for the project maintainer to easily create Windows exes.
|
||||||
# This is not for Windows users!
|
# This is not for Windows users!
|
||||||
# If you're building on Windows with Cygwin or Mingw, just follow the Unix
|
# If you're building on Windows with Cygwin or Mingw, just follow the Unix
|
||||||
@@ -256,12 +258,12 @@ develop:
|
|||||||
mingw32:
|
mingw32:
|
||||||
$Q${MAKE} all test/gfx/randtilegen test/gfx/rgbgfx_test \
|
$Q${MAKE} all test/gfx/randtilegen test/gfx/rgbgfx_test \
|
||||||
CC=i686-w64-mingw32-gcc CXX=i686-w64-mingw32-g++ \
|
CC=i686-w64-mingw32-gcc CXX=i686-w64-mingw32-g++ \
|
||||||
BISON=bison PKG_CONFIG=i686-w64-mingw32-pkg-config -j
|
BISON=bison PKG_CONFIG="PKG_CONFIG_SYSROOT_DIR=/usr/i686-w64-mingw32 pkg-config"
|
||||||
|
|
||||||
mingw64:
|
mingw64:
|
||||||
$Q${MAKE} all test/gfx/randtilegen test/gfx/rgbgfx_test \
|
$Q${MAKE} all test/gfx/randtilegen test/gfx/rgbgfx_test \
|
||||||
CC=x86_64-w64-mingw32-gcc CXX=x86_64-w64-mingw32-g++ \
|
CC=x86_64-w64-mingw32-gcc CXX=x86_64-w64-mingw32-g++ \
|
||||||
BISON=bison PKG_CONFIG=x86_64-w64-mingw32-pkg-config -j
|
BISON=bison PKG_CONFIG="PKG_CONFIG_SYSROOT_DIR=/usr/x86_64-w64-mingw32 pkg-config"
|
||||||
|
|
||||||
wine-shim:
|
wine-shim:
|
||||||
$Qecho '#!/bin/bash' > rgbshim.sh
|
$Qecho '#!/bin/bash' > rgbshim.sh
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
# Known bugs:
|
# Known bugs:
|
||||||
# - Newlines in file/directory names break this script
|
# - Newlines in file/directory names break this script
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
# - Directories are not completed as such in "coalesced" short-opt arguments. For example,
|
# - Directories are not completed as such in "coalesced" short-opt arguments. For example,
|
||||||
# `rgbasm -M d<tab>` can autocomplete to `rgbasm -M dir/` (no space), but
|
# `rgbasm -M d<tab>` can autocomplete to `rgbasm -M dir/` (no space), but
|
||||||
# `rgbasm -Md<tab>` would autocomplete to `rgbasm -Mdir ` (trailing space) instead.
|
# `rgbasm -Md<tab>` would autocomplete to `rgbasm -Mdir ` (trailing space) instead.
|
||||||
# This is because dircetory handling is performed by Readline, whom we can't tell about the short
|
# This is because directory handling is performed by Readline, whom we can't tell about the short
|
||||||
# opt kerfuffle. The user can work around by separating the argument, as shown above.
|
# opt kerfuffle. The user can work around by separating the argument, as shown above.
|
||||||
# (Also, there might be more possible bugs if `-Mdir` is actually a directory. Ugh.)
|
# (Also, there might be more possible bugs if `-Mdir` is actually a directory. Ugh.)
|
||||||
|
|
||||||
@@ -20,25 +20,27 @@
|
|||||||
# Thus, we don't need to do much to handle that form of argument passing: skip '=' after long opts.
|
# Thus, we don't need to do much to handle that form of argument passing: skip '=' after long opts.
|
||||||
|
|
||||||
_rgbasm_completions() {
|
_rgbasm_completions() {
|
||||||
COMPREPLY=()
|
|
||||||
|
|
||||||
# Format: "long_opt:state_after"
|
# Format: "long_opt:state_after"
|
||||||
# Empty long opt = it doesn't exit
|
# Empty long opt = it doesn't exit
|
||||||
# See the `state` variable below for info about `state_after`
|
# See the `state` variable below for info about `state_after`
|
||||||
declare -A opts=(
|
declare -A opts=(
|
||||||
[V]="version:normal"
|
[V]="version:normal"
|
||||||
[E]="export-all:normal"
|
[E]="export-all:normal"
|
||||||
|
[H]="nop-after-halt:normal"
|
||||||
[h]="halt-without-nop:normal"
|
[h]="halt-without-nop:normal"
|
||||||
[L]="preserve-ld:normal"
|
[L]="preserve-ld:normal"
|
||||||
|
[l]="auto-ldh:normal"
|
||||||
[v]="verbose:normal"
|
[v]="verbose:normal"
|
||||||
[w]=":normal"
|
[w]=":normal"
|
||||||
[b]="binary-digits:unk"
|
[b]="binary-digits:unk"
|
||||||
[D]="define:unk"
|
[D]="define:unk"
|
||||||
[g]="gfx-chars:unk"
|
[g]="gfx-chars:unk"
|
||||||
[i]="include:dir"
|
[I]="include:dir"
|
||||||
[M]="dependfile:glob-*.mk *.d"
|
[M]="dependfile:glob-*.mk *.d"
|
||||||
[o]="output:glob-*.o"
|
[o]="output:glob-*.o"
|
||||||
|
[P]="preinclude:glob-*.asm *.inc"
|
||||||
[p]="pad-value:unk"
|
[p]="pad-value:unk"
|
||||||
|
[Q]="q-precision:unk"
|
||||||
[r]="recursion-depth:unk"
|
[r]="recursion-depth:unk"
|
||||||
[W]="warning:warning"
|
[W]="warning:warning"
|
||||||
)
|
)
|
||||||
@@ -58,6 +60,18 @@ _rgbasm_completions() {
|
|||||||
# "normal" is not returned, `optlen` will be set to the length (dash included) of the "option"
|
# "normal" is not returned, `optlen` will be set to the length (dash included) of the "option"
|
||||||
# part of the argument.
|
# part of the argument.
|
||||||
parse_short_opt() {
|
parse_short_opt() {
|
||||||
|
# These options act like a long option (= takes up the entire word), but only use a single dash
|
||||||
|
# So, they need some special handling
|
||||||
|
if [[ "$1" = "-M"[GP] ]]; then
|
||||||
|
state=normal
|
||||||
|
optlen=${#1}
|
||||||
|
return;
|
||||||
|
elif [[ "$1" = "-M"[QT] ]]; then
|
||||||
|
state='glob-*.d *.mk *.o'
|
||||||
|
optlen=${#1}
|
||||||
|
return;
|
||||||
|
fi
|
||||||
|
|
||||||
for (( i = 1; i < "${#1}"; i++ )); do
|
for (( i = 1; i < "${#1}"; i++ )); do
|
||||||
# If the option is not known, assume it doesn't take an argument
|
# If the option is not known, assume it doesn't take an argument
|
||||||
local opt="${opts["${1:$i:1}"]:-":normal"}"
|
local opt="${opts["${1:$i:1}"]:-":normal"}"
|
||||||
@@ -71,7 +85,7 @@ _rgbasm_completions() {
|
|||||||
optlen=0
|
optlen=0
|
||||||
}
|
}
|
||||||
|
|
||||||
for (( i = 1; i < $COMP_CWORD; i++ )); do
|
for (( i = 1; i < COMP_CWORD; i++ )); do
|
||||||
local word="${COMP_WORDS[$i]}"
|
local word="${COMP_WORDS[$i]}"
|
||||||
|
|
||||||
# If currently processing an argument, skip this word
|
# If currently processing an argument, skip this word
|
||||||
@@ -87,7 +101,7 @@ _rgbasm_completions() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Check if it's a long option
|
# Check if it's a long option
|
||||||
if [[ "${word:0:2}" = '--' ]]; then
|
if [[ "$word" = '--'* ]]; then
|
||||||
# If the option is unknown, assume it takes no arguments: keep the state at "normal"
|
# If the option is unknown, assume it takes no arguments: keep the state at "normal"
|
||||||
for long_opt in "${opts[@]}"; do
|
for long_opt in "${opts[@]}"; do
|
||||||
if [[ "$word" = "--${long_opt%%:*}" ]]; then
|
if [[ "$word" = "--${long_opt%%:*}" ]]; then
|
||||||
@@ -103,25 +117,16 @@ _rgbasm_completions() {
|
|||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
# Check if it's a short option
|
# Check if it's a short option
|
||||||
elif [[ "${word:0:1}" = '-' ]]; then
|
elif [[ "$word" = '-'* ]]; then
|
||||||
# The `-M?` ones are a mix of short and long, augh
|
parse_short_opt "$word"
|
||||||
# They must match the *full* word, but only take a single dash
|
# The last option takes an argument...
|
||||||
# So, handle them here
|
if [[ "$state" != 'normal' ]]; then
|
||||||
if [[ "$1" = "-M"[GP] ]]; then
|
if [[ "$optlen" -ne "${#word}" ]]; then
|
||||||
state=normal
|
# If it's contained within the word, we won't complete it, revert to "normal"
|
||||||
elif [[ "$1" = "-M"[TQ] ]]; then
|
state=normal
|
||||||
state='glob-*.d *.mk *.o'
|
else
|
||||||
else
|
# Otherwise, complete it, but start at the beginning of *that* word
|
||||||
parse_short_opt "$word"
|
optlen=0
|
||||||
# The last option takes an argument...
|
|
||||||
if [[ "$state" != 'normal' ]]; then
|
|
||||||
if [[ "$optlen" -ne "${#word}" ]]; then
|
|
||||||
# If it's contained within the word, we won't complete it, revert to "normal"
|
|
||||||
state=normal
|
|
||||||
else
|
|
||||||
# Otherwise, complete it, but start at the beginning of *that* word
|
|
||||||
optlen=0
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@@ -129,48 +134,48 @@ _rgbasm_completions() {
|
|||||||
|
|
||||||
# Parse current word
|
# Parse current word
|
||||||
# Careful that it might look like an option, so use `--` aggressively!
|
# Careful that it might look like an option, so use `--` aggressively!
|
||||||
local cur_word="${COMP_WORDS[$COMP_CWORD]}"
|
local cur_word="${COMP_WORDS[$i]}"
|
||||||
|
|
||||||
# Process options, as short ones may change the state
|
# Process options, as short ones may change the state
|
||||||
if $opt_ena && [[ "$state" = 'normal' && "${cur_word:0:1}" = '-' ]]; then
|
if $opt_ena && [[ "$state" = 'normal' && "$cur_word" = '-'* ]]; then
|
||||||
# We might want to complete to an option or an arg to that option
|
# We might want to complete to an option or an arg to that option
|
||||||
# Parse the option word to check
|
# Parse the option word to check
|
||||||
# There's no whitespace in the option names, so we can ride a little dirty...
|
# There's no whitespace in the option names, so we can ride a little dirty...
|
||||||
|
|
||||||
# Is this a long option?
|
# Is this a long option?
|
||||||
if [[ "${cur_word:1:1}" = '-' ]]; then
|
if [[ "$cur_word" = '--'* ]]; then
|
||||||
# It is, try to complete one
|
# It is, try to complete one
|
||||||
COMPREPLY+=( $(compgen -W "${opts[*]%%:*}" -P '--' -- "${cur_word#--}") )
|
mapfile -t COMPREPLY < <(compgen -W "${opts[*]%%:*}" -P '--' -- "${cur_word#--}")
|
||||||
|
return 0
|
||||||
|
elif [[ "$cur_word" = '-M'[GPQT] ]]; then
|
||||||
|
# These options act like long opts with no arguments, so return them and exactly them
|
||||||
|
COMPREPLY=( "$cur_word" )
|
||||||
return 0
|
return 0
|
||||||
else
|
else
|
||||||
# Short options may be grouped, parse them to determine what to complete
|
# Short options may be grouped, parse them to determine what to complete
|
||||||
# The `-M?` ones may not be followed by anything
|
parse_short_opt "$cur_word"
|
||||||
if [[ "$1" != "-M"[GPTQ] ]]; then
|
|
||||||
parse_short_opt "$cur_word"
|
|
||||||
# We got some short options that behave like long ones
|
|
||||||
COMPREPLY+=( $(compgen -W '-MG -MP -MT -MQ' -- "$cur_word") )
|
|
||||||
|
|
||||||
if [[ "$state" = 'normal' ]]; then
|
if [[ "$state" = 'normal' ]]; then
|
||||||
COMPREPLY+=( $(compgen -W "${!opts[*]}" -P "$cur_word" '') )
|
mapfile -t COMPREPLY < <(compgen -W "${!opts[*]}" -P "$cur_word" ''; compgen -W '-MG -MP -MQ -MT' "$cur_word")
|
||||||
return 0
|
return 0
|
||||||
elif [[ "$optlen" = "${#cur_word}" && "$state" != "warning" ]]; then
|
elif [[ "$optlen" = "${#cur_word}" && "$state" != "warning" ]]; then
|
||||||
# This short option group only awaits its argument!
|
# This short option group only awaits its argument!
|
||||||
# Post the option group as-is as a reply so that Readline inserts a space,
|
# Post the option group as-is as a reply so that Readline inserts a space,
|
||||||
# so that the next completion request switches to the argument
|
# so that the next completion request switches to the argument
|
||||||
# An exception is made for warnings, since it's idiomatic to stick them to the
|
# An exception is made for warnings, since it's idiomatic to stick them to the
|
||||||
# `-W`, and it doesn't break anything.
|
# `-W`, and it doesn't break anything.
|
||||||
COMPREPLY+=( "$cur_word" )
|
COMPREPLY=( "$cur_word" )
|
||||||
return 0
|
return 0
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
COMPREPLY=()
|
||||||
case "$state" in
|
case "$state" in
|
||||||
unk) # Return with no replies: no idea what to complete!
|
unk) # Return with no replies: no idea what to complete!
|
||||||
;;
|
;;
|
||||||
warning)
|
warning)
|
||||||
COMPREPLY+=( $(compgen -W "
|
mapfile -t COMPREPLY < <(compgen -W "
|
||||||
assert
|
assert
|
||||||
backwards-for
|
backwards-for
|
||||||
builtin-args
|
builtin-args
|
||||||
@@ -188,11 +193,12 @@ _rgbasm_completions() {
|
|||||||
shift
|
shift
|
||||||
shift-amount
|
shift-amount
|
||||||
truncation
|
truncation
|
||||||
|
unmapped-char
|
||||||
user
|
user
|
||||||
all
|
all
|
||||||
extra
|
extra
|
||||||
everything
|
everything
|
||||||
error" -P "${cur_word:0:$optlen}" -- "${cur_word:$optlen}") )
|
error" -P "${cur_word:0:$optlen}" -- "${cur_word:$optlen}")
|
||||||
;;
|
;;
|
||||||
normal) # Acts like a glob...
|
normal) # Acts like a glob...
|
||||||
state="glob-*.asm *.inc *.sm83"
|
state="glob-*.asm *.inc *.sm83"
|
||||||
@@ -209,6 +215,10 @@ _rgbasm_completions() {
|
|||||||
done < <(compgen -A directory -- "${cur_word:$optlen}")
|
done < <(compgen -A directory -- "${cur_word:$optlen}")
|
||||||
compopt -o filenames
|
compopt -o filenames
|
||||||
;;
|
;;
|
||||||
|
*)
|
||||||
|
echo >&2 "Internal completion error: invalid state \"$state\", please report this bug"
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
#/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
# Same notes as RGBASM
|
# Same notes as RGBASM
|
||||||
|
|
||||||
_rgbfix_completions() {
|
_rgbfix_completions() {
|
||||||
COMPREPLY=()
|
|
||||||
|
|
||||||
# Format: "long_opt:state_after"
|
# Format: "long_opt:state_after"
|
||||||
# Empty long opt = it doesn't exit
|
# Empty long opt = it doesn't exit
|
||||||
# See the `state` variable below for info about `state_after`
|
# See the `state` variable below for info about `state_after`
|
||||||
@@ -54,7 +52,7 @@ _rgbfix_completions() {
|
|||||||
optlen=0
|
optlen=0
|
||||||
}
|
}
|
||||||
|
|
||||||
for (( i = 1; i < $COMP_CWORD; i++ )); do
|
for (( i = 1; i < COMP_CWORD; i++ )); do
|
||||||
local word="${COMP_WORDS[$i]}"
|
local word="${COMP_WORDS[$i]}"
|
||||||
|
|
||||||
# If currently processing an argument, skip this word
|
# If currently processing an argument, skip this word
|
||||||
@@ -70,7 +68,7 @@ _rgbfix_completions() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Check if it's a long option
|
# Check if it's a long option
|
||||||
if [[ "${word:0:2}" = '--' ]]; then
|
if [[ "$word" = '--'* ]]; then
|
||||||
# If the option is unknown, assume it takes no arguments: keep the state at "normal"
|
# If the option is unknown, assume it takes no arguments: keep the state at "normal"
|
||||||
for long_opt in "${opts[@]}"; do
|
for long_opt in "${opts[@]}"; do
|
||||||
if [[ "$word" = "--${long_opt%%:*}" ]]; then
|
if [[ "$word" = "--${long_opt%%:*}" ]]; then
|
||||||
@@ -86,7 +84,7 @@ _rgbfix_completions() {
|
|||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
# Check if it's a short option
|
# Check if it's a short option
|
||||||
elif [[ "${word:0:1}" = '-' ]]; then
|
elif [[ "$word" = '-'* ]]; then
|
||||||
parse_short_opt "$word"
|
parse_short_opt "$word"
|
||||||
# The last option takes an argument...
|
# The last option takes an argument...
|
||||||
if [[ "$state" != 'normal' ]]; then
|
if [[ "$state" != 'normal' ]]; then
|
||||||
@@ -103,25 +101,25 @@ _rgbfix_completions() {
|
|||||||
|
|
||||||
# Parse current word
|
# Parse current word
|
||||||
# Careful that it might look like an option, so use `--` aggressively!
|
# Careful that it might look like an option, so use `--` aggressively!
|
||||||
local cur_word="${COMP_WORDS[$COMP_CWORD]}"
|
local cur_word="${COMP_WORDS[$i]}"
|
||||||
|
|
||||||
# Process options, as short ones may change the state
|
# Process options, as short ones may change the state
|
||||||
if $opt_ena && [[ "$state" = 'normal' && "${cur_word:0:1}" = '-' ]]; then
|
if $opt_ena && [[ "$state" = 'normal' && "$cur_word" = '-'* ]]; then
|
||||||
# We might want to complete to an option or an arg to that option
|
# We might want to complete to an option or an arg to that option
|
||||||
# Parse the option word to check
|
# Parse the option word to check
|
||||||
# There's no whitespace in the option names, so we can ride a little dirty...
|
# There's no whitespace in the option names, so we can ride a little dirty...
|
||||||
|
|
||||||
# Is this a long option?
|
# Is this a long option?
|
||||||
if [[ "${cur_word:1:1}" = '-' ]]; then
|
if [[ "$cur_word" = '--'* ]]; then
|
||||||
# It is, try to complete one
|
# It is, try to complete one
|
||||||
COMPREPLY+=( $(compgen -W "${opts[*]%%:*}" -P '--' -- "${cur_word#--}") )
|
mapfile -t COMPREPLY < <(compgen -W "${opts[*]%%:*}" -P '--' -- "${cur_word#--}")
|
||||||
return 0
|
return 0
|
||||||
else
|
else
|
||||||
# Short options may be grouped, parse them to determine what to complete
|
# Short options may be grouped, parse them to determine what to complete
|
||||||
parse_short_opt "$cur_word"
|
parse_short_opt "$cur_word"
|
||||||
|
|
||||||
if [[ "$state" = 'normal' ]]; then
|
if [[ "$state" = 'normal' ]]; then
|
||||||
COMPREPLY+=( $(compgen -W "${!opts[*]}" -P "$cur_word" '') )
|
mapfile -t COMPREPLY < <(compgen -W "${!opts[*]}" -P "$cur_word" '')
|
||||||
return 0
|
return 0
|
||||||
elif [[ "$optlen" = "${#cur_word}" && "$state" != "warning" ]]; then
|
elif [[ "$optlen" = "${#cur_word}" && "$state" != "warning" ]]; then
|
||||||
# This short option group only awaits its argument!
|
# This short option group only awaits its argument!
|
||||||
@@ -129,22 +127,25 @@ _rgbfix_completions() {
|
|||||||
# so that the next completion request switches to the argument
|
# so that the next completion request switches to the argument
|
||||||
# An exception is made for warnings, since it's idiomatic to stick them to the
|
# An exception is made for warnings, since it's idiomatic to stick them to the
|
||||||
# `-W`, and it doesn't break anything.
|
# `-W`, and it doesn't break anything.
|
||||||
COMPREPLY+=( "$cur_word" )
|
COMPREPLY=( "$cur_word" )
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
COMPREPLY=()
|
||||||
case "$state" in
|
case "$state" in
|
||||||
unk) # Return with no replies: no idea what to complete!
|
unk) # Return with no replies: no idea what to complete!
|
||||||
;;
|
;;
|
||||||
fix-spec)
|
fix-spec)
|
||||||
COMPREPLY+=( "${cur_word}"{l,h,g,L,H,G} )
|
COMPREPLY=( "${cur_word}"{l,h,g,L,H,G} )
|
||||||
;;
|
;;
|
||||||
mbc)
|
mbc)
|
||||||
local cur_arg="${cur_word:$optlen}"
|
local cur_arg="${cur_word:$optlen}"
|
||||||
cur_arg="${cur_arg@U}"
|
cur_arg="${cur_arg@U}"
|
||||||
COMPREPLY=( $(compgen -W "
|
compopt -o nosort # Keep `help` first in the list, mainly
|
||||||
|
mapfile -t COMPREPLY < <(compgen -W "help" -P "${cur_word:0:$optlen}" -- "${cur_word:$optlen}")
|
||||||
|
mapfile -t COMPREPLY -O ${#COMPREPLY} < <(compgen -W "
|
||||||
ROM_ONLY
|
ROM_ONLY
|
||||||
MBC1{,+RAM,+RAM+BATTERY}
|
MBC1{,+RAM,+RAM+BATTERY}
|
||||||
MBC2{,+BATTERY}
|
MBC2{,+BATTERY}
|
||||||
@@ -157,8 +158,7 @@ _rgbfix_completions() {
|
|||||||
BANDAI_TAMA5
|
BANDAI_TAMA5
|
||||||
HUC3
|
HUC3
|
||||||
HUC1+RAM+BATTERY
|
HUC1+RAM+BATTERY
|
||||||
TPP1_1.0{,+BATTERY}{,+RTC}{,+RUMBLE,+MULTIRUMBLE}" -P "${cur_word:0:$optlen}" -- "`tr 'a-z ' 'A-Z_' <<<"${cur_word/ /_}"`") )
|
TPP1_1.0{,+BATTERY}{,+RTC}{,+RUMBLE,+MULTIRUMBLE}" -P "${cur_word:0:$optlen}" -- "${cur_word/ /_}")
|
||||||
COMPREPLY+=( $(compgen -W "help" -P "${cur_word:0:$optlen}" -- "${cur_word:$optlen}") )
|
|
||||||
;;
|
;;
|
||||||
normal) # Acts like a glob...
|
normal) # Acts like a glob...
|
||||||
state="glob-*.gb *.gbc *.sgb"
|
state="glob-*.gb *.gbc *.sgb"
|
||||||
@@ -175,6 +175,10 @@ _rgbfix_completions() {
|
|||||||
done < <(compgen -A directory -- "${cur_word:$optlen}")
|
done < <(compgen -A directory -- "${cur_word:$optlen}")
|
||||||
compopt -o filenames
|
compopt -o filenames
|
||||||
;;
|
;;
|
||||||
|
*)
|
||||||
|
echo >&2 "Internal completion error: invalid state \"$state\", please report this bug"
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
#/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
# Same notes as RGBASM
|
# Same notes as RGBASM
|
||||||
|
|
||||||
_rgbgfx_completions() {
|
_rgbgfx_completions() {
|
||||||
COMPREPLY=()
|
|
||||||
|
|
||||||
# Format: "long_opt:state_after"
|
# Format: "long_opt:state_after"
|
||||||
# Empty long opt = it doesn't exit
|
# Empty long opt = it doesn't exit
|
||||||
# See the `state` variable below for info about `state_after`
|
# See the `state` variable below for info about `state_after`
|
||||||
@@ -15,21 +13,21 @@ _rgbgfx_completions() {
|
|||||||
[u]="unique-tiles:normal"
|
[u]="unique-tiles:normal"
|
||||||
[v]="verbose:normal"
|
[v]="verbose:normal"
|
||||||
[Z]="columns:normal"
|
[Z]="columns:normal"
|
||||||
[a]="attr-map:*.attrmap"
|
[a]="attr-map:glob-*.attrmap"
|
||||||
[A]="output-attr-map:normal"
|
[A]="output-attr-map:normal"
|
||||||
[b]="base-tiles:unk"
|
[b]="base-tiles:unk"
|
||||||
[d]="depth:unk"
|
[d]="depth:unk"
|
||||||
[L]="slice:unk"
|
[L]="slice:unk"
|
||||||
[N]="nb-tiles:unk"
|
[N]="nb-tiles:unk"
|
||||||
[n]="nb-palettes:unk"
|
[n]="nb-palettes:unk"
|
||||||
[o]="output:glob *.2bpp"
|
[o]="output:glob-*.2bpp"
|
||||||
[p]="palette:glob *.pal"
|
[p]="palette:glob-*.pal"
|
||||||
[P]="output-palette:normal"
|
[P]="output-palette:normal"
|
||||||
[q]="palette-map:glob *.palmap"
|
[q]="palette-map:glob-*.palmap"
|
||||||
[Q]="output-palette-map:normal"
|
[Q]="output-palette-map:normal"
|
||||||
[r]="reverse:unk"
|
[r]="reverse:unk"
|
||||||
[s]="palette-size:unk"
|
[s]="palette-size:unk"
|
||||||
[t]="tilemap:glob *.tilemap"
|
[t]="tilemap:glob-*.tilemap"
|
||||||
[T]="output-tilemap:normal"
|
[T]="output-tilemap:normal"
|
||||||
[x]="trim-end:unk"
|
[x]="trim-end:unk"
|
||||||
)
|
)
|
||||||
@@ -62,7 +60,7 @@ _rgbgfx_completions() {
|
|||||||
optlen=0
|
optlen=0
|
||||||
}
|
}
|
||||||
|
|
||||||
for (( i = 1; i < $COMP_CWORD; i++ )); do
|
for (( i = 1; i < COMP_CWORD; i++ )); do
|
||||||
local word="${COMP_WORDS[$i]}"
|
local word="${COMP_WORDS[$i]}"
|
||||||
|
|
||||||
# If currently processing an argument, skip this word
|
# If currently processing an argument, skip this word
|
||||||
@@ -78,7 +76,7 @@ _rgbgfx_completions() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Check if it's a long option
|
# Check if it's a long option
|
||||||
if [[ "${word:0:2}" = '--' ]]; then
|
if [[ "$word" = '--'* ]]; then
|
||||||
# If the option is unknown, assume it takes no arguments: keep the state at "normal"
|
# If the option is unknown, assume it takes no arguments: keep the state at "normal"
|
||||||
for long_opt in "${opts[@]}"; do
|
for long_opt in "${opts[@]}"; do
|
||||||
if [[ "$word" = "--${long_opt%%:*}" ]]; then
|
if [[ "$word" = "--${long_opt%%:*}" ]]; then
|
||||||
@@ -94,7 +92,7 @@ _rgbgfx_completions() {
|
|||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
# Check if it's a short option
|
# Check if it's a short option
|
||||||
elif [[ "${word:0:1}" = '-' ]]; then
|
elif [[ "$word" = '-'* ]]; then
|
||||||
parse_short_opt "$word"
|
parse_short_opt "$word"
|
||||||
# The last option takes an argument...
|
# The last option takes an argument...
|
||||||
if [[ "$state" != 'normal' ]]; then
|
if [[ "$state" != 'normal' ]]; then
|
||||||
@@ -111,25 +109,25 @@ _rgbgfx_completions() {
|
|||||||
|
|
||||||
# Parse current word
|
# Parse current word
|
||||||
# Careful that it might look like an option, so use `--` aggressively!
|
# Careful that it might look like an option, so use `--` aggressively!
|
||||||
local cur_word="${COMP_WORDS[$COMP_CWORD]}"
|
local cur_word="${COMP_WORDS[$i]}"
|
||||||
|
|
||||||
# Process options, as short ones may change the state
|
# Process options, as short ones may change the state
|
||||||
if $opt_ena && [[ "$state" = 'normal' && "${cur_word:0:1}" = '-' ]]; then
|
if $opt_ena && [[ "$state" = 'normal' && "$cur_word" = '-'* ]]; then
|
||||||
# We might want to complete to an option or an arg to that option
|
# We might want to complete to an option or an arg to that option
|
||||||
# Parse the option word to check
|
# Parse the option word to check
|
||||||
# There's no whitespace in the option names, so we can ride a little dirty...
|
# There's no whitespace in the option names, so we can ride a little dirty...
|
||||||
|
|
||||||
# Is this a long option?
|
# Is this a long option?
|
||||||
if [[ "${cur_word:1:1}" = '-' ]]; then
|
if [[ "$cur_word" = '--'* ]]; then
|
||||||
# It is, try to complete one
|
# It is, try to complete one
|
||||||
COMPREPLY+=( $(compgen -W "${opts[*]%%:*}" -P '--' -- "${cur_word#--}") )
|
mapfile -t COMPREPLY < <(compgen -W "${opts[*]%%:*}" -P '--' -- "${cur_word#--}")
|
||||||
return 0
|
return 0
|
||||||
else
|
else
|
||||||
# Short options may be grouped, parse them to determine what to complete
|
# Short options may be grouped, parse them to determine what to complete
|
||||||
parse_short_opt "$cur_word"
|
parse_short_opt "$cur_word"
|
||||||
|
|
||||||
if [[ "$state" = 'normal' ]]; then
|
if [[ "$state" = 'normal' ]]; then
|
||||||
COMPREPLY+=( $(compgen -W "${!opts[*]}" -P "$cur_word" '') )
|
mapfile -t COMPREPLY < <(compgen -W "${!opts[*]}" -P "$cur_word" '')
|
||||||
return 0
|
return 0
|
||||||
elif [[ "$optlen" = "${#cur_word}" && "$state" != "warning" ]]; then
|
elif [[ "$optlen" = "${#cur_word}" && "$state" != "warning" ]]; then
|
||||||
# This short option group only awaits its argument!
|
# This short option group only awaits its argument!
|
||||||
@@ -137,12 +135,13 @@ _rgbgfx_completions() {
|
|||||||
# so that the next completion request switches to the argument
|
# so that the next completion request switches to the argument
|
||||||
# An exception is made for warnings, since it's idiomatic to stick them to the
|
# An exception is made for warnings, since it's idiomatic to stick them to the
|
||||||
# `-W`, and it doesn't break anything.
|
# `-W`, and it doesn't break anything.
|
||||||
COMPREPLY+=( "$cur_word" )
|
COMPREPLY=( "$cur_word" )
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
COMPREPLY=()
|
||||||
case "$state" in
|
case "$state" in
|
||||||
unk) # Return with no replies: no idea what to complete!
|
unk) # Return with no replies: no idea what to complete!
|
||||||
;;
|
;;
|
||||||
@@ -161,6 +160,10 @@ _rgbgfx_completions() {
|
|||||||
done < <(compgen -A directory -- "${cur_word:$optlen}")
|
done < <(compgen -A directory -- "${cur_word:$optlen}")
|
||||||
compopt -o filenames
|
compopt -o filenames
|
||||||
;;
|
;;
|
||||||
|
*)
|
||||||
|
echo >&2 "Internal completion error: invalid state \"$state\", please report this bug"
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
#/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
# Same notes as RGBASM
|
# Same notes as RGBASM
|
||||||
|
|
||||||
_rgblink_completions() {
|
_rgblink_completions() {
|
||||||
COMPREPLY=()
|
|
||||||
|
|
||||||
# Format: "long_opt:state_after"
|
# Format: "long_opt:state_after"
|
||||||
# Empty long opt = it doesn't exit
|
# Empty long opt = it doesn't exit
|
||||||
# See the `state` variable below for info about `state_after`
|
# See the `state` variable below for info about `state_after`
|
||||||
@@ -16,6 +14,7 @@ _rgblink_completions() {
|
|||||||
[w]="wramx:normal"
|
[w]="wramx:normal"
|
||||||
[x]="nopad:normal"
|
[x]="nopad:normal"
|
||||||
[l]="linkerscript:glob-*"
|
[l]="linkerscript:glob-*"
|
||||||
|
[M]="no-sym-in-map:normal"
|
||||||
[m]="map:glob-*.map"
|
[m]="map:glob-*.map"
|
||||||
[n]="sym:glob-*.sym"
|
[n]="sym:glob-*.sym"
|
||||||
[O]="overlay:glob-*.gb *.gbc *.sgb"
|
[O]="overlay:glob-*.gb *.gbc *.sgb"
|
||||||
@@ -52,7 +51,7 @@ _rgblink_completions() {
|
|||||||
optlen=0
|
optlen=0
|
||||||
}
|
}
|
||||||
|
|
||||||
for (( i = 1; i < $COMP_CWORD; i++ )); do
|
for (( i = 1; i < COMP_CWORD; i++ )); do
|
||||||
local word="${COMP_WORDS[$i]}"
|
local word="${COMP_WORDS[$i]}"
|
||||||
|
|
||||||
# If currently processing an argument, skip this word
|
# If currently processing an argument, skip this word
|
||||||
@@ -68,7 +67,7 @@ _rgblink_completions() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Check if it's a long option
|
# Check if it's a long option
|
||||||
if [[ "${word:0:2}" = '--' ]]; then
|
if [[ "$word" = '--'* ]]; then
|
||||||
# If the option is unknown, assume it takes no arguments: keep the state at "normal"
|
# If the option is unknown, assume it takes no arguments: keep the state at "normal"
|
||||||
for long_opt in "${opts[@]}"; do
|
for long_opt in "${opts[@]}"; do
|
||||||
if [[ "$word" = "--${long_opt%%:*}" ]]; then
|
if [[ "$word" = "--${long_opt%%:*}" ]]; then
|
||||||
@@ -84,7 +83,7 @@ _rgblink_completions() {
|
|||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
# Check if it's a short option
|
# Check if it's a short option
|
||||||
elif [[ "${word:0:1}" = '-' ]]; then
|
elif [[ "$word" = '-'* ]]; then
|
||||||
parse_short_opt "$word"
|
parse_short_opt "$word"
|
||||||
# The last option takes an argument...
|
# The last option takes an argument...
|
||||||
if [[ "$state" != 'normal' ]]; then
|
if [[ "$state" != 'normal' ]]; then
|
||||||
@@ -101,25 +100,25 @@ _rgblink_completions() {
|
|||||||
|
|
||||||
# Parse current word
|
# Parse current word
|
||||||
# Careful that it might look like an option, so use `--` aggressively!
|
# Careful that it might look like an option, so use `--` aggressively!
|
||||||
local cur_word="${COMP_WORDS[$COMP_CWORD]}"
|
local cur_word="${COMP_WORDS[$i]}"
|
||||||
|
|
||||||
# Process options, as short ones may change the state
|
# Process options, as short ones may change the state
|
||||||
if $opt_ena && [[ "$state" = 'normal' && "${cur_word:0:1}" = '-' ]]; then
|
if $opt_ena && [[ "$state" = 'normal' && "$cur_word" = '-'* ]]; then
|
||||||
# We might want to complete to an option or an arg to that option
|
# We might want to complete to an option or an arg to that option
|
||||||
# Parse the option word to check
|
# Parse the option word to check
|
||||||
# There's no whitespace in the option names, so we can ride a little dirty...
|
# There's no whitespace in the option names, so we can ride a little dirty...
|
||||||
|
|
||||||
# Is this a long option?
|
# Is this a long option?
|
||||||
if [[ "${cur_word:1:1}" = '-' ]]; then
|
if [[ "$cur_word" = '--'* ]]; then
|
||||||
# It is, try to complete one
|
# It is, try to complete one
|
||||||
COMPREPLY+=( $(compgen -W "${opts[*]%%:*}" -P '--' -- "${cur_word#--}") )
|
mapfile -t COMPREPLY < <(compgen -W "${opts[*]%%:*}" -P '--' -- "${cur_word#--}")
|
||||||
return 0
|
return 0
|
||||||
else
|
else
|
||||||
# Short options may be grouped, parse them to determine what to complete
|
# Short options may be grouped, parse them to determine what to complete
|
||||||
parse_short_opt "$cur_word"
|
parse_short_opt "$cur_word"
|
||||||
|
|
||||||
if [[ "$state" = 'normal' ]]; then
|
if [[ "$state" = 'normal' ]]; then
|
||||||
COMPREPLY+=( $(compgen -W "${!opts[*]}" -P "$cur_word" '') )
|
mapfile -t COMPREPLY < <(compgen -W "${!opts[*]}" -P "$cur_word" '')
|
||||||
return 0
|
return 0
|
||||||
elif [[ "$optlen" = "${#cur_word}" && "$state" != "warning" ]]; then
|
elif [[ "$optlen" = "${#cur_word}" && "$state" != "warning" ]]; then
|
||||||
# This short option group only awaits its argument!
|
# This short option group only awaits its argument!
|
||||||
@@ -127,12 +126,13 @@ _rgblink_completions() {
|
|||||||
# so that the next completion request switches to the argument
|
# so that the next completion request switches to the argument
|
||||||
# An exception is made for warnings, since it's idiomatic to stick them to the
|
# An exception is made for warnings, since it's idiomatic to stick them to the
|
||||||
# `-W`, and it doesn't break anything.
|
# `-W`, and it doesn't break anything.
|
||||||
COMPREPLY+=( "$cur_word" )
|
COMPREPLY=( "$cur_word" )
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
COMPREPLY=()
|
||||||
case "$state" in
|
case "$state" in
|
||||||
unk) # Return with no replies: no idea what to complete!
|
unk) # Return with no replies: no idea what to complete!
|
||||||
;;
|
;;
|
||||||
@@ -151,6 +151,10 @@ _rgblink_completions() {
|
|||||||
done < <(compgen -A directory -- "${cur_word:$optlen}")
|
done < <(compgen -A directory -- "${cur_word:$optlen}")
|
||||||
compopt -o filenames
|
compopt -o filenames
|
||||||
;;
|
;;
|
||||||
|
*)
|
||||||
|
echo >&2 "Internal completion error: invalid state \"$state\", please report this bug"
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,10 +25,11 @@ _rgbasm_warnings() {
|
|||||||
'shift:Warn when shifting negative values'
|
'shift:Warn when shifting negative values'
|
||||||
'shift-amount:Warn when a shift'\''s operand it negative or \> 32'
|
'shift-amount:Warn when a shift'\''s operand it negative or \> 32'
|
||||||
'truncation:Warn when implicit truncation loses bits'
|
'truncation:Warn when implicit truncation loses bits'
|
||||||
|
'unmapped-char:Warn on unmapped character'
|
||||||
'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?
|
||||||
# TODO: handle `=0|1|2` levels for `numeric-string` and `truncation`?
|
# TODO: handle `=0|1|2` levels for `numeric-string`, `truncation`, and `unmapped-char`?
|
||||||
_describe warning warnings
|
_describe warning warnings
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,22 +38,26 @@ local args=(
|
|||||||
'(- : * options)'{-V,--version}'[Print version number]'
|
'(- : * options)'{-V,--version}'[Print version number]'
|
||||||
|
|
||||||
'(-E --export-all)'{-E,--export-all}'[Export all symbols]'
|
'(-E --export-all)'{-E,--export-all}'[Export all symbols]'
|
||||||
'(-h --halt-without-nop)'{-h,--halt-without-nop}'[Avoid outputting a `nop` after `halt`]'
|
'(-H --nop-after-halt)'{-H,--nop-after-halt}'[Output a `nop` after `halt`]'
|
||||||
'(-L ---preserve-ld)'{-L,--preserve-ld}'[Prevent auto-optimizing `ld` into `ldh`]'
|
'(-h --halt-without-nop)'{-h,--halt-without-nop}'[Prevent outputting a `nop` after `halt`]'
|
||||||
|
'(-L --preserve-ld)'{-L,--preserve-ld}'[Prevent optimizing `ld` into `ldh`]'
|
||||||
|
'(-l --auto-ldh)'{-l,--auto-ldh}'[Optimize `ld` into `ldh`]'
|
||||||
'(-v --verbose)'{-v,--verbose}'[Print additional messages regarding progression]'
|
'(-v --verbose)'{-v,--verbose}'[Print additional messages regarding progression]'
|
||||||
-w'[Disable all warnings]'
|
-w'[Disable all warnings]'
|
||||||
|
|
||||||
'(-b --binary-digits)'{-b,--binary-digits}'+[Change chars for binary constants]:digit spec:'
|
'(-b --binary-digits)'{-b,--binary-digits}'+[Change chars for binary constants]:digit spec:'
|
||||||
'*'{-D,--define}'+[Define a string symbol]:name + value (default 1):'
|
'*'{-D,--define}'+[Define a string symbol]:name + value (default 1):'
|
||||||
'(-g --gfx-chars)'{-g,--gfx-chars}'+[Change chars for gfx constants]:chars spec:'
|
'(-g --gfx-chars)'{-g,--gfx-chars}'+[Change chars for gfx constants]:chars spec:'
|
||||||
'(-i --include)'{-i,--include}'+[Add an include directory]:include path:_files -/'
|
'(-I --include)'{-I,--include}'+[Add an include directory]:include path:_files -/'
|
||||||
'(-M --dependfile)'{-M,--dependfile}"+[List deps in make format]:output file:_files -g '*.{d,mk}'"
|
'(-M --dependfile)'{-M,--dependfile}"+[List deps in make format]:output file:_files -g '*.{d,mk}'"
|
||||||
-MG'[Assume missing files should be generated]'
|
-MG'[Assume missing files should be generated]'
|
||||||
-MP'[Add phony targets to all deps]'
|
-MP'[Add phony targets to all deps]'
|
||||||
'*'-MT"+[Add a target to the rules]:target:_files -g '*.{d,mk,o}'"
|
'*'-MT"+[Add a target to the rules]:target:_files -g '*.{d,mk,o}'"
|
||||||
'*'-MQ"+[Add a target to the rules]:target:_files -g '*.{d,mk,o}'"
|
'*'-MQ"+[Add a target to the rules]:target:_files -g '*.{d,mk,o}'"
|
||||||
'(-o --output)'{-o,--output}'+[Output file]:output file:_files'
|
'(-o --output)'{-o,--output}'+[Output file]:output file:_files'
|
||||||
|
'(-P --preinclude)'{-P,--preinclude}"+[Pre-include a file]:include file:_files -g '*.{asm,inc}'"
|
||||||
'(-p --pad-value)'{-p,--pad-value}'+[Set padding byte]:padding byte:'
|
'(-p --pad-value)'{-p,--pad-value}'+[Set padding byte]:padding byte:'
|
||||||
|
'(-Q --q-precision)'{-Q,--q-precision}'+[Set fixed-point precision]:precision:'
|
||||||
'(-r --recursion-depth)'{-r,--recursion-depth}'+[Set maximum recursion depth]:depth:'
|
'(-r --recursion-depth)'{-r,--recursion-depth}'+[Set maximum recursion depth]:depth:'
|
||||||
'(-W --warning)'{-W,--warning}'+[Toggle warning flags]:warning flag:_rgbasm_warnings'
|
'(-W --warning)'{-W,--warning}'+[Toggle warning flags]:warning flag:_rgbasm_warnings'
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ local args=(
|
|||||||
'(-x --nopad)'{-x,--nopad}'[Disable padding the end of the final file]'
|
'(-x --nopad)'{-x,--nopad}'[Disable padding the end of the final file]'
|
||||||
|
|
||||||
'(-l --linkerscript)'{-l,--linkerscript}"+[Use a linker script]:linker script:_files -g '*.link'"
|
'(-l --linkerscript)'{-l,--linkerscript}"+[Use a linker script]:linker script:_files -g '*.link'"
|
||||||
|
'(-M --no-sym-in-map)'{-M,--no-sym-in-map}'[Do not output symbol names in map file]'
|
||||||
'(-m --map)'{-m,--map}"+[Produce a map file]:map file:_files -g '*.map'"
|
'(-m --map)'{-m,--map}"+[Produce a map file]:map file:_files -g '*.map'"
|
||||||
'(-n --sym)'(-n,--sym)"+[Produce a symbol file]:sym file:_files -g '*.sym'"
|
'(-n --sym)'(-n,--sym)"+[Produce a symbol file]:sym file:_files -g '*.sym'"
|
||||||
'(-O --overlay)'{-O,--overlay}'+[Overlay sections over on top of bin file]:base overlay:_files'
|
'(-O --overlay)'{-O,--overlay}'+[Overlay sections over on top of bin file]:base overlay:_files'
|
||||||
|
|||||||
@@ -11,6 +11,8 @@
|
|||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#define DEFAULT_CHARMAP_NAME "main"
|
||||||
|
|
||||||
struct Charmap *charmap_New(char const *name, char const *baseName);
|
struct Charmap *charmap_New(char const *name, char const *baseName);
|
||||||
void charmap_Delete(struct Charmap *charmap);
|
void charmap_Delete(struct Charmap *charmap);
|
||||||
void charmap_Set(char const *name);
|
void charmap_Set(char const *name);
|
||||||
@@ -20,4 +22,4 @@ void charmap_Add(char *mapping, uint8_t value);
|
|||||||
size_t charmap_Convert(char const *input, uint8_t *output);
|
size_t charmap_Convert(char const *input, uint8_t *output);
|
||||||
size_t charmap_ConvertNext(char const **input, uint8_t **output);
|
size_t charmap_ConvertNext(char const **input, uint8_t **output);
|
||||||
|
|
||||||
#endif /* RGBDS_ASM_CHARMAP_H */
|
#endif // RGBDS_ASM_CHARMAP_H
|
||||||
|
|||||||
@@ -11,20 +11,24 @@
|
|||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
void fix_Print(int32_t i);
|
extern uint8_t fixPrecision;
|
||||||
int32_t fix_Sin(int32_t i);
|
|
||||||
int32_t fix_Cos(int32_t i);
|
|
||||||
int32_t fix_Tan(int32_t i);
|
|
||||||
int32_t fix_ASin(int32_t i);
|
|
||||||
int32_t fix_ACos(int32_t i);
|
|
||||||
int32_t fix_ATan(int32_t i);
|
|
||||||
int32_t fix_ATan2(int32_t i, int32_t j);
|
|
||||||
int32_t fix_Mul(int32_t i, int32_t j);
|
|
||||||
int32_t fix_Div(int32_t i, int32_t j);
|
|
||||||
int32_t fix_Pow(int32_t i, int32_t j);
|
|
||||||
int32_t fix_Log(int32_t i, int32_t j);
|
|
||||||
int32_t fix_Round(int32_t i);
|
|
||||||
int32_t fix_Ceil(int32_t i);
|
|
||||||
int32_t fix_Floor(int32_t i);
|
|
||||||
|
|
||||||
#endif /* RGBDS_ASM_FIXPOINT_H */
|
uint8_t fix_Precision(void);
|
||||||
|
double fix_PrecisionFactor(void);
|
||||||
|
int32_t fix_Sin(int32_t i, int32_t q);
|
||||||
|
int32_t fix_Cos(int32_t i, int32_t q);
|
||||||
|
int32_t fix_Tan(int32_t i, int32_t q);
|
||||||
|
int32_t fix_ASin(int32_t i, int32_t q);
|
||||||
|
int32_t fix_ACos(int32_t i, int32_t q);
|
||||||
|
int32_t fix_ATan(int32_t i, int32_t q);
|
||||||
|
int32_t fix_ATan2(int32_t i, int32_t j, int32_t q);
|
||||||
|
int32_t fix_Mul(int32_t i, int32_t j, int32_t q);
|
||||||
|
int32_t fix_Mod(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_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_Round(int32_t i, int32_t q);
|
||||||
|
int32_t fix_Ceil(int32_t i, int32_t q);
|
||||||
|
int32_t fix_Floor(int32_t i, int32_t q);
|
||||||
|
|
||||||
|
#endif // RGBDS_ASM_FIXPOINT_H
|
||||||
|
|||||||
@@ -60,4 +60,4 @@ void fmt_FinishCharacters(struct FormatSpec *fmt);
|
|||||||
void fmt_PrintString(char *buf, size_t bufLen, struct FormatSpec const *fmt, char const *value);
|
void fmt_PrintString(char *buf, size_t bufLen, struct FormatSpec const *fmt, char const *value);
|
||||||
void fmt_PrintNumber(char *buf, size_t bufLen, struct FormatSpec const *fmt, uint32_t value);
|
void fmt_PrintNumber(char *buf, size_t bufLen, struct FormatSpec const *fmt, uint32_t value);
|
||||||
|
|
||||||
#endif /* RGBDS_FORMAT_SPEC_H */
|
#endif // RGBDS_FORMAT_SPEC_H
|
||||||
|
|||||||
@@ -6,9 +6,7 @@
|
|||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
// Contains some assembler-wide defines and externs
|
||||||
* Contains some assembler-wide defines and externs
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef RGBDS_ASM_FSTACK_H
|
#ifndef RGBDS_ASM_FSTACK_H
|
||||||
#define RGBDS_ASM_FSTACK_H
|
#define RGBDS_ASM_FSTACK_H
|
||||||
@@ -21,13 +19,13 @@
|
|||||||
|
|
||||||
|
|
||||||
struct FileStackNode {
|
struct FileStackNode {
|
||||||
struct FileStackNode *parent; /* Pointer to parent node, for error reporting */
|
struct FileStackNode *parent; // Pointer to parent node, for error reporting
|
||||||
/* Line at which the parent context was exited; meaningless for the root level */
|
// Line at which the parent context was exited; meaningless for the root level
|
||||||
uint32_t lineNo;
|
uint32_t lineNo;
|
||||||
|
|
||||||
struct FileStackNode *next; /* Next node in the output linked list */
|
struct FileStackNode *next; // Next node in the output linked list
|
||||||
bool referenced; /* If referenced, don't free! */
|
bool referenced; // If referenced, don't free!
|
||||||
uint32_t ID; /* Set only if referenced: ID within the object file, -1 if not output yet */
|
uint32_t ID; // Set only if referenced: ID within the object file, -1 if not output yet
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
NODE_REPT,
|
NODE_REPT,
|
||||||
@@ -36,18 +34,19 @@ struct FileStackNode {
|
|||||||
} type;
|
} type;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct FileStackReptNode { /* NODE_REPT */
|
struct FileStackReptNode { // NODE_REPT
|
||||||
struct FileStackNode node;
|
struct FileStackNode node;
|
||||||
uint32_t reptDepth;
|
uint32_t reptDepth;
|
||||||
/* WARNING: if changing this type, change overflow check in `fstk_Init` */
|
// WARNING: if changing this type, change overflow check in `fstk_Init`
|
||||||
uint32_t iters[]; /* REPT iteration counts since last named node, in reverse depth order */
|
uint32_t iters[]; // REPT iteration counts since last named node, in reverse depth order
|
||||||
};
|
};
|
||||||
|
|
||||||
struct FileStackNamedNode { /* NODE_FILE, NODE_MACRO */
|
struct FileStackNamedNode { // NODE_FILE, NODE_MACRO
|
||||||
struct FileStackNode node;
|
struct FileStackNode node;
|
||||||
char name[]; /* File name for files, file::macro name for macros */
|
char name[]; // File name for files, file::macro name for macros
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#define DEFAULT_MAX_DEPTH 64
|
||||||
extern size_t maxRecursionDepth;
|
extern size_t maxRecursionDepth;
|
||||||
|
|
||||||
struct MacroArgs;
|
struct MacroArgs;
|
||||||
@@ -55,11 +54,12 @@ struct MacroArgs;
|
|||||||
void fstk_Dump(struct FileStackNode const *node, uint32_t lineNo);
|
void fstk_Dump(struct FileStackNode const *node, uint32_t lineNo);
|
||||||
void fstk_DumpCurrent(void);
|
void fstk_DumpCurrent(void);
|
||||||
struct FileStackNode *fstk_GetFileStack(void);
|
struct FileStackNode *fstk_GetFileStack(void);
|
||||||
/* The lifetime of the returned chars is until reaching the end of that file */
|
// The lifetime of the returned chars is until reaching the end of that file
|
||||||
char const *fstk_GetFileName(void);
|
char const *fstk_GetFileName(void);
|
||||||
|
|
||||||
void fstk_AddIncludePath(char const *s);
|
void fstk_AddIncludePath(char const *s);
|
||||||
/**
|
void fstk_SetPreIncludeFile(char const *s);
|
||||||
|
/*
|
||||||
* @param path The user-provided file name
|
* @param path The user-provided file name
|
||||||
* @param fullPath The address of a pointer, which will be made to point at the full path
|
* @param fullPath The address of a pointer, which will be made to point at the full path
|
||||||
* The pointer's value must be a valid argument to `realloc`, including NULL
|
* The pointer's value must be a valid argument to `realloc`, including NULL
|
||||||
@@ -80,4 +80,4 @@ bool fstk_Break(void);
|
|||||||
void fstk_NewRecursionDepth(size_t newDepth);
|
void fstk_NewRecursionDepth(size_t newDepth);
|
||||||
void fstk_Init(char const *mainPath, size_t maxDepth);
|
void fstk_Init(char const *mainPath, size_t maxDepth);
|
||||||
|
|
||||||
#endif /* RGBDS_ASM_FSTACK_H */
|
#endif // RGBDS_ASM_FSTACK_H
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
|
||||||
#define MAXSTRLEN 255
|
#define MAXSTRLEN 255
|
||||||
|
|
||||||
struct LexerState;
|
struct LexerState;
|
||||||
extern struct LexerState *lexerState;
|
extern struct LexerState *lexerState;
|
||||||
@@ -49,9 +49,7 @@ static inline void lexer_SetGfxDigits(char const digits[4])
|
|||||||
gfxDigits[3] = digits[3];
|
gfxDigits[3] = digits[3];
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// `path` is referenced, but not held onto..!
|
||||||
* `path` is referenced, but not held onto..!
|
|
||||||
*/
|
|
||||||
struct LexerState *lexer_OpenFile(char const *path);
|
struct LexerState *lexer_OpenFile(char const *path);
|
||||||
struct LexerState *lexer_OpenFileView(char const *path, char *buf, size_t size, uint32_t lineNo);
|
struct LexerState *lexer_OpenFileView(char const *path, char *buf, size_t size, uint32_t lineNo);
|
||||||
void lexer_RestartRept(uint32_t lineNo);
|
void lexer_RestartRept(uint32_t lineNo);
|
||||||
@@ -99,4 +97,4 @@ struct DsArgList {
|
|||||||
struct Expression *args;
|
struct Expression *args;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /* RGBDS_ASM_LEXER_H */
|
#endif // RGBDS_ASM_LEXER_H
|
||||||
|
|||||||
@@ -31,7 +31,8 @@ uint32_t macro_GetUniqueID(void);
|
|||||||
char const *macro_GetUniqueIDStr(void);
|
char const *macro_GetUniqueIDStr(void);
|
||||||
void macro_SetUniqueID(uint32_t id);
|
void macro_SetUniqueID(uint32_t id);
|
||||||
uint32_t macro_UseNewUniqueID(void);
|
uint32_t macro_UseNewUniqueID(void);
|
||||||
|
uint32_t macro_UndefUniqueID(void);
|
||||||
void macro_ShiftCurrentArgs(int32_t count);
|
void macro_ShiftCurrentArgs(int32_t count);
|
||||||
uint32_t macro_NbArgs(void);
|
uint32_t macro_NbArgs(void);
|
||||||
|
|
||||||
#endif
|
#endif // RGBDS_MACRO_H
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ extern bool warnOnHaltNop;
|
|||||||
extern bool optimizeLoads;
|
extern bool optimizeLoads;
|
||||||
extern bool warnOnLdOpt;
|
extern bool warnOnLdOpt;
|
||||||
extern bool verbose;
|
extern bool verbose;
|
||||||
extern bool warnings; /* True to enable warnings, false to disable them. */
|
extern bool warnings; // True to enable warnings, false to disable them.
|
||||||
|
|
||||||
extern FILE *dependfile;
|
extern FILE *dependfile;
|
||||||
extern char *targetFileName;
|
extern char *targetFileName;
|
||||||
@@ -28,4 +28,4 @@ extern bool generatedMissingIncludes;
|
|||||||
extern bool failedOnMissingInclude;
|
extern bool failedOnMissingInclude;
|
||||||
extern bool generatePhonyDeps;
|
extern bool generatePhonyDeps;
|
||||||
|
|
||||||
#endif /* RGBDS_MAIN_H */
|
#endif // RGBDS_MAIN_H
|
||||||
|
|||||||
@@ -14,7 +14,8 @@
|
|||||||
|
|
||||||
void opt_B(char const chars[2]);
|
void opt_B(char const chars[2]);
|
||||||
void opt_G(char const chars[4]);
|
void opt_G(char const chars[4]);
|
||||||
void opt_P(uint8_t fill);
|
void opt_P(uint8_t padByte);
|
||||||
|
void opt_Q(uint8_t precision);
|
||||||
void opt_L(bool optimize);
|
void opt_L(bool optimize);
|
||||||
void opt_W(char const *flag);
|
void opt_W(char const *flag);
|
||||||
void opt_Parse(char const *option);
|
void opt_Parse(char const *option);
|
||||||
@@ -22,4 +23,4 @@ void opt_Parse(char const *option);
|
|||||||
void opt_Push(void);
|
void opt_Push(void);
|
||||||
void opt_Pop(void);
|
void opt_Pop(void);
|
||||||
|
|
||||||
#endif
|
#endif // RGBDS_OPT_H
|
||||||
|
|||||||
@@ -27,4 +27,4 @@ bool out_CreateAssert(enum AssertionType type, struct Expression const *expr,
|
|||||||
char const *message, uint32_t ofs);
|
char const *message, uint32_t ofs);
|
||||||
void out_WriteObject(void);
|
void out_WriteObject(void);
|
||||||
|
|
||||||
#endif /* RGBDS_ASM_OUTPUT_H */
|
#endif // RGBDS_ASM_OUTPUT_H
|
||||||
|
|||||||
@@ -27,17 +27,13 @@ struct Expression {
|
|||||||
uint32_t rpnPatchSize; // Size the expression will take in the object file
|
uint32_t rpnPatchSize; // Size the expression will take in the object file
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
// Determines if an expression is known at assembly time
|
||||||
* Determines if an expression is known at assembly time
|
|
||||||
*/
|
|
||||||
static inline bool rpn_isKnown(struct Expression const *expr)
|
static inline bool rpn_isKnown(struct Expression const *expr)
|
||||||
{
|
{
|
||||||
return expr->isKnown;
|
return expr->isKnown;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Determines if an expression is a symbol suitable for const diffing
|
||||||
* Determines if an expression is a symbol suitable for const diffing
|
|
||||||
*/
|
|
||||||
static inline bool rpn_isSymbol(const struct Expression *expr)
|
static inline bool rpn_isSymbol(const struct Expression *expr)
|
||||||
{
|
{
|
||||||
return expr->isSymbol;
|
return expr->isSymbol;
|
||||||
@@ -67,4 +63,4 @@ void rpn_CheckRST(struct Expression *expr, const struct Expression *src);
|
|||||||
void rpn_CheckNBit(struct Expression const *expr, uint8_t n);
|
void rpn_CheckNBit(struct Expression const *expr, uint8_t n);
|
||||||
int32_t rpn_GetConstVal(struct Expression const *expr);
|
int32_t rpn_GetConstVal(struct Expression const *expr);
|
||||||
|
|
||||||
#endif /* RGBDS_ASM_RPN_H */
|
#endif // RGBDS_ASM_RPN_H
|
||||||
|
|||||||
@@ -23,8 +23,8 @@ struct Section {
|
|||||||
char *name;
|
char *name;
|
||||||
enum SectionType type;
|
enum SectionType type;
|
||||||
enum SectionModifier modifier;
|
enum SectionModifier modifier;
|
||||||
struct FileStackNode *src; /* Where the section was defined */
|
struct FileStackNode *src; // Where the section was defined
|
||||||
uint32_t fileLine; /* Line where the section was defined */
|
uint32_t fileLine; // Line where the section was defined
|
||||||
uint32_t size;
|
uint32_t size;
|
||||||
uint32_t org;
|
uint32_t org;
|
||||||
uint32_t bank;
|
uint32_t bank;
|
||||||
@@ -79,4 +79,4 @@ void sect_PopSection(void);
|
|||||||
|
|
||||||
bool sect_IsSizeKnown(struct Section const NONNULL(name));
|
bool sect_IsSizeKnown(struct Section const NONNULL(name));
|
||||||
|
|
||||||
#endif
|
#endif // RGBDS_SECTION_H
|
||||||
|
|||||||
@@ -32,28 +32,28 @@ enum SymbolType {
|
|||||||
struct Symbol {
|
struct Symbol {
|
||||||
char name[MAXSYMLEN + 1];
|
char name[MAXSYMLEN + 1];
|
||||||
enum SymbolType type;
|
enum SymbolType type;
|
||||||
bool isExported; /* Whether the symbol is to be exported */
|
bool isExported; // Whether the symbol is to be exported
|
||||||
bool isBuiltin; /* Whether the symbol is a built-in */
|
bool isBuiltin; // Whether the symbol is a built-in
|
||||||
struct Section *section;
|
struct Section *section;
|
||||||
struct FileStackNode *src; /* Where the symbol was defined */
|
struct FileStackNode *src; // Where the symbol was defined
|
||||||
uint32_t fileLine; /* Line where the symbol was defined */
|
uint32_t fileLine; // Line where the symbol was defined
|
||||||
|
|
||||||
bool hasCallback;
|
bool hasCallback;
|
||||||
union {
|
union {
|
||||||
/* If sym_IsNumeric */
|
// If sym_IsNumeric
|
||||||
int32_t value;
|
int32_t value;
|
||||||
int32_t (*numCallback)(void);
|
int32_t (*numCallback)(void);
|
||||||
/* For SYM_MACRO and SYM_EQUS; TODO: have separate fields */
|
// For SYM_MACRO and SYM_EQUS; TODO: have separate fields
|
||||||
struct {
|
struct {
|
||||||
size_t macroSize;
|
size_t macroSize;
|
||||||
char *macro;
|
char *macro;
|
||||||
};
|
};
|
||||||
/* For SYM_EQUS */
|
// For SYM_EQUS
|
||||||
char const *(*strCallback)(void);
|
char const *(*strCallback)(void);
|
||||||
};
|
};
|
||||||
|
|
||||||
uint32_t ID; /* ID of the symbol in the object file (-1 if none) */
|
uint32_t ID; // ID of the symbol in the object file (-1 if none)
|
||||||
struct Symbol *next; /* Next object to output in the object file */
|
struct Symbol *next; // Next object to output in the object file
|
||||||
};
|
};
|
||||||
|
|
||||||
bool sym_IsPC(struct Symbol const *sym);
|
bool sym_IsPC(struct Symbol const *sym);
|
||||||
@@ -98,9 +98,7 @@ static inline bool sym_IsExported(struct Symbol const *sym)
|
|||||||
return sym->isExported;
|
return sym->isExported;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Get a string equate's value
|
||||||
* Get a string equate's value
|
|
||||||
*/
|
|
||||||
static inline char const *sym_GetStringValue(struct Symbol const *sym)
|
static inline char const *sym_GetStringValue(struct Symbol const *sym)
|
||||||
{
|
{
|
||||||
if (sym->hasCallback)
|
if (sym->hasCallback)
|
||||||
@@ -123,18 +121,14 @@ struct Symbol *sym_AddVar(char const *symName, int32_t value);
|
|||||||
uint32_t sym_GetPCValue(void);
|
uint32_t sym_GetPCValue(void);
|
||||||
uint32_t sym_GetConstantSymValue(struct Symbol const *sym);
|
uint32_t sym_GetConstantSymValue(struct Symbol const *sym);
|
||||||
uint32_t sym_GetConstantValue(char const *symName);
|
uint32_t sym_GetConstantValue(char const *symName);
|
||||||
/*
|
// Find a symbol by exact name, bypassing expansion checks
|
||||||
* Find a symbol by exact name, bypassing expansion checks
|
|
||||||
*/
|
|
||||||
struct Symbol *sym_FindExactSymbol(char const *symName);
|
struct Symbol *sym_FindExactSymbol(char const *symName);
|
||||||
/*
|
// Find a symbol by exact name; may not be scoped, produces an error if it is
|
||||||
* Find a symbol by exact name; may not be scoped, produces an error if it is
|
|
||||||
*/
|
|
||||||
struct Symbol *sym_FindUnscopedSymbol(char const *symName);
|
struct Symbol *sym_FindUnscopedSymbol(char const *symName);
|
||||||
/*
|
// Find a symbol, possibly scoped, by name
|
||||||
* Find a symbol, possibly scoped, by name
|
|
||||||
*/
|
|
||||||
struct Symbol *sym_FindScopedSymbol(char const *symName);
|
struct Symbol *sym_FindScopedSymbol(char const *symName);
|
||||||
|
// Find a scoped symbol by name; do not return `@` or `_NARG` when they have no value
|
||||||
|
struct Symbol *sym_FindScopedValidSymbol(char const *symName);
|
||||||
struct Symbol const *sym_GetPC(void);
|
struct Symbol const *sym_GetPC(void);
|
||||||
struct Symbol *sym_AddMacro(char const *symName, int32_t defLineNo, char *body, size_t size);
|
struct Symbol *sym_AddMacro(char const *symName, int32_t defLineNo, char *body, size_t size);
|
||||||
struct Symbol *sym_Ref(char const *symName);
|
struct Symbol *sym_Ref(char const *symName);
|
||||||
@@ -143,8 +137,8 @@ struct Symbol *sym_RedefString(char const *symName, char const *value);
|
|||||||
void sym_Purge(char const *symName);
|
void sym_Purge(char const *symName);
|
||||||
void sym_Init(time_t now);
|
void sym_Init(time_t now);
|
||||||
|
|
||||||
/* Functions to save and restore the current symbol scope. */
|
// Functions to save and restore the current symbol scope.
|
||||||
char const *sym_GetCurrentSymbolScope(void);
|
char const *sym_GetCurrentSymbolScope(void);
|
||||||
void sym_SetCurrentSymbolScope(char const *newScope);
|
void sym_SetCurrentSymbolScope(char const *newScope);
|
||||||
|
|
||||||
#endif /* RGBDS_SYMBOL_H */
|
#endif // RGBDS_SYMBOL_H
|
||||||
|
|||||||
@@ -18,4 +18,4 @@ char const *printChar(int c);
|
|||||||
*/
|
*/
|
||||||
size_t readUTF8Char(uint8_t *dest, char const *src);
|
size_t readUTF8Char(uint8_t *dest, char const *src);
|
||||||
|
|
||||||
#endif /* RGBDS_UTIL_H */
|
#endif // RGBDS_UTIL_H
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ 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, // Division undefined 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
|
||||||
@@ -48,6 +48,9 @@ enum WarningID {
|
|||||||
// Implicit truncation loses some bits
|
// Implicit truncation loses some bits
|
||||||
WARNING_TRUNCATION_1,
|
WARNING_TRUNCATION_1,
|
||||||
WARNING_TRUNCATION_2,
|
WARNING_TRUNCATION_2,
|
||||||
|
// Character without charmap entry
|
||||||
|
WARNING_UNMAPPED_CHAR_1,
|
||||||
|
WARNING_UNMAPPED_CHAR_2,
|
||||||
|
|
||||||
NB_PLAIN_AND_PARAM_WARNINGS,
|
NB_PLAIN_AND_PARAM_WARNINGS,
|
||||||
#define NB_PARAM_WARNINGS (NB_PLAIN_AND_PARAM_WARNINGS - PARAM_WARNINGS_START)
|
#define NB_PARAM_WARNINGS (NB_PLAIN_AND_PARAM_WARNINGS - PARAM_WARNINGS_START)
|
||||||
@@ -90,4 +93,4 @@ _Noreturn void fatalerror(char const *fmt, ...) format_(printf, 1, 2);
|
|||||||
*/
|
*/
|
||||||
void error(char const *fmt, ...) format_(printf, 1, 2);
|
void error(char const *fmt, ...) format_(printf, 1, 2);
|
||||||
|
|
||||||
#endif
|
#endif // WARNING_H
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/**
|
/*
|
||||||
* 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).
|
||||||
@@ -36,4 +36,4 @@ public:
|
|||||||
template<typename T>
|
template<typename T>
|
||||||
using DefaultInitVec = std::vector<T, default_init_allocator<T>>;
|
using DefaultInitVec = std::vector<T, default_init_allocator<T>>;
|
||||||
|
|
||||||
#endif
|
#endif // DEFAULT_INIT_ALLOC_H
|
||||||
|
|||||||
@@ -26,4 +26,4 @@ _Noreturn void errx(char const NONNULL(fmt), ...) format_(printf, 1, 2);
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#endif /* RGBDS_ERROR_H */
|
#endif // RGBDS_ERROR_H
|
||||||
|
|||||||
2
include/extern/utf8decoder.h
vendored
2
include/extern/utf8decoder.h
vendored
@@ -11,4 +11,4 @@
|
|||||||
|
|
||||||
uint32_t decode(uint32_t *state, uint32_t *codep, uint8_t byte);
|
uint32_t decode(uint32_t *state, uint32_t *codep, uint8_t byte);
|
||||||
|
|
||||||
#endif /* EXTERN_UTF8DECODER_H */
|
#endif // EXTERN_UTF8DECODER_H
|
||||||
|
|||||||
103
include/file.hpp
Normal file
103
include/file.hpp
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of RGBDS.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2022, Eldred Habert and RGBDS contributors.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef RGBDS_FILE_HPP
|
||||||
|
#define RGBDS_FILE_HPP
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <cassert>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <fstream>
|
||||||
|
#include <ios>
|
||||||
|
#include <iostream>
|
||||||
|
#include <streambuf>
|
||||||
|
#include <string>
|
||||||
|
#include <string.h>
|
||||||
|
#include <string_view>
|
||||||
|
#include <variant>
|
||||||
|
|
||||||
|
#include "helpers.h"
|
||||||
|
#include "platform.h"
|
||||||
|
|
||||||
|
#include "gfx/main.hpp"
|
||||||
|
|
||||||
|
// Convenience feature for visiting the below.
|
||||||
|
template<typename... Ts>
|
||||||
|
struct Visitor : Ts... {
|
||||||
|
using Ts::operator()...;
|
||||||
|
};
|
||||||
|
template<typename... Ts>
|
||||||
|
Visitor(Ts...) -> Visitor<Ts...>;
|
||||||
|
|
||||||
|
class File {
|
||||||
|
// Construct a `std::streambuf *` by default, since it's probably lighter than a `filebuf`.
|
||||||
|
std::variant<std::streambuf *, std::filebuf> _file;
|
||||||
|
|
||||||
|
public:
|
||||||
|
File() {}
|
||||||
|
~File() { close(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This should only be called once, and before doing any `->` operations.
|
||||||
|
* Returns `nullptr` on error, and a non-null pointer otherwise.
|
||||||
|
*/
|
||||||
|
File *open(std::string const &path, std::ios_base::openmode mode) {
|
||||||
|
if (path != "-") {
|
||||||
|
return _file.emplace<std::filebuf>().open(path, mode) ? this : nullptr;
|
||||||
|
} else if (mode & std::ios_base::in) {
|
||||||
|
assert(!(mode & std::ios_base::out));
|
||||||
|
_file.emplace<std::streambuf *>(std::cin.rdbuf());
|
||||||
|
if (setmode(STDIN_FILENO, mode & std::ios_base::binary ? O_BINARY : O_TEXT) == -1) {
|
||||||
|
fatal("Failed to set stdin to %s mode: %s",
|
||||||
|
mode & std::ios_base::binary ? "binary" : "text", strerror(errno));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
assert(mode & std::ios_base::out);
|
||||||
|
_file.emplace<std::streambuf *>(std::cout.rdbuf());
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
std::streambuf &operator*() {
|
||||||
|
return std::visit(Visitor{[](std::filebuf &file) -> std::streambuf & { return file; },
|
||||||
|
[](std::streambuf *buf) -> std::streambuf & { return *buf; }},
|
||||||
|
_file);
|
||||||
|
}
|
||||||
|
std::streambuf const &operator*() const {
|
||||||
|
// The non-`const` version does not perform any modifications, so it's okay.
|
||||||
|
return **const_cast<File *>(this);
|
||||||
|
}
|
||||||
|
std::streambuf *operator->() { return &**this; }
|
||||||
|
std::streambuf const *operator->() const {
|
||||||
|
// See the `operator*` equivalent.
|
||||||
|
return const_cast<File *>(this)->operator->();
|
||||||
|
}
|
||||||
|
|
||||||
|
File *close() {
|
||||||
|
return std::visit(Visitor{[this](std::filebuf &file) {
|
||||||
|
// This is called by the destructor, and an explicit `close`
|
||||||
|
// shouldn't close twice.
|
||||||
|
_file.emplace<std::streambuf *>(nullptr);
|
||||||
|
return file.close() != nullptr;
|
||||||
|
},
|
||||||
|
[](std::streambuf *buf) { return buf != nullptr; }},
|
||||||
|
_file)
|
||||||
|
? this
|
||||||
|
: nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
char const *c_str(std::string const &path) const {
|
||||||
|
return std::visit(Visitor{[&path](std::filebuf const &) { return path.c_str(); },
|
||||||
|
[](std::streambuf const *buf) {
|
||||||
|
return buf == std::cin.rdbuf() ? "<stdin>" : "<stdout>";
|
||||||
|
}},
|
||||||
|
_file);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // RGBDS_FILE_HPP
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
#include "gfx/rgba.hpp"
|
#include "gfx/rgba.hpp"
|
||||||
|
|
||||||
struct Options {
|
struct Options {
|
||||||
uint8_t reversedWidth = 0; // -r, in pixels
|
uint16_t reversedWidth = 0; // -r, in tiles
|
||||||
bool reverse() const { return reversedWidth != 0; }
|
bool reverse() const { return reversedWidth != 0; }
|
||||||
|
|
||||||
bool useColorCurve = false; // -C
|
bool useColorCurve = false; // -C
|
||||||
@@ -71,19 +71,19 @@ 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();
|
||||||
/**
|
/*
|
||||||
* Prints a warning, and does not change the error count
|
* Prints a warning, and does not change the error count
|
||||||
*/
|
*/
|
||||||
void warning(char const *fmt, ...);
|
void warning(char const *fmt, ...);
|
||||||
/**
|
/*
|
||||||
* Prints an error, and increments the error count
|
* Prints an error, and increments the error count
|
||||||
*/
|
*/
|
||||||
void error(char const *fmt, ...);
|
void error(char const *fmt, ...);
|
||||||
/**
|
/*
|
||||||
* Prints a fatal error, increments the error count, and gives up
|
* Prints a fatal error, increments the error count, and gives up
|
||||||
*/
|
*/
|
||||||
[[noreturn]] void fatal(char const *fmt, ...);
|
[[noreturn]] void fatal(char const *fmt, ...);
|
||||||
@@ -120,4 +120,4 @@ static constexpr auto flipTable(std::integer_sequence<T, i...>) {
|
|||||||
// Flipping tends to happen fairly often, so take a bite out of dcache to speed it up
|
// Flipping tends to happen fairly often, so take a bite out of dcache to speed it up
|
||||||
static constexpr auto flipTable = detail::flipTable(std::make_integer_sequence<uint16_t, 256>());
|
static constexpr auto flipTable = detail::flipTable(std::make_integer_sequence<uint16_t, 256>());
|
||||||
|
|
||||||
#endif /* RGBDS_GFX_MAIN_HPP */
|
#endif // RGBDS_GFX_MAIN_HPP
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ class ProtoPalette;
|
|||||||
|
|
||||||
namespace packing {
|
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>
|
||||||
@@ -29,4 +29,4 @@ std::tuple<DefaultInitVec<size_t>, size_t>
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif /* RGBDS_GFX_PAL_PACKING_HPP */
|
#endif // RGBDS_GFX_PAL_PACKING_HPP
|
||||||
|
|||||||
@@ -29,4 +29,4 @@ void rgb(std::vector<Palette> &palettes);
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif /* RGBDS_GFX_PAL_SORTING_HPP */
|
#endif // RGBDS_GFX_PAL_SORTING_HPP
|
||||||
|
|||||||
@@ -12,4 +12,4 @@
|
|||||||
void parseInlinePalSpec(char const * const arg);
|
void parseInlinePalSpec(char const * const arg);
|
||||||
void parseExternalPalSpec(char const *arg);
|
void parseExternalPalSpec(char const *arg);
|
||||||
|
|
||||||
#endif /* RGBDS_GFX_PAL_SPEC_HPP */
|
#endif // RGBDS_GFX_PAL_SPEC_HPP
|
||||||
|
|||||||
@@ -11,4 +11,4 @@
|
|||||||
|
|
||||||
void process();
|
void process();
|
||||||
|
|
||||||
#endif /* RGBDS_GFX_CONVERT_HPP */
|
#endif // RGBDS_GFX_CONVERT_HPP
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ class ProtoPalette {
|
|||||||
std::array<uint16_t, 4> _colorIndices{UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX};
|
std::array<uint16_t, 4> _colorIndices{UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/**
|
/*
|
||||||
* Adds the specified color to the set
|
* Adds the specified color to the set
|
||||||
* Returns false if the set is full
|
* Returns false if the set is full
|
||||||
*/
|
*/
|
||||||
@@ -41,4 +41,4 @@ public:
|
|||||||
decltype(_colorIndices)::const_iterator end() const;
|
decltype(_colorIndices)::const_iterator end() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /* RGBDS_GFX_PROTO_PALETTE_HPP */
|
#endif // RGBDS_GFX_PROTO_PALETTE_HPP
|
||||||
|
|||||||
@@ -11,4 +11,4 @@
|
|||||||
|
|
||||||
void reverse();
|
void reverse();
|
||||||
|
|
||||||
#endif /* RGBDS_GFX_REVERSE_HPP */
|
#endif // RGBDS_GFX_REVERSE_HPP
|
||||||
|
|||||||
@@ -20,7 +20,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)
|
||||||
@@ -35,7 +35,7 @@ struct Rgba {
|
|||||||
(uint8_t)(cgbColor & 0x8000 ? 0x00 : 0xFF)};
|
(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
|
||||||
*/
|
*/
|
||||||
@@ -45,7 +45,7 @@ struct Rgba {
|
|||||||
}
|
}
|
||||||
friend bool operator!=(Rgba const &lhs, Rgba const &rhs) { return lhs.toCSS() != rhs.toCSS(); }
|
friend bool operator!=(Rgba const &lhs, Rgba const &rhs) { return lhs.toCSS() != rhs.toCSS(); }
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* 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.
|
||||||
*/
|
*/
|
||||||
@@ -55,7 +55,7 @@ struct Rgba {
|
|||||||
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;
|
||||||
@@ -64,4 +64,4 @@ struct Rgba {
|
|||||||
uint8_t grayIndex() const;
|
uint8_t grayIndex() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /* RGBDS_GFX_RGBA_HPP */
|
#endif // RGBDS_GFX_RGBA_HPP
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* Generic hashmap implementation (C++ templates are calling...) */
|
// Generic hashmap implementation (C++ templates are calling...)
|
||||||
#ifndef RGBDS_LINK_HASHMAP_H
|
#ifndef RGBDS_LINK_HASHMAP_H
|
||||||
#define RGBDS_LINK_HASHMAP_H
|
#define RGBDS_LINK_HASHMAP_H
|
||||||
|
|
||||||
@@ -18,10 +18,10 @@
|
|||||||
static_assert(HALF_HASH_NB_BITS * 2 == HASH_NB_BITS, "");
|
static_assert(HALF_HASH_NB_BITS * 2 == HASH_NB_BITS, "");
|
||||||
#define HASHMAP_NB_BUCKETS (1 << HALF_HASH_NB_BITS)
|
#define HASHMAP_NB_BUCKETS (1 << HALF_HASH_NB_BITS)
|
||||||
|
|
||||||
/* HashMapEntry is internal, please do not attempt to use it */
|
// HashMapEntry is internal, please do not attempt to use it
|
||||||
typedef struct HashMapEntry *HashMap[HASHMAP_NB_BUCKETS];
|
typedef struct HashMapEntry *HashMap[HASHMAP_NB_BUCKETS];
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Adds an element to a hashmap.
|
* Adds an element to a hashmap.
|
||||||
* @warning Adding a new element with an already-present key will not cause an
|
* @warning Adding a new element with an already-present key will not cause an
|
||||||
* error, this must be handled externally.
|
* error, this must be handled externally.
|
||||||
@@ -33,7 +33,7 @@ typedef struct HashMapEntry *HashMap[HASHMAP_NB_BUCKETS];
|
|||||||
*/
|
*/
|
||||||
void **hash_AddElement(HashMap map, char const *key, void *element);
|
void **hash_AddElement(HashMap map, char const *key, void *element);
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Removes an element from a hashmap.
|
* Removes an element from a hashmap.
|
||||||
* @param map The HashMap to remove the element from
|
* @param map The HashMap to remove the element from
|
||||||
* @param key The key to search the element with
|
* @param key The key to search the element with
|
||||||
@@ -41,7 +41,7 @@ void **hash_AddElement(HashMap map, char const *key, void *element);
|
|||||||
*/
|
*/
|
||||||
bool hash_RemoveElement(HashMap map, char const *key);
|
bool hash_RemoveElement(HashMap map, char const *key);
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Finds an element in a hashmap, and returns a pointer to its value field.
|
* Finds an element in a hashmap, and returns a pointer to its value field.
|
||||||
* @param map The map to consider the elements of
|
* @param map The map to consider the elements of
|
||||||
* @param key The key to search an element for
|
* @param key The key to search an element for
|
||||||
@@ -49,7 +49,7 @@ bool hash_RemoveElement(HashMap map, char const *key);
|
|||||||
*/
|
*/
|
||||||
void **hash_GetNode(HashMap const map, char const *key);
|
void **hash_GetNode(HashMap const map, char const *key);
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Finds an element in a hashmap.
|
* Finds an element in a hashmap.
|
||||||
* @param map The map to consider the elements of
|
* @param map The map to consider the elements of
|
||||||
* @param key The key to search an element for
|
* @param key The key to search an element for
|
||||||
@@ -58,7 +58,7 @@ void **hash_GetNode(HashMap const map, char const *key);
|
|||||||
*/
|
*/
|
||||||
void *hash_GetElement(HashMap const map, char const *key);
|
void *hash_GetElement(HashMap const map, char const *key);
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Executes a function on each element in a hashmap.
|
* Executes a function on each element in a hashmap.
|
||||||
* @param map The map to consider the elements of
|
* @param map The map to consider the elements of
|
||||||
* @param func The function to run. The first argument will be the element,
|
* @param func The function to run. The first argument will be the element,
|
||||||
@@ -67,11 +67,11 @@ void *hash_GetElement(HashMap const map, char const *key);
|
|||||||
*/
|
*/
|
||||||
void hash_ForEach(HashMap const map, void (*func)(void *, void *), void *arg);
|
void hash_ForEach(HashMap const map, void (*func)(void *, void *), void *arg);
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Cleanly empties a hashmap from its contents.
|
* Cleanly empties a hashmap from its contents.
|
||||||
* This does not `free` the data structure itself!
|
* This does not `free` the data structure itself!
|
||||||
* @param map The map to empty
|
* @param map The map to empty
|
||||||
*/
|
*/
|
||||||
void hash_EmptyMap(HashMap map);
|
void hash_EmptyMap(HashMap map);
|
||||||
|
|
||||||
#endif /* RGBDS_LINK_HASHMAP_H */
|
#endif // RGBDS_LINK_HASHMAP_H
|
||||||
|
|||||||
@@ -93,4 +93,4 @@
|
|||||||
// (Having two instances of `arr` is OK because the contents of `sizeof` are not evaluated.)
|
// (Having two instances of `arr` is OK because the contents of `sizeof` are not evaluated.)
|
||||||
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof *(arr))
|
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof *(arr))
|
||||||
|
|
||||||
#endif /* HELPERS_H */
|
#endif // HELPERS_H
|
||||||
|
|||||||
@@ -79,12 +79,10 @@ using Holder = std::conditional_t<std::is_lvalue_reference_v<T>, T,
|
|||||||
std::remove_cv_t<std::remove_reference_t<T>>>;
|
std::remove_cv_t<std::remove_reference_t<T>>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// 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... Containers>
|
||||||
static constexpr auto zip(Containers &&...cs) {
|
static constexpr auto zip(Containers &&...cs) {
|
||||||
return detail::ZipContainer<detail::Holder<Containers>...>(std::forward<Containers>(cs)...);
|
return detail::ZipContainer<detail::Holder<Containers>...>(std::forward<Containers>(cs)...);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif /* RGBDS_ITERTOOLS_HPP */
|
#endif // RGBDS_ITERTOOLS_HPP
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* Assigning all sections a place */
|
// Assigning all sections a place
|
||||||
#ifndef RGBDS_LINK_ASSIGN_H
|
#ifndef RGBDS_LINK_ASSIGN_H
|
||||||
#define RGBDS_LINK_ASSIGN_H
|
#define RGBDS_LINK_ASSIGN_H
|
||||||
|
|
||||||
@@ -14,14 +14,10 @@
|
|||||||
|
|
||||||
extern uint64_t nbSectionsToAssign;
|
extern uint64_t nbSectionsToAssign;
|
||||||
|
|
||||||
/**
|
// Assigns all sections a slice of the address space
|
||||||
* Assigns all sections a slice of the address space
|
|
||||||
*/
|
|
||||||
void assign_AssignSections(void);
|
void assign_AssignSections(void);
|
||||||
|
|
||||||
/**
|
// `free`s all assignment memory that was allocated
|
||||||
* `free`s all assignment memory that was allocated.
|
|
||||||
*/
|
|
||||||
void assign_Cleanup(void);
|
void assign_Cleanup(void);
|
||||||
|
|
||||||
#endif /* RGBDS_LINK_ASSIGN_H */
|
#endif // RGBDS_LINK_ASSIGN_H
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* Declarations that all modules use, as well as `main` and related */
|
// Declarations that all modules use, as well as `main` and related
|
||||||
#ifndef RGBDS_LINK_MAIN_H
|
#ifndef RGBDS_LINK_MAIN_H
|
||||||
#define RGBDS_LINK_MAIN_H
|
#define RGBDS_LINK_MAIN_H
|
||||||
|
|
||||||
@@ -16,10 +16,11 @@
|
|||||||
|
|
||||||
#include "helpers.h"
|
#include "helpers.h"
|
||||||
|
|
||||||
/* Variables related to CLI options */
|
// Variables related to CLI options
|
||||||
extern bool isDmgMode;
|
extern bool isDmgMode;
|
||||||
extern char *linkerScriptName;
|
extern char *linkerScriptName;
|
||||||
extern char const *mapFileName;
|
extern char const *mapFileName;
|
||||||
|
extern bool noSymInMap;
|
||||||
extern char const *symFileName;
|
extern char const *symFileName;
|
||||||
extern char const *overlayFileName;
|
extern char const *overlayFileName;
|
||||||
extern char const *outputFileName;
|
extern char const *outputFileName;
|
||||||
@@ -34,7 +35,7 @@ extern bool disablePadding;
|
|||||||
|
|
||||||
struct FileStackNode {
|
struct FileStackNode {
|
||||||
struct FileStackNode *parent;
|
struct FileStackNode *parent;
|
||||||
/* Line at which the parent context was exited; meaningless for the root level */
|
// Line at which the parent context was exited; meaningless for the root level
|
||||||
uint32_t lineNo;
|
uint32_t lineNo;
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
@@ -43,21 +44,21 @@ struct FileStackNode {
|
|||||||
NODE_MACRO,
|
NODE_MACRO,
|
||||||
} type;
|
} type;
|
||||||
union {
|
union {
|
||||||
char *name; /* NODE_FILE, NODE_MACRO */
|
char *name; // NODE_FILE, NODE_MACRO
|
||||||
struct { /* NODE_REPT */
|
struct { // NODE_REPT
|
||||||
uint32_t reptDepth;
|
uint32_t reptDepth;
|
||||||
uint32_t *iters;
|
uint32_t *iters;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Helper macro for printing verbose-mode messages */
|
// Helper macro for printing verbose-mode messages
|
||||||
#define verbosePrint(...) do { \
|
#define verbosePrint(...) do { \
|
||||||
if (beVerbose) \
|
if (beVerbose) \
|
||||||
fprintf(stderr, __VA_ARGS__); \
|
fprintf(stderr, __VA_ARGS__); \
|
||||||
} while (0)
|
} while (0)
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Dump a file stack to stderr
|
* Dump a file stack to stderr
|
||||||
* @param node The leaf node to dump the context of
|
* @param node The leaf node to dump the context of
|
||||||
*/
|
*/
|
||||||
@@ -72,7 +73,7 @@ void error(struct FileStackNode const *where, uint32_t lineNo,
|
|||||||
_Noreturn void fatal(struct FileStackNode const *where, uint32_t lineNo,
|
_Noreturn void fatal(struct FileStackNode const *where, uint32_t lineNo,
|
||||||
char const *fmt, ...) format_(printf, 3, 4);
|
char const *fmt, ...) format_(printf, 3, 4);
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Opens a file if specified, and aborts on error.
|
* Opens a file if specified, and aborts on error.
|
||||||
* @param fileName The name of the file to open; if NULL, no file will be opened
|
* @param fileName The name of the file to open; if NULL, no file will be opened
|
||||||
* @param mode The mode to open the file with
|
* @param mode The mode to open the file with
|
||||||
@@ -86,4 +87,4 @@ FILE *openFile(char const *fileName, char const *mode);
|
|||||||
fclose(tmp); \
|
fclose(tmp); \
|
||||||
} while (0)
|
} while (0)
|
||||||
|
|
||||||
#endif /* RGBDS_LINK_MAIN_H */
|
#endif // RGBDS_LINK_MAIN_H
|
||||||
|
|||||||
@@ -6,37 +6,37 @@
|
|||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* Declarations related to processing of object (.o) files */
|
// Declarations related to processing of object (.o) files
|
||||||
|
|
||||||
#ifndef RGBDS_LINK_OBJECT_H
|
#ifndef RGBDS_LINK_OBJECT_H
|
||||||
#define RGBDS_LINK_OBJECT_H
|
#define RGBDS_LINK_OBJECT_H
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* 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.
|
||||||
* @param fileName A path to the object file to be read
|
* @param fileName A path to the object file to be read
|
||||||
* @param i The ID of the file
|
* @param i The ID of the file
|
||||||
*/
|
*/
|
||||||
void obj_ReadFile(char const *fileName, unsigned int i);
|
void obj_ReadFile(char const *fileName, unsigned int i);
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Perform validation on the object files' contents
|
* Perform validation on the object files' contents
|
||||||
*/
|
*/
|
||||||
void obj_DoSanityChecks(void);
|
void obj_DoSanityChecks(void);
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Evaluate all assertions
|
* Evaluate all assertions
|
||||||
*/
|
*/
|
||||||
void obj_CheckAssertions(void);
|
void obj_CheckAssertions(void);
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Sets up object file reading
|
* Sets up object file reading
|
||||||
* @param nbFiles The number of object files that will be read
|
* @param nbFiles The number of object files that will be read
|
||||||
*/
|
*/
|
||||||
void obj_Setup(unsigned int nbFiles);
|
void obj_Setup(unsigned int nbFiles);
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* `free`s all object memory that was allocated.
|
* `free`s all object memory that was allocated.
|
||||||
*/
|
*/
|
||||||
void obj_Cleanup(void);
|
void obj_Cleanup(void);
|
||||||
|
|
||||||
#endif /* RGBDS_LINK_OBJECT_H */
|
#endif // RGBDS_LINK_OBJECT_H
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* Outputting the result of linking */
|
// Outputting the result of linking
|
||||||
#ifndef RGBDS_LINK_OUTPUT_H
|
#ifndef RGBDS_LINK_OUTPUT_H
|
||||||
#define RGBDS_LINK_OUTPUT_H
|
#define RGBDS_LINK_OUTPUT_H
|
||||||
|
|
||||||
@@ -14,22 +14,22 @@
|
|||||||
|
|
||||||
#include "link/section.h"
|
#include "link/section.h"
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Registers a section for output.
|
* Registers a section for output.
|
||||||
* @param section The section to add
|
* @param section The section to add
|
||||||
*/
|
*/
|
||||||
void out_AddSection(struct Section const *section);
|
void out_AddSection(struct Section const *section);
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Finds an assigned section overlapping another one.
|
* Finds an assigned section overlapping another one.
|
||||||
* @param section The section that is being overlapped
|
* @param section The section that is being overlapped
|
||||||
* @return A section overlapping it
|
* @return A section overlapping it
|
||||||
*/
|
*/
|
||||||
struct Section const *out_OverlappingSection(struct Section const *section);
|
struct Section const *out_OverlappingSection(struct Section const *section);
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Writes all output (bin, sym, map) files.
|
* Writes all output (bin, sym, map) files.
|
||||||
*/
|
*/
|
||||||
void out_WriteFiles(void);
|
void out_WriteFiles(void);
|
||||||
|
|
||||||
#endif /* RGBDS_LINK_OUTPUT_H */
|
#endif // RGBDS_LINK_OUTPUT_H
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* Applying patches to SECTIONs */
|
// Applying patches to SECTIONs
|
||||||
#ifndef RGBDS_LINK_PATCH_H
|
#ifndef RGBDS_LINK_PATCH_H
|
||||||
#define RGBDS_LINK_PATCH_H
|
#define RGBDS_LINK_PATCH_H
|
||||||
|
|
||||||
@@ -27,15 +27,15 @@ struct Assertion {
|
|||||||
struct Assertion *next;
|
struct Assertion *next;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Checks all assertions
|
* Checks all assertions
|
||||||
* @return true if assertion failed
|
* @return true if assertion failed
|
||||||
*/
|
*/
|
||||||
void patch_CheckAssertions(struct Assertion *assertion);
|
void patch_CheckAssertions(struct Assertion *assertion);
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Applies all SECTIONs' patches to them
|
* Applies all SECTIONs' patches to them
|
||||||
*/
|
*/
|
||||||
void patch_ApplyPatches(void);
|
void patch_ApplyPatches(void);
|
||||||
|
|
||||||
#endif /* RGBDS_LINK_PATCH_H */
|
#endif // RGBDS_LINK_PATCH_H
|
||||||
|
|||||||
@@ -6,31 +6,33 @@
|
|||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* Parsing a linker script */
|
// Parsing a linker script
|
||||||
#ifndef RGBDS_LINK_SCRIPT_H
|
#ifndef RGBDS_LINK_SCRIPT_H
|
||||||
#define RGBDS_LINK_SCRIPT_H
|
#define RGBDS_LINK_SCRIPT_H
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include "linkdefs.h"
|
||||||
|
|
||||||
extern FILE * linkerScript;
|
extern FILE * linkerScript;
|
||||||
|
|
||||||
struct SectionPlacement {
|
struct SectionPlacement {
|
||||||
struct Section *section;
|
struct Section *section;
|
||||||
|
enum SectionType type;
|
||||||
uint16_t org;
|
uint16_t org;
|
||||||
uint32_t bank;
|
uint32_t bank;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern uint64_t script_lineNo;
|
extern uint64_t script_lineNo;
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Parses the linker script to return the next section constraint
|
* Parses the linker script to return the next section constraint
|
||||||
* @return A pointer to a struct, or NULL on EOF. The pointer shouldn't be freed
|
* @return A pointer to a struct, or NULL on EOF. The pointer shouldn't be freed
|
||||||
*/
|
*/
|
||||||
struct SectionPlacement *script_NextSection(void);
|
struct SectionPlacement *script_NextSection(void);
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* `free`s all assignment memory that was allocated.
|
* `free`s all assignment memory that was allocated.
|
||||||
*/
|
*/
|
||||||
void script_Cleanup(void);
|
void script_Cleanup(void);
|
||||||
|
|
||||||
#endif /* RGBDS_LINK_SCRIPT_H */
|
#endif // RGBDS_LINK_SCRIPT_H
|
||||||
|
|||||||
19
include/link/sdas_obj.h
Normal file
19
include/link/sdas_obj.h
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of RGBDS.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2022, Eldred Habert and RGBDS contributors.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Assigning all sections a place
|
||||||
|
#ifndef RGBDS_LINK_SDAS_OBJ_H
|
||||||
|
#define RGBDS_LINK_SDAS_OBJ_H
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
struct FileStackNode;
|
||||||
|
|
||||||
|
void sdobj_ReadFile(struct FileStackNode const *fileName, FILE *file);
|
||||||
|
|
||||||
|
#endif // RGBDS_LINK_SDAS_OBJ_H
|
||||||
@@ -6,11 +6,11 @@
|
|||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* Declarations manipulating symbols */
|
// Declarations manipulating symbols
|
||||||
#ifndef RGBDS_LINK_SECTION_H
|
#ifndef RGBDS_LINK_SECTION_H
|
||||||
#define RGBDS_LINK_SECTION_H
|
#define RGBDS_LINK_SECTION_H
|
||||||
|
|
||||||
/* GUIDELINE: external code MUST NOT BE AWARE of the data structure used!! */
|
// GUIDELINE: external code MUST NOT BE AWARE of the data structure used!
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
@@ -41,27 +41,29 @@ struct Patch {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct Section {
|
struct Section {
|
||||||
/* Info contained in the object files */
|
// Info contained in the object files
|
||||||
char *name;
|
char *name;
|
||||||
uint16_t size;
|
uint16_t size;
|
||||||
uint16_t offset;
|
uint16_t offset;
|
||||||
enum SectionType type;
|
enum SectionType type;
|
||||||
enum SectionModifier modifier;
|
enum SectionModifier modifier;
|
||||||
bool isAddressFixed;
|
bool isAddressFixed;
|
||||||
|
// This `struct`'s address in ROM.
|
||||||
|
// Importantly for fragments, this does not include `offset`!
|
||||||
uint16_t org;
|
uint16_t org;
|
||||||
bool isBankFixed;
|
bool isBankFixed;
|
||||||
uint32_t bank;
|
uint32_t bank;
|
||||||
bool isAlignFixed;
|
bool isAlignFixed;
|
||||||
uint16_t alignMask;
|
uint16_t alignMask;
|
||||||
uint16_t alignOfs;
|
uint16_t alignOfs;
|
||||||
uint8_t *data; /* Array of size `size`*/
|
uint8_t *data; // Array of size `size`
|
||||||
uint32_t nbPatches;
|
uint32_t nbPatches;
|
||||||
struct Patch *patches;
|
struct Patch *patches;
|
||||||
/* Extra info computed during linking */
|
// Extra info computed during linking
|
||||||
struct Symbol **fileSymbols;
|
struct Symbol **fileSymbols;
|
||||||
uint32_t nbSymbols;
|
uint32_t nbSymbols;
|
||||||
struct Symbol const **symbols;
|
struct Symbol **symbols;
|
||||||
struct Section *nextu; /* The next "component" of this unionized sect */
|
struct Section *nextu; // The next "component" of this unionized sect
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -74,27 +76,27 @@ struct Section {
|
|||||||
*/
|
*/
|
||||||
void sect_ForEach(void (*callback)(struct Section *, void *), void *arg);
|
void sect_ForEach(void (*callback)(struct Section *, void *), void *arg);
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Registers a section to be processed.
|
* Registers a section to be processed.
|
||||||
* @param section The section to register.
|
* @param section The section to register.
|
||||||
*/
|
*/
|
||||||
void sect_AddSection(struct Section *section);
|
void sect_AddSection(struct Section *section);
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Finds a section by its name.
|
* Finds a section by its name.
|
||||||
* @param name The name of the section to look for
|
* @param name The name of the section to look for
|
||||||
* @return A pointer to the section, or NULL if it wasn't found
|
* @return A pointer to the section, or NULL if it wasn't found
|
||||||
*/
|
*/
|
||||||
struct Section *sect_GetSection(char const *name);
|
struct Section *sect_GetSection(char const *name);
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* `free`s all section memory that was allocated.
|
* `free`s all section memory that was allocated.
|
||||||
*/
|
*/
|
||||||
void sect_CleanupSections(void);
|
void sect_CleanupSections(void);
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* 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);
|
void sect_DoSanityChecks(void);
|
||||||
|
|
||||||
#endif /* RGBDS_LINK_SECTION_H */
|
#endif // RGBDS_LINK_SECTION_H
|
||||||
|
|||||||
@@ -6,11 +6,11 @@
|
|||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* Declarations manipulating symbols */
|
// Declarations manipulating symbols
|
||||||
#ifndef RGBDS_LINK_SYMBOL_H
|
#ifndef RGBDS_LINK_SYMBOL_H
|
||||||
#define RGBDS_LINK_SYMBOL_H
|
#define RGBDS_LINK_SYMBOL_H
|
||||||
|
|
||||||
/* GUIDELINE: external code MUST NOT BE AWARE of the data structure used!! */
|
// GUIDELINE: external code MUST NOT BE AWARE of the data structure used!
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
struct FileStackNode;
|
struct FileStackNode;
|
||||||
|
|
||||||
struct Symbol {
|
struct Symbol {
|
||||||
/* Info contained in the object files */
|
// Info contained in the object files
|
||||||
char *name;
|
char *name;
|
||||||
enum ExportLevel type;
|
enum ExportLevel type;
|
||||||
char const *objFileName;
|
char const *objFileName;
|
||||||
@@ -27,10 +27,11 @@ struct Symbol {
|
|||||||
int32_t lineNo;
|
int32_t lineNo;
|
||||||
int32_t sectionID;
|
int32_t sectionID;
|
||||||
union {
|
union {
|
||||||
|
// Both types must be identical
|
||||||
int32_t offset;
|
int32_t offset;
|
||||||
int32_t value;
|
int32_t value;
|
||||||
};
|
};
|
||||||
/* Extra info computed during linking */
|
// Extra info computed during linking
|
||||||
struct Section *section;
|
struct Section *section;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -46,16 +47,16 @@ void sym_ForEach(void (*callback)(struct Symbol *, void *), void *arg);
|
|||||||
|
|
||||||
void sym_AddSymbol(struct Symbol *symbol);
|
void sym_AddSymbol(struct 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
|
* @param name The name of the symbol to look for
|
||||||
* @return A pointer to the symbol, or NULL if not found.
|
* @return A pointer to the symbol, or NULL if not found.
|
||||||
*/
|
*/
|
||||||
struct Symbol *sym_GetSymbol(char const *name);
|
struct Symbol *sym_GetSymbol(char const *name);
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* `free`s all symbol memory that was allocated.
|
* `free`s all symbol memory that was allocated.
|
||||||
*/
|
*/
|
||||||
void sym_CleanupSymbols(void);
|
void sym_CleanupSymbols(void);
|
||||||
|
|
||||||
#endif /* RGBDS_LINK_SYMBOL_H */
|
#endif // RGBDS_LINK_SYMBOL_H
|
||||||
|
|||||||
@@ -9,11 +9,11 @@
|
|||||||
#ifndef RGBDS_LINKDEFS_H
|
#ifndef RGBDS_LINKDEFS_H
|
||||||
#define RGBDS_LINKDEFS_H
|
#define RGBDS_LINKDEFS_H
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
#define RGBDS_OBJECT_VERSION_STRING "RGB%1u"
|
#define RGBDS_OBJECT_VERSION_STRING "RGB9"
|
||||||
#define RGBDS_OBJECT_VERSION_NUMBER 9U
|
|
||||||
#define RGBDS_OBJECT_REV 9U
|
#define RGBDS_OBJECT_REV 9U
|
||||||
|
|
||||||
enum AssertionType {
|
enum AssertionType {
|
||||||
@@ -74,9 +74,50 @@ enum SectionType {
|
|||||||
SECTTYPE_SRAM,
|
SECTTYPE_SRAM,
|
||||||
SECTTYPE_OAM,
|
SECTTYPE_OAM,
|
||||||
|
|
||||||
|
// In RGBLINK, this is used for "indeterminate" sections; this is primarily for SDCC
|
||||||
|
// areas, which do not carry any section type info and must be told from the linker script
|
||||||
SECTTYPE_INVALID
|
SECTTYPE_INVALID
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Nont-`const` members may be patched in RGBLINK depending on CLI flags
|
||||||
|
extern struct SectionTypeInfo {
|
||||||
|
char const *const name;
|
||||||
|
uint16_t const startAddr;
|
||||||
|
uint16_t size;
|
||||||
|
uint32_t const firstBank;
|
||||||
|
uint32_t lastBank;
|
||||||
|
} sectionTypeInfo[SECTTYPE_INVALID];
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Tells whether a section has data in its object file definition,
|
||||||
|
* depending on type.
|
||||||
|
* @param type The section's type
|
||||||
|
* @return `true` if the section's definition includes data
|
||||||
|
*/
|
||||||
|
static inline bool sect_HasData(enum SectionType type)
|
||||||
|
{
|
||||||
|
assert(type != SECTTYPE_INVALID);
|
||||||
|
return type == SECTTYPE_ROM0 || type == SECTTYPE_ROMX;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Computes a memory region's end address (last byte), eg. 0x7FFF
|
||||||
|
* @return The address of the last byte in that memory region
|
||||||
|
*/
|
||||||
|
static inline uint16_t endaddr(enum SectionType type)
|
||||||
|
{
|
||||||
|
return sectionTypeInfo[type].startAddr + sectionTypeInfo[type].size - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Computes a memory region's number of banks
|
||||||
|
* @return The number of banks, 1 for regions without banking
|
||||||
|
*/
|
||||||
|
static inline uint32_t nbbanks(enum SectionType type)
|
||||||
|
{
|
||||||
|
return sectionTypeInfo[type].lastBank - sectionTypeInfo[type].firstBank + 1;
|
||||||
|
}
|
||||||
|
|
||||||
enum SectionModifier {
|
enum SectionModifier {
|
||||||
SECTION_NORMAL,
|
SECTION_NORMAL,
|
||||||
SECTION_UNION,
|
SECTION_UNION,
|
||||||
@@ -85,17 +126,6 @@ enum SectionModifier {
|
|||||||
|
|
||||||
extern char const * const sectionModNames[];
|
extern char const * const sectionModNames[];
|
||||||
|
|
||||||
/**
|
|
||||||
* Tells whether a section has data in its object file definition,
|
|
||||||
* depending on type.
|
|
||||||
* @param type The section's type
|
|
||||||
* @return `true` if the section's definition includes data
|
|
||||||
*/
|
|
||||||
static inline bool sect_HasData(enum SectionType type)
|
|
||||||
{
|
|
||||||
return type == SECTTYPE_ROM0 || type == SECTTYPE_ROMX;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum ExportLevel {
|
enum ExportLevel {
|
||||||
SYMTYPE_LOCAL,
|
SYMTYPE_LOCAL,
|
||||||
SYMTYPE_IMPORT,
|
SYMTYPE_IMPORT,
|
||||||
@@ -111,45 +141,4 @@ enum PatchType {
|
|||||||
PATCHTYPE_INVALID
|
PATCHTYPE_INVALID
|
||||||
};
|
};
|
||||||
|
|
||||||
#define BANK_MIN_ROM0 0
|
#endif // RGBDS_LINKDEFS_H
|
||||||
#define BANK_MAX_ROM0 0
|
|
||||||
#define BANK_MIN_ROMX 1
|
|
||||||
#define BANK_MAX_ROMX 511
|
|
||||||
#define BANK_MIN_VRAM 0
|
|
||||||
#define BANK_MAX_VRAM 1
|
|
||||||
#define BANK_MIN_SRAM 0
|
|
||||||
#define BANK_MAX_SRAM 15
|
|
||||||
#define BANK_MIN_WRAM0 0
|
|
||||||
#define BANK_MAX_WRAM0 0
|
|
||||||
#define BANK_MIN_WRAMX 1
|
|
||||||
#define BANK_MAX_WRAMX 7
|
|
||||||
#define BANK_MIN_OAM 0
|
|
||||||
#define BANK_MAX_OAM 0
|
|
||||||
#define BANK_MIN_HRAM 0
|
|
||||||
#define BANK_MAX_HRAM 0
|
|
||||||
|
|
||||||
extern uint16_t startaddr[];
|
|
||||||
extern uint16_t maxsize[];
|
|
||||||
extern uint32_t bankranges[][2];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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(enum SectionType type)
|
|
||||||
{
|
|
||||||
return startaddr[type] + maxsize[type] - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Computes a memory region's number of banks
|
|
||||||
* @return The number of banks, 1 for regions without banking
|
|
||||||
*/
|
|
||||||
static inline uint32_t nbbanks(enum SectionType type)
|
|
||||||
{
|
|
||||||
return bankranges[type][1] - bankranges[type][0] + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
extern char const * const typeNames[SECTTYPE_INVALID];
|
|
||||||
|
|
||||||
#endif /* RGBDS_LINKDEFS_H */
|
|
||||||
|
|||||||
@@ -18,4 +18,4 @@ int32_t op_shift_left(int32_t value, int32_t amount);
|
|||||||
int32_t op_shift_right(int32_t value, int32_t amount);
|
int32_t op_shift_right(int32_t value, int32_t amount);
|
||||||
int32_t op_shift_right_unsigned(int32_t value, int32_t amount);
|
int32_t op_shift_right_unsigned(int32_t value, int32_t amount);
|
||||||
|
|
||||||
#endif /* RGBDS_OP_MATH_H */
|
#endif // RGBDS_OP_MATH_H
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* platform-specific hacks */
|
// platform-specific hacks
|
||||||
|
|
||||||
#ifndef RGBDS_PLATFORM_H
|
#ifndef RGBDS_PLATFORM_H
|
||||||
#define RGBDS_PLATFORM_H
|
#define RGBDS_PLATFORM_H
|
||||||
@@ -20,20 +20,20 @@
|
|||||||
# include <strings.h>
|
# include <strings.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* MSVC has deprecated strdup in favor of _strdup */
|
// MSVC has deprecated strdup in favor of _strdup
|
||||||
#ifdef _MSC_VER
|
#ifdef _MSC_VER
|
||||||
# define strdup _strdup
|
# define strdup _strdup
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* MSVC prefixes the names of S_* macros with underscores,
|
// MSVC prefixes the names of S_* macros with underscores,
|
||||||
and doesn't define any S_IS* macros. Define them ourselves */
|
// and doesn't define any S_IS* macros; define them ourselves
|
||||||
#ifdef _MSC_VER
|
#ifdef _MSC_VER
|
||||||
# define S_IFMT _S_IFMT
|
# define S_IFMT _S_IFMT
|
||||||
# define S_IFDIR _S_IFDIR
|
# define S_IFDIR _S_IFDIR
|
||||||
# define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR)
|
# define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* MSVC doesn't use POSIX types or defines for `read` */
|
// MSVC doesn't use POSIX types or defines for `read`
|
||||||
#ifdef _MSC_VER
|
#ifdef _MSC_VER
|
||||||
# include <io.h>
|
# include <io.h>
|
||||||
# define STDIN_FILENO 0
|
# define STDIN_FILENO 0
|
||||||
@@ -46,7 +46,7 @@
|
|||||||
# include <unistd.h>
|
# include <unistd.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* MSVC doesn't support `[static N]` for array arguments from C99 or C11 */
|
// MSVC doesn't support `[static N]` for array arguments from C99 or C11
|
||||||
#ifdef _MSC_VER
|
#ifdef _MSC_VER
|
||||||
# define MIN_NB_ELMS(N)
|
# define MIN_NB_ELMS(N)
|
||||||
# define ARR_QUALS(...)
|
# define ARR_QUALS(...)
|
||||||
@@ -63,8 +63,10 @@
|
|||||||
# define O_RDWR _O_RDWR
|
# define O_RDWR _O_RDWR
|
||||||
# define S_ISREG(field) ((field) & _S_IFREG)
|
# define S_ISREG(field) ((field) & _S_IFREG)
|
||||||
# define O_BINARY _O_BINARY
|
# define O_BINARY _O_BINARY
|
||||||
|
# define O_TEXT _O_TEXT
|
||||||
#elif !defined(O_BINARY) // Cross-compilers define O_BINARY
|
#elif !defined(O_BINARY) // Cross-compilers define O_BINARY
|
||||||
# define O_BINARY 0 // POSIX says we shouldn't care!
|
# define O_BINARY 0 // POSIX says we shouldn't care!
|
||||||
|
# define O_TEXT 0 // Assume that it's not defined either
|
||||||
#endif // _MSC_VER
|
#endif // _MSC_VER
|
||||||
|
|
||||||
// Windows has stdin and stdout open as text by default, which we may not want
|
// Windows has stdin and stdout open as text by default, which we may not want
|
||||||
@@ -72,7 +74,7 @@
|
|||||||
# include <io.h>
|
# include <io.h>
|
||||||
# define setmode(fd, mode) _setmode(fd, mode)
|
# define setmode(fd, mode) _setmode(fd, mode)
|
||||||
#else
|
#else
|
||||||
# define setmode(fd, mode) ((void)0)
|
# define setmode(fd, mode) (0)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#endif /* RGBDS_PLATFORM_H */
|
#endif // RGBDS_PLATFORM_H
|
||||||
|
|||||||
@@ -15,8 +15,7 @@ extern "C" {
|
|||||||
|
|
||||||
#define PACKAGE_VERSION_MAJOR 0
|
#define PACKAGE_VERSION_MAJOR 0
|
||||||
#define PACKAGE_VERSION_MINOR 6
|
#define PACKAGE_VERSION_MINOR 6
|
||||||
#define PACKAGE_VERSION_PATCH 0
|
#define PACKAGE_VERSION_PATCH 1
|
||||||
#define PACKAGE_VERSION_RC 1
|
|
||||||
|
|
||||||
char const *get_package_version_string(void);
|
char const *get_package_version_string(void);
|
||||||
|
|
||||||
@@ -24,4 +23,4 @@ char const *get_package_version_string(void);
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#endif /* EXTERN_VERSION_H */
|
#endif // EXTERN_VERSION_H
|
||||||
|
|||||||
36
man/gbz80.7
36
man/gbz80.7
@@ -171,8 +171,8 @@ and
|
|||||||
.It Sx JP HL
|
.It Sx JP HL
|
||||||
.It Sx JP n16
|
.It Sx JP n16
|
||||||
.It Sx JP cc,n16
|
.It Sx JP cc,n16
|
||||||
.It Sx JR e8
|
.It Sx JR n16
|
||||||
.It Sx JR cc,e8
|
.It Sx JR cc,n16
|
||||||
.It Sx RET cc
|
.It Sx RET cc
|
||||||
.It Sx RET
|
.It Sx RET
|
||||||
.It Sx RETI
|
.It Sx RETI
|
||||||
@@ -754,22 +754,34 @@ Cycles: 1
|
|||||||
Bytes: 1
|
Bytes: 1
|
||||||
.Pp
|
.Pp
|
||||||
Flags: None affected.
|
Flags: None affected.
|
||||||
.Ss JR e8
|
.Ss JR n16
|
||||||
Relative Jump by adding
|
Relative Jump to address
|
||||||
.Ar e8
|
.Ar n16 .
|
||||||
to the address of the instruction following the
|
The address is encoded as a signed 8-bit offset from the address immediately following the
|
||||||
.Sy JR .
|
.Ic JR
|
||||||
To clarify, an operand of 0 is equivalent to no jumping.
|
instruction, so the target address
|
||||||
|
.Ar n16
|
||||||
|
must be between
|
||||||
|
.Sy -128
|
||||||
|
and
|
||||||
|
.Sy 127
|
||||||
|
bytes away.
|
||||||
|
For example:
|
||||||
|
.Bd -literal -offset indent
|
||||||
|
JR Label ; no-op; encoded offset of 0
|
||||||
|
Label:
|
||||||
|
JR Label ; infinite loop; encoded offset of -2
|
||||||
|
.Ed
|
||||||
.Pp
|
.Pp
|
||||||
Cycles: 3
|
Cycles: 3
|
||||||
.Pp
|
.Pp
|
||||||
Bytes: 2
|
Bytes: 2
|
||||||
.Pp
|
.Pp
|
||||||
Flags: None affected.
|
Flags: None affected.
|
||||||
.Ss JR cc,e8
|
.Ss JR cc,n16
|
||||||
Relative Jump by adding
|
Relative Jump to address
|
||||||
.Ar e8
|
.Ar n16
|
||||||
to the current address if condition
|
if condition
|
||||||
.Ar cc
|
.Ar cc
|
||||||
is met.
|
is met.
|
||||||
.Pp
|
.Pp
|
||||||
|
|||||||
93
man/rgbasm.1
93
man/rgbasm.1
@@ -13,31 +13,32 @@
|
|||||||
.Nd Game Boy assembler
|
.Nd Game Boy assembler
|
||||||
.Sh SYNOPSIS
|
.Sh SYNOPSIS
|
||||||
.Nm
|
.Nm
|
||||||
.Op Fl EhLVvw
|
.Op Fl EHhLlVvw
|
||||||
.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
|
||||||
.Op Fl i Ar path
|
.Op Fl I Ar path
|
||||||
.Op Fl M Ar depend_file
|
.Op Fl M Ar depend_file
|
||||||
.Op Fl MG
|
.Op Fl MG
|
||||||
.Op Fl MP
|
.Op Fl MP
|
||||||
.Op Fl MT Ar target_file
|
.Op Fl MT Ar target_file
|
||||||
.Op Fl MQ Ar target_file
|
.Op Fl MQ Ar target_file
|
||||||
.Op Fl o Ar out_file
|
.Op Fl o Ar out_file
|
||||||
|
.Op Fl P Ar include_file
|
||||||
.Op Fl p Ar pad_value
|
.Op Fl p Ar pad_value
|
||||||
|
.Op Fl Q Ar fix_precision
|
||||||
.Op Fl r Ar recursion_depth
|
.Op Fl r Ar recursion_depth
|
||||||
.Op Fl W Ar warning
|
.Op Fl W Ar warning
|
||||||
.Ar
|
.Ar asmfile
|
||||||
.Sh DESCRIPTION
|
.Sh DESCRIPTION
|
||||||
The
|
The
|
||||||
.Nm
|
.Nm
|
||||||
program creates an RGB object file from an assembly source file.
|
program creates an RGB object file from an assembly source file.
|
||||||
The input
|
The input
|
||||||
.Ar file
|
.Ar asmfile
|
||||||
can be a file path, or
|
can be a path to a file, or
|
||||||
.Cm \-
|
.Cm \-
|
||||||
denoting
|
to read from standard input.
|
||||||
.Cm stdin .
|
|
||||||
.Pp
|
.Pp
|
||||||
Note that options can be abbreviated as long as the abbreviation is unambiguous:
|
Note that options can be abbreviated as long as the abbreviation is unambiguous:
|
||||||
.Fl Fl verb
|
.Fl Fl verb
|
||||||
@@ -66,25 +67,55 @@ Export all labels, including unreferenced and local labels.
|
|||||||
.It Fl g Ar chars , Fl Fl gfx-chars Ar chars
|
.It Fl g Ar chars , Fl 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 Fl halt-without-nop
|
.It Fl H , Fl Fl nop-after-halt
|
||||||
By default,
|
By default,
|
||||||
.Nm
|
.Nm
|
||||||
inserts a
|
inserts a
|
||||||
.Ic nop
|
.Ic nop
|
||||||
instruction immediately after any
|
instruction immediately after any
|
||||||
.Ic halt
|
.Ic halt
|
||||||
instruction.
|
instruction,
|
||||||
|
but this has been deprecated and prints a warning message the first time it occurs.
|
||||||
The
|
The
|
||||||
.Fl h
|
.Fl H
|
||||||
option disables this behavior.
|
option opts into this insertion,
|
||||||
.It Fl i Ar path , Fl Fl include Ar path
|
so no warning will be printed.
|
||||||
Add an include path.
|
.It Fl h , Fl Fl halt-without-nop
|
||||||
|
Disables inserting a
|
||||||
|
.Ic nop
|
||||||
|
instruction immediately after any
|
||||||
|
.Ic halt
|
||||||
|
instruction.
|
||||||
|
.It Fl I Ar path , Fl Fl include Ar path
|
||||||
|
Add a new
|
||||||
|
.Dq include path ; Ar path
|
||||||
|
must point to a directory.
|
||||||
|
When a
|
||||||
|
.Ic INCLUDE
|
||||||
|
.Pq including the implicit one from Fl P
|
||||||
|
or
|
||||||
|
.Ic INCBIN
|
||||||
|
is attempted,
|
||||||
|
.Nm
|
||||||
|
first looks up the provided path from its working directory; if this fails, it tries again from each of the
|
||||||
|
.Dq include path
|
||||||
|
directories, in the order they were provided.
|
||||||
.It Fl L , Fl Fl preserve-ld
|
.It Fl L , Fl Fl preserve-ld
|
||||||
Disable the optimization that turns loads of the form
|
By default,
|
||||||
|
.Nm
|
||||||
|
optimizes loads of the form
|
||||||
.Ic LD [$FF00+n8],A
|
.Ic LD [$FF00+n8],A
|
||||||
into the opcode
|
into the opcode
|
||||||
.Ic LDH [$FF00+n8],A
|
.Ic LDH [$FF00+n8],A ,
|
||||||
in order to have full control of the result in the final ROM.
|
but this has been deprecated and prints a warning message the first time it occurs.
|
||||||
|
The
|
||||||
|
.Fl L
|
||||||
|
option disables this optimization.
|
||||||
|
.It Fl l , Fl Fl auto-ldh
|
||||||
|
Optimize loads of the form
|
||||||
|
.Ic LD [$FF00+n8],A
|
||||||
|
into the opcode
|
||||||
|
.Ic LDH [$FF00+n8],A .
|
||||||
.It Fl M Ar depend_file , Fl Fl dependfile Ar depend_file
|
.It Fl M Ar depend_file , Fl Fl dependfile Ar depend_file
|
||||||
Print
|
Print
|
||||||
.Xr make 1
|
.Xr make 1
|
||||||
@@ -97,6 +128,7 @@ This makes
|
|||||||
.Nm
|
.Nm
|
||||||
assume that missing files are auto-generated: when
|
assume that missing files are auto-generated: when
|
||||||
.Ic INCLUDE
|
.Ic INCLUDE
|
||||||
|
.Pq including the implicit one from Fl P
|
||||||
or
|
or
|
||||||
.Ic INCBIN
|
.Ic INCBIN
|
||||||
is attempted on a non-existent file, it is added as a dependency, then
|
is attempted on a non-existent file, it is added as a dependency, then
|
||||||
@@ -127,11 +159,25 @@ characters, essentially
|
|||||||
.Sq $ .
|
.Sq $ .
|
||||||
.It Fl o Ar out_file , Fl Fl output Ar out_file
|
.It Fl o Ar out_file , Fl Fl output Ar out_file
|
||||||
Write an object file to the given filename.
|
Write an object file to the given filename.
|
||||||
|
.It Fl P Ar include_file , Fl Fl preinclude Ar include_file
|
||||||
|
Pre-include a file.
|
||||||
|
This acts as if a
|
||||||
|
.Ql Ic INCLUDE Qq Ar include_file
|
||||||
|
was read before the input
|
||||||
|
.Ar asmfile .
|
||||||
.It Fl p Ar pad_value , Fl Fl pad-value Ar pad_value
|
.It Fl p Ar pad_value , Fl Fl pad-value Ar pad_value
|
||||||
When padding an image, pad with this value.
|
When padding an image, pad with this value.
|
||||||
The default is 0x00.
|
The default is 0x00.
|
||||||
|
.It Fl Q Ar fix_precision , Fl Fl q-precision Ar fix_precision
|
||||||
|
Use this as the precision of fixed-point numbers after the decimal point, unless they specify their own precision.
|
||||||
|
The default is 16, so fixed-point numbers are Q16.16 (since they are 32-bit integers).
|
||||||
|
The argument may start with a
|
||||||
|
.Ql \&.
|
||||||
|
to match the Q notation, for example,
|
||||||
|
.Ql Fl Q Ar .16 .
|
||||||
.It Fl r Ar recursion_depth , Fl Fl recursion-depth Ar recursion_depth
|
.It Fl r Ar recursion_depth , Fl Fl recursion-depth Ar recursion_depth
|
||||||
Specifies the recursion depth at which RGBASM will assume being in an infinite loop.
|
Specifies the recursion depth past which RGBASM will assume being in an infinite loop.
|
||||||
|
The default is 64.
|
||||||
.It Fl V , Fl Fl version
|
.It Fl V , Fl Fl version
|
||||||
Print the version of the program and exit.
|
Print the version of the program and exit.
|
||||||
.It Fl v , Fl Fl verbose
|
.It Fl v , Fl Fl verbose
|
||||||
@@ -274,6 +320,19 @@ warns when an N-bit value's absolute value is 2**N or greater.
|
|||||||
or just
|
or just
|
||||||
.Fl Wtruncation
|
.Fl Wtruncation
|
||||||
also warns when an N-bit value is less than -2**(N-1), which will not fit in two's complement encoding.
|
also warns when an N-bit value is less than -2**(N-1), which will not fit in two's complement encoding.
|
||||||
|
.It Fl Wunmapped-char=
|
||||||
|
Warn when a character goes through charmap conversion but has no defined mapping.
|
||||||
|
.Fl Wunmapped-char=0
|
||||||
|
or
|
||||||
|
.Fl Wunmapped-char
|
||||||
|
disables this warning.
|
||||||
|
.Fl Wunmapped-char=1
|
||||||
|
or just
|
||||||
|
.Fl Wunmapped-char
|
||||||
|
only warns if the active charmap is not empty.
|
||||||
|
.Fl Wunmapped-char=2
|
||||||
|
warns if the active charmap is empty, and/or is not the default charmap
|
||||||
|
.Sq main .
|
||||||
.It Fl Wno-user
|
.It Fl Wno-user
|
||||||
Warn when the
|
Warn when the
|
||||||
.Ic WARN
|
.Ic WARN
|
||||||
|
|||||||
276
man/rgbasm.5
276
man/rgbasm.5
@@ -18,7 +18,7 @@ This is the full description of the language used by
|
|||||||
The description of the instructions supported by the Game Boy CPU is in
|
The description of the instructions supported by the Game Boy CPU is in
|
||||||
.Xr gbz80 7 .
|
.Xr gbz80 7 .
|
||||||
.Pp
|
.Pp
|
||||||
It is strongly recommended to have some familiarity with the Game Boy hardware before reading this document.
|
It is advisable to have some familiarity with the Game Boy hardware before reading this document.
|
||||||
RGBDS is specifically targeted at the Game Boy, and thus a lot of its features tie directly to its concepts.
|
RGBDS is specifically targeted at the Game Boy, and thus a lot of its features tie directly to its concepts.
|
||||||
This document is not intended to be a Game Boy hardware reference.
|
This document is not intended to be a Game Boy hardware reference.
|
||||||
.Pp
|
.Pp
|
||||||
@@ -57,27 +57,25 @@ and ending with
|
|||||||
.Ql */ .
|
.Ql */ .
|
||||||
It can be split across multiple lines, or occur in the middle of an expression:
|
It can be split across multiple lines, or occur in the middle of an expression:
|
||||||
.Bd -literal -offset indent
|
.Bd -literal -offset indent
|
||||||
X = /* the value of x
|
DEF X = /* the value of x
|
||||||
should be 3 */ 3
|
should be 3 */ 3
|
||||||
.Ed
|
.Ed
|
||||||
.Pp
|
.Pp
|
||||||
Sometimes lines can be too long and it may be necessary to split them.
|
Sometimes lines can be too long and it may be necessary to split them.
|
||||||
To do so, put a backslash at the end of the line:
|
To do so, put a backslash at the end of the line:
|
||||||
.Bd -literal -offset indent
|
.Bd -literal -offset indent
|
||||||
DB 1, 2, 3,\ \[rs]
|
DB 1, 2, 3,\ \e
|
||||||
4, 5, 6,\ \[rs]\ ;\ Put it before any comments
|
4, 5, 6,\ \e\ ;\ Put it before any comments
|
||||||
7, 8, 9
|
7, 8, 9
|
||||||
DB "Hello,\ \[rs]\ \ ;\ Space before the \[rs] is included
|
DB "Hello,\ \e\ \ ;\ Space before the \e is included
|
||||||
world!"\ \ \ \ \ \ \ \ \ \ \ ;\ Any leading space is included
|
world!"\ \ \ \ \ \ \ \ \ \ \ ;\ Any leading space is included
|
||||||
.Ed
|
.Ed
|
||||||
.Ss Symbol interpolation
|
.Ss Symbol interpolation
|
||||||
A funky feature is
|
A funky feature is writing a symbol between
|
||||||
.Ql {symbol}
|
.Ql {braces} ,
|
||||||
within a string, called
|
called
|
||||||
.Dq symbol interpolation .
|
.Dq symbol interpolation .
|
||||||
This will paste the contents of
|
This will paste the symbol's contents as if they were part of the source file.
|
||||||
.Ql symbol
|
|
||||||
as if they were part of the source file.
|
|
||||||
If it is a string symbol, its characters are simply inserted as-is.
|
If it is a string symbol, its characters are simply inserted as-is.
|
||||||
If it is a numeric symbol, its value is converted to hexadecimal notation with a dollar sign
|
If it is a numeric symbol, its value is converted to hexadecimal notation with a dollar sign
|
||||||
.Sq $
|
.Sq $
|
||||||
@@ -85,7 +83,7 @@ prepended.
|
|||||||
.Pp
|
.Pp
|
||||||
Symbol interpolations can be nested, too!
|
Symbol interpolations can be nested, too!
|
||||||
.Bd -literal -offset indent
|
.Bd -literal -offset indent
|
||||||
DEF topic EQUS "life, the universe, and \[rs]"everything\[rs]""
|
DEF topic EQUS "life, the universe, and \e"everything\e""
|
||||||
DEF meaning EQUS "answer"
|
DEF meaning EQUS "answer"
|
||||||
;\ Defines answer = 42
|
;\ Defines answer = 42
|
||||||
DEF {meaning} = 42
|
DEF {meaning} = 42
|
||||||
@@ -170,20 +168,20 @@ Valid print types are:
|
|||||||
Examples:
|
Examples:
|
||||||
.Bd -literal -offset indent
|
.Bd -literal -offset indent
|
||||||
SECTION "Test", ROM0[2]
|
SECTION "Test", ROM0[2]
|
||||||
X: ;\ This works with labels **whose address is known**
|
X: ;\ This works with labels **whose address is known**
|
||||||
Y = 3 ;\ This also works with variables
|
DEF Y = 3 ;\ This also works with variables
|
||||||
SUM equ X + Y ;\ And likewise with numeric constants
|
DEF SUM EQU X + Y ;\ And likewise with numeric constants
|
||||||
; Prints "%0010 + $3 == 5"
|
; Prints "%0010 + $3 == 5"
|
||||||
PRINTLN "{#05b:X} + {#x:Y} == {d:SUM}"
|
PRINTLN "{#05b:X} + {#x:Y} == {d:SUM}"
|
||||||
|
|
||||||
rsset 32
|
rsset 32
|
||||||
PERCENT rb 1 ;\ Same with offset constants
|
DEF PERCENT rb 1 ;\ Same with offset constants
|
||||||
VALUE = 20
|
DEF VALUE = 20
|
||||||
RESULT = MUL(20.0, 0.32)
|
DEF RESULT = MUL(20.0, 0.32)
|
||||||
; Prints "32% of 20 = 6.40"
|
; Prints "32% of 20 = 6.40"
|
||||||
PRINTLN "{d:PERCENT}% of {d:VALUE} = {f:RESULT}"
|
PRINTLN "{d:PERCENT}% of {d:VALUE} = {f:RESULT}"
|
||||||
|
|
||||||
WHO equs STRLWR("WORLD")
|
DEF WHO EQUS STRLWR("WORLD")
|
||||||
; Prints "Hello world!"
|
; Prints "Hello world!"
|
||||||
PRINTLN "Hello {s:WHO}!"
|
PRINTLN "Hello {s:WHO}!"
|
||||||
.Ed
|
.Ed
|
||||||
@@ -208,13 +206,14 @@ section.
|
|||||||
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 "Fixed point (Q16.16)" "Prefix"
|
.Bl -column -offset indent "Precise fixed-point" "Prefix"
|
||||||
.It Sy Format type Ta Sy Prefix Ta Sy Accepted characters
|
.It Sy Format type Ta Sy Prefix Ta Sy Accepted characters
|
||||||
.It Hexadecimal Ta $ Ta 0123456789ABCDEF
|
.It Hexadecimal Ta $ Ta 0123456789ABCDEF
|
||||||
.It Decimal Ta none Ta 0123456789
|
.It Decimal Ta none Ta 0123456789
|
||||||
.It Octal Ta & Ta 01234567
|
.It Octal Ta & Ta 01234567
|
||||||
.It Binary Ta % Ta 01
|
.It Binary Ta % Ta 01
|
||||||
.It Fixed point (Q16.16) Ta none Ta 01234.56789
|
.It Fixed-point Ta none Ta 01234.56789
|
||||||
|
.It Precise fixed-point Ta none Ta 12.34q8
|
||||||
.It Character constant Ta none Ta \(dqABYZ\(dq
|
.It Character constant Ta none Ta \(dqABYZ\(dq
|
||||||
.It Gameboy graphics Ta \` Ta 0123
|
.It Gameboy graphics Ta \` Ta 0123
|
||||||
.El
|
.El
|
||||||
@@ -301,9 +300,19 @@ and
|
|||||||
.Ic \&!
|
.Ic \&!
|
||||||
returns 1 if the operand was 0, and 0 otherwise.
|
returns 1 if the operand was 0, and 0 otherwise.
|
||||||
.Ss Fixed-point expressions
|
.Ss Fixed-point expressions
|
||||||
Fixed-point numbers are basically normal (32-bit) integers, which count 65536ths instead of entire units, offering better precision than integers but limiting the range of values.
|
Fixed-point numbers are basically normal (32-bit) integers, which count fractions instead of whole numbers.
|
||||||
The upper 16 bits are used for the integer part and the lower 16 bits are used for the fraction (65536ths).
|
They offer better precision than integers but limit the range of values.
|
||||||
Since they are still akin to integers, you can use them in normal integer expressions, and some integer operators like
|
By default, the upper 16 bits are used for the integer part and the lower 16 bits are used for the fraction (65536ths).
|
||||||
|
The default number of fractional bits can be changed with the
|
||||||
|
.Fl Q
|
||||||
|
command-line option.
|
||||||
|
You can also specify a precise fixed-point value by appending a
|
||||||
|
.Dq q
|
||||||
|
to it followed by the number of fractional bits, such as
|
||||||
|
.Ql 12.34q8 .
|
||||||
|
.Pp
|
||||||
|
Since fixed-point values are still just integers, you can use them in normal integer expressions.
|
||||||
|
Some integer operators like
|
||||||
.Sq +
|
.Sq +
|
||||||
and
|
and
|
||||||
.Sq -
|
.Sq -
|
||||||
@@ -317,8 +326,9 @@ delim $$
|
|||||||
.EN
|
.EN
|
||||||
.Bl -column -offset indent "ATAN2(x, y)"
|
.Bl -column -offset indent "ATAN2(x, y)"
|
||||||
.It Sy Name Ta Sy Operation
|
.It Sy Name Ta Sy Operation
|
||||||
.It Fn DIV x y Ta $x \[di] y$
|
.It Fn DIV x y Ta Fixed-point division $( x \[di] y ) \[mu] ( 2 ^ precision )$
|
||||||
.It Fn MUL x y Ta $x \[mu] y$
|
.It Fn MUL x y Ta Fixed-point multiplication $( x \[mu] y ) \[di] ( 2 ^ precision )$
|
||||||
|
.It Fn FMOD x y Ta Fixed-point modulo $( x % y ) \[di] ( 2 ^ precision )$
|
||||||
.It Fn POW x y Ta $x$ to the $y$ power
|
.It Fn POW x y Ta $x$ to the $y$ power
|
||||||
.It Fn LOG x y Ta Logarithm of $x$ to the base $y$
|
.It Fn LOG x y Ta Logarithm of $x$ to the base $y$
|
||||||
.It Fn ROUND x Ta Round $x$ to the nearest integer
|
.It Fn ROUND x Ta Round $x$ to the nearest integer
|
||||||
@@ -336,44 +346,53 @@ delim $$
|
|||||||
delim off
|
delim off
|
||||||
.EN
|
.EN
|
||||||
.Pp
|
.Pp
|
||||||
|
All of these fixed-point functions can take an optional final argument, which is the precision to use.
|
||||||
|
For example,
|
||||||
|
.Ql MUL(6.0q8, 7.0q8, 8)
|
||||||
|
will evaluate to
|
||||||
|
.Ql 42.0q8
|
||||||
|
no matter what value is set as the current
|
||||||
|
.Cm Q
|
||||||
|
option.
|
||||||
|
.Pp
|
||||||
The trigonometry functions (
|
The trigonometry functions (
|
||||||
.Ic SIN ,
|
.Ic SIN ,
|
||||||
.Ic COS ,
|
.Ic COS ,
|
||||||
.Ic TAN ,
|
.Ic TAN ,
|
||||||
etc) are defined in terms of a circle divided into 65535.0 degrees.
|
etc) are defined in terms of a circle divided into 1.0 "turns" (equal to 2pi radians or 360 degrees).
|
||||||
.Pp
|
.Pp
|
||||||
These functions are useful for automatic generation of various tables.
|
These functions are useful for automatic generation of various tables.
|
||||||
For example:
|
For example:
|
||||||
.Bd -literal -offset indent
|
.Bd -literal -offset indent
|
||||||
; Generate a 256-byte sine table with values in the range [0, 128]
|
; Generate a table of sine values from sin(0.0) to sin(1.0), with
|
||||||
; (shifted and scaled from the range [-1.0, 1.0])
|
; amplitude scaled from [-1.0, 1.0] to [0.0, 128.0]
|
||||||
ANGLE = 0.0
|
DEF turns = 0.0
|
||||||
REPT 256
|
REPT 256
|
||||||
db (MUL(64.0, SIN(ANGLE)) + 64.0) >> 16
|
db MUL(64.0, SIN(turns) + 1.0) >> 16
|
||||||
ANGLE = ANGLE + 256.0 ; 256.0 = 65536 degrees / 256 entries
|
DEF turns += 1.0 / 256
|
||||||
ENDR
|
ENDR
|
||||||
.Ed
|
.Ed
|
||||||
.Ss String expressions
|
.Ss String expressions
|
||||||
The most basic string expression is any number of characters contained in double quotes
|
The most basic string expression is any number of characters contained in double quotes
|
||||||
.Pq Ql \&"for instance" .
|
.Pq Ql \&"for instance" .
|
||||||
The backslash character
|
The backslash character
|
||||||
.Ql \[rs]
|
.Ql \e
|
||||||
is special in that it causes the character following it to be
|
is special in that it causes the character following it to be
|
||||||
.Dq escaped ,
|
.Dq escaped ,
|
||||||
meaning that it is treated differently from normal.
|
meaning that it is treated differently from normal.
|
||||||
There are a number of escape sequences you can use within a string:
|
There are a number of escape sequences you can use within a string:
|
||||||
.Bl -column -offset indent "Qo \[rs]1 Qc \[en] Qo \[rs]9 Qc"
|
.Bl -column -offset indent "Qo \e1 Qc \[en] Qo \e9 Qc"
|
||||||
.It Sy String Ta Sy Meaning
|
.It Sy String Ta Sy Meaning
|
||||||
.It Ql \[rs]\[rs] Ta Produces a backslash
|
.It Ql \e\e Ta Produces a backslash
|
||||||
.It Ql \[rs]" Ta Produces a double quote without terminating
|
.It Ql \e" Ta Produces a double quote without terminating
|
||||||
.It Ql \[rs]{ Ta Curly bracket left
|
.It Ql \e{ Ta Curly bracket left
|
||||||
.It Ql \[rs]} Ta Curly bracket right
|
.It Ql \e} Ta Curly bracket right
|
||||||
.It Ql \[rs]n Ta Newline ($0A)
|
.It Ql \en Ta Newline ($0A)
|
||||||
.It Ql \[rs]r Ta Carriage return ($0D)
|
.It Ql \er Ta Carriage return ($0D)
|
||||||
.It Ql \[rs]t Ta Tab ($09)
|
.It Ql \et Ta Tab ($09)
|
||||||
.It Qo \[rs]1 Qc \[en] Qo \[rs]9 Qc Ta Macro argument (Only in the body of a macro; see Sx Invoking macros )
|
.It Qo \e1 Qc \[en] Qo \e9 Qc Ta Macro argument (Only in the body of a macro; see Sx Invoking macros )
|
||||||
.It Ql \[rs]# Ta All Dv _NARG No macro arguments, separated by commas (Only in the body of a macro)
|
.It Ql \e# Ta All Dv _NARG No macro arguments, separated by commas (Only in the body of a macro)
|
||||||
.It Ql \[rs]@ Ta Label name suffix (Only in the body of a macro or a Ic REPT No block)
|
.It Ql \e@ Ta Label name suffix (Only in the body of a macro or a Ic REPT No block)
|
||||||
.El
|
.El
|
||||||
(Note that some of those can be used outside of strings, when noted further in this document.)
|
(Note that some of those can be used outside of strings, when noted further in this document.)
|
||||||
.Pp
|
.Pp
|
||||||
@@ -381,9 +400,9 @@ Multi-line strings are contained in triple quotes
|
|||||||
.Pq Ql \&"\&"\&"for instance\&"\&"\&" .
|
.Pq Ql \&"\&"\&"for instance\&"\&"\&" .
|
||||||
Escape sequences work the same way in multi-line strings; however, literal newline
|
Escape sequences work the same way in multi-line strings; however, literal newline
|
||||||
characters will be included as-is, without needing to escape them with
|
characters will be included as-is, without needing to escape them with
|
||||||
.Ql \[rs]r
|
.Ql \er
|
||||||
or
|
or
|
||||||
.Ql \[rs]n .
|
.Ql \en .
|
||||||
.Pp
|
.Pp
|
||||||
The following functions operate on string expressions.
|
The following functions operate on string expressions.
|
||||||
Most of them return a string, however some of these functions actually return an integer and can be used as part of an integer expression!
|
Most of them return a string, however some of these functions actually return an integer and can be used as part of an integer expression!
|
||||||
@@ -462,6 +481,11 @@ is a label, it returns the bank number the label is in.
|
|||||||
The result may be constant if
|
The result may be constant if
|
||||||
.Nm
|
.Nm
|
||||||
is able to compute it.
|
is able to compute it.
|
||||||
|
.It Fn SECTION symbol Ta Returns the name of the section that
|
||||||
|
.Ar symbol
|
||||||
|
is in.
|
||||||
|
.Ar symbol
|
||||||
|
must have been defined already.
|
||||||
.It Fn SIZEOF arg Ta Returns the size of the section named
|
.It Fn SIZEOF arg Ta Returns the size of the section named
|
||||||
.Ar arg .
|
.Ar arg .
|
||||||
The result is not constant, since only RGBLINK can compute its value.
|
The result is not constant, since only RGBLINK can compute its value.
|
||||||
@@ -945,9 +969,9 @@ assuming the section ends up at
|
|||||||
.Ad $80C0 :
|
.Ad $80C0 :
|
||||||
.Bd -literal -offset indent
|
.Bd -literal -offset indent
|
||||||
SECTION "Player tiles", VRAM
|
SECTION "Player tiles", VRAM
|
||||||
PlayerTiles:
|
vPlayerTiles:
|
||||||
ds 6 * 16
|
ds 6 * 16
|
||||||
.end
|
\&.end
|
||||||
.Ed
|
.Ed
|
||||||
.Pp
|
.Pp
|
||||||
A label's location (and thus value) is usually not determined until the linking stage, so labels usually cannot be used as constants.
|
A label's location (and thus value) is usually not determined until the linking stage, so labels usually cannot be used as constants.
|
||||||
@@ -1001,7 +1025,7 @@ DEF ARRAY_SIZE EQU 4
|
|||||||
DEF COUNT = 2
|
DEF COUNT = 2
|
||||||
DEF COUNT = 3
|
DEF COUNT = 3
|
||||||
DEF COUNT = ARRAY_SIZE + COUNT
|
DEF COUNT = ARRAY_SIZE + COUNT
|
||||||
COUNT = COUNT*2
|
DEF COUNT *= 2
|
||||||
;\ COUNT now has the value 14
|
;\ COUNT now has the value 14
|
||||||
.Ed
|
.Ed
|
||||||
.Pp
|
.Pp
|
||||||
@@ -1054,7 +1078,7 @@ This can be used, for example, to update a constant using a macro, without makin
|
|||||||
def NUM_ITEMS equ 0
|
def NUM_ITEMS equ 0
|
||||||
MACRO add_item
|
MACRO add_item
|
||||||
redef NUM_ITEMS equ NUM_ITEMS + 1
|
redef NUM_ITEMS equ NUM_ITEMS + 1
|
||||||
def ITEM_{02x:NUM_ITEMS} equ \[rs]1
|
def ITEM_{02x:NUM_ITEMS} equ \e1
|
||||||
ENDM
|
ENDM
|
||||||
add_item 1
|
add_item 1
|
||||||
add_item 4
|
add_item 4
|
||||||
@@ -1119,7 +1143,7 @@ will not expand string constants in their names.
|
|||||||
DEF COUNTREG EQUS "[hl+]"
|
DEF COUNTREG EQUS "[hl+]"
|
||||||
ld a,COUNTREG
|
ld a,COUNTREG
|
||||||
|
|
||||||
DEF PLAYER_NAME EQUS "\[rs]"John\[rs]""
|
DEF PLAYER_NAME EQUS "\e"John\e""
|
||||||
db PLAYER_NAME
|
db PLAYER_NAME
|
||||||
.Ed
|
.Ed
|
||||||
.Pp
|
.Pp
|
||||||
@@ -1131,7 +1155,7 @@ This will be interpreted as:
|
|||||||
.Pp
|
.Pp
|
||||||
String constants can also be used to define small one-line macros:
|
String constants can also be used to define small one-line macros:
|
||||||
.Bd -literal -offset indent
|
.Bd -literal -offset indent
|
||||||
DEF pusha EQUS "push af\[rs]npush bc\[rs]npush de\[rs]npush hl\[rs]n"
|
DEF pusha EQUS "push af\enpush bc\enpush de\enpush hl\en"
|
||||||
.Ed
|
.Ed
|
||||||
.Pp
|
.Pp
|
||||||
Note that colons
|
Note that colons
|
||||||
@@ -1213,14 +1237,16 @@ The example above defines
|
|||||||
.Ql MyMacro
|
.Ql MyMacro
|
||||||
as a new macro.
|
as a new macro.
|
||||||
String constants are not expanded within the name of the macro.
|
String constants are not expanded within the name of the macro.
|
||||||
You may use the older syntax
|
.Pp
|
||||||
|
(Using the
|
||||||
|
.Em deprecated
|
||||||
|
older syntax
|
||||||
.Ql MyMacro: MACRO
|
.Ql MyMacro: MACRO
|
||||||
instead of
|
instead of
|
||||||
.Ql MACRO MyMacro ,
|
.Ql MACRO MyMacro ,
|
||||||
with a single colon
|
with a single colon
|
||||||
.Ql \&:
|
.Ql \&:
|
||||||
following the macro's name.
|
following the macro's name, string constants may be expanded for the name.)
|
||||||
With the older syntax, string constants may be expanded for the name.
|
|
||||||
.Pp
|
.Pp
|
||||||
Macros can't be exported or imported.
|
Macros can't be exported or imported.
|
||||||
.Pp
|
.Pp
|
||||||
@@ -1238,18 +1264,18 @@ ENDM
|
|||||||
But this will:
|
But this will:
|
||||||
.Bd -literal -offset indent
|
.Bd -literal -offset indent
|
||||||
MACRO outer
|
MACRO outer
|
||||||
DEF definition EQUS "MACRO inner\[rs]nPRINTLN \[rs]"Hello!\[rs]"\[rs]nENDM"
|
DEF definition EQUS "MACRO inner\enPRINTLN \e"Hello!\e"\enENDM"
|
||||||
definition
|
definition
|
||||||
PURGE definition
|
PURGE definition
|
||||||
ENDM
|
ENDM
|
||||||
.Ed
|
.Ed
|
||||||
.Pp
|
.Pp
|
||||||
Macro arguments support all the escape sequences of strings, as well as
|
Macro arguments support all the escape sequences of strings, as well as
|
||||||
.Ql \[rs],
|
.Ql \e,
|
||||||
to escape commas, as well as
|
to escape commas, as well as
|
||||||
.Ql \[rs](
|
.Ql \e(
|
||||||
and
|
and
|
||||||
.Ql \[rs])
|
.Ql \e)
|
||||||
to escape parentheses, since those otherwise separate and enclose arguments, respectively.
|
to escape parentheses, since those otherwise separate and enclose arguments, respectively.
|
||||||
.Ss Exporting and importing symbols
|
.Ss Exporting and importing symbols
|
||||||
Importing and exporting of symbols is a feature that is very useful when your project spans many source files and, for example, you need to jump to a routine defined in another file.
|
Importing and exporting of symbols is a feature that is very useful when your project spans many source files and, for example, you need to jump to a routine defined in another file.
|
||||||
@@ -1310,7 +1336,7 @@ Note also that only exported symbols will appear in symbol and map files produce
|
|||||||
.Ss Purging symbols
|
.Ss Purging symbols
|
||||||
.Ic PURGE
|
.Ic PURGE
|
||||||
allows you to completely remove a symbol from the symbol table as if it had never existed.
|
allows you to completely remove a symbol from the symbol table as if it had never existed.
|
||||||
.Em USE WITH EXTREME CAUTION!!!
|
.Em USE WITH EXTREME CAUTION!
|
||||||
I can't stress this enough,
|
I can't stress this enough,
|
||||||
.Sy you seriously need to know what you are doing .
|
.Sy you seriously need to know what you are doing .
|
||||||
DON'T purge a symbol that you use in expressions the linker needs to calculate.
|
DON'T purge a symbol that you use in expressions the linker needs to calculate.
|
||||||
@@ -1324,13 +1350,11 @@ DEF AOLer EQUS "Me too"
|
|||||||
String constants are not expanded within the symbol names.
|
String constants are not expanded within the symbol names.
|
||||||
.Ss Predeclared symbols
|
.Ss Predeclared symbols
|
||||||
The following symbols are defined by the assembler:
|
The following symbols are defined by the assembler:
|
||||||
.Bl -column -offset indent "EQUS" "__ISO_8601_LOCAL__"
|
.Bl -column -offset indent "__ISO_8601_LOCAL__" "EQUS"
|
||||||
.It Sy Name Ta Sy Type Ta Sy Contents
|
.It Sy Name Ta Sy Type Ta Sy Contents
|
||||||
.It Dv @ Ta Ic EQU Ta PC value (essentially, the current memory address)
|
.It Dv @ Ta Ic EQU Ta PC value (essentially, the current memory address)
|
||||||
.It Dv _RS Ta Ic = Ta _RS Counter
|
.It Dv _RS Ta Ic = Ta _RS Counter
|
||||||
.It Dv _NARG Ta Ic EQU Ta Number of arguments passed to macro, updated by Ic SHIFT
|
.It Dv _NARG Ta Ic EQU Ta Number of arguments passed to macro, updated by Ic SHIFT
|
||||||
.It Dv __LINE__ Ta Ic EQU Ta The current line number
|
|
||||||
.It Dv __FILE__ Ta Ic EQUS Ta The current filename
|
|
||||||
.It Dv __DATE__ Ta Ic EQUS Ta Today's date
|
.It Dv __DATE__ Ta Ic EQUS Ta Today's date
|
||||||
.It Dv __TIME__ Ta Ic EQUS Ta The current time
|
.It Dv __TIME__ Ta Ic EQUS Ta The current time
|
||||||
.It Dv __ISO_8601_LOCAL__ Ta Ic EQUS Ta ISO 8601 timestamp (local)
|
.It Dv __ISO_8601_LOCAL__ Ta Ic EQUS Ta ISO 8601 timestamp (local)
|
||||||
@@ -1547,19 +1571,19 @@ ENDM
|
|||||||
.Pp
|
.Pp
|
||||||
This is fine, but only if you use the macro no more than once per scope.
|
This is fine, but only if you use the macro no more than once per scope.
|
||||||
To get around this problem, there is the escape sequence
|
To get around this problem, there is the escape sequence
|
||||||
.Ic \[rs]@
|
.Ic \e@
|
||||||
that expands to a unique string.
|
that expands to a unique string.
|
||||||
.Pp
|
.Pp
|
||||||
.Ic \[rs]@
|
.Ic \e@
|
||||||
also works in
|
also works in
|
||||||
.Ic REPT
|
.Ic REPT
|
||||||
blocks.
|
blocks.
|
||||||
.Bd -literal -offset indent
|
.Bd -literal -offset indent
|
||||||
MACRO LoopyMacro
|
MACRO LoopyMacro
|
||||||
xor a,a
|
xor a,a
|
||||||
\&.loop\[rs]@ ld [hl+],a
|
\&.loop\e@ ld [hl+],a
|
||||||
dec c
|
dec c
|
||||||
jr nz,.loop\[rs]@
|
jr nz,.loop\e@
|
||||||
ENDM
|
ENDM
|
||||||
.Ed
|
.Ed
|
||||||
.Pp
|
.Pp
|
||||||
@@ -1579,18 +1603,18 @@ which references the same macro, which has the same problem.
|
|||||||
.Pp
|
.Pp
|
||||||
It's possible to pass arguments to macros as well!
|
It's possible to pass arguments to macros as well!
|
||||||
You retrieve the arguments by using the escape sequences
|
You retrieve the arguments by using the escape sequences
|
||||||
.Ic \[rs]1
|
.Ic \e1
|
||||||
through
|
through
|
||||||
.Ic \[rs]9 , \[rs]1
|
.Ic \e9 , \e1
|
||||||
being the first argument specified on the macro invocation.
|
being the first argument specified on the macro invocation.
|
||||||
.Bd -literal -offset indent
|
.Bd -literal -offset indent
|
||||||
MACRO LoopyMacro
|
MACRO LoopyMacro
|
||||||
ld hl,\[rs]1
|
ld hl,\e1
|
||||||
ld c,\[rs]2
|
ld c,\e2
|
||||||
xor a,a
|
xor a,a
|
||||||
\&.loop\[rs]@ ld [hl+],a
|
\&.loop\e@ ld [hl+],a
|
||||||
dec c
|
dec c
|
||||||
jr nz,.loop\[rs]@
|
jr nz,.loop\e@
|
||||||
ENDM
|
ENDM
|
||||||
.Ed
|
.Ed
|
||||||
.Pp
|
.Pp
|
||||||
@@ -1603,14 +1627,14 @@ LoopyMacro MyVars,54
|
|||||||
Arguments are passed as string constants, although there's no need to enclose them in quotes.
|
Arguments are passed as string constants, although there's no need to enclose them in quotes.
|
||||||
Thus, an expression will not be evaluated first but kind of copy-pasted.
|
Thus, an expression will not be evaluated first but kind of copy-pasted.
|
||||||
This means that it's probably a very good idea to use brackets around
|
This means that it's probably a very good idea to use brackets around
|
||||||
.Ic \[rs]1
|
.Ic \e1
|
||||||
to
|
to
|
||||||
.Ic \[rs]9
|
.Ic \e9
|
||||||
if you perform further calculations on them.
|
if you perform further calculations on them.
|
||||||
For instance, consider the following:
|
For instance, consider the following:
|
||||||
.Bd -literal -offset indent
|
.Bd -literal -offset indent
|
||||||
MACRO print_double
|
MACRO print_double
|
||||||
PRINTLN \[rs]1 * 2
|
PRINTLN \e1 * 2
|
||||||
ENDM
|
ENDM
|
||||||
print_double 1 + 2
|
print_double 1 + 2
|
||||||
.Ed
|
.Ed
|
||||||
@@ -1625,15 +1649,15 @@ Line continuations work as usual inside macros or lists of macro arguments.
|
|||||||
However, some characters need to be escaped, as in the following example:
|
However, some characters need to be escaped, as in the following example:
|
||||||
.Bd -literal -offset indent
|
.Bd -literal -offset indent
|
||||||
MACRO PrintMacro1
|
MACRO PrintMacro1
|
||||||
PRINTLN STRCAT(\[rs]1)
|
PRINTLN STRCAT(\e1)
|
||||||
ENDM
|
ENDM
|
||||||
PrintMacro1 "Hello "\[rs], \[rs]
|
PrintMacro1 "Hello "\e, \e
|
||||||
"world"
|
"world"
|
||||||
MACRO PrintMacro2
|
MACRO PrintMacro2
|
||||||
PRINT \[rs]1
|
PRINT \e1
|
||||||
ENDM
|
ENDM
|
||||||
PrintMacro2 STRCAT("Hello ", \[rs]
|
PrintMacro2 STRCAT("Hello ", \e
|
||||||
"world\[rs]n")
|
"world\en")
|
||||||
.Ed
|
.Ed
|
||||||
.Pp
|
.Pp
|
||||||
The comma in
|
The comma in
|
||||||
@@ -1643,34 +1667,34 @@ The comma in
|
|||||||
.Ql PrintMacro2
|
.Ql PrintMacro2
|
||||||
does not need escaping because it is inside parentheses, similar to macro arguments in C.
|
does not need escaping because it is inside parentheses, similar to macro arguments in C.
|
||||||
The backslash in
|
The backslash in
|
||||||
.Ql \[rs]n
|
.Ql \en
|
||||||
also does not need escaping because string literals work as usual inside macro arguments.
|
also does not need escaping because string literals work as usual inside macro arguments.
|
||||||
.Pp
|
.Pp
|
||||||
Since there are only nine digits, you can only access the first nine macro arguments like this.
|
Since there are only nine digits, you can only access the first nine macro arguments like this.
|
||||||
To use the rest, you need to put the multi-digit argument number in angle brackets, like
|
To use the rest, you need to put the multi-digit argument number in angle brackets, like
|
||||||
.Ql \[rs]<10> .
|
.Ql \e<10> .
|
||||||
This bracketed syntax supports decimal numbers and numeric constant symbols.
|
This bracketed syntax supports decimal numbers and numeric constant symbols.
|
||||||
For example,
|
For example,
|
||||||
.Ql \[rs]<_NARG>
|
.Ql \e<_NARG>
|
||||||
will get the last argument.
|
will get the last argument.
|
||||||
.Pp
|
.Pp
|
||||||
Other macro arguments and symbol interpolations will be expanded inside the angle brackets.
|
Other macro arguments and symbol interpolations will be expanded inside the angle brackets.
|
||||||
For example, if
|
For example, if
|
||||||
.Ql \[rs]1
|
.Ql \e1
|
||||||
is
|
is
|
||||||
.Ql 13 ,
|
.Ql 13 ,
|
||||||
then
|
then
|
||||||
.Ql \[rs]<\[rs]1>
|
.Ql \e<\e1>
|
||||||
will expand to
|
will expand to
|
||||||
.Ql \[rs]<13> .
|
.Ql \e<13> .
|
||||||
Or if
|
Or if
|
||||||
.Ql v10 = 42
|
.Ql v10 = 42
|
||||||
and
|
and
|
||||||
.Ql x = 10 ,
|
.Ql x = 10 ,
|
||||||
then
|
then
|
||||||
.Ql \[rs]<v{d:x}>
|
.Ql \e<v{d:x}>
|
||||||
will expand to
|
will expand to
|
||||||
.Ql \[rs]<42> .
|
.Ql \e<42> .
|
||||||
.Pp
|
.Pp
|
||||||
Another way to access more than nine macro arguments is the
|
Another way to access more than nine macro arguments is the
|
||||||
.Ic SHIFT
|
.Ic SHIFT
|
||||||
@@ -1678,11 +1702,11 @@ command, a special command only available in macros.
|
|||||||
It will shift the arguments by one to the left, and decrease
|
It will shift the arguments by one to the left, and decrease
|
||||||
.Dv _NARG
|
.Dv _NARG
|
||||||
by 1.
|
by 1.
|
||||||
.Ic \[rs]1
|
.Ic \e1
|
||||||
will get the value of
|
will get the value of
|
||||||
.Ic \[rs]2 , \[rs]2
|
.Ic \e2 , \e2
|
||||||
will get the value of
|
will get the value of
|
||||||
.Ic \[rs]3 ,
|
.Ic \e3 ,
|
||||||
and so forth.
|
and so forth.
|
||||||
.Pp
|
.Pp
|
||||||
.Ic SHIFT
|
.Ic SHIFT
|
||||||
@@ -1701,9 +1725,9 @@ and
|
|||||||
commands print text and values to the standard output.
|
commands print text and values to the standard output.
|
||||||
Useful for debugging macros, or wherever you may feel the need to tell yourself some important information.
|
Useful for debugging macros, or wherever you may feel the need to tell yourself some important information.
|
||||||
.Bd -literal -offset indent
|
.Bd -literal -offset indent
|
||||||
PRINT "Hello world!\[rs]n"
|
PRINT "Hello world!\en"
|
||||||
PRINTLN "Hello world!"
|
PRINTLN "Hello world!"
|
||||||
PRINT _NARG, " arguments\[rs]n"
|
PRINT _NARG, " arguments\en"
|
||||||
PRINTLN "sum: ", 2+3, " product: ", 2*3
|
PRINTLN "sum: ", 2+3, " product: ", 2*3
|
||||||
PRINTLN "Line #", __LINE__
|
PRINTLN "Line #", __LINE__
|
||||||
PRINTLN STRFMT("E = %f", 2.718)
|
PRINTLN STRFMT("E = %f", 2.718)
|
||||||
@@ -1717,7 +1741,7 @@ For different formats, use
|
|||||||
.Ic STRFMT .
|
.Ic STRFMT .
|
||||||
.It Ic PRINTLN
|
.It Ic PRINTLN
|
||||||
prints out each of its comma-separated arguments, if any, followed by a line feed
|
prints out each of its comma-separated arguments, if any, followed by a line feed
|
||||||
.Pq Ql \[rs]n .
|
.Pq Ql \en .
|
||||||
.El
|
.El
|
||||||
.Ss Automatically repeating blocks of code
|
.Ss Automatically repeating blocks of code
|
||||||
Suppose you want to unroll a time consuming loop without copy-pasting it.
|
Suppose you want to unroll a time consuming loop without copy-pasting it.
|
||||||
@@ -1741,17 +1765,16 @@ You can also use
|
|||||||
.Ic REPT
|
.Ic REPT
|
||||||
to generate tables on the fly:
|
to generate tables on the fly:
|
||||||
.Bd -literal -offset indent
|
.Bd -literal -offset indent
|
||||||
; Generate a 256-byte sine table with values in the range [0, 128]
|
; Generate a table of square values from 0**2 = 0 to 100**2 = 10000
|
||||||
; (shifted and scaled from the range [-1.0, 1.0])
|
DEF x = 0
|
||||||
ANGLE = 0.0
|
REPT 101
|
||||||
REPT 256
|
dw x * x
|
||||||
db (MUL(64.0, SIN(ANGLE)) + 64.0) >> 16
|
DEF x += 1
|
||||||
ANGLE = ANGLE + 256.0 ; 256.0 = 65536 degrees / 256 entries
|
ENDR
|
||||||
ENDR
|
|
||||||
.Ed
|
.Ed
|
||||||
.Pp
|
.Pp
|
||||||
As in macros, you can also use the escape sequence
|
As in macros, you can also use the escape sequence
|
||||||
.Ic \[rs]@ .
|
.Ic \e@ .
|
||||||
.Ic REPT
|
.Ic REPT
|
||||||
blocks can be nested.
|
blocks can be nested.
|
||||||
.Pp
|
.Pp
|
||||||
@@ -1804,11 +1827,29 @@ The
|
|||||||
value will be updated by
|
value will be updated by
|
||||||
.Ar step
|
.Ar step
|
||||||
until it reaches or exceeds
|
until it reaches or exceeds
|
||||||
.Ar stop .
|
.Ar stop ,
|
||||||
|
i.e. it covers the half-open range from
|
||||||
|
.Ar start
|
||||||
|
(inclusive) to
|
||||||
|
.Ar stop
|
||||||
|
(exclusive).
|
||||||
|
The variable
|
||||||
|
.Ar V
|
||||||
|
will be assigned this value at the beginning of each new iteration; any changes made to it within the
|
||||||
|
.Ic FOR
|
||||||
|
loop's body will be overwritten.
|
||||||
|
So the symbol
|
||||||
|
.Ar V
|
||||||
|
need not be already defined before any iterations of the
|
||||||
|
.Ic FOR
|
||||||
|
loop, but it must be a variable
|
||||||
|
.Pq Sx Variables
|
||||||
|
if so.
|
||||||
For example:
|
For example:
|
||||||
.Bd -literal -offset indent
|
.Bd -literal -offset indent
|
||||||
FOR V, 4, 25, 5
|
FOR V, 4, 25, 5
|
||||||
PRINT "{d:V} "
|
PRINT "{d:V} "
|
||||||
|
DEF V *= 2
|
||||||
ENDR
|
ENDR
|
||||||
PRINTLN "done {d:V}"
|
PRINTLN "done {d:V}"
|
||||||
.Ed
|
.Ed
|
||||||
@@ -1821,7 +1862,7 @@ This will print:
|
|||||||
Just like with
|
Just like with
|
||||||
.Ic REPT
|
.Ic REPT
|
||||||
blocks, you can use the escape sequence
|
blocks, you can use the escape sequence
|
||||||
.Ic \[rs]@
|
.Ic \e@
|
||||||
inside of
|
inside of
|
||||||
.Ic FOR
|
.Ic FOR
|
||||||
blocks, and they can be nested.
|
blocks, and they can be nested.
|
||||||
@@ -1942,6 +1983,13 @@ calls infinitely (or until you run out of memory, whichever comes first).
|
|||||||
.Bd -literal -offset indent
|
.Bd -literal -offset indent
|
||||||
INCLUDE "irq.inc"
|
INCLUDE "irq.inc"
|
||||||
.Ed
|
.Ed
|
||||||
|
.Pp
|
||||||
|
You may also implicitly
|
||||||
|
.Ic INCLUDE
|
||||||
|
a file before the source file with the
|
||||||
|
.Fl P
|
||||||
|
option of
|
||||||
|
.Xr rgbasm 1 .
|
||||||
.Ss Conditional assembling
|
.Ss Conditional assembling
|
||||||
The four commands
|
The four commands
|
||||||
.Ic IF , ELIF , ELSE ,
|
.Ic IF , ELIF , ELSE ,
|
||||||
@@ -2013,17 +2061,15 @@ POPO
|
|||||||
The options that
|
The options that
|
||||||
.Ic OPT
|
.Ic OPT
|
||||||
can modify are currently:
|
can modify are currently:
|
||||||
.Cm b , g , p , r , h , L ,
|
.Cm b , g , p , Q , r , h , L ,
|
||||||
and
|
and
|
||||||
.Cm W .
|
.Cm W .
|
||||||
The Boolean flag options
|
The Boolean flag options
|
||||||
.Cm h
|
.Cm H , h , L ,
|
||||||
and
|
and
|
||||||
.Cm L
|
.Cm l
|
||||||
can be negated as
|
can be negated like
|
||||||
.Ql OPT !h
|
.Ql OPT !H
|
||||||
and
|
|
||||||
.Ql OPT !L
|
|
||||||
to act like omitting them from the command-line.
|
to act like omitting them from the command-line.
|
||||||
.Pp
|
.Pp
|
||||||
.Ic POPO
|
.Ic POPO
|
||||||
|
|||||||
602
man/rgbds.5
602
man/rgbds.5
@@ -16,251 +16,381 @@ This is the description of the object files used by
|
|||||||
.Xr rgbasm 1
|
.Xr rgbasm 1
|
||||||
and
|
and
|
||||||
.Xr rgblink 1 .
|
.Xr rgblink 1 .
|
||||||
.Em Please note that the specifications may change .
|
.Em Please note that the specification is not stable yet.
|
||||||
This toolchain is in development and new features may require adding more information to the current format, or modifying some fields, which would break compatibility with older versions.
|
RGBDS is still in active development, and some new features require adding more information to the object file, or modifying some fields, both of which break compatibility with older versions.
|
||||||
.Sh FILE STRUCTURE
|
.Sh FILE STRUCTURE
|
||||||
The following types are used:
|
The following types are used:
|
||||||
.Pp
|
.Pp
|
||||||
.Ar LONG
|
.Cm LONG
|
||||||
is a 32-bit integer stored in little-endian format.
|
is a 32-bit integer stored in little-endian format.
|
||||||
.Ar BYTE
|
.Cm BYTE
|
||||||
is an 8-bit integer.
|
is an 8-bit integer.
|
||||||
.Ar STRING
|
.Cm STRING
|
||||||
is a 0-terminated string of
|
is a 0-terminated string of
|
||||||
.Ar BYTE .
|
.Cm BYTE .
|
||||||
.Bd -literal
|
Brackets after a type
|
||||||
; Header
|
.Pq e.g. Cm LONG Ns Bq Ar n
|
||||||
|
indicate
|
||||||
BYTE ID[4] ; "RGB9"
|
.Ar n
|
||||||
LONG RevisionNumber ; The format's revision number this file uses.
|
consecutive elements
|
||||||
LONG NumberOfSymbols ; The number of symbols used in this file.
|
.Pq here, Cm LONG Ns s .
|
||||||
LONG NumberOfSections ; The number of sections used in this file.
|
All items are contiguous, with no padding anywhere\(emthis also means that they may not be aligned in the file!
|
||||||
|
.Pp
|
||||||
; File info
|
.Cm REPT Ar n
|
||||||
|
indicates that the fields between the
|
||||||
LONG NumberOfNodes ; The number of nodes contained in this file.
|
.Cm REPT
|
||||||
|
and corresponding
|
||||||
REPT NumberOfNodes ; IMPORTANT NOTE: the nodes are actually written in
|
.Cm ENDR
|
||||||
; **reverse** order, meaning the node with ID 0 is
|
are repeated
|
||||||
; the last one in the file!
|
.Ar n
|
||||||
|
times.
|
||||||
LONG ParentID ; ID of the parent node, -1 means this is the root.
|
.Pp
|
||||||
|
All IDs refer to objects within the file; for example, symbol ID $0001 refers to the second symbol defined in
|
||||||
LONG ParentLineNo ; Line at which the parent context was exited.
|
.Em this
|
||||||
; Meaningless on the root node.
|
object file's
|
||||||
|
.Sx Symbols
|
||||||
BYTE Type ; 0 = REPT node
|
array.
|
||||||
; 1 = File node
|
The only exception is the
|
||||||
; 2 = Macro node
|
.Sx Source file info
|
||||||
|
nodes, whose IDs are backwards, i.e. source node ID $0000 refers to the
|
||||||
IF Type != 0 ; If the node is not a REPT...
|
.Em last
|
||||||
|
node in the array, not the first one.
|
||||||
STRING Name ; The node's name: either a file name, or macro name
|
References to other object files are made by imports (symbols), by name (sections), etc.\(embut never by ID.
|
||||||
; prefixed by its definition file name.
|
.Ss Header
|
||||||
|
.Bl -tag -width Ds -compact
|
||||||
ELSE ; If the node is a REPT, it also contains the iter
|
.It Cm BYTE Ar Magic[4]
|
||||||
; counts of all the parent REPTs.
|
"RGB9"
|
||||||
|
.It Cm LONG Ar RevisionNumber
|
||||||
LONG Depth ; Size of the array below.
|
The format's revision number this file uses.
|
||||||
|
.Pq This is always in the same place in all revisions.
|
||||||
LONG Iter[Depth] ; The number of REPT iterations by increasing depth.
|
.It Cm LONG Ar NumberOfSymbols
|
||||||
|
How many symbols are defined in this object file.
|
||||||
ENDC
|
.It Cm LONG Ar NumberOfSections
|
||||||
|
How many sections are defined in this object file.
|
||||||
ENDR
|
.El
|
||||||
|
.Ss Source file info
|
||||||
; Symbols
|
.Bl -tag -width Ds -compact
|
||||||
|
.It Cm LONG Ar NumberOfNodes
|
||||||
REPT NumberOfSymbols ; Number of symbols defined in this object file.
|
The number of source context nodes contained in this file.
|
||||||
|
.It Cm REPT Ar NumberOfNodes
|
||||||
STRING Name ; The name of this symbol. Local symbols are stored
|
.Bl -tag -width Ds -compact
|
||||||
; as "Scope.Symbol".
|
.It Cm LONG Ar ParentID
|
||||||
|
ID of the parent node, -1 meaning that this is the root node.
|
||||||
BYTE Type ; 0 = LOCAL symbol only used in this file.
|
.Pp
|
||||||
; 1 = IMPORT this symbol from elsewhere
|
.Sy Important :
|
||||||
; 2 = EXPORT this symbol to other objects.
|
the nodes are actually written in
|
||||||
|
.Sy reverse
|
||||||
IF (Type & 0x7F) != 1 ; If symbol is defined in this object file.
|
order, meaning the node with ID 0 is the last one in the list!
|
||||||
|
.It Cm LONG Ar ParentLineNo
|
||||||
LONG SourceFile ; File where the symbol is defined.
|
Line at which the parent node's context was exited; meaningless for the root node.
|
||||||
|
.It Cm BYTE Ar Type
|
||||||
LONG LineNum ; Line number in the file where the symbol is defined.
|
.Bl -column "Value" -compact
|
||||||
|
.It Sy Value Ta Sy Meaning
|
||||||
LONG SectionID ; The section number (of this object file) in which
|
.It 0 Ta REPT node
|
||||||
; this symbol is defined. If it doesn't belong to any
|
.It 1 Ta File node
|
||||||
; specific section (like a constant), this field has
|
.It 2 Ta Macro node
|
||||||
; the value -1.
|
.El
|
||||||
|
.It Cm IF Ar Type No \(!= 0
|
||||||
LONG Value ; The symbols value. It's the offset into that
|
If the node is not a REPT node...
|
||||||
; symbol's section.
|
.Pp
|
||||||
|
.Bl -tag -width Ds -compact
|
||||||
ENDC
|
.It Cm STRING Ar Name
|
||||||
|
The node's name: either a file name, or the macro's name prefixes by its definition's file name
|
||||||
ENDR
|
.Pq e.g. Ql src/includes/defines.asm::error .
|
||||||
|
.El
|
||||||
; Sections
|
.It Cm ELSE
|
||||||
|
If the node is a REPT, it also contains the iteration counter of all parent REPTs.
|
||||||
REPT NumberOfSections
|
.Pp
|
||||||
STRING Name ; Name of the section
|
.Bl -tag -width Ds -compact
|
||||||
|
.It Cm LONG Ar Depth
|
||||||
LONG Size ; Size in bytes of this section
|
.It Cm LONG Ar Iter Ns Bq Ar Depth
|
||||||
|
The number of REPT iterations, by increasing depth.
|
||||||
BYTE Type ; 0 = WRAM0
|
.El
|
||||||
; 1 = VRAM
|
.It Cm ENDC
|
||||||
; 2 = ROMX
|
.El
|
||||||
; 3 = ROM0
|
.It Cm ENDR
|
||||||
; 4 = HRAM
|
.El
|
||||||
; 5 = WRAMX
|
.Ss Symbols
|
||||||
; 6 = SRAM
|
.Bl -tag -width Ds -compact
|
||||||
; 7 = OAM
|
.It Cm REPT Ar NumberOfSymbols
|
||||||
; Bits 7 and 6 are independent from the above value:
|
.Bl -tag -width Ds -compact
|
||||||
; Bit 7 encodes whether the section is unionized
|
.It Cm STRING Ar Name
|
||||||
; Bit 6 encodes whether the section is a fragment
|
This symbol's name.
|
||||||
; Bits 6 and 7 may not be both set at the same time!
|
Local symbols are stored as their full name
|
||||||
|
.Pq Ql Scope.symbol .
|
||||||
LONG Org ; Address to fix this section at. -1 if the linker should
|
.It Cm BYTE Ar Type
|
||||||
; decide (floating address).
|
.Bl -column "Value" -compact
|
||||||
|
.It Sy Value Ta Sy Meaning
|
||||||
LONG Bank ; Bank to load this section into. -1 if the linker should
|
.It 0 Ta Sy Local No symbol only used in this file.
|
||||||
; decide (floating bank). This field is only valid for ROMX,
|
.It 1 Ta Sy Import No of an exported symbol (by name) from another object file.
|
||||||
; VRAM, WRAMX and SRAM sections.
|
.It 2 Ta Sy Exported No symbol visible from other object files.
|
||||||
|
.El
|
||||||
BYTE Align ; Alignment of this section, as N bits. 0 when not specified.
|
.It Cm IF Ar Type No \(!= 1
|
||||||
|
If the symbol is defined in this object file...
|
||||||
LONG Ofs ; Offset relative to the alignment specified above.
|
.Pp
|
||||||
; Must be below 1 << Align.
|
.Bl -tag -width Ds -compact
|
||||||
|
.It Cm LONG Ar NodeID
|
||||||
IF (Type == ROMX) || (Type == ROM0) ; Sections that can contain data.
|
Context in which the symbol was defined.
|
||||||
|
.It Cm LONG Ar LineNo
|
||||||
BYTE Data[Size] ; Raw data of the section.
|
Line number in the context at which the symbol was defined.
|
||||||
|
.It Cm LONG Ar SectionID
|
||||||
LONG NumberOfPatches ; Number of patches to apply.
|
The ID of the section in which the symbol is defined.
|
||||||
|
If the symbol doesn't belong to any specific section (i.e. it's a constant), this field contains -1.
|
||||||
REPT NumberOfPatches
|
.It Cm LONG Ar Value
|
||||||
|
The symbol's value.
|
||||||
LONG SourceFile ; ID of the source file node (for printing
|
If the symbol belongs to a section, this is the offset within that symbol's section.
|
||||||
; error messages).
|
.El
|
||||||
|
.It Cm ENDC
|
||||||
LONG LineNo ; Line at which the patch was created.
|
.El
|
||||||
|
.It Cm ENDR
|
||||||
LONG Offset ; Offset into the section where patch should
|
.El
|
||||||
; be applied (in bytes).
|
.Ss Sections
|
||||||
|
.Bl -tag -width Ds -compact
|
||||||
LONG PCSectionID ; Index within the file of the section in which
|
.It Cm REPT Ar NumberOfSections
|
||||||
; PC is located.
|
.Bl -tag -width Ds -compact
|
||||||
; This is usually the same section that the
|
.It Cm STRING Ar Name
|
||||||
; patch should be applied into, except e.g.
|
The section's name.
|
||||||
; with LOAD blocks.
|
.It Cm LONG Ar Size
|
||||||
|
The section's size, in bytes.
|
||||||
LONG PCOffset ; PC's offset into the above section.
|
.It Cm BYTE Ar Type
|
||||||
; Used because the section may be floating, so
|
Bits 0\(en2 indicate the section's type:
|
||||||
; PC's value is not known to RGBASM.
|
.Bl -column "Value" -compact
|
||||||
|
.It Sy Value Ta Sy Meaning
|
||||||
BYTE Type ; 0 = BYTE patch.
|
.It 0 Ta WRAM0
|
||||||
; 1 = little endian WORD patch.
|
.It 1 Ta VRAM
|
||||||
; 2 = little endian LONG patch.
|
.It 2 Ta ROMX
|
||||||
; 3 = JR offset value BYTE patch.
|
.It 3 Ta ROM0
|
||||||
|
.It 4 Ta HRAM
|
||||||
LONG RPNSize ; Size of the buffer with the RPN.
|
.It 5 Ta WRAMX
|
||||||
; expression.
|
.It 6 Ta SRAM
|
||||||
|
.It 7 Ta OAM
|
||||||
BYTE RPN[RPNSize] ; RPN expression. Definition below.
|
.El
|
||||||
|
.Pp
|
||||||
ENDR
|
Bit\ 7 being set means that the section is a "union"
|
||||||
|
.Pq see Do Unionized sections Dc in Xr rgbasm 5 .
|
||||||
ENDC
|
Bit\ 6 being set means that the section is a "fragment"
|
||||||
|
.Pq see Do Section fragments Dc in Xr rgbasm 5 .
|
||||||
ENDR
|
These two bits are mutually exclusive.
|
||||||
|
.It Cm LONG Ar Address
|
||||||
; Assertions
|
Address this section must be placed at.
|
||||||
|
This must either be valid for the section's
|
||||||
LONG NumberOfAssertions
|
.Ar Type
|
||||||
|
(as affected by flags like
|
||||||
REPT NumberOfAssertions
|
.Fl t
|
||||||
|
or
|
||||||
LONG SourceFile ; ID of the source file node (for printing the failure).
|
.Fl d
|
||||||
|
in
|
||||||
LONG LineNo ; Line at which the assertion was created.
|
.Xr rgblink 1 ) ,
|
||||||
|
or -1 to indicate that the linker should automatically decide
|
||||||
LONG Offset ; Offset into the section where the assertion is located.
|
.Pq the section is Dq floating .
|
||||||
|
.It Cm LONG Ar Bank
|
||||||
LONG SectionID ; Index within the file of the section in which PC is
|
ID of the bank this section must be placed in.
|
||||||
; located, or -1 if defined outside a section.
|
This must either be valid for the section's
|
||||||
|
.Ar Type
|
||||||
LONG PCOffset ; PC's offset into the above section.
|
(with the same caveats as for the
|
||||||
; Used because the section may be floating, so PC's value
|
.Ar Address ) ,
|
||||||
; is not known to RGBASM.
|
or -1 to indicate that the linker should automatically decide.
|
||||||
|
.It Cm BYTE Ar Alignment
|
||||||
BYTE Type ; 0 = Prints the message but allows linking to continue
|
How many bits of the section's address should be equal to
|
||||||
; 1 = Prints the message and evaluates other assertions,
|
.Ar AlignOfs ,
|
||||||
; but linking fails afterwards
|
starting from the least-significant bit.
|
||||||
; 2 = Prints the message and immediately fails linking
|
.It Cm LONG Ar AlignOfs
|
||||||
|
Alignment offset.
|
||||||
LONG RPNSize ; Size of the RPN expression's buffer.
|
Must be strictly less than
|
||||||
|
.Ql 1 << Ar Alignment .
|
||||||
BYTE RPN[RPNSize] ; RPN expression, same as patches. Assert fails if == 0.
|
.It Cm IF Ar Type No \(eq 2 || Ar Type No \(eq 3
|
||||||
|
If the section has ROM type, it contains data.
|
||||||
STRING Message ; A message displayed when the assert fails. If set to
|
.Pp
|
||||||
; the empty string, a generic message is printed instead.
|
.Bl -tag -width Ds -compact
|
||||||
|
.It Cm BYTE Ar Data Ns Bq Size
|
||||||
ENDR
|
The section's raw data.
|
||||||
.Ed
|
Bytes that will be patched over must be present, even though their contents will be overwritten.
|
||||||
.Ss RPN DATA
|
.It Cm LONG Ar NumberOfPatches
|
||||||
Expressions in the object file are stored as RPN.
|
How many patches must be applied to this section's
|
||||||
This is an expression of the form
|
.Ar Data .
|
||||||
.Dq 2 5 + .
|
.It Cm REPT Ar NumberOfPatches
|
||||||
This will first push the value
|
.Bl -tag -width Ds -compact
|
||||||
.Do 2 Dc to the stack, then
|
.It Cm LONG Ar NodeID
|
||||||
|
Context in which the patch was defined.
|
||||||
|
.It Cm LONG Ar LineNo
|
||||||
|
Line number in the context at which the patch was defined.
|
||||||
|
.It Cm LONG Ar Offset
|
||||||
|
Offset within the section's
|
||||||
|
.Ar Data
|
||||||
|
at which the patch should be applied.
|
||||||
|
Must not be greater than the section's
|
||||||
|
.Ar Size
|
||||||
|
minus the patch's size
|
||||||
|
.Pq see Ar Type No below .
|
||||||
|
.It Cm LONG Ar PCSectionID
|
||||||
|
ID of the section in which PC is located.
|
||||||
|
(This is usually the same section within which the patch is applied, except for e.g.\&
|
||||||
|
.Ql LOAD
|
||||||
|
blocks, see
|
||||||
|
.Do RAM code Dc in Xr rgbasm 5 . )
|
||||||
|
.It Cm LONG Ar PCOffset
|
||||||
|
Offset of the PC symbol within the section designated by
|
||||||
|
.Ar PCSectionID .
|
||||||
|
It is expected that PC points to the instruction's first byte for instruction operands (i.e.\&
|
||||||
|
.Ql jp @
|
||||||
|
must be an infinite loop), and to the patch's first byte otherwise
|
||||||
|
.Ql ( db ,
|
||||||
|
.Ql dw ,
|
||||||
|
.Ql dl ) .
|
||||||
|
.It Cm BYTE Ar Type
|
||||||
|
.Bl -column "Value" -compact
|
||||||
|
.It Sy Value Ta Sy Meaning
|
||||||
|
.It 0 Ta Single-byte patch
|
||||||
|
.It 1 Ta Little-endian two-byte patch
|
||||||
|
.It 2 Ta Little-endian four-byte patch
|
||||||
|
.It 3 Ta Single-byte Ql jr
|
||||||
|
patch; the patch's value will be subtracted to PC + 2 (i.e.\&
|
||||||
|
.Ql jr @
|
||||||
|
must be the infinite loop
|
||||||
|
.Ql 18 FE ) .
|
||||||
|
.El
|
||||||
|
.It Cm LONG Ar RPNSize
|
||||||
|
Size of the
|
||||||
|
.Ar RPNExpr
|
||||||
|
below.
|
||||||
|
.It Cm BYTE Ar RPNExpr Ns Bq RPNSize
|
||||||
|
The patch's value, encoded as a RPN expression
|
||||||
|
.Pq see Sx RPN EXPRESSIONS .
|
||||||
|
.El
|
||||||
|
.It Cm ENDR
|
||||||
|
.El
|
||||||
|
.It Cm ENDC
|
||||||
|
.El
|
||||||
|
.El
|
||||||
|
.Ss Assertions
|
||||||
|
.Bl -tag -width Ds -compact
|
||||||
|
.It Cm LONG Ar NumberOfAssertions
|
||||||
|
How many assertions this object file contains.
|
||||||
|
.It Cm REPT Ar NumberOfAssertions
|
||||||
|
Assertions are essentially patches with a message.
|
||||||
|
.Pp
|
||||||
|
.Bl -tag -width Ds -compact
|
||||||
|
.It Cm LONG Ar NodeID
|
||||||
|
Context in which the assertions was defined.
|
||||||
|
.It Cm LONG Ar LineNo
|
||||||
|
Line number in the context at which the assertion was defined.
|
||||||
|
.It Cm LONG Ar Offset
|
||||||
|
Unused leftover from the patch structure.
|
||||||
|
.It Cm LONG Ar PCSectionID
|
||||||
|
ID of the section in which PC is located.
|
||||||
|
.It Cm LONG Ar PCOffset
|
||||||
|
Offset of the PC symbol within the section designated by
|
||||||
|
.Ar PCSectionID .
|
||||||
|
.It Cm BYTE Ar Type
|
||||||
|
Describes what should happen if the expression evaluates to a non-zero value.
|
||||||
|
.Bl -column "Value" -compact
|
||||||
|
.It Sy Value Ta Sy Meaning
|
||||||
|
.It 0 Ta Print a warning message, and continue linking normally.
|
||||||
|
.It 1 Ta Print an error message, so linking will fail, but allow other assertions to be evaluated.
|
||||||
|
.It 2 Ta Print a fatal error message, and abort immediately.
|
||||||
|
.El
|
||||||
|
.It Cm LONG Ar RPNSize
|
||||||
|
Size of the
|
||||||
|
.Ar RPNExpr
|
||||||
|
below.
|
||||||
|
.It Cm BYTE Ar RPNExpr Ns Bq RPNSize
|
||||||
|
The patch's value, encoded as a RPN expression
|
||||||
|
.Pq see Sx RPN EXPRESSIONS .
|
||||||
|
.It Cm STRING Ar Message
|
||||||
|
The message displayed if the expression evaluates to a non-zero value.
|
||||||
|
If empty, a generic message is displayed instead.
|
||||||
|
.El
|
||||||
|
.It Cm ENDR
|
||||||
|
.El
|
||||||
|
.Ss RPN EXPRESSIONS
|
||||||
|
Expressions in the object file are stored as RPN, or
|
||||||
|
.Dq Reverse Polish Notation ,
|
||||||
|
which is a notation that allows computing arbitrary expressions with just a simple stack.
|
||||||
|
For example, the expression
|
||||||
|
.Ql 2 5 -
|
||||||
|
will first push the value
|
||||||
|
.Dq 2
|
||||||
|
to the stack, then
|
||||||
.Dq 5 .
|
.Dq 5 .
|
||||||
The
|
The
|
||||||
.Do + Dc operator pops two arguments from the stack, adds them, and then pushes the result on the stack, effectively replacing the two top arguments with their sum.
|
.Ql -
|
||||||
In the RGB format, RPN expressions are stored as
|
operator pops two arguments from the stack, subtracts them, and then pushes back the result
|
||||||
.Ar BYTE Ns s
|
.Pq Dq 3
|
||||||
with some bytes being special prefixes for integers and symbols.
|
on the stack.
|
||||||
.Bl -column -offset indent "Sy String" "Sy String"
|
A well-formed RPN expression never tries to pop from an empty stack, and leaves exactly one value in it at the end.
|
||||||
|
.Pp
|
||||||
|
RGBDS encodes RPN expressions as an array of
|
||||||
|
.Cm BYTE Ns s .
|
||||||
|
The first byte encodes either an operator, or a literal, which consumes more
|
||||||
|
.Cm BYTE Ns s
|
||||||
|
after it.
|
||||||
|
.Bl -column -offset Ds "Value"
|
||||||
.It Sy Value Ta Sy Meaning
|
.It Sy Value Ta Sy Meaning
|
||||||
.It Li $00 Ta Li + operator
|
.It Li $00 Ta Addition operator Pq Ql +
|
||||||
.It Li $01 Ta Li - operator
|
.It Li $01 Ta Subtraction operator Pq Ql -
|
||||||
.It Li $02 Ta Li * operator
|
.It Li $02 Ta Multiplication operator Pq Ql *
|
||||||
.It Li $03 Ta Li / operator
|
.It Li $03 Ta Division operator Pq Ql /
|
||||||
.It Li $04 Ta Li % operator
|
.It Li $04 Ta Modulo operator Pq Ql %
|
||||||
.It Li $05 Ta Li unary -
|
.It Li $05 Ta Negation Pq unary Ql -
|
||||||
.It Li $06 Ta Li ** operator
|
.It Li $06 Ta Exponent operator Pq Ql **
|
||||||
.It Li $10 Ta Li \&| operator
|
.It Li $10 Ta Bitwise OR operator Pq Ql \&|
|
||||||
.It Li $11 Ta Li & operator
|
.It Li $11 Ta Bitwise AND operator Pq Ql &
|
||||||
.It Li $12 Ta Li ^ operator
|
.It Li $12 Ta Bitwise XOR operator Pq Ql ^
|
||||||
.It Li $13 Ta Li unary ~
|
.It Li $13 Ta Bitwise complement operator Pq unary Ql ~
|
||||||
.It Li $21 Ta Li && comparison
|
.It Li $21 Ta Logical AND operator Pq Ql &&
|
||||||
.It Li $22 Ta Li || comparison
|
.It Li $22 Ta Logical OR operator Pq Ql ||
|
||||||
.It Li $23 Ta Li unary \&!
|
.It Li $23 Ta Logical complement operator Pq unary Ql \&!
|
||||||
.It Li $30 Ta Li == comparison
|
.It Li $30 Ta Equality operator Pq Ql ==
|
||||||
.It Li $31 Ta Li != comparison
|
.It Li $31 Ta Non-equality operator Pq Ql !=
|
||||||
.It Li $32 Ta Li > comparison
|
.It Li $32 Ta Greater-than operator Pq Ql >
|
||||||
.It Li $33 Ta Li < comparison
|
.It Li $33 Ta Less-than operator Pq Ql <
|
||||||
.It Li $34 Ta Li >= comparison
|
.It Li $34 Ta Greater-than-or-equal operator Pq Ql >=
|
||||||
.It Li $35 Ta Li <= comparison
|
.It Li $35 Ta Less-than-or-equal operator Pq Ql <=
|
||||||
.It Li $40 Ta Li << operator
|
.It Li $40 Ta Left shift operator Pq Ql <<
|
||||||
.It Li $41 Ta Li >> operator
|
.It Li $41 Ta Arithmetic/signed right shift operator Pq Ql >>
|
||||||
.It Li $42 Ta Li >>> operator
|
.It Li $42 Ta Logical/unsigned right shift operator Pq Ql >>>
|
||||||
.It Li $50 Ta Li BANK(symbol) ,
|
.It Li $50 Ta Fn BANK symbol ,
|
||||||
a
|
followed by the
|
||||||
.Ar LONG
|
.Ar symbol Ap s Cm LONG
|
||||||
Symbol ID follows, where -1 means PC
|
ID.
|
||||||
.It Li $51 Ta Li BANK(section_name) ,
|
.It Li $51 Ta Fn BANK section ,
|
||||||
a null-terminated string follows.
|
followed by the
|
||||||
.It Li $52 Ta Li Current BANK()
|
.Ar section Ap s Cm STRING
|
||||||
.It Li $53 Ta Li SIZEOF(section_name) ,
|
name.
|
||||||
a null-terminated string follows.
|
.It Li $52 Ta PC's Fn BANK Pq i.e. Ql BANK(@) .
|
||||||
.It Li $54 Ta Li STARTOF(section_name) ,
|
.It Li $53 Ta Fn SIZEOF section ,
|
||||||
a null-terminated string follows.
|
followed by the
|
||||||
.It Li $60 Ta Li HRAMCheck .
|
.Ar section Ap s Cm STRING
|
||||||
Checks if the value is in HRAM, ANDs it with 0xFF.
|
name.
|
||||||
.It Li $61 Ta Li RSTCheck .
|
.It Li $54 Ta Fn STARTOF section ,
|
||||||
Checks if the value is a RST vector, ORs it with 0xC7.
|
followed by the
|
||||||
.It Li $80 Ta Ar LONG
|
.Ar section Ap s Cm STRING
|
||||||
integer follows.
|
name.
|
||||||
.It Li $81 Ta Ar LONG
|
.It Li $60 Ta Ql ldh
|
||||||
symbol ID follows.
|
check.
|
||||||
|
Checks if the value is a valid
|
||||||
|
.Ql ldh
|
||||||
|
operand
|
||||||
|
.Pq see Do Load Instructions Dc in Xr gbz80 7 ,
|
||||||
|
i.e. that it is between either $00 and $FF, or $FF00 and $FFFF, both inclusive.
|
||||||
|
The value is then ANDed with $00FF
|
||||||
|
.Pq Ql & $FF .
|
||||||
|
.It Li $61 Ta Ql rst
|
||||||
|
check.
|
||||||
|
Checks if the value is a valid
|
||||||
|
.Ql rst
|
||||||
|
.Pq see Do RST vec Dc in Xr gbz80 7
|
||||||
|
vector, that is one of $00, $08, $10, $18, $20, $28, $30, or $38.
|
||||||
|
The value is then ORed with $C7
|
||||||
|
.Pq Ql \&| $C7 .
|
||||||
|
.It Li $80 Ta Integer literal.
|
||||||
|
Followed by the
|
||||||
|
.Cm LONG
|
||||||
|
integer.
|
||||||
|
.It Li $81 Ta A symbol's value.
|
||||||
|
Followed by the symbol's
|
||||||
|
.Cm LONG
|
||||||
|
ID.
|
||||||
.El
|
.El
|
||||||
.Sh SEE ALSO
|
.Sh SEE ALSO
|
||||||
.Xr rgbasm 1 ,
|
.Xr rgbasm 1 ,
|
||||||
|
|||||||
71
man/rgbgfx.1
71
man/rgbgfx.1
@@ -14,7 +14,6 @@
|
|||||||
.Nd Game Boy graphics converter
|
.Nd Game Boy graphics converter
|
||||||
.Sh SYNOPSIS
|
.Sh SYNOPSIS
|
||||||
.Nm
|
.Nm
|
||||||
.Op Fl r Ar stride
|
|
||||||
.Op Fl CmuVZ
|
.Op Fl CmuVZ
|
||||||
.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
|
||||||
@@ -27,6 +26,7 @@
|
|||||||
.Op Fl o Ar out_file
|
.Op Fl o Ar out_file
|
||||||
.Op Fl p Ar pal_file | Fl P
|
.Op Fl p Ar pal_file | Fl P
|
||||||
.Op Fl q Ar pal_map | Fl Q
|
.Op Fl q Ar pal_map | Fl Q
|
||||||
|
.Op Fl r Ar stride
|
||||||
.Op Fl s Ar nb_colors
|
.Op Fl s Ar nb_colors
|
||||||
.Op Fl t Ar tilemap | Fl T
|
.Op Fl t Ar tilemap | Fl T
|
||||||
.Op Fl x Ar quantity
|
.Op Fl x Ar quantity
|
||||||
@@ -72,6 +72,18 @@ All of these are equivalent:
|
|||||||
.Ql 0X2A ,
|
.Ql 0X2A ,
|
||||||
.Ql 0x2a .
|
.Ql 0x2a .
|
||||||
.Pp
|
.Pp
|
||||||
|
Unless otherwise noted, passing
|
||||||
|
.Ql -
|
||||||
|
(a single dash) as a file name makes
|
||||||
|
.Nm
|
||||||
|
use standard input (for input files) or standard output (for output files).
|
||||||
|
To suppress this behavior, and open a file in the current directory actually called
|
||||||
|
.Ql - ,
|
||||||
|
pass
|
||||||
|
.Ql ./-
|
||||||
|
instead.
|
||||||
|
Using standard input or output more than once in a single command will likely produce unexpected results.
|
||||||
|
.Pp
|
||||||
The following options are accepted:
|
The following options are accepted:
|
||||||
.Bl -tag -width Ds
|
.Bl -tag -width Ds
|
||||||
.It Fl a Ar attrmap , Fl Fl attr-map Ar attrmap
|
.It Fl a Ar attrmap , Fl Fl attr-map Ar attrmap
|
||||||
@@ -145,7 +157,9 @@ The expected format is
|
|||||||
.Ql format:path ,
|
.Ql format:path ,
|
||||||
where
|
where
|
||||||
.Ar path
|
.Ar path
|
||||||
is a path to a file, which will be processed according to the
|
is a path to a file
|
||||||
|
.Ql ( -
|
||||||
|
is not treated specially), which will be processed according to the
|
||||||
.Ar format .
|
.Ar format .
|
||||||
See
|
See
|
||||||
.Sx PALETTE SPECIFICATION FORMATS
|
.Sx PALETTE SPECIFICATION FORMATS
|
||||||
@@ -321,7 +335,22 @@ any command-line argument that begins with an at sign
|
|||||||
.Pq Ql @
|
.Pq Ql @
|
||||||
is interpreted as one.
|
is interpreted as one.
|
||||||
The rest of the argument (without the @, that is) is interpreted as the path to a file, whose contents are interpreted as if given on the command line.
|
The rest of the argument (without the @, that is) is interpreted as the path to a file, whose contents are interpreted as if given on the command line.
|
||||||
At-files can be stored right next to the corresponding image, for example.
|
At-files can be stored right next to the corresponding image, for example:
|
||||||
|
.Pp
|
||||||
|
.Dl $ rgbgfx -o image.2bpp -t image.tilemap @image.flags image.png
|
||||||
|
.Pp
|
||||||
|
This will read additional flags from file
|
||||||
|
.Ql image.flags ,
|
||||||
|
which could contains for example
|
||||||
|
.Ql -b 128
|
||||||
|
to specify a base offset for the image's tiles.
|
||||||
|
The above command could be generated from the following
|
||||||
|
.Xr make 1
|
||||||
|
rule, for example:
|
||||||
|
.Bd -literal -offset indent
|
||||||
|
%.2bpp %.tilemap: %.flags %.png
|
||||||
|
rgbgfx -o $*.2bpp -t $*.tilemap @$*.flags $*.png
|
||||||
|
.Ed
|
||||||
.Pp
|
.Pp
|
||||||
Since the contents of at-files are interpreted by
|
Since the contents of at-files are interpreted by
|
||||||
.Nm ,
|
.Nm ,
|
||||||
@@ -336,17 +365,19 @@ optionally preceded by whitespace, are considered comments and also ignored.
|
|||||||
Each line can contain any number of arguments, which are separated by whitespace.
|
Each line can contain any number of arguments, which are separated by whitespace.
|
||||||
.Pq \&No quoting feature to prevent this is provided.
|
.Pq \&No quoting feature to prevent this is provided.
|
||||||
.Pp
|
.Pp
|
||||||
Note that this special meaning given to arguments has less precedence than option arguments, and that the standard
|
Note that a leading
|
||||||
|
.Ql @
|
||||||
|
has no special meaning on option arguments, and that the standard
|
||||||
.Ql --
|
.Ql --
|
||||||
to stop option processing also disables at-file processing.
|
to stop option processing also disables at-file processing.
|
||||||
For example, the following command line processes
|
For example, the following command line reads command-line options from
|
||||||
.Ql @tilesets/town.png ,
|
|
||||||
outputs tile data to
|
|
||||||
.Ql @tilesets/town.2bpp ,
|
|
||||||
and reads command-line options from
|
|
||||||
.Ql tilesets/town.flags
|
.Ql tilesets/town.flags
|
||||||
then
|
then
|
||||||
.Ql tilesets.flags :
|
.Ql tilesets.flags ,
|
||||||
|
but processes
|
||||||
|
.Ql @tilesets/town.png
|
||||||
|
as the input image and outputs tile data to
|
||||||
|
.Ql @tilesets/town.2bpp :
|
||||||
.Pp
|
.Pp
|
||||||
.Dl $ rgbgfx -o @tilesets/town.2bpp @tilesets/town.flags @tilesets.flags -- @tilesets/town.png
|
.Dl $ rgbgfx -o @tilesets/town.2bpp @tilesets/town.flags @tilesets.flags -- @tilesets/town.png
|
||||||
.Pp
|
.Pp
|
||||||
@@ -357,11 +388,21 @@ can be used in an at-file (with identical semantics), it is only effective insid
|
|||||||
.Sh PALETTE SPECIFICATION FORMATS
|
.Sh PALETTE SPECIFICATION FORMATS
|
||||||
The following formats are supported:
|
The following formats are supported:
|
||||||
.Bl -tag -width Ds
|
.Bl -tag -width Ds
|
||||||
.It Sy act
|
.It Cm act
|
||||||
.Lk https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577411_pgfId-1070626 Adobe Photoshop color table .
|
.Lk https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577411_pgfId-1070626 Adobe Photoshop color table .
|
||||||
.It Sy aco
|
.It Cm aco
|
||||||
.Lk https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577411_pgfId-1055819 Adobe Photoshop color swatch .
|
.Lk https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577411_pgfId-1055819 Adobe Photoshop color swatch .
|
||||||
.It Sy psp
|
.It Cm gbc
|
||||||
|
A GBC palette memory dump, as emitted by
|
||||||
|
.Nm Fl p .
|
||||||
|
Useful to force several images to share the same palette.
|
||||||
|
.It Cm gpl
|
||||||
|
.Lk https://docs.gimp.org/2.10/en/gimp-concepts-palettes.html GIMP palette .
|
||||||
|
.It Cm hex
|
||||||
|
Plaintext lines of hexadecimal colors in
|
||||||
|
.Ql rrggbb
|
||||||
|
format.
|
||||||
|
.It Cm psp
|
||||||
.Lk https://www.selapa.net/swatches/colors/fileformats.php#psp_pal Paint Shop Pro palette .
|
.Lk https://www.selapa.net/swatches/colors/fileformats.php#psp_pal Paint Shop Pro palette .
|
||||||
.El
|
.El
|
||||||
.Pp
|
.Pp
|
||||||
@@ -421,7 +462,7 @@ be
|
|||||||
.Em exactly
|
.Em exactly
|
||||||
the same.
|
the same.
|
||||||
.It
|
.It
|
||||||
If none of the above apply, colors are sorted from lightest to darkest.
|
If none of the above apply, colors are sorted from lightest (first) to darkest (last).
|
||||||
The definition of luminance that
|
The definition of luminance that
|
||||||
.Nm
|
.Nm
|
||||||
uses is
|
uses is
|
||||||
@@ -472,7 +513,7 @@ delim off
|
|||||||
.EN
|
.EN
|
||||||
Note that
|
Note that
|
||||||
.Fl n
|
.Fl n
|
||||||
only puts a limit on the amount of palettes, but does not fix this file's size.
|
only caps how many palettes are generated (and thus this file's size), but fewer may be generated still.
|
||||||
.Ss Tile map data
|
.Ss Tile map data
|
||||||
A tile map is an array of tile IDs, with one byte per tile ID.
|
A tile map is an array of tile IDs, with one byte per tile ID.
|
||||||
The first byte always corresponds to the ID of the tile in top-left corner of the input image; the second byte is either the ID of the tile to its right (by default), or below it
|
The first byte always corresponds to the ID of the tile in top-left corner of the input image; the second byte is either the ID of the tile to its right (by default), or below it
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
.Nd Game Boy linker
|
.Nd Game Boy linker
|
||||||
.Sh SYNOPSIS
|
.Sh SYNOPSIS
|
||||||
.Nm
|
.Nm
|
||||||
.Op Fl dtVvwx
|
.Op Fl dMtVvwx
|
||||||
.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
|
||||||
@@ -73,6 +73,8 @@ The attributes assigned in the linker script must be consistent with any assigne
|
|||||||
See
|
See
|
||||||
.Xr rgblink 5
|
.Xr rgblink 5
|
||||||
for more information about the linker script format.
|
for more information about the linker script format.
|
||||||
|
.It Fl M , Fl Fl no-sym-in-map
|
||||||
|
If specified, the map file will not list symbols, only sections.
|
||||||
.It Fl m Ar map_file , Fl Fl map Ar map_file
|
.It Fl m Ar map_file , Fl Fl map Ar map_file
|
||||||
Write a map file to the given filename, listing how sections and symbols were assigned.
|
Write a map file to the given filename, listing how sections and symbols were assigned.
|
||||||
.It Fl n Ar sym_file , Fl Fl sym Ar sym_file
|
.It Fl n Ar sym_file , Fl Fl sym Ar sym_file
|
||||||
|
|||||||
@@ -14,18 +14,22 @@
|
|||||||
.Sh DESCRIPTION
|
.Sh DESCRIPTION
|
||||||
The linker script is an external file that allows the user to specify the order of sections at link time and in a centralized manner.
|
The linker script is an external file that allows the user to specify the order of sections at link time and in a centralized manner.
|
||||||
.Pp
|
.Pp
|
||||||
A linker script consists on a series of banks followed by a list of sections and, optionally, commands.
|
A linker script consists of a series of bank declarations, each optionally followed by a list of section names (in double quotes) or commands.
|
||||||
They can be lowercase or uppercase, it is ignored.
|
All reserved keywords (bank types and command names) are case-insensitive; all section names are case-sensitive.
|
||||||
|
.Pp
|
||||||
Any line can contain a comment starting with
|
Any line can contain a comment starting with
|
||||||
.Ql \&;
|
.Ql \&;
|
||||||
that ends at the end of the line:
|
that ends at the end of the line.
|
||||||
|
.Pp
|
||||||
.Bd -literal -offset indent
|
.Bd -literal -offset indent
|
||||||
ROMX $F ; This is a comment
|
; This line is a comment
|
||||||
"Functions to read array"
|
ROMX $F ; start a bank
|
||||||
ALIGN 8
|
"Some functions" ; a section name
|
||||||
"Array aligned to 256 bytes"
|
ALIGN 8 ; a command
|
||||||
|
"Some array"
|
||||||
|
|
||||||
WRAMX 2
|
WRAMX 2 ; start another bank
|
||||||
|
org $d123 ; another command
|
||||||
"Some variables"
|
"Some variables"
|
||||||
.Ed
|
.Ed
|
||||||
.Pp
|
.Pp
|
||||||
@@ -47,6 +51,19 @@ and
|
|||||||
.Cm WRAMX ,
|
.Cm WRAMX ,
|
||||||
it is needed to specify a bank number after the type.
|
it is needed to specify a bank number after the type.
|
||||||
.Pp
|
.Pp
|
||||||
|
Section names in double quotes support the same character escape sequences as strings in
|
||||||
|
.Xr rgbasm 5 ,
|
||||||
|
specifically
|
||||||
|
.Ql \e\e ,
|
||||||
|
.Ql \e" ,
|
||||||
|
.Ql \en ,
|
||||||
|
.Ql \er ,
|
||||||
|
and
|
||||||
|
.Ql \et .
|
||||||
|
Other backslash escape sequences in
|
||||||
|
.Xr rgbasm 5
|
||||||
|
are only relevant to assembly code and do not apply in section names.
|
||||||
|
.Pp
|
||||||
When a new bank statement is found, sections found after it will be placed right from the beginning of that bank.
|
When a new bank statement is found, sections found after it will be placed right from the beginning of that bank.
|
||||||
If the linker script switches to a different bank and then comes back to a previous one, it will continue from the last address that was used.
|
If the linker script switches to a different bank and then comes back to a previous one, it will continue from the last address that was used.
|
||||||
.Pp
|
.Pp
|
||||||
|
|||||||
@@ -14,21 +14,20 @@ set(common_src
|
|||||||
"_version.c"
|
"_version.c"
|
||||||
)
|
)
|
||||||
|
|
||||||
find_package(BISON REQUIRED)
|
find_package(BISON 3.0.0 REQUIRED)
|
||||||
set(BISON_FLAGS "-Wall")
|
set(BISON_FLAGS "-Wall")
|
||||||
# Set sompe optimization flags on versions that support them
|
# Set some optimization flags on versions that support them
|
||||||
if(BISON_VERSION VERSION_GREATER_EQUAL "3.5")
|
if(BISON_VERSION VERSION_GREATER_EQUAL "3.5")
|
||||||
set(BISON_FLAGS "${BISON_FLAGS} -Dapi.token.raw=true")
|
set(BISON_FLAGS "${BISON_FLAGS} -Dapi.token.raw=true")
|
||||||
endif()
|
endif()
|
||||||
if(BISON_VERSION VERSION_GREATER_EQUAL "3.6")
|
if(BISON_VERSION VERSION_GREATER_EQUAL "3.6")
|
||||||
set(BISON_FLAGS "${BISON_FLAGS} -Dparse.error=detailed")
|
set(BISON_FLAGS "${BISON_FLAGS} -Dparse.error=detailed")
|
||||||
elseif(BISON_VERSION VERSION_GREATER_EQUAL "3.0")
|
else()
|
||||||
set(BISON_FLAGS "${BISON_FLAGS} -Dparse.error=verbose")
|
set(BISON_FLAGS "${BISON_FLAGS} -Dparse.error=verbose")
|
||||||
endif()
|
endif()
|
||||||
if(BISON_VERSION VERSION_GREATER_EQUAL "3.0")
|
set(BISON_FLAGS "${BISON_FLAGS} -Dparse.lac=full")
|
||||||
set(BISON_FLAGS "${BISON_FLAGS} -Dparse.lac=full")
|
set(BISON_FLAGS "${BISON_FLAGS} -Dlr.type=ielr")
|
||||||
set(BISON_FLAGS "${BISON_FLAGS} -Dlr.type=ielr")
|
|
||||||
endif()
|
|
||||||
BISON_TARGET(PARSER "asm/parser.y"
|
BISON_TARGET(PARSER "asm/parser.y"
|
||||||
"${PROJECT_SOURCE_DIR}/src/asm/parser.c"
|
"${PROJECT_SOURCE_DIR}/src/asm/parser.c"
|
||||||
COMPILE_FLAGS "${BISON_FLAGS}"
|
COMPILE_FLAGS "${BISON_FLAGS}"
|
||||||
@@ -81,8 +80,10 @@ set(rgblink_src
|
|||||||
"link/output.c"
|
"link/output.c"
|
||||||
"link/patch.c"
|
"link/patch.c"
|
||||||
"link/script.c"
|
"link/script.c"
|
||||||
|
"link/sdas_obj.c"
|
||||||
"link/section.c"
|
"link/section.c"
|
||||||
"link/symbol.c"
|
"link/symbol.c"
|
||||||
|
"extern/utf8decoder.c"
|
||||||
"hashmap.c"
|
"hashmap.c"
|
||||||
"linkdefs.c"
|
"linkdefs.c"
|
||||||
"opmath.c"
|
"opmath.c"
|
||||||
|
|||||||
@@ -21,33 +21,29 @@
|
|||||||
|
|
||||||
#include "hashmap.h"
|
#include "hashmap.h"
|
||||||
|
|
||||||
/*
|
// Charmaps are stored using a structure known as "trie".
|
||||||
* Charmaps are stored using a structure known as "trie".
|
// Essentially a tree, where each nodes stores a single character's worth of info:
|
||||||
* Essentially a tree, where each nodes stores a single character's worth of info:
|
// whether there exists a mapping that ends at the current character,
|
||||||
* whether there exists a mapping that ends at the current character,
|
|
||||||
*/
|
|
||||||
struct Charnode {
|
struct Charnode {
|
||||||
bool isTerminal; /* Whether there exists a mapping that ends here */
|
bool isTerminal; // Whether there exists a mapping that ends here
|
||||||
uint8_t value; /* If the above is true, its corresponding value */
|
uint8_t value; // If the above is true, its corresponding value
|
||||||
/* This MUST be indexes and not pointers, because pointers get invalidated by `realloc`!! */
|
// This MUST be indexes and not pointers, because pointers get invalidated by `realloc`!
|
||||||
size_t next[255]; /* Indexes of where to go next, 0 = nowhere */
|
size_t next[255]; // Indexes of where to go next, 0 = nowhere
|
||||||
};
|
};
|
||||||
|
|
||||||
#define INITIAL_CAPACITY 32
|
#define INITIAL_CAPACITY 32
|
||||||
|
|
||||||
struct Charmap {
|
struct Charmap {
|
||||||
char *name;
|
char *name;
|
||||||
size_t usedNodes; /* How many nodes are being used */
|
size_t usedNodes; // How many nodes are being used
|
||||||
size_t capacity; /* How many nodes have been allocated */
|
size_t capacity; // How many nodes have been allocated
|
||||||
struct Charnode nodes[]; /* first node is reserved for the root node */
|
struct Charnode nodes[]; // first node is reserved for the root node
|
||||||
};
|
};
|
||||||
|
|
||||||
static HashMap charmaps;
|
static HashMap charmaps;
|
||||||
|
|
||||||
/*
|
// Store pointers to hashmap nodes, so that there is only one pointer to the memory block
|
||||||
* Store pointers to hashmap nodes, so that there is only one pointer to the memory block
|
// that gets reallocated.
|
||||||
* that gets reallocated.
|
|
||||||
*/
|
|
||||||
static struct Charmap **currentCharmap;
|
static struct Charmap **currentCharmap;
|
||||||
|
|
||||||
struct CharmapStackEntry {
|
struct CharmapStackEntry {
|
||||||
@@ -96,7 +92,7 @@ struct Charmap *charmap_New(char const *name, char const *baseName)
|
|||||||
return charmap;
|
return charmap;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Init the new charmap's fields */
|
// Init the new charmap's fields
|
||||||
if (base) {
|
if (base) {
|
||||||
resizeCharmap(&charmap, base->capacity);
|
resizeCharmap(&charmap, base->capacity);
|
||||||
charmap->usedNodes = base->usedNodes;
|
charmap->usedNodes = base->usedNodes;
|
||||||
@@ -105,7 +101,7 @@ struct Charmap *charmap_New(char const *name, char const *baseName)
|
|||||||
} else {
|
} else {
|
||||||
resizeCharmap(&charmap, INITIAL_CAPACITY);
|
resizeCharmap(&charmap, INITIAL_CAPACITY);
|
||||||
charmap->usedNodes = 1;
|
charmap->usedNodes = 1;
|
||||||
initNode(&charmap->nodes[0]); /* Init the root node */
|
initNode(&charmap->nodes[0]); // Init the root node
|
||||||
}
|
}
|
||||||
charmap->name = strdup(name);
|
charmap->name = strdup(name);
|
||||||
|
|
||||||
@@ -169,16 +165,16 @@ void charmap_Add(char *mapping, uint8_t value)
|
|||||||
if (node->next[c]) {
|
if (node->next[c]) {
|
||||||
node = &charmap->nodes[node->next[c]];
|
node = &charmap->nodes[node->next[c]];
|
||||||
} else {
|
} else {
|
||||||
/* Register next available node */
|
// Register next available node
|
||||||
node->next[c] = charmap->usedNodes;
|
node->next[c] = charmap->usedNodes;
|
||||||
/* If no more nodes are available, get new ones */
|
// If no more nodes are available, get new ones
|
||||||
if (charmap->usedNodes == charmap->capacity) {
|
if (charmap->usedNodes == charmap->capacity) {
|
||||||
charmap->capacity *= 2;
|
charmap->capacity *= 2;
|
||||||
resizeCharmap(currentCharmap, charmap->capacity);
|
resizeCharmap(currentCharmap, charmap->capacity);
|
||||||
charmap = *currentCharmap;
|
charmap = *currentCharmap;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Switch to and init new node */
|
// Switch to and init new node
|
||||||
node = &charmap->nodes[charmap->usedNodes++];
|
node = &charmap->nodes[charmap->usedNodes++];
|
||||||
initNode(node);
|
initNode(node);
|
||||||
}
|
}
|
||||||
@@ -203,12 +199,10 @@ size_t charmap_Convert(char const *input, uint8_t *output)
|
|||||||
|
|
||||||
size_t charmap_ConvertNext(char const **input, uint8_t **output)
|
size_t charmap_ConvertNext(char const **input, uint8_t **output)
|
||||||
{
|
{
|
||||||
/*
|
// The goal is to match the longest mapping possible.
|
||||||
* The goal is to match the longest mapping possible.
|
// For that, advance through the trie with each character read.
|
||||||
* For that, advance through the trie with each character read.
|
// If that would lead to a dead end, rewind characters until the last match, and output.
|
||||||
* If that would lead to a dead end, rewind characters until the last match, and output.
|
// If no match, read a UTF-8 codepoint and output that.
|
||||||
* If no match, read a UTF-8 codepoint and output that.
|
|
||||||
*/
|
|
||||||
struct Charmap const *charmap = *currentCharmap;
|
struct Charmap const *charmap = *currentCharmap;
|
||||||
struct Charnode const *node = &charmap->nodes[0];
|
struct Charnode const *node = &charmap->nodes[0];
|
||||||
struct Charnode const *match = NULL;
|
struct Charnode const *match = NULL;
|
||||||
@@ -242,6 +236,7 @@ size_t charmap_ConvertNext(char const **input, uint8_t **output)
|
|||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
} else if (**input) { // No match found, but there is some input left
|
} else if (**input) { // No match found, but there is some input left
|
||||||
|
int firstChar = **input;
|
||||||
// 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 ? *output : NULL,
|
size_t codepointLen = readUTF8Char(output ? *output : NULL,
|
||||||
*input);
|
*input);
|
||||||
@@ -254,6 +249,15 @@ size_t charmap_ConvertNext(char const **input, uint8_t **output)
|
|||||||
if (output)
|
if (output)
|
||||||
*output += codepointLen;
|
*output += codepointLen;
|
||||||
|
|
||||||
|
// Warn if this character is not mapped but any others are
|
||||||
|
if (charmap->usedNodes > 1)
|
||||||
|
warning(WARNING_UNMAPPED_CHAR_1,
|
||||||
|
"Unmapped character %s\n", printChar(firstChar));
|
||||||
|
else if (strcmp(charmap->name, DEFAULT_CHARMAP_NAME))
|
||||||
|
warning(WARNING_UNMAPPED_CHAR_2,
|
||||||
|
"Unmapped character %s not in " DEFAULT_CHARMAP_NAME
|
||||||
|
" charmap\n", printChar(firstChar));
|
||||||
|
|
||||||
return codepointLen;
|
return codepointLen;
|
||||||
|
|
||||||
} else { // End of input
|
} else { // End of input
|
||||||
|
|||||||
@@ -6,155 +6,110 @@
|
|||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
// Fixed-point math routines
|
||||||
* Fixed-point math routines
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
#include "asm/fixpoint.h"
|
#include "asm/fixpoint.h"
|
||||||
#include "asm/symbol.h"
|
#include "asm/symbol.h"
|
||||||
#include "asm/warning.h"
|
#include "asm/warning.h"
|
||||||
|
|
||||||
#define fix2double(i) ((double)((i) / 65536.0))
|
|
||||||
#define double2fix(d) ((int32_t)round((d) * 65536.0))
|
|
||||||
|
|
||||||
// pi radians == 32768 fixed-point "degrees"
|
|
||||||
#define fdeg2rad(f) ((f) * (M_PI / 32768.0))
|
|
||||||
#define rad2fdeg(r) ((r) * (32768.0 / M_PI))
|
|
||||||
|
|
||||||
#ifndef M_PI
|
#ifndef M_PI
|
||||||
#define M_PI 3.14159265358979323846
|
#define M_PI 3.14159265358979323846
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/*
|
#define fix2double(i, q) ((double)((i) / pow(2.0, q)))
|
||||||
* Print a fixed point value
|
#define double2fix(d, q) ((int32_t)round((d) * pow(2.0, q)))
|
||||||
*/
|
|
||||||
void fix_Print(int32_t i)
|
// 2*pi radians == 1 turn
|
||||||
|
#define turn2rad(f) ((f) * (M_PI * 2))
|
||||||
|
#define rad2turn(r) ((r) / (M_PI * 2))
|
||||||
|
|
||||||
|
uint8_t fixPrecision;
|
||||||
|
|
||||||
|
uint8_t fix_Precision(void)
|
||||||
{
|
{
|
||||||
uint32_t u = i;
|
return fixPrecision;
|
||||||
char const *sign = "";
|
|
||||||
|
|
||||||
if (i < 0) {
|
|
||||||
u = -u;
|
|
||||||
sign = "-";
|
|
||||||
}
|
|
||||||
|
|
||||||
printf("%s%" PRIu32 ".%05" PRIu32, sign, u >> 16,
|
|
||||||
((uint32_t)(fix2double(u) * 100000 + 0.5)) % 100000);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
double fix_PrecisionFactor(void)
|
||||||
* Calculate sine
|
|
||||||
*/
|
|
||||||
int32_t fix_Sin(int32_t i)
|
|
||||||
{
|
{
|
||||||
return double2fix(sin(fdeg2rad(fix2double(i))));
|
return pow(2.0, fixPrecision);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
int32_t fix_Sin(int32_t i, int32_t q)
|
||||||
* Calculate cosine
|
|
||||||
*/
|
|
||||||
int32_t fix_Cos(int32_t i)
|
|
||||||
{
|
{
|
||||||
return double2fix(cos(fdeg2rad(fix2double(i))));
|
return double2fix(sin(turn2rad(fix2double(i, q))), q);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
int32_t fix_Cos(int32_t i, int32_t q)
|
||||||
* Calculate tangent
|
|
||||||
*/
|
|
||||||
int32_t fix_Tan(int32_t i)
|
|
||||||
{
|
{
|
||||||
return double2fix(tan(fdeg2rad(fix2double(i))));
|
return double2fix(cos(turn2rad(fix2double(i, q))), q);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
int32_t fix_Tan(int32_t i, int32_t q)
|
||||||
* Calculate arcsine
|
|
||||||
*/
|
|
||||||
int32_t fix_ASin(int32_t i)
|
|
||||||
{
|
{
|
||||||
return double2fix(rad2fdeg(asin(fix2double(i))));
|
return double2fix(tan(turn2rad(fix2double(i, q))), q);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
int32_t fix_ASin(int32_t i, int32_t q)
|
||||||
* Calculate arccosine
|
|
||||||
*/
|
|
||||||
int32_t fix_ACos(int32_t i)
|
|
||||||
{
|
{
|
||||||
return double2fix(rad2fdeg(acos(fix2double(i))));
|
return double2fix(rad2turn(asin(fix2double(i, q))), q);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
int32_t fix_ACos(int32_t i, int32_t q)
|
||||||
* Calculate arctangent
|
|
||||||
*/
|
|
||||||
int32_t fix_ATan(int32_t i)
|
|
||||||
{
|
{
|
||||||
return double2fix(rad2fdeg(atan(fix2double(i))));
|
return double2fix(rad2turn(acos(fix2double(i, q))), q);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
int32_t fix_ATan(int32_t i, int32_t q)
|
||||||
* Calculate atan2
|
|
||||||
*/
|
|
||||||
int32_t fix_ATan2(int32_t i, int32_t j)
|
|
||||||
{
|
{
|
||||||
return double2fix(rad2fdeg(atan2(fix2double(i), fix2double(j))));
|
return double2fix(rad2turn(atan(fix2double(i, q))), q);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
int32_t fix_ATan2(int32_t i, int32_t j, int32_t q)
|
||||||
* Multiplication
|
|
||||||
*/
|
|
||||||
int32_t fix_Mul(int32_t i, int32_t j)
|
|
||||||
{
|
{
|
||||||
return double2fix(fix2double(i) * fix2double(j));
|
return double2fix(rad2turn(atan2(fix2double(i, q), fix2double(j, q))), q);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
int32_t fix_Mul(int32_t i, int32_t j, int32_t q)
|
||||||
* Division
|
|
||||||
*/
|
|
||||||
int32_t fix_Div(int32_t i, int32_t j)
|
|
||||||
{
|
{
|
||||||
return double2fix(fix2double(i) / fix2double(j));
|
return double2fix(fix2double(i, q) * fix2double(j, q), q);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
int32_t fix_Div(int32_t i, int32_t j, int32_t q)
|
||||||
* Power
|
|
||||||
*/
|
|
||||||
int32_t fix_Pow(int32_t i, int32_t j)
|
|
||||||
{
|
{
|
||||||
return double2fix(pow(fix2double(i), fix2double(j)));
|
return double2fix(fix2double(i, q) / fix2double(j, q), q);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
int32_t fix_Mod(int32_t i, int32_t j, int32_t q)
|
||||||
* Logarithm
|
|
||||||
*/
|
|
||||||
int32_t fix_Log(int32_t i, int32_t j)
|
|
||||||
{
|
{
|
||||||
return double2fix(log(fix2double(i)) / log(fix2double(j)));
|
return double2fix(fmod(fix2double(i, q), fix2double(j, q)), q);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
int32_t fix_Pow(int32_t i, int32_t j, int32_t q)
|
||||||
* Round
|
|
||||||
*/
|
|
||||||
int32_t fix_Round(int32_t i)
|
|
||||||
{
|
{
|
||||||
return double2fix(round(fix2double(i)));
|
return double2fix(pow(fix2double(i, q), fix2double(j, q)), q);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
int32_t fix_Log(int32_t i, int32_t j, int32_t q)
|
||||||
* Ceil
|
|
||||||
*/
|
|
||||||
int32_t fix_Ceil(int32_t i)
|
|
||||||
{
|
{
|
||||||
return double2fix(ceil(fix2double(i)));
|
return double2fix(log(fix2double(i, q)) / log(fix2double(j, q)), q);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
int32_t fix_Round(int32_t i, int32_t q)
|
||||||
* Floor
|
|
||||||
*/
|
|
||||||
int32_t fix_Floor(int32_t i)
|
|
||||||
{
|
{
|
||||||
return double2fix(floor(fix2double(i)));
|
return double2fix(round(fix2double(i, q)), q);
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t fix_Ceil(int32_t i, int32_t q)
|
||||||
|
{
|
||||||
|
return double2fix(ceil(fix2double(i, q)), q);
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t fix_Floor(int32_t i, int32_t q)
|
||||||
|
{
|
||||||
|
return double2fix(floor(fix2double(i, q)), q);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "asm/fixpoint.h"
|
||||||
#include "asm/format.h"
|
#include "asm/format.h"
|
||||||
#include "asm/warning.h"
|
#include "asm/warning.h"
|
||||||
|
|
||||||
@@ -46,7 +47,7 @@ void fmt_UseCharacter(struct FormatSpec *fmt, int c)
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
switch (c) {
|
switch (c) {
|
||||||
/* sign */
|
// sign
|
||||||
case ' ':
|
case ' ':
|
||||||
case '+':
|
case '+':
|
||||||
if (fmt->state > FORMAT_SIGN)
|
if (fmt->state > FORMAT_SIGN)
|
||||||
@@ -55,7 +56,7 @@ void fmt_UseCharacter(struct FormatSpec *fmt, int c)
|
|||||||
fmt->sign = c;
|
fmt->sign = c;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
/* prefix */
|
// prefix
|
||||||
case '#':
|
case '#':
|
||||||
if (fmt->state > FORMAT_PREFIX)
|
if (fmt->state > FORMAT_PREFIX)
|
||||||
goto invalid;
|
goto invalid;
|
||||||
@@ -63,7 +64,7 @@ void fmt_UseCharacter(struct FormatSpec *fmt, int c)
|
|||||||
fmt->prefix = true;
|
fmt->prefix = true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
/* align */
|
// align
|
||||||
case '-':
|
case '-':
|
||||||
if (fmt->state > FORMAT_ALIGN)
|
if (fmt->state > FORMAT_ALIGN)
|
||||||
goto invalid;
|
goto invalid;
|
||||||
@@ -71,11 +72,11 @@ void fmt_UseCharacter(struct FormatSpec *fmt, int c)
|
|||||||
fmt->alignLeft = true;
|
fmt->alignLeft = true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
/* pad and width */
|
// pad and width
|
||||||
case '0':
|
case '0':
|
||||||
if (fmt->state < FORMAT_WIDTH)
|
if (fmt->state < FORMAT_WIDTH)
|
||||||
fmt->padZero = true;
|
fmt->padZero = true;
|
||||||
/* fallthrough */
|
// fallthrough
|
||||||
case '1':
|
case '1':
|
||||||
case '2':
|
case '2':
|
||||||
case '3':
|
case '3':
|
||||||
@@ -104,7 +105,7 @@ void fmt_UseCharacter(struct FormatSpec *fmt, int c)
|
|||||||
fmt->hasFrac = true;
|
fmt->hasFrac = true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
/* type */
|
// type
|
||||||
case 'd':
|
case 'd':
|
||||||
case 'u':
|
case 'u':
|
||||||
case 'X':
|
case 'X':
|
||||||
@@ -149,7 +150,7 @@ void fmt_PrintString(char *buf, size_t bufLen, struct FormatSpec const *fmt, cha
|
|||||||
size_t len = strlen(value);
|
size_t len = strlen(value);
|
||||||
size_t totalLen = fmt->width > len ? fmt->width : len;
|
size_t totalLen = fmt->width > len ? fmt->width : len;
|
||||||
|
|
||||||
if (totalLen > bufLen - 1) { /* bufLen includes terminator */
|
if (totalLen > bufLen - 1) { // bufLen includes terminator
|
||||||
error("Formatted string value too long\n");
|
error("Formatted string value too long\n");
|
||||||
totalLen = bufLen - 1;
|
totalLen = bufLen - 1;
|
||||||
if (len > totalLen)
|
if (len > totalLen)
|
||||||
@@ -182,7 +183,7 @@ void fmt_PrintNumber(char *buf, size_t bufLen, struct FormatSpec const *fmt, uin
|
|||||||
if (fmt->type == 's')
|
if (fmt->type == 's')
|
||||||
error("Formatting number as type 's'\n");
|
error("Formatting number as type 's'\n");
|
||||||
|
|
||||||
char sign = fmt->sign; /* 0 or ' ' or '+' */
|
char sign = fmt->sign; // 0 or ' ' or '+'
|
||||||
|
|
||||||
if (fmt->type == 'd' || fmt->type == 'f') {
|
if (fmt->type == 'd' || fmt->type == 'f') {
|
||||||
int32_t v = value;
|
int32_t v = value;
|
||||||
@@ -200,10 +201,10 @@ void fmt_PrintNumber(char *buf, size_t bufLen, struct FormatSpec const *fmt, uin
|
|||||||
: fmt->type == 'o' ? '&'
|
: fmt->type == 'o' ? '&'
|
||||||
: 0;
|
: 0;
|
||||||
|
|
||||||
char valueBuf[262]; /* Max 5 digits + decimal + 255 fraction digits + terminator */
|
char valueBuf[262]; // Max 5 digits + decimal + 255 fraction digits + terminator
|
||||||
|
|
||||||
if (fmt->type == 'b') {
|
if (fmt->type == 'b') {
|
||||||
/* Special case for binary */
|
// Special case for binary
|
||||||
char *ptr = valueBuf;
|
char *ptr = valueBuf;
|
||||||
|
|
||||||
do {
|
do {
|
||||||
@@ -213,7 +214,7 @@ void fmt_PrintNumber(char *buf, size_t bufLen, struct FormatSpec const *fmt, uin
|
|||||||
|
|
||||||
*ptr = '\0';
|
*ptr = '\0';
|
||||||
|
|
||||||
/* Reverse the digits */
|
// Reverse the digits
|
||||||
size_t valueLen = ptr - valueBuf;
|
size_t valueLen = ptr - valueBuf;
|
||||||
|
|
||||||
for (size_t i = 0, j = valueLen - 1; i < j; i++, j--) {
|
for (size_t i = 0, j = valueLen - 1; i < j; i++, j--) {
|
||||||
@@ -223,9 +224,9 @@ void fmt_PrintNumber(char *buf, size_t bufLen, struct FormatSpec const *fmt, uin
|
|||||||
valueBuf[j] = c;
|
valueBuf[j] = c;
|
||||||
}
|
}
|
||||||
} else if (fmt->type == 'f') {
|
} else if (fmt->type == 'f') {
|
||||||
/* Special case for fixed-point */
|
// Special case for fixed-point
|
||||||
|
|
||||||
/* Default fractional width (C's is 6 for "%f"; here 5 is enough) */
|
// Default fractional width (C's is 6 for "%f"; here 5 is enough for Q16.16)
|
||||||
size_t fracWidth = fmt->hasFrac ? fmt->fracWidth : 5;
|
size_t fracWidth = fmt->hasFrac ? fmt->fracWidth : 5;
|
||||||
|
|
||||||
if (fracWidth > 255) {
|
if (fracWidth > 255) {
|
||||||
@@ -234,7 +235,8 @@ void fmt_PrintNumber(char *buf, size_t bufLen, struct FormatSpec const *fmt, uin
|
|||||||
fracWidth = 255;
|
fracWidth = 255;
|
||||||
}
|
}
|
||||||
|
|
||||||
snprintf(valueBuf, sizeof(valueBuf), "%.*f", (int)fracWidth, value / 65536.0);
|
snprintf(valueBuf, sizeof(valueBuf), "%.*f", (int)fracWidth,
|
||||||
|
value / fix_PrecisionFactor());
|
||||||
} else {
|
} else {
|
||||||
char const *spec = fmt->type == 'd' ? "%" PRId32
|
char const *spec = fmt->type == 'd' ? "%" PRId32
|
||||||
: fmt->type == 'u' ? "%" PRIu32
|
: fmt->type == 'u' ? "%" PRIu32
|
||||||
@@ -247,10 +249,10 @@ void fmt_PrintNumber(char *buf, size_t bufLen, struct FormatSpec const *fmt, uin
|
|||||||
}
|
}
|
||||||
|
|
||||||
size_t len = strlen(valueBuf);
|
size_t len = strlen(valueBuf);
|
||||||
size_t numLen = !!sign + !!prefix + len;
|
size_t numLen = (sign != 0) + (prefix != 0) + len;
|
||||||
size_t totalLen = fmt->width > numLen ? fmt->width : numLen;
|
size_t totalLen = fmt->width > numLen ? fmt->width : numLen;
|
||||||
|
|
||||||
if (totalLen > bufLen - 1) { /* bufLen includes terminator */
|
if (totalLen > bufLen - 1) { // bufLen includes terminator
|
||||||
error("Formatted numeric value too long\n");
|
error("Formatted numeric value too long\n");
|
||||||
totalLen = bufLen - 1;
|
totalLen = bufLen - 1;
|
||||||
if (numLen > totalLen) {
|
if (numLen > totalLen) {
|
||||||
@@ -273,7 +275,7 @@ void fmt_PrintNumber(char *buf, size_t bufLen, struct FormatSpec const *fmt, uin
|
|||||||
buf[i] = ' ';
|
buf[i] = ' ';
|
||||||
} else {
|
} else {
|
||||||
if (fmt->padZero) {
|
if (fmt->padZero) {
|
||||||
/* sign, then prefix, then zero padding */
|
// sign, then prefix, then zero padding
|
||||||
if (sign)
|
if (sign)
|
||||||
buf[pos++] = sign;
|
buf[pos++] = sign;
|
||||||
if (prefix)
|
if (prefix)
|
||||||
@@ -281,7 +283,7 @@ void fmt_PrintNumber(char *buf, size_t bufLen, struct FormatSpec const *fmt, uin
|
|||||||
for (size_t i = 0; i < padLen; i++)
|
for (size_t i = 0; i < padLen; i++)
|
||||||
buf[pos++] = '0';
|
buf[pos++] = '0';
|
||||||
} else {
|
} else {
|
||||||
/* space padding, then sign, then prefix */
|
// space padding, then sign, then prefix
|
||||||
for (size_t i = 0; i < padLen; i++)
|
for (size_t i = 0; i < padLen; i++)
|
||||||
buf[pos++] = ' ';
|
buf[pos++] = ' ';
|
||||||
if (sign)
|
if (sign)
|
||||||
|
|||||||
150
src/asm/fstack.c
150
src/asm/fstack.c
@@ -19,7 +19,8 @@
|
|||||||
#include "asm/main.h"
|
#include "asm/main.h"
|
||||||
#include "asm/symbol.h"
|
#include "asm/symbol.h"
|
||||||
#include "asm/warning.h"
|
#include "asm/warning.h"
|
||||||
#include "platform.h" /* S_ISDIR (stat macro) */
|
#include "error.h"
|
||||||
|
#include "platform.h" // S_ISDIR (stat macro)
|
||||||
|
|
||||||
#define MAXINCPATHS 128
|
#define MAXINCPATHS 128
|
||||||
|
|
||||||
@@ -28,7 +29,7 @@ struct Context {
|
|||||||
struct FileStackNode *fileInfo;
|
struct FileStackNode *fileInfo;
|
||||||
struct LexerState *lexerState;
|
struct LexerState *lexerState;
|
||||||
uint32_t uniqueID;
|
uint32_t uniqueID;
|
||||||
struct MacroArgs *macroArgs; /* Macro args are *saved* here */
|
struct MacroArgs *macroArgs; // Macro args are *saved* here
|
||||||
uint32_t nbReptIters;
|
uint32_t nbReptIters;
|
||||||
int32_t forValue;
|
int32_t forValue;
|
||||||
int32_t forStep;
|
int32_t forStep;
|
||||||
@@ -37,18 +38,19 @@ struct Context {
|
|||||||
|
|
||||||
static struct Context *contextStack;
|
static struct Context *contextStack;
|
||||||
static size_t contextDepth = 0;
|
static size_t contextDepth = 0;
|
||||||
#define DEFAULT_MAX_DEPTH 64
|
|
||||||
size_t maxRecursionDepth;
|
size_t maxRecursionDepth;
|
||||||
|
|
||||||
static unsigned int nbIncPaths = 0;
|
static unsigned int nbIncPaths = 0;
|
||||||
static char const *includePaths[MAXINCPATHS];
|
static char const *includePaths[MAXINCPATHS];
|
||||||
|
|
||||||
|
static const char *preIncludeName;
|
||||||
|
|
||||||
static const char *dumpNodeAndParents(struct FileStackNode const *node)
|
static const char *dumpNodeAndParents(struct FileStackNode const *node)
|
||||||
{
|
{
|
||||||
char const *name;
|
char const *name;
|
||||||
|
|
||||||
if (node->type == NODE_REPT) {
|
if (node->type == NODE_REPT) {
|
||||||
assert(node->parent); /* REPT nodes should always have a parent */
|
assert(node->parent); // REPT nodes should always have a parent
|
||||||
struct FileStackReptNode const *reptInfo = (struct FileStackReptNode const *)node;
|
struct FileStackReptNode const *reptInfo = (struct FileStackReptNode const *)node;
|
||||||
|
|
||||||
name = dumpNodeAndParents(node->parent);
|
name = dumpNodeAndParents(node->parent);
|
||||||
@@ -89,7 +91,7 @@ struct FileStackNode *fstk_GetFileStack(void)
|
|||||||
|
|
||||||
struct FileStackNode *node = contextStack->fileInfo;
|
struct FileStackNode *node = contextStack->fileInfo;
|
||||||
|
|
||||||
/* Mark node and all of its parents as referenced if not already so they don't get freed */
|
// Mark node and all of its parents as referenced if not already so they don't get freed
|
||||||
while (node && !node->referenced) {
|
while (node && !node->referenced) {
|
||||||
node->ID = -1;
|
node->ID = -1;
|
||||||
node->referenced = true;
|
node->referenced = true;
|
||||||
@@ -100,7 +102,7 @@ struct FileStackNode *fstk_GetFileStack(void)
|
|||||||
|
|
||||||
char const *fstk_GetFileName(void)
|
char const *fstk_GetFileName(void)
|
||||||
{
|
{
|
||||||
/* Iterating via the nodes themselves skips nested REPTs */
|
// Iterating via the nodes themselves skips nested REPTs
|
||||||
struct FileStackNode const *node = contextStack->fileInfo;
|
struct FileStackNode const *node = contextStack->fileInfo;
|
||||||
|
|
||||||
while (node->type != NODE_FILE)
|
while (node->type != NODE_FILE)
|
||||||
@@ -121,7 +123,7 @@ void fstk_AddIncludePath(char const *path)
|
|||||||
char *str = malloc(allocSize);
|
char *str = malloc(allocSize);
|
||||||
|
|
||||||
if (!str) {
|
if (!str) {
|
||||||
/* Attempt to continue without that path */
|
// Attempt to continue without that path
|
||||||
error("Failed to allocate new include path: %s\n", strerror(errno));
|
error("Failed to allocate new include path: %s\n", strerror(errno));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -134,6 +136,15 @@ void fstk_AddIncludePath(char const *path)
|
|||||||
includePaths[nbIncPaths++] = str;
|
includePaths[nbIncPaths++] = str;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void fstk_SetPreIncludeFile(char const *path)
|
||||||
|
{
|
||||||
|
if (preIncludeName)
|
||||||
|
warnx("Overriding pre-included filename %s", preIncludeName);
|
||||||
|
preIncludeName = path;
|
||||||
|
if (verbose)
|
||||||
|
printf("Pre-included filename %s\n", preIncludeName);
|
||||||
|
}
|
||||||
|
|
||||||
static void printDep(char const *path)
|
static void printDep(char const *path)
|
||||||
{
|
{
|
||||||
if (dependfile) {
|
if (dependfile) {
|
||||||
@@ -150,14 +161,14 @@ static bool isPathValid(char const *path)
|
|||||||
if (stat(path, &statbuf) != 0)
|
if (stat(path, &statbuf) != 0)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
/* Reject directories */
|
// Reject directories
|
||||||
return !S_ISDIR(statbuf.st_mode);
|
return !S_ISDIR(statbuf.st_mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool fstk_FindFile(char const *path, char **fullPath, size_t *size)
|
bool fstk_FindFile(char const *path, char **fullPath, size_t *size)
|
||||||
{
|
{
|
||||||
if (!*size) {
|
if (!*size) {
|
||||||
*size = 64; /* This is arbitrary, really */
|
*size = 64; // This is arbitrary, really
|
||||||
*fullPath = realloc(*fullPath, *size);
|
*fullPath = realloc(*fullPath, *size);
|
||||||
if (!*fullPath)
|
if (!*fullPath)
|
||||||
error("realloc error during include path search: %s\n",
|
error("realloc error during include path search: %s\n",
|
||||||
@@ -175,8 +186,8 @@ bool fstk_FindFile(char const *path, char **fullPath, size_t *size)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Oh how I wish `asnprintf` was standard... */
|
// Oh how I wish `asnprintf` was standard...
|
||||||
if ((size_t)len >= *size) { /* `len` doesn't include the terminator, `size` does */
|
if ((size_t)len >= *size) { // `size` includes the terminator, `len` doesn't
|
||||||
*size = len + 1;
|
*size = len + 1;
|
||||||
*fullPath = realloc(*fullPath, *size);
|
*fullPath = realloc(*fullPath, *size);
|
||||||
if (!*fullPath) {
|
if (!*fullPath) {
|
||||||
@@ -213,17 +224,18 @@ bool yywrap(void)
|
|||||||
fatalerror("Ended block with %" PRIu32 " unterminated IF construct%s\n",
|
fatalerror("Ended block with %" PRIu32 " unterminated IF construct%s\n",
|
||||||
ifDepth, ifDepth == 1 ? "" : "s");
|
ifDepth, ifDepth == 1 ? "" : "s");
|
||||||
|
|
||||||
if (contextStack->fileInfo->type == NODE_REPT) { /* The context is a REPT block, which may loop */
|
if (contextStack->fileInfo->type == NODE_REPT) {
|
||||||
|
// The context is a REPT or FOR block, which may loop
|
||||||
struct FileStackReptNode *fileInfo = (struct FileStackReptNode *)contextStack->fileInfo;
|
struct FileStackReptNode *fileInfo = (struct FileStackReptNode *)contextStack->fileInfo;
|
||||||
|
|
||||||
/* If the node is referenced, we can't edit it; duplicate it */
|
// If the node is referenced, we can't edit it; duplicate it
|
||||||
if (contextStack->fileInfo->referenced) {
|
if (contextStack->fileInfo->referenced) {
|
||||||
size_t size = sizeof(*fileInfo) + sizeof(fileInfo->iters[0]) * fileInfo->reptDepth;
|
size_t size = sizeof(*fileInfo) + sizeof(fileInfo->iters[0]) * fileInfo->reptDepth;
|
||||||
struct FileStackReptNode *copy = malloc(size);
|
struct FileStackReptNode *copy = malloc(size);
|
||||||
|
|
||||||
if (!copy)
|
if (!copy)
|
||||||
fatalerror("Failed to duplicate REPT file node: %s\n", strerror(errno));
|
fatalerror("Failed to duplicate REPT file node: %s\n", strerror(errno));
|
||||||
/* Copy all info but the referencing */
|
// Copy all info but the referencing
|
||||||
memcpy(copy, fileInfo, size);
|
memcpy(copy, fileInfo, size);
|
||||||
copy->node.next = NULL;
|
copy->node.next = NULL;
|
||||||
copy->node.referenced = false;
|
copy->node.referenced = false;
|
||||||
@@ -232,19 +244,19 @@ bool yywrap(void)
|
|||||||
contextStack->fileInfo = (struct FileStackNode *)fileInfo;
|
contextStack->fileInfo = (struct FileStackNode *)fileInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* If this is a FOR, update the symbol value */
|
// If this is a FOR, update the symbol value
|
||||||
if (contextStack->forName && fileInfo->iters[0] <= contextStack->nbReptIters) {
|
if (contextStack->forName && fileInfo->iters[0] <= contextStack->nbReptIters) {
|
||||||
contextStack->forValue += contextStack->forStep;
|
contextStack->forValue += contextStack->forStep;
|
||||||
struct Symbol *sym = sym_AddVar(contextStack->forName,
|
struct Symbol *sym = sym_AddVar(contextStack->forName,
|
||||||
contextStack->forValue);
|
contextStack->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
|
||||||
fileInfo->iters[0]++;
|
fileInfo->iters[0]++;
|
||||||
/* If this wasn't the last iteration, wrap instead of popping */
|
// If this wasn't the last iteration, wrap instead of popping
|
||||||
if (fileInfo->iters[0] <= contextStack->nbReptIters) {
|
if (fileInfo->iters[0] <= contextStack->nbReptIters) {
|
||||||
lexer_RestartRept(contextStack->fileInfo->lineNo);
|
lexer_RestartRept(contextStack->fileInfo->lineNo);
|
||||||
contextStack->uniqueID = macro_UseNewUniqueID();
|
contextStack->uniqueID = macro_UseNewUniqueID();
|
||||||
@@ -261,46 +273,47 @@ bool yywrap(void)
|
|||||||
contextDepth--;
|
contextDepth--;
|
||||||
|
|
||||||
lexer_DeleteState(context->lexerState);
|
lexer_DeleteState(context->lexerState);
|
||||||
/* Restore args if a macro (not REPT) saved them */
|
// Restore args if a macro (not REPT) saved them
|
||||||
if (context->fileInfo->type == NODE_MACRO)
|
if (context->fileInfo->type == NODE_MACRO)
|
||||||
macro_UseNewArgs(contextStack->macroArgs);
|
macro_UseNewArgs(contextStack->macroArgs);
|
||||||
/* Free the file stack node */
|
// Free the file stack node
|
||||||
if (!context->fileInfo->referenced)
|
if (!context->fileInfo->referenced)
|
||||||
free(context->fileInfo);
|
free(context->fileInfo);
|
||||||
/* Free the FOR symbol name */
|
// Free the FOR symbol name
|
||||||
free(context->forName);
|
free(context->forName);
|
||||||
/* Free the entry and make its parent the current entry */
|
// Free the entry and make its parent the current entry
|
||||||
free(context);
|
free(context);
|
||||||
|
|
||||||
lexer_SetState(contextStack->lexerState);
|
lexer_SetState(contextStack->lexerState);
|
||||||
macro_SetUniqueID(contextStack->uniqueID);
|
macro_SetUniqueID(contextStack->uniqueID);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Make sure not to switch the lexer state before calling this, so the saved line no is correct.
|
||||||
* Make sure not to switch the lexer state before calling this, so the saved line no is correct
|
// BE CAREFUL! This modifies the file stack directly, you should have set up the file info first.
|
||||||
* BE CAREFUL!! This modifies the file stack directly, you should have set up the file info first
|
// Callers should set contextStack->lexerState after this so it is not NULL.
|
||||||
* Callers should set contextStack->lexerState after this so it is not NULL
|
|
||||||
*/
|
|
||||||
static void newContext(struct FileStackNode *fileInfo)
|
static void newContext(struct FileStackNode *fileInfo)
|
||||||
{
|
{
|
||||||
++contextDepth;
|
++contextDepth;
|
||||||
fstk_NewRecursionDepth(maxRecursionDepth); // Only checks if the max depth was exceeded
|
fstk_NewRecursionDepth(maxRecursionDepth); // Only checks if the max depth was exceeded
|
||||||
|
|
||||||
|
// Save the current `\@` value, to be restored when this context ends
|
||||||
|
contextStack->uniqueID = macro_GetUniqueID();
|
||||||
|
|
||||||
struct Context *context = malloc(sizeof(*context));
|
struct Context *context = malloc(sizeof(*context));
|
||||||
|
|
||||||
if (!context)
|
if (!context)
|
||||||
fatalerror("Failed to allocate memory for new context: %s\n", strerror(errno));
|
fatalerror("Failed to allocate memory for new context: %s\n", strerror(errno));
|
||||||
fileInfo->parent = contextStack->fileInfo;
|
fileInfo->parent = contextStack->fileInfo;
|
||||||
fileInfo->lineNo = 0; /* Init to a default value, see struct definition for info */
|
fileInfo->lineNo = 0; // Init to a default value, see struct definition for info
|
||||||
fileInfo->referenced = false;
|
fileInfo->referenced = false;
|
||||||
fileInfo->lineNo = lexer_GetLineNo();
|
fileInfo->lineNo = lexer_GetLineNo();
|
||||||
context->fileInfo = fileInfo;
|
context->fileInfo = fileInfo;
|
||||||
context->forName = NULL;
|
context->forName = NULL;
|
||||||
/*
|
|
||||||
* Link new entry to its parent so it's reachable later
|
// Link new entry to its parent so it's reachable later
|
||||||
* ERRORS SHOULD NOT OCCUR AFTER THIS!!
|
// ERRORS SHOULD NOT OCCUR AFTER THIS!
|
||||||
*/
|
|
||||||
context->parent = contextStack;
|
context->parent = contextStack;
|
||||||
contextStack = context;
|
contextStack = context;
|
||||||
}
|
}
|
||||||
@@ -338,9 +351,43 @@ void fstk_RunInclude(char const *path)
|
|||||||
if (!contextStack->lexerState)
|
if (!contextStack->lexerState)
|
||||||
fatalerror("Failed to set up lexer for file include\n");
|
fatalerror("Failed to set up lexer for file include\n");
|
||||||
lexer_SetStateAtEOL(contextStack->lexerState);
|
lexer_SetStateAtEOL(contextStack->lexerState);
|
||||||
/* We're back at top-level, so most things are reset */
|
// We're back at top-level, so most things are reset
|
||||||
contextStack->uniqueID = 0;
|
contextStack->uniqueID = macro_UndefUniqueID();
|
||||||
macro_SetUniqueID(0);
|
}
|
||||||
|
|
||||||
|
// Similar to `fstk_RunInclude`, but not subject to `-MG`, and
|
||||||
|
// calling `lexer_SetState` instead of `lexer_SetStateAtEOL`.
|
||||||
|
static void runPreIncludeFile(void)
|
||||||
|
{
|
||||||
|
if (!preIncludeName)
|
||||||
|
return;
|
||||||
|
|
||||||
|
char *fullPath = NULL;
|
||||||
|
size_t size = 0;
|
||||||
|
|
||||||
|
if (!fstk_FindFile(preIncludeName, &fullPath, &size)) {
|
||||||
|
free(fullPath);
|
||||||
|
error("Unable to open included file '%s': %s\n", preIncludeName, strerror(errno));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FileStackNamedNode *fileInfo = malloc(sizeof(*fileInfo) + size);
|
||||||
|
|
||||||
|
if (!fileInfo) {
|
||||||
|
error("Failed to alloc file info for pre-include: %s\n", strerror(errno));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fileInfo->node.type = NODE_FILE;
|
||||||
|
strcpy(fileInfo->name, fullPath);
|
||||||
|
free(fullPath);
|
||||||
|
|
||||||
|
newContext((struct FileStackNode *)fileInfo);
|
||||||
|
contextStack->lexerState = lexer_OpenFile(fileInfo->name);
|
||||||
|
if (!contextStack->lexerState)
|
||||||
|
fatalerror("Failed to set up lexer for file include\n");
|
||||||
|
lexer_SetState(contextStack->lexerState);
|
||||||
|
// We're back at top-level, so most things are reset
|
||||||
|
contextStack->uniqueID = macro_UndefUniqueID();
|
||||||
}
|
}
|
||||||
|
|
||||||
void fstk_RunMacro(char const *macroName, struct MacroArgs *args)
|
void fstk_RunMacro(char const *macroName, struct MacroArgs *args)
|
||||||
@@ -357,16 +404,16 @@ void fstk_RunMacro(char const *macroName, struct MacroArgs *args)
|
|||||||
}
|
}
|
||||||
contextStack->macroArgs = macro_GetCurrentArgs();
|
contextStack->macroArgs = macro_GetCurrentArgs();
|
||||||
|
|
||||||
/* Compute total length of this node's name: <base name>::<macro> */
|
// Compute total length of this node's name: <base name>::<macro>
|
||||||
size_t reptNameLen = 0;
|
size_t reptNameLen = 0;
|
||||||
struct FileStackNode const *node = macro->src;
|
struct FileStackNode const *node = macro->src;
|
||||||
|
|
||||||
if (node->type == NODE_REPT) {
|
if (node->type == NODE_REPT) {
|
||||||
struct FileStackReptNode const *reptNode = (struct FileStackReptNode const *)node;
|
struct FileStackReptNode const *reptNode = (struct FileStackReptNode const *)node;
|
||||||
|
|
||||||
/* 4294967295 = 2^32 - 1, aka UINT32_MAX */
|
// 4294967295 = 2^32 - 1, aka UINT32_MAX
|
||||||
reptNameLen += reptNode->reptDepth * strlen("::REPT~4294967295");
|
reptNameLen += reptNode->reptDepth * strlen("::REPT~4294967295");
|
||||||
/* Look for next named node */
|
// Look for next named node
|
||||||
do {
|
do {
|
||||||
node = node->parent;
|
node = node->parent;
|
||||||
} while (node->type == NODE_REPT);
|
} while (node->type == NODE_REPT);
|
||||||
@@ -382,7 +429,7 @@ void fstk_RunMacro(char const *macroName, struct MacroArgs *args)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
fileInfo->node.type = NODE_MACRO;
|
fileInfo->node.type = NODE_MACRO;
|
||||||
/* Print the name... */
|
// Print the name...
|
||||||
char *dest = fileInfo->name;
|
char *dest = fileInfo->name;
|
||||||
|
|
||||||
memcpy(dest, baseNode->name, baseLen);
|
memcpy(dest, baseNode->name, baseLen);
|
||||||
@@ -429,13 +476,13 @@ static bool newReptContext(int32_t reptLineNo, char *body, size_t size)
|
|||||||
fileInfo->reptDepth = reptDepth + 1;
|
fileInfo->reptDepth = reptDepth + 1;
|
||||||
fileInfo->iters[0] = 1;
|
fileInfo->iters[0] = 1;
|
||||||
if (reptDepth)
|
if (reptDepth)
|
||||||
/* Copy all parent iter counts */
|
// Copy all parent iter counts
|
||||||
memcpy(&fileInfo->iters[1],
|
memcpy(&fileInfo->iters[1],
|
||||||
((struct FileStackReptNode *)contextStack->fileInfo)->iters,
|
((struct FileStackReptNode *)contextStack->fileInfo)->iters,
|
||||||
reptDepth * sizeof(fileInfo->iters[0]));
|
reptDepth * sizeof(fileInfo->iters[0]));
|
||||||
|
|
||||||
newContext((struct FileStackNode *)fileInfo);
|
newContext((struct FileStackNode *)fileInfo);
|
||||||
/* Correct our line number, which currently points to the `ENDR` line */
|
// Correct our line number, which currently points to the `ENDR` line
|
||||||
contextStack->fileInfo->lineNo = reptLineNo;
|
contextStack->fileInfo->lineNo = reptLineNo;
|
||||||
|
|
||||||
contextStack->lexerState = lexer_OpenFileView("REPT", body, size, reptLineNo);
|
contextStack->lexerState = lexer_OpenFileView("REPT", body, size, reptLineNo);
|
||||||
@@ -493,7 +540,7 @@ void fstk_RunFor(char const *symName, int32_t start, int32_t stop, int32_t step,
|
|||||||
|
|
||||||
void fstk_StopRept(void)
|
void fstk_StopRept(void)
|
||||||
{
|
{
|
||||||
/* Prevent more iterations */
|
// Prevent more iterations
|
||||||
contextStack->nbReptIters = 0;
|
contextStack->nbReptIters = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -510,7 +557,7 @@ bool fstk_Break(void)
|
|||||||
|
|
||||||
void fstk_NewRecursionDepth(size_t newDepth)
|
void fstk_NewRecursionDepth(size_t newDepth)
|
||||||
{
|
{
|
||||||
if (contextDepth >= newDepth)
|
if (contextDepth > newDepth)
|
||||||
fatalerror("Recursion limit (%zu) exceeded\n", newDepth);
|
fatalerror("Recursion limit (%zu) exceeded\n", newDepth);
|
||||||
maxRecursionDepth = newDepth;
|
maxRecursionDepth = newDepth;
|
||||||
}
|
}
|
||||||
@@ -533,7 +580,7 @@ void fstk_Init(char const *mainPath, size_t maxDepth)
|
|||||||
fatalerror("Failed to allocate memory for main file info: %s\n", strerror(errno));
|
fatalerror("Failed to allocate memory for main file info: %s\n", strerror(errno));
|
||||||
|
|
||||||
context->fileInfo = (struct FileStackNode *)fileInfo;
|
context->fileInfo = (struct FileStackNode *)fileInfo;
|
||||||
/* lineNo and reptIter are unused on the top-level context */
|
// lineNo and reptIter are unused on the top-level context
|
||||||
context->fileInfo->parent = NULL;
|
context->fileInfo->parent = NULL;
|
||||||
context->fileInfo->lineNo = 0; // This still gets written to the object file, so init it
|
context->fileInfo->lineNo = 0; // This still gets written to the object file, so init it
|
||||||
context->fileInfo->referenced = false;
|
context->fileInfo->referenced = false;
|
||||||
@@ -542,20 +589,17 @@ void fstk_Init(char const *mainPath, size_t maxDepth)
|
|||||||
|
|
||||||
context->parent = NULL;
|
context->parent = NULL;
|
||||||
context->lexerState = state;
|
context->lexerState = state;
|
||||||
context->uniqueID = 0;
|
context->uniqueID = macro_UndefUniqueID();
|
||||||
macro_SetUniqueID(0);
|
|
||||||
context->nbReptIters = 0;
|
context->nbReptIters = 0;
|
||||||
context->forValue = 0;
|
context->forValue = 0;
|
||||||
context->forStep = 0;
|
context->forStep = 0;
|
||||||
context->forName = NULL;
|
context->forName = NULL;
|
||||||
|
|
||||||
/* Now that it's set up properly, register the context */
|
// Now that it's set up properly, register the context
|
||||||
contextStack = context;
|
contextStack = context;
|
||||||
|
|
||||||
/*
|
// Check that max recursion depth won't allow overflowing node `malloc`s
|
||||||
* Check that max recursion depth won't allow overflowing node `malloc`s
|
// This assumes that the rept node is larger
|
||||||
* This assumes that the rept node is larger
|
|
||||||
*/
|
|
||||||
#define DEPTH_LIMIT ((SIZE_MAX - sizeof(struct FileStackReptNode)) / sizeof(uint32_t))
|
#define DEPTH_LIMIT ((SIZE_MAX - sizeof(struct FileStackReptNode)) / sizeof(uint32_t))
|
||||||
if (maxDepth > DEPTH_LIMIT) {
|
if (maxDepth > DEPTH_LIMIT) {
|
||||||
error("Recursion depth may not be higher than %zu, defaulting to "
|
error("Recursion depth may not be higher than %zu, defaulting to "
|
||||||
@@ -564,7 +608,9 @@ void fstk_Init(char const *mainPath, size_t maxDepth)
|
|||||||
} else {
|
} else {
|
||||||
maxRecursionDepth = maxDepth;
|
maxRecursionDepth = maxDepth;
|
||||||
}
|
}
|
||||||
/* Make sure that the default of 64 is OK, though */
|
// Make sure that the default of 64 is OK, though
|
||||||
assert(DEPTH_LIMIT >= DEFAULT_MAX_DEPTH);
|
assert(DEPTH_LIMIT >= DEFAULT_MAX_DEPTH);
|
||||||
#undef DEPTH_LIMIT
|
#undef DEPTH_LIMIT
|
||||||
|
|
||||||
|
runPreIncludeFile();
|
||||||
}
|
}
|
||||||
|
|||||||
603
src/asm/lexer.c
603
src/asm/lexer.c
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,11 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of RGBDS.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2022, Eldred Habert and RGBDS contributors.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
#include <assert.h>
|
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
@@ -12,13 +18,11 @@
|
|||||||
|
|
||||||
#define MAXMACROARGS 99999
|
#define MAXMACROARGS 99999
|
||||||
|
|
||||||
/*
|
// Your average macro invocation does not go past the tens, but some go further
|
||||||
* Your average macro invocation does not go past the tens, but some go further
|
// This ensures that sane and slightly insane invocations suffer no penalties,
|
||||||
* This ensures that sane and slightly insane invocations suffer no penalties,
|
// and the rest is insane and thus will assume responsibility.
|
||||||
* and the rest is insane and thus will assume responsibility.
|
// Additionally, ~300 bytes (on x64) of memory per level of nesting has been
|
||||||
* Additionally, ~300 bytes (on x64) of memory per level of nesting has been
|
// deemed reasonable. (Halve that on x86.)
|
||||||
* deemed reasonable. (Halve that on x86.)
|
|
||||||
*/
|
|
||||||
#define INITIAL_ARG_SIZE 32
|
#define INITIAL_ARG_SIZE 32
|
||||||
struct MacroArgs {
|
struct MacroArgs {
|
||||||
unsigned int nbArgs;
|
unsigned int nbArgs;
|
||||||
@@ -33,11 +37,9 @@ struct MacroArgs {
|
|||||||
static struct MacroArgs *macroArgs = NULL;
|
static struct MacroArgs *macroArgs = NULL;
|
||||||
static uint32_t uniqueID = 0;
|
static uint32_t uniqueID = 0;
|
||||||
static uint32_t maxUniqueID = 0;
|
static uint32_t maxUniqueID = 0;
|
||||||
/*
|
// The initialization is somewhat harmful, since it is never used, but it
|
||||||
* The initialization is somewhat harmful, since it is never used, but it
|
// guarantees the size of the buffer will be correct. I was unable to find a
|
||||||
* guarantees the size of the buffer will be correct. I was unable to find a
|
// better solution, but if you have one, please feel free!
|
||||||
* better solution, but if you have one, please feel free!
|
|
||||||
*/
|
|
||||||
static char uniqueIDBuf[] = "_u4294967295"; // UINT32_MAX
|
static char uniqueIDBuf[] = "_u4294967295"; // UINT32_MAX
|
||||||
static char *uniqueIDPtr = NULL;
|
static char *uniqueIDPtr = NULL;
|
||||||
|
|
||||||
@@ -68,7 +70,7 @@ void macro_AppendArg(struct MacroArgs **argPtr, char *s)
|
|||||||
error("A maximum of " EXPAND_AND_STR(MAXMACROARGS) " arguments is allowed\n");
|
error("A maximum of " EXPAND_AND_STR(MAXMACROARGS) " arguments is allowed\n");
|
||||||
if (macArgs->nbArgs >= macArgs->capacity) {
|
if (macArgs->nbArgs >= macArgs->capacity) {
|
||||||
macArgs->capacity *= 2;
|
macArgs->capacity *= 2;
|
||||||
/* Check that overflow didn't roll us back */
|
// Check that overflow didn't roll us back
|
||||||
if (macArgs->capacity <= macArgs->nbArgs)
|
if (macArgs->capacity <= macArgs->nbArgs)
|
||||||
fatalerror("Failed to add new macro argument: capacity overflow\n");
|
fatalerror("Failed to add new macro argument: capacity overflow\n");
|
||||||
macArgs = realloc(macArgs, SIZEOF_ARGS(macArgs->capacity));
|
macArgs = realloc(macArgs, SIZEOF_ARGS(macArgs->capacity));
|
||||||
@@ -112,9 +114,9 @@ char const *macro_GetAllArgs(void)
|
|||||||
size_t len = 0;
|
size_t len = 0;
|
||||||
|
|
||||||
for (uint32_t i = macroArgs->shift; i < macroArgs->nbArgs; i++)
|
for (uint32_t i = macroArgs->shift; i < macroArgs->nbArgs; i++)
|
||||||
len += strlen(macroArgs->args[i]) + 1; /* 1 for comma */
|
len += strlen(macroArgs->args[i]) + 1; // 1 for comma
|
||||||
|
|
||||||
char *str = malloc(len + 1); /* 1 for '\0' */
|
char *str = malloc(len + 1); // 1 for '\0'
|
||||||
char *ptr = str;
|
char *ptr = str;
|
||||||
|
|
||||||
if (!str)
|
if (!str)
|
||||||
@@ -126,9 +128,9 @@ char const *macro_GetAllArgs(void)
|
|||||||
memcpy(ptr, macroArgs->args[i], n);
|
memcpy(ptr, macroArgs->args[i], n);
|
||||||
ptr += n;
|
ptr += n;
|
||||||
|
|
||||||
/* Commas go between args and after a last empty arg */
|
// Commas go between args and after a last empty arg
|
||||||
if (i < macroArgs->nbArgs - 1 || n == 0)
|
if (i < macroArgs->nbArgs - 1 || n == 0)
|
||||||
*ptr++ = ','; /* no space after comma */
|
*ptr++ = ','; // no space after comma
|
||||||
}
|
}
|
||||||
*ptr = '\0';
|
*ptr = '\0';
|
||||||
|
|
||||||
@@ -142,19 +144,21 @@ uint32_t macro_GetUniqueID(void)
|
|||||||
|
|
||||||
char const *macro_GetUniqueIDStr(void)
|
char const *macro_GetUniqueIDStr(void)
|
||||||
{
|
{
|
||||||
|
// Generate a new unique ID on the first use of `\@`
|
||||||
|
if (uniqueID == 0)
|
||||||
|
macro_SetUniqueID(++maxUniqueID);
|
||||||
|
|
||||||
return uniqueIDPtr;
|
return uniqueIDPtr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void macro_SetUniqueID(uint32_t id)
|
void macro_SetUniqueID(uint32_t id)
|
||||||
{
|
{
|
||||||
uniqueID = id;
|
uniqueID = id;
|
||||||
if (id == 0) {
|
if (id == 0 || id == (uint32_t)-1) {
|
||||||
uniqueIDPtr = NULL;
|
uniqueIDPtr = NULL;
|
||||||
} else {
|
} else {
|
||||||
if (uniqueID > maxUniqueID)
|
// The buffer is guaranteed to be the correct size
|
||||||
maxUniqueID = uniqueID;
|
// This is a valid label fragment, but not a valid numeric
|
||||||
/* The buffer is guaranteed to be the correct size */
|
|
||||||
/* This is a valid label fragment, but not a valid numeric */
|
|
||||||
sprintf(uniqueIDBuf, "_u%" PRIu32, id);
|
sprintf(uniqueIDBuf, "_u%" PRIu32, id);
|
||||||
uniqueIDPtr = uniqueIDBuf;
|
uniqueIDPtr = uniqueIDBuf;
|
||||||
}
|
}
|
||||||
@@ -162,8 +166,16 @@ void macro_SetUniqueID(uint32_t id)
|
|||||||
|
|
||||||
uint32_t macro_UseNewUniqueID(void)
|
uint32_t macro_UseNewUniqueID(void)
|
||||||
{
|
{
|
||||||
macro_SetUniqueID(++maxUniqueID);
|
// A new ID will be generated on the first use of `\@`
|
||||||
return maxUniqueID;
|
macro_SetUniqueID(0);
|
||||||
|
return uniqueID;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t macro_UndefUniqueID(void)
|
||||||
|
{
|
||||||
|
// No ID will be generated; use of `\@` is an error
|
||||||
|
macro_SetUniqueID((uint32_t)-1);
|
||||||
|
return uniqueID;
|
||||||
}
|
}
|
||||||
|
|
||||||
void macro_ShiftCurrentArgs(int32_t count)
|
void macro_ShiftCurrentArgs(int32_t count)
|
||||||
|
|||||||
143
src/asm/main.c
143
src/asm/main.c
@@ -19,6 +19,7 @@
|
|||||||
#include <time.h>
|
#include <time.h>
|
||||||
|
|
||||||
#include "asm/charmap.h"
|
#include "asm/charmap.h"
|
||||||
|
#include "asm/fixpoint.h"
|
||||||
#include "asm/format.h"
|
#include "asm/format.h"
|
||||||
#include "asm/fstack.h"
|
#include "asm/fstack.h"
|
||||||
#include "asm/lexer.h"
|
#include "asm/lexer.h"
|
||||||
@@ -39,8 +40,8 @@
|
|||||||
#ifdef __clang__
|
#ifdef __clang__
|
||||||
#if __has_feature(address_sanitizer) && !defined(__SANITIZE_ADDRESS__)
|
#if __has_feature(address_sanitizer) && !defined(__SANITIZE_ADDRESS__)
|
||||||
#define __SANITIZE_ADDRESS__
|
#define __SANITIZE_ADDRESS__
|
||||||
#endif /* __has_feature(address_sanitizer) && !defined(__SANITIZE_ADDRESS__) */
|
#endif // __has_feature(address_sanitizer) && !defined(__SANITIZE_ADDRESS__)
|
||||||
#endif /* __clang__ */
|
#endif // __clang__
|
||||||
|
|
||||||
#ifdef __SANITIZE_ADDRESS__
|
#ifdef __SANITIZE_ADDRESS__
|
||||||
// There are known, non-trivial to fix leaks. We would still like to have `make develop'
|
// There are known, non-trivial to fix leaks. We would still like to have `make develop'
|
||||||
@@ -52,20 +53,20 @@ char const *__asan_default_options(void) { return "detect_leaks=0"; }
|
|||||||
// Unfortunately, macOS still ships 2.3, which is from 2008...
|
// Unfortunately, macOS still ships 2.3, which is from 2008...
|
||||||
int yyparse(void);
|
int yyparse(void);
|
||||||
|
|
||||||
FILE * dependfile;
|
FILE *dependfile = NULL;
|
||||||
bool generatedMissingIncludes;
|
bool generatedMissingIncludes = false;
|
||||||
bool failedOnMissingInclude;
|
bool failedOnMissingInclude = false;
|
||||||
bool generatePhonyDeps;
|
bool generatePhonyDeps = false;
|
||||||
char *targetFileName;
|
char *targetFileName = NULL;
|
||||||
|
|
||||||
bool haltnop;
|
bool haltnop;
|
||||||
bool warnOnHaltNop;
|
bool warnOnHaltNop;
|
||||||
bool optimizeLoads;
|
bool optimizeLoads;
|
||||||
bool warnOnLdOpt;
|
bool warnOnLdOpt;
|
||||||
bool verbose;
|
bool verbose;
|
||||||
bool warnings; /* True to enable warnings, false to disable them. */
|
bool warnings; // True to enable warnings, false to disable them.
|
||||||
|
|
||||||
/* Escapes Make-special chars from a string */
|
// Escapes Make-special chars from a string
|
||||||
static char *make_escape(char const *str)
|
static char *make_escape(char const *str)
|
||||||
{
|
{
|
||||||
char * const escaped_str = malloc(strlen(str) * 2 + 1);
|
char * const escaped_str = malloc(strlen(str) * 2 + 1);
|
||||||
@@ -75,7 +76,7 @@ static char *make_escape(char const *str)
|
|||||||
err("%s: Failed to allocate memory", __func__);
|
err("%s: Failed to allocate memory", __func__);
|
||||||
|
|
||||||
while (*str) {
|
while (*str) {
|
||||||
/* All dollars needs to be doubled */
|
// All dollars needs to be doubled
|
||||||
if (*str == '$')
|
if (*str == '$')
|
||||||
*dest++ = '$';
|
*dest++ = '$';
|
||||||
*dest++ = *str++;
|
*dest++ = *str++;
|
||||||
@@ -85,22 +86,20 @@ static char *make_escape(char const *str)
|
|||||||
return escaped_str;
|
return escaped_str;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Short options */
|
// Short options
|
||||||
static const char *optstring = "b:D:Eg:Hhi:LlM:o:p:r:VvW:w";
|
static const char *optstring = "b:D:Eg:Hhi:I:LlM:o:P:p:Q:r:VvW:w";
|
||||||
|
|
||||||
/* 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 struct option const longopts[] = {
|
static struct option const longopts[] = {
|
||||||
{ "binary-digits", required_argument, NULL, 'b' },
|
{ "binary-digits", required_argument, NULL, 'b' },
|
||||||
{ "define", required_argument, NULL, 'D' },
|
{ "define", required_argument, NULL, 'D' },
|
||||||
@@ -108,7 +107,7 @@ static struct option const longopts[] = {
|
|||||||
{ "gfx-chars", required_argument, NULL, 'g' },
|
{ "gfx-chars", required_argument, NULL, 'g' },
|
||||||
{ "nop-after-halt", no_argument, NULL, 'H' },
|
{ "nop-after-halt", no_argument, NULL, 'H' },
|
||||||
{ "halt-without-nop", no_argument, NULL, 'h' },
|
{ "halt-without-nop", no_argument, NULL, 'h' },
|
||||||
{ "include", required_argument, NULL, 'i' },
|
{ "include", required_argument, NULL, 'I' },
|
||||||
{ "preserve-ld", no_argument, NULL, 'L' },
|
{ "preserve-ld", no_argument, NULL, 'L' },
|
||||||
{ "auto-ldh", no_argument, NULL, 'l' },
|
{ "auto-ldh", no_argument, NULL, 'l' },
|
||||||
{ "dependfile", required_argument, NULL, 'M' },
|
{ "dependfile", required_argument, NULL, 'M' },
|
||||||
@@ -117,7 +116,9 @@ static struct option const longopts[] = {
|
|||||||
{ "MT", required_argument, &depType, 'T' },
|
{ "MT", required_argument, &depType, 'T' },
|
||||||
{ "MQ", required_argument, &depType, 'Q' },
|
{ "MQ", required_argument, &depType, 'Q' },
|
||||||
{ "output", required_argument, NULL, 'o' },
|
{ "output", required_argument, NULL, 'o' },
|
||||||
|
{ "preinclude", required_argument, NULL, 'P' },
|
||||||
{ "pad-value", required_argument, NULL, 'p' },
|
{ "pad-value", required_argument, NULL, 'p' },
|
||||||
|
{ "q-precision", required_argument, NULL, 'Q' },
|
||||||
{ "recursion-depth", required_argument, NULL, 'r' },
|
{ "recursion-depth", required_argument, NULL, 'r' },
|
||||||
{ "version", no_argument, NULL, 'V' },
|
{ "version", no_argument, NULL, 'V' },
|
||||||
{ "verbose", no_argument, NULL, 'v' },
|
{ "verbose", no_argument, NULL, 'v' },
|
||||||
@@ -128,9 +129,10 @@ static struct option const longopts[] = {
|
|||||||
static void print_usage(void)
|
static void print_usage(void)
|
||||||
{
|
{
|
||||||
fputs(
|
fputs(
|
||||||
"Usage: rgbasm [-EhLVvw] [-b chars] [-D name[=value]] [-g chars] [-i path]\n"
|
"Usage: rgbasm [-EHhLlVvw] [-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 pad_value] [-r depth] [-W warning] <file>\n"
|
" [-o out_file] [-P include_file] [-p pad_value] [-Q precision]\n"
|
||||||
|
" [-r depth] [-W warning] <file>\n"
|
||||||
"Useful options:\n"
|
"Useful options:\n"
|
||||||
" -E, --export-all export all labels\n"
|
" -E, --export-all export all labels\n"
|
||||||
" -M, --dependfile <path> set the output dependency file\n"
|
" -M, --dependfile <path> set the output dependency file\n"
|
||||||
@@ -146,34 +148,23 @@ static void print_usage(void)
|
|||||||
|
|
||||||
int main(int argc, char *argv[])
|
int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
int ch;
|
|
||||||
char *ep;
|
|
||||||
|
|
||||||
time_t now = time(NULL);
|
time_t now = time(NULL);
|
||||||
char const *sourceDateEpoch = getenv("SOURCE_DATE_EPOCH");
|
char const *sourceDateEpoch = getenv("SOURCE_DATE_EPOCH");
|
||||||
|
|
||||||
/*
|
// 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 (sourceDateEpoch)
|
if (sourceDateEpoch)
|
||||||
now = (time_t)strtoul(sourceDateEpoch, NULL, 0);
|
now = (time_t)strtoul(sourceDateEpoch, NULL, 0);
|
||||||
|
|
||||||
dependfile = NULL;
|
|
||||||
|
|
||||||
// Perform some init for below
|
// Perform some init for below
|
||||||
sym_Init(now);
|
sym_Init(now);
|
||||||
|
|
||||||
// Set defaults
|
// Set defaults
|
||||||
|
|
||||||
generatePhonyDeps = false;
|
|
||||||
generatedMissingIncludes = false;
|
|
||||||
failedOnMissingInclude = false;
|
|
||||||
targetFileName = NULL;
|
|
||||||
|
|
||||||
opt_B("01");
|
opt_B("01");
|
||||||
opt_G("0123");
|
opt_G("0123");
|
||||||
opt_P(0);
|
opt_P(0);
|
||||||
|
opt_Q(16);
|
||||||
haltnop = true;
|
haltnop = true;
|
||||||
warnOnHaltNop = true;
|
warnOnHaltNop = true;
|
||||||
optimizeLoads = true;
|
optimizeLoads = true;
|
||||||
@@ -181,14 +172,17 @@ int main(int argc, char *argv[])
|
|||||||
verbose = false;
|
verbose = false;
|
||||||
warnings = true;
|
warnings = true;
|
||||||
sym_SetExportAll(false);
|
sym_SetExportAll(false);
|
||||||
uint32_t maxDepth = 64;
|
uint32_t maxDepth = DEFAULT_MAX_DEPTH;
|
||||||
|
char *dependFileName = NULL;
|
||||||
size_t targetFileNameLen = 0;
|
size_t targetFileNameLen = 0;
|
||||||
|
|
||||||
|
int ch;
|
||||||
|
char *ep;
|
||||||
while ((ch = musl_getopt_long_only(argc, argv, optstring, longopts, NULL)) != -1) {
|
while ((ch = musl_getopt_long_only(argc, argv, optstring, longopts, NULL)) != -1) {
|
||||||
switch (ch) {
|
switch (ch) {
|
||||||
case 'b':
|
case 'b':
|
||||||
if (strlen(musl_optarg) == 2)
|
if (strlen(musl_optarg) == 2)
|
||||||
opt_B(&musl_optarg[1]);
|
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;
|
||||||
@@ -210,7 +204,7 @@ int main(int argc, char *argv[])
|
|||||||
|
|
||||||
case 'g':
|
case 'g':
|
||||||
if (strlen(musl_optarg) == 4)
|
if (strlen(musl_optarg) == 4)
|
||||||
opt_G(&musl_optarg[1]);
|
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;
|
||||||
@@ -226,6 +220,10 @@ int main(int argc, char *argv[])
|
|||||||
haltnop = false;
|
haltnop = false;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
// `-i` was the only short option for `--include` until `-I` was
|
||||||
|
// introduced to better match the `-I dir` option of gcc and clang.
|
||||||
|
// `-i` is now undocumented but still supported for now.
|
||||||
|
case 'I':
|
||||||
case 'i':
|
case 'i':
|
||||||
fstk_AddIncludePath(musl_optarg);
|
fstk_AddIncludePath(musl_optarg);
|
||||||
break;
|
break;
|
||||||
@@ -242,29 +240,55 @@ int main(int argc, char *argv[])
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'M':
|
case 'M':
|
||||||
if (!strcmp("-", musl_optarg))
|
if (dependfile)
|
||||||
|
warnx("Overriding dependfile %s", dependFileName);
|
||||||
|
if (!strcmp("-", musl_optarg)) {
|
||||||
dependfile = stdout;
|
dependfile = stdout;
|
||||||
else
|
dependFileName = "<stdout>";
|
||||||
|
} else {
|
||||||
dependfile = fopen(musl_optarg, "w");
|
dependfile = fopen(musl_optarg, "w");
|
||||||
|
dependFileName = musl_optarg;
|
||||||
|
}
|
||||||
if (dependfile == NULL)
|
if (dependfile == NULL)
|
||||||
err("Could not open dependfile %s", musl_optarg);
|
err("Could not open dependfile %s", dependFileName);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'o':
|
case 'o':
|
||||||
out_SetFileName(musl_optarg);
|
out_SetFileName(musl_optarg);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
unsigned long fill;
|
case 'P':
|
||||||
|
fstk_SetPreIncludeFile(musl_optarg);
|
||||||
|
break;
|
||||||
|
|
||||||
|
unsigned long padByte;
|
||||||
case 'p':
|
case 'p':
|
||||||
fill = strtoul(musl_optarg, &ep, 0);
|
padByte = strtoul(musl_optarg, &ep, 0);
|
||||||
|
|
||||||
if (musl_optarg[0] == '\0' || *ep != '\0')
|
if (musl_optarg[0] == '\0' || *ep != '\0')
|
||||||
errx("Invalid argument for option 'p'");
|
errx("Invalid argument for option 'p'");
|
||||||
|
|
||||||
if (fill > 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(fill);
|
opt_P(padByte);
|
||||||
|
break;
|
||||||
|
|
||||||
|
unsigned long precision;
|
||||||
|
const char *precisionArg;
|
||||||
|
case 'Q':
|
||||||
|
precisionArg = musl_optarg;
|
||||||
|
if (precisionArg[0] == '.')
|
||||||
|
precisionArg++;
|
||||||
|
precision = strtoul(precisionArg, &ep, 0);
|
||||||
|
|
||||||
|
if (musl_optarg[0] == '\0' || *ep != '\0')
|
||||||
|
errx("Invalid argument for option 'Q'");
|
||||||
|
|
||||||
|
if (precision < 1 || precision > 31)
|
||||||
|
errx("Argument for option 'Q' must be between 1 and 31");
|
||||||
|
|
||||||
|
opt_Q(precision);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'r':
|
case 'r':
|
||||||
@@ -277,6 +301,7 @@ int main(int argc, char *argv[])
|
|||||||
case 'V':
|
case 'V':
|
||||||
printf("rgbasm %s\n", get_package_version_string());
|
printf("rgbasm %s\n", get_package_version_string());
|
||||||
exit(0);
|
exit(0);
|
||||||
|
|
||||||
case 'v':
|
case 'v':
|
||||||
verbose = true;
|
verbose = true;
|
||||||
break;
|
break;
|
||||||
@@ -289,7 +314,7 @@ int main(int argc, char *argv[])
|
|||||||
warnings = false;
|
warnings = false;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
/* Long-only options */
|
// Long-only options
|
||||||
case 0:
|
case 0:
|
||||||
switch (depType) {
|
switch (depType) {
|
||||||
case 'G':
|
case 'G':
|
||||||
@@ -321,10 +346,10 @@ int main(int argc, char *argv[])
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
/* Unrecognized options */
|
// Unrecognized options
|
||||||
default:
|
default:
|
||||||
print_usage();
|
print_usage();
|
||||||
/* NOTREACHED */
|
// NOTREACHED
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -334,10 +359,10 @@ int main(int argc, char *argv[])
|
|||||||
targetFileName[targetFileNameLen - 1] = '\0'; // Overwrite the last space
|
targetFileName[targetFileNameLen - 1] = '\0'; // Overwrite the last space
|
||||||
|
|
||||||
if (argc == musl_optind) {
|
if (argc == musl_optind) {
|
||||||
fputs("FATAL: No input files\n", stderr);
|
fputs("FATAL: Please specify an input file (pass `-` to read from standard input)\n", stderr);
|
||||||
print_usage();
|
print_usage();
|
||||||
} else if (argc != musl_optind + 1) {
|
} else if (argc != musl_optind + 1) {
|
||||||
fputs("FATAL: More than one input file given\n", stderr);
|
fputs("FATAL: More than one input file specified\n", stderr);
|
||||||
print_usage();
|
print_usage();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -353,9 +378,9 @@ int main(int argc, char *argv[])
|
|||||||
fprintf(dependfile, "%s: %s\n", targetFileName, mainFileName);
|
fprintf(dependfile, "%s: %s\n", targetFileName, mainFileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
charmap_New("main", NULL);
|
charmap_New(DEFAULT_CHARMAP_NAME, NULL);
|
||||||
|
|
||||||
// Init lexer and file stack, prodiving file info
|
// Init lexer and file stack, providing file info
|
||||||
lexer_Init();
|
lexer_Init();
|
||||||
fstk_Init(mainFileName, maxDepth);
|
fstk_Init(mainFileName, maxDepth);
|
||||||
|
|
||||||
@@ -376,7 +401,7 @@ int main(int argc, char *argv[])
|
|||||||
if (failedOnMissingInclude)
|
if (failedOnMissingInclude)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
/* If no path specified, don't write file */
|
// If no path specified, don't write file
|
||||||
if (objectName != NULL)
|
if (objectName != NULL)
|
||||||
out_WriteObject();
|
out_WriteObject();
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
@@ -1,3 +1,11 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of RGBDS.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2022, RGBDS contributors.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
@@ -6,6 +14,7 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "asm/fixpoint.h"
|
||||||
#include "asm/fstack.h"
|
#include "asm/fstack.h"
|
||||||
#include "asm/lexer.h"
|
#include "asm/lexer.h"
|
||||||
#include "asm/main.h"
|
#include "asm/main.h"
|
||||||
@@ -15,7 +24,8 @@
|
|||||||
struct OptStackEntry {
|
struct OptStackEntry {
|
||||||
char binary[2];
|
char binary[2];
|
||||||
char gbgfx[4];
|
char gbgfx[4];
|
||||||
int32_t fillByte;
|
uint8_t fixPrecision;
|
||||||
|
uint8_t fillByte;
|
||||||
bool haltnop;
|
bool haltnop;
|
||||||
bool warnOnHaltNop;
|
bool warnOnHaltNop;
|
||||||
bool optimizeLoads;
|
bool optimizeLoads;
|
||||||
@@ -39,9 +49,14 @@ void opt_G(char const chars[4])
|
|||||||
lexer_SetGfxDigits(chars);
|
lexer_SetGfxDigits(chars);
|
||||||
}
|
}
|
||||||
|
|
||||||
void opt_P(uint8_t fill)
|
void opt_P(uint8_t padByte)
|
||||||
{
|
{
|
||||||
fillByte = fill;
|
fillByte = padByte;
|
||||||
|
}
|
||||||
|
|
||||||
|
void opt_Q(uint8_t precision)
|
||||||
|
{
|
||||||
|
fixPrecision = precision;
|
||||||
}
|
}
|
||||||
|
|
||||||
void opt_R(size_t newDepth)
|
void opt_R(size_t newDepth)
|
||||||
@@ -95,18 +110,41 @@ void opt_Parse(char *s)
|
|||||||
case 'p':
|
case 'p':
|
||||||
if (strlen(&s[1]) <= 2) {
|
if (strlen(&s[1]) <= 2) {
|
||||||
int result;
|
int result;
|
||||||
unsigned int fillchar;
|
unsigned int padByte;
|
||||||
|
|
||||||
result = sscanf(&s[1], "%x", &fillchar);
|
result = sscanf(&s[1], "%x", &padByte);
|
||||||
if (result != EOF && result != 1)
|
if (result != 1)
|
||||||
error("Invalid argument for option 'p'\n");
|
error("Invalid argument for option 'p'\n");
|
||||||
|
else if (padByte > 0xFF)
|
||||||
|
error("Argument for option 'p' must be between 0 and 0xFF\n");
|
||||||
else
|
else
|
||||||
opt_P(fillchar);
|
opt_P(padByte);
|
||||||
} else {
|
} else {
|
||||||
error("Invalid argument for option 'p'\n");
|
error("Invalid argument for option 'p'\n");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
const char *precisionArg;
|
||||||
|
case 'Q':
|
||||||
|
precisionArg = &s[1];
|
||||||
|
if (precisionArg[0] == '.')
|
||||||
|
precisionArg++;
|
||||||
|
if (strlen(precisionArg) <= 2) {
|
||||||
|
int result;
|
||||||
|
unsigned int precision;
|
||||||
|
|
||||||
|
result = sscanf(precisionArg, "%u", &precision);
|
||||||
|
if (result != 1)
|
||||||
|
error("Invalid argument for option 'Q'\n");
|
||||||
|
else if (precision < 1 || precision > 31)
|
||||||
|
error("Argument for option 'Q' must be between 1 and 31\n");
|
||||||
|
else
|
||||||
|
opt_Q(precision);
|
||||||
|
} else {
|
||||||
|
error("Invalid argument for option 'Q'\n");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
case 'r': {
|
case 'r': {
|
||||||
++s; // Skip 'r'
|
++s; // Skip 'r'
|
||||||
while (isblank(*s))
|
while (isblank(*s))
|
||||||
@@ -167,6 +205,13 @@ void opt_Parse(char *s)
|
|||||||
|
|
||||||
case '!': // negates flag options that do not take an argument
|
case '!': // negates flag options that do not take an argument
|
||||||
switch (s[1]) {
|
switch (s[1]) {
|
||||||
|
case 'H':
|
||||||
|
if (s[2] == '\0')
|
||||||
|
opt_H(true);
|
||||||
|
else
|
||||||
|
error("Option '!H' does not take an argument\n");
|
||||||
|
break;
|
||||||
|
|
||||||
case 'h':
|
case 'h':
|
||||||
if (s[2] == '\0')
|
if (s[2] == '\0')
|
||||||
opt_h(true);
|
opt_h(true);
|
||||||
@@ -181,6 +226,13 @@ void opt_Parse(char *s)
|
|||||||
error("Option '!L' does not take an argument\n");
|
error("Option '!L' does not take an argument\n");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'l':
|
||||||
|
if (s[2] == '\0')
|
||||||
|
opt_l(true);
|
||||||
|
else
|
||||||
|
error("Option '!l' does not take an argument\n");
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
error("Unknown option '!%c'\n", s[1]);
|
error("Unknown option '!%c'\n", s[1]);
|
||||||
break;
|
break;
|
||||||
@@ -209,6 +261,8 @@ void opt_Push(void)
|
|||||||
entry->gbgfx[2] = gfxDigits[2];
|
entry->gbgfx[2] = gfxDigits[2];
|
||||||
entry->gbgfx[3] = gfxDigits[3];
|
entry->gbgfx[3] = gfxDigits[3];
|
||||||
|
|
||||||
|
entry->fixPrecision = fixPrecision; // Pulled from fixpoint.h
|
||||||
|
|
||||||
entry->fillByte = fillByte; // Pulled from section.h
|
entry->fillByte = fillByte; // Pulled from section.h
|
||||||
|
|
||||||
entry->haltnop = haltnop; // Pulled from main.h
|
entry->haltnop = haltnop; // Pulled from main.h
|
||||||
@@ -237,6 +291,7 @@ void opt_Pop(void)
|
|||||||
opt_B(entry->binary);
|
opt_B(entry->binary);
|
||||||
opt_G(entry->gbgfx);
|
opt_G(entry->gbgfx);
|
||||||
opt_P(entry->fillByte);
|
opt_P(entry->fillByte);
|
||||||
|
opt_Q(entry->fixPrecision);
|
||||||
opt_H(entry->warnOnHaltNop);
|
opt_H(entry->warnOnHaltNop);
|
||||||
opt_h(entry->haltnop);
|
opt_h(entry->haltnop);
|
||||||
opt_L(entry->optimizeLoads);
|
opt_L(entry->optimizeLoads);
|
||||||
|
|||||||
104
src/asm/output.c
104
src/asm/output.c
@@ -6,9 +6,7 @@
|
|||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
// Outputs an objectfile
|
||||||
* Outputs an objectfile
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
@@ -54,18 +52,16 @@ char *objectName;
|
|||||||
|
|
||||||
struct Section *sectionList;
|
struct Section *sectionList;
|
||||||
|
|
||||||
/* Linked list of symbols to put in the object file */
|
// Linked list of symbols to put in the object file
|
||||||
static struct Symbol *objectSymbols = NULL;
|
static struct Symbol *objectSymbols = NULL;
|
||||||
static struct Symbol **objectSymbolsTail = &objectSymbols;
|
static struct Symbol **objectSymbolsTail = &objectSymbols;
|
||||||
static uint32_t nbSymbols = 0; /* Length of the above list */
|
static uint32_t nbSymbols = 0; // Length of the above list
|
||||||
|
|
||||||
static struct Assertion *assertions = NULL;
|
static struct Assertion *assertions = NULL;
|
||||||
|
|
||||||
static struct FileStackNode *fileStackNodes = NULL;
|
static struct FileStackNode *fileStackNodes = NULL;
|
||||||
|
|
||||||
/*
|
// Count the number of sections used in this object
|
||||||
* Count the number of sections used in this object
|
|
||||||
*/
|
|
||||||
static uint32_t countSections(void)
|
static uint32_t countSections(void)
|
||||||
{
|
{
|
||||||
uint32_t count = 0;
|
uint32_t count = 0;
|
||||||
@@ -76,9 +72,7 @@ static uint32_t countSections(void)
|
|||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Count the number of patches used in this object
|
||||||
* Count the number of patches used in this object
|
|
||||||
*/
|
|
||||||
static uint32_t countPatches(struct Section const *sect)
|
static uint32_t countPatches(struct Section const *sect)
|
||||||
{
|
{
|
||||||
uint32_t r = 0;
|
uint32_t r = 0;
|
||||||
@@ -90,9 +84,7 @@ static uint32_t countPatches(struct Section const *sect)
|
|||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Count the number of assertions used in this object
|
||||||
* Count the number of assertions used in this object
|
|
||||||
*/
|
|
||||||
static uint32_t countAsserts(void)
|
static uint32_t countAsserts(void)
|
||||||
{
|
{
|
||||||
struct Assertion *assert = assertions;
|
struct Assertion *assert = assertions;
|
||||||
@@ -105,9 +97,7 @@ static uint32_t countAsserts(void)
|
|||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Write a long to a file (little-endian)
|
||||||
* Write a long to a file (little-endian)
|
|
||||||
*/
|
|
||||||
static void putlong(uint32_t i, FILE *f)
|
static void putlong(uint32_t i, FILE *f)
|
||||||
{
|
{
|
||||||
putc(i, f);
|
putc(i, f);
|
||||||
@@ -116,9 +106,7 @@ static void putlong(uint32_t i, FILE *f)
|
|||||||
putc(i >> 24, f);
|
putc(i >> 24, f);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Write a NULL-terminated string to a file
|
||||||
* Write a NULL-terminated string to a file
|
|
||||||
*/
|
|
||||||
static void putstring(char const *s, FILE *f)
|
static void putstring(char const *s, FILE *f)
|
||||||
{
|
{
|
||||||
while (*s)
|
while (*s)
|
||||||
@@ -133,7 +121,7 @@ static uint32_t getNbFileStackNodes(void)
|
|||||||
|
|
||||||
void out_RegisterNode(struct FileStackNode *node)
|
void out_RegisterNode(struct 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
|
||||||
while (node->ID == (uint32_t)-1) {
|
while (node->ID == (uint32_t)-1) {
|
||||||
node->ID = getNbFileStackNodes();
|
node->ID = getNbFileStackNodes();
|
||||||
if (node->ID == (uint32_t)-1)
|
if (node->ID == (uint32_t)-1)
|
||||||
@@ -141,7 +129,7 @@ void out_RegisterNode(struct FileStackNode *node)
|
|||||||
node->next = fileStackNodes;
|
node->next = fileStackNodes;
|
||||||
fileStackNodes = node;
|
fileStackNodes = node;
|
||||||
|
|
||||||
/* Also register the node's parents */
|
// Also register the node's parents
|
||||||
node = node->parent;
|
node = node->parent;
|
||||||
if (!node)
|
if (!node)
|
||||||
break;
|
break;
|
||||||
@@ -156,25 +144,21 @@ This is code intended to replace a node, which is pretty useless until ref count
|
|||||||
|
|
||||||
struct FileStackNode **ptr = &fileStackNodes;
|
struct FileStackNode **ptr = &fileStackNodes;
|
||||||
|
|
||||||
/*
|
// The linked list is supposed to have decrementing IDs, so iterate with less memory reads,
|
||||||
* The linked list is supposed to have decrementing IDs, so iterate with less memory reads,
|
// to hopefully hit the cache less. A debug check is added after, in case a change is made
|
||||||
* to hopefully hit the cache less. A debug check is added after, in case a change is made
|
// that breaks this assumption.
|
||||||
* that breaks this assumption.
|
|
||||||
*/
|
|
||||||
for (uint32_t i = fileStackNodes->ID; i != node->ID; i--)
|
for (uint32_t i = fileStackNodes->ID; i != node->ID; i--)
|
||||||
ptr = &(*ptr)->next;
|
ptr = &(*ptr)->next;
|
||||||
assert((*ptr)->ID == node->ID);
|
assert((*ptr)->ID == node->ID);
|
||||||
|
|
||||||
node->next = (*ptr)->next;
|
node->next = (*ptr)->next;
|
||||||
assert(!node->next || node->next->ID == node->ID - 1); /* Catch inconsistencies early */
|
assert(!node->next || node->next->ID == node->ID - 1); // Catch inconsistencies early
|
||||||
/* TODO: unreference the node */
|
// TODO: unreference the node
|
||||||
*ptr = node;
|
*ptr = node;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Return a section's ID
|
||||||
* Return a section's ID
|
|
||||||
*/
|
|
||||||
static uint32_t getsectid(struct Section const *sect)
|
static uint32_t getsectid(struct Section const *sect)
|
||||||
{
|
{
|
||||||
struct Section const *sec = sectionList;
|
struct Section const *sec = sectionList;
|
||||||
@@ -195,9 +179,7 @@ static uint32_t getSectIDIfAny(struct Section const *sect)
|
|||||||
return sect ? getsectid(sect) : (uint32_t)-1;
|
return sect ? getsectid(sect) : (uint32_t)-1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Write a patch to a file
|
||||||
* Write a patch to a file
|
|
||||||
*/
|
|
||||||
static void writepatch(struct Patch const *patch, FILE *f)
|
static void writepatch(struct Patch const *patch, FILE *f)
|
||||||
{
|
{
|
||||||
assert(patch->src->ID != (uint32_t)-1);
|
assert(patch->src->ID != (uint32_t)-1);
|
||||||
@@ -211,9 +193,7 @@ static void writepatch(struct Patch const *patch, FILE *f)
|
|||||||
fwrite(patch->rpn, 1, patch->rpnSize, f);
|
fwrite(patch->rpn, 1, patch->rpnSize, f);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Write a section to a file
|
||||||
* Write a section to a file
|
|
||||||
*/
|
|
||||||
static void writesection(struct Section const *sect, FILE *f)
|
static void writesection(struct Section const *sect, FILE *f)
|
||||||
{
|
{
|
||||||
putstring(sect->name, f);
|
putstring(sect->name, f);
|
||||||
@@ -255,9 +235,7 @@ static void freesection(struct Section const *sect)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Write a symbol to a file
|
||||||
* Write a symbol to a file
|
|
||||||
*/
|
|
||||||
static void writesymbol(struct Symbol const *sym, FILE *f)
|
static void writesymbol(struct Symbol const *sym, FILE *f)
|
||||||
{
|
{
|
||||||
putstring(sym->name, f);
|
putstring(sym->name, f);
|
||||||
@@ -285,10 +263,8 @@ static void registerSymbol(struct Symbol *sym)
|
|||||||
sym->ID = nbSymbols++;
|
sym->ID = nbSymbols++;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Returns a symbol's ID within the object file
|
||||||
* Returns a symbol's ID within the object file
|
// If the symbol does not have one, one is assigned by registering the symbol
|
||||||
* If the symbol does not have one, one is assigned by registering the symbol
|
|
||||||
*/
|
|
||||||
static uint32_t getSymbolID(struct Symbol *sym)
|
static uint32_t getSymbolID(struct Symbol *sym)
|
||||||
{
|
{
|
||||||
if (sym->ID == (uint32_t)-1 && !sym_IsPC(sym))
|
if (sym->ID == (uint32_t)-1 && !sym_IsPC(sym))
|
||||||
@@ -392,10 +368,8 @@ static void writerpn(uint8_t *rpnexpr, uint32_t *rpnptr, uint8_t *rpn,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Allocate a new patch structure and link it into the list
|
||||||
* Allocate a new patch structure and link it into the list
|
// WARNING: all patches are assumed to eventually be written, so the file stack node is registered
|
||||||
* WARNING: all patches are assumed to eventually be written, so the file stack node is registered
|
|
||||||
*/
|
|
||||||
static struct Patch *allocpatch(uint32_t type, struct Expression const *expr, uint32_t ofs)
|
static struct Patch *allocpatch(uint32_t type, struct Expression const *expr, uint32_t ofs)
|
||||||
{
|
{
|
||||||
struct Patch *patch = malloc(sizeof(struct Patch));
|
struct Patch *patch = malloc(sizeof(struct Patch));
|
||||||
@@ -417,10 +391,10 @@ static struct Patch *allocpatch(uint32_t type, struct Expression const *expr, ui
|
|||||||
patch->pcSection = sect_GetSymbolSection();
|
patch->pcSection = sect_GetSymbolSection();
|
||||||
patch->pcOffset = sect_GetSymbolOffset();
|
patch->pcOffset = sect_GetSymbolOffset();
|
||||||
|
|
||||||
/* If the rpnSize's value is known, output a constant RPN rpnSize directly */
|
// If the rpnSize's value is known, output a constant RPN rpnSize directly
|
||||||
if (expr->isKnown) {
|
if (expr->isKnown) {
|
||||||
patch->rpnSize = rpnSize;
|
patch->rpnSize = rpnSize;
|
||||||
/* Make sure to update `rpnSize` above if modifying this! */
|
// Make sure to update `rpnSize` above if modifying this!
|
||||||
patch->rpn[0] = RPN_CONST;
|
patch->rpn[0] = RPN_CONST;
|
||||||
patch->rpn[1] = (uint32_t)(expr->val) & 0xFF;
|
patch->rpn[1] = (uint32_t)(expr->val) & 0xFF;
|
||||||
patch->rpn[2] = (uint32_t)(expr->val) >> 8;
|
patch->rpn[2] = (uint32_t)(expr->val) >> 8;
|
||||||
@@ -435,9 +409,7 @@ static struct Patch *allocpatch(uint32_t type, struct Expression const *expr, ui
|
|||||||
return patch;
|
return patch;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Create a new patch (includes the rpn expr)
|
||||||
* Create a new patch (includes the rpn expr)
|
|
||||||
*/
|
|
||||||
void out_CreatePatch(uint32_t type, struct Expression const *expr, uint32_t ofs, uint32_t pcShift)
|
void out_CreatePatch(uint32_t type, struct Expression const *expr, uint32_t ofs, uint32_t pcShift)
|
||||||
{
|
{
|
||||||
struct Patch *patch = allocpatch(type, expr, ofs);
|
struct Patch *patch = allocpatch(type, expr, ofs);
|
||||||
@@ -451,9 +423,7 @@ void out_CreatePatch(uint32_t type, struct Expression const *expr, uint32_t ofs,
|
|||||||
currentSection->patches = patch;
|
currentSection->patches = patch;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Creates an assert that will be written to the object file
|
||||||
* Creates an assert that will be written to the object file
|
|
||||||
*/
|
|
||||||
bool out_CreateAssert(enum AssertionType type, struct Expression const *expr,
|
bool out_CreateAssert(enum AssertionType type, struct Expression const *expr,
|
||||||
char const *message, uint32_t ofs)
|
char const *message, uint32_t ofs)
|
||||||
{
|
{
|
||||||
@@ -499,7 +469,7 @@ static void writeFileStackNode(struct FileStackNode const *node, FILE *f)
|
|||||||
struct FileStackReptNode const *reptNode = (struct FileStackReptNode const *)node;
|
struct FileStackReptNode const *reptNode = (struct FileStackReptNode const *)node;
|
||||||
|
|
||||||
putlong(reptNode->reptDepth, f);
|
putlong(reptNode->reptDepth, f);
|
||||||
/* 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 = reptNode->reptDepth; i--; )
|
for (uint32_t i = reptNode->reptDepth; i--; )
|
||||||
putlong(reptNode->iters[i], f);
|
putlong(reptNode->iters[i], f);
|
||||||
}
|
}
|
||||||
@@ -515,24 +485,22 @@ static void registerUnregisteredSymbol(struct Symbol *symbol, void *arg)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Write an objectfile
|
||||||
* Write an objectfile
|
|
||||||
*/
|
|
||||||
void out_WriteObject(void)
|
void out_WriteObject(void)
|
||||||
{
|
{
|
||||||
FILE *f;
|
FILE *f;
|
||||||
if (strcmp(objectName, "-") != 0)
|
if (strcmp(objectName, "-") != 0)
|
||||||
f = fopen(objectName, "wb");
|
f = fopen(objectName, "wb");
|
||||||
else
|
else
|
||||||
f = fdopen(1, "wb");
|
f = fdopen(STDOUT_FILENO, "wb");
|
||||||
|
|
||||||
if (!f)
|
if (!f)
|
||||||
err("Couldn't write file '%s'", objectName);
|
err("Couldn't write file '%s'", objectName);
|
||||||
|
|
||||||
/* Also write symbols that weren't written above */
|
// Also write symbols that weren't written above
|
||||||
sym_ForEach(registerUnregisteredSymbol, NULL);
|
sym_ForEach(registerUnregisteredSymbol, NULL);
|
||||||
|
|
||||||
fprintf(f, RGBDS_OBJECT_VERSION_STRING, RGBDS_OBJECT_VERSION_NUMBER);
|
fprintf(f, RGBDS_OBJECT_VERSION_STRING);
|
||||||
putlong(RGBDS_OBJECT_REV, f);
|
putlong(RGBDS_OBJECT_REV, f);
|
||||||
|
|
||||||
putlong(nbSymbols, f);
|
putlong(nbSymbols, f);
|
||||||
@@ -569,12 +537,12 @@ void out_WriteObject(void)
|
|||||||
fclose(f);
|
fclose(f);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Set the objectfilename
|
||||||
* Set the objectfilename
|
|
||||||
*/
|
|
||||||
void out_SetFileName(char *s)
|
void out_SetFileName(char *s)
|
||||||
{
|
{
|
||||||
|
if (objectName)
|
||||||
|
warnx("Overriding output filename %s", objectName);
|
||||||
objectName = s;
|
objectName = s;
|
||||||
if (verbose)
|
if (verbose)
|
||||||
printf("Output filename %s\n", s);
|
printf("Output filename %s\n", objectName);
|
||||||
}
|
}
|
||||||
|
|||||||
187
src/asm/parser.y
187
src/asm/parser.y
@@ -36,7 +36,7 @@
|
|||||||
#include "linkdefs.h"
|
#include "linkdefs.h"
|
||||||
#include "platform.h" // strncasecmp, strdup
|
#include "platform.h" // strncasecmp, strdup
|
||||||
|
|
||||||
static struct CaptureBody captureBody; /* Captures a REPT/FOR or MACRO */
|
static struct CaptureBody captureBody; // Captures a REPT/FOR or MACRO
|
||||||
|
|
||||||
static void upperstring(char *dest, char const *src)
|
static void upperstring(char *dest, char const *src)
|
||||||
{
|
{
|
||||||
@@ -104,14 +104,14 @@ static size_t strlenUTF8(char const *s)
|
|||||||
case 1:
|
case 1:
|
||||||
errorInvalidUTF8Byte(byte, "STRLEN");
|
errorInvalidUTF8Byte(byte, "STRLEN");
|
||||||
state = 0;
|
state = 0;
|
||||||
/* fallthrough */
|
// fallthrough
|
||||||
case 0:
|
case 0:
|
||||||
len++;
|
len++;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Check for partial code point. */
|
// Check for partial code point.
|
||||||
if (state != 0)
|
if (state != 0)
|
||||||
error("STRLEN: Incomplete UTF-8 character\n");
|
error("STRLEN: Incomplete UTF-8 character\n");
|
||||||
|
|
||||||
@@ -127,13 +127,13 @@ static void strsubUTF8(char *dest, size_t destLen, char const *src, uint32_t pos
|
|||||||
uint32_t curLen = 0;
|
uint32_t curLen = 0;
|
||||||
uint32_t curPos = 1;
|
uint32_t curPos = 1;
|
||||||
|
|
||||||
/* Advance to starting position in source string. */
|
// Advance to starting position in source string.
|
||||||
while (src[srcIndex] && curPos < pos) {
|
while (src[srcIndex] && curPos < pos) {
|
||||||
switch (decode(&state, &codep, src[srcIndex])) {
|
switch (decode(&state, &codep, src[srcIndex])) {
|
||||||
case 1:
|
case 1:
|
||||||
errorInvalidUTF8Byte(src[srcIndex], "STRSUB");
|
errorInvalidUTF8Byte(src[srcIndex], "STRSUB");
|
||||||
state = 0;
|
state = 0;
|
||||||
/* fallthrough */
|
// fallthrough
|
||||||
case 0:
|
case 0:
|
||||||
curPos++;
|
curPos++;
|
||||||
break;
|
break;
|
||||||
@@ -141,21 +141,19 @@ static void strsubUTF8(char *dest, size_t destLen, char const *src, uint32_t pos
|
|||||||
srcIndex++;
|
srcIndex++;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// A position 1 past the end of the string is allowed, but will trigger the
|
||||||
* A position 1 past the end of the string is allowed, but will trigger the
|
// "Length too big" warning below if the length is nonzero.
|
||||||
* "Length too big" warning below if the length is nonzero.
|
|
||||||
*/
|
|
||||||
if (!src[srcIndex] && pos > curPos)
|
if (!src[srcIndex] && pos > curPos)
|
||||||
warning(WARNING_BUILTIN_ARG,
|
warning(WARNING_BUILTIN_ARG,
|
||||||
"STRSUB: Position %" PRIu32 " is past the end of the string\n", pos);
|
"STRSUB: Position %" PRIu32 " is past the end of the string\n", pos);
|
||||||
|
|
||||||
/* Copy from source to destination. */
|
// Copy from source to destination.
|
||||||
while (src[srcIndex] && destIndex < destLen - 1 && curLen < len) {
|
while (src[srcIndex] && destIndex < destLen - 1 && curLen < len) {
|
||||||
switch (decode(&state, &codep, src[srcIndex])) {
|
switch (decode(&state, &codep, src[srcIndex])) {
|
||||||
case 1:
|
case 1:
|
||||||
errorInvalidUTF8Byte(src[srcIndex], "STRSUB");
|
errorInvalidUTF8Byte(src[srcIndex], "STRSUB");
|
||||||
state = 0;
|
state = 0;
|
||||||
/* fallthrough */
|
// fallthrough
|
||||||
case 0:
|
case 0:
|
||||||
curLen++;
|
curLen++;
|
||||||
break;
|
break;
|
||||||
@@ -166,7 +164,7 @@ static void strsubUTF8(char *dest, size_t destLen, char const *src, uint32_t pos
|
|||||||
if (curLen < len)
|
if (curLen < len)
|
||||||
warning(WARNING_BUILTIN_ARG, "STRSUB: Length too big: %" PRIu32 "\n", len);
|
warning(WARNING_BUILTIN_ARG, "STRSUB: Length too big: %" PRIu32 "\n", len);
|
||||||
|
|
||||||
/* Check for partial code point. */
|
// Check for partial code point.
|
||||||
if (state != 0)
|
if (state != 0)
|
||||||
error("STRSUB: Incomplete UTF-8 character\n");
|
error("STRSUB: Incomplete UTF-8 character\n");
|
||||||
|
|
||||||
@@ -187,7 +185,7 @@ static void charsubUTF8(char *dest, char const *src, uint32_t pos)
|
|||||||
{
|
{
|
||||||
size_t charLen = 1;
|
size_t charLen = 1;
|
||||||
|
|
||||||
/* Advance to starting position in source string. */
|
// Advance to starting position in source string.
|
||||||
for (uint32_t curPos = 1; charLen && curPos < pos; curPos++)
|
for (uint32_t curPos = 1; charLen && curPos < pos; curPos++)
|
||||||
charLen = charmap_ConvertNext(&src, NULL);
|
charLen = charmap_ConvertNext(&src, NULL);
|
||||||
|
|
||||||
@@ -197,7 +195,7 @@ static void charsubUTF8(char *dest, char const *src, uint32_t pos)
|
|||||||
warning(WARNING_BUILTIN_ARG,
|
warning(WARNING_BUILTIN_ARG,
|
||||||
"CHARSUB: Position %" PRIu32 " is past the end of the string\n", pos);
|
"CHARSUB: Position %" PRIu32 " is past the end of the string\n", pos);
|
||||||
|
|
||||||
/* Copy from source to destination. */
|
// Copy from source to destination.
|
||||||
memcpy(dest, start, src - start);
|
memcpy(dest, start, src - start);
|
||||||
|
|
||||||
dest[src - start] = '\0';
|
dest[src - start] = '\0';
|
||||||
@@ -205,10 +203,8 @@ static void charsubUTF8(char *dest, char const *src, uint32_t pos)
|
|||||||
|
|
||||||
static uint32_t adjustNegativePos(int32_t pos, size_t len, char const *functionName)
|
static uint32_t adjustNegativePos(int32_t pos, size_t len, char const *functionName)
|
||||||
{
|
{
|
||||||
/*
|
// STRSUB and CHARSUB adjust negative `pos` arguments the same way,
|
||||||
* STRSUB and CHARSUB adjust negative `pos` arguments the same way,
|
// such that position -1 is the last character of a string.
|
||||||
* such that position -1 is the last character of a string.
|
|
||||||
*/
|
|
||||||
if (pos < 0)
|
if (pos < 0)
|
||||||
pos += len + 1;
|
pos += len + 1;
|
||||||
if (pos < 1) {
|
if (pos < 1) {
|
||||||
@@ -545,7 +541,7 @@ enum {
|
|||||||
%left T_OP_SHL T_OP_SHR T_OP_USHR
|
%left T_OP_SHL T_OP_SHR T_OP_USHR
|
||||||
%left T_OP_MUL T_OP_DIV T_OP_MOD
|
%left T_OP_MUL T_OP_DIV T_OP_MOD
|
||||||
|
|
||||||
%precedence NEG /* negation -- unary minus */
|
%precedence NEG // negation -- unary minus
|
||||||
|
|
||||||
%token T_OP_EXP "**"
|
%token T_OP_EXP "**"
|
||||||
%left T_OP_EXP
|
%left T_OP_EXP
|
||||||
@@ -554,14 +550,17 @@ enum {
|
|||||||
%token T_OP_BANK "BANK"
|
%token T_OP_BANK "BANK"
|
||||||
%token T_OP_ALIGN "ALIGN"
|
%token T_OP_ALIGN "ALIGN"
|
||||||
%token T_OP_SIZEOF "SIZEOF" T_OP_STARTOF "STARTOF"
|
%token T_OP_SIZEOF "SIZEOF" T_OP_STARTOF "STARTOF"
|
||||||
|
|
||||||
%token T_OP_SIN "SIN" T_OP_COS "COS" T_OP_TAN "TAN"
|
%token T_OP_SIN "SIN" T_OP_COS "COS" T_OP_TAN "TAN"
|
||||||
%token T_OP_ASIN "ASIN" T_OP_ACOS "ACOS" T_OP_ATAN "ATAN" T_OP_ATAN2 "ATAN2"
|
%token T_OP_ASIN "ASIN" T_OP_ACOS "ACOS" T_OP_ATAN "ATAN" T_OP_ATAN2 "ATAN2"
|
||||||
%token T_OP_FDIV "FDIV"
|
%token T_OP_FDIV "FDIV"
|
||||||
%token T_OP_FMUL "FMUL"
|
%token T_OP_FMUL "FMUL"
|
||||||
|
%token T_OP_FMOD "FMOD"
|
||||||
%token T_OP_POW "POW"
|
%token T_OP_POW "POW"
|
||||||
%token T_OP_LOG "LOG"
|
%token T_OP_LOG "LOG"
|
||||||
%token T_OP_ROUND "ROUND"
|
%token T_OP_ROUND "ROUND"
|
||||||
%token T_OP_CEIL "CEIL" T_OP_FLOOR "FLOOR"
|
%token T_OP_CEIL "CEIL" T_OP_FLOOR "FLOOR"
|
||||||
|
%type <constValue> opt_q_arg
|
||||||
|
|
||||||
%token T_OP_HIGH "HIGH" T_OP_LOW "LOW"
|
%token T_OP_HIGH "HIGH" T_OP_LOW "LOW"
|
||||||
%token T_OP_ISCONST "ISCONST"
|
%token T_OP_ISCONST "ISCONST"
|
||||||
@@ -587,7 +586,6 @@ enum {
|
|||||||
%type <symName> scoped_id
|
%type <symName> scoped_id
|
||||||
%type <symName> scoped_anon_id
|
%type <symName> scoped_anon_id
|
||||||
%token T_POP_EQU "EQU"
|
%token T_POP_EQU "EQU"
|
||||||
%token T_POP_SET "SET"
|
|
||||||
%token T_POP_EQUAL "="
|
%token T_POP_EQUAL "="
|
||||||
%token T_POP_EQUS "EQUS"
|
%token T_POP_EQUS "EQUS"
|
||||||
|
|
||||||
@@ -641,7 +639,7 @@ enum {
|
|||||||
%type <forArgs> for_args
|
%type <forArgs> for_args
|
||||||
|
|
||||||
%token T_Z80_ADC "adc" T_Z80_ADD "add" T_Z80_AND "and"
|
%token T_Z80_ADC "adc" T_Z80_ADD "add" T_Z80_AND "and"
|
||||||
%token T_Z80_BIT "bit" // There is no T_Z80_SET, only T_POP_SET
|
%token T_Z80_BIT "bit"
|
||||||
%token T_Z80_CALL "call" T_Z80_CCF "ccf" T_Z80_CP "cp" T_Z80_CPL "cpl"
|
%token T_Z80_CALL "call" T_Z80_CCF "ccf" T_Z80_CP "cp" T_Z80_CPL "cpl"
|
||||||
%token T_Z80_DAA "daa" T_Z80_DEC "dec" T_Z80_DI "di"
|
%token T_Z80_DAA "daa" T_Z80_DEC "dec" T_Z80_DI "di"
|
||||||
%token T_Z80_EI "ei"
|
%token T_Z80_EI "ei"
|
||||||
@@ -658,7 +656,7 @@ enum {
|
|||||||
%token T_Z80_RES "res" T_Z80_RET "ret" T_Z80_RETI "reti" T_Z80_RST "rst"
|
%token T_Z80_RES "res" T_Z80_RET "ret" T_Z80_RETI "reti" T_Z80_RST "rst"
|
||||||
%token T_Z80_RL "rl" T_Z80_RLA "rla" T_Z80_RLC "rlc" T_Z80_RLCA "rlca"
|
%token T_Z80_RL "rl" T_Z80_RLA "rla" T_Z80_RLC "rlc" T_Z80_RLCA "rlca"
|
||||||
%token T_Z80_RR "rr" T_Z80_RRA "rra" T_Z80_RRC "rrc" T_Z80_RRCA "rrca"
|
%token T_Z80_RR "rr" T_Z80_RRA "rra" T_Z80_RRC "rrc" T_Z80_RRCA "rrca"
|
||||||
%token T_Z80_SBC "sbc" T_Z80_SCF "scf" T_Z80_STOP "stop"
|
%token T_Z80_SBC "sbc" T_Z80_SCF "scf" T_Z80_SET "set" T_Z80_STOP "stop"
|
||||||
%token T_Z80_SLA "sla" T_Z80_SRA "sra" T_Z80_SRL "srl" T_Z80_SUB "sub"
|
%token T_Z80_SLA "sla" T_Z80_SRA "sra" T_Z80_SRL "srl" T_Z80_SUB "sub"
|
||||||
%token T_Z80_SWAP "swap"
|
%token T_Z80_SWAP "swap"
|
||||||
%token T_Z80_XOR "xor"
|
%token T_Z80_XOR "xor"
|
||||||
@@ -692,22 +690,24 @@ asmfile : lines
|
|||||||
;
|
;
|
||||||
|
|
||||||
lines : %empty
|
lines : %empty
|
||||||
| lines line
|
| lines opt_diff_mark line
|
||||||
;
|
;
|
||||||
|
|
||||||
endofline : T_NEWLINE | T_EOB
|
endofline : T_NEWLINE | T_EOB
|
||||||
;
|
;
|
||||||
|
|
||||||
plain_directive : label
|
opt_diff_mark : %empty // OK
|
||||||
| label cpu_command
|
| T_OP_ADD {
|
||||||
| label macro
|
error("syntax error, unexpected + at the beginning of the line (is it a leftover diff mark?)\n");
|
||||||
| label directive
|
}
|
||||||
| assignment_directive
|
| T_OP_SUB {
|
||||||
|
error("syntax error, unexpected - at the beginning of the line (is it a leftover diff mark?)\n");
|
||||||
|
}
|
||||||
;
|
;
|
||||||
|
|
||||||
line : plain_directive endofline
|
line : plain_directive endofline
|
||||||
| line_directive /* Directives that manage newlines themselves */
|
| line_directive // Directives that manage newlines themselves
|
||||||
/* Continue parsing the next line on a syntax error */
|
// Continue parsing the next line on a syntax error
|
||||||
| error {
|
| error {
|
||||||
lexer_SetMode(LEXER_NORMAL);
|
lexer_SetMode(LEXER_NORMAL);
|
||||||
lexer_ToggleStringExpansion(true);
|
lexer_ToggleStringExpansion(true);
|
||||||
@@ -715,7 +715,7 @@ line : plain_directive endofline
|
|||||||
fstk_StopRept();
|
fstk_StopRept();
|
||||||
yyerrok;
|
yyerrok;
|
||||||
}
|
}
|
||||||
/* Hint about unindented macros parsed as labels */
|
// Hint about unindented macros parsed as labels
|
||||||
| T_LABEL error {
|
| T_LABEL error {
|
||||||
lexer_SetMode(LEXER_NORMAL);
|
lexer_SetMode(LEXER_NORMAL);
|
||||||
lexer_ToggleStringExpansion(true);
|
lexer_ToggleStringExpansion(true);
|
||||||
@@ -730,19 +730,17 @@ line : plain_directive endofline
|
|||||||
}
|
}
|
||||||
;
|
;
|
||||||
|
|
||||||
/*
|
// For "logistical" reasons, these directives must manage newlines themselves.
|
||||||
* For "logistical" reasons, these directives must manage newlines themselves.
|
// This is because we need to switch the lexer's mode *after* the newline has been read,
|
||||||
* This is because we need to switch the lexer's mode *after* the newline has been read,
|
// and to avoid causing some grammar conflicts (token reducing is finicky).
|
||||||
* and to avoid causing some grammar conflicts (token reducing is finicky).
|
// This is DEFINITELY one of the more FRAGILE parts of the codebase, handle with care.
|
||||||
* This is DEFINITELY one of the more FRAGILE parts of the codebase, handle with care.
|
|
||||||
*/
|
|
||||||
line_directive : macrodef
|
line_directive : macrodef
|
||||||
| rept
|
| rept
|
||||||
| for
|
| for
|
||||||
| break
|
| break
|
||||||
| include
|
| include
|
||||||
| if
|
| if
|
||||||
/* It's important that all of these require being at line start for `skipIfBlock` */
|
// It's important that all of these require being at line start for `skipIfBlock`
|
||||||
| elif
|
| elif
|
||||||
| else
|
| else
|
||||||
;
|
;
|
||||||
@@ -790,6 +788,13 @@ else : T_POP_ELSE T_NEWLINE {
|
|||||||
}
|
}
|
||||||
;
|
;
|
||||||
|
|
||||||
|
plain_directive : label
|
||||||
|
| label cpu_command
|
||||||
|
| label macro
|
||||||
|
| label directive
|
||||||
|
| assignment_directive
|
||||||
|
;
|
||||||
|
|
||||||
endc : T_POP_ENDC {
|
endc : T_POP_ENDC {
|
||||||
lexer_DecIFDepth();
|
lexer_DecIFDepth();
|
||||||
}
|
}
|
||||||
@@ -853,7 +858,7 @@ macroargs : %empty {
|
|||||||
}
|
}
|
||||||
;
|
;
|
||||||
|
|
||||||
/* These commands start with a T_LABEL. */
|
// These commands start with a T_LABEL.
|
||||||
assignment_directive : equ
|
assignment_directive : equ
|
||||||
| assignment
|
| assignment
|
||||||
| rb
|
| rb
|
||||||
@@ -1099,6 +1104,7 @@ macrodef : T_POP_MACRO {
|
|||||||
captureBody.size);
|
captureBody.size);
|
||||||
}
|
}
|
||||||
| T_LABEL T_COLON T_POP_MACRO T_NEWLINE {
|
| T_LABEL T_COLON T_POP_MACRO T_NEWLINE {
|
||||||
|
warning(WARNING_OBSOLETE, "`%s: MACRO` is deprecated; use `MACRO %s`\n", $1, $1);
|
||||||
$<captureTerminated>$ = lexer_CaptureMacroBody(&captureBody);
|
$<captureTerminated>$ = lexer_CaptureMacroBody(&captureBody);
|
||||||
} endofline {
|
} endofline {
|
||||||
if ($<captureTerminated>5)
|
if ($<captureTerminated>5)
|
||||||
@@ -1298,7 +1304,7 @@ constlist_8bit_entry : reloc_8bit_no_str {
|
|||||||
sect_RelByte(&$1, 0);
|
sect_RelByte(&$1, 0);
|
||||||
}
|
}
|
||||||
| string {
|
| string {
|
||||||
uint8_t *output = malloc(strlen($1)); /* Cannot be larger than that */
|
uint8_t *output = malloc(strlen($1)); // Cannot be larger than that
|
||||||
size_t length = charmap_Convert($1, output);
|
size_t length = charmap_Convert($1, output);
|
||||||
|
|
||||||
sect_AbsByteGroup(output, length);
|
sect_AbsByteGroup(output, length);
|
||||||
@@ -1314,7 +1320,7 @@ constlist_16bit_entry : reloc_16bit_no_str {
|
|||||||
sect_RelWord(&$1, 0);
|
sect_RelWord(&$1, 0);
|
||||||
}
|
}
|
||||||
| string {
|
| string {
|
||||||
uint8_t *output = malloc(strlen($1)); /* Cannot be larger than that */
|
uint8_t *output = malloc(strlen($1)); // Cannot be larger than that
|
||||||
size_t length = charmap_Convert($1, output);
|
size_t length = charmap_Convert($1, output);
|
||||||
|
|
||||||
sect_AbsWordGroup(output, length);
|
sect_AbsWordGroup(output, length);
|
||||||
@@ -1467,53 +1473,54 @@ relocexpr_no_str : scoped_anon_id { rpn_Symbol(&$$, $1); }
|
|||||||
| T_OP_DEF {
|
| T_OP_DEF {
|
||||||
lexer_ToggleStringExpansion(false);
|
lexer_ToggleStringExpansion(false);
|
||||||
} T_LPAREN scoped_anon_id T_RPAREN {
|
} T_LPAREN scoped_anon_id T_RPAREN {
|
||||||
struct Symbol const *sym = sym_FindScopedSymbol($4);
|
rpn_Number(&$$, sym_FindScopedValidSymbol($4) != NULL);
|
||||||
|
|
||||||
rpn_Number(&$$, !!sym);
|
|
||||||
|
|
||||||
lexer_ToggleStringExpansion(true);
|
lexer_ToggleStringExpansion(true);
|
||||||
}
|
}
|
||||||
| T_OP_ROUND T_LPAREN const T_RPAREN {
|
| T_OP_ROUND T_LPAREN const opt_q_arg T_RPAREN {
|
||||||
rpn_Number(&$$, fix_Round($3));
|
rpn_Number(&$$, fix_Round($3, $4));
|
||||||
}
|
}
|
||||||
| T_OP_CEIL T_LPAREN const T_RPAREN {
|
| T_OP_CEIL T_LPAREN const opt_q_arg T_RPAREN {
|
||||||
rpn_Number(&$$, fix_Ceil($3));
|
rpn_Number(&$$, fix_Ceil($3, $4));
|
||||||
}
|
}
|
||||||
| T_OP_FLOOR T_LPAREN const T_RPAREN {
|
| T_OP_FLOOR T_LPAREN const opt_q_arg T_RPAREN {
|
||||||
rpn_Number(&$$, fix_Floor($3));
|
rpn_Number(&$$, fix_Floor($3, $4));
|
||||||
}
|
}
|
||||||
| T_OP_FDIV T_LPAREN const T_COMMA const T_RPAREN {
|
| T_OP_FDIV T_LPAREN const T_COMMA const opt_q_arg T_RPAREN {
|
||||||
rpn_Number(&$$, fix_Div($3, $5));
|
rpn_Number(&$$, fix_Div($3, $5, $6));
|
||||||
}
|
}
|
||||||
| T_OP_FMUL T_LPAREN const T_COMMA const T_RPAREN {
|
| T_OP_FMUL T_LPAREN const T_COMMA const opt_q_arg T_RPAREN {
|
||||||
rpn_Number(&$$, fix_Mul($3, $5));
|
rpn_Number(&$$, fix_Mul($3, $5, $6));
|
||||||
}
|
}
|
||||||
| T_OP_POW T_LPAREN const T_COMMA const T_RPAREN {
|
| T_OP_FMOD T_LPAREN const T_COMMA const opt_q_arg T_RPAREN {
|
||||||
rpn_Number(&$$, fix_Pow($3, $5));
|
rpn_Number(&$$, fix_Mod($3, $5, $6));
|
||||||
}
|
}
|
||||||
| T_OP_LOG T_LPAREN const T_COMMA const T_RPAREN {
|
| T_OP_POW T_LPAREN const T_COMMA const opt_q_arg T_RPAREN {
|
||||||
rpn_Number(&$$, fix_Log($3, $5));
|
rpn_Number(&$$, fix_Pow($3, $5, $6));
|
||||||
}
|
}
|
||||||
| T_OP_SIN T_LPAREN const T_RPAREN {
|
| T_OP_LOG T_LPAREN const T_COMMA const opt_q_arg T_RPAREN {
|
||||||
rpn_Number(&$$, fix_Sin($3));
|
rpn_Number(&$$, fix_Log($3, $5, $6));
|
||||||
}
|
}
|
||||||
| T_OP_COS T_LPAREN const T_RPAREN {
|
| T_OP_SIN T_LPAREN const opt_q_arg T_RPAREN {
|
||||||
rpn_Number(&$$, fix_Cos($3));
|
rpn_Number(&$$, fix_Sin($3, $4));
|
||||||
}
|
}
|
||||||
| T_OP_TAN T_LPAREN const T_RPAREN {
|
| T_OP_COS T_LPAREN const opt_q_arg T_RPAREN {
|
||||||
rpn_Number(&$$, fix_Tan($3));
|
rpn_Number(&$$, fix_Cos($3, $4));
|
||||||
}
|
}
|
||||||
| T_OP_ASIN T_LPAREN const T_RPAREN {
|
| T_OP_TAN T_LPAREN const opt_q_arg T_RPAREN {
|
||||||
rpn_Number(&$$, fix_ASin($3));
|
rpn_Number(&$$, fix_Tan($3, $4));
|
||||||
}
|
}
|
||||||
| T_OP_ACOS T_LPAREN const T_RPAREN {
|
| T_OP_ASIN T_LPAREN const opt_q_arg T_RPAREN {
|
||||||
rpn_Number(&$$, fix_ACos($3));
|
rpn_Number(&$$, fix_ASin($3, $4));
|
||||||
}
|
}
|
||||||
| T_OP_ATAN T_LPAREN const T_RPAREN {
|
| T_OP_ACOS T_LPAREN const opt_q_arg T_RPAREN {
|
||||||
rpn_Number(&$$, fix_ATan($3));
|
rpn_Number(&$$, fix_ACos($3, $4));
|
||||||
}
|
}
|
||||||
| T_OP_ATAN2 T_LPAREN const T_COMMA const T_RPAREN {
|
| T_OP_ATAN T_LPAREN const opt_q_arg T_RPAREN {
|
||||||
rpn_Number(&$$, fix_ATan2($3, $5));
|
rpn_Number(&$$, fix_ATan($3, $4));
|
||||||
|
}
|
||||||
|
| T_OP_ATAN2 T_LPAREN const T_COMMA const opt_q_arg T_RPAREN {
|
||||||
|
rpn_Number(&$$, fix_ATan2($3, $5, $6));
|
||||||
}
|
}
|
||||||
| T_OP_STRCMP T_LPAREN string T_COMMA string T_RPAREN {
|
| T_OP_STRCMP T_LPAREN string T_COMMA string T_RPAREN {
|
||||||
rpn_Number(&$$, strcmp($3, $5));
|
rpn_Number(&$$, strcmp($3, $5));
|
||||||
@@ -1540,7 +1547,7 @@ relocexpr_no_str : scoped_anon_id { rpn_Symbol(&$$, $1); }
|
|||||||
uconst : const {
|
uconst : const {
|
||||||
$$ = $1;
|
$$ = $1;
|
||||||
if ($$ < 0)
|
if ($$ < 0)
|
||||||
fatalerror("Constant mustn't be negative: %d\n", $1);
|
fatalerror("Constant must not be negative: %d\n", $1);
|
||||||
}
|
}
|
||||||
;
|
;
|
||||||
|
|
||||||
@@ -1553,6 +1560,17 @@ const_no_str : relocexpr_no_str { $$ = rpn_GetConstVal(&$1); }
|
|||||||
const_8bit : reloc_8bit { $$ = rpn_GetConstVal(&$1); }
|
const_8bit : reloc_8bit { $$ = rpn_GetConstVal(&$1); }
|
||||||
;
|
;
|
||||||
|
|
||||||
|
opt_q_arg : %empty { $$ = fix_Precision(); }
|
||||||
|
| T_COMMA const {
|
||||||
|
if ($2 >= 1 && $2 <= 31) {
|
||||||
|
$$ = $2;
|
||||||
|
} else {
|
||||||
|
error("Fixed-point precision must be between 1 and 31\n");
|
||||||
|
$$ = fix_Precision();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
;
|
||||||
|
|
||||||
string : T_STRING
|
string : T_STRING
|
||||||
| T_OP_STRSUB T_LPAREN string T_COMMA const T_COMMA uconst T_RPAREN {
|
| T_OP_STRSUB T_LPAREN string T_COMMA const T_COMMA uconst T_RPAREN {
|
||||||
size_t len = strlenUTF8($3);
|
size_t len = strlenUTF8($3);
|
||||||
@@ -1591,6 +1609,19 @@ string : T_STRING
|
|||||||
strfmt($$, sizeof($$), $3.format, $3.nbArgs, $3.args);
|
strfmt($$, sizeof($$), $3.format, $3.nbArgs, $3.args);
|
||||||
freeStrFmtArgList(&$3);
|
freeStrFmtArgList(&$3);
|
||||||
}
|
}
|
||||||
|
| T_POP_SECTION T_LPAREN scoped_anon_id T_RPAREN {
|
||||||
|
struct Symbol *sym = sym_FindScopedValidSymbol($3);
|
||||||
|
|
||||||
|
if (!sym)
|
||||||
|
fatalerror("Unknown symbol \"%s\"\n", $3);
|
||||||
|
struct Section const *section = sym_GetSection(sym);
|
||||||
|
|
||||||
|
if (!section)
|
||||||
|
fatalerror("\"%s\" does not belong to any section\n", sym->name);
|
||||||
|
// Section names are capped by rgbasm's maximum string length,
|
||||||
|
// so this currently can't overflow.
|
||||||
|
strcpy($$, section->name);
|
||||||
|
}
|
||||||
;
|
;
|
||||||
|
|
||||||
strcat_args : string
|
strcat_args : string
|
||||||
@@ -1676,7 +1707,7 @@ sectattrs : %empty {
|
|||||||
$$.alignOfs = $7;
|
$$.alignOfs = $7;
|
||||||
}
|
}
|
||||||
| sectattrs T_COMMA T_OP_BANK T_LBRACK uconst T_RBRACK {
|
| sectattrs T_COMMA T_OP_BANK T_LBRACK uconst T_RBRACK {
|
||||||
/* We cannot check the validity of this now */
|
// We cannot check the validity of this now
|
||||||
$$.bank = $5;
|
$$.bank = $5;
|
||||||
}
|
}
|
||||||
;
|
;
|
||||||
@@ -1993,10 +2024,8 @@ z80_ld_ss : T_Z80_LD T_MODE_BC T_COMMA reloc_16bit {
|
|||||||
sect_AbsByte(0x01 | (REG_DE << 4));
|
sect_AbsByte(0x01 | (REG_DE << 4));
|
||||||
sect_RelWord(&$4, 1);
|
sect_RelWord(&$4, 1);
|
||||||
}
|
}
|
||||||
/*
|
// HL is taken care of in z80_ld_hl
|
||||||
* HL is taken care of in z80_ld_hl
|
// SP is taken care of in z80_ld_sp
|
||||||
* SP is taken care of in z80_ld_sp
|
|
||||||
*/
|
|
||||||
;
|
;
|
||||||
|
|
||||||
z80_nop : T_Z80_NOP { sect_AbsByte(0x00); }
|
z80_nop : T_Z80_NOP { sect_AbsByte(0x00); }
|
||||||
@@ -2084,7 +2113,7 @@ z80_sbc : T_Z80_SBC op_a_n {
|
|||||||
z80_scf : T_Z80_SCF { sect_AbsByte(0x37); }
|
z80_scf : T_Z80_SCF { sect_AbsByte(0x37); }
|
||||||
;
|
;
|
||||||
|
|
||||||
z80_set : T_POP_SET const_3bit T_COMMA reg_r {
|
z80_set : T_Z80_SET const_3bit T_COMMA reg_r {
|
||||||
sect_AbsByte(0xCB);
|
sect_AbsByte(0xCB);
|
||||||
sect_AbsByte(0xC0 | ($2 << 3) | $4);
|
sect_AbsByte(0xC0 | ($2 << 3) | $4);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,9 +6,7 @@
|
|||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
// Controls RPN expressions for objectfiles
|
||||||
* Controls RPN expressions for objectfiles
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
@@ -28,7 +26,7 @@
|
|||||||
|
|
||||||
#include "opmath.h"
|
#include "opmath.h"
|
||||||
|
|
||||||
/* Makes an expression "not known", also setting its error message */
|
// Makes an expression "not known", also setting its error message
|
||||||
#define makeUnknown(expr_, ...) do { \
|
#define makeUnknown(expr_, ...) do { \
|
||||||
struct Expression *_expr = expr_; \
|
struct Expression *_expr = expr_; \
|
||||||
_expr->isKnown = false; \
|
_expr->isKnown = false; \
|
||||||
@@ -47,17 +45,15 @@
|
|||||||
|
|
||||||
static uint8_t *reserveSpace(struct Expression *expr, uint32_t size)
|
static uint8_t *reserveSpace(struct Expression *expr, uint32_t size)
|
||||||
{
|
{
|
||||||
/* This assumes the RPN length is always less than the capacity */
|
// This assumes the RPN length is always less than the capacity
|
||||||
if (expr->rpnCapacity - expr->rpnLength < size) {
|
if (expr->rpnCapacity - expr->rpnLength < size) {
|
||||||
/* If there isn't enough room to reserve the space, realloc */
|
// If there isn't enough room to reserve the space, realloc
|
||||||
if (!expr->rpn)
|
if (!expr->rpn)
|
||||||
expr->rpnCapacity = 256; /* Initial size */
|
expr->rpnCapacity = 256; // Initial size
|
||||||
while (expr->rpnCapacity - expr->rpnLength < size) {
|
while (expr->rpnCapacity - expr->rpnLength < size) {
|
||||||
if (expr->rpnCapacity >= MAXRPNLEN)
|
if (expr->rpnCapacity >= MAXRPNLEN)
|
||||||
/*
|
// To avoid generating humongous object files, cap the
|
||||||
* To avoid generating humongous object files, cap the
|
// size of RPN expressions
|
||||||
* size of RPN expressions
|
|
||||||
*/
|
|
||||||
fatalerror("RPN expression cannot grow larger than "
|
fatalerror("RPN expression cannot grow larger than "
|
||||||
EXPAND_AND_STR(MAXRPNLEN) " bytes\n");
|
EXPAND_AND_STR(MAXRPNLEN) " bytes\n");
|
||||||
else if (expr->rpnCapacity > MAXRPNLEN / 2)
|
else if (expr->rpnCapacity > MAXRPNLEN / 2)
|
||||||
@@ -77,9 +73,7 @@ static uint8_t *reserveSpace(struct Expression *expr, uint32_t size)
|
|||||||
return ptr;
|
return ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Init a RPN expression
|
||||||
* Init a RPN expression
|
|
||||||
*/
|
|
||||||
static void rpn_Init(struct Expression *expr)
|
static void rpn_Init(struct Expression *expr)
|
||||||
{
|
{
|
||||||
expr->reason = NULL;
|
expr->reason = NULL;
|
||||||
@@ -91,9 +85,7 @@ static void rpn_Init(struct Expression *expr)
|
|||||||
expr->rpnPatchSize = 0;
|
expr->rpnPatchSize = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Free the RPN expression
|
||||||
* Free the RPN expression
|
|
||||||
*/
|
|
||||||
void rpn_Free(struct Expression *expr)
|
void rpn_Free(struct Expression *expr)
|
||||||
{
|
{
|
||||||
free(expr->rpn);
|
free(expr->rpn);
|
||||||
@@ -101,9 +93,7 @@ void rpn_Free(struct Expression *expr)
|
|||||||
rpn_Init(expr);
|
rpn_Init(expr);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Add symbols, constants and operators to expression
|
||||||
* Add symbols, constants and operators to expression
|
|
||||||
*/
|
|
||||||
void rpn_Number(struct Expression *expr, uint32_t i)
|
void rpn_Number(struct Expression *expr, uint32_t i)
|
||||||
{
|
{
|
||||||
rpn_Init(expr);
|
rpn_Init(expr);
|
||||||
@@ -124,9 +114,9 @@ void rpn_Symbol(struct Expression *expr, char const *symName)
|
|||||||
makeUnknown(expr, sym_IsPC(sym) ? "PC is not constant at assembly time"
|
makeUnknown(expr, sym_IsPC(sym) ? "PC is not constant at assembly time"
|
||||||
: "'%s' is not constant at assembly time", symName);
|
: "'%s' is not constant at assembly time", symName);
|
||||||
sym = sym_Ref(symName);
|
sym = sym_Ref(symName);
|
||||||
expr->rpnPatchSize += 5; /* 1-byte opcode + 4-byte symbol ID */
|
expr->rpnPatchSize += 5; // 1-byte opcode + 4-byte symbol ID
|
||||||
|
|
||||||
size_t nameLen = strlen(sym->name) + 1; /* Don't forget NUL! */
|
size_t nameLen = strlen(sym->name) + 1; // Don't forget NUL!
|
||||||
uint8_t *ptr = reserveSpace(expr, nameLen + 1);
|
uint8_t *ptr = reserveSpace(expr, nameLen + 1);
|
||||||
*ptr++ = RPN_SYM;
|
*ptr++ = RPN_SYM;
|
||||||
memcpy(ptr, sym->name, nameLen);
|
memcpy(ptr, sym->name, nameLen);
|
||||||
@@ -155,7 +145,7 @@ void rpn_BankSymbol(struct Expression *expr, char const *symName)
|
|||||||
{
|
{
|
||||||
struct Symbol const *sym = sym_FindScopedSymbol(symName);
|
struct Symbol const *sym = sym_FindScopedSymbol(symName);
|
||||||
|
|
||||||
/* The @ symbol is treated differently. */
|
// The @ symbol is treated differently.
|
||||||
if (sym_IsPC(sym)) {
|
if (sym_IsPC(sym)) {
|
||||||
rpn_BankSelf(expr);
|
rpn_BankSelf(expr);
|
||||||
return;
|
return;
|
||||||
@@ -169,13 +159,13 @@ void rpn_BankSymbol(struct Expression *expr, char const *symName)
|
|||||||
assert(sym); // If the symbol didn't exist, it should have been created
|
assert(sym); // If the symbol didn't exist, it should have been created
|
||||||
|
|
||||||
if (sym_GetSection(sym) && sym_GetSection(sym)->bank != (uint32_t)-1) {
|
if (sym_GetSection(sym) && sym_GetSection(sym)->bank != (uint32_t)-1) {
|
||||||
/* Symbol's section is known and bank is fixed */
|
// Symbol's section is known and bank is fixed
|
||||||
expr->val = sym_GetSection(sym)->bank;
|
expr->val = sym_GetSection(sym)->bank;
|
||||||
} else {
|
} else {
|
||||||
makeUnknown(expr, "\"%s\"'s bank is not known", symName);
|
makeUnknown(expr, "\"%s\"'s bank is not known", symName);
|
||||||
expr->rpnPatchSize += 5; /* opcode + 4-byte sect ID */
|
expr->rpnPatchSize += 5; // opcode + 4-byte sect ID
|
||||||
|
|
||||||
size_t nameLen = strlen(sym->name) + 1; /* Room for NUL! */
|
size_t nameLen = strlen(sym->name) + 1; // Room for NUL!
|
||||||
uint8_t *ptr = reserveSpace(expr, nameLen + 1);
|
uint8_t *ptr = reserveSpace(expr, nameLen + 1);
|
||||||
*ptr++ = RPN_BANK_SYM;
|
*ptr++ = RPN_BANK_SYM;
|
||||||
memcpy(ptr, sym->name, nameLen);
|
memcpy(ptr, sym->name, nameLen);
|
||||||
@@ -194,7 +184,7 @@ void rpn_BankSection(struct Expression *expr, char const *sectionName)
|
|||||||
} else {
|
} else {
|
||||||
makeUnknown(expr, "Section \"%s\"'s bank is not known", sectionName);
|
makeUnknown(expr, "Section \"%s\"'s bank is not known", sectionName);
|
||||||
|
|
||||||
size_t nameLen = strlen(sectionName) + 1; /* Room for NUL! */
|
size_t nameLen = strlen(sectionName) + 1; // Room for NUL!
|
||||||
uint8_t *ptr = reserveSpace(expr, nameLen + 1);
|
uint8_t *ptr = reserveSpace(expr, nameLen + 1);
|
||||||
|
|
||||||
expr->rpnPatchSize += nameLen + 1;
|
expr->rpnPatchSize += nameLen + 1;
|
||||||
@@ -214,7 +204,7 @@ void rpn_SizeOfSection(struct Expression *expr, char const *sectionName)
|
|||||||
} else {
|
} else {
|
||||||
makeUnknown(expr, "Section \"%s\"'s size is not known", sectionName);
|
makeUnknown(expr, "Section \"%s\"'s size is not known", sectionName);
|
||||||
|
|
||||||
size_t nameLen = strlen(sectionName) + 1; /* Room for NUL! */
|
size_t nameLen = strlen(sectionName) + 1; // Room for NUL!
|
||||||
uint8_t *ptr = reserveSpace(expr, nameLen + 1);
|
uint8_t *ptr = reserveSpace(expr, nameLen + 1);
|
||||||
|
|
||||||
expr->rpnPatchSize += nameLen + 1;
|
expr->rpnPatchSize += nameLen + 1;
|
||||||
@@ -234,7 +224,7 @@ void rpn_StartOfSection(struct Expression *expr, char const *sectionName)
|
|||||||
} else {
|
} else {
|
||||||
makeUnknown(expr, "Section \"%s\"'s start is not known", sectionName);
|
makeUnknown(expr, "Section \"%s\"'s start is not known", sectionName);
|
||||||
|
|
||||||
size_t nameLen = strlen(sectionName) + 1; /* Room for NUL! */
|
size_t nameLen = strlen(sectionName) + 1; // Room for NUL!
|
||||||
uint8_t *ptr = reserveSpace(expr, nameLen + 1);
|
uint8_t *ptr = reserveSpace(expr, nameLen + 1);
|
||||||
|
|
||||||
expr->rpnPatchSize += nameLen + 1;
|
expr->rpnPatchSize += nameLen + 1;
|
||||||
@@ -252,7 +242,7 @@ void rpn_CheckHRAM(struct Expression *expr, const struct Expression *src)
|
|||||||
expr->rpnPatchSize++;
|
expr->rpnPatchSize++;
|
||||||
*reserveSpace(expr, 1) = RPN_HRAM;
|
*reserveSpace(expr, 1) = RPN_HRAM;
|
||||||
} else if (expr->val >= 0xFF00 && expr->val <= 0xFFFF) {
|
} else if (expr->val >= 0xFF00 && expr->val <= 0xFFFF) {
|
||||||
/* That range is valid, but only keep the lower byte */
|
// That range is valid, but only keep the lower byte
|
||||||
expr->val &= 0xFF;
|
expr->val &= 0xFF;
|
||||||
} else if (expr->val < 0 || expr->val > 0xFF) {
|
} else if (expr->val < 0 || expr->val > 0xFF) {
|
||||||
error("Source address $%" PRIx32 " not between $FF00 to $FFFF\n", expr->val);
|
error("Source address $%" PRIx32 " not between $FF00 to $FFFF\n", expr->val);
|
||||||
@@ -264,10 +254,10 @@ void rpn_CheckRST(struct Expression *expr, const struct Expression *src)
|
|||||||
*expr = *src;
|
*expr = *src;
|
||||||
|
|
||||||
if (rpn_isKnown(expr)) {
|
if (rpn_isKnown(expr)) {
|
||||||
/* A valid RST address must be masked with 0x38 */
|
// A valid RST address must be masked with 0x38
|
||||||
if (expr->val & ~0x38)
|
if (expr->val & ~0x38)
|
||||||
error("Invalid address $%" PRIx32 " for RST\n", expr->val);
|
error("Invalid address $%" PRIx32 " for RST\n", expr->val);
|
||||||
/* The target is in the "0x38" bits, all other bits are set */
|
// The target is in the "0x38" bits, all other bits are set
|
||||||
expr->val |= 0xC7;
|
expr->val |= 0xC7;
|
||||||
} else {
|
} else {
|
||||||
expr->rpnPatchSize++;
|
expr->rpnPatchSize++;
|
||||||
@@ -275,9 +265,7 @@ void rpn_CheckRST(struct Expression *expr, const struct Expression *src)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// 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 rpn_CheckNBit(struct Expression const *expr, uint8_t n)
|
void rpn_CheckNBit(struct Expression const *expr, uint8_t n)
|
||||||
{
|
{
|
||||||
assert(n != 0); // That doesn't make sense
|
assert(n != 0); // That doesn't make sense
|
||||||
@@ -324,7 +312,7 @@ struct Symbol const *rpn_SymbolOf(struct Expression const *expr)
|
|||||||
|
|
||||||
bool rpn_IsDiffConstant(struct Expression const *src, struct Symbol const *sym)
|
bool rpn_IsDiffConstant(struct Expression const *src, struct Symbol const *sym)
|
||||||
{
|
{
|
||||||
/* Check if both expressions only refer to a single symbol */
|
// Check if both expressions only refer to a single symbol
|
||||||
struct Symbol const *sym1 = rpn_SymbolOf(src);
|
struct Symbol const *sym1 = rpn_SymbolOf(src);
|
||||||
|
|
||||||
if (!sym1 || !sym || sym1->type != SYM_LABEL || sym->type != SYM_LABEL)
|
if (!sym1 || !sym || sym1->type != SYM_LABEL || sym->type != SYM_LABEL)
|
||||||
@@ -341,7 +329,7 @@ static bool isDiffConstant(struct Expression const *src1,
|
|||||||
return rpn_IsDiffConstant(src1, rpn_SymbolOf(src2));
|
return rpn_IsDiffConstant(src1, rpn_SymbolOf(src2));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Attempts to compute a constant binary AND from non-constant operands
|
* Attempts to compute a constant binary AND from 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.
|
||||||
@@ -387,12 +375,12 @@ void rpn_BinaryOp(enum RPNCommand op, struct Expression *expr,
|
|||||||
expr->isSymbol = false;
|
expr->isSymbol = false;
|
||||||
int32_t constMaskVal;
|
int32_t constMaskVal;
|
||||||
|
|
||||||
/* First, check if the expression is known */
|
// First, check if the expression is known
|
||||||
expr->isKnown = src1->isKnown && src2->isKnown;
|
expr->isKnown = src1->isKnown && src2->isKnown;
|
||||||
if (expr->isKnown) {
|
if (expr->isKnown) {
|
||||||
rpn_Init(expr); /* Init the expression to something sane */
|
rpn_Init(expr); // Init the expression to something sane
|
||||||
|
|
||||||
/* If both expressions are known, just compute the value */
|
// If both expressions are known, just compute the value
|
||||||
uint32_t uleft = src1->val, uright = src2->val;
|
uint32_t uleft = src1->val, uright = src2->val;
|
||||||
|
|
||||||
switch (op) {
|
switch (op) {
|
||||||
@@ -537,9 +525,9 @@ void rpn_BinaryOp(enum RPNCommand op, struct Expression *expr,
|
|||||||
expr->val = constMaskVal;
|
expr->val = constMaskVal;
|
||||||
expr->isKnown = true;
|
expr->isKnown = true;
|
||||||
} else {
|
} else {
|
||||||
/* If it's not known, start computing the RPN expression */
|
// If it's not known, start computing the RPN expression
|
||||||
|
|
||||||
/* Convert the left-hand expression if it's constant */
|
// Convert the left-hand expression if it's constant
|
||||||
if (src1->isKnown) {
|
if (src1->isKnown) {
|
||||||
uint32_t lval = src1->val;
|
uint32_t lval = src1->val;
|
||||||
uint8_t bytes[] = {RPN_CONST, lval, lval >> 8,
|
uint8_t bytes[] = {RPN_CONST, lval, lval >> 8,
|
||||||
@@ -551,11 +539,11 @@ void rpn_BinaryOp(enum RPNCommand op, struct Expression *expr,
|
|||||||
memcpy(reserveSpace(expr, sizeof(bytes)), bytes,
|
memcpy(reserveSpace(expr, sizeof(bytes)), bytes,
|
||||||
sizeof(bytes));
|
sizeof(bytes));
|
||||||
|
|
||||||
/* Use the other expression's un-const reason */
|
// Use the other expression's un-const reason
|
||||||
expr->reason = src2->reason;
|
expr->reason = src2->reason;
|
||||||
free(src1->reason);
|
free(src1->reason);
|
||||||
} else {
|
} else {
|
||||||
/* Otherwise just reuse its RPN buffer */
|
// Otherwise just reuse its RPN buffer
|
||||||
expr->rpnPatchSize = src1->rpnPatchSize;
|
expr->rpnPatchSize = src1->rpnPatchSize;
|
||||||
expr->rpn = src1->rpn;
|
expr->rpn = src1->rpn;
|
||||||
expr->rpnCapacity = src1->rpnCapacity;
|
expr->rpnCapacity = src1->rpnCapacity;
|
||||||
@@ -564,12 +552,12 @@ void rpn_BinaryOp(enum RPNCommand op, struct Expression *expr,
|
|||||||
free(src2->reason);
|
free(src2->reason);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Now, merge the right expression into the left one */
|
// Now, merge the right expression into the left one
|
||||||
uint8_t *ptr = src2->rpn; /* Pointer to the right RPN */
|
uint8_t *ptr = src2->rpn; // Pointer to the right RPN
|
||||||
uint32_t len = src2->rpnLength; /* Size of the right RPN */
|
uint32_t len = src2->rpnLength; // Size of the right RPN
|
||||||
uint32_t patchSize = src2->rpnPatchSize;
|
uint32_t patchSize = src2->rpnPatchSize;
|
||||||
|
|
||||||
/* If the right expression is constant, merge a shim instead */
|
// If the right expression is constant, merge a shim instead
|
||||||
uint32_t rval = src2->val;
|
uint32_t rval = src2->val;
|
||||||
uint8_t bytes[] = {RPN_CONST, rval, rval >> 8, rval >> 16,
|
uint8_t bytes[] = {RPN_CONST, rval, rval >> 8, rval >> 16,
|
||||||
rval >> 24};
|
rval >> 24};
|
||||||
@@ -578,13 +566,13 @@ void rpn_BinaryOp(enum RPNCommand op, struct Expression *expr,
|
|||||||
len = sizeof(bytes);
|
len = sizeof(bytes);
|
||||||
patchSize = sizeof(bytes);
|
patchSize = sizeof(bytes);
|
||||||
}
|
}
|
||||||
/* Copy the right RPN and append the operator */
|
// Copy the right RPN and append the operator
|
||||||
uint8_t *buf = reserveSpace(expr, len + 1);
|
uint8_t *buf = reserveSpace(expr, len + 1);
|
||||||
|
|
||||||
memcpy(buf, ptr, len);
|
memcpy(buf, ptr, len);
|
||||||
buf[len] = op;
|
buf[len] = op;
|
||||||
|
|
||||||
free(src2->rpn); /* If there was none, this is `free(NULL)` */
|
free(src2->rpn); // If there was none, this is `free(NULL)`
|
||||||
expr->rpnPatchSize += patchSize + 1;
|
expr->rpnPatchSize += patchSize + 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of RGBDS.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2022, RGBDS contributors.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
@@ -16,6 +23,7 @@
|
|||||||
#include "asm/warning.h"
|
#include "asm/warning.h"
|
||||||
|
|
||||||
#include "error.h"
|
#include "error.h"
|
||||||
|
#include "linkdefs.h"
|
||||||
#include "platform.h" // strdup
|
#include "platform.h" // strdup
|
||||||
|
|
||||||
uint8_t fillByte;
|
uint8_t fillByte;
|
||||||
@@ -29,7 +37,7 @@ struct UnionStackEntry {
|
|||||||
struct SectionStackEntry {
|
struct SectionStackEntry {
|
||||||
struct Section *section;
|
struct Section *section;
|
||||||
struct Section *loadSection;
|
struct Section *loadSection;
|
||||||
char const *scope; /* Section's symbol scope */
|
char const *scope; // Section's symbol scope
|
||||||
uint32_t offset;
|
uint32_t offset;
|
||||||
int32_t loadOffset;
|
int32_t loadOffset;
|
||||||
struct UnionStackEntry *unionStack;
|
struct UnionStackEntry *unionStack;
|
||||||
@@ -37,14 +45,12 @@ struct SectionStackEntry {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct SectionStackEntry *sectionStack;
|
struct SectionStackEntry *sectionStack;
|
||||||
uint32_t curOffset; /* Offset into the current section (see sect_GetSymbolOffset) */
|
uint32_t curOffset; // Offset into the current section (see sect_GetSymbolOffset)
|
||||||
struct Section *currentSection = NULL;
|
struct Section *currentSection = NULL;
|
||||||
static struct Section *currentLoadSection = NULL;
|
static struct Section *currentLoadSection = NULL;
|
||||||
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
|
|
||||||
*/
|
|
||||||
attr_(warn_unused_result) static bool checksection(void)
|
attr_(warn_unused_result) static bool checksection(void)
|
||||||
{
|
{
|
||||||
if (currentSection)
|
if (currentSection)
|
||||||
@@ -54,10 +60,8 @@ attr_(warn_unused_result) static bool checksection(void)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// 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
|
|
||||||
*/
|
|
||||||
attr_(warn_unused_result) static bool checkcodesection(void)
|
attr_(warn_unused_result) static bool checkcodesection(void)
|
||||||
{
|
{
|
||||||
if (!checksection())
|
if (!checksection())
|
||||||
@@ -73,7 +77,7 @@ attr_(warn_unused_result) static bool checkcodesection(void)
|
|||||||
|
|
||||||
attr_(warn_unused_result) static bool checkSectionSize(struct Section const *sect, uint32_t size)
|
attr_(warn_unused_result) static bool checkSectionSize(struct Section const *sect, uint32_t size)
|
||||||
{
|
{
|
||||||
uint32_t maxSize = maxsize[sect->type];
|
uint32_t maxSize = sectionTypeInfo[sect->type].size;
|
||||||
|
|
||||||
// If the new size is reasonable, keep going
|
// If the new size is reasonable, keep going
|
||||||
if (size <= maxSize)
|
if (size <= maxSize)
|
||||||
@@ -84,17 +88,13 @@ attr_(warn_unused_result) static bool checkSectionSize(struct Section const *sec
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Check if the section has grown too much.
|
||||||
* Check if the section has grown too much.
|
|
||||||
*/
|
|
||||||
attr_(warn_unused_result) static bool reserveSpace(uint32_t delta_size)
|
attr_(warn_unused_result) static bool reserveSpace(uint32_t delta_size)
|
||||||
{
|
{
|
||||||
/*
|
// This check is here to trap broken code that generates sections that are too big and to
|
||||||
* This check is here to trap broken code that generates sections that are too big and to
|
// prevent the assembler from generating huge object files or trying to allocate too much
|
||||||
* prevent the assembler from generating huge object files or trying to allocate too much
|
// memory.
|
||||||
* memory.
|
// A check at the linking stage is still necessary.
|
||||||
* A check at the linking stage is still necessary.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// If the section has already overflowed, skip the check to avoid erroring out ad nauseam
|
// If the section has already overflowed, skip the check to avoid erroring out ad nauseam
|
||||||
if (currentSection->size != UINT32_MAX
|
if (currentSection->size != UINT32_MAX
|
||||||
@@ -132,15 +132,13 @@ static unsigned int mergeSectUnion(struct Section *sect, enum SectionType type,
|
|||||||
assert(alignment < 16); // Should be ensured by the caller
|
assert(alignment < 16); // Should be ensured by the caller
|
||||||
unsigned int nbSectErrors = 0;
|
unsigned int nbSectErrors = 0;
|
||||||
|
|
||||||
/*
|
// 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))
|
||||||
fail("Cannot declare ROM sections as UNION\n");
|
fail("Cannot declare ROM sections as UNION\n");
|
||||||
|
|
||||||
if (org != (uint32_t)-1) {
|
if (org != (uint32_t)-1) {
|
||||||
/* 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_t)-1 && sect->org != org)
|
||||||
fail("Section already declared as fixed at different address $%04"
|
fail("Section already declared as fixed at different address $%04"
|
||||||
PRIx32 "\n", sect->org);
|
PRIx32 "\n", sect->org);
|
||||||
@@ -148,16 +146,16 @@ static unsigned int mergeSectUnion(struct Section *sect, enum SectionType type,
|
|||||||
fail("Section already declared as aligned to %u bytes (offset %"
|
fail("Section already declared as aligned to %u bytes (offset %"
|
||||||
PRIu16 ")\n", 1U << sect->align, sect->alignOfs);
|
PRIu16 ")\n", 1U << sect->align, 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_t)-1) {
|
||||||
if ((sect->org - alignOffset) & mask(alignment))
|
if ((sect->org - alignOffset) & mask(alignment))
|
||||||
fail("Section already declared as fixed at incompatible address $%04"
|
fail("Section already declared as fixed at incompatible address $%04"
|
||||||
PRIx32 "\n", sect->org);
|
PRIx32 "\n", sect->org);
|
||||||
/* Check if alignment offsets are compatible */
|
// Check if alignment offsets are compatible
|
||||||
} else if ((alignOffset & mask(sect->align))
|
} else if ((alignOffset & mask(sect->align))
|
||||||
!= (sect->alignOfs & mask(alignment))) {
|
!= (sect->alignOfs & mask(alignment))) {
|
||||||
fail("Section already declared with incompatible %u"
|
fail("Section already declared with incompatible %u"
|
||||||
@@ -180,15 +178,13 @@ static unsigned int mergeFragments(struct Section *sect, enum SectionType type,
|
|||||||
assert(alignment < 16); // Should be ensured by the caller
|
assert(alignment < 16); // Should be ensured by the caller
|
||||||
unsigned int nbSectErrors = 0;
|
unsigned int nbSectErrors = 0;
|
||||||
|
|
||||||
/*
|
// 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_t)-1) {
|
||||||
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_t)-1 && sect->org != curOrg)
|
||||||
fail("Section already declared as fixed at incompatible address $%04"
|
fail("Section already declared as fixed at incompatible address $%04"
|
||||||
PRIx32 " (cur addr = %04" PRIx32 ")\n",
|
PRIx32 " (cur addr = %04" PRIx32 ")\n",
|
||||||
@@ -197,7 +193,7 @@ static unsigned int mergeFragments(struct Section *sect, enum SectionType type,
|
|||||||
fail("Section already declared as aligned to %u bytes (offset %"
|
fail("Section already declared as aligned to %u bytes (offset %"
|
||||||
PRIu16 ")\n", 1U << sect->align, sect->alignOfs);
|
PRIu16 ")\n", 1U << sect->align, sect->alignOfs);
|
||||||
else
|
else
|
||||||
/* Otherwise, just override */
|
// Otherwise, just override
|
||||||
sect->org = curOrg;
|
sect->org = curOrg;
|
||||||
|
|
||||||
} else if (alignment != 0) {
|
} else if (alignment != 0) {
|
||||||
@@ -206,12 +202,12 @@ static unsigned int mergeFragments(struct Section *sect, enum SectionType type,
|
|||||||
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_t)-1) {
|
||||||
if ((sect->org - curOfs) & mask(alignment))
|
if ((sect->org - curOfs) & mask(alignment))
|
||||||
fail("Section already declared as fixed at incompatible address $%04"
|
fail("Section already declared as fixed at incompatible address $%04"
|
||||||
PRIx32 "\n", sect->org);
|
PRIx32 "\n", 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))) {
|
||||||
fail("Section already declared with incompatible %u"
|
fail("Section already declared with incompatible %u"
|
||||||
"-byte alignment (offset %" PRIu16 ")\n",
|
"-byte alignment (offset %" PRIu16 ")\n",
|
||||||
@@ -232,7 +228,7 @@ static void mergeSections(struct Section *sect, enum SectionType type, uint32_t
|
|||||||
unsigned int nbSectErrors = 0;
|
unsigned int nbSectErrors = 0;
|
||||||
|
|
||||||
if (type != sect->type)
|
if (type != sect->type)
|
||||||
fail("Section already exists but with type %s\n", typeNames[sect->type]);
|
fail("Section already exists but with type %s\n", sectionTypeInfo[sect->type].name);
|
||||||
|
|
||||||
if (sect->modifier != mod) {
|
if (sect->modifier != mod) {
|
||||||
fail("Section already declared as %s section\n", sectionModNames[sect->modifier]);
|
fail("Section already declared as %s section\n", sectionModNames[sect->modifier]);
|
||||||
@@ -245,10 +241,10 @@ static void mergeSections(struct Section *sect, enum SectionType type, uint32_t
|
|||||||
|
|
||||||
// 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_t)-1)
|
||||||
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_t)-1 && sect->bank != bank)
|
||||||
fail("Section already declared with different bank %" PRIu32 "\n",
|
fail("Section already declared with different bank %" PRIu32 "\n",
|
||||||
sect->bank);
|
sect->bank);
|
||||||
@@ -269,9 +265,7 @@ static void mergeSections(struct Section *sect, enum SectionType type, uint32_t
|
|||||||
|
|
||||||
#undef fail
|
#undef fail
|
||||||
|
|
||||||
/*
|
// Create a new section, not yet in the list.
|
||||||
* Create a new section, not yet in the list.
|
|
||||||
*/
|
|
||||||
static struct Section *createSection(char const *name, enum SectionType type,
|
static struct Section *createSection(char const *name, enum SectionType type,
|
||||||
uint32_t org, uint32_t bank, uint8_t alignment,
|
uint32_t org, uint32_t bank, uint8_t alignment,
|
||||||
uint16_t alignOffset, enum SectionModifier mod)
|
uint16_t alignOffset, enum SectionModifier mod)
|
||||||
@@ -297,9 +291,9 @@ static struct Section *createSection(char const *name, enum SectionType type,
|
|||||||
sect->next = NULL;
|
sect->next = NULL;
|
||||||
sect->patches = NULL;
|
sect->patches = NULL;
|
||||||
|
|
||||||
/* 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 = malloc(maxsize[type]);
|
sect->data = malloc(sectionTypeInfo[type].size);
|
||||||
if (sect->data == NULL)
|
if (sect->data == NULL)
|
||||||
fatalerror("Not enough memory for section: %s\n", strerror(errno));
|
fatalerror("Not enough memory for section: %s\n", strerror(errno));
|
||||||
} else {
|
} else {
|
||||||
@@ -309,9 +303,7 @@ static struct Section *createSection(char const *name, enum SectionType type,
|
|||||||
return sect;
|
return sect;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Find a section by name and type. If it doesn't exist, create it.
|
||||||
* Find a section by name and type. If it doesn't exist, create it.
|
|
||||||
*/
|
|
||||||
static struct Section *getSection(char const *name, enum SectionType type, uint32_t org,
|
static struct Section *getSection(char const *name, enum SectionType type, uint32_t org,
|
||||||
struct SectionSpec const *attrs, enum SectionModifier mod)
|
struct SectionSpec const *attrs, enum SectionModifier mod)
|
||||||
{
|
{
|
||||||
@@ -325,14 +317,13 @@ static struct Section *getSection(char const *name, enum SectionType type, uint3
|
|||||||
if (type != SECTTYPE_ROMX && type != SECTTYPE_VRAM
|
if (type != SECTTYPE_ROMX && type != SECTTYPE_VRAM
|
||||||
&& type != SECTTYPE_SRAM && type != SECTTYPE_WRAMX)
|
&& type != SECTTYPE_SRAM && type != SECTTYPE_WRAMX)
|
||||||
error("BANK only allowed for ROMX, WRAMX, SRAM, or VRAM sections\n");
|
error("BANK only allowed for ROMX, WRAMX, SRAM, or VRAM sections\n");
|
||||||
else if (bank < bankranges[type][0]
|
else if (bank < sectionTypeInfo[type].firstBank || bank > sectionTypeInfo[type].lastBank)
|
||||||
|| bank > bankranges[type][1])
|
|
||||||
error("%s bank value $%04" PRIx32 " out of range ($%04" PRIx32 " to $%04"
|
error("%s bank value $%04" PRIx32 " out of range ($%04" PRIx32 " to $%04"
|
||||||
PRIx32 ")\n", typeNames[type], bank,
|
PRIx32 ")\n", sectionTypeInfo[type].name, bank,
|
||||||
bankranges[type][0], bankranges[type][1]);
|
sectionTypeInfo[type].firstBank, 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 = bankranges[type][0];
|
bank = sectionTypeInfo[type].firstBank;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (alignOffset >= 1 << alignment) {
|
if (alignOffset >= 1 << alignment) {
|
||||||
@@ -342,10 +333,10 @@ static struct Section *getSection(char const *name, enum SectionType type, uint3
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (org != (uint32_t)-1) {
|
if (org != (uint32_t)-1) {
|
||||||
if (org < startaddr[type] || org > endaddr(type))
|
if (org < sectionTypeInfo[type].startAddr || org > endaddr(type))
|
||||||
error("Section \"%s\"'s fixed address %#" PRIx32
|
error("Section \"%s\"'s fixed address %#" PRIx32
|
||||||
" is outside of range [%#" PRIx16 "; %#" PRIx16 "]\n",
|
" is outside of range [%#" PRIx16 "; %#" PRIx16 "]\n",
|
||||||
name, org, startaddr[type], endaddr(type));
|
name, org, sectionTypeInfo[type].startAddr, endaddr(type));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (alignment != 0) {
|
if (alignment != 0) {
|
||||||
@@ -353,18 +344,18 @@ static struct Section *getSection(char const *name, enum SectionType type, uint3
|
|||||||
error("Alignment must be between 0 and 16, not %u\n", alignment);
|
error("Alignment must be between 0 and 16, not %u\n", alignment);
|
||||||
alignment = 16;
|
alignment = 16;
|
||||||
}
|
}
|
||||||
/* It doesn't make sense to have both alignment and org set */
|
// It doesn't make sense to have both alignment and org set
|
||||||
uint32_t mask = mask(alignment);
|
uint32_t mask = mask(alignment);
|
||||||
|
|
||||||
if (org != (uint32_t)-1) {
|
if (org != (uint32_t)-1) {
|
||||||
if ((org - alignOffset) & mask)
|
if ((org - alignOffset) & mask)
|
||||||
error("Section \"%s\"'s fixed address doesn't match its alignment\n",
|
error("Section \"%s\"'s fixed address doesn't match its alignment\n",
|
||||||
name);
|
name);
|
||||||
alignment = 0; /* Ignore it if it's satisfied */
|
alignment = 0; // Ignore it if it's satisfied
|
||||||
} else if (startaddr[type] & mask) {
|
} else if (sectionTypeInfo[type].startAddr & mask) {
|
||||||
error("Section \"%s\"'s alignment cannot be attained in %s\n",
|
error("Section \"%s\"'s alignment cannot be attained in %s\n",
|
||||||
name, typeNames[type]);
|
name, sectionTypeInfo[type].name);
|
||||||
alignment = 0; /* Ignore it if it's unattainable */
|
alignment = 0; // Ignore it if it's unattainable
|
||||||
org = 0;
|
org = 0;
|
||||||
} else if (alignment == 16) {
|
} else if (alignment == 16) {
|
||||||
// Treat an alignment of 16 as being fixed at address 0
|
// Treat an alignment of 16 as being fixed at address 0
|
||||||
@@ -390,9 +381,7 @@ static struct Section *getSection(char const *name, enum SectionType type, uint3
|
|||||||
return sect;
|
return sect;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Set the current section
|
||||||
* Set the current section
|
|
||||||
*/
|
|
||||||
static void changeSection(void)
|
static void changeSection(void)
|
||||||
{
|
{
|
||||||
if (unionStack)
|
if (unionStack)
|
||||||
@@ -401,9 +390,7 @@ static void changeSection(void)
|
|||||||
sym_SetCurrentSymbolScope(NULL);
|
sym_SetCurrentSymbolScope(NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Set the current section by name and type
|
||||||
* Set the current section by name and type
|
|
||||||
*/
|
|
||||||
void sect_NewSection(char const *name, uint32_t type, uint32_t org,
|
void sect_NewSection(char const *name, uint32_t type, uint32_t org,
|
||||||
struct SectionSpec const *attribs, enum SectionModifier mod)
|
struct SectionSpec const *attribs, enum SectionModifier mod)
|
||||||
{
|
{
|
||||||
@@ -423,9 +410,7 @@ void sect_NewSection(char const *name, uint32_t type, uint32_t org,
|
|||||||
currentSection = sect;
|
currentSection = sect;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Set the current section by name and type
|
||||||
* Set the current section by name and type
|
|
||||||
*/
|
|
||||||
void sect_SetLoadSection(char const *name, uint32_t type, uint32_t org,
|
void sect_SetLoadSection(char const *name, uint32_t type, uint32_t org,
|
||||||
struct SectionSpec const *attribs, enum SectionModifier mod)
|
struct SectionSpec const *attribs, enum SectionModifier mod)
|
||||||
{
|
{
|
||||||
@@ -478,9 +463,7 @@ struct Section *sect_GetSymbolSection(void)
|
|||||||
return currentLoadSection ? currentLoadSection : currentSection;
|
return currentLoadSection ? currentLoadSection : currentSection;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// The offset into the section above
|
||||||
* The offset into the section above
|
|
||||||
*/
|
|
||||||
uint32_t sect_GetSymbolOffset(void)
|
uint32_t sect_GetSymbolOffset(void)
|
||||||
{
|
{
|
||||||
return curOffset;
|
return curOffset;
|
||||||
@@ -615,9 +598,7 @@ void sect_CheckUnionClosed(void)
|
|||||||
error("Unterminated UNION construct!\n");
|
error("Unterminated UNION construct!\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Output an absolute byte
|
||||||
* Output an absolute byte
|
|
||||||
*/
|
|
||||||
void sect_AbsByte(uint8_t b)
|
void sect_AbsByte(uint8_t b)
|
||||||
{
|
{
|
||||||
if (!checkcodesection())
|
if (!checkcodesection())
|
||||||
@@ -661,9 +642,7 @@ void sect_AbsLongGroup(uint8_t const *s, size_t length)
|
|||||||
writelong(*s++);
|
writelong(*s++);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// 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 (!checksection())
|
if (!checksection())
|
||||||
@@ -683,9 +662,7 @@ void sect_Skip(uint32_t skip, bool ds)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Output a NULL terminated string (excluding the NULL-character)
|
||||||
* Output a NULL terminated string (excluding the NULL-character)
|
|
||||||
*/
|
|
||||||
void sect_String(char const *s)
|
void sect_String(char const *s)
|
||||||
{
|
{
|
||||||
if (!checkcodesection())
|
if (!checkcodesection())
|
||||||
@@ -697,10 +674,8 @@ void sect_String(char const *s)
|
|||||||
writebyte(*s++);
|
writebyte(*s++);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Output a relocatable byte. Checking will be done to see if it
|
||||||
* Output a relocatable byte. Checking will be done to see if it
|
// is an absolute value in disguise.
|
||||||
* is an absolute value in disguise.
|
|
||||||
*/
|
|
||||||
void sect_RelByte(struct Expression *expr, uint32_t pcShift)
|
void sect_RelByte(struct Expression *expr, uint32_t pcShift)
|
||||||
{
|
{
|
||||||
if (!checkcodesection())
|
if (!checkcodesection())
|
||||||
@@ -717,10 +692,8 @@ void sect_RelByte(struct Expression *expr, uint32_t pcShift)
|
|||||||
rpn_Free(expr);
|
rpn_Free(expr);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Output several copies of a relocatable byte. Checking will be done to see if
|
||||||
* Output several copies of a relocatable byte. Checking will be done to see if
|
// it is an absolute value in disguise.
|
||||||
* it is an absolute value in disguise.
|
|
||||||
*/
|
|
||||||
void sect_RelBytes(uint32_t n, struct Expression *exprs, size_t size)
|
void sect_RelBytes(uint32_t n, struct Expression *exprs, size_t size)
|
||||||
{
|
{
|
||||||
if (!checkcodesection())
|
if (!checkcodesection())
|
||||||
@@ -743,10 +716,8 @@ void sect_RelBytes(uint32_t n, struct Expression *exprs, size_t size)
|
|||||||
rpn_Free(&exprs[i]);
|
rpn_Free(&exprs[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Output a relocatable word. Checking will be done to see if
|
||||||
* Output a relocatable word. Checking will be done to see if
|
// it's an absolute value in disguise.
|
||||||
* it's an absolute value in disguise.
|
|
||||||
*/
|
|
||||||
void sect_RelWord(struct Expression *expr, uint32_t pcShift)
|
void sect_RelWord(struct Expression *expr, uint32_t pcShift)
|
||||||
{
|
{
|
||||||
if (!checkcodesection())
|
if (!checkcodesection())
|
||||||
@@ -763,10 +734,8 @@ void sect_RelWord(struct Expression *expr, uint32_t pcShift)
|
|||||||
rpn_Free(expr);
|
rpn_Free(expr);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Output a relocatable longword. Checking will be done to see if
|
||||||
* Output a relocatable longword. Checking will be done to see if
|
// is an absolute value in disguise.
|
||||||
* is an absolute value in disguise.
|
|
||||||
*/
|
|
||||||
void sect_RelLong(struct Expression *expr, uint32_t pcShift)
|
void sect_RelLong(struct Expression *expr, uint32_t pcShift)
|
||||||
{
|
{
|
||||||
if (!checkcodesection())
|
if (!checkcodesection())
|
||||||
@@ -783,10 +752,8 @@ void sect_RelLong(struct Expression *expr, uint32_t pcShift)
|
|||||||
rpn_Free(expr);
|
rpn_Free(expr);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Output a PC-relative relocatable byte. Checking will be done to see if it
|
||||||
* Output a PC-relative relocatable byte. Checking will be done to see if it
|
// is an absolute value in disguise.
|
||||||
* is an absolute value in disguise.
|
|
||||||
*/
|
|
||||||
void sect_PCRelByte(struct Expression *expr, uint32_t pcShift)
|
void sect_PCRelByte(struct Expression *expr, uint32_t pcShift)
|
||||||
{
|
{
|
||||||
if (!checkcodesection())
|
if (!checkcodesection())
|
||||||
@@ -800,12 +767,12 @@ void sect_PCRelByte(struct Expression *expr, uint32_t pcShift)
|
|||||||
writebyte(0);
|
writebyte(0);
|
||||||
} else {
|
} else {
|
||||||
struct Symbol const *sym = rpn_SymbolOf(expr);
|
struct Symbol const *sym = rpn_SymbolOf(expr);
|
||||||
/* The offset wraps (jump from ROM to HRAM, for example) */
|
// The offset wraps (jump from ROM to HRAM, for example)
|
||||||
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(sym) - (sym_GetValue(pc) + 1);
|
offset = sym_GetValue(sym) - (sym_GetValue(pc) + 1);
|
||||||
|
|
||||||
@@ -820,9 +787,7 @@ void sect_PCRelByte(struct Expression *expr, uint32_t pcShift)
|
|||||||
rpn_Free(expr);
|
rpn_Free(expr);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Output a binary file
|
||||||
* Output a binary file
|
|
||||||
*/
|
|
||||||
void sect_BinaryFile(char const *s, int32_t startPos)
|
void sect_BinaryFile(char const *s, int32_t startPos)
|
||||||
{
|
{
|
||||||
if (startPos < 0) {
|
if (startPos < 0) {
|
||||||
@@ -869,7 +834,7 @@ void sect_BinaryFile(char const *s, int32_t startPos)
|
|||||||
if (errno != ESPIPE)
|
if (errno != ESPIPE)
|
||||||
error("Error determining size of INCBIN file '%s': %s\n",
|
error("Error determining size of INCBIN file '%s': %s\n",
|
||||||
s, strerror(errno));
|
s, strerror(errno));
|
||||||
/* The file isn't seekable, so we'll just skip bytes */
|
// The file isn't seekable, so we'll just skip bytes
|
||||||
while (startPos--)
|
while (startPos--)
|
||||||
(void)fgetc(f);
|
(void)fgetc(f);
|
||||||
}
|
}
|
||||||
@@ -901,7 +866,7 @@ void sect_BinaryFileSlice(char const *s, int32_t start_pos, int32_t length)
|
|||||||
|
|
||||||
if (!checkcodesection())
|
if (!checkcodesection())
|
||||||
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;
|
||||||
if (!reserveSpace(length))
|
if (!reserveSpace(length))
|
||||||
return;
|
return;
|
||||||
@@ -946,7 +911,7 @@ void sect_BinaryFileSlice(char const *s, int32_t start_pos, int32_t length)
|
|||||||
if (errno != ESPIPE)
|
if (errno != ESPIPE)
|
||||||
error("Error determining size of INCBIN file '%s': %s\n",
|
error("Error determining size of INCBIN file '%s': %s\n",
|
||||||
s, strerror(errno));
|
s, strerror(errno));
|
||||||
/* The file isn't seekable, so we'll just skip bytes */
|
// The file isn't seekable, so we'll just skip bytes
|
||||||
while (start_pos--)
|
while (start_pos--)
|
||||||
(void)fgetc(f);
|
(void)fgetc(f);
|
||||||
}
|
}
|
||||||
@@ -968,9 +933,7 @@ cleanup:
|
|||||||
fclose(f);
|
fclose(f);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Section stack routines
|
||||||
* Section stack routines
|
|
||||||
*/
|
|
||||||
void sect_PushSection(void)
|
void sect_PushSection(void)
|
||||||
{
|
{
|
||||||
struct SectionStackEntry *entry = malloc(sizeof(*entry));
|
struct SectionStackEntry *entry = malloc(sizeof(*entry));
|
||||||
|
|||||||
191
src/asm/symbol.c
191
src/asm/symbol.c
@@ -6,9 +6,7 @@
|
|||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
// Symboltable and macroargs stuff
|
||||||
* Symboltable and macroargs stuff
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
@@ -36,8 +34,9 @@
|
|||||||
|
|
||||||
HashMap symbols;
|
HashMap symbols;
|
||||||
|
|
||||||
static char const *labelScope; /* Current section's label scope */
|
static const char *labelScope; // Current section's label scope
|
||||||
static struct Symbol *PCSymbol;
|
static struct Symbol *PCSymbol;
|
||||||
|
static struct Symbol *_NARGSymbol;
|
||||||
static char savedTIME[256];
|
static char savedTIME[256];
|
||||||
static char savedDATE[256];
|
static char savedDATE[256];
|
||||||
static char savedTIMESTAMP_ISO8601_LOCAL[256];
|
static char savedTIMESTAMP_ISO8601_LOCAL[256];
|
||||||
@@ -80,41 +79,42 @@ static int32_t Callback_NARG(void)
|
|||||||
|
|
||||||
static int32_t Callback__LINE__(void)
|
static int32_t Callback__LINE__(void)
|
||||||
{
|
{
|
||||||
|
warning(WARNING_OBSOLETE, "`__LINE__` is deprecated\n");
|
||||||
|
|
||||||
return lexer_GetLineNo();
|
return lexer_GetLineNo();
|
||||||
}
|
}
|
||||||
|
|
||||||
static char const *Callback__FILE__(void)
|
static char const *Callback__FILE__(void)
|
||||||
{
|
{
|
||||||
/*
|
warning(WARNING_OBSOLETE, "`__FILE__` is deprecated\n");
|
||||||
* FIXME: this is dangerous, and here's why this is CURRENTLY okay. It's still bad, fix it.
|
|
||||||
* There are only two call sites for this; one copies the contents directly, the other is
|
// There are only two call sites for this; one copies the contents directly, the other is
|
||||||
* EQUS expansions, which cannot straddle file boundaries. So this should be fine.
|
// EQUS expansions, which cannot straddle file boundaries. So this should be fine.
|
||||||
*/
|
|
||||||
static char *buf = NULL;
|
static char *buf = NULL;
|
||||||
static size_t bufsize = 0;
|
static size_t bufsize = 0;
|
||||||
char const *fileName = fstk_GetFileName();
|
char const *fileName = fstk_GetFileName();
|
||||||
size_t j = 1;
|
size_t j = 1;
|
||||||
|
|
||||||
assert(fileName[0]);
|
assert(fileName[0]);
|
||||||
/* The assertion above ensures the loop runs at least once */
|
// The assertion above ensures the loop runs at least once
|
||||||
for (size_t i = 0; fileName[i]; i++, j++) {
|
for (size_t i = 0; fileName[i]; i++, j++) {
|
||||||
/* Account for the extra backslash inserted below */
|
// Account for the extra backslash inserted below
|
||||||
if (fileName[i] == '"')
|
if (fileName[i] == '"')
|
||||||
j++;
|
j++;
|
||||||
/* Ensure there will be enough room; DO NOT PRINT ANYTHING ABOVE THIS!! */
|
// Ensure there will be enough room; DO NOT PRINT ANYTHING ABOVE THIS!
|
||||||
if (j + 2 >= bufsize) { /* Always keep room for 2 tail chars */
|
if (j + 2 >= bufsize) { // Always keep room for 2 tail chars
|
||||||
bufsize = bufsize ? bufsize * 2 : 64;
|
bufsize = bufsize ? bufsize * 2 : 64;
|
||||||
buf = realloc(buf, bufsize);
|
buf = realloc(buf, bufsize);
|
||||||
if (!buf)
|
if (!buf)
|
||||||
fatalerror("Failed to grow buffer for file name: %s\n",
|
fatalerror("Failed to grow buffer for file name: %s\n",
|
||||||
strerror(errno));
|
strerror(errno));
|
||||||
}
|
}
|
||||||
/* Escape quotes, since we're returning a string */
|
// Escape quotes, since we're returning a string
|
||||||
if (fileName[i] == '"')
|
if (fileName[i] == '"')
|
||||||
buf[j - 1] = '\\';
|
buf[j - 1] = '\\';
|
||||||
buf[j] = fileName[i];
|
buf[j] = fileName[i];
|
||||||
}
|
}
|
||||||
/* Write everything after the loop, to ensure the buffer has been allocated */
|
// Write everything after the loop, to ensure the buffer has been allocated
|
||||||
buf[0] = '"';
|
buf[0] = '"';
|
||||||
buf[j++] = '"';
|
buf[j++] = '"';
|
||||||
buf[j] = '\0';
|
buf[j] = '\0';
|
||||||
@@ -128,16 +128,14 @@ static int32_t CallbackPC(void)
|
|||||||
return section ? section->org + sect_GetSymbolOffset() : 0;
|
return section ? section->org + sect_GetSymbolOffset() : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Get the value field of a symbol
|
||||||
* Get the value field of a symbol
|
|
||||||
*/
|
|
||||||
int32_t sym_GetValue(struct Symbol const *sym)
|
int32_t sym_GetValue(struct Symbol const *sym)
|
||||||
{
|
{
|
||||||
if (sym_IsNumeric(sym) && sym->hasCallback)
|
if (sym_IsNumeric(sym) && sym->hasCallback)
|
||||||
return sym->numCallback();
|
return sym->numCallback();
|
||||||
|
|
||||||
if (sym->type == SYM_LABEL)
|
if (sym->type == SYM_LABEL)
|
||||||
/* TODO: do not use section's org directly */
|
// TODO: do not use section's org directly
|
||||||
return sym->value + sym_GetSection(sym)->org;
|
return sym->value + sym_GetSection(sym)->org;
|
||||||
|
|
||||||
return sym->value;
|
return sym->value;
|
||||||
@@ -153,32 +151,26 @@ static void dumpFilename(struct Symbol const *sym)
|
|||||||
fputs("<builtin>", stderr);
|
fputs("<builtin>", stderr);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Set a symbol's definition filename and line
|
||||||
* Set a symbol's definition filename and line
|
|
||||||
*/
|
|
||||||
static void setSymbolFilename(struct Symbol *sym)
|
static void setSymbolFilename(struct Symbol *sym)
|
||||||
{
|
{
|
||||||
sym->src = fstk_GetFileStack();
|
sym->src = fstk_GetFileStack();
|
||||||
sym->fileLine = sym->src ? lexer_GetLineNo() : 0; // This is (NULL, 1) for built-ins
|
sym->fileLine = sym->src ? lexer_GetLineNo() : 0; // This is (NULL, 1) for built-ins
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Update a symbol's definition filename and line
|
||||||
* Update a symbol's definition filename and line
|
|
||||||
*/
|
|
||||||
static void updateSymbolFilename(struct Symbol *sym)
|
static void updateSymbolFilename(struct Symbol *sym)
|
||||||
{
|
{
|
||||||
struct FileStackNode *oldSrc = sym->src;
|
struct FileStackNode *oldSrc = sym->src;
|
||||||
|
|
||||||
setSymbolFilename(sym);
|
setSymbolFilename(sym);
|
||||||
/* If the old node was referenced, ensure the new one is */
|
// If the old node was referenced, ensure the new one is
|
||||||
if (oldSrc && oldSrc->referenced && oldSrc->ID != (uint32_t)-1)
|
if (oldSrc && oldSrc->referenced && oldSrc->ID != (uint32_t)-1)
|
||||||
out_RegisterNode(sym->src);
|
out_RegisterNode(sym->src);
|
||||||
/* TODO: unref the old node, and use `out_ReplaceNode` instead of deleting it */
|
// TODO: unref the old node, and use `out_ReplaceNode` instead of deleting it
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Create a new symbol by name
|
||||||
* Create a new symbol by name
|
|
||||||
*/
|
|
||||||
static struct Symbol *createsymbol(char const *symName)
|
static struct Symbol *createsymbol(char const *symName)
|
||||||
{
|
{
|
||||||
struct Symbol *sym = malloc(sizeof(*sym));
|
struct Symbol *sym = malloc(sizeof(*sym));
|
||||||
@@ -201,10 +193,8 @@ static struct Symbol *createsymbol(char const *symName)
|
|||||||
return sym;
|
return sym;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Creates the full name of a local symbol in a given scope, by prepending
|
||||||
* Creates the full name of a local symbol in a given scope, by prepending
|
// the name with the parent symbol's name.
|
||||||
* the name with the parent symbol's name.
|
|
||||||
*/
|
|
||||||
static void fullSymbolName(char *output, size_t outputSize,
|
static void fullSymbolName(char *output, size_t outputSize,
|
||||||
char const *localName, char const *scopeName)
|
char const *localName, char const *scopeName)
|
||||||
{
|
{
|
||||||
@@ -250,8 +240,8 @@ struct Symbol *sym_FindScopedSymbol(char const *symName)
|
|||||||
if (strchr(localName + 1, '.'))
|
if (strchr(localName + 1, '.'))
|
||||||
fatalerror("'%s' is a nonsensical reference to a nested local symbol\n",
|
fatalerror("'%s' is a nonsensical reference to a nested local symbol\n",
|
||||||
symName);
|
symName);
|
||||||
/* If auto-scoped local label, expand the name */
|
// If auto-scoped local label, expand the name
|
||||||
if (localName == symName) { /* Meaning, the name begins with the dot */
|
if (localName == symName) { // Meaning, the name begins with the dot
|
||||||
char fullName[MAXSYMLEN + 1];
|
char fullName[MAXSYMLEN + 1];
|
||||||
|
|
||||||
fullSymbolName(fullName, sizeof(fullName), symName, labelScope);
|
fullSymbolName(fullName, sizeof(fullName), symName, labelScope);
|
||||||
@@ -261,6 +251,21 @@ struct Symbol *sym_FindScopedSymbol(char const *symName)
|
|||||||
return sym_FindExactSymbol(symName);
|
return sym_FindExactSymbol(symName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct Symbol *sym_FindScopedValidSymbol(char const *symName)
|
||||||
|
{
|
||||||
|
struct Symbol *sym = sym_FindScopedSymbol(symName);
|
||||||
|
|
||||||
|
// `@` has no value outside a section
|
||||||
|
if (sym == PCSymbol && !sect_GetSymbolSection()) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
// `_NARG` has no value outside a macro
|
||||||
|
if (sym == _NARGSymbol && !macro_GetCurrentArgs()) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return sym;
|
||||||
|
}
|
||||||
|
|
||||||
struct Symbol const *sym_GetPC(void)
|
struct Symbol const *sym_GetPC(void)
|
||||||
{
|
{
|
||||||
return PCSymbol;
|
return PCSymbol;
|
||||||
@@ -271,12 +276,10 @@ static bool isReferenced(struct Symbol const *sym)
|
|||||||
return sym->ID != (uint32_t)-1;
|
return sym->ID != (uint32_t)-1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Purge a symbol
|
||||||
* Purge a symbol
|
|
||||||
*/
|
|
||||||
void sym_Purge(char const *symName)
|
void sym_Purge(char const *symName)
|
||||||
{
|
{
|
||||||
struct Symbol *sym = sym_FindScopedSymbol(symName);
|
struct Symbol *sym = sym_FindScopedValidSymbol(symName);
|
||||||
|
|
||||||
if (!sym) {
|
if (!sym) {
|
||||||
error("'%s' not defined\n", symName);
|
error("'%s' not defined\n", symName);
|
||||||
@@ -285,16 +288,14 @@ void sym_Purge(char const *symName)
|
|||||||
} else if (isReferenced(sym)) {
|
} else if (isReferenced(sym)) {
|
||||||
error("Symbol \"%s\" is referenced and thus cannot be purged\n", symName);
|
error("Symbol \"%s\" is referenced and thus cannot be purged\n", symName);
|
||||||
} else {
|
} else {
|
||||||
/* Do not keep a reference to the label's name after purging it */
|
// Do not keep a reference to the label's name after purging it
|
||||||
if (sym->name == labelScope)
|
if (sym->name == labelScope)
|
||||||
sym_SetCurrentSymbolScope(NULL);
|
sym_SetCurrentSymbolScope(NULL);
|
||||||
|
|
||||||
/*
|
// FIXME: this leaks sym->macro for SYM_EQUS and SYM_MACRO, but this can't
|
||||||
* FIXME: this leaks sym->macro for SYM_EQUS and SYM_MACRO, but this can't
|
// free(sym->macro) because the expansion may be purging itself.
|
||||||
* free(sym->macro) because the expansion may be purging itself.
|
|
||||||
*/
|
|
||||||
hash_RemoveElement(symbols, sym->name);
|
hash_RemoveElement(symbols, sym->name);
|
||||||
/* TODO: ideally, also unref the file stack nodes */
|
// TODO: ideally, also unref the file stack nodes
|
||||||
free(sym);
|
free(sym);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -312,9 +313,7 @@ uint32_t sym_GetPCValue(void)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Return a constant symbol's value, assuming it's defined
|
||||||
* Return a constant symbol's value, assuming it's defined
|
|
||||||
*/
|
|
||||||
uint32_t sym_GetConstantSymValue(struct Symbol const *sym)
|
uint32_t sym_GetConstantSymValue(struct Symbol const *sym)
|
||||||
{
|
{
|
||||||
if (sym == PCSymbol)
|
if (sym == PCSymbol)
|
||||||
@@ -327,9 +326,7 @@ uint32_t sym_GetConstantSymValue(struct Symbol const *sym)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Return a constant symbol's value
|
||||||
* Return a constant symbol's value
|
|
||||||
*/
|
|
||||||
uint32_t sym_GetConstantValue(char const *symName)
|
uint32_t sym_GetConstantValue(char const *symName)
|
||||||
{
|
{
|
||||||
struct Symbol const *sym = sym_FindScopedSymbol(symName);
|
struct Symbol const *sym = sym_FindScopedSymbol(symName);
|
||||||
@@ -381,9 +378,7 @@ static struct Symbol *createNonrelocSymbol(char const *symName, bool numeric)
|
|||||||
return sym;
|
return sym;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Add an equated symbol
|
||||||
* Add an equated symbol
|
|
||||||
*/
|
|
||||||
struct Symbol *sym_AddEqu(char const *symName, int32_t value)
|
struct Symbol *sym_AddEqu(char const *symName, int32_t value)
|
||||||
{
|
{
|
||||||
struct Symbol *sym = createNonrelocSymbol(symName, true);
|
struct Symbol *sym = createNonrelocSymbol(symName, true);
|
||||||
@@ -465,18 +460,14 @@ struct Symbol *sym_RedefString(char const *symName, char const *value)
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateSymbolFilename(sym);
|
updateSymbolFilename(sym);
|
||||||
/*
|
// FIXME: this leaks the previous sym->macro value, but this can't
|
||||||
* FIXME: this leaks the previous sym->macro value, but this can't
|
// free(sym->macro) because the expansion may be redefining itself.
|
||||||
* free(sym->macro) because the expansion may be redefining itself.
|
|
||||||
*/
|
|
||||||
assignStringSymbol(sym, value);
|
assignStringSymbol(sym, value);
|
||||||
|
|
||||||
return sym;
|
return sym;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Alter a mutable symbol's value
|
||||||
* Alter a mutable symbol's value
|
|
||||||
*/
|
|
||||||
struct Symbol *sym_AddVar(char const *symName, int32_t value)
|
struct Symbol *sym_AddVar(char const *symName, int32_t value)
|
||||||
{
|
{
|
||||||
struct Symbol *sym = sym_FindExactSymbol(symName);
|
struct Symbol *sym = sym_FindExactSymbol(symName);
|
||||||
@@ -506,7 +497,7 @@ struct Symbol *sym_AddVar(char const *symName, int32_t value)
|
|||||||
*/
|
*/
|
||||||
static struct Symbol *addLabel(char const *symName)
|
static struct Symbol *addLabel(char const *symName)
|
||||||
{
|
{
|
||||||
assert(symName[0] != '.'); /* The symbol name must have been expanded prior */
|
assert(symName[0] != '.'); // The symbol name must have been expanded prior
|
||||||
struct Symbol *sym = sym_FindExactSymbol(symName);
|
struct Symbol *sym = sym_FindExactSymbol(symName);
|
||||||
|
|
||||||
if (!sym) {
|
if (!sym) {
|
||||||
@@ -519,7 +510,7 @@ static struct Symbol *addLabel(char const *symName)
|
|||||||
} else {
|
} else {
|
||||||
updateSymbolFilename(sym);
|
updateSymbolFilename(sym);
|
||||||
}
|
}
|
||||||
/* 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->value = sect_GetSymbolOffset();
|
sym->value = sect_GetSymbolOffset();
|
||||||
if (exportall)
|
if (exportall)
|
||||||
@@ -531,43 +522,41 @@ static struct Symbol *addLabel(char const *symName)
|
|||||||
return sym;
|
return sym;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Add a local (`.name` or `Parent.name`) relocatable symbol
|
||||||
* Add a local (`.name` or `Parent.name`) relocatable symbol
|
|
||||||
*/
|
|
||||||
struct Symbol *sym_AddLocalLabel(char const *symName)
|
struct Symbol *sym_AddLocalLabel(char const *symName)
|
||||||
{
|
{
|
||||||
if (!labelScope) {
|
if (!labelScope) {
|
||||||
error("Local label '%s' in main scope\n", symName);
|
error("Local label '%s' in main scope\n", symName);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
assert(!strchr(labelScope, '.')); /* Assuming no dots in `labelScope` */
|
assert(!strchr(labelScope, '.')); // Assuming no dots in `labelScope`
|
||||||
|
|
||||||
char fullName[MAXSYMLEN + 1];
|
char fullName[MAXSYMLEN + 1];
|
||||||
char const *localName = strchr(symName, '.');
|
char const *localName = strchr(symName, '.');
|
||||||
|
|
||||||
assert(localName); /* There should be at least one dot in `symName` */
|
assert(localName); // There should be at least one dot in `symName`
|
||||||
/* Check for something after the dot in `localName` */
|
// Check for something after the dot in `localName`
|
||||||
if (localName[1] == '\0') {
|
if (localName[1] == '\0') {
|
||||||
fatalerror("'%s' is a nonsensical reference to an empty local label\n",
|
fatalerror("'%s' is a nonsensical reference to an empty local label\n",
|
||||||
symName);
|
symName);
|
||||||
}
|
}
|
||||||
/* Check for more than one dot in `localName` */
|
// Check for more than one dot in `localName`
|
||||||
if (strchr(localName + 1, '.'))
|
if (strchr(localName + 1, '.'))
|
||||||
fatalerror("'%s' is a nonsensical reference to a nested local label\n",
|
fatalerror("'%s' is a nonsensical reference to a nested local label\n",
|
||||||
symName);
|
symName);
|
||||||
|
|
||||||
if (localName == symName) {
|
if (localName == symName) {
|
||||||
/* Expand `symName` to the full `labelScope.symName` name */
|
// Expand `symName` to the full `labelScope.symName` name
|
||||||
fullSymbolName(fullName, sizeof(fullName), symName, labelScope);
|
fullSymbolName(fullName, sizeof(fullName), symName, labelScope);
|
||||||
symName = fullName;
|
symName = fullName;
|
||||||
} else {
|
} else {
|
||||||
size_t i = 0;
|
size_t i = 0;
|
||||||
|
|
||||||
/* Find where `labelScope` and `symName` first differ */
|
// Find where `labelScope` and `symName` first differ
|
||||||
while (labelScope[i] && symName[i] == labelScope[i])
|
while (labelScope[i] && symName[i] == labelScope[i])
|
||||||
i++;
|
i++;
|
||||||
|
|
||||||
/* Check that `symName` starts with `labelScope` and then a '.' */
|
// Check that `symName` starts with `labelScope` and then a '.'
|
||||||
if (labelScope[i] != '\0' || symName[i] != '.') {
|
if (labelScope[i] != '\0' || symName[i] != '.') {
|
||||||
size_t parentLen = localName - symName;
|
size_t parentLen = localName - symName;
|
||||||
|
|
||||||
@@ -579,14 +568,12 @@ struct Symbol *sym_AddLocalLabel(char const *symName)
|
|||||||
return addLabel(symName);
|
return addLabel(symName);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Add a relocatable symbol
|
||||||
* Add a relocatable symbol
|
|
||||||
*/
|
|
||||||
struct Symbol *sym_AddLabel(char const *symName)
|
struct Symbol *sym_AddLabel(char const *symName)
|
||||||
{
|
{
|
||||||
struct Symbol *sym = addLabel(symName);
|
struct Symbol *sym = addLabel(symName);
|
||||||
|
|
||||||
/* Set the symbol as the new scope */
|
// Set the symbol as the new scope
|
||||||
if (sym)
|
if (sym)
|
||||||
sym_SetCurrentSymbolScope(sym->name);
|
sym_SetCurrentSymbolScope(sym->name);
|
||||||
return sym;
|
return sym;
|
||||||
@@ -594,9 +581,7 @@ struct Symbol *sym_AddLabel(char const *symName)
|
|||||||
|
|
||||||
static uint32_t anonLabelID;
|
static uint32_t anonLabelID;
|
||||||
|
|
||||||
/*
|
// Add an anonymous label
|
||||||
* Add an anonymous label
|
|
||||||
*/
|
|
||||||
struct Symbol *sym_AddAnonLabel(void)
|
struct Symbol *sym_AddAnonLabel(void)
|
||||||
{
|
{
|
||||||
if (anonLabelID == UINT32_MAX) {
|
if (anonLabelID == UINT32_MAX) {
|
||||||
@@ -605,14 +590,12 @@ struct Symbol *sym_AddAnonLabel(void)
|
|||||||
}
|
}
|
||||||
char name[MAXSYMLEN + 1];
|
char name[MAXSYMLEN + 1];
|
||||||
|
|
||||||
sym_WriteAnonLabelName(name, 0, true); // The direction is important!!
|
sym_WriteAnonLabelName(name, 0, true); // The direction is important!
|
||||||
anonLabelID++;
|
anonLabelID++;
|
||||||
return addLabel(name);
|
return addLabel(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Write an anonymous label's name to a buffer
|
||||||
* Write an anonymous label's name to a buffer
|
|
||||||
*/
|
|
||||||
void sym_WriteAnonLabelName(char buf[MIN_NB_ELMS(MAXSYMLEN + 1)], uint32_t ofs, bool neg)
|
void sym_WriteAnonLabelName(char buf[MIN_NB_ELMS(MAXSYMLEN + 1)], uint32_t ofs, bool neg)
|
||||||
{
|
{
|
||||||
uint32_t id = 0;
|
uint32_t id = 0;
|
||||||
@@ -636,9 +619,7 @@ void sym_WriteAnonLabelName(char buf[MIN_NB_ELMS(MAXSYMLEN + 1)], uint32_t ofs,
|
|||||||
sprintf(buf, "!%u", id);
|
sprintf(buf, "!%u", id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Export a symbol
|
||||||
* Export a symbol
|
|
||||||
*/
|
|
||||||
void sym_Export(char const *symName)
|
void sym_Export(char const *symName)
|
||||||
{
|
{
|
||||||
if (symName[0] == '!') {
|
if (symName[0] == '!') {
|
||||||
@@ -648,15 +629,13 @@ void sym_Export(char const *symName)
|
|||||||
|
|
||||||
struct Symbol *sym = sym_FindScopedSymbol(symName);
|
struct 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Add a macro definition
|
||||||
* Add a macro definition
|
|
||||||
*/
|
|
||||||
struct Symbol *sym_AddMacro(char const *symName, int32_t defLineNo, char *body, size_t size)
|
struct Symbol *sym_AddMacro(char const *symName, int32_t defLineNo, char *body, size_t size)
|
||||||
{
|
{
|
||||||
struct Symbol *sym = createNonrelocSymbol(symName, false);
|
struct Symbol *sym = createNonrelocSymbol(symName, false);
|
||||||
@@ -667,20 +646,16 @@ struct Symbol *sym_AddMacro(char const *symName, int32_t defLineNo, char *body,
|
|||||||
sym->type = SYM_MACRO;
|
sym->type = SYM_MACRO;
|
||||||
sym->macroSize = size;
|
sym->macroSize = size;
|
||||||
sym->macro = body;
|
sym->macro = body;
|
||||||
setSymbolFilename(sym); /* TODO: is this really necessary? */
|
setSymbolFilename(sym); // TODO: is this really necessary?
|
||||||
/*
|
// The symbol is created at the line after the `endm`,
|
||||||
* The symbol is created at the line after the `endm`,
|
// override this with the actual definition line
|
||||||
* override this with the actual definition line
|
|
||||||
*/
|
|
||||||
sym->fileLine = defLineNo;
|
sym->fileLine = defLineNo;
|
||||||
|
|
||||||
return sym;
|
return sym;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Flag that a symbol is referenced in an RPN expression
|
||||||
* Flag that a symbol is referenced in an RPN expression
|
// and create it if it doesn't exist yet
|
||||||
* and create it if it doesn't exist yet
|
|
||||||
*/
|
|
||||||
struct Symbol *sym_Ref(char const *symName)
|
struct Symbol *sym_Ref(char const *symName)
|
||||||
{
|
{
|
||||||
struct Symbol *sym = sym_FindScopedSymbol(symName);
|
struct Symbol *sym = sym_FindScopedSymbol(symName);
|
||||||
@@ -702,9 +677,7 @@ struct Symbol *sym_Ref(char const *symName)
|
|||||||
return sym;
|
return sym;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Set whether to export all relocatable symbols by default
|
||||||
* Set whether to export all relocatable symbols by default
|
|
||||||
*/
|
|
||||||
void sym_SetExportAll(bool set)
|
void sym_SetExportAll(bool set)
|
||||||
{
|
{
|
||||||
exportall = set;
|
exportall = set;
|
||||||
@@ -721,14 +694,14 @@ static struct Symbol *createBuiltinSymbol(char const *symName)
|
|||||||
return sym;
|
return sym;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Initialize the symboltable
|
||||||
* Initialize the symboltable
|
|
||||||
*/
|
|
||||||
void sym_Init(time_t now)
|
void sym_Init(time_t now)
|
||||||
{
|
{
|
||||||
PCSymbol = createBuiltinSymbol("@");
|
PCSymbol = createBuiltinSymbol("@");
|
||||||
struct Symbol *_NARGSymbol = createBuiltinSymbol("_NARG");
|
_NARGSymbol = createBuiltinSymbol("_NARG");
|
||||||
|
// __LINE__ is deprecated
|
||||||
struct Symbol *__LINE__Symbol = createBuiltinSymbol("__LINE__");
|
struct Symbol *__LINE__Symbol = createBuiltinSymbol("__LINE__");
|
||||||
|
// __FILE__ is deprecated
|
||||||
struct Symbol *__FILE__Symbol = createBuiltinSymbol("__FILE__");
|
struct Symbol *__FILE__Symbol = createBuiltinSymbol("__FILE__");
|
||||||
|
|
||||||
PCSymbol->type = SYM_LABEL;
|
PCSymbol->type = SYM_LABEL;
|
||||||
@@ -761,7 +734,7 @@ void sym_Init(time_t now)
|
|||||||
|
|
||||||
if (now == (time_t)-1) {
|
if (now == (time_t)-1) {
|
||||||
warn("Couldn't determine current time");
|
warn("Couldn't 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ char const *printChar(int c)
|
|||||||
buf[2] = 't';
|
buf[2] = 't';
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default: /* Print as hex */
|
default: // Print as hex
|
||||||
buf[0] = '0';
|
buf[0] = '0';
|
||||||
buf[1] = 'x';
|
buf[1] = 'x';
|
||||||
snprintf(&buf[2], 3, "%02hhX", (uint8_t)c); // includes the '\0'
|
snprintf(&buf[2], 3, "%02hhX", (uint8_t)c); // includes the '\0'
|
||||||
|
|||||||
@@ -44,23 +44,25 @@ static const enum WarningState defaultWarnings[ARRAY_SIZE(warningStates)] = {
|
|||||||
[WARNING_NUMERIC_STRING_2] = WARNING_DISABLED,
|
[WARNING_NUMERIC_STRING_2] = WARNING_DISABLED,
|
||||||
[WARNING_TRUNCATION_1] = WARNING_ENABLED,
|
[WARNING_TRUNCATION_1] = WARNING_ENABLED,
|
||||||
[WARNING_TRUNCATION_2] = WARNING_DISABLED,
|
[WARNING_TRUNCATION_2] = WARNING_DISABLED,
|
||||||
|
[WARNING_UNMAPPED_CHAR_1] = WARNING_ENABLED,
|
||||||
|
[WARNING_UNMAPPED_CHAR_2] = WARNING_DISABLED,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum WarningState warningStates[ARRAY_SIZE(warningStates)];
|
enum WarningState warningStates[ARRAY_SIZE(warningStates)];
|
||||||
|
|
||||||
bool warningsAreErrors; /* Set if `-Werror` was specified */
|
bool warningsAreErrors; // Set if `-Werror` was specified
|
||||||
|
|
||||||
static enum WarningState warningState(enum WarningID id)
|
static enum WarningState warningState(enum WarningID id)
|
||||||
{
|
{
|
||||||
/* Check if warnings are globally disabled */
|
// Check if warnings are globally disabled
|
||||||
if (!warnings)
|
if (!warnings)
|
||||||
return WARNING_DISABLED;
|
return WARNING_DISABLED;
|
||||||
|
|
||||||
/* Get the actual state */
|
// Get the actual state
|
||||||
enum WarningState state = warningStates[id];
|
enum WarningState state = warningStates[id];
|
||||||
|
|
||||||
if (state == WARNING_DEFAULT)
|
if (state == WARNING_DEFAULT)
|
||||||
/* The state isn't set, grab its default state */
|
// The state isn't set, grab its default state
|
||||||
state = defaultWarnings[id];
|
state = defaultWarnings[id];
|
||||||
|
|
||||||
if (warningsAreErrors && state == WARNING_ENABLED)
|
if (warningsAreErrors && state == WARNING_ENABLED)
|
||||||
@@ -92,11 +94,13 @@ static const char * const warningFlags[NB_WARNINGS] = {
|
|||||||
"numeric-string",
|
"numeric-string",
|
||||||
"truncation",
|
"truncation",
|
||||||
"truncation",
|
"truncation",
|
||||||
|
"unmapped-char",
|
||||||
|
"unmapped-char",
|
||||||
|
|
||||||
/* Meta warnings */
|
// Meta warnings
|
||||||
"all",
|
"all",
|
||||||
"extra",
|
"extra",
|
||||||
"everything", /* Especially useful for testing */
|
"everything", // Especially useful for testing
|
||||||
};
|
};
|
||||||
|
|
||||||
static const struct {
|
static const struct {
|
||||||
@@ -106,6 +110,7 @@ static const struct {
|
|||||||
} paramWarnings[] = {
|
} paramWarnings[] = {
|
||||||
{ "numeric-string", 2, 1 },
|
{ "numeric-string", 2, 1 },
|
||||||
{ "truncation", 2, 2 },
|
{ "truncation", 2, 2 },
|
||||||
|
{ "unmapped-char", 2, 1 },
|
||||||
};
|
};
|
||||||
|
|
||||||
static bool tryProcessParamWarning(char const *flag, uint8_t param, enum WarningState state)
|
static bool tryProcessParamWarning(char const *flag, uint8_t param, enum WarningState state)
|
||||||
@@ -149,7 +154,7 @@ enum MetaWarningCommand {
|
|||||||
META_WARNING_DONE = NB_WARNINGS
|
META_WARNING_DONE = NB_WARNINGS
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Warnings that probably indicate an error */
|
// Warnings that probably indicate an error
|
||||||
static uint8_t const _wallCommands[] = {
|
static uint8_t const _wallCommands[] = {
|
||||||
WARNING_BACKWARDS_FOR,
|
WARNING_BACKWARDS_FOR,
|
||||||
WARNING_BUILTIN_ARG,
|
WARNING_BUILTIN_ARG,
|
||||||
@@ -161,10 +166,11 @@ static uint8_t const _wallCommands[] = {
|
|||||||
WARNING_NESTED_COMMENT,
|
WARNING_NESTED_COMMENT,
|
||||||
WARNING_OBSOLETE,
|
WARNING_OBSOLETE,
|
||||||
WARNING_NUMERIC_STRING_1,
|
WARNING_NUMERIC_STRING_1,
|
||||||
|
WARNING_UNMAPPED_CHAR_1,
|
||||||
META_WARNING_DONE
|
META_WARNING_DONE
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Warnings that are less likely to indicate an error */
|
// Warnings that are less likely to indicate an error
|
||||||
static uint8_t const _wextraCommands[] = {
|
static uint8_t const _wextraCommands[] = {
|
||||||
WARNING_EMPTY_MACRO_ARG,
|
WARNING_EMPTY_MACRO_ARG,
|
||||||
WARNING_MACRO_SHIFT,
|
WARNING_MACRO_SHIFT,
|
||||||
@@ -173,10 +179,12 @@ static uint8_t const _wextraCommands[] = {
|
|||||||
WARNING_NUMERIC_STRING_2,
|
WARNING_NUMERIC_STRING_2,
|
||||||
WARNING_TRUNCATION_1,
|
WARNING_TRUNCATION_1,
|
||||||
WARNING_TRUNCATION_2,
|
WARNING_TRUNCATION_2,
|
||||||
|
WARNING_UNMAPPED_CHAR_1,
|
||||||
|
WARNING_UNMAPPED_CHAR_2,
|
||||||
META_WARNING_DONE
|
META_WARNING_DONE
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Literally everything. Notably useful for testing */
|
// Literally everything. Notably useful for testing
|
||||||
static uint8_t const _weverythingCommands[] = {
|
static uint8_t const _weverythingCommands[] = {
|
||||||
WARNING_BACKWARDS_FOR,
|
WARNING_BACKWARDS_FOR,
|
||||||
WARNING_BUILTIN_ARG,
|
WARNING_BUILTIN_ARG,
|
||||||
@@ -195,7 +203,9 @@ static uint8_t const _weverythingCommands[] = {
|
|||||||
WARNING_NUMERIC_STRING_2,
|
WARNING_NUMERIC_STRING_2,
|
||||||
WARNING_TRUNCATION_1,
|
WARNING_TRUNCATION_1,
|
||||||
WARNING_TRUNCATION_2,
|
WARNING_TRUNCATION_2,
|
||||||
/* WARNING_USER, */
|
WARNING_UNMAPPED_CHAR_1,
|
||||||
|
WARNING_UNMAPPED_CHAR_2,
|
||||||
|
// WARNING_USER,
|
||||||
META_WARNING_DONE
|
META_WARNING_DONE
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -209,18 +219,18 @@ void processWarningFlag(char *flag)
|
|||||||
{
|
{
|
||||||
static bool setError = false;
|
static bool setError = false;
|
||||||
|
|
||||||
/* First, try to match against a "meta" warning */
|
// First, try to match against a "meta" warning
|
||||||
for (enum WarningID id = META_WARNINGS_START; id < NB_WARNINGS; id++) {
|
for (enum WarningID id = META_WARNINGS_START; id < NB_WARNINGS; id++) {
|
||||||
/* TODO: improve the matching performance? */
|
// TODO: improve the matching performance?
|
||||||
if (!strcmp(flag, warningFlags[id])) {
|
if (!strcmp(flag, warningFlags[id])) {
|
||||||
/* We got a match! */
|
// We got a match!
|
||||||
if (setError)
|
if (setError)
|
||||||
errx("Cannot make meta warning \"%s\" into an error",
|
errx("Cannot make meta warning \"%s\" into an error",
|
||||||
flag);
|
flag);
|
||||||
|
|
||||||
for (uint8_t const *ptr = metaWarningCommands[id - META_WARNINGS_START];
|
for (uint8_t const *ptr = metaWarningCommands[id - META_WARNINGS_START];
|
||||||
*ptr != META_WARNING_DONE; ptr++) {
|
*ptr != META_WARNING_DONE; ptr++) {
|
||||||
/* Warning flag, set without override */
|
// Warning flag, set without override
|
||||||
if (warningStates[*ptr] == WARNING_DEFAULT)
|
if (warningStates[*ptr] == WARNING_DEFAULT)
|
||||||
warningStates[*ptr] = WARNING_ENABLED;
|
warningStates[*ptr] = WARNING_ENABLED;
|
||||||
}
|
}
|
||||||
@@ -229,31 +239,31 @@ void processWarningFlag(char *flag)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* If it's not a meta warning, specially check against `-Werror` */
|
// If it's not a meta warning, specially check against `-Werror`
|
||||||
if (!strncmp(flag, "error", strlen("error"))) {
|
if (!strncmp(flag, "error", strlen("error"))) {
|
||||||
char *errorFlag = flag + strlen("error");
|
char *errorFlag = flag + strlen("error");
|
||||||
|
|
||||||
switch (*errorFlag) {
|
switch (*errorFlag) {
|
||||||
case '\0':
|
case '\0':
|
||||||
/* `-Werror` */
|
// `-Werror`
|
||||||
warningsAreErrors = true;
|
warningsAreErrors = true;
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case '=':
|
case '=':
|
||||||
/* `-Werror=XXX` */
|
// `-Werror=XXX`
|
||||||
setError = true;
|
setError = true;
|
||||||
processWarningFlag(errorFlag + 1); /* Skip the `=` */
|
processWarningFlag(errorFlag + 1); // Skip the `=`
|
||||||
setError = false;
|
setError = false;
|
||||||
return;
|
return;
|
||||||
|
|
||||||
/* Otherwise, allow parsing as another flag */
|
// Otherwise, allow parsing as another flag
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Well, it's either a normal warning or a mistake */
|
// Well, it's either a normal warning or a mistake
|
||||||
|
|
||||||
enum WarningState state = setError ? WARNING_ERROR :
|
enum WarningState state = setError ? WARNING_ERROR :
|
||||||
/* Not an error, then check if this is a negation */
|
// Not an error, then check if this is a negation
|
||||||
strncmp(flag, "no-", strlen("no-")) ? WARNING_ENABLED
|
strncmp(flag, "no-", strlen("no-")) ? WARNING_ENABLED
|
||||||
: WARNING_DISABLED;
|
: WARNING_DISABLED;
|
||||||
char const *rootFlag = state == WARNING_DISABLED ? flag + strlen("no-") : flag;
|
char const *rootFlag = state == WARNING_DISABLED ? flag + strlen("no-") : flag;
|
||||||
@@ -304,10 +314,10 @@ void processWarningFlag(char *flag)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Try to match the flag against a "normal" flag */
|
// Try to match the flag against a "normal" flag
|
||||||
for (enum WarningID id = 0; id < NB_PLAIN_WARNINGS; id++) {
|
for (enum WarningID id = 0; id < NB_PLAIN_WARNINGS; id++) {
|
||||||
if (!strcmp(rootFlag, warningFlags[id])) {
|
if (!strcmp(rootFlag, warningFlags[id])) {
|
||||||
/* We got a match! */
|
// We got a match!
|
||||||
warningStates[id] = state;
|
warningStates[id] = state;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -370,7 +380,7 @@ void warning(enum WarningID id, char const *fmt, ...)
|
|||||||
|
|
||||||
case WARNING_DEFAULT:
|
case WARNING_DEFAULT:
|
||||||
unreachable_();
|
unreachable_();
|
||||||
/* Not reached */
|
// Not reached
|
||||||
|
|
||||||
case WARNING_ENABLED:
|
case WARNING_ENABLED:
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
|
|
||||||
#define BANK_SIZE 0x4000
|
#define BANK_SIZE 0x4000
|
||||||
|
|
||||||
/* Short options */
|
// Short options
|
||||||
static const char *optstring = "Ccf:i:jk:l:m:n:Op:r:st:Vv";
|
static const char *optstring = "Ccf:i:jk:l:m:n:Op:r:st:Vv";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -191,7 +191,7 @@ static void printAcceptedMBCNames(void)
|
|||||||
|
|
||||||
static uint8_t tpp1Rev[2];
|
static uint8_t tpp1Rev[2];
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* @return False on failure
|
* @return False on failure
|
||||||
*/
|
*/
|
||||||
static bool readMBCSlice(char const **name, char const *expected)
|
static bool readMBCSlice(char const **name, char const *expected)
|
||||||
@@ -837,7 +837,7 @@ static ssize_t writeBytes(int fd, void *buf, size_t len)
|
|||||||
return total;
|
return total;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* @param rom0 A pointer to rom0
|
* @param rom0 A pointer to rom0
|
||||||
* @param addr What address to check
|
* @param addr What address to check
|
||||||
* @param fixedByte The fixed byte at the address
|
* @param fixedByte The fixed byte at the address
|
||||||
@@ -853,7 +853,7 @@ static void overwriteByte(uint8_t *rom0, uint16_t addr, uint8_t fixedByte, char
|
|||||||
rom0[addr] = fixedByte;
|
rom0[addr] = fixedByte;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* @param rom0 A pointer to rom0
|
* @param rom0 A pointer to rom0
|
||||||
* @param startAddr What address to begin checking from
|
* @param startAddr What address to begin checking from
|
||||||
* @param fixed The fixed bytes at the address
|
* @param fixed The fixed bytes at the address
|
||||||
@@ -878,7 +878,7 @@ static void overwriteBytes(uint8_t *rom0, uint16_t startAddr, uint8_t const *fix
|
|||||||
memcpy(&rom0[startAddr], fixed, size);
|
memcpy(&rom0[startAddr], fixed, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* @param input File descriptor to be used for reading
|
* @param input File descriptor to be used for reading
|
||||||
* @param output File descriptor to be used for writing, may be equal to `input`
|
* @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 name The file's name, to be displayed for error output
|
||||||
@@ -1174,8 +1174,8 @@ static bool processFilename(char const *name)
|
|||||||
{
|
{
|
||||||
nbErrors = 0;
|
nbErrors = 0;
|
||||||
if (!strcmp(name, "-")) {
|
if (!strcmp(name, "-")) {
|
||||||
setmode(STDIN_FILENO, O_BINARY);
|
(void)setmode(STDIN_FILENO, O_BINARY);
|
||||||
setmode(STDOUT_FILENO, O_BINARY);
|
(void)setmode(STDOUT_FILENO, O_BINARY);
|
||||||
name = "<stdin>";
|
name = "<stdin>";
|
||||||
processFile(STDIN_FILENO, STDOUT_FILENO, name, 0);
|
processFile(STDIN_FILENO, STDOUT_FILENO, name, 0);
|
||||||
|
|
||||||
|
|||||||
@@ -23,8 +23,10 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
#include "extern/getopt.h"
|
#include "extern/getopt.h"
|
||||||
|
#include "file.hpp"
|
||||||
#include "platform.h"
|
#include "platform.h"
|
||||||
#include "version.h"
|
#include "version.h"
|
||||||
|
|
||||||
@@ -153,7 +155,7 @@ static void printUsage(void) {
|
|||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* 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
|
||||||
*/
|
*/
|
||||||
@@ -179,7 +181,7 @@ 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
|
||||||
@@ -248,15 +250,18 @@ static void registerInput(char const *arg) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Turn an "at-file"'s contents into an argv that `getopt` can handle
|
* 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.
|
* @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) {
|
||||||
std::filebuf file;
|
File file;
|
||||||
file.open(path, std::ios_base::in);
|
if (!file.open(path, std::ios_base::in)) {
|
||||||
|
fatal("Error reading @%s: %s", file.c_str(path), strerror(errno));
|
||||||
|
}
|
||||||
|
|
||||||
static_assert(decltype(file)::traits_type::eof() == EOF,
|
// We only filter out `EOF`, but calling `isblank()` on anything else is UB!
|
||||||
|
static_assert(std::remove_reference_t<decltype(*file)>::traits_type::eof() == EOF,
|
||||||
"isblank(char_traits<...>::eof()) is UB!");
|
"isblank(char_traits<...>::eof()) is UB!");
|
||||||
std::vector<size_t> argvOfs;
|
std::vector<size_t> argvOfs;
|
||||||
|
|
||||||
@@ -265,7 +270,7 @@ static std::vector<size_t> readAtFile(std::string const &path, std::vector<char>
|
|||||||
|
|
||||||
// First, discard any leading whitespace
|
// First, discard any leading whitespace
|
||||||
do {
|
do {
|
||||||
c = file.sbumpc();
|
c = file->sbumpc();
|
||||||
if (c == EOF) {
|
if (c == EOF) {
|
||||||
return argvOfs;
|
return argvOfs;
|
||||||
}
|
}
|
||||||
@@ -273,7 +278,7 @@ static std::vector<size_t> readAtFile(std::string const &path, std::vector<char>
|
|||||||
|
|
||||||
switch (c) {
|
switch (c) {
|
||||||
case '#': // If it's a comment, discard everything until EOL
|
case '#': // If it's a comment, discard everything until EOL
|
||||||
while ((c = file.sbumpc()) != '\n') {
|
while ((c = file->sbumpc()) != '\n') {
|
||||||
if (c == EOF) {
|
if (c == EOF) {
|
||||||
return argvOfs;
|
return argvOfs;
|
||||||
}
|
}
|
||||||
@@ -281,7 +286,7 @@ static std::vector<size_t> readAtFile(std::string const &path, std::vector<char>
|
|||||||
continue; // Start processing the next line
|
continue; // Start processing the next line
|
||||||
// If it's an empty line, ignore it
|
// If it's an empty line, ignore it
|
||||||
case '\r': // Assuming CRLF here
|
case '\r': // Assuming CRLF here
|
||||||
file.sbumpc(); // Discard the upcoming '\n'
|
file->sbumpc(); // Discard the upcoming '\n'
|
||||||
[[fallthrough]];
|
[[fallthrough]];
|
||||||
case '\n':
|
case '\n':
|
||||||
continue; // Start processing the next line
|
continue; // Start processing the next line
|
||||||
@@ -296,11 +301,11 @@ static std::vector<size_t> readAtFile(std::string const &path, std::vector<char>
|
|||||||
// on `vector` and `sbumpc` to do the right thing here.
|
// on `vector` and `sbumpc` to do the right thing here.
|
||||||
argPool.push_back(c); // Push the character we've already read
|
argPool.push_back(c); // Push the character we've already read
|
||||||
for (;;) {
|
for (;;) {
|
||||||
c = file.sbumpc();
|
c = file->sbumpc();
|
||||||
if (isblank(c) || c == '\n' || c == EOF) {
|
if (c == EOF || c == '\n' || isblank(c)) {
|
||||||
break;
|
break;
|
||||||
} else if (c == '\r') {
|
} else if (c == '\r') {
|
||||||
file.sbumpc(); // Discard the '\n'
|
file->sbumpc(); // Discard the '\n'
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
argPool.push_back(c);
|
argPool.push_back(c);
|
||||||
@@ -309,15 +314,15 @@ static std::vector<size_t> readAtFile(std::string const &path, std::vector<char>
|
|||||||
|
|
||||||
// Discard whitespace until the next argument (candidate)
|
// Discard whitespace until the next argument (candidate)
|
||||||
while (isblank(c)) {
|
while (isblank(c)) {
|
||||||
c = file.sbumpc();
|
c = file->sbumpc();
|
||||||
}
|
}
|
||||||
if (c == '\r') {
|
if (c == '\r') {
|
||||||
c = file.sbumpc(); // Skip the '\n'
|
c = file->sbumpc(); // Skip the '\n'
|
||||||
}
|
}
|
||||||
} while (c != '\n' && c != EOF); // End if we reached EOL
|
} while (c != '\n' && c != EOF); // End if we reached EOL
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/**
|
/*
|
||||||
* Parses an arg vector, modifying `options` as options are read.
|
* Parses an arg vector, modifying `options` as options are read.
|
||||||
* The three booleans are for the "auto path" flags, since their processing must be deferred to the
|
* The three booleans are for the "auto path" flags, since their processing must be deferred to the
|
||||||
* end of option parsing.
|
* end of option parsing.
|
||||||
@@ -337,6 +342,8 @@ static char *parseArgv(int argc, char **argv, bool &autoAttrmap, bool &autoTilem
|
|||||||
break;
|
break;
|
||||||
case 'a':
|
case 'a':
|
||||||
autoAttrmap = false;
|
autoAttrmap = false;
|
||||||
|
if (!options.attrmap.empty())
|
||||||
|
warning("Overriding attrmap file %s", options.attrmap.c_str());
|
||||||
options.attrmap = musl_optarg;
|
options.attrmap = musl_optarg;
|
||||||
break;
|
break;
|
||||||
case 'b':
|
case 'b':
|
||||||
@@ -479,6 +486,8 @@ static char *parseArgv(int argc, char **argv, bool &autoAttrmap, bool &autoTilem
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'o':
|
case 'o':
|
||||||
|
if (!options.output.empty())
|
||||||
|
warning("Overriding tile data file %s", options.output.c_str());
|
||||||
options.output = musl_optarg;
|
options.output = musl_optarg;
|
||||||
break;
|
break;
|
||||||
case 'P':
|
case 'P':
|
||||||
@@ -486,6 +495,8 @@ static char *parseArgv(int argc, char **argv, bool &autoAttrmap, bool &autoTilem
|
|||||||
break;
|
break;
|
||||||
case 'p':
|
case 'p':
|
||||||
autoPalettes = false;
|
autoPalettes = false;
|
||||||
|
if (!options.palettes.empty())
|
||||||
|
warning("Overriding palettes file %s", options.palettes.c_str());
|
||||||
options.palettes = musl_optarg;
|
options.palettes = musl_optarg;
|
||||||
break;
|
break;
|
||||||
case 'Q':
|
case 'Q':
|
||||||
@@ -493,6 +504,8 @@ static char *parseArgv(int argc, char **argv, bool &autoAttrmap, bool &autoTilem
|
|||||||
break;
|
break;
|
||||||
case 'q':
|
case 'q':
|
||||||
autoPalmap = false;
|
autoPalmap = false;
|
||||||
|
if (!options.palmap.empty())
|
||||||
|
warning("Overriding palette map file %s", options.palmap.c_str());
|
||||||
options.palmap = musl_optarg;
|
options.palmap = musl_optarg;
|
||||||
break;
|
break;
|
||||||
case 'r':
|
case 'r':
|
||||||
@@ -520,6 +533,8 @@ static char *parseArgv(int argc, char **argv, bool &autoAttrmap, bool &autoTilem
|
|||||||
break;
|
break;
|
||||||
case 't':
|
case 't':
|
||||||
autoTilemap = false;
|
autoTilemap = false;
|
||||||
|
if (!options.tilemap.empty())
|
||||||
|
warning("Overriding tilemap file %s", options.tilemap.c_str());
|
||||||
options.tilemap = musl_optarg;
|
options.tilemap = musl_optarg;
|
||||||
break;
|
break;
|
||||||
case 'V':
|
case 'V':
|
||||||
@@ -785,7 +800,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 {
|
||||||
|
|||||||
@@ -39,12 +39,12 @@ namespace packing {
|
|||||||
// Tile | Proto-palette
|
// Tile | Proto-palette
|
||||||
// Page | Palette
|
// Page | Palette
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* A reference to a proto-palette, and attached attributes for sorting purposes
|
* A reference to a proto-palette, and attached attributes for sorting purposes
|
||||||
*/
|
*/
|
||||||
struct ProtoPalAttrs {
|
struct ProtoPalAttrs {
|
||||||
size_t const protoPalIndex;
|
size_t const protoPalIndex;
|
||||||
/**
|
/*
|
||||||
* Pages from which we are banned (to prevent infinite loops)
|
* Pages from which we are banned (to prevent infinite loops)
|
||||||
* This is dynamic because we wish not to hard-cap the amount of palettes
|
* This is dynamic because we wish not to hard-cap the amount of palettes
|
||||||
*/
|
*/
|
||||||
@@ -62,7 +62,7 @@ struct ProtoPalAttrs {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* A collection of proto-palettes assigned to a palette
|
* A collection of proto-palettes assigned to a palette
|
||||||
* Does not contain the actual color indices because we need to be able to remove elements
|
* Does not contain the actual color indices because we need to be able to remove elements
|
||||||
*/
|
*/
|
||||||
@@ -139,7 +139,7 @@ public:
|
|||||||
}
|
}
|
||||||
const_iterator end() const { return const_iterator{&_assigned, _assigned.end()}; }
|
const_iterator end() const { return const_iterator{&_assigned, _assigned.end()}; }
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Assigns a new ProtoPalAttrs in a free slot, assuming there is one
|
* Assigns a new ProtoPalAttrs in a free slot, assuming there is one
|
||||||
* Args are passed to the `ProtoPalAttrs`'s constructor
|
* Args are passed to the `ProtoPalAttrs`'s constructor
|
||||||
*/
|
*/
|
||||||
@@ -198,7 +198,7 @@ private:
|
|||||||
return colors;
|
return colors;
|
||||||
}
|
}
|
||||||
public:
|
public:
|
||||||
/**
|
/*
|
||||||
* Returns the number of distinct colors
|
* Returns the number of distinct colors
|
||||||
*/
|
*/
|
||||||
size_t volume() const { return uniqueColors().size(); }
|
size_t volume() const { return uniqueColors().size(); }
|
||||||
@@ -208,7 +208,7 @@ public:
|
|||||||
return colors.size() <= options.maxOpaqueColors();
|
return colors.size() <= options.maxOpaqueColors();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Computes the "relative size" of a proto-palette on this palette
|
* Computes the "relative size" of a proto-palette on this palette
|
||||||
*/
|
*/
|
||||||
double relSizeOf(ProtoPalette const &protoPal) const {
|
double relSizeOf(ProtoPalette const &protoPal) const {
|
||||||
@@ -227,7 +227,7 @@ public:
|
|||||||
return relSize;
|
return relSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Computes the "relative size" of a set of proto-palettes on this palette
|
* Computes the "relative size" of a set of proto-palettes on this palette
|
||||||
*/
|
*/
|
||||||
template<typename Iter>
|
template<typename Iter>
|
||||||
@@ -237,7 +237,7 @@ public:
|
|||||||
addUniqueColors(colors, std::forward<Iter>(begin), end, protoPals);
|
addUniqueColors(colors, std::forward<Iter>(begin), end, protoPals);
|
||||||
return colors.size();
|
return colors.size();
|
||||||
}
|
}
|
||||||
/**
|
/*
|
||||||
* Computes the "relative size" of a set of colors on this palette
|
* Computes the "relative size" of a set of colors on this palette
|
||||||
*/
|
*/
|
||||||
template<typename Iter>
|
template<typename Iter>
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of RGBDS.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2022, Eldred Habert and RGBDS contributors.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
#include "gfx/pal_sorting.hpp"
|
#include "gfx/pal_sorting.hpp"
|
||||||
|
|
||||||
@@ -21,31 +28,6 @@ void indexed(std::vector<Palette> &palettes, int palSize, png_color const *palRG
|
|||||||
return Rgba(c.red, c.green, c.blue, palAlpha ? palAlpha[index] : 0xFF);
|
return Rgba(c.red, c.green, c.blue, palAlpha ? palAlpha[index] : 0xFF);
|
||||||
};
|
};
|
||||||
|
|
||||||
// HACK: for compatibility with old versions, add unused colors if:
|
|
||||||
// - there is only one palette, and
|
|
||||||
// - only some of the first N colors are being used
|
|
||||||
if (palettes.size() == 1) {
|
|
||||||
Palette &palette = palettes[0];
|
|
||||||
// Build our candidate array of colors
|
|
||||||
decltype(palette.colors) colors{UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX};
|
|
||||||
for (int i = 0; i < options.maxOpaqueColors(); ++i) {
|
|
||||||
colors[i + options.hasTransparentPixels] = pngToRgb(i).cgbColor();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check that the palette only uses those colors
|
|
||||||
if (std::all_of(palette.begin(), palette.end(), [&colors](uint16_t color) {
|
|
||||||
return std::find(colors.begin(), colors.end(), color) != colors.end();
|
|
||||||
})) {
|
|
||||||
if (palette.size() != options.maxOpaqueColors()) {
|
|
||||||
warning("Unused color in PNG embedded palette was re-added; please use `-c "
|
|
||||||
"embedded` to get this in future versions");
|
|
||||||
}
|
|
||||||
// Overwrite the palette, and return with that (it's already sorted)
|
|
||||||
palette.colors = colors;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Palette &pal : palettes) {
|
for (Palette &pal : palettes) {
|
||||||
std::sort(pal.begin(), pal.end(), [&](uint16_t lhs, uint16_t rhs) {
|
std::sort(pal.begin(), pal.end(), [&](uint16_t lhs, uint16_t rhs) {
|
||||||
// Iterate through the PNG's palette, looking for either of the two
|
// Iterate through the PNG's palette, looking for either of the two
|
||||||
@@ -97,7 +79,7 @@ void rgb(std::vector<Palette> &palettes) {
|
|||||||
|
|
||||||
for (Palette &pal : palettes) {
|
for (Palette &pal : palettes) {
|
||||||
std::sort(pal.begin(), pal.end(), [](uint16_t lhs, uint16_t rhs) {
|
std::sort(pal.begin(), pal.end(), [](uint16_t lhs, uint16_t rhs) {
|
||||||
return legacyLuminance(lhs) < legacyLuminance(rhs);
|
return legacyLuminance(lhs) > legacyLuminance(rhs);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,8 @@
|
|||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
#include <limits>
|
||||||
|
#include <optional>
|
||||||
#include <ostream>
|
#include <ostream>
|
||||||
#include <streambuf>
|
#include <streambuf>
|
||||||
#include <string>
|
#include <string>
|
||||||
@@ -53,7 +55,7 @@ constexpr uint8_t singleToHex(char c) {
|
|||||||
|
|
||||||
template<typename Str> // Should be std::string or std::string_view
|
template<typename Str> // Should be std::string or std::string_view
|
||||||
static void skipWhitespace(Str const &str, typename Str::size_type &pos) {
|
static void skipWhitespace(Str const &str, typename Str::size_type &pos) {
|
||||||
pos = std::min(str.find_first_not_of(" \t", pos), str.length());
|
pos = std::min(str.find_first_not_of(" \t"sv, pos), str.length());
|
||||||
}
|
}
|
||||||
|
|
||||||
void parseInlinePalSpec(char const * const rawArg) {
|
void parseInlinePalSpec(char const * const rawArg) {
|
||||||
@@ -165,7 +167,7 @@ void parseInlinePalSpec(char const * const rawArg) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Tries to read some magic bytes from the provided `file`.
|
* Tries to read some magic bytes from the provided `file`.
|
||||||
* Returns whether the magic was correctly read.
|
* Returns whether the magic was correctly read.
|
||||||
*/
|
*/
|
||||||
@@ -191,7 +193,16 @@ static T readBE(U const *bytes) {
|
|||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
template<typename T, typename U>
|
||||||
|
static T readLE(U const *bytes) {
|
||||||
|
T val = 0;
|
||||||
|
for (size_t i = 0; i < sizeof(val); ++i) {
|
||||||
|
val |= static_cast<uint8_t>(bytes[i]) << (i * 8);
|
||||||
|
}
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
* **Appends** the first line read from `file` to the end of the provided `buffer`.
|
* **Appends** the first line read from `file` to the end of the provided `buffer`.
|
||||||
*/
|
*/
|
||||||
static void readLine(std::filebuf &file, std::string &buffer) {
|
static void readLine(std::filebuf &file, std::string &buffer) {
|
||||||
@@ -214,16 +225,56 @@ static void readLine(std::filebuf &file, std::string &buffer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: Normally we'd use `std::from_chars`, but that's not available with GCC 7
|
// FIXME: Normally we'd use `std::from_chars`, but that's not available with GCC 7
|
||||||
/**
|
/*
|
||||||
* Parses the initial part of a string_view, advancing the "read index" as it does
|
* Parses the initial part of a string_view, advancing the "read index" as it does
|
||||||
*/
|
*/
|
||||||
static uint16_t parseDec(std::string const &str, std::string::size_type &n) {
|
template<typename U> // Should be uint*_t
|
||||||
uint32_t value = 0; // Use a larger type to handle overflow more easily
|
static std::optional<U> parseDec(std::string const &str, std::string::size_type &n) {
|
||||||
for (auto end = std::min(str.length(), str.find_first_not_of("0123456789", n)); n < end; ++n) {
|
std::string::size_type start = n;
|
||||||
value = std::min<uint32_t>(value * 10 + (str[n] - '0'), UINT16_MAX);
|
|
||||||
|
uintmax_t value = 0; // Use a larger type to handle overflow more easily
|
||||||
|
for (auto end = std::min(str.length(), str.find_first_not_of("0123456789"sv, n)); n < end;
|
||||||
|
++n) {
|
||||||
|
value = std::min(value * 10 + (str[n] - '0'), (uintmax_t)std::numeric_limits<U>::max);
|
||||||
}
|
}
|
||||||
|
|
||||||
return value;
|
return n > start ? std::optional<U>{value} : std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::optional<Rgba> parseColor(std::string const &str, std::string::size_type &n,
|
||||||
|
uint16_t i) {
|
||||||
|
std::optional<uint8_t> r = parseDec<uint8_t>(str, n);
|
||||||
|
if (!r) {
|
||||||
|
error("Failed to parse color #%" PRIu16 " (\"%s\"): invalid red component", i + 1,
|
||||||
|
str.c_str());
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
skipWhitespace(str, n);
|
||||||
|
if (n == str.length()) {
|
||||||
|
error("Failed to parse color #%" PRIu16 " (\"%s\"): missing green component", i + 1,
|
||||||
|
str.c_str());
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
std::optional<uint8_t> g = parseDec<uint8_t>(str, n);
|
||||||
|
if (!g) {
|
||||||
|
error("Failed to parse color #%" PRIu16 " (\"%s\"): invalid green component", i + 1,
|
||||||
|
str.c_str());
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
skipWhitespace(str, n);
|
||||||
|
if (n == str.length()) {
|
||||||
|
error("Failed to parse color #%" PRIu16 " (\"%s\"): missing blue component", i + 1,
|
||||||
|
str.c_str());
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
std::optional<uint8_t> b = parseDec<uint8_t>(str, n);
|
||||||
|
if (!b) {
|
||||||
|
error("Failed to parse color #%" PRIu16 " (\"%s\"): invalid blue component", i + 1,
|
||||||
|
str.c_str());
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::optional<Rgba>{Rgba(*r, *g, *b, 0xFF)};
|
||||||
}
|
}
|
||||||
|
|
||||||
static void parsePSPFile(std::filebuf &file) {
|
static void parsePSPFile(std::filebuf &file) {
|
||||||
@@ -246,41 +297,30 @@ static void parsePSPFile(std::filebuf &file) {
|
|||||||
line.clear();
|
line.clear();
|
||||||
readLine(file, line);
|
readLine(file, line);
|
||||||
std::string::size_type n = 0;
|
std::string::size_type n = 0;
|
||||||
uint16_t nbColors = parseDec(line, n);
|
std::optional<uint16_t> nbColors = parseDec<uint16_t>(line, n);
|
||||||
if (n != line.length()) {
|
if (!nbColors || n != line.length()) {
|
||||||
error("Invalid \"number of colors\" line in PSP file (%s)", line.c_str());
|
error("Invalid \"number of colors\" line in PSP file (%s)", line.c_str());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nbColors > options.nbColorsPerPal * options.nbPalettes) {
|
if (*nbColors > options.nbColorsPerPal * options.nbPalettes) {
|
||||||
warning("PSP file contains %" PRIu16 " colors, but there can only be %" PRIu16
|
warning("PSP file contains %" PRIu16 " colors, but there can only be %" PRIu16
|
||||||
"; ignoring extra",
|
"; ignoring extra",
|
||||||
nbColors, options.nbColorsPerPal * options.nbPalettes);
|
*nbColors, options.nbColorsPerPal * options.nbPalettes);
|
||||||
nbColors = options.nbColorsPerPal * options.nbPalettes;
|
nbColors = options.nbColorsPerPal * options.nbPalettes;
|
||||||
}
|
}
|
||||||
|
|
||||||
options.palSpec.clear();
|
options.palSpec.clear();
|
||||||
|
|
||||||
for (uint16_t i = 0; i < nbColors; ++i) {
|
for (uint16_t i = 0; i < *nbColors; ++i) {
|
||||||
line.clear();
|
line.clear();
|
||||||
readLine(file, line);
|
readLine(file, line);
|
||||||
n = 0;
|
|
||||||
|
|
||||||
uint8_t r = parseDec(line, n);
|
n = 0;
|
||||||
skipWhitespace(line, n);
|
std::optional<Rgba> color = parseColor(line, n, i + 1);
|
||||||
if (n == line.length()) {
|
if (!color) {
|
||||||
error("Failed to parse color #%" PRIu16 " (\"%s\"): missing green component", i + 1,
|
|
||||||
line.c_str());
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
uint8_t g = parseDec(line, n);
|
|
||||||
if (n == line.length()) {
|
|
||||||
error("Failed to parse color #%" PRIu16 " (\"%s\"): missing green component", i + 1,
|
|
||||||
line.c_str());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
skipWhitespace(line, n);
|
|
||||||
uint8_t b = parseDec(line, n);
|
|
||||||
if (n != line.length()) {
|
if (n != line.length()) {
|
||||||
error("Failed to parse color #%" PRIu16
|
error("Failed to parse color #%" PRIu16
|
||||||
" (\"%s\"): trailing characters after blue component",
|
" (\"%s\"): trailing characters after blue component",
|
||||||
@@ -291,11 +331,98 @@ static void parsePSPFile(std::filebuf &file) {
|
|||||||
if (i % options.nbColorsPerPal == 0) {
|
if (i % options.nbColorsPerPal == 0) {
|
||||||
options.palSpec.emplace_back();
|
options.palSpec.emplace_back();
|
||||||
}
|
}
|
||||||
options.palSpec.back()[i % options.nbColorsPerPal] = Rgba(r, g, b, 0xFF);
|
options.palSpec.back()[i % options.nbColorsPerPal] = *color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void parseACTFile(std::filebuf &file) {
|
static void parseGPLFile(std::filebuf &file) {
|
||||||
|
// https://gitlab.gnome.org/GNOME/gimp/-/blob/gimp-2-10/app/core/gimppalette-load.c#L39
|
||||||
|
|
||||||
|
std::string line;
|
||||||
|
readLine(file, line);
|
||||||
|
// FIXME: C++20 will allow `!line.starts_with` instead of `line.rfind` with 0
|
||||||
|
if (line.rfind("GIMP Palette", 0)) {
|
||||||
|
error("Palette file does not appear to be a GPL palette file");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t nbColors = 0;
|
||||||
|
uint16_t maxNbColors = options.nbColorsPerPal * options.nbPalettes;
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
line.clear();
|
||||||
|
readLine(file, line);
|
||||||
|
if (!line.length()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: C++20 will allow `line.starts_with` instead of `!line.rfind` with 0
|
||||||
|
if (!line.rfind("#", 0) || !line.rfind("Name:", 0) || !line.rfind("Column:", 0)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string::size_type n = 0;
|
||||||
|
std::optional<Rgba> color = parseColor(line, n, nbColors + 1);
|
||||||
|
if (!color) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
++nbColors;
|
||||||
|
if (nbColors < maxNbColors) {
|
||||||
|
if (nbColors % options.nbColorsPerPal == 1) {
|
||||||
|
options.palSpec.emplace_back();
|
||||||
|
}
|
||||||
|
options.palSpec.back()[nbColors % options.nbColorsPerPal] = *color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nbColors > maxNbColors) {
|
||||||
|
warning("GPL file contains %" PRIu16 " colors, but there can only be %" PRIu16
|
||||||
|
"; ignoring extra",
|
||||||
|
nbColors, maxNbColors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void parseHEXFile(std::filebuf &file) {
|
||||||
|
// https://lospec.com/palette-list/tag/gbc
|
||||||
|
|
||||||
|
uint16_t nbColors = 0;
|
||||||
|
uint16_t maxNbColors = options.nbColorsPerPal * options.nbPalettes;
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
std::string line;
|
||||||
|
readLine(file, line);
|
||||||
|
if (!line.length()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (line.length() != 6
|
||||||
|
|| line.find_first_not_of("0123456789ABCDEFabcdef"sv) != std::string::npos) {
|
||||||
|
error("Failed to parse color #%" PRIu16 " (\"%s\"): invalid \"rrggbb\" line",
|
||||||
|
nbColors + 1, line.c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Rgba color =
|
||||||
|
Rgba(toHex(line[0], line[1]), toHex(line[2], line[3]), toHex(line[4], line[5]), 0xFF);
|
||||||
|
|
||||||
|
++nbColors;
|
||||||
|
if (nbColors < maxNbColors) {
|
||||||
|
if (nbColors % options.nbColorsPerPal == 1) {
|
||||||
|
options.palSpec.emplace_back();
|
||||||
|
}
|
||||||
|
options.palSpec.back()[nbColors % options.nbColorsPerPal] = color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nbColors > maxNbColors) {
|
||||||
|
warning("HEX file contains %" PRIu16 " colors, but there can only be %" PRIu16
|
||||||
|
"; ignoring extra",
|
||||||
|
nbColors, maxNbColors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void parseACTFile(std::filebuf &file) {
|
||||||
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577411_pgfId-1070626
|
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577411_pgfId-1070626
|
||||||
|
|
||||||
std::array<char, 772> buf;
|
std::array<char, 772> buf;
|
||||||
@@ -344,8 +471,8 @@ void parseACTFile(std::filebuf &file) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void parseACOFile(std::filebuf &file) {
|
static void parseACOFile(std::filebuf &file) {
|
||||||
// rhttps://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577411_pgfId-1055819
|
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577411_pgfId-1055819
|
||||||
// http://www.nomodes.com/aco.html
|
// http://www.nomodes.com/aco.html
|
||||||
|
|
||||||
char buf[10];
|
char buf[10];
|
||||||
@@ -412,6 +539,29 @@ void parseACOFile(std::filebuf &file) {
|
|||||||
// `codecvt` can be used to convert from UTF-16 to UTF-8
|
// `codecvt` can be used to convert from UTF-16 to UTF-8
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void parseGBCFile(std::filebuf &file) {
|
||||||
|
// This only needs to be able to read back files generated by `rgbgfx -p`
|
||||||
|
options.palSpec.clear();
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
char buf[2 * 4];
|
||||||
|
auto len = file.sgetn(buf, sizeof(buf));
|
||||||
|
if (len == 0) {
|
||||||
|
break;
|
||||||
|
} else if (len != sizeof(buf)) {
|
||||||
|
error("GBC palette dump contains %zu 8-byte palette%s, plus %zu byte%s",
|
||||||
|
options.palSpec.size(), options.palSpec.size() == 1 ? "" : "s", len,
|
||||||
|
len == 1 ? "" : "s");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
options.palSpec.push_back({Rgba::fromCGBColor(readLE<uint16_t>(&buf[0])),
|
||||||
|
Rgba::fromCGBColor(readLE<uint16_t>(&buf[2])),
|
||||||
|
Rgba::fromCGBColor(readLE<uint16_t>(&buf[4])),
|
||||||
|
Rgba::fromCGBColor(readLE<uint16_t>(&buf[6]))});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void parseExternalPalSpec(char const *arg) {
|
void parseExternalPalSpec(char const *arg) {
|
||||||
// `fmt:path`, parse the file according to the given format
|
// `fmt:path`, parse the file according to the given format
|
||||||
|
|
||||||
@@ -425,8 +575,11 @@ void parseExternalPalSpec(char const *arg) {
|
|||||||
|
|
||||||
static std::array parsers{
|
static std::array parsers{
|
||||||
std::tuple{"PSP", &parsePSPFile, std::ios::in },
|
std::tuple{"PSP", &parsePSPFile, std::ios::in },
|
||||||
|
std::tuple{"GPL", &parseGPLFile, std::ios::in },
|
||||||
|
std::tuple{"HEX", &parseHEXFile, std::ios::in },
|
||||||
std::tuple{"ACT", &parseACTFile, std::ios::binary},
|
std::tuple{"ACT", &parseACTFile, std::ios::binary},
|
||||||
std::tuple{"ACO", &parseACOFile, std::ios::binary},
|
std::tuple{"ACO", &parseACOFile, std::ios::binary},
|
||||||
|
std::tuple{"GBC", &parseGBCFile, std::ios::binary},
|
||||||
};
|
};
|
||||||
|
|
||||||
auto iter = std::find_if(parsers.begin(), parsers.end(),
|
auto iter = std::find_if(parsers.begin(), parsers.end(),
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "defaultinitalloc.hpp"
|
#include "defaultinitalloc.hpp"
|
||||||
|
#include "file.hpp"
|
||||||
#include "helpers.h"
|
#include "helpers.h"
|
||||||
#include "itertools.hpp"
|
#include "itertools.hpp"
|
||||||
|
|
||||||
@@ -42,7 +43,7 @@ class ImagePalette {
|
|||||||
public:
|
public:
|
||||||
ImagePalette() = default;
|
ImagePalette() = default;
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Registers a color in the palette.
|
* Registers a color in the palette.
|
||||||
* If the newly inserted color "conflicts" with another one (different color, but same CGB
|
* If the newly inserted color "conflicts" with another one (different color, but same CGB
|
||||||
* color), then the other color is returned. Otherwise, `nullptr` is returned.
|
* color), then the other color is returned. Otherwise, `nullptr` is returned.
|
||||||
@@ -77,7 +78,7 @@ public:
|
|||||||
|
|
||||||
class Png {
|
class Png {
|
||||||
std::string const &path;
|
std::string const &path;
|
||||||
std::filebuf file{};
|
File file{};
|
||||||
png_structp png = nullptr;
|
png_structp png = nullptr;
|
||||||
png_infop info = nullptr;
|
png_infop info = nullptr;
|
||||||
|
|
||||||
@@ -93,25 +94,26 @@ class Png {
|
|||||||
[[noreturn]] static void handleError(png_structp png, char const *msg) {
|
[[noreturn]] static void handleError(png_structp png, char const *msg) {
|
||||||
Png *self = reinterpret_cast<Png *>(png_get_error_ptr(png));
|
Png *self = reinterpret_cast<Png *>(png_get_error_ptr(png));
|
||||||
|
|
||||||
fatal("Error reading input image (\"%s\"): %s", self->path.c_str(), msg);
|
fatal("Error reading input image (\"%s\"): %s", self->file.c_str(self->path), msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void handleWarning(png_structp png, char const *msg) {
|
static void handleWarning(png_structp png, char const *msg) {
|
||||||
Png *self = reinterpret_cast<Png *>(png_get_error_ptr(png));
|
Png *self = reinterpret_cast<Png *>(png_get_error_ptr(png));
|
||||||
|
|
||||||
warning("In input image (\"%s\"): %s", self->path.c_str(), msg);
|
warning("In input image (\"%s\"): %s", self->file.c_str(self->path), msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void readData(png_structp png, png_bytep data, size_t length) {
|
static void readData(png_structp png, png_bytep data, size_t length) {
|
||||||
Png *self = reinterpret_cast<Png *>(png_get_io_ptr(png));
|
Png *self = reinterpret_cast<Png *>(png_get_io_ptr(png));
|
||||||
std::streamsize expectedLen = length;
|
std::streamsize expectedLen = length;
|
||||||
std::streamsize nbBytesRead = self->file.sgetn(reinterpret_cast<char *>(data), expectedLen);
|
std::streamsize nbBytesRead =
|
||||||
|
self->file->sgetn(reinterpret_cast<char *>(data), expectedLen);
|
||||||
|
|
||||||
if (nbBytesRead != expectedLen) {
|
if (nbBytesRead != expectedLen) {
|
||||||
fatal("Error reading input image (\"%s\"): file too short (expected at least %zd more "
|
fatal("Error reading input image (\"%s\"): file too short (expected at least %zd more "
|
||||||
"bytes after reading %lld)",
|
"bytes after reading %lld)",
|
||||||
self->path.c_str(), length - nbBytesRead,
|
self->file.c_str(self->path), length - nbBytesRead,
|
||||||
self->file.pubseekoff(0, std::ios_base::cur));
|
self->file->pubseekoff(0, std::ios_base::cur));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,7 +144,7 @@ public:
|
|||||||
}
|
}
|
||||||
uint8_t bins = 0;
|
uint8_t bins = 0;
|
||||||
for (auto const &color : colors) {
|
for (auto const &color : colors) {
|
||||||
if (color->isTransparent()) {
|
if (!color.has_value() || color->isTransparent()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!color->isGray()) {
|
if (!color->isGray()) {
|
||||||
@@ -164,7 +166,7 @@ public:
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Reads a PNG and notes all of its colors
|
* Reads a PNG and notes all of its colors
|
||||||
*
|
*
|
||||||
* This code is more complicated than strictly necessary, but that's because of the API
|
* This code is more complicated than strictly necessary, but that's because of the API
|
||||||
@@ -175,17 +177,17 @@ public:
|
|||||||
*/
|
*/
|
||||||
explicit Png(std::string const &filePath) : path(filePath), colors() {
|
explicit Png(std::string const &filePath) : path(filePath), colors() {
|
||||||
if (file.open(path, std::ios_base::in | std::ios_base::binary) == nullptr) {
|
if (file.open(path, std::ios_base::in | std::ios_base::binary) == nullptr) {
|
||||||
fatal("Failed to open input image (\"%s\"): %s", path.c_str(), strerror(errno));
|
fatal("Failed to open input image (\"%s\"): %s", file.c_str(path), strerror(errno));
|
||||||
}
|
}
|
||||||
|
|
||||||
options.verbosePrint(Options::VERB_LOG_ACT, "Opened input file\n");
|
options.verbosePrint(Options::VERB_LOG_ACT, "Opened input file\n");
|
||||||
|
|
||||||
std::array<unsigned char, 8> pngHeader;
|
std::array<unsigned char, 8> pngHeader;
|
||||||
|
|
||||||
if (file.sgetn(reinterpret_cast<char *>(pngHeader.data()), pngHeader.size())
|
if (file->sgetn(reinterpret_cast<char *>(pngHeader.data()), pngHeader.size())
|
||||||
!= static_cast<std::streamsize>(pngHeader.size()) // Not enough bytes?
|
!= static_cast<std::streamsize>(pngHeader.size()) // Not enough bytes?
|
||||||
|| png_sig_cmp(pngHeader.data(), 0, pngHeader.size()) != 0) {
|
|| png_sig_cmp(pngHeader.data(), 0, pngHeader.size()) != 0) {
|
||||||
fatal("Input file (\"%s\") is not a PNG image!", path.c_str());
|
fatal("Input file (\"%s\") is not a PNG image!", file.c_str(path));
|
||||||
}
|
}
|
||||||
|
|
||||||
options.verbosePrint(Options::VERB_INTERM, "PNG header signature is OK\n");
|
options.verbosePrint(Options::VERB_INTERM, "PNG header signature is OK\n");
|
||||||
@@ -254,8 +256,8 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
options.verbosePrint(Options::VERB_INTERM,
|
options.verbosePrint(Options::VERB_INTERM,
|
||||||
"Input image: %" PRIu32 "x%" PRIu32 " pixels, %dbpp %s, %s\n", height,
|
"Input image: %" PRIu32 "x%" PRIu32 " pixels, %dbpp %s, %s\n", width,
|
||||||
width, bitDepth, colorTypeName(), interlaceTypeName());
|
height, bitDepth, colorTypeName(), interlaceTypeName());
|
||||||
|
|
||||||
if (png_get_PLTE(png, info, &embeddedPal, &nbColors) != 0) {
|
if (png_get_PLTE(png, info, &embeddedPal, &nbColors) != 0) {
|
||||||
int nbTransparentEntries;
|
int nbTransparentEntries;
|
||||||
@@ -466,7 +468,7 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
class RawTiles {
|
class RawTiles {
|
||||||
/**
|
/*
|
||||||
* A tile which only contains indices into the image's global palette
|
* A tile which only contains indices into the image's global palette
|
||||||
*/
|
*/
|
||||||
class RawTile {
|
class RawTile {
|
||||||
@@ -481,7 +483,7 @@ private:
|
|||||||
std::vector<RawTile> _tiles;
|
std::vector<RawTile> _tiles;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/**
|
/*
|
||||||
* Creates a new raw tile, and returns a reference to it so it can be filled in
|
* Creates a new raw tile, and returns a reference to it so it can be filled in
|
||||||
*/
|
*/
|
||||||
RawTile &newTile() {
|
RawTile &newTile() {
|
||||||
@@ -491,7 +493,7 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct AttrmapEntry {
|
struct AttrmapEntry {
|
||||||
/**
|
/*
|
||||||
* This field can either be a proto-palette ID, or `transparent` to indicate that the
|
* This field can either be a proto-palette ID, or `transparent` to indicate that the
|
||||||
* corresponding tile is fully transparent. If you are looking to get the palette ID for this
|
* corresponding tile is fully transparent. If you are looking to get the palette ID for this
|
||||||
* attrmap entry while correctly handling the above, use `getPalID`.
|
* attrmap entry while correctly handling the above, use `getPalID`.
|
||||||
@@ -624,14 +626,16 @@ static std::tuple<DefaultInitVec<size_t>, std::vector<Palette>>
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void outputPalettes(std::vector<Palette> const &palettes) {
|
static void outputPalettes(std::vector<Palette> const &palettes) {
|
||||||
std::filebuf output;
|
File output;
|
||||||
output.open(options.palettes, std::ios_base::out | std::ios_base::binary);
|
if (!output.open(options.palettes, std::ios_base::out | std::ios_base::binary)) {
|
||||||
|
fatal("Failed to open \"%s\": %s", output.c_str(options.palettes), strerror(errno));
|
||||||
|
}
|
||||||
|
|
||||||
for (Palette const &palette : palettes) {
|
for (Palette const &palette : palettes) {
|
||||||
for (uint8_t i = 0; i < options.nbColorsPerPal; ++i) {
|
for (uint8_t i = 0; i < options.nbColorsPerPal; ++i) {
|
||||||
uint16_t color = palette.colors[i]; // Will return `UINT16_MAX` for unused slots
|
uint16_t color = palette.colors[i]; // Will return `UINT16_MAX` for unused slots
|
||||||
output.sputc(color & 0xFF);
|
output->sputc(color & 0xFF);
|
||||||
output.sputc(color >> 8);
|
output->sputc(color >> 8);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -750,8 +754,10 @@ namespace unoptimized {
|
|||||||
static void outputTileData(Png const &png, DefaultInitVec<AttrmapEntry> const &attrmap,
|
static void outputTileData(Png const &png, DefaultInitVec<AttrmapEntry> const &attrmap,
|
||||||
std::vector<Palette> const &palettes,
|
std::vector<Palette> const &palettes,
|
||||||
DefaultInitVec<size_t> const &mappings) {
|
DefaultInitVec<size_t> const &mappings) {
|
||||||
std::filebuf output;
|
File output;
|
||||||
output.open(options.output, std::ios_base::out | std::ios_base::binary);
|
if (!output.open(options.output, std::ios_base::out | std::ios_base::binary)) {
|
||||||
|
fatal("Failed to open \"%s\": %s", output.c_str(options.output), strerror(errno));
|
||||||
|
}
|
||||||
|
|
||||||
uint64_t remainingTiles = (png.getWidth() / 8) * (png.getHeight() / 8);
|
uint64_t remainingTiles = (png.getWidth() / 8) * (png.getHeight() / 8);
|
||||||
if (remainingTiles <= options.trim) {
|
if (remainingTiles <= options.trim) {
|
||||||
@@ -764,9 +770,9 @@ static void outputTileData(Png const &png, DefaultInitVec<AttrmapEntry> const &a
|
|||||||
Palette const &palette = palettes[attr.getPalID(mappings)];
|
Palette const &palette = palettes[attr.getPalID(mappings)];
|
||||||
for (uint32_t y = 0; y < 8; ++y) {
|
for (uint32_t y = 0; y < 8; ++y) {
|
||||||
uint16_t bitplanes = TileData::rowBitplanes(tile, palette, y);
|
uint16_t bitplanes = TileData::rowBitplanes(tile, palette, y);
|
||||||
output.sputc(bitplanes & 0xFF);
|
output->sputc(bitplanes & 0xFF);
|
||||||
if (options.bitDepth == 2) {
|
if (options.bitDepth == 2) {
|
||||||
output.sputc(bitplanes >> 8);
|
output->sputc(bitplanes >> 8);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -780,18 +786,27 @@ static void outputTileData(Png const &png, DefaultInitVec<AttrmapEntry> const &a
|
|||||||
|
|
||||||
static void outputMaps(DefaultInitVec<AttrmapEntry> const &attrmap,
|
static void outputMaps(DefaultInitVec<AttrmapEntry> const &attrmap,
|
||||||
DefaultInitVec<size_t> const &mappings) {
|
DefaultInitVec<size_t> const &mappings) {
|
||||||
std::optional<std::filebuf> tilemapOutput, attrmapOutput, palmapOutput;
|
std::optional<File> tilemapOutput, attrmapOutput, palmapOutput;
|
||||||
if (!options.tilemap.empty()) {
|
if (!options.tilemap.empty()) {
|
||||||
tilemapOutput.emplace();
|
tilemapOutput.emplace();
|
||||||
tilemapOutput->open(options.tilemap, std::ios_base::out | std::ios_base::binary);
|
if (!tilemapOutput->open(options.tilemap, std::ios_base::out | std::ios_base::binary)) {
|
||||||
|
fatal("Failed to open \"%s\": %s", tilemapOutput->c_str(options.tilemap),
|
||||||
|
strerror(errno));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!options.attrmap.empty()) {
|
if (!options.attrmap.empty()) {
|
||||||
attrmapOutput.emplace();
|
attrmapOutput.emplace();
|
||||||
attrmapOutput->open(options.attrmap, std::ios_base::out | std::ios_base::binary);
|
if (!attrmapOutput->open(options.attrmap, std::ios_base::out | std::ios_base::binary)) {
|
||||||
|
fatal("Failed to open \"%s\": %s", attrmapOutput->c_str(options.attrmap),
|
||||||
|
strerror(errno));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!options.palmap.empty()) {
|
if (!options.palmap.empty()) {
|
||||||
palmapOutput.emplace();
|
palmapOutput.emplace();
|
||||||
palmapOutput->open(options.palmap, std::ios_base::out | std::ios_base::binary);
|
if (!palmapOutput->open(options.palmap, std::ios_base::out | std::ios_base::binary)) {
|
||||||
|
fatal("Failed to open \"%s\": %s", palmapOutput->c_str(options.palmap),
|
||||||
|
strerror(errno));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t tileID = 0;
|
uint8_t tileID = 0;
|
||||||
@@ -804,14 +819,14 @@ static void outputMaps(DefaultInitVec<AttrmapEntry> const &attrmap,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (tilemapOutput.has_value()) {
|
if (tilemapOutput.has_value()) {
|
||||||
tilemapOutput->sputc(tileID + options.baseTileIDs[bank]);
|
(*tilemapOutput)->sputc(tileID + options.baseTileIDs[bank]);
|
||||||
}
|
}
|
||||||
if (attrmapOutput.has_value()) {
|
if (attrmapOutput.has_value()) {
|
||||||
uint8_t palID = attr.getPalID(mappings) & 7;
|
uint8_t palID = attr.getPalID(mappings) & 7;
|
||||||
attrmapOutput->sputc(palID | bank << 3); // The other flags are all 0
|
(*attrmapOutput)->sputc(palID | bank << 3); // The other flags are all 0
|
||||||
}
|
}
|
||||||
if (palmapOutput.has_value()) {
|
if (palmapOutput.has_value()) {
|
||||||
palmapOutput->sputc(attr.getPalID(mappings));
|
(*palmapOutput)->sputc(attr.getPalID(mappings));
|
||||||
}
|
}
|
||||||
++tileID;
|
++tileID;
|
||||||
}
|
}
|
||||||
@@ -831,7 +846,7 @@ struct UniqueTiles {
|
|||||||
UniqueTiles(UniqueTiles const &) = delete;
|
UniqueTiles(UniqueTiles const &) = delete;
|
||||||
UniqueTiles(UniqueTiles &&) = default;
|
UniqueTiles(UniqueTiles &&) = default;
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Adds a tile to the collection, and returns its ID
|
* Adds a tile to the collection, and returns its ID
|
||||||
*/
|
*/
|
||||||
std::tuple<uint16_t, TileData::MatchType> addTile(Png::TilesVisitor::Tile const &tile,
|
std::tuple<uint16_t, TileData::MatchType> addTile(Png::TilesVisitor::Tile const &tile,
|
||||||
@@ -857,7 +872,7 @@ struct UniqueTiles {
|
|||||||
auto end() const { return tiles.end(); }
|
auto end() const { return tiles.end(); }
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Generate tile data while deduplicating unique tiles (via mirroring if enabled)
|
* Generate tile data while deduplicating unique tiles (via mirroring if enabled)
|
||||||
* Additionally, while we have the info handy, convert from the 16-bit "global" tile IDs to
|
* Additionally, while we have the info handy, convert from the 16-bit "global" tile IDs to
|
||||||
* 8-bit tile IDs + the bank bit; this will save the work when we output the data later (potentially
|
* 8-bit tile IDs + the bank bit; this will save the work when we output the data later (potentially
|
||||||
@@ -886,47 +901,55 @@ static UniqueTiles dedupTiles(Png const &png, DefaultInitVec<AttrmapEntry> &attr
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void outputTileData(UniqueTiles const &tiles) {
|
static void outputTileData(UniqueTiles const &tiles) {
|
||||||
std::filebuf output;
|
File output;
|
||||||
output.open(options.output, std::ios_base::out | std::ios_base::binary);
|
if (!output.open(options.output, std::ios_base::out | std::ios_base::binary)) {
|
||||||
|
fatal("Failed to create \"%s\": %s", output.c_str(options.output), strerror(errno));
|
||||||
|
}
|
||||||
|
|
||||||
uint16_t tileID = 0;
|
uint16_t tileID = 0;
|
||||||
for (auto iter = tiles.begin(), end = tiles.end() - options.trim; iter != end; ++iter) {
|
for (auto iter = tiles.begin(), end = tiles.end() - options.trim; iter != end; ++iter) {
|
||||||
TileData const *tile = *iter;
|
TileData const *tile = *iter;
|
||||||
assert(tile->tileID == tileID);
|
assert(tile->tileID == tileID);
|
||||||
++tileID;
|
++tileID;
|
||||||
output.sputn(reinterpret_cast<char const *>(tile->data().data()), options.bitDepth * 8);
|
output->sputn(reinterpret_cast<char const *>(tile->data().data()), options.bitDepth * 8);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void outputTilemap(DefaultInitVec<AttrmapEntry> const &attrmap) {
|
static void outputTilemap(DefaultInitVec<AttrmapEntry> const &attrmap) {
|
||||||
std::filebuf output;
|
File output;
|
||||||
output.open(options.tilemap, std::ios_base::out | std::ios_base::binary);
|
if (!output.open(options.tilemap, std::ios_base::out | std::ios_base::binary)) {
|
||||||
|
fatal("Failed to create \"%s\": %s", output.c_str(options.tilemap), strerror(errno));
|
||||||
|
}
|
||||||
|
|
||||||
for (AttrmapEntry const &entry : attrmap) {
|
for (AttrmapEntry const &entry : attrmap) {
|
||||||
output.sputc(entry.tileID); // The tile ID has already been converted
|
output->sputc(entry.tileID); // The tile ID has already been converted
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void outputAttrmap(DefaultInitVec<AttrmapEntry> const &attrmap,
|
static void outputAttrmap(DefaultInitVec<AttrmapEntry> const &attrmap,
|
||||||
DefaultInitVec<size_t> const &mappings) {
|
DefaultInitVec<size_t> const &mappings) {
|
||||||
std::filebuf output;
|
File output;
|
||||||
output.open(options.attrmap, std::ios_base::out | std::ios_base::binary);
|
if (!output.open(options.attrmap, std::ios_base::out | std::ios_base::binary)) {
|
||||||
|
fatal("Failed to create \"%s\": %s", output.c_str(options.attrmap), strerror(errno));
|
||||||
|
}
|
||||||
|
|
||||||
for (AttrmapEntry const &entry : attrmap) {
|
for (AttrmapEntry const &entry : attrmap) {
|
||||||
uint8_t attr = entry.xFlip << 5 | entry.yFlip << 6;
|
uint8_t attr = entry.xFlip << 5 | entry.yFlip << 6;
|
||||||
attr |= entry.bank << 3;
|
attr |= entry.bank << 3;
|
||||||
attr |= entry.getPalID(mappings) & 7;
|
attr |= entry.getPalID(mappings) & 7;
|
||||||
output.sputc(attr);
|
output->sputc(attr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void outputPalmap(DefaultInitVec<AttrmapEntry> const &attrmap,
|
static void outputPalmap(DefaultInitVec<AttrmapEntry> const &attrmap,
|
||||||
DefaultInitVec<size_t> const &mappings) {
|
DefaultInitVec<size_t> const &mappings) {
|
||||||
std::filebuf output;
|
File output;
|
||||||
output.open(options.attrmap, std::ios_base::out | std::ios_base::binary);
|
if (!output.open(options.attrmap, std::ios_base::out | std::ios_base::binary)) {
|
||||||
|
fatal("Failed to create \"%s\": %s", output.c_str(options.attrmap), strerror(errno));
|
||||||
|
}
|
||||||
|
|
||||||
for (AttrmapEntry const &entry : attrmap) {
|
for (AttrmapEntry const &entry : attrmap) {
|
||||||
output.sputc(entry.getPalID(mappings));
|
output->sputc(entry.getPalID(mappings));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -986,17 +1009,18 @@ void process() {
|
|||||||
protoPalettes[n] = tileColors; // Override them
|
protoPalettes[n] = tileColors; // Override them
|
||||||
// Remove any other proto-palettes that we encompass
|
// Remove any other proto-palettes that we encompass
|
||||||
// (Example [(0, 1), (0, 2)], inserting (0, 1, 2))
|
// (Example [(0, 1), (0, 2)], inserting (0, 1, 2))
|
||||||
/* The following code does its job, except that references to the removed
|
/*
|
||||||
|
* The following code does its job, except that references to the removed
|
||||||
* proto-palettes are not updated, causing issues.
|
* proto-palettes are not updated, causing issues.
|
||||||
* TODO: overlap might not be detrimental to the packing algorithm.
|
* TODO: overlap might not be detrimental to the packing algorithm.
|
||||||
* Investigation is necessary, especially if pathological cases are found.
|
* Investigation is necessary, especially if pathological cases are found.
|
||||||
|
*
|
||||||
for (size_t i = protoPalettes.size(); --i != n;) {
|
* for (size_t i = protoPalettes.size(); --i != n;) {
|
||||||
if (tileColors.compare(protoPalettes[i]) == ProtoPalette::WE_BIGGER) {
|
* if (tileColors.compare(protoPalettes[i]) == ProtoPalette::WE_BIGGER) {
|
||||||
protoPalettes.erase(protoPalettes.begin() + i);
|
* protoPalettes.erase(protoPalettes.begin() + i);
|
||||||
}
|
* }
|
||||||
}
|
* }
|
||||||
*/
|
*/
|
||||||
[[fallthrough]];
|
[[fallthrough]];
|
||||||
|
|
||||||
case ProtoPalette::THEY_BIGGER:
|
case ProtoPalette::THEY_BIGGER:
|
||||||
|
|||||||
@@ -21,14 +21,17 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "defaultinitalloc.hpp"
|
#include "defaultinitalloc.hpp"
|
||||||
|
#include "file.hpp"
|
||||||
#include "helpers.h"
|
#include "helpers.h"
|
||||||
#include "itertools.hpp"
|
#include "itertools.hpp"
|
||||||
|
|
||||||
#include "gfx/main.hpp"
|
#include "gfx/main.hpp"
|
||||||
|
|
||||||
static DefaultInitVec<uint8_t> readInto(std::string path) {
|
static DefaultInitVec<uint8_t> readInto(std::string path) {
|
||||||
std::filebuf file;
|
File file;
|
||||||
file.open(path, std::ios::in | std::ios::binary);
|
if (!file.open(path, std::ios::in | std::ios::binary)) {
|
||||||
|
fatal("Failed to open \"%s\": %s", file.c_str(path), strerror(errno));
|
||||||
|
}
|
||||||
DefaultInitVec<uint8_t> data(128 * 16); // Begin with some room pre-allocated
|
DefaultInitVec<uint8_t> data(128 * 16); // Begin with some room pre-allocated
|
||||||
|
|
||||||
size_t curSize = 0;
|
size_t curSize = 0;
|
||||||
@@ -38,7 +41,7 @@ static DefaultInitVec<uint8_t> readInto(std::string path) {
|
|||||||
|
|
||||||
// Fill the new area ([oldSize; curSize[) with bytes
|
// Fill the new area ([oldSize; curSize[) with bytes
|
||||||
size_t nbRead =
|
size_t nbRead =
|
||||||
file.sgetn(reinterpret_cast<char *>(&data.data()[oldSize]), curSize - oldSize);
|
file->sgetn(reinterpret_cast<char *>(&data.data()[oldSize]), curSize - oldSize);
|
||||||
if (nbRead != curSize - oldSize) {
|
if (nbRead != curSize - oldSize) {
|
||||||
// Shrink the vector to discard bytes that weren't read
|
// Shrink the vector to discard bytes that weren't read
|
||||||
data.resize(oldSize + nbRead);
|
data.resize(oldSize + nbRead);
|
||||||
@@ -66,13 +69,13 @@ static void pngWarning(png_structp png, char const *msg) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void writePng(png_structp png, png_bytep data, size_t length) {
|
void writePng(png_structp png, png_bytep data, size_t length) {
|
||||||
auto &pngFile = *static_cast<std::filebuf *>(png_get_io_ptr(png));
|
auto &pngFile = *static_cast<File *>(png_get_io_ptr(png));
|
||||||
pngFile.sputn(reinterpret_cast<char *>(data), length);
|
pngFile->sputn(reinterpret_cast<char *>(data), length);
|
||||||
}
|
}
|
||||||
|
|
||||||
void flushPng(png_structp png) {
|
void flushPng(png_structp png) {
|
||||||
auto &pngFile = *static_cast<std::filebuf *>(png_get_io_ptr(png));
|
auto &pngFile = *static_cast<File *>(png_get_io_ptr(png));
|
||||||
pngFile.pubsync();
|
pngFile->pubsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
void reverse() {
|
void reverse() {
|
||||||
@@ -117,8 +120,12 @@ void reverse() {
|
|||||||
if (!options.tilemap.empty()) {
|
if (!options.tilemap.empty()) {
|
||||||
tilemap = readInto(options.tilemap);
|
tilemap = readInto(options.tilemap);
|
||||||
nbTileInstances = tilemap->size();
|
nbTileInstances = tilemap->size();
|
||||||
|
options.verbosePrint(Options::VERB_INTERM, "Read %zu tilemap entries.\n", nbTileInstances);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (nbTileInstances == 0) {
|
||||||
|
fatal("Cannot generate empty image");
|
||||||
|
}
|
||||||
if (nbTileInstances > options.maxNbTiles[0] + options.maxNbTiles[1]) {
|
if (nbTileInstances > options.maxNbTiles[0] + options.maxNbTiles[1]) {
|
||||||
warning("Read %zu tiles, more than the limit of %zu + %zu", nbTileInstances,
|
warning("Read %zu tiles, more than the limit of %zu + %zu", nbTileInstances,
|
||||||
options.maxNbTiles[0], options.maxNbTiles[1]);
|
options.maxNbTiles[0], options.maxNbTiles[1]);
|
||||||
@@ -140,14 +147,16 @@ void reverse() {
|
|||||||
{Rgba(0xffffffff), Rgba(0xaaaaaaff), Rgba(0x555555ff), Rgba(0x000000ff)}
|
{Rgba(0xffffffff), Rgba(0xaaaaaaff), Rgba(0x555555ff), Rgba(0x000000ff)}
|
||||||
};
|
};
|
||||||
if (!options.palettes.empty()) {
|
if (!options.palettes.empty()) {
|
||||||
std::filebuf file;
|
File file;
|
||||||
file.open(options.palettes, std::ios::in | std::ios::binary);
|
if (!file.open(options.palettes, std::ios::in | std::ios::binary)) {
|
||||||
|
fatal("Failed to open \"%s\": %s", file.c_str(options.palettes), strerror(errno));
|
||||||
|
}
|
||||||
|
|
||||||
palettes.clear();
|
palettes.clear();
|
||||||
std::array<uint8_t, sizeof(uint16_t) * 4> buf; // 4 colors
|
std::array<uint8_t, sizeof(uint16_t) * 4> buf; // 4 colors
|
||||||
size_t nbRead;
|
size_t nbRead;
|
||||||
do {
|
do {
|
||||||
nbRead = file.sgetn(reinterpret_cast<char *>(buf.data()), buf.size());
|
nbRead = file->sgetn(reinterpret_cast<char *>(buf.data()), buf.size());
|
||||||
if (nbRead == buf.size()) {
|
if (nbRead == buf.size()) {
|
||||||
// Expand the colors
|
// Expand the colors
|
||||||
auto &palette = palettes.emplace_back();
|
auto &palette = palettes.emplace_back();
|
||||||
@@ -225,11 +234,13 @@ void reverse() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
options.verbosePrint(Options::VERB_LOG_ACT, "Writing image...\n");
|
options.verbosePrint(Options::VERB_LOG_ACT, "Writing image...\n");
|
||||||
std::filebuf pngFile;
|
File pngFile;
|
||||||
pngFile.open(options.input, std::ios::out | std::ios::binary);
|
if (!pngFile.open(options.input, std::ios::out | std::ios::binary)) {
|
||||||
|
fatal("Failed to create \"%s\": %s", pngFile.c_str(options.input), strerror(errno));
|
||||||
|
}
|
||||||
png_structp png = png_create_write_struct(
|
png_structp png = png_create_write_struct(
|
||||||
PNG_LIBPNG_VER_STRING,
|
PNG_LIBPNG_VER_STRING,
|
||||||
const_cast<png_voidp>(static_cast<void const *>(options.input.c_str())), pngError,
|
const_cast<png_voidp>(static_cast<void const *>(pngFile.c_str(options.input))), pngError,
|
||||||
pngWarning);
|
pngWarning);
|
||||||
if (!png) {
|
if (!png) {
|
||||||
fatal("Couldn't create PNG write struct: %s", strerror(errno));
|
fatal("Couldn't create PNG write struct: %s", strerror(errno));
|
||||||
@@ -268,7 +279,7 @@ void reverse() {
|
|||||||
uint8_t attribute = attrmap.has_value() ? (*attrmap)[index] : 0x00;
|
uint8_t attribute = attrmap.has_value() ? (*attrmap)[index] : 0x00;
|
||||||
bool bank = attribute & 0x08;
|
bool bank = attribute & 0x08;
|
||||||
// Get the tile ID at this location
|
// Get the tile ID at this location
|
||||||
uint8_t tileID = index;
|
size_t tileID = index;
|
||||||
if (tilemap.has_value()) {
|
if (tilemap.has_value()) {
|
||||||
tileID =
|
tileID =
|
||||||
(*tilemap)[index] - options.baseTileIDs[bank] + bank * options.maxNbTiles[0];
|
(*tilemap)[index] - options.baseTileIDs[bank] + bank * options.maxNbTiles[0];
|
||||||
|
|||||||
@@ -1,3 +1,11 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of RGBDS.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2022, Eldred Habert and RGBDS contributors.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
#include "gfx/rgba.hpp"
|
#include "gfx/rgba.hpp"
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|||||||
@@ -15,10 +15,8 @@
|
|||||||
#include "error.h"
|
#include "error.h"
|
||||||
#include "hashmap.h"
|
#include "hashmap.h"
|
||||||
|
|
||||||
/*
|
// The lower half of the hash is used to index the "master" table,
|
||||||
* The lower half of the hash is used to index the "master" table,
|
// the upper half is used to help resolve collisions more quickly
|
||||||
* the upper half is used to help resolve collisions more quickly
|
|
||||||
*/
|
|
||||||
#define UINT_BITS_(NB_BITS) uint##NB_BITS##_t
|
#define UINT_BITS_(NB_BITS) uint##NB_BITS##_t
|
||||||
#define UINT_BITS(NB_BITS) UINT_BITS_(NB_BITS)
|
#define UINT_BITS(NB_BITS) UINT_BITS_(NB_BITS)
|
||||||
typedef UINT_BITS(HASH_NB_BITS) HashType;
|
typedef UINT_BITS(HASH_NB_BITS) HashType;
|
||||||
@@ -34,7 +32,7 @@ struct HashMapEntry {
|
|||||||
#define FNV_OFFSET_BASIS 0x811c9dc5
|
#define FNV_OFFSET_BASIS 0x811c9dc5
|
||||||
#define FNV_PRIME 16777619
|
#define FNV_PRIME 16777619
|
||||||
|
|
||||||
/* FNV-1a hash */
|
// FNV-1a hash
|
||||||
static HashType hash(char const *str)
|
static HashType hash(char const *str)
|
||||||
{
|
{
|
||||||
HashType hash = FNV_OFFSET_BASIS;
|
HashType hash = FNV_OFFSET_BASIS;
|
||||||
|
|||||||
@@ -17,11 +17,11 @@
|
|||||||
#include "link/symbol.h"
|
#include "link/symbol.h"
|
||||||
#include "link/object.h"
|
#include "link/object.h"
|
||||||
#include "link/main.h"
|
#include "link/main.h"
|
||||||
#include "link/script.h"
|
|
||||||
#include "link/output.h"
|
#include "link/output.h"
|
||||||
|
|
||||||
#include "error.h"
|
#include "error.h"
|
||||||
#include "helpers.h"
|
#include "helpers.h"
|
||||||
|
#include "linkdefs.h"
|
||||||
|
|
||||||
struct MemoryLocation {
|
struct MemoryLocation {
|
||||||
uint16_t address;
|
uint16_t address;
|
||||||
@@ -34,14 +34,12 @@ struct FreeSpace {
|
|||||||
struct FreeSpace *next, *prev;
|
struct FreeSpace *next, *prev;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Table of free space for each bank */
|
// Table of free space for each bank
|
||||||
struct FreeSpace *memory[SECTTYPE_INVALID];
|
struct FreeSpace *memory[SECTTYPE_INVALID];
|
||||||
|
|
||||||
uint64_t nbSectionsToAssign;
|
uint64_t nbSectionsToAssign;
|
||||||
|
|
||||||
/**
|
// Init the free space-modelling structs
|
||||||
* Init the free space-modelling structs
|
|
||||||
*/
|
|
||||||
static void initFreeSpace(void)
|
static void initFreeSpace(void)
|
||||||
{
|
{
|
||||||
for (enum SectionType type = 0; type < SECTTYPE_INVALID; type++) {
|
for (enum SectionType type = 0; type < SECTTYPE_INVALID; type++) {
|
||||||
@@ -55,54 +53,15 @@ static void initFreeSpace(void)
|
|||||||
if (!memory[type][bank].next)
|
if (!memory[type][bank].next)
|
||||||
err("Failed to init free space for region %d bank %" PRIu32,
|
err("Failed to init free space for region %d bank %" PRIu32,
|
||||||
type, bank);
|
type, bank);
|
||||||
memory[type][bank].next->address = startaddr[type];
|
memory[type][bank].next->address = sectionTypeInfo[type].startAddr;
|
||||||
memory[type][bank].next->size = maxsize[type];
|
memory[type][bank].next->size = sectionTypeInfo[type].size;
|
||||||
memory[type][bank].next->next = NULL;
|
memory[type][bank].next->next = NULL;
|
||||||
memory[type][bank].next->prev = &memory[type][bank];
|
memory[type][bank].next->prev = &memory[type][bank];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Alter sections' attributes based on the linker script
|
|
||||||
*/
|
|
||||||
static void processLinkerScript(void)
|
|
||||||
{
|
|
||||||
if (!linkerScriptName)
|
|
||||||
return;
|
|
||||||
verbosePrint("Reading linker script...\n");
|
|
||||||
|
|
||||||
linkerScript = openFile(linkerScriptName, "r");
|
|
||||||
|
|
||||||
/* Modify all sections according to the linker script */
|
|
||||||
struct SectionPlacement *placement;
|
|
||||||
|
|
||||||
while ((placement = script_NextSection())) {
|
|
||||||
struct Section *section = placement->section;
|
|
||||||
|
|
||||||
/* Check if this doesn't conflict with what the code says */
|
|
||||||
if (section->isBankFixed && placement->bank != section->bank)
|
|
||||||
error(NULL, 0, "Linker script contradicts \"%s\"'s bank placement",
|
|
||||||
section->name);
|
|
||||||
if (section->isAddressFixed && placement->org != section->org)
|
|
||||||
error(NULL, 0, "Linker script contradicts \"%s\"'s address placement",
|
|
||||||
section->name);
|
|
||||||
if (section->isAlignFixed
|
|
||||||
&& (placement->org & section->alignMask) != 0)
|
|
||||||
error(NULL, 0, "Linker script contradicts \"%s\"'s alignment",
|
|
||||||
section->name);
|
|
||||||
|
|
||||||
section->isAddressFixed = true;
|
|
||||||
section->org = placement->org;
|
|
||||||
section->isBankFixed = true;
|
|
||||||
section->bank = placement->bank;
|
|
||||||
section->isAlignFixed = false; /* The alignment is satisfied */
|
|
||||||
}
|
|
||||||
|
|
||||||
fclose(linkerScript);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Assigns a section to a given memory location
|
* Assigns a section to a given memory location
|
||||||
* @param section The section to assign
|
* @param section The section to assign
|
||||||
* @param location The location to assign the section to
|
* @param location The location to assign the section to
|
||||||
@@ -124,7 +83,7 @@ static void assignSection(struct Section *section, struct MemoryLocation const *
|
|||||||
out_AddSection(section);
|
out_AddSection(section);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Checks whether a given location is suitable for placing a given section
|
* Checks whether a given location is suitable for placing a given section
|
||||||
* This checks not only that the location has enough room for the section, but
|
* This checks not only that the location has enough room for the section, but
|
||||||
* also that the constraints (alignment...) are respected.
|
* also that the constraints (alignment...) are respected.
|
||||||
@@ -150,7 +109,7 @@ static bool isLocationSuitable(struct Section const *section,
|
|||||||
<= freeSpace->address + freeSpace->size;
|
<= freeSpace->address + freeSpace->size;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Finds a suitable location to place a section at.
|
* Finds a suitable location to place a section at.
|
||||||
* @param section The section to be placed
|
* @param section The section to be placed
|
||||||
* @param location A pointer to a location struct that will be filled
|
* @param location A pointer to a location struct that will be filled
|
||||||
@@ -180,74 +139,70 @@ static struct FreeSpace *getPlacement(struct Section const *section,
|
|||||||
if (curScrambleSRAM > scrambleSRAM)
|
if (curScrambleSRAM > scrambleSRAM)
|
||||||
curScrambleSRAM = 0;
|
curScrambleSRAM = 0;
|
||||||
} else {
|
} else {
|
||||||
location->bank = bankranges[section->type][0];
|
location->bank = sectionTypeInfo[section->type].firstBank;
|
||||||
}
|
}
|
||||||
struct FreeSpace *space;
|
struct FreeSpace *space;
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
/* Switch to the beginning of the next bank */
|
// Switch to the beginning of the next bank
|
||||||
#define BANK_INDEX (location->bank - bankranges[section->type][0])
|
#define BANK_INDEX (location->bank - sectionTypeInfo[section->type].firstBank)
|
||||||
space = memory[section->type][BANK_INDEX].next;
|
space = memory[section->type][BANK_INDEX].next;
|
||||||
if (space)
|
if (space)
|
||||||
location->address = space->address;
|
location->address = space->address;
|
||||||
|
|
||||||
/* Process locations in that bank */
|
// Process locations in that bank
|
||||||
while (space) {
|
while (space) {
|
||||||
/* If that location is OK, return it */
|
// If that location is OK, return it
|
||||||
if (isLocationSuitable(section, space, location))
|
if (isLocationSuitable(section, space, location))
|
||||||
return space;
|
return space;
|
||||||
|
|
||||||
/* Go to the next *possible* location */
|
// Go to the next *possible* location
|
||||||
if (section->isAddressFixed) {
|
if (section->isAddressFixed) {
|
||||||
/*
|
// If the address is fixed, there can be only
|
||||||
* If the address is fixed, there can be only
|
// one candidate block per bank; if we already
|
||||||
* one candidate block per bank; if we already
|
// reached it, give up.
|
||||||
* reached it, give up.
|
|
||||||
*/
|
|
||||||
if (location->address < section->org)
|
if (location->address < section->org)
|
||||||
location->address = section->org;
|
location->address = section->org;
|
||||||
else
|
else
|
||||||
/* Try again in next bank */
|
// Try again in next bank
|
||||||
space = NULL;
|
space = NULL;
|
||||||
} else if (section->isAlignFixed) {
|
} else if (section->isAlignFixed) {
|
||||||
/* Move to next aligned location */
|
// Move to next aligned location
|
||||||
/* Move back to alignment boundary */
|
// Move back to alignment boundary
|
||||||
location->address -= section->alignOfs;
|
location->address -= section->alignOfs;
|
||||||
/* Ensure we're there (e.g. on first check) */
|
// Ensure we're there (e.g. on first check)
|
||||||
location->address &= ~section->alignMask;
|
location->address &= ~section->alignMask;
|
||||||
/* Go to next align boundary and add offset */
|
// Go to next align boundary and add offset
|
||||||
location->address += section->alignMask + 1
|
location->address += section->alignMask + 1
|
||||||
+ section->alignOfs;
|
+ section->alignOfs;
|
||||||
} else {
|
} else {
|
||||||
/* Any location is fine, so, next free block */
|
// Any location is fine, so, next free block
|
||||||
space = space->next;
|
space = space->next;
|
||||||
if (space)
|
if (space)
|
||||||
location->address = space->address;
|
location->address = space->address;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// If that location is past the current block's end,
|
||||||
* If that location is past the current block's end,
|
// go forwards until that is no longer the case.
|
||||||
* go forwards until that is no longer the case.
|
|
||||||
*/
|
|
||||||
while (space && location->address >=
|
while (space && location->address >=
|
||||||
space->address + space->size)
|
space->address + space->size)
|
||||||
space = space->next;
|
space = space->next;
|
||||||
|
|
||||||
/* Try again with the new location/free space combo */
|
// Try again with the new location/free space combo
|
||||||
}
|
}
|
||||||
|
|
||||||
if (section->isBankFixed)
|
if (section->isBankFixed)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
/* Try again in the next bank */
|
// Try again in the next bank
|
||||||
location->bank++;
|
location->bank++;
|
||||||
if (location->bank > bankranges[section->type][1])
|
if (location->bank > sectionTypeInfo[section->type].lastBank)
|
||||||
return NULL;
|
return NULL;
|
||||||
#undef BANK_INDEX
|
#undef BANK_INDEX
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Places a section in a suitable location, or error out if it fails to.
|
* Places a section in a suitable location, or error out if it fails to.
|
||||||
* @warning Due to the implemented algorithm, this should be called with
|
* @warning Due to the implemented algorithm, this should be called with
|
||||||
* sections of decreasing size.
|
* sections of decreasing size.
|
||||||
@@ -257,76 +212,70 @@ static void placeSection(struct Section *section)
|
|||||||
{
|
{
|
||||||
struct MemoryLocation location;
|
struct MemoryLocation location;
|
||||||
|
|
||||||
/* Specially handle 0-byte SECTIONs, as they can't overlap anything */
|
// Specially handle 0-byte SECTIONs, as they can't overlap anything
|
||||||
if (section->size == 0) {
|
if (section->size == 0) {
|
||||||
/*
|
// Unless the SECTION's address was fixed, the starting address
|
||||||
* Unless the SECTION's address was fixed, the starting address
|
// is fine for any alignment, as checked in sect_DoSanityChecks.
|
||||||
* is fine for any alignment, as checked in sect_DoSanityChecks.
|
|
||||||
*/
|
|
||||||
location.address = section->isAddressFixed
|
location.address = section->isAddressFixed
|
||||||
? section->org
|
? section->org
|
||||||
: startaddr[section->type];
|
: sectionTypeInfo[section->type].startAddr;
|
||||||
location.bank = section->isBankFixed
|
location.bank = section->isBankFixed
|
||||||
? section->bank
|
? section->bank
|
||||||
: bankranges[section->type][0];
|
: sectionTypeInfo[section->type].firstBank;
|
||||||
assignSection(section, &location);
|
assignSection(section, &location);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Place section using first-fit decreasing algorithm
|
||||||
* Place section using first-fit decreasing algorithm
|
// https://en.wikipedia.org/wiki/Bin_packing_problem#First-fit_algorithm
|
||||||
* https://en.wikipedia.org/wiki/Bin_packing_problem#First-fit_algorithm
|
|
||||||
*/
|
|
||||||
struct FreeSpace *freeSpace = getPlacement(section, &location);
|
struct FreeSpace *freeSpace = getPlacement(section, &location);
|
||||||
|
|
||||||
if (freeSpace) {
|
if (freeSpace) {
|
||||||
assignSection(section, &location);
|
assignSection(section, &location);
|
||||||
|
|
||||||
/* Split the free space */
|
// Split the free space
|
||||||
bool noLeftSpace = freeSpace->address == section->org;
|
bool noLeftSpace = freeSpace->address == section->org;
|
||||||
bool noRightSpace = freeSpace->address + freeSpace->size
|
bool noRightSpace = freeSpace->address + freeSpace->size
|
||||||
== section->org + section->size;
|
== section->org + section->size;
|
||||||
if (noLeftSpace && noRightSpace) {
|
if (noLeftSpace && noRightSpace) {
|
||||||
/* The free space is entirely deleted */
|
// The free space is entirely deleted
|
||||||
freeSpace->prev->next = freeSpace->next;
|
freeSpace->prev->next = freeSpace->next;
|
||||||
if (freeSpace->next)
|
if (freeSpace->next)
|
||||||
freeSpace->next->prev = freeSpace->prev;
|
freeSpace->next->prev = freeSpace->prev;
|
||||||
/*
|
// If the space is the last one on the list, set its
|
||||||
* If the space is the last one on the list, set its
|
// size to 0 so it doesn't get picked, but don't free()
|
||||||
* size to 0 so it doesn't get picked, but don't free()
|
// it as it will be freed when cleaning up
|
||||||
* it as it will be freed when cleaning up
|
|
||||||
*/
|
|
||||||
free(freeSpace);
|
free(freeSpace);
|
||||||
} else if (!noLeftSpace && !noRightSpace) {
|
} else if (!noLeftSpace && !noRightSpace) {
|
||||||
/* The free space is split in two */
|
// The free space is split in two
|
||||||
struct FreeSpace *newSpace = malloc(sizeof(*newSpace));
|
struct FreeSpace *newSpace = malloc(sizeof(*newSpace));
|
||||||
|
|
||||||
if (!newSpace)
|
if (!newSpace)
|
||||||
err("Failed to split new free space");
|
err("Failed to split new free space");
|
||||||
/* Append the new space after the chosen one */
|
// Append the new space after the chosen one
|
||||||
newSpace->prev = freeSpace;
|
newSpace->prev = freeSpace;
|
||||||
newSpace->next = freeSpace->next;
|
newSpace->next = freeSpace->next;
|
||||||
if (freeSpace->next)
|
if (freeSpace->next)
|
||||||
freeSpace->next->prev = newSpace;
|
freeSpace->next->prev = newSpace;
|
||||||
freeSpace->next = newSpace;
|
freeSpace->next = newSpace;
|
||||||
/* Set its parameters */
|
// Set its parameters
|
||||||
newSpace->address = section->org + section->size;
|
newSpace->address = section->org + section->size;
|
||||||
newSpace->size = freeSpace->address + freeSpace->size -
|
newSpace->size = freeSpace->address + freeSpace->size -
|
||||||
newSpace->address;
|
newSpace->address;
|
||||||
/* Set the original space's new parameters */
|
// Set the original space's new parameters
|
||||||
freeSpace->size = section->org - freeSpace->address;
|
freeSpace->size = section->org - freeSpace->address;
|
||||||
/* address is unmodified */
|
// address is unmodified
|
||||||
} else {
|
} else {
|
||||||
/* The amount of free spaces doesn't change: resize! */
|
// The amount of free spaces doesn't change: resize!
|
||||||
freeSpace->size -= section->size;
|
freeSpace->size -= section->size;
|
||||||
if (noLeftSpace)
|
if (noLeftSpace)
|
||||||
/* The free space is moved *and* resized */
|
// The free space is moved *and* resized
|
||||||
freeSpace->address += section->size;
|
freeSpace->address += section->size;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Please adjust depending on longest message below */
|
// Please adjust depending on longest message below
|
||||||
char where[64];
|
char where[64];
|
||||||
|
|
||||||
if (section->isBankFixed && nbbanks(section->type) != 1) {
|
if (section->isBankFixed && nbbanks(section->type) != 1) {
|
||||||
@@ -351,19 +300,19 @@ static void placeSection(struct Section *section)
|
|||||||
strcpy(where, "anywhere");
|
strcpy(where, "anywhere");
|
||||||
}
|
}
|
||||||
|
|
||||||
/* If a section failed to go to several places, nothing we can report */
|
// If a section failed to go to several places, nothing we can report
|
||||||
if (!section->isBankFixed || !section->isAddressFixed)
|
if (!section->isBankFixed || !section->isAddressFixed)
|
||||||
errx("Unable to place \"%s\" (%s section) %s",
|
errx("Unable to place \"%s\" (%s section) %s",
|
||||||
section->name, typeNames[section->type], where);
|
section->name, sectionTypeInfo[section->type].name, where);
|
||||||
/* If the section just can't fit the bank, report that */
|
// If the section just can't fit the bank, report that
|
||||||
else if (section->org + section->size > endaddr(section->type) + 1)
|
else if (section->org + section->size > endaddr(section->type) + 1)
|
||||||
errx("Unable to place \"%s\" (%s section) %s: section runs past end of region ($%04x > $%04x)",
|
errx("Unable to place \"%s\" (%s section) %s: section runs past end of region ($%04x > $%04x)",
|
||||||
section->name, typeNames[section->type], where,
|
section->name, sectionTypeInfo[section->type].name, where,
|
||||||
section->org + section->size, endaddr(section->type) + 1);
|
section->org + section->size, endaddr(section->type) + 1);
|
||||||
/* Otherwise there is overlap with another section */
|
// Otherwise there is overlap with another section
|
||||||
else
|
else
|
||||||
errx("Unable to place \"%s\" (%s section) %s: section overlaps with \"%s\"",
|
errx("Unable to place \"%s\" (%s section) %s: section overlaps with \"%s\"",
|
||||||
section->name, typeNames[section->type], where,
|
section->name, sectionTypeInfo[section->type].name, where,
|
||||||
out_OverlappingSection(section)->name);
|
out_OverlappingSection(section)->name);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -378,7 +327,7 @@ struct UnassignedSection {
|
|||||||
static struct UnassignedSection *unassignedSections[1 << 3] = {0};
|
static struct UnassignedSection *unassignedSections[1 << 3] = {0};
|
||||||
static struct UnassignedSection *sections;
|
static struct UnassignedSection *sections;
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Categorize a section depending on how constrained it is
|
* Categorize a section depending on how constrained it is
|
||||||
* This is so the most-constrained sections are placed first
|
* This is so the most-constrained sections are placed first
|
||||||
* @param section The section to categorize
|
* @param section The section to categorize
|
||||||
@@ -393,13 +342,13 @@ static void categorizeSection(struct Section *section, void *arg)
|
|||||||
constraints |= BANK_CONSTRAINED;
|
constraints |= BANK_CONSTRAINED;
|
||||||
if (section->isAddressFixed)
|
if (section->isAddressFixed)
|
||||||
constraints |= ORG_CONSTRAINED;
|
constraints |= ORG_CONSTRAINED;
|
||||||
/* Can't have both! */
|
// Can't have both!
|
||||||
else if (section->isAlignFixed)
|
else if (section->isAlignFixed)
|
||||||
constraints |= ALIGN_CONSTRAINED;
|
constraints |= ALIGN_CONSTRAINED;
|
||||||
|
|
||||||
struct UnassignedSection **ptr = &unassignedSections[constraints];
|
struct UnassignedSection **ptr = &unassignedSections[constraints];
|
||||||
|
|
||||||
/* Insert section while keeping the list sorted by decreasing size */
|
// Insert section while keeping the list sorted by decreasing size
|
||||||
while (*ptr && (*ptr)->section->size > section->size)
|
while (*ptr && (*ptr)->section->size > section->size)
|
||||||
ptr = &(*ptr)->next;
|
ptr = &(*ptr)->next;
|
||||||
|
|
||||||
@@ -414,24 +363,21 @@ void assign_AssignSections(void)
|
|||||||
{
|
{
|
||||||
verbosePrint("Beginning assignment...\n");
|
verbosePrint("Beginning assignment...\n");
|
||||||
|
|
||||||
/** Initialize assignment **/
|
// Initialize assignment
|
||||||
|
|
||||||
/* Generate linked lists of sections to assign */
|
// Generate linked lists of sections to assign
|
||||||
sections = malloc(sizeof(*sections) * nbSectionsToAssign + 1);
|
sections = malloc(sizeof(*sections) * nbSectionsToAssign + 1);
|
||||||
if (!sections)
|
if (!sections)
|
||||||
err("Failed to allocate memory for section assignment");
|
err("Failed to allocate memory for section assignment");
|
||||||
|
|
||||||
initFreeSpace();
|
initFreeSpace();
|
||||||
|
|
||||||
/* Process linker script, if any */
|
|
||||||
processLinkerScript();
|
|
||||||
|
|
||||||
nbSectionsToAssign = 0;
|
nbSectionsToAssign = 0;
|
||||||
sect_ForEach(categorizeSection, NULL);
|
sect_ForEach(categorizeSection, NULL);
|
||||||
|
|
||||||
/** Place sections, starting with the most constrained **/
|
// Place sections, starting with the most constrained
|
||||||
|
|
||||||
/* Specially process fully-constrained sections because of overlaying */
|
// Specially process fully-constrained sections because of overlaying
|
||||||
struct UnassignedSection *sectionPtr =
|
struct UnassignedSection *sectionPtr =
|
||||||
unassignedSections[BANK_CONSTRAINED | ORG_CONSTRAINED];
|
unassignedSections[BANK_CONSTRAINED | ORG_CONSTRAINED];
|
||||||
|
|
||||||
@@ -441,11 +387,11 @@ void assign_AssignSections(void)
|
|||||||
sectionPtr = sectionPtr->next;
|
sectionPtr = sectionPtr->next;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* If all sections were fully constrained, we have nothing left to do */
|
// If all sections were fully constrained, we have nothing left to do
|
||||||
if (!nbSectionsToAssign)
|
if (!nbSectionsToAssign)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
/* Overlaying requires only fully-constrained sections */
|
// Overlaying requires only fully-constrained sections
|
||||||
verbosePrint("Assigning other sections...\n");
|
verbosePrint("Assigning other sections...\n");
|
||||||
if (overlayFileName) {
|
if (overlayFileName) {
|
||||||
fprintf(stderr, "FATAL: All sections must be fixed when using an overlay file");
|
fprintf(stderr, "FATAL: All sections must be fixed when using an overlay file");
|
||||||
@@ -469,7 +415,7 @@ max_out:
|
|||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Assign all remaining sections by decreasing constraint order */
|
// Assign all remaining sections by decreasing constraint order
|
||||||
for (int8_t constraints = BANK_CONSTRAINED | ALIGN_CONSTRAINED;
|
for (int8_t constraints = BANK_CONSTRAINED | ALIGN_CONSTRAINED;
|
||||||
constraints >= 0; constraints--) {
|
constraints >= 0; constraints--) {
|
||||||
sectionPtr = unassignedSections[constraints];
|
sectionPtr = unassignedSections[constraints];
|
||||||
@@ -505,6 +451,4 @@ void assign_Cleanup(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
free(sections);
|
free(sections);
|
||||||
|
|
||||||
script_Cleanup();
|
|
||||||
}
|
}
|
||||||
|
|||||||
168
src/link/main.c
168
src/link/main.c
@@ -18,46 +18,48 @@
|
|||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
|
|
||||||
#include "link/object.h"
|
|
||||||
#include "link/symbol.h"
|
|
||||||
#include "link/section.h"
|
|
||||||
#include "link/assign.h"
|
#include "link/assign.h"
|
||||||
#include "link/patch.h"
|
#include "link/object.h"
|
||||||
#include "link/output.h"
|
#include "link/output.h"
|
||||||
|
#include "link/patch.h"
|
||||||
|
#include "link/section.h"
|
||||||
|
#include "link/script.h"
|
||||||
|
#include "link/symbol.h"
|
||||||
|
|
||||||
#include "extern/getopt.h"
|
#include "extern/getopt.h"
|
||||||
|
|
||||||
#include "error.h"
|
#include "error.h"
|
||||||
|
#include "linkdefs.h"
|
||||||
#include "platform.h"
|
#include "platform.h"
|
||||||
#include "version.h"
|
#include "version.h"
|
||||||
|
|
||||||
bool isDmgMode; /* -d */
|
bool isDmgMode; // -d
|
||||||
char *linkerScriptName; /* -l */
|
char *linkerScriptName; // -l
|
||||||
char const *mapFileName; /* -m */
|
char const *mapFileName; // -m
|
||||||
char const *symFileName; /* -n */
|
bool noSymInMap; // -M
|
||||||
char const *overlayFileName; /* -O */
|
char const *symFileName; // -n
|
||||||
char const *outputFileName; /* -o */
|
char const *overlayFileName; // -O
|
||||||
uint8_t padValue; /* -p */
|
char const *outputFileName; // -o
|
||||||
|
uint8_t padValue; // -p
|
||||||
// Setting these three to 0 disables the functionality
|
// Setting these three to 0 disables the functionality
|
||||||
uint16_t scrambleROMX = 0; /* -S */
|
uint16_t scrambleROMX = 0; // -S
|
||||||
uint8_t scrambleWRAMX = 0;
|
uint8_t scrambleWRAMX = 0;
|
||||||
uint8_t scrambleSRAM = 0;
|
uint8_t scrambleSRAM = 0;
|
||||||
bool is32kMode; /* -t */
|
bool is32kMode; // -t
|
||||||
bool beVerbose; /* -v */
|
bool beVerbose; // -v
|
||||||
bool isWRA0Mode; /* -w */
|
bool isWRA0Mode; // -w
|
||||||
bool disablePadding; /* -x */
|
bool disablePadding; // -x
|
||||||
|
|
||||||
static uint32_t nbErrors = 0;
|
static uint32_t nbErrors = 0;
|
||||||
|
|
||||||
/***** Helper function to dump a file stack to stderr *****/
|
// Helper function to dump a file stack to stderr
|
||||||
|
|
||||||
char const *dumpFileStack(struct FileStackNode const *node)
|
char const *dumpFileStack(struct FileStackNode const *node)
|
||||||
{
|
{
|
||||||
char const *lastName;
|
char const *lastName;
|
||||||
|
|
||||||
if (node->parent) {
|
if (node->parent) {
|
||||||
lastName = dumpFileStack(node->parent);
|
lastName = dumpFileStack(node->parent);
|
||||||
/* REPT nodes use their parent's name */
|
// REPT nodes use their parent's name
|
||||||
if (node->type != NODE_REPT)
|
if (node->type != NODE_REPT)
|
||||||
lastName = node->name;
|
lastName = node->name;
|
||||||
fprintf(stderr, "(%" PRIu32 ") -> %s", node->lineNo, lastName);
|
fprintf(stderr, "(%" PRIu32 ") -> %s", node->lineNo, lastName);
|
||||||
@@ -152,9 +154,9 @@ FILE *openFile(char const *fileName, char const *mode)
|
|||||||
if (strcmp(fileName, "-") != 0)
|
if (strcmp(fileName, "-") != 0)
|
||||||
file = fopen(fileName, mode);
|
file = fopen(fileName, mode);
|
||||||
else if (mode[0] == 'r')
|
else if (mode[0] == 'r')
|
||||||
file = fdopen(0, mode);
|
file = fdopen(STDIN_FILENO, mode);
|
||||||
else
|
else
|
||||||
file = fdopen(1, mode);
|
file = fdopen(STDOUT_FILENO, mode);
|
||||||
|
|
||||||
if (!file)
|
if (!file)
|
||||||
err("Could not open file \"%s\"", fileName);
|
err("Could not open file \"%s\"", fileName);
|
||||||
@@ -162,8 +164,8 @@ FILE *openFile(char const *fileName, char const *mode)
|
|||||||
return file;
|
return file;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Short options */
|
// Short options
|
||||||
static const char *optstring = "dl:m:n:O:o:p:S:s:tVvWwx";
|
static const char *optstring = "dl:m:Mn:O:o:p:S:s:tVvWwx";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Equivalent long options
|
* Equivalent long options
|
||||||
@@ -179,6 +181,7 @@ static struct option const longopts[] = {
|
|||||||
{ "dmg", no_argument, NULL, 'd' },
|
{ "dmg", no_argument, NULL, 'd' },
|
||||||
{ "linkerscript", required_argument, NULL, 'l' },
|
{ "linkerscript", required_argument, NULL, 'l' },
|
||||||
{ "map", required_argument, NULL, 'm' },
|
{ "map", required_argument, NULL, 'm' },
|
||||||
|
{ "no-sym-in-map", no_argument, NULL, 'M' },
|
||||||
{ "sym", required_argument, NULL, 'n' },
|
{ "sym", required_argument, NULL, 'n' },
|
||||||
{ "overlay", required_argument, NULL, 'O' },
|
{ "overlay", required_argument, NULL, 'O' },
|
||||||
{ "output", required_argument, NULL, 'o' },
|
{ "output", required_argument, NULL, 'o' },
|
||||||
@@ -193,13 +196,11 @@ static struct option const longopts[] = {
|
|||||||
{ NULL, no_argument, NULL, 0 }
|
{ NULL, no_argument, NULL, 0 }
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
// Prints the program's usage to stdout.
|
||||||
* Prints the program's usage to stdout.
|
|
||||||
*/
|
|
||||||
static void printUsage(void)
|
static void printUsage(void)
|
||||||
{
|
{
|
||||||
fputs(
|
fputs(
|
||||||
"Usage: rgblink [-dtVvwx] [-l script] [-m map_file] [-n sym_file]\n"
|
"Usage: rgblink [-dMtVvwx] [-l script] [-m map_file] [-n sym_file]\n"
|
||||||
" [-O overlay_file] [-o out_file] [-p pad_value]\n"
|
" [-O overlay_file] [-o out_file] [-p pad_value]\n"
|
||||||
" [-S spec] [-s symbol] <file> ...\n"
|
" [-S spec] [-s symbol] <file> ...\n"
|
||||||
"Useful options:\n"
|
"Useful options:\n"
|
||||||
@@ -215,10 +216,8 @@ static void printUsage(void)
|
|||||||
stderr);
|
stderr);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Cleans up what has been done
|
||||||
* Cleans up what has been done
|
// Mostly here to please tools such as `valgrind` so actual errors can be seen
|
||||||
* Mostly here to please tools such as `valgrind` so actual errors can be seen
|
|
||||||
*/
|
|
||||||
static void cleanup(void)
|
static void cleanup(void)
|
||||||
{
|
{
|
||||||
obj_Cleanup();
|
obj_Cleanup();
|
||||||
@@ -349,13 +348,19 @@ next:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_Noreturn void reportErrors(void) {
|
||||||
|
fprintf(stderr, "Linking failed with %" PRIu32 " error%s\n",
|
||||||
|
nbErrors, nbErrors == 1 ? "" : "s");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
int main(int argc, char *argv[])
|
int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
int optionChar;
|
int optionChar;
|
||||||
char *endptr; /* For error checking with `strtoul` */
|
char *endptr; // For error checking with `strtoul`
|
||||||
unsigned long value; /* For storing `strtoul`'s return value */
|
unsigned long value; // For storing `strtoul`'s return value
|
||||||
|
|
||||||
/* Parse options */
|
// Parse options
|
||||||
while ((optionChar = musl_getopt_long_only(argc, argv, optstring,
|
while ((optionChar = musl_getopt_long_only(argc, argv, optstring,
|
||||||
longopts, NULL)) != -1) {
|
longopts, NULL)) != -1) {
|
||||||
switch (optionChar) {
|
switch (optionChar) {
|
||||||
@@ -364,18 +369,31 @@ int main(int argc, char *argv[])
|
|||||||
isWRA0Mode = true;
|
isWRA0Mode = true;
|
||||||
break;
|
break;
|
||||||
case 'l':
|
case 'l':
|
||||||
|
if (linkerScriptName)
|
||||||
|
warnx("Overriding linkerscript %s", musl_optarg);
|
||||||
linkerScriptName = musl_optarg;
|
linkerScriptName = musl_optarg;
|
||||||
break;
|
break;
|
||||||
|
case 'M':
|
||||||
|
noSymInMap = true;
|
||||||
|
break;
|
||||||
case 'm':
|
case 'm':
|
||||||
|
if (mapFileName)
|
||||||
|
warnx("Overriding mapfile %s", musl_optarg);
|
||||||
mapFileName = musl_optarg;
|
mapFileName = musl_optarg;
|
||||||
break;
|
break;
|
||||||
case 'n':
|
case 'n':
|
||||||
|
if (symFileName)
|
||||||
|
warnx("Overriding symfile %s", musl_optarg);
|
||||||
symFileName = musl_optarg;
|
symFileName = musl_optarg;
|
||||||
break;
|
break;
|
||||||
case 'O':
|
case 'O':
|
||||||
|
if (overlayFileName)
|
||||||
|
warnx("Overriding overlay file %s", musl_optarg);
|
||||||
overlayFileName = musl_optarg;
|
overlayFileName = musl_optarg;
|
||||||
break;
|
break;
|
||||||
case 'o':
|
case 'o':
|
||||||
|
if (outputFileName)
|
||||||
|
warnx("Overriding output file %s", musl_optarg);
|
||||||
outputFileName = musl_optarg;
|
outputFileName = musl_optarg;
|
||||||
break;
|
break;
|
||||||
case 'p':
|
case 'p':
|
||||||
@@ -394,7 +412,7 @@ int main(int argc, char *argv[])
|
|||||||
parseScrambleSpec(musl_optarg);
|
parseScrambleSpec(musl_optarg);
|
||||||
break;
|
break;
|
||||||
case 's':
|
case 's':
|
||||||
/* FIXME: nobody knows what this does, figure it out */
|
// FIXME: nobody knows what this does, figure it out
|
||||||
(void)musl_optarg;
|
(void)musl_optarg;
|
||||||
warning(NULL, 0, "Nobody has any idea what `-s` does");
|
warning(NULL, 0, "Nobody has any idea what `-s` does");
|
||||||
break;
|
break;
|
||||||
@@ -412,7 +430,7 @@ int main(int argc, char *argv[])
|
|||||||
break;
|
break;
|
||||||
case 'x':
|
case 'x':
|
||||||
disablePadding = true;
|
disablePadding = true;
|
||||||
/* implies tiny mode */
|
// implies tiny mode
|
||||||
is32kMode = true;
|
is32kMode = true;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -423,42 +441,90 @@ int main(int argc, char *argv[])
|
|||||||
|
|
||||||
int curArgIndex = musl_optind;
|
int curArgIndex = musl_optind;
|
||||||
|
|
||||||
/* If no input files were specified, the user must have screwed up */
|
// If no input files were specified, the user must have screwed up
|
||||||
if (curArgIndex == argc) {
|
if (curArgIndex == argc) {
|
||||||
fputs("FATAL: no input files\n", stderr);
|
fputs("FATAL: no input files\n", stderr);
|
||||||
printUsage();
|
printUsage();
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Patch the size array depending on command-line options */
|
// Patch the size array depending on command-line options
|
||||||
if (!is32kMode)
|
if (!is32kMode)
|
||||||
maxsize[SECTTYPE_ROM0] = 0x4000;
|
sectionTypeInfo[SECTTYPE_ROM0].size = 0x4000;
|
||||||
if (!isWRA0Mode)
|
if (!isWRA0Mode)
|
||||||
maxsize[SECTTYPE_WRAM0] = 0x1000;
|
sectionTypeInfo[SECTTYPE_WRAM0].size = 0x1000;
|
||||||
|
|
||||||
/* Patch the bank ranges array depending on command-line options */
|
// Patch the bank ranges array depending on command-line options
|
||||||
if (isDmgMode)
|
if (isDmgMode)
|
||||||
bankranges[SECTTYPE_VRAM][1] = BANK_MIN_VRAM;
|
sectionTypeInfo[SECTTYPE_VRAM].lastBank = 0;
|
||||||
|
|
||||||
/* Read all object files first, */
|
// Read all object files first,
|
||||||
for (obj_Setup(argc - curArgIndex); curArgIndex < argc; curArgIndex++)
|
for (obj_Setup(argc - curArgIndex); curArgIndex < argc; curArgIndex++)
|
||||||
obj_ReadFile(argv[curArgIndex], argc - curArgIndex - 1);
|
obj_ReadFile(argv[curArgIndex], argc - curArgIndex - 1);
|
||||||
|
|
||||||
/* then process them, */
|
// apply the linker script's modifications,
|
||||||
|
if (linkerScriptName) {
|
||||||
|
verbosePrint("Reading linker script...\n");
|
||||||
|
|
||||||
|
linkerScript = openFile(linkerScriptName, "r");
|
||||||
|
|
||||||
|
// Modify all sections according to the linker script
|
||||||
|
struct SectionPlacement *placement;
|
||||||
|
|
||||||
|
while ((placement = script_NextSection())) {
|
||||||
|
struct Section *section = placement->section;
|
||||||
|
|
||||||
|
assert(section->offset == 0);
|
||||||
|
// Check if this doesn't conflict with what the code says
|
||||||
|
if (section->type == SECTTYPE_INVALID) {
|
||||||
|
for (struct Section *sect = section; sect; sect = sect->nextu)
|
||||||
|
sect->type = placement->type; // SDCC "unknown" sections
|
||||||
|
} else if (section->type != placement->type) {
|
||||||
|
error(NULL, 0, "Linker script contradicts \"%s\"'s type",
|
||||||
|
section->name);
|
||||||
|
}
|
||||||
|
if (section->isBankFixed && placement->bank != section->bank)
|
||||||
|
error(NULL, 0, "Linker script contradicts \"%s\"'s bank placement",
|
||||||
|
section->name);
|
||||||
|
if (section->isAddressFixed && placement->org != section->org)
|
||||||
|
error(NULL, 0, "Linker script contradicts \"%s\"'s address placement",
|
||||||
|
section->name);
|
||||||
|
if (section->isAlignFixed
|
||||||
|
&& (placement->org & section->alignMask) != 0)
|
||||||
|
error(NULL, 0, "Linker script contradicts \"%s\"'s alignment",
|
||||||
|
section->name);
|
||||||
|
|
||||||
|
section->isAddressFixed = true;
|
||||||
|
section->org = placement->org;
|
||||||
|
section->isBankFixed = true;
|
||||||
|
section->bank = placement->bank;
|
||||||
|
section->isAlignFixed = false; // The alignment is satisfied
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(linkerScript);
|
||||||
|
|
||||||
|
script_Cleanup();
|
||||||
|
|
||||||
|
// If the linker script produced any errors, some sections may be in an invalid state
|
||||||
|
if (nbErrors != 0)
|
||||||
|
reportErrors();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// then process them,
|
||||||
obj_DoSanityChecks();
|
obj_DoSanityChecks();
|
||||||
|
if (nbErrors != 0)
|
||||||
|
reportErrors();
|
||||||
assign_AssignSections();
|
assign_AssignSections();
|
||||||
obj_CheckAssertions();
|
obj_CheckAssertions();
|
||||||
assign_Cleanup();
|
assign_Cleanup();
|
||||||
|
|
||||||
/* and finally output the result. */
|
// and finally output the result.
|
||||||
patch_ApplyPatches();
|
patch_ApplyPatches();
|
||||||
if (nbErrors) {
|
if (nbErrors != 0)
|
||||||
fprintf(stderr, "Linking failed with %" PRIu32 " error%s\n",
|
reportErrors();
|
||||||
nbErrors, nbErrors == 1 ? "" : "s");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
out_WriteFiles();
|
out_WriteFiles();
|
||||||
|
|
||||||
/* Do cleanup before quitting, though. */
|
// Do cleanup before quitting, though.
|
||||||
cleanup();
|
cleanup();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
#include "link/main.h"
|
#include "link/main.h"
|
||||||
#include "link/object.h"
|
#include "link/object.h"
|
||||||
#include "link/patch.h"
|
#include "link/patch.h"
|
||||||
|
#include "link/sdas_obj.h"
|
||||||
#include "link/section.h"
|
#include "link/section.h"
|
||||||
#include "link/symbol.h"
|
#include "link/symbol.h"
|
||||||
|
|
||||||
@@ -38,12 +39,10 @@ static struct {
|
|||||||
} *nodes;
|
} *nodes;
|
||||||
static struct Assertion *assertions;
|
static struct Assertion *assertions;
|
||||||
|
|
||||||
/***** Helper functions for reading object files *****/
|
// Helper functions for reading object files
|
||||||
|
|
||||||
/*
|
// Internal, DO NOT USE.
|
||||||
* Internal, DO NOT USE.
|
// For helper wrapper macros defined below, such as `tryReadlong`
|
||||||
* For helper wrapper macros defined below, such as `tryReadlong`
|
|
||||||
*/
|
|
||||||
#define tryRead(func, type, errval, var, file, ...) \
|
#define tryRead(func, type, errval, var, file, ...) \
|
||||||
do { \
|
do { \
|
||||||
FILE *tmpFile = file; \
|
FILE *tmpFile = file; \
|
||||||
@@ -57,7 +56,7 @@ static struct Assertion *assertions;
|
|||||||
var = tmpVal; \
|
var = tmpVal; \
|
||||||
} while (0)
|
} while (0)
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Reads an unsigned long (32-bit) value from a file.
|
* Reads an unsigned long (32-bit) value from a file.
|
||||||
* @param file The file to read from. This will read 4 bytes from the file.
|
* @param file The file to read from. This will read 4 bytes from the file.
|
||||||
* @return The value read, cast to a int64_t, or -1 on failure.
|
* @return The value read, cast to a int64_t, or -1 on failure.
|
||||||
@@ -66,25 +65,24 @@ static int64_t readlong(FILE *file)
|
|||||||
{
|
{
|
||||||
uint32_t value = 0;
|
uint32_t value = 0;
|
||||||
|
|
||||||
/* Read the little-endian value byte by byte */
|
// Read the little-endian value byte by byte
|
||||||
for (uint8_t shift = 0; shift < sizeof(value) * CHAR_BIT; shift += 8) {
|
for (uint8_t shift = 0; shift < sizeof(value) * CHAR_BIT; shift += 8) {
|
||||||
int byte = getc(file);
|
int byte = getc(file);
|
||||||
|
|
||||||
if (byte == EOF)
|
if (byte == EOF)
|
||||||
return INT64_MAX;
|
return INT64_MAX;
|
||||||
/* This must be casted to `unsigned`, not `uint8_t`. Rationale:
|
// This must be casted to `unsigned`, not `uint8_t`. Rationale:
|
||||||
* the type of the shift is the type of `byte` after undergoing
|
// the type of the shift is the type of `byte` after undergoing
|
||||||
* integer promotion, which would be `int` if this was casted to
|
// integer promotion, which would be `int` if this was casted to
|
||||||
* `uint8_t`, because int is large enough to hold a byte. This
|
// `uint8_t`, because int is large enough to hold a byte. This
|
||||||
* however causes values larger than 127 to be too large when
|
// however causes values larger than 127 to be too large when
|
||||||
* shifted, potentially triggering undefined behavior.
|
// shifted, potentially triggering undefined behavior.
|
||||||
*/
|
|
||||||
value |= (unsigned int)byte << shift;
|
value |= (unsigned int)byte << shift;
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Helper macro for reading longs from a file, and errors out if it fails to.
|
* Helper macro for reading longs from a file, and errors out if it fails to.
|
||||||
* Not as a function to avoid overhead in the general case.
|
* Not as a function to avoid overhead in the general case.
|
||||||
* @param var The variable to stash the number into
|
* @param var The variable to stash the number into
|
||||||
@@ -95,9 +93,9 @@ static int64_t readlong(FILE *file)
|
|||||||
#define tryReadlong(var, file, ...) \
|
#define tryReadlong(var, file, ...) \
|
||||||
tryRead(readlong, int64_t, INT64_MAX, var, file, __VA_ARGS__)
|
tryRead(readlong, int64_t, INT64_MAX, var, file, __VA_ARGS__)
|
||||||
|
|
||||||
/* There is no `readbyte`, just use `fgetc` or `getc`. */
|
// There is no `readbyte`, just use `fgetc` or `getc`.
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Helper macro for reading bytes from a file, and errors out if it fails to.
|
* Helper macro for reading bytes from a file, and errors out if it fails to.
|
||||||
* Differs from `tryGetc` in that the backing function is fgetc(1).
|
* Differs from `tryGetc` in that the backing function is fgetc(1).
|
||||||
* Not as a function to avoid overhead in the general case.
|
* Not as a function to avoid overhead in the general case.
|
||||||
@@ -109,7 +107,7 @@ static int64_t readlong(FILE *file)
|
|||||||
#define tryFgetc(var, file, ...) \
|
#define tryFgetc(var, file, ...) \
|
||||||
tryRead(fgetc, int, EOF, var, file, __VA_ARGS__)
|
tryRead(fgetc, int, EOF, var, file, __VA_ARGS__)
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Helper macro for reading bytes from a file, and errors out if it fails to.
|
* Helper macro for reading bytes from a file, and errors out if it fails to.
|
||||||
* Differs from `tryGetc` in that the backing function is fgetc(1).
|
* Differs from `tryGetc` in that the backing function is fgetc(1).
|
||||||
* Not as a function to avoid overhead in the general case.
|
* Not as a function to avoid overhead in the general case.
|
||||||
@@ -121,7 +119,7 @@ static int64_t readlong(FILE *file)
|
|||||||
#define tryGetc(var, file, ...) \
|
#define tryGetc(var, file, ...) \
|
||||||
tryRead(getc, int, EOF, var, file, __VA_ARGS__)
|
tryRead(getc, int, EOF, var, file, __VA_ARGS__)
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Reads a '\0'-terminated string from a file.
|
* Reads a '\0'-terminated string from a file.
|
||||||
* @param file The file to read from. The file position will be advanced.
|
* @param file The file to read from. The file position will be advanced.
|
||||||
* @return The string read, or NULL on failure.
|
* @return The string read, or NULL on failure.
|
||||||
@@ -129,26 +127,26 @@ static int64_t readlong(FILE *file)
|
|||||||
*/
|
*/
|
||||||
static char *readstr(FILE *file)
|
static char *readstr(FILE *file)
|
||||||
{
|
{
|
||||||
/* Default buffer size, have it close to the average string length */
|
// Default buffer size, have it close to the average string length
|
||||||
size_t capacity = 32 / 2;
|
size_t capacity = 32 / 2;
|
||||||
size_t index = -1;
|
size_t index = -1;
|
||||||
/* Force the first iteration to allocate */
|
// Force the first iteration to allocate
|
||||||
char *str = NULL;
|
char *str = NULL;
|
||||||
|
|
||||||
do {
|
do {
|
||||||
/* Prepare going to next char */
|
// Prepare going to next char
|
||||||
index++;
|
index++;
|
||||||
|
|
||||||
/* If the buffer isn't suitable to write the next char... */
|
// If the buffer isn't suitable to write the next char...
|
||||||
if (index >= capacity || !str) {
|
if (index >= capacity || !str) {
|
||||||
capacity *= 2;
|
capacity *= 2;
|
||||||
str = realloc(str, capacity);
|
str = realloc(str, capacity);
|
||||||
/* End now in case of error */
|
// End now in case of error
|
||||||
if (!str)
|
if (!str)
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Read char */
|
// Read char
|
||||||
int byte = getc(file);
|
int byte = getc(file);
|
||||||
|
|
||||||
if (byte == EOF) {
|
if (byte == EOF) {
|
||||||
@@ -160,7 +158,7 @@ static char *readstr(FILE *file)
|
|||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Helper macro for reading bytes from a file, and errors out if it fails to.
|
* Helper macro for reading bytes from a file, and errors out if it fails to.
|
||||||
* Not as a function to avoid overhead in the general case.
|
* Not as a function to avoid overhead in the general case.
|
||||||
* @param var The variable to stash the string into
|
* @param var The variable to stash the string into
|
||||||
@@ -171,9 +169,9 @@ static char *readstr(FILE *file)
|
|||||||
#define tryReadstr(var, file, ...) \
|
#define tryReadstr(var, file, ...) \
|
||||||
tryRead(readstr, char*, NULL, var, file, __VA_ARGS__)
|
tryRead(readstr, char*, NULL, var, file, __VA_ARGS__)
|
||||||
|
|
||||||
/***** Functions to parse object files *****/
|
// Functions to parse object files
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Reads a file stack node form a file.
|
* Reads a file stack node form a file.
|
||||||
* @param file The file to read from
|
* @param file The file to read from
|
||||||
* @param nodes The file's array of nodes
|
* @param nodes The file's array of nodes
|
||||||
@@ -216,7 +214,7 @@ static void readFileStackNode(FILE *file, struct FileStackNode fileNodes[], uint
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Reads a symbol from a file.
|
* Reads a symbol from a file.
|
||||||
* @param file The file to read from
|
* @param file The file to read from
|
||||||
* @param symbol The struct to fill
|
* @param symbol The struct to fill
|
||||||
@@ -229,7 +227,7 @@ static void readSymbol(FILE *file, struct Symbol *symbol,
|
|||||||
fileName);
|
fileName);
|
||||||
tryGetc(symbol->type, file, "%s: Cannot read \"%s\"'s type: %s",
|
tryGetc(symbol->type, file, "%s: Cannot read \"%s\"'s type: %s",
|
||||||
fileName, symbol->name);
|
fileName, symbol->name);
|
||||||
/* If the symbol is defined in this file, read its definition */
|
// If the symbol is defined in this file, read its definition
|
||||||
if (symbol->type != SYMTYPE_IMPORT) {
|
if (symbol->type != SYMTYPE_IMPORT) {
|
||||||
symbol->objFileName = fileName;
|
symbol->objFileName = fileName;
|
||||||
uint32_t nodeID;
|
uint32_t nodeID;
|
||||||
@@ -252,7 +250,7 @@ static void readSymbol(FILE *file, struct Symbol *symbol,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Reads a patch from a file.
|
* Reads a patch from a file.
|
||||||
* @param file The file to read from
|
* @param file The file to read from
|
||||||
* @param patch The struct to fill
|
* @param patch The struct to fill
|
||||||
@@ -302,7 +300,7 @@ static void readPatch(FILE *file, struct Patch *patch, char const *fileName, cha
|
|||||||
feof(file) ? "Unexpected end of file" : strerror(errno));
|
feof(file) ? "Unexpected end of file" : strerror(errno));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Sets a patch's pcSection from its pcSectionID.
|
* Sets a patch's pcSection from its pcSectionID.
|
||||||
* @param patch The struct to fix
|
* @param patch The struct to fix
|
||||||
*/
|
*/
|
||||||
@@ -312,7 +310,7 @@ static void linkPatchToPCSect(struct Patch *patch, struct Section *fileSections[
|
|||||||
: fileSections[patch->pcSectionID];
|
: fileSections[patch->pcSectionID];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Reads a section from a file.
|
* Reads a section from a file.
|
||||||
* @param file The file to read from
|
* @param file The file to read from
|
||||||
* @param section The struct to fill
|
* @param section The struct to fill
|
||||||
@@ -371,7 +369,7 @@ static void readSection(FILE *file, struct Section *section, char const *fileNam
|
|||||||
section->alignOfs = tmp;
|
section->alignOfs = tmp;
|
||||||
|
|
||||||
if (sect_HasData(section->type)) {
|
if (sect_HasData(section->type)) {
|
||||||
/* Ensure we never allocate 0 bytes */
|
// Ensure we never allocate 0 bytes
|
||||||
uint8_t *data = malloc(sizeof(*data) * section->size + 1);
|
uint8_t *data = malloc(sizeof(*data) * section->size + 1);
|
||||||
|
|
||||||
if (!data)
|
if (!data)
|
||||||
@@ -400,15 +398,17 @@ static void readSection(FILE *file, struct Section *section, char const *fileNam
|
|||||||
for (uint32_t i = 0; i < section->nbPatches; i++)
|
for (uint32_t i = 0; i < section->nbPatches; i++)
|
||||||
readPatch(file, &patches[i], fileName, section->name, i, fileNodes);
|
readPatch(file, &patches[i], fileName, section->name, i, fileNodes);
|
||||||
section->patches = patches;
|
section->patches = patches;
|
||||||
|
} else {
|
||||||
|
section->data = NULL; // `mergeSections()` expects to be able to always read the ptr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Links a symbol to a section, keeping the section's symbol list sorted.
|
* Links a symbol to a section, keeping the section's symbol list sorted.
|
||||||
* @param symbol The symbol to link
|
* @param symbol The symbol to link
|
||||||
* @param section The section to link
|
* @param section The section to link
|
||||||
*/
|
*/
|
||||||
static void linkSymToSect(struct Symbol const *symbol, struct Section *section)
|
static void linkSymToSect(struct Symbol *symbol, struct Section *section)
|
||||||
{
|
{
|
||||||
uint32_t a = 0, b = section->nbSymbols;
|
uint32_t a = 0, b = section->nbSymbols;
|
||||||
|
|
||||||
@@ -421,7 +421,7 @@ static void linkSymToSect(struct Symbol const *symbol, struct Section *section)
|
|||||||
a = c + 1;
|
a = c + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Symbol const *tmp = symbol;
|
struct Symbol *tmp = symbol;
|
||||||
|
|
||||||
for (uint32_t i = a; i <= section->nbSymbols; i++) {
|
for (uint32_t i = a; i <= section->nbSymbols; i++) {
|
||||||
symbol = tmp;
|
symbol = tmp;
|
||||||
@@ -432,7 +432,7 @@ static void linkSymToSect(struct Symbol const *symbol, struct Section *section)
|
|||||||
section->nbSymbols++;
|
section->nbSymbols++;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Reads an assertion from a file
|
* Reads an assertion from a file
|
||||||
* @param file The file to read from
|
* @param file The file to read from
|
||||||
* @param assert The struct to fill
|
* @param assert The struct to fill
|
||||||
@@ -461,25 +461,58 @@ static struct Section *getMainSection(struct Section *section)
|
|||||||
|
|
||||||
void obj_ReadFile(char const *fileName, unsigned int fileID)
|
void obj_ReadFile(char const *fileName, unsigned int fileID)
|
||||||
{
|
{
|
||||||
FILE *file = strcmp("-", fileName) ? fopen(fileName, "rb") : stdin;
|
FILE *file;
|
||||||
|
|
||||||
|
if (strcmp("-", fileName) != 0)
|
||||||
|
file = fopen(fileName, "rb");
|
||||||
|
else
|
||||||
|
file = fdopen(STDIN_FILENO, "rb"); // `stdin` is in text mode by default
|
||||||
|
|
||||||
if (!file)
|
if (!file)
|
||||||
err("Could not open file %s", fileName);
|
err("Could not open file %s", fileName);
|
||||||
|
|
||||||
/* Begin by reading the magic bytes and version number */
|
// First, check if the object is a RGBDS object or a SDCC one. If the first byte is 'R',
|
||||||
unsigned versionNumber;
|
// we'll assume it's a RGBDS object file, and otherwise, that it's a SDCC object file.
|
||||||
int matchedElems = fscanf(file, RGBDS_OBJECT_VERSION_STRING,
|
int c = getc(file);
|
||||||
&versionNumber);
|
|
||||||
|
|
||||||
if (matchedElems != 1)
|
ungetc(c, file); // Guaranteed to work
|
||||||
|
switch (c) {
|
||||||
|
case EOF:
|
||||||
|
fatal(NULL, 0, "File \"%s\" is empty!", fileName);
|
||||||
|
|
||||||
|
case 'R':
|
||||||
|
break;
|
||||||
|
|
||||||
|
default: // This is (probably) a SDCC object file, defer the rest of detection to it
|
||||||
|
// Since SDCC does not provide line info, everything will be reported as coming from the
|
||||||
|
// object file. It's better than nothing.
|
||||||
|
nodes[fileID].nbNodes = 1;
|
||||||
|
nodes[fileID].nodes = malloc(sizeof(nodes[fileID].nodes[0]) * nodes[fileID].nbNodes);
|
||||||
|
if (!nodes[fileID].nodes)
|
||||||
|
err("Failed to get memory for %s's nodes", fileName);
|
||||||
|
struct FileStackNode *where = &nodes[fileID].nodes[0];
|
||||||
|
|
||||||
|
if (!where)
|
||||||
|
fatal(NULL, 0, "Failed to alloc fstack node for \"%s\": %s", fileName, strerror(errno));
|
||||||
|
where->parent = NULL;
|
||||||
|
where->type = NODE_FILE;
|
||||||
|
where->name = strdup(fileName);
|
||||||
|
if (!where->name)
|
||||||
|
fatal(NULL, 0, "Failed to duplicate \"%s\"'s name: %s", fileName, strerror(errno));
|
||||||
|
|
||||||
|
sdobj_ReadFile(where, file);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Begin by reading the magic bytes
|
||||||
|
int matchedElems;
|
||||||
|
|
||||||
|
if (fscanf(file, RGBDS_OBJECT_VERSION_STRING "%n", &matchedElems) == 1
|
||||||
|
&& matchedElems != strlen(RGBDS_OBJECT_VERSION_STRING))
|
||||||
errx("\"%s\" is not a RGBDS object file", fileName);
|
errx("\"%s\" is not a RGBDS object file", fileName);
|
||||||
|
|
||||||
verbosePrint("Reading object file %s, version %u\n",
|
verbosePrint("Reading object file %s\n",
|
||||||
fileName, versionNumber);
|
fileName);
|
||||||
|
|
||||||
if (versionNumber != RGBDS_OBJECT_VERSION_NUMBER)
|
|
||||||
errx("\"%s\" is an incompatible version %u object file",
|
|
||||||
fileName, versionNumber);
|
|
||||||
|
|
||||||
uint32_t revNum;
|
uint32_t revNum;
|
||||||
|
|
||||||
@@ -507,9 +540,8 @@ void obj_ReadFile(char const *fileName, unsigned int fileID)
|
|||||||
for (uint32_t i = nodes[fileID].nbNodes; i--; )
|
for (uint32_t i = nodes[fileID].nbNodes; i--; )
|
||||||
readFileStackNode(file, nodes[fileID].nodes, i, fileName);
|
readFileStackNode(file, nodes[fileID].nodes, i, fileName);
|
||||||
|
|
||||||
/* This file's symbols, kept to link sections to them */
|
// This file's symbols, kept to link sections to them
|
||||||
struct Symbol **fileSymbols =
|
struct Symbol **fileSymbols = malloc(sizeof(*fileSymbols) * nbSymbols + 1);
|
||||||
malloc(sizeof(*fileSymbols) * nbSymbols + 1);
|
|
||||||
|
|
||||||
if (!fileSymbols)
|
if (!fileSymbols)
|
||||||
err("Failed to get memory for %s's symbols", fileName);
|
err("Failed to get memory for %s's symbols", fileName);
|
||||||
@@ -528,7 +560,7 @@ void obj_ReadFile(char const *fileName, unsigned int fileID)
|
|||||||
|
|
||||||
verbosePrint("Reading %" PRIu32 " symbols...\n", nbSymbols);
|
verbosePrint("Reading %" PRIu32 " symbols...\n", nbSymbols);
|
||||||
for (uint32_t i = 0; i < nbSymbols; i++) {
|
for (uint32_t i = 0; i < nbSymbols; i++) {
|
||||||
/* Read symbol */
|
// Read symbol
|
||||||
struct Symbol *symbol = malloc(sizeof(*symbol));
|
struct Symbol *symbol = malloc(sizeof(*symbol));
|
||||||
|
|
||||||
if (!symbol)
|
if (!symbol)
|
||||||
@@ -542,13 +574,13 @@ void obj_ReadFile(char const *fileName, unsigned int fileID)
|
|||||||
nbSymPerSect[symbol->sectionID]++;
|
nbSymPerSect[symbol->sectionID]++;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* This file's sections, stored in a table to link symbols to them */
|
// This file's sections, stored in a table to link symbols to them
|
||||||
struct Section **fileSections = malloc(sizeof(*fileSections)
|
struct Section **fileSections = malloc(sizeof(*fileSections)
|
||||||
* (nbSections ? nbSections : 1));
|
* (nbSections ? nbSections : 1));
|
||||||
|
|
||||||
verbosePrint("Reading %" PRIu32 " sections...\n", nbSections);
|
verbosePrint("Reading %" PRIu32 " sections...\n", nbSections);
|
||||||
for (uint32_t i = 0; i < nbSections; i++) {
|
for (uint32_t i = 0; i < nbSections; i++) {
|
||||||
/* Read section */
|
// Read section
|
||||||
fileSections[i] = malloc(sizeof(*fileSections[i]));
|
fileSections[i] = malloc(sizeof(*fileSections[i]));
|
||||||
if (!fileSections[i])
|
if (!fileSections[i])
|
||||||
err("%s: Couldn't create new section", fileName);
|
err("%s: Couldn't create new section", fileName);
|
||||||
@@ -572,7 +604,7 @@ void obj_ReadFile(char const *fileName, unsigned int fileID)
|
|||||||
|
|
||||||
free(nbSymPerSect);
|
free(nbSymPerSect);
|
||||||
|
|
||||||
/* Give patches' PC section pointers to their sections */
|
// Give patches' PC section pointers to their sections
|
||||||
for (uint32_t i = 0; i < nbSections; i++) {
|
for (uint32_t i = 0; i < nbSections; i++) {
|
||||||
if (sect_HasData(fileSections[i]->type)) {
|
if (sect_HasData(fileSections[i]->type)) {
|
||||||
for (uint32_t j = 0; j < fileSections[i]->nbPatches; j++)
|
for (uint32_t j = 0; j < fileSections[i]->nbPatches; j++)
|
||||||
@@ -580,7 +612,7 @@ void obj_ReadFile(char const *fileName, unsigned int fileID)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Give symbols' section pointers to their sections */
|
// Give symbols' section pointers to their sections
|
||||||
for (uint32_t i = 0; i < nbSymbols; i++) {
|
for (uint32_t i = 0; i < nbSymbols; i++) {
|
||||||
int32_t sectionID = fileSymbols[i]->sectionID;
|
int32_t sectionID = fileSymbols[i]->sectionID;
|
||||||
|
|
||||||
@@ -589,12 +621,12 @@ void obj_ReadFile(char const *fileName, unsigned int fileID)
|
|||||||
} else {
|
} else {
|
||||||
struct Section *section = fileSections[sectionID];
|
struct Section *section = fileSections[sectionID];
|
||||||
|
|
||||||
/* Give the section a pointer to the symbol as well */
|
// Give the section a pointer to the symbol as well
|
||||||
linkSymToSect(fileSymbols[i], section);
|
linkSymToSect(fileSymbols[i], section);
|
||||||
|
|
||||||
if (section->modifier != SECTION_NORMAL) {
|
if (section->modifier != SECTION_NORMAL) {
|
||||||
if (section->modifier == SECTION_FRAGMENT)
|
if (section->modifier == SECTION_FRAGMENT)
|
||||||
/* Add the fragment's offset to the symbol's */
|
// Add the fragment's offset to the symbol's
|
||||||
fileSymbols[i]->offset += section->offset;
|
fileSymbols[i]->offset += section->offset;
|
||||||
section = getMainSection(section);
|
section = getMainSection(section);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
#include "link/output.h"
|
#include "link/output.h"
|
||||||
@@ -16,6 +17,8 @@
|
|||||||
#include "link/section.h"
|
#include "link/section.h"
|
||||||
#include "link/symbol.h"
|
#include "link/symbol.h"
|
||||||
|
|
||||||
|
#include "extern/utf8decoder.h"
|
||||||
|
|
||||||
#include "error.h"
|
#include "error.h"
|
||||||
#include "linkdefs.h"
|
#include "linkdefs.h"
|
||||||
#include "platform.h" // MIN_NB_ELMS
|
#include "platform.h" // MIN_NB_ELMS
|
||||||
@@ -46,7 +49,7 @@ static struct {
|
|||||||
} *banks;
|
} *banks;
|
||||||
} sections[SECTTYPE_INVALID];
|
} sections[SECTTYPE_INVALID];
|
||||||
|
|
||||||
/* Defines the order in which types are output to the sym and map files */
|
// Defines the order in which types are output to the sym and map files
|
||||||
static enum SectionType typeMap[SECTTYPE_INVALID] = {
|
static enum SectionType typeMap[SECTTYPE_INVALID] = {
|
||||||
SECTTYPE_ROM0,
|
SECTTYPE_ROM0,
|
||||||
SECTTYPE_ROMX,
|
SECTTYPE_ROMX,
|
||||||
@@ -71,7 +74,7 @@ void out_AddSection(struct Section const *section)
|
|||||||
[SECTTYPE_HRAM] = 1
|
[SECTTYPE_HRAM] = 1
|
||||||
};
|
};
|
||||||
|
|
||||||
uint32_t targetBank = section->bank - bankranges[section->type][0];
|
uint32_t targetBank = section->bank - sectionTypeInfo[section->type].firstBank;
|
||||||
uint32_t minNbBanks = targetBank + 1;
|
uint32_t minNbBanks = targetBank + 1;
|
||||||
|
|
||||||
if (minNbBanks > maxNbBanks[section->type])
|
if (minNbBanks > maxNbBanks[section->type])
|
||||||
@@ -112,7 +115,7 @@ struct Section const *out_OverlappingSection(struct Section const *section)
|
|||||||
{
|
{
|
||||||
struct SortedSections *banks = sections[section->type].banks;
|
struct SortedSections *banks = sections[section->type].banks;
|
||||||
struct SortedSection *ptr =
|
struct SortedSection *ptr =
|
||||||
banks[section->bank - bankranges[section->type][0]].sections;
|
banks[section->bank - sectionTypeInfo[section->type].firstBank].sections;
|
||||||
|
|
||||||
while (ptr) {
|
while (ptr) {
|
||||||
if (ptr->section->org < section->org + section->size
|
if (ptr->section->org < section->org + section->size
|
||||||
@@ -123,7 +126,7 @@ struct Section const *out_OverlappingSection(struct Section const *section)
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Performs sanity checks on the overlay file.
|
* Performs sanity checks on the overlay file.
|
||||||
* @return The number of ROM banks in the overlay file
|
* @return The number of ROM banks in the overlay file
|
||||||
*/
|
*/
|
||||||
@@ -139,7 +142,7 @@ static uint32_t checkOverlaySize(void)
|
|||||||
|
|
||||||
long overlaySize = ftell(overlayFile);
|
long overlaySize = ftell(overlayFile);
|
||||||
|
|
||||||
/* Reset back to beginning */
|
// Reset back to beginning
|
||||||
fseek(overlayFile, 0, SEEK_SET);
|
fseek(overlayFile, 0, SEEK_SET);
|
||||||
|
|
||||||
if (overlaySize % BANK_SIZE)
|
if (overlaySize % BANK_SIZE)
|
||||||
@@ -156,7 +159,7 @@ static uint32_t checkOverlaySize(void)
|
|||||||
return nbOverlayBanks;
|
return nbOverlayBanks;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Expand sections[SECTTYPE_ROMX].banks to cover all the overlay banks.
|
* Expand sections[SECTTYPE_ROMX].banks to cover all the overlay banks.
|
||||||
* This ensures that writeROM will output each bank, even if some are not
|
* This ensures that writeROM will output each bank, even if some are not
|
||||||
* covered by any sections.
|
* covered by any sections.
|
||||||
@@ -164,9 +167,9 @@ static uint32_t checkOverlaySize(void)
|
|||||||
*/
|
*/
|
||||||
static void coverOverlayBanks(uint32_t nbOverlayBanks)
|
static void coverOverlayBanks(uint32_t nbOverlayBanks)
|
||||||
{
|
{
|
||||||
/* 2 if is32kMode, 1 otherwise */
|
// 2 if is32kMode, 1 otherwise
|
||||||
uint32_t nbRom0Banks = maxsize[SECTTYPE_ROM0] / BANK_SIZE;
|
uint32_t nbRom0Banks = sectionTypeInfo[SECTTYPE_ROM0].size / BANK_SIZE;
|
||||||
/* Discount ROM0 banks to avoid outputting too much */
|
// Discount ROM0 banks to avoid outputting too much
|
||||||
uint32_t nbUncoveredBanks = nbOverlayBanks - nbRom0Banks > sections[SECTTYPE_ROMX].nbBanks
|
uint32_t nbUncoveredBanks = nbOverlayBanks - nbRom0Banks > sections[SECTTYPE_ROMX].nbBanks
|
||||||
? nbOverlayBanks - nbRom0Banks
|
? nbOverlayBanks - nbRom0Banks
|
||||||
: 0;
|
: 0;
|
||||||
@@ -185,7 +188,7 @@ static void coverOverlayBanks(uint32_t nbOverlayBanks)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Write a ROM bank's sections to the output file.
|
* Write a ROM bank's sections to the output file.
|
||||||
* @param bankSections The bank's sections, ordered by increasing address
|
* @param bankSections The bank's sections, ordered by increasing address
|
||||||
* @param baseOffset The address of the bank's first byte in GB address space
|
* @param baseOffset The address of the bank's first byte in GB address space
|
||||||
@@ -199,18 +202,19 @@ static void writeBank(struct SortedSection *bankSections, uint16_t baseOffset,
|
|||||||
while (bankSections) {
|
while (bankSections) {
|
||||||
struct Section const *section = bankSections->section;
|
struct Section const *section = bankSections->section;
|
||||||
|
|
||||||
/* Output padding up to the next SECTION */
|
assert(section->offset == 0);
|
||||||
|
// Output padding up to the next SECTION
|
||||||
while (offset + baseOffset < section->org) {
|
while (offset + baseOffset < section->org) {
|
||||||
putc(overlayFile ? getc(overlayFile) : padValue,
|
putc(overlayFile ? getc(overlayFile) : padValue,
|
||||||
outputFile);
|
outputFile);
|
||||||
offset++;
|
offset++;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Output the section itself */
|
// Output the section itself
|
||||||
fwrite(section->data, sizeof(*section->data), section->size,
|
fwrite(section->data, sizeof(*section->data), section->size,
|
||||||
outputFile);
|
outputFile);
|
||||||
if (overlayFile) {
|
if (overlayFile) {
|
||||||
/* Skip bytes even with pipes */
|
// Skip bytes even with pipes
|
||||||
for (uint16_t i = 0; i < section->size; i++)
|
for (uint16_t i = 0; i < section->size; i++)
|
||||||
getc(overlayFile);
|
getc(overlayFile);
|
||||||
}
|
}
|
||||||
@@ -229,9 +233,7 @@ static void writeBank(struct SortedSection *bankSections, uint16_t baseOffset,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Writes a ROM file to the output.
|
||||||
* Writes a ROM file to the output.
|
|
||||||
*/
|
|
||||||
static void writeROM(void)
|
static void writeROM(void)
|
||||||
{
|
{
|
||||||
outputFile = openFile(outputFileName, "wb");
|
outputFile = openFile(outputFileName, "wb");
|
||||||
@@ -245,18 +247,18 @@ static void writeROM(void)
|
|||||||
if (outputFile) {
|
if (outputFile) {
|
||||||
writeBank(sections[SECTTYPE_ROM0].banks ? sections[SECTTYPE_ROM0].banks[0].sections
|
writeBank(sections[SECTTYPE_ROM0].banks ? sections[SECTTYPE_ROM0].banks[0].sections
|
||||||
: NULL,
|
: NULL,
|
||||||
startaddr[SECTTYPE_ROM0], maxsize[SECTTYPE_ROM0]);
|
sectionTypeInfo[SECTTYPE_ROM0].startAddr, sectionTypeInfo[SECTTYPE_ROM0].size);
|
||||||
|
|
||||||
for (uint32_t i = 0 ; i < sections[SECTTYPE_ROMX].nbBanks; i++)
|
for (uint32_t i = 0 ; i < sections[SECTTYPE_ROMX].nbBanks; i++)
|
||||||
writeBank(sections[SECTTYPE_ROMX].banks[i].sections,
|
writeBank(sections[SECTTYPE_ROMX].banks[i].sections,
|
||||||
startaddr[SECTTYPE_ROMX], maxsize[SECTTYPE_ROMX]);
|
sectionTypeInfo[SECTTYPE_ROMX].startAddr, sectionTypeInfo[SECTTYPE_ROMX].size);
|
||||||
}
|
}
|
||||||
|
|
||||||
closeFile(outputFile);
|
closeFile(outputFile);
|
||||||
closeFile(overlayFile);
|
closeFile(overlayFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Get the lowest section by address out of the two
|
* Get the lowest section by address out of the two
|
||||||
* @param s1 One choice
|
* @param s1 One choice
|
||||||
* @param s2 The other
|
* @param s2 The other
|
||||||
@@ -273,10 +275,57 @@ static struct SortedSection const **nextSection(struct SortedSection const **s1,
|
|||||||
return (*s1)->section->org < (*s2)->section->org ? s1 : s2;
|
return (*s1)->section->org < (*s2)->section->org ? s1 : s2;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Checks whether this character is legal as the first character of a symbol's name in a sym file
|
||||||
* Comparator function for `qsort` to sort symbols
|
static bool canStartSymName(char c)
|
||||||
* Symbols are ordered by address, or else by original index for a stable sort
|
{
|
||||||
*/
|
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks whether this character is legal in a symbol's name in a sym file
|
||||||
|
static bool isLegalForSymName(char c)
|
||||||
|
{
|
||||||
|
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') ||
|
||||||
|
c == '_' || c == '@' || c == '#' || c == '$' || c == '.';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prints a symbol's name to `symFile`, assuming that the first character is legal.
|
||||||
|
// Illegal characters are UTF-8-decoded (errors are replaced by U+FFFD) and emitted as `\u`/`\U`.
|
||||||
|
static void printSymName(char const *name)
|
||||||
|
{
|
||||||
|
for (char const *ptr = name; *ptr != '\0'; ) {
|
||||||
|
char c = *ptr;
|
||||||
|
|
||||||
|
if (isLegalForSymName(c)) {
|
||||||
|
// Output legal ASCII characters as-is
|
||||||
|
fputc(c, symFile);
|
||||||
|
++ptr;
|
||||||
|
} else {
|
||||||
|
// Output illegal characters using Unicode escapes
|
||||||
|
// Decode the UTF-8 codepoint; or at least attempt to
|
||||||
|
uint32_t state = 0, codepoint;
|
||||||
|
|
||||||
|
do {
|
||||||
|
decode(&state, &codepoint, *ptr);
|
||||||
|
if (state == 1) {
|
||||||
|
// This sequence was invalid; emit a U+FFFD, and recover
|
||||||
|
codepoint = 0xFFFD;
|
||||||
|
// Skip continuation bytes
|
||||||
|
// A NUL byte does not qualify, so we're good
|
||||||
|
while ((*ptr & 0xC0) == 0x80)
|
||||||
|
++ptr;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
++ptr;
|
||||||
|
} while (state != 0);
|
||||||
|
|
||||||
|
fprintf(symFile, codepoint <= 0xFFFF ? "\\u%04" PRIx32 : "\\U%08" PRIx32,
|
||||||
|
codepoint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Comparator function for `qsort` to sort symbols
|
||||||
|
// Symbols are ordered by address, or else by original index for a stable sort
|
||||||
static int compareSymbols(void const *a, void const *b)
|
static int compareSymbols(void const *a, void const *b)
|
||||||
{
|
{
|
||||||
struct SortedSymbol const *sym1 = (struct SortedSymbol const *)a;
|
struct SortedSymbol const *sym1 = (struct SortedSymbol const *)a;
|
||||||
@@ -288,7 +337,7 @@ static int compareSymbols(void const *a, void const *b)
|
|||||||
return sym1->idx < sym2->idx ? -1 : sym1->idx > sym2->idx ? 1 : 0;
|
return sym1->idx < sym2->idx ? -1 : sym1->idx > sym2->idx ? 1 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Write a bank's contents to the sym file
|
* Write a bank's contents to the sym file
|
||||||
* @param bankSections The bank's sections
|
* @param bankSections The bank's sections
|
||||||
*/
|
*/
|
||||||
@@ -298,16 +347,22 @@ static void writeSymBank(struct SortedSections const *bankSections,
|
|||||||
if (!symFile)
|
if (!symFile)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
#define forEachSortedSection(sect, ...) do { \
|
||||||
|
for (struct SortedSection const *ssp = bankSections->zeroLenSections; ssp; ssp = ssp->next) { \
|
||||||
|
for (struct Section const *sect = ssp->section; sect; sect = sect->nextu) \
|
||||||
|
__VA_ARGS__ \
|
||||||
|
} \
|
||||||
|
for (struct SortedSection const *ssp = bankSections->sections; ssp; ssp = ssp->next) { \
|
||||||
|
for (struct Section const *sect = ssp->section; sect; sect = sect->nextu) \
|
||||||
|
__VA_ARGS__ \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
uint32_t nbSymbols = 0;
|
uint32_t nbSymbols = 0;
|
||||||
|
|
||||||
for (struct SortedSection const *ptr = bankSections->zeroLenSections; ptr; ptr = ptr->next) {
|
forEachSortedSection(sect, {
|
||||||
for (struct Section const *sect = ptr->section; sect; sect = sect->nextu)
|
nbSymbols += sect->nbSymbols;
|
||||||
nbSymbols += sect->nbSymbols;
|
});
|
||||||
}
|
|
||||||
for (struct SortedSection const *ptr = bankSections->sections; ptr; ptr = ptr->next) {
|
|
||||||
for (struct Section const *sect = ptr->section; sect; sect = sect->nextu)
|
|
||||||
nbSymbols += sect->nbSymbols;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!nbSymbols)
|
if (!nbSymbols)
|
||||||
return;
|
return;
|
||||||
@@ -317,45 +372,49 @@ static void writeSymBank(struct SortedSections const *bankSections,
|
|||||||
if (!symList)
|
if (!symList)
|
||||||
err("Failed to allocate symbol list");
|
err("Failed to allocate symbol list");
|
||||||
|
|
||||||
uint32_t idx = 0;
|
nbSymbols = 0;
|
||||||
|
|
||||||
for (struct SortedSection const *ptr = bankSections->zeroLenSections; ptr; ptr = ptr->next) {
|
forEachSortedSection(sect, {
|
||||||
for (struct Section const *sect = ptr->section; sect; sect = sect->nextu) {
|
for (uint32_t i = 0; i < sect->nbSymbols; i++) {
|
||||||
for (uint32_t i = 0; i < sect->nbSymbols; i++) {
|
if (!canStartSymName(sect->symbols[i]->name[0]))
|
||||||
symList[idx].idx = idx;
|
// Don't output symbols that begin with an illegal character
|
||||||
symList[idx].sym = sect->symbols[i];
|
continue;
|
||||||
symList[idx].addr = symList[idx].sym->offset + sect->org;
|
symList[nbSymbols].idx = nbSymbols;
|
||||||
idx++;
|
symList[nbSymbols].sym = sect->symbols[i];
|
||||||
}
|
symList[nbSymbols].addr = symList[nbSymbols].sym->offset + sect->org;
|
||||||
|
nbSymbols++;
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
for (struct SortedSection const *ptr = bankSections->sections; ptr; ptr = ptr->next) {
|
|
||||||
for (struct Section const *sect = ptr->section; sect; sect = sect->nextu) {
|
#undef forEachSortedSection
|
||||||
for (uint32_t i = 0; i < sect->nbSymbols; i++) {
|
|
||||||
symList[idx].idx = idx;
|
|
||||||
symList[idx].sym = sect->symbols[i];
|
|
||||||
symList[idx].addr = symList[idx].sym->offset + sect->org;
|
|
||||||
idx++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
assert(idx == nbSymbols);
|
|
||||||
|
|
||||||
qsort(symList, nbSymbols, sizeof(*symList), compareSymbols);
|
qsort(symList, nbSymbols, sizeof(*symList), compareSymbols);
|
||||||
|
|
||||||
uint32_t symBank = bank + bankranges[type][0];
|
uint32_t symBank = bank + sectionTypeInfo[type].firstBank;
|
||||||
|
|
||||||
for (uint32_t i = 0; i < nbSymbols; i++) {
|
for (uint32_t i = 0; i < nbSymbols; i++) {
|
||||||
struct SortedSymbol *sym = &symList[i];
|
struct SortedSymbol *sym = &symList[i];
|
||||||
|
|
||||||
fprintf(symFile, "%02" PRIx32 ":%04" PRIx16 " %s\n",
|
fprintf(symFile, "%02" PRIx32 ":%04" PRIx16 " ", symBank, sym->addr);
|
||||||
symBank, sym->addr, sym->sym->name);
|
printSymName(sym->sym->name);
|
||||||
|
fputc('\n', symFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
free(symList);
|
free(symList);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
static void writeEmptySpace(uint16_t begin, uint16_t end)
|
||||||
|
{
|
||||||
|
if (begin < end) {
|
||||||
|
uint16_t len = end - begin;
|
||||||
|
|
||||||
|
fprintf(mapFile, "\tEMPTY: $%04x-$%04x ($%04" PRIx16 " byte%s)\n",
|
||||||
|
begin, end - 1, len, len == 1 ? "" : "s");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
* Write a bank's contents to the map file
|
* Write a bank's contents to the map file
|
||||||
* @param bankSections The bank's sections
|
* @param bankSections The bank's sections
|
||||||
* @return The bank's used space
|
* @return The bank's used space
|
||||||
@@ -369,10 +428,11 @@ static uint16_t writeMapBank(struct SortedSections const *sectList,
|
|||||||
struct SortedSection const *section = sectList->sections;
|
struct SortedSection const *section = sectList->sections;
|
||||||
struct SortedSection const *zeroLenSection = sectList->zeroLenSections;
|
struct SortedSection const *zeroLenSection = sectList->zeroLenSections;
|
||||||
|
|
||||||
fprintf(mapFile, "%s bank #%" PRIu32 ":\n", typeNames[type],
|
fprintf(mapFile, "%s bank #%" PRIu32 ":\n", sectionTypeInfo[type].name,
|
||||||
bank + bankranges[type][0]);
|
bank + sectionTypeInfo[type].firstBank);
|
||||||
|
|
||||||
uint16_t used = 0;
|
uint16_t used = 0;
|
||||||
|
uint16_t prevEndAddr = sectionTypeInfo[type].startAddr;
|
||||||
|
|
||||||
while (section || zeroLenSection) {
|
while (section || zeroLenSection) {
|
||||||
struct SortedSection const **pickedSection =
|
struct SortedSection const **pickedSection =
|
||||||
@@ -380,54 +440,75 @@ static uint16_t writeMapBank(struct SortedSections const *sectList,
|
|||||||
struct Section const *sect = (*pickedSection)->section;
|
struct Section const *sect = (*pickedSection)->section;
|
||||||
|
|
||||||
used += sect->size;
|
used += sect->size;
|
||||||
|
assert(sect->offset == 0);
|
||||||
|
|
||||||
|
writeEmptySpace(prevEndAddr, sect->org);
|
||||||
|
|
||||||
|
prevEndAddr = sect->org + sect->size;
|
||||||
|
|
||||||
if (sect->size != 0)
|
if (sect->size != 0)
|
||||||
fprintf(mapFile, " SECTION: $%04" PRIx16 "-$%04x ($%04" PRIx16
|
fprintf(mapFile, "\tSECTION: $%04" PRIx16 "-$%04x ($%04" PRIx16
|
||||||
" byte%s) [\"%s\"]\n",
|
" byte%s) [\"%s\"]\n",
|
||||||
sect->org, sect->org + sect->size - 1,
|
sect->org, prevEndAddr - 1,
|
||||||
sect->size, sect->size == 1 ? "" : "s",
|
sect->size, sect->size == 1 ? "" : "s",
|
||||||
sect->name);
|
sect->name);
|
||||||
else
|
else
|
||||||
fprintf(mapFile, " SECTION: $%04" PRIx16 " (0 bytes) [\"%s\"]\n",
|
fprintf(mapFile, "\tSECTION: $%04" PRIx16 " (0 bytes) [\"%s\"]\n",
|
||||||
sect->org, sect->name);
|
sect->org, sect->name);
|
||||||
|
|
||||||
uint16_t org = sect->org;
|
if (!noSymInMap) {
|
||||||
|
uint16_t org = sect->org;
|
||||||
|
|
||||||
while (sect) {
|
while (sect) {
|
||||||
for (size_t i = 0; i < sect->nbSymbols; i++)
|
for (size_t i = 0; i < sect->nbSymbols; i++)
|
||||||
fprintf(mapFile, " $%04" PRIx32 " = %s\n",
|
// Space matches "\tSECTION: $xxxx ..."
|
||||||
sect->symbols[i]->offset + org,
|
fprintf(mapFile, "\t $%04" PRIx32 " = %s\n",
|
||||||
sect->symbols[i]->name);
|
sect->symbols[i]->offset + org,
|
||||||
|
sect->symbols[i]->name);
|
||||||
|
|
||||||
sect = sect->nextu; // Also print symbols in the following "pieces"
|
if (sect->nextu) {
|
||||||
|
// Announce the following "piece"
|
||||||
|
if (sect->nextu->modifier == SECTION_UNION)
|
||||||
|
fprintf(mapFile,
|
||||||
|
"\t ; Next union\n");
|
||||||
|
else if (sect->nextu->modifier == SECTION_FRAGMENT)
|
||||||
|
fprintf(mapFile,
|
||||||
|
"\t ; Next fragment\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
sect = sect->nextu; // Also print symbols in the following "pieces"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
*pickedSection = (*pickedSection)->next;
|
*pickedSection = (*pickedSection)->next;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint16_t bankEndAddr = sectionTypeInfo[type].startAddr + sectionTypeInfo[type].size;
|
||||||
|
|
||||||
if (used == 0) {
|
if (used == 0) {
|
||||||
fputs(" EMPTY\n\n", mapFile);
|
fputs("\tEMPTY\n\n", mapFile);
|
||||||
} else {
|
} else {
|
||||||
uint16_t slack = maxsize[type] - used;
|
writeEmptySpace(prevEndAddr, bankEndAddr);
|
||||||
|
|
||||||
fprintf(mapFile, " SLACK: $%04" PRIx16 " byte%s\n\n", slack,
|
uint16_t slack = sectionTypeInfo[type].size - used;
|
||||||
|
|
||||||
|
fprintf(mapFile, "\tTOTAL EMPTY: $%04" PRIx16 " byte%s\n\n", slack,
|
||||||
slack == 1 ? "" : "s");
|
slack == 1 ? "" : "s");
|
||||||
}
|
}
|
||||||
|
|
||||||
return used;
|
return used;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Write the total used space by section type to the map file
|
* Write the total used and free space by section type to the map file
|
||||||
* @param usedMap The total used space by section type
|
* @param usedMap The total used space by section type
|
||||||
*/
|
*/
|
||||||
static void writeMapUsed(uint32_t usedMap[MIN_NB_ELMS(SECTTYPE_INVALID)])
|
static void writeMapSummary(uint32_t usedMap[MIN_NB_ELMS(SECTTYPE_INVALID)])
|
||||||
{
|
{
|
||||||
if (!mapFile)
|
if (!mapFile)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
fputs("USED:\n", mapFile);
|
fputs("SUMMARY:\n", mapFile);
|
||||||
|
|
||||||
for (uint8_t i = 0; i < SECTTYPE_INVALID; i++) {
|
for (uint8_t i = 0; i < SECTTYPE_INVALID; i++) {
|
||||||
enum SectionType type = typeMap[i];
|
enum SectionType type = typeMap[i];
|
||||||
@@ -436,17 +517,22 @@ static void writeMapUsed(uint32_t usedMap[MIN_NB_ELMS(SECTTYPE_INVALID)])
|
|||||||
if (type == SECTTYPE_VRAM || type == SECTTYPE_OAM)
|
if (type == SECTTYPE_VRAM || type == SECTTYPE_OAM)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (sections[type].nbBanks > 0) {
|
// Do not output unused section types
|
||||||
fprintf(mapFile, " %s: $%04" PRIx32 " byte%s in %" PRIu32 " bank%s\n",
|
if (sections[type].nbBanks == 0)
|
||||||
typeNames[type], usedMap[type], usedMap[type] == 1 ? "" : "s",
|
continue;
|
||||||
sections[type].nbBanks, sections[type].nbBanks == 1 ? "" : "s");
|
|
||||||
}
|
fprintf(mapFile, "\t%s: %" PRId32 " byte%s used / %" PRId32 " free",
|
||||||
|
sectionTypeInfo[type].name, usedMap[type], usedMap[type] == 1 ? "" : "s",
|
||||||
|
sections[type].nbBanks * sectionTypeInfo[type].size - usedMap[type]);
|
||||||
|
if (sectionTypeInfo[type].firstBank != sectionTypeInfo[type].lastBank ||
|
||||||
|
sections[type].nbBanks > 1)
|
||||||
|
fprintf(mapFile, " in %d bank%s", sections[type].nbBanks,
|
||||||
|
sections[type].nbBanks == 1 ? "" : "s");
|
||||||
|
fputc('\n', mapFile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Writes the sym and/or map files, if applicable.
|
||||||
* Writes the sym and/or map files, if applicable.
|
|
||||||
*/
|
|
||||||
static void writeSymAndMap(void)
|
static void writeSymAndMap(void)
|
||||||
{
|
{
|
||||||
if (!symFileName && !mapFileName)
|
if (!symFileName && !mapFileName)
|
||||||
@@ -471,7 +557,7 @@ static void writeSymAndMap(void)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
writeMapUsed(usedMap);
|
writeMapSummary(usedMap);
|
||||||
|
|
||||||
closeFile(symFile);
|
closeFile(symFile);
|
||||||
closeFile(mapFile);
|
closeFile(mapFile);
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user