mirror of
https://github.com/gbdev/rgbds.git
synced 2025-11-20 10:12:06 +00:00
Compare commits
139 Commits
v0.9.0-rc2
...
v0.9.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1715f85d50 | ||
|
|
c2de0a991a | ||
|
|
2e6e1ccf06 | ||
|
|
081f48404c | ||
|
|
bdac0ce053 | ||
|
|
122d91509f | ||
|
|
7c6f778ae7 | ||
|
|
8cf6c5423a | ||
|
|
56f7222230 | ||
|
|
e45b9625ca | ||
|
|
e0a8eb8aff | ||
|
|
2a5b9b5f98 | ||
|
|
a72843748f | ||
|
|
762e2311d2 | ||
|
|
0b7cda9e0c | ||
|
|
df83bc31d2 | ||
|
|
bc8d99d915 | ||
|
|
c841672059 | ||
|
|
75b605797d | ||
|
|
00d0ae840d | ||
|
|
2cdbb145da | ||
|
|
d8192560b0 | ||
|
|
9b395f3bf1 | ||
|
|
0150eb4bf3 | ||
|
|
632342b254 | ||
|
|
c9060c7f2d | ||
|
|
993879a2ed | ||
|
|
62309d5c87 | ||
|
|
b2e865ee2a | ||
|
|
3feb75f84f | ||
|
|
ad4d9da4cf | ||
|
|
1489854932 | ||
|
|
2aef09c8d9 | ||
|
|
48412e9c56 | ||
|
|
177a3abfac | ||
|
|
4c916b8da8 | ||
|
|
d9d381cb62 | ||
|
|
fbde24ee17 | ||
|
|
91310c9eb6 | ||
|
|
81ea4ee920 | ||
|
|
29ece2940d | ||
|
|
03452c6d4f | ||
|
|
b35e9d86fb | ||
|
|
e20347e38c | ||
|
|
f61019dd68 | ||
|
|
c19ddc80f0 | ||
|
|
a59867cd78 | ||
|
|
375adc6804 | ||
|
|
44caffe04a | ||
|
|
d54619a453 | ||
|
|
e49291b7cf | ||
|
|
34a9c8e083 | ||
|
|
c4b456b166 | ||
|
|
79401cce8b | ||
|
|
4e44958d26 | ||
|
|
cae31005f8 | ||
|
|
25c9f8f383 | ||
|
|
01c9106b59 | ||
|
|
192fc808c8 | ||
|
|
9c8e327ae2 | ||
|
|
9ebd2a7e8e | ||
|
|
b8b60207f5 | ||
|
|
c5e59f40fd | ||
|
|
a354af3d08 | ||
|
|
20c18256ed | ||
|
|
890528812e | ||
|
|
84f59e14ed | ||
|
|
91d7ce5e09 | ||
|
|
d9654b752f | ||
|
|
157826bf82 | ||
|
|
a5e36f924f | ||
|
|
82f7bdb480 | ||
|
|
056190413e | ||
|
|
c2db23aef0 | ||
|
|
2426068409 | ||
|
|
147a5c9bf3 | ||
|
|
6ae3f040b8 | ||
|
|
e561f63db3 | ||
|
|
af9de812ec | ||
|
|
edc9e07a2d | ||
|
|
382ad17969 | ||
|
|
fac5e35d24 | ||
|
|
a85d6b3b57 | ||
|
|
f23a14afc7 | ||
|
|
f63167dd0f | ||
|
|
0ee4ba95b3 | ||
|
|
727c1f5b50 | ||
|
|
d829fd2ffe | ||
|
|
b13c0f2f8e | ||
|
|
d9773424e4 | ||
|
|
4e2464a69d | ||
|
|
a5f12f66bb | ||
|
|
73ad431b8d | ||
|
|
d88feee1c0 | ||
|
|
5963dc9e0e | ||
|
|
8363f25d47 | ||
|
|
72b2a4d7c0 | ||
|
|
06daf2a9b5 | ||
|
|
ad95d2e06f | ||
|
|
5197e6b79f | ||
|
|
b99ce3845e | ||
|
|
d63955eccd | ||
|
|
2c4fc4cbe8 | ||
|
|
7d3c31b6d8 | ||
|
|
151f83db6d | ||
|
|
22838ce2d8 | ||
|
|
b058bb6e15 | ||
|
|
36b04b5dea | ||
|
|
a7296ecb31 | ||
|
|
92917ceb2f | ||
|
|
c1c5b10082 | ||
|
|
f44de0c7ae | ||
|
|
b18cfe6bdb | ||
|
|
a8ec9228d4 | ||
|
|
c1b85554a8 | ||
|
|
b877c81c32 | ||
|
|
e66da4c8c7 | ||
|
|
573e044b30 | ||
|
|
ceb43c7aa4 | ||
|
|
eae1ecb77e | ||
|
|
c4de6c402b | ||
|
|
0b147c9386 | ||
|
|
6982c8a116 | ||
|
|
d5f39c8dce | ||
|
|
a5d18d62df | ||
|
|
a27f704c25 | ||
|
|
9216485bca | ||
|
|
c33acb905b | ||
|
|
81c3521610 | ||
|
|
e0ee9dc3ad | ||
|
|
cb546f0cd8 | ||
|
|
a60186db2f | ||
|
|
d9f87a5721 | ||
|
|
a7fdb2c3d3 | ||
|
|
5efd303b7f | ||
|
|
0d3980d039 | ||
|
|
ab6244d81c | ||
|
|
7fcf4ba60f | ||
|
|
f048cbbb11 |
@@ -24,6 +24,7 @@ AttributeMacros:
|
||||
BinPackArguments: false
|
||||
BinPackParameters: false
|
||||
BitFieldColonSpacing: Both
|
||||
BreakAfterAttributes: Always
|
||||
BreakBeforeBinaryOperators: NonAssignment
|
||||
BreakBeforeBraces: Attach
|
||||
BreakBeforeConceptDeclarations: true
|
||||
@@ -54,12 +55,14 @@ IncludeCategories:
|
||||
IndentAccessModifiers: false
|
||||
IndentCaseBlocks: false
|
||||
IndentCaseLabels: false
|
||||
IndentExternBlock: NoIndent
|
||||
IndentExternBlock: Indent
|
||||
IndentGotoLabels: false
|
||||
IndentPPDirectives: BeforeHash
|
||||
IndentRequires: true
|
||||
IndentWidth: 4
|
||||
IndentWrappedFunctionNames: true
|
||||
InsertBraces: true
|
||||
InsertNewlineAtEOF: true
|
||||
# Only support for Javascript as of clang-format 14...
|
||||
# InsertTrailingCommas: Wrapped
|
||||
KeepEmptyLinesAtTheStartOfBlocks: false
|
||||
@@ -71,6 +74,8 @@ PPIndentWidth: -1
|
||||
PointerAlignment: Right
|
||||
QualifierAlignment: Right
|
||||
ReflowComments: true
|
||||
RemoveParentheses: ReturnStatement
|
||||
RemoveSemicolon: true
|
||||
SortIncludes: CaseSensitive
|
||||
SortUsingDeclarations: true
|
||||
SpaceAfterCStyleCast: false
|
||||
|
||||
4
.github/scripts/build_libpng.sh
vendored
4
.github/scripts/build_libpng.sh
vendored
@@ -1,13 +1,13 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
pngver=1.6.43
|
||||
pngver=1.6.45
|
||||
|
||||
## Grab sources and check them
|
||||
|
||||
curl -LOJ "http://prdownloads.sourceforge.net/libpng/libpng-$pngver.tar.xz?download"
|
||||
# Brew doesn't provide any sha256sum, so we're making do with `sha2` instead.
|
||||
if [ "$(sha2 -q -256 libpng-$pngver.tar.xz)" != 6a5ca0652392a2d7c9db2ae5b40210843c0bbc081cbd410825ab00cc59f14a6c ]; then
|
||||
if [ "$(sha2 -q -256 libpng-$pngver.tar.xz)" != 926485350139ffb51ef69760db35f78846c805fef3d59bfdcb2fba704663f370 ]; then
|
||||
sha2 -256 libpng-$pngver.tar.xz
|
||||
echo Checksum mismatch! Aborting. >&2
|
||||
exit 1
|
||||
|
||||
4
.github/scripts/get_win_deps.ps1
vendored
4
.github/scripts/get_win_deps.ps1
vendored
@@ -16,8 +16,8 @@ function getlibrary ([string] $URI, [string] $filename, [string] $hash, [string]
|
||||
}
|
||||
|
||||
getlibrary 'https://www.zlib.net/zlib131.zip' 'zlib.zip' '72af66d44fcc14c22013b46b814d5d2514673dda3d115e64b690c1ad636e7b17' .
|
||||
getlibrary 'https://github.com/glennrp/libpng/archive/refs/tags/v1.6.43.zip' 'libpng.zip' '5e18474a26814ae479e02ca6432da32d19dc6e615551d140c954a68d63b3f192' .
|
||||
getlibrary 'https://github.com/glennrp/libpng/archive/refs/tags/v1.6.45.zip' 'libpng.zip' '1b3d94b2f1d137db1bf1842cb9f03df179772a517f7b86e26351742190632785' .
|
||||
getlibrary 'https://github.com/lexxmark/winflexbison/releases/download/v2.5.25/win_flex_bison-2.5.25.zip' 'winflexbison.zip' '8d324b62be33604b2c45ad1dd34ab93d722534448f55a16ca7292de32b6ac135' install_dir
|
||||
|
||||
Move-Item zlib-1.3.1 zlib
|
||||
Move-Item libpng-1.6.43 libpng
|
||||
Move-Item libpng-1.6.45 libpng
|
||||
|
||||
2
.github/scripts/install.sh
vendored
2
.github/scripts/install.sh
vendored
@@ -3,5 +3,5 @@
|
||||
install -d /usr/local/bin/ /usr/local/share/man/man1/ /usr/local/share/man/man5/ /usr/local/share/man/man7/
|
||||
install -s -m 755 rgbasm rgblink rgbfix rgbgfx /usr/local/bin/
|
||||
install -m 644 rgbasm.1 rgblink.1 rgbfix.1 rgbgfx.1 /usr/local/share/man/man1/
|
||||
install -m 644 rgbds.5 rgbasm.5 rgblink.5 /usr/local/share/man/man5/
|
||||
install -m 644 rgbds.5 rgbasm.5 rgblink.5 rgbasm-old.5 /usr/local/share/man/man5/
|
||||
install -m 644 rgbds.7 gbz80.7 /usr/local/share/man/man7/
|
||||
|
||||
2
.github/scripts/mingw-w64-libpng-dev.sh
vendored
2
.github/scripts/mingw-w64-libpng-dev.sh
vendored
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
pngver=1.6.43
|
||||
pngver=1.6.45
|
||||
arch="$1"
|
||||
|
||||
## Grab sources and check them
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
d6bd2a3f43f17020918a4c1bd81c1a78111b6f759af9c1d3c754f704a1bf0429 libpng-1.6.43-apng.patch.gz
|
||||
6a5ca0652392a2d7c9db2ae5b40210843c0bbc081cbd410825ab00cc59f14a6c libpng-1.6.43.tar.xz
|
||||
f7caa3b55f003ce23d6a087b1c2a643262647bfd1a1b31afdc9b18eabf1bbc7e libpng-1.6.45-apng.patch.gz
|
||||
926485350139ffb51ef69760db35f78846c805fef3d59bfdcb2fba704663f370 libpng-1.6.45.tar.xz
|
||||
|
||||
18
.github/workflows/analysis.yml
vendored
Normal file
18
.github/workflows/analysis.yml
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
name: Static analysis
|
||||
on:
|
||||
- push
|
||||
- pull_request
|
||||
|
||||
jobs:
|
||||
analysis:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
- name: Install deps
|
||||
shell: bash
|
||||
run: |
|
||||
./.github/scripts/install_deps.sh ubuntu-latest
|
||||
- name: Static analysis
|
||||
run: | # Silence warnings with too many false positives (https://stackoverflow.com/a/73913076)
|
||||
make -kj CXX=g++-14 CXXFLAGS="-fanalyzer -fanalyzer-verbosity=0 -Wno-analyzer-use-of-uninitialized-value -DNDEBUG" Q=
|
||||
21
.github/workflows/build-container.yml
vendored
21
.github/workflows/build-container.yml
vendored
@@ -1,18 +1,16 @@
|
||||
name: Build container image
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
tags:
|
||||
- '*' # This triggers the action on all tag pushes
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
publish-docker-image:
|
||||
if: github.repository_owner == 'gbdev'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
# So that the workflow can write to the ghcr an upload there
|
||||
packages: write
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
@@ -24,21 +22,30 @@ jobs:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
|
||||
- name: Build and push the master container image
|
||||
# When a commit is pushed to master
|
||||
if: github.ref == 'refs/heads/master'
|
||||
run: |
|
||||
COMMIT_HASH=$(git rev-parse --short HEAD)
|
||||
sed -i "2i LABEL org.opencontainers.image.description=\"RGBDS container image, containing the git version master:$COMMIT_HASH\"" Dockerfile
|
||||
docker build . --tag ghcr.io/gbdev/rgbds:master
|
||||
docker push ghcr.io/gbdev/rgbds:master
|
||||
|
||||
|
||||
- name: Build and push the version-tagged container image
|
||||
# When a tag is pushed
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
run: |
|
||||
TAG_NAME=${GITHUB_REF#refs/tags/}
|
||||
sed -i "2i LABEL org.opencontainers.image.description=\"RGBDS container image for the release version $TAG_NAME\"" Dockerfile
|
||||
docker build . --tag ghcr.io/gbdev/rgbds:$TAG_NAME
|
||||
docker push ghcr.io/gbdev/rgbds:$TAG_NAME
|
||||
|
||||
- name: Delete untagged container images
|
||||
if: github.repository_owner == 'gbdev'
|
||||
uses: Chizkiyahu/delete-untagged-ghcr-action@v5
|
||||
with:
|
||||
# Requires a personal access token with delete:packages permissions
|
||||
token: ${{ secrets.PAT_TOKEN }}
|
||||
package_name: 'rgbds'
|
||||
untagged_only: true
|
||||
except_untagged_multiplatform: true
|
||||
owner_type: 'org'
|
||||
|
||||
2
.github/workflows/checkdiff.yml
vendored
2
.github/workflows/checkdiff.yml
vendored
@@ -11,7 +11,7 @@ jobs:
|
||||
cd rgbds
|
||||
git remote add upstream "${{ github.event.pull_request.base.repo.clone_url }}"
|
||||
git fetch upstream
|
||||
- name: Checkdiff
|
||||
- name: Check diff
|
||||
working-directory: rgbds
|
||||
run: |
|
||||
make checkdiff "BASE_REF=${{ github.event.pull_request.base.sha }}" Q= | tee log
|
||||
|
||||
12
.github/workflows/checkformat.yml
vendored
Normal file
12
.github/workflows/checkformat.yml
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
name: Code format checking
|
||||
on: pull_request
|
||||
|
||||
jobs:
|
||||
checkformat:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
- name: Check format
|
||||
run: |
|
||||
./contrib/checkformat.bash
|
||||
@@ -100,7 +100,7 @@ jobs:
|
||||
path: rgbds-${{ env.version }}-macos.zip
|
||||
|
||||
linux:
|
||||
runs-on: ubuntu-20.04 # Oldest supported, for best glibc compatibility.
|
||||
runs-on: ubuntu-22.04 # Oldest supported, for best glibc compatibility.
|
||||
steps:
|
||||
- name: Get version from tag
|
||||
shell: bash
|
||||
@@ -112,7 +112,7 @@ jobs:
|
||||
- name: Install deps
|
||||
shell: bash
|
||||
run: |
|
||||
./.github/scripts/install_deps.sh ubuntu-20.04
|
||||
./.github/scripts/install_deps.sh ubuntu-22.04
|
||||
- name: Build binaries
|
||||
run: |
|
||||
make -kj WARNFLAGS="-Wall -Wextra -pedantic -static" PKG_CONFIG="pkg-config --static" Q=
|
||||
|
||||
68
.github/workflows/testing.yml
vendored
68
.github/workflows/testing.yml
vendored
@@ -7,7 +7,7 @@ jobs:
|
||||
unix:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-20.04, ubuntu-22.04, macos-14]
|
||||
os: [ubuntu-22.04, macos-14]
|
||||
cxx: [g++, clang++]
|
||||
buildsys: [make, cmake]
|
||||
exclude:
|
||||
@@ -320,3 +320,69 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
test/run-tests.sh
|
||||
|
||||
cygwin:
|
||||
strategy:
|
||||
matrix:
|
||||
bits: [32, 64]
|
||||
include:
|
||||
- bits: 32
|
||||
arch: x86
|
||||
- bits: 64
|
||||
arch: x86_64
|
||||
fail-fast: false
|
||||
runs-on: windows-2019
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
fetch-tags: true
|
||||
- name: Setup Cygwin
|
||||
uses: cygwin/cygwin-install-action@v4
|
||||
with:
|
||||
platform: ${{ matrix.arch }}
|
||||
packages: >-
|
||||
bison
|
||||
gcc-g++
|
||||
git
|
||||
libpng-devel
|
||||
make
|
||||
pkg-config
|
||||
- name: Build & install using Make
|
||||
shell: C:\cygwin\bin\env.exe CYGWIN_NOWINPATH=1 CHERE_INVOKING=1 C:\cygwin\bin\bash.exe -o igncr '{0}'
|
||||
run: | # Cygwin does not support `make develop` sanitizers ASan or UBSan
|
||||
make -kj Q=
|
||||
make install -j Q=
|
||||
- name: Run tests
|
||||
shell: C:\cygwin\bin\env.exe CYGWIN_NOWINPATH=1 CHERE_INVOKING=1 C:\cygwin\bin\bash.exe -o igncr '{0}'
|
||||
run: |
|
||||
test/run-tests.sh --only-internal
|
||||
|
||||
freebsd:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
fetch-tags: true
|
||||
- name: Build & test using CMake on FreeBSD
|
||||
uses: vmactions/freebsd-vm@v1
|
||||
with:
|
||||
release: "15.0"
|
||||
usesh: true
|
||||
prepare: |
|
||||
pkg install -y \
|
||||
bash \
|
||||
bison \
|
||||
cmake \
|
||||
git \
|
||||
png
|
||||
run: | # FreeBSD `c++` compiler does not support `make develop` sanitizers ASan or UBSan
|
||||
cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_COMPILER=c++ -DUSE_EXTERNAL_TESTS=OFF
|
||||
cmake --build build -j4 --verbose
|
||||
cmake --install build --verbose
|
||||
cmake --build build --target test
|
||||
|
||||
1
.github/workflows/update-master-docs.yml
vendored
1
.github/workflows/update-master-docs.yml
vendored
@@ -9,6 +9,7 @@ on:
|
||||
- man/rgbds.7
|
||||
- man/rgbasm.1
|
||||
- man/rgbasm.5
|
||||
- man/rgbasm-old.5
|
||||
- man/rgblink.1
|
||||
- man/rgblink.5
|
||||
- man/rgbfix.1
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -14,4 +14,5 @@ CMakeCache.txt
|
||||
CMakeFiles/
|
||||
cmake_install.cmake
|
||||
build/
|
||||
*.dSYM/
|
||||
callgrind.out.*
|
||||
|
||||
@@ -45,25 +45,21 @@ else()
|
||||
add_compile_options(-Wno-gnu-zero-variadic-macro-arguments)
|
||||
endif()
|
||||
if(SANITIZERS)
|
||||
set(SAN_FLAGS -fsanitize=shift -fsanitize=integer-divide-by-zero
|
||||
-fsanitize=unreachable -fsanitize=vla-bound
|
||||
-fsanitize=signed-integer-overflow -fsanitize=bounds
|
||||
-fsanitize=object-size -fsanitize=bool -fsanitize=enum
|
||||
-fsanitize=alignment -fsanitize=null -fsanitize=address)
|
||||
set(SAN_FLAGS -fsanitize=address -fsanitize=undefined
|
||||
-fsanitize=float-divide-by-zero)
|
||||
add_compile_options(${SAN_FLAGS})
|
||||
add_link_options(${SAN_FLAGS})
|
||||
add_definitions(-D_GLIBCXX_ASSERTIONS)
|
||||
# A non-zero optimization level is desired in debug mode, but allow overriding it nonetheless
|
||||
# TODO: this overrides anything previously set... that's a bit sloppy!
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "-g -Og -fno-omit-frame-pointer -fno-optimize-sibling-calls" CACHE STRING "" FORCE)
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "-g -Og -fno-omit-frame-pointer -fno-optimize-sibling-calls ${CMAKE_CXX_FLAGS_DEBUG}"
|
||||
CACHE STRING "" FORCE)
|
||||
endif()
|
||||
|
||||
if(MORE_WARNINGS)
|
||||
add_compile_options(-Werror -Wextra
|
||||
-Walloc-zero -Wcast-align -Wcast-qual -Wduplicated-branches -Wduplicated-cond
|
||||
-Wfloat-equal -Wlogical-op -Wnull-dereference -Wshift-overflow=2
|
||||
-Wstringop-overflow=4 -Wundef -Wuninitialized -Wunused
|
||||
-Wshadow # TODO: -Wshadow=compatible-local?
|
||||
-Wfloat-equal -Wlogical-op -Wnull-dereference -Wold-style-cast -Wshift-overflow=2
|
||||
-Wstringop-overflow=4 -Wtrampolines -Wundef -Wuninitialized -Wunused -Wshadow
|
||||
-Wformat=2 -Wformat-overflow=2 -Wformat-truncation=1
|
||||
-Wno-format-nonliteral -Wno-strict-overflow
|
||||
-Wno-unused-but-set-variable # bison's `yynerrs_` is incremented but unused
|
||||
@@ -78,7 +74,7 @@ endif()
|
||||
|
||||
find_program(GIT git)
|
||||
if(GIT)
|
||||
execute_process(COMMAND ${GIT} --git-dir=.git describe --tags --dirty --always
|
||||
execute_process(COMMAND ${GIT} --git-dir=.git -c safe.directory='*' describe --tags --dirty --always
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||
OUTPUT_VARIABLE GIT_REV OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
ERROR_QUIET)
|
||||
@@ -126,6 +122,7 @@ set(man1 "man/rgbasm.1"
|
||||
"man/rgbgfx.1"
|
||||
"man/rgblink.1")
|
||||
set(man5 "man/rgbasm.5"
|
||||
"man/rgbasm-old.5"
|
||||
"man/rgblink.5"
|
||||
"man/rgbds.5")
|
||||
set(man7 "man/gbz80.7"
|
||||
|
||||
@@ -174,3 +174,17 @@ Each one is a binary RNG file which is passed to the `rgbgfx_test` program.
|
||||
```sh
|
||||
test_downstream <owner> <repo> <makefile target> <build file> <sha1 hash of build file>
|
||||
```
|
||||
|
||||
## Container images
|
||||
|
||||
The CI will [take care](https://github.com/gbdev/rgbds/blob/master/.github/workflows/build-container.yml) of updating the [rgbds container](https://github.com/gbdev/rgbds/pkgs/container/rgbds) image tagged `master`.
|
||||
|
||||
When a git tag is pushed, the image is also tagged with that tag.
|
||||
|
||||
The image can be built locally and pushed to the GitHub container registry by manually running:
|
||||
|
||||
```bash
|
||||
# e.g. to build and tag as 'master'
|
||||
docker build . --tag ghcr.io/gbdev/rgbds:master
|
||||
docker push ghcr.io/gbdev/rgbds:master
|
||||
```
|
||||
@@ -1,6 +1,6 @@
|
||||
FROM debian:11-slim
|
||||
FROM debian:12-slim
|
||||
LABEL org.opencontainers.image.source=https://github.com/gbdev/rgbds
|
||||
ARG version=0.9.0-rc2
|
||||
ARG version=0.9.2
|
||||
WORKDIR /rgbds
|
||||
|
||||
COPY . .
|
||||
@@ -8,7 +8,7 @@ COPY . .
|
||||
RUN apt-get update && \
|
||||
apt-get install sudo make cmake gcc build-essential -y
|
||||
|
||||
RUN ./.github/scripts/install_deps.sh ubuntu-20.04
|
||||
RUN ./.github/scripts/install_deps.sh ubuntu-22.04
|
||||
RUN make -j CXXFLAGS="-O3 -flto -DNDEBUG -static" PKG_CONFIG="pkg-config --static" Q=
|
||||
|
||||
RUN tar caf rgbds-${version}-linux-x86_64.tar.xz --transform='s#.*/##' rgbasm rgblink rgbfix rgbgfx man/* .github/scripts/install.sh
|
||||
|
||||
58
Makefile
58
Makefile
@@ -7,43 +7,43 @@
|
||||
|
||||
# User-defined variables
|
||||
|
||||
Q := @
|
||||
PREFIX := /usr/local
|
||||
bindir := ${PREFIX}/bin
|
||||
mandir := ${PREFIX}/share/man
|
||||
SUFFIX :=
|
||||
STRIP := -s
|
||||
BINMODE := 755
|
||||
MANMODE := 644
|
||||
Q := @
|
||||
PREFIX := /usr/local
|
||||
bindir := ${PREFIX}/bin
|
||||
mandir := ${PREFIX}/share/man
|
||||
SUFFIX :=
|
||||
STRIP := -s
|
||||
BINMODE := 755
|
||||
MANMODE := 644
|
||||
|
||||
# Other variables
|
||||
|
||||
PKG_CONFIG := pkg-config
|
||||
PNGCFLAGS := `${PKG_CONFIG} --cflags libpng`
|
||||
PNGLDFLAGS := `${PKG_CONFIG} --libs-only-L libpng`
|
||||
PNGLDLIBS := `${PKG_CONFIG} --libs-only-l libpng`
|
||||
PKG_CONFIG := pkg-config
|
||||
PNGCFLAGS := `${PKG_CONFIG} --cflags libpng`
|
||||
PNGLDFLAGS := `${PKG_CONFIG} --libs-only-L libpng`
|
||||
PNGLDLIBS := `${PKG_CONFIG} --libs-only-l libpng`
|
||||
|
||||
# Note: if this comes up empty, `version.cpp` will automatically fall back to last release number
|
||||
VERSION_STRING := `git --git-dir=.git describe --tags --dirty --always 2>/dev/null`
|
||||
VERSION_STRING := `git --git-dir=.git -c safe.directory='*' describe --tags --dirty --always 2>/dev/null`
|
||||
|
||||
WARNFLAGS := -Wall -pedantic -Wno-unknown-warning-option -Wno-gnu-zero-variadic-macro-arguments
|
||||
WARNFLAGS := -Wall -pedantic -Wno-unknown-warning-option -Wno-gnu-zero-variadic-macro-arguments
|
||||
|
||||
# Overridable CXXFLAGS
|
||||
CXXFLAGS ?= -O3 -flto -DNDEBUG
|
||||
CXXFLAGS ?= -O3 -flto -DNDEBUG
|
||||
# Non-overridable CXXFLAGS
|
||||
REALCXXFLAGS := ${CXXFLAGS} ${WARNFLAGS} -std=c++2a -I include -fno-exceptions -fno-rtti
|
||||
REALCXXFLAGS := ${CXXFLAGS} ${WARNFLAGS} -std=c++2a -I include -fno-exceptions -fno-rtti
|
||||
# Overridable LDFLAGS
|
||||
LDFLAGS ?=
|
||||
LDFLAGS ?=
|
||||
# Non-overridable LDFLAGS
|
||||
REALLDFLAGS := ${LDFLAGS} ${WARNFLAGS} -DBUILD_VERSION_STRING=\"${VERSION_STRING}\"
|
||||
REALLDFLAGS := ${LDFLAGS} ${WARNFLAGS} -DBUILD_VERSION_STRING=\"${VERSION_STRING}\"
|
||||
|
||||
# Wrapper around bison that passes flags depending on what the version supports
|
||||
BISON := src/bison.sh
|
||||
BISON := src/bison.sh
|
||||
|
||||
RM := rm -rf
|
||||
RM := rm -rf
|
||||
|
||||
# Used for checking pull requests
|
||||
BASE_REF := origin/master
|
||||
BASE_REF := origin/master
|
||||
|
||||
# Rules to build the RGBDS binaries
|
||||
|
||||
@@ -185,7 +185,7 @@ install: all
|
||||
$Qinstall ${STRIP} -m ${BINMODE} rgbfix ${DESTDIR}${bindir}/rgbfix${SUFFIX}
|
||||
$Qinstall ${STRIP} -m ${BINMODE} rgbgfx ${DESTDIR}${bindir}/rgbgfx${SUFFIX}
|
||||
$Qinstall -m ${MANMODE} man/rgbasm.1 man/rgblink.1 man/rgbfix.1 man/rgbgfx.1 ${DESTDIR}${mandir}/man1/
|
||||
$Qinstall -m ${MANMODE} man/rgbds.5 man/rgbasm.5 man/rgblink.5 ${DESTDIR}${mandir}/man5/
|
||||
$Qinstall -m ${MANMODE} man/rgbds.5 man/rgbasm.5 man/rgbasm-old.5 man/rgblink.5 ${DESTDIR}${mandir}/man5/
|
||||
$Qinstall -m ${MANMODE} man/rgbds.7 man/gbz80.7 ${DESTDIR}${mandir}/man7/
|
||||
|
||||
# Target used to check for suspiciously missing changed files.
|
||||
@@ -201,17 +201,13 @@ checkdiff:
|
||||
develop:
|
||||
$Q${MAKE} WARNFLAGS="${WARNFLAGS} -Werror -Wextra \
|
||||
-Walloc-zero -Wcast-align -Wcast-qual -Wduplicated-branches -Wduplicated-cond \
|
||||
-Wfloat-equal -Wlogical-op -Wnull-dereference -Wshift-overflow=2 \
|
||||
-Wstringop-overflow=4 -Wundef -Wuninitialized -Wunused -Wshadow \
|
||||
-Wfloat-equal -Wlogical-op -Wnull-dereference -Wold-style-cast -Wshift-overflow=2 \
|
||||
-Wstringop-overflow=4 -Wtrampolines -Wundef -Wuninitialized -Wunused -Wshadow \
|
||||
-Wformat=2 -Wformat-overflow=2 -Wformat-truncation=1 \
|
||||
-Wno-format-nonliteral -Wno-strict-overflow -Wno-unused-but-set-variable \
|
||||
-Wno-type-limits -Wno-tautological-constant-out-of-range-compare -Wvla \
|
||||
-D_GLIBCXX_ASSERTIONS \
|
||||
-fsanitize=shift -fsanitize=integer-divide-by-zero \
|
||||
-fsanitize=unreachable -fsanitize=vla-bound \
|
||||
-fsanitize=signed-integer-overflow -fsanitize=bounds \
|
||||
-fsanitize=object-size -fsanitize=bool -fsanitize=enum \
|
||||
-fsanitize=alignment -fsanitize=null -fsanitize=address" \
|
||||
-D_GLIBCXX_ASSERTIONS -fsanitize=address -fsanitize=undefined \
|
||||
-fsanitize=float-divide-by-zero" \
|
||||
CXXFLAGS="-ggdb3 -Og -fno-omit-frame-pointer -fno-optimize-sibling-calls"
|
||||
|
||||
# This target is used during development in order to more easily debug with gdb.
|
||||
@@ -269,4 +265,4 @@ wine-shim:
|
||||
|
||||
dist:
|
||||
$Qgit ls-files | sed s~^~$${PWD##*/}/~ \
|
||||
| tar -czf rgbds-`git describe --tags | cut -c 2-`.tar.gz -C .. -T -
|
||||
| tar -czf rgbds-`git -c safe.directory='*' describe --tags | cut -c 2-`.tar.gz -C .. -T -
|
||||
|
||||
@@ -137,9 +137,12 @@ The RGBDS source code file structure is as follows:
|
||||
this version as [rgbds-linux](https://github.com/vegard/rgbds-linux).
|
||||
- 2010-01-12: Anthony J. Bentley [forks](https://github.com/bentley) Nossum's
|
||||
repository. The fork becomes the reference implementation of RGBDS.
|
||||
- 2010-09-25: Sørensen continues development of
|
||||
[ASMotor](https://github.com/asmotor/asmotor) to this day.
|
||||
- 2015-01-18: stag019 begins implementing [RGBGFX](https://github.com/stag019/rgbgfx),
|
||||
a PNG‐to‐Game Boy graphics converter, for eventual integration into RGBDS.
|
||||
- 2016-09-05: RGBGFX is [integrated](https://github.com/gbdev/rgbds/commit/c3c31138ddbd8680d4e67957e387f2816798a71b)
|
||||
- 2016-09-05: RGBGFX is
|
||||
[integrated](https://github.com/gbdev/rgbds/commit/c3c31138ddbd8680d4e67957e387f2816798a71b)
|
||||
into Bentley's repository.
|
||||
- 2017-02-23: Bentley's repository is moved to the [rednex](https://github.com/rednex)
|
||||
organization.
|
||||
|
||||
26
RELEASE.md
26
RELEASE.md
@@ -5,11 +5,13 @@ GitHub.
|
||||
|
||||
1. Update the following files, then commit and push.
|
||||
You can use <code>git commit -m "Release <i><version></i>"</code> and `git push origin master`.
|
||||
|
||||
- [include/version.hpp](include/version.hpp): set appropriate values for `PACKAGE_VERSION_MAJOR`, `PACKAGE_VERSION_MINOR`, `PACKAGE_VERSION_PATCH`, and `PACKAGE_VERSION_RC`
|
||||
|
||||
- [include/version.hpp](include/version.hpp): set appropriate values for `PACKAGE_VERSION_MAJOR`,
|
||||
`PACKAGE_VERSION_MINOR`, `PACKAGE_VERSION_PATCH`, and `PACKAGE_VERSION_RC`.
|
||||
**Only** define `PACKAGE_VERSION_RC` if you are publishing a release candidate!
|
||||
- [Dockerfile](Dockerfile): update `ARG version`.
|
||||
- [test/fetch-test-deps.sh](test/fetch-test-deps.sh): update test dependency commits (preferably, use the latest available).
|
||||
- [test/fetch-test-deps.sh](test/fetch-test-deps.sh): update test dependency commits
|
||||
(preferably, use the latest available).
|
||||
- [man/\*](man/): update dates and authors.
|
||||
|
||||
2. Create a Git tag formatted as <code>v<i><MAJOR></i>.<i><MINOR></i>.<i><PATCH></i></code>,
|
||||
@@ -55,8 +57,8 @@ GitHub.
|
||||
|
||||
If you do not have `groff` installed, you can change
|
||||
`groff -Tpdf -mdoc -wall` to `mandoc -Tpdf -I os=Linux` in
|
||||
[.github/actions/get-pages.sh](.github/actions/get-pages.sh) and it
|
||||
will suffice.
|
||||
[maintainer/man_to_html.sh](https://github.com/gbdev/rgbds-www/blob/master/maintainer/man_to_html.sh)
|
||||
and it will suffice.
|
||||
|
||||
4. Commit and push the documentation. You can use <code>git commit -m
|
||||
"Create RGBDS <i><tag></i> documentation"</code> and `git push origin master`
|
||||
@@ -67,3 +69,17 @@ GitHub.
|
||||
6. Click the "Publish release" button to publish it!
|
||||
|
||||
7. Update the `release` branch. You can use `git push origin release`.
|
||||
|
||||
8. Update the following related projects.
|
||||
|
||||
1. [rgbds-www](https://github.com/gbdev/rgbds-www): update
|
||||
[src/pages/versions.mdx](https://github.com/gbdev/rgbds-www/blob/master/src/pages/versions.mdx)
|
||||
to list the new release.
|
||||
2. [rgbds-live](https://github.com/gbdev/rgbds-live): update the `rgbds` submodule (and
|
||||
[patches/rgbds.patch](https://github.com/gbdev/rgbds-live/blob/master/patches/rgbds.patch)
|
||||
if necessary) to use the new release, and
|
||||
[index.html](https://github.com/gbdev/rgbds-live/blob/master/index.html)
|
||||
to link to the new manual version.
|
||||
3. [rgbobj](https://github.com/gbdev/rgbobj) and [rgbds-obj](https://github.com/gbdev/rgbds-obj):
|
||||
make sure that object files created by the latest RGBASM can be parsed and displayed.
|
||||
If the object file revision has been updated, `rgbobj` will need a corresponding release.
|
||||
|
||||
@@ -25,6 +25,7 @@ _rgbasm_completions() {
|
||||
# See the `state` variable below for info about `state_after`
|
||||
declare -A opts=(
|
||||
[V]="version:normal"
|
||||
[h]="help:normal"
|
||||
[E]="export-all:normal"
|
||||
[v]="verbose:normal"
|
||||
[w]=":normal"
|
||||
@@ -192,6 +193,7 @@ _rgbasm_completions() {
|
||||
shift-amount
|
||||
truncation
|
||||
unmapped-char
|
||||
unmatched-directive
|
||||
unterminated-load
|
||||
user
|
||||
all
|
||||
|
||||
@@ -8,6 +8,7 @@ _rgbfix_completions() {
|
||||
# See the `state` variable below for info about `state_after`
|
||||
declare -A opts=(
|
||||
[V]="version:normal"
|
||||
[h]="help:normal"
|
||||
[j]="non-japanese:normal"
|
||||
[s]="sgb-compatible:normal"
|
||||
[v]="validate:normal"
|
||||
@@ -20,6 +21,7 @@ _rgbfix_completions() {
|
||||
[l]="old-licensee:unk"
|
||||
[m]="mbc-type:mbc"
|
||||
[n]="rom-version:unk"
|
||||
[o]="output:glob-*.gb *.gbc *.sgb"
|
||||
[p]="pad-value:unk"
|
||||
[r]="ram-size:unk"
|
||||
[t]="title:unk"
|
||||
|
||||
@@ -8,6 +8,7 @@ _rgbgfx_completions() {
|
||||
# See the `state` variable below for info about `state_after`
|
||||
declare -A opts=(
|
||||
[V]="version:normal"
|
||||
[h]="help:normal"
|
||||
[C]="color-curve:normal"
|
||||
[m]="mirror-tiles:normal"
|
||||
[O]="group-outputs:normal"
|
||||
@@ -18,6 +19,7 @@ _rgbgfx_completions() {
|
||||
[Z]="columns:normal"
|
||||
[a]="attr-map:glob-*.attrmap"
|
||||
[A]="auto-attr-map:normal"
|
||||
[B]="background-color:unk"
|
||||
[b]="base-tiles:unk"
|
||||
[c]="colors:unk"
|
||||
[d]="depth:unk"
|
||||
|
||||
@@ -8,6 +8,7 @@ _rgblink_completions() {
|
||||
# See the `state` variable below for info about `state_after`
|
||||
declare -A opts=(
|
||||
[V]="version:normal"
|
||||
[h]="help:normal"
|
||||
[d]="dmg:normal"
|
||||
[t]="tiny:normal"
|
||||
[v]="verbose:normal"
|
||||
|
||||
15
contrib/checkformat.bash
Executable file
15
contrib/checkformat.bash
Executable file
@@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
clang-format --version
|
||||
|
||||
find . -type f \( -iname '*.hpp' -o -iname '*.cpp' \) -exec clang-format -i {} +
|
||||
|
||||
if ! git diff-index --quiet HEAD --; then
|
||||
echo 'Unformatted files:'
|
||||
git diff-index --name-only HEAD --
|
||||
echo
|
||||
git diff HEAD --
|
||||
return 1
|
||||
fi
|
||||
@@ -32,14 +32,15 @@ diff <(xxd "$1") <(xxd "$2") | while read -r LINE; do
|
||||
# Ignore comment lines, only pick matching bank
|
||||
# (The bank regex ignores comments already, make `cut` and `tr` process less lines)
|
||||
grep -Ei "$(printf "^%02x:" $BANK)" "$SYMFILE" |
|
||||
sed "s/$(printf "^%02x:" $BANK)/0x/g" |
|
||||
cut -d ';' -f 1 |
|
||||
tr -d "\r" |
|
||||
sort -g |
|
||||
while read -r SYMADDR SYM; do
|
||||
SYMADDR=$((0x${SYMADDR#*:}))
|
||||
SYMADDR=$(($SYMADDR))
|
||||
if [[ $SYMADDR -le $ADDR ]]; then
|
||||
printf " (%s+0x%x)\n" "$SYM" $((ADDR - SYMADDR))
|
||||
fi
|
||||
# TODO: assumes sorted sym files
|
||||
done | tail -n 1
|
||||
fi)
|
||||
printf "%02x:%04x %s\n" $BANK $ADDR "$EXTRA"
|
||||
|
||||
@@ -26,6 +26,7 @@ _rgbasm_warnings() {
|
||||
'shift-amount:Warn when a shift'\''s operand it negative or \> 32'
|
||||
'truncation:Warn when implicit truncation loses bits'
|
||||
'unmapped-char:Warn on unmapped character'
|
||||
'unmatched-directive:Warn on unmatched directive pair'
|
||||
'unterminated-load:Warn on LOAD without ENDL'
|
||||
'user:Warn when executing the WARN built-in'
|
||||
)
|
||||
@@ -35,8 +36,9 @@ _rgbasm_warnings() {
|
||||
}
|
||||
|
||||
local args=(
|
||||
# Arguments are listed here in the same order as in the manual, except for the version
|
||||
'(- : * options)'{-V,--version}'[Print version number]'
|
||||
# Arguments are listed here in the same order as in the manual, except for the version and help
|
||||
'(- : * options)'{-V,--version}'[Print version number and exit]'
|
||||
'(- : * options)'{-h,--help}'[Print help text and exit]'
|
||||
|
||||
'(-E --export-all)'{-E,--export-all}'[Export all symbols]'
|
||||
'(-v --verbose)'{-v,--verbose}'[Print additional messages regarding progression]'
|
||||
|
||||
@@ -35,8 +35,9 @@ _mbc_names() {
|
||||
}
|
||||
|
||||
local args=(
|
||||
# Arguments are listed here in the same order as in the manual, except for the version
|
||||
'(- : * options)'{-V,--version}'[Print version number]'
|
||||
# Arguments are listed here in the same order as in the manual, except for the version and help
|
||||
'(- : * options)'{-V,--version}'[Print version number and exit]'
|
||||
'(- : * options)'{-h,--help}'[Print help text and exit]'
|
||||
|
||||
'(-C --color-only -c --color-compatible)'{-C,--color-only}'[Mark ROM as GBC-only]'
|
||||
'(-C --color-only -c --color-compatible)'{-c,--color-compatible}'[Mark ROM as GBC-compatible]'
|
||||
@@ -52,6 +53,7 @@ local args=(
|
||||
'(-L --logo)'{-L,--logo}'+[Set custom logo]:1bpp image:'
|
||||
'(-m --mbc-type)'{-m,--mbc-type}"+[Set MBC flags]:mbc name:_mbc_names"
|
||||
'(-n --rom-version)'{-n,--rom-version}'+[Set ROM version]:rom version byte:'
|
||||
'(-o --output)'{-o,--output}"+[Output file]:output file:_files -g '*.{gb,sgb,gbc}'"
|
||||
'(-p --pad-value)'{-p,--pad-value}'+[Pad to next valid size using this byte as padding]:padding byte:'
|
||||
'(-r --ram-size)'{-r,--ram-size}'+[Set RAM size]:ram size byte:'
|
||||
'(-t --title)'{-t,--title}'+[Set title string]:11-char title string:'
|
||||
|
||||
@@ -10,8 +10,9 @@ _depths() {
|
||||
}
|
||||
|
||||
local args=(
|
||||
# Arguments are listed here in the same order as in the manual, except for the version
|
||||
'(- : * options)'{-V,--version}'[Print version number]'
|
||||
# Arguments are listed here in the same order as in the manual, except for the version and help
|
||||
'(- : * options)'{-V,--version}'[Print version number and exit]'
|
||||
'(- : * options)'{-h,--help}'[Print help text and exit]'
|
||||
|
||||
'(-a --attr-map -A --auto-attr-map)'{-A,--auto-attr-map}'[Shortcut for -a <file>.attrmap]'
|
||||
'(-C --color-curve)'{-C,--color-curve}'[Generate palettes using GBC color curve]'
|
||||
@@ -27,6 +28,7 @@ local args=(
|
||||
'(-Z --columns)'{-Z,--columns}'[Read the image in column-major order]'
|
||||
|
||||
'(-a --attr-map -A --auto-attr-map)'{-a,--attr-map}'+[Generate a map of tile attributes (mirroring)]:attrmap file:_files'
|
||||
'(-B --background-color)'{-B,--background-color}'+[Ignore tiles containing only specified color]:color:'
|
||||
'(-b --base-tiles)'{-b,--base-tiles}'+[Base tile IDs for tile map output]:base tile IDs:'
|
||||
'(-c --colors)'{-c,--colors}'+[Specify color palettes]:palette spec:'
|
||||
'(-d --depth)'{-d,--depth}'+[Set bit depth]:bit depth:_depths'
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
#compdef rgblink
|
||||
|
||||
local args=(
|
||||
# Arguments are listed here in the same order as in the manual, except for the version
|
||||
'(- : * options)'{-V,--version}'[Print version number]'
|
||||
# Arguments are listed here in the same order as in the manual, except for the version and help
|
||||
'(- : * options)'{-V,--version}'[Print version number and exit]'
|
||||
'(- : * options)'{-h,--help}'[Print help text and exit]'
|
||||
|
||||
'(-d --dmg)'{-d,--dmg}'[Enable DMG mode (-w + no VRAM banking)]'
|
||||
'(-t --tiny)'{-t,--tiny}'[Enable tiny mode, disabling ROM banking]'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_ASM_CHARMAP_HPP
|
||||
#define RGBDS_ASM_CHARMAP_HPP
|
||||
@@ -18,9 +18,12 @@ void charmap_New(std::string const &name, std::string const *baseName);
|
||||
void charmap_Set(std::string const &name);
|
||||
void charmap_Push();
|
||||
void charmap_Pop();
|
||||
void charmap_CheckStack();
|
||||
void charmap_Add(std::string const &mapping, std::vector<int32_t> &&value);
|
||||
bool charmap_HasChar(std::string const &input);
|
||||
bool charmap_HasChar(std::string const &mapping);
|
||||
size_t charmap_CharSize(std::string const &mapping);
|
||||
std::vector<int32_t> charmap_Convert(std::string const &input);
|
||||
size_t charmap_ConvertNext(std::string_view &input, std::vector<int32_t> *output);
|
||||
std::string charmap_Reverse(std::vector<int32_t> const &value, bool &unique);
|
||||
|
||||
#endif // RGBDS_ASM_CHARMAP_HPP
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_ASM_FIXPOINT_HPP
|
||||
#define RGBDS_ASM_FIXPOINT_HPP
|
||||
@@ -8,7 +8,6 @@
|
||||
extern uint8_t fixPrecision;
|
||||
|
||||
uint8_t fix_Precision();
|
||||
double fix_PrecisionFactor();
|
||||
int32_t fix_Sin(int32_t i, int32_t q);
|
||||
int32_t fix_Cos(int32_t i, int32_t q);
|
||||
int32_t fix_Tan(int32_t i, int32_t q);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_ASM_FORMAT_HPP
|
||||
#define RGBDS_ASM_FORMAT_HPP
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// Contains some assembler-wide defines and externs
|
||||
|
||||
@@ -30,8 +30,8 @@ struct FileStackNode {
|
||||
// Meaningless at the root level, but gets written to the object file anyway, so init it
|
||||
uint32_t lineNo = 0;
|
||||
|
||||
// Set only if referenced: ID within the object file, -1 if not output yet
|
||||
uint32_t ID = -1;
|
||||
// Set only if referenced: ID within the object file, `UINT32_MAX` if not output yet
|
||||
uint32_t ID = UINT32_MAX;
|
||||
|
||||
// REPT iteration counts since last named node, in reverse depth order
|
||||
std::vector<uint32_t> &iters() { return data.get<std::vector<uint32_t>>(); }
|
||||
@@ -41,15 +41,11 @@ struct FileStackNode {
|
||||
std::string const &name() const { return data.get<std::string>(); }
|
||||
|
||||
FileStackNode(FileStackNodeType type_, Either<std::vector<uint32_t>, std::string> data_)
|
||||
: type(type_), data(data_){};
|
||||
: type(type_), data(data_) {}
|
||||
|
||||
std::string const &dump(uint32_t curLineNo) const;
|
||||
|
||||
// If true, entering this context generates a new unique ID.
|
||||
bool generatesUniqueID() const { return type == NODE_REPT || type == NODE_MACRO; }
|
||||
};
|
||||
|
||||
#define DEFAULT_MAX_DEPTH 64
|
||||
extern size_t maxRecursionDepth;
|
||||
|
||||
struct MacroArgs;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_ASM_LEXER_HPP
|
||||
#define RGBDS_ASM_LEXER_HPP
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
// This value is a compromise between `LexerState` allocation performance when `mmap` works, and
|
||||
// buffering performance when it doesn't/can't (e.g. when piping a file into RGBASM).
|
||||
#define LEXER_BUF_SIZE 64
|
||||
static constexpr size_t LEXER_BUF_SIZE = 64;
|
||||
// The buffer needs to be large enough for the maximum `lexerState->peek()` lookahead distance
|
||||
static_assert(LEXER_BUF_SIZE > 1, "Lexer buffer size is too small");
|
||||
// This caps the size of buffer reads, and according to POSIX, passing more than SSIZE_MAX is UB
|
||||
@@ -83,7 +83,6 @@ struct LexerState {
|
||||
LexerMode mode;
|
||||
bool atLineStart;
|
||||
uint32_t lineNo;
|
||||
uint32_t colNo;
|
||||
int lastToken;
|
||||
|
||||
std::deque<IfStackEntry> ifStack;
|
||||
@@ -147,7 +146,6 @@ void lexer_ReachELSEBlock();
|
||||
|
||||
void lexer_CheckRecursionDepth();
|
||||
uint32_t lexer_GetLineNo();
|
||||
uint32_t lexer_GetColNo();
|
||||
void lexer_DumpStringExpansions();
|
||||
|
||||
struct Capture {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_ASM_MACRO_HPP
|
||||
#define RGBDS_ASM_MACRO_HPP
|
||||
@@ -9,11 +9,11 @@
|
||||
#include <vector>
|
||||
|
||||
struct MacroArgs {
|
||||
unsigned int shift;
|
||||
uint32_t shift;
|
||||
std::vector<std::shared_ptr<std::string>> args;
|
||||
|
||||
uint32_t nbArgs() const { return args.size() - shift; }
|
||||
std::shared_ptr<std::string> getArg(uint32_t i) const;
|
||||
std::shared_ptr<std::string> getArg(int32_t i) const;
|
||||
std::shared_ptr<std::string> getAllArgs() const;
|
||||
|
||||
void appendArg(std::shared_ptr<std::string> arg);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_ASM_MAIN_HPP
|
||||
#define RGBDS_ASM_MAIN_HPP
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_ASM_OPT_HPP
|
||||
#define RGBDS_ASM_OPT_HPP
|
||||
@@ -14,5 +14,6 @@ void opt_Parse(char const *option);
|
||||
|
||||
void opt_Push();
|
||||
void opt_Pop();
|
||||
void opt_CheckStack();
|
||||
|
||||
#endif // RGBDS_ASM_OPT_HPP
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_ASM_OUTPUT_HPP
|
||||
#define RGBDS_ASM_OUTPUT_HPP
|
||||
@@ -14,14 +14,7 @@
|
||||
struct Expression;
|
||||
struct FileStackNode;
|
||||
|
||||
enum StateFeature {
|
||||
STATE_EQU,
|
||||
STATE_VAR,
|
||||
STATE_EQUS,
|
||||
STATE_CHAR,
|
||||
STATE_MACRO,
|
||||
NB_STATE_FEATURES
|
||||
};
|
||||
enum StateFeature { STATE_EQU, STATE_VAR, STATE_EQUS, STATE_CHAR, STATE_MACRO, NB_STATE_FEATURES };
|
||||
|
||||
extern std::string objectFileName;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_ASM_RPN_HPP
|
||||
#define RGBDS_ASM_RPN_HPP
|
||||
@@ -22,21 +22,8 @@ struct Expression {
|
||||
std::vector<uint8_t> rpn{}; // Bytes serializing the RPN expression
|
||||
uint32_t rpnPatchSize = 0; // Size the expression will take in the object file
|
||||
|
||||
Expression() = default;
|
||||
Expression(Expression &&) = default;
|
||||
#ifdef _MSC_VER
|
||||
// MSVC and WinFlexBison won't build without this...
|
||||
Expression(Expression const &) = default;
|
||||
#endif
|
||||
|
||||
Expression &operator=(Expression &&) = default;
|
||||
|
||||
bool isKnown() const {
|
||||
return data.holds<int32_t>();
|
||||
}
|
||||
int32_t value() const {
|
||||
return data.get<int32_t>();
|
||||
}
|
||||
bool isKnown() const { return data.holds<int32_t>(); }
|
||||
int32_t value() const { return data.get<int32_t>(); }
|
||||
|
||||
int32_t getConstVal() const;
|
||||
Symbol const *symbolOf() const;
|
||||
@@ -53,8 +40,9 @@ struct Expression {
|
||||
void makeUnaryOp(RPNCommand op, Expression &&src);
|
||||
void makeBinaryOp(RPNCommand op, Expression &&src1, Expression const &src2);
|
||||
|
||||
void makeCheckHRAM();
|
||||
bool makeCheckHRAM();
|
||||
void makeCheckRST();
|
||||
void makeCheckBitIndex(uint8_t mask);
|
||||
|
||||
void checkNBit(uint8_t n) const;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_ASM_SECTION_HPP
|
||||
#define RGBDS_ASM_SECTION_HPP
|
||||
@@ -91,16 +91,17 @@ void sect_ByteString(std::vector<int32_t> const &string);
|
||||
void sect_WordString(std::vector<int32_t> const &string);
|
||||
void sect_LongString(std::vector<int32_t> const &string);
|
||||
void sect_Skip(uint32_t skip, bool ds);
|
||||
void sect_RelByte(Expression &expr, uint32_t pcShift);
|
||||
void sect_RelBytes(uint32_t n, std::vector<Expression> &exprs);
|
||||
void sect_RelWord(Expression &expr, uint32_t pcShift);
|
||||
void sect_RelLong(Expression &expr, uint32_t pcShift);
|
||||
void sect_PCRelByte(Expression &expr, uint32_t pcShift);
|
||||
void sect_RelByte(Expression const &expr, uint32_t pcShift);
|
||||
void sect_RelBytes(uint32_t n, std::vector<Expression> const &exprs);
|
||||
void sect_RelWord(Expression const &expr, uint32_t pcShift);
|
||||
void sect_RelLong(Expression const &expr, uint32_t pcShift);
|
||||
void sect_PCRelByte(Expression const &expr, uint32_t pcShift);
|
||||
void sect_BinaryFile(std::string const &name, int32_t startPos);
|
||||
void sect_BinaryFileSlice(std::string const &name, int32_t startPos, int32_t length);
|
||||
|
||||
void sect_EndSection();
|
||||
void sect_PushSection();
|
||||
void sect_PopSection();
|
||||
void sect_CheckStack();
|
||||
|
||||
#endif // RGBDS_ASM_SECTION_HPP
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_ASM_SYMBOL_HPP
|
||||
#define RGBDS_ASM_SYMBOL_HPP
|
||||
@@ -45,7 +45,7 @@ struct Symbol {
|
||||
>
|
||||
data;
|
||||
|
||||
uint32_t ID; // ID of the symbol in the object file (-1 if none)
|
||||
uint32_t ID; // ID of the symbol in the object file (`UINT32_MAX` if none)
|
||||
uint32_t defIndex; // Ordering of the symbol in the state file
|
||||
|
||||
bool isDefined() const { return type != SYM_REF; }
|
||||
@@ -55,7 +55,7 @@ struct Symbol {
|
||||
bool isConstant() const {
|
||||
if (type == SYM_LABEL) {
|
||||
Section const *sect = getSection();
|
||||
return sect && sect->org != (uint32_t)-1;
|
||||
return sect && sect->org != UINT32_MAX;
|
||||
}
|
||||
return type == SYM_EQU || type == SYM_VAR;
|
||||
}
|
||||
@@ -82,7 +82,6 @@ Symbol *sym_RedefEqu(std::string const &symName, int32_t value);
|
||||
Symbol *sym_AddVar(std::string const &symName, int32_t value);
|
||||
int32_t sym_GetRSValue();
|
||||
void sym_SetRSValue(int32_t value);
|
||||
uint32_t sym_GetConstantValue(std::string const &symName);
|
||||
// Find a symbol by exact name, bypassing expansion checks
|
||||
Symbol *sym_FindExactSymbol(std::string const &symName);
|
||||
// Find a symbol, possibly scoped, by name
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_ASM_WARNING_HPP
|
||||
#define RGBDS_ASM_WARNING_HPP
|
||||
@@ -20,6 +20,7 @@ enum WarningID {
|
||||
WARNING_OBSOLETE, // Obsolete/deprecated things
|
||||
WARNING_SHIFT, // Undefined `SHIFT` behavior
|
||||
WARNING_SHIFT_AMOUNT, // Strange `SHIFT` amount
|
||||
WARNING_UNMATCHED_DIRECTIVE, // `PUSH[C|O|S]` without `POP[C|O|S]`
|
||||
WARNING_UNTERMINATED_LOAD, // `LOAD` without `ENDL`
|
||||
WARNING_USER, // User-defined `WARN`ings
|
||||
|
||||
@@ -61,27 +62,24 @@ extern bool warningsAreErrors;
|
||||
|
||||
void processWarningFlag(char const *flag);
|
||||
|
||||
/*
|
||||
* Used to warn the user about problems that don't prevent the generation of
|
||||
* valid code.
|
||||
*/
|
||||
[[gnu::format(printf, 2, 3)]] void warning(WarningID id, char const *fmt, ...);
|
||||
// Used to warn the user about problems that don't prevent the generation of
|
||||
// valid code.
|
||||
[[gnu::format(printf, 2, 3)]]
|
||||
void warning(WarningID id, char const *fmt, ...);
|
||||
|
||||
/*
|
||||
* Used for errors that compromise the whole assembly process by affecting the
|
||||
* following code, potencially making the assembler generate errors caused by
|
||||
* the first one and unrelated to the code that the assembler complains about.
|
||||
* It is also used when the assembler goes into an invalid state (for example,
|
||||
* when it fails to allocate memory).
|
||||
*/
|
||||
[[gnu::format(printf, 1, 2), noreturn]] void fatalerror(char const *fmt, ...);
|
||||
// Used for errors that compromise the whole assembly process by affecting the
|
||||
// following code, potencially making the assembler generate errors caused by
|
||||
// the first one and unrelated to the code that the assembler complains about.
|
||||
// It is also used when the assembler goes into an invalid state (for example,
|
||||
// when it fails to allocate memory).
|
||||
[[gnu::format(printf, 1, 2), noreturn]]
|
||||
void fatalerror(char const *fmt, ...);
|
||||
|
||||
/*
|
||||
* Used for errors that make it impossible to assemble correctly, but don't
|
||||
* affect the following code. The code will fail to assemble but the user will
|
||||
* get a list of all errors at the end, making it easier to fix all of them at
|
||||
* once.
|
||||
*/
|
||||
[[gnu::format(printf, 1, 2)]] void error(char const *fmt, ...);
|
||||
// Used for errors that make it impossible to assemble correctly, but don't
|
||||
// affect the following code. The code will fail to assemble but the user will
|
||||
// get a list of all errors at the end, making it easier to fix all of them at
|
||||
// once.
|
||||
[[gnu::format(printf, 1, 2)]]
|
||||
void error(char const *fmt, ...);
|
||||
|
||||
#endif // RGBDS_ASM_WARNING_HPP
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_DEFAULT_INIT_ALLOC_HPP
|
||||
#define RGBDS_DEFAULT_INIT_ALLOC_HPP
|
||||
@@ -6,14 +6,11 @@
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
/*
|
||||
* Allocator adaptor that interposes construct() calls to convert value-initialization
|
||||
* (which is what you get with e.g. `vector::resize`) into default-initialization (which does not
|
||||
* zero out non-class types).
|
||||
* From
|
||||
* https://stackoverflow.com/questions/21028299/is-this-behavior-of-vectorresizesize-type-n-under-c11-and-boost-container/21028912#21028912
|
||||
*/
|
||||
|
||||
// Allocator adaptor that interposes construct() calls to convert value-initialization
|
||||
// (which is what you get with e.g. `vector::resize`) into default-initialization (which does not
|
||||
// zero out non-class types).
|
||||
// From
|
||||
// https://stackoverflow.com/questions/21028299/is-this-behavior-of-vectorresizesize-type-n-under-c11-and-boost-container/21028912#21028912
|
||||
template<typename T, typename A = std::allocator<T>>
|
||||
class default_init_allocator : public A {
|
||||
using a_t = std::allocator_traits<A>;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_EITHER_HPP
|
||||
#define RGBDS_EITHER_HPP
|
||||
@@ -43,11 +43,11 @@ private:
|
||||
// Generic field accessors; for internal use only.
|
||||
template<typename T>
|
||||
auto &field() {
|
||||
return pick((T *)nullptr);
|
||||
return pick(static_cast<T *>(nullptr));
|
||||
}
|
||||
template<typename T>
|
||||
auto const &field() const {
|
||||
return pick((T *)nullptr);
|
||||
return pick(static_cast<T *>(nullptr));
|
||||
}
|
||||
|
||||
public:
|
||||
@@ -103,7 +103,7 @@ public:
|
||||
} else if (other._tag == other._t2.tag_value) {
|
||||
*this = other._t2.value;
|
||||
} else {
|
||||
_tag = nulltag;
|
||||
_tag = nulltag; // LCOV_EXCL_LINE
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_ERROR_HPP
|
||||
#define RGBDS_ERROR_HPP
|
||||
|
||||
extern "C" {
|
||||
[[gnu::format(printf, 1, 2)]]
|
||||
void warn(char const *fmt...);
|
||||
[[gnu::format(printf, 1, 2)]]
|
||||
void warnx(char const *fmt, ...);
|
||||
|
||||
[[gnu::format(printf, 1, 2)]] void warn(char const *fmt...);
|
||||
[[gnu::format(printf, 1, 2)]] void warnx(char const *fmt, ...);
|
||||
|
||||
[[gnu::format(printf, 1, 2), noreturn]] void err(char const *fmt, ...);
|
||||
[[gnu::format(printf, 1, 2), noreturn]] void errx(char const *fmt, ...);
|
||||
[[gnu::format(printf, 1, 2), noreturn]]
|
||||
void err(char const *fmt, ...);
|
||||
[[gnu::format(printf, 1, 2), noreturn]]
|
||||
void errx(char const *fmt, ...);
|
||||
}
|
||||
|
||||
#endif // RGBDS_ERROR_HPP
|
||||
|
||||
16
include/extern/getopt.hpp
vendored
16
include/extern/getopt.hpp
vendored
@@ -1,11 +1,15 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
/* This implementation was taken from musl and modified for RGBDS */
|
||||
// This implementation was taken from musl and modified for RGBDS
|
||||
|
||||
#ifndef RGBDS_EXTERN_GETOPT_HPP
|
||||
#define RGBDS_EXTERN_GETOPT_HPP
|
||||
|
||||
extern "C" {
|
||||
// clang-format off: vertically align values
|
||||
static constexpr int no_argument = 0;
|
||||
static constexpr int required_argument = 1;
|
||||
static constexpr int optional_argument = 2;
|
||||
// clang-format on
|
||||
|
||||
extern char *musl_optarg;
|
||||
extern int musl_optind, musl_opterr, musl_optopt, musl_optreset;
|
||||
@@ -21,10 +25,4 @@ int musl_getopt_long_only(
|
||||
int argc, char **argv, char const *optstring, option const *longopts, int *idx
|
||||
);
|
||||
|
||||
#define no_argument 0
|
||||
#define required_argument 1
|
||||
#define optional_argument 2
|
||||
|
||||
} // extern "C"
|
||||
|
||||
#endif // RGBDS_EXTERN_GETOPT_HPP
|
||||
|
||||
2
include/extern/utf8decoder.hpp
vendored
2
include/extern/utf8decoder.hpp
vendored
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_EXTERN_UTF8DECODER_HPP
|
||||
#define RGBDS_EXTERN_UTF8DECODER_HPP
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_FILE_HPP
|
||||
#define RGBDS_FILE_HPP
|
||||
@@ -18,17 +18,13 @@
|
||||
#include "gfx/main.hpp"
|
||||
|
||||
class File {
|
||||
// Construct a `std::streambuf *` by default, since it's probably lighter than a `filebuf`.
|
||||
Either<std::streambuf *, std::filebuf> _file;
|
||||
|
||||
public:
|
||||
File() {}
|
||||
~File() { close(); }
|
||||
File() : _file(nullptr) {}
|
||||
|
||||
/**
|
||||
* This should only be called once, and before doing any `->` operations.
|
||||
* Returns `nullptr` on error, and a non-null pointer otherwise.
|
||||
*/
|
||||
// This should only be called once, and before doing any `->` operations.
|
||||
// Returns `nullptr` on error, and a non-null pointer otherwise.
|
||||
File *open(std::string const &path, std::ios_base::openmode mode) {
|
||||
if (path != "-") {
|
||||
_file.emplace<std::filebuf>();
|
||||
@@ -63,20 +59,6 @@ public:
|
||||
return const_cast<File *>(this)->operator->();
|
||||
}
|
||||
|
||||
File *close() {
|
||||
if (_file.holds<std::filebuf>()) {
|
||||
// This is called by the destructor, and an explicit `close` shouldn't close twice.
|
||||
std::filebuf fileBuf = std::move(_file.get<std::filebuf>());
|
||||
_file.emplace<std::streambuf *>(nullptr);
|
||||
if (fileBuf.close() != nullptr) {
|
||||
return this;
|
||||
}
|
||||
} else if (_file.get<std::streambuf *>() != nullptr) {
|
||||
return this;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
char const *c_str(std::string const &path) const {
|
||||
return _file.holds<std::filebuf>() ? path.c_str()
|
||||
: _file.get<std::streambuf *>() == std::cin.rdbuf() ? "<stdin>"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_GFX_MAIN_HPP
|
||||
#define RGBDS_GFX_MAIN_HPP
|
||||
@@ -10,6 +10,8 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "helpers.hpp"
|
||||
|
||||
#include "gfx/rgba.hpp"
|
||||
|
||||
struct Options {
|
||||
@@ -21,6 +23,7 @@ struct Options {
|
||||
uint8_t verbosity = 0; // -v
|
||||
|
||||
std::string attrmap{}; // -a, -A
|
||||
std::optional<Rgba> bgColor{}; // -B
|
||||
std::array<uint8_t, 2> baseTileIDs{0, 0}; // -b
|
||||
enum {
|
||||
NO_SPEC,
|
||||
@@ -48,14 +51,17 @@ struct Options {
|
||||
|
||||
std::string input{}; // positional arg
|
||||
|
||||
static constexpr uint8_t VERB_NONE = 0; // Normal, no extra output
|
||||
static constexpr uint8_t VERB_CFG = 1; // Print configuration after parsing options
|
||||
static constexpr uint8_t VERB_LOG_ACT = 2; // Log actions before doing them
|
||||
static constexpr uint8_t VERB_INTERM = 3; // Print some intermediate results
|
||||
static constexpr uint8_t VERB_DEBUG = 4; // Internals are logged
|
||||
static constexpr uint8_t VERB_UNMAPPED = 5; // Unused so far
|
||||
static constexpr uint8_t VERB_VVVVVV = 6; // What, can't I have a little fun?
|
||||
[[gnu::format(printf, 3, 4)]] void verbosePrint(uint8_t level, char const *fmt, ...) const;
|
||||
// clang-format off: vertically align values
|
||||
static constexpr uint8_t VERB_NONE = 0; // Normal, no extra output
|
||||
static constexpr uint8_t VERB_CFG = 1; // Print configuration after parsing options
|
||||
static constexpr uint8_t VERB_LOG_ACT = 2; // Log actions before doing them
|
||||
static constexpr uint8_t VERB_INTERM = 3; // Print some intermediate results
|
||||
static constexpr uint8_t VERB_DEBUG = 4; // Internals are logged
|
||||
static constexpr uint8_t VERB_TRACE = 5; // Step-by-step algorithm details
|
||||
static constexpr uint8_t VERB_VVVVVV = 6; // What, can't I have a little fun?
|
||||
// clang-format on
|
||||
[[gnu::format(printf, 3, 4)]]
|
||||
void verbosePrint(uint8_t level, char const *fmt, ...) const;
|
||||
|
||||
mutable bool hasTransparentPixels = false;
|
||||
uint8_t maxOpaqueColors() const { return nbColorsPerPal - hasTransparentPixels; }
|
||||
@@ -63,32 +69,24 @@ struct Options {
|
||||
|
||||
extern Options options;
|
||||
|
||||
/*
|
||||
* Prints the error count, and exits with failure
|
||||
*/
|
||||
[[noreturn]] void giveUp();
|
||||
/*
|
||||
* If any error has been emitted thus far, calls `giveUp()`.
|
||||
*/
|
||||
// Prints the error count, and exits with failure
|
||||
[[noreturn]]
|
||||
void giveUp();
|
||||
// If any error has been emitted thus far, calls `giveUp()`.
|
||||
void requireZeroErrors();
|
||||
/*
|
||||
* Prints a warning, and does not change the error count
|
||||
*/
|
||||
[[gnu::format(printf, 1, 2)]] void warning(char const *fmt, ...);
|
||||
/*
|
||||
* Prints an error, and increments the error count
|
||||
*/
|
||||
[[gnu::format(printf, 1, 2)]] void error(char const *fmt, ...);
|
||||
/*
|
||||
* Prints an error, and increments the error count
|
||||
* Does not take format arguments so `format_` and `-Wformat-security` won't complain about
|
||||
* calling `errorMessage(msg)`.
|
||||
*/
|
||||
// Prints a warning, and does not change the error count
|
||||
[[gnu::format(printf, 1, 2)]]
|
||||
void warning(char const *fmt, ...);
|
||||
// Prints an error, and increments the error count
|
||||
[[gnu::format(printf, 1, 2)]]
|
||||
void error(char const *fmt, ...);
|
||||
// Prints an error, and increments the error count
|
||||
// Does not take format arguments so `format_` and `-Wformat-security` won't complain about
|
||||
// calling `errorMessage(msg)`.
|
||||
void errorMessage(char const *msg);
|
||||
/*
|
||||
* Prints a fatal error, increments the error count, and gives up
|
||||
*/
|
||||
[[gnu::format(printf, 1, 2), noreturn]] void fatal(char const *fmt, ...);
|
||||
// Prints a fatal error, increments the error count, and gives up
|
||||
[[gnu::format(printf, 1, 2), noreturn]]
|
||||
void fatal(char const *fmt, ...);
|
||||
|
||||
struct Palette {
|
||||
// An array of 4 GBC-native (RGB555) colors
|
||||
@@ -121,4 +119,27 @@ static constexpr auto flipTable = ([]() constexpr {
|
||||
return table;
|
||||
})();
|
||||
|
||||
// Parsing helpers.
|
||||
|
||||
static constexpr uint8_t nibble(char c) {
|
||||
if (c >= 'a') {
|
||||
assume(c <= 'f');
|
||||
return c - 'a' + 10;
|
||||
} else if (c >= 'A') {
|
||||
assume(c <= 'F');
|
||||
return c - 'A' + 10;
|
||||
} else {
|
||||
assume(c >= '0' && c <= '9');
|
||||
return c - '0';
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr uint8_t toHex(char c1, char c2) {
|
||||
return nibble(c1) * 16 + nibble(c2);
|
||||
}
|
||||
|
||||
static constexpr uint8_t singleToHex(char c) {
|
||||
return toHex(c, c);
|
||||
}
|
||||
|
||||
#endif // RGBDS_GFX_MAIN_HPP
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_GFX_PAL_PACKING_HPP
|
||||
#define RGBDS_GFX_PAL_PACKING_HPP
|
||||
@@ -11,9 +11,7 @@
|
||||
struct Palette;
|
||||
class ProtoPalette;
|
||||
|
||||
/*
|
||||
* Returns which palette each proto-palette maps to, and how many palettes are necessary
|
||||
*/
|
||||
// Returns which palette each proto-palette maps to, and how many palettes are necessary
|
||||
std::tuple<DefaultInitVec<size_t>, size_t>
|
||||
overloadAndRemove(std::vector<ProtoPalette> const &protoPalettes);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_GFX_PAL_SORTING_HPP
|
||||
#define RGBDS_GFX_PAL_SORTING_HPP
|
||||
@@ -10,6 +10,10 @@
|
||||
|
||||
#include "gfx/rgba.hpp"
|
||||
|
||||
// Allow a slot for every possible CGB color, plus one for transparency
|
||||
// 32 (1 << 5) per channel, times 3 RGB channels = 32768 CGB colors
|
||||
static constexpr size_t NB_COLOR_SLOTS = (1 << (5 * 3)) + 1;
|
||||
|
||||
struct Palette;
|
||||
|
||||
void sortIndexed(
|
||||
@@ -20,7 +24,7 @@ void sortIndexed(
|
||||
png_byte *palAlpha
|
||||
);
|
||||
void sortGrayscale(
|
||||
std::vector<Palette> &palettes, std::array<std::optional<Rgba>, 0x8001> const &colors
|
||||
std::vector<Palette> &palettes, std::array<std::optional<Rgba>, NB_COLOR_SLOTS> const &colors
|
||||
);
|
||||
void sortRgb(std::vector<Palette> &palettes);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_GFX_PAL_SPEC_HPP
|
||||
#define RGBDS_GFX_PAL_SPEC_HPP
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_GFX_PROCESS_HPP
|
||||
#define RGBDS_GFX_PROCESS_HPP
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_GFX_PROTO_PALETTE_HPP
|
||||
#define RGBDS_GFX_PROTO_PALETTE_HPP
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_GFX_REVERSE_HPP
|
||||
#define RGBDS_GFX_REVERSE_HPP
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_GFX_RGBA_HPP
|
||||
#define RGBDS_GFX_RGBA_HPP
|
||||
@@ -13,9 +13,7 @@ struct Rgba {
|
||||
|
||||
constexpr Rgba(uint8_t r, uint8_t g, uint8_t b, uint8_t a)
|
||||
: red(r), green(g), blue(b), alpha(a) {}
|
||||
/*
|
||||
* Constructs the color from a "packed" RGBA representation (0xRRGGBBAA)
|
||||
*/
|
||||
// Constructs the color from a "packed" RGBA representation (0xRRGGBBAA)
|
||||
explicit constexpr Rgba(uint32_t rgba = 0)
|
||||
: red(rgba >> 24), green(rgba >> 16), blue(rgba >> 8), alpha(rgba) {}
|
||||
|
||||
@@ -28,34 +26,28 @@ struct Rgba {
|
||||
_5to8(cgbColor),
|
||||
_5to8(cgbColor >> 5),
|
||||
_5to8(cgbColor >> 10),
|
||||
(uint8_t)(cgbColor & 0x8000 ? 0x00 : 0xFF),
|
||||
static_cast<uint8_t>(cgbColor & 0x8000 ? 0x00 : 0xFF),
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns this RGBA as a 32-bit number that can be printed in hex (`%08x`) to yield its CSS
|
||||
* representation
|
||||
*/
|
||||
// Returns this RGBA as a 32-bit number that can be printed in hex (`%08x`) to yield its CSS
|
||||
// representation
|
||||
uint32_t toCSS() const {
|
||||
auto shl = [](uint8_t val, unsigned shift) { return static_cast<uint32_t>(val) << shift; };
|
||||
return shl(red, 24) | shl(green, 16) | shl(blue, 8) | shl(alpha, 0);
|
||||
}
|
||||
bool operator==(Rgba const &rhs) const { return toCSS() == rhs.toCSS(); }
|
||||
bool operator!=(Rgba const &rhs) const { return toCSS() != rhs.toCSS(); }
|
||||
|
||||
/*
|
||||
* CGB colors are RGB555, so we use bit 15 to signify that the color is transparent instead
|
||||
* Since the rest of the bits don't matter then, we return 0x8000 exactly.
|
||||
*/
|
||||
bool operator==(Rgba const &rhs) const { return toCSS() == rhs.toCSS(); }
|
||||
|
||||
// CGB colors are RGB555, so we use bit 15 to signify that the color is transparent instead
|
||||
// Since the rest of the bits don't matter then, we return 0x8000 exactly.
|
||||
static constexpr uint16_t transparent = 0b1'00000'00000'00000;
|
||||
|
||||
static constexpr uint8_t transparency_threshold = 0x10;
|
||||
bool isTransparent() const { return alpha < transparency_threshold; }
|
||||
static constexpr uint8_t opacity_threshold = 0xF0;
|
||||
bool isOpaque() const { return alpha >= opacity_threshold; }
|
||||
/*
|
||||
* Computes the equivalent CGB color, respects the color curve depending on options
|
||||
*/
|
||||
// Computes the equivalent CGB color, respects the color curve depending on options
|
||||
uint16_t cgbColor() const;
|
||||
|
||||
bool isGray() const { return red == green && green == blue; }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_HELPERS_HPP
|
||||
#define RGBDS_HELPERS_HPP
|
||||
@@ -14,7 +14,8 @@
|
||||
#else
|
||||
// This seems to generate similar code to __builtin_unreachable, despite different semantics
|
||||
// Note that executing this is undefined behavior (declared [[noreturn]], but does return)
|
||||
[[noreturn]] static inline void unreachable_() {
|
||||
[[noreturn]]
|
||||
static inline void unreachable_() {
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -26,8 +27,9 @@
|
||||
// `[[gnu::assume()]]` for GCC or compatible also has insufficient support (GCC 13+ only)
|
||||
#define assume(x) \
|
||||
do { \
|
||||
if (!(x)) \
|
||||
if (!(x)) { \
|
||||
unreachable_(); \
|
||||
} \
|
||||
} while (0)
|
||||
#endif
|
||||
#else
|
||||
@@ -93,15 +95,14 @@ static inline int clz(unsigned int x) {
|
||||
#define CAT(x, y) x##y
|
||||
#define EXPAND_AND_CAT(x, y) CAT(x, y)
|
||||
|
||||
// Obtaining the size of an array; `arr` must be an expression, not a type!
|
||||
// (Having two instances of `arr` is OK because the contents of `sizeof` are not evaluated.)
|
||||
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof *(arr))
|
||||
|
||||
// For lack of <ranges>, this adds some more brevity
|
||||
#define RANGE(s) std::begin(s), std::end(s)
|
||||
|
||||
// MSVC does not inline `strlen()` or `.length()` of a constant string, so we use `sizeof`
|
||||
#define QUOTEDSTRLEN(s) (sizeof(s) - 1)
|
||||
// MSVC does not inline `strlen()` or `.length()` of a constant string
|
||||
template<int N>
|
||||
static constexpr int literal_strlen(char const (&)[N]) {
|
||||
return N - 1;
|
||||
}
|
||||
|
||||
// For ad-hoc RAII in place of a `defer` statement or cross-platform `__attribute__((cleanup))`
|
||||
template<typename T>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_ITERTOOLS_HPP
|
||||
#define RGBDS_ITERTOOLS_HPP
|
||||
@@ -18,18 +18,17 @@ class EnumSeq {
|
||||
explicit Iterator(T value) : _value(value) {}
|
||||
|
||||
Iterator &operator++() {
|
||||
_value = (T)(_value + 1);
|
||||
_value = static_cast<T>(_value + 1);
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto operator*() const { return _value; }
|
||||
|
||||
bool operator==(Iterator const &rhs) const { return _value == rhs._value; }
|
||||
bool operator!=(Iterator const &rhs) const { return _value != rhs._value; }
|
||||
};
|
||||
|
||||
public:
|
||||
explicit EnumSeq(T stop) : _start((T)0), _stop(stop) {}
|
||||
explicit EnumSeq(T stop) : _start(static_cast<T>(0)), _stop(stop) {}
|
||||
explicit EnumSeq(T start, T stop) : _start(start), _stop(stop) {}
|
||||
|
||||
Iterator begin() { return Iterator(_start); }
|
||||
@@ -59,10 +58,6 @@ public:
|
||||
bool operator==(ZipIterator const &rhs) const {
|
||||
return std::get<0>(_iters) == std::get<0>(rhs._iters);
|
||||
}
|
||||
|
||||
bool operator!=(ZipIterator const &rhs) const {
|
||||
return std::get<0>(_iters) != std::get<0>(rhs._iters);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename... Ts>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_LINK_ASSIGN_HPP
|
||||
#define RGBDS_LINK_ASSIGN_HPP
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_LINK_MAIN_HPP
|
||||
#define RGBDS_LINK_MAIN_HPP
|
||||
@@ -32,8 +32,9 @@ extern bool disablePadding;
|
||||
// Helper macro for printing verbose-mode messages
|
||||
#define verbosePrint(...) \
|
||||
do { \
|
||||
if (beVerbose) \
|
||||
if (beVerbose) { \
|
||||
fprintf(stderr, __VA_ARGS__); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
struct FileStackNode {
|
||||
@@ -58,11 +59,11 @@ struct FileStackNode {
|
||||
std::string const &dump(uint32_t curLineNo) const;
|
||||
};
|
||||
|
||||
[[gnu::format(printf, 3, 4)]] void
|
||||
warning(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...);
|
||||
[[gnu::format(printf, 3, 4)]] void
|
||||
error(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...);
|
||||
[[gnu::format(printf, 3, 4), noreturn]] void
|
||||
fatal(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...);
|
||||
[[gnu::format(printf, 3, 4)]]
|
||||
void warning(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...);
|
||||
[[gnu::format(printf, 3, 4)]]
|
||||
void error(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...);
|
||||
[[gnu::format(printf, 3, 4), noreturn]]
|
||||
void fatal(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...);
|
||||
|
||||
#endif // RGBDS_LINK_MAIN_HPP
|
||||
|
||||
@@ -1,19 +1,12 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_LINK_OBJECT_HPP
|
||||
#define RGBDS_LINK_OBJECT_HPP
|
||||
|
||||
/*
|
||||
* Read an object (.o) file, and add its info to the data structures.
|
||||
* @param fileName A path to the object file to be read
|
||||
* @param i The ID of the file
|
||||
*/
|
||||
void obj_ReadFile(char const *fileName, unsigned int i);
|
||||
// Read an object (.o) file, and add its info to the data structures.
|
||||
void obj_ReadFile(char const *fileName, unsigned int fileID);
|
||||
|
||||
/*
|
||||
* Sets up object file reading
|
||||
* @param nbFiles The number of object files that will be read
|
||||
*/
|
||||
// Sets up object file reading
|
||||
void obj_Setup(unsigned int nbFiles);
|
||||
|
||||
#endif // RGBDS_LINK_OBJECT_HPP
|
||||
|
||||
@@ -1,26 +1,17 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_LINK_OUTPUT_HPP
|
||||
#define RGBDS_LINK_OUTPUT_HPP
|
||||
|
||||
struct Section;
|
||||
|
||||
/*
|
||||
* Registers a section for output.
|
||||
* @param section The section to add
|
||||
*/
|
||||
// Registers a section for output.
|
||||
void out_AddSection(Section const §ion);
|
||||
|
||||
/*
|
||||
* Finds an assigned section overlapping another one.
|
||||
* @param section The section that is being overlapped
|
||||
* @return A section overlapping it
|
||||
*/
|
||||
// Finds an assigned section overlapping another one.
|
||||
Section const *out_OverlappingSection(Section const §ion);
|
||||
|
||||
/*
|
||||
* Writes all output (bin, sym, map) files.
|
||||
*/
|
||||
// Writes all output (bin, sym, map) files.
|
||||
void out_WriteFiles();
|
||||
|
||||
#endif // RGBDS_LINK_OUTPUT_HPP
|
||||
|
||||
@@ -1,17 +1,12 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_LINK_PATCH_HPP
|
||||
#define RGBDS_LINK_PATCH_HPP
|
||||
|
||||
/*
|
||||
* Checks all assertions
|
||||
* @return true if assertion failed
|
||||
*/
|
||||
// Checks all assertions
|
||||
void patch_CheckAssertions();
|
||||
|
||||
/*
|
||||
* Applies all SECTIONs' patches to them
|
||||
*/
|
||||
// Applies all SECTIONs' patches to them
|
||||
void patch_ApplyPatches();
|
||||
|
||||
#endif // RGBDS_LINK_PATCH_HPP
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_LINK_SDAS_OBJ_HPP
|
||||
#define RGBDS_LINK_SDAS_OBJ_HPP
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_LINK_SECTION_HPP
|
||||
#define RGBDS_LINK_SECTION_HPP
|
||||
@@ -65,29 +65,17 @@ struct Assertion {
|
||||
|
||||
extern std::deque<Assertion> assertions;
|
||||
|
||||
/*
|
||||
* Execute a callback for each section currently registered.
|
||||
* This is to avoid exposing the data structure in which sections are stored.
|
||||
* @param callback The function to call for each structure.
|
||||
*/
|
||||
// Execute a callback for each section currently registered.
|
||||
// This is to avoid exposing the data structure in which sections are stored.
|
||||
void sect_ForEach(void (*callback)(Section &));
|
||||
|
||||
/*
|
||||
* Registers a section to be processed.
|
||||
* @param section The section to register.
|
||||
*/
|
||||
// Registers a section to be processed.
|
||||
void sect_AddSection(std::unique_ptr<Section> &§ion);
|
||||
|
||||
/*
|
||||
* Finds a section by its name.
|
||||
* @param name The name of the section to look for
|
||||
* @return A pointer to the section, or `nullptr` if it wasn't found
|
||||
*/
|
||||
// Finds a section by its name.
|
||||
Section *sect_GetSection(std::string const &name);
|
||||
|
||||
/*
|
||||
* Checks if all sections meet reasonable criteria, such as max size
|
||||
*/
|
||||
// Checks if all sections meet reasonable criteria, such as max size
|
||||
void sect_DoSanityChecks();
|
||||
|
||||
#endif // RGBDS_LINK_SECTION_HPP
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_LINK_SYMBOL_HPP
|
||||
#define RGBDS_LINK_SYMBOL_HPP
|
||||
@@ -41,11 +41,7 @@ void sym_ForEach(void (*callback)(Symbol &));
|
||||
|
||||
void sym_AddSymbol(Symbol &symbol);
|
||||
|
||||
/*
|
||||
* Finds a symbol in all the defined symbols.
|
||||
* @param name The name of the symbol to look for
|
||||
* @return A pointer to the symbol, or `nullptr` if not found.
|
||||
*/
|
||||
// Finds a symbol in all the defined symbols.
|
||||
Symbol *sym_GetSymbol(std::string const &name);
|
||||
|
||||
void sym_DumpLocalAliasedSymbols(std::string const &name);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_LINKDEFS_HPP
|
||||
#define RGBDS_LINKDEFS_HPP
|
||||
@@ -9,7 +9,7 @@
|
||||
#include "helpers.hpp" // assume
|
||||
|
||||
#define RGBDS_OBJECT_VERSION_STRING "RGB9"
|
||||
#define RGBDS_OBJECT_REV 11U
|
||||
#define RGBDS_OBJECT_REV 12U
|
||||
|
||||
enum AssertionType { ASSERT_WARN, ASSERT_ERROR, ASSERT_FATAL };
|
||||
|
||||
@@ -52,6 +52,7 @@ enum RPNCommand {
|
||||
|
||||
RPN_HRAM = 0x60,
|
||||
RPN_RST = 0x61,
|
||||
RPN_BIT_INDEX = 0x62,
|
||||
|
||||
RPN_HIGH = 0x70,
|
||||
RPN_LOW = 0x71,
|
||||
@@ -92,29 +93,19 @@ extern struct SectionTypeInfo {
|
||||
uint32_t lastBank;
|
||||
} sectionTypeInfo[SECTTYPE_INVALID];
|
||||
|
||||
/*
|
||||
* Tells whether a section has data in its object file definition,
|
||||
* depending on type.
|
||||
* @param type The section's type
|
||||
* @return `true` if the section's definition includes data
|
||||
*/
|
||||
// Tells whether a section has data in its object file definition,
|
||||
// depending on type.
|
||||
static inline bool sect_HasData(SectionType type) {
|
||||
assume(type != SECTTYPE_INVALID);
|
||||
return type == SECTTYPE_ROM0 || type == SECTTYPE_ROMX;
|
||||
}
|
||||
|
||||
/*
|
||||
* Computes a memory region's end address (last byte), eg. 0x7FFF
|
||||
* @return The address of the last byte in that memory region
|
||||
*/
|
||||
// Returns a memory region's end address (last byte), e.g. 0x7FFF
|
||||
static inline uint16_t endaddr(SectionType type) {
|
||||
return sectionTypeInfo[type].startAddr + sectionTypeInfo[type].size - 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Computes a memory region's number of banks
|
||||
* @return The number of banks, 1 for regions without banking
|
||||
*/
|
||||
// Returns a memory region's number of banks, or 1 for regions without banking
|
||||
static inline uint32_t nbbanks(SectionType type) {
|
||||
return sectionTypeInfo[type].lastBank - sectionTypeInfo[type].firstBank + 1;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_OP_MATH_HPP
|
||||
#define RGBDS_OP_MATH_HPP
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// platform-specific hacks
|
||||
|
||||
|
||||
@@ -1,17 +1,8 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_UTIL_HPP
|
||||
#define RGBDS_UTIL_HPP
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <vector>
|
||||
|
||||
char const *printChar(int c);
|
||||
|
||||
/*
|
||||
* @return The number of bytes read, or 0 if invalid data was found
|
||||
*/
|
||||
size_t readUTF8Char(std::vector<int32_t> *dest, char const *src);
|
||||
|
||||
#endif // RGBDS_UTIL_HPP
|
||||
|
||||
@@ -1,16 +1,12 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_VERSION_HPP
|
||||
#define RGBDS_VERSION_HPP
|
||||
|
||||
extern "C" {
|
||||
|
||||
#define PACKAGE_VERSION_MAJOR 0
|
||||
#define PACKAGE_VERSION_MINOR 9
|
||||
#define PACKAGE_VERSION_PATCH 0
|
||||
#define PACKAGE_VERSION_RC 2
|
||||
#define PACKAGE_VERSION_PATCH 2
|
||||
|
||||
char const *get_package_version_string();
|
||||
}
|
||||
|
||||
#endif // RGBDS_VERSION_H
|
||||
|
||||
609
man/gbz80.7
609
man/gbz80.7
File diff suppressed because it is too large
Load Diff
445
man/rgbasm-old.5
Normal file
445
man/rgbasm-old.5
Normal file
@@ -0,0 +1,445 @@
|
||||
'\" e
|
||||
.\"
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd May 4, 2025
|
||||
.Dt RGBASM-OLD 5
|
||||
.Os
|
||||
.Sh NAME
|
||||
.Nm rgbasm-old
|
||||
.Nd obsolete language documentation
|
||||
.Sh DESCRIPTION
|
||||
This is the list of features that have been removed from the
|
||||
.Xr rgbasm 5
|
||||
assembly language over its decades of evolution, along with their modern alternatives.
|
||||
Its goal is to be a reference for backwards incompatibility, when upgrading an old assembly codebase to work with the latest RGBDS release.
|
||||
It does
|
||||
.Em not
|
||||
attempt to list every syntax bug that was ever fixed (with some notable exceptions), nor new reserved keywords that may conflict with old identifiers.
|
||||
.Sh REMOVED
|
||||
These are features which have been completely removed, without any direct alternatives.
|
||||
Usually these features were limiting the addition of other features, or had awkward limits on their own intended effects.
|
||||
.Ss Automatic LD to LDH conversion (rgbasm -l)
|
||||
Deprecated in 0.7.0, removed in 0.8.0.
|
||||
.Pp
|
||||
.Xr rgbasm 1
|
||||
used to automatically treat
|
||||
.Ql LD
|
||||
as
|
||||
.Ql LDH
|
||||
if the address was known to be in the
|
||||
.Ad $FF00-$FFFF
|
||||
range, with the
|
||||
.Fl L
|
||||
flag to opt out.
|
||||
.Xr rgbasm 1
|
||||
0.6.0 added a
|
||||
.Fl l
|
||||
flag to opt in instead.
|
||||
.Pp
|
||||
Instead, use
|
||||
.Ql LDH ,
|
||||
and remove the
|
||||
.Fl L
|
||||
and
|
||||
.Fl l
|
||||
flags from
|
||||
.Xr rgbasm 1 .
|
||||
.Ss Automatic NOP after HALT (rgbasm -H)
|
||||
Deprecated in 0.7.0, removed in 0.8.0.
|
||||
.Pp
|
||||
.Xr rgbasm 1
|
||||
used to automatically insert a
|
||||
.Ql NOP
|
||||
after
|
||||
.Ql HALT ,
|
||||
with the
|
||||
.Fl h
|
||||
flag to opt out.
|
||||
.Xr rgbasm 1
|
||||
0.6.0 added a
|
||||
.Fl H
|
||||
flag to opt in instead.
|
||||
.Pp
|
||||
Instead, use an explicit
|
||||
.Ql NOP
|
||||
after
|
||||
.Ql HALT ,
|
||||
and remove the
|
||||
.Fl h
|
||||
and
|
||||
.Fl H
|
||||
flags from
|
||||
.Xr rgbasm 1 .
|
||||
.Ss Nested macro definitions
|
||||
Removed in 0.4.2.
|
||||
.Pp
|
||||
Instead, put the nested macro definition inside a quoted string (making sure that none of its lines start with
|
||||
.Ic ENDM ) ,
|
||||
then interpolate that string.
|
||||
For example:
|
||||
.Bd -literal -offset indent
|
||||
MACRO outer
|
||||
DEF definition EQUS """
|
||||
MACRO inner
|
||||
println (\e1) - (\e\e1)
|
||||
\enENDM"""
|
||||
{definition}
|
||||
PURGE definition
|
||||
ENDM
|
||||
outer 10
|
||||
inner 3 ; prints 7
|
||||
.Ed
|
||||
.Ss Negative DS
|
||||
Removed in 0.3.2.
|
||||
.Pp
|
||||
This was used to "rewind" the value of
|
||||
.Ic @
|
||||
in RAM sections, allowing labeled space allocations to overlap.
|
||||
.Pp
|
||||
Instead, use
|
||||
.Ic UNION .
|
||||
.Ss __FILE__ and __LINE__
|
||||
Deprecated in 0.6.0, removed in 0.7.0.
|
||||
.Pp
|
||||
Instead, use
|
||||
.Ic WARN
|
||||
or
|
||||
.Ic FAIL
|
||||
to print a complete trace of filenames and line numbers.
|
||||
.Ss _PI
|
||||
Deprecated in 0.5.0, removed in 0.6.0.
|
||||
.Pp
|
||||
Instead, use
|
||||
.Ql 3.141592653 .
|
||||
.Ss Treating multi-character strings as numbers
|
||||
Deprecated in 0.9.0.
|
||||
.Pp
|
||||
Instead, use a multi-value
|
||||
.Ic CHARMAP ,
|
||||
or explicitly combine the values of individual characters.
|
||||
.Ss rgbgfx -f/--fix and -F/--fix-and-save
|
||||
Removed in 0.6.0.
|
||||
.Pp
|
||||
Instead, use
|
||||
.Ql rgbgfx -c/--colors
|
||||
to explicitly specify a color palette.
|
||||
If using
|
||||
.Ql -c embedded ,
|
||||
arrange the PNG's indexed palette in a separate graphics editor.
|
||||
.Ss rgbgfx -D/--debug
|
||||
Removed in 0.6.0.
|
||||
.Sh REPLACED
|
||||
These are features whose syntax has been changed without affecting functionality.
|
||||
They can generally be updated with a single search-and-replace.
|
||||
.Ss Defining constants and variables without DEF
|
||||
Deprecated in 0.7.0, removed in 0.8.0.
|
||||
.Pp
|
||||
.Ic EQU , EQUS , = , RB , RW ,
|
||||
and
|
||||
.Ic RL
|
||||
definitions used to just start with the symbol name, but had to be typed in column 1.
|
||||
.Pp
|
||||
Instead, use
|
||||
.Ic DEF
|
||||
before constant and variable definitions.
|
||||
Note that
|
||||
.Ic EQUS
|
||||
expansion does not occur for the symbol name, so you have to use explicit
|
||||
.Ql {interpolation} .
|
||||
.Ss Defining macros like labels
|
||||
Deprecated in 0.6.0, removed in 0.7.0.
|
||||
.Pp
|
||||
Macros used to be defined as
|
||||
.Ql name: MACRO .
|
||||
.Pp
|
||||
Instead, use
|
||||
.Ql MACRO name .
|
||||
Note that
|
||||
.Ic EQUS
|
||||
expansion does not occur for the macro name, so you have to use explicit
|
||||
.Ql {interpolation} .
|
||||
.Ss Defining variables with SET
|
||||
Deprecated in 0.5.2, removed in 0.6.0.
|
||||
.Pp
|
||||
Variables used to be defined as
|
||||
.Ql name SET value .
|
||||
.Pp
|
||||
Instead, use
|
||||
.Ql DEF name = value .
|
||||
.Ss Global labels without colons
|
||||
Deprecated in 0.4.0, removed in 0.5.0.
|
||||
.Pp
|
||||
Labels used to be definable with just a name, but had to be typed in column 1.
|
||||
.Pp
|
||||
Instead, use explicit colons; for example,
|
||||
.Ql Label:
|
||||
or exported
|
||||
.Ql Label:: .
|
||||
.Ss '\e,' in strings within macro arguments
|
||||
Deprecated in 0.5.0, removed in 0.7.0.
|
||||
.Pp
|
||||
Macro arguments now handle quoted strings and parenthesized expressions as single arguments, so commas inside them are not argument separators and do not need escaping.
|
||||
.Pp
|
||||
Instead, just use commas without backslashes.
|
||||
.Ss '*' comments
|
||||
Deprecated in 0.4.1, removed in 0.5.0.
|
||||
.Pp
|
||||
These comments had to have the
|
||||
.Ql *
|
||||
typed in column 1.
|
||||
.Pp
|
||||
Instead, use
|
||||
.Ql \&;
|
||||
comments.
|
||||
.Ss PRINTT, PRINTI, PRINTV, and PRINTF
|
||||
Deprecated in 0.5.0, removed in 0.6.0.
|
||||
.Pp
|
||||
These directives were each specific to one type of value.
|
||||
.Pp
|
||||
Instead, use
|
||||
.Ic PRINT
|
||||
and
|
||||
.Ic PRINTLN ,
|
||||
with
|
||||
.Ic STRFMT
|
||||
or
|
||||
.Ql {interpolation}
|
||||
for type-specific formatting.
|
||||
.Ss IMPORT and XREF
|
||||
Removed in 0.4.0.
|
||||
.Pp
|
||||
Symbols are now automatically resolved if they were exported from elsewhere.
|
||||
.Pp
|
||||
Instead, just remove these directives.
|
||||
.Ss GLOBAL and XDEF
|
||||
Deprecated in 0.4.2, removed in 0.5.0.
|
||||
.Pp
|
||||
Instead, use
|
||||
.Ic EXPORT .
|
||||
.Ss HOME, CODE, DATA, and BSS
|
||||
Deprecated in 0.3.0, removed in 0.4.0.
|
||||
.Pp
|
||||
Instead of
|
||||
.Ic HOME ,
|
||||
use
|
||||
.Ic ROM0 ;
|
||||
instead of
|
||||
.Ic CODE
|
||||
and
|
||||
.Ic DATA ,
|
||||
use
|
||||
.Ic ROMX ;
|
||||
instead of
|
||||
.Ic BSS ,
|
||||
use
|
||||
.Ic WRAM0 .
|
||||
.Ss JP [HL]
|
||||
Deprecated in 0.3.0, removed in 0.4.0.
|
||||
.Pp
|
||||
Instead, use
|
||||
.Ql JP HL .
|
||||
.Ss LDI A, HL and LDD A, HL
|
||||
Deprecated in 0.3.0, removed in 0.4.0.
|
||||
.Pp
|
||||
Instead, use
|
||||
.Ql LDI A, [HL]
|
||||
and
|
||||
.Ql LDD A, [HL]
|
||||
(or
|
||||
.Ql LD A, [HLI]
|
||||
and
|
||||
.Ql LD A, [HLD] ;
|
||||
or
|
||||
.Ql LD A, [HL+]
|
||||
and
|
||||
.Ql LD A, [HL-] ) .
|
||||
.Ss LDIO
|
||||
Deprecated in 0.9.0.
|
||||
.Pp
|
||||
Instead, use
|
||||
.Ql LDH .
|
||||
.Ss LD [C], A and LD A, [C]
|
||||
Deprecated in 0.9.0.
|
||||
.Pp
|
||||
Instead, use
|
||||
.Ql LDH [C], A
|
||||
and
|
||||
.Ql LDH A, [C] .
|
||||
.Pp
|
||||
Note that
|
||||
.Ql LD [$FF00+C], A
|
||||
and
|
||||
.Ql LD A, [$FF00+C]
|
||||
were also deprecated in 0.9.0, but were
|
||||
.Em undeprecated
|
||||
in 0.9.1.
|
||||
.Ss LDH [n8], A and LDH A, [n8]
|
||||
Deprecated in 0.9.0.
|
||||
.Pp
|
||||
.Ql LDH
|
||||
used to treat "addresses" from
|
||||
.Ad $00
|
||||
to
|
||||
.Ad $FF
|
||||
as if they were the low byte of an address from
|
||||
.Ad $FF00
|
||||
to
|
||||
.Ad $FFFF .
|
||||
.Pp
|
||||
Instead, use
|
||||
.Ql LDH [n16], A
|
||||
and
|
||||
.Ql LDH A, [n16] .
|
||||
.Ss LD HL, [SP + e8]
|
||||
Deprecated in 0.3.0, removed in 0.4.0.
|
||||
.Pp
|
||||
Instead, use
|
||||
.Ql LD HL, SP + e8 .
|
||||
.Ss LDHL SP, e8
|
||||
Supported in ASMotor, removed in RGBDS.
|
||||
.Pp
|
||||
Instead, use
|
||||
.Ql LD HL, SP + e8 .
|
||||
.Ss rgbasm -i
|
||||
Deprecated in 0.6.0, removed in 0.8.0.
|
||||
.Pp
|
||||
Instead, use
|
||||
.Fl I
|
||||
or
|
||||
.Fl -include .
|
||||
.Ss rgbgfx -h
|
||||
Removed in 0.6.0.
|
||||
.Pp
|
||||
Instead, use
|
||||
.Fl Z
|
||||
or
|
||||
.Fl -columns .
|
||||
.Ss rgbgfx --output-*
|
||||
Deprecated in 0.7.0, removed in 0.8.0.
|
||||
.Pp
|
||||
Instead, use
|
||||
.Fl -auto-* .
|
||||
.Sh CHANGED
|
||||
These are breaking changes that did not alter syntax, and so could not practically be deprecated.
|
||||
.Ss Trigonometry function units
|
||||
Changed in 0.6.0.
|
||||
.Pp
|
||||
Instead of dividing a circle into 65536.0 "binary degrees", it is now divided into 1.0 "turns".
|
||||
.Pp
|
||||
For example, previously we had:
|
||||
.EQ
|
||||
delim $$
|
||||
.EN
|
||||
.Bl -bullet -offset indent
|
||||
.It
|
||||
.Ql SIN(0.25) == 0.00002 ,
|
||||
because 0.25 binary degrees = $0.25 / 65536.0$ turns = $0.000004 tau$ radians = $0.000008 pi$ radians, and $sin ( 0.000008 pi ) = 0.00002$
|
||||
.It
|
||||
.Ql SIN(16384.0) == 1.0 ,
|
||||
because 16384.0 binary degrees = $16384.0 / 65536.0$ turns = $0.25 tau$ radians = $pi / 2$ radians, and $sin ( pi / 2 ) = 1$
|
||||
.It
|
||||
.Ql ASIN(1.0) == 16384.0
|
||||
.El
|
||||
.Pp
|
||||
Instead, now we have:
|
||||
.Bl -bullet -offset indent
|
||||
.It
|
||||
.Ql SIN(0.25) == 1.0 ,
|
||||
because $0.25$ turns = $0.25 tau$ radians = $pi / 2$ radians, and $sin ( pi / 2 ) = 1$
|
||||
.It
|
||||
.Ql SIN(16384.0) == 0.0 ,
|
||||
because $16384$ turns = $16384 tau$ radians = $32768 pi$ radians, and $sin ( 32768 pi ) = 0$
|
||||
.It
|
||||
.Ql ASIN(1.0) == 0.25
|
||||
.El
|
||||
.EQ
|
||||
delim off
|
||||
.EN
|
||||
.Ss ** operator associativity
|
||||
Changed in 0.9.0.
|
||||
.Pp
|
||||
Instead of being left-associative,
|
||||
.Ql **
|
||||
is now right-associative.
|
||||
.Pp
|
||||
Previously we had
|
||||
.Ql p ** q ** r == (p ** q) ** r .
|
||||
.Pp
|
||||
Instead, now we have
|
||||
.Ql p ** q ** r == p ** (q ** r) .
|
||||
.Sh BUGS
|
||||
These are misfeatures that may have been possible by mistake.
|
||||
They do not get deprecated, just fixed.
|
||||
.Ss Space between exported labels' colons
|
||||
Fixed in 0.7.0.
|
||||
.Pp
|
||||
Labels with two colons used to ignore a space between them; for example,
|
||||
.Ql Label:\ : .
|
||||
.Pp
|
||||
Instead, use
|
||||
.Ql Label:: .
|
||||
.Ss Space between label and colon
|
||||
Fixed in 0.9.0.
|
||||
.Pp
|
||||
Space between a label and its colon(s) used to be ignored; for example,
|
||||
.Ql Label\ :
|
||||
and
|
||||
.Ql Label\ :: .
|
||||
Now they are treated as invocations of the
|
||||
.Ql Label
|
||||
macro with
|
||||
.Ql \&:
|
||||
and
|
||||
.Ql ::
|
||||
as arguments.
|
||||
.Pp
|
||||
Instead, use
|
||||
.Ql Label:
|
||||
and
|
||||
.Ql Label:: .
|
||||
.Ss ADD r16 with implicit first HL operand
|
||||
Fixed in 0.5.0.
|
||||
.Pp
|
||||
For example,
|
||||
.Ql ADD BC
|
||||
used to be treated as
|
||||
.Ql ADD HL, BC ,
|
||||
and likewise for
|
||||
.Ql DE ,
|
||||
.Ql HL ,
|
||||
and
|
||||
.Ql SP .
|
||||
.Pp
|
||||
Instead, use an explicit first
|
||||
.Ql HL
|
||||
operand.
|
||||
.Ss = instead of SET
|
||||
Fixed in 0.4.0.
|
||||
.Pp
|
||||
The
|
||||
.Ic =
|
||||
operator used to be an alias for the
|
||||
.Ic SET
|
||||
keyword, which included using
|
||||
.Ic =
|
||||
for the
|
||||
.Ic SET
|
||||
.Em instruction .
|
||||
.Pp
|
||||
Instead, just use
|
||||
.Ic SET
|
||||
for the instruction.
|
||||
.Sh SEE ALSO
|
||||
.Xr rgbasm 1 ,
|
||||
.Xr gbz80 7 ,
|
||||
.Xr rgbds 5 ,
|
||||
.Xr rgbds 7
|
||||
.Sh HISTORY
|
||||
.Xr rgbasm 1
|
||||
was originally written by
|
||||
.An Carsten S\(/orensen
|
||||
as part of the ASMotor package, and was later repackaged in RGBDS by
|
||||
.An Justin Lloyd .
|
||||
It is now maintained by a number of contributors at
|
||||
.Lk https://github.com/gbdev/rgbds .
|
||||
20
man/rgbasm.1
20
man/rgbasm.1
@@ -1,6 +1,6 @@
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd October 21, 2024
|
||||
.Dd May 4, 2025
|
||||
.Dt RGBASM 1
|
||||
.Os
|
||||
.Sh NAME
|
||||
@@ -8,7 +8,7 @@
|
||||
.Nd Game Boy assembler
|
||||
.Sh SYNOPSIS
|
||||
.Nm
|
||||
.Op Fl EVvw
|
||||
.Op Fl EhVvw
|
||||
.Op Fl b Ar chars
|
||||
.Op Fl D Ar name Ns Op = Ns Ar value
|
||||
.Op Fl g Ar chars
|
||||
@@ -67,6 +67,8 @@ Export all labels, including unreferenced and local labels.
|
||||
.It Fl g Ar chars , Fl \-gfx-chars Ar chars
|
||||
Change the four characters used for gfx constants.
|
||||
The defaults are 0123.
|
||||
.It Fl h , Fl \-help
|
||||
Print help text for the program and exit.
|
||||
.It Fl I Ar path , Fl \-include Ar path
|
||||
Add a new
|
||||
.Dq include path ;
|
||||
@@ -200,7 +202,7 @@ section for a list of warnings.
|
||||
Disable all warning output, even when turned into errors.
|
||||
.It Fl X Ar max_errors , Fl \-max-errors Ar max_errors
|
||||
If more than this number of errors (not warnings) occur, then abort the assembly process;
|
||||
.Fl X 0
|
||||
.Fl X Ar 0
|
||||
disables this behavior.
|
||||
The default is 100 if
|
||||
.Nm
|
||||
@@ -370,6 +372,17 @@ only warns if the active charmap is not empty.
|
||||
.Fl Wunmapped-char=2
|
||||
warns if the active charmap is empty, and/or is not the default charmap
|
||||
.Sq main .
|
||||
.It Fl Wunmatched-directive
|
||||
Warn when a
|
||||
.Ic PUSHC , PUSHO ,
|
||||
or
|
||||
.Ic PUSHS
|
||||
directive does not have a corresponding
|
||||
.Ic POPC , POPO ,
|
||||
or
|
||||
.Ic POPS .
|
||||
This warning is enabled by
|
||||
.Fl Wextra .
|
||||
.It Fl Wunterminated-load
|
||||
Warn when a
|
||||
.Ic LOAD
|
||||
@@ -416,6 +429,7 @@ Please report bugs on
|
||||
.Xr rgbfix 1 ,
|
||||
.Xr rgbgfx 1 ,
|
||||
.Xr gbz80 7 ,
|
||||
.Xr rgbasm-old 5 ,
|
||||
.Xr rgbds 5 ,
|
||||
.Xr rgbds 7
|
||||
.Sh HISTORY
|
||||
|
||||
259
man/rgbasm.5
259
man/rgbasm.5
@@ -2,7 +2,7 @@
|
||||
.\"
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd October 21, 2024
|
||||
.Dd May 4, 2025
|
||||
.Dt RGBASM 5
|
||||
.Os
|
||||
.Sh NAME
|
||||
@@ -253,7 +253,7 @@ Although, for these examples,
|
||||
.Ic STRFMT
|
||||
would be more appropriate; see
|
||||
.Sx String expressions
|
||||
further below.
|
||||
below.
|
||||
.Sh EXPRESSIONS
|
||||
An expression can be composed of many things.
|
||||
Numeric expressions are always evaluated using signed 32-bit math.
|
||||
@@ -267,7 +267,7 @@ This is generally always the case, unless a label is involved, as explained in t
|
||||
section.
|
||||
However, some operators can be constant even with non-constant operands, as explained in
|
||||
.Sx Operators
|
||||
further below.
|
||||
below.
|
||||
.Pp
|
||||
The instructions in the macro-language generally require constant expressions.
|
||||
.Ss Numeric formats
|
||||
@@ -309,26 +309,35 @@ is equivalent to
|
||||
.Pp
|
||||
You can also use symbols, which are implicitly replaced with their value.
|
||||
.Ss Operators
|
||||
A great number of operators you can use in expressions are available (listed from highest to lowest precedence):
|
||||
You can use these operators in numeric expressions (listed from highest to lowest precedence):
|
||||
.Bl -column -offset indent "!= == <= >= < >"
|
||||
.It Sy Operator Ta Sy Meaning
|
||||
.It Li \&( \&) Ta Precedence override
|
||||
.It Li \&( \&) Ta Grouping
|
||||
.It Li FUNC() Ta Built-in function call
|
||||
.It Li ** Ta Exponent
|
||||
.It Li ~ + - Ta Unary complement/plus/minus
|
||||
.It Li * / % Ta Multiply/divide/modulo
|
||||
.It Li << Ta Shift left
|
||||
.It Li >> Ta Signed shift right (sign-extension)
|
||||
.It Li >>> Ta Unsigned shift right (zero-extension)
|
||||
.It Li & \&| ^ Ta Binary and/or/xor
|
||||
.It Li + - Ta Add/subtract
|
||||
.It Li != == <= >= < > Ta Comparison
|
||||
.It Li && || Ta Boolean and/or
|
||||
.It Li \&! Ta Unary not
|
||||
.It Li ** Ta Exponentiation
|
||||
.It Li + - ~ \&! Ta Unary plus, minus (negation), complement (bitwise negation), and Boolean negation
|
||||
.It Li * / % Ta Multiplication, division, and modulo (remainder)
|
||||
.It Li << >> >>> Ta Bit shifts (left, sign-extended right, zero-extended right)
|
||||
.It Li & \&| ^ Ta Bitwise AND/OR/XOR
|
||||
.It Li + - Ta Addition and subtraction
|
||||
.It Li == != < > <= >= Ta Comparisons
|
||||
.It Li && Ta Boolean AND
|
||||
.It Li || Ta Boolean OR
|
||||
.El
|
||||
.Pp
|
||||
.Sq **
|
||||
raises a number to a non-negative power. It is the only
|
||||
.Em right-associative
|
||||
operator, meaning that
|
||||
.Ql p ** q ** r
|
||||
is equal to
|
||||
.Ql p ** (q ** r) ,
|
||||
not
|
||||
.Ql (p ** q) ** r .
|
||||
All other binary operators are left-associative.
|
||||
.Pp
|
||||
.Sq ~
|
||||
complements a value by inverting all its bits.
|
||||
complements a value by inverting all 32 of its bits.
|
||||
.Pp
|
||||
.Sq %
|
||||
is used to get the remainder of the corresponding division, so that
|
||||
@@ -555,22 +564,17 @@ is equivalent to the regular string
|
||||
(Note that this prevents raw strings from including the double quote character.)
|
||||
Raw strings also may be contained in triple quotes for them to be multi-line, so they can include literal newline or quote characters (although still not three quotes in a row).
|
||||
.Pp
|
||||
The following functions operate on string expressions.
|
||||
Most of them return a string, however some of these functions actually return an integer and can be used as part of an integer expression!
|
||||
.Bl -column "STRSUB(str, pos, len)"
|
||||
The following functions operate on string expressions, and return strings themselves.
|
||||
.Bl -column "STRSLICE(str, start, stop)"
|
||||
.It Sy Name Ta Sy Operation
|
||||
.It Fn STRLEN str Ta Returns the number of characters in Ar str .
|
||||
.It Fn STRCAT strs... Ta Concatenates Ar strs .
|
||||
.It Fn STRCMP str1 str2 Ta Returns -1 if Ar str1 No is alphabetically lower than Ar str2 No , zero if they match, 1 if Ar str1 No is greater than Ar str2 .
|
||||
.It Fn STRIN str1 str2 Ta Returns the first position of Ar str2 No in Ar str1 No or zero if it's not present Pq first character is position 1 .
|
||||
.It Fn STRRIN str1 str2 Ta Returns the last position of Ar str2 No in Ar str1 No or zero if it's not present Pq first character is position 1 .
|
||||
.It Fn STRSUB str pos len Ta Returns a substring from Ar str No starting at Ar pos No (first character is position 1, last is position -1) and Ar len No characters long. If Ar len No is not specified the substring continues to the end of Ar str .
|
||||
.It Fn STRUPR str Ta Returns Ar str No with all ASCII letters
|
||||
.Pq Ql a-z
|
||||
in uppercase.
|
||||
.It Fn STRLWR str Ta Returns Ar str No with all ASCII letters
|
||||
.Pq Ql A-Z
|
||||
in lowercase.
|
||||
.It Fn STRSLICE str start stop Ta Returns a substring of Ar str No starting at Ar start No and ending at Ar stop No (exclusive). If Ar stop No is not specified, the substring continues to the end of Ar str Ns .
|
||||
.It Fn STRRPL str old new Ta Returns Ar str No with each non-overlapping occurrence of the substring Ar old No replaced with Ar new .
|
||||
.It Fn STRFMT fmt args... Ta Returns the string Ar fmt No with each
|
||||
.Ql %spec
|
||||
@@ -580,9 +584,35 @@ pattern replaced by interpolating the format
|
||||
with its corresponding argument in
|
||||
.Ar args
|
||||
.Pq So %% Sc is replaced by the So % Sc character .
|
||||
.It Fn INCHARMAP str Ta Returns 1 if Ar str No has an entry in the current charmap, and 0 otherwise .
|
||||
.It Fn STRCHAR str idx Ta Returns the substring of Ar str No for the charmap entry at Ar idx No with the current charmap . Pq Ar idx No counts charmap entries, not characters.
|
||||
.It Fn REVCHAR vals... Ta Returns the string that is mapped to Ar vals No with the current charmap. If there is no unique charmap entry for Ar vals Ns , an error occurs.
|
||||
.El
|
||||
.Pp
|
||||
The following functions operate on string expressions, but return integers.
|
||||
.Bl -column "STRRFIND(str, sub)"
|
||||
.It Sy Name Ta Sy Operation
|
||||
.It Fn STRLEN str Ta Returns the number of characters in Ar str .
|
||||
.It Fn STRCMP str1 str2 Ta Compares Ar str1 No and Ar str2 No according to ASCII ordering of their characters. Returns -1 if Ar str1 No is lower than Ar str2 Ns , 1 if Ar str1 No is greater than Ar str2 Ns , or 0 if they match.
|
||||
.It Fn STRFIND str sub Ta Returns the first index of Ar sub No in Ar str Ns , or -1 if it's not present.
|
||||
.It Fn STRRFIND str sub Ta Returns the last index of Ar sub No in Ar str Ns , or -1 if it's not present.
|
||||
.It Fn INCHARMAP str Ta Returns 1 if Ar str No has an entry in the current charmap, or 0 otherwise .
|
||||
.It Fn CHARLEN str Ta Returns the number of charmap entries in Ar str No with the current charmap .
|
||||
.It Fn CHARSUB str pos Ta Returns the substring for the charmap entry at Ar pos No in Ar str No (first character is position 1, last is position -1) with the current charmap .
|
||||
.It Fn CHARCMP str1 str2 Ta Compares Ar str1 No and Ar str2 No according to their charmap entry values with the current charmap. Returns -1 if Ar str1 No is lower than Ar str2 Ns , 1 if Ar str1 No is greater than Ar str2 Ns , or 0 if they match.
|
||||
.It Fn CHARSIZE char Ta Returns how many values are in the charmap entry for Ar char No with the current charmap.
|
||||
.El
|
||||
.Pp
|
||||
Note that the first character of a string is at index 0, and the last is at index -1.
|
||||
.Pp
|
||||
The following legacy functions are similar to other functions that operate on string expressions, but for historical reasons, they count characters starting from
|
||||
.Em position 1 ,
|
||||
not from index 0!
|
||||
(Position -1 still counts from the last character.)
|
||||
.Bl -column "STRSUB(str, pos, len)"
|
||||
.It Sy Name Ta Sy Operation
|
||||
.It Fn STRSUB str pos len Ta Returns a substring of Ar str No starting at Ar pos No and Ar len No characters long. If Ar len No is not specified, the substring continues to the end of Ar str No .
|
||||
.It Fn STRIN str sub Ta Returns the first position of Ar sub No in Ar str Ns , or 0 if it's not present.
|
||||
.It Fn STRRIN str sub Ta Returns the last position of Ar sub No in Ar str Ns , or 0 if it's not present.
|
||||
.It Fn CHARSUB str pos Ta Returns the substring of Ar str No for the charmap entry at Ar pos No with the current charmap . Pq Ar pos No counts charmap entries, not characters.
|
||||
.El
|
||||
.Ss Character maps
|
||||
When writing text strings that are meant to be displayed on the Game Boy, the character encoding in the ROM may need to be different than the source file encoding.
|
||||
@@ -968,12 +998,14 @@ block before performing its own function.
|
||||
.Ic LOAD
|
||||
blocks can use the
|
||||
.Ic UNION
|
||||
modifier as described below, but not the
|
||||
or
|
||||
.Ic FRAGMENT
|
||||
modifier.
|
||||
modifiers as described in
|
||||
.Sx Unionized sections
|
||||
below.
|
||||
.Ss Unionized sections
|
||||
When you're tight on RAM, you may want to define overlapping static memory allocations, as explained in the
|
||||
.Sx Unions
|
||||
.Sx Allocating overlapping spaces in RAM
|
||||
section.
|
||||
However, a
|
||||
.Ic UNION
|
||||
@@ -1014,7 +1046,7 @@ or
|
||||
.El
|
||||
.Pp
|
||||
Different declarations of the same unionized section are not appended, but instead overlaid on top of each other, just like
|
||||
.Sx Unions .
|
||||
.Sx Allocating overlapping spaces in RAM .
|
||||
Similarly, the size of an unionized section is the largest of all its declarations.
|
||||
.Ss Section fragments
|
||||
Section fragments are sections with a small twist: when several of the same name are encountered, they are concatenated instead of producing an error.
|
||||
@@ -1041,7 +1073,7 @@ and
|
||||
.Ic WRAMX
|
||||
types are still considered different.
|
||||
.It
|
||||
Different constraints (alignment, bank, etc.) can be specified for each unionized section declaration, but they must all be compatible.
|
||||
Different constraints (alignment, bank, etc.) can be specified for each section fragment declaration, but they must all be compatible.
|
||||
For example, alignment must be compatible with any fixed address, all specified banks must be the same, etc.
|
||||
.It
|
||||
A section fragment may not be unionized; after all, that wouldn't make much sense.
|
||||
@@ -1093,7 +1125,9 @@ Additionally, label names can contain up to a single dot
|
||||
.Ql \&. ,
|
||||
which may not be the first character.
|
||||
.Pp
|
||||
A symbol cannot have the same name as a reserved keyword, unless it is prefixed by a hash
|
||||
A symbol cannot have the same name as a reserved keyword, unless its name is a
|
||||
.Dq raw identifier
|
||||
prefixed by a hash
|
||||
.Sq # .
|
||||
For example,
|
||||
.Ql #load
|
||||
@@ -1137,7 +1171,7 @@ otherwise, it is said to be
|
||||
.Dq exported ,
|
||||
explained in
|
||||
.Sx Exporting and importing symbols
|
||||
further below).
|
||||
below).
|
||||
More than one dot in label names is not allowed.
|
||||
.Pp
|
||||
For convenience, local labels can use a shorthand syntax: when a symbol name starting with a dot is found (for example, inside an expression, or when declaring a label), then the current
|
||||
@@ -1268,7 +1302,7 @@ it at the same time.
|
||||
below).
|
||||
.Ss Numeric constants
|
||||
.Ic EQU
|
||||
is used to define immutable numeric symbols.
|
||||
is used to define numeric constant symbols.
|
||||
Unlike
|
||||
.Sq =
|
||||
above, constants defined this way cannot be redefined.
|
||||
@@ -1376,6 +1410,8 @@ This expansion is disabled in a few contexts:
|
||||
and
|
||||
.Ql MACRO name
|
||||
will not expand string constants in their names.
|
||||
Expansion is also disabled if the string constant's name is a raw identifier prefixed by a hash
|
||||
.Sq # .
|
||||
.Bd -literal -offset indent
|
||||
DEF COUNTREG EQUS "[hl+]"
|
||||
ld a, COUNTREG
|
||||
@@ -1567,47 +1603,6 @@ environment variable if that is defined as a UNIX timestamp.
|
||||
Refer to the spec at
|
||||
.Lk https://reproducible-builds.org/docs/source-date-epoch/ reproducible-builds.org .
|
||||
.Sh DEFINING DATA
|
||||
.Ss Statically allocating space in RAM
|
||||
.Ic DS
|
||||
statically allocates a number of empty bytes.
|
||||
This is the preferred method of allocating space in a RAM section.
|
||||
You can also use
|
||||
.Ic DB , DW
|
||||
and
|
||||
.Ic DL
|
||||
without any arguments instead (see
|
||||
.Sx Defining constant data in ROM
|
||||
below).
|
||||
.Bd -literal -offset indent
|
||||
DS 42 ;\ Allocates 42 bytes
|
||||
.Ed
|
||||
.Pp
|
||||
Empty space in RAM sections will not be initialized.
|
||||
In ROM sections, it will be filled with the value passed to the
|
||||
.Fl p
|
||||
command-line option, except when using overlays with
|
||||
.Fl O .
|
||||
.Pp
|
||||
Instead of an exact number of bytes, you can specify
|
||||
.Ic ALIGN Ns Bq Ar align , offset
|
||||
to allocate however many bytes are required to align the subsequent data.
|
||||
Thus,
|
||||
.Sq Ic DS ALIGN Ns Bo Ar align , offset Bc , No ...
|
||||
is equivalent to
|
||||
.Sq Ic DS Ar n , No ...
|
||||
followed by
|
||||
.Sq Ic ALIGN Ns Bq Ar align , offset ,
|
||||
where
|
||||
.Ar n
|
||||
is the minimum value needed to satisfy the
|
||||
.Ic ALIGN
|
||||
constraint (see
|
||||
.Sx Requesting alignment
|
||||
below).
|
||||
Note that
|
||||
.Ic ALIGN Ns Bq Ar align
|
||||
is a shorthand for
|
||||
.Ic ALIGN Ns Bq Ar align , No 0 .
|
||||
.Ss Defining constant data in ROM
|
||||
.Ic DB
|
||||
defines a list of bytes that will be stored in the final image.
|
||||
@@ -1674,7 +1669,7 @@ can be used in a
|
||||
/
|
||||
.Ic SRAM
|
||||
section.
|
||||
.Ss Including binary files
|
||||
.Ss Including binary data files
|
||||
You probably have some graphics, level data, etc. you'd like to include.
|
||||
Use
|
||||
.Ic INCBIN
|
||||
@@ -1698,7 +1693,48 @@ INCBIN "data.bin", 78, 256
|
||||
.Pp
|
||||
The length argument is optional.
|
||||
If only the start position is specified, the bytes from the start position until the end of the file will be included.
|
||||
.Ss Unions
|
||||
.Ss Statically allocating space in RAM
|
||||
.Ic DS
|
||||
statically allocates a number of empty bytes.
|
||||
This is the preferred method of allocating space in a RAM section.
|
||||
You can also use
|
||||
.Ic DB , DW
|
||||
and
|
||||
.Ic DL
|
||||
without any arguments instead (see
|
||||
.Sx Defining constant data in ROM
|
||||
below).
|
||||
.Bd -literal -offset indent
|
||||
DS 42 ;\ Allocates 42 bytes
|
||||
.Ed
|
||||
.Pp
|
||||
Empty space in RAM sections will not be initialized.
|
||||
In ROM sections, it will be filled with the value passed to the
|
||||
.Fl p
|
||||
command-line option, except when using overlays with
|
||||
.Fl O .
|
||||
.Pp
|
||||
Instead of an exact number of bytes, you can specify
|
||||
.Ic ALIGN Ns Bq Ar align , offset
|
||||
to allocate however many bytes are required to align the subsequent data.
|
||||
Thus,
|
||||
.Sq Ic DS ALIGN Ns Bo Ar align , offset Bc , No ...
|
||||
is equivalent to
|
||||
.Sq Ic DS Ar n , No ...
|
||||
followed by
|
||||
.Sq Ic ALIGN Ns Bq Ar align , offset ,
|
||||
where
|
||||
.Ar n
|
||||
is the minimum value needed to satisfy the
|
||||
.Ic ALIGN
|
||||
constraint (see
|
||||
.Sx Requesting alignment
|
||||
below).
|
||||
Note that
|
||||
.Ic ALIGN Ns Bq Ar align
|
||||
is a shorthand for
|
||||
.Ic ALIGN Ns Bq Ar align , No 0 .
|
||||
.Ss Allocating overlapping spaces in RAM
|
||||
Unions allow multiple static memory allocations to overlap, like unions in C.
|
||||
This does not increase the amount of memory available, but allows re-using the same memory region for different purposes.
|
||||
.Pp
|
||||
@@ -1759,6 +1795,37 @@ Unions may be used in any section, but they may only contain space-allocating di
|
||||
.Ic DS
|
||||
(see
|
||||
.Sx Statically allocating space in RAM ) .
|
||||
.Ss Requesting alignment
|
||||
While
|
||||
.Ic ALIGN
|
||||
as presented in
|
||||
.Sx SECTIONS
|
||||
is often useful as-is, sometimes you instead want a particular piece of data (or code) in the middle of the section to be aligned.
|
||||
This is made easier through the use of mid-section
|
||||
.Ic ALIGN Ar align , offset .
|
||||
It will retroactively alter the section's attributes to ensure that the location the
|
||||
.Ic ALIGN
|
||||
directive is at, has its
|
||||
.Ar align
|
||||
lower bits equal to
|
||||
.Ar offset .
|
||||
.Pp
|
||||
If the constraint cannot be met (for example because the section is fixed at an incompatible address), an error is produced.
|
||||
Note that
|
||||
.Ic ALIGN Ar align
|
||||
is a shorthand for
|
||||
.Ic ALIGN Ar align , No 0 .
|
||||
.Pp
|
||||
There may be times when you don't just want to specify an alignment constraint at the current location, but also skip ahead until the constraint can be satisfied.
|
||||
In that case, you can use
|
||||
.Ic DS ALIGN Ns Bq Ar align , offset
|
||||
to allocate however many bytes are required to align the subsequent data.
|
||||
.Pp
|
||||
If the constraint cannot be met by skipping any amount of space, an error is produced.
|
||||
Note that
|
||||
.Ic ALIGN Ns Bq Ar align
|
||||
is a shorthand for
|
||||
.Ic ALIGN Ns Bq Ar align , No 0 .
|
||||
.Sh THE MACRO LANGUAGE
|
||||
.Ss Invoking macros
|
||||
A macro is invoked by using its name at the beginning of a line, like a directive, followed by any comma-separated arguments.
|
||||
@@ -1810,9 +1877,11 @@ being the second, and so on. Since there are only nine digits, you can only use
|
||||
To use the rest, you put the argument number in angle brackets, like
|
||||
.Ic \e<10> .
|
||||
.Pp
|
||||
This bracketed syntax supports decimal numbers and numeric constant symbols.
|
||||
This bracketed syntax supports decimal numbers and numeric symbols, where negative values count from the last argument.
|
||||
For example,
|
||||
.Ql \e<_NARG>
|
||||
or
|
||||
.Ql \e<-1>
|
||||
will get the last argument.
|
||||
.Pp
|
||||
Other macro arguments and symbol interpolations will also be expanded inside the angle brackets.
|
||||
@@ -2320,37 +2389,6 @@ PUSHO b.X, g.oOX
|
||||
DW `..ooOOXX
|
||||
POPO
|
||||
.Ed
|
||||
.Ss Requesting alignment
|
||||
While
|
||||
.Ic ALIGN
|
||||
as presented in
|
||||
.Sx SECTIONS
|
||||
is often useful as-is, sometimes you instead want a particular piece of data (or code) in the middle of the section to be aligned.
|
||||
This is made easier through the use of mid-section
|
||||
.Ic ALIGN Ar align , offset .
|
||||
It will alter the section's attributes to ensure that the location the
|
||||
.Ic ALIGN
|
||||
directive is at, has its
|
||||
.Ar align
|
||||
lower bits equal to
|
||||
.Ar offset .
|
||||
.Pp
|
||||
If the constraint cannot be met (for example because the section is fixed at an incompatible address), an error is produced.
|
||||
Note that
|
||||
.Ic ALIGN Ar align
|
||||
is a shorthand for
|
||||
.Ic ALIGN Ar align , No 0 .
|
||||
.Pp
|
||||
There may be times when you don't just want to specify an alignment constraint at the current location, but also skip ahead until the constraint can be satisfied.
|
||||
In that case, you can use
|
||||
.Ic DS ALIGN Ns Bq Ar align , offset
|
||||
to allocate however many bytes are required to align the subsequent data.
|
||||
.Pp
|
||||
If the constraint cannot be met by skipping any amount of space, an error is produced.
|
||||
Note that
|
||||
.Ic ALIGN Ns Bq Ar align
|
||||
is a shorthand for
|
||||
.Ic ALIGN Ns Bq Ar align , No 0 .
|
||||
.Sh SEE ALSO
|
||||
.Xr rgbasm 1 ,
|
||||
.Xr rgblink 1 ,
|
||||
@@ -2358,6 +2396,7 @@ is a shorthand for
|
||||
.Xr rgbfix 1 ,
|
||||
.Xr rgbgfx 1 ,
|
||||
.Xr gbz80 7 ,
|
||||
.Xr rgbasm-old 5 ,
|
||||
.Xr rgbds 5 ,
|
||||
.Xr rgbds 7
|
||||
.Sh HISTORY
|
||||
|
||||
21
man/rgbds.5
21
man/rgbds.5
@@ -1,6 +1,6 @@
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd October 21, 2024
|
||||
.Dd May 4, 2025
|
||||
.Dt RGBDS 5
|
||||
.Os
|
||||
.Sh NAME
|
||||
@@ -254,7 +254,7 @@ Size of the
|
||||
below.
|
||||
.It Cm BYTE Ar RPNExpr Ns Bq RPNSize
|
||||
The patch's value, encoded as a RPN expression
|
||||
.Pq see Sx RPN EXPRESSIONS .
|
||||
.Pq see Sx RPN expressions .
|
||||
.El
|
||||
.It Cm ENDR
|
||||
.El
|
||||
@@ -294,14 +294,14 @@ Size of the
|
||||
below.
|
||||
.It Cm BYTE Ar RPNExpr Ns Bq RPNSize
|
||||
The patch's value, encoded as a RPN expression
|
||||
.Pq see Sx RPN EXPRESSIONS .
|
||||
.Pq see Sx RPN expressions .
|
||||
.It Cm STRING Ar Message
|
||||
The message displayed if the expression evaluates to a non-zero value.
|
||||
If empty, a generic message is displayed instead.
|
||||
.El
|
||||
.It Cm ENDR
|
||||
.El
|
||||
.Ss RPN EXPRESSIONS
|
||||
.Ss RPN expressions
|
||||
Expressions in the object file are stored as RPN, or
|
||||
.Dq Reverse Polish Notation ,
|
||||
which is a notation that allows computing arbitrary expressions with just a simple stack.
|
||||
@@ -388,10 +388,19 @@ The value is then ANDed with $00FF
|
||||
check.
|
||||
Checks if the value is a valid
|
||||
.Ql rst
|
||||
.Pq see Do RST vec Dc in Xr gbz80 7
|
||||
vector, that is one of $00, $08, $10, $18, $20, $28, $30, or $38.
|
||||
vector
|
||||
.Pq see Do RST vec Dc in Xr gbz80 7 ,
|
||||
that is, one of $00, $08, $10, $18, $20, $28, $30, or $38.
|
||||
The value is then ORed with $C7
|
||||
.Pq Ql \&| $C7 .
|
||||
.It Li $62 Ta Ql bit/res/set
|
||||
check; followed by the instruction's
|
||||
.Cm BYTE
|
||||
mask.
|
||||
Checks if the value is a valid bit index
|
||||
.Pq see e.g. Do BIT u3, r8 Dc in Xr gbz80 7 ,
|
||||
that is, from 0 to 7.
|
||||
The value is then ORed with the instruction's mask.
|
||||
.It Li $80 Ta Integer literal; followed by the
|
||||
.Cm LONG
|
||||
integer.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd October 21, 2024
|
||||
.Dd May 4, 2025
|
||||
.Dt RGBDS 7
|
||||
.Os
|
||||
.Sh NAME
|
||||
@@ -51,6 +51,10 @@ adapts the code to be more UNIX-like and releases this version as rgbds-linux.
|
||||
forks Nossum's repository.
|
||||
The fork becomes the reference implementation of RGBDS.
|
||||
.It
|
||||
2010-09-25: S\(/orensen continues development of
|
||||
.Lk https://github.com/asmotor/asmotor ASMotor
|
||||
to this day.
|
||||
.It
|
||||
2015-01-18:
|
||||
.An stag019
|
||||
begins implementing RGBGFX, a PNG‐to‐Game Boy graphics converter, for eventual integration into RGBDS.
|
||||
|
||||
10
man/rgbfix.1
10
man/rgbfix.1
@@ -1,6 +1,6 @@
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd October 21, 2024
|
||||
.Dd May 4, 2025
|
||||
.Dt RGBFIX 1
|
||||
.Os
|
||||
.Sh NAME
|
||||
@@ -8,7 +8,7 @@
|
||||
.Nd Game Boy header utility and checksum fixer
|
||||
.Sh SYNOPSIS
|
||||
.Nm
|
||||
.Op Fl jOsVv
|
||||
.Op Fl hjOsVv
|
||||
.Op Fl C | c
|
||||
.Op Fl f Ar fix_spec
|
||||
.Op Fl i Ar game_id
|
||||
@@ -17,6 +17,7 @@
|
||||
.Op Fl l Ar licensee_id
|
||||
.Op Fl m Ar mbc_type
|
||||
.Op Fl n Ar rom_version
|
||||
.Op Fl o Ar out_file
|
||||
.Op Fl p Ar pad_value
|
||||
.Op Fl r Ar ram_size
|
||||
.Op Fl t Ar title_str
|
||||
@@ -91,6 +92,8 @@ Fix the global checksum
|
||||
.It Cm G
|
||||
Trash the global checksum.
|
||||
.El
|
||||
.It Fl h , Fl \-help
|
||||
Print help text for the program and exit.
|
||||
.It Fl i Ar game_id , Fl \-game-id Ar game_id
|
||||
Set the game ID string
|
||||
.Pq Ad 0x13F Ns \(en Ns Ad 0x142
|
||||
@@ -132,6 +135,9 @@ Set the ROM version
|
||||
to a given value from 0 to 0xFF.
|
||||
.It Fl O , Fl \-overwrite
|
||||
Allow overwriting different non-zero bytes in the header without a warning being emitted.
|
||||
.It Fl o Ar out_file , Fl \-output Ar out_file
|
||||
Write the modified ROM image to the given file, or '-' to write to standard output.
|
||||
If not specified, the input files are modified in-place, or written to standard output if read from standard input.
|
||||
.It Fl p Ar pad_value , Fl \-pad-value Ar pad_value
|
||||
Pad the ROM image to a valid size with a given pad value from 0 to 255 (0xFF).
|
||||
.Nm
|
||||
|
||||
34
man/rgbgfx.1
34
man/rgbgfx.1
@@ -2,7 +2,7 @@
|
||||
.\"
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd October 21, 2024
|
||||
.Dd May 4, 2025
|
||||
.Dt RGBGFX 1
|
||||
.Os
|
||||
.Sh NAME
|
||||
@@ -10,7 +10,7 @@
|
||||
.Nd Game Boy graphics converter
|
||||
.Sh SYNOPSIS
|
||||
.Nm
|
||||
.Op Fl CmOuVZ
|
||||
.Op Fl CmhOuVXYZ
|
||||
.Op Fl v Op Fl v No ...
|
||||
.Op Fl a Ar attrmap | Fl A
|
||||
.Op Fl b Ar base_ids
|
||||
@@ -103,6 +103,19 @@ and has the same size.
|
||||
Same as
|
||||
.Fl a Ar base_path Ns .attrmap
|
||||
.Pq see Sx Automatic output paths .
|
||||
.It Fl B Ar color , Fl \-background-color Ar color
|
||||
Set a background color to be omitted from output.
|
||||
Colors are accepted in
|
||||
.Ql #rgb
|
||||
or
|
||||
.Ql #rrggbb
|
||||
format, or as
|
||||
.Ql transparent .
|
||||
Input tiles which are entirely the specified background color are ignored and will not be output in tile data file.
|
||||
The tilemap, atrribute map, or palette map files
|
||||
.Em will
|
||||
use placeholder values where background tiles were.
|
||||
If a background color is specified, it cannot be used within tiles which are not ignored.
|
||||
.It Fl b Ar base_ids , Fl \-base-tiles Ar base_ids
|
||||
Set the base IDs for tile map output.
|
||||
.Ar base_ids
|
||||
@@ -126,7 +139,7 @@ begins with a hash character
|
||||
.Ql # ,
|
||||
it is treated as an inline palette specification.
|
||||
It should contain a comma-separated list of hexadecimal colors, each beginning with a hash.
|
||||
Colors are accepted either as
|
||||
Colors are accepted in
|
||||
.Ql #rgb
|
||||
or
|
||||
.Ql #rrggbb
|
||||
@@ -165,6 +178,8 @@ for a list of formats and their descriptions.
|
||||
.It Fl d Ar depth , Fl \-depth Ar depth
|
||||
Set the bit depth of the output tile data, in bits per pixel (bpp), either 1 or 2 (the default).
|
||||
This changes how tile data is output, and the maximum number of colors per palette (2 and 4 respectively).
|
||||
.It Fl h , Fl \-help
|
||||
Print help text for the program and exit.
|
||||
.It Fl i Ar input_tiles , Fl \-input-tileset Ar input_tiles
|
||||
Use the specified input tiles in addition to having
|
||||
.Nm
|
||||
@@ -229,9 +244,8 @@ The second number pair specifies how many tiles to process horizontally and vert
|
||||
.Pp
|
||||
.Fl L Sy is ignored in reverse mode , No no padding is inserted .
|
||||
.It Fl m , Fl \-mirror-tiles
|
||||
Deduplicate tiles that are symmetrical mirror images of each other.
|
||||
Deduplicate tiles that are horizontally and/or vertically symmetrical mirror images of each other.
|
||||
Only one of each unique tile will be saved in the tile data file, with mirror images counting as duplicates.
|
||||
Tiles are checked for horizontal, vertical, and horizontal-vertical mirroring.
|
||||
Useful with a tile map and attribute map together (see
|
||||
.Fl a
|
||||
and
|
||||
@@ -239,6 +253,8 @@ and
|
||||
to keep track of the duplicated tiles and the dimension(s) mirrored.
|
||||
Implies
|
||||
.Fl u .
|
||||
Equivalent to
|
||||
.Fl XY .
|
||||
.It Fl N Ar nb_tiles , Fl \-nb-tiles Ar nb_tiles
|
||||
Set a maximum number of tiles that can be placed in each VRAM bank.
|
||||
.Ar nb_tiles
|
||||
@@ -353,6 +369,10 @@ Some internal debug printing is enabled.
|
||||
The verbosity level does not go past 6.
|
||||
.Pp
|
||||
Note that verbose output is only intended to be consumed by humans, and may change without notice between RGBDS releases; relying on those for scripts is not advised.
|
||||
.It Fl X , Fl \-mirror-x
|
||||
Deduplicate tiles that are horizontally symmetrical mirror images of each other across the X axis.
|
||||
Implies
|
||||
.Fl u .
|
||||
.It Fl x Ar quantity , Fl \-trim-end Ar quantity
|
||||
Do not output the last
|
||||
.Ar quantity
|
||||
@@ -373,6 +393,10 @@ was enabled, so you probably don't want to use this option in combination with
|
||||
Note also that the tiles that don't get output will not count towards
|
||||
.Fl N Ap s
|
||||
limit.
|
||||
.It Fl Y , Fl \-mirror-y
|
||||
Deduplicate tiles that are vertically symmetrical mirror images of each other across the Y axis.
|
||||
Implies
|
||||
.Fl u .
|
||||
.It Fl Z , Fl \-columns
|
||||
Read squares from the PNG in column-major order (column by column), instead of the default row-major order (line by line).
|
||||
This primarily affects tile map and attribute map output, although it may also change generated tile data and palettes.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd October 21, 2024
|
||||
.Dd May 4, 2025
|
||||
.Dt RGBLINK 1
|
||||
.Os
|
||||
.Sh NAME
|
||||
@@ -8,7 +8,7 @@
|
||||
.Nd Game Boy linker
|
||||
.Sh SYNOPSIS
|
||||
.Nm
|
||||
.Op Fl dMtVvwx
|
||||
.Op Fl dhMtVvwx
|
||||
.Op Fl l Ar linker_script
|
||||
.Op Fl m Ar map_file
|
||||
.Op Fl n Ar sym_file
|
||||
@@ -67,6 +67,8 @@ Enable DMG mode.
|
||||
Prohibit the use of sections that doesn't exist on a DMG, such as VRAM bank 1.
|
||||
This option automatically enables
|
||||
.Fl w .
|
||||
.It Fl h , Fl \-help
|
||||
Print help text for the program and exit.
|
||||
.It Fl l Ar linker_script , Fl \-linkerscript Ar linker_script
|
||||
Specify a linker script file that tells the linker how sections must be placed in the ROM.
|
||||
The attributes assigned in the linker script must be consistent with any assigned in the code.
|
||||
@@ -119,8 +121,8 @@ WRAMX sections that are fixed to a bank other than 1 become errors, other WRAMX
|
||||
Disables padding the end of the final file.
|
||||
This option automatically enables
|
||||
.Fl t .
|
||||
You can use this when not not making a ROM.
|
||||
When making a ROM, be careful that not using this is not a replacement for
|
||||
You can use this to make binary files that are not a ROM.
|
||||
When making a ROM, note that not using this is not a replacement for
|
||||
.Xr rgbfix 1 Ap s Fl p
|
||||
option!
|
||||
.El
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd October 21, 2024
|
||||
.Dd May 4, 2025
|
||||
.Dt RGBLINK 5
|
||||
.Os
|
||||
.Sh NAME
|
||||
@@ -120,7 +120,7 @@ causes all sections between it and the next
|
||||
.Ic ORG
|
||||
or bank specification to be placed at addresses automatically determined by
|
||||
.Nm .
|
||||
.Pq It is, however, compatible with Ic ALIGN No below.
|
||||
.Pq \&It is, however, compatible with Ic ALIGN No below.
|
||||
.Pp
|
||||
.Ql Ic ALIGN Ar addr , Ar offset
|
||||
increases the
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "asm/charmap.hpp"
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
|
||||
#include "extern/utf8decoder.hpp"
|
||||
#include "helpers.hpp"
|
||||
#include "util.hpp"
|
||||
|
||||
@@ -30,6 +31,29 @@ struct CharmapNode {
|
||||
struct Charmap {
|
||||
std::string name;
|
||||
std::vector<CharmapNode> nodes; // first node is reserved for the root node
|
||||
|
||||
// Traverse the trie depth-first to derive the character mappings in definition order
|
||||
template<typename F>
|
||||
bool forEachChar(F callback) const {
|
||||
// clang-format off: nested initializers
|
||||
for (std::stack<std::pair<size_t, std::string>> prefixes({{0, ""}}); !prefixes.empty();) {
|
||||
// clang-format on
|
||||
auto [nodeIdx, mapping] = std::move(prefixes.top());
|
||||
prefixes.pop();
|
||||
CharmapNode const &node = nodes[nodeIdx];
|
||||
if (node.isTerminal()) {
|
||||
if (!callback(nodeIdx, mapping)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (unsigned c = 0; c < std::size(node.next); c++) {
|
||||
if (size_t nextIdx = node.next[c]; nextIdx) {
|
||||
prefixes.push({nextIdx, mapping + static_cast<char>(c)});
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
static std::deque<Charmap> charmapList;
|
||||
@@ -43,34 +67,29 @@ bool charmap_ForEach(
|
||||
void (*charFunc)(std::string const &, std::vector<int32_t>)
|
||||
) {
|
||||
for (Charmap const &charmap : charmapList) {
|
||||
// Traverse the trie depth-first to derive the character mappings in definition order
|
||||
std::map<size_t, std::string> mappings;
|
||||
for (std::stack<std::pair<size_t, std::string>> prefixes({{0, ""}}); !prefixes.empty();) {
|
||||
auto [nodeIdx, mapping] = std::move(prefixes.top());
|
||||
prefixes.pop();
|
||||
CharmapNode const &node = charmap.nodes[nodeIdx];
|
||||
if (node.isTerminal())
|
||||
mappings[nodeIdx] = mapping;
|
||||
for (unsigned c = 0; c < 256; c++) {
|
||||
if (size_t nextIdx = node.next[c]; nextIdx)
|
||||
prefixes.push({nextIdx, mapping + (char)c});
|
||||
}
|
||||
}
|
||||
charmap.forEachChar([&mappings](size_t nodeIdx, std::string const &mapping) {
|
||||
mappings[nodeIdx] = mapping;
|
||||
return true;
|
||||
});
|
||||
|
||||
mapFunc(charmap.name);
|
||||
for (auto [nodeIdx, mapping] : mappings)
|
||||
for (auto [nodeIdx, mapping] : mappings) {
|
||||
charFunc(mapping, charmap.nodes[nodeIdx].value);
|
||||
}
|
||||
}
|
||||
return !charmapList.empty();
|
||||
}
|
||||
|
||||
void charmap_New(std::string const &name, std::string const *baseName) {
|
||||
size_t baseIdx = (size_t)-1;
|
||||
size_t baseIdx = SIZE_MAX;
|
||||
|
||||
if (baseName != nullptr) {
|
||||
if (auto search = charmapMap.find(*baseName); search == charmapMap.end())
|
||||
if (auto search = charmapMap.find(*baseName); search == charmapMap.end()) {
|
||||
error("Base charmap '%s' doesn't exist\n", baseName->c_str());
|
||||
else
|
||||
} else {
|
||||
baseIdx = search->second;
|
||||
}
|
||||
}
|
||||
|
||||
if (charmapMap.find(name) != charmapMap.end()) {
|
||||
@@ -82,10 +101,11 @@ void charmap_New(std::string const &name, std::string const *baseName) {
|
||||
charmapMap[name] = charmapList.size();
|
||||
Charmap &charmap = charmapList.emplace_back();
|
||||
|
||||
if (baseIdx != (size_t)-1)
|
||||
if (baseIdx != SIZE_MAX) {
|
||||
charmap.nodes = charmapList[baseIdx].nodes; // Copies `charmapList[baseIdx].nodes`
|
||||
else
|
||||
} else {
|
||||
charmap.nodes.emplace_back(); // Zero-init the root node
|
||||
}
|
||||
|
||||
charmap.name = name;
|
||||
|
||||
@@ -93,10 +113,11 @@ void charmap_New(std::string const &name, std::string const *baseName) {
|
||||
}
|
||||
|
||||
void charmap_Set(std::string const &name) {
|
||||
if (auto search = charmapMap.find(name); search == charmapMap.end())
|
||||
if (auto search = charmapMap.find(name); search == charmapMap.end()) {
|
||||
error("Charmap '%s' doesn't exist\n", name.c_str());
|
||||
else
|
||||
} else {
|
||||
currentCharmap = &charmapList[search->second];
|
||||
}
|
||||
}
|
||||
|
||||
void charmap_Push() {
|
||||
@@ -113,6 +134,12 @@ void charmap_Pop() {
|
||||
charmapStack.pop();
|
||||
}
|
||||
|
||||
void charmap_CheckStack() {
|
||||
if (!charmapStack.empty()) {
|
||||
warning(WARNING_UNMATCHED_DIRECTIVE, "`PUSHC` without corresponding `POPC`\n");
|
||||
}
|
||||
}
|
||||
|
||||
void charmap_Add(std::string const &mapping, std::vector<int32_t> &&value) {
|
||||
if (mapping.empty()) {
|
||||
error("Cannot map an empty string\n");
|
||||
@@ -123,7 +150,7 @@ void charmap_Add(std::string const &mapping, std::vector<int32_t> &&value) {
|
||||
size_t nodeIdx = 0;
|
||||
|
||||
for (char c : mapping) {
|
||||
size_t &nextIdxRef = charmap.nodes[nodeIdx].next[(uint8_t)c];
|
||||
size_t &nextIdxRef = charmap.nodes[nodeIdx].next[static_cast<uint8_t>(c)];
|
||||
size_t nextIdx = nextIdxRef;
|
||||
|
||||
if (!nextIdx) {
|
||||
@@ -140,30 +167,47 @@ void charmap_Add(std::string const &mapping, std::vector<int32_t> &&value) {
|
||||
|
||||
CharmapNode &node = charmap.nodes[nodeIdx];
|
||||
|
||||
if (node.isTerminal())
|
||||
if (node.isTerminal()) {
|
||||
warning(WARNING_CHARMAP_REDEF, "Overriding charmap mapping\n");
|
||||
}
|
||||
|
||||
std::swap(node.value, value);
|
||||
}
|
||||
|
||||
bool charmap_HasChar(std::string const &input) {
|
||||
bool charmap_HasChar(std::string const &mapping) {
|
||||
Charmap const &charmap = *currentCharmap;
|
||||
size_t nodeIdx = 0;
|
||||
|
||||
for (char c : input) {
|
||||
nodeIdx = charmap.nodes[nodeIdx].next[(uint8_t)c];
|
||||
for (char c : mapping) {
|
||||
nodeIdx = charmap.nodes[nodeIdx].next[static_cast<uint8_t>(c)];
|
||||
|
||||
if (!nodeIdx)
|
||||
if (!nodeIdx) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return charmap.nodes[nodeIdx].isTerminal();
|
||||
}
|
||||
|
||||
size_t charmap_CharSize(std::string const &mapping) {
|
||||
Charmap const &charmap = *currentCharmap;
|
||||
size_t nodeIdx = 0;
|
||||
|
||||
for (char c : mapping) {
|
||||
nodeIdx = charmap.nodes[nodeIdx].next[static_cast<uint8_t>(c)];
|
||||
|
||||
if (!nodeIdx) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
CharmapNode const &node = charmap.nodes[nodeIdx];
|
||||
return node.isTerminal() ? node.value.size() : 0;
|
||||
}
|
||||
|
||||
std::vector<int32_t> charmap_Convert(std::string const &input) {
|
||||
std::vector<int32_t> output;
|
||||
for (std::string_view inputView = input; charmap_ConvertNext(inputView, &output);)
|
||||
;
|
||||
for (std::string_view inputView = input; charmap_ConvertNext(inputView, &output);) {}
|
||||
return output;
|
||||
}
|
||||
|
||||
@@ -178,10 +222,11 @@ size_t charmap_ConvertNext(std::string_view &input, std::vector<int32_t> *output
|
||||
size_t inputIdx = 0;
|
||||
|
||||
for (size_t nodeIdx = 0; inputIdx < input.length();) {
|
||||
nodeIdx = charmap.nodes[nodeIdx].next[(uint8_t)input[inputIdx]];
|
||||
nodeIdx = charmap.nodes[nodeIdx].next[static_cast<uint8_t>(input[inputIdx])];
|
||||
|
||||
if (!nodeIdx)
|
||||
if (!nodeIdx) {
|
||||
break;
|
||||
}
|
||||
|
||||
inputIdx++; // Consume that char
|
||||
|
||||
@@ -201,27 +246,42 @@ size_t charmap_ConvertNext(std::string_view &input, std::vector<int32_t> *output
|
||||
if (matchIdx) { // A match was found, use it
|
||||
std::vector<int32_t> const &value = charmap.nodes[matchIdx].value;
|
||||
|
||||
if (output)
|
||||
if (output) {
|
||||
output->insert(output->end(), RANGE(value));
|
||||
}
|
||||
|
||||
matchLen = value.size();
|
||||
} else if (inputIdx < input.length()) { // No match found, but there is some input left
|
||||
int firstChar = input[inputIdx];
|
||||
size_t codepointLen = 0;
|
||||
// This will write the codepoint's value to `output`, little-endian
|
||||
size_t codepointLen = readUTF8Char(output, input.data() + inputIdx);
|
||||
for (uint32_t state = 0, codepoint = 0; inputIdx + codepointLen < input.length();) {
|
||||
if (decode(&state, &codepoint, input[inputIdx + codepointLen]) == 1) {
|
||||
error("Input string is not valid UTF-8\n");
|
||||
codepointLen = 1;
|
||||
break;
|
||||
}
|
||||
codepointLen++;
|
||||
if (state == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (codepointLen == 0)
|
||||
error("Input string is not valid UTF-8\n");
|
||||
if (output) {
|
||||
output->insert(
|
||||
output->end(), input.data() + inputIdx, input.data() + inputIdx + codepointLen
|
||||
);
|
||||
}
|
||||
|
||||
// Warn if this character is not mapped but any others are
|
||||
if (charmap.nodes.size() > 1)
|
||||
if (int firstChar = input[inputIdx]; charmap.nodes.size() > 1) {
|
||||
warning(WARNING_UNMAPPED_CHAR_1, "Unmapped character %s\n", printChar(firstChar));
|
||||
else if (charmap.name != DEFAULT_CHARMAP_NAME)
|
||||
} else if (charmap.name != DEFAULT_CHARMAP_NAME) {
|
||||
warning(
|
||||
WARNING_UNMAPPED_CHAR_2,
|
||||
"Unmapped character %s not in " DEFAULT_CHARMAP_NAME " charmap\n",
|
||||
printChar(firstChar)
|
||||
);
|
||||
}
|
||||
|
||||
inputIdx += codepointLen;
|
||||
matchLen = codepointLen;
|
||||
@@ -230,3 +290,20 @@ size_t charmap_ConvertNext(std::string_view &input, std::vector<int32_t> *output
|
||||
input = input.substr(inputIdx);
|
||||
return matchLen;
|
||||
}
|
||||
|
||||
std::string charmap_Reverse(std::vector<int32_t> const &value, bool &unique) {
|
||||
Charmap const &charmap = *currentCharmap;
|
||||
std::string revMapping;
|
||||
unique = charmap.forEachChar([&](size_t nodeIdx, std::string const &mapping) {
|
||||
if (charmap.nodes[nodeIdx].value == value) {
|
||||
if (revMapping.empty()) {
|
||||
revMapping = mapping;
|
||||
} else {
|
||||
revMapping.clear();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return revMapping;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// Fixed-point math routines
|
||||
|
||||
@@ -16,20 +16,18 @@ uint8_t fix_Precision() {
|
||||
return fixPrecision;
|
||||
}
|
||||
|
||||
double fix_PrecisionFactor() {
|
||||
return pow(2.0, fixPrecision);
|
||||
}
|
||||
|
||||
static double fix2double(int32_t i, int32_t q) {
|
||||
return i / pow(2.0, q);
|
||||
}
|
||||
|
||||
static int32_t double2fix(double d, int32_t q) {
|
||||
if (isnan(d))
|
||||
if (isnan(d)) {
|
||||
return 0;
|
||||
if (isinf(d))
|
||||
}
|
||||
if (isinf(d)) {
|
||||
return d < 0 ? INT32_MIN : INT32_MAX;
|
||||
return (int32_t)round(d * pow(2.0, q));
|
||||
}
|
||||
return static_cast<int32_t>(round(d * pow(2.0, q)));
|
||||
}
|
||||
|
||||
static double turn2rad(double t) {
|
||||
@@ -73,7 +71,12 @@ int32_t fix_Mul(int32_t i, int32_t j, int32_t q) {
|
||||
}
|
||||
|
||||
int32_t fix_Div(int32_t i, int32_t j, int32_t q) {
|
||||
return double2fix(fix2double(i, q) / fix2double(j, q), q);
|
||||
double dividend = fix2double(i, q);
|
||||
double divisor = fix2double(j, q);
|
||||
if (fpclassify(divisor) == FP_ZERO) {
|
||||
return dividend < 0 ? INT32_MIN : dividend > 0 ? INT32_MAX : 0;
|
||||
}
|
||||
return double2fix(dividend / divisor, q);
|
||||
}
|
||||
|
||||
int32_t fix_Mod(int32_t i, int32_t j, int32_t q) {
|
||||
@@ -85,7 +88,11 @@ int32_t fix_Pow(int32_t i, int32_t j, int32_t q) {
|
||||
}
|
||||
|
||||
int32_t fix_Log(int32_t i, int32_t j, int32_t q) {
|
||||
return double2fix(log(fix2double(i, q)) / log(fix2double(j, q)), q);
|
||||
double divisor = log(fix2double(j, q));
|
||||
if (fpclassify(divisor) == FP_ZERO) {
|
||||
return INT32_MAX;
|
||||
}
|
||||
return double2fix(log(fix2double(i, q)) / divisor, q);
|
||||
}
|
||||
|
||||
int32_t fix_Round(int32_t i, int32_t q) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "asm/format.hpp"
|
||||
|
||||
@@ -13,39 +13,44 @@
|
||||
#include "asm/warning.hpp"
|
||||
|
||||
void FormatSpec::useCharacter(int c) {
|
||||
if (state == FORMAT_INVALID)
|
||||
if (state == FORMAT_INVALID) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (c) {
|
||||
// sign
|
||||
case ' ':
|
||||
case '+':
|
||||
if (state > FORMAT_SIGN)
|
||||
goto invalid;
|
||||
if (state > FORMAT_SIGN) {
|
||||
break;
|
||||
}
|
||||
state = FORMAT_EXACT;
|
||||
sign = c;
|
||||
break;
|
||||
return;
|
||||
|
||||
// exact
|
||||
case '#':
|
||||
if (state > FORMAT_EXACT)
|
||||
goto invalid;
|
||||
if (state > FORMAT_EXACT) {
|
||||
break;
|
||||
}
|
||||
state = FORMAT_ALIGN;
|
||||
exact = true;
|
||||
break;
|
||||
return;
|
||||
|
||||
// align
|
||||
case '-':
|
||||
if (state > FORMAT_ALIGN)
|
||||
goto invalid;
|
||||
if (state > FORMAT_ALIGN) {
|
||||
break;
|
||||
}
|
||||
state = FORMAT_WIDTH;
|
||||
alignLeft = true;
|
||||
break;
|
||||
return;
|
||||
|
||||
// pad, width, and prec values
|
||||
case '0':
|
||||
if (state < FORMAT_WIDTH)
|
||||
if (state < FORMAT_WIDTH) {
|
||||
padZero = true;
|
||||
}
|
||||
[[fallthrough]];
|
||||
case '1':
|
||||
case '2':
|
||||
@@ -66,25 +71,27 @@ void FormatSpec::useCharacter(int c) {
|
||||
} else if (state == FORMAT_PREC) {
|
||||
precision = precision * 10 + (c - '0');
|
||||
} else {
|
||||
goto invalid;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
return;
|
||||
|
||||
// width
|
||||
case '.':
|
||||
if (state > FORMAT_WIDTH)
|
||||
goto invalid;
|
||||
if (state > FORMAT_WIDTH) {
|
||||
break;
|
||||
}
|
||||
state = FORMAT_FRAC;
|
||||
hasFrac = true;
|
||||
break;
|
||||
return;
|
||||
|
||||
// prec
|
||||
case 'q':
|
||||
if (state > FORMAT_PREC)
|
||||
goto invalid;
|
||||
if (state > FORMAT_PREC) {
|
||||
break;
|
||||
}
|
||||
state = FORMAT_PREC;
|
||||
hasPrec = true;
|
||||
break;
|
||||
return;
|
||||
|
||||
// type
|
||||
case 'd':
|
||||
@@ -95,23 +102,26 @@ void FormatSpec::useCharacter(int c) {
|
||||
case 'o':
|
||||
case 'f':
|
||||
case 's':
|
||||
if (state >= FORMAT_DONE)
|
||||
goto invalid;
|
||||
if (state >= FORMAT_DONE) {
|
||||
break;
|
||||
}
|
||||
state = FORMAT_DONE;
|
||||
valid = true;
|
||||
type = c;
|
||||
break;
|
||||
return;
|
||||
|
||||
default:
|
||||
invalid:
|
||||
state = FORMAT_INVALID;
|
||||
valid = false;
|
||||
break;
|
||||
}
|
||||
|
||||
state = FORMAT_INVALID;
|
||||
valid = false;
|
||||
}
|
||||
|
||||
void FormatSpec::finishCharacters() {
|
||||
if (!isValid())
|
||||
if (!isValid()) {
|
||||
state = FORMAT_INVALID;
|
||||
}
|
||||
}
|
||||
|
||||
static std::string escapeString(std::string const &str) {
|
||||
@@ -151,16 +161,21 @@ void FormatSpec::appendString(std::string &str, std::string const &value) const
|
||||
useType = 's';
|
||||
}
|
||||
|
||||
if (sign)
|
||||
if (sign) {
|
||||
error("Formatting string with sign flag '%c'\n", sign);
|
||||
if (padZero)
|
||||
}
|
||||
if (padZero) {
|
||||
error("Formatting string with padding flag '0'\n");
|
||||
if (hasFrac)
|
||||
}
|
||||
if (hasFrac) {
|
||||
error("Formatting string with fractional width\n");
|
||||
if (hasPrec)
|
||||
}
|
||||
if (hasPrec) {
|
||||
error("Formatting string with fractional precision\n");
|
||||
if (useType != 's')
|
||||
}
|
||||
if (useType != 's') {
|
||||
error("Formatting string as type '%c'\n", useType);
|
||||
}
|
||||
|
||||
std::string useValue = exact ? escapeString(value) : value;
|
||||
size_t valueLen = useValue.length();
|
||||
@@ -187,22 +202,27 @@ void FormatSpec::appendNumber(std::string &str, uint32_t value) const {
|
||||
}
|
||||
|
||||
if (useType != 'X' && useType != 'x' && useType != 'b' && useType != 'o' && useType != 'f'
|
||||
&& useExact)
|
||||
&& useExact) {
|
||||
error("Formatting type '%c' with exact flag '#'\n", useType);
|
||||
if (useType != 'f' && hasFrac)
|
||||
}
|
||||
if (useType != 'f' && hasFrac) {
|
||||
error("Formatting type '%c' with fractional width\n", useType);
|
||||
if (useType != 'f' && hasPrec)
|
||||
}
|
||||
if (useType != 'f' && hasPrec) {
|
||||
error("Formatting type '%c' with fractional precision\n", useType);
|
||||
if (useType == 's')
|
||||
}
|
||||
if (useType == 's') {
|
||||
error("Formatting number as type 's'\n");
|
||||
}
|
||||
|
||||
char signChar = sign; // 0 or ' ' or '+'
|
||||
|
||||
if (useType == 'd' || useType == 'f') {
|
||||
if (int32_t v = value; v < 0) {
|
||||
signChar = '-';
|
||||
if (v != INT32_MIN)
|
||||
if (v != INT32_MIN) {
|
||||
value = -v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -250,15 +270,17 @@ void FormatSpec::appendNumber(std::string &str, uint32_t value) const {
|
||||
}
|
||||
|
||||
double fval = fabs(value / pow(2.0, usePrec));
|
||||
if (useExact)
|
||||
snprintf(valueBuf, sizeof(valueBuf), "%.*fq%zu", (int)useFracWidth, fval, usePrec);
|
||||
else
|
||||
snprintf(valueBuf, sizeof(valueBuf), "%.*f", (int)useFracWidth, fval);
|
||||
if (int fracWidthArg = static_cast<int>(useFracWidth); useExact) {
|
||||
snprintf(valueBuf, sizeof(valueBuf), "%.*fq%zu", fracWidthArg, fval, usePrec);
|
||||
} else {
|
||||
snprintf(valueBuf, sizeof(valueBuf), "%.*f", fracWidthArg, fval);
|
||||
}
|
||||
} else if (useType == 'd') {
|
||||
// Decimal numbers may be formatted with a '-' sign by `snprintf`, so `abs` prevents that,
|
||||
// with a special case for `INT32_MIN` since `labs(INT32_MIN)` is UB. The sign will be
|
||||
// printed later from `signChar`.
|
||||
uint32_t uval = value != (uint32_t)INT32_MIN ? labs((int32_t)value) : value;
|
||||
uint32_t uval =
|
||||
value != static_cast<uint32_t>(INT32_MIN) ? labs(static_cast<int32_t>(value)) : value;
|
||||
snprintf(valueBuf, sizeof(valueBuf), "%" PRIu32, uval);
|
||||
} else {
|
||||
char const *spec = useType == 'u' ? "%" PRIu32
|
||||
@@ -277,27 +299,33 @@ void FormatSpec::appendNumber(std::string &str, uint32_t value) const {
|
||||
|
||||
str.reserve(str.length() + totalLen);
|
||||
if (alignLeft) {
|
||||
if (signChar)
|
||||
if (signChar) {
|
||||
str += signChar;
|
||||
if (prefixChar)
|
||||
}
|
||||
if (prefixChar) {
|
||||
str += prefixChar;
|
||||
}
|
||||
str.append(valueBuf);
|
||||
str.append(padLen, ' ');
|
||||
} else {
|
||||
if (padZero) {
|
||||
// sign, then prefix, then zero padding
|
||||
if (signChar)
|
||||
if (signChar) {
|
||||
str += signChar;
|
||||
if (prefixChar)
|
||||
}
|
||||
if (prefixChar) {
|
||||
str += prefixChar;
|
||||
}
|
||||
str.append(padLen, '0');
|
||||
} else {
|
||||
// space padding, then sign, then prefix
|
||||
str.append(padLen, ' ');
|
||||
if (signChar)
|
||||
if (signChar) {
|
||||
str += signChar;
|
||||
if (prefixChar)
|
||||
}
|
||||
if (prefixChar) {
|
||||
str += prefixChar;
|
||||
}
|
||||
}
|
||||
str.append(valueBuf);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "asm/fstack.hpp"
|
||||
#include <sys/stat.h>
|
||||
@@ -90,8 +90,9 @@ std::shared_ptr<std::string> fstk_GetUniqueIDStr() {
|
||||
std::shared_ptr<std::string> &str = contextStack.top().uniqueIDStr;
|
||||
|
||||
// If a unique ID is allowed but has not been generated yet, generate one now.
|
||||
if (str && str->empty())
|
||||
if (str && str->empty()) {
|
||||
*str = "_u"s + std::to_string(nextUniqueID++);
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
@@ -103,27 +104,34 @@ MacroArgs *fstk_GetCurrentMacroArgs() {
|
||||
}
|
||||
|
||||
void fstk_AddIncludePath(std::string const &path) {
|
||||
if (path.empty())
|
||||
if (path.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::string &includePath = includePaths.emplace_back(path);
|
||||
if (includePath.back() != '/')
|
||||
if (includePath.back() != '/') {
|
||||
includePath += '/';
|
||||
}
|
||||
}
|
||||
|
||||
void fstk_SetPreIncludeFile(std::string const &path) {
|
||||
if (!preIncludeName.empty())
|
||||
if (!preIncludeName.empty()) {
|
||||
warnx("Overriding pre-included filename %s", preIncludeName.c_str());
|
||||
}
|
||||
preIncludeName = path;
|
||||
if (verbose)
|
||||
// LCOV_EXCL_START
|
||||
if (verbose) {
|
||||
printf("Pre-included filename %s\n", preIncludeName.c_str());
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
static void printDep(std::string const &path) {
|
||||
if (dependFile) {
|
||||
fprintf(dependFile, "%s: %s\n", targetFileName.c_str(), path.c_str());
|
||||
if (generatePhonyDeps)
|
||||
if (generatePhonyDeps) {
|
||||
fprintf(dependFile, "%s:\n", path.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,20 +149,22 @@ std::optional<std::string> fstk_FindFile(std::string const &path) {
|
||||
}
|
||||
|
||||
errno = ENOENT;
|
||||
if (generatedMissingIncludes)
|
||||
if (generatedMissingIncludes) {
|
||||
printDep(path);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
bool yywrap() {
|
||||
uint32_t ifDepth = lexer_GetIFDepth();
|
||||
|
||||
if (ifDepth != 0)
|
||||
if (ifDepth != 0) {
|
||||
fatalerror(
|
||||
"Ended block with %" PRIu32 " unterminated IF construct%s\n",
|
||||
ifDepth,
|
||||
ifDepth == 1 ? "" : "s"
|
||||
);
|
||||
}
|
||||
|
||||
if (Context &context = contextStack.top(); context.fileInfo->type == NODE_REPT) {
|
||||
// The context is a REPT or FOR block, which may loop
|
||||
@@ -162,7 +172,7 @@ bool yywrap() {
|
||||
// If the node is referenced outside this context, we can't edit it, so duplicate it
|
||||
if (context.fileInfo.use_count() > 1) {
|
||||
context.fileInfo = std::make_shared<FileStackNode>(*context.fileInfo);
|
||||
context.fileInfo->ID = -1; // The copy is not yet registered
|
||||
context.fileInfo->ID = UINT32_MAX; // The copy is not yet registered
|
||||
}
|
||||
|
||||
std::vector<uint32_t> &fileInfoIters = context.fileInfo->iters();
|
||||
@@ -170,13 +180,16 @@ bool yywrap() {
|
||||
// If this is a FOR, update the symbol value
|
||||
if (context.isForLoop && fileInfoIters.front() <= context.nbReptIters) {
|
||||
// Avoid arithmetic overflow runtime error
|
||||
uint32_t forValue = (uint32_t)context.forValue + (uint32_t)context.forStep;
|
||||
context.forValue = forValue <= INT32_MAX ? forValue : -(int32_t)~forValue - 1;
|
||||
uint32_t forValue =
|
||||
static_cast<uint32_t>(context.forValue) + static_cast<uint32_t>(context.forStep);
|
||||
context.forValue =
|
||||
forValue <= INT32_MAX ? forValue : -static_cast<int32_t>(~forValue) - 1;
|
||||
Symbol *sym = sym_AddVar(context.forName, context.forValue);
|
||||
|
||||
// This error message will refer to the current iteration
|
||||
if (sym->type != SYM_VAR)
|
||||
if (sym->type != SYM_VAR) {
|
||||
fatalerror("Failed to update FOR symbol value\n");
|
||||
}
|
||||
}
|
||||
// Advance to the next iteration
|
||||
fileInfoIters.front()++;
|
||||
@@ -197,8 +210,9 @@ bool yywrap() {
|
||||
}
|
||||
|
||||
static void checkRecursionDepth() {
|
||||
if (contextStack.size() > maxRecursionDepth)
|
||||
if (contextStack.size() > maxRecursionDepth) {
|
||||
fatalerror("Recursion limit (%zu) exceeded\n", maxRecursionDepth);
|
||||
}
|
||||
}
|
||||
|
||||
static bool newFileContext(std::string const &filePath, bool updateStateNow) {
|
||||
@@ -208,7 +222,7 @@ static bool newFileContext(std::string const &filePath, bool updateStateNow) {
|
||||
std::shared_ptr<MacroArgs> macroArgs = nullptr;
|
||||
|
||||
auto fileInfo =
|
||||
std::make_shared<FileStackNode>(NODE_MACRO, filePath == "-" ? "<stdin>" : filePath);
|
||||
std::make_shared<FileStackNode>(NODE_FILE, filePath == "-" ? "<stdin>" : filePath);
|
||||
if (!contextStack.empty()) {
|
||||
Context &oldContext = contextStack.top();
|
||||
fileInfo->parent = oldContext.fileInfo;
|
||||
@@ -296,8 +310,11 @@ void fstk_RunInclude(std::string const &path, bool preInclude) {
|
||||
|
||||
if (!fullPath) {
|
||||
if (generatedMissingIncludes && !preInclude) {
|
||||
if (verbose)
|
||||
// LCOV_EXCL_START
|
||||
if (verbose) {
|
||||
printf("Aborting (-MG) on INCLUDE file '%s' (%s)\n", path.c_str(), strerror(errno));
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
failedOnMissingInclude = true;
|
||||
} else {
|
||||
error("Unable to open included file '%s': %s\n", path.c_str(), strerror(errno));
|
||||
@@ -305,18 +322,20 @@ void fstk_RunInclude(std::string const &path, bool preInclude) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!newFileContext(*fullPath, false))
|
||||
fatalerror("Failed to set up lexer for file include\n");
|
||||
if (!newFileContext(*fullPath, false)) {
|
||||
fatalerror("Failed to set up lexer for file include\n"); // LCOV_EXCL_LINE
|
||||
}
|
||||
}
|
||||
|
||||
void fstk_RunMacro(std::string const ¯oName, std::shared_ptr<MacroArgs> macroArgs) {
|
||||
Symbol *macro = sym_FindExactSymbol(macroName);
|
||||
|
||||
if (!macro) {
|
||||
if (sym_IsPurgedExact(macroName))
|
||||
if (sym_IsPurgedExact(macroName)) {
|
||||
error("Macro \"%s\" not defined; it was purged\n", macroName.c_str());
|
||||
else
|
||||
} else {
|
||||
error("Macro \"%s\" not defined\n", macroName.c_str());
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (macro->type != SYM_MACRO) {
|
||||
@@ -328,8 +347,9 @@ void fstk_RunMacro(std::string const ¯oName, std::shared_ptr<MacroArgs> macr
|
||||
}
|
||||
|
||||
void fstk_RunRept(uint32_t count, int32_t reptLineNo, ContentSpan const &span) {
|
||||
if (count == 0)
|
||||
if (count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
newReptContext(reptLineNo, span, count);
|
||||
}
|
||||
@@ -342,24 +362,28 @@ void fstk_RunFor(
|
||||
int32_t reptLineNo,
|
||||
ContentSpan const &span
|
||||
) {
|
||||
if (Symbol *sym = sym_AddVar(symName, start); sym->type != SYM_VAR)
|
||||
if (Symbol *sym = sym_AddVar(symName, start); sym->type != SYM_VAR) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t count = 0;
|
||||
if (step > 0 && start < stop)
|
||||
count = ((int64_t)stop - start - 1) / step + 1;
|
||||
else if (step < 0 && stop < start)
|
||||
count = ((int64_t)start - stop - 1) / -(int64_t)step + 1;
|
||||
else if (step == 0)
|
||||
if (step > 0 && start < stop) {
|
||||
count = (static_cast<int64_t>(stop) - start - 1) / step + 1;
|
||||
} else if (step < 0 && stop < start) {
|
||||
count = (static_cast<int64_t>(start) - stop - 1) / -static_cast<int64_t>(step) + 1;
|
||||
} else if (step == 0) {
|
||||
error("FOR cannot have a step value of 0\n");
|
||||
}
|
||||
|
||||
if ((step > 0 && start > stop) || (step < 0 && start < stop))
|
||||
if ((step > 0 && start > stop) || (step < 0 && start < stop)) {
|
||||
warning(
|
||||
WARNING_BACKWARDS_FOR, "FOR goes backwards from %d to %d by %d\n", start, stop, step
|
||||
);
|
||||
}
|
||||
|
||||
if (count == 0)
|
||||
if (count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
Context &context = newReptContext(reptLineNo, span, count);
|
||||
context.isForLoop = true;
|
||||
@@ -383,17 +407,20 @@ bool fstk_Break() {
|
||||
}
|
||||
|
||||
void fstk_NewRecursionDepth(size_t newDepth) {
|
||||
if (contextStack.size() > newDepth + 1)
|
||||
if (contextStack.size() > newDepth + 1) {
|
||||
fatalerror("Recursion limit (%zu) exceeded\n", newDepth);
|
||||
}
|
||||
maxRecursionDepth = newDepth;
|
||||
}
|
||||
|
||||
void fstk_Init(std::string const &mainPath, size_t maxDepth) {
|
||||
if (!newFileContext(mainPath, true))
|
||||
if (!newFileContext(mainPath, true)) {
|
||||
fatalerror("Failed to open main file\n");
|
||||
}
|
||||
|
||||
maxRecursionDepth = maxDepth;
|
||||
|
||||
if (!preIncludeName.empty())
|
||||
if (!preIncludeName.empty()) {
|
||||
fstk_RunInclude(preIncludeName, true);
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "asm/macro.hpp"
|
||||
|
||||
@@ -6,28 +6,32 @@
|
||||
#include <string.h>
|
||||
#include <string>
|
||||
|
||||
#include "helpers.hpp"
|
||||
|
||||
#include "asm/warning.hpp"
|
||||
|
||||
#define MAXMACROARGS 99999
|
||||
std::shared_ptr<std::string> MacroArgs::getArg(int32_t i) const {
|
||||
// Bracketed macro arguments adjust negative indexes such that -1 is the last argument.
|
||||
if (i < 0) {
|
||||
i += args.size() + 1;
|
||||
}
|
||||
|
||||
std::shared_ptr<std::string> MacroArgs::getArg(uint32_t i) const {
|
||||
uint32_t realIndex = i + shift - 1;
|
||||
int32_t realIndex = i + shift - 1;
|
||||
|
||||
return realIndex >= args.size() ? nullptr : args[realIndex];
|
||||
return realIndex < 0 || static_cast<uint32_t>(realIndex) >= args.size() ? nullptr
|
||||
: args[realIndex];
|
||||
}
|
||||
|
||||
std::shared_ptr<std::string> MacroArgs::getAllArgs() const {
|
||||
size_t nbArgs = args.size();
|
||||
|
||||
if (shift >= nbArgs)
|
||||
if (shift >= nbArgs) {
|
||||
return std::make_shared<std::string>("");
|
||||
}
|
||||
|
||||
size_t len = 0;
|
||||
|
||||
for (uint32_t i = shift; i < nbArgs; i++)
|
||||
for (uint32_t i = shift; i < nbArgs; i++) {
|
||||
len += args[i]->length() + 1; // 1 for comma
|
||||
}
|
||||
|
||||
auto str = std::make_shared<std::string>();
|
||||
str->reserve(len + 1); // 1 for comma
|
||||
@@ -38,27 +42,27 @@ std::shared_ptr<std::string> MacroArgs::getAllArgs() const {
|
||||
str->append(*arg);
|
||||
|
||||
// Commas go between args and after a last empty arg
|
||||
if (i < nbArgs - 1 || arg->empty())
|
||||
if (i < nbArgs - 1 || arg->empty()) {
|
||||
str->push_back(','); // no space after comma
|
||||
}
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
void MacroArgs::appendArg(std::shared_ptr<std::string> arg) {
|
||||
if (arg->empty())
|
||||
if (arg->empty()) {
|
||||
warning(WARNING_EMPTY_MACRO_ARG, "Empty macro argument\n");
|
||||
if (args.size() == MAXMACROARGS)
|
||||
error("A maximum of " EXPAND_AND_STR(MAXMACROARGS) " arguments is allowed\n");
|
||||
}
|
||||
args.push_back(arg);
|
||||
}
|
||||
|
||||
void MacroArgs::shiftArgs(int32_t count) {
|
||||
if (size_t nbArgs = args.size();
|
||||
count > 0 && ((uint32_t)count > nbArgs || shift > nbArgs - count)) {
|
||||
count > 0 && (static_cast<uint32_t>(count) > nbArgs || shift > nbArgs - count)) {
|
||||
warning(WARNING_MACRO_SHIFT, "Cannot shift macro arguments past their end\n");
|
||||
shift = nbArgs;
|
||||
} else if (count < 0 && shift < (uint32_t)-count) {
|
||||
} else if (count < 0 && shift < static_cast<uint32_t>(-count)) {
|
||||
warning(WARNING_MACRO_SHIFT, "Cannot shift macro arguments past their beginning\n");
|
||||
shift = 0;
|
||||
} else {
|
||||
|
||||
148
src/asm/main.cpp
148
src/asm/main.cpp
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "asm/main.hpp"
|
||||
|
||||
@@ -37,30 +37,30 @@ static std::string make_escape(std::string &str) {
|
||||
for (;;) {
|
||||
// All dollars needs to be doubled
|
||||
size_t nextPos = str.find("$", pos);
|
||||
if (nextPos == std::string::npos)
|
||||
if (nextPos == std::string::npos) {
|
||||
break;
|
||||
}
|
||||
escaped.append(str, pos, nextPos - pos);
|
||||
escaped.append("$$");
|
||||
pos = nextPos + QUOTEDSTRLEN("$");
|
||||
pos = nextPos + literal_strlen("$");
|
||||
}
|
||||
escaped.append(str, pos, str.length() - pos);
|
||||
return escaped;
|
||||
}
|
||||
|
||||
// Short options
|
||||
static char const *optstring = "b:D:Eg:I:M:o:P:p:Q:r:s:VvW:wX:";
|
||||
static char const *optstring = "b:D:Eg:hI:M:o:P:p:Q:r:s:VvW:wX:";
|
||||
|
||||
// Variables for the long-only options
|
||||
static int depType; // Variants of `-M`
|
||||
|
||||
// Equivalent long options
|
||||
// Please keep in the same order as short opts
|
||||
//
|
||||
// Please keep in the same order as short opts.
|
||||
// Also, make sure long opts don't create ambiguity:
|
||||
// A long opt's name should start with the same letter as its short opt,
|
||||
// except if it doesn't create any ambiguity (`verbose` versus `version`).
|
||||
// This is because long opt matching, even to a single char, is prioritized
|
||||
// over short opt matching
|
||||
// over short opt matching.
|
||||
static option const longopts[] = {
|
||||
{"binary-digits", required_argument, nullptr, 'b'},
|
||||
{"define", required_argument, nullptr, 'D'},
|
||||
@@ -69,6 +69,7 @@ static option const longopts[] = {
|
||||
{"include", required_argument, nullptr, 'I'},
|
||||
{"dependfile", required_argument, nullptr, 'M'},
|
||||
{"MG", no_argument, &depType, 'G'},
|
||||
{"help", no_argument, nullptr, 'h'},
|
||||
{"MP", no_argument, &depType, 'P'},
|
||||
{"MT", required_argument, &depType, 'T'},
|
||||
{"warning", required_argument, nullptr, 'W'},
|
||||
@@ -86,9 +87,10 @@ static option const longopts[] = {
|
||||
{nullptr, no_argument, nullptr, 0 }
|
||||
};
|
||||
|
||||
// LCOV_EXCL_START
|
||||
static void printUsage() {
|
||||
fputs(
|
||||
"Usage: rgbasm [-EVvw] [-b chars] [-D name[=value]] [-g chars] [-I path]\n"
|
||||
"Usage: rgbasm [-EhVvw] [-b chars] [-D name[=value]] [-g chars] [-I path]\n"
|
||||
" [-M depend_file] [-MG] [-MP] [-MT target_file] [-MQ target_file]\n"
|
||||
" [-o out_file] [-P include_file] [-p pad_value] [-Q precision]\n"
|
||||
" [-r depth] [-s features:state_file] [-W warning] [-X max_errors]\n"
|
||||
@@ -106,17 +108,20 @@ static void printUsage() {
|
||||
stderr
|
||||
);
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
time_t now = time(nullptr);
|
||||
// Support SOURCE_DATE_EPOCH for reproducible builds
|
||||
// https://reproducible-builds.org/docs/source-date-epoch/
|
||||
if (char const *sourceDateEpoch = getenv("SOURCE_DATE_EPOCH"); sourceDateEpoch)
|
||||
now = (time_t)strtoul(sourceDateEpoch, nullptr, 0);
|
||||
if (char const *sourceDateEpoch = getenv("SOURCE_DATE_EPOCH"); sourceDateEpoch) {
|
||||
now = static_cast<time_t>(strtoul(sourceDateEpoch, nullptr, 0));
|
||||
}
|
||||
|
||||
Defer closeDependFile{[&] {
|
||||
if (dependFile)
|
||||
if (dependFile) {
|
||||
fclose(dependFile);
|
||||
}
|
||||
}};
|
||||
|
||||
// Perform some init for below
|
||||
@@ -128,23 +133,25 @@ int main(int argc, char *argv[]) {
|
||||
opt_P(0);
|
||||
opt_Q(16);
|
||||
sym_SetExportAll(false);
|
||||
uint32_t maxDepth = DEFAULT_MAX_DEPTH;
|
||||
uint32_t maxDepth = 64;
|
||||
char const *dependFileName = nullptr;
|
||||
std::unordered_map<std::string, std::vector<StateFeature>> stateFileSpecs;
|
||||
std::string newTarget;
|
||||
// Maximum of 100 errors only applies if rgbasm is printing errors to a terminal.
|
||||
if (isatty(STDERR_FILENO))
|
||||
if (isatty(STDERR_FILENO)) {
|
||||
maxErrors = 100;
|
||||
}
|
||||
|
||||
for (int ch; (ch = musl_getopt_long_only(argc, argv, optstring, longopts, nullptr)) != -1;) {
|
||||
switch (ch) {
|
||||
char *endptr;
|
||||
|
||||
case 'b':
|
||||
if (strlen(musl_optarg) == 2)
|
||||
if (strlen(musl_optarg) == 2) {
|
||||
opt_B(musl_optarg);
|
||||
else
|
||||
} else {
|
||||
errx("Must specify exactly 2 characters for option 'b'");
|
||||
}
|
||||
break;
|
||||
|
||||
char *equals;
|
||||
@@ -163,19 +170,27 @@ int main(int argc, char *argv[]) {
|
||||
break;
|
||||
|
||||
case 'g':
|
||||
if (strlen(musl_optarg) == 4)
|
||||
if (strlen(musl_optarg) == 4) {
|
||||
opt_G(musl_optarg);
|
||||
else
|
||||
} else {
|
||||
errx("Must specify exactly 4 characters for option 'g'");
|
||||
}
|
||||
break;
|
||||
|
||||
case 'h':
|
||||
// LCOV_EXCL_START
|
||||
printUsage();
|
||||
exit(0);
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
case 'I':
|
||||
fstk_AddIncludePath(musl_optarg);
|
||||
break;
|
||||
|
||||
case 'M':
|
||||
if (dependFile)
|
||||
if (dependFile) {
|
||||
warnx("Overriding dependfile %s", dependFileName);
|
||||
}
|
||||
if (strcmp("-", musl_optarg)) {
|
||||
dependFile = fopen(musl_optarg, "w");
|
||||
dependFileName = musl_optarg;
|
||||
@@ -183,8 +198,9 @@ int main(int argc, char *argv[]) {
|
||||
dependFile = stdout;
|
||||
dependFileName = "<stdout>";
|
||||
}
|
||||
if (dependFile == nullptr)
|
||||
err("Failed to open dependfile \"%s\"", dependFileName);
|
||||
if (dependFile == nullptr) {
|
||||
err("Failed to open dependfile \"%s\"", dependFileName); // LCOV_EXCL_LINE
|
||||
}
|
||||
break;
|
||||
|
||||
case 'o':
|
||||
@@ -199,11 +215,13 @@ int main(int argc, char *argv[]) {
|
||||
case 'p':
|
||||
padByte = strtoul(musl_optarg, &endptr, 0);
|
||||
|
||||
if (musl_optarg[0] == '\0' || *endptr != '\0')
|
||||
if (musl_optarg[0] == '\0' || *endptr != '\0') {
|
||||
errx("Invalid argument for option 'p'");
|
||||
}
|
||||
|
||||
if (padByte > 0xFF)
|
||||
if (padByte > 0xFF) {
|
||||
errx("Argument for option 'p' must be between 0 and 0xFF");
|
||||
}
|
||||
|
||||
opt_P(padByte);
|
||||
break;
|
||||
@@ -212,15 +230,18 @@ int main(int argc, char *argv[]) {
|
||||
char const *precisionArg;
|
||||
case 'Q':
|
||||
precisionArg = musl_optarg;
|
||||
if (precisionArg[0] == '.')
|
||||
if (precisionArg[0] == '.') {
|
||||
precisionArg++;
|
||||
}
|
||||
precision = strtoul(precisionArg, &endptr, 0);
|
||||
|
||||
if (musl_optarg[0] == '\0' || *endptr != '\0')
|
||||
if (musl_optarg[0] == '\0' || *endptr != '\0') {
|
||||
errx("Invalid argument for option 'Q'");
|
||||
}
|
||||
|
||||
if (precision < 1 || precision > 31)
|
||||
if (precision < 1 || precision > 31) {
|
||||
errx("Argument for option 'Q' must be between 1 and 31");
|
||||
}
|
||||
|
||||
opt_Q(precision);
|
||||
break;
|
||||
@@ -228,35 +249,41 @@ int main(int argc, char *argv[]) {
|
||||
case 'r':
|
||||
maxDepth = strtoul(musl_optarg, &endptr, 0);
|
||||
|
||||
if (musl_optarg[0] == '\0' || *endptr != '\0')
|
||||
if (musl_optarg[0] == '\0' || *endptr != '\0') {
|
||||
errx("Invalid argument for option 'r'");
|
||||
}
|
||||
break;
|
||||
|
||||
case 's': {
|
||||
// Split "<features>:<name>" so `musl_optarg` is "<features>" and `name` is "<name>"
|
||||
char *name = strchr(musl_optarg, ':');
|
||||
if (!name)
|
||||
if (!name) {
|
||||
errx("Invalid argument for option 's'");
|
||||
}
|
||||
*name++ = '\0';
|
||||
|
||||
std::vector<StateFeature> features;
|
||||
for (char *feature = musl_optarg; feature;) {
|
||||
// Split "<feature>,<rest>" so `feature` is "<feature>" and `next` is "<rest>"
|
||||
char *next = strchr(feature, ',');
|
||||
if (next)
|
||||
if (next) {
|
||||
*next++ = '\0';
|
||||
}
|
||||
// Trim whitespace from the beginning of `feature`...
|
||||
feature += strspn(feature, " \t");
|
||||
// ...and from the end
|
||||
if (char *end = strpbrk(feature, " \t"); end)
|
||||
if (char *end = strpbrk(feature, " \t"); end) {
|
||||
*end = '\0';
|
||||
}
|
||||
// A feature must be specified
|
||||
if (*feature == '\0')
|
||||
if (*feature == '\0') {
|
||||
errx("Empty feature for option 's'");
|
||||
}
|
||||
// Parse the `feature` and update the `features` list
|
||||
if (!strcasecmp(feature, "all")) {
|
||||
if (!features.empty())
|
||||
if (!features.empty()) {
|
||||
warnx("Redundant feature before \"%s\" for option 's'", feature);
|
||||
}
|
||||
features.assign({STATE_EQU, STATE_VAR, STATE_EQUS, STATE_CHAR, STATE_MACRO});
|
||||
} else {
|
||||
StateFeature value = !strcasecmp(feature, "equ") ? STATE_EQU
|
||||
@@ -276,10 +303,14 @@ int main(int argc, char *argv[]) {
|
||||
feature = next;
|
||||
}
|
||||
|
||||
if (stateFileSpecs.find(name) != stateFileSpecs.end())
|
||||
if (stateFileSpecs.find(name) != stateFileSpecs.end()) {
|
||||
warnx("Overriding state filename %s", name);
|
||||
if (verbose)
|
||||
}
|
||||
// LCOV_EXCL_START
|
||||
if (verbose) {
|
||||
printf("State filename %s\n", name);
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
stateFileSpecs.emplace(name, std::move(features));
|
||||
break;
|
||||
}
|
||||
@@ -289,8 +320,10 @@ int main(int argc, char *argv[]) {
|
||||
exit(0);
|
||||
|
||||
case 'v':
|
||||
// LCOV_EXCL_START
|
||||
verbose = true;
|
||||
break;
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
case 'W':
|
||||
opt_W(musl_optarg);
|
||||
@@ -304,11 +337,13 @@ int main(int argc, char *argv[]) {
|
||||
case 'X':
|
||||
maxValue = strtoul(musl_optarg, &endptr, 0);
|
||||
|
||||
if (musl_optarg[0] == '\0' || *endptr != '\0')
|
||||
if (musl_optarg[0] == '\0' || *endptr != '\0') {
|
||||
errx("Invalid argument for option 'X'");
|
||||
}
|
||||
|
||||
if (maxValue > UINT_MAX)
|
||||
if (maxValue > UINT_MAX) {
|
||||
errx("Argument for option 'X' must be between 0 and %u", UINT_MAX);
|
||||
}
|
||||
|
||||
maxErrors = maxValue;
|
||||
break;
|
||||
@@ -327,10 +362,12 @@ int main(int argc, char *argv[]) {
|
||||
case 'Q':
|
||||
case 'T':
|
||||
newTarget = musl_optarg;
|
||||
if (depType == 'Q')
|
||||
if (depType == 'Q') {
|
||||
newTarget = make_escape(newTarget);
|
||||
if (!targetFileName.empty())
|
||||
}
|
||||
if (!targetFileName.empty()) {
|
||||
targetFileName += ' ';
|
||||
}
|
||||
targetFileName += newTarget;
|
||||
break;
|
||||
}
|
||||
@@ -338,13 +375,16 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
// Unrecognized options
|
||||
default:
|
||||
// LCOV_EXCL_START
|
||||
printUsage();
|
||||
exit(1);
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
}
|
||||
|
||||
if (targetFileName.empty() && !objectFileName.empty())
|
||||
if (targetFileName.empty() && !objectFileName.empty()) {
|
||||
targetFileName = objectFileName;
|
||||
}
|
||||
|
||||
if (argc == musl_optind) {
|
||||
fputs(
|
||||
@@ -360,13 +400,15 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
std::string mainFileName = argv[musl_optind];
|
||||
|
||||
if (verbose)
|
||||
printf("Assembling %s\n", mainFileName.c_str());
|
||||
if (verbose) {
|
||||
printf("Assembling %s\n", mainFileName.c_str()); // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
if (dependFile) {
|
||||
if (targetFileName.empty())
|
||||
if (targetFileName.empty()) {
|
||||
errx("Dependency files can only be created if a target file is specified with either "
|
||||
"-o, -MQ or -MT");
|
||||
}
|
||||
|
||||
fprintf(dependFile, "%s: %s\n", targetFileName.c_str(), mainFileName.c_str());
|
||||
}
|
||||
@@ -377,24 +419,34 @@ int main(int argc, char *argv[]) {
|
||||
fstk_Init(mainFileName, maxDepth);
|
||||
|
||||
// Perform parse (`yy::parser` is auto-generated from `parser.y`)
|
||||
if (yy::parser parser; parser.parse() != 0 && nbErrors == 0)
|
||||
if (yy::parser parser; parser.parse() != 0 && nbErrors == 0) {
|
||||
nbErrors = 1;
|
||||
}
|
||||
|
||||
sect_CheckUnionClosed();
|
||||
sect_CheckLoadClosed();
|
||||
sect_CheckSizes();
|
||||
if (!failedOnMissingInclude) {
|
||||
sect_CheckUnionClosed();
|
||||
sect_CheckLoadClosed();
|
||||
sect_CheckSizes();
|
||||
|
||||
if (nbErrors != 0)
|
||||
charmap_CheckStack();
|
||||
opt_CheckStack();
|
||||
sect_CheckStack();
|
||||
}
|
||||
|
||||
if (nbErrors != 0) {
|
||||
errx("Assembly aborted (%u error%s)!", nbErrors, nbErrors == 1 ? "" : "s");
|
||||
}
|
||||
|
||||
// If parse aborted due to missing an include, and `-MG` was given, exit normally
|
||||
if (failedOnMissingInclude)
|
||||
if (failedOnMissingInclude) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
out_WriteObject();
|
||||
|
||||
for (auto [name, features] : stateFileSpecs)
|
||||
for (auto [name, features] : stateFileSpecs) {
|
||||
out_WriteState(name, features);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
@@ -53,17 +53,19 @@ void opt_W(char const *flag) {
|
||||
void opt_Parse(char const *s) {
|
||||
switch (s[0]) {
|
||||
case 'b':
|
||||
if (strlen(&s[1]) == 2)
|
||||
if (strlen(&s[1]) == 2) {
|
||||
opt_B(&s[1]);
|
||||
else
|
||||
} else {
|
||||
error("Must specify exactly 2 characters for option 'b'\n");
|
||||
}
|
||||
break;
|
||||
|
||||
case 'g':
|
||||
if (strlen(&s[1]) == 4)
|
||||
if (strlen(&s[1]) == 4) {
|
||||
opt_G(&s[1]);
|
||||
else
|
||||
} else {
|
||||
error("Must specify exactly 4 characters for option 'g'\n");
|
||||
}
|
||||
break;
|
||||
|
||||
case 'p':
|
||||
@@ -72,12 +74,13 @@ void opt_Parse(char const *s) {
|
||||
unsigned int padByte;
|
||||
|
||||
result = sscanf(&s[1], "%x", &padByte);
|
||||
if (result != 1)
|
||||
if (result != 1) {
|
||||
error("Invalid argument for option 'p'\n");
|
||||
else if (padByte > 0xFF)
|
||||
} else if (padByte > 0xFF) {
|
||||
error("Argument for option 'p' must be between 0 and 0xFF\n");
|
||||
else
|
||||
} else {
|
||||
opt_P(padByte);
|
||||
}
|
||||
} else {
|
||||
error("Invalid argument for option 'p'\n");
|
||||
}
|
||||
@@ -86,19 +89,21 @@ void opt_Parse(char const *s) {
|
||||
char const *precisionArg;
|
||||
case 'Q':
|
||||
precisionArg = &s[1];
|
||||
if (precisionArg[0] == '.')
|
||||
if (precisionArg[0] == '.') {
|
||||
precisionArg++;
|
||||
}
|
||||
if (strlen(precisionArg) <= 2) {
|
||||
int result;
|
||||
unsigned int precision;
|
||||
|
||||
result = sscanf(precisionArg, "%u", &precision);
|
||||
if (result != 1)
|
||||
if (result != 1) {
|
||||
error("Invalid argument for option 'Q'\n");
|
||||
else if (precision < 1 || precision > 31)
|
||||
} else if (precision < 1 || precision > 31) {
|
||||
error("Argument for option 'Q' must be between 1 and 31\n");
|
||||
else
|
||||
} else {
|
||||
opt_Q(precision);
|
||||
}
|
||||
} else {
|
||||
error("Invalid argument for option 'Q'\n");
|
||||
}
|
||||
@@ -106,8 +111,9 @@ void opt_Parse(char const *s) {
|
||||
|
||||
case 'r': {
|
||||
++s; // Skip 'r'
|
||||
while (isblank(*s))
|
||||
while (isblank(*s)) {
|
||||
++s; // Skip leading whitespace
|
||||
}
|
||||
|
||||
if (s[0] == '\0') {
|
||||
error("Missing argument to option 'r'\n");
|
||||
@@ -128,10 +134,11 @@ void opt_Parse(char const *s) {
|
||||
}
|
||||
|
||||
case 'W':
|
||||
if (strlen(&s[1]) > 0)
|
||||
if (strlen(&s[1]) > 0) {
|
||||
opt_W(&s[1]);
|
||||
else
|
||||
} else {
|
||||
error("Must specify an argument for option 'W'\n");
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -184,3 +191,9 @@ void opt_Pop() {
|
||||
warningsAreErrors = entry.warningsAreErrors;
|
||||
warningStates = entry.warningStates;
|
||||
}
|
||||
|
||||
void opt_CheckStack() {
|
||||
if (!stack.empty()) {
|
||||
warning(WARNING_UNMATCHED_DIRECTIVE, "`PUSHO` without corresponding `POPO`\n");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "asm/output.hpp"
|
||||
|
||||
@@ -40,10 +40,10 @@ static std::deque<std::shared_ptr<FileStackNode>> fileStackNodes;
|
||||
|
||||
static void putLong(uint32_t n, FILE *file) {
|
||||
uint8_t bytes[] = {
|
||||
(uint8_t)n,
|
||||
(uint8_t)(n >> 8),
|
||||
(uint8_t)(n >> 16),
|
||||
(uint8_t)(n >> 24),
|
||||
static_cast<uint8_t>(n),
|
||||
static_cast<uint8_t>(n >> 8),
|
||||
static_cast<uint8_t>(n >> 16),
|
||||
static_cast<uint8_t>(n >> 24),
|
||||
};
|
||||
fwrite(bytes, 1, sizeof(bytes), file);
|
||||
}
|
||||
@@ -55,25 +55,28 @@ static void putString(std::string const &s, FILE *file) {
|
||||
|
||||
void out_RegisterNode(std::shared_ptr<FileStackNode> node) {
|
||||
// If node is not already registered, register it (and parents), and give it a unique ID
|
||||
for (; node && node->ID == (uint32_t)-1; node = node->parent) {
|
||||
for (; node && node->ID == UINT32_MAX; node = node->parent) {
|
||||
node->ID = fileStackNodes.size();
|
||||
fileStackNodes.push_front(node);
|
||||
}
|
||||
}
|
||||
|
||||
// Return a section's ID, or -1 if the section is not in the list
|
||||
// Return a section's ID, or UINT32_MAX if the section does not exist
|
||||
static uint32_t getSectIDIfAny(Section *sect) {
|
||||
if (!sect)
|
||||
return (uint32_t)-1;
|
||||
if (!sect) {
|
||||
return UINT32_MAX;
|
||||
}
|
||||
|
||||
if (auto search = sectionMap.find(sect->name); search != sectionMap.end())
|
||||
return (uint32_t)(sectionMap.size() - search->second - 1);
|
||||
if (auto search = sectionMap.find(sect->name); search != sectionMap.end()) {
|
||||
return static_cast<uint32_t>(search->second);
|
||||
}
|
||||
|
||||
fatalerror("Unknown section '%s'\n", sect->name.c_str());
|
||||
// Every section that exists should be in `sectionMap`
|
||||
fatalerror("Unknown section '%s'\n", sect->name.c_str()); // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
static void writePatch(Patch const &patch, FILE *file) {
|
||||
assume(patch.src->ID != (uint32_t)-1);
|
||||
assume(patch.src->ID != UINT32_MAX);
|
||||
|
||||
putLong(patch.src->ID, file);
|
||||
putLong(patch.lineNo, file);
|
||||
@@ -86,7 +89,7 @@ static void writePatch(Patch const &patch, FILE *file) {
|
||||
}
|
||||
|
||||
static void writeSection(Section const §, FILE *file) {
|
||||
assume(sect.src->ID != (uint32_t)-1);
|
||||
assume(sect.src->ID != UINT32_MAX);
|
||||
|
||||
putString(sect.name, file);
|
||||
|
||||
@@ -109,8 +112,9 @@ static void writeSection(Section const §, FILE *file) {
|
||||
fwrite(sect.data.data(), 1, sect.size, file);
|
||||
putLong(sect.patches.size(), file);
|
||||
|
||||
for (Patch const &patch : sect.patches)
|
||||
for (Patch const &patch : sect.patches) {
|
||||
writePatch(patch, file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,7 +123,7 @@ static void writeSymbol(Symbol const &sym, FILE *file) {
|
||||
if (!sym.isDefined()) {
|
||||
putc(SYMTYPE_IMPORT, file);
|
||||
} else {
|
||||
assume(sym.src->ID != (uint32_t)-1);
|
||||
assume(sym.src->ID != UINT32_MAX);
|
||||
|
||||
putc(sym.isExported ? SYMTYPE_EXPORT : SYMTYPE_LOCAL, file);
|
||||
putLong(sym.src->ID, file);
|
||||
@@ -131,7 +135,7 @@ static void writeSymbol(Symbol const &sym, FILE *file) {
|
||||
|
||||
static void registerUnregisteredSymbol(Symbol &sym) {
|
||||
// Check for `sym.src`, to skip any built-in symbol from rgbasm
|
||||
if (sym.src && sym.ID == (uint32_t)-1 && !sym_IsPC(&sym)) {
|
||||
if (sym.src && sym.ID == UINT32_MAX && !sym_IsPC(&sym)) {
|
||||
sym.ID = objectSymbols.size(); // Set the symbol's ID within the object file
|
||||
objectSymbols.push_back(&sym);
|
||||
out_RegisterNode(sym.src);
|
||||
@@ -162,8 +166,9 @@ static void writeRpn(std::vector<uint8_t> &rpnexpr, std::vector<uint8_t> const &
|
||||
symName.clear();
|
||||
for (;;) {
|
||||
uint8_t c = rpn[offset++];
|
||||
if (c == 0)
|
||||
if (c == 0) {
|
||||
break;
|
||||
}
|
||||
symName += c;
|
||||
}
|
||||
|
||||
@@ -171,7 +176,7 @@ static void writeRpn(std::vector<uint8_t> &rpnexpr, std::vector<uint8_t> const &
|
||||
sym = sym_FindExactSymbol(symName);
|
||||
if (sym->isConstant()) {
|
||||
rpnexpr[rpnptr++] = RPN_CONST;
|
||||
value = sym_GetConstantValue(symName);
|
||||
value = sym->getConstantValue();
|
||||
} else {
|
||||
rpnexpr[rpnptr++] = RPN_SYM;
|
||||
registerUnregisteredSymbol(*sym); // Ensure that `sym->ID` is set
|
||||
@@ -188,8 +193,9 @@ static void writeRpn(std::vector<uint8_t> &rpnexpr, std::vector<uint8_t> const &
|
||||
symName.clear();
|
||||
for (;;) {
|
||||
uint8_t c = rpn[offset++];
|
||||
if (c == 0)
|
||||
if (c == 0) {
|
||||
break;
|
||||
}
|
||||
symName += c;
|
||||
}
|
||||
|
||||
@@ -282,13 +288,13 @@ void out_CreateAssert(
|
||||
assertion.message = message;
|
||||
}
|
||||
|
||||
static void writeAssert(Assertion &assert, FILE *file) {
|
||||
static void writeAssert(Assertion const &assert, FILE *file) {
|
||||
writePatch(assert.patch, file);
|
||||
putString(assert.message, file);
|
||||
}
|
||||
|
||||
static void writeFileStackNode(FileStackNode const &node, FILE *file) {
|
||||
putLong(node.parent ? node.parent->ID : (uint32_t)-1, file);
|
||||
putLong(node.parent ? node.parent->ID : UINT32_MAX, file);
|
||||
putLong(node.lineNo, file);
|
||||
putc(node.type, file);
|
||||
if (node.type != NODE_REPT) {
|
||||
@@ -298,14 +304,16 @@ static void writeFileStackNode(FileStackNode const &node, FILE *file) {
|
||||
|
||||
putLong(nodeIters.size(), file);
|
||||
// Iters are stored by decreasing depth, so reverse the order for output
|
||||
for (uint32_t i = nodeIters.size(); i--;)
|
||||
for (uint32_t i = nodeIters.size(); i--;) {
|
||||
putLong(nodeIters[i], file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void out_WriteObject() {
|
||||
if (objectFileName.empty())
|
||||
if (objectFileName.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
FILE *file;
|
||||
if (objectFileName != "-") {
|
||||
@@ -315,14 +323,15 @@ void out_WriteObject() {
|
||||
(void)setmode(STDOUT_FILENO, O_BINARY);
|
||||
file = stdout;
|
||||
}
|
||||
if (!file)
|
||||
err("Failed to open object file '%s'", objectFileName.c_str());
|
||||
if (!file) {
|
||||
err("Failed to open object file '%s'", objectFileName.c_str()); // LCOV_EXCL_LINE
|
||||
}
|
||||
Defer closeFile{[&] { fclose(file); }};
|
||||
|
||||
// Also write symbols that weren't written above
|
||||
sym_ForEach(registerUnregisteredSymbol);
|
||||
|
||||
fprintf(file, RGBDS_OBJECT_VERSION_STRING);
|
||||
fputs(RGBDS_OBJECT_VERSION_STRING, file);
|
||||
putLong(RGBDS_OBJECT_REV, file);
|
||||
|
||||
putLong(objectSymbols.size(), file);
|
||||
@@ -335,33 +344,34 @@ void out_WriteObject() {
|
||||
writeFileStackNode(node, file);
|
||||
|
||||
// The list is supposed to have decrementing IDs
|
||||
if (it + 1 != fileStackNodes.end() && it[1]->ID != node.ID - 1)
|
||||
fatalerror(
|
||||
"Internal error: fstack node #%" PRIu32 " follows #%" PRIu32
|
||||
". Please report this to the developers!\n",
|
||||
it[1]->ID,
|
||||
node.ID
|
||||
);
|
||||
assume(it + 1 == fileStackNodes.end() || it[1]->ID == node.ID - 1);
|
||||
}
|
||||
|
||||
for (Symbol const *sym : objectSymbols)
|
||||
for (Symbol const *sym : objectSymbols) {
|
||||
writeSymbol(*sym, file);
|
||||
}
|
||||
|
||||
for (auto it = sectionList.rbegin(); it != sectionList.rend(); it++)
|
||||
writeSection(*it, file);
|
||||
for (Section const § : sectionList) {
|
||||
writeSection(sect, file);
|
||||
}
|
||||
|
||||
putLong(assertions.size(), file);
|
||||
|
||||
for (Assertion &assert : assertions)
|
||||
for (Assertion const &assert : assertions) {
|
||||
writeAssert(assert, file);
|
||||
}
|
||||
}
|
||||
|
||||
void out_SetFileName(std::string const &name) {
|
||||
if (!objectFileName.empty())
|
||||
if (!objectFileName.empty()) {
|
||||
warnx("Overriding output filename %s", objectFileName.c_str());
|
||||
}
|
||||
objectFileName = name;
|
||||
if (verbose)
|
||||
// LCOV_EXCL_START
|
||||
if (verbose) {
|
||||
printf("Output filename %s\n", objectFileName.c_str());
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
static void dumpString(std::string const &escape, FILE *file) {
|
||||
@@ -397,8 +407,9 @@ static bool dumpEquConstants(FILE *file) {
|
||||
equConstants.clear();
|
||||
|
||||
sym_ForEach([](Symbol &sym) {
|
||||
if (!sym.isBuiltin && sym.type == SYM_EQU)
|
||||
if (!sym.isBuiltin && sym.type == SYM_EQU) {
|
||||
equConstants.push_back(&sym);
|
||||
}
|
||||
});
|
||||
// Constants are ordered by file, then by definition order
|
||||
std::sort(RANGE(equConstants), [](Symbol *sym1, Symbol *sym2) -> bool {
|
||||
@@ -418,8 +429,9 @@ static bool dumpVariables(FILE *file) {
|
||||
variables.clear();
|
||||
|
||||
sym_ForEach([](Symbol &sym) {
|
||||
if (!sym.isBuiltin && sym.type == SYM_VAR)
|
||||
if (!sym.isBuiltin && sym.type == SYM_VAR) {
|
||||
variables.push_back(&sym);
|
||||
}
|
||||
});
|
||||
// Variables are ordered by file, then by definition order
|
||||
std::sort(RANGE(variables), [](Symbol *sym1, Symbol *sym2) -> bool {
|
||||
@@ -439,8 +451,9 @@ static bool dumpEqusConstants(FILE *file) {
|
||||
equsConstants.clear();
|
||||
|
||||
sym_ForEach([](Symbol &sym) {
|
||||
if (!sym.isBuiltin && sym.type == SYM_EQUS)
|
||||
if (!sym.isBuiltin && sym.type == SYM_EQUS) {
|
||||
equsConstants.push_back(&sym);
|
||||
}
|
||||
});
|
||||
// Constants are ordered by file, then by definition order
|
||||
std::sort(RANGE(equsConstants), [](Symbol *sym1, Symbol *sym2) -> bool {
|
||||
@@ -467,8 +480,9 @@ static bool dumpCharmaps(FILE *file) {
|
||||
fputs("charmap \"", charmapFile);
|
||||
dumpString(mapping, charmapFile);
|
||||
putc('"', charmapFile);
|
||||
for (int32_t v : value)
|
||||
for (int32_t v : value) {
|
||||
fprintf(charmapFile, ", $%" PRIx32, v);
|
||||
}
|
||||
putc('\n', charmapFile);
|
||||
}
|
||||
);
|
||||
@@ -479,8 +493,9 @@ static bool dumpMacros(FILE *file) {
|
||||
macros.clear();
|
||||
|
||||
sym_ForEach([](Symbol &sym) {
|
||||
if (!sym.isBuiltin && sym.type == SYM_MACRO)
|
||||
if (!sym.isBuiltin && sym.type == SYM_MACRO) {
|
||||
macros.push_back(&sym);
|
||||
}
|
||||
});
|
||||
// Macros are ordered by file, then by definition order
|
||||
std::sort(RANGE(macros), [](Symbol *sym1, Symbol *sym2) -> bool {
|
||||
@@ -508,8 +523,9 @@ void out_WriteState(std::string name, std::vector<StateFeature> const &features)
|
||||
(void)setmode(STDOUT_FILENO, O_BINARY);
|
||||
file = stdout;
|
||||
}
|
||||
if (!file)
|
||||
err("Failed to open state file '%s'", name.c_str());
|
||||
if (!file) {
|
||||
err("Failed to open state file '%s'", name.c_str()); // LCOV_EXCL_LINE
|
||||
}
|
||||
Defer closeFile{[&] { fclose(file); }};
|
||||
|
||||
static char const *dumpHeadings[NB_STATE_FEATURES] = {
|
||||
@@ -530,7 +546,8 @@ void out_WriteState(std::string name, std::vector<StateFeature> const &features)
|
||||
fputs("; File generated by rgbasm\n", file);
|
||||
for (StateFeature feature : features) {
|
||||
fprintf(file, "\n; %s\n", dumpHeadings[feature]);
|
||||
if (!dumpFuncs[feature](file))
|
||||
if (!dumpFuncs[feature](file)) {
|
||||
fprintf(file, "; No values\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
1354
src/asm/parser.y
1354
src/asm/parser.y
File diff suppressed because it is too large
Load Diff
272
src/asm/rpn.cpp
272
src/asm/rpn.cpp
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "asm/rpn.hpp"
|
||||
|
||||
@@ -46,17 +46,19 @@ int32_t Expression::getConstVal() const {
|
||||
}
|
||||
|
||||
Symbol const *Expression::symbolOf() const {
|
||||
if (!isSymbol)
|
||||
if (!isSymbol) {
|
||||
return nullptr;
|
||||
return sym_FindScopedSymbol((char const *)&rpn[1]);
|
||||
}
|
||||
return sym_FindScopedSymbol(reinterpret_cast<char const *>(&rpn[1]));
|
||||
}
|
||||
|
||||
bool Expression::isDiffConstant(Symbol const *sym) const {
|
||||
// Check if both expressions only refer to a single symbol
|
||||
Symbol const *sym1 = symbolOf();
|
||||
|
||||
if (!sym1 || !sym || sym1->type != SYM_LABEL || sym->type != SYM_LABEL)
|
||||
if (!sym1 || !sym || sym1->type != SYM_LABEL || sym->type != SYM_LABEL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Section const *sect1 = sym1->getSection();
|
||||
Section const *sect2 = sym->getSection();
|
||||
@@ -65,7 +67,7 @@ bool Expression::isDiffConstant(Symbol const *sym) const {
|
||||
|
||||
void Expression::makeNumber(uint32_t value) {
|
||||
clear();
|
||||
data = (int32_t)value;
|
||||
data = static_cast<int32_t>(value);
|
||||
}
|
||||
|
||||
void Expression::makeSymbol(std::string const &symName) {
|
||||
@@ -73,13 +75,16 @@ void Expression::makeSymbol(std::string const &symName) {
|
||||
if (Symbol *sym = sym_FindScopedSymbol(symName); sym_IsPC(sym) && !sect_GetSymbolSection()) {
|
||||
error("PC has no value outside of a section\n");
|
||||
data = 0;
|
||||
} else if (sym && !sym->isNumeric() && !sym->isLabel()) {
|
||||
error("'%s' is not a numeric symbol\n", symName.c_str());
|
||||
data = 0;
|
||||
} else if (!sym || !sym->isConstant()) {
|
||||
isSymbol = true;
|
||||
|
||||
data = sym_IsPC(sym) ? "PC is not constant at assembly time"
|
||||
: sym_IsPurgedScoped(symName)
|
||||
? "'"s + symName + "' is not constant at assembly time; it was purged"
|
||||
: "'"s + symName + "' is not constant at assembly time";
|
||||
: sym_IsPurgedScoped(symName)
|
||||
? "'"s + symName + "' is not constant at assembly time; it was purged"
|
||||
: "'"s + symName + "' is not constant at assembly time";
|
||||
sym = sym_Ref(symName);
|
||||
|
||||
size_t nameLen = sym->name.length() + 1; // Don't forget NUL!
|
||||
@@ -89,7 +94,7 @@ void Expression::makeSymbol(std::string const &symName) {
|
||||
*ptr++ = RPN_SYM;
|
||||
memcpy(ptr, sym->name.c_str(), nameLen);
|
||||
} else {
|
||||
data = (int32_t)sym_GetConstantValue(symName);
|
||||
data = static_cast<int32_t>(sym->getConstantValue());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,12 +105,12 @@ void Expression::makeBankSymbol(std::string const &symName) {
|
||||
if (!currentSection) {
|
||||
error("PC has no bank outside of a section\n");
|
||||
data = 1;
|
||||
} else if (currentSection->bank == (uint32_t)-1) {
|
||||
} else if (currentSection->bank == UINT32_MAX) {
|
||||
data = "Current section's bank is not known";
|
||||
|
||||
*reserveSpace(1) = RPN_BANK_SELF;
|
||||
} else {
|
||||
data = (int32_t)currentSection->bank;
|
||||
data = static_cast<int32_t>(currentSection->bank);
|
||||
}
|
||||
return;
|
||||
} else if (sym && !sym->isLabel()) {
|
||||
@@ -115,13 +120,13 @@ void Expression::makeBankSymbol(std::string const &symName) {
|
||||
sym = sym_Ref(symName);
|
||||
assume(sym); // If the symbol didn't exist, it should have been created
|
||||
|
||||
if (sym->getSection() && sym->getSection()->bank != (uint32_t)-1) {
|
||||
if (sym->getSection() && sym->getSection()->bank != UINT32_MAX) {
|
||||
// Symbol's section is known and bank is fixed
|
||||
data = (int32_t)sym->getSection()->bank;
|
||||
data = static_cast<int32_t>(sym->getSection()->bank);
|
||||
} else {
|
||||
data = sym_IsPurgedScoped(symName)
|
||||
? "\""s + symName + "\"'s bank is not known; it was purged"
|
||||
: "\""s + symName + "\"'s bank is not known";
|
||||
? "\""s + symName + "\"'s bank is not known; it was purged"
|
||||
: "\""s + symName + "\"'s bank is not known";
|
||||
|
||||
size_t nameLen = sym->name.length() + 1; // Room for NUL!
|
||||
|
||||
@@ -135,8 +140,8 @@ void Expression::makeBankSymbol(std::string const &symName) {
|
||||
|
||||
void Expression::makeBankSection(std::string const §Name) {
|
||||
clear();
|
||||
if (Section *sect = sect_FindSectionByName(sectName); sect && sect->bank != (uint32_t)-1) {
|
||||
data = (int32_t)sect->bank;
|
||||
if (Section *sect = sect_FindSectionByName(sectName); sect && sect->bank != UINT32_MAX) {
|
||||
data = static_cast<int32_t>(sect->bank);
|
||||
} else {
|
||||
data = "Section \""s + sectName + "\"'s bank is not known";
|
||||
|
||||
@@ -151,7 +156,7 @@ void Expression::makeBankSection(std::string const §Name) {
|
||||
void Expression::makeSizeOfSection(std::string const §Name) {
|
||||
clear();
|
||||
if (Section *sect = sect_FindSectionByName(sectName); sect && sect->isSizeKnown()) {
|
||||
data = (int32_t)sect->size;
|
||||
data = static_cast<int32_t>(sect->size);
|
||||
} else {
|
||||
data = "Section \""s + sectName + "\"'s size is not known";
|
||||
|
||||
@@ -165,8 +170,8 @@ void Expression::makeSizeOfSection(std::string const §Name) {
|
||||
|
||||
void Expression::makeStartOfSection(std::string const §Name) {
|
||||
clear();
|
||||
if (Section *sect = sect_FindSectionByName(sectName); sect && sect->org != (uint32_t)-1) {
|
||||
data = (int32_t)sect->org;
|
||||
if (Section *sect = sect_FindSectionByName(sectName); sect && sect->org != UINT32_MAX) {
|
||||
data = static_cast<int32_t>(sect->org);
|
||||
} else {
|
||||
data = "Section \""s + sectName + "\"'s start is not known";
|
||||
|
||||
@@ -208,71 +213,68 @@ static bool tryConstNonzero(Expression const &lhs, Expression const &rhs) {
|
||||
|
||||
static bool tryConstLogNot(Expression const &expr) {
|
||||
Symbol const *sym = expr.symbolOf();
|
||||
if (!sym || !sym->getSection() || !sym->isDefined())
|
||||
if (!sym || !sym->getSection() || !sym->isDefined()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
assume(sym->isNumeric());
|
||||
|
||||
Section const § = *sym->getSection();
|
||||
int32_t unknownBits = (1 << 16) - (1 << sect.align);
|
||||
|
||||
// `sym->getValue()` attempts to add the section's address, but that's "-1"
|
||||
// `sym->getValue()` attempts to add the section's address, but that's `UINT32_MAX`
|
||||
// because the section is floating (otherwise we wouldn't be here)
|
||||
assume(sect.org == (uint32_t)-1);
|
||||
assume(sect.org == UINT32_MAX);
|
||||
int32_t symbolOfs = sym->getValue() + 1;
|
||||
|
||||
int32_t knownBits = (symbolOfs + sect.alignOfs) & ~unknownBits;
|
||||
return knownBits != 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Attempts to compute a constant LOW() from non-constant argument
|
||||
* This is possible if the argument is a symbol belonging to an `ALIGN[8]` section.
|
||||
*
|
||||
* @return The constant `LOW(expr)` result if it can be computed, or -1 otherwise.
|
||||
*/
|
||||
// Returns a constant LOW() from non-constant argument, or -1 if it cannot be computed.
|
||||
// This is possible if the argument is a symbol belonging to an `ALIGN[8]` section.
|
||||
static int32_t tryConstLow(Expression const &expr) {
|
||||
Symbol const *sym = expr.symbolOf();
|
||||
if (!sym || !sym->getSection() || !sym->isDefined())
|
||||
if (!sym || !sym->getSection() || !sym->isDefined()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
assume(sym->isNumeric());
|
||||
|
||||
// The low byte must not cover any unknown bits
|
||||
Section const § = *sym->getSection();
|
||||
if (sect.align < 8)
|
||||
if (sect.align < 8) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// `sym->getValue()` attempts to add the section's address, but that's "-1"
|
||||
// `sym->getValue()` attempts to add the section's address, but that's `UINT32_MAX`
|
||||
// because the section is floating (otherwise we wouldn't be here)
|
||||
assume(sect.org == (uint32_t)-1);
|
||||
assume(sect.org == UINT32_MAX);
|
||||
int32_t symbolOfs = sym->getValue() + 1;
|
||||
|
||||
return (symbolOfs + sect.alignOfs) & 0xFF;
|
||||
}
|
||||
|
||||
/*
|
||||
* Attempts to compute a constant binary AND with one non-constant operands
|
||||
* This is possible if one operand is a symbol belonging to an `ALIGN[N]` section, and the other is
|
||||
* a constant that only keeps (some of) the lower N bits.
|
||||
*
|
||||
* @return The constant `lhs & rhs` result if it can be computed, or -1 otherwise.
|
||||
*/
|
||||
// Returns a constant binary AND with one non-constant operand, or -1 if it cannot be computed.
|
||||
// This is possible if one operand is a symbol belonging to an `ALIGN[N]` section, and the other is
|
||||
// a constant that only keeps (some of) the lower N bits.
|
||||
static int32_t tryConstMask(Expression const &lhs, Expression const &rhs) {
|
||||
Symbol const *lhsSymbol = lhs.symbolOf();
|
||||
Symbol const *rhsSymbol = lhsSymbol ? nullptr : rhs.symbolOf();
|
||||
bool lhsIsSymbol = lhsSymbol && lhsSymbol->getSection();
|
||||
bool rhsIsSymbol = rhsSymbol && rhsSymbol->getSection();
|
||||
|
||||
if (!lhsIsSymbol && !rhsIsSymbol)
|
||||
if (!lhsIsSymbol && !rhsIsSymbol) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// If the lhs isn't a symbol, try again the other way around
|
||||
Symbol const &sym = lhsIsSymbol ? *lhsSymbol : *rhsSymbol;
|
||||
Expression const &expr = lhsIsSymbol ? rhs : lhs; // Opposite side of `sym`
|
||||
|
||||
if (!sym.isDefined() || !expr.isKnown())
|
||||
if (!sym.isDefined() || !expr.isKnown()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
assume(sym.isNumeric());
|
||||
|
||||
@@ -281,12 +283,13 @@ static int32_t tryConstMask(Expression const &lhs, Expression const &rhs) {
|
||||
|
||||
// The mask must not cover any unknown bits
|
||||
Section const § = *sym.getSection();
|
||||
if (int32_t unknownBits = (1 << 16) - (1 << sect.align); (unknownBits & mask) != 0)
|
||||
if (int32_t unknownBits = (1 << 16) - (1 << sect.align); (unknownBits & mask) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// `sym.getValue()` attempts to add the section's address, but that's "-1"
|
||||
// `sym.getValue()` attempts to add the section's address, but that's `UINT32_MAX`
|
||||
// because the section is floating (otherwise we wouldn't be here)
|
||||
assume(sect.org == (uint32_t)-1);
|
||||
assume(sect.org == UINT32_MAX);
|
||||
int32_t symbolOfs = sym.getValue() + 1;
|
||||
|
||||
return (symbolOfs + sect.alignOfs) & mask;
|
||||
@@ -298,10 +301,11 @@ void Expression::makeUnaryOp(RPNCommand op, Expression &&src) {
|
||||
if (src.isKnown()) {
|
||||
// If the expressions is known, just compute the value
|
||||
int32_t val = src.value();
|
||||
uint32_t uval = static_cast<uint32_t>(val);
|
||||
|
||||
switch (op) {
|
||||
case RPN_NEG:
|
||||
data = (int32_t) - (uint32_t)val;
|
||||
data = static_cast<int32_t>(-uval);
|
||||
break;
|
||||
case RPN_NOT:
|
||||
data = ~val;
|
||||
@@ -310,51 +314,23 @@ void Expression::makeUnaryOp(RPNCommand op, Expression &&src) {
|
||||
data = !val;
|
||||
break;
|
||||
case RPN_HIGH:
|
||||
data = (int32_t)((uint32_t)val >> 8 & 0xFF);
|
||||
data = static_cast<int32_t>(uval >> 8 & 0xFF);
|
||||
break;
|
||||
case RPN_LOW:
|
||||
data = val & 0xFF;
|
||||
break;
|
||||
case RPN_BITWIDTH:
|
||||
data = val != 0 ? 32 - clz((uint32_t)val) : 0;
|
||||
data = val != 0 ? 32 - clz(uval) : 0;
|
||||
break;
|
||||
case RPN_TZCOUNT:
|
||||
data = val != 0 ? ctz((uint32_t)val) : 32;
|
||||
data = val != 0 ? ctz(uval) : 32;
|
||||
break;
|
||||
|
||||
case RPN_LOGOR:
|
||||
case RPN_LOGAND:
|
||||
case RPN_LOGEQ:
|
||||
case RPN_LOGGT:
|
||||
case RPN_LOGLT:
|
||||
case RPN_LOGGE:
|
||||
case RPN_LOGLE:
|
||||
case RPN_LOGNE:
|
||||
case RPN_ADD:
|
||||
case RPN_SUB:
|
||||
case RPN_XOR:
|
||||
case RPN_OR:
|
||||
case RPN_AND:
|
||||
case RPN_SHL:
|
||||
case RPN_SHR:
|
||||
case RPN_USHR:
|
||||
case RPN_MUL:
|
||||
case RPN_DIV:
|
||||
case RPN_MOD:
|
||||
case RPN_EXP:
|
||||
case RPN_BANK_SYM:
|
||||
case RPN_BANK_SECT:
|
||||
case RPN_BANK_SELF:
|
||||
case RPN_SIZEOF_SECT:
|
||||
case RPN_STARTOF_SECT:
|
||||
case RPN_SIZEOF_SECTTYPE:
|
||||
case RPN_STARTOF_SECTTYPE:
|
||||
case RPN_HRAM:
|
||||
case RPN_RST:
|
||||
case RPN_CONST:
|
||||
case RPN_SYM:
|
||||
fatalerror("%d is not an unary operator\n", op);
|
||||
default:
|
||||
// `makeUnaryOp` should never be called with a non-unary operator!
|
||||
// LCOV_EXCL_START
|
||||
unreachable_();
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
} else if (op == RPN_LOGNOT && tryConstLogNot(src)) {
|
||||
data = 0;
|
||||
} else if (int32_t constVal; op == RPN_LOW && (constVal = tryConstLow(src)) != -1) {
|
||||
@@ -374,6 +350,7 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const
|
||||
if (src1.isKnown() && src2.isKnown()) {
|
||||
// If both expressions are known, just compute the value
|
||||
int32_t lval = src1.value(), rval = src2.value();
|
||||
uint32_t ulval = static_cast<uint32_t>(lval), urval = static_cast<uint32_t>(rval);
|
||||
|
||||
switch (op) {
|
||||
case RPN_LOGOR:
|
||||
@@ -401,10 +378,10 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const
|
||||
data = lval != rval;
|
||||
break;
|
||||
case RPN_ADD:
|
||||
data = (int32_t)((uint32_t)lval + (uint32_t)rval);
|
||||
data = static_cast<int32_t>(ulval + urval);
|
||||
break;
|
||||
case RPN_SUB:
|
||||
data = (int32_t)((uint32_t)lval - (uint32_t)rval);
|
||||
data = static_cast<int32_t>(ulval - urval);
|
||||
break;
|
||||
case RPN_XOR:
|
||||
data = lval ^ rval;
|
||||
@@ -416,47 +393,55 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const
|
||||
data = lval & rval;
|
||||
break;
|
||||
case RPN_SHL:
|
||||
if (rval < 0)
|
||||
if (rval < 0) {
|
||||
warning(
|
||||
WARNING_SHIFT_AMOUNT, "Shifting left by negative amount %" PRId32 "\n", rval
|
||||
);
|
||||
}
|
||||
|
||||
if (rval >= 32)
|
||||
if (rval >= 32) {
|
||||
warning(WARNING_SHIFT_AMOUNT, "Shifting left by large amount %" PRId32 "\n", rval);
|
||||
}
|
||||
|
||||
data = op_shift_left(lval, rval);
|
||||
break;
|
||||
case RPN_SHR:
|
||||
if (lval < 0)
|
||||
if (lval < 0) {
|
||||
warning(WARNING_SHIFT, "Shifting right negative value %" PRId32 "\n", lval);
|
||||
}
|
||||
|
||||
if (rval < 0)
|
||||
if (rval < 0) {
|
||||
warning(
|
||||
WARNING_SHIFT_AMOUNT, "Shifting right by negative amount %" PRId32 "\n", rval
|
||||
);
|
||||
}
|
||||
|
||||
if (rval >= 32)
|
||||
if (rval >= 32) {
|
||||
warning(WARNING_SHIFT_AMOUNT, "Shifting right by large amount %" PRId32 "\n", rval);
|
||||
}
|
||||
|
||||
data = op_shift_right(lval, rval);
|
||||
break;
|
||||
case RPN_USHR:
|
||||
if (rval < 0)
|
||||
if (rval < 0) {
|
||||
warning(
|
||||
WARNING_SHIFT_AMOUNT, "Shifting right by negative amount %" PRId32 "\n", rval
|
||||
);
|
||||
}
|
||||
|
||||
if (rval >= 32)
|
||||
if (rval >= 32) {
|
||||
warning(WARNING_SHIFT_AMOUNT, "Shifting right by large amount %" PRId32 "\n", rval);
|
||||
}
|
||||
|
||||
data = op_shift_right_unsigned(lval, rval);
|
||||
break;
|
||||
case RPN_MUL:
|
||||
data = (int32_t)((uint32_t)lval * (uint32_t)rval);
|
||||
data = static_cast<int32_t>(ulval * urval);
|
||||
break;
|
||||
case RPN_DIV:
|
||||
if (rval == 0)
|
||||
if (rval == 0) {
|
||||
fatalerror("Division by zero\n");
|
||||
}
|
||||
|
||||
if (lval == INT32_MIN && rval == -1) {
|
||||
warning(
|
||||
@@ -471,41 +456,29 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const
|
||||
}
|
||||
break;
|
||||
case RPN_MOD:
|
||||
if (rval == 0)
|
||||
if (rval == 0) {
|
||||
fatalerror("Modulo by zero\n");
|
||||
}
|
||||
|
||||
if (lval == INT32_MIN && rval == -1)
|
||||
if (lval == INT32_MIN && rval == -1) {
|
||||
data = 0;
|
||||
else
|
||||
} else {
|
||||
data = op_modulo(lval, rval);
|
||||
}
|
||||
break;
|
||||
case RPN_EXP:
|
||||
if (rval < 0)
|
||||
if (rval < 0) {
|
||||
fatalerror("Exponentiation by negative power\n");
|
||||
}
|
||||
|
||||
data = op_exponent(lval, rval);
|
||||
break;
|
||||
|
||||
case RPN_NEG:
|
||||
case RPN_NOT:
|
||||
case RPN_LOGNOT:
|
||||
case RPN_BANK_SYM:
|
||||
case RPN_BANK_SECT:
|
||||
case RPN_BANK_SELF:
|
||||
case RPN_SIZEOF_SECT:
|
||||
case RPN_STARTOF_SECT:
|
||||
case RPN_SIZEOF_SECTTYPE:
|
||||
case RPN_STARTOF_SECTTYPE:
|
||||
case RPN_HRAM:
|
||||
case RPN_RST:
|
||||
case RPN_HIGH:
|
||||
case RPN_LOW:
|
||||
case RPN_BITWIDTH:
|
||||
case RPN_TZCOUNT:
|
||||
case RPN_CONST:
|
||||
case RPN_SYM:
|
||||
fatalerror("%d is not a binary operator\n", op);
|
||||
default:
|
||||
// `makeBinaryOp` should never be called with a non-binary operator!
|
||||
// LCOV_EXCL_START
|
||||
unreachable_();
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
} else if (op == RPN_SUB && src1.isDiffConstant(src2.symbolOf())) {
|
||||
data = src1.symbolOf()->getValue() - src2.symbolOf()->getValue();
|
||||
} else if ((op == RPN_LOGAND || op == RPN_AND) && tryConstZero(src1, src2)) {
|
||||
@@ -522,10 +495,10 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const
|
||||
uint32_t lval = src1.value();
|
||||
uint8_t bytes[] = {
|
||||
RPN_CONST,
|
||||
(uint8_t)lval,
|
||||
(uint8_t)(lval >> 8),
|
||||
(uint8_t)(lval >> 16),
|
||||
(uint8_t)(lval >> 24),
|
||||
static_cast<uint8_t>(lval),
|
||||
static_cast<uint8_t>(lval >> 8),
|
||||
static_cast<uint8_t>(lval >> 16),
|
||||
static_cast<uint8_t>(lval >> 24),
|
||||
};
|
||||
rpn.clear();
|
||||
rpnPatchSize = 0;
|
||||
@@ -546,10 +519,10 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const
|
||||
uint32_t rval = src2.value();
|
||||
uint8_t bytes[] = {
|
||||
RPN_CONST,
|
||||
(uint8_t)rval,
|
||||
(uint8_t)(rval >> 8),
|
||||
(uint8_t)(rval >> 16),
|
||||
(uint8_t)(rval >> 24),
|
||||
static_cast<uint8_t>(rval),
|
||||
static_cast<uint8_t>(rval >> 8),
|
||||
static_cast<uint8_t>(rval >> 16),
|
||||
static_cast<uint8_t>(rval >> 24),
|
||||
};
|
||||
uint8_t *ptr = reserveSpace(sizeof(bytes) + 1, sizeof(bytes) + 1);
|
||||
memcpy(ptr, bytes, sizeof(bytes));
|
||||
@@ -558,24 +531,29 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const
|
||||
// Copy the right RPN and append the operator
|
||||
uint32_t rightRpnSize = src2.rpn.size();
|
||||
uint8_t *ptr = reserveSpace(rightRpnSize + 1, src2.rpnPatchSize + 1);
|
||||
if (rightRpnSize > 0)
|
||||
if (rightRpnSize > 0) {
|
||||
// If `rightRpnSize == 0`, then `memcpy(ptr, nullptr, rightRpnSize)` would be UB
|
||||
memcpy(ptr, src2.rpn.data(), rightRpnSize);
|
||||
}
|
||||
ptr[rightRpnSize] = op;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Expression::makeCheckHRAM() {
|
||||
bool Expression::makeCheckHRAM() {
|
||||
isSymbol = false;
|
||||
if (!isKnown()) {
|
||||
*reserveSpace(1) = RPN_HRAM;
|
||||
} else if (int32_t val = value(); val >= 0xFF00 && val <= 0xFFFF) {
|
||||
// That range is valid, but only keep the lower byte
|
||||
data = val & 0xFF;
|
||||
} else if (val < 0 || val > 0xFF) {
|
||||
} else if (val >= 0 && val <= 0xFF) {
|
||||
// That range is valid, but deprecated
|
||||
return true;
|
||||
} else {
|
||||
error("Source address $%" PRIx32 " not between $FF00 to $FFFF\n", val);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Expression::makeCheckRST() {
|
||||
@@ -584,16 +562,28 @@ void Expression::makeCheckRST() {
|
||||
} else if (int32_t val = value(); val & ~0x38) {
|
||||
// A valid RST address must be masked with 0x38
|
||||
error("Invalid address $%" PRIx32 " for RST\n", val);
|
||||
} else {
|
||||
// The target is in the "0x38" bits, all other bits are set
|
||||
data = val | 0xC7;
|
||||
}
|
||||
}
|
||||
|
||||
void Expression::makeCheckBitIndex(uint8_t mask) {
|
||||
assume((mask & 0xC0) != 0x00); // The high two bits must correspond to BIT, RES, or SET
|
||||
|
||||
if (!isKnown()) {
|
||||
uint8_t *ptr = reserveSpace(2);
|
||||
*ptr++ = RPN_BIT_INDEX;
|
||||
*ptr = mask;
|
||||
} else if (int32_t val = value(); val & ~0x07) {
|
||||
// A valid bit index must be masked with 0x07
|
||||
static char const *instructions[4] = {"instruction", "BIT", "RES", "SET"};
|
||||
error("Invalid bit index %" PRId32 " for %s\n", val, instructions[mask >> 6]);
|
||||
}
|
||||
}
|
||||
|
||||
// Checks that an RPN expression's value fits within N bits (signed or unsigned)
|
||||
void Expression::checkNBit(uint8_t n) const {
|
||||
if (isKnown())
|
||||
::checkNBit(value(), n, "Expression");
|
||||
if (isKnown()) {
|
||||
::checkNBit(value(), n, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
bool checkNBit(int32_t v, uint8_t n, char const *name) {
|
||||
@@ -601,11 +591,23 @@ bool checkNBit(int32_t v, uint8_t n, char const *name) {
|
||||
assume(n < CHAR_BIT * sizeof(int)); // Otherwise `1 << n` is UB
|
||||
|
||||
if (v < -(1 << n) || v >= 1 << n) {
|
||||
warning(WARNING_TRUNCATION_1, "%s must be %u-bit\n", name, n);
|
||||
warning(
|
||||
WARNING_TRUNCATION_1,
|
||||
n == 8 && !name ? "%s must be %u-bit; use LOW() to force 8-bit\n"
|
||||
: "%s must be %u-bit\n",
|
||||
name ? name : "Expression",
|
||||
n
|
||||
);
|
||||
return false;
|
||||
}
|
||||
if (v < -(1 << (n - 1))) {
|
||||
warning(WARNING_TRUNCATION_2, "%s must be %u-bit\n", name, n);
|
||||
warning(
|
||||
WARNING_TRUNCATION_2,
|
||||
n == 8 && !name ? "%s must be %u-bit; use LOW() to force 8-bit\n"
|
||||
: "%s must be %u-bit\n",
|
||||
name ? name : "Expression",
|
||||
n
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "asm/section.hpp"
|
||||
|
||||
@@ -49,9 +49,11 @@ static std::pair<Symbol const *, Symbol const *> currentLoadLabelScopes = {nullp
|
||||
int32_t loadOffset; // Offset into the LOAD section's parent (see sect_GetOutputOffset)
|
||||
|
||||
// A quick check to see if we have an initialized section
|
||||
[[nodiscard]] static bool requireSection() {
|
||||
if (currentSection)
|
||||
[[nodiscard]]
|
||||
static bool requireSection() {
|
||||
if (currentSection) {
|
||||
return true;
|
||||
}
|
||||
|
||||
error("Cannot output data outside of a SECTION\n");
|
||||
return false;
|
||||
@@ -59,12 +61,15 @@ int32_t loadOffset; // Offset into the LOAD section's parent (see sect_GetOutput
|
||||
|
||||
// A quick check to see if we have an initialized section that can contain
|
||||
// this much initialized data
|
||||
[[nodiscard]] static bool requireCodeSection() {
|
||||
if (!requireSection())
|
||||
[[nodiscard]]
|
||||
static bool requireCodeSection() {
|
||||
if (!requireSection()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sect_HasData(currentSection->type))
|
||||
if (sect_HasData(currentSection->type)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
error(
|
||||
"Section '%s' cannot contain code or data (not ROM0 or ROMX)\n",
|
||||
@@ -75,14 +80,15 @@ int32_t loadOffset; // Offset into the LOAD section's parent (see sect_GetOutput
|
||||
|
||||
void sect_CheckSizes() {
|
||||
for (Section const § : sectionList) {
|
||||
if (uint32_t maxSize = sectionTypeInfo[sect.type].size; sect.size > maxSize)
|
||||
if (uint32_t maxSize = sectionTypeInfo[sect.type].size; sect.size > maxSize) {
|
||||
error(
|
||||
"Section '%s' grew too big (max size = 0x%" PRIX32 " bytes, reached 0x%" PRIX32
|
||||
").\n",
|
||||
")\n",
|
||||
sect.name.c_str(),
|
||||
maxSize,
|
||||
sect.size
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,33 +112,36 @@ static unsigned int mergeSectUnion(
|
||||
|
||||
// Unionized sections only need "compatible" constraints, and they end up with the strictest
|
||||
// combination of both.
|
||||
if (sect_HasData(type))
|
||||
if (sect_HasData(type)) {
|
||||
sectError("Cannot declare ROM sections as UNION\n");
|
||||
}
|
||||
|
||||
if (org != (uint32_t)-1) {
|
||||
if (org != UINT32_MAX) {
|
||||
// If both are fixed, they must be the same
|
||||
if (sect.org != (uint32_t)-1 && sect.org != org)
|
||||
if (sect.org != UINT32_MAX && sect.org != org) {
|
||||
sectError(
|
||||
"Section already declared as fixed at different address $%04" PRIx32 "\n", sect.org
|
||||
);
|
||||
else if (sect.align != 0 && (mask(sect.align) & (org - sect.alignOfs)))
|
||||
} else if (sect.align != 0 && (mask(sect.align) & (org - sect.alignOfs))) {
|
||||
sectError(
|
||||
"Section already declared as aligned to %u bytes (offset %" PRIu16 ")\n",
|
||||
1U << sect.align,
|
||||
sect.alignOfs
|
||||
);
|
||||
else
|
||||
} else {
|
||||
// Otherwise, just override
|
||||
sect.org = org;
|
||||
}
|
||||
|
||||
} else if (alignment != 0) {
|
||||
// Make sure any fixed address given is compatible
|
||||
if (sect.org != (uint32_t)-1) {
|
||||
if ((sect.org - alignOffset) & mask(alignment))
|
||||
if (sect.org != UINT32_MAX) {
|
||||
if ((sect.org - alignOffset) & mask(alignment)) {
|
||||
sectError(
|
||||
"Section already declared as fixed at incompatible address $%04" PRIx32 "\n",
|
||||
sect.org
|
||||
);
|
||||
}
|
||||
// Check if alignment offsets are compatible
|
||||
} else if ((alignOffset & mask(sect.align)) != (sect.alignOfs & mask(alignment))) {
|
||||
sectError(
|
||||
@@ -159,38 +168,41 @@ static unsigned int
|
||||
// Fragments only need "compatible" constraints, and they end up with the strictest
|
||||
// combination of both.
|
||||
// The merging is however performed at the *end* of the original section!
|
||||
if (org != (uint32_t)-1) {
|
||||
if (org != UINT32_MAX) {
|
||||
uint16_t curOrg = org - sect.size;
|
||||
|
||||
// If both are fixed, they must be the same
|
||||
if (sect.org != (uint32_t)-1 && sect.org != curOrg)
|
||||
if (sect.org != UINT32_MAX && sect.org != curOrg) {
|
||||
sectError(
|
||||
"Section already declared as fixed at incompatible address $%04" PRIx32 "\n",
|
||||
sect.org
|
||||
);
|
||||
else if (sect.align != 0 && (mask(sect.align) & (curOrg - sect.alignOfs)))
|
||||
} else if (sect.align != 0 && (mask(sect.align) & (curOrg - sect.alignOfs))) {
|
||||
sectError(
|
||||
"Section already declared as aligned to %u bytes (offset %" PRIu16 ")\n",
|
||||
1U << sect.align,
|
||||
sect.alignOfs
|
||||
);
|
||||
else
|
||||
} else {
|
||||
// Otherwise, just override
|
||||
sect.org = curOrg;
|
||||
}
|
||||
|
||||
} else if (alignment != 0) {
|
||||
int32_t curOfs = (alignOffset - sect.size) % (1U << alignment);
|
||||
|
||||
if (curOfs < 0)
|
||||
if (curOfs < 0) {
|
||||
curOfs += 1U << alignment;
|
||||
}
|
||||
|
||||
// Make sure any fixed address given is compatible
|
||||
if (sect.org != (uint32_t)-1) {
|
||||
if ((sect.org - curOfs) & mask(alignment))
|
||||
if (sect.org != UINT32_MAX) {
|
||||
if ((sect.org - curOfs) & mask(alignment)) {
|
||||
sectError(
|
||||
"Section already declared as fixed at incompatible address $%04" PRIx32 "\n",
|
||||
sect.org
|
||||
);
|
||||
}
|
||||
// Check if alignment offsets are compatible
|
||||
} else if ((curOfs & mask(sect.align)) != (sect.alignOfs & mask(alignment))) {
|
||||
sectError(
|
||||
@@ -220,13 +232,14 @@ static void mergeSections(
|
||||
) {
|
||||
unsigned int nbSectErrors = 0;
|
||||
|
||||
if (type != sect.type)
|
||||
if (type != sect.type) {
|
||||
sectError(
|
||||
"Section already exists but with type %s\n", sectionTypeInfo[sect.type].name.c_str()
|
||||
);
|
||||
}
|
||||
|
||||
if (sect.modifier != mod) {
|
||||
sectError("Section already declared as %s section\n", sectionModNames[sect.modifier]);
|
||||
sectError("Section already declared as SECTION %s\n", sectionModNames[sect.modifier]);
|
||||
} else {
|
||||
switch (mod) {
|
||||
case SECTION_UNION:
|
||||
@@ -238,11 +251,13 @@ static void mergeSections(
|
||||
// Common checks
|
||||
|
||||
// If the section's bank is unspecified, override it
|
||||
if (sect.bank == (uint32_t)-1)
|
||||
if (sect.bank == UINT32_MAX) {
|
||||
sect.bank = bank;
|
||||
}
|
||||
// If both specify a bank, it must be the same one
|
||||
else if (bank != (uint32_t)-1 && sect.bank != bank)
|
||||
else if (bank != UINT32_MAX && sect.bank != bank) {
|
||||
sectError("Section already declared with different bank %" PRIu32 "\n", sect.bank);
|
||||
}
|
||||
break;
|
||||
|
||||
case SECTION_NORMAL:
|
||||
@@ -253,13 +268,14 @@ static void mergeSections(
|
||||
}
|
||||
}
|
||||
|
||||
if (nbSectErrors)
|
||||
if (nbSectErrors) {
|
||||
fatalerror(
|
||||
"Cannot create section \"%s\" (%u error%s)\n",
|
||||
sect.name.c_str(),
|
||||
nbSectErrors,
|
||||
nbSectErrors == 1 ? "" : "s"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#undef sectError
|
||||
@@ -292,8 +308,9 @@ static Section *createSection(
|
||||
out_RegisterNode(sect.src);
|
||||
|
||||
// It is only needed to allocate memory for ROM sections.
|
||||
if (sect_HasData(type))
|
||||
if (sect_HasData(type)) {
|
||||
sect.data.resize(sectionTypeInfo[type].size);
|
||||
}
|
||||
|
||||
return §
|
||||
}
|
||||
@@ -312,11 +329,12 @@ static Section *getSection(
|
||||
|
||||
// First, validate parameters, and normalize them if applicable
|
||||
|
||||
if (bank != (uint32_t)-1) {
|
||||
if (bank != UINT32_MAX) {
|
||||
if (type != SECTTYPE_ROMX && type != SECTTYPE_VRAM && type != SECTTYPE_SRAM
|
||||
&& type != SECTTYPE_WRAMX)
|
||||
&& type != SECTTYPE_WRAMX) {
|
||||
error("BANK only allowed for ROMX, WRAMX, SRAM, or VRAM sections\n");
|
||||
else if (bank < sectionTypeInfo[type].firstBank || bank > sectionTypeInfo[type].lastBank)
|
||||
} else if (bank < sectionTypeInfo[type].firstBank
|
||||
|| bank > sectionTypeInfo[type].lastBank) {
|
||||
error(
|
||||
"%s bank value $%04" PRIx32 " out of range ($%04" PRIx32 " to $%04" PRIx32 ")\n",
|
||||
sectionTypeInfo[type].name.c_str(),
|
||||
@@ -324,6 +342,7 @@ static Section *getSection(
|
||||
sectionTypeInfo[type].firstBank,
|
||||
sectionTypeInfo[type].lastBank
|
||||
);
|
||||
}
|
||||
} else if (nbbanks(type) == 1) {
|
||||
// If the section type only has a single bank, implicitly force it
|
||||
bank = sectionTypeInfo[type].firstBank;
|
||||
@@ -338,8 +357,8 @@ static Section *getSection(
|
||||
alignOffset = 0;
|
||||
}
|
||||
|
||||
if (org != (uint32_t)-1) {
|
||||
if (org < sectionTypeInfo[type].startAddr || org > endaddr(type))
|
||||
if (org != UINT32_MAX) {
|
||||
if (org < sectionTypeInfo[type].startAddr || org > endaddr(type)) {
|
||||
error(
|
||||
"Section \"%s\"'s fixed address $%04" PRIx32 " is outside of range [$%04" PRIx16
|
||||
"; $%04" PRIx16 "]\n",
|
||||
@@ -348,6 +367,7 @@ static Section *getSection(
|
||||
sectionTypeInfo[type].startAddr,
|
||||
endaddr(type)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (alignment != 0) {
|
||||
@@ -358,9 +378,10 @@ static Section *getSection(
|
||||
// It doesn't make sense to have both alignment and org set
|
||||
uint32_t mask = mask(alignment);
|
||||
|
||||
if (org != (uint32_t)-1) {
|
||||
if ((org - alignOffset) & mask)
|
||||
if (org != UINT32_MAX) {
|
||||
if ((org - alignOffset) & mask) {
|
||||
error("Section \"%s\"'s fixed address doesn't match its alignment\n", name.c_str());
|
||||
}
|
||||
alignment = 0; // Ignore it if it's satisfied
|
||||
} else if (sectionTypeInfo[type].startAddr & mask) {
|
||||
error(
|
||||
@@ -393,25 +414,29 @@ static Section *getSection(
|
||||
|
||||
// Set the current section
|
||||
static void changeSection() {
|
||||
if (!currentUnionStack.empty())
|
||||
if (!currentUnionStack.empty()) {
|
||||
fatalerror("Cannot change the section within a UNION\n");
|
||||
}
|
||||
|
||||
sym_ResetCurrentLabelScopes();
|
||||
}
|
||||
|
||||
bool Section::isSizeKnown() const {
|
||||
// SECTION UNION and SECTION FRAGMENT can still grow
|
||||
if (modifier != SECTION_NORMAL)
|
||||
if (modifier != SECTION_NORMAL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The current section (or current load section if within one) is still growing
|
||||
if (this == currentSection || this == currentLoadSection)
|
||||
if (this == currentSection || this == currentLoadSection) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Any section on the stack is still growing
|
||||
for (SectionStackEntry &entry : sectionStack) {
|
||||
if (entry.section && entry.section->name == name)
|
||||
if (entry.section && entry.section->name == name) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -426,12 +451,14 @@ void sect_NewSection(
|
||||
SectionModifier mod
|
||||
) {
|
||||
for (SectionStackEntry &entry : sectionStack) {
|
||||
if (entry.section && entry.section->name == name)
|
||||
if (entry.section && entry.section->name == name) {
|
||||
fatalerror("Section '%s' is already on the stack\n", name.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
if (currentLoadSection)
|
||||
if (currentLoadSection) {
|
||||
sect_EndLoadSection("SECTION");
|
||||
}
|
||||
|
||||
Section *sect = getSection(name, type, org, attrs, mod);
|
||||
|
||||
@@ -454,21 +481,18 @@ void sect_SetLoadSection(
|
||||
// Therefore, any interactions are NOT TESTED, so lift either of those restrictions at
|
||||
// your own peril! ^^
|
||||
|
||||
if (!requireCodeSection())
|
||||
if (!requireCodeSection()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (sect_HasData(type)) {
|
||||
error("`LOAD` blocks cannot create a ROM section\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (mod == SECTION_FRAGMENT) {
|
||||
error("`LOAD FRAGMENT` is not allowed\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentLoadSection)
|
||||
if (currentLoadSection) {
|
||||
sect_EndLoadSection("LOAD");
|
||||
}
|
||||
|
||||
Section *sect = getSection(name, type, org, attrs, mod);
|
||||
|
||||
@@ -480,12 +504,11 @@ void sect_SetLoadSection(
|
||||
}
|
||||
|
||||
void sect_EndLoadSection(char const *cause) {
|
||||
if (cause)
|
||||
if (cause) {
|
||||
warning(
|
||||
WARNING_UNTERMINATED_LOAD,
|
||||
"`LOAD` block without `ENDL` terminated by `%s`\n",
|
||||
cause
|
||||
WARNING_UNTERMINATED_LOAD, "`LOAD` block without `ENDL` terminated by `%s`\n", cause
|
||||
);
|
||||
}
|
||||
|
||||
if (!currentLoadSection) {
|
||||
error("Found `ENDL` outside of a `LOAD` block\n");
|
||||
@@ -500,8 +523,9 @@ void sect_EndLoadSection(char const *cause) {
|
||||
}
|
||||
|
||||
void sect_CheckLoadClosed() {
|
||||
if (currentLoadSection)
|
||||
if (currentLoadSection) {
|
||||
warning(WARNING_UNTERMINATED_LOAD, "`LOAD` block without `ENDL` terminated by EOF\n");
|
||||
}
|
||||
}
|
||||
|
||||
Section *sect_GetSymbolSection() {
|
||||
@@ -520,16 +544,18 @@ uint32_t sect_GetOutputOffset() {
|
||||
// Returns how many bytes need outputting for the specified alignment and offset to succeed
|
||||
uint32_t sect_GetAlignBytes(uint8_t alignment, uint16_t offset) {
|
||||
Section *sect = sect_GetSymbolSection();
|
||||
if (!sect)
|
||||
if (!sect) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool isFixed = sect->org != (uint32_t)-1;
|
||||
bool isFixed = sect->org != UINT32_MAX;
|
||||
|
||||
// If the section is not aligned, no bytes are needed
|
||||
// (fixed sections count as being maximally aligned for this purpose)
|
||||
uint8_t curAlignment = isFixed ? 16 : sect->align;
|
||||
if (curAlignment == 0)
|
||||
if (curAlignment == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// We need `(pcValue + curOffset + return value) % (1 << alignment) == offset`
|
||||
uint16_t pcValue = isFixed ? sect->org : sect->alignOfs;
|
||||
@@ -538,18 +564,20 @@ uint32_t sect_GetAlignBytes(uint8_t alignment, uint16_t offset) {
|
||||
}
|
||||
|
||||
void sect_AlignPC(uint8_t alignment, uint16_t offset) {
|
||||
if (!requireSection())
|
||||
if (!requireSection()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Section *sect = sect_GetSymbolSection();
|
||||
uint32_t alignSize = 1 << alignment; // Size of an aligned "block"
|
||||
|
||||
if (sect->org != (uint32_t)-1) {
|
||||
if ((sect->org + curOffset - offset) % alignSize)
|
||||
if (sect->org != UINT32_MAX) {
|
||||
if ((sect->org + curOffset - offset) % alignSize) {
|
||||
error(
|
||||
"Section's fixed address fails required alignment (PC = $%04" PRIx32 ")\n",
|
||||
sect->org + curOffset
|
||||
);
|
||||
}
|
||||
} else if (sect->align != 0
|
||||
&& (((sect->alignOfs + curOffset) % (1u << sect->align)) - offset) % alignSize) {
|
||||
error(
|
||||
@@ -573,18 +601,22 @@ void sect_AlignPC(uint8_t alignment, uint16_t offset) {
|
||||
}
|
||||
|
||||
static void growSection(uint32_t growth) {
|
||||
if (growth > 0 && curOffset > UINT32_MAX - growth)
|
||||
if (growth > 0 && curOffset > UINT32_MAX - growth) {
|
||||
fatalerror("Section size would overflow internal counter\n");
|
||||
}
|
||||
curOffset += growth;
|
||||
if (uint32_t outOffset = sect_GetOutputOffset(); outOffset > currentSection->size)
|
||||
if (uint32_t outOffset = sect_GetOutputOffset(); outOffset > currentSection->size) {
|
||||
currentSection->size = outOffset;
|
||||
if (currentLoadSection && curOffset > currentLoadSection->size)
|
||||
}
|
||||
if (currentLoadSection && curOffset > currentLoadSection->size) {
|
||||
currentLoadSection->size = curOffset;
|
||||
}
|
||||
}
|
||||
|
||||
static void writeByte(uint8_t byte) {
|
||||
if (uint32_t index = sect_GetOutputOffset(); index < currentSection->data.size())
|
||||
if (uint32_t index = sect_GetOutputOffset(); index < currentSection->data.size()) {
|
||||
currentSection->data[index] = byte;
|
||||
}
|
||||
growSection(1);
|
||||
}
|
||||
|
||||
@@ -626,8 +658,9 @@ static void endUnionMember() {
|
||||
UnionStackEntry &member = currentUnionStack.top();
|
||||
uint32_t memberSize = curOffset - member.start;
|
||||
|
||||
if (memberSize > member.size)
|
||||
if (memberSize > member.size) {
|
||||
member.size = memberSize;
|
||||
}
|
||||
curOffset = member.start;
|
||||
}
|
||||
|
||||
@@ -650,64 +683,75 @@ void sect_EndUnion() {
|
||||
}
|
||||
|
||||
void sect_CheckUnionClosed() {
|
||||
if (!currentUnionStack.empty())
|
||||
if (!currentUnionStack.empty()) {
|
||||
error("Unterminated UNION construct\n");
|
||||
}
|
||||
}
|
||||
|
||||
// Output a constant byte
|
||||
void sect_ConstByte(uint8_t byte) {
|
||||
if (!requireCodeSection())
|
||||
if (!requireCodeSection()) {
|
||||
return;
|
||||
}
|
||||
|
||||
writeByte(byte);
|
||||
}
|
||||
|
||||
// Output a string's character units as bytes
|
||||
void sect_ByteString(std::vector<int32_t> const &string) {
|
||||
if (!requireCodeSection())
|
||||
if (!requireCodeSection()) {
|
||||
return;
|
||||
|
||||
for (int32_t unit : string) {
|
||||
if (!checkNBit(unit, 8, "All character units"))
|
||||
break;
|
||||
}
|
||||
|
||||
for (int32_t unit : string)
|
||||
for (int32_t unit : string) {
|
||||
if (!checkNBit(unit, 8, "All character units")) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (int32_t unit : string) {
|
||||
writeByte(static_cast<uint8_t>(unit));
|
||||
}
|
||||
}
|
||||
|
||||
// Output a string's character units as words
|
||||
void sect_WordString(std::vector<int32_t> const &string) {
|
||||
if (!requireCodeSection())
|
||||
if (!requireCodeSection()) {
|
||||
return;
|
||||
|
||||
for (int32_t unit : string) {
|
||||
if (!checkNBit(unit, 16, "All character units"))
|
||||
break;
|
||||
}
|
||||
|
||||
for (int32_t unit : string)
|
||||
for (int32_t unit : string) {
|
||||
if (!checkNBit(unit, 16, "All character units")) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (int32_t unit : string) {
|
||||
writeWord(static_cast<uint16_t>(unit));
|
||||
}
|
||||
}
|
||||
|
||||
// Output a string's character units as longs
|
||||
void sect_LongString(std::vector<int32_t> const &string) {
|
||||
if (!requireCodeSection())
|
||||
if (!requireCodeSection()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int32_t unit : string)
|
||||
for (int32_t unit : string) {
|
||||
writeLong(static_cast<uint32_t>(unit));
|
||||
}
|
||||
}
|
||||
|
||||
// Skip this many bytes
|
||||
void sect_Skip(uint32_t skip, bool ds) {
|
||||
if (!requireSection())
|
||||
if (!requireSection()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!sect_HasData(currentSection->type)) {
|
||||
growSection(skip);
|
||||
} else {
|
||||
if (!ds)
|
||||
if (!ds) {
|
||||
warning(
|
||||
WARNING_EMPTY_DATA_DIRECTIVE,
|
||||
"%s directive without data in ROM\n",
|
||||
@@ -715,16 +759,19 @@ void sect_Skip(uint32_t skip, bool ds) {
|
||||
: (skip == 2) ? "DW"
|
||||
: "DB"
|
||||
);
|
||||
}
|
||||
// We know we're in a code SECTION
|
||||
while (skip--)
|
||||
while (skip--) {
|
||||
writeByte(fillByte);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Output a byte that can be relocatable or constant
|
||||
void sect_RelByte(Expression &expr, uint32_t pcShift) {
|
||||
if (!requireCodeSection())
|
||||
void sect_RelByte(Expression const &expr, uint32_t pcShift) {
|
||||
if (!requireCodeSection()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!expr.isKnown()) {
|
||||
createPatch(PATCHTYPE_BYTE, expr, pcShift);
|
||||
@@ -735,14 +782,13 @@ void sect_RelByte(Expression &expr, uint32_t pcShift) {
|
||||
}
|
||||
|
||||
// Output several bytes that can be relocatable or constant
|
||||
void sect_RelBytes(uint32_t n, std::vector<Expression> &exprs) {
|
||||
if (!requireCodeSection())
|
||||
void sect_RelBytes(uint32_t n, std::vector<Expression> const &exprs) {
|
||||
if (!requireCodeSection()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < n; i++) {
|
||||
Expression &expr = exprs[i % exprs.size()];
|
||||
|
||||
if (!expr.isKnown()) {
|
||||
if (Expression const &expr = exprs[i % exprs.size()]; !expr.isKnown()) {
|
||||
createPatch(PATCHTYPE_BYTE, expr, i);
|
||||
writeByte(0);
|
||||
} else {
|
||||
@@ -752,9 +798,10 @@ void sect_RelBytes(uint32_t n, std::vector<Expression> &exprs) {
|
||||
}
|
||||
|
||||
// Output a word that can be relocatable or constant
|
||||
void sect_RelWord(Expression &expr, uint32_t pcShift) {
|
||||
if (!requireCodeSection())
|
||||
void sect_RelWord(Expression const &expr, uint32_t pcShift) {
|
||||
if (!requireCodeSection()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!expr.isKnown()) {
|
||||
createPatch(PATCHTYPE_WORD, expr, pcShift);
|
||||
@@ -765,9 +812,10 @@ void sect_RelWord(Expression &expr, uint32_t pcShift) {
|
||||
}
|
||||
|
||||
// Output a long that can be relocatable or constant
|
||||
void sect_RelLong(Expression &expr, uint32_t pcShift) {
|
||||
if (!requireCodeSection())
|
||||
void sect_RelLong(Expression const &expr, uint32_t pcShift) {
|
||||
if (!requireCodeSection()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!expr.isKnown()) {
|
||||
createPatch(PATCHTYPE_LONG, expr, pcShift);
|
||||
@@ -778,9 +826,10 @@ void sect_RelLong(Expression &expr, uint32_t pcShift) {
|
||||
}
|
||||
|
||||
// Output a PC-relative byte that can be relocatable or constant
|
||||
void sect_PCRelByte(Expression &expr, uint32_t pcShift) {
|
||||
if (!requireCodeSection())
|
||||
void sect_PCRelByte(Expression const &expr, uint32_t pcShift) {
|
||||
if (!requireCodeSection()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Symbol const *pc = sym_GetPC(); !expr.isDiffConstant(pc)) {
|
||||
createPatch(PATCHTYPE_JR, expr, pcShift);
|
||||
@@ -791,15 +840,16 @@ void sect_PCRelByte(Expression &expr, uint32_t pcShift) {
|
||||
int16_t offset;
|
||||
|
||||
// Offset is relative to the byte *after* the operand
|
||||
if (sym == pc)
|
||||
if (sym == pc) {
|
||||
offset = -2; // PC as operand to `jr` is lower than reference PC by 2
|
||||
else
|
||||
} else {
|
||||
offset = sym->getValue() - (pc->getValue() + 1);
|
||||
}
|
||||
|
||||
if (offset < -128 || offset > 127) {
|
||||
error(
|
||||
"jr target must be between -128 and 127 bytes away, not %" PRId16
|
||||
"; use jp instead\n",
|
||||
"JR target must be between -128 and 127 bytes away, not %" PRId16
|
||||
"; use JP instead\n",
|
||||
offset
|
||||
);
|
||||
writeByte(0);
|
||||
@@ -815,16 +865,21 @@ void sect_BinaryFile(std::string const &name, int32_t startPos) {
|
||||
error("Start position cannot be negative (%" PRId32 ")\n", startPos);
|
||||
startPos = 0;
|
||||
}
|
||||
if (!requireCodeSection())
|
||||
if (!requireCodeSection()) {
|
||||
return;
|
||||
}
|
||||
|
||||
FILE *file = nullptr;
|
||||
if (std::optional<std::string> fullPath = fstk_FindFile(name); fullPath)
|
||||
if (std::optional<std::string> fullPath = fstk_FindFile(name); fullPath) {
|
||||
file = fopen(fullPath->c_str(), "rb");
|
||||
}
|
||||
if (!file) {
|
||||
if (generatedMissingIncludes) {
|
||||
if (verbose)
|
||||
// LCOV_EXCL_START
|
||||
if (verbose) {
|
||||
printf("Aborting (-MG) on INCBIN file '%s' (%s)\n", name.c_str(), strerror(errno));
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
failedOnMissingInclude = true;
|
||||
} else {
|
||||
error("Error opening INCBIN file '%s': %s\n", name.c_str(), strerror(errno));
|
||||
@@ -841,10 +896,11 @@ void sect_BinaryFile(std::string const &name, int32_t startPos) {
|
||||
// The file is seekable; skip to the specified start position
|
||||
fseek(file, startPos, SEEK_SET);
|
||||
} else {
|
||||
if (errno != ESPIPE)
|
||||
if (errno != ESPIPE) {
|
||||
error(
|
||||
"Error determining size of INCBIN file '%s': %s\n", name.c_str(), strerror(errno)
|
||||
);
|
||||
}
|
||||
// The file isn't seekable, so we'll just skip bytes one at a time
|
||||
while (startPos--) {
|
||||
if (fgetc(file) == EOF) {
|
||||
@@ -856,11 +912,13 @@ void sect_BinaryFile(std::string const &name, int32_t startPos) {
|
||||
}
|
||||
}
|
||||
|
||||
for (int byte; (byte = fgetc(file)) != EOF;)
|
||||
for (int byte; (byte = fgetc(file)) != EOF;) {
|
||||
writeByte(byte);
|
||||
}
|
||||
|
||||
if (ferror(file))
|
||||
if (ferror(file)) {
|
||||
error("Error reading INCBIN file '%s': %s\n", name.c_str(), strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
// Output a slice of a binary file
|
||||
@@ -873,18 +931,24 @@ void sect_BinaryFileSlice(std::string const &name, int32_t startPos, int32_t len
|
||||
error("Number of bytes to read cannot be negative (%" PRId32 ")\n", length);
|
||||
length = 0;
|
||||
}
|
||||
if (!requireCodeSection())
|
||||
if (!requireCodeSection()) {
|
||||
return;
|
||||
if (length == 0) // Don't even bother with 0-byte slices
|
||||
}
|
||||
if (length == 0) { // Don't even bother with 0-byte slices
|
||||
return;
|
||||
}
|
||||
|
||||
FILE *file = nullptr;
|
||||
if (std::optional<std::string> fullPath = fstk_FindFile(name); fullPath)
|
||||
if (std::optional<std::string> fullPath = fstk_FindFile(name); fullPath) {
|
||||
file = fopen(fullPath->c_str(), "rb");
|
||||
}
|
||||
if (!file) {
|
||||
if (generatedMissingIncludes) {
|
||||
if (verbose)
|
||||
// LCOV_EXCL_START
|
||||
if (verbose) {
|
||||
printf("Aborting (-MG) on INCBIN file '%s' (%s)\n", name.c_str(), strerror(errno));
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
failedOnMissingInclude = true;
|
||||
} else {
|
||||
error("Error opening INCBIN file '%s': %s\n", name.c_str(), strerror(errno));
|
||||
@@ -911,10 +975,11 @@ void sect_BinaryFileSlice(std::string const &name, int32_t startPos, int32_t len
|
||||
// The file is seekable; skip to the specified start position
|
||||
fseek(file, startPos, SEEK_SET);
|
||||
} else {
|
||||
if (errno != ESPIPE)
|
||||
if (errno != ESPIPE) {
|
||||
error(
|
||||
"Error determining size of INCBIN file '%s': %s\n", name.c_str(), strerror(errno)
|
||||
);
|
||||
}
|
||||
// The file isn't seekable, so we'll just skip bytes one at a time
|
||||
while (startPos--) {
|
||||
if (fgetc(file) == EOF) {
|
||||
@@ -960,11 +1025,13 @@ void sect_PushSection() {
|
||||
}
|
||||
|
||||
void sect_PopSection() {
|
||||
if (sectionStack.empty())
|
||||
if (sectionStack.empty()) {
|
||||
fatalerror("No entries in the section stack\n");
|
||||
}
|
||||
|
||||
if (currentLoadSection)
|
||||
if (currentLoadSection) {
|
||||
sect_EndLoadSection("POPS");
|
||||
}
|
||||
|
||||
SectionStackEntry entry = sectionStack.front();
|
||||
sectionStack.pop_front();
|
||||
@@ -978,15 +1045,24 @@ void sect_PopSection() {
|
||||
std::swap(currentUnionStack, entry.unionStack);
|
||||
}
|
||||
|
||||
void sect_CheckStack() {
|
||||
if (!sectionStack.empty()) {
|
||||
warning(WARNING_UNMATCHED_DIRECTIVE, "`PUSHS` without corresponding `POPS`\n");
|
||||
}
|
||||
}
|
||||
|
||||
void sect_EndSection() {
|
||||
if (!currentSection)
|
||||
if (!currentSection) {
|
||||
fatalerror("Cannot end the section outside of a SECTION\n");
|
||||
}
|
||||
|
||||
if (!currentUnionStack.empty())
|
||||
if (!currentUnionStack.empty()) {
|
||||
fatalerror("Cannot end the section within a UNION\n");
|
||||
}
|
||||
|
||||
if (currentLoadSection)
|
||||
if (currentLoadSection) {
|
||||
sect_EndLoadSection("ENDSECTION");
|
||||
}
|
||||
|
||||
// Reset the section scope
|
||||
currentSection = nullptr;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "asm/symbol.hpp"
|
||||
|
||||
@@ -40,8 +40,9 @@ bool sym_IsPC(Symbol const *sym) {
|
||||
}
|
||||
|
||||
void sym_ForEach(void (*callback)(Symbol &)) {
|
||||
for (auto &it : symbols)
|
||||
for (auto &it : symbols) {
|
||||
callback(it.second);
|
||||
}
|
||||
}
|
||||
|
||||
static int32_t NARGCallback() {
|
||||
@@ -92,7 +93,7 @@ int32_t Symbol::getOutputValue() const {
|
||||
}
|
||||
|
||||
ContentSpan const &Symbol::getMacro() const {
|
||||
assume((std::holds_alternative<ContentSpan>(data)));
|
||||
assume(std::holds_alternative<ContentSpan>(data));
|
||||
return std::get<ContentSpan>(data);
|
||||
}
|
||||
|
||||
@@ -101,8 +102,9 @@ std::shared_ptr<std::string> Symbol::getEqus() const {
|
||||
std::holds_alternative<std::shared_ptr<std::string>>(data)
|
||||
|| std::holds_alternative<std::shared_ptr<std::string> (*)()>(data)
|
||||
);
|
||||
if (auto *callback = std::get_if<std::shared_ptr<std::string> (*)()>(&data); callback)
|
||||
if (auto *callback = std::get_if<std::shared_ptr<std::string> (*)()>(&data); callback) {
|
||||
return (*callback)();
|
||||
}
|
||||
return std::get<std::shared_ptr<std::string>>(data);
|
||||
}
|
||||
|
||||
@@ -123,8 +125,9 @@ static void updateSymbolFilename(Symbol &sym) {
|
||||
sym.fileLine = sym.src ? lexer_GetLineNo() : 0;
|
||||
|
||||
// If the old node was registered, ensure the new one is too
|
||||
if (oldSrc && oldSrc->ID != (uint32_t)-1)
|
||||
if (oldSrc && oldSrc->ID != UINT32_MAX) {
|
||||
out_RegisterNode(sym.src);
|
||||
}
|
||||
}
|
||||
|
||||
static void alreadyDefinedError(Symbol const &sym, char const *asType) {
|
||||
@@ -133,8 +136,9 @@ static void alreadyDefinedError(Symbol const &sym, char const *asType) {
|
||||
error("'%s' is reserved for a built-in symbol\n", sym.name.c_str());
|
||||
} else {
|
||||
error("'%s' already defined", sym.name.c_str());
|
||||
if (asType)
|
||||
if (asType) {
|
||||
fprintf(stderr, " as %s", asType);
|
||||
}
|
||||
fputs(" at ", stderr);
|
||||
dumpFilename(sym);
|
||||
}
|
||||
@@ -169,7 +173,7 @@ static Symbol &createSymbol(std::string const &symName) {
|
||||
sym.section = nullptr;
|
||||
sym.src = fstk_GetFileStack();
|
||||
sym.fileLine = sym.src ? lexer_GetLineNo() : 0;
|
||||
sym.ID = -1;
|
||||
sym.ID = UINT32_MAX;
|
||||
sym.defIndex = nextDefIndex++;
|
||||
|
||||
return sym;
|
||||
@@ -184,28 +188,34 @@ static bool isAutoScoped(std::string const &symName) {
|
||||
size_t dotPos = symName.find('.');
|
||||
|
||||
// If there are no dots, it's not a local label
|
||||
if (dotPos == std::string::npos)
|
||||
if (dotPos == std::string::npos) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Label scopes `.` and `..` are the only nonlocal identifiers that start with a dot
|
||||
if (dotPos == 0 && symName.find_first_not_of('.') == symName.npos)
|
||||
if (dotPos == 0 && symName.find_first_not_of('.') == symName.npos) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for nothing after the dot
|
||||
if (dotPos == symName.length() - 1)
|
||||
if (dotPos == symName.length() - 1) {
|
||||
fatalerror("'%s' is a nonsensical reference to an empty local label\n", symName.c_str());
|
||||
}
|
||||
|
||||
// Check for more than one dot
|
||||
if (symName.find('.', dotPos + 1) != std::string::npos)
|
||||
if (symName.find('.', dotPos + 1) != std::string::npos) {
|
||||
fatalerror("'%s' is a nonsensical reference to a nested local label\n", symName.c_str());
|
||||
}
|
||||
|
||||
// Check for already-qualified local label
|
||||
if (dotPos > 0)
|
||||
if (dotPos > 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for unqualifiable local label
|
||||
if (!globalScope)
|
||||
if (!globalScope) {
|
||||
fatalerror("Unqualified local label '%s' in main scope\n", symName.c_str());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -252,24 +262,28 @@ void sym_Purge(std::string const &symName) {
|
||||
Symbol *sym = sym_FindScopedValidSymbol(symName);
|
||||
|
||||
if (!sym) {
|
||||
if (sym_IsPurgedScoped(symName))
|
||||
if (sym_IsPurgedScoped(symName)) {
|
||||
error("'%s' was already purged\n", symName.c_str());
|
||||
else
|
||||
} else {
|
||||
error("'%s' not defined\n", symName.c_str());
|
||||
}
|
||||
} else if (sym->isBuiltin) {
|
||||
error("Built-in symbol '%s' cannot be purged\n", symName.c_str());
|
||||
} else if (sym->ID != (uint32_t)-1) {
|
||||
} else if (sym->ID != UINT32_MAX) {
|
||||
error("Symbol \"%s\" is referenced and thus cannot be purged\n", symName.c_str());
|
||||
} else {
|
||||
if (sym->isExported)
|
||||
if (sym->isExported) {
|
||||
warning(WARNING_PURGE_1, "Purging an exported symbol \"%s\"\n", symName.c_str());
|
||||
else if (sym->isLabel())
|
||||
} else if (sym->isLabel()) {
|
||||
warning(WARNING_PURGE_2, "Purging a label \"%s\"\n", symName.c_str());
|
||||
}
|
||||
// Do not keep a reference to the label after purging it
|
||||
if (sym == globalScope)
|
||||
if (sym == globalScope) {
|
||||
globalScope = nullptr;
|
||||
if (sym == localScope)
|
||||
}
|
||||
if (sym == localScope) {
|
||||
localScope = nullptr;
|
||||
}
|
||||
purgedSymbols.emplace(sym->name);
|
||||
symbols.erase(sym->name);
|
||||
}
|
||||
@@ -295,31 +309,22 @@ void sym_SetRSValue(int32_t value) {
|
||||
}
|
||||
|
||||
uint32_t Symbol::getConstantValue() const {
|
||||
if (isConstant())
|
||||
if (isConstant()) {
|
||||
return getValue();
|
||||
}
|
||||
|
||||
if (sym_IsPC(this)) {
|
||||
if (!getSection())
|
||||
if (!getSection()) {
|
||||
error("PC has no value outside of a section\n");
|
||||
else
|
||||
} else {
|
||||
error("PC does not have a constant value; the current section is not fixed\n");
|
||||
}
|
||||
} else {
|
||||
error("\"%s\" does not have a constant value\n", name.c_str());
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t sym_GetConstantValue(std::string const &symName) {
|
||||
if (Symbol const *sym = sym_FindScopedSymbol(symName); sym)
|
||||
return sym->getConstantValue();
|
||||
|
||||
if (sym_IsPurgedScoped(symName))
|
||||
error("'%s' not defined; it was purged\n", symName.c_str());
|
||||
else
|
||||
error("'%s' not defined\n", symName.c_str());
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::pair<Symbol const *, Symbol const *> sym_GetCurrentLabelScopes() {
|
||||
return {globalScope, localScope};
|
||||
}
|
||||
@@ -361,8 +366,9 @@ static Symbol *createNonrelocSymbol(std::string const &symName, bool numeric) {
|
||||
Symbol *sym_AddEqu(std::string const &symName, int32_t value) {
|
||||
Symbol *sym = createNonrelocSymbol(symName, true);
|
||||
|
||||
if (!sym)
|
||||
if (!sym) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
sym->type = SYM_EQU;
|
||||
sym->data = value;
|
||||
@@ -373,8 +379,9 @@ Symbol *sym_AddEqu(std::string const &symName, int32_t value) {
|
||||
Symbol *sym_RedefEqu(std::string const &symName, int32_t value) {
|
||||
Symbol *sym = sym_FindExactSymbol(symName);
|
||||
|
||||
if (!sym)
|
||||
if (!sym) {
|
||||
return sym_AddEqu(symName, value);
|
||||
}
|
||||
|
||||
if (sym->isDefined() && sym->type != SYM_EQU) {
|
||||
alreadyDefinedError(*sym, "non-EQU");
|
||||
@@ -394,8 +401,9 @@ Symbol *sym_RedefEqu(std::string const &symName, int32_t value) {
|
||||
Symbol *sym_AddString(std::string const &symName, std::shared_ptr<std::string> str) {
|
||||
Symbol *sym = createNonrelocSymbol(symName, false);
|
||||
|
||||
if (!sym)
|
||||
if (!sym) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
sym->type = SYM_EQUS;
|
||||
sym->data = str;
|
||||
@@ -405,8 +413,9 @@ Symbol *sym_AddString(std::string const &symName, std::shared_ptr<std::string> s
|
||||
Symbol *sym_RedefString(std::string const &symName, std::shared_ptr<std::string> str) {
|
||||
Symbol *sym = sym_FindExactSymbol(symName);
|
||||
|
||||
if (!sym)
|
||||
if (!sym) {
|
||||
return sym_AddString(symName, str);
|
||||
}
|
||||
|
||||
if (sym->type != SYM_EQUS) {
|
||||
if (sym->isDefined()) {
|
||||
@@ -460,14 +469,16 @@ static Symbol *addLabel(std::string const &symName) {
|
||||
}
|
||||
// If the symbol already exists as a ref, just "take over" it
|
||||
sym->type = SYM_LABEL;
|
||||
sym->data = (int32_t)sect_GetSymbolOffset();
|
||||
sym->data = static_cast<int32_t>(sect_GetSymbolOffset());
|
||||
// Don't export anonymous labels
|
||||
if (exportAll && !symName.starts_with('!'))
|
||||
if (exportAll && !symName.starts_with('!')) {
|
||||
sym->isExported = true;
|
||||
}
|
||||
sym->section = sect_GetSymbolSection();
|
||||
|
||||
if (sym && !sym->section)
|
||||
if (sym && !sym->section) {
|
||||
error("Label \"%s\" created outside of a SECTION\n", symName.c_str());
|
||||
}
|
||||
|
||||
return sym;
|
||||
}
|
||||
@@ -478,8 +489,9 @@ Symbol *sym_AddLocalLabel(std::string const &symName) {
|
||||
|
||||
Symbol *sym = addLabel(isAutoScoped(symName) ? globalScope->name + symName : symName);
|
||||
|
||||
if (sym)
|
||||
if (sym) {
|
||||
localScope = sym;
|
||||
}
|
||||
|
||||
return sym;
|
||||
}
|
||||
@@ -503,8 +515,10 @@ static uint32_t anonLabelID = 0;
|
||||
|
||||
Symbol *sym_AddAnonLabel() {
|
||||
if (anonLabelID == UINT32_MAX) {
|
||||
// LCOV_EXCL_START
|
||||
error("Only %" PRIu32 " anonymous labels can be created!", anonLabelID);
|
||||
return nullptr;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
std::string anon = sym_MakeAnonLabelName(0, true); // The direction is important!
|
||||
@@ -516,7 +530,7 @@ std::string sym_MakeAnonLabelName(uint32_t ofs, bool neg) {
|
||||
uint32_t id = 0;
|
||||
|
||||
if (neg) {
|
||||
if (ofs > anonLabelID)
|
||||
if (ofs > anonLabelID) {
|
||||
error(
|
||||
"Reference to anonymous label %" PRIu32 " before, when only %" PRIu32
|
||||
" ha%s been created so far\n",
|
||||
@@ -524,45 +538,52 @@ std::string sym_MakeAnonLabelName(uint32_t ofs, bool neg) {
|
||||
anonLabelID,
|
||||
anonLabelID == 1 ? "s" : "ve"
|
||||
);
|
||||
else
|
||||
} else {
|
||||
id = anonLabelID - ofs;
|
||||
}
|
||||
} else {
|
||||
ofs--; // We're referencing symbols that haven't been created yet...
|
||||
if (ofs > UINT32_MAX - anonLabelID)
|
||||
if (ofs > UINT32_MAX - anonLabelID) {
|
||||
// LCOV_EXCL_START
|
||||
error(
|
||||
"Reference to anonymous label %" PRIu32 " after, when only %" PRIu32
|
||||
" may still be created\n",
|
||||
ofs + 1,
|
||||
UINT32_MAX - anonLabelID
|
||||
);
|
||||
else
|
||||
} else {
|
||||
// LCOV_EXCL_STOP
|
||||
id = anonLabelID + ofs;
|
||||
}
|
||||
}
|
||||
|
||||
std::string anon("!");
|
||||
anon += std::to_string(id);
|
||||
return anon;
|
||||
return "!"s + std::to_string(id);
|
||||
}
|
||||
|
||||
void sym_Export(std::string const &symName) {
|
||||
if (symName.starts_with('!')) {
|
||||
// LCOV_EXCL_START
|
||||
// The parser does not accept anonymous labels for an `EXPORT` directive
|
||||
error("Anonymous labels cannot be exported\n");
|
||||
return;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
Symbol *sym = sym_FindScopedSymbol(symName);
|
||||
|
||||
// If the symbol doesn't exist, create a ref that can be purged
|
||||
if (!sym)
|
||||
if (!sym) {
|
||||
sym = sym_Ref(symName);
|
||||
}
|
||||
sym->isExported = true;
|
||||
}
|
||||
|
||||
Symbol *sym_AddMacro(std::string const &symName, int32_t defLineNo, ContentSpan const &span) {
|
||||
Symbol *sym = createNonrelocSymbol(symName, false);
|
||||
|
||||
if (!sym)
|
||||
if (!sym) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
sym->type = SYM_MACRO;
|
||||
sym->data = span;
|
||||
@@ -627,11 +648,13 @@ void sym_Init(time_t now) {
|
||||
sym_AddEqu("__RGBDS_RC__"s, PACKAGE_VERSION_RC)->isBuiltin = true;
|
||||
#endif
|
||||
|
||||
if (now == (time_t)-1) {
|
||||
// LCOV_EXCL_START
|
||||
if (now == static_cast<time_t>(-1)) {
|
||||
warn("Failed to determine current time");
|
||||
// Fall back by pretending we are at the Epoch
|
||||
now = 0;
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
tm const *time_local = localtime(&now);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "asm/warning.hpp"
|
||||
|
||||
@@ -35,13 +35,13 @@ struct WarningFlag {
|
||||
WarningLevel level;
|
||||
};
|
||||
|
||||
static const WarningFlag metaWarnings[] = {
|
||||
static WarningFlag const metaWarnings[] = {
|
||||
{"all", LEVEL_ALL },
|
||||
{"extra", LEVEL_EXTRA },
|
||||
{"everything", LEVEL_EVERYTHING},
|
||||
};
|
||||
|
||||
static const WarningFlag warningFlags[NB_WARNINGS] = {
|
||||
static WarningFlag const warningFlags[NB_WARNINGS] = {
|
||||
{"assert", LEVEL_DEFAULT },
|
||||
{"backwards-for", LEVEL_ALL },
|
||||
{"builtin-args", LEVEL_ALL },
|
||||
@@ -56,6 +56,7 @@ static const WarningFlag warningFlags[NB_WARNINGS] = {
|
||||
{"obsolete", LEVEL_DEFAULT },
|
||||
{"shift", LEVEL_EVERYTHING},
|
||||
{"shift-amount", LEVEL_EVERYTHING},
|
||||
{"unmatched-directive", LEVEL_EXTRA },
|
||||
{"unterminated-load", LEVEL_EXTRA },
|
||||
{"user", LEVEL_DEFAULT },
|
||||
// Parametric warnings
|
||||
@@ -84,8 +85,9 @@ enum WarningBehavior { DISABLED, ENABLED, ERROR };
|
||||
|
||||
static WarningBehavior getWarningBehavior(WarningID id) {
|
||||
// Check if warnings are globally disabled
|
||||
if (!warnings)
|
||||
if (!warnings) {
|
||||
return WarningBehavior::DISABLED;
|
||||
}
|
||||
|
||||
// Get the state of this warning flag
|
||||
WarningState const &flagState = warningStates.flagStates[id];
|
||||
@@ -99,34 +101,43 @@ static WarningBehavior getWarningBehavior(WarningID id) {
|
||||
warningIsError ? WarningBehavior::ERROR : WarningBehavior::ENABLED;
|
||||
|
||||
// First, check the state of the specific warning flag
|
||||
if (flagState.state == WARNING_DISABLED) // -Wno-<flag>
|
||||
if (flagState.state == WARNING_DISABLED) { // -Wno-<flag>
|
||||
return WarningBehavior::DISABLED;
|
||||
if (flagState.error == WARNING_ENABLED) // -Werror=<flag>
|
||||
}
|
||||
if (flagState.error == WARNING_ENABLED) { // -Werror=<flag>
|
||||
return WarningBehavior::ERROR;
|
||||
if (flagState.state == WARNING_ENABLED) // -W<flag>
|
||||
}
|
||||
if (flagState.state == WARNING_ENABLED) { // -W<flag>
|
||||
return enabledBehavior;
|
||||
}
|
||||
|
||||
// If no flag is specified, check the state of the "meta" flags that affect this warning flag
|
||||
if (metaState.state == WARNING_DISABLED) // -Wno-<meta>
|
||||
if (metaState.state == WARNING_DISABLED) { // -Wno-<meta>
|
||||
return WarningBehavior::DISABLED;
|
||||
if (metaState.error == WARNING_ENABLED) // -Werror=<meta>
|
||||
}
|
||||
if (metaState.error == WARNING_ENABLED) { // -Werror=<meta>
|
||||
return WarningBehavior::ERROR;
|
||||
if (metaState.state == WARNING_ENABLED) // -W<meta>
|
||||
}
|
||||
if (metaState.state == WARNING_ENABLED) { // -W<meta>
|
||||
return enabledBehavior;
|
||||
}
|
||||
|
||||
// If no meta flag is specified, check the default state of this warning flag
|
||||
if (warningFlags[id].level == LEVEL_DEFAULT) // enabled by default
|
||||
if (warningFlags[id].level == LEVEL_DEFAULT) { // enabled by default
|
||||
return enabledBehavior;
|
||||
}
|
||||
|
||||
// No flag enables this warning, explicitly or implicitly
|
||||
return WarningBehavior::DISABLED;
|
||||
}
|
||||
|
||||
void WarningState::update(WarningState other) {
|
||||
if (other.state != WARNING_DEFAULT)
|
||||
if (other.state != WARNING_DEFAULT) {
|
||||
state = other.state;
|
||||
if (other.error != WARNING_DEFAULT)
|
||||
}
|
||||
if (other.error != WARNING_DEFAULT) {
|
||||
error = other.error;
|
||||
}
|
||||
}
|
||||
|
||||
void processWarningFlag(char const *flag) {
|
||||
@@ -148,16 +159,16 @@ void processWarningFlag(char const *flag) {
|
||||
if (rootFlag.starts_with("error=")) {
|
||||
// `-Werror=<flag>` enables the flag as an error
|
||||
state = {.state = WARNING_ENABLED, .error = WARNING_ENABLED};
|
||||
rootFlag.erase(0, QUOTEDSTRLEN("error="));
|
||||
rootFlag.erase(0, literal_strlen("error="));
|
||||
} else if (rootFlag.starts_with("no-error=")) {
|
||||
// `-Wno-error=<flag>` prevents the flag from being an error,
|
||||
// without affecting whether it is enabled
|
||||
state = {.state = WARNING_DEFAULT, .error = WARNING_DISABLED};
|
||||
rootFlag.erase(0, QUOTEDSTRLEN("no-error="));
|
||||
rootFlag.erase(0, literal_strlen("no-error="));
|
||||
} else if (rootFlag.starts_with("no-")) {
|
||||
// `-Wno-<flag>` disables the flag
|
||||
state = {.state = WARNING_DISABLED, .error = WARNING_DEFAULT};
|
||||
rootFlag.erase(0, QUOTEDSTRLEN("no-"));
|
||||
rootFlag.erase(0, literal_strlen("no-"));
|
||||
} else {
|
||||
// `-W<flag>` enables the flag
|
||||
state = {.state = WARNING_ENABLED, .error = WARNING_DEFAULT};
|
||||
@@ -183,12 +194,14 @@ void processWarningFlag(char const *flag) {
|
||||
// The `if`'s condition above ensures that this will run at least once
|
||||
do {
|
||||
// If we don't have a digit, bail
|
||||
if (*ptr < '0' || *ptr > '9')
|
||||
if (*ptr < '0' || *ptr > '9') {
|
||||
break;
|
||||
}
|
||||
// Avoid overflowing!
|
||||
if (param > UINT8_MAX - (*ptr - '0')) {
|
||||
if (!warned)
|
||||
if (!warned) {
|
||||
warnx("Invalid warning flag \"%s\": capping parameter at 255", flag);
|
||||
}
|
||||
warned = true; // Only warn once, cap always
|
||||
param = 255;
|
||||
continue;
|
||||
@@ -202,8 +215,9 @@ void processWarningFlag(char const *flag) {
|
||||
if (*ptr == '\0') {
|
||||
rootFlag.resize(equals);
|
||||
// `-W<flag>=0` is equivalent to `-Wno-<flag>`
|
||||
if (param == 0)
|
||||
if (param == 0) {
|
||||
state.state = WARNING_DISABLED;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -217,8 +231,9 @@ void processWarningFlag(char const *flag) {
|
||||
assume(paramWarning.defaultLevel <= maxParam);
|
||||
|
||||
if (rootFlag == warningFlags[baseID].name) { // Match!
|
||||
if (rootFlag == "numeric-string")
|
||||
if (rootFlag == "numeric-string") {
|
||||
warning(WARNING_OBSOLETE, "Warning flag \"numeric-string\" is deprecated\n");
|
||||
}
|
||||
|
||||
// If making the warning an error but param is 0, set to the maximum
|
||||
// This accommodates `-Werror=<flag>`, but also `-Werror=<flag>=0`, which is
|
||||
@@ -228,7 +243,7 @@ void processWarningFlag(char const *flag) {
|
||||
if (param == 0) {
|
||||
param = paramWarning.defaultLevel;
|
||||
} else if (param > maxParam) {
|
||||
if (param != 255) // Don't warn if already capped
|
||||
if (param != 255) { // Don't warn if already capped
|
||||
warnx(
|
||||
"Invalid parameter %" PRIu8
|
||||
" for warning flag \"%s\"; capping at maximum %" PRIu8,
|
||||
@@ -236,16 +251,18 @@ void processWarningFlag(char const *flag) {
|
||||
rootFlag.c_str(),
|
||||
maxParam
|
||||
);
|
||||
}
|
||||
param = maxParam;
|
||||
}
|
||||
|
||||
// Set the first <param> to enabled/error, and disable the rest
|
||||
for (uint8_t ofs = 0; ofs < maxParam; ofs++) {
|
||||
WarningState &warning = warningStates.flagStates[baseID + ofs];
|
||||
if (ofs < param)
|
||||
if (ofs < param) {
|
||||
warning.update(state);
|
||||
else
|
||||
} else {
|
||||
warning.state = WARNING_DISABLED;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -258,8 +275,9 @@ void processWarningFlag(char const *flag) {
|
||||
if (rootFlag == metaWarning.name) {
|
||||
// Set each of the warning flags that meets this level
|
||||
for (WarningID id : EnumSeq(NB_WARNINGS)) {
|
||||
if (metaWarning.level >= warningFlags[id].level)
|
||||
if (metaWarning.level >= warningFlags[id].level) {
|
||||
warningStates.metaStates[id].update(state);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -298,16 +316,18 @@ void error(char const *fmt, ...) {
|
||||
|
||||
// This intentionally makes 0 act as "unlimited" (or at least "limited to sizeof(unsigned)")
|
||||
nbErrors++;
|
||||
if (nbErrors == maxErrors)
|
||||
if (nbErrors == maxErrors) {
|
||||
errx(
|
||||
"The maximum of %u error%s was reached (configure with \"-X/--max-errors\"); assembly "
|
||||
"aborted!",
|
||||
maxErrors,
|
||||
maxErrors == 1 ? "" : "s"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
[[noreturn]] void fatalerror(char const *fmt, ...) {
|
||||
[[noreturn]]
|
||||
void fatalerror(char const *fmt, ...) {
|
||||
va_list args;
|
||||
|
||||
va_start(args, fmt);
|
||||
|
||||
@@ -15,14 +15,17 @@ fi
|
||||
BISON_FLAGS="-Wall -Dparse.lac=full -Dlr.type=ielr"
|
||||
|
||||
# Set some optimization flags on versions that support them
|
||||
if [ "$BISON_MAJOR" -eq 4 ] || [ "$BISON_MAJOR" -eq 3 ] && [ "$BISON_MINOR" -ge 5 ]; then
|
||||
if [ "$BISON_MAJOR" -ge 4 ] || [ "$BISON_MAJOR" -eq 3 ] && [ "$BISON_MINOR" -ge 5 ]; then
|
||||
BISON_FLAGS="$BISON_FLAGS -Dapi.token.raw=true"
|
||||
fi
|
||||
if [ "$BISON_MAJOR" -eq 4 ] || [ "$BISON_MAJOR" -eq 3 ] && [ "$BISON_MINOR" -ge 6 ]; then
|
||||
if [ "$BISON_MAJOR" -ge 4 ] || [ "$BISON_MAJOR" -eq 3 ] && [ "$BISON_MINOR" -ge 6 ]; then
|
||||
BISON_FLAGS="$BISON_FLAGS -Dparse.error=detailed"
|
||||
else
|
||||
BISON_FLAGS="$BISON_FLAGS -Dparse.error=verbose"
|
||||
fi
|
||||
if [ "$BISON_MAJOR" -ge 4 ] || [ "$BISON_MAJOR" -eq 3 ] && [ "$BISON_MINOR" -ge 7 ]; then
|
||||
BISON_FLAGS="$BISON_FLAGS -Wcounterexamples"
|
||||
fi
|
||||
|
||||
# Replace the arguments to this script ($@) with the ones in $BISON_FLAGS
|
||||
eval "set -- $BISON_FLAGS"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "error.hpp"
|
||||
|
||||
@@ -22,7 +22,8 @@ static void vwarnx(char const *fmt, va_list ap) {
|
||||
putc('\n', stderr);
|
||||
}
|
||||
|
||||
[[noreturn]] static void verr(char const *fmt, va_list ap) {
|
||||
[[noreturn]]
|
||||
static void verr(char const *fmt, va_list ap) {
|
||||
char const *error = strerror(errno);
|
||||
|
||||
fprintf(stderr, "error: ");
|
||||
@@ -32,7 +33,8 @@ static void vwarnx(char const *fmt, va_list ap) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
[[noreturn]] static void verrx(char const *fmt, va_list ap) {
|
||||
[[noreturn]]
|
||||
static void verrx(char const *fmt, va_list ap) {
|
||||
fprintf(stderr, "error: ");
|
||||
vfprintf(stderr, fmt, ap);
|
||||
putc('\n', stderr);
|
||||
@@ -56,14 +58,16 @@ void warnx(char const *fmt, ...) {
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
[[noreturn]] void err(char const *fmt, ...) {
|
||||
[[noreturn]]
|
||||
void err(char const *fmt, ...) {
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
verr(fmt, ap);
|
||||
}
|
||||
|
||||
[[noreturn]] void errx(char const *fmt, ...) {
|
||||
[[noreturn]]
|
||||
void errx(char const *fmt, ...) {
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
|
||||
77
src/extern/getopt.cpp
vendored
77
src/extern/getopt.cpp
vendored
@@ -1,6 +1,6 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
/* This implementation was taken from musl and modified for RGBDS */
|
||||
// This implementation was taken from musl and modified for RGBDS
|
||||
|
||||
#include "extern/getopt.hpp"
|
||||
|
||||
@@ -19,8 +19,9 @@ static int musl_optpos;
|
||||
static void musl_getopt_msg(char const *a, char const *b, char const *c, size_t l) {
|
||||
FILE *f = stderr;
|
||||
|
||||
if (fputs(a, f) >= 0 && fwrite(b, strlen(b), 1, f) && fwrite(c, 1, l, f) == l)
|
||||
if (fputs(a, f) >= 0 && fwrite(b, strlen(b), 1, f) && fwrite(c, 1, l, f) == l) {
|
||||
putc('\n', f);
|
||||
}
|
||||
}
|
||||
|
||||
static int getopt(int argc, char *argv[], char const *optstring) {
|
||||
@@ -35,8 +36,9 @@ static int getopt(int argc, char *argv[], char const *optstring) {
|
||||
musl_optind = 1;
|
||||
}
|
||||
|
||||
if (musl_optind >= argc || !argv[musl_optind])
|
||||
if (musl_optind >= argc || !argv[musl_optind]) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (argv[musl_optind][0] != '-') {
|
||||
if (optstring[0] == '-') {
|
||||
@@ -46,18 +48,21 @@ static int getopt(int argc, char *argv[], char const *optstring) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!argv[musl_optind][1])
|
||||
if (!argv[musl_optind][1]) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (argv[musl_optind][1] == '-' && !argv[musl_optind][2])
|
||||
if (argv[musl_optind][1] == '-' && !argv[musl_optind][2]) {
|
||||
return musl_optind++, -1;
|
||||
}
|
||||
|
||||
if (!musl_optpos)
|
||||
if (!musl_optpos) {
|
||||
musl_optpos++;
|
||||
}
|
||||
k = mbtowc(&c, argv[musl_optind] + musl_optpos, MB_LEN_MAX);
|
||||
if (k < 0) {
|
||||
k = 1;
|
||||
c = 0xFFFD; /* replacement char */
|
||||
c = 0xFFFD; // replacement char
|
||||
}
|
||||
optchar = argv[musl_optind] + musl_optpos;
|
||||
musl_optpos += k;
|
||||
@@ -67,23 +72,26 @@ static int getopt(int argc, char *argv[], char const *optstring) {
|
||||
musl_optpos = 0;
|
||||
}
|
||||
|
||||
if (optstring[0] == '-' || optstring[0] == '+')
|
||||
if (optstring[0] == '-' || optstring[0] == '+') {
|
||||
optstring++;
|
||||
}
|
||||
|
||||
i = 0;
|
||||
d = 0;
|
||||
do {
|
||||
l = mbtowc(&d, optstring + i, MB_LEN_MAX);
|
||||
if (l > 0)
|
||||
if (l > 0) {
|
||||
i += l;
|
||||
else
|
||||
} else {
|
||||
i++;
|
||||
}
|
||||
} while (l && d != c);
|
||||
|
||||
if (d != c || c == ':') {
|
||||
musl_optopt = c;
|
||||
if (optstring[0] != ':' && musl_opterr)
|
||||
if (optstring[0] != ':' && musl_opterr) {
|
||||
musl_getopt_msg(argv[0], ": unrecognized option: ", optchar, k);
|
||||
}
|
||||
return '?';
|
||||
}
|
||||
if (optstring[i] == ':') {
|
||||
@@ -94,10 +102,12 @@ static int getopt(int argc, char *argv[], char const *optstring) {
|
||||
}
|
||||
if (musl_optind > argc) {
|
||||
musl_optopt = c;
|
||||
if (optstring[0] == ':')
|
||||
if (optstring[0] == ':') {
|
||||
return ':';
|
||||
if (musl_opterr)
|
||||
}
|
||||
if (musl_opterr) {
|
||||
musl_getopt_msg(argv[0], ": option requires an argument: ", optchar, k);
|
||||
}
|
||||
return '?';
|
||||
}
|
||||
}
|
||||
@@ -108,8 +118,9 @@ static void permute(char **argv, int dest, int src) {
|
||||
char *tmp = argv[src];
|
||||
int i;
|
||||
|
||||
for (i = src; i > dest; i--)
|
||||
for (i = src; i > dest; i--) {
|
||||
argv[i] = argv[i - 1];
|
||||
}
|
||||
argv[dest] = tmp;
|
||||
}
|
||||
|
||||
@@ -128,17 +139,20 @@ static int musl_getopt_long(
|
||||
musl_optind = 1;
|
||||
}
|
||||
|
||||
if (musl_optind >= argc || !argv[musl_optind])
|
||||
if (musl_optind >= argc || !argv[musl_optind]) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
skipped = musl_optind;
|
||||
if (optstring[0] != '+' && optstring[0] != '-') {
|
||||
int i;
|
||||
for (i = musl_optind;; i++) {
|
||||
if (i >= argc || !argv[i])
|
||||
if (i >= argc || !argv[i]) {
|
||||
return -1;
|
||||
if (argv[i][0] == '-' && argv[i][1])
|
||||
}
|
||||
if (argv[i][0] == '-' && argv[i][1]) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
musl_optind = i;
|
||||
}
|
||||
@@ -147,8 +161,9 @@ static int musl_getopt_long(
|
||||
if (resumed > skipped) {
|
||||
int i, cnt = musl_optind - resumed;
|
||||
|
||||
for (i = 0; i < cnt; i++)
|
||||
for (i = 0; i < cnt; i++) {
|
||||
permute(argv, skipped, musl_optind - 1);
|
||||
}
|
||||
musl_optind = skipped + cnt;
|
||||
}
|
||||
return ret;
|
||||
@@ -169,14 +184,16 @@ static int musl_getopt_long_core(
|
||||
char const *name = longopts[i].name;
|
||||
|
||||
opt = start;
|
||||
if (*opt == '-')
|
||||
if (*opt == '-') {
|
||||
opt++;
|
||||
}
|
||||
while (*opt && *opt != '=' && *opt == *name) {
|
||||
name++;
|
||||
opt++;
|
||||
}
|
||||
if (*opt && *opt != '=')
|
||||
if (*opt && *opt != '=') {
|
||||
continue;
|
||||
}
|
||||
arg = opt;
|
||||
match = i;
|
||||
if (!*name) {
|
||||
@@ -191,8 +208,9 @@ static int musl_getopt_long_core(
|
||||
for (i = 0; optstring[i]; i++) {
|
||||
int j = 0;
|
||||
|
||||
while (j < l && start[j] == optstring[i + j])
|
||||
while (j < l && start[j] == optstring[i + j]) {
|
||||
j++;
|
||||
}
|
||||
if (j == l) {
|
||||
cnt++;
|
||||
break;
|
||||
@@ -206,8 +224,9 @@ static int musl_getopt_long_core(
|
||||
if (*opt == '=') {
|
||||
if (!longopts[i].has_arg) {
|
||||
musl_optopt = longopts[i].val;
|
||||
if (colon || !musl_opterr)
|
||||
if (colon || !musl_opterr) {
|
||||
return '?';
|
||||
}
|
||||
musl_getopt_msg(
|
||||
argv[0],
|
||||
": option does not take an argument: ",
|
||||
@@ -221,10 +240,12 @@ static int musl_getopt_long_core(
|
||||
musl_optarg = argv[musl_optind];
|
||||
if (!musl_optarg) {
|
||||
musl_optopt = longopts[i].val;
|
||||
if (colon)
|
||||
if (colon) {
|
||||
return ':';
|
||||
if (!musl_opterr)
|
||||
}
|
||||
if (!musl_opterr) {
|
||||
return '?';
|
||||
}
|
||||
musl_getopt_msg(
|
||||
argv[0],
|
||||
": option requires an argument: ",
|
||||
@@ -235,8 +256,9 @@ static int musl_getopt_long_core(
|
||||
}
|
||||
musl_optind++;
|
||||
}
|
||||
if (idx)
|
||||
if (idx) {
|
||||
*idx = i;
|
||||
}
|
||||
if (longopts[i].flag) {
|
||||
*longopts[i].flag = longopts[i].val;
|
||||
return 0;
|
||||
@@ -245,13 +267,14 @@ static int musl_getopt_long_core(
|
||||
}
|
||||
if (argv[musl_optind][1] == '-') {
|
||||
musl_optopt = 0;
|
||||
if (!colon && musl_opterr)
|
||||
if (!colon && musl_opterr) {
|
||||
musl_getopt_msg(
|
||||
argv[0],
|
||||
cnt ? ": option is ambiguous: " : ": unrecognized option: ",
|
||||
argv[musl_optind] + 2,
|
||||
strlen(argv[musl_optind] + 2)
|
||||
);
|
||||
}
|
||||
musl_optind++;
|
||||
return '?';
|
||||
}
|
||||
|
||||
54
src/extern/utf8decoder.cpp
vendored
54
src/extern/utf8decoder.cpp
vendored
@@ -1,35 +1,35 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
/* UTF-8 decoder: http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ */
|
||||
// UTF-8 decoder: http://bjoern.hoehrmann.de/utf-8/decoder/dfa/
|
||||
|
||||
#include "extern/utf8decoder.hpp"
|
||||
|
||||
static uint8_t const utf8d[] = {
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 00..0f */
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 10..1f */
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 20..2f */
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 30..3f */
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 40..4f */
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 50..5f */
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 60..6f */
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 70..7f */
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 80..8f */
|
||||
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, /* 90..9f */
|
||||
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, /* a0..af */
|
||||
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, /* b0..bf */
|
||||
8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* c0..cf */
|
||||
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* d0..df */
|
||||
10, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 3, /* e0..ef */
|
||||
11, 6, 6, 6, 5, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, /* f0..ff */
|
||||
0, 1, 2, 3, 5, 8, 7, 1, 1, 1, 4, 6, 1, 1, 1, 1, /* s0 */
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* s1 */
|
||||
1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, /* s1 */
|
||||
1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, /* s3 */
|
||||
1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, /* s4 */
|
||||
1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, /* s5 */
|
||||
1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, /* s6 */
|
||||
1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, /* s7 */
|
||||
1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* s8 */
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 00..0f
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 10..1f
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20..2f
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 30..3f
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 40..4f
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 50..5f
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 60..6f
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 70..7f
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 80..8f
|
||||
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, // 90..9f
|
||||
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // a0..af
|
||||
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // b0..bf
|
||||
8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // c0..cf
|
||||
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // d0..df
|
||||
10, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 3, // e0..ef
|
||||
11, 6, 6, 6, 5, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, // f0..ff
|
||||
0, 1, 2, 3, 5, 8, 7, 1, 1, 1, 4, 6, 1, 1, 1, 1, // s0
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // s1
|
||||
1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, // s1
|
||||
1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, // s3
|
||||
1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, // s4
|
||||
1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, // s5
|
||||
1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, // s6
|
||||
1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, // s7
|
||||
1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // s8
|
||||
};
|
||||
|
||||
uint32_t decode(uint32_t *state, uint32_t *codep, uint8_t byte) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user