Compare commits

..

62 Commits

Author SHA1 Message Date
Rangi42
81ea4ee920 Release 0.9.1 2025-02-02 20:16:54 +01:00
Rangi
29ece2940d Mention ASMotor's continued development (#1643) 2025-02-01 21:39:19 +01:00
Rangi
03452c6d4f Allow git describe to get the version for FreeBSD and Cygwin in CI (#1640)
* Specify `safe.directory`
* Fetch tags
* Fetch all commits
2025-01-29 19:57:15 -05:00
Rangi
b35e9d86fb Remove redundant @-style doc comment tags (#1641) 2025-01-29 19:56:28 -05:00
Rangi
e20347e38c Add more RGBLINK tests (#1639) 2025-01-29 12:53:44 -05:00
Rangi
f61019dd68 Add more RGBLINK test coverage (#1637) 2025-01-29 11:41:08 -05:00
Rangi
c19ddc80f0 Fix failing assertion with backslash at EOF in macro arguments (#1634)
`Expansion::advance()` can increase its offset beyond the size,
so I don't think this assumption was valid in the first place;
`BufferedContent::advance()` should be able to as well.
2025-01-28 21:51:11 -05:00
Rangi
a59867cd78 Consistently use LF line endings in expected .out and .err output (#1635)
Test scripts compare files as text
2025-01-28 21:24:40 -05:00
Rangi
375adc6804 Fix STRLEN and STRSUB on incomplete UTF-8 (#1633) 2025-01-28 13:13:35 -05:00
Rangi
44caffe04a Fix CHARLEN and CHARSUB on invalid UTF-8 (#1630) 2025-01-28 02:01:18 -05:00
Rangi42
d54619a453 Remove colNo column tracking from lexer
This was added as part of 71f88717 just for debug and fstack trace
output, but we no longer output it anyway.
2025-01-28 01:12:18 -05:00
Rangi42
e49291b7cf Refactor readUTF8Char into charmap_ConvertNext 2025-01-28 00:07:08 -05:00
Rangi42
34a9c8e083 Add some more string test cases 2025-01-28 00:02:25 -05:00
Rangi42
c4b456b166 Remove unused fix_PrecisionFactor function 2025-01-27 23:04:11 -05:00
Rangi42
79401cce8b Add braces inside #define macro bodies 2025-01-27 20:12:12 -05:00
Rangi42
4e44958d26 Add braces to Bison .y files 2025-01-27 20:12:12 -05:00
Rangi42
cae31005f8 Always use braces with InsertBraces: true in .clang-format 2025-01-27 20:12:12 -05:00
Rangi42
25c9f8f383 Add more rules to .clang-format 2025-01-27 20:12:12 -05:00
Rangi42
01c9106b59 Include windows.h before other Win32 header files 2025-01-27 20:12:12 -05:00
Rangi42
192fc808c8 Run clang-format on some Bison .y file contents 2025-01-27 20:12:12 -05:00
Rangi42
9c8e327ae2 Zero-initialize trimmedTile array 2025-01-27 20:12:12 -05:00
Rangi42
9ebd2a7e8e Fix clang-format of sectionTypeInfo array 2025-01-27 20:12:12 -05:00
Rangi42
b8b60207f5 Use // line comments not /* block comments 2025-01-27 20:12:12 -05:00
Rangi42
c5e59f40fd Get rid of unnecessary extern "C" blocks 2025-01-27 20:12:12 -05:00
Rangi42
a354af3d08 Reformat source files with clang-format 19.1.7 2025-01-27 20:12:12 -05:00
Rangi
20c18256ed Avoid errors after missing INCLUDE with -MG (#1627) 2025-01-25 12:38:17 -05:00
Rangi42
890528812e Prefer C++ constructs to C-style sizeof-based macros 2025-01-24 18:56:41 -05:00
Rangi42
84f59e14ed Rename Z80 prefix to SM83 2025-01-24 12:11:46 -05:00
Rangi42
91d7ce5e09 Add test case for expansions changing context 2025-01-21 21:47:22 -05:00
Rangi
d9654b752f Support -h/--help for all programs (#1620) 2025-01-21 21:24:17 -05:00
Rangi42
157826bf82 Support fetch-test-deps.sh --get-deps debian
Also use `apt-get` instead of `pip` to install
Pillow for libbet
2025-01-21 14:40:09 -05:00
Rangi42
a5e36f924f Update help and error messages in run-tests.sh 2025-01-21 14:10:36 -05:00
robbi-blechdose
82f7bdb480 Allow running external tests against installed rgbds (#1621) 2025-01-21 13:43:31 -05:00
Rangi42
056190413e Add test to cover RGBLINK behavior for patch overflow 2025-01-20 14:42:58 -05:00
Rangi
c2db23aef0 Run internal tests in FreeBSD (#1616) 2025-01-20 14:08:48 -05:00
Rangi
2426068409 Undeprecate ld [$ff00+c] (#1619) 2025-01-20 14:05:15 -05:00
Rangi
147a5c9bf3 Document more obsolete syntax (#1618) 2025-01-18 23:50:20 -05:00
Rangi
6ae3f040b8 Correct the DAA documentation (#1617) 2025-01-17 23:04:03 -05:00
Rangi
e561f63db3 Run internal tests in Cygwin (#1592) 2025-01-17 18:31:37 -05:00
Rangi
af9de812ec Update libpng to 1.6.45 (#1615) 2025-01-17 14:41:38 -05:00
Rangi42
edc9e07a2d Move all common error checks together inside mergeSections 2025-01-17 02:18:40 -05:00
Rangi
382ad17969 Don't output sections in reverse order (#1613) 2025-01-17 01:28:17 -05:00
Rangi42
fac5e35d24 Prefer empty braces to semicolons for empty loop bodies 2025-01-17 00:20:33 -05:00
Rangi42
a85d6b3b57 Remove unused readMagic function 2025-01-17 00:09:47 -05:00
Rangi42
f23a14afc7 Remove unnecessary semicolons after closing braces 2025-01-17 00:01:06 -05:00
Rangi42
f63167dd0f Use const reference 2025-01-16 23:45:43 -05:00
Rangi42
0ee4ba95b3 Replace old-style cast in Windows-only code with static_cast 2025-01-16 23:41:12 -05:00
Rangi
727c1f5b50 Update Dockerfile to use Debian 12 slim (#1599) 2025-01-04 14:54:30 -05:00
Rangi
d829fd2ffe Remove the 99999 macro arg limit (#1597) 2025-01-04 04:04:12 -05:00
Rangi
b13c0f2f8e Use a constant for 0x8001 (#1596) 2025-01-04 04:03:40 -05:00
Rangi42
d9773424e4 RGBDS_OBJECT_VERSION_STRING is a literal 2025-01-04 03:53:59 -05:00
Rangi42
4e2464a69d Replace some #define with constexpr 2025-01-04 03:53:59 -05:00
Rangi42
a5f12f66bb Define the default -recursion depth in main.cpp with other default values 2025-01-04 03:53:59 -05:00
Rangi
73ad431b8d Fix the node type for "file" nodes in object files (#1593) 2025-01-03 17:20:06 +01:00
Rangi42
d88feee1c0 Update test dependencies 2024-12-31 13:07:46 -05:00
Rangi42
5963dc9e0e Only define __asan_default_options in make develop builds
`NDEBUG` is not defined in `develop`, `debug`, `profile`, and `coverage`
builds.
`__SANITIZE_ADDRESS__` is defined in `develop` builds.
2024-12-31 11:01:26 -05:00
Rangi
8363f25d47 Enable more sanitizers in make develop (#1588)
- `-fsanitize=undefined` encompasses multiple checks we were specifying

- "detect_leaks=1" for `__asan_default_options` checks for memory leaks
  (except for with macOS clang++, which does not support LSan)

- `-fsanitize=float-divide-by-zero` is an extra UBSan check
  (and reveals a UB bug to fix with fixed-point `DIV` and `LOG`)
2024-12-31 10:02:20 +01:00
Rangi
72b2a4d7c0 Use if constexpr to guarantee compile-time simplification (#1590) 2024-12-30 23:44:12 -05:00
Rangi
06daf2a9b5 Include <signal.h> in rgbgfx_test.cpp (#1589) 2024-12-30 23:22:14 +01:00
Rangi
ad95d2e06f Allow deduplicating tiles with neither an input nor output tileset (#1585) 2024-12-30 18:58:07 +01:00
Rangi
5197e6b79f Run gcc static analysis in CI (#1587) 2024-12-30 09:57:41 -05:00
Rangi42
b99ce3845e Fix RGBFIX writing bytes when one syscall is not sufficient 2024-12-30 11:25:20 +01:00
194 changed files with 3124 additions and 2353 deletions

View File

@@ -24,6 +24,7 @@ AttributeMacros:
BinPackArguments: false BinPackArguments: false
BinPackParameters: false BinPackParameters: false
BitFieldColonSpacing: Both BitFieldColonSpacing: Both
BreakAfterAttributes: Always
BreakBeforeBinaryOperators: NonAssignment BreakBeforeBinaryOperators: NonAssignment
BreakBeforeBraces: Attach BreakBeforeBraces: Attach
BreakBeforeConceptDeclarations: true BreakBeforeConceptDeclarations: true
@@ -54,12 +55,14 @@ IncludeCategories:
IndentAccessModifiers: false IndentAccessModifiers: false
IndentCaseBlocks: false IndentCaseBlocks: false
IndentCaseLabels: false IndentCaseLabels: false
IndentExternBlock: NoIndent IndentExternBlock: Indent
IndentGotoLabels: false IndentGotoLabels: false
IndentPPDirectives: BeforeHash IndentPPDirectives: BeforeHash
IndentRequires: true IndentRequires: true
IndentWidth: 4 IndentWidth: 4
IndentWrappedFunctionNames: true IndentWrappedFunctionNames: true
InsertBraces: true
InsertNewlineAtEOF: true
# Only support for Javascript as of clang-format 14... # Only support for Javascript as of clang-format 14...
# InsertTrailingCommas: Wrapped # InsertTrailingCommas: Wrapped
KeepEmptyLinesAtTheStartOfBlocks: false KeepEmptyLinesAtTheStartOfBlocks: false
@@ -71,6 +74,8 @@ PPIndentWidth: -1
PointerAlignment: Right PointerAlignment: Right
QualifierAlignment: Right QualifierAlignment: Right
ReflowComments: true ReflowComments: true
RemoveParentheses: ReturnStatement
RemoveSemicolon: true
SortIncludes: CaseSensitive SortIncludes: CaseSensitive
SortUsingDeclarations: true SortUsingDeclarations: true
SpaceAfterCStyleCast: false SpaceAfterCStyleCast: false

View File

@@ -1,13 +1,13 @@
#!/bin/bash #!/bin/bash
set -euo pipefail set -euo pipefail
pngver=1.6.43 pngver=1.6.45
## Grab sources and check them ## Grab sources and check them
curl -LOJ "http://prdownloads.sourceforge.net/libpng/libpng-$pngver.tar.xz?download" curl -LOJ "http://prdownloads.sourceforge.net/libpng/libpng-$pngver.tar.xz?download"
# Brew doesn't provide any sha256sum, so we're making do with `sha2` instead. # Brew doesn't provide any sha256sum, so we're making do with `sha2` instead.
if [ "$(sha2 -q -256 libpng-$pngver.tar.xz)" != 6a5ca0652392a2d7c9db2ae5b40210843c0bbc081cbd410825ab00cc59f14a6c ]; then if [ "$(sha2 -q -256 libpng-$pngver.tar.xz)" != 926485350139ffb51ef69760db35f78846c805fef3d59bfdcb2fba704663f370 ]; then
sha2 -256 libpng-$pngver.tar.xz sha2 -256 libpng-$pngver.tar.xz
echo Checksum mismatch! Aborting. >&2 echo Checksum mismatch! Aborting. >&2
exit 1 exit 1

View File

@@ -16,8 +16,8 @@ function getlibrary ([string] $URI, [string] $filename, [string] $hash, [string]
} }
getlibrary 'https://www.zlib.net/zlib131.zip' 'zlib.zip' '72af66d44fcc14c22013b46b814d5d2514673dda3d115e64b690c1ad636e7b17' . getlibrary 'https://www.zlib.net/zlib131.zip' 'zlib.zip' '72af66d44fcc14c22013b46b814d5d2514673dda3d115e64b690c1ad636e7b17' .
getlibrary 'https://github.com/glennrp/libpng/archive/refs/tags/v1.6.43.zip' 'libpng.zip' '5e18474a26814ae479e02ca6432da32d19dc6e615551d140c954a68d63b3f192' . getlibrary 'https://github.com/glennrp/libpng/archive/refs/tags/v1.6.45.zip' 'libpng.zip' '1b3d94b2f1d137db1bf1842cb9f03df179772a517f7b86e26351742190632785' .
getlibrary 'https://github.com/lexxmark/winflexbison/releases/download/v2.5.25/win_flex_bison-2.5.25.zip' 'winflexbison.zip' '8d324b62be33604b2c45ad1dd34ab93d722534448f55a16ca7292de32b6ac135' install_dir getlibrary 'https://github.com/lexxmark/winflexbison/releases/download/v2.5.25/win_flex_bison-2.5.25.zip' 'winflexbison.zip' '8d324b62be33604b2c45ad1dd34ab93d722534448f55a16ca7292de32b6ac135' install_dir
Move-Item zlib-1.3.1 zlib Move-Item zlib-1.3.1 zlib
Move-Item libpng-1.6.43 libpng Move-Item libpng-1.6.45 libpng

View File

@@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
set -euo pipefail set -euo pipefail
pngver=1.6.43 pngver=1.6.45
arch="$1" arch="$1"
## Grab sources and check them ## Grab sources and check them

View File

@@ -1,2 +1,2 @@
d6bd2a3f43f17020918a4c1bd81c1a78111b6f759af9c1d3c754f704a1bf0429 libpng-1.6.43-apng.patch.gz f7caa3b55f003ce23d6a087b1c2a643262647bfd1a1b31afdc9b18eabf1bbc7e libpng-1.6.45-apng.patch.gz
6a5ca0652392a2d7c9db2ae5b40210843c0bbc081cbd410825ab00cc59f14a6c libpng-1.6.43.tar.xz 926485350139ffb51ef69760db35f78846c805fef3d59bfdcb2fba704663f370 libpng-1.6.45.tar.xz

18
.github/workflows/analysis.yml vendored Normal file
View 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=

View File

@@ -320,3 +320,69 @@ jobs:
shell: bash shell: bash
run: | run: |
test/run-tests.sh test/run-tests.sh
cygwin:
strategy:
matrix:
bits: [32, 64]
include:
- bits: 32
arch: x86
- bits: 64
arch: x86_64
fail-fast: false
runs-on: windows-2019
timeout-minutes: 30
steps:
- name: Checkout repo
uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-tags: true
- name: Setup Cygwin
uses: cygwin/cygwin-install-action@v4
with:
platform: ${{ matrix.arch }}
packages: >-
bison
gcc-g++
git
libpng-devel
make
pkg-config
- name: Build & install using Make
shell: C:\cygwin\bin\env.exe CYGWIN_NOWINPATH=1 CHERE_INVOKING=1 C:\cygwin\bin\bash.exe -o igncr '{0}'
run: | # Cygwin does not support `make develop` sanitizers ASan or UBSan
make -kj Q=
make install -j Q=
- name: Run tests
shell: C:\cygwin\bin\env.exe CYGWIN_NOWINPATH=1 CHERE_INVOKING=1 C:\cygwin\bin\bash.exe -o igncr '{0}'
run: |
test/run-tests.sh --only-internal
freebsd:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout repo
uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-tags: true
- name: Build & test using CMake on FreeBSD
uses: vmactions/freebsd-vm@v1
with:
release: "15.0"
usesh: true
prepare: |
pkg install -y \
bash \
bison \
cmake \
git \
png
run: | # FreeBSD `c++` compiler does not support `make develop` sanitizers ASan or UBSan
cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_COMPILER=c++ -DUSE_EXTERNAL_TESTS=OFF
cmake --build build -j4 --verbose
cmake --install build --verbose
cmake --build build --target test

View File

@@ -45,11 +45,8 @@ else()
add_compile_options(-Wno-gnu-zero-variadic-macro-arguments) add_compile_options(-Wno-gnu-zero-variadic-macro-arguments)
endif() endif()
if(SANITIZERS) if(SANITIZERS)
set(SAN_FLAGS -fsanitize=shift -fsanitize=integer-divide-by-zero set(SAN_FLAGS -fsanitize=address -fsanitize=undefined
-fsanitize=unreachable -fsanitize=vla-bound -fsanitize=float-divide-by-zero)
-fsanitize=signed-integer-overflow -fsanitize=bounds
-fsanitize=object-size -fsanitize=bool -fsanitize=enum
-fsanitize=alignment -fsanitize=null -fsanitize=address)
add_compile_options(${SAN_FLAGS}) add_compile_options(${SAN_FLAGS})
add_link_options(${SAN_FLAGS}) add_link_options(${SAN_FLAGS})
add_definitions(-D_GLIBCXX_ASSERTIONS) add_definitions(-D_GLIBCXX_ASSERTIONS)
@@ -78,7 +75,7 @@ endif()
find_program(GIT git) find_program(GIT git)
if(GIT) if(GIT)
execute_process(COMMAND ${GIT} --git-dir=.git describe --tags --dirty --always execute_process(COMMAND ${GIT} --git-dir=.git -c safe.directory='*' describe --tags --dirty --always
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
OUTPUT_VARIABLE GIT_REV OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE GIT_REV OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_QUIET) ERROR_QUIET)

View File

@@ -1,6 +1,6 @@
FROM debian:11-slim FROM debian:12-slim
LABEL org.opencontainers.image.source=https://github.com/gbdev/rgbds LABEL org.opencontainers.image.source=https://github.com/gbdev/rgbds
ARG version=0.9.0 ARG version=0.9.1
WORKDIR /rgbds WORKDIR /rgbds
COPY . . COPY . .
@@ -8,7 +8,7 @@ COPY . .
RUN apt-get update && \ RUN apt-get update && \
apt-get install sudo make cmake gcc build-essential -y apt-get install sudo make cmake gcc build-essential -y
RUN ./.github/scripts/install_deps.sh ubuntu-20.04 RUN ./.github/scripts/install_deps.sh ubuntu-22.04
RUN make -j CXXFLAGS="-O3 -flto -DNDEBUG -static" PKG_CONFIG="pkg-config --static" Q= RUN make -j CXXFLAGS="-O3 -flto -DNDEBUG -static" PKG_CONFIG="pkg-config --static" Q=
RUN tar caf rgbds-${version}-linux-x86_64.tar.xz --transform='s#.*/##' rgbasm rgblink rgbfix rgbgfx man/* .github/scripts/install.sh RUN tar caf rgbds-${version}-linux-x86_64.tar.xz --transform='s#.*/##' rgbasm rgblink rgbfix rgbgfx man/* .github/scripts/install.sh

View File

@@ -24,7 +24,7 @@ PNGLDFLAGS := `${PKG_CONFIG} --libs-only-L libpng`
PNGLDLIBS := `${PKG_CONFIG} --libs-only-l libpng` PNGLDLIBS := `${PKG_CONFIG} --libs-only-l libpng`
# Note: if this comes up empty, `version.cpp` will automatically fall back to last release number # Note: if this comes up empty, `version.cpp` will automatically fall back to last release number
VERSION_STRING := `git --git-dir=.git describe --tags --dirty --always 2>/dev/null` VERSION_STRING := `git --git-dir=.git -c safe.directory='*' describe --tags --dirty --always 2>/dev/null`
WARNFLAGS := -Wall -pedantic -Wno-unknown-warning-option -Wno-gnu-zero-variadic-macro-arguments WARNFLAGS := -Wall -pedantic -Wno-unknown-warning-option -Wno-gnu-zero-variadic-macro-arguments
@@ -206,12 +206,8 @@ develop:
-Wformat=2 -Wformat-overflow=2 -Wformat-truncation=1 \ -Wformat=2 -Wformat-overflow=2 -Wformat-truncation=1 \
-Wno-format-nonliteral -Wno-strict-overflow -Wno-unused-but-set-variable \ -Wno-format-nonliteral -Wno-strict-overflow -Wno-unused-but-set-variable \
-Wno-type-limits -Wno-tautological-constant-out-of-range-compare -Wvla \ -Wno-type-limits -Wno-tautological-constant-out-of-range-compare -Wvla \
-D_GLIBCXX_ASSERTIONS \ -D_GLIBCXX_ASSERTIONS -fsanitize=address -fsanitize=undefined \
-fsanitize=shift -fsanitize=integer-divide-by-zero \ -fsanitize=float-divide-by-zero" \
-fsanitize=unreachable -fsanitize=vla-bound \
-fsanitize=signed-integer-overflow -fsanitize=bounds \
-fsanitize=object-size -fsanitize=bool -fsanitize=enum \
-fsanitize=alignment -fsanitize=null -fsanitize=address" \
CXXFLAGS="-ggdb3 -Og -fno-omit-frame-pointer -fno-optimize-sibling-calls" CXXFLAGS="-ggdb3 -Og -fno-omit-frame-pointer -fno-optimize-sibling-calls"
# This target is used during development in order to more easily debug with gdb. # This target is used during development in order to more easily debug with gdb.
@@ -269,4 +265,4 @@ wine-shim:
dist: dist:
$Qgit ls-files | sed s~^~$${PWD##*/}/~ \ $Qgit ls-files | sed s~^~$${PWD##*/}/~ \
| tar -czf rgbds-`git describe --tags | cut -c 2-`.tar.gz -C .. -T - | tar -czf rgbds-`git -c safe.directory='*' describe --tags | cut -c 2-`.tar.gz -C .. -T -

View File

@@ -137,9 +137,12 @@ The RGBDS source code file structure is as follows:
this version as [rgbds-linux](https://github.com/vegard/rgbds-linux). this version as [rgbds-linux](https://github.com/vegard/rgbds-linux).
- 2010-01-12: Anthony J. Bentley [forks](https://github.com/bentley) Nossum's - 2010-01-12: Anthony J. Bentley [forks](https://github.com/bentley) Nossum's
repository. The fork becomes the reference implementation of RGBDS. repository. The fork becomes the reference implementation of RGBDS.
- 2010-09-25: Sørensen continues development of
[ASMotor](https://github.com/asmotor/asmotor) to this day.
- 2015-01-18: stag019 begins implementing [RGBGFX](https://github.com/stag019/rgbgfx), - 2015-01-18: stag019 begins implementing [RGBGFX](https://github.com/stag019/rgbgfx),
a PNGtoGame Boy graphics converter, for eventual integration into RGBDS. a PNGtoGame Boy graphics converter, for eventual integration into RGBDS.
- 2016-09-05: RGBGFX is [integrated](https://github.com/gbdev/rgbds/commit/c3c31138ddbd8680d4e67957e387f2816798a71b) - 2016-09-05: RGBGFX is
[integrated](https://github.com/gbdev/rgbds/commit/c3c31138ddbd8680d4e67957e387f2816798a71b)
into Bentley's repository. into Bentley's repository.
- 2017-02-23: Bentley's repository is moved to the [rednex](https://github.com/rednex) - 2017-02-23: Bentley's repository is moved to the [rednex](https://github.com/rednex)
organization. organization.

View File

@@ -25,6 +25,7 @@ _rgbasm_completions() {
# See the `state` variable below for info about `state_after` # See the `state` variable below for info about `state_after`
declare -A opts=( declare -A opts=(
[V]="version:normal" [V]="version:normal"
[h]="help:normal"
[E]="export-all:normal" [E]="export-all:normal"
[v]="verbose:normal" [v]="verbose:normal"
[w]=":normal" [w]=":normal"

View File

@@ -8,6 +8,7 @@ _rgbfix_completions() {
# See the `state` variable below for info about `state_after` # See the `state` variable below for info about `state_after`
declare -A opts=( declare -A opts=(
[V]="version:normal" [V]="version:normal"
[h]="help:normal"
[j]="non-japanese:normal" [j]="non-japanese:normal"
[s]="sgb-compatible:normal" [s]="sgb-compatible:normal"
[v]="validate:normal" [v]="validate:normal"

View File

@@ -8,6 +8,7 @@ _rgbgfx_completions() {
# See the `state` variable below for info about `state_after` # See the `state` variable below for info about `state_after`
declare -A opts=( declare -A opts=(
[V]="version:normal" [V]="version:normal"
[h]="help:normal"
[C]="color-curve:normal" [C]="color-curve:normal"
[m]="mirror-tiles:normal" [m]="mirror-tiles:normal"
[O]="group-outputs:normal" [O]="group-outputs:normal"

View File

@@ -8,6 +8,7 @@ _rgblink_completions() {
# See the `state` variable below for info about `state_after` # See the `state` variable below for info about `state_after`
declare -A opts=( declare -A opts=(
[V]="version:normal" [V]="version:normal"
[h]="help:normal"
[d]="dmg:normal" [d]="dmg:normal"
[t]="tiny:normal" [t]="tiny:normal"
[v]="verbose:normal" [v]="verbose:normal"

View File

@@ -36,8 +36,9 @@ _rgbasm_warnings() {
} }
local args=( local args=(
# Arguments are listed here in the same order as in the manual, except for the version # Arguments are listed here in the same order as in the manual, except for the version and help
'(- : * options)'{-V,--version}'[Print version number]' '(- : * options)'{-V,--version}'[Print version number and exit]'
'(- : * options)'{-h,--help}'[Print help text and exit]'
'(-E --export-all)'{-E,--export-all}'[Export all symbols]' '(-E --export-all)'{-E,--export-all}'[Export all symbols]'
'(-v --verbose)'{-v,--verbose}'[Print additional messages regarding progression]' '(-v --verbose)'{-v,--verbose}'[Print additional messages regarding progression]'

View File

@@ -35,8 +35,9 @@ _mbc_names() {
} }
local args=( local args=(
# Arguments are listed here in the same order as in the manual, except for the version # Arguments are listed here in the same order as in the manual, except for the version and help
'(- : * options)'{-V,--version}'[Print version number]' '(- : * options)'{-V,--version}'[Print version number and exit]'
'(- : * options)'{-h,--help}'[Print help text and exit]'
'(-C --color-only -c --color-compatible)'{-C,--color-only}'[Mark ROM as GBC-only]' '(-C --color-only -c --color-compatible)'{-C,--color-only}'[Mark ROM as GBC-only]'
'(-C --color-only -c --color-compatible)'{-c,--color-compatible}'[Mark ROM as GBC-compatible]' '(-C --color-only -c --color-compatible)'{-c,--color-compatible}'[Mark ROM as GBC-compatible]'

View File

@@ -10,8 +10,9 @@ _depths() {
} }
local args=( local args=(
# Arguments are listed here in the same order as in the manual, except for the version # Arguments are listed here in the same order as in the manual, except for the version and help
'(- : * options)'{-V,--version}'[Print version number]' '(- : * options)'{-V,--version}'[Print version number and exit]'
'(- : * options)'{-h,--help}'[Print help text and exit]'
'(-a --attr-map -A --auto-attr-map)'{-A,--auto-attr-map}'[Shortcut for -a <file>.attrmap]' '(-a --attr-map -A --auto-attr-map)'{-A,--auto-attr-map}'[Shortcut for -a <file>.attrmap]'
'(-C --color-curve)'{-C,--color-curve}'[Generate palettes using GBC color curve]' '(-C --color-curve)'{-C,--color-curve}'[Generate palettes using GBC color curve]'

View File

@@ -1,8 +1,9 @@
#compdef rgblink #compdef rgblink
local args=( local args=(
# Arguments are listed here in the same order as in the manual, except for the version # Arguments are listed here in the same order as in the manual, except for the version and help
'(- : * options)'{-V,--version}'[Print version number]' '(- : * options)'{-V,--version}'[Print version number and exit]'
'(- : * options)'{-h,--help}'[Print help text and exit]'
'(-d --dmg)'{-d,--dmg}'[Enable DMG mode (-w + no VRAM banking)]' '(-d --dmg)'{-d,--dmg}'[Enable DMG mode (-w + no VRAM banking)]'
'(-t --tiny)'{-t,--tiny}'[Enable tiny mode, disabling ROM banking]' '(-t --tiny)'{-t,--tiny}'[Enable tiny mode, disabling ROM banking]'

View File

@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: MIT */ // SPDX-License-Identifier: MIT
#ifndef RGBDS_ASM_CHARMAP_HPP #ifndef RGBDS_ASM_CHARMAP_HPP
#define RGBDS_ASM_CHARMAP_HPP #define RGBDS_ASM_CHARMAP_HPP

View File

@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: MIT */ // SPDX-License-Identifier: MIT
#ifndef RGBDS_ASM_FIXPOINT_HPP #ifndef RGBDS_ASM_FIXPOINT_HPP
#define RGBDS_ASM_FIXPOINT_HPP #define RGBDS_ASM_FIXPOINT_HPP
@@ -8,7 +8,6 @@
extern uint8_t fixPrecision; extern uint8_t fixPrecision;
uint8_t fix_Precision(); uint8_t fix_Precision();
double fix_PrecisionFactor();
int32_t fix_Sin(int32_t i, int32_t q); int32_t fix_Sin(int32_t i, int32_t q);
int32_t fix_Cos(int32_t i, int32_t q); int32_t fix_Cos(int32_t i, int32_t q);
int32_t fix_Tan(int32_t i, int32_t q); int32_t fix_Tan(int32_t i, int32_t q);

View File

@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: MIT */ // SPDX-License-Identifier: MIT
#ifndef RGBDS_ASM_FORMAT_HPP #ifndef RGBDS_ASM_FORMAT_HPP
#define RGBDS_ASM_FORMAT_HPP #define RGBDS_ASM_FORMAT_HPP

View File

@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: MIT */ // SPDX-License-Identifier: MIT
// Contains some assembler-wide defines and externs // Contains some assembler-wide defines and externs
@@ -41,15 +41,11 @@ struct FileStackNode {
std::string const &name() const { return data.get<std::string>(); } std::string const &name() const { return data.get<std::string>(); }
FileStackNode(FileStackNodeType type_, Either<std::vector<uint32_t>, std::string> data_) FileStackNode(FileStackNodeType type_, Either<std::vector<uint32_t>, std::string> data_)
: type(type_), data(data_){}; : type(type_), data(data_) {}
std::string const &dump(uint32_t curLineNo) const; std::string const &dump(uint32_t curLineNo) const;
// If true, entering this context generates a new unique ID.
bool generatesUniqueID() const { return type == NODE_REPT || type == NODE_MACRO; }
}; };
#define DEFAULT_MAX_DEPTH 64
extern size_t maxRecursionDepth; extern size_t maxRecursionDepth;
struct MacroArgs; struct MacroArgs;

View File

@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: MIT */ // SPDX-License-Identifier: MIT
#ifndef RGBDS_ASM_LEXER_HPP #ifndef RGBDS_ASM_LEXER_HPP
#define RGBDS_ASM_LEXER_HPP #define RGBDS_ASM_LEXER_HPP
@@ -15,7 +15,7 @@
// This value is a compromise between `LexerState` allocation performance when `mmap` works, and // This value is a compromise between `LexerState` allocation performance when `mmap` works, and
// buffering performance when it doesn't/can't (e.g. when piping a file into RGBASM). // buffering performance when it doesn't/can't (e.g. when piping a file into RGBASM).
#define LEXER_BUF_SIZE 64 static constexpr size_t LEXER_BUF_SIZE = 64;
// The buffer needs to be large enough for the maximum `lexerState->peek()` lookahead distance // The buffer needs to be large enough for the maximum `lexerState->peek()` lookahead distance
static_assert(LEXER_BUF_SIZE > 1, "Lexer buffer size is too small"); static_assert(LEXER_BUF_SIZE > 1, "Lexer buffer size is too small");
// This caps the size of buffer reads, and according to POSIX, passing more than SSIZE_MAX is UB // This caps the size of buffer reads, and according to POSIX, passing more than SSIZE_MAX is UB
@@ -83,7 +83,6 @@ struct LexerState {
LexerMode mode; LexerMode mode;
bool atLineStart; bool atLineStart;
uint32_t lineNo; uint32_t lineNo;
uint32_t colNo;
int lastToken; int lastToken;
std::deque<IfStackEntry> ifStack; std::deque<IfStackEntry> ifStack;
@@ -147,7 +146,6 @@ void lexer_ReachELSEBlock();
void lexer_CheckRecursionDepth(); void lexer_CheckRecursionDepth();
uint32_t lexer_GetLineNo(); uint32_t lexer_GetLineNo();
uint32_t lexer_GetColNo();
void lexer_DumpStringExpansions(); void lexer_DumpStringExpansions();
struct Capture { struct Capture {

View File

@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: MIT */ // SPDX-License-Identifier: MIT
#ifndef RGBDS_ASM_MACRO_HPP #ifndef RGBDS_ASM_MACRO_HPP
#define RGBDS_ASM_MACRO_HPP #define RGBDS_ASM_MACRO_HPP

View File

@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: MIT */ // SPDX-License-Identifier: MIT
#ifndef RGBDS_ASM_MAIN_HPP #ifndef RGBDS_ASM_MAIN_HPP
#define RGBDS_ASM_MAIN_HPP #define RGBDS_ASM_MAIN_HPP

View File

@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: MIT */ // SPDX-License-Identifier: MIT
#ifndef RGBDS_ASM_OPT_HPP #ifndef RGBDS_ASM_OPT_HPP
#define RGBDS_ASM_OPT_HPP #define RGBDS_ASM_OPT_HPP

View File

@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: MIT */ // SPDX-License-Identifier: MIT
#ifndef RGBDS_ASM_OUTPUT_HPP #ifndef RGBDS_ASM_OUTPUT_HPP
#define RGBDS_ASM_OUTPUT_HPP #define RGBDS_ASM_OUTPUT_HPP
@@ -14,14 +14,7 @@
struct Expression; struct Expression;
struct FileStackNode; struct FileStackNode;
enum StateFeature { enum StateFeature { STATE_EQU, STATE_VAR, STATE_EQUS, STATE_CHAR, STATE_MACRO, NB_STATE_FEATURES };
STATE_EQU,
STATE_VAR,
STATE_EQUS,
STATE_CHAR,
STATE_MACRO,
NB_STATE_FEATURES
};
extern std::string objectFileName; extern std::string objectFileName;

View File

@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: MIT */ // SPDX-License-Identifier: MIT
#ifndef RGBDS_ASM_RPN_HPP #ifndef RGBDS_ASM_RPN_HPP
#define RGBDS_ASM_RPN_HPP #define RGBDS_ASM_RPN_HPP
@@ -31,12 +31,8 @@ struct Expression {
Expression &operator=(Expression &&) = default; Expression &operator=(Expression &&) = default;
bool isKnown() const { bool isKnown() const { return data.holds<int32_t>(); }
return data.holds<int32_t>(); int32_t value() const { return data.get<int32_t>(); }
}
int32_t value() const {
return data.get<int32_t>();
}
int32_t getConstVal() const; int32_t getConstVal() const;
Symbol const *symbolOf() const; Symbol const *symbolOf() const;

View File

@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: MIT */ // SPDX-License-Identifier: MIT
#ifndef RGBDS_ASM_SECTION_HPP #ifndef RGBDS_ASM_SECTION_HPP
#define RGBDS_ASM_SECTION_HPP #define RGBDS_ASM_SECTION_HPP

View File

@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: MIT */ // SPDX-License-Identifier: MIT
#ifndef RGBDS_ASM_SYMBOL_HPP #ifndef RGBDS_ASM_SYMBOL_HPP
#define RGBDS_ASM_SYMBOL_HPP #define RGBDS_ASM_SYMBOL_HPP

View File

@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: MIT */ // SPDX-License-Identifier: MIT
#ifndef RGBDS_ASM_WARNING_HPP #ifndef RGBDS_ASM_WARNING_HPP
#define RGBDS_ASM_WARNING_HPP #define RGBDS_ASM_WARNING_HPP
@@ -62,27 +62,24 @@ extern bool warningsAreErrors;
void processWarningFlag(char const *flag); void processWarningFlag(char const *flag);
/* // Used to warn the user about problems that don't prevent the generation of
* Used to warn the user about problems that don't prevent the generation of // valid code.
* valid code. [[gnu::format(printf, 2, 3)]]
*/ void warning(WarningID id, char const *fmt, ...);
[[gnu::format(printf, 2, 3)]] void warning(WarningID id, char const *fmt, ...);
/* // Used for errors that compromise the whole assembly process by affecting the
* Used for errors that compromise the whole assembly process by affecting the // following code, potencially making the assembler generate errors caused by
* following code, potencially making the assembler generate errors caused by // the first one and unrelated to the code that the assembler complains about.
* the first one and unrelated to the code that the assembler complains about. // It is also used when the assembler goes into an invalid state (for example,
* It is also used when the assembler goes into an invalid state (for example, // when it fails to allocate memory).
* when it fails to allocate memory). [[gnu::format(printf, 1, 2), noreturn]]
*/ void fatalerror(char const *fmt, ...);
[[gnu::format(printf, 1, 2), noreturn]] void fatalerror(char const *fmt, ...);
/* // Used for errors that make it impossible to assemble correctly, but don't
* Used for errors that make it impossible to assemble correctly, but don't // affect the following code. The code will fail to assemble but the user will
* affect the following code. The code will fail to assemble but the user will // get a list of all errors at the end, making it easier to fix all of them at
* get a list of all errors at the end, making it easier to fix all of them at // once.
* once. [[gnu::format(printf, 1, 2)]]
*/ void error(char const *fmt, ...);
[[gnu::format(printf, 1, 2)]] void error(char const *fmt, ...);
#endif // RGBDS_ASM_WARNING_HPP #endif // RGBDS_ASM_WARNING_HPP

View File

@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: MIT */ // SPDX-License-Identifier: MIT
#ifndef RGBDS_DEFAULT_INIT_ALLOC_HPP #ifndef RGBDS_DEFAULT_INIT_ALLOC_HPP
#define RGBDS_DEFAULT_INIT_ALLOC_HPP #define RGBDS_DEFAULT_INIT_ALLOC_HPP
@@ -6,14 +6,11 @@
#include <memory> #include <memory>
#include <vector> #include <vector>
/* // Allocator adaptor that interposes construct() calls to convert value-initialization
* Allocator adaptor that interposes construct() calls to convert value-initialization // (which is what you get with e.g. `vector::resize`) into default-initialization (which does not
* (which is what you get with e.g. `vector::resize`) into default-initialization (which does not // zero out non-class types).
* zero out non-class types). // From
* From // https://stackoverflow.com/questions/21028299/is-this-behavior-of-vectorresizesize-type-n-under-c11-and-boost-container/21028912#21028912
* https://stackoverflow.com/questions/21028299/is-this-behavior-of-vectorresizesize-type-n-under-c11-and-boost-container/21028912#21028912
*/
template<typename T, typename A = std::allocator<T>> template<typename T, typename A = std::allocator<T>>
class default_init_allocator : public A { class default_init_allocator : public A {
using a_t = std::allocator_traits<A>; using a_t = std::allocator_traits<A>;

View File

@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: MIT */ // SPDX-License-Identifier: MIT
#ifndef RGBDS_EITHER_HPP #ifndef RGBDS_EITHER_HPP
#define RGBDS_EITHER_HPP #define RGBDS_EITHER_HPP

View File

@@ -1,15 +1,18 @@
/* SPDX-License-Identifier: MIT */ // SPDX-License-Identifier: MIT
#ifndef RGBDS_ERROR_HPP #ifndef RGBDS_ERROR_HPP
#define RGBDS_ERROR_HPP #define RGBDS_ERROR_HPP
extern "C" { extern "C" {
[[gnu::format(printf, 1, 2)]]
void warn(char const *fmt...);
[[gnu::format(printf, 1, 2)]]
void warnx(char const *fmt, ...);
[[gnu::format(printf, 1, 2)]] void warn(char const *fmt...); [[gnu::format(printf, 1, 2), noreturn]]
[[gnu::format(printf, 1, 2)]] void warnx(char const *fmt, ...); void err(char const *fmt, ...);
[[gnu::format(printf, 1, 2), noreturn]]
[[gnu::format(printf, 1, 2), noreturn]] void err(char const *fmt, ...); void errx(char const *fmt, ...);
[[gnu::format(printf, 1, 2), noreturn]] void errx(char const *fmt, ...);
} }
#endif // RGBDS_ERROR_HPP #endif // RGBDS_ERROR_HPP

View File

@@ -1,11 +1,15 @@
/* SPDX-License-Identifier: MIT */ // SPDX-License-Identifier: MIT
/* This implementation was taken from musl and modified for RGBDS */ // This implementation was taken from musl and modified for RGBDS
#ifndef RGBDS_EXTERN_GETOPT_HPP #ifndef RGBDS_EXTERN_GETOPT_HPP
#define RGBDS_EXTERN_GETOPT_HPP #define RGBDS_EXTERN_GETOPT_HPP
extern "C" { // clang-format off: vertically align values
static constexpr int no_argument = 0;
static constexpr int required_argument = 1;
static constexpr int optional_argument = 2;
// clang-format on
extern char *musl_optarg; extern char *musl_optarg;
extern int musl_optind, musl_opterr, musl_optopt, musl_optreset; extern int musl_optind, musl_opterr, musl_optopt, musl_optreset;
@@ -21,10 +25,4 @@ int musl_getopt_long_only(
int argc, char **argv, char const *optstring, option const *longopts, int *idx int argc, char **argv, char const *optstring, option const *longopts, int *idx
); );
#define no_argument 0
#define required_argument 1
#define optional_argument 2
} // extern "C"
#endif // RGBDS_EXTERN_GETOPT_HPP #endif // RGBDS_EXTERN_GETOPT_HPP

View File

@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: MIT */ // SPDX-License-Identifier: MIT
#ifndef RGBDS_EXTERN_UTF8DECODER_HPP #ifndef RGBDS_EXTERN_UTF8DECODER_HPP
#define RGBDS_EXTERN_UTF8DECODER_HPP #define RGBDS_EXTERN_UTF8DECODER_HPP

View File

@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: MIT */ // SPDX-License-Identifier: MIT
#ifndef RGBDS_FILE_HPP #ifndef RGBDS_FILE_HPP
#define RGBDS_FILE_HPP #define RGBDS_FILE_HPP
@@ -25,10 +25,8 @@ public:
File() {} File() {}
~File() { close(); } ~File() { close(); }
/** // This should only be called once, and before doing any `->` operations.
* This should only be called once, and before doing any `->` operations. // Returns `nullptr` on error, and a non-null pointer otherwise.
* Returns `nullptr` on error, and a non-null pointer otherwise.
*/
File *open(std::string const &path, std::ios_base::openmode mode) { File *open(std::string const &path, std::ios_base::openmode mode) {
if (path != "-") { if (path != "-") {
_file.emplace<std::filebuf>(); _file.emplace<std::filebuf>();

View File

@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: MIT */ // SPDX-License-Identifier: MIT
#ifndef RGBDS_GFX_MAIN_HPP #ifndef RGBDS_GFX_MAIN_HPP
#define RGBDS_GFX_MAIN_HPP #define RGBDS_GFX_MAIN_HPP
@@ -48,6 +48,7 @@ struct Options {
std::string input{}; // positional arg std::string input{}; // positional arg
// clang-format off: vertically align values
static constexpr uint8_t VERB_NONE = 0; // Normal, no extra output static constexpr uint8_t VERB_NONE = 0; // Normal, no extra output
static constexpr uint8_t VERB_CFG = 1; // Print configuration after parsing options static constexpr uint8_t VERB_CFG = 1; // Print configuration after parsing options
static constexpr uint8_t VERB_LOG_ACT = 2; // Log actions before doing them static constexpr uint8_t VERB_LOG_ACT = 2; // Log actions before doing them
@@ -55,7 +56,9 @@ struct Options {
static constexpr uint8_t VERB_DEBUG = 4; // Internals are logged static constexpr uint8_t VERB_DEBUG = 4; // Internals are logged
static constexpr uint8_t VERB_TRACE = 5; // Step-by-step algorithm details static constexpr uint8_t VERB_TRACE = 5; // Step-by-step algorithm details
static constexpr uint8_t VERB_VVVVVV = 6; // What, can't I have a little fun? static constexpr uint8_t VERB_VVVVVV = 6; // What, can't I have a little fun?
[[gnu::format(printf, 3, 4)]] void verbosePrint(uint8_t level, char const *fmt, ...) const; // clang-format on
[[gnu::format(printf, 3, 4)]]
void verbosePrint(uint8_t level, char const *fmt, ...) const;
mutable bool hasTransparentPixels = false; mutable bool hasTransparentPixels = false;
uint8_t maxOpaqueColors() const { return nbColorsPerPal - hasTransparentPixels; } uint8_t maxOpaqueColors() const { return nbColorsPerPal - hasTransparentPixels; }
@@ -63,32 +66,24 @@ struct Options {
extern Options options; extern Options options;
/* // Prints the error count, and exits with failure
* Prints the error count, and exits with failure [[noreturn]]
*/ void giveUp();
[[noreturn]] void giveUp(); // If any error has been emitted thus far, calls `giveUp()`.
/*
* If any error has been emitted thus far, calls `giveUp()`.
*/
void requireZeroErrors(); void requireZeroErrors();
/* // Prints a warning, and does not change the error count
* Prints a warning, and does not change the error count [[gnu::format(printf, 1, 2)]]
*/ void warning(char const *fmt, ...);
[[gnu::format(printf, 1, 2)]] void warning(char const *fmt, ...); // Prints an error, and increments the error count
/* [[gnu::format(printf, 1, 2)]]
* Prints an error, and increments the error count void error(char const *fmt, ...);
*/ // Prints an error, and increments the error count
[[gnu::format(printf, 1, 2)]] void error(char const *fmt, ...); // Does not take format arguments so `format_` and `-Wformat-security` won't complain about
/* // calling `errorMessage(msg)`.
* Prints an error, and increments the error count
* Does not take format arguments so `format_` and `-Wformat-security` won't complain about
* calling `errorMessage(msg)`.
*/
void errorMessage(char const *msg); void errorMessage(char const *msg);
/* // Prints a fatal error, increments the error count, and gives up
* Prints a fatal error, increments the error count, and gives up [[gnu::format(printf, 1, 2), noreturn]]
*/ void fatal(char const *fmt, ...);
[[gnu::format(printf, 1, 2), noreturn]] void fatal(char const *fmt, ...);
struct Palette { struct Palette {
// An array of 4 GBC-native (RGB555) colors // An array of 4 GBC-native (RGB555) colors

View File

@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: MIT */ // SPDX-License-Identifier: MIT
#ifndef RGBDS_GFX_PAL_PACKING_HPP #ifndef RGBDS_GFX_PAL_PACKING_HPP
#define RGBDS_GFX_PAL_PACKING_HPP #define RGBDS_GFX_PAL_PACKING_HPP
@@ -11,9 +11,7 @@
struct Palette; struct Palette;
class ProtoPalette; 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> std::tuple<DefaultInitVec<size_t>, size_t>
overloadAndRemove(std::vector<ProtoPalette> const &protoPalettes); overloadAndRemove(std::vector<ProtoPalette> const &protoPalettes);

View File

@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: MIT */ // SPDX-License-Identifier: MIT
#ifndef RGBDS_GFX_PAL_SORTING_HPP #ifndef RGBDS_GFX_PAL_SORTING_HPP
#define RGBDS_GFX_PAL_SORTING_HPP #define RGBDS_GFX_PAL_SORTING_HPP
@@ -10,6 +10,10 @@
#include "gfx/rgba.hpp" #include "gfx/rgba.hpp"
// Allow a slot for every possible CGB color, plus one for transparency
// 32 (1 << 5) per channel, times 3 RGB channels = 32768 CGB colors
static constexpr size_t NB_COLOR_SLOTS = (1 << (5 * 3)) + 1;
struct Palette; struct Palette;
void sortIndexed( void sortIndexed(
@@ -20,7 +24,7 @@ void sortIndexed(
png_byte *palAlpha png_byte *palAlpha
); );
void sortGrayscale( 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); void sortRgb(std::vector<Palette> &palettes);

View File

@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: MIT */ // SPDX-License-Identifier: MIT
#ifndef RGBDS_GFX_PAL_SPEC_HPP #ifndef RGBDS_GFX_PAL_SPEC_HPP
#define RGBDS_GFX_PAL_SPEC_HPP #define RGBDS_GFX_PAL_SPEC_HPP

View File

@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: MIT */ // SPDX-License-Identifier: MIT
#ifndef RGBDS_GFX_PROCESS_HPP #ifndef RGBDS_GFX_PROCESS_HPP
#define RGBDS_GFX_PROCESS_HPP #define RGBDS_GFX_PROCESS_HPP

View File

@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: MIT */ // SPDX-License-Identifier: MIT
#ifndef RGBDS_GFX_PROTO_PALETTE_HPP #ifndef RGBDS_GFX_PROTO_PALETTE_HPP
#define RGBDS_GFX_PROTO_PALETTE_HPP #define RGBDS_GFX_PROTO_PALETTE_HPP

View File

@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: MIT */ // SPDX-License-Identifier: MIT
#ifndef RGBDS_GFX_REVERSE_HPP #ifndef RGBDS_GFX_REVERSE_HPP
#define RGBDS_GFX_REVERSE_HPP #define RGBDS_GFX_REVERSE_HPP

View File

@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: MIT */ // SPDX-License-Identifier: MIT
#ifndef RGBDS_GFX_RGBA_HPP #ifndef RGBDS_GFX_RGBA_HPP
#define RGBDS_GFX_RGBA_HPP #define RGBDS_GFX_RGBA_HPP
@@ -13,9 +13,7 @@ struct Rgba {
constexpr Rgba(uint8_t r, uint8_t g, uint8_t b, uint8_t a) constexpr Rgba(uint8_t r, uint8_t g, uint8_t b, uint8_t a)
: red(r), green(g), blue(b), alpha(a) {} : red(r), green(g), blue(b), alpha(a) {}
/* // Constructs the color from a "packed" RGBA representation (0xRRGGBBAA)
* Constructs the color from a "packed" RGBA representation (0xRRGGBBAA)
*/
explicit constexpr Rgba(uint32_t rgba = 0) explicit constexpr Rgba(uint32_t rgba = 0)
: red(rgba >> 24), green(rgba >> 16), blue(rgba >> 8), alpha(rgba) {} : red(rgba >> 24), green(rgba >> 16), blue(rgba >> 8), alpha(rgba) {}
@@ -32,10 +30,8 @@ struct Rgba {
}; };
} }
/* // Returns this RGBA as a 32-bit number that can be printed in hex (`%08x`) to yield its CSS
* Returns this RGBA as a 32-bit number that can be printed in hex (`%08x`) to yield its CSS // representation
* representation
*/
uint32_t toCSS() const { uint32_t toCSS() const {
auto shl = [](uint8_t val, unsigned shift) { return static_cast<uint32_t>(val) << shift; }; auto shl = [](uint8_t val, unsigned shift) { return static_cast<uint32_t>(val) << shift; };
return shl(red, 24) | shl(green, 16) | shl(blue, 8) | shl(alpha, 0); return shl(red, 24) | shl(green, 16) | shl(blue, 8) | shl(alpha, 0);
@@ -43,19 +39,15 @@ struct Rgba {
bool operator==(Rgba const &rhs) const { return toCSS() == rhs.toCSS(); } bool operator==(Rgba const &rhs) const { return toCSS() == rhs.toCSS(); }
bool operator!=(Rgba const &rhs) const { return toCSS() != rhs.toCSS(); } bool operator!=(Rgba const &rhs) const { return toCSS() != rhs.toCSS(); }
/* // CGB colors are RGB555, so we use bit 15 to signify that the color is transparent instead
* CGB colors are RGB555, so we use bit 15 to signify that the color is transparent instead // Since the rest of the bits don't matter then, we return 0x8000 exactly.
* Since the rest of the bits don't matter then, we return 0x8000 exactly.
*/
static constexpr uint16_t transparent = 0b1'00000'00000'00000; static constexpr uint16_t transparent = 0b1'00000'00000'00000;
static constexpr uint8_t transparency_threshold = 0x10; static constexpr uint8_t transparency_threshold = 0x10;
bool isTransparent() const { return alpha < transparency_threshold; } bool isTransparent() const { return alpha < transparency_threshold; }
static constexpr uint8_t opacity_threshold = 0xF0; static constexpr uint8_t opacity_threshold = 0xF0;
bool isOpaque() const { return alpha >= opacity_threshold; } bool isOpaque() const { return alpha >= opacity_threshold; }
/* // Computes the equivalent CGB color, respects the color curve depending on options
* Computes the equivalent CGB color, respects the color curve depending on options
*/
uint16_t cgbColor() const; uint16_t cgbColor() const;
bool isGray() const { return red == green && green == blue; } bool isGray() const { return red == green && green == blue; }

View File

@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: MIT */ // SPDX-License-Identifier: MIT
#ifndef RGBDS_HELPERS_HPP #ifndef RGBDS_HELPERS_HPP
#define RGBDS_HELPERS_HPP #define RGBDS_HELPERS_HPP
@@ -14,7 +14,8 @@
#else #else
// This seems to generate similar code to __builtin_unreachable, despite different semantics // This seems to generate similar code to __builtin_unreachable, despite different semantics
// Note that executing this is undefined behavior (declared [[noreturn]], but does return) // Note that executing this is undefined behavior (declared [[noreturn]], but does return)
[[noreturn]] static inline void unreachable_() { [[noreturn]]
static inline void unreachable_() {
} }
#endif #endif
@@ -26,8 +27,9 @@
// `[[gnu::assume()]]` for GCC or compatible also has insufficient support (GCC 13+ only) // `[[gnu::assume()]]` for GCC or compatible also has insufficient support (GCC 13+ only)
#define assume(x) \ #define assume(x) \
do { \ do { \
if (!(x)) \ if (!(x)) { \
unreachable_(); \ unreachable_(); \
} \
} while (0) } while (0)
#endif #endif
#else #else
@@ -93,15 +95,14 @@ static inline int clz(unsigned int x) {
#define CAT(x, y) x##y #define CAT(x, y) x##y
#define EXPAND_AND_CAT(x, y) CAT(x, y) #define EXPAND_AND_CAT(x, y) CAT(x, y)
// Obtaining the size of an array; `arr` must be an expression, not a type!
// (Having two instances of `arr` is OK because the contents of `sizeof` are not evaluated.)
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof *(arr))
// For lack of <ranges>, this adds some more brevity // For lack of <ranges>, this adds some more brevity
#define RANGE(s) std::begin(s), std::end(s) #define RANGE(s) std::begin(s), std::end(s)
// MSVC does not inline `strlen()` or `.length()` of a constant string, so we use `sizeof` // MSVC does not inline `strlen()` or `.length()` of a constant string
#define QUOTEDSTRLEN(s) (sizeof(s) - 1) template<int N>
static constexpr int literal_strlen(char const (&)[N]) {
return N - 1;
}
// For ad-hoc RAII in place of a `defer` statement or cross-platform `__attribute__((cleanup))` // For ad-hoc RAII in place of a `defer` statement or cross-platform `__attribute__((cleanup))`
template<typename T> template<typename T>

View File

@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: MIT */ // SPDX-License-Identifier: MIT
#ifndef RGBDS_ITERTOOLS_HPP #ifndef RGBDS_ITERTOOLS_HPP
#define RGBDS_ITERTOOLS_HPP #define RGBDS_ITERTOOLS_HPP
@@ -25,7 +25,7 @@ class EnumSeq {
auto operator*() const { return _value; } 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; }
bool operator!=(Iterator const &rhs) const { return _value != rhs._value; } bool operator!=(Iterator const &rhs) const { return _value != rhs._value; }
}; };
public: public:

View File

@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: MIT */ // SPDX-License-Identifier: MIT
#ifndef RGBDS_LINK_ASSIGN_HPP #ifndef RGBDS_LINK_ASSIGN_HPP
#define RGBDS_LINK_ASSIGN_HPP #define RGBDS_LINK_ASSIGN_HPP

View File

@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: MIT */ // SPDX-License-Identifier: MIT
#ifndef RGBDS_LINK_MAIN_HPP #ifndef RGBDS_LINK_MAIN_HPP
#define RGBDS_LINK_MAIN_HPP #define RGBDS_LINK_MAIN_HPP
@@ -32,8 +32,9 @@ extern bool disablePadding;
// Helper macro for printing verbose-mode messages // Helper macro for printing verbose-mode messages
#define verbosePrint(...) \ #define verbosePrint(...) \
do { \ do { \
if (beVerbose) \ if (beVerbose) { \
fprintf(stderr, __VA_ARGS__); \ fprintf(stderr, __VA_ARGS__); \
} \
} while (0) } while (0)
struct FileStackNode { struct FileStackNode {
@@ -58,11 +59,11 @@ struct FileStackNode {
std::string const &dump(uint32_t curLineNo) const; std::string const &dump(uint32_t curLineNo) const;
}; };
[[gnu::format(printf, 3, 4)]] void [[gnu::format(printf, 3, 4)]]
warning(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...); void warning(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...);
[[gnu::format(printf, 3, 4)]] void [[gnu::format(printf, 3, 4)]]
error(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...); void error(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...);
[[gnu::format(printf, 3, 4), noreturn]] void [[gnu::format(printf, 3, 4), noreturn]]
fatal(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...); void fatal(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...);
#endif // RGBDS_LINK_MAIN_HPP #endif // RGBDS_LINK_MAIN_HPP

View File

@@ -1,19 +1,12 @@
/* SPDX-License-Identifier: MIT */ // SPDX-License-Identifier: MIT
#ifndef RGBDS_LINK_OBJECT_HPP #ifndef RGBDS_LINK_OBJECT_HPP
#define RGBDS_LINK_OBJECT_HPP #define RGBDS_LINK_OBJECT_HPP
/* // Read an object (.o) file, and add its info to the data structures.
* Read an object (.o) file, and add its info to the data structures. void obj_ReadFile(char const *fileName, unsigned int fileID);
* @param fileName A path to the object file to be read
* @param i The ID of the file
*/
void obj_ReadFile(char const *fileName, unsigned int i);
/* // Sets up object file reading
* Sets up object file reading
* @param nbFiles The number of object files that will be read
*/
void obj_Setup(unsigned int nbFiles); void obj_Setup(unsigned int nbFiles);
#endif // RGBDS_LINK_OBJECT_HPP #endif // RGBDS_LINK_OBJECT_HPP

View File

@@ -1,26 +1,17 @@
/* SPDX-License-Identifier: MIT */ // SPDX-License-Identifier: MIT
#ifndef RGBDS_LINK_OUTPUT_HPP #ifndef RGBDS_LINK_OUTPUT_HPP
#define RGBDS_LINK_OUTPUT_HPP #define RGBDS_LINK_OUTPUT_HPP
struct Section; struct Section;
/* // Registers a section for output.
* Registers a section for output.
* @param section The section to add
*/
void out_AddSection(Section const &section); void out_AddSection(Section const &section);
/* // Finds an assigned section overlapping another one.
* Finds an assigned section overlapping another one.
* @param section The section that is being overlapped
* @return A section overlapping it
*/
Section const *out_OverlappingSection(Section const &section); Section const *out_OverlappingSection(Section const &section);
/* // Writes all output (bin, sym, map) files.
* Writes all output (bin, sym, map) files.
*/
void out_WriteFiles(); void out_WriteFiles();
#endif // RGBDS_LINK_OUTPUT_HPP #endif // RGBDS_LINK_OUTPUT_HPP

View File

@@ -1,17 +1,12 @@
/* SPDX-License-Identifier: MIT */ // SPDX-License-Identifier: MIT
#ifndef RGBDS_LINK_PATCH_HPP #ifndef RGBDS_LINK_PATCH_HPP
#define RGBDS_LINK_PATCH_HPP #define RGBDS_LINK_PATCH_HPP
/* // Checks all assertions
* Checks all assertions
* @return true if assertion failed
*/
void patch_CheckAssertions(); void patch_CheckAssertions();
/* // Applies all SECTIONs' patches to them
* Applies all SECTIONs' patches to them
*/
void patch_ApplyPatches(); void patch_ApplyPatches();
#endif // RGBDS_LINK_PATCH_HPP #endif // RGBDS_LINK_PATCH_HPP

View File

@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: MIT */ // SPDX-License-Identifier: MIT
#ifndef RGBDS_LINK_SDAS_OBJ_HPP #ifndef RGBDS_LINK_SDAS_OBJ_HPP
#define RGBDS_LINK_SDAS_OBJ_HPP #define RGBDS_LINK_SDAS_OBJ_HPP

View File

@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: MIT */ // SPDX-License-Identifier: MIT
#ifndef RGBDS_LINK_SECTION_HPP #ifndef RGBDS_LINK_SECTION_HPP
#define RGBDS_LINK_SECTION_HPP #define RGBDS_LINK_SECTION_HPP
@@ -65,29 +65,17 @@ struct Assertion {
extern std::deque<Assertion> assertions; extern std::deque<Assertion> assertions;
/* // Execute a callback for each section currently registered.
* Execute a callback for each section currently registered. // This is to avoid exposing the data structure in which sections are stored.
* This is to avoid exposing the data structure in which sections are stored.
* @param callback The function to call for each structure.
*/
void sect_ForEach(void (*callback)(Section &)); void sect_ForEach(void (*callback)(Section &));
/* // Registers a section to be processed.
* Registers a section to be processed.
* @param section The section to register.
*/
void sect_AddSection(std::unique_ptr<Section> &&section); void sect_AddSection(std::unique_ptr<Section> &&section);
/* // Finds a section by its name.
* Finds a section by its name.
* @param name The name of the section to look for
* @return A pointer to the section, or `nullptr` if it wasn't found
*/
Section *sect_GetSection(std::string const &name); Section *sect_GetSection(std::string const &name);
/* // Checks if all sections meet reasonable criteria, such as max size
* Checks if all sections meet reasonable criteria, such as max size
*/
void sect_DoSanityChecks(); void sect_DoSanityChecks();
#endif // RGBDS_LINK_SECTION_HPP #endif // RGBDS_LINK_SECTION_HPP

View File

@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: MIT */ // SPDX-License-Identifier: MIT
#ifndef RGBDS_LINK_SYMBOL_HPP #ifndef RGBDS_LINK_SYMBOL_HPP
#define RGBDS_LINK_SYMBOL_HPP #define RGBDS_LINK_SYMBOL_HPP
@@ -41,11 +41,7 @@ void sym_ForEach(void (*callback)(Symbol &));
void sym_AddSymbol(Symbol &symbol); void sym_AddSymbol(Symbol &symbol);
/* // Finds a symbol in all the defined symbols.
* Finds a symbol in all the defined symbols.
* @param name The name of the symbol to look for
* @return A pointer to the symbol, or `nullptr` if not found.
*/
Symbol *sym_GetSymbol(std::string const &name); Symbol *sym_GetSymbol(std::string const &name);
void sym_DumpLocalAliasedSymbols(std::string const &name); void sym_DumpLocalAliasedSymbols(std::string const &name);

View File

@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: MIT */ // SPDX-License-Identifier: MIT
#ifndef RGBDS_LINKDEFS_HPP #ifndef RGBDS_LINKDEFS_HPP
#define RGBDS_LINKDEFS_HPP #define RGBDS_LINKDEFS_HPP
@@ -92,29 +92,19 @@ extern struct SectionTypeInfo {
uint32_t lastBank; uint32_t lastBank;
} sectionTypeInfo[SECTTYPE_INVALID]; } sectionTypeInfo[SECTTYPE_INVALID];
/* // Tells whether a section has data in its object file definition,
* Tells whether a section has data in its object file definition, // depending on type.
* depending on type.
* @param type The section's type
* @return `true` if the section's definition includes data
*/
static inline bool sect_HasData(SectionType type) { static inline bool sect_HasData(SectionType type) {
assume(type != SECTTYPE_INVALID); assume(type != SECTTYPE_INVALID);
return type == SECTTYPE_ROM0 || type == SECTTYPE_ROMX; return type == SECTTYPE_ROM0 || type == SECTTYPE_ROMX;
} }
/* // Returns a memory region's end address (last byte), e.g. 0x7FFF
* Computes a memory region's end address (last byte), eg. 0x7FFF
* @return The address of the last byte in that memory region
*/
static inline uint16_t endaddr(SectionType type) { static inline uint16_t endaddr(SectionType type) {
return sectionTypeInfo[type].startAddr + sectionTypeInfo[type].size - 1; return sectionTypeInfo[type].startAddr + sectionTypeInfo[type].size - 1;
} }
/* // Returns a memory region's number of banks, or 1 for regions without banking
* Computes a memory region's number of banks
* @return The number of banks, 1 for regions without banking
*/
static inline uint32_t nbbanks(SectionType type) { static inline uint32_t nbbanks(SectionType type) {
return sectionTypeInfo[type].lastBank - sectionTypeInfo[type].firstBank + 1; return sectionTypeInfo[type].lastBank - sectionTypeInfo[type].firstBank + 1;
} }

View File

@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: MIT */ // SPDX-License-Identifier: MIT
#ifndef RGBDS_OP_MATH_HPP #ifndef RGBDS_OP_MATH_HPP
#define RGBDS_OP_MATH_HPP #define RGBDS_OP_MATH_HPP

View File

@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: MIT */ // SPDX-License-Identifier: MIT
// platform-specific hacks // platform-specific hacks

View File

@@ -1,17 +1,8 @@
/* SPDX-License-Identifier: MIT */ // SPDX-License-Identifier: MIT
#ifndef RGBDS_UTIL_HPP #ifndef RGBDS_UTIL_HPP
#define RGBDS_UTIL_HPP #define RGBDS_UTIL_HPP
#include <stddef.h>
#include <stdint.h>
#include <vector>
char const *printChar(int c); char const *printChar(int c);
/*
* @return The number of bytes read, or 0 if invalid data was found
*/
size_t readUTF8Char(std::vector<int32_t> *dest, char const *src);
#endif // RGBDS_UTIL_HPP #endif // RGBDS_UTIL_HPP

View File

@@ -1,15 +1,12 @@
/* SPDX-License-Identifier: MIT */ // SPDX-License-Identifier: MIT
#ifndef RGBDS_VERSION_HPP #ifndef RGBDS_VERSION_HPP
#define RGBDS_VERSION_HPP #define RGBDS_VERSION_HPP
extern "C" {
#define PACKAGE_VERSION_MAJOR 0 #define PACKAGE_VERSION_MAJOR 0
#define PACKAGE_VERSION_MINOR 9 #define PACKAGE_VERSION_MINOR 9
#define PACKAGE_VERSION_PATCH 0 #define PACKAGE_VERSION_PATCH 1
char const *get_package_version_string(); char const *get_package_version_string();
}
#endif // RGBDS_VERSION_H #endif // RGBDS_VERSION_H

View File

@@ -1,6 +1,6 @@
.\" SPDX-License-Identifier: MIT .\" SPDX-License-Identifier: MIT
.\" .\"
.Dd December 25, 2024 .Dd February 2, 2025
.Dt GBZ80 7 .Dt GBZ80 7
.Os .Os
.Sh NAME .Sh NAME
@@ -615,9 +615,6 @@ to the adjustment.
.It .It
Subtract the adjustment from Subtract the adjustment from
.Sy A . .Sy A .
.It
Set the carry flag if borrow (i.e. if adjustment >
.Sy A ) .
.El .El
.It If the subtract flag Sy N No is not set: .It If the subtract flag Sy N No is not set:
.Bl -enum -compact .Bl -enum -compact
@@ -639,15 +636,13 @@ to the adjustment.
If the carry flag is set or If the carry flag is set or
.Sy A .Sy A
> >
.Ad $9F , .Ad $99 ,
then add then add
.Ad $60 .Ad $60
to the adjustment. to the adjustment and set the carry flag.
.It .It
Add the adjustment to Add the adjustment to
.Sy A . .Sy A .
.It
Set the carry flag if overflow from bit 7.
.El .El
.El .El
.Pp .Pp

View File

@@ -2,7 +2,7 @@
.\" .\"
.\" SPDX-License-Identifier: MIT .\" SPDX-License-Identifier: MIT
.\" .\"
.Dd December 25, 2024 .Dd February 2, 2025
.Dt RGBASM-OLD 5 .Dt RGBASM-OLD 5
.Os .Os
.Sh NAME .Sh NAME
@@ -15,7 +15,7 @@ assembly language over its decades of evolution, along with their modern alterna
Its goal is to be a reference for backwards incompatibility, when upgrading an old assembly codebase to work with the latest RGBDS release. 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 It does
.Em not .Em not
attempt to list syntax bugs that were fixed, nor new reserved keywords that may conflict with old identifiers. 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 .Sh REMOVED
These are features which have been completely removed, without any direct alternatives. 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. Usually these features were limiting the addition of other features, or had awkward limits on their own intended effects.
@@ -266,6 +266,14 @@ Instead, use
.Ql LDH [C], A .Ql LDH [C], A
and and
.Ql LDH A, [C] . .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] .Ss LDH [n8], A and LDH A, [n8]
Deprecated in 0.9.0. Deprecated in 0.9.0.
.Pp .Pp
@@ -288,7 +296,7 @@ Deprecated in 0.3.0, removed in 0.4.0.
.Pp .Pp
Instead, use Instead, use
.Ql LD HL, SP + e8 . .Ql LD HL, SP + e8 .
.Ss LDHL, SP, e8 .Ss LDHL SP, e8
Supported in ASMotor, removed in RGBDS. Supported in ASMotor, removed in RGBDS.
.Pp .Pp
Instead, use Instead, use
@@ -360,6 +368,68 @@ Previously we had
.Pp .Pp
Instead, now we have Instead, now we have
.Ql p ** q ** r == p ** (q ** r) . .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 .Sh SEE ALSO
.Xr rgbasm 1 , .Xr rgbasm 1 ,
.Xr gbz80 7 , .Xr gbz80 7 ,

View File

@@ -1,6 +1,6 @@
.\" SPDX-License-Identifier: MIT .\" SPDX-License-Identifier: MIT
.\" .\"
.Dd December 25, 2024 .Dd February 2, 2025
.Dt RGBASM 1 .Dt RGBASM 1
.Os .Os
.Sh NAME .Sh NAME
@@ -8,7 +8,7 @@
.Nd Game Boy assembler .Nd Game Boy assembler
.Sh SYNOPSIS .Sh SYNOPSIS
.Nm .Nm
.Op Fl EVvw .Op Fl EhVvw
.Op Fl b Ar chars .Op Fl b Ar chars
.Op Fl D Ar name Ns Op = Ns Ar value .Op Fl D Ar name Ns Op = Ns Ar value
.Op Fl g Ar chars .Op Fl g Ar chars
@@ -67,6 +67,8 @@ Export all labels, including unreferenced and local labels.
.It Fl g Ar chars , Fl \-gfx-chars Ar chars .It Fl g Ar chars , Fl \-gfx-chars Ar chars
Change the four characters used for gfx constants. Change the four characters used for gfx constants.
The defaults are 0123. The defaults are 0123.
.It Fl h , Fl \-help
Print help text for the program and exit.
.It Fl I Ar path , Fl \-include Ar path .It Fl I Ar path , Fl \-include Ar path
Add a new Add a new
.Dq include path ; .Dq include path ;

View File

@@ -2,7 +2,7 @@
.\" .\"
.\" SPDX-License-Identifier: MIT .\" SPDX-License-Identifier: MIT
.\" .\"
.Dd December 25, 2024 .Dd February 2, 2025
.Dt RGBASM 5 .Dt RGBASM 5
.Os .Os
.Sh NAME .Sh NAME

View File

@@ -1,6 +1,6 @@
.\" SPDX-License-Identifier: MIT .\" SPDX-License-Identifier: MIT
.\" .\"
.Dd December 25, 2024 .Dd February 2, 2025
.Dt RGBDS 5 .Dt RGBDS 5
.Os .Os
.Sh NAME .Sh NAME

View File

@@ -1,6 +1,6 @@
.\" SPDX-License-Identifier: MIT .\" SPDX-License-Identifier: MIT
.\" .\"
.Dd December 25, 2024 .Dd February 2, 2025
.Dt RGBDS 7 .Dt RGBDS 7
.Os .Os
.Sh NAME .Sh NAME
@@ -51,6 +51,10 @@ adapts the code to be more UNIX-like and releases this version as rgbds-linux.
forks Nossum's repository. forks Nossum's repository.
The fork becomes the reference implementation of RGBDS. The fork becomes the reference implementation of RGBDS.
.It .It
2010-09-25: S\(/orensen continues development of
.Lk https://github.com/asmotor/asmotor ASMotor
to this day.
.It
2015-01-18: 2015-01-18:
.An stag019 .An stag019
begins implementing RGBGFX, a PNGtoGame Boy graphics converter, for eventual integration into RGBDS. begins implementing RGBGFX, a PNGtoGame Boy graphics converter, for eventual integration into RGBDS.

View File

@@ -1,6 +1,6 @@
.\" SPDX-License-Identifier: MIT .\" SPDX-License-Identifier: MIT
.\" .\"
.Dd December 25, 2024 .Dd February 2, 2025
.Dt RGBFIX 1 .Dt RGBFIX 1
.Os .Os
.Sh NAME .Sh NAME
@@ -8,7 +8,7 @@
.Nd Game Boy header utility and checksum fixer .Nd Game Boy header utility and checksum fixer
.Sh SYNOPSIS .Sh SYNOPSIS
.Nm .Nm
.Op Fl jOsVv .Op Fl hjOsVv
.Op Fl C | c .Op Fl C | c
.Op Fl f Ar fix_spec .Op Fl f Ar fix_spec
.Op Fl i Ar game_id .Op Fl i Ar game_id
@@ -91,6 +91,8 @@ Fix the global checksum
.It Cm G .It Cm G
Trash the global checksum. Trash the global checksum.
.El .El
.It Fl h , Fl \-help
Print help text for the program and exit.
.It Fl i Ar game_id , Fl \-game-id Ar game_id .It Fl i Ar game_id , Fl \-game-id Ar game_id
Set the game ID string Set the game ID string
.Pq Ad 0x13F Ns \(en Ns Ad 0x142 .Pq Ad 0x13F Ns \(en Ns Ad 0x142

View File

@@ -2,7 +2,7 @@
.\" .\"
.\" SPDX-License-Identifier: MIT .\" SPDX-License-Identifier: MIT
.\" .\"
.Dd December 25, 2024 .Dd February 2, 2025
.Dt RGBGFX 1 .Dt RGBGFX 1
.Os .Os
.Sh NAME .Sh NAME
@@ -10,7 +10,7 @@
.Nd Game Boy graphics converter .Nd Game Boy graphics converter
.Sh SYNOPSIS .Sh SYNOPSIS
.Nm .Nm
.Op Fl CmOuVXYZ .Op Fl CmhOuVXYZ
.Op Fl v Op Fl v No ... .Op Fl v Op Fl v No ...
.Op Fl a Ar attrmap | Fl A .Op Fl a Ar attrmap | Fl A
.Op Fl b Ar base_ids .Op Fl b Ar base_ids
@@ -165,6 +165,8 @@ for a list of formats and their descriptions.
.It Fl d Ar depth , Fl \-depth Ar depth .It Fl d Ar depth , Fl \-depth Ar depth
Set the bit depth of the output tile data, in bits per pixel (bpp), either 1 or 2 (the default). Set the bit depth of the output tile data, in bits per pixel (bpp), either 1 or 2 (the default).
This changes how tile data is output, and the maximum number of colors per palette (2 and 4 respectively). This changes how tile data is output, and the maximum number of colors per palette (2 and 4 respectively).
.It Fl h , Fl \-help
Print help text for the program and exit.
.It Fl i Ar input_tiles , Fl \-input-tileset Ar input_tiles .It Fl i Ar input_tiles , Fl \-input-tileset Ar input_tiles
Use the specified input tiles in addition to having Use the specified input tiles in addition to having
.Nm .Nm

View File

@@ -1,6 +1,6 @@
.\" SPDX-License-Identifier: MIT .\" SPDX-License-Identifier: MIT
.\" .\"
.Dd December 25, 2024 .Dd February 2, 2025
.Dt RGBLINK 1 .Dt RGBLINK 1
.Os .Os
.Sh NAME .Sh NAME
@@ -8,7 +8,7 @@
.Nd Game Boy linker .Nd Game Boy linker
.Sh SYNOPSIS .Sh SYNOPSIS
.Nm .Nm
.Op Fl dMtVvwx .Op Fl dhMtVvwx
.Op Fl l Ar linker_script .Op Fl l Ar linker_script
.Op Fl m Ar map_file .Op Fl m Ar map_file
.Op Fl n Ar sym_file .Op Fl n Ar sym_file
@@ -67,6 +67,8 @@ Enable DMG mode.
Prohibit the use of sections that doesn't exist on a DMG, such as VRAM bank 1. Prohibit the use of sections that doesn't exist on a DMG, such as VRAM bank 1.
This option automatically enables This option automatically enables
.Fl w . .Fl w .
.It Fl h , Fl \-help
Print help text for the program and exit.
.It Fl l Ar linker_script , Fl \-linkerscript Ar linker_script .It Fl l Ar linker_script , Fl \-linkerscript Ar linker_script
Specify a linker script file that tells the linker how sections must be placed in the ROM. Specify a linker script file that tells the linker how sections must be placed in the ROM.
The attributes assigned in the linker script must be consistent with any assigned in the code. The attributes assigned in the linker script must be consistent with any assigned in the code.

View File

@@ -1,6 +1,6 @@
.\" SPDX-License-Identifier: MIT .\" SPDX-License-Identifier: MIT
.\" .\"
.Dd December 25, 2024 .Dd February 2, 2025
.Dt RGBLINK 5 .Dt RGBLINK 5
.Os .Os
.Sh NAME .Sh NAME

View File

@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: MIT */ // SPDX-License-Identifier: MIT
#include "asm/charmap.hpp" #include "asm/charmap.hpp"
@@ -11,6 +11,7 @@
#include <unordered_map> #include <unordered_map>
#include <utility> #include <utility>
#include "extern/utf8decoder.hpp"
#include "helpers.hpp" #include "helpers.hpp"
#include "util.hpp" #include "util.hpp"
@@ -45,20 +46,26 @@ bool charmap_ForEach(
for (Charmap const &charmap : charmapList) { for (Charmap const &charmap : charmapList) {
// Traverse the trie depth-first to derive the character mappings in definition order // Traverse the trie depth-first to derive the character mappings in definition order
std::map<size_t, std::string> mappings; std::map<size_t, std::string> mappings;
for (std::stack<std::pair<size_t, std::string>> prefixes({{0, ""}}); !prefixes.empty();) { // clang-format off: nested initializers
for (std::stack<std::pair<size_t, std::string>> prefixes({{0, ""}});
!prefixes.empty();) {
// clang-format on
auto [nodeIdx, mapping] = std::move(prefixes.top()); auto [nodeIdx, mapping] = std::move(prefixes.top());
prefixes.pop(); prefixes.pop();
CharmapNode const &node = charmap.nodes[nodeIdx]; CharmapNode const &node = charmap.nodes[nodeIdx];
if (node.isTerminal()) if (node.isTerminal()) {
mappings[nodeIdx] = mapping; mappings[nodeIdx] = mapping;
}
for (unsigned c = 0; c < 256; c++) { for (unsigned c = 0; c < 256; c++) {
if (size_t nextIdx = node.next[c]; nextIdx) if (size_t nextIdx = node.next[c]; nextIdx) {
prefixes.push({nextIdx, mapping + static_cast<char>(c)}); prefixes.push({nextIdx, mapping + static_cast<char>(c)});
}
} }
} }
mapFunc(charmap.name); mapFunc(charmap.name);
for (auto [nodeIdx, mapping] : mappings) for (auto [nodeIdx, mapping] : mappings) {
charFunc(mapping, charmap.nodes[nodeIdx].value); charFunc(mapping, charmap.nodes[nodeIdx].value);
}
} }
return !charmapList.empty(); return !charmapList.empty();
} }
@@ -67,10 +74,11 @@ void charmap_New(std::string const &name, std::string const *baseName) {
size_t baseIdx = SIZE_MAX; size_t baseIdx = SIZE_MAX;
if (baseName != nullptr) { if (baseName != nullptr) {
if (auto search = charmapMap.find(*baseName); search == charmapMap.end()) if (auto search = charmapMap.find(*baseName); search == charmapMap.end()) {
error("Base charmap '%s' doesn't exist\n", baseName->c_str()); error("Base charmap '%s' doesn't exist\n", baseName->c_str());
else } else {
baseIdx = search->second; baseIdx = search->second;
}
} }
if (charmapMap.find(name) != charmapMap.end()) { if (charmapMap.find(name) != charmapMap.end()) {
@@ -82,10 +90,11 @@ void charmap_New(std::string const &name, std::string const *baseName) {
charmapMap[name] = charmapList.size(); charmapMap[name] = charmapList.size();
Charmap &charmap = charmapList.emplace_back(); Charmap &charmap = charmapList.emplace_back();
if (baseIdx != SIZE_MAX) if (baseIdx != SIZE_MAX) {
charmap.nodes = charmapList[baseIdx].nodes; // Copies `charmapList[baseIdx].nodes` charmap.nodes = charmapList[baseIdx].nodes; // Copies `charmapList[baseIdx].nodes`
else } else {
charmap.nodes.emplace_back(); // Zero-init the root node charmap.nodes.emplace_back(); // Zero-init the root node
}
charmap.name = name; charmap.name = name;
@@ -93,10 +102,11 @@ void charmap_New(std::string const &name, std::string const *baseName) {
} }
void charmap_Set(std::string const &name) { void charmap_Set(std::string const &name) {
if (auto search = charmapMap.find(name); search == charmapMap.end()) if (auto search = charmapMap.find(name); search == charmapMap.end()) {
error("Charmap '%s' doesn't exist\n", name.c_str()); error("Charmap '%s' doesn't exist\n", name.c_str());
else } else {
currentCharmap = &charmapList[search->second]; currentCharmap = &charmapList[search->second];
}
} }
void charmap_Push() { void charmap_Push() {
@@ -146,8 +156,9 @@ void charmap_Add(std::string const &mapping, std::vector<int32_t> &&value) {
CharmapNode &node = charmap.nodes[nodeIdx]; CharmapNode &node = charmap.nodes[nodeIdx];
if (node.isTerminal()) if (node.isTerminal()) {
warning(WARNING_CHARMAP_REDEF, "Overriding charmap mapping\n"); warning(WARNING_CHARMAP_REDEF, "Overriding charmap mapping\n");
}
std::swap(node.value, value); std::swap(node.value, value);
} }
@@ -159,8 +170,9 @@ bool charmap_HasChar(std::string const &input) {
for (char c : input) { for (char c : input) {
nodeIdx = charmap.nodes[nodeIdx].next[static_cast<uint8_t>(c)]; nodeIdx = charmap.nodes[nodeIdx].next[static_cast<uint8_t>(c)];
if (!nodeIdx) if (!nodeIdx) {
return false; return false;
}
} }
return charmap.nodes[nodeIdx].isTerminal(); return charmap.nodes[nodeIdx].isTerminal();
@@ -168,8 +180,7 @@ bool charmap_HasChar(std::string const &input) {
std::vector<int32_t> charmap_Convert(std::string const &input) { std::vector<int32_t> charmap_Convert(std::string const &input) {
std::vector<int32_t> output; std::vector<int32_t> output;
for (std::string_view inputView = input; charmap_ConvertNext(inputView, &output);) for (std::string_view inputView = input; charmap_ConvertNext(inputView, &output);) {}
;
return output; return output;
} }
@@ -186,8 +197,9 @@ size_t charmap_ConvertNext(std::string_view &input, std::vector<int32_t> *output
for (size_t nodeIdx = 0; inputIdx < input.length();) { for (size_t nodeIdx = 0; inputIdx < input.length();) {
nodeIdx = charmap.nodes[nodeIdx].next[static_cast<uint8_t>(input[inputIdx])]; nodeIdx = charmap.nodes[nodeIdx].next[static_cast<uint8_t>(input[inputIdx])];
if (!nodeIdx) if (!nodeIdx) {
break; break;
}
inputIdx++; // Consume that char inputIdx++; // Consume that char
@@ -207,27 +219,42 @@ size_t charmap_ConvertNext(std::string_view &input, std::vector<int32_t> *output
if (matchIdx) { // A match was found, use it if (matchIdx) { // A match was found, use it
std::vector<int32_t> const &value = charmap.nodes[matchIdx].value; std::vector<int32_t> const &value = charmap.nodes[matchIdx].value;
if (output) if (output) {
output->insert(output->end(), RANGE(value)); output->insert(output->end(), RANGE(value));
}
matchLen = value.size(); matchLen = value.size();
} else if (inputIdx < input.length()) { // No match found, but there is some input left } else if (inputIdx < input.length()) { // No match found, but there is some input left
int firstChar = input[inputIdx]; size_t codepointLen = 0;
// This will write the codepoint's value to `output`, little-endian // This will write the codepoint's value to `output`, little-endian
size_t codepointLen = readUTF8Char(output, input.data() + inputIdx); for (uint32_t state = 0, codepoint = 0; inputIdx + codepointLen < input.length();) {
if (decode(&state, &codepoint, input[inputIdx + codepointLen]) == 1) {
error("Input string is not valid UTF-8\n");
codepointLen = 1;
break;
}
codepointLen++;
if (state == 0) {
break;
}
}
if (codepointLen == 0) if (output) {
error("Input string is not valid UTF-8\n"); output->insert(
output->end(), input.data() + inputIdx, input.data() + inputIdx + codepointLen
);
}
// Warn if this character is not mapped but any others are // Warn if this character is not mapped but any others are
if (charmap.nodes.size() > 1) if (int firstChar = input[inputIdx]; charmap.nodes.size() > 1) {
warning(WARNING_UNMAPPED_CHAR_1, "Unmapped character %s\n", printChar(firstChar)); warning(WARNING_UNMAPPED_CHAR_1, "Unmapped character %s\n", printChar(firstChar));
else if (charmap.name != DEFAULT_CHARMAP_NAME) } else if (charmap.name != DEFAULT_CHARMAP_NAME) {
warning( warning(
WARNING_UNMAPPED_CHAR_2, WARNING_UNMAPPED_CHAR_2,
"Unmapped character %s not in " DEFAULT_CHARMAP_NAME " charmap\n", "Unmapped character %s not in " DEFAULT_CHARMAP_NAME " charmap\n",
printChar(firstChar) printChar(firstChar)
); );
}
inputIdx += codepointLen; inputIdx += codepointLen;
matchLen = codepointLen; matchLen = codepointLen;

View File

@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: MIT */ // SPDX-License-Identifier: MIT
// Fixed-point math routines // Fixed-point math routines
@@ -16,19 +16,17 @@ uint8_t fix_Precision() {
return fixPrecision; return fixPrecision;
} }
double fix_PrecisionFactor() {
return pow(2.0, fixPrecision);
}
static double fix2double(int32_t i, int32_t q) { static double fix2double(int32_t i, int32_t q) {
return i / pow(2.0, q); return i / pow(2.0, q);
} }
static int32_t double2fix(double d, int32_t q) { static int32_t double2fix(double d, int32_t q) {
if (isnan(d)) if (isnan(d)) {
return 0; return 0;
if (isinf(d)) }
if (isinf(d)) {
return d < 0 ? INT32_MIN : INT32_MAX; return d < 0 ? INT32_MIN : INT32_MAX;
}
return static_cast<int32_t>(round(d * pow(2.0, q))); return static_cast<int32_t>(round(d * pow(2.0, q)));
} }
@@ -73,7 +71,12 @@ int32_t fix_Mul(int32_t i, int32_t j, int32_t q) {
} }
int32_t fix_Div(int32_t i, int32_t j, int32_t q) { int32_t fix_Div(int32_t i, int32_t j, int32_t q) {
return double2fix(fix2double(i, q) / fix2double(j, q), q); double dividend = fix2double(i, q);
double divisor = fix2double(j, q);
if (fpclassify(divisor) == FP_ZERO) {
return dividend < 0 ? INT32_MIN : dividend > 0 ? INT32_MAX : 0;
}
return double2fix(dividend / divisor, q);
} }
int32_t fix_Mod(int32_t i, int32_t j, int32_t q) { int32_t fix_Mod(int32_t i, int32_t j, int32_t q) {
@@ -85,7 +88,11 @@ int32_t fix_Pow(int32_t i, int32_t j, int32_t q) {
} }
int32_t fix_Log(int32_t i, int32_t j, int32_t q) { int32_t fix_Log(int32_t i, int32_t j, int32_t q) {
return double2fix(log(fix2double(i, q)) / log(fix2double(j, q)), q); double divisor = log(fix2double(j, q));
if (fpclassify(divisor) == FP_ZERO) {
return INT32_MAX;
}
return double2fix(log(fix2double(i, q)) / divisor, q);
} }
int32_t fix_Round(int32_t i, int32_t q) { int32_t fix_Round(int32_t i, int32_t q) {

View File

@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: MIT */ // SPDX-License-Identifier: MIT
#include "asm/format.hpp" #include "asm/format.hpp"
@@ -13,39 +13,44 @@
#include "asm/warning.hpp" #include "asm/warning.hpp"
void FormatSpec::useCharacter(int c) { void FormatSpec::useCharacter(int c) {
if (state == FORMAT_INVALID) if (state == FORMAT_INVALID) {
return; return;
}
switch (c) { switch (c) {
// sign // sign
case ' ': case ' ':
case '+': case '+':
if (state > FORMAT_SIGN) if (state > FORMAT_SIGN) {
goto invalid; goto invalid;
}
state = FORMAT_EXACT; state = FORMAT_EXACT;
sign = c; sign = c;
break; break;
// exact // exact
case '#': case '#':
if (state > FORMAT_EXACT) if (state > FORMAT_EXACT) {
goto invalid; goto invalid;
}
state = FORMAT_ALIGN; state = FORMAT_ALIGN;
exact = true; exact = true;
break; break;
// align // align
case '-': case '-':
if (state > FORMAT_ALIGN) if (state > FORMAT_ALIGN) {
goto invalid; goto invalid;
}
state = FORMAT_WIDTH; state = FORMAT_WIDTH;
alignLeft = true; alignLeft = true;
break; break;
// pad, width, and prec values // pad, width, and prec values
case '0': case '0':
if (state < FORMAT_WIDTH) if (state < FORMAT_WIDTH) {
padZero = true; padZero = true;
}
[[fallthrough]]; [[fallthrough]];
case '1': case '1':
case '2': case '2':
@@ -72,16 +77,18 @@ void FormatSpec::useCharacter(int c) {
// width // width
case '.': case '.':
if (state > FORMAT_WIDTH) if (state > FORMAT_WIDTH) {
goto invalid; goto invalid;
}
state = FORMAT_FRAC; state = FORMAT_FRAC;
hasFrac = true; hasFrac = true;
break; break;
// prec // prec
case 'q': case 'q':
if (state > FORMAT_PREC) if (state > FORMAT_PREC) {
goto invalid; goto invalid;
}
state = FORMAT_PREC; state = FORMAT_PREC;
hasPrec = true; hasPrec = true;
break; break;
@@ -95,8 +102,9 @@ void FormatSpec::useCharacter(int c) {
case 'o': case 'o':
case 'f': case 'f':
case 's': case 's':
if (state >= FORMAT_DONE) if (state >= FORMAT_DONE) {
goto invalid; goto invalid;
}
state = FORMAT_DONE; state = FORMAT_DONE;
valid = true; valid = true;
type = c; type = c;
@@ -110,8 +118,9 @@ invalid:
} }
void FormatSpec::finishCharacters() { void FormatSpec::finishCharacters() {
if (!isValid()) if (!isValid()) {
state = FORMAT_INVALID; state = FORMAT_INVALID;
}
} }
static std::string escapeString(std::string const &str) { static std::string escapeString(std::string const &str) {
@@ -151,16 +160,21 @@ void FormatSpec::appendString(std::string &str, std::string const &value) const
useType = 's'; useType = 's';
} }
if (sign) if (sign) {
error("Formatting string with sign flag '%c'\n", sign); error("Formatting string with sign flag '%c'\n", sign);
if (padZero) }
if (padZero) {
error("Formatting string with padding flag '0'\n"); error("Formatting string with padding flag '0'\n");
if (hasFrac) }
if (hasFrac) {
error("Formatting string with fractional width\n"); error("Formatting string with fractional width\n");
if (hasPrec) }
if (hasPrec) {
error("Formatting string with fractional precision\n"); error("Formatting string with fractional precision\n");
if (useType != 's') }
if (useType != 's') {
error("Formatting string as type '%c'\n", useType); error("Formatting string as type '%c'\n", useType);
}
std::string useValue = exact ? escapeString(value) : value; std::string useValue = exact ? escapeString(value) : value;
size_t valueLen = useValue.length(); size_t valueLen = useValue.length();
@@ -187,22 +201,27 @@ void FormatSpec::appendNumber(std::string &str, uint32_t value) const {
} }
if (useType != 'X' && useType != 'x' && useType != 'b' && useType != 'o' && useType != 'f' if (useType != 'X' && useType != 'x' && useType != 'b' && useType != 'o' && useType != 'f'
&& useExact) && useExact) {
error("Formatting type '%c' with exact flag '#'\n", useType); error("Formatting type '%c' with exact flag '#'\n", useType);
if (useType != 'f' && hasFrac) }
if (useType != 'f' && hasFrac) {
error("Formatting type '%c' with fractional width\n", useType); error("Formatting type '%c' with fractional width\n", useType);
if (useType != 'f' && hasPrec) }
if (useType != 'f' && hasPrec) {
error("Formatting type '%c' with fractional precision\n", useType); error("Formatting type '%c' with fractional precision\n", useType);
if (useType == 's') }
if (useType == 's') {
error("Formatting number as type 's'\n"); error("Formatting number as type 's'\n");
}
char signChar = sign; // 0 or ' ' or '+' char signChar = sign; // 0 or ' ' or '+'
if (useType == 'd' || useType == 'f') { if (useType == 'd' || useType == 'f') {
if (int32_t v = value; v < 0) { if (int32_t v = value; v < 0) {
signChar = '-'; signChar = '-';
if (v != INT32_MIN) if (v != INT32_MIN) {
value = -v; value = -v;
}
} }
} }
@@ -250,10 +269,11 @@ void FormatSpec::appendNumber(std::string &str, uint32_t value) const {
} }
double fval = fabs(value / pow(2.0, usePrec)); double fval = fabs(value / pow(2.0, usePrec));
if (int fracWidthArg = static_cast<int>(useFracWidth); useExact) if (int fracWidthArg = static_cast<int>(useFracWidth); useExact) {
snprintf(valueBuf, sizeof(valueBuf), "%.*fq%zu", fracWidthArg, fval, usePrec); snprintf(valueBuf, sizeof(valueBuf), "%.*fq%zu", fracWidthArg, fval, usePrec);
else } else {
snprintf(valueBuf, sizeof(valueBuf), "%.*f", fracWidthArg, fval); snprintf(valueBuf, sizeof(valueBuf), "%.*f", fracWidthArg, fval);
}
} else if (useType == 'd') { } else if (useType == 'd') {
// Decimal numbers may be formatted with a '-' sign by `snprintf`, so `abs` prevents that, // Decimal numbers may be formatted with a '-' sign by `snprintf`, so `abs` prevents that,
// with a special case for `INT32_MIN` since `labs(INT32_MIN)` is UB. The sign will be // with a special case for `INT32_MIN` since `labs(INT32_MIN)` is UB. The sign will be
@@ -278,27 +298,33 @@ void FormatSpec::appendNumber(std::string &str, uint32_t value) const {
str.reserve(str.length() + totalLen); str.reserve(str.length() + totalLen);
if (alignLeft) { if (alignLeft) {
if (signChar) if (signChar) {
str += signChar; str += signChar;
if (prefixChar) }
if (prefixChar) {
str += prefixChar; str += prefixChar;
}
str.append(valueBuf); str.append(valueBuf);
str.append(padLen, ' '); str.append(padLen, ' ');
} else { } else {
if (padZero) { if (padZero) {
// sign, then prefix, then zero padding // sign, then prefix, then zero padding
if (signChar) if (signChar) {
str += signChar; str += signChar;
if (prefixChar) }
if (prefixChar) {
str += prefixChar; str += prefixChar;
}
str.append(padLen, '0'); str.append(padLen, '0');
} else { } else {
// space padding, then sign, then prefix // space padding, then sign, then prefix
str.append(padLen, ' '); str.append(padLen, ' ');
if (signChar) if (signChar) {
str += signChar; str += signChar;
if (prefixChar) }
if (prefixChar) {
str += prefixChar; str += prefixChar;
}
} }
str.append(valueBuf); str.append(valueBuf);
} }

View File

@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: MIT */ // SPDX-License-Identifier: MIT
#include "asm/fstack.hpp" #include "asm/fstack.hpp"
#include <sys/stat.h> #include <sys/stat.h>
@@ -90,8 +90,9 @@ std::shared_ptr<std::string> fstk_GetUniqueIDStr() {
std::shared_ptr<std::string> &str = contextStack.top().uniqueIDStr; std::shared_ptr<std::string> &str = contextStack.top().uniqueIDStr;
// If a unique ID is allowed but has not been generated yet, generate one now. // If a unique ID is allowed but has not been generated yet, generate one now.
if (str && str->empty()) if (str && str->empty()) {
*str = "_u"s + std::to_string(nextUniqueID++); *str = "_u"s + std::to_string(nextUniqueID++);
}
return str; return str;
} }
@@ -103,27 +104,32 @@ MacroArgs *fstk_GetCurrentMacroArgs() {
} }
void fstk_AddIncludePath(std::string const &path) { void fstk_AddIncludePath(std::string const &path) {
if (path.empty()) if (path.empty()) {
return; return;
}
std::string &includePath = includePaths.emplace_back(path); std::string &includePath = includePaths.emplace_back(path);
if (includePath.back() != '/') if (includePath.back() != '/') {
includePath += '/'; includePath += '/';
}
} }
void fstk_SetPreIncludeFile(std::string const &path) { void fstk_SetPreIncludeFile(std::string const &path) {
if (!preIncludeName.empty()) if (!preIncludeName.empty()) {
warnx("Overriding pre-included filename %s", preIncludeName.c_str()); warnx("Overriding pre-included filename %s", preIncludeName.c_str());
}
preIncludeName = path; preIncludeName = path;
if (verbose) if (verbose) {
printf("Pre-included filename %s\n", preIncludeName.c_str()); printf("Pre-included filename %s\n", preIncludeName.c_str());
}
} }
static void printDep(std::string const &path) { static void printDep(std::string const &path) {
if (dependFile) { if (dependFile) {
fprintf(dependFile, "%s: %s\n", targetFileName.c_str(), path.c_str()); fprintf(dependFile, "%s: %s\n", targetFileName.c_str(), path.c_str());
if (generatePhonyDeps) if (generatePhonyDeps) {
fprintf(dependFile, "%s:\n", path.c_str()); fprintf(dependFile, "%s:\n", path.c_str());
}
} }
} }
@@ -141,20 +147,22 @@ std::optional<std::string> fstk_FindFile(std::string const &path) {
} }
errno = ENOENT; errno = ENOENT;
if (generatedMissingIncludes) if (generatedMissingIncludes) {
printDep(path); printDep(path);
}
return std::nullopt; return std::nullopt;
} }
bool yywrap() { bool yywrap() {
uint32_t ifDepth = lexer_GetIFDepth(); uint32_t ifDepth = lexer_GetIFDepth();
if (ifDepth != 0) if (ifDepth != 0) {
fatalerror( fatalerror(
"Ended block with %" PRIu32 " unterminated IF construct%s\n", "Ended block with %" PRIu32 " unterminated IF construct%s\n",
ifDepth, ifDepth,
ifDepth == 1 ? "" : "s" ifDepth == 1 ? "" : "s"
); );
}
if (Context &context = contextStack.top(); context.fileInfo->type == NODE_REPT) { if (Context &context = contextStack.top(); context.fileInfo->type == NODE_REPT) {
// The context is a REPT or FOR block, which may loop // The context is a REPT or FOR block, which may loop
@@ -177,8 +185,9 @@ bool yywrap() {
Symbol *sym = sym_AddVar(context.forName, context.forValue); Symbol *sym = sym_AddVar(context.forName, context.forValue);
// This error message will refer to the current iteration // This error message will refer to the current iteration
if (sym->type != SYM_VAR) if (sym->type != SYM_VAR) {
fatalerror("Failed to update FOR symbol value\n"); fatalerror("Failed to update FOR symbol value\n");
}
} }
// Advance to the next iteration // Advance to the next iteration
fileInfoIters.front()++; fileInfoIters.front()++;
@@ -199,8 +208,9 @@ bool yywrap() {
} }
static void checkRecursionDepth() { static void checkRecursionDepth() {
if (contextStack.size() > maxRecursionDepth) if (contextStack.size() > maxRecursionDepth) {
fatalerror("Recursion limit (%zu) exceeded\n", maxRecursionDepth); fatalerror("Recursion limit (%zu) exceeded\n", maxRecursionDepth);
}
} }
static bool newFileContext(std::string const &filePath, bool updateStateNow) { static bool newFileContext(std::string const &filePath, bool updateStateNow) {
@@ -210,7 +220,7 @@ static bool newFileContext(std::string const &filePath, bool updateStateNow) {
std::shared_ptr<MacroArgs> macroArgs = nullptr; std::shared_ptr<MacroArgs> macroArgs = nullptr;
auto fileInfo = auto fileInfo =
std::make_shared<FileStackNode>(NODE_MACRO, filePath == "-" ? "<stdin>" : filePath); std::make_shared<FileStackNode>(NODE_FILE, filePath == "-" ? "<stdin>" : filePath);
if (!contextStack.empty()) { if (!contextStack.empty()) {
Context &oldContext = contextStack.top(); Context &oldContext = contextStack.top();
fileInfo->parent = oldContext.fileInfo; fileInfo->parent = oldContext.fileInfo;
@@ -298,8 +308,9 @@ void fstk_RunInclude(std::string const &path, bool preInclude) {
if (!fullPath) { if (!fullPath) {
if (generatedMissingIncludes && !preInclude) { if (generatedMissingIncludes && !preInclude) {
if (verbose) if (verbose) {
printf("Aborting (-MG) on INCLUDE file '%s' (%s)\n", path.c_str(), strerror(errno)); printf("Aborting (-MG) on INCLUDE file '%s' (%s)\n", path.c_str(), strerror(errno));
}
failedOnMissingInclude = true; failedOnMissingInclude = true;
} else { } else {
error("Unable to open included file '%s': %s\n", path.c_str(), strerror(errno)); error("Unable to open included file '%s': %s\n", path.c_str(), strerror(errno));
@@ -307,18 +318,20 @@ void fstk_RunInclude(std::string const &path, bool preInclude) {
return; return;
} }
if (!newFileContext(*fullPath, false)) if (!newFileContext(*fullPath, false)) {
fatalerror("Failed to set up lexer for file include\n"); fatalerror("Failed to set up lexer for file include\n");
}
} }
void fstk_RunMacro(std::string const &macroName, std::shared_ptr<MacroArgs> macroArgs) { void fstk_RunMacro(std::string const &macroName, std::shared_ptr<MacroArgs> macroArgs) {
Symbol *macro = sym_FindExactSymbol(macroName); Symbol *macro = sym_FindExactSymbol(macroName);
if (!macro) { if (!macro) {
if (sym_IsPurgedExact(macroName)) if (sym_IsPurgedExact(macroName)) {
error("Macro \"%s\" not defined; it was purged\n", macroName.c_str()); error("Macro \"%s\" not defined; it was purged\n", macroName.c_str());
else } else {
error("Macro \"%s\" not defined\n", macroName.c_str()); error("Macro \"%s\" not defined\n", macroName.c_str());
}
return; return;
} }
if (macro->type != SYM_MACRO) { if (macro->type != SYM_MACRO) {
@@ -330,8 +343,9 @@ void fstk_RunMacro(std::string const &macroName, std::shared_ptr<MacroArgs> macr
} }
void fstk_RunRept(uint32_t count, int32_t reptLineNo, ContentSpan const &span) { void fstk_RunRept(uint32_t count, int32_t reptLineNo, ContentSpan const &span) {
if (count == 0) if (count == 0) {
return; return;
}
newReptContext(reptLineNo, span, count); newReptContext(reptLineNo, span, count);
} }
@@ -344,24 +358,28 @@ void fstk_RunFor(
int32_t reptLineNo, int32_t reptLineNo,
ContentSpan const &span ContentSpan const &span
) { ) {
if (Symbol *sym = sym_AddVar(symName, start); sym->type != SYM_VAR) if (Symbol *sym = sym_AddVar(symName, start); sym->type != SYM_VAR) {
return; return;
}
uint32_t count = 0; uint32_t count = 0;
if (step > 0 && start < stop) if (step > 0 && start < stop) {
count = (static_cast<int64_t>(stop) - start - 1) / step + 1; count = (static_cast<int64_t>(stop) - start - 1) / step + 1;
else if (step < 0 && stop < start) } else if (step < 0 && stop < start) {
count = (static_cast<int64_t>(start) - stop - 1) / -static_cast<int64_t>(step) + 1; count = (static_cast<int64_t>(start) - stop - 1) / -static_cast<int64_t>(step) + 1;
else if (step == 0) } else if (step == 0) {
error("FOR cannot have a step value of 0\n"); error("FOR cannot have a step value of 0\n");
}
if ((step > 0 && start > stop) || (step < 0 && start < stop)) if ((step > 0 && start > stop) || (step < 0 && start < stop)) {
warning( warning(
WARNING_BACKWARDS_FOR, "FOR goes backwards from %d to %d by %d\n", start, stop, step WARNING_BACKWARDS_FOR, "FOR goes backwards from %d to %d by %d\n", start, stop, step
); );
}
if (count == 0) if (count == 0) {
return; return;
}
Context &context = newReptContext(reptLineNo, span, count); Context &context = newReptContext(reptLineNo, span, count);
context.isForLoop = true; context.isForLoop = true;
@@ -385,17 +403,20 @@ bool fstk_Break() {
} }
void fstk_NewRecursionDepth(size_t newDepth) { void fstk_NewRecursionDepth(size_t newDepth) {
if (contextStack.size() > newDepth + 1) if (contextStack.size() > newDepth + 1) {
fatalerror("Recursion limit (%zu) exceeded\n", newDepth); fatalerror("Recursion limit (%zu) exceeded\n", newDepth);
}
maxRecursionDepth = newDepth; maxRecursionDepth = newDepth;
} }
void fstk_Init(std::string const &mainPath, size_t maxDepth) { void fstk_Init(std::string const &mainPath, size_t maxDepth) {
if (!newFileContext(mainPath, true)) if (!newFileContext(mainPath, true)) {
fatalerror("Failed to open main file\n"); fatalerror("Failed to open main file\n");
}
maxRecursionDepth = maxDepth; maxRecursionDepth = maxDepth;
if (!preIncludeName.empty()) if (!preIncludeName.empty()) {
fstk_RunInclude(preIncludeName, true); fstk_RunInclude(preIncludeName, true);
}
} }

View File

@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: MIT */ // SPDX-License-Identifier: MIT
#include "asm/lexer.hpp" #include "asm/lexer.hpp"
#include <sys/stat.h> #include <sys/stat.h>
@@ -19,7 +19,7 @@
#include <unistd.h> #include <unistd.h>
#endif #endif
#include "helpers.hpp" // assume, QUOTEDSTRLEN #include "helpers.hpp"
#include "util.hpp" #include "util.hpp"
#include "asm/fixpoint.hpp" #include "asm/fixpoint.hpp"
@@ -35,15 +35,14 @@
// Neither MSVC nor MinGW provide `mmap` // Neither MSVC nor MinGW provide `mmap`
#if defined(_MSC_VER) || defined(__MINGW32__) #if defined(_MSC_VER) || defined(__MINGW32__)
// clang-format off // clang-format off: maintain `include` order
// (we need these `include`s in this order) #define WIN32_LEAN_AND_MEAN // Include less from `windows.h`
#define WIN32_LEAN_AND_MEAN // include less from windows.h
#include <windows.h> // target architecture #include <windows.h> // target architecture
#include <fileapi.h> // CreateFileA
#include <winbase.h> // CreateFileMappingA
#include <memoryapi.h> // MapViewOfFile
#include <handleapi.h> // CloseHandle
// clang-format on // clang-format on
#include <fileapi.h> // CreateFileA
#include <handleapi.h> // CloseHandle
#include <memoryapi.h> // MapViewOfFile
#include <winbase.h> // CreateFileMappingA
static char *mapFile(int fd, std::string const &path, size_t) { static char *mapFile(int fd, std::string const &path, size_t) {
void *mappingAddr = nullptr; void *mappingAddr = nullptr;
@@ -64,7 +63,7 @@ static char *mapFile(int fd, std::string const &path, size_t) {
} }
CloseHandle(file); CloseHandle(file);
} }
return (char *)mappingAddr; return static_cast<char *>(mappingAddr);
} }
struct FileUnmapDeleter { struct FileUnmapDeleter {
@@ -82,8 +81,9 @@ static char *mapFile(int fd, std::string const &path, size_t size) {
// The implementation may not support MAP_PRIVATE; try again with MAP_SHARED // The implementation may not support MAP_PRIVATE; try again with MAP_SHARED
// instead, offering, I believe, weaker guarantees about external modifications to // instead, offering, I believe, weaker guarantees about external modifications to
// the file while reading it. That's still better than not opening it at all, though. // the file while reading it. That's still better than not opening it at all, though.
if (verbose) if (verbose) {
printf("mmap(%s, MAP_PRIVATE) failed, retrying with MAP_SHARED\n", path.c_str()); printf("mmap(%s, MAP_PRIVATE) failed, retrying with MAP_SHARED\n", path.c_str());
}
mappingAddr = mmap(nullptr, size, PROT_READ, MAP_SHARED, fd, 0); mappingAddr = mmap(nullptr, size, PROT_READ, MAP_SHARED, fd, 0);
} }
return mappingAddr != MAP_FAILED ? static_cast<char *>(mappingAddr) : nullptr; return mappingAddr != MAP_FAILED ? static_cast<char *>(mappingAddr) : nullptr;
@@ -120,8 +120,9 @@ struct CaseInsensitive {
size_t operator()(std::string const &str) const { size_t operator()(std::string const &str) const {
size_t hash = 0x811C9DC5; size_t hash = 0x811C9DC5;
for (char const &c : str) for (char const &c : str) {
hash = (hash ^ toupper(c)) * 16777619; hash = (hash ^ toupper(c)) * 16777619;
}
return hash; return hash;
} }
@@ -139,58 +140,58 @@ struct CaseInsensitive {
// Tokens / keywords not handled here are handled in `yylex_NORMAL`'s switch. // Tokens / keywords not handled here are handled in `yylex_NORMAL`'s switch.
// This assumes that no two keywords have the same name. // This assumes that no two keywords have the same name.
static std::unordered_map<std::string, int, CaseInsensitive, CaseInsensitive> keywordDict = { static std::unordered_map<std::string, int, CaseInsensitive, CaseInsensitive> keywordDict = {
{"ADC", T_(Z80_ADC) }, {"ADC", T_(SM83_ADC) },
{"ADD", T_(Z80_ADD) }, {"ADD", T_(SM83_ADD) },
{"AND", T_(Z80_AND) }, {"AND", T_(SM83_AND) },
{"BIT", T_(Z80_BIT) }, {"BIT", T_(SM83_BIT) },
{"CALL", T_(Z80_CALL) }, {"CALL", T_(SM83_CALL) },
{"CCF", T_(Z80_CCF) }, {"CCF", T_(SM83_CCF) },
{"CPL", T_(Z80_CPL) }, {"CPL", T_(SM83_CPL) },
{"CP", T_(Z80_CP) }, {"CP", T_(SM83_CP) },
{"DAA", T_(Z80_DAA) }, {"DAA", T_(SM83_DAA) },
{"DEC", T_(Z80_DEC) }, {"DEC", T_(SM83_DEC) },
{"DI", T_(Z80_DI) }, {"DI", T_(SM83_DI) },
{"EI", T_(Z80_EI) }, {"EI", T_(SM83_EI) },
{"HALT", T_(Z80_HALT) }, {"HALT", T_(SM83_HALT) },
{"INC", T_(Z80_INC) }, {"INC", T_(SM83_INC) },
{"JP", T_(Z80_JP) }, {"JP", T_(SM83_JP) },
{"JR", T_(Z80_JR) }, {"JR", T_(SM83_JR) },
{"LD", T_(Z80_LD) }, {"LD", T_(SM83_LD) },
{"LDI", T_(Z80_LDI) }, {"LDI", T_(SM83_LDI) },
{"LDD", T_(Z80_LDD) }, {"LDD", T_(SM83_LDD) },
{"LDIO", T_(Z80_LDH) }, {"LDIO", T_(SM83_LDH) },
{"LDH", T_(Z80_LDH) }, {"LDH", T_(SM83_LDH) },
{"NOP", T_(Z80_NOP) }, {"NOP", T_(SM83_NOP) },
{"OR", T_(Z80_OR) }, {"OR", T_(SM83_OR) },
{"POP", T_(Z80_POP) }, {"POP", T_(SM83_POP) },
{"PUSH", T_(Z80_PUSH) }, {"PUSH", T_(SM83_PUSH) },
{"RES", T_(Z80_RES) }, {"RES", T_(SM83_RES) },
{"RETI", T_(Z80_RETI) }, {"RETI", T_(SM83_RETI) },
{"RET", T_(Z80_RET) }, {"RET", T_(SM83_RET) },
{"RLCA", T_(Z80_RLCA) }, {"RLCA", T_(SM83_RLCA) },
{"RLC", T_(Z80_RLC) }, {"RLC", T_(SM83_RLC) },
{"RLA", T_(Z80_RLA) }, {"RLA", T_(SM83_RLA) },
{"RL", T_(Z80_RL) }, {"RL", T_(SM83_RL) },
{"RRC", T_(Z80_RRC) }, {"RRC", T_(SM83_RRC) },
{"RRCA", T_(Z80_RRCA) }, {"RRCA", T_(SM83_RRCA) },
{"RRA", T_(Z80_RRA) }, {"RRA", T_(SM83_RRA) },
{"RR", T_(Z80_RR) }, {"RR", T_(SM83_RR) },
{"RST", T_(Z80_RST) }, {"RST", T_(SM83_RST) },
{"SBC", T_(Z80_SBC) }, {"SBC", T_(SM83_SBC) },
{"SCF", T_(Z80_SCF) }, {"SCF", T_(SM83_SCF) },
{"SET", T_(Z80_SET) }, {"SET", T_(SM83_SET) },
{"SLA", T_(Z80_SLA) }, {"SLA", T_(SM83_SLA) },
{"SRA", T_(Z80_SRA) }, {"SRA", T_(SM83_SRA) },
{"SRL", T_(Z80_SRL) }, {"SRL", T_(SM83_SRL) },
{"STOP", T_(Z80_STOP) }, {"STOP", T_(SM83_STOP) },
{"SUB", T_(Z80_SUB) }, {"SUB", T_(SM83_SUB) },
{"SWAP", T_(Z80_SWAP) }, {"SWAP", T_(SM83_SWAP) },
{"XOR", T_(Z80_XOR) }, {"XOR", T_(SM83_XOR) },
{"NZ", T_(CC_NZ) }, {"NZ", T_(CC_NZ) },
{"Z", T_(CC_Z) }, {"Z", T_(CC_Z) },
{"NC", T_(CC_NC) }, {"NC", T_(CC_NC) },
// There is no `T_(CC_C)`; it's handled before as `T_(TOKEN_C)` // There is no `T_(CC_C)`; it's handled before as `T_(TOKEN_C)`
{"AF", T_(MODE_AF) }, {"AF", T_(MODE_AF) },
{"BC", T_(MODE_BC) }, {"BC", T_(MODE_BC) },
@@ -315,7 +316,7 @@ static std::unordered_map<std::string, int, CaseInsensitive, CaseInsensitive> ke
{"RB", T_(POP_RB) }, {"RB", T_(POP_RB) },
{"RW", T_(POP_RW) }, {"RW", T_(POP_RW) },
// There is no `T_(POP_RL)`; it's handled before as `T_(Z80_RL)` // There is no `T_(POP_RL)`; it's handled before as `T_(SM83_RL)`
{"EQU", T_(POP_EQU) }, {"EQU", T_(POP_EQU) },
{"EQUS", T_(POP_EQUS) }, {"EQUS", T_(POP_EQUS) },
@@ -344,7 +345,7 @@ bool lexer_AtTopLevel() {
void LexerState::clear(uint32_t lineNo_) { void LexerState::clear(uint32_t lineNo_) {
mode = LEXER_NORMAL; mode = LEXER_NORMAL;
atLineStart = true; // yylex() will init colNo due to this atLineStart = true;
lastToken = T_(YYEOF); lastToken = T_(YYEOF);
ifStack.clear(); ifStack.clear();
@@ -364,7 +365,6 @@ void LexerState::clear(uint32_t lineNo_) {
static void nextLine() { static void nextLine() {
lexerState->lineNo++; lexerState->lineNo++;
lexerState->colNo = 1;
} }
uint32_t lexer_GetIFDepth() { uint32_t lexer_GetIFDepth() {
@@ -376,8 +376,9 @@ void lexer_IncIFDepth() {
} }
void lexer_DecIFDepth() { void lexer_DecIFDepth() {
if (lexerState->ifStack.empty()) if (lexerState->ifStack.empty()) {
fatalerror("Found ENDC outside of an IF construct\n"); fatalerror("Found ENDC outside of an IF construct\n");
}
lexerState->ifStack.pop_front(); lexerState->ifStack.pop_front();
} }
@@ -406,8 +407,9 @@ bool LexerState::setFileAsNextState(std::string const &filePath, bool updateStat
if (filePath == "-") { if (filePath == "-") {
path = "<stdin>"; path = "<stdin>";
content.emplace<BufferedContent>(STDIN_FILENO); content.emplace<BufferedContent>(STDIN_FILENO);
if (verbose) if (verbose) {
printf("Opening stdin\n"); printf("Opening stdin\n");
}
} else { } else {
struct stat statBuf; struct stat statBuf;
if (stat(filePath.c_str(), &statBuf) != 0) { if (stat(filePath.c_str(), &statBuf) != 0) {
@@ -431,8 +433,9 @@ bool LexerState::setFileAsNextState(std::string const &filePath, bool updateStat
content.emplace<ViewedContent>( content.emplace<ViewedContent>(
std::shared_ptr<char[]>(mappingAddr, FileUnmapDeleter(size)), size std::shared_ptr<char[]>(mappingAddr, FileUnmapDeleter(size)), size
); );
if (verbose) if (verbose) {
printf("File \"%s\" is mmap()ped\n", path.c_str()); printf("File \"%s\" is mmap()ped\n", path.c_str());
}
isMmapped = true; isMmapped = true;
} }
} }
@@ -453,10 +456,11 @@ bool LexerState::setFileAsNextState(std::string const &filePath, bool updateStat
} }
clear(0); clear(0);
if (updateStateNow) if (updateStateNow) {
lexerState = this; lexerState = this;
else } else {
lexerStateEOL = this; lexerStateEOL = this;
}
return true; return true;
} }
@@ -500,45 +504,51 @@ BufferedContent::~BufferedContent() {
} }
void BufferedContent::advance() { void BufferedContent::advance() {
assume(offset < LEXER_BUF_SIZE); assume(offset < std::size(buf));
offset++; offset++;
if (offset == LEXER_BUF_SIZE) if (offset == std::size(buf)) {
offset = 0; // Wrap around if necessary offset = 0; // Wrap around if necessary
assume(size > 0); }
size--; if (size > 0) {
size--;
}
} }
void BufferedContent::refill() { void BufferedContent::refill() {
size_t target = LEXER_BUF_SIZE - size; // Aim: making the buf full size_t target = std::size(buf) - size; // Aim: making the buf full
// Compute the index we'll start writing to // Compute the index we'll start writing to
size_t startIndex = (offset + size) % LEXER_BUF_SIZE; size_t startIndex = (offset + size) % std::size(buf);
// If the range to fill passes over the buffer wrapping point, we need two reads // If the range to fill passes over the buffer wrapping point, we need two reads
if (startIndex + target > LEXER_BUF_SIZE) { if (startIndex + target > std::size(buf)) {
size_t nbExpectedChars = LEXER_BUF_SIZE - startIndex; size_t nbExpectedChars = std::size(buf) - startIndex;
size_t nbReadChars = readMore(startIndex, nbExpectedChars); size_t nbReadChars = readMore(startIndex, nbExpectedChars);
startIndex += nbReadChars; startIndex += nbReadChars;
if (startIndex == LEXER_BUF_SIZE) if (startIndex == std::size(buf)) {
startIndex = 0; startIndex = 0;
}
// If the read was incomplete, don't perform a second read // If the read was incomplete, don't perform a second read
target -= nbReadChars; target -= nbReadChars;
if (nbReadChars < nbExpectedChars) if (nbReadChars < nbExpectedChars) {
target = 0; target = 0;
}
} }
if (target != 0) if (target != 0) {
readMore(startIndex, target); readMore(startIndex, target);
}
} }
size_t BufferedContent::readMore(size_t startIndex, size_t nbChars) { size_t BufferedContent::readMore(size_t startIndex, size_t nbChars) {
// This buffer overflow made me lose WEEKS of my life. Never again. // This buffer overflow made me lose WEEKS of my life. Never again.
assume(startIndex + nbChars <= LEXER_BUF_SIZE); assume(startIndex + nbChars <= std::size(buf));
ssize_t nbReadChars = read(fd, &buf[startIndex], nbChars); ssize_t nbReadChars = read(fd, &buf[startIndex], nbChars);
if (nbReadChars == -1) if (nbReadChars == -1) {
fatalerror("Error while reading \"%s\": %s\n", lexerState->path.c_str(), strerror(errno)); fatalerror("Error while reading \"%s\": %s\n", lexerState->path.c_str(), strerror(errno));
}
size += nbReadChars; size += nbReadChars;
@@ -557,19 +567,22 @@ void lexer_ToggleStringExpansion(bool enable) {
// Functions for the actual lexer to obtain characters // Functions for the actual lexer to obtain characters
static void beginExpansion(std::shared_ptr<std::string> str, std::optional<std::string> name) { static void beginExpansion(std::shared_ptr<std::string> str, std::optional<std::string> name) {
if (name) if (name) {
lexer_CheckRecursionDepth(); lexer_CheckRecursionDepth();
}
// Do not expand empty strings // Do not expand empty strings
if (str->empty()) if (str->empty()) {
return; return;
}
lexerState->expansions.push_front({.name = name, .contents = str, .offset = 0}); lexerState->expansions.push_front({.name = name, .contents = str, .offset = 0});
} }
void lexer_CheckRecursionDepth() { void lexer_CheckRecursionDepth() {
if (lexerState->expansions.size() > maxRecursionDepth + 1) if (lexerState->expansions.size() > maxRecursionDepth + 1) {
fatalerror("Recursion limit (%zu) exceeded\n", maxRecursionDepth); fatalerror("Recursion limit (%zu) exceeded\n", maxRecursionDepth);
}
} }
static bool isMacroChar(char c) { static bool isMacroChar(char c) {
@@ -620,10 +633,11 @@ static uint32_t readBracketedMacroArgNum() {
Symbol const *sym = sym_FindScopedValidSymbol(symName); Symbol const *sym = sym_FindScopedValidSymbol(symName);
if (!sym) { if (!sym) {
if (sym_IsPurgedScoped(symName)) if (sym_IsPurgedScoped(symName)) {
error("Bracketed symbol \"%s\" does not exist; it was purged\n", symName.c_str()); error("Bracketed symbol \"%s\" does not exist; it was purged\n", symName.c_str());
else } else {
error("Bracketed symbol \"%s\" does not exist\n", symName.c_str()); error("Bracketed symbol \"%s\" does not exist\n", symName.c_str());
}
num = 0; num = 0;
symbolError = true; symbolError = true;
} else if (!sym->isNumeric()) { } else if (!sym->isNumeric()) {
@@ -708,21 +722,25 @@ static std::shared_ptr<std::string> readMacroArg(char name) {
int LexerState::peekChar() { int LexerState::peekChar() {
// This is `.peekCharAhead()` modified for zero lookahead distance // This is `.peekCharAhead()` modified for zero lookahead distance
for (Expansion &exp : expansions) { for (Expansion &exp : expansions) {
if (exp.offset < exp.size()) if (exp.offset < exp.size()) {
return static_cast<uint8_t>((*exp.contents)[exp.offset]); return static_cast<uint8_t>((*exp.contents)[exp.offset]);
}
} }
if (content.holds<ViewedContent>()) { if (content.holds<ViewedContent>()) {
auto &view = content.get<ViewedContent>(); auto &view = content.get<ViewedContent>();
if (view.offset < view.span.size) if (view.offset < view.span.size) {
return static_cast<uint8_t>(view.span.ptr[view.offset]); return static_cast<uint8_t>(view.span.ptr[view.offset]);
}
} else { } else {
auto &cbuf = content.get<BufferedContent>(); auto &cbuf = content.get<BufferedContent>();
if (cbuf.size == 0) if (cbuf.size == 0) {
cbuf.refill(); cbuf.refill();
assume(cbuf.offset < LEXER_BUF_SIZE); }
if (cbuf.size > 0) assume(cbuf.offset < std::size(cbuf.buf));
if (cbuf.size > 0) {
return static_cast<uint8_t>(cbuf.buf[cbuf.offset]); return static_cast<uint8_t>(cbuf.buf[cbuf.offset]);
}
} }
// If there aren't enough chars, give up // If there aren't enough chars, give up
@@ -737,22 +755,26 @@ int LexerState::peekCharAhead() {
// An expansion that has reached its end will have `exp.offset` == `exp.size()`, // An expansion that has reached its end will have `exp.offset` == `exp.size()`,
// and `.peekCharAhead()` will continue with its parent // and `.peekCharAhead()` will continue with its parent
assume(exp.offset <= exp.size()); assume(exp.offset <= exp.size());
if (exp.offset + distance < exp.size()) if (exp.offset + distance < exp.size()) {
return static_cast<uint8_t>((*exp.contents)[exp.offset + distance]); return static_cast<uint8_t>((*exp.contents)[exp.offset + distance]);
}
distance -= exp.size() - exp.offset; distance -= exp.size() - exp.offset;
} }
if (content.holds<ViewedContent>()) { if (content.holds<ViewedContent>()) {
auto &view = content.get<ViewedContent>(); auto &view = content.get<ViewedContent>();
if (view.offset + distance < view.span.size) if (view.offset + distance < view.span.size) {
return static_cast<uint8_t>(view.span.ptr[view.offset + distance]); return static_cast<uint8_t>(view.span.ptr[view.offset + distance]);
}
} else { } else {
auto &cbuf = content.get<BufferedContent>(); auto &cbuf = content.get<BufferedContent>();
assume(distance < LEXER_BUF_SIZE); assume(distance < std::size(cbuf.buf));
if (cbuf.size <= distance) if (cbuf.size <= distance) {
cbuf.refill(); cbuf.refill();
if (cbuf.size > distance) }
return static_cast<uint8_t>(cbuf.buf[(cbuf.offset + distance) % LEXER_BUF_SIZE]); if (cbuf.size > distance) {
return static_cast<uint8_t>(cbuf.buf[(cbuf.offset + distance) % std::size(cbuf.buf)]);
}
} }
// If there aren't enough chars, give up // If there aren't enough chars, give up
@@ -766,8 +788,9 @@ static std::shared_ptr<std::string> readInterpolation(size_t depth);
static int peek() { static int peek() {
int c = lexerState->peekChar(); int c = lexerState->peekChar();
if (lexerState->macroArgScanDistance > 0) if (lexerState->macroArgScanDistance > 0) {
return c; return c;
}
lexerState->macroArgScanDistance++; // Do not consider again lexerState->macroArgScanDistance++; // Do not consider again
@@ -810,8 +833,9 @@ static int peek() {
static void shiftChar() { static void shiftChar() {
if (lexerState->capturing) { if (lexerState->capturing) {
if (lexerState->captureBuf) if (lexerState->captureBuf) {
lexerState->captureBuf->push_back(peek()); lexerState->captureBuf->push_back(peek());
}
lexerState->captureSize++; lexerState->captureSize++;
} }
@@ -828,7 +852,6 @@ restart:
} }
} else { } else {
// Advance within the file contents // Advance within the file contents
lexerState->colNo++;
if (lexerState->content.holds<ViewedContent>()) { if (lexerState->content.holds<ViewedContent>()) {
lexerState->content.get<ViewedContent>().offset++; lexerState->content.get<ViewedContent>().offset++;
} else { } else {
@@ -841,14 +864,16 @@ static int nextChar() {
int c = peek(); int c = peek();
// If not at EOF, advance read position // If not at EOF, advance read position
if (c != EOF) if (c != EOF) {
shiftChar(); shiftChar();
}
return c; return c;
} }
static void handleCRLF(int c) { static void handleCRLF(int c) {
if (c == '\r' && peek() == '\n') if (c == '\r' && peek() == '\n') {
shiftChar(); shiftChar();
}
} }
static auto scopedDisableExpansions() { static auto scopedDisableExpansions() {
@@ -866,18 +891,16 @@ uint32_t lexer_GetLineNo() {
return lexerState->lineNo; return lexerState->lineNo;
} }
uint32_t lexer_GetColNo() {
return lexerState->colNo;
}
void lexer_DumpStringExpansions() { void lexer_DumpStringExpansions() {
if (!lexerState) if (!lexerState) {
return; return;
}
for (Expansion &exp : lexerState->expansions) { for (Expansion &exp : lexerState->expansions) {
// Only register EQUS expansions, not string args // Only register EQUS expansions, not string args
if (exp.name) if (exp.name) {
fprintf(stderr, "while expanding symbol \"%s\"\n", exp.name->c_str()); fprintf(stderr, "while expanding symbol \"%s\"\n", exp.name->c_str());
}
} }
} }
@@ -893,12 +916,12 @@ static void discardBlockComment() {
error("Unterminated block comment\n"); error("Unterminated block comment\n");
return; return;
case '\r': case '\r':
// Handle CRLF before nextLine() since shiftChar updates colNo
handleCRLF(c); handleCRLF(c);
[[fallthrough]]; [[fallthrough]];
case '\n': case '\n':
if (lexerState->expansions.empty()) if (lexerState->expansions.empty()) {
nextLine(); nextLine();
}
continue; continue;
case '/': case '/':
if (peek() == '*') { if (peek() == '*') {
@@ -922,8 +945,9 @@ static void discardComment() {
for (;; shiftChar()) { for (;; shiftChar()) {
int c = peek(); int c = peek();
if (c == EOF || c == '\r' || c == '\n') if (c == EOF || c == '\r' || c == '\n') {
break; break;
}
} }
} }
@@ -935,10 +959,10 @@ static void discardLineContinuation() {
shiftChar(); shiftChar();
} else if (c == '\r' || c == '\n') { } else if (c == '\r' || c == '\n') {
shiftChar(); shiftChar();
// Handle CRLF before nextLine() since shiftChar updates colNo
handleCRLF(c); handleCRLF(c);
if (lexerState->expansions.empty()) if (lexerState->expansions.empty()) {
nextLine(); nextLine();
}
break; break;
} else if (c == ';') { } else if (c == ';') {
discardComment(); discardComment();
@@ -969,12 +993,14 @@ static uint32_t readNumber(int radix, uint32_t baseValue) {
for (;; shiftChar()) { for (;; shiftChar()) {
int c = peek(); int c = peek();
if (c == '_') if (c == '_') {
continue; continue;
else if (c < '0' || c > '0' + radix - 1) } else if (c < '0' || c > '0' + radix - 1) {
break; break;
if (value > (UINT32_MAX - (c - '0')) / radix) }
if (value > (UINT32_MAX - (c - '0')) / radix) {
warning(WARNING_LARGE_CONSTANT, "Integer constant is too large\n"); warning(WARNING_LARGE_CONSTANT, "Integer constant is too large\n");
}
value = value * radix + (c - '0'); value = value * radix + (c - '0');
} }
@@ -1006,8 +1032,9 @@ static uint32_t readFractionalPart(uint32_t integer) {
warning(WARNING_LARGE_CONSTANT, "Precision of fixed-point constant is too large\n"); warning(WARNING_LARGE_CONSTANT, "Precision of fixed-point constant is too large\n");
// Discard any additional digits // Discard any additional digits
shiftChar(); shiftChar();
while (c = peek(), (c >= '0' && c <= '9') || c == '_') while (c = peek(), (c >= '0' && c <= '9') || c == '_') {
shiftChar(); shiftChar();
}
break; break;
} }
value = value * 10 + (c - '0'); value = value * 10 + (c - '0');
@@ -1024,16 +1051,18 @@ static uint32_t readFractionalPart(uint32_t integer) {
} }
if (precision == 0) { if (precision == 0) {
if (state >= READFRACTIONALPART_PRECISION) if (state >= READFRACTIONALPART_PRECISION) {
error("Invalid fixed-point constant, no significant digits after 'q'\n"); error("Invalid fixed-point constant, no significant digits after 'q'\n");
}
precision = fixPrecision; precision = fixPrecision;
} else if (precision > 31) { } else if (precision > 31) {
error("Fixed-point constant precision must be between 1 and 31\n"); error("Fixed-point constant precision must be between 1 and 31\n");
precision = fixPrecision; precision = fixPrecision;
} }
if (integer >= (1ULL << (32 - precision))) if (integer >= (1ULL << (32 - precision))) {
warning(WARNING_LARGE_CONSTANT, "Magnitude of fixed-point constant is too large\n"); warning(WARNING_LARGE_CONSTANT, "Magnitude of fixed-point constant is too large\n");
}
// Cast to unsigned avoids undefined overflow behavior // Cast to unsigned avoids undefined overflow behavior
uint32_t fractional = uint32_t fractional =
@@ -1052,16 +1081,18 @@ static uint32_t readBinaryNumber() {
int bit; int bit;
// Check for '_' after digits in case one of the digits is '_' // Check for '_' after digits in case one of the digits is '_'
if (c == binDigits[0]) if (c == binDigits[0]) {
bit = 0; bit = 0;
else if (c == binDigits[1]) } else if (c == binDigits[1]) {
bit = 1; bit = 1;
else if (c == '_') } else if (c == '_') {
continue; continue;
else } else {
break; break;
if (value > (UINT32_MAX - bit) / 2) }
if (value > (UINT32_MAX - bit) / 2) {
warning(WARNING_LARGE_CONSTANT, "Integer constant is too large\n"); warning(WARNING_LARGE_CONSTANT, "Integer constant is too large\n");
}
value = value * 2 + bit; value = value * 2 + bit;
} }
@@ -1075,26 +1106,29 @@ static uint32_t readHexNumber() {
for (;; shiftChar()) { for (;; shiftChar()) {
int c = peek(); int c = peek();
if (c >= 'a' && c <= 'f') if (c >= 'a' && c <= 'f') {
c = c - 'a' + 10; c = c - 'a' + 10;
else if (c >= 'A' && c <= 'F') } else if (c >= 'A' && c <= 'F') {
c = c - 'A' + 10; c = c - 'A' + 10;
else if (c >= '0' && c <= '9') } else if (c >= '0' && c <= '9') {
c = c - '0'; c = c - '0';
else if (c == '_' && !empty) } else if (c == '_' && !empty) {
continue; continue;
else } else {
break; break;
}
if (value > (UINT32_MAX - c) / 16) if (value > (UINT32_MAX - c) / 16) {
warning(WARNING_LARGE_CONSTANT, "Integer constant is too large\n"); warning(WARNING_LARGE_CONSTANT, "Integer constant is too large\n");
}
value = value * 16 + c; value = value * 16 + c;
empty = false; empty = false;
} }
if (empty) if (empty) {
error("Invalid integer constant, no digits after '$'\n"); error("Invalid integer constant, no digits after '$'\n");
}
return value; return value;
} }
@@ -1110,34 +1144,37 @@ static uint32_t readGfxConstant() {
uint32_t pixel; uint32_t pixel;
// Check for '_' after digits in case one of the digits is '_' // Check for '_' after digits in case one of the digits is '_'
if (c == gfxDigits[0]) if (c == gfxDigits[0]) {
pixel = 0; pixel = 0;
else if (c == gfxDigits[1]) } else if (c == gfxDigits[1]) {
pixel = 1; pixel = 1;
else if (c == gfxDigits[2]) } else if (c == gfxDigits[2]) {
pixel = 2; pixel = 2;
else if (c == gfxDigits[3]) } else if (c == gfxDigits[3]) {
pixel = 3; pixel = 3;
else if (c == '_' && width > 0) } else if (c == '_' && width > 0) {
continue; continue;
else } else {
break; break;
}
if (width < 8) { if (width < 8) {
bitPlaneLower = bitPlaneLower << 1 | (pixel & 1); bitPlaneLower = bitPlaneLower << 1 | (pixel & 1);
bitPlaneUpper = bitPlaneUpper << 1 | (pixel >> 1); bitPlaneUpper = bitPlaneUpper << 1 | (pixel >> 1);
} }
if (width < 9) if (width < 9) {
width++; width++;
}
} }
if (width == 0) if (width == 0) {
error("Invalid graphics constant, no digits after '`'\n"); error("Invalid graphics constant, no digits after '`'\n");
else if (width == 9) } else if (width == 9) {
warning( warning(
WARNING_LARGE_CONSTANT, WARNING_LARGE_CONSTANT,
"Graphics constant is too long, only first 8 pixels considered\n" "Graphics constant is too long, only first 8 pixels considered\n"
); );
}
return bitPlaneUpper << 8 | bitPlaneLower; return bitPlaneUpper << 8 | bitPlaneLower;
} }
@@ -1165,8 +1202,9 @@ static Token readIdentifier(char firstChar, bool raw) {
identifier += c; identifier += c;
// If the char was a dot, mark the identifier as local // If the char was a dot, mark the identifier as local
if (c == '.') if (c == '.') {
tokenType = T_(LOCAL_ID); tokenType = T_(LOCAL_ID);
}
} }
// Attempt to check for a keyword if the identifier is not raw // Attempt to check for a keyword if the identifier is not raw
@@ -1180,8 +1218,9 @@ static Token readIdentifier(char firstChar, bool raw) {
} }
// Label scopes `.` and `..` are the only nonlocal identifiers that start with a dot // Label scopes `.` and `..` are the only nonlocal identifiers that start with a dot
if (identifier.find_first_not_of('.') == identifier.npos) if (identifier.find_first_not_of('.') == identifier.npos) {
tokenType = T_(ID); tokenType = T_(ID);
}
return Token(tokenType, identifier); return Token(tokenType, identifier);
} }
@@ -1189,8 +1228,9 @@ static Token readIdentifier(char firstChar, bool raw) {
// Functions to read strings // Functions to read strings
static std::shared_ptr<std::string> readInterpolation(size_t depth) { static std::shared_ptr<std::string> readInterpolation(size_t depth) {
if (depth > maxRecursionDepth) if (depth > maxRecursionDepth) {
fatalerror("Recursion limit (%zu) exceeded\n", maxRecursionDepth); fatalerror("Recursion limit (%zu) exceeded\n", maxRecursionDepth);
}
std::string fmtBuf; std::string fmtBuf;
FormatSpec fmt{}; FormatSpec fmt{};
@@ -1218,11 +1258,13 @@ static std::shared_ptr<std::string> readInterpolation(size_t depth) {
break; break;
} else if (c == ':' && !fmt.isFinished()) { // Format spec, only once } else if (c == ':' && !fmt.isFinished()) { // Format spec, only once
shiftChar(); shiftChar();
for (char f : fmtBuf) for (char f : fmtBuf) {
fmt.useCharacter(f); fmt.useCharacter(f);
}
fmt.finishCharacters(); fmt.finishCharacters();
if (!fmt.isValid()) if (!fmt.isValid()) {
error("Invalid format spec '%s'\n", fmtBuf.c_str()); error("Invalid format spec '%s'\n", fmtBuf.c_str());
}
fmtBuf.clear(); // Now that format has been set, restart at beginning of string fmtBuf.clear(); // Now that format has been set, restart at beginning of string
} else { } else {
shiftChar(); shiftChar();
@@ -1249,10 +1291,11 @@ static std::shared_ptr<std::string> readInterpolation(size_t depth) {
Symbol const *sym = sym_FindScopedValidSymbol(fmtBuf); Symbol const *sym = sym_FindScopedValidSymbol(fmtBuf);
if (!sym || !sym->isDefined()) { if (!sym || !sym->isDefined()) {
if (sym_IsPurgedScoped(fmtBuf)) if (sym_IsPurgedScoped(fmtBuf)) {
error("Interpolated symbol \"%s\" does not exist; it was purged\n", fmtBuf.c_str()); error("Interpolated symbol \"%s\" does not exist; it was purged\n", fmtBuf.c_str());
else } else {
error("Interpolated symbol \"%s\" does not exist\n", fmtBuf.c_str()); error("Interpolated symbol \"%s\" does not exist\n", fmtBuf.c_str());
}
} else if (sym->type == SYM_EQUS) { } else if (sym->type == SYM_EQUS) {
auto buf = std::make_shared<std::string>(); auto buf = std::make_shared<std::string>();
fmt.appendString(*buf, *sym->getEqus()); fmt.appendString(*buf, *sym->getEqus());
@@ -1326,7 +1369,6 @@ static std::string readString(bool raw) {
// Handle '\r' or '\n' (in multiline strings only, already handled above otherwise) // Handle '\r' or '\n' (in multiline strings only, already handled above otherwise)
if (c == '\r' || c == '\n') { if (c == '\r' || c == '\n') {
// Handle CRLF before nextLine() since shiftChar updates colNo
handleCRLF(c); handleCRLF(c);
nextLine(); nextLine();
c = '\n'; c = '\n';
@@ -1336,8 +1378,9 @@ static std::string readString(bool raw) {
case '"': case '"':
if (multiline) { if (multiline) {
// Only """ ends a multi-line string // Only """ ends a multi-line string
if (peek() != '"') if (peek() != '"') {
break; break;
}
shiftChar(); shiftChar();
if (peek() != '"') { if (peek() != '"') {
str += '"'; str += '"';
@@ -1348,8 +1391,9 @@ static std::string readString(bool raw) {
return str; return str;
case '\\': // Character escape or macro arg case '\\': // Character escape or macro arg
if (raw) if (raw) {
break; break;
}
c = peek(); c = peek();
switch (c) { switch (c) {
case '\\': case '\\':
@@ -1416,8 +1460,9 @@ static std::string readString(bool raw) {
break; break;
case '{': // Symbol interpolation case '{': // Symbol interpolation
if (raw) if (raw) {
break; break;
}
// We'll be exiting the string scope, so re-enable expansions // We'll be exiting the string scope, so re-enable expansions
// (Not interpolations, since they're handled by the function itself...) // (Not interpolations, since they're handled by the function itself...)
lexerState->disableMacroArgs = false; lexerState->disableMacroArgs = false;
@@ -1468,7 +1513,6 @@ static void appendStringLiteral(std::string &str, bool raw) {
// Handle '\r' or '\n' (in multiline strings only, already handled above otherwise) // Handle '\r' or '\n' (in multiline strings only, already handled above otherwise)
if (c == '\r' || c == '\n') { if (c == '\r' || c == '\n') {
// Handle CRLF before nextLine() since shiftChar updates colNo
handleCRLF(c); handleCRLF(c);
nextLine(); nextLine();
c = '\n'; c = '\n';
@@ -1478,12 +1522,14 @@ static void appendStringLiteral(std::string &str, bool raw) {
case '"': case '"':
if (multiline) { if (multiline) {
// Only """ ends a multi-line string // Only """ ends a multi-line string
if (peek() != '"') if (peek() != '"') {
break; break;
}
str += '"'; str += '"';
shiftChar(); shiftChar();
if (peek() != '"') if (peek() != '"') {
break; break;
}
str += '"'; str += '"';
shiftChar(); shiftChar();
} }
@@ -1491,8 +1537,9 @@ static void appendStringLiteral(std::string &str, bool raw) {
return; return;
case '\\': // Character escape or macro arg case '\\': // Character escape or macro arg
if (raw) if (raw) {
break; break;
}
c = peek(); c = peek();
switch (c) { switch (c) {
// Character escape // Character escape
@@ -1550,8 +1597,9 @@ static void appendStringLiteral(std::string &str, bool raw) {
break; break;
case '{': // Symbol interpolation case '{': // Symbol interpolation
if (raw) if (raw) {
break; break;
}
// We'll be exiting the string scope, so re-enable expansions // We'll be exiting the string scope, so re-enable expansions
// (Not interpolations, since they're handled by the function itself...) // (Not interpolations, since they're handled by the function itself...)
lexerState->disableMacroArgs = false; lexerState->disableMacroArgs = false;
@@ -1850,12 +1898,14 @@ static Token yylex_NORMAL() {
// An ELIF after a taken IF needs to not evaluate its condition // An ELIF after a taken IF needs to not evaluate its condition
if (token.type == T_(POP_ELIF) && lexerState->lastToken == T_(NEWLINE) if (token.type == T_(POP_ELIF) && lexerState->lastToken == T_(NEWLINE)
&& lexer_GetIFDepth() > 0 && lexer_RanIFBlock() && !lexer_ReachedELSEBlock()) && lexer_GetIFDepth() > 0 && lexer_RanIFBlock() && !lexer_ReachedELSEBlock()) {
return yylex_SKIP_TO_ENDC(); return yylex_SKIP_TO_ENDC();
}
// If a keyword, don't try to expand // If a keyword, don't try to expand
if (token.type != T_(ID) && token.type != T_(LOCAL_ID)) if (token.type != T_(ID) && token.type != T_(LOCAL_ID)) {
return token; return token;
}
// `token` is either an `ID` or a `LOCAL_ID`, and both have a `std::string` value. // `token` is either an `ID` or a `LOCAL_ID`, and both have a `std::string` value.
assume(token.value.holds<std::string>()); assume(token.value.holds<std::string>());
@@ -1886,8 +1936,9 @@ static Token yylex_NORMAL() {
// character *immediately* follows the identifier. Thus, at the beginning of a line, // character *immediately* follows the identifier. Thus, at the beginning of a line,
// "Label:" and "mac:" are treated as label definitions, but "Label :" and "mac :" // "Label:" and "mac:" are treated as label definitions, but "Label :" and "mac :"
// are treated as macro invocations. // are treated as macro invocations.
if (token.type == T_(ID) && peek() == ':') if (token.type == T_(ID) && peek() == ':') {
token.type = T_(LABEL); token.type = T_(LABEL);
}
return token; return token;
} }
@@ -1917,8 +1968,9 @@ static Token yylex_RAW() {
shiftChar(); shiftChar();
c = peek(); c = peek();
// If not a line continuation, handle as a normal char // If not a line continuation, handle as a normal char
if (!isWhitespace(c) && c != '\n' && c != '\r') if (!isWhitespace(c) && c != '\n' && c != '\r') {
goto backslash; goto backslash;
}
// Line continuations count as "whitespace" // Line continuations count as "whitespace"
discardLineContinuation(); discardLineContinuation();
} else { } else {
@@ -1964,18 +2016,21 @@ static Token yylex_RAW() {
break; break;
case ',': // End of macro arg case ',': // End of macro arg
if (parenDepth == 0) if (parenDepth == 0) {
goto finish; goto finish;
}
goto append; goto append;
case '(': // Open parentheses inside macro args case '(': // Open parentheses inside macro args
if (parenDepth < UINT_MAX) if (parenDepth < UINT_MAX) {
parenDepth++; parenDepth++;
}
goto append; goto append;
case ')': // Close parentheses inside macro args case ')': // Close parentheses inside macro args
if (parenDepth > 0) if (parenDepth > 0) {
parenDepth--; parenDepth--;
}
goto append; goto append;
case '\\': // Character escape case '\\': // Character escape
@@ -2055,8 +2110,9 @@ finish:
// an empty raw string before it). This will not be treated as a // an empty raw string before it). This will not be treated as a
// macro argument. To pass an empty last argument, use a second // macro argument. To pass an empty last argument, use a second
// trailing comma. // trailing comma.
if (!str.empty()) if (!str.empty()) {
return Token(T_(STRING), str); return Token(T_(STRING), str);
}
lexer_SetMode(LEXER_NORMAL); lexer_SetMode(LEXER_NORMAL);
if (c == '\r' || c == '\n') { if (c == '\r' || c == '\n') {
@@ -2088,8 +2144,9 @@ static Token skipIfBlock(bool toEndc) {
for (;; shiftChar()) { for (;; shiftChar()) {
c = peek(); c = peek();
if (!isWhitespace(c)) if (!isWhitespace(c)) {
break; break;
}
} }
if (startsIdentifier(c)) { if (startsIdentifier(c)) {
@@ -2100,23 +2157,28 @@ static Token skipIfBlock(bool toEndc) {
break; break;
case T_(POP_ELIF): case T_(POP_ELIF):
if (lexer_ReachedELSEBlock()) if (lexer_ReachedELSEBlock()) {
fatalerror("Found ELIF after an ELSE block\n"); fatalerror("Found ELIF after an ELSE block\n");
if (!toEndc && lexer_GetIFDepth() == startingDepth) }
if (!toEndc && lexer_GetIFDepth() == startingDepth) {
return token; return token;
}
break; break;
case T_(POP_ELSE): case T_(POP_ELSE):
if (lexer_ReachedELSEBlock()) if (lexer_ReachedELSEBlock()) {
fatalerror("Found ELSE after an ELSE block\n"); fatalerror("Found ELSE after an ELSE block\n");
}
lexer_ReachELSEBlock(); lexer_ReachELSEBlock();
if (!toEndc && lexer_GetIFDepth() == startingDepth) if (!toEndc && lexer_GetIFDepth() == startingDepth) {
return token; return token;
}
break; break;
case T_(POP_ENDC): case T_(POP_ENDC):
if (lexer_GetIFDepth() == startingDepth) if (lexer_GetIFDepth() == startingDepth) {
return token; return token;
}
lexer_DecIFDepth(); lexer_DecIFDepth();
break; break;
@@ -2141,7 +2203,6 @@ static Token skipIfBlock(bool toEndc) {
} }
if (c == '\r' || c == '\n') { if (c == '\r' || c == '\n') {
// Handle CRLF before nextLine() since shiftChar updates colNo
handleCRLF(c); handleCRLF(c);
// Do this both on line continuations and plain EOLs // Do this both on line continuations and plain EOLs
nextLine(); nextLine();
@@ -2173,8 +2234,9 @@ static Token yylex_SKIP_TO_ENDR() {
for (;;) { for (;;) {
c = peek(); c = peek();
if (!isWhitespace(c)) if (!isWhitespace(c)) {
break; break;
}
shiftChar(); shiftChar();
} }
@@ -2188,8 +2250,9 @@ static Token yylex_SKIP_TO_ENDR() {
case T_(POP_ENDR): case T_(POP_ENDR):
depth--; depth--;
if (!depth) if (!depth) {
return Token(T_(YYEOF)); // yywrap() will finish the REPT/FOR loop return Token(T_(YYEOF)); // yywrap() will finish the REPT/FOR loop
}
break; break;
case T_(POP_IF): case T_(POP_IF):
@@ -2221,7 +2284,6 @@ static Token yylex_SKIP_TO_ENDR() {
} }
if (c == '\r' || c == '\n') { if (c == '\r' || c == '\n') {
// Handle CRLF before nextLine() since shiftChar updates colNo
handleCRLF(c); handleCRLF(c);
// Do this both on line continuations and plain EOLs // Do this both on line continuations and plain EOLs
nextLine(); nextLine();
@@ -2235,11 +2297,13 @@ yy::parser::symbol_type yylex() {
lexerState = lexerStateEOL; lexerState = lexerStateEOL;
lexerStateEOL = nullptr; lexerStateEOL = nullptr;
} }
if (lexerState->lastToken == T_(EOB) && yywrap()) if (lexerState->lastToken == T_(EOB) && yywrap()) {
return yy::parser::make_YYEOF(); return yy::parser::make_YYEOF();
}
// Newlines read within an expansion should not increase the line count // Newlines read within an expansion should not increase the line count
if (lexerState->atLineStart && lexerState->expansions.empty()) if (lexerState->atLineStart && lexerState->expansions.empty()) {
nextLine(); nextLine();
}
static Token (* const lexerModeFuncs[NB_LEXER_MODES])() = { static Token (* const lexerModeFuncs[NB_LEXER_MODES])() = {
yylex_NORMAL, yylex_NORMAL,
@@ -2286,22 +2350,23 @@ static Capture startCapture() {
auto &view = lexerState->content.get<ViewedContent>(); auto &view = lexerState->content.get<ViewedContent>();
return { return {
.lineNo = lineNo, .span = {.ptr = view.makeSharedContentPtr(), .size = 0} .lineNo = lineNo, .span = {.ptr = view.makeSharedContentPtr(), .size = 0}
}; };
} else { } else {
assume(lexerState->captureBuf == nullptr); assume(lexerState->captureBuf == nullptr);
lexerState->captureBuf = std::make_shared<std::vector<char>>(); lexerState->captureBuf = std::make_shared<std::vector<char>>();
// `.span.ptr == nullptr`; indicates to retrieve the capture buffer when done capturing // `.span.ptr == nullptr`; indicates to retrieve the capture buffer when done capturing
return { return {
.lineNo = lineNo, .span = {.ptr = nullptr, .size = 0} .lineNo = lineNo, .span = {.ptr = nullptr, .size = 0}
}; };
} }
} }
static void endCapture(Capture &capture) { static void endCapture(Capture &capture) {
// This being `nullptr` means we're capturing from the capture buffer, which is reallocated // This being `nullptr` means we're capturing from the capture buffer, which is reallocated
// during the whole capture process, and so MUST be retrieved at the end // during the whole capture process, and so MUST be retrieved at the end
if (!capture.span.ptr) if (!capture.span.ptr) {
capture.span.ptr = lexerState->makeSharedCaptureBufPtr(); capture.span.ptr = lexerState->makeSharedCaptureBufPtr();
}
capture.span.size = lexerState->captureSize; capture.span.size = lexerState->captureSize;
// ENDR/ENDM or EOF puts us past the start of the line // ENDR/ENDM or EOF puts us past the start of the line
@@ -2339,7 +2404,7 @@ Capture lexer_CaptureRept() {
endCapture(capture); endCapture(capture);
// The final ENDR has been captured, but we don't want it! // The final ENDR has been captured, but we don't want it!
// We know we have read exactly "ENDR", not e.g. an EQUS // We know we have read exactly "ENDR", not e.g. an EQUS
capture.span.size -= QUOTEDSTRLEN("ENDR"); capture.span.size -= literal_strlen("ENDR");
return capture; return capture;
} }
depth--; depth--;
@@ -2385,7 +2450,7 @@ Capture lexer_CaptureMacro() {
endCapture(capture); endCapture(capture);
// The ENDM has been captured, but we don't want it! // The ENDM has been captured, but we don't want it!
// We know we have read exactly "ENDM", not e.g. an EQUS // We know we have read exactly "ENDM", not e.g. an EQUS
capture.span.size -= QUOTEDSTRLEN("ENDM"); capture.span.size -= literal_strlen("ENDM");
return capture; return capture;
default: default:

View File

@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: MIT */ // SPDX-License-Identifier: MIT
#include "asm/macro.hpp" #include "asm/macro.hpp"
@@ -6,12 +6,8 @@
#include <string.h> #include <string.h>
#include <string> #include <string>
#include "helpers.hpp"
#include "asm/warning.hpp" #include "asm/warning.hpp"
#define MAXMACROARGS 99999
std::shared_ptr<std::string> MacroArgs::getArg(uint32_t i) const { std::shared_ptr<std::string> MacroArgs::getArg(uint32_t i) const {
uint32_t realIndex = i + shift - 1; uint32_t realIndex = i + shift - 1;
@@ -21,13 +17,15 @@ std::shared_ptr<std::string> MacroArgs::getArg(uint32_t i) const {
std::shared_ptr<std::string> MacroArgs::getAllArgs() const { std::shared_ptr<std::string> MacroArgs::getAllArgs() const {
size_t nbArgs = args.size(); size_t nbArgs = args.size();
if (shift >= nbArgs) if (shift >= nbArgs) {
return std::make_shared<std::string>(""); return std::make_shared<std::string>("");
}
size_t len = 0; size_t len = 0;
for (uint32_t i = shift; i < nbArgs; i++) for (uint32_t i = shift; i < nbArgs; i++) {
len += args[i]->length() + 1; // 1 for comma len += args[i]->length() + 1; // 1 for comma
}
auto str = std::make_shared<std::string>(); auto str = std::make_shared<std::string>();
str->reserve(len + 1); // 1 for comma str->reserve(len + 1); // 1 for comma
@@ -38,18 +36,18 @@ std::shared_ptr<std::string> MacroArgs::getAllArgs() const {
str->append(*arg); str->append(*arg);
// Commas go between args and after a last empty arg // Commas go between args and after a last empty arg
if (i < nbArgs - 1 || arg->empty()) if (i < nbArgs - 1 || arg->empty()) {
str->push_back(','); // no space after comma str->push_back(','); // no space after comma
}
} }
return str; return str;
} }
void MacroArgs::appendArg(std::shared_ptr<std::string> arg) { void MacroArgs::appendArg(std::shared_ptr<std::string> arg) {
if (arg->empty()) if (arg->empty()) {
warning(WARNING_EMPTY_MACRO_ARG, "Empty macro argument\n"); warning(WARNING_EMPTY_MACRO_ARG, "Empty macro argument\n");
if (args.size() == MAXMACROARGS) }
error("A maximum of " EXPAND_AND_STR(MAXMACROARGS) " arguments is allowed\n");
args.push_back(arg); args.push_back(arg);
} }

View File

@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: MIT */ // SPDX-License-Identifier: MIT
#include "asm/main.hpp" #include "asm/main.hpp"
@@ -37,30 +37,30 @@ static std::string make_escape(std::string &str) {
for (;;) { for (;;) {
// All dollars needs to be doubled // All dollars needs to be doubled
size_t nextPos = str.find("$", pos); size_t nextPos = str.find("$", pos);
if (nextPos == std::string::npos) if (nextPos == std::string::npos) {
break; break;
}
escaped.append(str, pos, nextPos - pos); escaped.append(str, pos, nextPos - pos);
escaped.append("$$"); escaped.append("$$");
pos = nextPos + QUOTEDSTRLEN("$"); pos = nextPos + literal_strlen("$");
} }
escaped.append(str, pos, str.length() - pos); escaped.append(str, pos, str.length() - pos);
return escaped; return escaped;
} }
// Short options // Short options
static char const *optstring = "b:D:Eg:I:M:o:P:p:Q:r:s:VvW:wX:"; static char const *optstring = "b:D:Eg:hI:M:o:P:p:Q:r:s:VvW:wX:";
// Variables for the long-only options // Variables for the long-only options
static int depType; // Variants of `-M` static int depType; // Variants of `-M`
// Equivalent long options // Equivalent long options
// Please keep in the same order as short opts // Please keep in the same order as short opts.
//
// Also, make sure long opts don't create ambiguity: // Also, make sure long opts don't create ambiguity:
// A long opt's name should start with the same letter as its short opt, // A long opt's name should start with the same letter as its short opt,
// except if it doesn't create any ambiguity (`verbose` versus `version`). // except if it doesn't create any ambiguity (`verbose` versus `version`).
// This is because long opt matching, even to a single char, is prioritized // This is because long opt matching, even to a single char, is prioritized
// over short opt matching // over short opt matching.
static option const longopts[] = { static option const longopts[] = {
{"binary-digits", required_argument, nullptr, 'b'}, {"binary-digits", required_argument, nullptr, 'b'},
{"define", required_argument, nullptr, 'D'}, {"define", required_argument, nullptr, 'D'},
@@ -69,6 +69,7 @@ static option const longopts[] = {
{"include", required_argument, nullptr, 'I'}, {"include", required_argument, nullptr, 'I'},
{"dependfile", required_argument, nullptr, 'M'}, {"dependfile", required_argument, nullptr, 'M'},
{"MG", no_argument, &depType, 'G'}, {"MG", no_argument, &depType, 'G'},
{"help", no_argument, nullptr, 'h'},
{"MP", no_argument, &depType, 'P'}, {"MP", no_argument, &depType, 'P'},
{"MT", required_argument, &depType, 'T'}, {"MT", required_argument, &depType, 'T'},
{"warning", required_argument, nullptr, 'W'}, {"warning", required_argument, nullptr, 'W'},
@@ -88,7 +89,7 @@ static option const longopts[] = {
static void printUsage() { static void printUsage() {
fputs( fputs(
"Usage: rgbasm [-EVvw] [-b chars] [-D name[=value]] [-g chars] [-I path]\n" "Usage: rgbasm [-EhVvw] [-b chars] [-D name[=value]] [-g chars] [-I path]\n"
" [-M depend_file] [-MG] [-MP] [-MT target_file] [-MQ target_file]\n" " [-M depend_file] [-MG] [-MP] [-MT target_file] [-MQ target_file]\n"
" [-o out_file] [-P include_file] [-p pad_value] [-Q precision]\n" " [-o out_file] [-P include_file] [-p pad_value] [-Q precision]\n"
" [-r depth] [-s features:state_file] [-W warning] [-X max_errors]\n" " [-r depth] [-s features:state_file] [-W warning] [-X max_errors]\n"
@@ -111,12 +112,14 @@ int main(int argc, char *argv[]) {
time_t now = time(nullptr); time_t now = time(nullptr);
// Support SOURCE_DATE_EPOCH for reproducible builds // Support SOURCE_DATE_EPOCH for reproducible builds
// https://reproducible-builds.org/docs/source-date-epoch/ // https://reproducible-builds.org/docs/source-date-epoch/
if (char const *sourceDateEpoch = getenv("SOURCE_DATE_EPOCH"); sourceDateEpoch) if (char const *sourceDateEpoch = getenv("SOURCE_DATE_EPOCH"); sourceDateEpoch) {
now = static_cast<time_t>(strtoul(sourceDateEpoch, nullptr, 0)); now = static_cast<time_t>(strtoul(sourceDateEpoch, nullptr, 0));
}
Defer closeDependFile{[&] { Defer closeDependFile{[&] {
if (dependFile) if (dependFile) {
fclose(dependFile); fclose(dependFile);
}
}}; }};
// Perform some init for below // Perform some init for below
@@ -128,23 +131,25 @@ int main(int argc, char *argv[]) {
opt_P(0); opt_P(0);
opt_Q(16); opt_Q(16);
sym_SetExportAll(false); sym_SetExportAll(false);
uint32_t maxDepth = DEFAULT_MAX_DEPTH; uint32_t maxDepth = 64;
char const *dependFileName = nullptr; char const *dependFileName = nullptr;
std::unordered_map<std::string, std::vector<StateFeature>> stateFileSpecs; std::unordered_map<std::string, std::vector<StateFeature>> stateFileSpecs;
std::string newTarget; std::string newTarget;
// Maximum of 100 errors only applies if rgbasm is printing errors to a terminal. // Maximum of 100 errors only applies if rgbasm is printing errors to a terminal.
if (isatty(STDERR_FILENO)) if (isatty(STDERR_FILENO)) {
maxErrors = 100; maxErrors = 100;
}
for (int ch; (ch = musl_getopt_long_only(argc, argv, optstring, longopts, nullptr)) != -1;) { for (int ch; (ch = musl_getopt_long_only(argc, argv, optstring, longopts, nullptr)) != -1;) {
switch (ch) { switch (ch) {
char *endptr; char *endptr;
case 'b': case 'b':
if (strlen(musl_optarg) == 2) if (strlen(musl_optarg) == 2) {
opt_B(musl_optarg); opt_B(musl_optarg);
else } else {
errx("Must specify exactly 2 characters for option 'b'"); errx("Must specify exactly 2 characters for option 'b'");
}
break; break;
char *equals; char *equals;
@@ -163,19 +168,25 @@ int main(int argc, char *argv[]) {
break; break;
case 'g': case 'g':
if (strlen(musl_optarg) == 4) if (strlen(musl_optarg) == 4) {
opt_G(musl_optarg); opt_G(musl_optarg);
else } else {
errx("Must specify exactly 4 characters for option 'g'"); errx("Must specify exactly 4 characters for option 'g'");
}
break; break;
case 'h':
printUsage();
exit(0);
case 'I': case 'I':
fstk_AddIncludePath(musl_optarg); fstk_AddIncludePath(musl_optarg);
break; break;
case 'M': case 'M':
if (dependFile) if (dependFile) {
warnx("Overriding dependfile %s", dependFileName); warnx("Overriding dependfile %s", dependFileName);
}
if (strcmp("-", musl_optarg)) { if (strcmp("-", musl_optarg)) {
dependFile = fopen(musl_optarg, "w"); dependFile = fopen(musl_optarg, "w");
dependFileName = musl_optarg; dependFileName = musl_optarg;
@@ -183,8 +194,9 @@ int main(int argc, char *argv[]) {
dependFile = stdout; dependFile = stdout;
dependFileName = "<stdout>"; dependFileName = "<stdout>";
} }
if (dependFile == nullptr) if (dependFile == nullptr) {
err("Failed to open dependfile \"%s\"", dependFileName); err("Failed to open dependfile \"%s\"", dependFileName);
}
break; break;
case 'o': case 'o':
@@ -199,11 +211,13 @@ int main(int argc, char *argv[]) {
case 'p': case 'p':
padByte = strtoul(musl_optarg, &endptr, 0); padByte = strtoul(musl_optarg, &endptr, 0);
if (musl_optarg[0] == '\0' || *endptr != '\0') if (musl_optarg[0] == '\0' || *endptr != '\0') {
errx("Invalid argument for option 'p'"); errx("Invalid argument for option 'p'");
}
if (padByte > 0xFF) if (padByte > 0xFF) {
errx("Argument for option 'p' must be between 0 and 0xFF"); errx("Argument for option 'p' must be between 0 and 0xFF");
}
opt_P(padByte); opt_P(padByte);
break; break;
@@ -212,15 +226,18 @@ int main(int argc, char *argv[]) {
char const *precisionArg; char const *precisionArg;
case 'Q': case 'Q':
precisionArg = musl_optarg; precisionArg = musl_optarg;
if (precisionArg[0] == '.') if (precisionArg[0] == '.') {
precisionArg++; precisionArg++;
}
precision = strtoul(precisionArg, &endptr, 0); precision = strtoul(precisionArg, &endptr, 0);
if (musl_optarg[0] == '\0' || *endptr != '\0') if (musl_optarg[0] == '\0' || *endptr != '\0') {
errx("Invalid argument for option 'Q'"); errx("Invalid argument for option 'Q'");
}
if (precision < 1 || precision > 31) if (precision < 1 || precision > 31) {
errx("Argument for option 'Q' must be between 1 and 31"); errx("Argument for option 'Q' must be between 1 and 31");
}
opt_Q(precision); opt_Q(precision);
break; break;
@@ -228,35 +245,41 @@ int main(int argc, char *argv[]) {
case 'r': case 'r':
maxDepth = strtoul(musl_optarg, &endptr, 0); maxDepth = strtoul(musl_optarg, &endptr, 0);
if (musl_optarg[0] == '\0' || *endptr != '\0') if (musl_optarg[0] == '\0' || *endptr != '\0') {
errx("Invalid argument for option 'r'"); errx("Invalid argument for option 'r'");
}
break; break;
case 's': { case 's': {
// Split "<features>:<name>" so `musl_optarg` is "<features>" and `name` is "<name>" // Split "<features>:<name>" so `musl_optarg` is "<features>" and `name` is "<name>"
char *name = strchr(musl_optarg, ':'); char *name = strchr(musl_optarg, ':');
if (!name) if (!name) {
errx("Invalid argument for option 's'"); errx("Invalid argument for option 's'");
}
*name++ = '\0'; *name++ = '\0';
std::vector<StateFeature> features; std::vector<StateFeature> features;
for (char *feature = musl_optarg; feature;) { for (char *feature = musl_optarg; feature;) {
// Split "<feature>,<rest>" so `feature` is "<feature>" and `next` is "<rest>" // Split "<feature>,<rest>" so `feature` is "<feature>" and `next` is "<rest>"
char *next = strchr(feature, ','); char *next = strchr(feature, ',');
if (next) if (next) {
*next++ = '\0'; *next++ = '\0';
}
// Trim whitespace from the beginning of `feature`... // Trim whitespace from the beginning of `feature`...
feature += strspn(feature, " \t"); feature += strspn(feature, " \t");
// ...and from the end // ...and from the end
if (char *end = strpbrk(feature, " \t"); end) if (char *end = strpbrk(feature, " \t"); end) {
*end = '\0'; *end = '\0';
}
// A feature must be specified // A feature must be specified
if (*feature == '\0') if (*feature == '\0') {
errx("Empty feature for option 's'"); errx("Empty feature for option 's'");
}
// Parse the `feature` and update the `features` list // Parse the `feature` and update the `features` list
if (!strcasecmp(feature, "all")) { if (!strcasecmp(feature, "all")) {
if (!features.empty()) if (!features.empty()) {
warnx("Redundant feature before \"%s\" for option 's'", feature); warnx("Redundant feature before \"%s\" for option 's'", feature);
}
features.assign({STATE_EQU, STATE_VAR, STATE_EQUS, STATE_CHAR, STATE_MACRO}); features.assign({STATE_EQU, STATE_VAR, STATE_EQUS, STATE_CHAR, STATE_MACRO});
} else { } else {
StateFeature value = !strcasecmp(feature, "equ") ? STATE_EQU StateFeature value = !strcasecmp(feature, "equ") ? STATE_EQU
@@ -276,10 +299,12 @@ int main(int argc, char *argv[]) {
feature = next; feature = next;
} }
if (stateFileSpecs.find(name) != stateFileSpecs.end()) if (stateFileSpecs.find(name) != stateFileSpecs.end()) {
warnx("Overriding state filename %s", name); warnx("Overriding state filename %s", name);
if (verbose) }
if (verbose) {
printf("State filename %s\n", name); printf("State filename %s\n", name);
}
stateFileSpecs.emplace(name, std::move(features)); stateFileSpecs.emplace(name, std::move(features));
break; break;
} }
@@ -304,11 +329,13 @@ int main(int argc, char *argv[]) {
case 'X': case 'X':
maxValue = strtoul(musl_optarg, &endptr, 0); maxValue = strtoul(musl_optarg, &endptr, 0);
if (musl_optarg[0] == '\0' || *endptr != '\0') if (musl_optarg[0] == '\0' || *endptr != '\0') {
errx("Invalid argument for option 'X'"); errx("Invalid argument for option 'X'");
}
if (maxValue > UINT_MAX) if (maxValue > UINT_MAX) {
errx("Argument for option 'X' must be between 0 and %u", UINT_MAX); errx("Argument for option 'X' must be between 0 and %u", UINT_MAX);
}
maxErrors = maxValue; maxErrors = maxValue;
break; break;
@@ -327,10 +354,12 @@ int main(int argc, char *argv[]) {
case 'Q': case 'Q':
case 'T': case 'T':
newTarget = musl_optarg; newTarget = musl_optarg;
if (depType == 'Q') if (depType == 'Q') {
newTarget = make_escape(newTarget); newTarget = make_escape(newTarget);
if (!targetFileName.empty()) }
if (!targetFileName.empty()) {
targetFileName += ' '; targetFileName += ' ';
}
targetFileName += newTarget; targetFileName += newTarget;
break; break;
} }
@@ -343,8 +372,9 @@ int main(int argc, char *argv[]) {
} }
} }
if (targetFileName.empty() && !objectFileName.empty()) if (targetFileName.empty() && !objectFileName.empty()) {
targetFileName = objectFileName; targetFileName = objectFileName;
}
if (argc == musl_optind) { if (argc == musl_optind) {
fputs( fputs(
@@ -360,13 +390,15 @@ int main(int argc, char *argv[]) {
std::string mainFileName = argv[musl_optind]; std::string mainFileName = argv[musl_optind];
if (verbose) if (verbose) {
printf("Assembling %s\n", mainFileName.c_str()); printf("Assembling %s\n", mainFileName.c_str());
}
if (dependFile) { if (dependFile) {
if (targetFileName.empty()) if (targetFileName.empty()) {
errx("Dependency files can only be created if a target file is specified with either " errx("Dependency files can only be created if a target file is specified with either "
"-o, -MQ or -MT"); "-o, -MQ or -MT");
}
fprintf(dependFile, "%s: %s\n", targetFileName.c_str(), mainFileName.c_str()); fprintf(dependFile, "%s: %s\n", targetFileName.c_str(), mainFileName.c_str());
} }
@@ -377,28 +409,34 @@ int main(int argc, char *argv[]) {
fstk_Init(mainFileName, maxDepth); fstk_Init(mainFileName, maxDepth);
// Perform parse (`yy::parser` is auto-generated from `parser.y`) // Perform parse (`yy::parser` is auto-generated from `parser.y`)
if (yy::parser parser; parser.parse() != 0 && nbErrors == 0) if (yy::parser parser; parser.parse() != 0 && nbErrors == 0) {
nbErrors = 1; nbErrors = 1;
}
sect_CheckUnionClosed(); if (!failedOnMissingInclude) {
sect_CheckLoadClosed(); sect_CheckUnionClosed();
sect_CheckSizes(); sect_CheckLoadClosed();
sect_CheckSizes();
charmap_CheckStack(); charmap_CheckStack();
opt_CheckStack(); opt_CheckStack();
sect_CheckStack(); sect_CheckStack();
}
if (nbErrors != 0) if (nbErrors != 0) {
errx("Assembly aborted (%u error%s)!", nbErrors, nbErrors == 1 ? "" : "s"); errx("Assembly aborted (%u error%s)!", nbErrors, nbErrors == 1 ? "" : "s");
}
// If parse aborted due to missing an include, and `-MG` was given, exit normally // If parse aborted due to missing an include, and `-MG` was given, exit normally
if (failedOnMissingInclude) if (failedOnMissingInclude) {
return 0; return 0;
}
out_WriteObject(); out_WriteObject();
for (auto [name, features] : stateFileSpecs) for (auto [name, features] : stateFileSpecs) {
out_WriteState(name, features); out_WriteState(name, features);
}
return 0; return 0;
} }

View File

@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: MIT */ // SPDX-License-Identifier: MIT
#include <ctype.h> #include <ctype.h>
#include <errno.h> #include <errno.h>
@@ -53,17 +53,19 @@ void opt_W(char const *flag) {
void opt_Parse(char const *s) { void opt_Parse(char const *s) {
switch (s[0]) { switch (s[0]) {
case 'b': case 'b':
if (strlen(&s[1]) == 2) if (strlen(&s[1]) == 2) {
opt_B(&s[1]); opt_B(&s[1]);
else } else {
error("Must specify exactly 2 characters for option 'b'\n"); error("Must specify exactly 2 characters for option 'b'\n");
}
break; break;
case 'g': case 'g':
if (strlen(&s[1]) == 4) if (strlen(&s[1]) == 4) {
opt_G(&s[1]); opt_G(&s[1]);
else } else {
error("Must specify exactly 4 characters for option 'g'\n"); error("Must specify exactly 4 characters for option 'g'\n");
}
break; break;
case 'p': case 'p':
@@ -72,12 +74,13 @@ void opt_Parse(char const *s) {
unsigned int padByte; unsigned int padByte;
result = sscanf(&s[1], "%x", &padByte); result = sscanf(&s[1], "%x", &padByte);
if (result != 1) if (result != 1) {
error("Invalid argument for option 'p'\n"); error("Invalid argument for option 'p'\n");
else if (padByte > 0xFF) } else if (padByte > 0xFF) {
error("Argument for option 'p' must be between 0 and 0xFF\n"); error("Argument for option 'p' must be between 0 and 0xFF\n");
else } else {
opt_P(padByte); opt_P(padByte);
}
} else { } else {
error("Invalid argument for option 'p'\n"); error("Invalid argument for option 'p'\n");
} }
@@ -86,19 +89,21 @@ void opt_Parse(char const *s) {
char const *precisionArg; char const *precisionArg;
case 'Q': case 'Q':
precisionArg = &s[1]; precisionArg = &s[1];
if (precisionArg[0] == '.') if (precisionArg[0] == '.') {
precisionArg++; precisionArg++;
}
if (strlen(precisionArg) <= 2) { if (strlen(precisionArg) <= 2) {
int result; int result;
unsigned int precision; unsigned int precision;
result = sscanf(precisionArg, "%u", &precision); result = sscanf(precisionArg, "%u", &precision);
if (result != 1) if (result != 1) {
error("Invalid argument for option 'Q'\n"); error("Invalid argument for option 'Q'\n");
else if (precision < 1 || precision > 31) } else if (precision < 1 || precision > 31) {
error("Argument for option 'Q' must be between 1 and 31\n"); error("Argument for option 'Q' must be between 1 and 31\n");
else } else {
opt_Q(precision); opt_Q(precision);
}
} else { } else {
error("Invalid argument for option 'Q'\n"); error("Invalid argument for option 'Q'\n");
} }
@@ -106,8 +111,9 @@ void opt_Parse(char const *s) {
case 'r': { case 'r': {
++s; // Skip 'r' ++s; // Skip 'r'
while (isblank(*s)) while (isblank(*s)) {
++s; // Skip leading whitespace ++s; // Skip leading whitespace
}
if (s[0] == '\0') { if (s[0] == '\0') {
error("Missing argument to option 'r'\n"); error("Missing argument to option 'r'\n");
@@ -128,10 +134,11 @@ void opt_Parse(char const *s) {
} }
case 'W': case 'W':
if (strlen(&s[1]) > 0) if (strlen(&s[1]) > 0) {
opt_W(&s[1]); opt_W(&s[1]);
else } else {
error("Must specify an argument for option 'W'\n"); error("Must specify an argument for option 'W'\n");
}
break; break;
default: default:

View File

@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: MIT */ // SPDX-License-Identifier: MIT
#include "asm/output.hpp" #include "asm/output.hpp"
@@ -63,11 +63,13 @@ void out_RegisterNode(std::shared_ptr<FileStackNode> node) {
// Return a section's ID, or UINT32_MAX if the section is not in the list // Return a section's ID, or UINT32_MAX if the section is not in the list
static uint32_t getSectIDIfAny(Section *sect) { static uint32_t getSectIDIfAny(Section *sect) {
if (!sect) if (!sect) {
return UINT32_MAX; return UINT32_MAX;
}
if (auto search = sectionMap.find(sect->name); search != sectionMap.end()) if (auto search = sectionMap.find(sect->name); search != sectionMap.end()) {
return static_cast<uint32_t>(sectionMap.size() - search->second - 1); return static_cast<uint32_t>(search->second);
}
fatalerror("Unknown section '%s'\n", sect->name.c_str()); fatalerror("Unknown section '%s'\n", sect->name.c_str());
} }
@@ -109,8 +111,9 @@ static void writeSection(Section const &sect, FILE *file) {
fwrite(sect.data.data(), 1, sect.size, file); fwrite(sect.data.data(), 1, sect.size, file);
putLong(sect.patches.size(), file); putLong(sect.patches.size(), file);
for (Patch const &patch : sect.patches) for (Patch const &patch : sect.patches) {
writePatch(patch, file); writePatch(patch, file);
}
} }
} }
@@ -162,8 +165,9 @@ static void writeRpn(std::vector<uint8_t> &rpnexpr, std::vector<uint8_t> const &
symName.clear(); symName.clear();
for (;;) { for (;;) {
uint8_t c = rpn[offset++]; uint8_t c = rpn[offset++];
if (c == 0) if (c == 0) {
break; break;
}
symName += c; symName += c;
} }
@@ -188,8 +192,9 @@ static void writeRpn(std::vector<uint8_t> &rpnexpr, std::vector<uint8_t> const &
symName.clear(); symName.clear();
for (;;) { for (;;) {
uint8_t c = rpn[offset++]; uint8_t c = rpn[offset++];
if (c == 0) if (c == 0) {
break; break;
}
symName += c; symName += c;
} }
@@ -282,7 +287,7 @@ void out_CreateAssert(
assertion.message = message; assertion.message = message;
} }
static void writeAssert(Assertion &assert, FILE *file) { static void writeAssert(Assertion const &assert, FILE *file) {
writePatch(assert.patch, file); writePatch(assert.patch, file);
putString(assert.message, file); putString(assert.message, file);
} }
@@ -298,14 +303,16 @@ static void writeFileStackNode(FileStackNode const &node, FILE *file) {
putLong(nodeIters.size(), file); putLong(nodeIters.size(), file);
// Iters are stored by decreasing depth, so reverse the order for output // Iters are stored by decreasing depth, so reverse the order for output
for (uint32_t i = nodeIters.size(); i--;) for (uint32_t i = nodeIters.size(); i--;) {
putLong(nodeIters[i], file); putLong(nodeIters[i], file);
}
} }
} }
void out_WriteObject() { void out_WriteObject() {
if (objectFileName.empty()) if (objectFileName.empty()) {
return; return;
}
FILE *file; FILE *file;
if (objectFileName != "-") { if (objectFileName != "-") {
@@ -315,14 +322,15 @@ void out_WriteObject() {
(void)setmode(STDOUT_FILENO, O_BINARY); (void)setmode(STDOUT_FILENO, O_BINARY);
file = stdout; file = stdout;
} }
if (!file) if (!file) {
err("Failed to open object file '%s'", objectFileName.c_str()); err("Failed to open object file '%s'", objectFileName.c_str());
}
Defer closeFile{[&] { fclose(file); }}; Defer closeFile{[&] { fclose(file); }};
// Also write symbols that weren't written above // Also write symbols that weren't written above
sym_ForEach(registerUnregisteredSymbol); sym_ForEach(registerUnregisteredSymbol);
fprintf(file, RGBDS_OBJECT_VERSION_STRING); fputs(RGBDS_OBJECT_VERSION_STRING, file);
putLong(RGBDS_OBJECT_REV, file); putLong(RGBDS_OBJECT_REV, file);
putLong(objectSymbols.size(), file); putLong(objectSymbols.size(), file);
@@ -335,33 +343,39 @@ void out_WriteObject() {
writeFileStackNode(node, file); writeFileStackNode(node, file);
// The list is supposed to have decrementing IDs // The list is supposed to have decrementing IDs
if (it + 1 != fileStackNodes.end() && it[1]->ID != node.ID - 1) if (it + 1 != fileStackNodes.end() && it[1]->ID != node.ID - 1) {
fatalerror( fatalerror(
"Internal error: fstack node #%" PRIu32 " follows #%" PRIu32 "Internal error: fstack node #%" PRIu32 " follows #%" PRIu32
". Please report this to the developers!\n", ". Please report this to the developers!\n",
it[1]->ID, it[1]->ID,
node.ID node.ID
); );
}
} }
for (Symbol const *sym : objectSymbols) for (Symbol const *sym : objectSymbols) {
writeSymbol(*sym, file); writeSymbol(*sym, file);
}
for (auto it = sectionList.rbegin(); it != sectionList.rend(); it++) for (Section const &sect : sectionList) {
writeSection(*it, file); writeSection(sect, file);
}
putLong(assertions.size(), file); putLong(assertions.size(), file);
for (Assertion &assert : assertions) for (Assertion const &assert : assertions) {
writeAssert(assert, file); writeAssert(assert, file);
}
} }
void out_SetFileName(std::string const &name) { void out_SetFileName(std::string const &name) {
if (!objectFileName.empty()) if (!objectFileName.empty()) {
warnx("Overriding output filename %s", objectFileName.c_str()); warnx("Overriding output filename %s", objectFileName.c_str());
}
objectFileName = name; objectFileName = name;
if (verbose) if (verbose) {
printf("Output filename %s\n", objectFileName.c_str()); printf("Output filename %s\n", objectFileName.c_str());
}
} }
static void dumpString(std::string const &escape, FILE *file) { static void dumpString(std::string const &escape, FILE *file) {
@@ -397,8 +411,9 @@ static bool dumpEquConstants(FILE *file) {
equConstants.clear(); equConstants.clear();
sym_ForEach([](Symbol &sym) { sym_ForEach([](Symbol &sym) {
if (!sym.isBuiltin && sym.type == SYM_EQU) if (!sym.isBuiltin && sym.type == SYM_EQU) {
equConstants.push_back(&sym); equConstants.push_back(&sym);
}
}); });
// Constants are ordered by file, then by definition order // Constants are ordered by file, then by definition order
std::sort(RANGE(equConstants), [](Symbol *sym1, Symbol *sym2) -> bool { std::sort(RANGE(equConstants), [](Symbol *sym1, Symbol *sym2) -> bool {
@@ -418,8 +433,9 @@ static bool dumpVariables(FILE *file) {
variables.clear(); variables.clear();
sym_ForEach([](Symbol &sym) { sym_ForEach([](Symbol &sym) {
if (!sym.isBuiltin && sym.type == SYM_VAR) if (!sym.isBuiltin && sym.type == SYM_VAR) {
variables.push_back(&sym); variables.push_back(&sym);
}
}); });
// Variables are ordered by file, then by definition order // Variables are ordered by file, then by definition order
std::sort(RANGE(variables), [](Symbol *sym1, Symbol *sym2) -> bool { std::sort(RANGE(variables), [](Symbol *sym1, Symbol *sym2) -> bool {
@@ -439,8 +455,9 @@ static bool dumpEqusConstants(FILE *file) {
equsConstants.clear(); equsConstants.clear();
sym_ForEach([](Symbol &sym) { sym_ForEach([](Symbol &sym) {
if (!sym.isBuiltin && sym.type == SYM_EQUS) if (!sym.isBuiltin && sym.type == SYM_EQUS) {
equsConstants.push_back(&sym); equsConstants.push_back(&sym);
}
}); });
// Constants are ordered by file, then by definition order // Constants are ordered by file, then by definition order
std::sort(RANGE(equsConstants), [](Symbol *sym1, Symbol *sym2) -> bool { std::sort(RANGE(equsConstants), [](Symbol *sym1, Symbol *sym2) -> bool {
@@ -467,8 +484,9 @@ static bool dumpCharmaps(FILE *file) {
fputs("charmap \"", charmapFile); fputs("charmap \"", charmapFile);
dumpString(mapping, charmapFile); dumpString(mapping, charmapFile);
putc('"', charmapFile); putc('"', charmapFile);
for (int32_t v : value) for (int32_t v : value) {
fprintf(charmapFile, ", $%" PRIx32, v); fprintf(charmapFile, ", $%" PRIx32, v);
}
putc('\n', charmapFile); putc('\n', charmapFile);
} }
); );
@@ -479,8 +497,9 @@ static bool dumpMacros(FILE *file) {
macros.clear(); macros.clear();
sym_ForEach([](Symbol &sym) { sym_ForEach([](Symbol &sym) {
if (!sym.isBuiltin && sym.type == SYM_MACRO) if (!sym.isBuiltin && sym.type == SYM_MACRO) {
macros.push_back(&sym); macros.push_back(&sym);
}
}); });
// Macros are ordered by file, then by definition order // Macros are ordered by file, then by definition order
std::sort(RANGE(macros), [](Symbol *sym1, Symbol *sym2) -> bool { std::sort(RANGE(macros), [](Symbol *sym1, Symbol *sym2) -> bool {
@@ -508,8 +527,9 @@ void out_WriteState(std::string name, std::vector<StateFeature> const &features)
(void)setmode(STDOUT_FILENO, O_BINARY); (void)setmode(STDOUT_FILENO, O_BINARY);
file = stdout; file = stdout;
} }
if (!file) if (!file) {
err("Failed to open state file '%s'", name.c_str()); err("Failed to open state file '%s'", name.c_str());
}
Defer closeFile{[&] { fclose(file); }}; Defer closeFile{[&] { fclose(file); }};
static char const *dumpHeadings[NB_STATE_FEATURES] = { static char const *dumpHeadings[NB_STATE_FEATURES] = {
@@ -530,7 +550,8 @@ void out_WriteState(std::string name, std::vector<StateFeature> const &features)
fputs("; File generated by rgbasm\n", file); fputs("; File generated by rgbasm\n", file);
for (StateFeature feature : features) { for (StateFeature feature : features) {
fprintf(file, "\n; %s\n", dumpHeadings[feature]); fprintf(file, "\n; %s\n", dumpHeadings[feature]);
if (!dumpFuncs[feature](file)) if (!dumpFuncs[feature](file)) {
fprintf(file, "; No values\n"); fprintf(file, "; No values\n");
}
} }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: MIT */ // SPDX-License-Identifier: MIT
#include "asm/rpn.hpp" #include "asm/rpn.hpp"
@@ -46,8 +46,9 @@ int32_t Expression::getConstVal() const {
} }
Symbol const *Expression::symbolOf() const { Symbol const *Expression::symbolOf() const {
if (!isSymbol) if (!isSymbol) {
return nullptr; return nullptr;
}
return sym_FindScopedSymbol(reinterpret_cast<char const *>(&rpn[1])); return sym_FindScopedSymbol(reinterpret_cast<char const *>(&rpn[1]));
} }
@@ -55,8 +56,9 @@ bool Expression::isDiffConstant(Symbol const *sym) const {
// Check if both expressions only refer to a single symbol // Check if both expressions only refer to a single symbol
Symbol const *sym1 = symbolOf(); Symbol const *sym1 = symbolOf();
if (!sym1 || !sym || sym1->type != SYM_LABEL || sym->type != SYM_LABEL) if (!sym1 || !sym || sym1->type != SYM_LABEL || sym->type != SYM_LABEL) {
return false; return false;
}
Section const *sect1 = sym1->getSection(); Section const *sect1 = sym1->getSection();
Section const *sect2 = sym->getSection(); Section const *sect2 = sym->getSection();
@@ -77,9 +79,9 @@ void Expression::makeSymbol(std::string const &symName) {
isSymbol = true; isSymbol = true;
data = sym_IsPC(sym) ? "PC is not constant at assembly time" data = sym_IsPC(sym) ? "PC is not constant at assembly time"
: sym_IsPurgedScoped(symName) : sym_IsPurgedScoped(symName)
? "'"s + symName + "' is not constant at assembly time; it was purged" ? "'"s + symName + "' is not constant at assembly time; it was purged"
: "'"s + symName + "' is not constant at assembly time"; : "'"s + symName + "' is not constant at assembly time";
sym = sym_Ref(symName); sym = sym_Ref(symName);
size_t nameLen = sym->name.length() + 1; // Don't forget NUL! size_t nameLen = sym->name.length() + 1; // Don't forget NUL!
@@ -120,8 +122,8 @@ void Expression::makeBankSymbol(std::string const &symName) {
data = static_cast<int32_t>(sym->getSection()->bank); data = static_cast<int32_t>(sym->getSection()->bank);
} else { } else {
data = sym_IsPurgedScoped(symName) data = sym_IsPurgedScoped(symName)
? "\""s + symName + "\"'s bank is not known; it was purged" ? "\""s + symName + "\"'s bank is not known; it was purged"
: "\""s + symName + "\"'s bank is not known"; : "\""s + symName + "\"'s bank is not known";
size_t nameLen = sym->name.length() + 1; // Room for NUL! size_t nameLen = sym->name.length() + 1; // Room for NUL!
@@ -208,8 +210,9 @@ static bool tryConstNonzero(Expression const &lhs, Expression const &rhs) {
static bool tryConstLogNot(Expression const &expr) { static bool tryConstLogNot(Expression const &expr) {
Symbol const *sym = expr.symbolOf(); Symbol const *sym = expr.symbolOf();
if (!sym || !sym->getSection() || !sym->isDefined()) if (!sym || !sym->getSection() || !sym->isDefined()) {
return false; return false;
}
assume(sym->isNumeric()); assume(sym->isNumeric());
@@ -225,23 +228,21 @@ static bool tryConstLogNot(Expression const &expr) {
return knownBits != 0; return knownBits != 0;
} }
/* // Returns a constant LOW() from non-constant argument, or -1 if it cannot be computed.
* Attempts to compute a constant LOW() from non-constant argument // This is possible if the argument is a symbol belonging to an `ALIGN[8]` section.
* This is possible if the argument is a symbol belonging to an `ALIGN[8]` section.
*
* @return The constant `LOW(expr)` result if it can be computed, or -1 otherwise.
*/
static int32_t tryConstLow(Expression const &expr) { static int32_t tryConstLow(Expression const &expr) {
Symbol const *sym = expr.symbolOf(); Symbol const *sym = expr.symbolOf();
if (!sym || !sym->getSection() || !sym->isDefined()) if (!sym || !sym->getSection() || !sym->isDefined()) {
return -1; return -1;
}
assume(sym->isNumeric()); assume(sym->isNumeric());
// The low byte must not cover any unknown bits // The low byte must not cover any unknown bits
Section const &sect = *sym->getSection(); Section const &sect = *sym->getSection();
if (sect.align < 8) if (sect.align < 8) {
return -1; return -1;
}
// `sym->getValue()` attempts to add the section's address, but that's `UINT32_MAX` // `sym->getValue()` attempts to add the section's address, but that's `UINT32_MAX`
// because the section is floating (otherwise we wouldn't be here) // because the section is floating (otherwise we wouldn't be here)
@@ -251,28 +252,26 @@ static int32_t tryConstLow(Expression const &expr) {
return (symbolOfs + sect.alignOfs) & 0xFF; return (symbolOfs + sect.alignOfs) & 0xFF;
} }
/* // Returns a constant binary AND with one non-constant operand, or -1 if it cannot be computed.
* Attempts to compute a constant binary AND with one non-constant operands // This is possible if one operand is a symbol belonging to an `ALIGN[N]` section, and the other is
* This is possible if one operand is a symbol belonging to an `ALIGN[N]` section, and the other is // a constant that only keeps (some of) the lower N bits.
* a constant that only keeps (some of) the lower N bits.
*
* @return The constant `lhs & rhs` result if it can be computed, or -1 otherwise.
*/
static int32_t tryConstMask(Expression const &lhs, Expression const &rhs) { static int32_t tryConstMask(Expression const &lhs, Expression const &rhs) {
Symbol const *lhsSymbol = lhs.symbolOf(); Symbol const *lhsSymbol = lhs.symbolOf();
Symbol const *rhsSymbol = lhsSymbol ? nullptr : rhs.symbolOf(); Symbol const *rhsSymbol = lhsSymbol ? nullptr : rhs.symbolOf();
bool lhsIsSymbol = lhsSymbol && lhsSymbol->getSection(); bool lhsIsSymbol = lhsSymbol && lhsSymbol->getSection();
bool rhsIsSymbol = rhsSymbol && rhsSymbol->getSection(); bool rhsIsSymbol = rhsSymbol && rhsSymbol->getSection();
if (!lhsIsSymbol && !rhsIsSymbol) if (!lhsIsSymbol && !rhsIsSymbol) {
return -1; return -1;
}
// If the lhs isn't a symbol, try again the other way around // If the lhs isn't a symbol, try again the other way around
Symbol const &sym = lhsIsSymbol ? *lhsSymbol : *rhsSymbol; Symbol const &sym = lhsIsSymbol ? *lhsSymbol : *rhsSymbol;
Expression const &expr = lhsIsSymbol ? rhs : lhs; // Opposite side of `sym` Expression const &expr = lhsIsSymbol ? rhs : lhs; // Opposite side of `sym`
if (!sym.isDefined() || !expr.isKnown()) if (!sym.isDefined() || !expr.isKnown()) {
return -1; return -1;
}
assume(sym.isNumeric()); assume(sym.isNumeric());
@@ -281,8 +280,9 @@ static int32_t tryConstMask(Expression const &lhs, Expression const &rhs) {
// The mask must not cover any unknown bits // The mask must not cover any unknown bits
Section const &sect = *sym.getSection(); Section const &sect = *sym.getSection();
if (int32_t unknownBits = (1 << 16) - (1 << sect.align); (unknownBits & mask) != 0) if (int32_t unknownBits = (1 << 16) - (1 << sect.align); (unknownBits & mask) != 0) {
return -1; return -1;
}
// `sym.getValue()` attempts to add the section's address, but that's `UINT32_MAX` // `sym.getValue()` attempts to add the section's address, but that's `UINT32_MAX`
// because the section is floating (otherwise we wouldn't be here) // because the section is floating (otherwise we wouldn't be here)
@@ -418,38 +418,45 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const
data = lval & rval; data = lval & rval;
break; break;
case RPN_SHL: case RPN_SHL:
if (rval < 0) if (rval < 0) {
warning( warning(
WARNING_SHIFT_AMOUNT, "Shifting left by negative amount %" PRId32 "\n", rval WARNING_SHIFT_AMOUNT, "Shifting left by negative amount %" PRId32 "\n", rval
); );
}
if (rval >= 32) if (rval >= 32) {
warning(WARNING_SHIFT_AMOUNT, "Shifting left by large amount %" PRId32 "\n", rval); warning(WARNING_SHIFT_AMOUNT, "Shifting left by large amount %" PRId32 "\n", rval);
}
data = op_shift_left(lval, rval); data = op_shift_left(lval, rval);
break; break;
case RPN_SHR: case RPN_SHR:
if (lval < 0) if (lval < 0) {
warning(WARNING_SHIFT, "Shifting right negative value %" PRId32 "\n", lval); warning(WARNING_SHIFT, "Shifting right negative value %" PRId32 "\n", lval);
}
if (rval < 0) if (rval < 0) {
warning( warning(
WARNING_SHIFT_AMOUNT, "Shifting right by negative amount %" PRId32 "\n", rval WARNING_SHIFT_AMOUNT, "Shifting right by negative amount %" PRId32 "\n", rval
); );
}
if (rval >= 32) if (rval >= 32) {
warning(WARNING_SHIFT_AMOUNT, "Shifting right by large amount %" PRId32 "\n", rval); warning(WARNING_SHIFT_AMOUNT, "Shifting right by large amount %" PRId32 "\n", rval);
}
data = op_shift_right(lval, rval); data = op_shift_right(lval, rval);
break; break;
case RPN_USHR: case RPN_USHR:
if (rval < 0) if (rval < 0) {
warning( warning(
WARNING_SHIFT_AMOUNT, "Shifting right by negative amount %" PRId32 "\n", rval WARNING_SHIFT_AMOUNT, "Shifting right by negative amount %" PRId32 "\n", rval
); );
}
if (rval >= 32) if (rval >= 32) {
warning(WARNING_SHIFT_AMOUNT, "Shifting right by large amount %" PRId32 "\n", rval); warning(WARNING_SHIFT_AMOUNT, "Shifting right by large amount %" PRId32 "\n", rval);
}
data = op_shift_right_unsigned(lval, rval); data = op_shift_right_unsigned(lval, rval);
break; break;
@@ -457,8 +464,9 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const
data = static_cast<int32_t>(ulval * urval); data = static_cast<int32_t>(ulval * urval);
break; break;
case RPN_DIV: case RPN_DIV:
if (rval == 0) if (rval == 0) {
fatalerror("Division by zero\n"); fatalerror("Division by zero\n");
}
if (lval == INT32_MIN && rval == -1) { if (lval == INT32_MIN && rval == -1) {
warning( warning(
@@ -473,17 +481,20 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const
} }
break; break;
case RPN_MOD: case RPN_MOD:
if (rval == 0) if (rval == 0) {
fatalerror("Modulo by zero\n"); fatalerror("Modulo by zero\n");
}
if (lval == INT32_MIN && rval == -1) if (lval == INT32_MIN && rval == -1) {
data = 0; data = 0;
else } else {
data = op_modulo(lval, rval); data = op_modulo(lval, rval);
}
break; break;
case RPN_EXP: case RPN_EXP:
if (rval < 0) if (rval < 0) {
fatalerror("Exponentiation by negative power\n"); fatalerror("Exponentiation by negative power\n");
}
data = op_exponent(lval, rval); data = op_exponent(lval, rval);
break; break;
@@ -560,9 +571,10 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const
// Copy the right RPN and append the operator // Copy the right RPN and append the operator
uint32_t rightRpnSize = src2.rpn.size(); uint32_t rightRpnSize = src2.rpn.size();
uint8_t *ptr = reserveSpace(rightRpnSize + 1, src2.rpnPatchSize + 1); uint8_t *ptr = reserveSpace(rightRpnSize + 1, src2.rpnPatchSize + 1);
if (rightRpnSize > 0) if (rightRpnSize > 0) {
// If `rightRpnSize == 0`, then `memcpy(ptr, nullptr, rightRpnSize)` would be UB // If `rightRpnSize == 0`, then `memcpy(ptr, nullptr, rightRpnSize)` would be UB
memcpy(ptr, src2.rpn.data(), rightRpnSize); memcpy(ptr, src2.rpn.data(), rightRpnSize);
}
ptr[rightRpnSize] = op; ptr[rightRpnSize] = op;
} }
} }
@@ -595,8 +607,9 @@ void Expression::makeCheckRST() {
// Checks that an RPN expression's value fits within N bits (signed or unsigned) // Checks that an RPN expression's value fits within N bits (signed or unsigned)
void Expression::checkNBit(uint8_t n) const { void Expression::checkNBit(uint8_t n) const {
if (isKnown()) if (isKnown()) {
::checkNBit(value(), n, "Expression"); ::checkNBit(value(), n, "Expression");
}
} }
bool checkNBit(int32_t v, uint8_t n, char const *name) { bool checkNBit(int32_t v, uint8_t n, char const *name) {

View File

@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: MIT */ // SPDX-License-Identifier: MIT
#include "asm/section.hpp" #include "asm/section.hpp"
@@ -49,9 +49,11 @@ static std::pair<Symbol const *, Symbol const *> currentLoadLabelScopes = {nullp
int32_t loadOffset; // Offset into the LOAD section's parent (see sect_GetOutputOffset) int32_t loadOffset; // Offset into the LOAD section's parent (see sect_GetOutputOffset)
// A quick check to see if we have an initialized section // A quick check to see if we have an initialized section
[[nodiscard]] static bool requireSection() { [[nodiscard]]
if (currentSection) static bool requireSection() {
if (currentSection) {
return true; return true;
}
error("Cannot output data outside of a SECTION\n"); error("Cannot output data outside of a SECTION\n");
return false; return false;
@@ -59,12 +61,15 @@ int32_t loadOffset; // Offset into the LOAD section's parent (see sect_GetOutput
// A quick check to see if we have an initialized section that can contain // A quick check to see if we have an initialized section that can contain
// this much initialized data // this much initialized data
[[nodiscard]] static bool requireCodeSection() { [[nodiscard]]
if (!requireSection()) static bool requireCodeSection() {
if (!requireSection()) {
return false; return false;
}
if (sect_HasData(currentSection->type)) if (sect_HasData(currentSection->type)) {
return true; return true;
}
error( error(
"Section '%s' cannot contain code or data (not ROM0 or ROMX)\n", "Section '%s' cannot contain code or data (not ROM0 or ROMX)\n",
@@ -75,14 +80,15 @@ int32_t loadOffset; // Offset into the LOAD section's parent (see sect_GetOutput
void sect_CheckSizes() { void sect_CheckSizes() {
for (Section const &sect : sectionList) { for (Section const &sect : sectionList) {
if (uint32_t maxSize = sectionTypeInfo[sect.type].size; sect.size > maxSize) if (uint32_t maxSize = sectionTypeInfo[sect.type].size; sect.size > maxSize) {
error( error(
"Section '%s' grew too big (max size = 0x%" PRIX32 " bytes, reached 0x%" PRIX32 "Section '%s' grew too big (max size = 0x%" PRIX32 " bytes, reached 0x%" PRIX32
").\n", ")\n",
sect.name.c_str(), sect.name.c_str(),
maxSize, maxSize,
sect.size sect.size
); );
}
} }
} }
@@ -106,33 +112,36 @@ static unsigned int mergeSectUnion(
// Unionized sections only need "compatible" constraints, and they end up with the strictest // Unionized sections only need "compatible" constraints, and they end up with the strictest
// combination of both. // combination of both.
if (sect_HasData(type)) if (sect_HasData(type)) {
sectError("Cannot declare ROM sections as UNION\n"); sectError("Cannot declare ROM sections as UNION\n");
}
if (org != UINT32_MAX) { if (org != UINT32_MAX) {
// If both are fixed, they must be the same // If both are fixed, they must be the same
if (sect.org != UINT32_MAX && sect.org != org) if (sect.org != UINT32_MAX && sect.org != org) {
sectError( sectError(
"Section already declared as fixed at different address $%04" PRIx32 "\n", sect.org "Section already declared as fixed at different address $%04" PRIx32 "\n", sect.org
); );
else if (sect.align != 0 && (mask(sect.align) & (org - sect.alignOfs))) } else if (sect.align != 0 && (mask(sect.align) & (org - sect.alignOfs))) {
sectError( sectError(
"Section already declared as aligned to %u bytes (offset %" PRIu16 ")\n", "Section already declared as aligned to %u bytes (offset %" PRIu16 ")\n",
1U << sect.align, 1U << sect.align,
sect.alignOfs sect.alignOfs
); );
else } else {
// Otherwise, just override // Otherwise, just override
sect.org = org; sect.org = org;
}
} else if (alignment != 0) { } else if (alignment != 0) {
// Make sure any fixed address given is compatible // Make sure any fixed address given is compatible
if (sect.org != UINT32_MAX) { if (sect.org != UINT32_MAX) {
if ((sect.org - alignOffset) & mask(alignment)) if ((sect.org - alignOffset) & mask(alignment)) {
sectError( sectError(
"Section already declared as fixed at incompatible address $%04" PRIx32 "\n", "Section already declared as fixed at incompatible address $%04" PRIx32 "\n",
sect.org sect.org
); );
}
// Check if alignment offsets are compatible // Check if alignment offsets are compatible
} else if ((alignOffset & mask(sect.align)) != (sect.alignOfs & mask(alignment))) { } else if ((alignOffset & mask(sect.align)) != (sect.alignOfs & mask(alignment))) {
sectError( sectError(
@@ -163,34 +172,37 @@ static unsigned int
uint16_t curOrg = org - sect.size; uint16_t curOrg = org - sect.size;
// If both are fixed, they must be the same // If both are fixed, they must be the same
if (sect.org != UINT32_MAX && sect.org != curOrg) if (sect.org != UINT32_MAX && sect.org != curOrg) {
sectError( sectError(
"Section already declared as fixed at incompatible address $%04" PRIx32 "\n", "Section already declared as fixed at incompatible address $%04" PRIx32 "\n",
sect.org sect.org
); );
else if (sect.align != 0 && (mask(sect.align) & (curOrg - sect.alignOfs))) } else if (sect.align != 0 && (mask(sect.align) & (curOrg - sect.alignOfs))) {
sectError( sectError(
"Section already declared as aligned to %u bytes (offset %" PRIu16 ")\n", "Section already declared as aligned to %u bytes (offset %" PRIu16 ")\n",
1U << sect.align, 1U << sect.align,
sect.alignOfs sect.alignOfs
); );
else } else {
// Otherwise, just override // Otherwise, just override
sect.org = curOrg; sect.org = curOrg;
}
} else if (alignment != 0) { } else if (alignment != 0) {
int32_t curOfs = (alignOffset - sect.size) % (1U << alignment); int32_t curOfs = (alignOffset - sect.size) % (1U << alignment);
if (curOfs < 0) if (curOfs < 0) {
curOfs += 1U << alignment; curOfs += 1U << alignment;
}
// Make sure any fixed address given is compatible // Make sure any fixed address given is compatible
if (sect.org != UINT32_MAX) { if (sect.org != UINT32_MAX) {
if ((sect.org - curOfs) & mask(alignment)) if ((sect.org - curOfs) & mask(alignment)) {
sectError( sectError(
"Section already declared as fixed at incompatible address $%04" PRIx32 "\n", "Section already declared as fixed at incompatible address $%04" PRIx32 "\n",
sect.org sect.org
); );
}
// Check if alignment offsets are compatible // Check if alignment offsets are compatible
} else if ((curOfs & mask(sect.align)) != (sect.alignOfs & mask(alignment))) { } else if ((curOfs & mask(sect.align)) != (sect.alignOfs & mask(alignment))) {
sectError( sectError(
@@ -220,13 +232,14 @@ static void mergeSections(
) { ) {
unsigned int nbSectErrors = 0; unsigned int nbSectErrors = 0;
if (type != sect.type) if (type != sect.type) {
sectError( sectError(
"Section already exists but with type %s\n", sectionTypeInfo[sect.type].name.c_str() "Section already exists but with type %s\n", sectionTypeInfo[sect.type].name.c_str()
); );
}
if (sect.modifier != mod) { if (sect.modifier != mod) {
sectError("Section already declared as %s section\n", sectionModNames[sect.modifier]); sectError("Section already declared as SECTION %s\n", sectionModNames[sect.modifier]);
} else { } else {
switch (mod) { switch (mod) {
case SECTION_UNION: case SECTION_UNION:
@@ -238,11 +251,13 @@ static void mergeSections(
// Common checks // Common checks
// If the section's bank is unspecified, override it // If the section's bank is unspecified, override it
if (sect.bank == UINT32_MAX) if (sect.bank == UINT32_MAX) {
sect.bank = bank; sect.bank = bank;
}
// If both specify a bank, it must be the same one // If both specify a bank, it must be the same one
else if (bank != UINT32_MAX && sect.bank != bank) else if (bank != UINT32_MAX && sect.bank != bank) {
sectError("Section already declared with different bank %" PRIu32 "\n", sect.bank); sectError("Section already declared with different bank %" PRIu32 "\n", sect.bank);
}
break; break;
case SECTION_NORMAL: case SECTION_NORMAL:
@@ -253,13 +268,14 @@ static void mergeSections(
} }
} }
if (nbSectErrors) if (nbSectErrors) {
fatalerror( fatalerror(
"Cannot create section \"%s\" (%u error%s)\n", "Cannot create section \"%s\" (%u error%s)\n",
sect.name.c_str(), sect.name.c_str(),
nbSectErrors, nbSectErrors,
nbSectErrors == 1 ? "" : "s" nbSectErrors == 1 ? "" : "s"
); );
}
} }
#undef sectError #undef sectError
@@ -292,8 +308,9 @@ static Section *createSection(
out_RegisterNode(sect.src); out_RegisterNode(sect.src);
// It is only needed to allocate memory for ROM sections. // It is only needed to allocate memory for ROM sections.
if (sect_HasData(type)) if (sect_HasData(type)) {
sect.data.resize(sectionTypeInfo[type].size); sect.data.resize(sectionTypeInfo[type].size);
}
return &sect; return &sect;
} }
@@ -314,9 +331,10 @@ static Section *getSection(
if (bank != UINT32_MAX) { if (bank != UINT32_MAX) {
if (type != SECTTYPE_ROMX && type != SECTTYPE_VRAM && type != SECTTYPE_SRAM if (type != SECTTYPE_ROMX && type != SECTTYPE_VRAM && type != SECTTYPE_SRAM
&& type != SECTTYPE_WRAMX) && type != SECTTYPE_WRAMX) {
error("BANK only allowed for ROMX, WRAMX, SRAM, or VRAM sections\n"); error("BANK only allowed for ROMX, WRAMX, SRAM, or VRAM sections\n");
else if (bank < sectionTypeInfo[type].firstBank || bank > sectionTypeInfo[type].lastBank) } else if (bank < sectionTypeInfo[type].firstBank
|| bank > sectionTypeInfo[type].lastBank) {
error( error(
"%s bank value $%04" PRIx32 " out of range ($%04" PRIx32 " to $%04" PRIx32 ")\n", "%s bank value $%04" PRIx32 " out of range ($%04" PRIx32 " to $%04" PRIx32 ")\n",
sectionTypeInfo[type].name.c_str(), sectionTypeInfo[type].name.c_str(),
@@ -324,6 +342,7 @@ static Section *getSection(
sectionTypeInfo[type].firstBank, sectionTypeInfo[type].firstBank,
sectionTypeInfo[type].lastBank sectionTypeInfo[type].lastBank
); );
}
} else if (nbbanks(type) == 1) { } else if (nbbanks(type) == 1) {
// If the section type only has a single bank, implicitly force it // If the section type only has a single bank, implicitly force it
bank = sectionTypeInfo[type].firstBank; bank = sectionTypeInfo[type].firstBank;
@@ -339,7 +358,7 @@ static Section *getSection(
} }
if (org != UINT32_MAX) { if (org != UINT32_MAX) {
if (org < sectionTypeInfo[type].startAddr || org > endaddr(type)) if (org < sectionTypeInfo[type].startAddr || org > endaddr(type)) {
error( error(
"Section \"%s\"'s fixed address $%04" PRIx32 " is outside of range [$%04" PRIx16 "Section \"%s\"'s fixed address $%04" PRIx32 " is outside of range [$%04" PRIx16
"; $%04" PRIx16 "]\n", "; $%04" PRIx16 "]\n",
@@ -348,6 +367,7 @@ static Section *getSection(
sectionTypeInfo[type].startAddr, sectionTypeInfo[type].startAddr,
endaddr(type) endaddr(type)
); );
}
} }
if (alignment != 0) { if (alignment != 0) {
@@ -359,8 +379,9 @@ static Section *getSection(
uint32_t mask = mask(alignment); uint32_t mask = mask(alignment);
if (org != UINT32_MAX) { if (org != UINT32_MAX) {
if ((org - alignOffset) & mask) if ((org - alignOffset) & mask) {
error("Section \"%s\"'s fixed address doesn't match its alignment\n", name.c_str()); error("Section \"%s\"'s fixed address doesn't match its alignment\n", name.c_str());
}
alignment = 0; // Ignore it if it's satisfied alignment = 0; // Ignore it if it's satisfied
} else if (sectionTypeInfo[type].startAddr & mask) { } else if (sectionTypeInfo[type].startAddr & mask) {
error( error(
@@ -393,25 +414,29 @@ static Section *getSection(
// Set the current section // Set the current section
static void changeSection() { static void changeSection() {
if (!currentUnionStack.empty()) if (!currentUnionStack.empty()) {
fatalerror("Cannot change the section within a UNION\n"); fatalerror("Cannot change the section within a UNION\n");
}
sym_ResetCurrentLabelScopes(); sym_ResetCurrentLabelScopes();
} }
bool Section::isSizeKnown() const { bool Section::isSizeKnown() const {
// SECTION UNION and SECTION FRAGMENT can still grow // SECTION UNION and SECTION FRAGMENT can still grow
if (modifier != SECTION_NORMAL) if (modifier != SECTION_NORMAL) {
return false; return false;
}
// The current section (or current load section if within one) is still growing // The current section (or current load section if within one) is still growing
if (this == currentSection || this == currentLoadSection) if (this == currentSection || this == currentLoadSection) {
return false; return false;
}
// Any section on the stack is still growing // Any section on the stack is still growing
for (SectionStackEntry &entry : sectionStack) { for (SectionStackEntry &entry : sectionStack) {
if (entry.section && entry.section->name == name) if (entry.section && entry.section->name == name) {
return false; return false;
}
} }
return true; return true;
@@ -426,12 +451,14 @@ void sect_NewSection(
SectionModifier mod SectionModifier mod
) { ) {
for (SectionStackEntry &entry : sectionStack) { for (SectionStackEntry &entry : sectionStack) {
if (entry.section && entry.section->name == name) if (entry.section && entry.section->name == name) {
fatalerror("Section '%s' is already on the stack\n", name.c_str()); fatalerror("Section '%s' is already on the stack\n", name.c_str());
}
} }
if (currentLoadSection) if (currentLoadSection) {
sect_EndLoadSection("SECTION"); sect_EndLoadSection("SECTION");
}
Section *sect = getSection(name, type, org, attrs, mod); Section *sect = getSection(name, type, org, attrs, mod);
@@ -454,16 +481,18 @@ void sect_SetLoadSection(
// Therefore, any interactions are NOT TESTED, so lift either of those restrictions at // Therefore, any interactions are NOT TESTED, so lift either of those restrictions at
// your own peril! ^^ // your own peril! ^^
if (!requireCodeSection()) if (!requireCodeSection()) {
return; return;
}
if (sect_HasData(type)) { if (sect_HasData(type)) {
error("`LOAD` blocks cannot create a ROM section\n"); error("`LOAD` blocks cannot create a ROM section\n");
return; return;
} }
if (currentLoadSection) if (currentLoadSection) {
sect_EndLoadSection("LOAD"); sect_EndLoadSection("LOAD");
}
Section *sect = getSection(name, type, org, attrs, mod); Section *sect = getSection(name, type, org, attrs, mod);
@@ -475,12 +504,11 @@ void sect_SetLoadSection(
} }
void sect_EndLoadSection(char const *cause) { void sect_EndLoadSection(char const *cause) {
if (cause) if (cause) {
warning( warning(
WARNING_UNTERMINATED_LOAD, WARNING_UNTERMINATED_LOAD, "`LOAD` block without `ENDL` terminated by `%s`\n", cause
"`LOAD` block without `ENDL` terminated by `%s`\n",
cause
); );
}
if (!currentLoadSection) { if (!currentLoadSection) {
error("Found `ENDL` outside of a `LOAD` block\n"); error("Found `ENDL` outside of a `LOAD` block\n");
@@ -495,8 +523,9 @@ void sect_EndLoadSection(char const *cause) {
} }
void sect_CheckLoadClosed() { void sect_CheckLoadClosed() {
if (currentLoadSection) if (currentLoadSection) {
warning(WARNING_UNTERMINATED_LOAD, "`LOAD` block without `ENDL` terminated by EOF\n"); warning(WARNING_UNTERMINATED_LOAD, "`LOAD` block without `ENDL` terminated by EOF\n");
}
} }
Section *sect_GetSymbolSection() { Section *sect_GetSymbolSection() {
@@ -515,16 +544,18 @@ uint32_t sect_GetOutputOffset() {
// Returns how many bytes need outputting for the specified alignment and offset to succeed // Returns how many bytes need outputting for the specified alignment and offset to succeed
uint32_t sect_GetAlignBytes(uint8_t alignment, uint16_t offset) { uint32_t sect_GetAlignBytes(uint8_t alignment, uint16_t offset) {
Section *sect = sect_GetSymbolSection(); Section *sect = sect_GetSymbolSection();
if (!sect) if (!sect) {
return 0; return 0;
}
bool isFixed = sect->org != UINT32_MAX; bool isFixed = sect->org != UINT32_MAX;
// If the section is not aligned, no bytes are needed // If the section is not aligned, no bytes are needed
// (fixed sections count as being maximally aligned for this purpose) // (fixed sections count as being maximally aligned for this purpose)
uint8_t curAlignment = isFixed ? 16 : sect->align; uint8_t curAlignment = isFixed ? 16 : sect->align;
if (curAlignment == 0) if (curAlignment == 0) {
return 0; return 0;
}
// We need `(pcValue + curOffset + return value) % (1 << alignment) == offset` // We need `(pcValue + curOffset + return value) % (1 << alignment) == offset`
uint16_t pcValue = isFixed ? sect->org : sect->alignOfs; uint16_t pcValue = isFixed ? sect->org : sect->alignOfs;
@@ -533,18 +564,20 @@ uint32_t sect_GetAlignBytes(uint8_t alignment, uint16_t offset) {
} }
void sect_AlignPC(uint8_t alignment, uint16_t offset) { void sect_AlignPC(uint8_t alignment, uint16_t offset) {
if (!requireSection()) if (!requireSection()) {
return; return;
}
Section *sect = sect_GetSymbolSection(); Section *sect = sect_GetSymbolSection();
uint32_t alignSize = 1 << alignment; // Size of an aligned "block" uint32_t alignSize = 1 << alignment; // Size of an aligned "block"
if (sect->org != UINT32_MAX) { if (sect->org != UINT32_MAX) {
if ((sect->org + curOffset - offset) % alignSize) if ((sect->org + curOffset - offset) % alignSize) {
error( error(
"Section's fixed address fails required alignment (PC = $%04" PRIx32 ")\n", "Section's fixed address fails required alignment (PC = $%04" PRIx32 ")\n",
sect->org + curOffset sect->org + curOffset
); );
}
} else if (sect->align != 0 } else if (sect->align != 0
&& (((sect->alignOfs + curOffset) % (1u << sect->align)) - offset) % alignSize) { && (((sect->alignOfs + curOffset) % (1u << sect->align)) - offset) % alignSize) {
error( error(
@@ -568,18 +601,22 @@ void sect_AlignPC(uint8_t alignment, uint16_t offset) {
} }
static void growSection(uint32_t growth) { static void growSection(uint32_t growth) {
if (growth > 0 && curOffset > UINT32_MAX - growth) if (growth > 0 && curOffset > UINT32_MAX - growth) {
fatalerror("Section size would overflow internal counter\n"); fatalerror("Section size would overflow internal counter\n");
}
curOffset += growth; curOffset += growth;
if (uint32_t outOffset = sect_GetOutputOffset(); outOffset > currentSection->size) if (uint32_t outOffset = sect_GetOutputOffset(); outOffset > currentSection->size) {
currentSection->size = outOffset; currentSection->size = outOffset;
if (currentLoadSection && curOffset > currentLoadSection->size) }
if (currentLoadSection && curOffset > currentLoadSection->size) {
currentLoadSection->size = curOffset; currentLoadSection->size = curOffset;
}
} }
static void writeByte(uint8_t byte) { static void writeByte(uint8_t byte) {
if (uint32_t index = sect_GetOutputOffset(); index < currentSection->data.size()) if (uint32_t index = sect_GetOutputOffset(); index < currentSection->data.size()) {
currentSection->data[index] = byte; currentSection->data[index] = byte;
}
growSection(1); growSection(1);
} }
@@ -621,8 +658,9 @@ static void endUnionMember() {
UnionStackEntry &member = currentUnionStack.top(); UnionStackEntry &member = currentUnionStack.top();
uint32_t memberSize = curOffset - member.start; uint32_t memberSize = curOffset - member.start;
if (memberSize > member.size) if (memberSize > member.size) {
member.size = memberSize; member.size = memberSize;
}
curOffset = member.start; curOffset = member.start;
} }
@@ -645,64 +683,75 @@ void sect_EndUnion() {
} }
void sect_CheckUnionClosed() { void sect_CheckUnionClosed() {
if (!currentUnionStack.empty()) if (!currentUnionStack.empty()) {
error("Unterminated UNION construct\n"); error("Unterminated UNION construct\n");
}
} }
// Output a constant byte // Output a constant byte
void sect_ConstByte(uint8_t byte) { void sect_ConstByte(uint8_t byte) {
if (!requireCodeSection()) if (!requireCodeSection()) {
return; return;
}
writeByte(byte); writeByte(byte);
} }
// Output a string's character units as bytes // Output a string's character units as bytes
void sect_ByteString(std::vector<int32_t> const &string) { void sect_ByteString(std::vector<int32_t> const &string) {
if (!requireCodeSection()) if (!requireCodeSection()) {
return; return;
for (int32_t unit : string) {
if (!checkNBit(unit, 8, "All character units"))
break;
} }
for (int32_t unit : string) for (int32_t unit : string) {
if (!checkNBit(unit, 8, "All character units")) {
break;
}
}
for (int32_t unit : string) {
writeByte(static_cast<uint8_t>(unit)); writeByte(static_cast<uint8_t>(unit));
}
} }
// Output a string's character units as words // Output a string's character units as words
void sect_WordString(std::vector<int32_t> const &string) { void sect_WordString(std::vector<int32_t> const &string) {
if (!requireCodeSection()) if (!requireCodeSection()) {
return; return;
for (int32_t unit : string) {
if (!checkNBit(unit, 16, "All character units"))
break;
} }
for (int32_t unit : string) for (int32_t unit : string) {
if (!checkNBit(unit, 16, "All character units")) {
break;
}
}
for (int32_t unit : string) {
writeWord(static_cast<uint16_t>(unit)); writeWord(static_cast<uint16_t>(unit));
}
} }
// Output a string's character units as longs // Output a string's character units as longs
void sect_LongString(std::vector<int32_t> const &string) { void sect_LongString(std::vector<int32_t> const &string) {
if (!requireCodeSection()) if (!requireCodeSection()) {
return; return;
}
for (int32_t unit : string) for (int32_t unit : string) {
writeLong(static_cast<uint32_t>(unit)); writeLong(static_cast<uint32_t>(unit));
}
} }
// Skip this many bytes // Skip this many bytes
void sect_Skip(uint32_t skip, bool ds) { void sect_Skip(uint32_t skip, bool ds) {
if (!requireSection()) if (!requireSection()) {
return; return;
}
if (!sect_HasData(currentSection->type)) { if (!sect_HasData(currentSection->type)) {
growSection(skip); growSection(skip);
} else { } else {
if (!ds) if (!ds) {
warning( warning(
WARNING_EMPTY_DATA_DIRECTIVE, WARNING_EMPTY_DATA_DIRECTIVE,
"%s directive without data in ROM\n", "%s directive without data in ROM\n",
@@ -710,16 +759,19 @@ void sect_Skip(uint32_t skip, bool ds) {
: (skip == 2) ? "DW" : (skip == 2) ? "DW"
: "DB" : "DB"
); );
}
// We know we're in a code SECTION // We know we're in a code SECTION
while (skip--) while (skip--) {
writeByte(fillByte); writeByte(fillByte);
}
} }
} }
// Output a byte that can be relocatable or constant // Output a byte that can be relocatable or constant
void sect_RelByte(Expression &expr, uint32_t pcShift) { void sect_RelByte(Expression &expr, uint32_t pcShift) {
if (!requireCodeSection()) if (!requireCodeSection()) {
return; return;
}
if (!expr.isKnown()) { if (!expr.isKnown()) {
createPatch(PATCHTYPE_BYTE, expr, pcShift); createPatch(PATCHTYPE_BYTE, expr, pcShift);
@@ -731,8 +783,9 @@ void sect_RelByte(Expression &expr, uint32_t pcShift) {
// Output several bytes that can be relocatable or constant // Output several bytes that can be relocatable or constant
void sect_RelBytes(uint32_t n, std::vector<Expression> &exprs) { void sect_RelBytes(uint32_t n, std::vector<Expression> &exprs) {
if (!requireCodeSection()) if (!requireCodeSection()) {
return; return;
}
for (uint32_t i = 0; i < n; i++) { for (uint32_t i = 0; i < n; i++) {
Expression &expr = exprs[i % exprs.size()]; Expression &expr = exprs[i % exprs.size()];
@@ -748,8 +801,9 @@ void sect_RelBytes(uint32_t n, std::vector<Expression> &exprs) {
// Output a word that can be relocatable or constant // Output a word that can be relocatable or constant
void sect_RelWord(Expression &expr, uint32_t pcShift) { void sect_RelWord(Expression &expr, uint32_t pcShift) {
if (!requireCodeSection()) if (!requireCodeSection()) {
return; return;
}
if (!expr.isKnown()) { if (!expr.isKnown()) {
createPatch(PATCHTYPE_WORD, expr, pcShift); createPatch(PATCHTYPE_WORD, expr, pcShift);
@@ -761,8 +815,9 @@ void sect_RelWord(Expression &expr, uint32_t pcShift) {
// Output a long that can be relocatable or constant // Output a long that can be relocatable or constant
void sect_RelLong(Expression &expr, uint32_t pcShift) { void sect_RelLong(Expression &expr, uint32_t pcShift) {
if (!requireCodeSection()) if (!requireCodeSection()) {
return; return;
}
if (!expr.isKnown()) { if (!expr.isKnown()) {
createPatch(PATCHTYPE_LONG, expr, pcShift); createPatch(PATCHTYPE_LONG, expr, pcShift);
@@ -774,8 +829,9 @@ void sect_RelLong(Expression &expr, uint32_t pcShift) {
// Output a PC-relative byte that can be relocatable or constant // Output a PC-relative byte that can be relocatable or constant
void sect_PCRelByte(Expression &expr, uint32_t pcShift) { void sect_PCRelByte(Expression &expr, uint32_t pcShift) {
if (!requireCodeSection()) if (!requireCodeSection()) {
return; return;
}
if (Symbol const *pc = sym_GetPC(); !expr.isDiffConstant(pc)) { if (Symbol const *pc = sym_GetPC(); !expr.isDiffConstant(pc)) {
createPatch(PATCHTYPE_JR, expr, pcShift); createPatch(PATCHTYPE_JR, expr, pcShift);
@@ -786,15 +842,16 @@ void sect_PCRelByte(Expression &expr, uint32_t pcShift) {
int16_t offset; int16_t offset;
// Offset is relative to the byte *after* the operand // Offset is relative to the byte *after* the operand
if (sym == pc) if (sym == pc) {
offset = -2; // PC as operand to `jr` is lower than reference PC by 2 offset = -2; // PC as operand to `jr` is lower than reference PC by 2
else } else {
offset = sym->getValue() - (pc->getValue() + 1); offset = sym->getValue() - (pc->getValue() + 1);
}
if (offset < -128 || offset > 127) { if (offset < -128 || offset > 127) {
error( error(
"jr target must be between -128 and 127 bytes away, not %" PRId16 "JR target must be between -128 and 127 bytes away, not %" PRId16
"; use jp instead\n", "; use JP instead\n",
offset offset
); );
writeByte(0); writeByte(0);
@@ -810,16 +867,19 @@ void sect_BinaryFile(std::string const &name, int32_t startPos) {
error("Start position cannot be negative (%" PRId32 ")\n", startPos); error("Start position cannot be negative (%" PRId32 ")\n", startPos);
startPos = 0; startPos = 0;
} }
if (!requireCodeSection()) if (!requireCodeSection()) {
return; return;
}
FILE *file = nullptr; FILE *file = nullptr;
if (std::optional<std::string> fullPath = fstk_FindFile(name); fullPath) if (std::optional<std::string> fullPath = fstk_FindFile(name); fullPath) {
file = fopen(fullPath->c_str(), "rb"); file = fopen(fullPath->c_str(), "rb");
}
if (!file) { if (!file) {
if (generatedMissingIncludes) { if (generatedMissingIncludes) {
if (verbose) if (verbose) {
printf("Aborting (-MG) on INCBIN file '%s' (%s)\n", name.c_str(), strerror(errno)); printf("Aborting (-MG) on INCBIN file '%s' (%s)\n", name.c_str(), strerror(errno));
}
failedOnMissingInclude = true; failedOnMissingInclude = true;
} else { } else {
error("Error opening INCBIN file '%s': %s\n", name.c_str(), strerror(errno)); error("Error opening INCBIN file '%s': %s\n", name.c_str(), strerror(errno));
@@ -836,10 +896,11 @@ void sect_BinaryFile(std::string const &name, int32_t startPos) {
// The file is seekable; skip to the specified start position // The file is seekable; skip to the specified start position
fseek(file, startPos, SEEK_SET); fseek(file, startPos, SEEK_SET);
} else { } else {
if (errno != ESPIPE) if (errno != ESPIPE) {
error( error(
"Error determining size of INCBIN file '%s': %s\n", name.c_str(), strerror(errno) "Error determining size of INCBIN file '%s': %s\n", name.c_str(), strerror(errno)
); );
}
// The file isn't seekable, so we'll just skip bytes one at a time // The file isn't seekable, so we'll just skip bytes one at a time
while (startPos--) { while (startPos--) {
if (fgetc(file) == EOF) { if (fgetc(file) == EOF) {
@@ -851,11 +912,13 @@ void sect_BinaryFile(std::string const &name, int32_t startPos) {
} }
} }
for (int byte; (byte = fgetc(file)) != EOF;) for (int byte; (byte = fgetc(file)) != EOF;) {
writeByte(byte); writeByte(byte);
}
if (ferror(file)) if (ferror(file)) {
error("Error reading INCBIN file '%s': %s\n", name.c_str(), strerror(errno)); error("Error reading INCBIN file '%s': %s\n", name.c_str(), strerror(errno));
}
} }
// Output a slice of a binary file // Output a slice of a binary file
@@ -868,18 +931,22 @@ void sect_BinaryFileSlice(std::string const &name, int32_t startPos, int32_t len
error("Number of bytes to read cannot be negative (%" PRId32 ")\n", length); error("Number of bytes to read cannot be negative (%" PRId32 ")\n", length);
length = 0; length = 0;
} }
if (!requireCodeSection()) if (!requireCodeSection()) {
return; return;
if (length == 0) // Don't even bother with 0-byte slices }
if (length == 0) { // Don't even bother with 0-byte slices
return; return;
}
FILE *file = nullptr; FILE *file = nullptr;
if (std::optional<std::string> fullPath = fstk_FindFile(name); fullPath) if (std::optional<std::string> fullPath = fstk_FindFile(name); fullPath) {
file = fopen(fullPath->c_str(), "rb"); file = fopen(fullPath->c_str(), "rb");
}
if (!file) { if (!file) {
if (generatedMissingIncludes) { if (generatedMissingIncludes) {
if (verbose) if (verbose) {
printf("Aborting (-MG) on INCBIN file '%s' (%s)\n", name.c_str(), strerror(errno)); printf("Aborting (-MG) on INCBIN file '%s' (%s)\n", name.c_str(), strerror(errno));
}
failedOnMissingInclude = true; failedOnMissingInclude = true;
} else { } else {
error("Error opening INCBIN file '%s': %s\n", name.c_str(), strerror(errno)); error("Error opening INCBIN file '%s': %s\n", name.c_str(), strerror(errno));
@@ -906,10 +973,11 @@ void sect_BinaryFileSlice(std::string const &name, int32_t startPos, int32_t len
// The file is seekable; skip to the specified start position // The file is seekable; skip to the specified start position
fseek(file, startPos, SEEK_SET); fseek(file, startPos, SEEK_SET);
} else { } else {
if (errno != ESPIPE) if (errno != ESPIPE) {
error( error(
"Error determining size of INCBIN file '%s': %s\n", name.c_str(), strerror(errno) "Error determining size of INCBIN file '%s': %s\n", name.c_str(), strerror(errno)
); );
}
// The file isn't seekable, so we'll just skip bytes one at a time // The file isn't seekable, so we'll just skip bytes one at a time
while (startPos--) { while (startPos--) {
if (fgetc(file) == EOF) { if (fgetc(file) == EOF) {
@@ -955,11 +1023,13 @@ void sect_PushSection() {
} }
void sect_PopSection() { void sect_PopSection() {
if (sectionStack.empty()) if (sectionStack.empty()) {
fatalerror("No entries in the section stack\n"); fatalerror("No entries in the section stack\n");
}
if (currentLoadSection) if (currentLoadSection) {
sect_EndLoadSection("POPS"); sect_EndLoadSection("POPS");
}
SectionStackEntry entry = sectionStack.front(); SectionStackEntry entry = sectionStack.front();
sectionStack.pop_front(); sectionStack.pop_front();
@@ -980,14 +1050,17 @@ void sect_CheckStack() {
} }
void sect_EndSection() { void sect_EndSection() {
if (!currentSection) if (!currentSection) {
fatalerror("Cannot end the section outside of a SECTION\n"); fatalerror("Cannot end the section outside of a SECTION\n");
}
if (!currentUnionStack.empty()) if (!currentUnionStack.empty()) {
fatalerror("Cannot end the section within a UNION\n"); fatalerror("Cannot end the section within a UNION\n");
}
if (currentLoadSection) if (currentLoadSection) {
sect_EndLoadSection("ENDSECTION"); sect_EndLoadSection("ENDSECTION");
}
// Reset the section scope // Reset the section scope
currentSection = nullptr; currentSection = nullptr;

View File

@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: MIT */ // SPDX-License-Identifier: MIT
#include "asm/symbol.hpp" #include "asm/symbol.hpp"
@@ -40,8 +40,9 @@ bool sym_IsPC(Symbol const *sym) {
} }
void sym_ForEach(void (*callback)(Symbol &)) { void sym_ForEach(void (*callback)(Symbol &)) {
for (auto &it : symbols) for (auto &it : symbols) {
callback(it.second); callback(it.second);
}
} }
static int32_t NARGCallback() { static int32_t NARGCallback() {
@@ -92,7 +93,7 @@ int32_t Symbol::getOutputValue() const {
} }
ContentSpan const &Symbol::getMacro() const { ContentSpan const &Symbol::getMacro() const {
assume((std::holds_alternative<ContentSpan>(data))); assume(std::holds_alternative<ContentSpan>(data));
return std::get<ContentSpan>(data); return std::get<ContentSpan>(data);
} }
@@ -101,8 +102,9 @@ std::shared_ptr<std::string> Symbol::getEqus() const {
std::holds_alternative<std::shared_ptr<std::string>>(data) std::holds_alternative<std::shared_ptr<std::string>>(data)
|| std::holds_alternative<std::shared_ptr<std::string> (*)()>(data) || std::holds_alternative<std::shared_ptr<std::string> (*)()>(data)
); );
if (auto *callback = std::get_if<std::shared_ptr<std::string> (*)()>(&data); callback) if (auto *callback = std::get_if<std::shared_ptr<std::string> (*)()>(&data); callback) {
return (*callback)(); return (*callback)();
}
return std::get<std::shared_ptr<std::string>>(data); return std::get<std::shared_ptr<std::string>>(data);
} }
@@ -123,8 +125,9 @@ static void updateSymbolFilename(Symbol &sym) {
sym.fileLine = sym.src ? lexer_GetLineNo() : 0; sym.fileLine = sym.src ? lexer_GetLineNo() : 0;
// If the old node was registered, ensure the new one is too // If the old node was registered, ensure the new one is too
if (oldSrc && oldSrc->ID != UINT32_MAX) if (oldSrc && oldSrc->ID != UINT32_MAX) {
out_RegisterNode(sym.src); out_RegisterNode(sym.src);
}
} }
static void alreadyDefinedError(Symbol const &sym, char const *asType) { static void alreadyDefinedError(Symbol const &sym, char const *asType) {
@@ -133,8 +136,9 @@ static void alreadyDefinedError(Symbol const &sym, char const *asType) {
error("'%s' is reserved for a built-in symbol\n", sym.name.c_str()); error("'%s' is reserved for a built-in symbol\n", sym.name.c_str());
} else { } else {
error("'%s' already defined", sym.name.c_str()); error("'%s' already defined", sym.name.c_str());
if (asType) if (asType) {
fprintf(stderr, " as %s", asType); fprintf(stderr, " as %s", asType);
}
fputs(" at ", stderr); fputs(" at ", stderr);
dumpFilename(sym); dumpFilename(sym);
} }
@@ -184,28 +188,34 @@ static bool isAutoScoped(std::string const &symName) {
size_t dotPos = symName.find('.'); size_t dotPos = symName.find('.');
// If there are no dots, it's not a local label // If there are no dots, it's not a local label
if (dotPos == std::string::npos) if (dotPos == std::string::npos) {
return false; return false;
}
// Label scopes `.` and `..` are the only nonlocal identifiers that start with a dot // Label scopes `.` and `..` are the only nonlocal identifiers that start with a dot
if (dotPos == 0 && symName.find_first_not_of('.') == symName.npos) if (dotPos == 0 && symName.find_first_not_of('.') == symName.npos) {
return false; return false;
}
// Check for nothing after the dot // Check for nothing after the dot
if (dotPos == symName.length() - 1) if (dotPos == symName.length() - 1) {
fatalerror("'%s' is a nonsensical reference to an empty local label\n", symName.c_str()); fatalerror("'%s' is a nonsensical reference to an empty local label\n", symName.c_str());
}
// Check for more than one dot // Check for more than one dot
if (symName.find('.', dotPos + 1) != std::string::npos) if (symName.find('.', dotPos + 1) != std::string::npos) {
fatalerror("'%s' is a nonsensical reference to a nested local label\n", symName.c_str()); fatalerror("'%s' is a nonsensical reference to a nested local label\n", symName.c_str());
}
// Check for already-qualified local label // Check for already-qualified local label
if (dotPos > 0) if (dotPos > 0) {
return false; return false;
}
// Check for unqualifiable local label // Check for unqualifiable local label
if (!globalScope) if (!globalScope) {
fatalerror("Unqualified local label '%s' in main scope\n", symName.c_str()); fatalerror("Unqualified local label '%s' in main scope\n", symName.c_str());
}
return true; return true;
} }
@@ -252,24 +262,28 @@ void sym_Purge(std::string const &symName) {
Symbol *sym = sym_FindScopedValidSymbol(symName); Symbol *sym = sym_FindScopedValidSymbol(symName);
if (!sym) { if (!sym) {
if (sym_IsPurgedScoped(symName)) if (sym_IsPurgedScoped(symName)) {
error("'%s' was already purged\n", symName.c_str()); error("'%s' was already purged\n", symName.c_str());
else } else {
error("'%s' not defined\n", symName.c_str()); error("'%s' not defined\n", symName.c_str());
}
} else if (sym->isBuiltin) { } else if (sym->isBuiltin) {
error("Built-in symbol '%s' cannot be purged\n", symName.c_str()); error("Built-in symbol '%s' cannot be purged\n", symName.c_str());
} else if (sym->ID != UINT32_MAX) { } else if (sym->ID != UINT32_MAX) {
error("Symbol \"%s\" is referenced and thus cannot be purged\n", symName.c_str()); error("Symbol \"%s\" is referenced and thus cannot be purged\n", symName.c_str());
} else { } else {
if (sym->isExported) if (sym->isExported) {
warning(WARNING_PURGE_1, "Purging an exported symbol \"%s\"\n", symName.c_str()); warning(WARNING_PURGE_1, "Purging an exported symbol \"%s\"\n", symName.c_str());
else if (sym->isLabel()) } else if (sym->isLabel()) {
warning(WARNING_PURGE_2, "Purging a label \"%s\"\n", symName.c_str()); warning(WARNING_PURGE_2, "Purging a label \"%s\"\n", symName.c_str());
}
// Do not keep a reference to the label after purging it // Do not keep a reference to the label after purging it
if (sym == globalScope) if (sym == globalScope) {
globalScope = nullptr; globalScope = nullptr;
if (sym == localScope) }
if (sym == localScope) {
localScope = nullptr; localScope = nullptr;
}
purgedSymbols.emplace(sym->name); purgedSymbols.emplace(sym->name);
symbols.erase(sym->name); symbols.erase(sym->name);
} }
@@ -295,14 +309,16 @@ void sym_SetRSValue(int32_t value) {
} }
uint32_t Symbol::getConstantValue() const { uint32_t Symbol::getConstantValue() const {
if (isConstant()) if (isConstant()) {
return getValue(); return getValue();
}
if (sym_IsPC(this)) { if (sym_IsPC(this)) {
if (!getSection()) if (!getSection()) {
error("PC has no value outside of a section\n"); error("PC has no value outside of a section\n");
else } else {
error("PC does not have a constant value; the current section is not fixed\n"); error("PC does not have a constant value; the current section is not fixed\n");
}
} else { } else {
error("\"%s\" does not have a constant value\n", name.c_str()); error("\"%s\" does not have a constant value\n", name.c_str());
} }
@@ -310,13 +326,15 @@ uint32_t Symbol::getConstantValue() const {
} }
uint32_t sym_GetConstantValue(std::string const &symName) { uint32_t sym_GetConstantValue(std::string const &symName) {
if (Symbol const *sym = sym_FindScopedSymbol(symName); sym) if (Symbol const *sym = sym_FindScopedSymbol(symName); sym) {
return sym->getConstantValue(); return sym->getConstantValue();
}
if (sym_IsPurgedScoped(symName)) if (sym_IsPurgedScoped(symName)) {
error("'%s' not defined; it was purged\n", symName.c_str()); error("'%s' not defined; it was purged\n", symName.c_str());
else } else {
error("'%s' not defined\n", symName.c_str()); error("'%s' not defined\n", symName.c_str());
}
return 0; return 0;
} }
@@ -361,8 +379,9 @@ static Symbol *createNonrelocSymbol(std::string const &symName, bool numeric) {
Symbol *sym_AddEqu(std::string const &symName, int32_t value) { Symbol *sym_AddEqu(std::string const &symName, int32_t value) {
Symbol *sym = createNonrelocSymbol(symName, true); Symbol *sym = createNonrelocSymbol(symName, true);
if (!sym) if (!sym) {
return nullptr; return nullptr;
}
sym->type = SYM_EQU; sym->type = SYM_EQU;
sym->data = value; sym->data = value;
@@ -373,8 +392,9 @@ Symbol *sym_AddEqu(std::string const &symName, int32_t value) {
Symbol *sym_RedefEqu(std::string const &symName, int32_t value) { Symbol *sym_RedefEqu(std::string const &symName, int32_t value) {
Symbol *sym = sym_FindExactSymbol(symName); Symbol *sym = sym_FindExactSymbol(symName);
if (!sym) if (!sym) {
return sym_AddEqu(symName, value); return sym_AddEqu(symName, value);
}
if (sym->isDefined() && sym->type != SYM_EQU) { if (sym->isDefined() && sym->type != SYM_EQU) {
alreadyDefinedError(*sym, "non-EQU"); alreadyDefinedError(*sym, "non-EQU");
@@ -394,8 +414,9 @@ Symbol *sym_RedefEqu(std::string const &symName, int32_t value) {
Symbol *sym_AddString(std::string const &symName, std::shared_ptr<std::string> str) { Symbol *sym_AddString(std::string const &symName, std::shared_ptr<std::string> str) {
Symbol *sym = createNonrelocSymbol(symName, false); Symbol *sym = createNonrelocSymbol(symName, false);
if (!sym) if (!sym) {
return nullptr; return nullptr;
}
sym->type = SYM_EQUS; sym->type = SYM_EQUS;
sym->data = str; sym->data = str;
@@ -405,8 +426,9 @@ Symbol *sym_AddString(std::string const &symName, std::shared_ptr<std::string> s
Symbol *sym_RedefString(std::string const &symName, std::shared_ptr<std::string> str) { Symbol *sym_RedefString(std::string const &symName, std::shared_ptr<std::string> str) {
Symbol *sym = sym_FindExactSymbol(symName); Symbol *sym = sym_FindExactSymbol(symName);
if (!sym) if (!sym) {
return sym_AddString(symName, str); return sym_AddString(symName, str);
}
if (sym->type != SYM_EQUS) { if (sym->type != SYM_EQUS) {
if (sym->isDefined()) { if (sym->isDefined()) {
@@ -462,12 +484,14 @@ static Symbol *addLabel(std::string const &symName) {
sym->type = SYM_LABEL; sym->type = SYM_LABEL;
sym->data = static_cast<int32_t>(sect_GetSymbolOffset()); sym->data = static_cast<int32_t>(sect_GetSymbolOffset());
// Don't export anonymous labels // Don't export anonymous labels
if (exportAll && !symName.starts_with('!')) if (exportAll && !symName.starts_with('!')) {
sym->isExported = true; sym->isExported = true;
}
sym->section = sect_GetSymbolSection(); sym->section = sect_GetSymbolSection();
if (sym && !sym->section) if (sym && !sym->section) {
error("Label \"%s\" created outside of a SECTION\n", symName.c_str()); error("Label \"%s\" created outside of a SECTION\n", symName.c_str());
}
return sym; return sym;
} }
@@ -478,8 +502,9 @@ Symbol *sym_AddLocalLabel(std::string const &symName) {
Symbol *sym = addLabel(isAutoScoped(symName) ? globalScope->name + symName : symName); Symbol *sym = addLabel(isAutoScoped(symName) ? globalScope->name + symName : symName);
if (sym) if (sym) {
localScope = sym; localScope = sym;
}
return sym; return sym;
} }
@@ -516,7 +541,7 @@ std::string sym_MakeAnonLabelName(uint32_t ofs, bool neg) {
uint32_t id = 0; uint32_t id = 0;
if (neg) { if (neg) {
if (ofs > anonLabelID) if (ofs > anonLabelID) {
error( error(
"Reference to anonymous label %" PRIu32 " before, when only %" PRIu32 "Reference to anonymous label %" PRIu32 " before, when only %" PRIu32
" ha%s been created so far\n", " ha%s been created so far\n",
@@ -524,19 +549,21 @@ std::string sym_MakeAnonLabelName(uint32_t ofs, bool neg) {
anonLabelID, anonLabelID,
anonLabelID == 1 ? "s" : "ve" anonLabelID == 1 ? "s" : "ve"
); );
else } else {
id = anonLabelID - ofs; id = anonLabelID - ofs;
}
} else { } else {
ofs--; // We're referencing symbols that haven't been created yet... ofs--; // We're referencing symbols that haven't been created yet...
if (ofs > UINT32_MAX - anonLabelID) if (ofs > UINT32_MAX - anonLabelID) {
error( error(
"Reference to anonymous label %" PRIu32 " after, when only %" PRIu32 "Reference to anonymous label %" PRIu32 " after, when only %" PRIu32
" may still be created\n", " may still be created\n",
ofs + 1, ofs + 1,
UINT32_MAX - anonLabelID UINT32_MAX - anonLabelID
); );
else } else {
id = anonLabelID + ofs; id = anonLabelID + ofs;
}
} }
std::string anon("!"); std::string anon("!");
@@ -553,16 +580,18 @@ void sym_Export(std::string const &symName) {
Symbol *sym = sym_FindScopedSymbol(symName); Symbol *sym = sym_FindScopedSymbol(symName);
// If the symbol doesn't exist, create a ref that can be purged // If the symbol doesn't exist, create a ref that can be purged
if (!sym) if (!sym) {
sym = sym_Ref(symName); sym = sym_Ref(symName);
}
sym->isExported = true; sym->isExported = true;
} }
Symbol *sym_AddMacro(std::string const &symName, int32_t defLineNo, ContentSpan const &span) { Symbol *sym_AddMacro(std::string const &symName, int32_t defLineNo, ContentSpan const &span) {
Symbol *sym = createNonrelocSymbol(symName, false); Symbol *sym = createNonrelocSymbol(symName, false);
if (!sym) if (!sym) {
return nullptr; return nullptr;
}
sym->type = SYM_MACRO; sym->type = SYM_MACRO;
sym->data = span; sym->data = span;

View File

@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: MIT */ // SPDX-License-Identifier: MIT
#include "asm/warning.hpp" #include "asm/warning.hpp"
@@ -35,13 +35,13 @@ struct WarningFlag {
WarningLevel level; WarningLevel level;
}; };
static const WarningFlag metaWarnings[] = { static WarningFlag const metaWarnings[] = {
{"all", LEVEL_ALL }, {"all", LEVEL_ALL },
{"extra", LEVEL_EXTRA }, {"extra", LEVEL_EXTRA },
{"everything", LEVEL_EVERYTHING}, {"everything", LEVEL_EVERYTHING},
}; };
static const WarningFlag warningFlags[NB_WARNINGS] = { static WarningFlag const warningFlags[NB_WARNINGS] = {
{"assert", LEVEL_DEFAULT }, {"assert", LEVEL_DEFAULT },
{"backwards-for", LEVEL_ALL }, {"backwards-for", LEVEL_ALL },
{"builtin-args", LEVEL_ALL }, {"builtin-args", LEVEL_ALL },
@@ -85,8 +85,9 @@ enum WarningBehavior { DISABLED, ENABLED, ERROR };
static WarningBehavior getWarningBehavior(WarningID id) { static WarningBehavior getWarningBehavior(WarningID id) {
// Check if warnings are globally disabled // Check if warnings are globally disabled
if (!warnings) if (!warnings) {
return WarningBehavior::DISABLED; return WarningBehavior::DISABLED;
}
// Get the state of this warning flag // Get the state of this warning flag
WarningState const &flagState = warningStates.flagStates[id]; WarningState const &flagState = warningStates.flagStates[id];
@@ -100,34 +101,43 @@ static WarningBehavior getWarningBehavior(WarningID id) {
warningIsError ? WarningBehavior::ERROR : WarningBehavior::ENABLED; warningIsError ? WarningBehavior::ERROR : WarningBehavior::ENABLED;
// First, check the state of the specific warning flag // 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; return WarningBehavior::DISABLED;
if (flagState.error == WARNING_ENABLED) // -Werror=<flag> }
if (flagState.error == WARNING_ENABLED) { // -Werror=<flag>
return WarningBehavior::ERROR; return WarningBehavior::ERROR;
if (flagState.state == WARNING_ENABLED) // -W<flag> }
if (flagState.state == WARNING_ENABLED) { // -W<flag>
return enabledBehavior; return enabledBehavior;
}
// If no flag is specified, check the state of the "meta" flags that affect this warning flag // 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; return WarningBehavior::DISABLED;
if (metaState.error == WARNING_ENABLED) // -Werror=<meta> }
if (metaState.error == WARNING_ENABLED) { // -Werror=<meta>
return WarningBehavior::ERROR; return WarningBehavior::ERROR;
if (metaState.state == WARNING_ENABLED) // -W<meta> }
if (metaState.state == WARNING_ENABLED) { // -W<meta>
return enabledBehavior; return enabledBehavior;
}
// If no meta flag is specified, check the default state of this warning flag // 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; return enabledBehavior;
}
// No flag enables this warning, explicitly or implicitly // No flag enables this warning, explicitly or implicitly
return WarningBehavior::DISABLED; return WarningBehavior::DISABLED;
} }
void WarningState::update(WarningState other) { void WarningState::update(WarningState other) {
if (other.state != WARNING_DEFAULT) if (other.state != WARNING_DEFAULT) {
state = other.state; state = other.state;
if (other.error != WARNING_DEFAULT) }
if (other.error != WARNING_DEFAULT) {
error = other.error; error = other.error;
}
} }
void processWarningFlag(char const *flag) { void processWarningFlag(char const *flag) {
@@ -149,16 +159,16 @@ void processWarningFlag(char const *flag) {
if (rootFlag.starts_with("error=")) { if (rootFlag.starts_with("error=")) {
// `-Werror=<flag>` enables the flag as an error // `-Werror=<flag>` enables the flag as an error
state = {.state = WARNING_ENABLED, .error = WARNING_ENABLED}; 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=")) { } else if (rootFlag.starts_with("no-error=")) {
// `-Wno-error=<flag>` prevents the flag from being an error, // `-Wno-error=<flag>` prevents the flag from being an error,
// without affecting whether it is enabled // without affecting whether it is enabled
state = {.state = WARNING_DEFAULT, .error = WARNING_DISABLED}; 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-")) { } else if (rootFlag.starts_with("no-")) {
// `-Wno-<flag>` disables the flag // `-Wno-<flag>` disables the flag
state = {.state = WARNING_DISABLED, .error = WARNING_DEFAULT}; state = {.state = WARNING_DISABLED, .error = WARNING_DEFAULT};
rootFlag.erase(0, QUOTEDSTRLEN("no-")); rootFlag.erase(0, literal_strlen("no-"));
} else { } else {
// `-W<flag>` enables the flag // `-W<flag>` enables the flag
state = {.state = WARNING_ENABLED, .error = WARNING_DEFAULT}; state = {.state = WARNING_ENABLED, .error = WARNING_DEFAULT};
@@ -184,12 +194,14 @@ void processWarningFlag(char const *flag) {
// The `if`'s condition above ensures that this will run at least once // The `if`'s condition above ensures that this will run at least once
do { do {
// If we don't have a digit, bail // If we don't have a digit, bail
if (*ptr < '0' || *ptr > '9') if (*ptr < '0' || *ptr > '9') {
break; break;
}
// Avoid overflowing! // Avoid overflowing!
if (param > UINT8_MAX - (*ptr - '0')) { if (param > UINT8_MAX - (*ptr - '0')) {
if (!warned) if (!warned) {
warnx("Invalid warning flag \"%s\": capping parameter at 255", flag); warnx("Invalid warning flag \"%s\": capping parameter at 255", flag);
}
warned = true; // Only warn once, cap always warned = true; // Only warn once, cap always
param = 255; param = 255;
continue; continue;
@@ -203,8 +215,9 @@ void processWarningFlag(char const *flag) {
if (*ptr == '\0') { if (*ptr == '\0') {
rootFlag.resize(equals); rootFlag.resize(equals);
// `-W<flag>=0` is equivalent to `-Wno-<flag>` // `-W<flag>=0` is equivalent to `-Wno-<flag>`
if (param == 0) if (param == 0) {
state.state = WARNING_DISABLED; state.state = WARNING_DISABLED;
}
} }
} }
} }
@@ -218,8 +231,9 @@ void processWarningFlag(char const *flag) {
assume(paramWarning.defaultLevel <= maxParam); assume(paramWarning.defaultLevel <= maxParam);
if (rootFlag == warningFlags[baseID].name) { // Match! if (rootFlag == warningFlags[baseID].name) { // Match!
if (rootFlag == "numeric-string") if (rootFlag == "numeric-string") {
warning(WARNING_OBSOLETE, "Warning flag \"numeric-string\" is deprecated\n"); warning(WARNING_OBSOLETE, "Warning flag \"numeric-string\" is deprecated\n");
}
// If making the warning an error but param is 0, set to the maximum // 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 // This accommodates `-Werror=<flag>`, but also `-Werror=<flag>=0`, which is
@@ -229,7 +243,7 @@ void processWarningFlag(char const *flag) {
if (param == 0) { if (param == 0) {
param = paramWarning.defaultLevel; param = paramWarning.defaultLevel;
} else if (param > maxParam) { } else if (param > maxParam) {
if (param != 255) // Don't warn if already capped if (param != 255) { // Don't warn if already capped
warnx( warnx(
"Invalid parameter %" PRIu8 "Invalid parameter %" PRIu8
" for warning flag \"%s\"; capping at maximum %" PRIu8, " for warning flag \"%s\"; capping at maximum %" PRIu8,
@@ -237,16 +251,18 @@ void processWarningFlag(char const *flag) {
rootFlag.c_str(), rootFlag.c_str(),
maxParam maxParam
); );
}
param = maxParam; param = maxParam;
} }
// Set the first <param> to enabled/error, and disable the rest // Set the first <param> to enabled/error, and disable the rest
for (uint8_t ofs = 0; ofs < maxParam; ofs++) { for (uint8_t ofs = 0; ofs < maxParam; ofs++) {
WarningState &warning = warningStates.flagStates[baseID + ofs]; WarningState &warning = warningStates.flagStates[baseID + ofs];
if (ofs < param) if (ofs < param) {
warning.update(state); warning.update(state);
else } else {
warning.state = WARNING_DISABLED; warning.state = WARNING_DISABLED;
}
} }
return; return;
} }
@@ -259,8 +275,9 @@ void processWarningFlag(char const *flag) {
if (rootFlag == metaWarning.name) { if (rootFlag == metaWarning.name) {
// Set each of the warning flags that meets this level // Set each of the warning flags that meets this level
for (WarningID id : EnumSeq(NB_WARNINGS)) { for (WarningID id : EnumSeq(NB_WARNINGS)) {
if (metaWarning.level >= warningFlags[id].level) if (metaWarning.level >= warningFlags[id].level) {
warningStates.metaStates[id].update(state); warningStates.metaStates[id].update(state);
}
} }
return; return;
} }
@@ -299,16 +316,18 @@ void error(char const *fmt, ...) {
// This intentionally makes 0 act as "unlimited" (or at least "limited to sizeof(unsigned)") // This intentionally makes 0 act as "unlimited" (or at least "limited to sizeof(unsigned)")
nbErrors++; nbErrors++;
if (nbErrors == maxErrors) if (nbErrors == maxErrors) {
errx( errx(
"The maximum of %u error%s was reached (configure with \"-X/--max-errors\"); assembly " "The maximum of %u error%s was reached (configure with \"-X/--max-errors\"); assembly "
"aborted!", "aborted!",
maxErrors, maxErrors,
maxErrors == 1 ? "" : "s" maxErrors == 1 ? "" : "s"
); );
}
} }
[[noreturn]] void fatalerror(char const *fmt, ...) { [[noreturn]]
void fatalerror(char const *fmt, ...) {
va_list args; va_list args;
va_start(args, fmt); va_start(args, fmt);

View File

@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: MIT */ // SPDX-License-Identifier: MIT
#include "error.hpp" #include "error.hpp"
@@ -22,7 +22,8 @@ static void vwarnx(char const *fmt, va_list ap) {
putc('\n', stderr); putc('\n', stderr);
} }
[[noreturn]] static void verr(char const *fmt, va_list ap) { [[noreturn]]
static void verr(char const *fmt, va_list ap) {
char const *error = strerror(errno); char const *error = strerror(errno);
fprintf(stderr, "error: "); fprintf(stderr, "error: ");
@@ -32,7 +33,8 @@ static void vwarnx(char const *fmt, va_list ap) {
exit(1); exit(1);
} }
[[noreturn]] static void verrx(char const *fmt, va_list ap) { [[noreturn]]
static void verrx(char const *fmt, va_list ap) {
fprintf(stderr, "error: "); fprintf(stderr, "error: ");
vfprintf(stderr, fmt, ap); vfprintf(stderr, fmt, ap);
putc('\n', stderr); putc('\n', stderr);
@@ -56,14 +58,16 @@ void warnx(char const *fmt, ...) {
va_end(ap); va_end(ap);
} }
[[noreturn]] void err(char const *fmt, ...) { [[noreturn]]
void err(char const *fmt, ...) {
va_list ap; va_list ap;
va_start(ap, fmt); va_start(ap, fmt);
verr(fmt, ap); verr(fmt, ap);
} }
[[noreturn]] void errx(char const *fmt, ...) { [[noreturn]]
void errx(char const *fmt, ...) {
va_list ap; va_list ap;
va_start(ap, fmt); va_start(ap, fmt);

77
src/extern/getopt.cpp vendored
View File

@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: MIT */ // SPDX-License-Identifier: MIT
/* This implementation was taken from musl and modified for RGBDS */ // This implementation was taken from musl and modified for RGBDS
#include "extern/getopt.hpp" #include "extern/getopt.hpp"
@@ -19,8 +19,9 @@ static int musl_optpos;
static void musl_getopt_msg(char const *a, char const *b, char const *c, size_t l) { static void musl_getopt_msg(char const *a, char const *b, char const *c, size_t l) {
FILE *f = stderr; FILE *f = stderr;
if (fputs(a, f) >= 0 && fwrite(b, strlen(b), 1, f) && fwrite(c, 1, l, f) == l) if (fputs(a, f) >= 0 && fwrite(b, strlen(b), 1, f) && fwrite(c, 1, l, f) == l) {
putc('\n', f); putc('\n', f);
}
} }
static int getopt(int argc, char *argv[], char const *optstring) { static int getopt(int argc, char *argv[], char const *optstring) {
@@ -35,8 +36,9 @@ static int getopt(int argc, char *argv[], char const *optstring) {
musl_optind = 1; musl_optind = 1;
} }
if (musl_optind >= argc || !argv[musl_optind]) if (musl_optind >= argc || !argv[musl_optind]) {
return -1; return -1;
}
if (argv[musl_optind][0] != '-') { if (argv[musl_optind][0] != '-') {
if (optstring[0] == '-') { if (optstring[0] == '-') {
@@ -46,18 +48,21 @@ static int getopt(int argc, char *argv[], char const *optstring) {
return -1; return -1;
} }
if (!argv[musl_optind][1]) if (!argv[musl_optind][1]) {
return -1; return -1;
}
if (argv[musl_optind][1] == '-' && !argv[musl_optind][2]) if (argv[musl_optind][1] == '-' && !argv[musl_optind][2]) {
return musl_optind++, -1; return musl_optind++, -1;
}
if (!musl_optpos) if (!musl_optpos) {
musl_optpos++; musl_optpos++;
}
k = mbtowc(&c, argv[musl_optind] + musl_optpos, MB_LEN_MAX); k = mbtowc(&c, argv[musl_optind] + musl_optpos, MB_LEN_MAX);
if (k < 0) { if (k < 0) {
k = 1; k = 1;
c = 0xFFFD; /* replacement char */ c = 0xFFFD; // replacement char
} }
optchar = argv[musl_optind] + musl_optpos; optchar = argv[musl_optind] + musl_optpos;
musl_optpos += k; musl_optpos += k;
@@ -67,23 +72,26 @@ static int getopt(int argc, char *argv[], char const *optstring) {
musl_optpos = 0; musl_optpos = 0;
} }
if (optstring[0] == '-' || optstring[0] == '+') if (optstring[0] == '-' || optstring[0] == '+') {
optstring++; optstring++;
}
i = 0; i = 0;
d = 0; d = 0;
do { do {
l = mbtowc(&d, optstring + i, MB_LEN_MAX); l = mbtowc(&d, optstring + i, MB_LEN_MAX);
if (l > 0) if (l > 0) {
i += l; i += l;
else } else {
i++; i++;
}
} while (l && d != c); } while (l && d != c);
if (d != c || c == ':') { if (d != c || c == ':') {
musl_optopt = c; musl_optopt = c;
if (optstring[0] != ':' && musl_opterr) if (optstring[0] != ':' && musl_opterr) {
musl_getopt_msg(argv[0], ": unrecognized option: ", optchar, k); musl_getopt_msg(argv[0], ": unrecognized option: ", optchar, k);
}
return '?'; return '?';
} }
if (optstring[i] == ':') { if (optstring[i] == ':') {
@@ -94,10 +102,12 @@ static int getopt(int argc, char *argv[], char const *optstring) {
} }
if (musl_optind > argc) { if (musl_optind > argc) {
musl_optopt = c; musl_optopt = c;
if (optstring[0] == ':') if (optstring[0] == ':') {
return ':'; return ':';
if (musl_opterr) }
if (musl_opterr) {
musl_getopt_msg(argv[0], ": option requires an argument: ", optchar, k); musl_getopt_msg(argv[0], ": option requires an argument: ", optchar, k);
}
return '?'; return '?';
} }
} }
@@ -108,8 +118,9 @@ static void permute(char **argv, int dest, int src) {
char *tmp = argv[src]; char *tmp = argv[src];
int i; int i;
for (i = src; i > dest; i--) for (i = src; i > dest; i--) {
argv[i] = argv[i - 1]; argv[i] = argv[i - 1];
}
argv[dest] = tmp; argv[dest] = tmp;
} }
@@ -128,17 +139,20 @@ static int musl_getopt_long(
musl_optind = 1; musl_optind = 1;
} }
if (musl_optind >= argc || !argv[musl_optind]) if (musl_optind >= argc || !argv[musl_optind]) {
return -1; return -1;
}
skipped = musl_optind; skipped = musl_optind;
if (optstring[0] != '+' && optstring[0] != '-') { if (optstring[0] != '+' && optstring[0] != '-') {
int i; int i;
for (i = musl_optind;; i++) { for (i = musl_optind;; i++) {
if (i >= argc || !argv[i]) if (i >= argc || !argv[i]) {
return -1; return -1;
if (argv[i][0] == '-' && argv[i][1]) }
if (argv[i][0] == '-' && argv[i][1]) {
break; break;
}
} }
musl_optind = i; musl_optind = i;
} }
@@ -147,8 +161,9 @@ static int musl_getopt_long(
if (resumed > skipped) { if (resumed > skipped) {
int i, cnt = musl_optind - resumed; int i, cnt = musl_optind - resumed;
for (i = 0; i < cnt; i++) for (i = 0; i < cnt; i++) {
permute(argv, skipped, musl_optind - 1); permute(argv, skipped, musl_optind - 1);
}
musl_optind = skipped + cnt; musl_optind = skipped + cnt;
} }
return ret; return ret;
@@ -169,14 +184,16 @@ static int musl_getopt_long_core(
char const *name = longopts[i].name; char const *name = longopts[i].name;
opt = start; opt = start;
if (*opt == '-') if (*opt == '-') {
opt++; opt++;
}
while (*opt && *opt != '=' && *opt == *name) { while (*opt && *opt != '=' && *opt == *name) {
name++; name++;
opt++; opt++;
} }
if (*opt && *opt != '=') if (*opt && *opt != '=') {
continue; continue;
}
arg = opt; arg = opt;
match = i; match = i;
if (!*name) { if (!*name) {
@@ -191,8 +208,9 @@ static int musl_getopt_long_core(
for (i = 0; optstring[i]; i++) { for (i = 0; optstring[i]; i++) {
int j = 0; int j = 0;
while (j < l && start[j] == optstring[i + j]) while (j < l && start[j] == optstring[i + j]) {
j++; j++;
}
if (j == l) { if (j == l) {
cnt++; cnt++;
break; break;
@@ -206,8 +224,9 @@ static int musl_getopt_long_core(
if (*opt == '=') { if (*opt == '=') {
if (!longopts[i].has_arg) { if (!longopts[i].has_arg) {
musl_optopt = longopts[i].val; musl_optopt = longopts[i].val;
if (colon || !musl_opterr) if (colon || !musl_opterr) {
return '?'; return '?';
}
musl_getopt_msg( musl_getopt_msg(
argv[0], argv[0],
": option does not take an argument: ", ": option does not take an argument: ",
@@ -221,10 +240,12 @@ static int musl_getopt_long_core(
musl_optarg = argv[musl_optind]; musl_optarg = argv[musl_optind];
if (!musl_optarg) { if (!musl_optarg) {
musl_optopt = longopts[i].val; musl_optopt = longopts[i].val;
if (colon) if (colon) {
return ':'; return ':';
if (!musl_opterr) }
if (!musl_opterr) {
return '?'; return '?';
}
musl_getopt_msg( musl_getopt_msg(
argv[0], argv[0],
": option requires an argument: ", ": option requires an argument: ",
@@ -235,8 +256,9 @@ static int musl_getopt_long_core(
} }
musl_optind++; musl_optind++;
} }
if (idx) if (idx) {
*idx = i; *idx = i;
}
if (longopts[i].flag) { if (longopts[i].flag) {
*longopts[i].flag = longopts[i].val; *longopts[i].flag = longopts[i].val;
return 0; return 0;
@@ -245,13 +267,14 @@ static int musl_getopt_long_core(
} }
if (argv[musl_optind][1] == '-') { if (argv[musl_optind][1] == '-') {
musl_optopt = 0; musl_optopt = 0;
if (!colon && musl_opterr) if (!colon && musl_opterr) {
musl_getopt_msg( musl_getopt_msg(
argv[0], argv[0],
cnt ? ": option is ambiguous: " : ": unrecognized option: ", cnt ? ": option is ambiguous: " : ": unrecognized option: ",
argv[musl_optind] + 2, argv[musl_optind] + 2,
strlen(argv[musl_optind] + 2) strlen(argv[musl_optind] + 2)
); );
}
musl_optind++; musl_optind++;
return '?'; return '?';
} }

View File

@@ -1,35 +1,35 @@
/* SPDX-License-Identifier: MIT */ // SPDX-License-Identifier: MIT
/* UTF-8 decoder: http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ */ // UTF-8 decoder: http://bjoern.hoehrmann.de/utf-8/decoder/dfa/
#include "extern/utf8decoder.hpp" #include "extern/utf8decoder.hpp"
static uint8_t const utf8d[] = { static uint8_t const utf8d[] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 00..0f */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 00..0f
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 10..1f */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 10..1f
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 20..2f */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20..2f
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 30..3f */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 30..3f
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 40..4f */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 40..4f
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 50..5f */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 50..5f
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 60..6f */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 60..6f
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 70..7f */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 70..7f
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 80..8f */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 80..8f
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, /* 90..9f */ 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, // 90..9f
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, /* a0..af */ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // a0..af
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, /* b0..bf */ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // b0..bf
8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* c0..cf */ 8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // c0..cf
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* d0..df */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // d0..df
10, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 3, /* e0..ef */ 10, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 3, // e0..ef
11, 6, 6, 6, 5, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, /* f0..ff */ 11, 6, 6, 6, 5, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, // f0..ff
0, 1, 2, 3, 5, 8, 7, 1, 1, 1, 4, 6, 1, 1, 1, 1, /* s0 */ 0, 1, 2, 3, 5, 8, 7, 1, 1, 1, 4, 6, 1, 1, 1, 1, // s0
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* s1 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // s1
1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, /* s1 */ 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, // s1
1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, /* s3 */ 1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, // s3
1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, /* s4 */ 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, // s4
1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, /* s5 */ 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, // s5
1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, /* s6 */ 1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, // s6
1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, /* s7 */ 1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, // s7
1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* s8 */ 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // s8
}; };
uint32_t decode(uint32_t *state, uint32_t *codep, uint8_t byte) { uint32_t decode(uint32_t *state, uint32_t *codep, uint8_t byte) {

View File

@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: MIT */ // SPDX-License-Identifier: MIT
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/types.h> #include <sys/types.h>
@@ -17,27 +17,26 @@
#include "platform.hpp" #include "platform.hpp"
#include "version.hpp" #include "version.hpp"
#define UNSPECIFIED 0x200 // Should not be in byte range static constexpr uint16_t UNSPECIFIED = 0x200;
static_assert(UNSPECIFIED > 0xFF, "UNSPECIFIED should not be in byte range!");
#define BANK_SIZE 0x4000 static constexpr off_t BANK_SIZE = 0x4000;
// Short options // Short options
static char const *optstring = "Ccf:i:jk:L:l:m:n:Op:r:st:Vv"; static char const *optstring = "Ccf:hi:jk:L:l:m:n:Op:r:st:Vv";
/* // Equivalent long options
* Equivalent long options // Please keep in the same order as short opts.
* Please keep in the same order as short opts // Also, make sure long opts don't create ambiguity:
* // A long opt's name should start with the same letter as its short opt,
* Also, make sure long opts don't create ambiguity: // except if it doesn't create any ambiguity (`verbose` versus `version`).
* A long opt's name should start with the same letter as its short opt, // This is because long opt matching, even to a single char, is prioritized
* except if it doesn't create any ambiguity (`verbose` versus `version`). // over short opt matching.
* This is because long opt matching, even to a single char, is prioritized
* over short opt matching
*/
static option const longopts[] = { static option const longopts[] = {
{"color-only", no_argument, nullptr, 'C'}, {"color-only", no_argument, nullptr, 'C'},
{"color-compatible", no_argument, nullptr, 'c'}, {"color-compatible", no_argument, nullptr, 'c'},
{"fix-spec", required_argument, nullptr, 'f'}, {"fix-spec", required_argument, nullptr, 'f'},
{"help", no_argument, nullptr, 'h'},
{"game-id", required_argument, nullptr, 'i'}, {"game-id", required_argument, nullptr, 'i'},
{"non-japanese", no_argument, nullptr, 'j'}, {"non-japanese", no_argument, nullptr, 'j'},
{"new-licensee", required_argument, nullptr, 'k'}, {"new-licensee", required_argument, nullptr, 'k'},
@@ -57,7 +56,7 @@ static option const longopts[] = {
static void printUsage() { static void printUsage() {
fputs( fputs(
"Usage: rgbfix [-jOsVv] [-C | -c] [-f <fix_spec>] [-i <game_id>] [-k <licensee>]\n" "Usage: rgbfix [-hjOsVv] [-C | -c] [-f <fix_spec>] [-i <game_id>] [-k <licensee>]\n"
" [-L <logo_file>] [-l <licensee_byte>] [-m <mbc_type>]\n" " [-L <logo_file>] [-l <licensee_byte>] [-m <mbc_type>]\n"
" [-n <rom_version>] [-p <pad_value>] [-r <ram_size>] [-t <title_str>]\n" " [-n <rom_version>] [-p <pad_value>] [-r <ram_size>] [-t <title_str>]\n"
" <file> ...\n" " <file> ...\n"
@@ -76,15 +75,17 @@ static void printUsage() {
static uint8_t nbErrors; static uint8_t nbErrors;
[[gnu::format(printf, 1, 2)]] static void report(char const *fmt, ...) { [[gnu::format(printf, 1, 2)]]
static void report(char const *fmt, ...) {
va_list ap; va_list ap;
va_start(ap, fmt); va_start(ap, fmt);
vfprintf(stderr, fmt, ap); vfprintf(stderr, fmt, ap);
va_end(ap); va_end(ap);
if (nbErrors != UINT8_MAX) if (nbErrors != UINT8_MAX) {
nbErrors++; nbErrors++;
}
} }
enum MbcType { enum MbcType {
@@ -184,23 +185,23 @@ static void printAcceptedMBCNames() {
static uint8_t tpp1Rev[2]; static uint8_t tpp1Rev[2];
/*
* @return False on failure
*/
static bool readMBCSlice(char const *&name, char const *expected) { static bool readMBCSlice(char const *&name, char const *expected) {
while (*expected) { while (*expected) {
char c = *name++; char c = *name++;
if (c == '\0') // Name too short if (c == '\0') { // Name too short
return false; return false;
}
if (c >= 'a' && c <= 'z') // Perform the comparison case-insensitive if (c >= 'a' && c <= 'z') { // Perform the comparison case-insensitive
c = c - 'a' + 'A'; c = c - 'a' + 'A';
else if (c == '_') // Treat underscores as spaces } else if (c == '_') { // Treat underscores as spaces
c = ' '; c = ' ';
}
if (c != *expected++) if (c != *expected++) {
return false; return false;
}
} }
return true; return true;
} }
@@ -223,10 +224,12 @@ static MbcType parseMBC(char const *name) {
char *endptr; char *endptr;
unsigned long mbc = strtoul(name, &endptr, base); unsigned long mbc = strtoul(name, &endptr, base);
if (*endptr) if (*endptr) {
return MBC_BAD; return MBC_BAD;
if (mbc > 0xFF) }
if (mbc > 0xFF) {
return MBC_BAD_RANGE; return MBC_BAD_RANGE;
}
return static_cast<MbcType>(mbc); return static_cast<MbcType>(mbc);
} else { } else {
@@ -235,13 +238,15 @@ static MbcType parseMBC(char const *name) {
char const *ptr = name; char const *ptr = name;
// Trim off leading whitespace // Trim off leading whitespace
while (*ptr == ' ' || *ptr == '\t') while (*ptr == ' ' || *ptr == '\t') {
ptr++; ptr++;
}
#define tryReadSlice(expected) \ #define tryReadSlice(expected) \
do { \ do { \
if (!readMBCSlice(ptr, expected)) \ if (!readMBCSlice(ptr, expected)) { \
return MBC_BAD; \ return MBC_BAD; \
} \
} while (0) } while (0)
switch (*ptr++) { switch (*ptr++) {
@@ -249,8 +254,9 @@ static MbcType parseMBC(char const *name) {
case 'r': case 'r':
tryReadSlice("OM"); tryReadSlice("OM");
// Handle optional " ONLY" // Handle optional " ONLY"
while (*ptr == ' ' || *ptr == '\t' || *ptr == '_') while (*ptr == ' ' || *ptr == '\t' || *ptr == '_') {
ptr++; ptr++;
}
if (*ptr == 'O' || *ptr == 'o') { if (*ptr == 'O' || *ptr == 'o') {
ptr++; ptr++;
tryReadSlice("NLY"); tryReadSlice("NLY");
@@ -325,8 +331,9 @@ static MbcType parseMBC(char const *name) {
case 'P': { case 'P': {
tryReadSlice("P1"); tryReadSlice("P1");
// Parse version // Parse version
while (*ptr == ' ' || *ptr == '_') while (*ptr == ' ' || *ptr == '_') {
ptr++; ptr++;
}
// Major // Major
char *endptr; char *endptr;
unsigned long val = strtoul(ptr, &endptr, 10); unsigned long val = strtoul(ptr, &endptr, 10);
@@ -383,27 +390,33 @@ static MbcType parseMBC(char const *name) {
// Read "additional features" // Read "additional features"
uint8_t features = 0; uint8_t features = 0;
#define RAM (1 << 7) // clang-format off: vertically align values
#define BATTERY (1 << 6) static constexpr uint8_t RAM = 1 << 7;
#define TIMER (1 << 5) static constexpr uint8_t BATTERY = 1 << 6;
#define RUMBLE (1 << 4) static constexpr uint8_t TIMER = 1 << 5;
#define SENSOR (1 << 3) static constexpr uint8_t RUMBLE = 1 << 4;
#define MULTIRUMBLE (1 << 2) static constexpr uint8_t SENSOR = 1 << 3;
static constexpr uint8_t MULTIRUMBLE = 1 << 2;
// clang-format on
for (;;) { for (;;) {
// Trim off trailing whitespace // Trim off trailing whitespace
while (*ptr == ' ' || *ptr == '\t' || *ptr == '_') while (*ptr == ' ' || *ptr == '\t' || *ptr == '_') {
ptr++; ptr++;
}
// If done, start processing "features" // If done, start processing "features"
if (!*ptr) if (!*ptr) {
break; break;
}
// We expect a '+' at this point // We expect a '+' at this point
if (*ptr++ != '+') if (*ptr++ != '+') {
return MBC_BAD; return MBC_BAD;
}
// Trim off leading whitespace // Trim off leading whitespace
while (*ptr == ' ' || *ptr == '\t' || *ptr == '_') while (*ptr == ' ' || *ptr == '\t' || *ptr == '_') {
ptr++; ptr++;
}
switch (*ptr++) { switch (*ptr++) {
case 'B': // BATTERY case 'B': // BATTERY
@@ -428,8 +441,9 @@ static MbcType parseMBC(char const *name) {
break; break;
case 'A': case 'A':
case 'a': case 'a':
if (*ptr != 'M' && *ptr != 'm') if (*ptr != 'M' && *ptr != 'm') {
return MBC_BAD; return MBC_BAD;
}
ptr++; ptr++;
features |= RAM; features |= RAM;
break; break;
@@ -458,8 +472,9 @@ static MbcType parseMBC(char const *name) {
switch (mbc) { switch (mbc) {
case ROM: case ROM:
if (!features) if (!features) {
break; break;
}
mbc = ROM_RAM - 1; mbc = ROM_RAM - 1;
static_assert(ROM_RAM + 1 == ROM_RAM_BATTERY, "Enum sanity check failed!"); static_assert(ROM_RAM + 1 == ROM_RAM_BATTERY, "Enum sanity check failed!");
static_assert(MBC1 + 1 == MBC1_RAM, "Enum sanity check failed!"); static_assert(MBC1 + 1 == MBC1_RAM, "Enum sanity check failed!");
@@ -469,26 +484,29 @@ static MbcType parseMBC(char const *name) {
[[fallthrough]]; [[fallthrough]];
case MBC1: case MBC1:
case MMM01: case MMM01:
if (features == RAM) if (features == RAM) {
mbc++; mbc++;
else if (features == (RAM | BATTERY)) } else if (features == (RAM | BATTERY)) {
mbc += 2; mbc += 2;
else if (features) } else if (features) {
return MBC_WRONG_FEATURES; return MBC_WRONG_FEATURES;
}
break; break;
case MBC2: case MBC2:
if (features == BATTERY) if (features == BATTERY) {
mbc = MBC2_BATTERY; mbc = MBC2_BATTERY;
else if (features) } else if (features) {
return MBC_WRONG_FEATURES; return MBC_WRONG_FEATURES;
}
break; break;
case MBC3: case MBC3:
// Handle timer, which also requires battery // Handle timer, which also requires battery
if (features & TIMER) { if (features & TIMER) {
if (!(features & BATTERY)) if (!(features & BATTERY)) {
fprintf(stderr, "warning: MBC3+TIMER implies BATTERY\n"); fprintf(stderr, "warning: MBC3+TIMER implies BATTERY\n");
}
features &= ~(TIMER | BATTERY); // Reset those bits features &= ~(TIMER | BATTERY); // Reset those bits
mbc = MBC3_TIMER_BATTERY; mbc = MBC3_TIMER_BATTERY;
// RAM is handled below // RAM is handled below
@@ -498,12 +516,13 @@ static MbcType parseMBC(char const *name) {
static_assert( static_assert(
MBC3_TIMER_BATTERY + 1 == MBC3_TIMER_RAM_BATTERY, "Enum sanity check failed!" MBC3_TIMER_BATTERY + 1 == MBC3_TIMER_RAM_BATTERY, "Enum sanity check failed!"
); );
if (features == RAM) if (features == RAM) {
mbc++; mbc++;
else if (features == (RAM | BATTERY)) } else if (features == (RAM | BATTERY)) {
mbc += 2; mbc += 2;
else if (features) } else if (features) {
return MBC_WRONG_FEATURES; return MBC_WRONG_FEATURES;
}
break; break;
case MBC5: case MBC5:
@@ -515,12 +534,13 @@ static MbcType parseMBC(char const *name) {
static_assert(MBC5 + 2 == MBC5_RAM_BATTERY, "Enum sanity check failed!"); static_assert(MBC5 + 2 == MBC5_RAM_BATTERY, "Enum sanity check failed!");
static_assert(MBC5_RUMBLE + 1 == MBC5_RUMBLE_RAM, "Enum sanity check failed!"); static_assert(MBC5_RUMBLE + 1 == MBC5_RUMBLE_RAM, "Enum sanity check failed!");
static_assert(MBC5_RUMBLE + 2 == MBC5_RUMBLE_RAM_BATTERY, "Enum sanity check failed!"); static_assert(MBC5_RUMBLE + 2 == MBC5_RUMBLE_RAM_BATTERY, "Enum sanity check failed!");
if (features == RAM) if (features == RAM) {
mbc++; mbc++;
else if (features == (RAM | BATTERY)) } else if (features == (RAM | BATTERY)) {
mbc += 2; mbc += 2;
else if (features) } else if (features) {
return MBC_WRONG_FEATURES; return MBC_WRONG_FEATURES;
}
break; break;
case MBC6: case MBC6:
@@ -528,45 +548,56 @@ static MbcType parseMBC(char const *name) {
case BANDAI_TAMA5: case BANDAI_TAMA5:
case HUC3: case HUC3:
// No extra features accepted // No extra features accepted
if (features) if (features) {
return MBC_WRONG_FEATURES; return MBC_WRONG_FEATURES;
}
break; break;
case MBC7_SENSOR_RUMBLE_RAM_BATTERY: case MBC7_SENSOR_RUMBLE_RAM_BATTERY:
if (features != (SENSOR | RUMBLE | RAM | BATTERY)) if (features != (SENSOR | RUMBLE | RAM | BATTERY)) {
return MBC_WRONG_FEATURES; return MBC_WRONG_FEATURES;
}
break; break;
case HUC1_RAM_BATTERY: case HUC1_RAM_BATTERY:
if (features != (RAM | BATTERY)) // HuC1 expects RAM+BATTERY if (features != (RAM | BATTERY)) { // HuC1 expects RAM+BATTERY
return MBC_WRONG_FEATURES; return MBC_WRONG_FEATURES;
}
break; break;
case TPP1: case TPP1:
if (features & RAM) if (features & RAM) {
fprintf( fprintf(
stderr, "warning: TPP1 requests RAM implicitly if given a non-zero RAM size" stderr, "warning: TPP1 requests RAM implicitly if given a non-zero RAM size"
); );
if (features & BATTERY) }
if (features & BATTERY) {
mbc |= 0x08; mbc |= 0x08;
if (features & TIMER) }
if (features & TIMER) {
mbc |= 0x04; mbc |= 0x04;
if (features & MULTIRUMBLE) }
if (features & MULTIRUMBLE) {
mbc |= 0x03; // Also set the rumble flag mbc |= 0x03; // Also set the rumble flag
if (features & RUMBLE) }
if (features & RUMBLE) {
mbc |= 0x01; mbc |= 0x01;
if (features & SENSOR) }
if (features & SENSOR) {
return MBC_WRONG_FEATURES; return MBC_WRONG_FEATURES;
}
break; break;
} }
// Trim off trailing whitespace // Trim off trailing whitespace
while (*ptr == ' ' || *ptr == '\t') while (*ptr == ' ' || *ptr == '\t') {
ptr++; ptr++;
}
// If there is still something past the whitespace, error out // If there is still something past the whitespace, error out
if (*ptr) if (*ptr) {
return MBC_BAD; return MBC_BAD;
}
return static_cast<MbcType>(mbc); return static_cast<MbcType>(mbc);
} }
@@ -740,12 +771,14 @@ static uint8_t const nintendoLogo[] = {
}; };
static uint8_t fixSpec = 0; static uint8_t fixSpec = 0;
#define FIX_LOGO (1 << 7) // clang-format off: vertically align values
#define TRASH_LOGO (1 << 6) static constexpr uint8_t FIX_LOGO = 1 << 7;
#define FIX_HEADER_SUM (1 << 5) static constexpr uint8_t TRASH_LOGO = 1 << 6;
#define TRASH_HEADER_SUM (1 << 4) static constexpr uint8_t FIX_HEADER_SUM = 1 << 5;
#define FIX_GLOBAL_SUM (1 << 3) static constexpr uint8_t TRASH_HEADER_SUM = 1 << 4;
#define TRASH_GLOBAL_SUM (1 << 2) static constexpr uint8_t FIX_GLOBAL_SUM = 1 << 3;
static constexpr uint8_t TRASH_GLOBAL_SUM = 1 << 2;
// clang-format on
static enum { DMG, BOTH, CGB } model = DMG; // If DMG, byte is left alone static enum { DMG, BOTH, CGB } model = DMG; // If DMG, byte is left alone
static char const *gameID = nullptr; static char const *gameID = nullptr;
@@ -778,11 +811,13 @@ static ssize_t readBytes(int fd, uint8_t *buf, size_t len) {
while (len) { while (len) {
ssize_t ret = read(fd, buf, len); ssize_t ret = read(fd, buf, len);
if (ret == -1 && errno != EINTR) // Return errors, unless we only were interrupted if (ret == -1 && errno != EINTR) { // Return errors, unless we only were interrupted
return -1; return -1;
}
// EOF reached // EOF reached
if (ret == 0) if (ret == 0) {
return total; return total;
}
// If anything was read, accumulate it, and continue // If anything was read, accumulate it, and continue
if (ret != -1) { if (ret != -1) {
total += ret; total += ret;
@@ -803,43 +838,30 @@ static ssize_t writeBytes(int fd, uint8_t *buf, size_t len) {
while (len) { while (len) {
ssize_t ret = write(fd, buf, len); ssize_t ret = write(fd, buf, len);
if (ret == -1 && errno != EINTR) // Return errors, unless we only were interrupted if (ret == -1 && errno != EINTR) { // Return errors, unless we only were interrupted
return -1; return -1;
// EOF reached }
if (ret == 0) // If anything was written, accumulate it, and continue
return total;
// If anything was read, accumulate it, and continue
if (ret != -1) { if (ret != -1) {
total += ret; total += ret;
len -= ret; len -= ret;
buf += ret;
} }
} }
return total; return total;
} }
/*
* @param rom0 A pointer to rom0
* @param addr What address to check
* @param fixedByte The fixed byte at the address
* @param areaName Name to be displayed in the warning message
*/
static void overwriteByte(uint8_t *rom0, uint16_t addr, uint8_t fixedByte, char const *areaName) { static void overwriteByte(uint8_t *rom0, uint16_t addr, uint8_t fixedByte, char const *areaName) {
uint8_t origByte = rom0[addr]; uint8_t origByte = rom0[addr];
if (!overwriteRom && origByte != 0 && origByte != fixedByte) if (!overwriteRom && origByte != 0 && origByte != fixedByte) {
fprintf(stderr, "warning: Overwrote a non-zero byte in the %s\n", areaName); fprintf(stderr, "warning: Overwrote a non-zero byte in the %s\n", areaName);
}
rom0[addr] = fixedByte; rom0[addr] = fixedByte;
} }
/*
* @param rom0 A pointer to rom0
* @param startAddr What address to begin checking from
* @param fixed The fixed bytes at the address
* @param size How many bytes to check
* @param areaName Name to be displayed in the warning message
*/
static void overwriteBytes( static void overwriteBytes(
uint8_t *rom0, uint16_t startAddr, uint8_t const *fixed, uint8_t size, char const *areaName uint8_t *rom0, uint16_t startAddr, uint8_t const *fixed, uint8_t size, char const *areaName
) { ) {
@@ -857,18 +879,13 @@ static void overwriteBytes(
memcpy(&rom0[startAddr], fixed, size); memcpy(&rom0[startAddr], fixed, size);
} }
/*
* @param input File descriptor to be used for reading
* @param output File descriptor to be used for writing, may be equal to `input`
* @param name The file's name, to be displayed for error output
* @param fileSize The file's size if known, 0 if not.
*/
static void processFile(int input, int output, char const *name, off_t fileSize) { static void processFile(int input, int output, char const *name, off_t fileSize) {
// Both of these should be true for seekable files, and neither otherwise // Both of these should be true for seekable files, and neither otherwise
if (input == output) if (input == output) {
assume(fileSize != 0); assume(fileSize != 0);
else } else {
assume(fileSize == 0); assume(fileSize == 0);
}
uint8_t rom0[BANK_SIZE]; uint8_t rom0[BANK_SIZE];
ssize_t rom0Len = readBytes(input, rom0, sizeof(rom0)); ssize_t rom0Len = readBytes(input, rom0, sizeof(rom0));
@@ -890,25 +907,25 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
} }
// Accept partial reads if the file contains at least the header // Accept partial reads if the file contains at least the header
if (fixSpec & (FIX_LOGO | TRASH_LOGO)) if (fixSpec & (FIX_LOGO | TRASH_LOGO)) {
overwriteBytes(rom0, 0x0104, logo, sizeof(logo), logoFilename ? "logo" : "Nintendo logo"); overwriteBytes(rom0, 0x0104, logo, sizeof(logo), logoFilename ? "logo" : "Nintendo logo");
}
if (title) if (title) {
overwriteBytes(rom0, 0x134, reinterpret_cast<uint8_t const *>(title), titleLen, "title"); overwriteBytes(rom0, 0x134, reinterpret_cast<uint8_t const *>(title), titleLen, "title");
}
if (gameID) if (gameID) {
overwriteBytes( overwriteBytes(
rom0, rom0, 0x13F, reinterpret_cast<uint8_t const *>(gameID), gameIDLen, "manufacturer code"
0x13F,
reinterpret_cast<uint8_t const *>(gameID),
gameIDLen,
"manufacturer code"
); );
}
if (model != DMG) if (model != DMG) {
overwriteByte(rom0, 0x143, model == BOTH ? 0x80 : 0xC0, "CGB flag"); overwriteByte(rom0, 0x143, model == BOTH ? 0x80 : 0xC0, "CGB flag");
}
if (newLicensee) if (newLicensee) {
overwriteBytes( overwriteBytes(
rom0, rom0,
0x144, 0x144,
@@ -916,9 +933,11 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
newLicenseeLen, newLicenseeLen,
"new licensee code" "new licensee code"
); );
}
if (sgb) if (sgb) {
overwriteByte(rom0, 0x146, 0x03, "SGB flag"); overwriteByte(rom0, 0x146, 0x03, "SGB flag");
}
// If a valid MBC was specified... // If a valid MBC was specified...
if (cartridgeType < MBC_NONE) { if (cartridgeType < MBC_NONE) {
@@ -941,31 +960,36 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
overwriteBytes(rom0, 0x150, tpp1Rev, sizeof(tpp1Rev), "TPP1 revision number"); overwriteBytes(rom0, 0x150, tpp1Rev, sizeof(tpp1Rev), "TPP1 revision number");
if (ramSize != UNSPECIFIED) if (ramSize != UNSPECIFIED) {
overwriteByte(rom0, 0x152, ramSize, "RAM size"); overwriteByte(rom0, 0x152, ramSize, "RAM size");
}
overwriteByte(rom0, 0x153, cartridgeType & 0xFF, "TPP1 feature flags"); overwriteByte(rom0, 0x153, cartridgeType & 0xFF, "TPP1 feature flags");
} else { } else {
// Regular mappers // Regular mappers
if (ramSize != UNSPECIFIED) if (ramSize != UNSPECIFIED) {
overwriteByte(rom0, 0x149, ramSize, "RAM size"); overwriteByte(rom0, 0x149, ramSize, "RAM size");
}
if (!japanese) if (!japanese) {
overwriteByte(rom0, 0x14A, 0x01, "destination code"); overwriteByte(rom0, 0x14A, 0x01, "destination code");
}
} }
if (oldLicensee != UNSPECIFIED) if (oldLicensee != UNSPECIFIED) {
overwriteByte(rom0, 0x14B, oldLicensee, "old licensee code"); overwriteByte(rom0, 0x14B, oldLicensee, "old licensee code");
else if (sgb && rom0[0x14B] != 0x33) } else if (sgb && rom0[0x14B] != 0x33) {
fprintf( fprintf(
stderr, stderr,
"warning: SGB compatibility enabled, but old licensee was 0x%02x, not 0x33\n", "warning: SGB compatibility enabled, but old licensee was 0x%02x, not 0x33\n",
rom0[0x14B] rom0[0x14B]
); );
}
if (romVersion != UNSPECIFIED) if (romVersion != UNSPECIFIED) {
overwriteByte(rom0, 0x14C, romVersion, "mask ROM version number"); overwriteByte(rom0, 0x14C, romVersion, "mask ROM version number");
}
// Remain to be handled the ROM size, and header checksum. // Remain to be handled the ROM size, and header checksum.
// The latter depends on the former, and so will be handled after it. // The latter depends on the former, and so will be handled after it.
@@ -1013,13 +1037,15 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
nbBanks++; nbBanks++;
// Update global checksum, too // Update global checksum, too
for (uint16_t i = 0; i < bankLen; i++) for (uint16_t i = 0; i < bankLen; i++) {
globalSum += romx[totalRomxLen + i]; globalSum += romx[totalRomxLen + i];
}
totalRomxLen += bankLen; totalRomxLen += bankLen;
} }
// Stop when an incomplete bank has been read // Stop when an incomplete bank has been read
if (bankLen != BANK_SIZE) if (bankLen != BANK_SIZE) {
break; break;
}
} }
} }
@@ -1046,8 +1072,9 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
// Alter number of banks to reflect required value // Alter number of banks to reflect required value
// x&(x-1) is zero iff x is a power of 2, or 0; we know for sure it's non-zero, // x&(x-1) is zero iff x is a power of 2, or 0; we know for sure it's non-zero,
// so this is true (non-zero) when we don't have a power of 2 // so this is true (non-zero) when we don't have a power of 2
if (nbBanks & (nbBanks - 1)) if (nbBanks & (nbBanks - 1)) {
nbBanks = 1 << (CHAR_BIT * sizeof(nbBanks) - clz(nbBanks)); nbBanks = 1 << (CHAR_BIT * sizeof(nbBanks) - clz(nbBanks));
}
// Write final ROM size // Write final ROM size
rom0[0x148] = ctz(nbBanks / 2); rom0[0x148] = ctz(nbBanks / 2);
// Alter global checksum based on how many bytes will be added (not counting ROM0) // Alter global checksum based on how many bytes will be added (not counting ROM0)
@@ -1058,8 +1085,9 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
if (fixSpec & (FIX_HEADER_SUM | TRASH_HEADER_SUM)) { if (fixSpec & (FIX_HEADER_SUM | TRASH_HEADER_SUM)) {
uint8_t sum = 0; uint8_t sum = 0;
for (uint16_t i = 0x134; i < 0x14D; i++) for (uint16_t i = 0x134; i < 0x14D; i++) {
sum -= rom0[i] + 1; sum -= rom0[i] + 1;
}
overwriteByte(rom0, 0x14D, fixSpec & TRASH_HEADER_SUM ? ~sum : sum, "header checksum"); overwriteByte(rom0, 0x14D, fixSpec & TRASH_HEADER_SUM ? ~sum : sum, "header checksum");
} }
@@ -1067,28 +1095,32 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
if (fixSpec & (FIX_GLOBAL_SUM | TRASH_GLOBAL_SUM)) { if (fixSpec & (FIX_GLOBAL_SUM | TRASH_GLOBAL_SUM)) {
// Computation of the global checksum does not include the checksum bytes // Computation of the global checksum does not include the checksum bytes
assume(rom0Len >= 0x14E); assume(rom0Len >= 0x14E);
for (uint16_t i = 0; i < 0x14E; i++) for (uint16_t i = 0; i < 0x14E; i++) {
globalSum += rom0[i]; globalSum += rom0[i];
for (uint16_t i = 0x150; i < rom0Len; i++) }
for (uint16_t i = 0x150; i < rom0Len; i++) {
globalSum += rom0[i]; globalSum += rom0[i];
}
// Pipes have already read ROMX and updated globalSum, but not regular files // Pipes have already read ROMX and updated globalSum, but not regular files
if (input == output) { if (input == output) {
for (;;) { for (;;) {
ssize_t bankLen = readBytes(input, bank, sizeof(bank)); ssize_t bankLen = readBytes(input, bank, sizeof(bank));
for (uint16_t i = 0; i < bankLen; i++) for (uint16_t i = 0; i < bankLen; i++) {
globalSum += bank[i]; globalSum += bank[i];
if (bankLen != sizeof(bank)) }
if (bankLen != sizeof(bank)) {
break; break;
}
} }
} }
if (fixSpec & TRASH_GLOBAL_SUM) if (fixSpec & TRASH_GLOBAL_SUM) {
globalSum = ~globalSum; globalSum = ~globalSum;
}
uint8_t bytes[2] = { uint8_t bytes[2] = {
static_cast<uint8_t>(globalSum >> 8), static_cast<uint8_t>(globalSum >> 8), static_cast<uint8_t>(globalSum & 0xFF)
static_cast<uint8_t>(globalSum & 0xFF)
}; };
overwriteBytes(rom0, 0x14E, bytes, sizeof(bytes), "global checksum"); overwriteBytes(rom0, 0x14E, bytes, sizeof(bytes), "global checksum");
@@ -1105,8 +1137,9 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
} }
// If modifying the file in-place, we only need to edit the header // If modifying the file in-place, we only need to edit the header
// However, padding may have modified ROM0 (added padding), so don't in that case // However, padding may have modified ROM0 (added padding), so don't in that case
if (padValue == UNSPECIFIED) if (padValue == UNSPECIFIED) {
rom0Len = headerSize; rom0Len = headerSize;
}
} }
writeLen = writeBytes(output, rom0, rom0Len); writeLen = writeBytes(output, rom0, rom0Len);
@@ -1209,7 +1242,7 @@ static bool processFilename(char const *name) {
} }
} }
if (nbErrors) if (nbErrors) {
fprintf( fprintf(
stderr, stderr,
"Fixing \"%s\" failed with %u error%s\n", "Fixing \"%s\" failed with %u error%s\n",
@@ -1217,6 +1250,7 @@ static bool processFilename(char const *name) {
nbErrors, nbErrors,
nbErrors == 1 ? "" : "s" nbErrors == 1 ? "" : "s"
); );
}
return nbErrors; return nbErrors;
} }
@@ -1266,16 +1300,17 @@ int main(int argc, char *argv[]) {
switch (*musl_optarg) { switch (*musl_optarg) {
#define OVERRIDE_SPEC(cur, bad, curFlag, badFlag) \ #define OVERRIDE_SPEC(cur, bad, curFlag, badFlag) \
case STR(cur)[0]: \ case STR(cur)[0]: \
if (fixSpec & badFlag) \ if (fixSpec & badFlag) { \
fprintf(stderr, "warning: '" STR(cur) "' overriding '" STR(bad) "' in fix spec\n"); \ fprintf(stderr, "warning: '" STR(cur) "' overriding '" STR(bad) "' in fix spec\n"); \
} \
fixSpec = (fixSpec & ~badFlag) | curFlag; \ fixSpec = (fixSpec & ~badFlag) | curFlag; \
break break
#define overrideSpecs(fix, fixFlag, trash, trashFlag) \ #define overrideSpecs(fix, fixFlag, trash, trashFlag) \
OVERRIDE_SPEC(fix, trash, fixFlag, trashFlag); \ OVERRIDE_SPEC(fix, trash, fixFlag, trashFlag); \
OVERRIDE_SPEC(trash, fix, trashFlag, fixFlag) OVERRIDE_SPEC(trash, fix, trashFlag, fixFlag)
overrideSpecs(l, FIX_LOGO, L, TRASH_LOGO); overrideSpecs(l, FIX_LOGO, L, TRASH_LOGO);
overrideSpecs(h, FIX_HEADER_SUM, H, TRASH_HEADER_SUM); overrideSpecs(h, FIX_HEADER_SUM, H, TRASH_HEADER_SUM);
overrideSpecs(g, FIX_GLOBAL_SUM, G, TRASH_GLOBAL_SUM); overrideSpecs(g, FIX_GLOBAL_SUM, G, TRASH_GLOBAL_SUM);
#undef OVERRIDE_SPEC #undef OVERRIDE_SPEC
#undef overrideSpecs #undef overrideSpecs
@@ -1286,6 +1321,10 @@ int main(int argc, char *argv[]) {
} }
break; break;
case 'h':
printUsage();
exit(0);
case 'i': case 'i':
gameID = musl_optarg; gameID = musl_optarg;
len = strlen(gameID); len = strlen(gameID);
@@ -1393,21 +1432,23 @@ int main(int argc, char *argv[]) {
} }
} }
if ((cartridgeType & 0xFF00) == TPP1 && !japanese) if ((cartridgeType & 0xFF00) == TPP1 && !japanese) {
fprintf( fprintf(
stderr, stderr,
"warning: TPP1 overwrites region flag for its identification code, ignoring `-j`\n" "warning: TPP1 overwrites region flag for its identification code, ignoring `-j`\n"
); );
}
// Check that RAM size is correct for "standard" mappers // Check that RAM size is correct for "standard" mappers
if (ramSize != UNSPECIFIED && (cartridgeType & 0xFF00) == 0) { if (ramSize != UNSPECIFIED && (cartridgeType & 0xFF00) == 0) {
if (cartridgeType == ROM_RAM || cartridgeType == ROM_RAM_BATTERY) { if (cartridgeType == ROM_RAM || cartridgeType == ROM_RAM_BATTERY) {
if (ramSize != 1) if (ramSize != 1) {
fprintf( fprintf(
stderr, stderr,
"warning: MBC \"%s\" should have 2 KiB of RAM (-r 1)\n", "warning: MBC \"%s\" should have 2 KiB of RAM (-r 1)\n",
mbcName(cartridgeType) mbcName(cartridgeType)
); );
}
} else if (hasRAM(cartridgeType)) { } else if (hasRAM(cartridgeType)) {
if (!ramSize) { if (!ramSize) {
fprintf( fprintf(
@@ -1432,12 +1473,13 @@ int main(int argc, char *argv[]) {
} }
} }
if (sgb && oldLicensee != UNSPECIFIED && oldLicensee != 0x33) if (sgb && oldLicensee != UNSPECIFIED && oldLicensee != 0x33) {
fprintf( fprintf(
stderr, stderr,
"warning: SGB compatibility enabled, but old licensee is 0x%02x, not 0x33\n", "warning: SGB compatibility enabled, but old licensee is 0x%02x, not 0x33\n",
oldLicensee oldLicensee
); );
}
argv += musl_optind; argv += musl_optind;
bool failed = nbErrors; bool failed = nbErrors;
@@ -1488,8 +1530,9 @@ int main(int argc, char *argv[]) {
memcpy(logo, nintendoLogo, sizeof(nintendoLogo)); memcpy(logo, nintendoLogo, sizeof(nintendoLogo));
} }
if (fixSpec & TRASH_LOGO) { if (fixSpec & TRASH_LOGO) {
for (uint16_t i = 0; i < sizeof(logo); i++) for (uint16_t i = 0; i < sizeof(logo); i++) {
logo[i] = 0xFF ^ logo[i]; logo[i] = 0xFF ^ logo[i];
}
} }
if (!*argv) { if (!*argv) {

View File

@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: MIT */ // SPDX-License-Identifier: MIT
#include "gfx/main.hpp" #include "gfx/main.hpp"
@@ -41,7 +41,8 @@ static struct LocalOptions {
static uintmax_t nbErrors; static uintmax_t nbErrors;
[[noreturn]] void giveUp() { [[noreturn]]
void giveUp() {
fprintf(stderr, "Conversion aborted after %ju error%s\n", nbErrors, nbErrors == 1 ? "" : "s"); fprintf(stderr, "Conversion aborted after %ju error%s\n", nbErrors, nbErrors == 1 ? "" : "s");
exit(1); exit(1);
} }
@@ -71,18 +72,21 @@ void error(char const *fmt, ...) {
va_end(ap); va_end(ap);
putc('\n', stderr); putc('\n', stderr);
if (nbErrors != std::numeric_limits<decltype(nbErrors)>::max()) if (nbErrors != std::numeric_limits<decltype(nbErrors)>::max()) {
nbErrors++; nbErrors++;
}
} }
void errorMessage(char const *msg) { void errorMessage(char const *msg) {
fprintf(stderr, "error: %s\n", msg); fprintf(stderr, "error: %s\n", msg);
if (nbErrors != std::numeric_limits<decltype(nbErrors)>::max()) if (nbErrors != std::numeric_limits<decltype(nbErrors)>::max()) {
nbErrors++; nbErrors++;
}
} }
[[noreturn]] void fatal(char const *fmt, ...) { [[noreturn]]
void fatal(char const *fmt, ...) {
va_list ap; va_list ap;
fputs("FATAL: ", stderr); fputs("FATAL: ", stderr);
@@ -91,8 +95,9 @@ void errorMessage(char const *msg) {
va_end(ap); va_end(ap);
putc('\n', stderr); putc('\n', stderr);
if (nbErrors != std::numeric_limits<decltype(nbErrors)>::max()) if (nbErrors != std::numeric_limits<decltype(nbErrors)>::max()) {
nbErrors++; nbErrors++;
}
giveUp(); giveUp();
} }
@@ -108,18 +113,15 @@ void Options::verbosePrint(uint8_t level, char const *fmt, ...) const {
} }
// Short options // Short options
static char const *optstring = "-Aa:b:Cc:d:i:L:mN:n:Oo:Pp:Qq:r:s:Tt:U:uVvXx:YZ"; static char const *optstring = "-Aa:b:Cc:d:hi:L:mN:n:Oo:Pp:Qq:r:s:Tt:U:uVvXx:YZ";
/* // Equivalent long options
* Equivalent long options // Please keep in the same order as short opts.
* Please keep in the same order as short opts // Also, make sure long opts don't create ambiguity:
* // A long opt's name should start with the same letter as its short opt,
* Also, make sure long opts don't create ambiguity: // except if it doesn't create any ambiguity (`verbose` versus `version`).
* A long opt's name should start with the same letter as its short opt, // This is because long opt matching, even to a single char, is prioritized
* except if it doesn't create any ambiguity (`verbose` versus `version`). // over short opt matching.
* This is because long opt matching, even to a single char, is prioritized
* over short opt matching
*/
static option const longopts[] = { static option const longopts[] = {
{"auto-attr-map", no_argument, nullptr, 'A'}, {"auto-attr-map", no_argument, nullptr, 'A'},
{"attr-map", required_argument, nullptr, 'a'}, {"attr-map", required_argument, nullptr, 'a'},
@@ -127,6 +129,7 @@ static option const longopts[] = {
{"color-curve", no_argument, nullptr, 'C'}, {"color-curve", no_argument, nullptr, 'C'},
{"colors", required_argument, nullptr, 'c'}, {"colors", required_argument, nullptr, 'c'},
{"depth", required_argument, nullptr, 'd'}, {"depth", required_argument, nullptr, 'd'},
{"help", no_argument, nullptr, 'h'},
{"input-tileset", required_argument, nullptr, 'i'}, {"input-tileset", required_argument, nullptr, 'i'},
{"slice", required_argument, nullptr, 'L'}, {"slice", required_argument, nullptr, 'L'},
{"mirror-tiles", no_argument, nullptr, 'm'}, {"mirror-tiles", no_argument, nullptr, 'm'},
@@ -155,7 +158,7 @@ static option const longopts[] = {
static void printUsage() { static void printUsage() {
fputs( fputs(
"Usage: rgbgfx [-r stride] [-CmOuVXYZ] [-v [-v ...]] [-a <attr_map> | -A]\n" "Usage: rgbgfx [-r stride] [-ChmOuVXYZ] [-v [-v ...]] [-a <attr_map> | -A]\n"
" [-b <base_ids>] [-c <colors>] [-d <depth>] [-i <tileset_file>]\n" " [-b <base_ids>] [-c <colors>] [-d <depth>] [-i <tileset_file>]\n"
" [-L <slice>] [-N <nb_tiles>] [-n <nb_pals>] [-o <out_file>]\n" " [-L <slice>] [-N <nb_tiles>] [-n <nb_pals>] [-o <out_file>]\n"
" [-p <pal_file> | -P] [-q <pal_map> | -Q] [-s <nb_colors>]\n" " [-p <pal_file> | -P] [-q <pal_map> | -Q] [-s <nb_colors>]\n"
@@ -172,10 +175,8 @@ static void printUsage() {
); );
} }
/* // Parses a number at the beginning of a string, moving the pointer to skip the parsed characters.
* Parses a number at the beginning of a string, moving the pointer to skip the parsed characters // Returns the provided errVal on error.
* Returns the provided errVal on error
*/
static uint16_t parseNumber(char *&string, char const *errPrefix, uint16_t errVal = UINT16_MAX) { static uint16_t parseNumber(char *&string, char const *errPrefix, uint16_t errVal = UINT16_MAX) {
uint8_t base = 10; uint8_t base = 10;
if (*string == '\0') { if (*string == '\0') {
@@ -198,12 +199,10 @@ static uint16_t parseNumber(char *&string, char const *errPrefix, uint16_t errVa
} }
} }
/* // Turns a digit into its numeric value in the current base, if it has one.
* Turns a digit into its numeric value in the current base, if it has one. // Maximum is inclusive. The string_view is modified to "consume" all digits.
* Maximum is inclusive. The string_view is modified to "consume" all digits. // Returns 255 on parse failure (including wrong char for base), in which case
* Returns 255 on parse failure (including wrong char for base), in which case // the string_view may be pointing on garbage.
* the string_view may be pointing on garbage.
*/
auto charIndex = [&base](unsigned char c) -> uint8_t { auto charIndex = [&base](unsigned char c) -> uint8_t {
unsigned char index = c - '0'; // Use wrapping semantics unsigned char index = c - '0'; // Use wrapping semantics
if (base == 2 && index >= 2) { if (base == 2 && index >= 2) {
@@ -271,10 +270,7 @@ static void registerInput(char const *arg) {
} }
} }
/* // Turn an at-file's contents into an argv that `getopt` can handle, appending them to `argPool`.
* Turn an "at-file"'s contents into an argv that `getopt` can handle
* @param argPool Argument characters will be appended to this vector, for storage purposes.
*/
static std::vector<size_t> readAtFile(std::string const &path, std::vector<char> &argPool) { static std::vector<size_t> readAtFile(std::string const &path, std::vector<char> &argPool) {
File file; File file;
if (!file.open(path, std::ios_base::in)) { if (!file.open(path, std::ios_base::in)) {
@@ -346,13 +342,10 @@ static std::vector<size_t> readAtFile(std::string const &path, std::vector<char>
} }
} }
/* // Parses an arg vector, modifying `options` and `localOptions` as options are read.
* Parses an arg vector, modifying `options` and `localOptions` as options are read. // The `localOptions` struct is for flags which must be processed after the option parsing finishes.
* The `localOptions` struct is for flags which must be processed after the option parsing finishes. // Returns `nullptr` if the vector was fully parsed, or a pointer (which is part of the arg vector)
* // to an "at-file" path if one is encountered.
* Returns `nullptr` if the vector was fully parsed, or a pointer (which is part of the arg vector)
* to an "at-file" path if one is encountered.
*/
static char *parseArgv(int argc, char *argv[]) { static char *parseArgv(int argc, char *argv[]) {
for (int ch; (ch = musl_getopt_long_only(argc, argv, optstring, longopts, nullptr)) != -1;) { for (int ch; (ch = musl_getopt_long_only(argc, argv, optstring, longopts, nullptr)) != -1;) {
char *arg = musl_optarg; // Make a copy for scanning char *arg = musl_optarg; // Make a copy for scanning
@@ -363,8 +356,9 @@ static char *parseArgv(int argc, char *argv[]) {
break; break;
case 'a': case 'a':
localOptions.autoAttrmap = false; localOptions.autoAttrmap = false;
if (!options.attrmap.empty()) if (!options.attrmap.empty()) {
warning("Overriding attrmap file %s", options.attrmap.c_str()); warning("Overriding attrmap file %s", options.attrmap.c_str());
}
options.attrmap = musl_optarg; options.attrmap = musl_optarg;
break; break;
case 'b': case 'b':
@@ -430,9 +424,13 @@ static char *parseArgv(int argc, char *argv[]) {
options.bitDepth = 2; options.bitDepth = 2;
} }
break; break;
case 'h':
printUsage();
exit(0);
case 'i': case 'i':
if (!options.inputTileset.empty()) if (!options.inputTileset.empty()) {
warning("Overriding input tileset file %s", options.inputTileset.c_str()); warning("Overriding input tileset file %s", options.inputTileset.c_str());
}
options.inputTileset = musl_optarg; options.inputTileset = musl_optarg;
break; break;
case 'L': case 'L':
@@ -530,8 +528,9 @@ static char *parseArgv(int argc, char *argv[]) {
localOptions.groupOutputs = true; localOptions.groupOutputs = true;
break; break;
case 'o': case 'o':
if (!options.output.empty()) if (!options.output.empty()) {
warning("Overriding tile data file %s", options.output.c_str()); warning("Overriding tile data file %s", options.output.c_str());
}
options.output = musl_optarg; options.output = musl_optarg;
break; break;
case 'P': case 'P':
@@ -539,8 +538,9 @@ static char *parseArgv(int argc, char *argv[]) {
break; break;
case 'p': case 'p':
localOptions.autoPalettes = false; localOptions.autoPalettes = false;
if (!options.palettes.empty()) if (!options.palettes.empty()) {
warning("Overriding palettes file %s", options.palettes.c_str()); warning("Overriding palettes file %s", options.palettes.c_str());
}
options.palettes = musl_optarg; options.palettes = musl_optarg;
break; break;
case 'Q': case 'Q':
@@ -548,8 +548,9 @@ static char *parseArgv(int argc, char *argv[]) {
break; break;
case 'q': case 'q':
localOptions.autoPalmap = false; localOptions.autoPalmap = false;
if (!options.palmap.empty()) if (!options.palmap.empty()) {
warning("Overriding palette map file %s", options.palmap.c_str()); warning("Overriding palette map file %s", options.palmap.c_str());
}
options.palmap = musl_optarg; options.palmap = musl_optarg;
break; break;
case 'r': case 'r':
@@ -575,8 +576,9 @@ static char *parseArgv(int argc, char *argv[]) {
break; break;
case 't': case 't':
localOptions.autoTilemap = false; localOptions.autoTilemap = false;
if (!options.tilemap.empty()) if (!options.tilemap.empty()) {
warning("Overriding tilemap file %s", options.tilemap.c_str()); warning("Overriding tilemap file %s", options.tilemap.c_str());
}
options.tilemap = musl_optarg; options.tilemap = musl_optarg;
break; break;
case 'V': case 'V':
@@ -774,19 +776,25 @@ int main(int argc, char *argv[]) {
} }
fputs("Options:\n", stderr); fputs("Options:\n", stderr);
if (options.columnMajor) if (options.columnMajor) {
fputs("\tVisit image in column-major order\n", stderr); fputs("\tVisit image in column-major order\n", stderr);
if (options.allowDedup) }
if (options.allowDedup) {
fputs("\tAllow deduplicating tiles\n", stderr); fputs("\tAllow deduplicating tiles\n", stderr);
if (options.allowMirroringX) }
if (options.allowMirroringX) {
fputs("\tAllow deduplicating horizontally mirrored tiles\n", stderr); fputs("\tAllow deduplicating horizontally mirrored tiles\n", stderr);
if (options.allowMirroringY) }
if (options.allowMirroringY) {
fputs("\tAllow deduplicating vertically mirrored tiles\n", stderr); fputs("\tAllow deduplicating vertically mirrored tiles\n", stderr);
if (options.useColorCurve) }
if (options.useColorCurve) {
fputs("\tUse color curve\n", stderr); fputs("\tUse color curve\n", stderr);
}
fprintf(stderr, "\tBit depth: %" PRIu8 "bpp\n", options.bitDepth); fprintf(stderr, "\tBit depth: %" PRIu8 "bpp\n", options.bitDepth);
if (options.trim != 0) if (options.trim != 0) {
fprintf(stderr, "\tTrim the last %" PRIu64 " tiles\n", options.trim); fprintf(stderr, "\tTrim the last %" PRIu64 " tiles\n", options.trim);
}
fprintf(stderr, "\tMaximum %" PRIu16 " palettes\n", options.nbPalettes); fprintf(stderr, "\tMaximum %" PRIu16 " palettes\n", options.nbPalettes);
fprintf(stderr, "\tPalettes contain %" PRIu8 " colors\n", options.nbColorsPerPal); fprintf(stderr, "\tPalettes contain %" PRIu8 " colors\n", options.nbColorsPerPal);
fprintf(stderr, "\t%s palette spec\n", [] { fprintf(stderr, "\t%s palette spec\n", [] {
@@ -883,9 +891,7 @@ void Palette::addColor(uint16_t color) {
} }
} }
/* // Returns the ID of the color in the palette, or `size()` if the color is not in
* Returns the ID of the color in the palette, or `size()` if the color is not in
*/
uint8_t Palette::indexOf(uint16_t color) const { uint8_t Palette::indexOf(uint16_t color) const {
return color == Rgba::transparent return color == Rgba::transparent
? 0 ? 0

View File

@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: MIT */ // SPDX-License-Identifier: MIT
#include "gfx/pal_packing.hpp" #include "gfx/pal_packing.hpp"
@@ -27,15 +27,11 @@
// Tile | Proto-palette // Tile | Proto-palette
// Page | Palette // Page | Palette
/* // A reference to a proto-palette, and attached attributes for sorting purposes
* A reference to a proto-palette, and attached attributes for sorting purposes
*/
struct ProtoPalAttrs { struct ProtoPalAttrs {
size_t protoPalIndex; size_t protoPalIndex;
/* // Pages from which we are banned (to prevent infinite loops)
* Pages from which we are banned (to prevent infinite loops) // This is dynamic because we wish not to hard-cap the amount of palettes
* This is dynamic because we wish not to hard-cap the amount of palettes
*/
std::vector<bool> bannedPages; std::vector<bool> bannedPages;
explicit ProtoPalAttrs(size_t index) : protoPalIndex(index) {} explicit ProtoPalAttrs(size_t index) : protoPalIndex(index) {}
@@ -50,10 +46,8 @@ struct ProtoPalAttrs {
} }
}; };
/* // A collection of proto-palettes assigned to a palette
* A collection of proto-palettes assigned to a palette // Does not contain the actual color indices because we need to be able to remove elements
* Does not contain the actual color indices because we need to be able to remove elements
*/
class AssignedProtos { class AssignedProtos {
// We leave room for emptied slots to avoid copying the structs around on removal // We leave room for emptied slots to avoid copying the structs around on removal
std::vector<std::optional<ProtoPalAttrs>> _assigned; std::vector<std::optional<ProtoPalAttrs>> _assigned;
@@ -127,10 +121,8 @@ public:
} }
const_iterator end() const { return const_iterator{&_assigned, _assigned.end()}; } const_iterator end() const { return const_iterator{&_assigned, _assigned.end()}; }
/* // Assigns a new ProtoPalAttrs in a free slot, assuming there is one
* Assigns a new ProtoPalAttrs in a free slot, assuming there is one // Args are passed to the `ProtoPalAttrs`'s constructor
* Args are passed to the `ProtoPalAttrs`'s constructor
*/
template<typename... Ts> template<typename... Ts>
void assign(Ts &&...args) { void assign(Ts &&...args) {
auto freeSlot = auto freeSlot =
@@ -192,9 +184,7 @@ private:
return colors; return colors;
} }
public: public:
/* // Returns the number of distinct colors
* Returns the number of distinct colors
*/
size_t volume() const { return uniqueColors().size(); } size_t volume() const { return uniqueColors().size(); }
bool canFit(ProtoPalette const &protoPal) const { bool canFit(ProtoPalette const &protoPal) const {
auto &colors = uniqueColors(); auto &colors = uniqueColors();
@@ -218,10 +208,8 @@ public:
return factor; return factor;
}(); }();
/* // Computes the "relative size" of a proto-palette on this palette;
* Computes the "relative size" of a proto-palette on this palette; // it's a measure of how much this proto-palette would "cost" to introduce.
* it's a measure of how much this proto-palette would "cost" to introduce.
*/
uint32_t relSizeOf(ProtoPalette const &protoPal) const { uint32_t relSizeOf(ProtoPalette const &protoPal) const {
// NOTE: this function must not call `uniqueColors`, or one of its callers will break! // NOTE: this function must not call `uniqueColors`, or one of its callers will break!
@@ -244,9 +232,7 @@ public:
return relSize; return relSize;
} }
/* // Computes the "relative size" of a set of proto-palettes on this palette
* Computes the "relative size" of a set of proto-palettes on this palette
*/
template<typename Iter> template<typename Iter>
auto combinedVolume(Iter &&begin, Iter const &end, std::vector<ProtoPalette> const &protoPals) auto combinedVolume(Iter &&begin, Iter const &end, std::vector<ProtoPalette> const &protoPals)
const { const {
@@ -254,9 +240,7 @@ public:
addUniqueColors(colors, std::forward<Iter>(begin), end, protoPals); addUniqueColors(colors, std::forward<Iter>(begin), end, protoPals);
return colors.size(); return colors.size();
} }
/* // Computes the "relative size" of a set of colors on this palette
* Computes the "relative size" of a set of colors on this palette
*/
template<typename Iter> template<typename Iter>
auto combinedVolume(Iter &&begin, Iter &&end) const { auto combinedVolume(Iter &&begin, Iter &&end) const {
auto &colors = uniqueColors(); auto &colors = uniqueColors();

View File

@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: MIT */ // SPDX-License-Identifier: MIT
#include "gfx/pal_sorting.hpp" #include "gfx/pal_sorting.hpp"
@@ -46,7 +46,7 @@ void sortIndexed(
} }
void sortGrayscale( 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
) { ) {
options.verbosePrint(Options::VERB_LOG_ACT, "Sorting palette by grayscale bins...\n"); options.verbosePrint(Options::VERB_LOG_ACT, "Sorting palette by grayscale bins...\n");

View File

@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: MIT */ // SPDX-License-Identifier: MIT
#include "gfx/pal_spec.hpp" #include "gfx/pal_spec.hpp"
@@ -171,20 +171,6 @@ void parseInlinePalSpec(char const * const rawArg) {
} }
} }
/*
* Tries to read some magic bytes from the provided `file`.
* Returns whether the magic was correctly read.
*/
template<size_t n>
[[gnu::warn_unused_result]] // Ignoring failure to match is a bad idea.
static bool
readMagic(std::filebuf &file, char const *magic) {
assume(strlen(magic) == n);
char magicBuf[n];
return file.sgetn(magicBuf, n) == n && memcmp(magicBuf, magic, n);
}
template<typename T, typename U> template<typename T, typename U>
static T readBE(U const *bytes) { static T readBE(U const *bytes) {
T val = 0; T val = 0;
@@ -203,14 +189,10 @@ static T readLE(U const *bytes) {
return val; return val;
} }
/* // Appends the first line read from `file` to the end of the provided `buffer`.
* **Appends** the first line read from `file` to the end of the provided `buffer`. // Returns true if a line was read.
* [[gnu::warn_unused_result]]
* @return true if a line was read. static bool readLine(std::filebuf &file, std::string &buffer) {
*/
[[gnu::warn_unused_result]] // Ignoring EOF is a bad idea.
static bool
readLine(std::filebuf &file, std::string &buffer) {
assume(buffer.empty()); assume(buffer.empty());
// TODO: maybe this can be optimized to bulk reads? // TODO: maybe this can be optimized to bulk reads?
for (;;) { for (;;) {
@@ -238,9 +220,7 @@ static bool
} \ } \
} while (0) } while (0)
/* // Parses the initial part of a string_view, advancing the "read index" as it does
* Parses the initial part of a string_view, advancing the "read index" as it does
*/
template<typename U> // Should be uint*_t template<typename U> // Should be uint*_t
static std::optional<U> parseDec(std::string const &str, std::string::size_type &n) { static std::optional<U> parseDec(std::string const &str, std::string::size_type &n) {
uintmax_t value = 0; uintmax_t value = 0;

View File

@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: MIT */ // SPDX-License-Identifier: MIT
#include "gfx/process.hpp" #include "gfx/process.hpp"
@@ -26,18 +26,16 @@
#include "gfx/proto_palette.hpp" #include "gfx/proto_palette.hpp"
class ImagePalette { class ImagePalette {
// Use as many slots as there are CGB colors (plus transparency) std::array<std::optional<Rgba>, NB_COLOR_SLOTS> _colors;
std::array<std::optional<Rgba>, 0x8001> _colors;
public: public:
ImagePalette() = default; ImagePalette() = default;
/* // Registers a color in the palette.
* Registers a color in the palette. // If the newly inserted color "conflicts" with another one (different color, but same CGB
* If the newly inserted color "conflicts" with another one (different color, but same CGB // color), then the other color is returned. Otherwise, `nullptr` is returned.
* color), then the other color is returned. Otherwise, `nullptr` is returned. [[nodiscard]]
*/ Rgba const *registerColor(Rgba const &rgba) {
[[nodiscard]] Rgba const *registerColor(Rgba const &rgba) {
decltype(_colors)::value_type &slot = _colors[rgba.cgbColor()]; decltype(_colors)::value_type &slot = _colors[rgba.cgbColor()];
if (rgba.cgbColor() == Rgba::transparent) { if (rgba.cgbColor() == Rgba::transparent) {
@@ -80,7 +78,8 @@ class Png {
int nbTransparentEntries; int nbTransparentEntries;
png_bytep transparencyPal = nullptr; png_bytep transparencyPal = nullptr;
[[noreturn]] static void handleError(png_structp png, char const *msg) { [[noreturn]]
static void handleError(png_structp png, char const *msg) {
Png *self = reinterpret_cast<Png *>(png_get_error_ptr(png)); Png *self = reinterpret_cast<Png *>(png_get_error_ptr(png));
fatal("Error reading input image (\"%s\"): %s", self->c_str(), msg); fatal("Error reading input image (\"%s\"): %s", self->c_str(), msg);
@@ -166,15 +165,13 @@ public:
return true; return true;
} }
/* // Reads a PNG and notes all of its colors
* Reads a PNG and notes all of its colors //
* // This code is more complicated than strictly necessary, but that's because of the API
* This code is more complicated than strictly necessary, but that's because of the API // being used: the "high-level" interface doesn't provide all the transformations we need,
* being used: the "high-level" interface doesn't provide all the transformations we need, // so we use the "lower-level" one instead.
* so we use the "lower-level" one instead. // We also use that occasion to only read the PNG one line at a time, since we store all of
* We also use that occasion to only read the PNG one line at a time, since we store all of // the pixel data in `pixels`, which saves on memory allocations.
* the pixel data in `pixels`, which saves on memory allocations.
*/
explicit Png(std::string const &filePath) : path(filePath), colors() { explicit Png(std::string const &filePath) : path(filePath), colors() {
if (file.open(path, std::ios_base::in | std::ios_base::binary) == nullptr) { if (file.open(path, std::ios_base::in | std::ios_base::binary) == nullptr) {
fatal("Failed to open input image (\"%s\"): %s", file.c_str(path), strerror(errno)); fatal("Failed to open input image (\"%s\"): %s", file.c_str(path), strerror(errno));
@@ -491,9 +488,7 @@ public:
}; };
class RawTiles { class RawTiles {
/* // A tile which only contains indices into the image's global palette
* A tile which only contains indices into the image's global palette
*/
class RawTile { class RawTile {
std::array<std::array<size_t, 8>, 8> _pixelIndices{}; std::array<std::array<size_t, 8>, 8> _pixelIndices{};
@@ -506,9 +501,7 @@ private:
std::vector<RawTile> _tiles; std::vector<RawTile> _tiles;
public: public:
/* // Creates a new raw tile, and returns a reference to it so it can be filled in
* Creates a new raw tile, and returns a reference to it so it can be filled in
*/
RawTile &newTile() { RawTile &newTile() {
_tiles.emplace_back(); _tiles.emplace_back();
return _tiles.back(); return _tiles.back();
@@ -516,11 +509,9 @@ public:
}; };
struct AttrmapEntry { struct AttrmapEntry {
/* // This field can either be a proto-palette ID, or `transparent` to indicate that the
* This field can either be a proto-palette ID, or `transparent` to indicate that the // corresponding tile is fully transparent. If you are looking to get the palette ID for this
* corresponding tile is fully transparent. If you are looking to get the palette ID for this // attrmap entry while correctly handling the above, use `getPalID`.
* attrmap entry while correctly handling the above, use `getPalID`.
*/
size_t protoPaletteID; // Only this field is used when outputting "unoptimized" data size_t protoPaletteID; // Only this field is used when outputting "unoptimized" data
uint8_t tileID; // This is the ID as it will be output to the tilemap uint8_t tileID; // This is the ID as it will be output to the tilemap
bool bank; bool bank;
@@ -624,7 +615,7 @@ static std::tuple<DefaultInitVec<size_t>, std::vector<Palette>>
for (uint16_t cgbColor : list) { for (uint16_t cgbColor : list) {
ptr += snprintf(ptr, sizeof(", $XXXX"), ", $%04x", cgbColor); ptr += snprintf(ptr, sizeof(", $XXXX"), ", $%04x", cgbColor);
} }
return &buf[QUOTEDSTRLEN(", ")]; return &buf[literal_strlen(", ")];
}; };
// Iterate through proto-palettes, and try mapping them to the specified palettes // Iterate through proto-palettes, and try mapping them to the specified palettes
@@ -713,8 +704,9 @@ static void hashBitplanes(uint16_t bitplanes, uint16_t &hash) {
class TileData { class TileData {
// Importantly, `TileData` is **always** 2bpp. // Importantly, `TileData` is **always** 2bpp.
// If the active bit depth is 1bpp, all tiles are processed as 2bpp nonetheless, but emitted as 1bpp. // If the active bit depth is 1bpp, all tiles are processed as 2bpp nonetheless, but emitted as
// This massively simplifies internal processing, since bit depth is always identical outside of I/O / serialization boundaries. // 1bpp. This massively simplifies internal processing, since bit depth is always identical
// outside of I/O / serialization boundaries.
std::array<uint8_t, 16> _data; std::array<uint8_t, 16> _data;
// The hash is a bit lax: it's the XOR of all lines, and every other nibble is identical // The hash is a bit lax: it's the XOR of all lines, and every other nibble is identical
// if horizontal mirroring is in effect. It should still be a reasonable tie-breaker in // if horizontal mirroring is in effect. It should still be a reasonable tie-breaker in
@@ -918,9 +910,7 @@ struct UniqueTiles {
UniqueTiles(UniqueTiles const &) = delete; UniqueTiles(UniqueTiles const &) = delete;
UniqueTiles(UniqueTiles &&) = default; UniqueTiles(UniqueTiles &&) = default;
/* // Adds a tile to the collection, and returns its ID
* Adds a tile to the collection, and returns its ID
*/
std::tuple<uint16_t, TileData::MatchType> addTile(TileData newTile) { std::tuple<uint16_t, TileData::MatchType> addTile(TileData newTile) {
auto [tileData, inserted] = tileset.insert(newTile); auto [tileData, inserted] = tileset.insert(newTile);
@@ -942,12 +932,10 @@ struct UniqueTiles {
auto end() const { return tiles.end(); } auto end() const { return tiles.end(); }
}; };
/* // Generate tile data while deduplicating unique tiles (via mirroring if enabled)
* Generate tile data while deduplicating unique tiles (via mirroring if enabled) // Additionally, while we have the info handy, convert from the 16-bit "global" tile IDs to
* Additionally, while we have the info handy, convert from the 16-bit "global" tile IDs to // 8-bit tile IDs + the bank bit; this will save the work when we output the data later (potentially
* 8-bit tile IDs + the bank bit; this will save the work when we output the data later (potentially // twice)
* twice)
*/
static UniqueTiles dedupTiles( static UniqueTiles dedupTiles(
Png const &png, Png const &png,
DefaultInitVec<AttrmapEntry> &attrmap, DefaultInitVec<AttrmapEntry> &attrmap,
@@ -999,10 +987,11 @@ static UniqueTiles dedupTiles(
} }
} }
bool inputWithoutOutput = !options.inputTileset.empty() && options.output.empty();
for (auto [tile, attr] : zip(png.visitAsTiles(), attrmap)) { for (auto [tile, attr] : zip(png.visitAsTiles(), attrmap)) {
auto [tileID, matchType] = tiles.addTile({tile, palettes[mappings[attr.protoPaletteID]]}); auto [tileID, matchType] = tiles.addTile({tile, palettes[mappings[attr.protoPaletteID]]});
if (matchType == TileData::NOPE && options.output.empty()) { if (inputWithoutOutput && matchType == TileData::NOPE) {
error( error(
"Tile at (%" PRIu32 ", %" PRIu32 "Tile at (%" PRIu32 ", %" PRIu32
") is not within the input tileset, and `-o` was not given!", ") is not within the input tileset, and `-o` was not given!",
@@ -1163,18 +1152,17 @@ void process() {
protoPalettes[n] = protoPalette; // Override them protoPalettes[n] = protoPalette; // Override them
// Remove any other proto-palettes that we encompass // Remove any other proto-palettes that we encompass
// (Example [(0, 1), (0, 2)], inserting (0, 1, 2)) // (Example [(0, 1), (0, 2)], inserting (0, 1, 2))
/* //
* The following code does its job, except that references to the removed // The following code does its job, except that references to the removed
* proto-palettes are not updated, causing issues. // proto-palettes are not updated, causing issues.
* TODO: overlap might not be detrimental to the packing algorithm. // TODO: overlap might not be detrimental to the packing algorithm.
* Investigation is necessary, especially if pathological cases are found. // Investigation is necessary, especially if pathological cases are found.
* //
* for (size_t i = protoPalettes.size(); --i != n;) { // for (size_t i = protoPalettes.size(); --i != n;) {
* if (protoPalette.compare(protoPalettes[i]) == ProtoPalette::WE_BIGGER) { // if (protoPalette.compare(protoPalettes[i]) == ProtoPalette::WE_BIGGER) {
* protoPalettes.erase(protoPalettes.begin() + i); // protoPalettes.erase(protoPalettes.begin() + i);
* } // }
* } // }
*/
[[fallthrough]]; [[fallthrough]];
case ProtoPalette::THEY_BIGGER: case ProtoPalette::THEY_BIGGER:

View File

@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: MIT */ // SPDX-License-Identifier: MIT
#include "gfx/proto_palette.hpp" #include "gfx/proto_palette.hpp"

View File

@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: MIT */ // SPDX-License-Identifier: MIT
#include "gfx/reverse.hpp" #include "gfx/reverse.hpp"
@@ -49,7 +49,8 @@ static DefaultInitVec<uint8_t> readInto(std::string const &path) {
return data; return data;
} }
[[noreturn]] static void pngError(png_structp png, char const *msg) { [[noreturn]]
static void pngError(png_structp png, char const *msg) {
fatal( fatal(
"Error writing reversed image (\"%s\"): %s", "Error writing reversed image (\"%s\"): %s",
static_cast<char const *>(png_get_error_ptr(png)), static_cast<char const *>(png_get_error_ptr(png)),
@@ -166,8 +167,9 @@ void reverse() {
// This avoids redundancy with `-r 1` which results in a vertical column. // This avoids redundancy with `-r 1` which results in a vertical column.
width = static_cast<size_t>(ceil(sqrt(mapSize))); width = static_cast<size_t>(ceil(sqrt(mapSize)));
for (; width < mapSize; ++width) { for (; width < mapSize; ++width) {
if (mapSize % width == 0) if (mapSize % width == 0) {
break; break;
}
} }
options.verbosePrint(Options::VERB_INTERM, "Picked reversing width of %zu tiles\n", width); options.verbosePrint(Options::VERB_INTERM, "Picked reversing width of %zu tiles\n", width);
} }
@@ -467,24 +469,7 @@ void reverse() {
assume(palID < palettes.size()); // Should be ensured on data read assume(palID < palettes.size()); // Should be ensured on data read
// We do not have data for tiles trimmed with `-x`, so assume they are "blank" // We do not have data for tiles trimmed with `-x`, so assume they are "blank"
static std::array<uint8_t, 16> const trimmedTile{ static std::array<uint8_t, 16> const trimmedTile{0x00};
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
};
uint8_t const *tileData = uint8_t const *tileData =
tileOfs >= nbTiles ? trimmedTile.data() : &tiles[tileOfs * tileSize]; tileOfs >= nbTiles ? trimmedTile.data() : &tiles[tileOfs * tileSize];
auto const &palette = palettes[palID]; auto const &palette = palettes[palID];

View File

@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: MIT */ // SPDX-License-Identifier: MIT
#include "gfx/rgba.hpp" #include "gfx/rgba.hpp"
@@ -10,29 +10,29 @@
#include "gfx/main.hpp" // options #include "gfx/main.hpp" // options
/* // Based on inverting the "Modern - Accurate" formula used by SameBoy
* Based on inverting the "Modern - Accurate" formula used by SameBoy // since commit b5a611c5db46d6a0649d04d24d8d6339200f9ca1 (Dec 2020),
* since commit b5a611c5db46d6a0649d04d24d8d6339200f9ca1 (Dec 2020), // with gaps in the scale curve filled by polynomial interpolation.
* with gaps in the scale curve filled by polynomial interpolation. // clang-format off: vertically align columns of values
*/
static std::array<uint8_t, 256> reverse_curve{ static std::array<uint8_t, 256> reverse_curve{
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, // These 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1,
1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, // comments 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3,
3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, // prevent 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5,
5, 5, 5, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, // clang-format 5, 5, 5, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7,
7, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 10, 10, 10, 10, // from 7, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 10, 10, 10, 10,
10, 10, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 13, 13, 13, // reflowing 10, 10, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 13, 13, 13,
13, 13, 14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 16, 16, 16, // these 13, 13, 14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 16, 16, 16,
16, 16, 16, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 19, 19, // sixteen 16, 16, 16, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 19, 19,
19, 19, 19, 20, 20, 20, 20, 20, 20, 21, 21, 21, 21, 21, 21, 22, // 16-item 19, 19, 19, 20, 20, 20, 20, 20, 20, 21, 21, 21, 21, 21, 21, 22,
22, 22, 22, 22, 22, 22, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, // lines, 22, 22, 22, 22, 22, 22, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24,
24, 24, 24, 25, 25, 25, 25, 25, 25, 25, 25, 26, 26, 26, 26, 26, // which, 24, 24, 24, 25, 25, 25, 25, 25, 25, 25, 25, 26, 26, 26, 26, 26,
26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 28, 28, 28, 28, // in 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 28, 28, 28, 28,
28, 28, 28, 28, 28, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, // my 28, 28, 28, 28, 28, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
29, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, // opinion, 29, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30,
31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, // help 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31,
31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, // visualization! 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31,
}; };
// clang-format on
uint16_t Rgba::cgbColor() const { uint16_t Rgba::cgbColor() const {
if (isTransparent()) { if (isTransparent()) {

View File

@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: MIT */ // SPDX-License-Identifier: MIT
#include "link/assign.hpp" #include "link/assign.hpp"
@@ -48,11 +48,7 @@ static void initFreeSpace() {
} }
} }
/* // Assigns a section to a given memory location
* Assigns a section to a given memory location
* @param section The section to assign
* @param location The location to assign the section to
*/
static void assignSection(Section &section, MemoryLocation const &location) { static void assignSection(Section &section, MemoryLocation const &location) {
// Propagate the assigned location to all UNIONs/FRAGMENTs // Propagate the assigned location to all UNIONs/FRAGMENTs
// so `jr` patches in them will have the correct offset // so `jr` patches in them will have the correct offset
@@ -66,37 +62,29 @@ static void assignSection(Section &section, MemoryLocation const &location) {
out_AddSection(section); out_AddSection(section);
} }
/* // Checks whether a given location is suitable for placing a given section
* Checks whether a given location is suitable for placing a given section // This checks not only that the location has enough room for the section, but
* This checks not only that the location has enough room for the section, but // also that the constraints (alignment...) are respected.
* also that the constraints (alignment...) are respected.
* @param section The section to be placed
* @param freeSpace The candidate free space to place the section into
* @param location The location to attempt placing the section at
* @return True if the location is suitable, false otherwise.
*/
static bool isLocationSuitable( static bool isLocationSuitable(
Section const &section, FreeSpace const &freeSpace, MemoryLocation const &location Section const &section, FreeSpace const &freeSpace, MemoryLocation const &location
) { ) {
if (section.isAddressFixed && section.org != location.address) if (section.isAddressFixed && section.org != location.address) {
return false; return false;
}
if (section.isAlignFixed && ((location.address - section.alignOfs) & section.alignMask)) if (section.isAlignFixed && ((location.address - section.alignOfs) & section.alignMask)) {
return false; return false;
}
if (location.address < freeSpace.address) if (location.address < freeSpace.address) {
return false; return false;
}
return location.address + section.size <= freeSpace.address + freeSpace.size; return location.address + section.size <= freeSpace.address + freeSpace.size;
} }
/* // Returns a suitable free space index into `memory[section->type]` at which to place the given
* Finds a suitable location to place a section at. // section, or -1 if none was found.
* @param section The section to be placed
* @param location A pointer to a memory location that will be filled
* @return The index into `memory[section->type]` of the free space encompassing the location,
* or -1 if none was found
*/
static ssize_t getPlacement(Section const &section, MemoryLocation &location) { static ssize_t getPlacement(Section const &section, MemoryLocation &location) {
SectionTypeInfo const &typeInfo = sectionTypeInfo[section.type]; SectionTypeInfo const &typeInfo = sectionTypeInfo[section.type];
@@ -108,16 +96,19 @@ static ssize_t getPlacement(Section const &section, MemoryLocation &location) {
if (section.isBankFixed) { if (section.isBankFixed) {
location.bank = section.bank; location.bank = section.bank;
} else if (scrambleROMX && section.type == SECTTYPE_ROMX) { } else if (scrambleROMX && section.type == SECTTYPE_ROMX) {
if (curScrambleROM < 1) if (curScrambleROM < 1) {
curScrambleROM = scrambleROMX; curScrambleROM = scrambleROMX;
}
location.bank = curScrambleROM--; location.bank = curScrambleROM--;
} else if (scrambleWRAMX && section.type == SECTTYPE_WRAMX) { } else if (scrambleWRAMX && section.type == SECTTYPE_WRAMX) {
if (curScrambleWRAM < 1) if (curScrambleWRAM < 1) {
curScrambleWRAM = scrambleWRAMX; curScrambleWRAM = scrambleWRAMX;
}
location.bank = curScrambleWRAM--; location.bank = curScrambleWRAM--;
} else if (scrambleSRAM && section.type == SECTTYPE_SRAM) { } else if (scrambleSRAM && section.type == SECTTYPE_SRAM) {
if (curScrambleSRAM < 0) if (curScrambleSRAM < 0) {
curScrambleSRAM = scrambleSRAM; curScrambleSRAM = scrambleSRAM;
}
location.bank = curScrambleSRAM--; location.bank = curScrambleSRAM--;
} else { } else {
location.bank = typeInfo.firstBank; location.bank = typeInfo.firstBank;
@@ -134,18 +125,20 @@ static ssize_t getPlacement(Section const &section, MemoryLocation &location) {
// Process locations in that bank // Process locations in that bank
while (spaceIdx < bankMem.size()) { while (spaceIdx < bankMem.size()) {
// If that location is OK, return it // If that location is OK, return it
if (isLocationSuitable(section, bankMem[spaceIdx], location)) if (isLocationSuitable(section, bankMem[spaceIdx], location)) {
return spaceIdx; return spaceIdx;
}
// Go to the next *possible* location // Go to the next *possible* location
if (section.isAddressFixed) { if (section.isAddressFixed) {
// If the address is fixed, there can be only // If the address is fixed, there can be only
// one candidate block per bank; if we already // one candidate block per bank; if we already
// reached it, give up. // reached it, give up.
if (location.address < section.org) if (location.address < section.org) {
location.address = section.org; location.address = section.org;
else } else {
break; // Try again in next bank break; // Try again in next bank
}
} else if (section.isAlignFixed) { } else if (section.isAlignFixed) {
// Move to next aligned location // Move to next aligned location
// Move back to alignment boundary // Move back to alignment boundary
@@ -157,15 +150,17 @@ static ssize_t getPlacement(Section const &section, MemoryLocation &location) {
} else { } else {
// Any location is fine, so, next free block // Any location is fine, so, next free block
spaceIdx++; spaceIdx++;
if (spaceIdx < bankMem.size()) if (spaceIdx < bankMem.size()) {
location.address = bankMem[spaceIdx].address; location.address = bankMem[spaceIdx].address;
}
} }
// If that location is past the current block's end, // If that location is past the current block's end,
// go forwards until that is no longer the case. // go forwards until that is no longer the case.
while (spaceIdx < bankMem.size() while (spaceIdx < bankMem.size()
&& location.address >= bankMem[spaceIdx].address + bankMem[spaceIdx].size) && location.address >= bankMem[spaceIdx].address + bankMem[spaceIdx].size) {
spaceIdx++; spaceIdx++;
}
// Try again with the new location/free space combo // Try again with the new location/free space combo
} }
@@ -177,26 +172,30 @@ static ssize_t getPlacement(Section const &section, MemoryLocation &location) {
if (section.isBankFixed) { if (section.isBankFixed) {
return -1; return -1;
} else if (scrambleROMX && section.type == SECTTYPE_ROMX && location.bank <= scrambleROMX) { } else if (scrambleROMX && section.type == SECTTYPE_ROMX && location.bank <= scrambleROMX) {
if (location.bank > typeInfo.firstBank) if (location.bank > typeInfo.firstBank) {
location.bank--; location.bank--;
else if (scrambleROMX < typeInfo.lastBank) } else if (scrambleROMX < typeInfo.lastBank) {
location.bank = scrambleROMX + 1; location.bank = scrambleROMX + 1;
else } else {
return -1; return -1;
} else if (scrambleWRAMX && section.type == SECTTYPE_WRAMX && location.bank <= scrambleWRAMX) { }
if (location.bank > typeInfo.firstBank) } else if (scrambleWRAMX && section.type == SECTTYPE_WRAMX
&& location.bank <= scrambleWRAMX) {
if (location.bank > typeInfo.firstBank) {
location.bank--; location.bank--;
else if (scrambleWRAMX < typeInfo.lastBank) } else if (scrambleWRAMX < typeInfo.lastBank) {
location.bank = scrambleWRAMX + 1; location.bank = scrambleWRAMX + 1;
else } else {
return -1; return -1;
}
} else if (scrambleSRAM && section.type == SECTTYPE_SRAM && location.bank <= scrambleSRAM) { } else if (scrambleSRAM && section.type == SECTTYPE_SRAM && location.bank <= scrambleSRAM) {
if (location.bank > typeInfo.firstBank) if (location.bank > typeInfo.firstBank) {
location.bank--; location.bank--;
else if (scrambleSRAM < typeInfo.lastBank) } else if (scrambleSRAM < typeInfo.lastBank) {
location.bank = scrambleSRAM + 1; location.bank = scrambleSRAM + 1;
else } else {
return -1; return -1;
}
} else if (location.bank < typeInfo.lastBank) { } else if (location.bank < typeInfo.lastBank) {
location.bank++; location.bank++;
} else { } else {
@@ -205,12 +204,8 @@ static ssize_t getPlacement(Section const &section, MemoryLocation &location) {
} }
} }
/* // Places a section in a suitable location, or error out if it fails to.
* Places a section in a suitable location, or error out if it fails to. // Due to the implemented algorithm, this should be called with sections of decreasing size!
* @warning Due to the implemented algorithm, this should be called with
* sections of decreasing size.
* @param section The section to place
*/
static void placeSection(Section &section) { static void placeSection(Section &section) {
MemoryLocation location; MemoryLocation location;
@@ -258,9 +253,10 @@ static void placeSection(Section &section) {
} else { } else {
// The amount of free spaces doesn't change: resize! // The amount of free spaces doesn't change: resize!
freeSpace.size -= section.size; freeSpace.size -= section.size;
if (noLeftSpace) if (noLeftSpace) {
// The free space is moved *and* resized // The free space is moved *and* resized
freeSpace.address += section.size; freeSpace.address += section.size;
}
} }
return; return;
} }
@@ -269,11 +265,11 @@ static void placeSection(Section &section) {
char where[64]; char where[64];
if (section.isBankFixed && nbbanks(section.type) != 1) { if (section.isBankFixed && nbbanks(section.type) != 1) {
if (section.isAddressFixed) if (section.isAddressFixed) {
snprintf( snprintf(
where, sizeof(where), "at $%02" PRIx32 ":%04" PRIx16, section.bank, section.org where, sizeof(where), "at $%02" PRIx32 ":%04" PRIx16, section.bank, section.org
); );
else if (section.isAlignFixed) } else if (section.isAlignFixed) {
snprintf( snprintf(
where, where,
sizeof(where), sizeof(where),
@@ -281,12 +277,13 @@ static void placeSection(Section &section) {
section.bank, section.bank,
static_cast<uint16_t>(~section.alignMask) static_cast<uint16_t>(~section.alignMask)
); );
else } else {
snprintf(where, sizeof(where), "in bank $%02" PRIx32, section.bank); snprintf(where, sizeof(where), "in bank $%02" PRIx32, section.bank);
}
} else { } else {
if (section.isAddressFixed) if (section.isAddressFixed) {
snprintf(where, sizeof(where), "at address $%04" PRIx16, section.org); snprintf(where, sizeof(where), "at address $%04" PRIx16, section.org);
else if (section.isAlignFixed) } else if (section.isAlignFixed) {
snprintf( snprintf(
where, where,
sizeof(where), sizeof(where),
@@ -294,20 +291,22 @@ static void placeSection(Section &section) {
static_cast<uint16_t>(~section.alignMask), static_cast<uint16_t>(~section.alignMask),
section.alignOfs section.alignOfs
); );
else } else {
strcpy(where, "anywhere"); strcpy(where, "anywhere");
}
} }
// If a section failed to go to several places, nothing we can report // If a section failed to go to several places, nothing we can report
if (!section.isBankFixed || !section.isAddressFixed) if (!section.isBankFixed || !section.isAddressFixed) {
errx( errx(
"Unable to place \"%s\" (%s section) %s", "Unable to place \"%s\" (%s section) %s",
section.name.c_str(), section.name.c_str(),
sectionTypeInfo[section.type].name.c_str(), sectionTypeInfo[section.type].name.c_str(),
where where
); );
}
// If the section just can't fit the bank, report that // If the section just can't fit the bank, report that
else if (section.org + section.size > endaddr(section.type) + 1) else if (section.org + section.size > endaddr(section.type) + 1) {
errx( errx(
"Unable to place \"%s\" (%s section) %s: section runs past end of region ($%04x > " "Unable to place \"%s\" (%s section) %s: section runs past end of region ($%04x > "
"$%04x)", "$%04x)",
@@ -317,8 +316,9 @@ static void placeSection(Section &section) {
section.org + section.size, section.org + section.size,
endaddr(section.type) + 1 endaddr(section.type) + 1
); );
}
// Otherwise there is overlap with another section // Otherwise there is overlap with another section
else else {
errx( errx(
"Unable to place \"%s\" (%s section) %s: section overlaps with \"%s\"", "Unable to place \"%s\" (%s section) %s: section overlaps with \"%s\"",
section.name.c_str(), section.name.c_str(),
@@ -326,35 +326,39 @@ static void placeSection(Section &section) {
where, where,
out_OverlappingSection(section)->name.c_str() out_OverlappingSection(section)->name.c_str()
); );
}
} }
#define BANK_CONSTRAINED (1 << 2) // clang-format off: vertically align values
#define ORG_CONSTRAINED (1 << 1) static constexpr uint8_t BANK_CONSTRAINED = 1 << 2;
#define ALIGN_CONSTRAINED (1 << 0) static constexpr uint8_t ORG_CONSTRAINED = 1 << 1;
static constexpr uint8_t ALIGN_CONSTRAINED = 1 << 0;
// clang-format on
static std::deque<Section *> unassignedSections[1 << 3]; static std::deque<Section *> unassignedSections[1 << 3];
/* // Categorize a section depending on how constrained it is.
* Categorize a section depending on how constrained it is // This is so the most-constrained sections are placed first.
* This is so the most-constrained sections are placed first
* @param section The section to categorize
*/
static void categorizeSection(Section &section) { static void categorizeSection(Section &section) {
uint8_t constraints = 0; uint8_t constraints = 0;
if (section.isBankFixed) if (section.isBankFixed) {
constraints |= BANK_CONSTRAINED; constraints |= BANK_CONSTRAINED;
if (section.isAddressFixed) }
if (section.isAddressFixed) {
constraints |= ORG_CONSTRAINED; constraints |= ORG_CONSTRAINED;
}
// Can't have both! // Can't have both!
else if (section.isAlignFixed) else if (section.isAlignFixed) {
constraints |= ALIGN_CONSTRAINED; constraints |= ALIGN_CONSTRAINED;
}
std::deque<Section *> &sections = unassignedSections[constraints]; std::deque<Section *> &sections = unassignedSections[constraints];
auto pos = sections.begin(); auto pos = sections.begin();
// Insert section while keeping the list sorted by decreasing size // Insert section while keeping the list sorted by decreasing size
while (pos != sections.end() && (*pos)->size > section.size) while (pos != sections.end() && (*pos)->size > section.size) {
pos++; pos++;
}
sections.insert(pos, &section); sections.insert(pos, &section);
nbSectionsToAssign++; nbSectionsToAssign++;
@@ -375,12 +379,14 @@ void assign_AssignSections() {
// Specially process fully-constrained sections because of overlaying // Specially process fully-constrained sections because of overlaying
verbosePrint("Assigning bank+org-constrained...\n"); verbosePrint("Assigning bank+org-constrained...\n");
for (Section *section : unassignedSections[BANK_CONSTRAINED | ORG_CONSTRAINED]) for (Section *section : unassignedSections[BANK_CONSTRAINED | ORG_CONSTRAINED]) {
placeSection(*section); placeSection(*section);
}
// If all sections were fully constrained, we have nothing left to do // If all sections were fully constrained, we have nothing left to do
if (!nbSectionsToAssign) if (!nbSectionsToAssign) {
return; return;
}
// Overlaying requires only fully-constrained sections // Overlaying requires only fully-constrained sections
verbosePrint("Assigning other sections...\n"); verbosePrint("Assigning other sections...\n");
@@ -392,14 +398,16 @@ void assign_AssignSections() {
for (Section *section : unassignedSections[constraints]) { for (Section *section : unassignedSections[constraints]) {
fprintf(stderr, "%c \"%s\"", nbSections == 0 ? ';' : ',', section->name.c_str()); fprintf(stderr, "%c \"%s\"", nbSections == 0 ? ';' : ',', section->name.c_str());
nbSections++; nbSections++;
if (nbSections == 10) if (nbSections == 10) {
goto max_out; // Can't `break` out of a nested loop goto max_out; // Can't `break` out of a nested loop
}
} }
} }
max_out: max_out:
if (nbSectionsToAssign != nbSections) if (nbSectionsToAssign != nbSections) {
fprintf(stderr, " and %" PRIu64 " more", nbSectionsToAssign - nbSections); fprintf(stderr, " and %" PRIu64 " more", nbSectionsToAssign - nbSections);
}
fprintf(stderr, " %sn't\n", nbSectionsToAssign == 1 ? "is" : "are"); fprintf(stderr, " %sn't\n", nbSectionsToAssign == 1 ? "is" : "are");
exit(1); exit(1);
} }
@@ -407,11 +415,13 @@ max_out:
// Assign all remaining sections by decreasing constraint order // Assign all remaining sections by decreasing constraint order
for (int8_t constraints = BANK_CONSTRAINED | ALIGN_CONSTRAINED; constraints >= 0; for (int8_t constraints = BANK_CONSTRAINED | ALIGN_CONSTRAINED; constraints >= 0;
constraints--) { constraints--) {
for (Section *section : unassignedSections[constraints]) for (Section *section : unassignedSections[constraints]) {
placeSection(*section); placeSection(*section);
}
if (!nbSectionsToAssign) if (!nbSectionsToAssign) {
return; return;
}
} }
unreachable_(); unreachable_();

View File

@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: MIT */ // SPDX-License-Identifier: MIT
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/types.h> #include <sys/types.h>
@@ -52,8 +52,9 @@ std::string const &FileStackNode::dump(uint32_t curLineNo) const {
std::string const &lastName = parent->dump(lineNo); std::string const &lastName = parent->dump(lineNo);
fputs(" -> ", stderr); fputs(" -> ", stderr);
fputs(lastName.c_str(), stderr); fputs(lastName.c_str(), stderr);
for (uint32_t iter : iters()) for (uint32_t iter : iters()) {
fprintf(stderr, "::REPT~%" PRIu32, iter); fprintf(stderr, "::REPT~%" PRIu32, iter);
}
fprintf(stderr, "(%" PRIu32 ")", curLineNo); fprintf(stderr, "(%" PRIu32 ")", curLineNo);
return lastName; return lastName;
} else { } else {
@@ -96,8 +97,9 @@ void error(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...) {
printDiag(fmt, args, "error", where, lineNo); printDiag(fmt, args, "error", where, lineNo);
va_end(args); va_end(args);
if (nbErrors != UINT32_MAX) if (nbErrors != UINT32_MAX) {
nbErrors++; nbErrors++;
}
} }
void argErr(char flag, char const *fmt, ...) { void argErr(char flag, char const *fmt, ...) {
@@ -109,19 +111,22 @@ void argErr(char flag, char const *fmt, ...) {
va_end(args); va_end(args);
putc('\n', stderr); putc('\n', stderr);
if (nbErrors != UINT32_MAX) if (nbErrors != UINT32_MAX) {
nbErrors++; nbErrors++;
}
} }
[[noreturn]] void fatal(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...) { [[noreturn]]
void fatal(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...) {
va_list args; va_list args;
va_start(args, fmt); va_start(args, fmt);
printDiag(fmt, args, "FATAL", where, lineNo); printDiag(fmt, args, "FATAL", where, lineNo);
va_end(args); va_end(args);
if (nbErrors != UINT32_MAX) if (nbErrors != UINT32_MAX) {
nbErrors++; nbErrors++;
}
fprintf( fprintf(
stderr, "Linking aborted after %" PRIu32 " error%s\n", nbErrors, nbErrors == 1 ? "" : "s" stderr, "Linking aborted after %" PRIu32 " error%s\n", nbErrors, nbErrors == 1 ? "" : "s"
@@ -130,20 +135,18 @@ void argErr(char flag, char const *fmt, ...) {
} }
// Short options // Short options
static char const *optstring = "dl:m:Mn:O:o:p:S:tVvWwx"; static char const *optstring = "dhl:m:Mn:O:o:p:S:tVvWwx";
/* // Equivalent long options
* Equivalent long options // Please keep in the same order as short opts.
* Please keep in the same order as short opts // Also, make sure long opts don't create ambiguity:
* // A long opt's name should start with the same letter as its short opt,
* Also, make sure long opts don't create ambiguity: // except if it doesn't create any ambiguity (`verbose` versus `version`).
* A long opt's name should start with the same letter as its short opt, // This is because long opt matching, even to a single char, is prioritized
* except if it doesn't create any ambiguity (`verbose` versus `version`). // over short opt matching.
* This is because long opt matching, even to a single char, is prioritized
* over short opt matching
*/
static option const longopts[] = { static option const longopts[] = {
{"dmg", no_argument, nullptr, 'd'}, {"dmg", no_argument, nullptr, 'd'},
{"help", no_argument, nullptr, 'h'},
{"linkerscript", required_argument, nullptr, 'l'}, {"linkerscript", required_argument, nullptr, 'l'},
{"map", required_argument, nullptr, 'm'}, {"map", required_argument, nullptr, 'm'},
{"no-sym-in-map", no_argument, nullptr, 'M'}, {"no-sym-in-map", no_argument, nullptr, 'M'},
@@ -162,7 +165,7 @@ static option const longopts[] = {
static void printUsage() { static void printUsage() {
fputs( fputs(
"Usage: rgblink [-dMtVvwx] [-l script] [-m map_file] [-n sym_file]\n" "Usage: rgblink [-dhMtVvwx] [-l script] [-m map_file] [-n sym_file]\n"
" [-O overlay_file] [-o out_file] [-p pad_value]\n" " [-O overlay_file] [-o out_file] [-p pad_value]\n"
" [-S spec] <file> ...\n" " [-S spec] <file> ...\n"
"Useful options:\n" "Useful options:\n"
@@ -217,19 +220,19 @@ static void parseScrambleSpec(char const *spec) {
if (regionNameLen == 0) { if (regionNameLen == 0) {
argErr('S', "Missing region name"); argErr('S', "Missing region name");
if (*spec == '\0') if (*spec == '\0') {
break; break;
if (*spec == '=') // Skip the limit, too }
if (*spec == '=') { // Skip the limit, too
spec = strchr(&spec[1], ','); // Skip to next comma, if any spec = strchr(&spec[1], ','); // Skip to next comma, if any
}
goto next; goto next;
} }
// Find the next non-blank char after the region name's end // Find the next non-blank char after the region name's end
spec += regionNameLen + strspn(&spec[regionNameLen], " \t"); spec += regionNameLen + strspn(&spec[regionNameLen], " \t");
if (*spec != '\0' && *spec != ',' && *spec != '=') { if (*spec != '\0' && *spec != ',' && *spec != '=') {
argErr( argErr('S', "Unexpected '%c' after region name \"%.*s\"", regionNameFmtLen, regionName);
'S', "Unexpected '%c' after region name \"%.*s\"", regionNameFmtLen, regionName
);
// Skip to next ',' or '=' (or NUL) and keep parsing // Skip to next ',' or '=' (or NUL) and keep parsing
spec += 1 + strcspn(&spec[1], ",="); spec += 1 + strcspn(&spec[1], ",=");
} }
@@ -245,8 +248,9 @@ static void parseScrambleSpec(char const *spec) {
} }
} }
if (region == SCRAMBLE_UNK) if (region == SCRAMBLE_UNK) {
argErr('S', "Unknown region \"%.*s\"", regionNameFmtLen, regionName); argErr('S', "Unknown region \"%.*s\"", regionNameFmtLen, regionName);
}
if (*spec == '=') { if (*spec == '=') {
spec++; // `strtoul` will skip the whitespace on its own spec++; // `strtoul` will skip the whitespace on its own
@@ -304,15 +308,18 @@ static void parseScrambleSpec(char const *spec) {
next: // Can't `continue` a `for` loop with this nontrivial iteration logic next: // Can't `continue` a `for` loop with this nontrivial iteration logic
if (spec) { if (spec) {
assume(*spec == ',' || *spec == '\0'); assume(*spec == ',' || *spec == '\0');
if (*spec == ',') if (*spec == ',') {
spec += 1 + strspn(&spec[1], " \t"); spec += 1 + strspn(&spec[1], " \t");
if (*spec == '\0') }
if (*spec == '\0') {
break; break;
}
} }
} }
} }
[[noreturn]] void reportErrors() { [[noreturn]]
void reportErrors() {
fprintf( fprintf(
stderr, "Linking failed with %" PRIu32 " error%s\n", nbErrors, nbErrors == 1 ? "" : "s" stderr, "Linking failed with %" PRIu32 " error%s\n", nbErrors, nbErrors == 1 ? "" : "s"
); );
@@ -327,32 +334,40 @@ int main(int argc, char *argv[]) {
isDmgMode = true; isDmgMode = true;
isWRAM0Mode = true; isWRAM0Mode = true;
break; break;
case 'h':
printUsage();
exit(0);
case 'l': case 'l':
if (linkerScriptName) if (linkerScriptName) {
warnx("Overriding linker script %s", linkerScriptName); warnx("Overriding linker script %s", linkerScriptName);
}
linkerScriptName = musl_optarg; linkerScriptName = musl_optarg;
break; break;
case 'M': case 'M':
noSymInMap = true; noSymInMap = true;
break; break;
case 'm': case 'm':
if (mapFileName) if (mapFileName) {
warnx("Overriding map file %s", mapFileName); warnx("Overriding map file %s", mapFileName);
}
mapFileName = musl_optarg; mapFileName = musl_optarg;
break; break;
case 'n': case 'n':
if (symFileName) if (symFileName) {
warnx("Overriding sym file %s", symFileName); warnx("Overriding sym file %s", symFileName);
}
symFileName = musl_optarg; symFileName = musl_optarg;
break; break;
case 'O': case 'O':
if (overlayFileName) if (overlayFileName) {
warnx("Overriding overlay file %s", overlayFileName); warnx("Overriding overlay file %s", overlayFileName);
}
overlayFileName = musl_optarg; overlayFileName = musl_optarg;
break; break;
case 'o': case 'o':
if (outputFileName) if (outputFileName) {
warnx("Overriding output file %s", outputFileName); warnx("Overriding output file %s", outputFileName);
}
outputFileName = musl_optarg; outputFileName = musl_optarg;
break; break;
case 'p': { case 'p': {
@@ -409,18 +424,22 @@ int main(int argc, char *argv[]) {
} }
// Patch the size array depending on command-line options // Patch the size array depending on command-line options
if (!is32kMode) if (!is32kMode) {
sectionTypeInfo[SECTTYPE_ROM0].size = 0x4000; sectionTypeInfo[SECTTYPE_ROM0].size = 0x4000;
if (!isWRAM0Mode) }
if (!isWRAM0Mode) {
sectionTypeInfo[SECTTYPE_WRAM0].size = 0x1000; sectionTypeInfo[SECTTYPE_WRAM0].size = 0x1000;
}
// Patch the bank ranges array depending on command-line options // Patch the bank ranges array depending on command-line options
if (isDmgMode) if (isDmgMode) {
sectionTypeInfo[SECTTYPE_VRAM].lastBank = 0; sectionTypeInfo[SECTTYPE_VRAM].lastBank = 0;
}
// Read all object files first, // Read all object files first,
for (obj_Setup(argc - curArgIndex); curArgIndex < argc; curArgIndex++) for (obj_Setup(argc - curArgIndex); curArgIndex < argc; curArgIndex++) {
obj_ReadFile(argv[curArgIndex], argc - curArgIndex - 1); obj_ReadFile(argv[curArgIndex], argc - curArgIndex - 1);
}
// apply the linker script's modifications, // apply the linker script's modifications,
if (linkerScriptName) { if (linkerScriptName) {
@@ -429,20 +448,23 @@ int main(int argc, char *argv[]) {
script_ProcessScript(linkerScriptName); script_ProcessScript(linkerScriptName);
// If the linker script produced any errors, some sections may be in an invalid state // If the linker script produced any errors, some sections may be in an invalid state
if (nbErrors != 0) if (nbErrors != 0) {
reportErrors(); reportErrors();
}
} }
// then process them, // then process them,
sect_DoSanityChecks(); sect_DoSanityChecks();
if (nbErrors != 0) if (nbErrors != 0) {
reportErrors(); reportErrors();
}
assign_AssignSections(); assign_AssignSections();
patch_CheckAssertions(); patch_CheckAssertions();
// and finally output the result. // and finally output the result.
patch_ApplyPatches(); patch_ApplyPatches();
if (nbErrors != 0) if (nbErrors != 0) {
reportErrors(); reportErrors();
}
out_WriteFiles(); out_WriteFiles();
} }

View File

@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: MIT */ // SPDX-License-Identifier: MIT
#include "link/object.hpp" #include "link/object.hpp"
@@ -41,11 +41,7 @@ static std::vector<std::vector<FileStackNode>> nodes;
var = static_cast<vartype>(tmpVal); \ var = static_cast<vartype>(tmpVal); \
} while (0) } while (0)
/* // Reads an unsigned long (32-bit) value from a file, or `INT64_MAX` on failure.
* Reads an unsigned long (32-bit) value from a file.
* @param file The file to read from. This will read 4 bytes from the file.
* @return The value read, cast to a int64_t, or `INT64_MAX` on failure.
*/
static int64_t readLong(FILE *file) { static int64_t readLong(FILE *file) {
uint32_t value = 0; uint32_t value = 0;
@@ -53,8 +49,9 @@ static int64_t readLong(FILE *file) {
for (uint8_t shift = 0; shift < sizeof(value) * CHAR_BIT; shift += 8) { for (uint8_t shift = 0; shift < sizeof(value) * CHAR_BIT; shift += 8) {
int byte = getc(file); int byte = getc(file);
if (byte == EOF) if (byte == EOF) {
return INT64_MAX; return INT64_MAX;
}
// This must be casted to `unsigned`, not `uint8_t`. Rationale: // This must be casted to `unsigned`, not `uint8_t`. Rationale:
// the type of the shift is the type of `byte` after undergoing // the type of the shift is the type of `byte` after undergoing
// integer promotion, which would be `int` if this was casted to // integer promotion, which would be `int` if this was casted to
@@ -66,37 +63,14 @@ static int64_t readLong(FILE *file) {
return value; return value;
} }
/* // Helper macro to read a long from a file to a var, or error out if it fails to.
* Helper macro for reading longs from a file, and errors out if it fails to.
* Not as a function to avoid overhead in the general case.
* @param var The variable to stash the number into
* @param file The file to read from. Its position will be advanced
* @param ... A format string and related arguments; note that an extra string
* argument is provided, the reason for failure
*/
#define tryReadLong(var, file, ...) \ #define tryReadLong(var, file, ...) \
tryRead(readLong, int64_t, INT64_MAX, long, var, file, __VA_ARGS__) tryRead(readLong, int64_t, INT64_MAX, long, var, file, __VA_ARGS__)
// There is no `readbyte`, just use `fgetc` or `getc`. // Helper macro to read a byte from a file to a var, or error out if it fails to.
/*
* Helper macro for reading bytes from a file, and errors out if it fails to.
* Not as a function to avoid overhead in the general case.
* @param var The variable to stash the number into
* @param file The file to read from. Its position will be advanced
* @param ... A format string and related arguments; note that an extra string
* argument is provided, the reason for failure
*/
#define tryGetc(type, var, file, ...) tryRead(getc, int, EOF, type, var, file, __VA_ARGS__) #define tryGetc(type, var, file, ...) tryRead(getc, int, EOF, type, var, file, __VA_ARGS__)
/* // Helper macro to read a '\0'-terminated string from a file, or error out if it fails to.
* Helper macro for readings '\0'-terminated strings from a file, and errors out if it fails to.
* Not as a function to avoid overhead in the general case.
* @param var The variable to stash the string into
* @param file The file to read from. Its position will be advanced
* @param ... A format string and related arguments; note that an extra string
* argument is provided, the reason for failure
*/
#define tryReadString(var, file, ...) \ #define tryReadString(var, file, ...) \
do { \ do { \
FILE *tmpFile = file; \ FILE *tmpFile = file; \
@@ -107,28 +81,24 @@ static int64_t readLong(FILE *file) {
} else { \ } else { \
tmpVal.push_back(tmpByte); \ tmpVal.push_back(tmpByte); \
} \ } \
}; \ } \
} while (0) } while (0)
// Functions to parse object files // Functions to parse object files
/* // Reads a file stack node from a file.
* Reads a file stack node form a file.
* @param file The file to read from
* @param nodes The file's array of nodes
* @param i The ID of the node in the array
* @param fileName The filename to report in errors
*/
static void readFileStackNode( static void readFileStackNode(
FILE *file, std::vector<FileStackNode> &fileNodes, uint32_t i, char const *fileName FILE *file, std::vector<FileStackNode> &fileNodes, uint32_t nodeID, char const *fileName
) { ) {
FileStackNode &node = fileNodes[i]; FileStackNode &node = fileNodes[nodeID];
uint32_t parentID; uint32_t parentID;
tryReadLong(parentID, file, "%s: Cannot read node #%" PRIu32 "'s parent ID: %s", fileName, i); tryReadLong(
parentID, file, "%s: Cannot read node #%" PRIu32 "'s parent ID: %s", fileName, nodeID
);
node.parent = parentID != UINT32_MAX ? &fileNodes[parentID] : nullptr; node.parent = parentID != UINT32_MAX ? &fileNodes[parentID] : nullptr;
tryReadLong( tryReadLong(
node.lineNo, file, "%s: Cannot read node #%" PRIu32 "'s line number: %s", fileName, i node.lineNo, file, "%s: Cannot read node #%" PRIu32 "'s line number: %s", fileName, nodeID
); );
tryGetc( tryGetc(
FileStackNodeType, FileStackNodeType,
@@ -136,47 +106,46 @@ static void readFileStackNode(
file, file,
"%s: Cannot read node #%" PRIu32 "'s type: %s", "%s: Cannot read node #%" PRIu32 "'s type: %s",
fileName, fileName,
i nodeID
); );
switch (node.type) { switch (node.type) {
case NODE_FILE: case NODE_FILE:
case NODE_MACRO: case NODE_MACRO:
node.data = ""; node.data = "";
tryReadString( tryReadString(
node.name(), file, "%s: Cannot read node #%" PRIu32 "'s file name: %s", fileName, i node.name(), file, "%s: Cannot read node #%" PRIu32 "'s file name: %s", fileName, nodeID
); );
break; break;
uint32_t depth; uint32_t depth;
case NODE_REPT: case NODE_REPT:
tryReadLong(depth, file, "%s: Cannot read node #%" PRIu32 "'s rept depth: %s", fileName, i); tryReadLong(
depth, file, "%s: Cannot read node #%" PRIu32 "'s rept depth: %s", fileName, nodeID
);
node.data = std::vector<uint32_t>(depth); node.data = std::vector<uint32_t>(depth);
for (uint32_t k = 0; k < depth; k++) for (uint32_t i = 0; i < depth; i++) {
tryReadLong( tryReadLong(
node.iters()[k], node.iters()[i],
file, file,
"%s: Cannot read node #%" PRIu32 "'s iter #%" PRIu32 ": %s", "%s: Cannot read node #%" PRIu32 "'s iter #%" PRIu32 ": %s",
fileName, fileName,
i, nodeID,
k i
); );
if (!node.parent) }
if (!node.parent) {
fatal( fatal(
nullptr, nullptr,
0, 0,
"%s is not a valid object file: root node (#%" PRIu32 ") may not be REPT", "%s is not a valid object file: root node (#%" PRIu32 ") may not be REPT",
fileName, fileName,
i nodeID
); );
}
} }
} }
/* // Reads a symbol from a file.
* Reads a symbol from a file.
* @param file The file to read from
* @param symbol The symbol to fill
* @param fileName The filename to report in errors
*/
static void readSymbol( static void readSymbol(
FILE *file, Symbol &symbol, char const *fileName, std::vector<FileStackNode> const &fileNodes FILE *file, Symbol &symbol, char const *fileName, std::vector<FileStackNode> const &fileNodes
) { ) {
@@ -229,19 +198,13 @@ static void readSymbol(
} }
} }
/* // Reads a patch from a file.
* Reads a patch from a file.
* @param file The file to read from
* @param patch The patch to fill
* @param fileName The filename to report in errors
* @param i The number of the patch to report in errors
*/
static void readPatch( static void readPatch(
FILE *file, FILE *file,
Patch &patch, Patch &patch,
char const *fileName, char const *fileName,
std::string const &sectName, std::string const &sectName,
uint32_t i, uint32_t patchID,
std::vector<FileStackNode> const &fileNodes std::vector<FileStackNode> const &fileNodes
) { ) {
uint32_t nodeID, rpnSize; uint32_t nodeID, rpnSize;
@@ -250,92 +213,85 @@ static void readPatch(
tryReadLong( tryReadLong(
nodeID, nodeID,
file, file,
"%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s node ID: %s", "%s: Cannot read \"%s\"'s patch #%" PRIu32 "'s node ID: %s",
fileName, fileName,
sectName.c_str(), sectName.c_str(),
i patchID
); );
patch.src = &fileNodes[nodeID]; patch.src = &fileNodes[nodeID];
tryReadLong( tryReadLong(
patch.lineNo, patch.lineNo,
file, file,
"%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s line number: %s", "%s: Cannot read \"%s\"'s patch #%" PRIu32 "'s line number: %s",
fileName, fileName,
sectName.c_str(), sectName.c_str(),
i patchID
); );
tryReadLong( tryReadLong(
patch.offset, patch.offset,
file, file,
"%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s offset: %s", "%s: Cannot read \"%s\"'s patch #%" PRIu32 "'s offset: %s",
fileName, fileName,
sectName.c_str(), sectName.c_str(),
i patchID
); );
tryReadLong( tryReadLong(
patch.pcSectionID, patch.pcSectionID,
file, file,
"%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s PC offset: %s", "%s: Cannot read \"%s\"'s patch #%" PRIu32 "'s PC offset: %s",
fileName, fileName,
sectName.c_str(), sectName.c_str(),
i patchID
); );
tryReadLong( tryReadLong(
patch.pcOffset, patch.pcOffset,
file, file,
"%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s PC offset: %s", "%s: Cannot read \"%s\"'s patch #%" PRIu32 "'s PC offset: %s",
fileName, fileName,
sectName.c_str(), sectName.c_str(),
i patchID
); );
tryGetc( tryGetc(
PatchType, PatchType,
type, type,
file, file,
"%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s type: %s", "%s: Cannot read \"%s\"'s patch #%" PRIu32 "'s type: %s",
fileName, fileName,
sectName.c_str(), sectName.c_str(),
i patchID
); );
patch.type = type; patch.type = type;
tryReadLong( tryReadLong(
rpnSize, rpnSize,
file, file,
"%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s RPN size: %s", "%s: Cannot read \"%s\"'s patch #%" PRIu32 "'s RPN size: %s",
fileName, fileName,
sectName.c_str(), sectName.c_str(),
i patchID
); );
patch.rpnExpression.resize(rpnSize); patch.rpnExpression.resize(rpnSize);
size_t nbElementsRead = fread(patch.rpnExpression.data(), 1, rpnSize, file); size_t nbElementsRead = fread(patch.rpnExpression.data(), 1, rpnSize, file);
if (nbElementsRead != rpnSize) if (nbElementsRead != rpnSize) {
errx( errx(
"%s: Cannot read \"%s\"'s patch #%" PRIu32 "'s RPN expression: %s", "%s: Cannot read \"%s\"'s patch #%" PRIu32 "'s RPN expression: %s",
fileName, fileName,
sectName.c_str(), sectName.c_str(),
i, patchID,
feof(file) ? "Unexpected end of file" : strerror(errno) feof(file) ? "Unexpected end of file" : strerror(errno)
); );
}
} }
/* // Sets a patch's `pcSection` from its `pcSectionID`.
* Sets a patch's pcSection from its pcSectionID.
* @param patch The patch to fix
*/
static void static void
linkPatchToPCSect(Patch &patch, std::vector<std::unique_ptr<Section>> const &fileSections) { linkPatchToPCSect(Patch &patch, std::vector<std::unique_ptr<Section>> const &fileSections) {
patch.pcSection = patch.pcSection =
patch.pcSectionID != UINT32_MAX ? fileSections[patch.pcSectionID].get() : nullptr; patch.pcSectionID != UINT32_MAX ? fileSections[patch.pcSectionID].get() : nullptr;
} }
/* // Reads a section from a file.
* Reads a section from a file.
* @param file The file to read from
* @param section The section to fill
* @param fileName The filename to report in errors
*/
static void readSection( static void readSection(
FILE *file, Section &section, char const *fileName, std::vector<FileStackNode> const &fileNodes FILE *file, Section &section, char const *fileName, std::vector<FileStackNode> const &fileNodes
) { ) {
@@ -356,8 +312,9 @@ static void readSection(
section.name.c_str() section.name.c_str()
); );
tryReadLong(tmp, file, "%s: Cannot read \"%s\"'s' size: %s", fileName, section.name.c_str()); tryReadLong(tmp, file, "%s: Cannot read \"%s\"'s' size: %s", fileName, section.name.c_str());
if (tmp < 0 || tmp > UINT16_MAX) if (tmp < 0 || tmp > UINT16_MAX) {
errx("\"%s\"'s section size ($%" PRIx32 ") is invalid", section.name.c_str(), tmp); errx("\"%s\"'s section size ($%" PRIx32 ") is invalid", section.name.c_str(), tmp);
}
section.size = tmp; section.size = tmp;
section.offset = 0; section.offset = 0;
tryGetc( tryGetc(
@@ -368,12 +325,13 @@ static void readSection(
} else { } else {
section.type = SectionType(type); section.type = SectionType(type);
} }
if (byte >> 7) if (byte >> 7) {
section.modifier = SECTION_UNION; section.modifier = SECTION_UNION;
else if (byte >> 6) } else if (byte >> 6) {
section.modifier = SECTION_FRAGMENT; section.modifier = SECTION_FRAGMENT;
else } else {
section.modifier = SECTION_NORMAL; section.modifier = SECTION_NORMAL;
}
tryReadLong(tmp, file, "%s: Cannot read \"%s\"'s org: %s", fileName, section.name.c_str()); tryReadLong(tmp, file, "%s: Cannot read \"%s\"'s org: %s", fileName, section.name.c_str());
section.isAddressFixed = tmp >= 0; section.isAddressFixed = tmp >= 0;
if (tmp > UINT16_MAX) { if (tmp > UINT16_MAX) {
@@ -392,8 +350,9 @@ static void readSection(
fileName, fileName,
section.name.c_str() section.name.c_str()
); );
if (byte > 16) if (byte > 16) {
byte = 16; byte = 16;
}
section.isAlignFixed = byte != 0; section.isAlignFixed = byte != 0;
section.alignMask = (1 << byte) - 1; section.alignMask = (1 << byte) - 1;
tryReadLong( tryReadLong(
@@ -415,13 +374,14 @@ static void readSection(
if (section.size) { if (section.size) {
section.data.resize(section.size); section.data.resize(section.size);
if (size_t nbRead = fread(section.data.data(), 1, section.size, file); if (size_t nbRead = fread(section.data.data(), 1, section.size, file);
nbRead != section.size) nbRead != section.size) {
errx( errx(
"%s: Cannot read \"%s\"'s data: %s", "%s: Cannot read \"%s\"'s data: %s",
fileName, fileName,
section.name.c_str(), section.name.c_str(),
feof(file) ? "Unexpected end of file" : strerror(errno) feof(file) ? "Unexpected end of file" : strerror(errno)
); );
}
} }
uint32_t nbPatches; uint32_t nbPatches;
@@ -435,16 +395,13 @@ static void readSection(
); );
section.patches.resize(nbPatches); section.patches.resize(nbPatches);
for (uint32_t i = 0; i < nbPatches; i++) for (uint32_t i = 0; i < nbPatches; i++) {
readPatch(file, section.patches[i], fileName, section.name, i, fileNodes); readPatch(file, section.patches[i], fileName, section.name, i, fileNodes);
}
} }
} }
/* // Links a symbol to a section, keeping the section's symbol list sorted.
* Links a symbol to a section, keeping the section's symbol list sorted.
* @param symbol The symbol to link
* @param section The section to link
*/
static void linkSymToSect(Symbol &symbol, Section &section) { static void linkSymToSect(Symbol &symbol, Section &section) {
uint32_t a = 0, b = section.symbols.size(); uint32_t a = 0, b = section.symbols.size();
int32_t symbolOffset = symbol.label().offset; int32_t symbolOffset = symbol.label().offset;
@@ -453,31 +410,27 @@ static void linkSymToSect(Symbol &symbol, Section &section) {
uint32_t c = (a + b) / 2; uint32_t c = (a + b) / 2;
int32_t otherOffset = section.symbols[c]->label().offset; int32_t otherOffset = section.symbols[c]->label().offset;
if (otherOffset > symbolOffset) if (otherOffset > symbolOffset) {
b = c; b = c;
else } else {
a = c + 1; a = c + 1;
}
} }
section.symbols.insert(section.symbols.begin() + a, &symbol); section.symbols.insert(section.symbols.begin() + a, &symbol);
} }
/* // Reads an assertion from a file.
* Reads an assertion from a file
* @param file The file to read from
* @param assert The assertion to fill
* @param fileName The filename to report in errors
*/
static void readAssertion( static void readAssertion(
FILE *file, FILE *file,
Assertion &assert, Assertion &assert,
char const *fileName, char const *fileName,
uint32_t i, uint32_t assertID,
std::vector<FileStackNode> const &fileNodes std::vector<FileStackNode> const &fileNodes
) { ) {
std::string assertName("Assertion #"); std::string assertName("Assertion #");
assertName += std::to_string(i); assertName += std::to_string(assertID);
readPatch(file, assert.patch, fileName, assertName, 0, fileNodes); readPatch(file, assert.patch, fileName, assertName, 0, fileNodes);
tryReadString(assert.message, file, "%s: Cannot read assertion's message: %s", fileName); tryReadString(assert.message, file, "%s: Cannot read assertion's message: %s", fileName);
} }
@@ -491,8 +444,9 @@ void obj_ReadFile(char const *fileName, unsigned int fileID) {
(void)setmode(STDIN_FILENO, O_BINARY); (void)setmode(STDIN_FILENO, O_BINARY);
file = stdin; file = stdin;
} }
if (!file) if (!file) {
err("Failed to open file \"%s\"", fileName); err("Failed to open file \"%s\"", fileName);
}
Defer closeFile{[&] { fclose(file); }}; Defer closeFile{[&] { fclose(file); }};
// First, check if the object is a RGBDS object or a SDCC one. If the first byte is 'R', // First, check if the object is a RGBDS object or a SDCC one. If the first byte is 'R',
@@ -528,15 +482,16 @@ void obj_ReadFile(char const *fileName, unsigned int fileID) {
int matchedElems; int matchedElems;
if (fscanf(file, RGBDS_OBJECT_VERSION_STRING "%n", &matchedElems) == 1 if (fscanf(file, RGBDS_OBJECT_VERSION_STRING "%n", &matchedElems) == 1
&& matchedElems != strlen(RGBDS_OBJECT_VERSION_STRING)) && matchedElems != literal_strlen(RGBDS_OBJECT_VERSION_STRING)) {
errx("%s: Not a RGBDS object file", fileName); errx("%s: Not a RGBDS object file", fileName);
}
verbosePrint("Reading object file %s\n", fileName); verbosePrint("Reading object file %s\n", fileName);
uint32_t revNum; uint32_t revNum;
tryReadLong(revNum, file, "%s: Cannot read revision number: %s", fileName); tryReadLong(revNum, file, "%s: Cannot read revision number: %s", fileName);
if (revNum != RGBDS_OBJECT_REV) if (revNum != RGBDS_OBJECT_REV) {
errx( errx(
"%s: Unsupported object file for rgblink %s; try rebuilding \"%s\"%s" "%s: Unsupported object file for rgblink %s; try rebuilding \"%s\"%s"
" (expected revision %d, got %d)", " (expected revision %d, got %d)",
@@ -547,6 +502,7 @@ void obj_ReadFile(char const *fileName, unsigned int fileID) {
RGBDS_OBJECT_REV, RGBDS_OBJECT_REV,
revNum revNum
); );
}
uint32_t nbNodes; uint32_t nbNodes;
uint32_t nbSymbols; uint32_t nbSymbols;
@@ -560,8 +516,9 @@ void obj_ReadFile(char const *fileName, unsigned int fileID) {
tryReadLong(nbNodes, file, "%s: Cannot read number of nodes: %s", fileName); tryReadLong(nbNodes, file, "%s: Cannot read number of nodes: %s", fileName);
nodes[fileID].resize(nbNodes); nodes[fileID].resize(nbNodes);
verbosePrint("Reading %u nodes...\n", nbNodes); verbosePrint("Reading %u nodes...\n", nbNodes);
for (uint32_t i = nbNodes; i--;) for (uint32_t i = nbNodes; i--;) {
readFileStackNode(file, nodes[fileID], i, fileName); readFileStackNode(file, nodes[fileID], i, fileName);
}
// This file's symbols, kept to link sections to them // This file's symbols, kept to link sections to them
std::vector<Symbol> &fileSymbols = symbolLists.emplace_front(nbSymbols); std::vector<Symbol> &fileSymbols = symbolLists.emplace_front(nbSymbols);
@@ -575,8 +532,9 @@ void obj_ReadFile(char const *fileName, unsigned int fileID) {
readSymbol(file, symbol, fileName, nodes[fileID]); readSymbol(file, symbol, fileName, nodes[fileID]);
sym_AddSymbol(symbol); sym_AddSymbol(symbol);
if (symbol.data.holds<Label>()) if (symbol.data.holds<Label>()) {
nbSymPerSect[symbol.data.get<Label>().sectionID]++; nbSymPerSect[symbol.data.get<Label>().sectionID]++;
}
} }
// This file's sections, stored in a table to link symbols to them // This file's sections, stored in a table to link symbols to them
@@ -607,8 +565,9 @@ void obj_ReadFile(char const *fileName, unsigned int fileID) {
// Give patches' PC section pointers to their sections // Give patches' PC section pointers to their sections
for (uint32_t i = 0; i < nbSections; i++) { for (uint32_t i = 0; i < nbSections; i++) {
if (sect_HasData(fileSections[i]->type)) { if (sect_HasData(fileSections[i]->type)) {
for (Patch &patch : fileSections[i]->patches) for (Patch &patch : fileSections[i]->patches) {
linkPatchToPCSect(patch, fileSections); linkPatchToPCSect(patch, fileSections);
}
} }
} }
@@ -623,8 +582,9 @@ void obj_ReadFile(char const *fileName, unsigned int fileID) {
} }
// Calling `sect_AddSection` invalidates the contents of `fileSections`! // Calling `sect_AddSection` invalidates the contents of `fileSections`!
for (uint32_t i = 0; i < nbSections; i++) for (uint32_t i = 0; i < nbSections; i++) {
sect_AddSection(std::move(fileSections[i])); sect_AddSection(std::move(fileSections[i]));
}
// Fix symbols' section pointers to component sections // Fix symbols' section pointers to component sections
// This has to run **after** all the `sect_AddSection()` calls, // This has to run **after** all the `sect_AddSection()` calls,

Some files were not shown because too many files have changed in this diff Show More