Compare commits

..

78 Commits

Author SHA1 Message Date
Rangi
9d993d84e8 Release 0.5.2 2021-11-23 17:08:03 -05:00
ISSOtm
fef168b2a5 Add the new completion scripts to checkdiff 2021-11-23 22:12:57 +01:00
ISSOtm
4a4b22c78c Add Bash completion scripts for the last 3 2021-11-23 22:10:08 +01:00
Rangi
db79689e81 Add -Wnumeric-string to shell autocompletion 2021-11-22 17:58:12 -05:00
Rangi
c2ca46c27d Remove TRACE_PARSER support
Attempting to build with this gave an "undefined reference to `yydebug'"
error (maybe a version issue with bison?), and I don't think it's been
used for recent parser debugging either.
2021-11-22 23:49:59 +01:00
Rangi
aac839f389 Remove dbgPrint and TRACE_LEXER support
I have not found `TRACE_LEXER` to be useful in debugging
actual lexer issues.
2021-11-22 23:49:59 +01:00
Rangi
9d9febe1d3 Consistent title case for rgbasm.5 headings 2021-11-22 17:12:16 -05:00
Rangi
b9fd85470e Reword docs now that SET is deprecated (#946)
- '=' constants are "variables" (not "mutable constants")
- EQU constants are "numeric constants" (not "immutable constants")
- EQUS constants are "string constants" (not "string equates")
- DS declarations are "static allocations" (not "variables")
2021-11-22 17:08:29 -05:00
Rangi
ec6d63bce3 Allow underscores in gfx literals (#951)
Fixes #950
2021-11-21 16:18:23 -05:00
Rangi
bdcef6f252 Refactor error reporting to simplify BSD-style err (#949) 2021-11-21 16:16:54 -05:00
ISSOtm
54293a9184 Remove unused "MAX_PATH" header
The header's name was also quite misleading.
Also remove an unused define in `asm/symbol.h`.
2021-11-21 18:32:29 +01:00
ISSOtm
b04e71ed34 Use correct length type for Abs*Group 2021-11-21 18:28:42 +01:00
Rangi
f82603f196 Lowercase "error:" in -Werror output 2021-11-20 11:21:46 +01:00
Rangi
cedfd2582a Move more statements into for loop clauses 2021-11-19 22:55:20 -05:00
Rangi
c7322258fc Refactor readGfxConstant for consistency, and edit warning message 2021-11-19 21:36:56 -05:00
Rangi
036b6c1b89 Capitalize "FATAL:" in rgblink error messages
"warning:" and "error:" are lowercase
This matches rgbasm's formatting
2021-11-19 19:52:57 -05:00
Rangi
8e2a164a32 Implement compound assignment operators for mutable constants
Fixes #943
2021-11-19 08:50:00 +01:00
Rangi
b76819792d Deprecate SET in favor of =
`SET` is redundant with `=`, and is already the name of an instruction.
2021-11-19 00:05:49 +01:00
Rangi
3e945679ad Standardize on "east const" (type const * not const type *)
Avoid "WARNING: Move const after static - use 'static const char'"
2021-11-18 09:12:00 +01:00
Rangi
efccf6c931 A few stylistic tweaks
- `goto free_romx` -> the more typical `goto cleanup`
- `goto fail` -> the more typical `goto finish`
- Remove a redundant `todo` variable
2021-11-17 23:51:40 -05:00
Rangi
e5552e27f2 strrstr can take and return char const * 2021-11-17 22:47:05 -05:00
Rangi
438963fb24 Remove unused #include "extern/utf8decoder.h" (#940)
Fixes #937
2021-11-12 23:37:19 +01:00
Rangi
0bb815edc0 Implement -Wnumeric-string[=0|1|2] (#935)
Fixes #934
2021-11-12 23:09:35 +01:00
Rangi
55a02981b5 Small updates to documentation wording (#939)
Fixes #936
2021-11-12 23:06:02 +01:00
ISSOtm
b06e3b239d Clean up -MT/-MQ code
Remove unreachable argument presence check (handled by `getopt`)
Merge allocation paths into a single `realloc` call
Avoid searching for string lengths multiple times
Tiny (compatible) change: no space between last dependent and colon if
`-MT` or `-MQ` is specified
2021-11-06 13:21:39 +01:00
Rangi
47442941b6 Support ! operator for condition codes (#720)
Fixes #719
2021-11-02 00:16:52 +01:00
DaKnig
b16d2d0695 Add Bash completion script for rgbasm (#895)
* Add Bash completion script for RGBASM

Should have large feature parity with the Zsh completion

Co-authored-by: DaKnig <37626476+DaKnig@users.noreply.github.com>

* Register RGBASM Bash completion in `checkdiff`

Co-authored-by: ISSOtm <eldredhabert0@gmail.com>
2021-10-31 23:44:01 +01:00
Eievui
8b1cc72f09 Added scramble flags to RGBLINK. (#921)
* Add scramble flags to RGBLINK

-S and -W will scramble ROMX and WRAMX respectively.

* Modify scramble CLI

CLI now takes a list of comma-separated values.
Added arg_error to clean up messages.

Co-authored-by: Eldred Habert <eldredhabert0@gmail.com>

* Document scrambling functionality

Co-authored-by: Eldred Habert <eldredhabert0@gmail.com>
2021-10-31 22:58:26 +01:00
Rangi
11a6a81169 Implement -Wtruncation=level (#931)
* Implement -Wtruncation=level

-Wtruncation=0 is the same as the current -Wno-truncation.
-Wtruncation=2 is the same as the current -Wtruncation.
-Wtruncation=1 is the new default; it's less strict, allowing
N-bit values to be between -2**N and 2**N (exclusive).

* Implement generic "parametrized warning" system

* Test more `Wtruncation` variants

Co-authored-by: ISSOtm <eldredhabert0@gmail.com>
2021-10-31 17:47:31 -04:00
ISSOtm
0ce66009c1 Parallelize CMake builds 2021-10-31 22:01:35 +01:00
ISSOtm
33f2f555a6 Enable sanitizers with Clang on Ubuntu as well 2021-10-31 21:41:58 +01:00
ISSOtm
0487f9f841 Enable more checks in CMake builds on macOS 2021-10-31 21:17:51 +01:00
ISSOtm
0d6bfb84ce Ignore unknown warning options
GCC and Clang do not understand the exact same warning option sets
2021-10-31 21:17:51 +01:00
ISSOtm
1e4ace8974 Update tested subproject commits 2021-10-31 20:50:26 +01:00
Eldred Habert
a378d1e8cb Reword label definition docs (#887)
* Reword label definition docs

A bunch of short sentences isn't very readable, this should be better

* Use correct wording for "computing difference"

Co-authored-by: Rangi <35663410+Rangi42@users.noreply.github.com>

* Explain how to define a label before mentioning local ones

* Move double-colon paragraph to make the explanation flow better

* Clarify what a label's value is

Co-authored-by: Rangi <35663410+Rangi42@users.noreply.github.com>
2021-10-31 20:20:57 +01:00
ISSOtm
81ea39effe Add two forgotten warnings to RGBASM Zsh completion 2021-10-31 20:16:03 +01:00
ISSOtm
1a07391a97 Introduce ARRAY_SIZE macro
Checked by `checkpatch`, and you know what? Not a bad thing
See https://github.com/gbdev/rgbds/pull/931#discussion_r738856724
2021-10-31 07:53:33 +01:00
Rangi
b002d95459 Fix precison of fixed-point formatting
Fixes #908
2021-10-28 23:29:16 +02:00
ISSOtm
646fc62b89 Avoid running duplicate CI on macOS
`gcc` is just an alias to `clang` on macOS, so save on CI time.
2021-10-26 01:39:05 +02:00
ISSOtm
699c00dc20 Enable MacOS Big Sur in CI 2021-10-20 23:11:19 +02:00
Eldred Habert
9b9f3ffb96 Remove Ubuntu 16.04 from CI
It's been deprecated, see https://github.com/actions/virtual-environments/issues/3287
2021-10-05 22:24:24 +02:00
Eldred Habert
00a67c3fb2 Clarify xref to fmt spec
Co-authored-by: Rangi <35663410+Rangi42@users.noreply.github.com>
2021-09-09 15:08:52 +02:00
ISSOtm
50d6403c72 Move section interpolation to its own section
Fixes #907
2021-09-09 15:08:52 +02:00
Eldred Habert
9111157b82 Add NieDzejkob and JL2210 to contributor list
Significant contributions all around are worth acknowledging.
2021-09-09 14:53:34 +02:00
Eldred Habert
794dd6cd7e Mark Rangi and I as main contributors
I think we've earned it, at least for being maintainers for a couple years.
I believe the work done by those before us is more important (relicensing & cleanup),
but we've both made significant changes on top of what they made possible.
2021-09-09 14:37:23 +02:00
ISSOtm
ae4352c198 Fix typos in rgbasm(5) 2021-08-23 18:39:19 +02:00
Rangi
4a73eb56ea Make peek() tail recursive instead of using goto
Compilation is identical with `gcc` or `clang`, -O3` or `-O2`
2021-08-18 01:30:47 +02:00
martendo
0f321bc797 Fix section merge alignment error message (#919)
Co-authored-by: Eldred Habert <eldredhabert0@gmail.com>
2021-08-17 18:03:46 -04:00
ISSOtm
94d07c78d8 Fix MBC3+TIMER + handle lack of BATTERY
`TIMER & BATTERY` is 0, but even `|` would only be checking if
*either* is set; instead, imply BATTERY as soon as TIMER is given,
printing a warning if it was not given.
2021-08-12 10:20:09 +02:00
Rangi
b51e1c7c2c Compare FOR ranges to Python's range 2021-08-01 10:45:44 +02:00
Rangi
26ddf1ff4d Prevent defining invalid local labels
Fixes #913
2021-07-30 15:21:47 +02:00
ISSOtm
20fd6eabbb Fix up gitignores
Move tests gitignore into its own directory
Use stricter patterns where they make sense
2021-07-25 12:15:56 +02:00
ISSOtm
fbe29006d4 Document two two .github subdirectories 2021-07-25 12:12:36 +02:00
Rangi
03bb510588 endCapture shouldn't handle lexerState->atLineStart
`startCapture` did not initialize `lexerState->atLineStart`;
its final value is a consequence of the separate but similar
behaviors within `lexer_CaptureRept` and `lexer_CaptureMacroBody`.
2021-07-04 18:31:46 -04:00
Rangi
695dfe9dbd Add missing file line-continuation-string.asm
Also make some minor formatting corrections
2021-07-04 16:12:34 -04:00
Rangi
9782f7d942 Factor out endCapture to go with startCapture (#904)
This also refactors `startCapture` to modify the
capture body as an argument.
2021-07-04 16:08:59 -04:00
Rangi
1b5648bb06 Line continuations *do* work inside strings
The rgbasm.5(5) documentation was outdated here
2021-07-01 17:56:49 -04:00
Rangi
a67f5d6e01 SIZEOF("Section") and STARTOF("Section") can be known
Fixes #890
2021-06-27 21:03:06 +02:00
Rangi
06b57aa1ce Avoid unnecessary "overwriting a non-zero byte" warnings
- Don't warn if the non-zero byte being written is the same as the original byte
- Add a `-O` / `--overwrite` flag to silence all such warnings

Fixes #897
2021-06-27 19:16:11 +02:00
Rangi
6d2db2ef64 make checkdiff does CI documentation checks (#900)
Fixes #744
2021-06-24 17:49:08 -04:00
Eldred Habert
9868a01163 Format -m help better in the man page
Use semantic macros instead of plain text
2021-05-30 13:59:02 +02:00
Eldred Habert
0c8cdd92d6 Make instruction descriptions more proper English
Use articles where appropriate
Use adjectives where it makes sense
2021-05-24 22:07:36 +02:00
Rangi
80a376f045 Syntax errors resets the lexer right away
`DEF`, `REDEF`, etc disable EQUS expansion, and reading
macro or OPT arguments sets the lexer to raw mode.
Syntax errors resume normal parsing at the line's end,
but should resume normal tokenization even before that.
2021-05-22 16:08:55 -04:00
Rangi
0068c1375c Syntax error message hints to indent macro invocations
This message is only printed for identifiers parsed as
`T_LABEL` (since they're at the start of a line) but
already defined as macros. Otherwise it may not be
relevant, e.g. for `MyLabel;:` or `My Label::`.
2021-05-22 14:58:26 +02:00
ISSOtm
872af9c7ed Remove dead store in linker script CRLF handling 2021-05-21 17:20:47 -04:00
ISSOtm
06ea7b20bf Reinstate "empty filename" assertion in __FILE__ callback
This assertion was mentioned by a comment, but deleted for some reason.
2021-05-21 17:20:47 -04:00
ISSOtm
a3c4652bfd Fix dead stores in charmap_ConvertNext
Also cleanup / rearrange some of the function while we're at it.
2021-05-21 17:20:47 -04:00
Rangi
5ad48851ed Allow error messages for subsequent syntax errors (#892)
Fixes #891
2021-05-21 17:04:27 -04:00
ISSOtm
e3b7339dd6 Save UNION stack when using PUSHS as well
This allows using the latter within the former
2021-05-21 09:47:27 +02:00
ISSOtm
69d7f84502 Reset LOAD offset when changing SECTIONs
This would cause spurious section overflow messages, since the load offset
is added to the section's when computing its size.
2021-05-21 09:37:17 +02:00
ISSOtm
e970b6d6eb Update Zsh completions with CLI changes
Also fix some misc. issues with them, and fix an incorrect reported ID for MBC1
2021-05-15 19:21:39 +02:00
ISSOtm
d9cce3fa1f Update TPP1 canonical name to "TIMER", not "RTC"
See previous commit
2021-05-15 19:13:24 +02:00
Rangi
23721694ea Comment that anonymous labels internally start with '!'
`startsIdentifier` should not accept this character so
anonymous labels won't conflict with nonymous ones.
2021-05-15 12:57:22 -04:00
ISSOtm
aa02958e18 Fix mandoc warnings 2021-05-15 12:23:05 +02:00
ISSOtm
011d4ec392 Use the Ad macro for RST addresses
Sy has no semantic meaning, and Ad may fix how the "x" is rendered
in the HTML export
2021-05-15 12:03:20 +02:00
ISSOtm
afbaf10185 Fix MBC help string
It's called TIMER, not RTC >_<
2021-05-15 11:25:09 +02:00
Rangi
6a5e2f439e Fix the STRFMT documentation in rgbasm(5)
Fixes #886
2021-05-09 17:23:57 -04:00
Rangi
fba77c4dce Specify to update the release branch in RELEASE.rst
This is done manually after publishing a release.
2021-05-08 23:44:53 -04:00
258 changed files with 3488 additions and 1633 deletions

17
.github/workflows/checkdiff.yml vendored Normal file
View File

@@ -0,0 +1,17 @@
name: "Code coverage checking"
on: pull_request
jobs:
checkdiff:
runs-on: ubuntu-latest
steps:
- name: Set up repo
run: |
git clone -b "${{ github.event.pull_request.head.ref }}" "${{ github.event.pull_request.head.repo.clone_url }}" rgbds
cd rgbds
git remote add upstream "${{ github.event.pull_request.base.repo.clone_url }}"
git fetch upstream
- name: Checkdiff
working-directory: rgbds
run: |
make checkdiff "BASE_REF=${{ github.event.pull_request.base.sha }}" Q= | tee log

View File

@@ -7,16 +7,26 @@ jobs:
unix-testing: unix-testing:
strategy: strategy:
matrix: matrix:
os: [ubuntu-20.04, ubuntu-18.04, ubuntu-16.04, macos-10.15] os: [ubuntu-20.04, ubuntu-18.04, macos-11.0, macos-10.15]
cc: [gcc, clang] cc: [gcc, clang]
buildsys: [make, cmake] buildsys: [make, cmake]
exclude:
# `gcc` is just an alias to `clang` on macOS, don't bother
- os: macos-10.15
cc: gcc
- os: macos-11.0
cc: gcc
include: include:
- os: ubuntu-18.04 - os: ubuntu-18.04
cc: gcc
target: develop target: develop
cmakevars: -DSANITIZERS=ON -DMORE_WARNINGS=ON -DCMAKE_BUILD_TYPE=Debug cmakevars: -DSANITIZERS=ON -DMORE_WARNINGS=ON -DCMAKE_BUILD_TYPE=Debug
- os: ubuntu-20.04 - os: ubuntu-20.04
cc: gcc target: develop
cmakevars: -DSANITIZERS=ON -DMORE_WARNINGS=ON -DCMAKE_BUILD_TYPE=Debug
- os: macos-11.0
target: develop
cmakevars: -DSANITIZERS=ON -DMORE_WARNINGS=ON -DCMAKE_BUILD_TYPE=Debug
- os: macos-10.15
target: develop target: develop
cmakevars: -DSANITIZERS=ON -DMORE_WARNINGS=ON -DCMAKE_BUILD_TYPE=Debug cmakevars: -DSANITIZERS=ON -DMORE_WARNINGS=ON -DCMAKE_BUILD_TYPE=Debug
fail-fast: false fail-fast: false
@@ -40,7 +50,7 @@ jobs:
run: | run: |
export PATH="/usr/local/opt/bison/bin:$PATH" export PATH="/usr/local/opt/bison/bin:$PATH"
cmake -S . -B build -DCMAKE_VERBOSE_MAKEFILE=ON -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=${{ matrix.cc }} ${{ matrix.cmakevars }} cmake -S . -B build -DCMAKE_VERBOSE_MAKEFILE=ON -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=${{ matrix.cc }} ${{ matrix.cmakevars }}
cmake --build build cmake --build build -j
cp build/src/rgb{asm,link,fix,gfx} . cp build/src/rgb{asm,link,fix,gfx} .
sudo cmake --install build sudo cmake --install build
if: matrix.buildsys == 'cmake' if: matrix.buildsys == 'cmake'
@@ -101,17 +111,17 @@ jobs:
- name: Build zlib - name: Build zlib
run: | # BUILD_SHARED_LIBS causes the output DLL to be correctly called `zlib1.dll` run: | # BUILD_SHARED_LIBS causes the output DLL to be correctly called `zlib1.dll`
cmake -S zlib -B zbuild -A ${{ matrix.platform }} -DCMAKE_INSTALL_PREFIX=install_dir -DBUILD_SHARED_LIBS=ON cmake -S zlib -B zbuild -A ${{ matrix.platform }} -DCMAKE_INSTALL_PREFIX=install_dir -DBUILD_SHARED_LIBS=ON
cmake --build zbuild --config Release cmake --build zbuild --config Release -j
cmake --install zbuild cmake --install zbuild
- name: Build libpng - name: Build libpng
run: | run: |
cmake -S libpng -B pngbuild -A ${{ matrix.platform }} -DCMAKE_INSTALL_PREFIX=install_dir -DPNG_SHARED=ON -DPNG_STATIC=ON -DPNG_TESTS=OFF cmake -S libpng -B pngbuild -A ${{ matrix.platform }} -DCMAKE_INSTALL_PREFIX=install_dir -DPNG_SHARED=ON -DPNG_STATIC=ON -DPNG_TESTS=OFF
cmake --build pngbuild --config Release cmake --build pngbuild --config Release -j
cmake --install pngbuild cmake --install pngbuild
- name: Build Windows binaries - name: Build Windows binaries
run: | run: |
cmake -S . -B build -A ${{ matrix.platform }} -DCMAKE_INSTALL_PREFIX=install_dir -DCMAKE_BUILD_TYPE=Release cmake -S . -B build -A ${{ matrix.platform }} -DCMAKE_INSTALL_PREFIX=install_dir -DCMAKE_BUILD_TYPE=Release
cmake --build build --config Release cmake --build build --config Release -j
cmake --install build cmake --install build
- name: Package binaries - name: Package binaries
shell: bash shell: bash

16
.gitignore vendored
View File

@@ -1,16 +1,12 @@
rgbasm /rgbasm
rgblink /rgblink
rgbfix /rgbfix
rgbgfx /rgbgfx
rgbshim.sh /rgbshim.sh
*.o *.o
*.exe *.exe
*.dll *.dll
.checkpatch-camelcase.* .checkpatch-camelcase.*
CMakeCache.txt CMakeCache.txt
CMakeFiles CMakeFiles/
cmake_install.cmake cmake_install.cmake
test/pokecrystal
test/pokered
test/ucity

View File

@@ -25,8 +25,6 @@ endif()
option(SANITIZERS "Build with sanitizers enabled" OFF) # Ignored on MSVC option(SANITIZERS "Build with sanitizers enabled" OFF) # Ignored on MSVC
option(MORE_WARNINGS "Turn on more warnings" OFF) # Ignored on MSVC option(MORE_WARNINGS "Turn on more warnings" OFF) # Ignored on MSVC
option(TRACE_PARSER "Trace parser execution" OFF)
option(TRACE_LEXER "Trace lexer execution" OFF)
if(MSVC) if(MSVC)
# MSVC's standard library triggers warning C5105, # MSVC's standard library triggers warning C5105,
@@ -54,7 +52,8 @@ else()
-Wstringop-overflow=4 -Walloc-zero -Wduplicated-cond -Wstringop-overflow=4 -Walloc-zero -Wduplicated-cond
-Wfloat-equal -Wshadow -Wcast-qual -Wcast-align -Wlogical-op -Wfloat-equal -Wshadow -Wcast-qual -Wcast-align -Wlogical-op
-Wnested-externs -Wno-aggressive-loop-optimizations -Winline -Wnested-externs -Wno-aggressive-loop-optimizations -Winline
-Wundef -Wstrict-prototypes -Wold-style-definition) -Wundef -Wstrict-prototypes -Wold-style-definition
-Wno-unknown-warning-option -Wno-tautological-constant-out-of-range-compare)
endif() endif()
endif() endif()
@@ -88,11 +87,3 @@ if(CMAKE_BUILD_TYPE STREQUAL "Release")
message(CHECK_FAIL "no") message(CHECK_FAIL "no")
endif() endif()
endif() endif()
if(TRACE_PARSER)
target_compile_definitions(rgbasm PRIVATE -DYYDEBUG)
endif()
if(TRACE_LEXER)
target_compile_definitions(rgbasm PRIVATE -DLEXER_DEBUG)
endif()

View File

@@ -19,6 +19,10 @@ Main contributors
- Antonio Niño Díaz <antonio_nd@outlook.com> - Antonio Niño Díaz <antonio_nd@outlook.com>
- Eldred "ISSOtm" Habert <eldredhabert0@gmail.com>
- Rangi <http://github.com/Rangi42>
Other contributors Other contributors
------------------ ------------------
@@ -30,7 +34,9 @@ Other contributors
- David Brotz <dbrotz007@gmail.com> - David Brotz <dbrotz007@gmail.com>
- Eldred Habert <eldredhabert0@gmail.com> - Jakub Kądziołka <kuba@kadziolka.net>
- James "JL2210" Larrowe <https://github.com/JL2210>
- The Musl C library <http://www.musl-libc.org> - The Musl C library <http://www.musl-libc.org>
@@ -40,8 +46,6 @@ Other contributors
- Quint Guvernator <quint@guvernator.net> - Quint Guvernator <quint@guvernator.net>
- Rangi <http://github.com/Rangi42>
- Sanqui <gsanky@gmail.com> - Sanqui <gsanky@gmail.com>
- YamaArashi <shadow962@live.com> - YamaArashi <shadow962@live.com>

View File

@@ -49,6 +49,9 @@ YFLAGS ?= -Wall
BISON := bison BISON := bison
RM := rm -rf RM := rm -rf
# Used for checking pull requests
BASE_REF := origin/master
# Rules to build the RGBDS binaries # Rules to build the RGBDS binaries
all: rgbasm rgblink rgbfix rgbgfx all: rgbasm rgblink rgbfix rgbgfx
@@ -69,9 +72,9 @@ rgbasm_obj := \
src/asm/symbol.o \ src/asm/symbol.o \
src/asm/util.o \ src/asm/util.o \
src/asm/warning.o \ src/asm/warning.o \
src/extern/err.o \
src/extern/getopt.o \ src/extern/getopt.o \
src/extern/utf8decoder.o \ src/extern/utf8decoder.o \
src/error.o \
src/hashmap.o \ src/hashmap.o \
src/linkdefs.o \ src/linkdefs.o \
src/opmath.o src/opmath.o
@@ -87,23 +90,23 @@ rgblink_obj := \
src/link/script.o \ src/link/script.o \
src/link/section.o \ src/link/section.o \
src/link/symbol.o \ src/link/symbol.o \
src/extern/err.o \
src/extern/getopt.o \ src/extern/getopt.o \
src/error.o \
src/hashmap.o \ src/hashmap.o \
src/linkdefs.o \ src/linkdefs.o \
src/opmath.o src/opmath.o
rgbfix_obj := \ rgbfix_obj := \
src/fix/main.o \ src/fix/main.o \
src/extern/err.o \ src/extern/getopt.o \
src/extern/getopt.o src/error.o
rgbgfx_obj := \ rgbgfx_obj := \
src/gfx/gb.o \ src/gfx/gb.o \
src/gfx/main.o \ src/gfx/main.o \
src/gfx/makepng.o \ src/gfx/makepng.o \
src/extern/err.o \ src/extern/getopt.o \
src/extern/getopt.o src/error.o
rgbasm: ${rgbasm_obj} rgbasm: ${rgbasm_obj}
$Q${CC} ${REALLDFLAGS} -o $@ ${rgbasm_obj} ${REALCFLAGS} src/version.c -lm $Q${CC} ${REALLDFLAGS} -o $@ ${rgbasm_obj} ${REALCFLAGS} src/version.c -lm
@@ -189,9 +192,8 @@ checkcodebase:
# the first common commit between the HEAD and origin/master. # the first common commit between the HEAD and origin/master.
# `.y` files aren't checked, unfortunately... # `.y` files aren't checked, unfortunately...
BASE_REF:= origin/master
checkpatch: checkpatch:
$Qeval COMMON_COMMIT=$$(git merge-base HEAD ${BASE_REF}); \ $QCOMMON_COMMIT=`git merge-base HEAD ${BASE_REF}`; \
for commit in `git rev-list $$COMMON_COMMIT..HEAD`; do \ for commit in `git rev-list $$COMMON_COMMIT..HEAD`; do \
echo "[*] Analyzing commit '$$commit'"; \ echo "[*] Analyzing commit '$$commit'"; \
git format-patch --stdout "$$commit~..$$commit" \ git format-patch --stdout "$$commit~..$$commit" \
@@ -199,6 +201,11 @@ checkpatch:
| ${CHECKPATCH} - || true; \ | ${CHECKPATCH} - || true; \
done done
# Target used to check for suspiciously missing changed files.
checkdiff:
$Qcontrib/checkdiff.bash `git merge-base HEAD ${BASE_REF}`
# This target is used during development in order to prevent adding new issues # This target is used during development in order to prevent adding new issues
# to the source code. All warnings are treated as errors in order to block the # to the source code. All warnings are treated as errors in order to block the
# compilation and make the continous integration infrastructure return failure. # compilation and make the continous integration infrastructure return failure.
@@ -212,6 +219,7 @@ develop:
-Wfloat-equal -Wshadow -Wcast-qual -Wcast-align -Wlogical-op \ -Wfloat-equal -Wshadow -Wcast-qual -Wcast-align -Wlogical-op \
-Wnested-externs -Wno-aggressive-loop-optimizations -Winline \ -Wnested-externs -Wno-aggressive-loop-optimizations -Winline \
-Wundef -Wstrict-prototypes -Wold-style-definition \ -Wundef -Wstrict-prototypes -Wold-style-definition \
-Wno-unknown-warning-option -Wno-tautological-constant-out-of-range-compare \
-fsanitize=shift -fsanitize=integer-divide-by-zero \ -fsanitize=shift -fsanitize=integer-divide-by-zero \
-fsanitize=unreachable -fsanitize=vla-bound \ -fsanitize=unreachable -fsanitize=vla-bound \
-fsanitize=signed-integer-overflow -fsanitize=bounds \ -fsanitize=signed-integer-overflow -fsanitize=bounds \

View File

@@ -47,31 +47,31 @@ The RGBDS source code file structure somewhat resembles the following:
. .
├── .github/ ├── .github/
   ├── actions/ ├── actions/
   │   └── ... └── ...
   └── workflows/ └── workflows/
      └── ... └── ...
├── contrib/ ├── contrib/
   ├── zsh_compl/ ├── zsh_compl/
   │   └── ... └── ...
   └── ... └── ...
├── include/ ├── include/
   └── ... └── ...
├── src/ ├── src/
   ├── asm/ ├── asm/
   │   └── ... └── ...
   ├── extern/ ├── extern/
   │   └── ... └── ...
   ├── fix/ ├── fix/
   │   └── ... └── ...
   ├── gfx/ ├── gfx/
   │   └── ... └── ...
   ├── link/ ├── link/
   │   └── ... └── ...
   ├── CMakeLists.txt ├── CMakeLists.txt
   └── ... └── ...
├── test/ ├── test/
   ├── ... ├── ...
│ └── run-tests.sh │ └── run-tests.sh
├── CMakeLists.txt ├── CMakeLists.txt
├── Makefile ├── Makefile
@@ -80,11 +80,16 @@ The RGBDS source code file structure somewhat resembles the following:
- ``.github/`` - files and scripts related to the integration of the RGBDS codebase with - ``.github/`` - files and scripts related to the integration of the RGBDS codebase with
GitHub. GitHub.
* ``actions/`` - scripts used by workflow files.
* ``workflows/`` - CI workflow description files.
- ``contrib/`` - scripts and other resources which may be useful to users and developers of - ``contrib/`` - scripts and other resources which may be useful to users and developers of
RGBDS. RGBDS.
* ``zsh_compl`` contains tab completion scripts for use with zsh. Put them somewhere in your ``fpath``, and they should auto-load. * ``zsh_compl`` contains tab completion scripts for use with zsh. Put them somewhere in your ``fpath``, and they should auto-load.
* ``bash_compl`` contains tab completion scripts for use with bash. Run them with ``source`` somewhere in your ``.bashrc``, and they should load every time you open a shell.
- ``include/`` - header files for each respective C files in `src`. - ``include/`` - header files for each respective C files in `src`.
- ``src/`` - source code and manual pages for RGBDS. - ``src/`` - source code and manual pages for RGBDS.

View File

@@ -63,3 +63,5 @@ GitHub.
5. Write a changelog in the GitHub draft release. 5. Write a changelog in the GitHub draft release.
6. Click the "Publish release" button to publish it! 6. Click the "Publish release" button to publish it!
7. Update the `release` branch. You can use ``git push origin release``.

215
contrib/bash_compl/_rgbasm.bash Executable file
View File

@@ -0,0 +1,215 @@
#/usr/bin/env bash
# Known bugs:
# - Newlines in file/directory names break this script
# This is because we rely on `compgen -A`, which is broken like this.
# A fix would require implementing it ourselves, and no thanks!
# - `rgbasm --binary-digits=a` is treated the same as `rgbasm --binary-digits=` (for example)
# This is not our fault, Bash passes both of these identically.
# Maybe it could be worked around, but such a fix would likely be involved.
# The user can work around it by typing `--binary-digits ''` instead, for example.
# - Directories are not completed as such in "coalesced" short-opt arguments. For example,
# `rgbasm -M d<tab>` can autocomplete to `rgbasm -M dir/` (no space), but
# `rgbasm -Md<tab>` would autocomplete to `rgbasm -Mdir ` (trailing space) instead.
# This is because dircetory handling is performed by Readline, whom we can't tell about the short
# opt kerfuffle. The user can work around by separating the argument, as shown above.
# (Also, there might be more possible bugs if `-Mdir` is actually a directory. Ugh.)
# Something to note:
# `rgbasm --binary-digits=a` gets passed to us as ('rgbasm' '--binary-digits' '=' 'a')
# Thus, we don't need to do much to handle that form of argument passing: skip '=' after long opts.
_rgbasm_completions() {
COMPREPLY=()
# Format: "long_opt:state_after"
# Empty long opt = it doesn't exit
# See the `state` variable below for info about `state_after`
declare -A opts=(
[V]="version:normal"
[E]="export-all:normal"
[h]="halt-without-nop:normal"
[L]="preserve-ld:normal"
[v]="verbose:normal"
[w]=":normal"
[b]="binary-digits:unk"
[D]="define:unk"
[g]="gfx-chars:unk"
[i]="include:dir"
[M]="dependfile:glob-*.mk *.d"
[o]="output:glob-*.o"
[p]="pad-value:unk"
[r]="recursion-depth:unk"
[W]="warning:warning"
)
# Parse command-line up to current word
local opt_ena=true
# Possible states:
# - normal = Well, normal. Options are parsed normally.
# - unk = An argument that can't be completed, and should just be skipped.
# - warning = A warning flag.
# - dir = A directory path
# - glob-* = A glob, after the dash is a whitespace-separated list of file globs to use
local state=normal
# The length of the option, used as a return value by the function below
local optlen=0
# $1: a short option word
# `state` will be set to the parsing state after the last option character in the word. If
# "normal" is not returned, `optlen` will be set to the length (dash included) of the "option"
# part of the argument.
parse_short_opt() {
for (( i = 1; i < "${#1}"; i++ )); do
# If the option is not known, assume it doesn't take an argument
local opt="${opts["${1:$i:1}"]:-":normal"}"
state="${opt#*:}"
# If the option takes an argument, record the length and exit
if [[ "$state" != 'normal' ]]; then
let optlen="$i + 1"
return
fi
done
optlen=0
}
for (( i = 1; i < $COMP_CWORD; i++ )); do
local word="${COMP_WORDS[$i]}"
# If currently processing an argument, skip this word
if [[ "$state" != 'normal' ]]; then
state=normal
continue
fi
if [[ "$word" = '--' ]]; then
# Options stop being parsed after this
opt_ena=false
break
fi
# Check if it's a long option
if [[ "${word:0:2}" = '--' ]]; then
# If the option is unknown, assume it takes no arguments: keep the state at "normal"
for long_opt in "${opts[@]}"; do
if [[ "$word" = "--${long_opt%%:*}" ]]; then
state="${long_opt#*:}"
# Check if the next word is just '='; if so, skip it, the argument must follow
# (See "known bugs" at the top of this script)
let i++
if [[ "${COMP_WORDS[$i]}" != '=' ]]; then
let i--
fi
optlen=0
break
fi
done
# Check if it's a short option
elif [[ "${word:0:1}" = '-' ]]; then
# The `-M?` ones are a mix of short and long, augh
# They must match the *full* word, but only take a single dash
# So, handle them here
if [[ "$1" = "-M"[GP] ]]; then
state=normal
elif [[ "$1" = "-M"[TQ] ]]; then
state='glob-*.d *.mk *.o'
else
parse_short_opt "$word"
# The last option takes an argument...
if [[ "$state" != 'normal' ]]; then
if [[ "$optlen" -ne "${#word}" ]]; then
# If it's contained within the word, we won't complete it, revert to "normal"
state=normal
else
# Otherwise, complete it, but start at the beginning of *that* word
optlen=0
fi
fi
fi
fi
done
# Parse current word
# Careful that it might look like an option, so use `--` aggressively!
local cur_word="${COMP_WORDS[$COMP_CWORD]}"
# Process options, as short ones may change the state
if $opt_ena && [[ "$state" = 'normal' && "${cur_word:0:1}" = '-' ]]; then
# We might want to complete to an option or an arg to that option
# Parse the option word to check
# There's no whitespace in the option names, so we can ride a little dirty...
# Is this a long option?
if [[ "${cur_word:1:1}" = '-' ]]; then
# It is, try to complete one
COMPREPLY+=( $(compgen -W "${opts[*]%%:*}" -P '--' -- "${cur_word#--}") )
return 0
else
# Short options may be grouped, parse them to determine what to complete
# The `-M?` ones may not be followed by anything
if [[ "$1" != "-M"[GPTQ] ]]; then
parse_short_opt "$cur_word"
# We got some short options that behave like long ones
COMPREPLY+=( $(compgen -W '-MG -MP -MT -MQ' -- "$cur_word") )
if [[ "$state" = 'normal' ]]; then
COMPREPLY+=( $(compgen -W "${!opts[*]}" -P "$cur_word" '') )
return 0
elif [[ "$optlen" = "${#cur_word}" && "$state" != "warning" ]]; then
# This short option group only awaits its argument!
# Post the option group as-is as a reply so that Readline inserts a space,
# so that the next completion request switches to the argument
# An exception is made for warnings, since it's idiomatic to stick them to the
# `-W`, and it doesn't break anything.
COMPREPLY+=( "$cur_word" )
return 0
fi
fi
fi
fi
case "$state" in
unk) # Return with no replies: no idea what to complete!
;;
warning)
COMPREPLY+=( $(compgen -W "
assert
backwards-for
builtin-args
charmap-redef
div
empty-data-directive
empty-macro-arg
empty-strrpl
large-constant
long-string
macro-shift
nested-comment
numeric-string
obsolete
shift
shift-amount
truncation
user
all
extra
everything
error" -P "${cur_word:0:$optlen}" -- "${cur_word:$optlen}") )
;;
normal) # Acts like a glob...
state="glob-*.asm *.inc *.sm83"
;&
glob-*)
while read -r word; do
COMPREPLY+=("${cur_word:0:$optlen}$word")
done < <(for glob in ${state#glob-}; do compgen -A file -X \!"$glob" -- "${cur_word:$optlen}"; done)
# Also complete directories
;&
dir)
while read -r word; do
COMPREPLY+=("${cur_word:0:$optlen}$word")
done < <(compgen -A directory -- "${cur_word:$optlen}")
compopt -o filenames
;;
esac
}
complete -F _rgbasm_completions rgbasm

181
contrib/bash_compl/_rgbfix.bash Executable file
View File

@@ -0,0 +1,181 @@
#/usr/bin/env bash
# Same notes as RGBASM
_rgbfix_completions() {
COMPREPLY=()
# Format: "long_opt:state_after"
# Empty long opt = it doesn't exit
# See the `state` variable below for info about `state_after`
declare -A opts=(
[V]="version:normal"
[j]="non-japanese:normal"
[s]="sgb-compatible:normal"
[v]="validate:normal"
[C]="color-only:normal"
[c]="color-compatible:normal"
[f]="fix-spec:fix-spec"
[i]="game-id:unk"
[k]="new-licensee:unk"
[l]="old-licensee:unk"
[m]="mbc-type:mbc"
[n]="rom-version:unk"
[p]="pad-value:unk"
[r]="ram-size:unk"
[t]="title:unk"
)
# Parse command-line up to current word
local opt_ena=true
# Possible states:
# - normal = Well, normal. Options are parsed normally.
# - unk = An argument that can't be completed, and should just be skipped.
# - warning = A warning flag.
# - dir = A directory path
# - glob-* = A glob, after the dash is a whitespace-separated list of file globs to use
local state=normal
# The length of the option, used as a return value by the function below
local optlen=0
# $1: a short option word
# `state` will be set to the parsing state after the last option character in the word. If
# "normal" is not returned, `optlen` will be set to the length (dash included) of the "option"
# part of the argument.
parse_short_opt() {
for (( i = 1; i < "${#1}"; i++ )); do
# If the option is not known, assume it doesn't take an argument
local opt="${opts["${1:$i:1}"]:-":normal"}"
state="${opt#*:}"
# If the option takes an argument, record the length and exit
if [[ "$state" != 'normal' ]]; then
let optlen="$i + 1"
return
fi
done
optlen=0
}
for (( i = 1; i < $COMP_CWORD; i++ )); do
local word="${COMP_WORDS[$i]}"
# If currently processing an argument, skip this word
if [[ "$state" != 'normal' ]]; then
state=normal
continue
fi
if [[ "$word" = '--' ]]; then
# Options stop being parsed after this
opt_ena=false
break
fi
# Check if it's a long option
if [[ "${word:0:2}" = '--' ]]; then
# If the option is unknown, assume it takes no arguments: keep the state at "normal"
for long_opt in "${opts[@]}"; do
if [[ "$word" = "--${long_opt%%:*}" ]]; then
state="${long_opt#*:}"
# Check if the next word is just '='; if so, skip it, the argument must follow
# (See "known bugs" at the top of this script)
let i++
if [[ "${COMP_WORDS[$i]}" != '=' ]]; then
let i--
fi
optlen=0
break
fi
done
# Check if it's a short option
elif [[ "${word:0:1}" = '-' ]]; then
parse_short_opt "$word"
# The last option takes an argument...
if [[ "$state" != 'normal' ]]; then
if [[ "$optlen" -ne "${#word}" ]]; then
# If it's contained within the word, we won't complete it, revert to "normal"
state=normal
else
# Otherwise, complete it, but start at the beginning of *that* word
optlen=0
fi
fi
fi
done
# Parse current word
# Careful that it might look like an option, so use `--` aggressively!
local cur_word="${COMP_WORDS[$COMP_CWORD]}"
# Process options, as short ones may change the state
if $opt_ena && [[ "$state" = 'normal' && "${cur_word:0:1}" = '-' ]]; then
# We might want to complete to an option or an arg to that option
# Parse the option word to check
# There's no whitespace in the option names, so we can ride a little dirty...
# Is this a long option?
if [[ "${cur_word:1:1}" = '-' ]]; then
# It is, try to complete one
COMPREPLY+=( $(compgen -W "${opts[*]%%:*}" -P '--' -- "${cur_word#--}") )
return 0
else
# Short options may be grouped, parse them to determine what to complete
parse_short_opt "$cur_word"
if [[ "$state" = 'normal' ]]; then
COMPREPLY+=( $(compgen -W "${!opts[*]}" -P "$cur_word" '') )
return 0
elif [[ "$optlen" = "${#cur_word}" && "$state" != "warning" ]]; then
# This short option group only awaits its argument!
# Post the option group as-is as a reply so that Readline inserts a space,
# so that the next completion request switches to the argument
# An exception is made for warnings, since it's idiomatic to stick them to the
# `-W`, and it doesn't break anything.
COMPREPLY+=( "$cur_word" )
return 0
fi
fi
fi
case "$state" in
unk) # Return with no replies: no idea what to complete!
;;
fix-spec)
COMPREPLY+=( "${cur_word}"{l,h,g,L,H,G} )
;;
mbc)
local cur_arg="${cur_word:$optlen}"
cur_arg="${cur_arg@U}"
COMPREPLY=( $(compgen -W "
ROM_ONLY
MBC1{,+RAM,+RAM+BATTERY}
MBC2{,+BATTERY}
MMM01{,+RAM}
MBC3{+TIMER+BATTERY,+TIMER+RAM+BATTERY,,+RAM,+RAM+BATTERY}
MBC5{,+RAM,+RAM+BATTERY,+RUMBLE,+RUMBLE+RAM,+RUMBLE+RAM+BATTERY}
MBC6
MBC7+SENSOR+RUMBLE+RAM+BATTERY
POCKET_CAMERA
BANDAI_TAMA5
HUC3
HUC1+RAM+BATTERY
TPP1_1.0{,+BATTERY}{,+RTC}{,+RUMBLE,+MULTIRUMBLE}" -P "${cur_word:0:$optlen}" -- "`tr 'a-z ' 'A-Z_' <<<"${cur_word/ /_}"`") )
COMPREPLY+=( $(compgen -W "help" -P "${cur_word:0:$optlen}" -- "${cur_word:$optlen}") )
;;
normal) # Acts like a glob...
state="glob-*.gb *.gbc *.sgb"
;&
glob-*)
while read -r word; do
COMPREPLY+=("${cur_word:0:$optlen}$word")
done < <(for glob in ${state#glob-}; do compgen -A file -X \!"$glob" -- "${cur_word:$optlen}"; done)
# Also complete directories
;&
dir)
while read -r word; do
COMPREPLY+=("${cur_word:0:$optlen}$word")
done < <(compgen -A directory -- "${cur_word:$optlen}")
compopt -o filenames
;;
esac
}
complete -F _rgbfix_completions rgbfix

162
contrib/bash_compl/_rgbgfx.bash Executable file
View File

@@ -0,0 +1,162 @@
#/usr/bin/env bash
# Same notes as RGBASM
_rgbgfx_completions() {
COMPREPLY=()
# Format: "long_opt:state_after"
# Empty long opt = it doesn't exit
# See the `state` variable below for info about `state_after`
declare -A opts=(
[V]="version:normal"
[C]="color-curve:normal"
[D]="debug:normal"
[h]="horizontal:normal"
[m]="mirror-tiles:normal"
[u]="unique-tiles:normal"
[v]="verbose:normal"
[f]="fix:normal"
[F]="fix-and-save:normal"
[a]="attr-map:*.attrmap"
[A]="output-attr-map:normal"
[d]="depth:unk"
[o]="output:glob *.2bpp"
[p]="palette:glob *.pal"
[P]="output-palette:normal"
[t]="tilemap:glob *.tilemap"
[T]="output-tilemap:normal"
[x]="trim-end:unk"
)
# Parse command-line up to current word
local opt_ena=true
# Possible states:
# - normal = Well, normal. Options are parsed normally.
# - unk = An argument that can't be completed, and should just be skipped.
# - warning = A warning flag.
# - dir = A directory path
# - glob-* = A glob, after the dash is a whitespace-separated list of file globs to use
local state=normal
# The length of the option, used as a return value by the function below
local optlen=0
# $1: a short option word
# `state` will be set to the parsing state after the last option character in the word. If
# "normal" is not returned, `optlen` will be set to the length (dash included) of the "option"
# part of the argument.
parse_short_opt() {
for (( i = 1; i < "${#1}"; i++ )); do
# If the option is not known, assume it doesn't take an argument
local opt="${opts["${1:$i:1}"]:-":normal"}"
state="${opt#*:}"
# If the option takes an argument, record the length and exit
if [[ "$state" != 'normal' ]]; then
let optlen="$i + 1"
return
fi
done
optlen=0
}
for (( i = 1; i < $COMP_CWORD; i++ )); do
local word="${COMP_WORDS[$i]}"
# If currently processing an argument, skip this word
if [[ "$state" != 'normal' ]]; then
state=normal
continue
fi
if [[ "$word" = '--' ]]; then
# Options stop being parsed after this
opt_ena=false
break
fi
# Check if it's a long option
if [[ "${word:0:2}" = '--' ]]; then
# If the option is unknown, assume it takes no arguments: keep the state at "normal"
for long_opt in "${opts[@]}"; do
if [[ "$word" = "--${long_opt%%:*}" ]]; then
state="${long_opt#*:}"
# Check if the next word is just '='; if so, skip it, the argument must follow
# (See "known bugs" at the top of this script)
let i++
if [[ "${COMP_WORDS[$i]}" != '=' ]]; then
let i--
fi
optlen=0
break
fi
done
# Check if it's a short option
elif [[ "${word:0:1}" = '-' ]]; then
parse_short_opt "$word"
# The last option takes an argument...
if [[ "$state" != 'normal' ]]; then
if [[ "$optlen" -ne "${#word}" ]]; then
# If it's contained within the word, we won't complete it, revert to "normal"
state=normal
else
# Otherwise, complete it, but start at the beginning of *that* word
optlen=0
fi
fi
fi
done
# Parse current word
# Careful that it might look like an option, so use `--` aggressively!
local cur_word="${COMP_WORDS[$COMP_CWORD]}"
# Process options, as short ones may change the state
if $opt_ena && [[ "$state" = 'normal' && "${cur_word:0:1}" = '-' ]]; then
# We might want to complete to an option or an arg to that option
# Parse the option word to check
# There's no whitespace in the option names, so we can ride a little dirty...
# Is this a long option?
if [[ "${cur_word:1:1}" = '-' ]]; then
# It is, try to complete one
COMPREPLY+=( $(compgen -W "${opts[*]%%:*}" -P '--' -- "${cur_word#--}") )
return 0
else
# Short options may be grouped, parse them to determine what to complete
parse_short_opt "$cur_word"
if [[ "$state" = 'normal' ]]; then
COMPREPLY+=( $(compgen -W "${!opts[*]}" -P "$cur_word" '') )
return 0
elif [[ "$optlen" = "${#cur_word}" && "$state" != "warning" ]]; then
# This short option group only awaits its argument!
# Post the option group as-is as a reply so that Readline inserts a space,
# so that the next completion request switches to the argument
# An exception is made for warnings, since it's idiomatic to stick them to the
# `-W`, and it doesn't break anything.
COMPREPLY+=( "$cur_word" )
return 0
fi
fi
fi
case "$state" in
unk) # Return with no replies: no idea what to complete!
;;
normal) # Acts like a glob...
state="glob-*.png"
;&
glob-*)
while read -r word; do
COMPREPLY+=("${cur_word:0:$optlen}$word")
done < <(for glob in ${state#glob-}; do compgen -A file -X \!"$glob" -- "${cur_word:$optlen}"; done)
# Also complete directories
;&
dir)
while read -r word; do
COMPREPLY+=("${cur_word:0:$optlen}$word")
done < <(compgen -A directory -- "${cur_word:$optlen}")
compopt -o filenames
;;
esac
}
complete -F _rgbgfx_completions rgbgfx

157
contrib/bash_compl/_rgblink.bash Executable file
View File

@@ -0,0 +1,157 @@
#/usr/bin/env bash
# Same notes as RGBASM
_rgblink_completions() {
COMPREPLY=()
# Format: "long_opt:state_after"
# Empty long opt = it doesn't exit
# See the `state` variable below for info about `state_after`
declare -A opts=(
[V]="version:normal"
[d]="dmg:normal"
[t]="tiny:normal"
[v]="verbose:normal"
[w]="wramx:normal"
[x]="nopad:normal"
[l]="linkerscript:glob-*"
[m]="map:glob-*.map"
[n]="sym:glob-*.sym"
[O]="overlay:glob-*.gb *.gbc *.sgb"
[o]="output:glob-*.gb *.gbc *.sgb"
[p]="pad:unk"
[s]="smart:unk"
)
# Parse command-line up to current word
local opt_ena=true
# Possible states:
# - normal = Well, normal. Options are parsed normally.
# - unk = An argument that can't be completed, and should just be skipped.
# - warning = A warning flag.
# - dir = A directory path
# - glob-* = A glob, after the dash is a whitespace-separated list of file globs to use
local state=normal
# The length of the option, used as a return value by the function below
local optlen=0
# $1: a short option word
# `state` will be set to the parsing state after the last option character in the word. If
# "normal" is not returned, `optlen` will be set to the length (dash included) of the "option"
# part of the argument.
parse_short_opt() {
for (( i = 1; i < "${#1}"; i++ )); do
# If the option is not known, assume it doesn't take an argument
local opt="${opts["${1:$i:1}"]:-":normal"}"
state="${opt#*:}"
# If the option takes an argument, record the length and exit
if [[ "$state" != 'normal' ]]; then
let optlen="$i + 1"
return
fi
done
optlen=0
}
for (( i = 1; i < $COMP_CWORD; i++ )); do
local word="${COMP_WORDS[$i]}"
# If currently processing an argument, skip this word
if [[ "$state" != 'normal' ]]; then
state=normal
continue
fi
if [[ "$word" = '--' ]]; then
# Options stop being parsed after this
opt_ena=false
break
fi
# Check if it's a long option
if [[ "${word:0:2}" = '--' ]]; then
# If the option is unknown, assume it takes no arguments: keep the state at "normal"
for long_opt in "${opts[@]}"; do
if [[ "$word" = "--${long_opt%%:*}" ]]; then
state="${long_opt#*:}"
# Check if the next word is just '='; if so, skip it, the argument must follow
# (See "known bugs" at the top of this script)
let i++
if [[ "${COMP_WORDS[$i]}" != '=' ]]; then
let i--
fi
optlen=0
break
fi
done
# Check if it's a short option
elif [[ "${word:0:1}" = '-' ]]; then
parse_short_opt "$word"
# The last option takes an argument...
if [[ "$state" != 'normal' ]]; then
if [[ "$optlen" -ne "${#word}" ]]; then
# If it's contained within the word, we won't complete it, revert to "normal"
state=normal
else
# Otherwise, complete it, but start at the beginning of *that* word
optlen=0
fi
fi
fi
done
# Parse current word
# Careful that it might look like an option, so use `--` aggressively!
local cur_word="${COMP_WORDS[$COMP_CWORD]}"
# Process options, as short ones may change the state
if $opt_ena && [[ "$state" = 'normal' && "${cur_word:0:1}" = '-' ]]; then
# We might want to complete to an option or an arg to that option
# Parse the option word to check
# There's no whitespace in the option names, so we can ride a little dirty...
# Is this a long option?
if [[ "${cur_word:1:1}" = '-' ]]; then
# It is, try to complete one
COMPREPLY+=( $(compgen -W "${opts[*]%%:*}" -P '--' -- "${cur_word#--}") )
return 0
else
# Short options may be grouped, parse them to determine what to complete
parse_short_opt "$cur_word"
if [[ "$state" = 'normal' ]]; then
COMPREPLY+=( $(compgen -W "${!opts[*]}" -P "$cur_word" '') )
return 0
elif [[ "$optlen" = "${#cur_word}" && "$state" != "warning" ]]; then
# This short option group only awaits its argument!
# Post the option group as-is as a reply so that Readline inserts a space,
# so that the next completion request switches to the argument
# An exception is made for warnings, since it's idiomatic to stick them to the
# `-W`, and it doesn't break anything.
COMPREPLY+=( "$cur_word" )
return 0
fi
fi
fi
case "$state" in
unk) # Return with no replies: no idea what to complete!
;;
normal) # Acts like a glob...
state="glob-*.o *.obj"
;&
glob-*)
while read -r word; do
COMPREPLY+=("${cur_word:0:$optlen}$word")
done < <(for glob in ${state#glob-}; do compgen -A file -X \!"$glob" -- "${cur_word:$optlen}"; done)
# Also complete directories
;&
dir)
while read -r word; do
COMPREPLY+=("${cur_word:0:$optlen}$word")
done < <(compgen -A directory -- "${cur_word:$optlen}")
compopt -o filenames
;;
esac
}
complete -F _rgblink_completions rgblink

85
contrib/checkdiff.bash Executable file
View File

@@ -0,0 +1,85 @@
#!/bin/bash
# SPDX-License-Identifier: MIT
#
# Copyright (c) 2021 Rangi
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
declare -A FILES
while read -r -d '' file; do
FILES["$file"]="true"
done < <(git diff --name-only -z $1 HEAD)
edited () {
${FILES["$1"]:-"false"}
}
dependency () {
if edited "$1" && ! edited "$2"; then
echo "'$1' was modified, but not '$2'! $3" | xargs
fi
}
# Pull requests that edit the first file without the second may be correct,
# but are suspicious enough to require review.
dependency include/linkdefs.h src/rgbds.5 \
"Was the object file format changed?"
dependency src/asm/parser.y src/asm/rgbasm.5 \
"Was the rgbasm grammar changed?"
dependency include/asm/warning.h src/asm/rgbasm.1 \
"Were the rgbasm warnings changed?"
dependency src/asm/object.c include/linkdefs.h \
"Should the object file revision be bumped?"
dependency src/link/object.c include/linkdefs.h \
"Should the object file revision be bumped?"
dependency Makefile CMakeLists.txt \
"Did the build process change?"
dependency Makefile src/CMakeLists.txt \
"Did the build process change?"
dependency src/asm/main.c src/asm/rgbasm.1 \
"Did the rgbasm CLI change?"
dependency src/asm/main.c contrib/zsh_compl/_rgbasm \
"Did the rgbasm CLI change?"
dependency src/asm/main.c contrib/bash_compl/_rgbasm.bash \
"Did the rgbasm CLI change?"
dependency src/link/main.c src/link/rgblink.1 \
"Did the rgblink CLI change?"
dependency src/link/main.c contrib/zsh_compl/_rgblink \
"Did the rgblink CLI change?"
dependency src/link/main.c contrib/bash_compl/_rgblink.bash \
"Did the rgblink CLI change?"
dependency src/fix/main.c src/fix/rgbfix.1 \
"Did the rgbfix CLI change?"
dependency src/fix/main.c contrib/zsh_compl/_rgbfix \
"Did the rgbfix CLI change?"
dependency src/fix/main.c contrib/bash_compl/_rgbfix.bash \
"Did the rgbfix CLI change?"
dependency src/gfx/main.c src/gfx/rgbgfx.1 \
"Did the rgbgfx CLI change?"
dependency src/gfx/main.c contrib/zsh_compl/_rgbgfx \
"Did the rgbgfx CLI change?"
dependency src/gfx/main.c contrib/bash_compl/_rgbgfx.bash \
"Did the rgbgfx CLI change?"

View File

@@ -9,18 +9,26 @@ _rgbasm_warnings() {
'everything:Enable literally everything' 'everything:Enable literally everything'
'assert:Warn when WARN-type asserts fail' 'assert:Warn when WARN-type asserts fail'
'backwards-for:Warn when start and stop are backwards relative to step'
'builtin-args:Report incorrect args to built-in funcs' 'builtin-args:Report incorrect args to built-in funcs'
'charmap-redef:Warn when redefining a charmap mapping'
'div:Warn when dividing the smallest int by -1' 'div:Warn when dividing the smallest int by -1'
'empty-entry:Warn on empty entries in db, dw, dl args' 'empty-data-directive:Warn on arg-less d[bwl] in ROM'
'empty-macro-arg:Warn on empty macro arg'
'empty-strrpl:Warn on calling STRRPL with empty pattern'
'large-constant:Warn on constants too large for a signed 32-bit int' 'large-constant:Warn on constants too large for a signed 32-bit int'
'long-string:Warn on strings too long' 'long-string:Warn on strings too long'
'macro-shift:Warn when shifting macro args part their limits'
'nested-comment:Warn on "/*" inside block comments'
'numeric-string:Warn when a multi-character string is treated as a number'
'obsolete:Warn when using deprecated features' 'obsolete:Warn when using deprecated features'
'shift:Warn when shifting negative values' 'shift:Warn when shifting negative values'
'shift-amount:Warn when a shift'\''s operand it negative or \> 32' 'shift-amount:Warn when a shift'\''s operand it negative or \> 32'
'truncation:Warn when implicit truncations lose bits' 'truncation:Warn when implicit truncation loses bits'
'user:Warn when executing the WARN built-in' 'user:Warn when executing the WARN built-in'
) )
# TODO: handle `no-` and `error=` somehow? # TODO: handle `no-` and `error=` somehow?
# TODO: handle `=0|1|2` levels for `numeric-string` and `truncation`?
_describe warning warnings _describe warning warnings
} }
@@ -35,15 +43,19 @@ local args=(
-w'[Disable all warnings]' -w'[Disable all warnings]'
'(-b --binary-digits)'{-b,--binary-digits}'+[Change chars for binary constants]:digit spec:' '(-b --binary-digits)'{-b,--binary-digits}'+[Change chars for binary constants]:digit spec:'
'(-D --define)'{-D,--define}'+[Define a string symbol]:name + value (default 1):' '*'{-D,--define}'+[Define a string symbol]:name + value (default 1):'
'(-g --gfx-chars)'{-g,--gfx-chars}'+[Change chars for gfx constants]:chars spec:' '(-g --gfx-chars)'{-g,--gfx-chars}'+[Change chars for gfx constants]:chars spec:'
'(-i --include)'{-i,--include}'+[Add an include directory]:include path:_files -/' '(-i --include)'{-i,--include}'+[Add an include directory]:include path:_files -/'
'(-M --dependfile)'{-M,--dependfile}"+[List deps in make format]:output file:_files -g '*.{d,mk}" '(-M --dependfile)'{-M,--dependfile}"+[List deps in make format]:output file:_files -g '*.{d,mk}'"
-MG'[Assume missing files should be generated]'
-MP'[Add phony targets to all deps]'
'*'-MT"+[Add a target to the rules]:target:_files -g '*.{d,mk,o}'"
'*'-MQ"+[Add a target to the rules]:target:_files -g '*.{d,mk,o}'"
'(-o --output)'{-o,--output}'+[Output file]:output file:_files' '(-o --output)'{-o,--output}'+[Output file]:output file:_files'
'(-p --pad-value)'{-p,--pad-value}'+[Set padding byte]:padding byte:' '(-p --pad-value)'{-p,--pad-value}'+[Set padding byte]:padding byte:'
'(-r --recursion-depth)'{-r,--recursion-depth}'+[Set maximum recursion depth]:depth:' '(-r --recursion-depth)'{-r,--recursion-depth}'+[Set maximum recursion depth]:depth:'
'(-W --warning)'{-W,--warning}'+[Toggle warning flags]:warning flag:_rgbasm_warnings' '(-W --warning)'{-W,--warning}'+[Toggle warning flags]:warning flag:_rgbasm_warnings'
'*'":assembly sources:_files -g '*.asm'" ":assembly sources:_files -g '*.asm'"
) )
_arguments -s -S : $args _arguments -s -S : $args

View File

@@ -1,12 +1,47 @@
#compdef rgbfix #compdef rgbfix
_mbc_names() {
local mbc_names=(
'ROM:$00'
'MBC1:$01'
'MBC1+RAM:$02'
'MBC1+RAM+BATTERY:$03'
'MBC2:$05'
'MBC2+BATTERY:$06'
'ROM+RAM:$08'
'ROM+RAM+BATTERY:$09'
'MMM01:$0B'
'MMM01+RAM:$0C'
'MMM01+RAM+BATTERY:$0D'
'MBC3+TIMER+BATTERY:$0F'
'MBC3+TIMER+RAM+BATTERY:$10'
'MBC3:$11'
'MBC3+RAM:$12'
'MBC3+RAM+BATTERY:$13'
'MBC5:$19'
'MBC5+RAM:$1A'
'MBC5+RAM+BATTERY:$1B'
'MBC5+RUMBLE:$1C'
'MBC5+RUMBLE+RAM:$1D'
'MBC5+RUMBLE+RAM+BATTERY:$1E'
'MBC6:$20'
'MBC7+SENSOR+RUMBLE+RAM+BATTERY:$22'
'POCKET_CAMERA:$FC'
'BANDAI_TAMA5:$FD'
'HUC3:$FE'
'HUC1+RAM+BATTERY:$FF'
)
_describe "MBC name" 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
'(- : * options)'{-V,--version}'[Print version number]' '(- : * options)'{-V,--version}'[Print version number]'
'(-C --color-only)'{-C,--color-only}'[Mark ROM as GBC-only]' '(-C --color-only -c --color-compatible)'{-C,--color-only}'[Mark ROM as GBC-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]'
'(-j --non-japanese)'{-j,--non-japanese}'[Set the non-Japanese region flag]' '(-j --non-japanese)'{-j,--non-japanese}'[Set the non-Japanese region flag]'
'(-O --overwrite)'{-O,--overwrite}'[Allow overwriting non-zero bytes]'
'(-s --sgb-compatible)'{-s,--sgb-compatible}'[Set the SGB flag]' '(-s --sgb-compatible)'{-s,--sgb-compatible}'[Set the SGB flag]'
'(-f --fix-spec -v --validate)'{-v,--validate}'[Shorthand for -f lhg]' '(-f --fix-spec -v --validate)'{-v,--validate}'[Shorthand for -f lhg]'
@@ -14,7 +49,7 @@ local args=(
'(-i --game-id)'{-i,--game-id}'+[Set game ID string]:4-char game ID:' '(-i --game-id)'{-i,--game-id}'+[Set game ID string]:4-char game ID:'
'(-k --new-licensee)'{-k,--new-licensee}'+[Set new licensee string]:2-char licensee ID:' '(-k --new-licensee)'{-k,--new-licensee}'+[Set new licensee string]:2-char licensee ID:'
'(-l --old-licensee)'{-l,--old-licensee}'+[Set old licensee ID]:licensee number:' '(-l --old-licensee)'{-l,--old-licensee}'+[Set old licensee ID]:licensee number:'
'(-m --mbc-type)'{-m,--mbc-type}'+[Set MBC flags]:mbc flags byte:' '(-m --mbc-type)'{-m,--mbc-type}"+[Set MBC flags]:mbc name:_mbc_names"
'(-n --rom-version)'{-n,--rom-version}'+[Set ROM version]:rom version byte:' '(-n --rom-version)'{-n,--rom-version}'+[Set ROM version]:rom version byte:'
'(-p --pad-value)'{-p,--pad-value}'+[Pad to next valid size using this byte as padding]:padding byte:' '(-p --pad-value)'{-p,--pad-value}'+[Pad to next valid size using this byte as padding]:padding byte:'
'(-r --ram-size)'{-r,--ram-size}'+[Set RAM size]:ram size byte:' '(-r --ram-size)'{-r,--ram-size}'+[Set RAM size]:ram size byte:'

View File

@@ -32,6 +32,6 @@ local args=(
'(-t --tilemap -T --output-tilemap)'{-t,--tilemap}'+[Generate a map of tile indices]:tilemap file:_files' '(-t --tilemap -T --output-tilemap)'{-t,--tilemap}'+[Generate a map of tile indices]:tilemap file:_files'
'(-x --trim-end)'{-x,--trim-end}'+[Trim end of output by this many tiles]:tile count:' '(-x --trim-end)'{-x,--trim-end}'+[Trim end of output by this many tiles]:tile count:'
'*'":input png files:_files -g '*.png'" ":input png file:_files -g '*.png'"
) )
_arguments -s -S : $args _arguments -s -S : $args

View File

@@ -8,6 +8,7 @@ local args=(
'(-t --tiny)'{-t,--tiny}'[Enable tiny mode, disabling ROM banking]' '(-t --tiny)'{-t,--tiny}'[Enable tiny mode, disabling ROM banking]'
'(-v --verbose)'{-v,--verbose}'[Enable verbose output]' '(-v --verbose)'{-v,--verbose}'[Enable verbose output]'
'(-w --wramx)'{-w,--wramx}'[Disable WRAM banking]' '(-w --wramx)'{-w,--wramx}'[Disable WRAM banking]'
'(-x --nopad)'{-x,--nopad}'[Disable padding the end of the final file]'
'(-l --linkerscript)'{-l,--linkerscript}"+[Use a linker script]:linker script:_files -g '*.link'" '(-l --linkerscript)'{-l,--linkerscript}"+[Use a linker script]:linker script:_files -g '*.link'"
'(-m --map)'{-m,--map}"+[Produce a map file]:map file:_files -g '*.map'" '(-m --map)'{-m,--map}"+[Produce a map file]:map file:_files -g '*.map'"
@@ -15,6 +16,7 @@ local args=(
'(-O --overlay)'{-O,--overlay}'+[Overlay sections over on top of bin file]:base overlay:_files' '(-O --overlay)'{-O,--overlay}'+[Overlay sections over on top of bin file]:base overlay:_files'
'(-o --output)'{-o,--output}"+[Write ROM image to this file]:rom file:_files -g '*.{gb,sgb,gbc}'" '(-o --output)'{-o,--output}"+[Write ROM image to this file]:rom file:_files -g '*.{gb,sgb,gbc}'"
'(-p --pad-value)'{-p,--pad-value}'+[Set padding byte]:padding byte:' '(-p --pad-value)'{-p,--pad-value}'+[Set padding byte]:padding byte:'
'(-S --scramble)'{-s,--scramble}'+[Activate scrambling]:scramble spec'
'(-s --smart)'{-s,--smart}'+[!BROKEN! Perform smart linking from this symbol]:symbol name:' '(-s --smart)'{-s,--smart}'+[!BROKEN! Perform smart linking from this symbol]:symbol name:'
'*'":object files:_files -g '*.o'" '*'":object files:_files -g '*.o'"

View File

@@ -11,9 +11,9 @@
#include <stdint.h> #include <stdint.h>
struct Charmap *charmap_New(const char *name, const char *baseName); struct Charmap *charmap_New(char const *name, char const *baseName);
void charmap_Delete(struct Charmap *charmap); void charmap_Delete(struct Charmap *charmap);
void charmap_Set(const char *name); void charmap_Set(char const *name);
void charmap_Push(void); void charmap_Push(void);
void charmap_Pop(void); void charmap_Pop(void);
void charmap_Add(char *mapping, uint8_t value); void charmap_Add(char *mapping, uint8_t value);

View File

@@ -19,7 +19,6 @@
#include "asm/lexer.h" #include "asm/lexer.h"
#include "types.h"
struct FileStackNode { struct FileStackNode {
struct FileStackNode *parent; /* Pointer to parent node, for error reporting */ struct FileStackNode *parent; /* Pointer to parent node, for error reporting */

View File

@@ -24,7 +24,7 @@ struct Expression {
uint8_t *rpn; // Array of bytes serializing the RPN expression uint8_t *rpn; // Array of bytes serializing the RPN expression
uint32_t rpnCapacity; // Size of the `rpn` buffer uint32_t rpnCapacity; // Size of the `rpn` buffer
uint32_t rpnLength; // Used size of the `rpn` buffer uint32_t rpnLength; // Used size of the `rpn` buffer
uint32_t rpnPatchSize; // Size the expression will take in the obj file uint32_t rpnPatchSize; // Size the expression will take in the object file
}; };
/* /*

View File

@@ -13,6 +13,7 @@
#include <stdbool.h> #include <stdbool.h>
#include "linkdefs.h" #include "linkdefs.h"
#include "platform.h" // NONNULL
extern uint8_t fillByte; extern uint8_t fillByte;
@@ -42,7 +43,7 @@ struct SectionSpec {
extern struct Section *currentSection; extern struct Section *currentSection;
struct Section *sect_FindSectionByName(const char *name); struct Section *sect_FindSectionByName(char const *name);
void sect_NewSection(char const *name, uint32_t secttype, uint32_t org, void sect_NewSection(char const *name, uint32_t secttype, uint32_t org,
struct SectionSpec const *attributes, enum SectionModifier mod); struct SectionSpec const *attributes, enum SectionModifier mod);
void sect_SetLoadSection(char const *name, uint32_t secttype, uint32_t org, void sect_SetLoadSection(char const *name, uint32_t secttype, uint32_t org,
@@ -60,10 +61,10 @@ void sect_EndUnion(void);
void sect_CheckUnionClosed(void); void sect_CheckUnionClosed(void);
void sect_AbsByte(uint8_t b); void sect_AbsByte(uint8_t b);
void sect_AbsByteGroup(uint8_t const *s, int32_t length); void sect_AbsByteGroup(uint8_t const *s, size_t length);
void sect_AbsWordGroup(uint8_t const *s, int32_t length); void sect_AbsWordGroup(uint8_t const *s, size_t length);
void sect_AbsLongGroup(uint8_t const *s, int32_t length); void sect_AbsLongGroup(uint8_t const *s, size_t length);
void sect_Skip(int32_t skip, bool ds); void sect_Skip(uint32_t skip, bool ds);
void sect_String(char const *s); void sect_String(char const *s);
void sect_RelByte(struct Expression *expr, uint32_t pcShift); void sect_RelByte(struct Expression *expr, uint32_t pcShift);
void sect_RelBytes(uint32_t n, struct Expression *exprs, size_t size); void sect_RelBytes(uint32_t n, struct Expression *exprs, size_t size);
@@ -76,4 +77,6 @@ void sect_BinaryFileSlice(char const *s, int32_t start_pos, int32_t length);
void sect_PushSection(void); void sect_PushSection(void);
void sect_PopSection(void); void sect_PopSection(void);
bool sect_IsSizeKnown(struct Section const NONNULL(name));
#endif #endif

View File

@@ -17,15 +17,13 @@
#include "asm/section.h" #include "asm/section.h"
#include "platform.h" // MIN_NB_ELMS #include "platform.h" // MIN_NB_ELMS
#include "types.h"
#define HASHSIZE (1 << 16) #define MAXSYMLEN 255
#define MAXSYMLEN 256
enum SymbolType { enum SymbolType {
SYM_LABEL, SYM_LABEL,
SYM_EQU, SYM_EQU,
SYM_SET, SYM_VAR,
SYM_MACRO, SYM_MACRO,
SYM_EQUS, SYM_EQUS,
SYM_REF // Forward reference to a label SYM_REF // Forward reference to a label
@@ -77,12 +75,12 @@ static inline bool sym_IsConstant(struct Symbol const *sym)
return sect && sect->org != (uint32_t)-1; return sect && sect->org != (uint32_t)-1;
} }
return sym->type == SYM_EQU || sym->type == SYM_SET; return sym->type == SYM_EQU || sym->type == SYM_VAR;
} }
static inline bool sym_IsNumeric(struct Symbol const *sym) static inline bool sym_IsNumeric(struct Symbol const *sym)
{ {
return sym->type == SYM_LABEL || sym->type == SYM_EQU || sym->type == SYM_SET; return sym->type == SYM_LABEL || sym->type == SYM_EQU || sym->type == SYM_VAR;
} }
static inline bool sym_IsLabel(struct Symbol const *sym) static inline bool sym_IsLabel(struct Symbol const *sym)
@@ -121,7 +119,7 @@ void sym_WriteAnonLabelName(char buf[MIN_NB_ELMS(MAXSYMLEN + 1)], uint32_t ofs,
void sym_Export(char const *symName); void sym_Export(char const *symName);
struct Symbol *sym_AddEqu(char const *symName, int32_t value); struct Symbol *sym_AddEqu(char const *symName, int32_t value);
struct Symbol *sym_RedefEqu(char const *symName, int32_t value); struct Symbol *sym_RedefEqu(char const *symName, int32_t value);
struct Symbol *sym_AddSet(char const *symName, int32_t value); struct Symbol *sym_AddVar(char const *symName, int32_t value);
uint32_t sym_GetPCValue(void); uint32_t sym_GetPCValue(void);
uint32_t sym_GetConstantSymValue(struct Symbol const *sym); uint32_t sym_GetConstantSymValue(struct Symbol const *sym);
uint32_t sym_GetConstantValue(char const *symName); uint32_t sym_GetConstantValue(char const *symName);

View File

@@ -21,45 +21,57 @@ enum WarningState {
}; };
enum WarningID { enum WarningID {
WARNING_ASSERT, /* Assertions */ WARNING_ASSERT, // Assertions
WARNING_BACKWARDS_FOR, /* `for` loop with backwards range */ WARNING_BACKWARDS_FOR, // `for` loop with backwards range
WARNING_BUILTIN_ARG, /* Invalid args to builtins */ WARNING_BUILTIN_ARG, // Invalid args to builtins
WARNING_CHARMAP_REDEF, /* Charmap entry re-definition */ WARNING_CHARMAP_REDEF, // Charmap entry re-definition
WARNING_DIV, /* Division undefined behavior */ WARNING_DIV, // Division undefined behavior
WARNING_EMPTY_DATA_DIRECTIVE, /* `db`, `dw` or `dl` directive without data in ROM */ WARNING_EMPTY_DATA_DIRECTIVE, // `db`, `dw` or `dl` directive without data in ROM
WARNING_EMPTY_MACRO_ARG, /* Empty macro argument */ WARNING_EMPTY_MACRO_ARG, // Empty macro argument
WARNING_EMPTY_STRRPL, /* Empty second argument in `STRRPL` */ WARNING_EMPTY_STRRPL, // Empty second argument in `STRRPL`
WARNING_LARGE_CONSTANT, /* Constants too large */ WARNING_LARGE_CONSTANT, // Constants too large
WARNING_LONG_STR, /* String too long for internal buffers */ WARNING_LONG_STR, // String too long for internal buffers
WARNING_MACRO_SHIFT, /* Shift past available arguments in macro */ WARNING_MACRO_SHIFT, // Shift past available arguments in macro
WARNING_NESTED_COMMENT, /* Comment-start delimiter in a block comment */ WARNING_NESTED_COMMENT, // Comment-start delimiter in a block comment
WARNING_OBSOLETE, /* Obsolete things */ WARNING_OBSOLETE, // Obsolete things
WARNING_SHIFT, /* Shifting undefined behavior */ WARNING_SHIFT, // Shifting undefined behavior
WARNING_SHIFT_AMOUNT, /* Strange shift amount */ WARNING_SHIFT_AMOUNT, // Strange shift amount
WARNING_TRUNCATION, /* Implicit truncation loses some bits */ WARNING_USER, // User warnings
WARNING_USER, /* User warnings */
NB_WARNINGS, NB_PLAIN_WARNINGS,
/* Warnings past this point are "meta" warnings */ // Warnings past this point are "parametric" warnings, only mapping to a single flag
WARNING_ALL = NB_WARNINGS, #define PARAM_WARNINGS_START NB_PLAIN_WARNINGS
// Treating string as number may lose some bits
WARNING_NUMERIC_STRING_1 = PARAM_WARNINGS_START,
WARNING_NUMERIC_STRING_2,
// Implicit truncation loses some bits
WARNING_TRUNCATION_1,
WARNING_TRUNCATION_2,
NB_PLAIN_AND_PARAM_WARNINGS,
#define NB_PARAM_WARNINGS (NB_PLAIN_AND_PARAM_WARNINGS - PARAM_WARNINGS_START)
// Warnings past this point are "meta" warnings
#define META_WARNINGS_START NB_PLAIN_AND_PARAM_WARNINGS
WARNING_ALL = META_WARNINGS_START,
WARNING_EXTRA, WARNING_EXTRA,
WARNING_EVERYTHING, WARNING_EVERYTHING,
NB_WARNINGS_ALL NB_WARNINGS,
#define NB_META_WARNINGS (NB_WARNINGS_ALL - NB_WARNINGS) #define NB_META_WARNINGS (NB_WARNINGS - META_WARNINGS_START)
}; };
extern enum WarningState warningStates[NB_WARNINGS]; extern enum WarningState warningStates[NB_PLAIN_AND_PARAM_WARNINGS];
extern bool warningsAreErrors; extern bool warningsAreErrors;
void processWarningFlag(char const *flag); void processWarningFlag(char *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.
*/ */
void warning(enum WarningID id, const char *fmt, ...) format_(printf, 2, 3); void warning(enum WarningID id, char const *fmt, ...) format_(printf, 2, 3);
/* /*
* Used for errors that compromise the whole assembly process by affecting the * Used for errors that compromise the whole assembly process by affecting the
@@ -68,7 +80,7 @@ void warning(enum WarningID id, const char *fmt, ...) format_(printf, 2, 3);
* 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).
*/ */
_Noreturn void fatalerror(const char *fmt, ...) format_(printf, 1, 2); _Noreturn void fatalerror(char const *fmt, ...) format_(printf, 1, 2);
/* /*
* 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
@@ -76,6 +88,6 @@ _Noreturn void fatalerror(const char *fmt, ...) format_(printf, 1, 2);
* 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.
*/ */
void error(const char *fmt, ...) format_(printf, 1, 2); void error(char const *fmt, ...) format_(printf, 1, 2);
#endif #endif

21
include/error.h Normal file
View File

@@ -0,0 +1,21 @@
/*
* This file is part of RGBDS.
*
* Copyright (c) 1997-2021, RGBDS contributors.
*
* SPDX-License-Identifier: MIT
*/
#ifndef RGBDS_ERROR_H
#define RGBDS_ERROR_H
#include "helpers.h"
#include "platform.h"
void warn(char const NONNULL(fmt), ...) format_(printf, 1, 2);
void warnx(char const NONNULL(fmt), ...) format_(printf, 1, 2);
_Noreturn void err(char const NONNULL(fmt), ...) format_(printf, 1, 2);
_Noreturn void errx(char const NONNULL(fmt), ...) format_(printf, 1, 2);
#endif /* RGBDS_ERROR_H */

44
include/extern/err.h vendored
View File

@@ -1,44 +0,0 @@
/*
* This file is part of RGBDS.
*
* Copyright (c) 1997-2018, RGBDS contributors.
*
* SPDX-License-Identifier: MIT
*/
#ifndef EXTERN_ERR_H
#define EXTERN_ERR_H
#ifdef ERR_IN_LIBC
#include <err.h>
#else /* ERR_IN_LIBC */
#include <stdarg.h>
#include "helpers.h"
#define warn rgbds_warn
#define vwarn rgbds_vwarn
#define warnx rgbds_warnx
#define vwarnx rgbds_vwarnx
#define err rgbds_err
#define verr rgbds_verr
#define errx rgbds_errx
#define verrx rgbds_verrx
void warn(const char *fmt, ...) format_(printf, 1, 2);
void vwarn(const char *fmt, va_list ap) format_(printf, 1, 0);
void warnx(const char *fmt, ...) format_(printf, 1, 2);
void vwarnx(const char *fmt, va_list ap) format_(printf, 1, 0);
_Noreturn void err(int status, const char *fmt, ...) format_(printf, 2, 3);
_Noreturn void verr(int status, const char *fmt, va_list ap) format_(printf, 2, 0);
_Noreturn void errx(int status, const char *fmt, ...) format_(printf, 2, 3);
_Noreturn void verrx(int status, const char *fmt, va_list ap) format_(printf, 2, 0);
#endif /* ERR_IN_LIBC */
#endif /* EXTERN_ERR_H */

View File

@@ -30,13 +30,14 @@ extern char *musl_optarg;
extern int musl_optind, musl_opterr, musl_optopt, musl_optreset; extern int musl_optind, musl_opterr, musl_optopt, musl_optreset;
struct option { struct option {
const char *name; char const *name;
int has_arg; int has_arg;
int *flag; int *flag;
int val; int val;
}; };
int musl_getopt_long_only(int, char **, const char *, const struct option *, int *); int musl_getopt_long_only(int argc, char **argv, char const *optstring,
const struct option *longopts, int *idx);
#define no_argument 0 #define no_argument 0
#define required_argument 1 #define required_argument 1

View File

@@ -13,7 +13,7 @@
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>
#include "extern/err.h" #include "error.h"
struct Options { struct Options {
bool debug; bool debug;

View File

@@ -89,4 +89,8 @@
#define STR(x) #x #define STR(x) #x
#define EXPAND_AND_STR(x) STR(x) #define EXPAND_AND_STR(x) STR(x)
// 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))
#endif /* HELPERS_H */ #endif /* HELPERS_H */

View File

@@ -24,6 +24,9 @@ extern char const *symFileName;
extern char const *overlayFileName; extern char const *overlayFileName;
extern char const *outputFileName; extern char const *outputFileName;
extern uint8_t padValue; extern uint8_t padValue;
extern uint16_t scrambleROMX;
extern uint8_t scrambleWRAMX;
extern uint8_t scrambleSRAM;
extern bool is32kMode; extern bool is32kMode;
extern bool beVerbose; extern bool beVerbose;
extern bool isWRA0Mode; extern bool isWRA0Mode;

View File

@@ -49,8 +49,10 @@
/* MSVC doesn't support `[static N]` for array arguments from C99 */ /* MSVC doesn't support `[static N]` for array arguments from C99 */
#ifdef _MSC_VER #ifdef _MSC_VER
# define MIN_NB_ELMS(N) # define MIN_NB_ELMS(N)
# define NONNULL(ptr) *ptr
#else #else
# define MIN_NB_ELMS(N) static (N) # define MIN_NB_ELMS(N) static (N)
# define NONNULL(ptr) ptr[static 1]
#endif #endif
// MSVC uses a different name for O_RDWR, and needs an additional _O_BINARY flag // MSVC uses a different name for O_RDWR, and needs an additional _O_BINARY flag

View File

@@ -1,16 +0,0 @@
/*
* This file is part of RGBDS.
*
* Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors.
*
* SPDX-License-Identifier: MIT
*/
#ifndef RGBDS_TYPES_H
#define RGBDS_TYPES_H
#ifndef _MAX_PATH
#define _MAX_PATH 512
#endif
#endif /* RGBDS_TYPES_H */

View File

@@ -11,8 +11,8 @@
#define PACKAGE_VERSION_MAJOR 0 #define PACKAGE_VERSION_MAJOR 0
#define PACKAGE_VERSION_MINOR 5 #define PACKAGE_VERSION_MINOR 5
#define PACKAGE_VERSION_PATCH 1 #define PACKAGE_VERSION_PATCH 2
const char *get_package_version_string(void); char const *get_package_version_string(void);
#endif /* EXTERN_VERSION_H */ #endif /* EXTERN_VERSION_H */

View File

@@ -7,7 +7,7 @@
# #
set(common_src set(common_src
"extern/err.c" "error.c"
"extern/getopt.c" "extern/getopt.c"
"version.c" "version.c"
) )

View File

@@ -78,7 +78,7 @@ static void initNode(struct Charnode *node)
memset(node->next, 0, sizeof(node->next)); memset(node->next, 0, sizeof(node->next));
} }
struct Charmap *charmap_New(const char *name, const char *baseName) struct Charmap *charmap_New(char const *name, char const *baseName)
{ {
struct Charmap *base = NULL; struct Charmap *base = NULL;
@@ -120,7 +120,7 @@ void charmap_Delete(struct Charmap *charmap)
free(charmap); free(charmap);
} }
void charmap_Set(const char *name) void charmap_Set(char const *name)
{ {
struct Charmap **charmap = (struct Charmap **)hash_GetNode(charmaps, name); struct Charmap **charmap = (struct Charmap **)hash_GetNode(charmaps, name);
@@ -215,49 +215,50 @@ size_t charmap_ConvertNext(char const **input, uint8_t **output)
size_t rewindDistance = 0; size_t rewindDistance = 0;
for (;;) { for (;;) {
/* We still want NULs to reach the `else` path, to give a chance to rewind */
uint8_t c = **input - 1; uint8_t c = **input - 1;
if (**input && node->next[c]) { if (**input && node->next[c]) {
(*input)++; /* Consume that char */ // Consume that char
(*input)++;
rewindDistance++; rewindDistance++;
// Advance to next node (index starts at 1)
node = &charmap->nodes[node->next[c]]; node = &charmap->nodes[node->next[c]];
if (node->isTerminal) { if (node->isTerminal) {
// This node matches, register it
match = node; match = node;
rewindDistance = 0; /* Rewind from after the match */ rewindDistance = 0; // If no longer match is found, rewind here
} }
} else { } else {
*input -= rewindDistance; /* Rewind */ // We are at a dead end (either because we reached the end of input, or of
rewindDistance = 0; // the trie), so rewind up to the last match, and output.
node = &charmap->nodes[0]; *input -= rewindDistance; // This will rewind all the way if no match found
if (match) { /* Arrived at a dead end with a match found */ if (match) { // A match was found, use it
if (output) if (output)
*(*output)++ = match->value; *(*output)++ = match->value;
return 1; return 1;
} else if (**input) { /* No match found */ } else if (**input) { // No match found, but there is some input left
// This will write the codepoint's value to `output`, little-endian
size_t codepointLen = readUTF8Char(output ? *output : NULL, size_t codepointLen = readUTF8Char(output ? *output : NULL,
*input); *input);
if (codepointLen == 0) if (codepointLen == 0)
error("Input string is not valid UTF-8!\n"); error("Input string is not valid UTF-8!\n");
/* OK because UTF-8 has no NUL in multi-byte chars */ // OK because UTF-8 has no NUL in multi-byte chars
*input += codepointLen; *input += codepointLen;
if (output) if (output)
*output += codepointLen; *output += codepointLen;
return codepointLen; return codepointLen;
} else { /* End of input */ } else { // End of input
return 0; return 0;
} }
} }
} }
unreachable_();
} }

View File

@@ -46,7 +46,7 @@ int32_t fix_Callback_PI(void)
void fix_Print(int32_t i) void fix_Print(int32_t i)
{ {
uint32_t u = i; uint32_t u = i;
const char *sign = ""; char const *sign = "";
if (i < 0) { if (i < 0) {
u = -u; u = -u;

View File

@@ -228,21 +228,13 @@ void fmt_PrintNumber(char *buf, size_t bufLen, struct FormatSpec const *fmt, uin
/* Default fractional width (C's is 6 for "%f"; here 5 is enough) */ /* Default fractional width (C's is 6 for "%f"; here 5 is enough) */
size_t fracWidth = fmt->hasFrac ? fmt->fracWidth : 5; size_t fracWidth = fmt->hasFrac ? fmt->fracWidth : 5;
if (fracWidth) {
if (fracWidth > 255) { if (fracWidth > 255) {
error("Fractional width %zu too long, limiting to 255\n", error("Fractional width %zu too long, limiting to 255\n",
fracWidth); fracWidth);
fracWidth = 255; fracWidth = 255;
} }
char spec[16]; /* Max "%" + 5-char PRIu32 + ".%0255.f" + terminator */ snprintf(valueBuf, sizeof(valueBuf), "%.*f", (int)fracWidth, value / 65536.0);
snprintf(spec, sizeof(spec), "%%" PRIu32 ".%%0%zu.f", fracWidth);
snprintf(valueBuf, sizeof(valueBuf), spec, value >> 16,
(value % 65536) / 65536.0 * pow(10, fracWidth) + 0.5);
} else {
snprintf(valueBuf, sizeof(valueBuf), "%" PRIu32, value >> 16);
}
} else { } else {
char const *spec = fmt->type == 'd' ? "%" PRId32 char const *spec = fmt->type == 'd' ? "%" PRId32
: fmt->type == 'u' ? "%" PRIu32 : fmt->type == 'u' ? "%" PRIu32

View File

@@ -23,12 +23,6 @@
#define MAXINCPATHS 128 #define MAXINCPATHS 128
#ifdef LEXER_DEBUG
#define dbgPrint(...) fprintf(stderr, "[fstack] " __VA_ARGS__)
#else
#define dbgPrint(...)
#endif
struct Context { struct Context {
struct Context *parent; struct Context *parent;
struct FileStackNode *fileInfo; struct FileStackNode *fileInfo;
@@ -241,11 +235,11 @@ bool yywrap(void)
/* If this is a FOR, update the symbol value */ /* If this is a FOR, update the symbol value */
if (contextStack->forName && fileInfo->iters[0] <= contextStack->nbReptIters) { if (contextStack->forName && fileInfo->iters[0] <= contextStack->nbReptIters) {
contextStack->forValue += contextStack->forStep; contextStack->forValue += contextStack->forStep;
struct Symbol *sym = sym_AddSet(contextStack->forName, struct Symbol *sym = sym_AddVar(contextStack->forName,
contextStack->forValue); contextStack->forValue);
/* This error message will refer to the current iteration */ /* This error message will refer to the current iteration */
if (sym->type != SYM_SET) 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 */
@@ -259,7 +253,6 @@ bool yywrap(void)
} else if (!contextStack->parent) { } else if (!contextStack->parent) {
return true; return true;
} }
dbgPrint("Popping context\n");
struct Context *context = contextStack; struct Context *context = contextStack;
@@ -269,10 +262,8 @@ bool yywrap(void)
lexer_DeleteState(context->lexerState); lexer_DeleteState(context->lexerState);
/* Restore args if a macro (not REPT) saved them */ /* Restore args if a macro (not REPT) saved them */
if (context->fileInfo->type == NODE_MACRO) { if (context->fileInfo->type == NODE_MACRO)
dbgPrint("Restoring macro args %p\n", (void *)contextStack->macroArgs);
macro_UseNewArgs(contextStack->macroArgs); macro_UseNewArgs(contextStack->macroArgs);
}
/* Free the file stack node */ /* Free the file stack node */
if (!context->fileInfo->referenced) if (!context->fileInfo->referenced)
free(context->fileInfo); free(context->fileInfo);
@@ -315,8 +306,6 @@ static void newContext(struct FileStackNode *fileInfo)
void fstk_RunInclude(char const *path) void fstk_RunInclude(char const *path)
{ {
dbgPrint("Including path \"%s\"\n", path);
char *fullPath = NULL; char *fullPath = NULL;
size_t size = 0; size_t size = 0;
@@ -332,7 +321,6 @@ void fstk_RunInclude(char const *path)
} }
return; return;
} }
dbgPrint("Full path: \"%s\"\n", fullPath);
struct FileStackNamedNode *fileInfo = malloc(sizeof(*fileInfo) + size); struct FileStackNamedNode *fileInfo = malloc(sizeof(*fileInfo) + size);
@@ -356,8 +344,6 @@ void fstk_RunInclude(char const *path)
void fstk_RunMacro(char const *macroName, struct MacroArgs *args) void fstk_RunMacro(char const *macroName, struct MacroArgs *args)
{ {
dbgPrint("Running macro \"%s\"\n", macroName);
struct Symbol *macro = sym_FindExactSymbol(macroName); struct Symbol *macro = sym_FindExactSymbol(macroName);
if (!macro) { if (!macro) {
@@ -461,8 +447,6 @@ static bool newReptContext(int32_t reptLineNo, char *body, size_t size)
void fstk_RunRept(uint32_t count, int32_t reptLineNo, char *body, size_t size) void fstk_RunRept(uint32_t count, int32_t reptLineNo, char *body, size_t size)
{ {
dbgPrint("Running REPT(%" PRIu32 ")\n", count);
if (count == 0) if (count == 0)
return; return;
if (!newReptContext(reptLineNo, body, size)) if (!newReptContext(reptLineNo, body, size))
@@ -475,12 +459,9 @@ void fstk_RunRept(uint32_t count, int32_t reptLineNo, char *body, size_t size)
void fstk_RunFor(char const *symName, int32_t start, int32_t stop, int32_t step, void fstk_RunFor(char const *symName, int32_t start, int32_t stop, int32_t step,
int32_t reptLineNo, char *body, size_t size) int32_t reptLineNo, char *body, size_t size)
{ {
dbgPrint("Running FOR(\"%s\", %" PRId32 ", %" PRId32 ", %" PRId32 ")\n", struct Symbol *sym = sym_AddVar(symName, start);
symName, start, stop, step);
struct Symbol *sym = sym_AddSet(symName, start); if (sym->type != SYM_VAR)
if (sym->type != SYM_SET)
return; return;
uint32_t count = 0; uint32_t count = 0;
@@ -517,8 +498,6 @@ void fstk_StopRept(void)
bool fstk_Break(void) bool fstk_Break(void)
{ {
dbgPrint("Breaking out of REPT/FOR\n");
if (contextStack->fileInfo->type != NODE_REPT) { if (contextStack->fileInfo->type != NODE_REPT) {
error("BREAK can only be used inside a REPT/FOR block\n"); error("BREAK can only be used inside a REPT/FOR block\n");
return false; return false;

View File

@@ -24,7 +24,6 @@
#include <unistd.h> #include <unistd.h>
#endif #endif
#include "extern/utf8decoder.h"
#include "platform.h" /* For `ssize_t` */ #include "platform.h" /* For `ssize_t` */
#include "asm/lexer.h" #include "asm/lexer.h"
@@ -39,12 +38,6 @@
/* Include this last so it gets all type & constant definitions */ /* Include this last so it gets all type & constant definitions */
#include "parser.h" /* For token definitions, generated from parser.y */ #include "parser.h" /* For token definitions, generated from parser.y */
#ifdef LEXER_DEBUG
#define dbgPrint(...) fprintf(stderr, "[lexer] " __VA_ARGS__)
#else
#define dbgPrint(...)
#endif
/* Neither MSVC nor MinGW provide `mmap` */ /* Neither MSVC nor MinGW provide `mmap` */
#if defined(_MSC_VER) || defined(__MINGW32__) #if defined(_MSC_VER) || defined(__MINGW32__)
# define WIN32_LEAN_AND_MEAN // include less from windows.h # define WIN32_LEAN_AND_MEAN // include less from windows.h
@@ -285,8 +278,6 @@ static struct KeywordMapping {
{"EQU", T_POP_EQU}, {"EQU", T_POP_EQU},
{"EQUS", T_POP_EQUS}, {"EQUS", T_POP_EQUS},
{"REDEF", T_POP_REDEF}, {"REDEF", T_POP_REDEF},
/* Handled before as T_Z80_SET */
/* {"SET", T_POP_SET}, */
{"PUSHS", T_POP_PUSHS}, {"PUSHS", T_POP_PUSHS},
{"POPS", T_POP_POPS}, {"POPS", T_POP_POPS},
@@ -455,8 +446,6 @@ void lexer_ReachELSEBlock(void)
struct LexerState *lexer_OpenFile(char const *path) struct LexerState *lexer_OpenFile(char const *path)
{ {
dbgPrint("Opening file \"%s\"\n", path);
bool isStdin = !strcmp(path, "-"); bool isStdin = !strcmp(path, "-");
struct LexerState *state = malloc(sizeof(*state)); struct LexerState *state = malloc(sizeof(*state));
struct stat fileInfo; struct stat fileInfo;
@@ -533,8 +522,6 @@ struct LexerState *lexer_OpenFile(char const *path)
struct LexerState *lexer_OpenFileView(char const *path, char *buf, size_t size, uint32_t lineNo) struct LexerState *lexer_OpenFileView(char const *path, char *buf, size_t size, uint32_t lineNo)
{ {
dbgPrint("Opening view on buffer \"%.*s\"[...]\n", size < 16 ? (int)size : 16, buf);
struct LexerState *state = malloc(sizeof(*state)); struct LexerState *state = malloc(sizeof(*state));
if (!state) { if (!state) {
@@ -556,7 +543,6 @@ struct LexerState *lexer_OpenFileView(char const *path, char *buf, size_t size,
void lexer_RestartRept(uint32_t lineNo) void lexer_RestartRept(uint32_t lineNo)
{ {
dbgPrint("Restarting REPT/FOR\n");
lexerState->offset = 0; lexerState->offset = 0;
initState(lexerState); initState(lexerState);
lexerState->lineNo = lineNo; lexerState->lineNo = lineNo;
@@ -618,7 +604,7 @@ void lexer_Init(void)
*/ */
uint16_t usedNodes = 1; uint16_t usedNodes = 1;
for (size_t i = 0; i < sizeof(keywords) / sizeof(*keywords); i++) { for (size_t i = 0; i < ARRAY_SIZE(keywords); i++) {
uint16_t nodeID = 0; uint16_t nodeID = 0;
/* Walk the dictionary, creating intermediate nodes for the keyword */ /* Walk the dictionary, creating intermediate nodes for the keyword */
@@ -645,8 +631,7 @@ void lexer_Init(void)
#ifdef PRINT_NODE_COUNT /* For the maintainer to check how many nodes are needed */ #ifdef PRINT_NODE_COUNT /* For the maintainer to check how many nodes are needed */
printf("Lexer keyword dictionary: %zu keywords in %u nodes (pool size %zu)\n", printf("Lexer keyword dictionary: %zu keywords in %u nodes (pool size %zu)\n",
sizeof(keywords) / sizeof(*keywords), usedNodes, ARRAY_SIZE(keywords), usedNodes, ARRAY_SIZE(keywordDict));
sizeof(keywordDict) / sizeof(*keywordDict));
#endif #endif
} }
@@ -729,7 +714,6 @@ static bool continuesIdentifier(int c);
static uint32_t readBracketedMacroArgNum(void) static uint32_t readBracketedMacroArgNum(void)
{ {
dbgPrint("Reading bracketed macro arg\n");
bool disableMacroArgs = lexerState->disableMacroArgs; bool disableMacroArgs = lexerState->disableMacroArgs;
bool disableInterpolation = lexerState->disableInterpolation; bool disableInterpolation = lexerState->disableInterpolation;
@@ -900,10 +884,7 @@ static char const *readInterpolation(size_t depth);
static int peek(void) static int peek(void)
{ {
int c; int c = peekInternal(0);
restart:
c = peekInternal(0);
if (lexerState->macroArgScanDistance > 0) if (lexerState->macroArgScanDistance > 0)
return c; return c;
@@ -924,7 +905,7 @@ restart:
* expanded, so skip it and keep peeking. * expanded, so skip it and keep peeking.
*/ */
if (!str || !str[0]) if (!str || !str[0])
goto restart; return peek();
beginExpansion(str, c == '#', NULL); beginExpansion(str, c == '#', NULL);
@@ -945,7 +926,7 @@ restart:
if (str && str[0]) if (str && str[0])
beginExpansion(str, false, str); beginExpansion(str, false, str);
goto restart; return peek();
} }
return c; return c;
@@ -1042,10 +1023,9 @@ void lexer_DumpStringExpansions(void)
} }
} }
/* Discards an block comment */ /* Discards a block comment */
static void discardBlockComment(void) static void discardBlockComment(void)
{ {
dbgPrint("Discarding block comment\n");
lexerState->disableMacroArgs = true; lexerState->disableMacroArgs = true;
lexerState->disableInterpolation = true; lexerState->disableInterpolation = true;
for (;;) { for (;;) {
@@ -1088,15 +1068,13 @@ finish:
static void discardComment(void) static void discardComment(void)
{ {
dbgPrint("Discarding comment\n");
lexerState->disableMacroArgs = true; lexerState->disableMacroArgs = true;
lexerState->disableInterpolation = true; lexerState->disableInterpolation = true;
for (;;) { for (;; shiftChar()) {
int c = peek(); int c = peek();
if (c == EOF || c == '\r' || c == '\n') if (c == EOF || c == '\r' || c == '\n')
break; break;
shiftChar();
} }
lexerState->disableMacroArgs = false; lexerState->disableMacroArgs = false;
lexerState->disableInterpolation = false; lexerState->disableInterpolation = false;
@@ -1106,7 +1084,6 @@ static void discardComment(void)
static void readLineContinuation(void) static void readLineContinuation(void)
{ {
dbgPrint("Beginning line continuation\n");
for (;;) { for (;;) {
int c = peek(); int c = peek();
@@ -1118,13 +1095,13 @@ static void readLineContinuation(void)
handleCRLF(c); handleCRLF(c);
if (!lexerState->expansions) if (!lexerState->expansions)
nextLine(); nextLine();
return; break;
} else if (c == ';') { } else if (c == ';') {
discardComment(); discardComment();
} else { } else {
error("Begun line continuation, but encountered character %s\n", error("Begun line continuation, but encountered character %s\n",
printChar(c)); printChar(c));
return; break;
} }
} }
} }
@@ -1169,7 +1146,6 @@ static uint32_t readFractionalPart(int32_t integer)
{ {
uint32_t value = 0, divisor = 1; uint32_t value = 0, divisor = 1;
dbgPrint("Reading fractional part\n");
for (;; shiftChar()) { for (;; shiftChar()) {
int c = peek(); int c = peek();
@@ -1207,11 +1183,11 @@ static uint32_t readBinaryNumber(void)
{ {
uint32_t value = 0; uint32_t value = 0;
dbgPrint("Reading binary number with digits [%c,%c]\n", binDigits[0], binDigits[1]);
for (;; shiftChar()) { for (;; shiftChar()) {
int c = peek(); int c = peek();
int bit; int bit;
// 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])
@@ -1233,11 +1209,10 @@ static uint32_t readHexNumber(void)
uint32_t value = 0; uint32_t value = 0;
bool empty = true; bool empty = true;
dbgPrint("Reading hex number\n");
for (;; shiftChar()) { for (;; shiftChar()) {
int c = peek(); int c = peek();
if (c >= 'a' && c <= 'f') /* Convert letters to right after digits */ 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;
@@ -1265,15 +1240,14 @@ char gfxDigits[4];
static uint32_t readGfxConstant(void) static uint32_t readGfxConstant(void)
{ {
uint32_t bp0 = 0, bp1 = 0; uint32_t bitPlaneLower = 0, bitPlaneUpper = 0;
uint8_t width = 0; uint8_t width = 0;
dbgPrint("Reading gfx constant with digits [%c,%c,%c,%c]\n", for (;; shiftChar()) {
gfxDigits[0], gfxDigits[1], gfxDigits[2], gfxDigits[3]);
for (;;) {
int c = peek(); int c = peek();
uint32_t pixel; uint32_t pixel;
// 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])
@@ -1282,31 +1256,33 @@ static uint32_t readGfxConstant(void)
pixel = 2; pixel = 2;
else if (c == gfxDigits[3]) else if (c == gfxDigits[3])
pixel = 3; pixel = 3;
else if (c == '_' && width > 0)
continue;
else else
break; break;
if (width < 8) { if (width < 8) {
bp0 = bp0 << 1 | (pixel & 1); bitPlaneLower = bitPlaneLower << 1 | (pixel & 1);
bp1 = bp1 << 1 | (pixel >> 1); bitPlaneUpper = bitPlaneUpper << 1 | (pixel >> 1);
} }
if (width < 9) if (width < 9)
width++; width++;
shiftChar();
} }
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_LARGE_CONSTANT, warning(WARNING_LARGE_CONSTANT,
"Graphics constant is too long, only 8 first pixels considered\n"); "Graphics constant is too long, only first 8 pixels considered\n");
return bp1 << 8 | bp0; return bitPlaneUpper << 8 | bitPlaneLower;
} }
/* Functions to read identifiers & keywords */ /* Functions to read identifiers & keywords */
static bool startsIdentifier(int c) static bool startsIdentifier(int c)
{ {
// Anonymous labels internally start with '!'
return (c <= 'Z' && c >= 'A') || (c <= 'z' && c >= 'a') || c == '.' || c == '_'; return (c <= 'Z' && c >= 'A') || (c <= 'z' && c >= 'a') || c == '.' || c == '_';
} }
@@ -1317,7 +1293,6 @@ static bool continuesIdentifier(int c)
static int readIdentifier(char firstChar) static int readIdentifier(char firstChar)
{ {
dbgPrint("Reading identifier or keyword\n");
/* Lex while checking for a keyword */ /* Lex while checking for a keyword */
yylval.symName[0] = firstChar; yylval.symName[0] = firstChar;
uint16_t nodeID = keywordDict[0].children[dictIndex(firstChar)]; uint16_t nodeID = keywordDict[0].children[dictIndex(firstChar)];
@@ -1347,7 +1322,6 @@ static int readIdentifier(char firstChar)
i = sizeof(yylval.symName) - 1; i = sizeof(yylval.symName) - 1;
} }
yylval.symName[i] = '\0'; /* Terminate the string */ yylval.symName[i] = '\0'; /* Terminate the string */
dbgPrint("Ident/keyword = \"%s\"\n", yylval.symName);
if (keywordDict[nodeID].keyword) if (keywordDict[nodeID].keyword)
return keywordDict[nodeID].keyword->token; return keywordDict[nodeID].keyword->token;
@@ -1482,7 +1456,6 @@ static size_t appendEscapedSubstring(char const *str, size_t i)
static void readString(void) static void readString(void)
{ {
dbgPrint("Reading string\n");
lexerState->disableMacroArgs = true; lexerState->disableMacroArgs = true;
lexerState->disableInterpolation = true; lexerState->disableInterpolation = true;
@@ -1627,14 +1600,12 @@ finish:
} }
yylval.string[i] = '\0'; yylval.string[i] = '\0';
dbgPrint("Read string \"%s\"\n", yylval.string);
lexerState->disableMacroArgs = false; lexerState->disableMacroArgs = false;
lexerState->disableInterpolation = false; lexerState->disableInterpolation = false;
} }
static size_t appendStringLiteral(size_t i) static size_t appendStringLiteral(size_t i)
{ {
dbgPrint("Reading string\n");
lexerState->disableMacroArgs = true; lexerState->disableMacroArgs = true;
lexerState->disableInterpolation = true; lexerState->disableInterpolation = true;
@@ -1777,7 +1748,6 @@ finish:
} }
yylval.string[i] = '\0'; yylval.string[i] = '\0';
dbgPrint("Read string \"%s\"\n", yylval.string);
lexerState->disableMacroArgs = false; lexerState->disableMacroArgs = false;
lexerState->disableInterpolation = false; lexerState->disableInterpolation = false;
@@ -1790,9 +1760,6 @@ static int yylex_SKIP_TO_ENDC(void); // forward declaration for yylex_NORMAL
static int yylex_NORMAL(void) static int yylex_NORMAL(void)
{ {
dbgPrint("Lexing in normal mode, line=%" PRIu32 ", col=%" PRIu32 "\n",
lexer_GetLineNo(), lexer_GetColNo());
for (;;) { for (;;) {
int c = nextChar(); int c = nextChar();
char secondChar; char secondChar;
@@ -1809,12 +1776,6 @@ static int yylex_NORMAL(void)
/* Handle unambiguous single-char tokens */ /* Handle unambiguous single-char tokens */
case '^':
return T_OP_XOR;
case '+':
return T_OP_ADD;
case '-':
return T_OP_SUB;
case '~': case '~':
return T_OP_NOT; return T_OP_NOT;
@@ -1836,66 +1797,113 @@ static int yylex_NORMAL(void)
/* Handle ambiguous 1- or 2-char tokens */ /* Handle ambiguous 1- or 2-char tokens */
case '*': /* Either MUL or EXP */ case '+': /* Either += or ADD */
if (peek() == '*') { if (peek() == '=') {
shiftChar();
return T_POP_ADDEQ;
}
return T_OP_ADD;
case '-': /* Either -= or SUB */
if (peek() == '=') {
shiftChar();
return T_POP_SUBEQ;
}
return T_OP_SUB;
case '*': /* Either *=, MUL, or EXP */
switch (peek()) {
case '=':
shiftChar();
return T_POP_MULEQ;
case '*':
shiftChar(); shiftChar();
return T_OP_EXP; return T_OP_EXP;
} default:
return T_OP_MUL; return T_OP_MUL;
}
case '/': /* Either division or a block comment */ case '/': /* Either /=, DIV, or a block comment */
if (peek() == '*') { switch (peek()) {
case '=':
shiftChar();
return T_POP_DIVEQ;
case '*':
shiftChar(); shiftChar();
discardBlockComment(); discardBlockComment();
break; break;
} default:
return T_OP_DIV; return T_OP_DIV;
}
break;
case '|': /* Either binary or logical OR */ case '|': /* Either |=, binary OR, or logical OR */
if (peek() == '|') { switch (peek()) {
case '=':
shiftChar();
return T_POP_OREQ;
case '|':
shiftChar(); shiftChar();
return T_OP_LOGICOR; return T_OP_LOGICOR;
} default:
return T_OP_OR; return T_OP_OR;
}
case '=': /* Either SET alias, or EQ */ case '^': /* Either ^= or XOR */
if (peek() == '=') {
shiftChar();
return T_POP_XOREQ;
}
return T_OP_XOR;
case '=': /* Either assignment or EQ */
if (peek() == '=') { if (peek() == '=') {
shiftChar(); shiftChar();
return T_OP_LOGICEQU; return T_OP_LOGICEQU;
} }
return T_POP_EQUAL; return T_POP_EQUAL;
case '<': /* Either a LT, LTE, or left shift */ case '!': /* Either a NEQ or negation */
if (peek() == '=') {
shiftChar();
return T_OP_LOGICNE;
}
return T_OP_LOGICNOT;
/* Handle ambiguous 1-, 2-, or 3-char tokens */
case '<': /* Either <<=, LT, LTE, or left shift */
switch (peek()) { switch (peek()) {
case '=': case '=':
shiftChar(); shiftChar();
return T_OP_LOGICLE; return T_OP_LOGICLE;
case '<': case '<':
shiftChar(); shiftChar();
if (peek() == '=') {
shiftChar();
return T_POP_SHLEQ;
}
return T_OP_SHL; return T_OP_SHL;
default: default:
return T_OP_LOGICLT; return T_OP_LOGICLT;
} }
case '>': /* Either a GT, GTE, or right shift */ case '>': /* Either >>=, GT, GTE, or right shift */
switch (peek()) { switch (peek()) {
case '=': case '=':
shiftChar(); shiftChar();
return T_OP_LOGICGE; return T_OP_LOGICGE;
case '>': case '>':
shiftChar(); shiftChar();
if (peek() == '=') {
shiftChar();
return T_POP_SHREQ;
}
return T_OP_SHR; return T_OP_SHR;
default: default:
return T_OP_LOGICGT; return T_OP_LOGICGT;
} }
case '!': /* Either a NEQ, or negation */
if (peek() == '=') {
shiftChar();
return T_OP_LOGICNE;
}
return T_OP_LOGICNOT;
/* Handle colon, which may begin an anonymous label ref */ /* Handle colon, which may begin an anonymous label ref */
case ':': case ':':
@@ -1908,11 +1916,7 @@ static int yylex_NORMAL(void)
/* Handle numbers */ /* Handle numbers */
case '$': case '0': /* Decimal or fixed-point number */
yylval.constValue = readHexNumber();
return T_NUMBER;
case '0': /* Decimal number */
case '1': case '1':
case '2': case '2':
case '3': case '3':
@@ -1929,9 +1933,12 @@ static int yylex_NORMAL(void)
} }
return T_NUMBER; return T_NUMBER;
case '&': case '&': /* Either &=, binary AND, logical AND, or an octal constant */
secondChar = peek(); secondChar = peek();
if (secondChar == '&') { if (secondChar == '=') {
shiftChar();
return T_POP_ANDEQ;
} else if (secondChar == '&') {
shiftChar(); shiftChar();
return T_OP_LOGICAND; return T_OP_LOGICAND;
} else if (secondChar >= '0' && secondChar <= '7') { } else if (secondChar >= '0' && secondChar <= '7') {
@@ -1940,12 +1947,19 @@ static int yylex_NORMAL(void)
} }
return T_OP_AND; return T_OP_AND;
case '%': /* Either a modulo, or a binary constant */ case '%': /* Either %=, MOD, or a binary constant */
secondChar = peek(); secondChar = peek();
if (secondChar != binDigits[0] && secondChar != binDigits[1]) if (secondChar == '=') {
shiftChar();
return T_POP_MODEQ;
} else if (secondChar == binDigits[0] || secondChar == binDigits[1]) {
yylval.constValue = readBinaryNumber();
return T_NUMBER;
}
return T_OP_MOD; return T_OP_MOD;
yylval.constValue = readBinaryNumber(); case '$': /* Hex constant */
yylval.constValue = readHexNumber();
return T_NUMBER; return T_NUMBER;
case '`': /* Gfx constant */ case '`': /* Gfx constant */
@@ -2026,9 +2040,6 @@ static int yylex_NORMAL(void)
static int yylex_RAW(void) static int yylex_RAW(void)
{ {
dbgPrint("Lexing in raw mode, line=%" PRIu32 ", col=%" PRIu32 "\n",
lexer_GetLineNo(), lexer_GetColNo());
/* This is essentially a modified `appendStringLiteral` */ /* This is essentially a modified `appendStringLiteral` */
size_t parenDepth = 0; size_t parenDepth = 0;
size_t i = 0; size_t i = 0;
@@ -2145,8 +2156,6 @@ finish:
i--; i--;
yylval.string[i] = '\0'; yylval.string[i] = '\0';
dbgPrint("Read raw string \"%s\"\n", yylval.string);
// Returning T_COMMAs to the parser would mean that two consecutive commas // Returning T_COMMAs to the parser would mean that two consecutive commas
// (i.e. an empty argument) need to return two different tokens (T_STRING // (i.e. an empty argument) need to return two different tokens (T_STRING
// then T_COMMA) without advancing the read. To avoid this, commas in raw // then T_COMMA) without advancing the read. To avoid this, commas in raw
@@ -2186,7 +2195,6 @@ finish:
*/ */
static int skipIfBlock(bool toEndc) static int skipIfBlock(bool toEndc)
{ {
dbgPrint("Skipping IF block (toEndc = %s)\n", toEndc ? "true" : "false");
lexer_SetMode(LEXER_NORMAL); lexer_SetMode(LEXER_NORMAL);
uint32_t startingDepth = lexer_GetIFDepth(); uint32_t startingDepth = lexer_GetIFDepth();
int token; int token;
@@ -2200,11 +2208,10 @@ static int skipIfBlock(bool toEndc)
if (atLineStart) { if (atLineStart) {
int c; int c;
for (;;) { for (;; shiftChar()) {
c = peek(); c = peek();
if (!isWhitespace(c)) if (!isWhitespace(c))
break; break;
shiftChar();
} }
if (startsIdentifier(c)) { if (startsIdentifier(c)) {
@@ -2261,8 +2268,8 @@ static int skipIfBlock(bool toEndc)
} }
} while (!atLineStart); } while (!atLineStart);
} }
finish:
finish:
lexerState->disableMacroArgs = false; lexerState->disableMacroArgs = false;
lexerState->disableInterpolation = false; lexerState->disableInterpolation = false;
lexerState->atLineStart = false; lexerState->atLineStart = false;
@@ -2282,7 +2289,6 @@ static int yylex_SKIP_TO_ENDC(void)
static int yylex_SKIP_TO_ENDR(void) static int yylex_SKIP_TO_ENDR(void)
{ {
dbgPrint("Skipping remainder of REPT/FOR block\n");
lexer_SetMode(LEXER_NORMAL); lexer_SetMode(LEXER_NORMAL);
int depth = 1; int depth = 1;
bool atLineStart = lexerState->atLineStart; bool atLineStart = lexerState->atLineStart;
@@ -2348,8 +2354,8 @@ static int yylex_SKIP_TO_ENDR(void)
} }
} while (!atLineStart); } while (!atLineStart);
} }
finish:
finish:
lexerState->disableMacroArgs = false; lexerState->disableMacroArgs = false;
lexerState->disableInterpolation = false; lexerState->disableInterpolation = false;
lexerState->atLineStart = false; lexerState->atLineStart = false;
@@ -2365,17 +2371,11 @@ int yylex(void)
lexerStateEOL = NULL; lexerStateEOL = NULL;
} }
/* `lexer_SetState` updates `lexerState`, so check for EOF after it */ /* `lexer_SetState` updates `lexerState`, so check for EOF after it */
if (lexerState->lastToken == T_EOB) { if (lexerState->lastToken == T_EOB && yywrap())
if (yywrap()) {
dbgPrint("Reached end of input.\n");
return T_EOF; return T_EOF;
}
}
if (lexerState->atLineStart) {
/* Newlines read within an expansion should not increase the line count */ /* Newlines read within an expansion should not increase the line count */
if (!lexerState->expansions) if (lexerState->atLineStart && !lexerState->expansions)
nextLine(); nextLine();
}
static int (* const lexerModeFuncs[])(void) = { static int (* const lexerModeFuncs[])(void) = {
[LEXER_NORMAL] = yylex_NORMAL, [LEXER_NORMAL] = yylex_NORMAL,
@@ -2386,19 +2386,16 @@ int yylex(void)
}; };
int token = lexerModeFuncs[lexerState->mode](); int token = lexerModeFuncs[lexerState->mode]();
if (token == T_EOF) {
dbgPrint("Reached EOB!\n");
/* Captures end at their buffer's boundary no matter what */ /* Captures end at their buffer's boundary no matter what */
if (!lexerState->capturing) if (token == T_EOF && !lexerState->capturing)
token = T_EOB; token = T_EOB;
}
lexerState->lastToken = token; lexerState->lastToken = token;
lexerState->atLineStart = token == T_NEWLINE || token == T_EOB; lexerState->atLineStart = token == T_NEWLINE || token == T_EOB;
return token; return token;
} }
static char *startCapture(void) static void startCapture(struct CaptureBody *capture)
{ {
assert(!lexerState->capturing); assert(!lexerState->capturing);
lexerState->capturing = true; lexerState->capturing = true;
@@ -2406,20 +2403,35 @@ static char *startCapture(void)
lexerState->disableMacroArgs = true; lexerState->disableMacroArgs = true;
lexerState->disableInterpolation = true; lexerState->disableInterpolation = true;
capture->lineNo = lexer_GetLineNo();
if (lexerState->isMmapped && !lexerState->expansions) { if (lexerState->isMmapped && !lexerState->expansions) {
return &lexerState->ptr[lexerState->offset]; capture->body = &lexerState->ptr[lexerState->offset];
} else { } else {
lexerState->captureCapacity = 128; /* The initial size will be twice that */ lexerState->captureCapacity = 128; /* The initial size will be twice that */
assert(lexerState->captureBuf == NULL); assert(lexerState->captureBuf == NULL);
reallocCaptureBuf(); reallocCaptureBuf();
return NULL; // Indicate to retrieve the capture buffer when done capturing capture->body = NULL; // Indicate to retrieve the capture buffer when done capturing
} }
} }
static void endCapture(struct CaptureBody *capture)
{
// This being NULL means we're capturing from the capture buf, which is `realloc`'d during
// the whole capture process, and so MUST be retrieved at the end
if (!capture->body)
capture->body = lexerState->captureBuf;
capture->size = lexerState->captureSize;
lexerState->capturing = false;
lexerState->captureBuf = NULL;
lexerState->disableMacroArgs = false;
lexerState->disableInterpolation = false;
}
bool lexer_CaptureRept(struct CaptureBody *capture) bool lexer_CaptureRept(struct CaptureBody *capture)
{ {
capture->lineNo = lexer_GetLineNo(); startCapture(capture);
capture->body = startCapture();
size_t depth = 0; size_t depth = 0;
int c = EOF; int c = EOF;
@@ -2459,7 +2471,7 @@ bool lexer_CaptureRept(struct CaptureBody *capture)
} }
/* Just consume characters until EOL or EOF */ /* Just consume characters until EOL or EOF */
for (;;) { for (;; c = nextChar()) {
if (c == EOF) { if (c == EOF) {
error("Unterminated REPT/FOR block\n"); error("Unterminated REPT/FOR block\n");
goto finish; goto finish;
@@ -2467,20 +2479,12 @@ bool lexer_CaptureRept(struct CaptureBody *capture)
handleCRLF(c); handleCRLF(c);
break; break;
} }
c = nextChar();
} }
} }
finish: finish:
// This being NULL means we're capturing from the capture buf, which is `realloc`'d during endCapture(capture);
// the whole capture process, and so MUST be retrieved at the end /* ENDR or EOF puts us past the start of the line */
if (!capture->body)
capture->body = lexerState->captureBuf;
capture->size = lexerState->captureSize;
lexerState->capturing = false;
lexerState->captureBuf = NULL;
lexerState->disableMacroArgs = false;
lexerState->disableInterpolation = false;
lexerState->atLineStart = false; lexerState->atLineStart = false;
/* Returns true if an ENDR terminated the block, false if it reached EOF first */ /* Returns true if an ENDR terminated the block, false if it reached EOF first */
@@ -2489,15 +2493,14 @@ finish:
bool lexer_CaptureMacroBody(struct CaptureBody *capture) bool lexer_CaptureMacroBody(struct CaptureBody *capture)
{ {
capture->lineNo = lexer_GetLineNo(); startCapture(capture);
capture->body = startCapture();
int c = EOF;
/* If the file is `mmap`ed, we need not to unmap it to keep access to the macro */ /* If the file is `mmap`ed, we need not to unmap it to keep access to the macro */
if (lexerState->isMmapped) if (lexerState->isMmapped)
lexerState->isReferenced = true; lexerState->isReferenced = true;
int c = EOF;
/* /*
* Due to parser internals, it reads the EOL after the expression before calling this. * Due to parser internals, it reads the EOL after the expression before calling this.
* Thus, we don't need to keep one in the buffer afterwards. * Thus, we don't need to keep one in the buffer afterwards.
@@ -2524,7 +2527,7 @@ bool lexer_CaptureMacroBody(struct CaptureBody *capture)
} }
/* Just consume characters until EOL or EOF */ /* Just consume characters until EOL or EOF */
for (;;) { for (;; c = nextChar()) {
if (c == EOF) { if (c == EOF) {
error("Unterminated macro definition\n"); error("Unterminated macro definition\n");
goto finish; goto finish;
@@ -2532,20 +2535,12 @@ bool lexer_CaptureMacroBody(struct CaptureBody *capture)
handleCRLF(c); handleCRLF(c);
break; break;
} }
c = nextChar();
} }
} }
finish: finish:
// This being NULL means we're capturing from the capture buf, which is `realloc`'d during endCapture(capture);
// the whole capture process, and so MUST be retrieved at the end /* ENDM or EOF puts us past the start of the line */
if (!capture->body)
capture->body = lexerState->captureBuf;
capture->size = lexerState->captureSize;
lexerState->capturing = false;
lexerState->captureBuf = NULL;
lexerState->disableMacroArgs = false;
lexerState->disableInterpolation = false;
lexerState->atLineStart = false; lexerState->atLineStart = false;
/* Returns true if an ENDM terminated the block, false if it reached EOF first */ /* Returns true if an ENDM terminated the block, false if it reached EOF first */

View File

@@ -30,10 +30,10 @@
#include "asm/warning.h" #include "asm/warning.h"
#include "parser.h" #include "parser.h"
#include "extern/err.h"
#include "extern/getopt.h" #include "extern/getopt.h"
#include "helpers.h" #include "helpers.h"
#include "error.h"
#include "version.h" #include "version.h"
#ifdef __clang__ #ifdef __clang__
@@ -45,17 +45,13 @@
#ifdef __SANITIZE_ADDRESS__ #ifdef __SANITIZE_ADDRESS__
// There are known, non-trivial to fix leaks. We would still like to have `make develop' // There are known, non-trivial to fix leaks. We would still like to have `make develop'
// detect memory corruption, though. // detect memory corruption, though.
const char *__asan_default_options(void) { return "detect_leaks=0"; } char const *__asan_default_options(void) { return "detect_leaks=0"; }
#endif #endif
// Old Bison versions (confirmed for 2.3) do not forward-declare `yyparse` in the generated header // Old Bison versions (confirmed for 2.3) do not forward-declare `yyparse` in the generated header
// Unfortunately, macOS still ships 2.3, which is from 2008... // Unfortunately, macOS still ships 2.3, which is from 2008...
int yyparse(void); int yyparse(void);
#if defined(YYDEBUG) && YYDEBUG
extern int yydebug;
#endif
FILE * dependfile; FILE * dependfile;
bool generatedMissingIncludes; bool generatedMissingIncludes;
bool failedOnMissingInclude; bool failedOnMissingInclude;
@@ -68,13 +64,13 @@ bool verbose;
bool warnings; /* True to enable warnings, false to disable them. */ bool warnings; /* True to enable warnings, false to disable them. */
/* Escapes Make-special chars from a string */ /* Escapes Make-special chars from a string */
static char *make_escape(const char *str) static char *make_escape(char const *str)
{ {
char * const escaped_str = malloc(strlen(str) * 2 + 1); char * const escaped_str = malloc(strlen(str) * 2 + 1);
char *dest = escaped_str; char *dest = escaped_str;
if (escaped_str == NULL) if (escaped_str == NULL)
err(1, "%s: Failed to allocate memory", __func__); err("%s: Failed to allocate memory", __func__);
while (*str) { while (*str) {
/* All dollars needs to be doubled */ /* All dollars needs to be doubled */
@@ -161,10 +157,6 @@ int main(int argc, char *argv[])
dependfile = NULL; dependfile = NULL;
#if defined(YYDEBUG) && YYDEBUG
yydebug = 1;
#endif
// Perform some init for below // Perform some init for below
sym_Init(now); sym_Init(now);
@@ -192,7 +184,7 @@ int main(int argc, char *argv[])
if (strlen(musl_optarg) == 2) if (strlen(musl_optarg) == 2)
opt_B(&musl_optarg[1]); opt_B(&musl_optarg[1]);
else else
errx(1, "Must specify exactly 2 characters for option 'b'"); errx("Must specify exactly 2 characters for option 'b'");
break; break;
char *equals; char *equals;
@@ -214,7 +206,7 @@ int main(int argc, char *argv[])
if (strlen(musl_optarg) == 4) if (strlen(musl_optarg) == 4)
opt_G(&musl_optarg[1]); opt_G(&musl_optarg[1]);
else else
errx(1, "Must specify exactly 4 characters for option 'g'"); errx("Must specify exactly 4 characters for option 'g'");
break; break;
case 'h': case 'h':
@@ -235,7 +227,7 @@ int main(int argc, char *argv[])
else else
dependfile = fopen(musl_optarg, "w"); dependfile = fopen(musl_optarg, "w");
if (dependfile == NULL) if (dependfile == NULL)
err(1, "Could not open dependfile %s", musl_optarg); err("Could not open dependfile %s", musl_optarg);
break; break;
case 'o': case 'o':
@@ -247,10 +239,10 @@ int main(int argc, char *argv[])
fill = strtoul(musl_optarg, &ep, 0); fill = strtoul(musl_optarg, &ep, 0);
if (musl_optarg[0] == '\0' || *ep != '\0') if (musl_optarg[0] == '\0' || *ep != '\0')
errx(1, "Invalid argument for option 'p'"); errx("Invalid argument for option 'p'");
if (fill < 0 || fill > 0xFF) if (fill < 0 || fill > 0xFF)
errx(1, "Argument for option 'p' must be between 0 and 0xFF"); errx("Argument for option 'p' must be between 0 and 0xFF");
opt_P(fill); opt_P(fill);
break; break;
@@ -259,7 +251,7 @@ int main(int argc, char *argv[])
maxDepth = strtoul(musl_optarg, &ep, 0); maxDepth = strtoul(musl_optarg, &ep, 0);
if (musl_optarg[0] == '\0' || *ep != '\0') if (musl_optarg[0] == '\0' || *ep != '\0')
errx(1, "Invalid argument for option 'r'"); errx("Invalid argument for option 'r'");
break; break;
case 'V': case 'V':
@@ -288,33 +280,23 @@ int main(int argc, char *argv[])
generatePhonyDeps = true; generatePhonyDeps = true;
break; break;
char *newTarget;
case 'Q': case 'Q':
case 'T': case 'T':
if (musl_optind == argc) newTarget = musl_optarg;
errx(1, "-M%c takes a target file name argument", depType);
ep = musl_optarg;
if (depType == 'Q') if (depType == 'Q')
ep = make_escape(ep); newTarget = make_escape(newTarget);
size_t newTargetLen = strlen(newTarget) + 1; // Plus the space
targetFileNameLen += strlen(ep) + 1;
if (!targetFileName) {
/* On first alloc, make an empty str */
targetFileName = malloc(targetFileNameLen + 1);
if (targetFileName)
*targetFileName = '\0';
} else {
targetFileName = realloc(targetFileName, targetFileName = realloc(targetFileName,
targetFileNameLen + 1); targetFileNameLen + newTargetLen + 1);
}
if (targetFileName == NULL) if (targetFileName == NULL)
err(1, "Cannot append new file to target file list"); err("Cannot append new file to target file list");
strcat(targetFileName, ep); memcpy(&targetFileName[targetFileNameLen], newTarget, newTargetLen);
if (depType == 'Q') if (depType == 'Q')
free(ep); free(newTarget);
char *ptr = targetFileName + strlen(targetFileName); targetFileNameLen += newTargetLen;
targetFileName[targetFileNameLen - 1] = ' ';
*ptr++ = ' ';
*ptr = '\0';
break; break;
} }
break; break;
@@ -328,6 +310,8 @@ int main(int argc, char *argv[])
if (targetFileName == NULL) if (targetFileName == NULL)
targetFileName = objectName; targetFileName = objectName;
else
targetFileName[targetFileNameLen - 1] = '\0'; // Overwrite the last space
if (argc == musl_optind) { if (argc == musl_optind) {
fputs("FATAL: No input files\n", stderr); fputs("FATAL: No input files\n", stderr);
@@ -344,7 +328,7 @@ int main(int argc, char *argv[])
if (dependfile) { if (dependfile) {
if (!targetFileName) if (!targetFileName)
errx(1, "Dependency files can only be created if a target file is specified with either -o, -MQ or -MT\n"); errx("Dependency files can only be created if a target file is specified with either -o, -MQ or -MT");
fprintf(dependfile, "%s: %s\n", targetFileName, mainFileName); fprintf(dependfile, "%s: %s\n", targetFileName, mainFileName);
} }
@@ -365,7 +349,7 @@ int main(int argc, char *argv[])
sect_CheckUnionClosed(); sect_CheckUnionClosed();
if (nbErrors != 0) if (nbErrors != 0)
errx(1, "Assembly aborted (%u error%s)!", nbErrors, errx("Assembly aborted (%u error%s)!", nbErrors,
nbErrors == 1 ? "" : "s"); 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

View File

@@ -17,7 +17,8 @@ struct OptStackEntry {
bool haltnop; bool haltnop;
bool optimizeLoads; bool optimizeLoads;
bool warningsAreErrors; bool warningsAreErrors;
enum WarningState warningStates[NB_WARNINGS]; // Don't be confused: we use the size of the **global variable** `warningStates`!
enum WarningState warningStates[sizeof(warningStates)];
struct OptStackEntry *next; struct OptStackEntry *next;
}; };
@@ -48,7 +49,7 @@ void opt_L(bool optimize)
optimizeLoads = optimize; optimizeLoads = optimize;
} }
void opt_W(char const *flag) void opt_W(char *flag)
{ {
processWarningFlag(flag); processWarningFlag(flag);
} }

View File

@@ -27,8 +27,7 @@
#include "asm/symbol.h" #include "asm/symbol.h"
#include "asm/warning.h" #include "asm/warning.h"
#include "extern/err.h" #include "error.h"
#include "linkdefs.h" #include "linkdefs.h"
#include "platform.h" // strdup #include "platform.h" // strdup
@@ -528,7 +527,7 @@ void out_WriteObject(void)
f = fdopen(1, "wb"); f = fdopen(1, "wb");
if (!f) if (!f)
err(1, "Couldn't write file '%s'", objectName); err("Couldn't write file '%s'", objectName);
/* Also write symbols that weren't written above */ /* Also write symbols that weren't written above */
sym_ForEach(registerUnregisteredSymbol, NULL); sym_ForEach(registerUnregisteredSymbol, NULL);

View File

@@ -52,22 +52,27 @@ static void lowerstring(char *dest, char const *src)
*dest = '\0'; *dest = '\0';
} }
static uint32_t str2int2(uint8_t *s, int32_t length) static uint32_t str2int2(uint8_t *s, uint32_t length)
{ {
int32_t i; if (length > 4)
warning(WARNING_NUMERIC_STRING_1,
"Treating string as a number ignores first %" PRIu32 " character%s\n",
length - 4, length == 5 ? "" : "s");
else if (length > 1)
warning(WARNING_NUMERIC_STRING_2,
"Treating %" PRIu32 "-character string as a number\n", length);
uint32_t r = 0; uint32_t r = 0;
i = length < 4 ? 0 : length - 4; for (uint32_t i = length < 4 ? 0 : length - 4; i < length; i++) {
while (i < length) {
r <<= 8; r <<= 8;
r |= s[i]; r |= s[i];
i++;
} }
return r; return r;
} }
static char *strrstr(char *s1, char *s2) static const char *strrstr(char const *s1, char const *s2)
{ {
size_t len1 = strlen(s1); size_t len1 = strlen(s1);
size_t len2 = strlen(s2); size_t len2 = strlen(s2);
@@ -75,7 +80,7 @@ static char *strrstr(char *s1, char *s2)
if (len2 > len1) if (len2 > len1)
return NULL; return NULL;
for (char *p = s1 + len1 - len2; p >= s1; p--) for (char const *p = s1 + len1 - len2; p >= s1; p--)
if (!strncmp(p, s2, len2)) if (!strncmp(p, s2, len2))
return p; return p;
@@ -359,6 +364,17 @@ static void strfmt(char *dest, size_t destLen, char const *fmt, size_t nbArgs, s
dest[i] = '\0'; dest[i] = '\0';
} }
static void compoundAssignment(const char *symName, enum RPNCommand op, int32_t constValue) {
struct Expression oldExpr, constExpr, newExpr;
int32_t newValue;
rpn_Symbol(&oldExpr, symName);
rpn_Number(&constExpr, constValue);
rpn_BinaryOp(op, &newExpr, &oldExpr, &constExpr);
newValue = rpn_GetConstVal(&newExpr);
sym_AddVar(symName, newValue);
}
static void initDsArgList(struct DsArgList *args) static void initDsArgList(struct DsArgList *args)
{ {
args->nbArgs = 0; args->nbArgs = 0;
@@ -463,6 +479,7 @@ enum {
char string[MAXSTRLEN + 1]; char string[MAXSTRLEN + 1];
struct Expression expr; struct Expression expr;
int32_t constValue; int32_t constValue;
enum RPNCommand compoundEqual;
enum SectionModifier sectMod; enum SectionModifier sectMod;
struct SectionSpec sectSpec; struct SectionSpec sectSpec;
struct MacroArgs *macroArg; struct MacroArgs *macroArg;
@@ -574,6 +591,12 @@ enum {
%token T_POP_EQUAL "=" %token T_POP_EQUAL "="
%token T_POP_EQUS "EQUS" %token T_POP_EQUS "EQUS"
%token T_POP_ADDEQ "+=" T_POP_SUBEQ "-="
%token T_POP_MULEQ "*=" T_POP_DIVEQ "/=" T_POP_MODEQ "%="
%token T_POP_OREQ "|=" T_POP_XOREQ "^=" T_POP_ANDEQ "&="
%token T_POP_SHLEQ "<<=" T_POP_SHREQ ">>="
%type <compoundEqual> compoundeq
%token T_POP_INCLUDE "INCLUDE" %token T_POP_INCLUDE "INCLUDE"
%token T_POP_PRINT "PRINT" T_POP_PRINTLN "PRINTLN" %token T_POP_PRINT "PRINT" T_POP_PRINTLN "PRINTLN"
%token T_POP_PRINTF "PRINTF" T_POP_PRINTT "PRINTT" T_POP_PRINTV "PRINTV" T_POP_PRINTI "PRINTI" %token T_POP_PRINTF "PRINTF" T_POP_PRINTT "PRINTT" T_POP_PRINTV "PRINTV" T_POP_PRINTI "PRINTI"
@@ -653,6 +676,7 @@ enum {
%type <constValue> reg_ss %type <constValue> reg_ss
%type <constValue> reg_rr %type <constValue> reg_rr
%type <constValue> reg_tt %type <constValue> reg_tt
%type <constValue> ccode_expr
%type <constValue> ccode %type <constValue> ccode
%type <expr> op_a_n %type <expr> op_a_n
%type <constValue> op_a_r %type <constValue> op_a_r
@@ -684,8 +708,26 @@ plain_directive : label
line : plain_directive endofline line : plain_directive endofline
| line_directive /* Directives that manage newlines themselves */ | line_directive /* Directives that manage newlines themselves */
| error endofline { /* Continue parsing the next line on a syntax error */ /* Continue parsing the next line on a syntax error */
| error {
lexer_SetMode(LEXER_NORMAL);
lexer_ToggleStringExpansion(true);
} endofline {
fstk_StopRept(); fstk_StopRept();
yyerrok;
}
/* Hint about unindented macros parsed as labels */
| T_LABEL error {
lexer_SetMode(LEXER_NORMAL);
lexer_ToggleStringExpansion(true);
} endofline {
struct Symbol *macro = sym_FindExactSymbol($1);
if (macro && macro->type == SYM_MACRO)
fprintf(stderr,
" To invoke `%s` as a macro it must be indented\n", $1);
fstk_StopRept();
yyerrok;
} }
; ;
@@ -870,13 +912,27 @@ directive : endc
trailing_comma : %empty | T_COMMA trailing_comma : %empty | T_COMMA
; ;
compoundeq : T_POP_ADDEQ { $$ = RPN_ADD; }
| T_POP_SUBEQ { $$ = RPN_SUB; }
| T_POP_MULEQ { $$ = RPN_MUL; }
| T_POP_DIVEQ { $$ = RPN_DIV; }
| T_POP_MODEQ { $$ = RPN_MOD; }
| T_POP_XOREQ { $$ = RPN_XOR; }
| T_POP_OREQ { $$ = RPN_OR; }
| T_POP_ANDEQ { $$ = RPN_AND; }
| T_POP_SHLEQ { $$ = RPN_SHL; }
| T_POP_SHREQ { $$ = RPN_SHR; }
;
equ : T_LABEL T_POP_EQU const { sym_AddEqu($1, $3); } equ : T_LABEL T_POP_EQU const { sym_AddEqu($1, $3); }
; ;
set_or_equal : T_POP_SET | T_POP_EQUAL set : T_LABEL T_POP_EQUAL const { sym_AddVar($1, $3); }
; | T_LABEL compoundeq const { compoundAssignment($1, $2, $3); }
| T_LABEL T_POP_SET const {
set : T_LABEL set_or_equal const { sym_AddSet($1, $3); } warning(WARNING_OBSOLETE, "`SET` for variables is deprecated; use `=`\n");
sym_AddVar($1, $3);
}
; ;
equs : T_LABEL T_POP_EQUS string { sym_AddString($1, $3); } equs : T_LABEL T_POP_EQUS string { sym_AddString($1, $3); }
@@ -884,19 +940,19 @@ equs : T_LABEL T_POP_EQUS string { sym_AddString($1, $3); }
rb : T_LABEL T_POP_RB rs_uconst { rb : T_LABEL T_POP_RB rs_uconst {
sym_AddEqu($1, sym_GetConstantValue("_RS")); sym_AddEqu($1, sym_GetConstantValue("_RS"));
sym_AddSet("_RS", sym_GetConstantValue("_RS") + $3); sym_AddVar("_RS", sym_GetConstantValue("_RS") + $3);
} }
; ;
rw : T_LABEL T_POP_RW rs_uconst { rw : T_LABEL T_POP_RW rs_uconst {
sym_AddEqu($1, sym_GetConstantValue("_RS")); sym_AddEqu($1, sym_GetConstantValue("_RS"));
sym_AddSet("_RS", sym_GetConstantValue("_RS") + 2 * $3); sym_AddVar("_RS", sym_GetConstantValue("_RS") + 2 * $3);
} }
; ;
rl : T_LABEL T_Z80_RL rs_uconst { rl : T_LABEL T_Z80_RL rs_uconst {
sym_AddEqu($1, sym_GetConstantValue("_RS")); sym_AddEqu($1, sym_GetConstantValue("_RS"));
sym_AddSet("_RS", sym_GetConstantValue("_RS") + 4 * $3); sym_AddVar("_RS", sym_GetConstantValue("_RS") + 4 * $3);
} }
; ;
@@ -1060,15 +1116,13 @@ macrodef : T_POP_MACRO {
} }
; ;
rsset : T_POP_RSSET uconst { sym_AddSet("_RS", $2); } rsset : T_POP_RSSET uconst { sym_AddVar("_RS", $2); }
; ;
rsreset : T_POP_RSRESET { sym_AddSet("_RS", 0); } rsreset : T_POP_RSRESET { sym_AddVar("_RS", 0); }
; ;
rs_uconst : %empty { rs_uconst : %empty { $$ = 1; }
$$ = 1;
}
| uconst | uconst
; ;
@@ -1114,50 +1168,48 @@ dl : T_POP_DL { sect_Skip(4, false); }
| T_POP_DL constlist_32bit trailing_comma | T_POP_DL constlist_32bit trailing_comma
; ;
def_equ : def_id T_POP_EQU const { def_equ : def_id T_POP_EQU const { sym_AddEqu($1, $3); }
sym_AddEqu($1, $3);
}
; ;
redef_equ : redef_id T_POP_EQU const { redef_equ : redef_id T_POP_EQU const { sym_RedefEqu($1, $3); }
sym_RedefEqu($1, $3);
}
; ;
def_set : def_id set_or_equal const { def_set : def_id T_POP_EQUAL const { sym_AddVar($1, $3); }
sym_AddSet($1, $3); | redef_id T_POP_EQUAL const { sym_AddVar($1, $3); }
| def_id compoundeq const { compoundAssignment($1, $2, $3); }
| redef_id compoundeq const { compoundAssignment($1, $2, $3); }
| def_id T_POP_SET const {
warning(WARNING_OBSOLETE, "`SET` for variables is deprecated; use `=`\n");
sym_AddVar($1, $3);
} }
| redef_id set_or_equal const { | redef_id T_POP_SET const {
sym_AddSet($1, $3); warning(WARNING_OBSOLETE, "`SET` for variables is deprecated; use `=`\n");
sym_AddVar($1, $3);
} }
; ;
def_rb : def_id T_POP_RB rs_uconst { def_rb : def_id T_POP_RB rs_uconst {
sym_AddEqu($1, sym_GetConstantValue("_RS")); sym_AddEqu($1, sym_GetConstantValue("_RS"));
sym_AddSet("_RS", sym_GetConstantValue("_RS") + $3); sym_AddVar("_RS", sym_GetConstantValue("_RS") + $3);
} }
; ;
def_rw : def_id T_POP_RW rs_uconst { def_rw : def_id T_POP_RW rs_uconst {
sym_AddEqu($1, sym_GetConstantValue("_RS")); sym_AddEqu($1, sym_GetConstantValue("_RS"));
sym_AddSet("_RS", sym_GetConstantValue("_RS") + 2 * $3); sym_AddVar("_RS", sym_GetConstantValue("_RS") + 2 * $3);
} }
; ;
def_rl : def_id T_Z80_RL rs_uconst { def_rl : def_id T_Z80_RL rs_uconst {
sym_AddEqu($1, sym_GetConstantValue("_RS")); sym_AddEqu($1, sym_GetConstantValue("_RS"));
sym_AddSet("_RS", sym_GetConstantValue("_RS") + 4 * $3); sym_AddVar("_RS", sym_GetConstantValue("_RS") + 4 * $3);
} }
; ;
def_equs : def_id T_POP_EQUS string { def_equs : def_id T_POP_EQUS string { sym_AddString($1, $3); }
sym_AddString($1, $3);
}
; ;
redef_equs : redef_id T_POP_EQUS string { redef_equs : redef_id T_POP_EQUS string { sym_RedefString($1, $3); }
sym_RedefString($1, $3);
}
; ;
purge : T_POP_PURGE { purge : T_POP_PURGE {
@@ -1260,13 +1312,13 @@ printv : T_POP_PRINTV const {
; ;
printi : T_POP_PRINTI const { printi : T_POP_PRINTI const {
warning(WARNING_OBSOLETE, "`PRINTI` is deprecated; use `PRINT` with `STRFMT`\n"); warning(WARNING_OBSOLETE, "`PRINTI` is deprecated; use `PRINT` with `STRFMT` \"%%d\"\n");
printf("%" PRId32, $2); printf("%" PRId32, $2);
} }
; ;
printf : T_POP_PRINTF const { printf : T_POP_PRINTF const {
warning(WARNING_OBSOLETE, "`PRINTF` is deprecated; use `PRINT` with `STRFMT`\n"); warning(WARNING_OBSOLETE, "`PRINTF` is deprecated; use `PRINT` with `STRFMT` \"%%f\"\n");
fix_Print($2); fix_Print($2);
} }
; ;
@@ -1292,7 +1344,7 @@ constlist_8bit_entry : reloc_8bit_no_str {
} }
| string { | string {
uint8_t *output = malloc(strlen($1)); /* Cannot be larger than that */ uint8_t *output = malloc(strlen($1)); /* Cannot be larger than that */
int32_t length = charmap_Convert($1, output); size_t length = charmap_Convert($1, output);
sect_AbsByteGroup(output, length); sect_AbsByteGroup(output, length);
free(output); free(output);
@@ -1308,7 +1360,7 @@ constlist_16bit_entry : reloc_16bit_no_str {
} }
| string { | string {
uint8_t *output = malloc(strlen($1)); /* Cannot be larger than that */ uint8_t *output = malloc(strlen($1)); /* Cannot be larger than that */
int32_t length = charmap_Convert($1, output); size_t length = charmap_Convert($1, output);
sect_AbsWordGroup(output, length); sect_AbsWordGroup(output, length);
free(output); free(output);
@@ -1325,7 +1377,7 @@ constlist_32bit_entry : relocexpr_no_str {
| string { | string {
// Charmaps cannot increase the length of a string // Charmaps cannot increase the length of a string
uint8_t *output = malloc(strlen($1)); uint8_t *output = malloc(strlen($1));
int32_t length = charmap_Convert($1, output); size_t length = charmap_Convert($1, output);
sect_AbsLongGroup(output, length); sect_AbsLongGroup(output, length);
free(output); free(output);
@@ -1371,7 +1423,7 @@ relocexpr : relocexpr_no_str
| string { | string {
// Charmaps cannot increase the length of a string // Charmaps cannot increase the length of a string
uint8_t *output = malloc(strlen($1)); uint8_t *output = malloc(strlen($1));
int32_t length = charmap_Convert($1, output); uint32_t length = charmap_Convert($1, output);
uint32_t r = str2int2(output, length); uint32_t r = str2int2(output, length);
free(output); free(output);
@@ -1509,12 +1561,12 @@ relocexpr_no_str : scoped_anon_id { rpn_Symbol(&$$, $1); }
rpn_Number(&$$, strcmp($3, $5)); rpn_Number(&$$, strcmp($3, $5));
} }
| T_OP_STRIN T_LPAREN string T_COMMA string T_RPAREN { | T_OP_STRIN T_LPAREN string T_COMMA string T_RPAREN {
char *p = strstr($3, $5); char const *p = strstr($3, $5);
rpn_Number(&$$, p ? p - $3 + 1 : 0); rpn_Number(&$$, p ? p - $3 + 1 : 0);
} }
| T_OP_STRRIN T_LPAREN string T_COMMA string T_RPAREN { | T_OP_STRRIN T_LPAREN string T_COMMA string T_RPAREN {
char *p = strrstr($3, $5); char const *p = strrstr($3, $5);
rpn_Number(&$$, p ? p - $3 + 1 : 0); rpn_Number(&$$, p ? p - $3 + 1 : 0);
} }
@@ -1757,7 +1809,7 @@ z80_call : T_Z80_CALL reloc_16bit {
sect_AbsByte(0xCD); sect_AbsByte(0xCD);
sect_RelWord(&$2, 1); sect_RelWord(&$2, 1);
} }
| T_Z80_CALL ccode T_COMMA reloc_16bit { | T_Z80_CALL ccode_expr T_COMMA reloc_16bit {
sect_AbsByte(0xC4 | ($2 << 3)); sect_AbsByte(0xC4 | ($2 << 3));
sect_RelWord(&$4, 1); sect_RelWord(&$4, 1);
} }
@@ -1804,7 +1856,7 @@ z80_jp : T_Z80_JP reloc_16bit {
sect_AbsByte(0xC3); sect_AbsByte(0xC3);
sect_RelWord(&$2, 1); sect_RelWord(&$2, 1);
} }
| T_Z80_JP ccode T_COMMA reloc_16bit { | T_Z80_JP ccode_expr T_COMMA reloc_16bit {
sect_AbsByte(0xC2 | ($2 << 3)); sect_AbsByte(0xC2 | ($2 << 3));
sect_RelWord(&$4, 1); sect_RelWord(&$4, 1);
} }
@@ -1817,7 +1869,7 @@ z80_jr : T_Z80_JR reloc_16bit {
sect_AbsByte(0x18); sect_AbsByte(0x18);
sect_PCRelByte(&$2, 1); sect_PCRelByte(&$2, 1);
} }
| T_Z80_JR ccode T_COMMA reloc_16bit { | T_Z80_JR ccode_expr T_COMMA reloc_16bit {
sect_AbsByte(0x20 | ($2 << 3)); sect_AbsByte(0x20 | ($2 << 3));
sect_PCRelByte(&$4, 1); sect_PCRelByte(&$4, 1);
} }
@@ -1999,7 +2051,7 @@ z80_res : T_Z80_RES const_3bit T_COMMA reg_r {
; ;
z80_ret : T_Z80_RET { sect_AbsByte(0xC9); } z80_ret : T_Z80_RET { sect_AbsByte(0xC9); }
| T_Z80_RET ccode { sect_AbsByte(0xC0 | ($2 << 3)); } | T_Z80_RET ccode_expr { sect_AbsByte(0xC0 | ($2 << 3)); }
; ;
z80_reti : T_Z80_RETI { sect_AbsByte(0xD9); } z80_reti : T_Z80_RETI { sect_AbsByte(0xD9); }
@@ -2154,6 +2206,12 @@ T_MODE_L : T_TOKEN_L
| T_OP_LOW T_LPAREN T_MODE_HL T_RPAREN | T_OP_LOW T_LPAREN T_MODE_HL T_RPAREN
; ;
ccode_expr : ccode
| T_OP_LOGICNOT ccode_expr {
$$ = $2 ^ 1;
}
;
ccode : T_CC_NZ { $$ = CC_NZ; } ccode : T_CC_NZ { $$ = CC_NZ; }
| T_CC_Z { $$ = CC_Z; } | T_CC_Z { $$ = CC_Z; }
| T_CC_NC { $$ = CC_NC; } | T_CC_NC { $$ = CC_NC; }

View File

@@ -214,7 +214,7 @@ Warn when re-defining a charmap mapping.
This warning is enabled by This warning is enabled by
.Fl Wall . .Fl Wall .
.It Fl Wdiv .It Fl Wdiv
Warn when dividing the smallest negative integer by -1, which yields itself due to integer overflow. Warn when dividing the smallest negative integer (-2**31) by -1, which yields itself due to integer overflow.
.It Fl Wempty-macro-arg .It Fl Wempty-macro-arg
Warn when a macro argument is empty. Warn when a macro argument is empty.
This warning is enabled by This warning is enabled by
@@ -243,15 +243,37 @@ Warn when obsolete constructs such as the
constant or constant or
.Ic PRINTT .Ic PRINTT
directive are encountered. directive are encountered.
.It Fl Wnumeric-string=
Warn when a multi-character string is treated as a number.
.Fl Wnumeric-string=0
or
.Fl Wno-numeric-string
disables this warning.
.Fl Wnumeric-string=1
or just
.Fl Wnumeric-string
warns about strings longer than four characters, since four or fewer characters fit within a 32-bit integer.
.Fl Wnumeric-string=2
warns about any multi-character string.
.It Fl Wshift .It Fl Wshift
Warn when shifting right a negative value. Warn when shifting right a negative value.
Use a division by 2**N instead. Use a division by 2**N instead.
.It Fl Wshift-amount .It Fl Wshift-amount
Warn when a shift's operand is negative or greater than 32. Warn when a shift's operand is negative or greater than 32.
.It Fl Wno-truncation .It Fl Wtruncation=
Warn when an implicit truncation (for example, Warn when an implicit truncation (for example,
.Ic db ) .Ic db
loses some bits. to an 8-bit value) loses some bits.
.Fl Wtruncation=0
or
.Fl Wno-truncation
disables this warning.
.Fl Wtruncation=1
warns when an N-bit value's absolute value is 2**N or greater.
.Fl Wtruncation=2
or just
.Fl Wtruncation
also warns when an N-bit value is less than -2**(N-1), which will not fit in two's complement encoding.
.It Fl Wno-user .It Fl Wno-user
Warn when the Warn when the
.Ic WARN .Ic WARN

View File

@@ -66,19 +66,135 @@ To do so, put a backslash at the end of the line:
DB 1, 2, 3,\ \[rs] DB 1, 2, 3,\ \[rs]
4, 5, 6,\ \[rs]\ ;\ Put it before any comments 4, 5, 6,\ \[rs]\ ;\ Put it before any comments
7, 8, 9 7, 8, 9
DB "Hello,\ \[rs]\ \ ;\ Space before the \[rs] is included
world!"\ \ \ \ \ \ \ \ \ \ \ ;\ Any leading space is included
.Ed
.Ss Symbol interpolation
A funky feature is
.Ql {symbol}
within a string, called
.Dq symbol interpolation .
This will paste the contents of
.Ql symbol
as if they were part of the source file.
If it is a string symbol, its characters are simply inserted as-is.
If it is a numeric symbol, its value is converted to hexadecimal notation with a dollar sign
.Sq $
prepended.
.Pp
Symbol interpolations can be nested, too!
.Bd -literal -offset indent
DEF topic EQUS "life, the universe, and \[rs]"everything\[rs]""
DEF meaning EQUS "answer"
;\ Defines answer = 42
DEF {meaning} = 42
;\ Prints "The answer to life, the universe, and "everything" is $2A"
PRINTLN "The {meaning} to {topic} is {{meaning}}"
PURGE topic, meaning, {meaning}
.Ed .Ed
.Pp .Pp
This works anywhere in the code except inside of strings. Symbols can be
To split strings it is needed to use .Em interpolated
.Fn STRCAT even in the contexts that disable automatic
like this: .Em expansion
of string constants:
.Ql name
will be expanded in all of
.Ql DEF({name}) ,
.Ql DEF {name} EQU/=/EQUS/etc ... ,
.Ql PURGE {name} ,
and
.Ql MACRO {name} ,
but, for example, won't be in
.Ql DEF(name) .
.Pp
It's possible to change the way symbols are printed by specifying a print format like so:
.Ql {fmt:symbol} .
The
.Ql fmt
specifier consists of these parts:
.Ql <sign><prefix><align><pad><width><frac><type> .
These parts are:
.Bl -column "<prefix>"
.It Sy Part Ta Sy Meaning
.It Ql <sign> Ta May be
.Ql +
or
.Ql \ .
If specified, prints this character in front of non-negative numbers.
.It Ql <prefix> Ta May be
.Ql # .
If specified, prints the appropriate prefix for numbers,
.Ql $ ,
.Ql & ,
or
.Ql % .
.It Ql <align> Ta May be
.Ql - .
If specified, aligns left instead of right.
.It Ql <pad> Ta May be
.Ql 0 .
If specified, pads right-aligned numbers with zeros instead of spaces.
.It Ql <width> Ta May be one or more
.Ql 0
\[en]
.Ql 9 .
If specified, pads the value to this width, right-aligned with spaces by default.
.It Ql <frac> Ta May be
.Ql \&.
followed by one or more
.Ql 0
\[en]
.Ql 9 .
If specified, prints this many digits of a fixed-point fraction.
Defaults to 5 digits, maximum 255 digits.
.It Ql <type> Ta Specifies the type of value.
.El
.Pp
All the format specifier parts are optional except the
.Ql <type> .
Valid print types are:
.Bl -column -offset indent "Print type" "Lowercase hexadecimal" "Example"
.It Sy Print type Ta Sy Format Ta Sy Example
.It Ql d Ta Signed decimal Ta -42
.It Ql u Ta Unsigned decimal Ta 42
.It Ql x Ta Lowercase hexadecimal Ta 2a
.It Ql X Ta Uppercase hexadecimal Ta 2A
.It Ql b Ta Binary Ta 101010
.It Ql o Ta Octal Ta 52
.It Ql f Ta Fixed-point Ta 1234.56789
.It Ql s Ta String Ta \&"example\&"
.El
.Pp
Examples:
.Bd -literal -offset indent .Bd -literal -offset indent
db STRCAT("Hello ",\ \[rs] SECTION "Test", ROM0[2]
"world!") X: ;\ This works with labels **whose address is known**
Y = 3 ;\ This also works with variables
SUM equ X + Y ;\ And likewise with numeric constants
; Prints "%0010 + $3 == 5"
PRINTLN "{#05b:X} + {#x:Y} == {d:SUM}"
rsset 32
PERCENT rb 1 ;\ Same with offset constants
VALUE = 20
RESULT = MUL(20.0, 0.32)
; Prints "32% of 20 = 6.40"
PRINTLN "{d:PERCENT}% of {d:VALUE} = {f:RESULT}"
WHO equs STRLWR("WORLD")
; Prints "Hello world!"
PRINTLN "Hello {s:WHO}!"
.Ed .Ed
.Pp
Although, for these examples,
.Ic STRFMT
would be more approriate; see
.Sx String expressions
further below.
.Sh EXPRESSIONS .Sh EXPRESSIONS
An expression can be composed of many things. An expression can be composed of many things.
Numerical expressions are always evaluated using signed 32-bit math. Numeric expressions are always evaluated using signed 32-bit math.
Zero is considered to be the only "false" number, all non-zero numbers (including negative) are "true". Zero is considered to be the only "false" number, all non-zero numbers (including negative) are "true".
.Pp .Pp
An expression is said to be "constant" if An expression is said to be "constant" if
@@ -89,7 +205,7 @@ This is generally always the case, unless a label is involved, as explained in t
section. section.
.Pp .Pp
The instructions in the macro-language generally require constant expressions. The instructions in the macro-language generally require constant expressions.
.Ss Numeric Formats .Ss Numeric formats
There are a number of numeric formats. There are a number of numeric formats.
.Bl -column -offset indent "Fixed point (Q16.16)" "Prefix" .Bl -column -offset indent "Fixed point (Q16.16)" "Prefix"
.It Sy Format type Ta Sy Prefix Ta Sy Accepted characters .It Sy Format type Ta Sy Prefix Ta Sy Accepted characters
@@ -103,6 +219,10 @@ There are a number of numeric formats.
.El .El
.Pp .Pp
Underscores are also accepted in numbers, except at the beginning of one. Underscores are also accepted in numbers, except at the beginning of one.
This can be useful for grouping digits, like
.Ql 123_456
or
.Ql %1100_1001 .
.Pp .Pp
The "character constant" form yields the value the character maps to in the current charmap. The "character constant" form yields the value the character maps to in the current charmap.
For example, by default For example, by default
@@ -177,7 +297,7 @@ and
.Pp .Pp
.Ic \&! .Ic \&!
returns 1 if the operand was 0, and 0 otherwise. returns 1 if the operand was 0, and 0 otherwise.
.Ss Fixed-point Expressions .Ss Fixed-point expressions
Fixed-point numbers are basically normal (32-bit) integers, which count 65536ths instead of entire units, offering better precision than integers but limiting the range of values. Fixed-point numbers are basically normal (32-bit) integers, which count 65536ths instead of entire units, offering better precision than integers but limiting the range of values.
The upper 16 bits are used for the integer part and the lower 16 bits are used for the fraction (65536ths). The upper 16 bits are used for the integer part and the lower 16 bits are used for the fraction (65536ths).
Since they are still akin to integers, you can use them in normal integer expressions, and some integer operators like Since they are still akin to integers, you can use them in normal integer expressions, and some integer operators like
@@ -230,7 +350,7 @@ ANGLE = 0.0
ANGLE = ANGLE + 256.0 ; 256.0 = 65536 degrees / 256 entries ANGLE = ANGLE + 256.0 ; 256.0 = 65536 degrees / 256 entries
ENDR ENDR
.Ed .Ed
.Ss String Expressions .Ss String expressions
The most basic string expression is any number of characters contained in double quotes The most basic string expression is any number of characters contained in double quotes
.Pq Ql \&"for instance" . .Pq Ql \&"for instance" .
The backslash character The backslash character
@@ -262,129 +382,6 @@ characters will be included as-is, without needing to escape them with
or or
.Ql \[rs]n . .Ql \[rs]n .
.Pp .Pp
A funky feature is
.Ql {symbol}
within a string, called
.Dq symbol interpolation .
This will paste the contents of
.Ql symbol
as if they were part of the source file.
If it's a string symbol, its characters are simply inserted.
If it's a numerical symbol, its value is converted to hexadecimal notation with a dollar sign
.Sq $
prepended.
.Pp
Symbols can be
.Em interpolated
even in the contexts that disable
.Em expansion
of string equates:
.Ql DEF({name}) ,
.Ql DEF {name} EQU/SET/EQUS/etc ... ,
.Ql PURGE {name} ,
and
.Ql MACRO {name}
will all interpolate the contents of
.Ql {name} .
.Pp
Symbol interpolations can be nested, too!
.Bd -literal -offset indent
DEF topic EQUS "life, the universe, and \[rs]"everything\[rs]""
DEF meaning EQUS "answer"
;\ Defines answer = 42
DEF {meaning} = 42
;\ Prints "The answer to life, the universe, and "everything" is 42"
PRINTLN "The {meaning} to {topic} is {d:{meaning}}"
PURGE topic, meaning, {meaning}
.Ed
.Pp
It's possible to change the way symbols are converted by specifying a print format like so:
.Ql {fmt:symbol} .
The
.Ql fmt
specifier consists of parts
.Ql <sign><prefix><align><pad><width><frac><type> .
These parts are:
.Bl -column "<prefix>"
.It Sy Part Ta Sy Meaning
.It Ql <sign> Ta May be
.Ql +
or
.Ql \ .
If specified, prints this character in front of non-negative numbers.
.It Ql <prefix> Ta May be
.Ql # .
If specified, prints the appropriate prefix for numbers,
.Ql $ ,
.Ql & ,
or
.Ql % .
.It Ql <align> Ta May be
.Ql - .
If specified, aligns left instead of right.
.It Ql <pad> Ta May be
.Ql 0 .
If specified, pads right-aligned numbers with zeros instead of spaces.
.It Ql <width> Ta May be one or more
.Ql 0
\[en]
.Ql 9 .
If specified, pads the value to this width, right-aligned with spaces by default.
.It Ql <frac> Ta May be
.Ql \&.
followed by one or more
.Ql 0
\[en]
.Ql 9 .
If specified, prints this many digits of a fixed-point fraction.
Defaults to 5 digits, maximum 255 digits.
.It Ql <type> Ta Specifies the type of value.
.El
.Pp
All the format specifier parts are optional except the
.Ql <type> .
Valid print types are:
.Bl -column -offset indent "Print type" "Lowercase hexadecimal" "Example"
.It Sy Print type Ta Sy Format Ta Sy Example
.It Ql d Ta Signed decimal Ta -42
.It Ql u Ta Unsigned decimal Ta 42
.It Ql x Ta Lowercase hexadecimal Ta 2a
.It Ql X Ta Uppercase hexadecimal Ta 2A
.It Ql b Ta Binary Ta 101010
.It Ql o Ta Octal Ta 52
.It Ql f Ta Fixed-point Ta 1234.56789
.It Ql s Ta String Ta \&"example\&"
.El
.Pp
Examples:
.Bd -literal -offset indent
; Prints "%0010 + $3 == 5"
PRINTLN STRFMT("%#05b + %#x == %d", 2, 3, 2+3)
; Prints "32% of 20 = 6.40"
PRINTLN STRFMT("%d%% of %d = %.2f", 32, 20, MUL(20.0, 0.32))
; Prints "Hello world!"
PRINTLN STRFMT("Hello %s!", STRLWR("WORLD"))
.Ed
.Pp
HINT: The
.Ic {symbol}
construct can also be used outside strings.
The symbol's value is again inserted directly.
.Bd -literal -offset indent
def NAME equs "ITEM"
def FMT equs "d"
def ZERO_NUM equ 0
def ZERO_STR equs "0"
;\ Defines INDEX as 100
INDEX = 1{ZERO_STR}{{FMT}:ZERO_NUM}
;\ Defines ITEM_100 as "\[rs]"hundredth\[rs]""
def {NAME}_{d:INDEX} equs "\[rs]"hundredth\[rs]""
;\ Prints "ITEM_100 is hundredth"
PRINTLN STRCAT("{NAME}_{d:INDEX} is ", {NAME}_{d:INDEX})
;\ Purges ITEM_100
PURGE {NAME}_{d:INDEX}
.Ed
.Pp
The following functions operate on string expressions. The following functions operate on string expressions.
Most of them return a string, however some of these functions actually return an integer and can be used as part of an integer expression! Most of them return a string, however some of these functions actually return an integer and can be used as part of an integer expression!
.Bl -column "STRSUB(str, pos, len)" .Bl -column "STRSUB(str, pos, len)"
@@ -399,14 +396,15 @@ Most of them return a string, however some of these functions actually return an
.It Fn STRLWR str Ta Returns Ar str No with all letters in lowercase. .It Fn STRLWR str Ta Returns Ar str No with all letters in lowercase.
.It Fn STRRPL str old new Ta Returns Ar str No with each non-overlapping occurrence of the substring Ar old No replaced with Ar new . .It Fn STRRPL str old new Ta Returns Ar str No with each non-overlapping occurrence of the substring Ar old No replaced with Ar new .
.It Fn STRFMT fmt args... Ta Returns the string Ar fmt No with each .It Fn STRFMT fmt args... Ta Returns the string Ar fmt No with each
.It Fn CHARLEN str Ta Returns the number of charmap entries in Ar str No with the current charmap.
.It Fn CHARSUB str pos Ta Returns the substring for the charmap entry at Ar pos No in Ar str No (first character is position 1, last is position -1) with the current charmap.
.Ql %spec .Ql %spec
pattern replaced by interpolating the format pattern replaced by interpolating the format
.Ar spec .Ar spec
.Pq using the same syntax as Sx Symbol interpolation
with its corresponding argument in with its corresponding argument in
.Ar args .Ar args
.Pq So %% Sc is replaced by the So % Sc character . .Pq So %% Sc is replaced by the So % Sc character .
.It Fn CHARLEN str Ta Returns the number of charmap entries in Ar str No with the current charmap.
.It Fn CHARSUB str pos Ta Returns the substring for the charmap entry at Ar pos No in Ar str No (first character is position 1, last is position -1) with the current charmap.
.El .El
.Ss Character maps .Ss Character maps
When writing text strings that are meant to be displayed on the Game Boy, the character encoding in the ROM may need to be different than the source file encoding. When writing text strings that are meant to be displayed on the Game Boy, the character encoding in the ROM may need to be different than the source file encoding.
@@ -470,7 +468,7 @@ The result is not constant, since only RGBLINK can compute its value.
.It Fn DEF symbol Ta Returns TRUE (1) if .It Fn DEF symbol Ta Returns TRUE (1) if
.Ar symbol .Ar symbol
has been defined, FALSE (0) otherwise. has been defined, FALSE (0) otherwise.
String equates are not expanded within the parentheses. String constants are not expanded within the parentheses.
.It Fn HIGH arg Ta Returns the top 8 bits of the operand if Ar arg No is a label or constant, or the top 8-bit register if it is a 16-bit register. .It Fn HIGH arg Ta Returns the top 8 bits of the operand if Ar arg No is a label or constant, or the top 8-bit register if it is a 16-bit register.
.It Fn LOW arg Ta Returns the bottom 8 bits of the operand if Ar arg No is a label or constant, or the bottom 8-bit register if it is a 16-bit register Pq Cm AF No isn't a valid register for this function . .It Fn LOW arg Ta Returns the bottom 8 bits of the operand if Ar arg No is a label or constant, or the bottom 8-bit register if it is a 16-bit register Pq Cm AF No isn't a valid register for this function .
.It Fn ISCONST arg Ta Returns 1 if Ar arg Ap s value is known by RGBASM (e.g. if it can be an argument to .It Fn ISCONST arg Ta Returns 1 if Ar arg Ap s value is known by RGBASM (e.g. if it can be an argument to
@@ -494,7 +492,7 @@ All other sections must have a unique name, even in different source files, or t
Possible section Possible section
.Ar type Ns s .Ar type Ns s
are as follows: are as follows:
.Bl -tag .Bl -tag -width Ds
.It Ic ROM0 .It Ic ROM0
A ROM section. A ROM section.
.Ar addr .Ar addr
@@ -602,7 +600,7 @@ sections.
To put some in RAM, have it stored in ROM, and copy it to RAM. To put some in RAM, have it stored in ROM, and copy it to RAM.
.Pp .Pp
.Ar option Ns s are comma-separated and may include: .Ar option Ns s are comma-separated and may include:
.Bl -tag .Bl -tag -width Ds
.It Ic BANK Ns Bq Ar bank .It Ic BANK Ns Bq Ar bank
Specify which Specify which
.Ar bank .Ar bank
@@ -678,7 +676,7 @@ SECTION "OAM Data",WRAM0,ALIGN[8] ;\ align to 256 bytes
SECTION "VRAM Data",ROMX,BANK[2],ALIGN[4] ;\ align to 16 bytes SECTION "VRAM Data",ROMX,BANK[2],ALIGN[4] ;\ align to 16 bytes
.Ed .Ed
.El .El
.Ss Section Stack .Ss Section stack
.Ic POPS .Ic POPS
and and
.Ic PUSHS .Ic PUSHS
@@ -690,7 +688,7 @@ will push the current section context on the section stack.
.Ic POPS .Ic POPS
can then later be used to restore it. can then later be used to restore it.
Useful for defining sections in included files when you don't want to override the section context at the point the file was included. Useful for defining sections in included files when you don't want to override the section context at the point the file was included.
.Ss RAM Code .Ss RAM code
Sometimes you want to have some code in RAM. Sometimes you want to have some code in RAM.
But then you can't simply put it in a RAM section, you have to store it in ROM and copy it to RAM at some point. But then you can't simply put it in a RAM section, you have to store it in ROM and copy it to RAM at some point.
.Pp .Pp
@@ -757,13 +755,13 @@ blocks can use the
or or
.Ic FRAGMENT .Ic FRAGMENT
modifiers, as described below. modifiers, as described below.
.Ss Unionized Sections .Ss Unionized sections
When you're tight on RAM, you may want to define overlapping blocks of variables, as explained in the When you're tight on RAM, you may want to define overlapping static memory allocations, as explained in the
.Sx Unions .Sx Unions
section. section.
However, the However, a
.Ic UNION .Ic UNION
keyword only works within a single file, which prevents e.g. defining temporary variables on a single memory area across several files. only works within a single file, so it can't be used e.g. to define temporary variables across several files, all of which use the same statically allocated memory.
Unionized sections solve this problem. Unionized sections solve this problem.
To declare an unionized section, add a To declare an unionized section, add a
.Ic UNION .Ic UNION
@@ -773,7 +771,7 @@ one; the declaration is otherwise not different.
Unionized sections follow some different rules from normal sections: Unionized sections follow some different rules from normal sections:
.Bl -bullet -offset indent .Bl -bullet -offset indent
.It .It
The same unionized section (= having the same name) can be declared several times per The same unionized section (i.e. having the same name) can be declared several times per
.Nm .Nm
invocation, and across several invocations. invocation, and across several invocations.
Different declarations are treated and merged identically whether within the same invocation, or different ones. Different declarations are treated and merged identically whether within the same invocation, or different ones.
@@ -802,7 +800,7 @@ or
Different declarations of the same unionized section are not appended, but instead overlaid on top of eachother, just like Different declarations of the same unionized section are not appended, but instead overlaid on top of eachother, just like
.Sx Unions . .Sx Unions .
Similarly, the size of an unionized section is the largest of all its declarations. Similarly, the size of an unionized section is the largest of all its declarations.
.Ss Section Fragments .Ss Section fragments
Section fragments are sections with a small twist: when several of the same name are encountered, they are concatenated instead of producing an error. Section fragments are sections with a small twist: when several of the same name are encountered, they are concatenated instead of producing an error.
This works within the same file (paralleling the behavior "plain" sections has in previous versions), but also across object files. This works within the same file (paralleling the behavior "plain" sections has in previous versions), but also across object files.
To declare an section fragment, add a To declare an section fragment, add a
@@ -811,7 +809,7 @@ keyword after the
.Ic SECTION .Ic SECTION
one; the declaration is otherwise not different. one; the declaration is otherwise not different.
However, similarly to However, similarly to
.Sx Unionized Sections , .Sx Unionized sections ,
some rules must be followed: some rules must be followed:
.Bl -bullet -offset indent .Bl -bullet -offset indent
.It .It
@@ -854,70 +852,113 @@ last.
RGBDS supports several types of symbols: RGBDS supports several types of symbols:
.Bl -hang .Bl -hang
.It Sy Label .It Sy Label
Numerical symbol designating a memory location. Numeric symbol designating a memory location.
May or may not have a value known at assembly time. May or may not have a value known at assembly time.
.It Sy Constant .It Sy Constant
Numerical symbol whose value has to be known at assembly time. Numeric symbol whose value has to be known at assembly time.
.It Sy Macro .It Sy Macro
A block of A block of
.Nm .Nm
code that can be invoked later. code that can be invoked later.
.It Sy String equate .It Sy String
A text string that can be expanded later, similarly to a macro. A text string that can be expanded later, similarly to a macro.
.El .El
.Pp .Pp
Symbol names can contain letters, numbers, underscores Symbol names can contain ASCII letters, numbers, underscores
.Sq _ , .Sq _ ,
hashes hashes
.Sq # .Sq #
and at signs and at signs
.Sq @ . .Sq @ .
However, they must begin with either a letter or an underscore. However, they must begin with either a letter or an underscore.
Periods Additionally, label names can contain up to a single dot
.Sq \&. .Ql \&. ,
are allowed exclusively in labels, as described below. which may not be the first character.
A symbol cannot have the same name as a reserved keyword.
.Ss Label declaration
One of the assembler's main tasks is to keep track of addresses for you, so you can work with meaningful names instead of "magic" numbers.
.Pp .Pp
This can be done in a number of ways: A symbol cannot have the same name as a reserved keyword.
.Ss Labels
One of the assembler's main tasks is to keep track of addresses for you, so you can work with meaningful names instead of
.Dq magic
numbers.
Labels enable just that: a label ties a name to a specific location within a section.
A label resolves to a bank and address, determined at the same time as its parent section's (see further in this section).
.Pp
A label is defined by writing its name at the beginning of a line, followed by one or two colons, without any whitespace between the label name and the colon(s).
Declaring a label (global or local) with two colons
.Ql ::
will define and
.Ic EXPORT
it at the same time.
(See
.Sx Exporting and importing symbols
below).
When defining a local label, the colon can be omitted, and
.Nm
will act as if there was only one.
.Pp
A label is said to be
.Em local
if its name contains a dot
.Ql \&. ;
otherwise, it is said to be
.Em global
(not to be mistaken with
.Dq exported ,
explained in
.Sx Exporting and importing symbols
further below).
More than one dot in label names is not allowed.
.Pp
For convenience, local labels can use a shorthand syntax: when a symbol name starting with a dot is found (for example, inside an expression, or when declaring a label), then the current
.Dq label scope
is implicitly prepended.
.Pp
Defining a global label sets it as the current
.Dq label scope ,
until the next global label definition, or the end of the current section.
.Pp
Here are some examples of label definitions:
.Bd -literal -offset indent .Bd -literal -offset indent
GlobalLabel: GlobalLabel:
AnotherGlobal: AnotherGlobal:
\&.locallabel \&.locallabel ;\ This defines "AnotherGlobal.locallabel"
\&.another_local: \&.another_local:
AnotherGlobal.with_another_local: AnotherGlobal.with_another_local:
ThisWillBeExported:: ;\ Note the two colons ThisWillBeExported:: ;\ Note the two colons
ThisWillBeExported.too:: ThisWillBeExported.too::
.Ed .Ed
.Pp .Pp
Any label whose name does not contain a period is a global label. In a numeric expression, a label evaluates to its address in memory.
Declaring a global label sets it as the current scoped label, until the next global one. .Po To obtain its bank, use the
Global labels must be followed by one or two colons. .Ql BANK()
function described in
.Sx Other functions
.Pc .
For example, given the following,
.Ql ld de, vPlayerTiles
would be equivalent to
.Ql ld de, $80C0
assuming the section ends up at
.Ad $80C0 :
.Bd -literal -offset indent
SECTION "Player tiles", VRAM
PlayerTiles:
ds 6 * 16
.end
.Ed
.Pp .Pp
Any label whose name contains a single period is a local label. A label's location (and thus value) is usually not determined until the linking stage, so labels usually cannot be used as constants.
Label names cannot contain more than one period. However, if the section in which the label is defined has a fixed base address, its value is known at assembly time.
If the period is the first character, it will have the current scoped label's name implicitly prepended.
Local labels may optionally be followed by one or two colons.
Local labels can be declared as
.Ql scoped.local
or simply as
.Ql .local .
If the former notation is used, then
.Ql scoped
must actually be the current scoped label.
.Pp .Pp
Declaring a label (global or local) with two colons Also, while
.Ql :: .Nm
will obviously can compute the difference between two labels if both are constant, it is also able to compute the difference between two non-constant labels if they both belong to the same section, such as
.Ic EXPORT .Ql PlayerTiles
and define it at the same time. and
(See .Ql PlayerTiles.end
.Sx Exporting and importing symbols above.
below). .Ss Anonymous labels
.Pp Anonymous labels are useful for short blocks of code.
.Sy Anonymous labels
are useful for short blocks of code.
They are defined like normal labels, but without a name before the colon. They are defined like normal labels, but without a name before the colon.
Anonymous labels are independent of label scoping, so defining one does not change the scoped label, and referencing one is not affected by the current scoped label. Anonymous labels are independent of label scoping, so defining one does not change the scoped label, and referencing one is not affected by the current scoped label.
.Pp .Pp
@@ -946,18 +987,49 @@ and so on.
: ; referenced by "ld hl" : ; referenced by "ld hl"
dw $7FFF, $1061, $03E0, $58A5 dw $7FFF, $1061, $03E0, $58A5
.Ed .Ed
.Ss Variables
An equal sign
.Ic =
is used to define mutable numeric symbols.
Unlike the other symbols described below, variables can be redefined.
This is useful for internal symbols in macros, for counters, etc.
.Bd -literal -offset indent
DEF ARRAY_SIZE EQU 4
DEF COUNT = 2
DEF COUNT = 3
DEF COUNT = ARRAY_SIZE + COUNT
COUNT = COUNT*2
;\ COUNT now has the value 14
.Ed
.Pp .Pp
A label's location (and thus value) is usually not determined until the linking stage, so labels usually cannot be used as constants. Note that colons
However, if the section in which the label is declared has a fixed base address, its value is known at assembly time. .Ql \&:
following the name are not allowed.
.Pp .Pp
.Nm Variables can be conveniently redefined by compound assignment operators like in C:
is able to compute the subtraction of two labels either if both are constant as described above, or if both belong to the same section. .Bl -column -offset indent "*= /= %="
.Ss Immutable constants .It Sy Operator Ta Sy Meaning
.It Li += -= Ta Compound plus/minus
.It Li *= /= %= Ta Compound multiply/divide/modulo
.It Li <<= >>= Ta Compound shift left/right
.It Li &= \&|= ^= Ta Compound and/or/xor
.El
.Pp
Examples:
.Bd -literal -offset indent
DEF x = 10
DEF x += 1 ; x == 11
DEF y = x - 1 ; y == 10
DEF y *= 2 ; y == 20
DEF y >>= 1 ; y == 10
DEF x ^= y ; x == 1
.Ed
.Ss Numeric constants
.Ic EQU .Ic EQU
is used to define numerical constant symbols. is used to define immutable numeric symbols.
Unlike Unlike
.Ic SET .Ic =
below, constants defined this way cannot be redefined. above, constants defined this way cannot be redefined.
These constants can be used for unchanging values such as properties of the hardware. These constants can be used for unchanging values such as properties of the hardware.
.Bd -literal -offset indent .Bd -literal -offset indent
def SCREEN_WIDTH equ 160 ;\ In pixels def SCREEN_WIDTH equ 160 ;\ In pixels
@@ -972,13 +1044,14 @@ If you
.Em really .Em really
need to, the need to, the
.Ic REDEF .Ic REDEF
keyword will define or redefine a constant symbol. keyword will define or redefine a numeric constant symbol.
(It can also be used for variables, although it's not necessary since they are mutable.)
This can be used, for example, to update a constant using a macro, without making it mutable in general. This can be used, for example, to update a constant using a macro, without making it mutable in general.
.Bd -literal -offset indent .Bd -literal -offset indent
def NUM_ITEMS equ 0 def NUM_ITEMS equ 0
MACRO add_item MACRO add_item
redef NUM_ITEMS equ NUM_ITEMS + 1 redef NUM_ITEMS equ NUM_ITEMS + 1
def ITEM_{02x:NUM_ITEMS} equ \1 def ITEM_{02x:NUM_ITEMS} equ \[rs]1
ENDM ENDM
add_item 1 add_item 1
add_item 4 add_item 4
@@ -987,26 +1060,6 @@ ENDM
assert NUM_ITEMS == 4 assert NUM_ITEMS == 4
assert ITEM_04 == 16 assert ITEM_04 == 16
.Ed .Ed
.Ss Mutable constants
.Ic SET ,
or its synonym
.Ic = ,
is used to define numerical symbols like
.Ic EQU ,
but these symbols can be redefined.
This is useful for variables in macros, for counters, etc.
.Bd -literal -offset indent
DEF ARRAY_SIZE EQU 4
DEF COUNT SET 2
DEF COUNT SET 3
REDEF COUNT SET ARRAY_SIZE+COUNT
COUNT = COUNT*2
;\ COUNT now has the value 14
.Ed
.Pp
Note that colons
.Ql \&:
following the name are not allowed.
.Ss Offset constants .Ss Offset constants
The RS group of commands is a handy way of defining structure offsets: The RS group of commands is a handy way of defining structure offsets:
.Bd -literal -offset indent .Bd -literal -offset indent
@@ -1044,21 +1097,21 @@ is omitted, it's assumed to be 1.
Note that colons Note that colons
.Ql \&: .Ql \&:
following the name are not allowed. following the name are not allowed.
.Ss String equates .Ss String constants
.Ic EQUS .Ic EQUS
is used to define string equate symbols. is used to define string constant symbols.
Wherever the assembler reads a string equate, it gets Wherever the assembler reads a string constant, it gets
.Em expanded : .Em expanded :
the symbol's name is replaced with its contents. the symbol's name is replaced with its contents.
If you are familiar with C, you can think of it as similar to If you are familiar with C, you can think of it as similar to
.Fd #define . .Fd #define .
This expansion is disabled in a few contexts: This expansion is disabled in a few contexts:
.Ql DEF(name) , .Ql DEF(name) ,
.Ql DEF name EQU/SET/EQUS/etc ... , .Ql DEF name EQU/=/EQUS/etc ... ,
.Ql PURGE name , .Ql PURGE name ,
and and
.Ql MACRO name .Ql MACRO name
will not expand string equates in their names. will not expand string constants in their names.
.Bd -literal -offset indent .Bd -literal -offset indent
DEF COUNTREG EQUS "[hl+]" DEF COUNTREG EQUS "[hl+]"
ld a,COUNTREG ld a,COUNTREG
@@ -1073,7 +1126,7 @@ This will be interpreted as:
db "John" db "John"
.Ed .Ed
.Pp .Pp
String equates can also be used to define small one-line macros: String constants can also be used to define small one-line macros:
.Bd -literal -offset indent .Bd -literal -offset indent
DEF pusha EQUS "push af\[rs]npush bc\[rs]npush de\[rs]npush hl\[rs]n" DEF pusha EQUS "push af\[rs]npush bc\[rs]npush de\[rs]npush hl\[rs]n"
.Ed .Ed
@@ -1082,14 +1135,12 @@ Note that colons
.Ql \&: .Ql \&:
following the name are not allowed. following the name are not allowed.
.Pp .Pp
String equates can't be exported or imported. String constants can't be exported or imported.
.Pp .Pp
String equates, like String constants, like numeric constants, cannot be redefined.
.Ic EQU
constants, cannot be redefined.
However, the However, the
.Ic REDEF .Ic REDEF
keyword will define or redefine a string symbol. keyword will define or redefine a string constant symbol.
For example: For example:
.Bd -literal -offset indent .Bd -literal -offset indent
DEF s EQUS "Hello, " DEF s EQUS "Hello, "
@@ -1099,11 +1150,7 @@ PRINTLN "{s}\n"
.Ed .Ed
.Pp .Pp
.Sy Important note : .Sy Important note :
An When a string constant is expanded, its expansion may contain another string constant, which will be expanded as well.
.Ic EQUS
can be expanded to a string that contains another
.Ic EQUS
and it will be expanded as well.
If this creates an infinite loop, If this creates an infinite loop,
.Nm .Nm
will error out once a certain depth is will error out once a certain depth is
@@ -1112,14 +1159,10 @@ See the
.Fl r .Fl r
command-line option in command-line option in
.Xr rgbasm 1 . .Xr rgbasm 1 .
Also, a macro can contain an The same problem can occur if the expansion of a macro invokes another macro, recursively.
.Ic EQUS
which calls the same macro, which causes the same problem.
.Pp .Pp
The examples above for The examples above for
.Ql EQU , .Ql EQU ,
.Ql SET
or
.Ql = , .Ql = ,
.Ql RB , .Ql RB ,
.Ql RW , .Ql RW ,
@@ -1128,11 +1171,7 @@ and
.Ql EQUS .Ql EQUS
all start with all start with
.Ql DEF . .Ql DEF .
(A (A variable definition may start with
.Ql SET
or
.Ql =
definition may start with
.Ql REDEF .Ql REDEF
instead, since they are redefinable.) instead, since they are redefinable.)
You may use the older syntax without You may use the older syntax without
@@ -1146,7 +1185,7 @@ will treat it as a macro invocation.
Furthermore, without the Furthermore, without the
.Ql DEF .Ql DEF
keyword, keyword,
string equates may be expanded for the name. string constants may be expanded for the name.
This can lead to surprising results: This can lead to surprising results:
.Bd -literal -offset indent .Bd -literal -offset indent
X EQUS "Y" X EQUS "Y"
@@ -1170,7 +1209,7 @@ ENDM
The example above defines The example above defines
.Ql MyMacro .Ql MyMacro
as a new macro. as a new macro.
String equates are not expanded within the name of the macro. String constants are not expanded within the name of the macro.
You may use the older syntax You may use the older syntax
.Ql MyMacro: MACRO .Ql MyMacro: MACRO
instead of instead of
@@ -1178,7 +1217,7 @@ instead of
with a single colon with a single colon
.Ql \&: .Ql \&:
following the macro's name. following the macro's name.
With the older syntax, string equates may be expanded for the name. With the older syntax, string constants may be expanded for the name.
.Pp .Pp
Macros can't be exported or imported. Macros can't be exported or imported.
.Pp .Pp
@@ -1265,7 +1304,6 @@ Linking failed with 1 error
.Pp .Pp
Note also that only exported symbols will appear in symbol and map files produced by Note also that only exported symbols will appear in symbol and map files produced by
.Xr rgblink 1 . .Xr rgblink 1 .
.Pp
.Ss Purging symbols .Ss Purging symbols
.Ic PURGE .Ic PURGE
allows you to completely remove a symbol from the symbol table as if it had never existed. allows you to completely remove a symbol from the symbol table as if it had never existed.
@@ -1273,20 +1311,20 @@ allows you to completely remove a symbol from the symbol table as if it had neve
I can't stress this enough, I can't stress this enough,
.Sy you seriously need to know what you are doing . .Sy you seriously need to know what you are doing .
DON'T purge a symbol that you use in expressions the linker needs to calculate. DON'T purge a symbol that you use in expressions the linker needs to calculate.
When not sure, it's probably not safe to purge anything other than string symbols, macros, and constants. When not sure, it's probably not safe to purge anything other than variables, numeric or string constants, or macros.
.Bd -literal -offset indent .Bd -literal -offset indent
DEF Kamikaze EQUS "I don't want to live anymore" DEF Kamikaze EQUS "I don't want to live anymore"
DEF AOLer EQUS "Me too" DEF AOLer EQUS "Me too"
PURGE Kamikaze, AOLer PURGE Kamikaze, AOLer
.Ed .Ed
.Pp .Pp
String equates are not expanded within the symbol names. String constants are not expanded within the symbol names.
.Ss Predeclared Symbols .Ss Predeclared symbols
The following symbols are defined by the assembler: The following symbols are defined by the assembler:
.Bl -column -offset indent "EQUS" "__ISO_8601_LOCAL__" .Bl -column -offset indent "EQUS" "__ISO_8601_LOCAL__"
.It Sy Name Ta Sy Type Ta Sy Contents .It Sy Name Ta Sy Type Ta Sy Contents
.It Dv @ Ta Ic EQU Ta PC value (essentially, the current memory address) .It Dv @ Ta Ic EQU Ta PC value (essentially, the current memory address)
.It Dv _RS Ta Ic SET Ta _RS Counter .It Dv _RS Ta Ic = Ta _RS Counter
.It Dv _NARG Ta Ic EQU Ta Number of arguments passed to macro, updated by Ic SHIFT .It Dv _NARG Ta Ic EQU Ta Number of arguments passed to macro, updated by Ic SHIFT
.It Dv __LINE__ Ta Ic EQU Ta The current line number .It Dv __LINE__ Ta Ic EQU Ta The current line number
.It Dv __FILE__ Ta Ic EQUS Ta The current filename .It Dv __FILE__ Ta Ic EQUS Ta The current filename
@@ -1313,16 +1351,16 @@ environment variable if that is defined as a UNIX timestamp.
Refer to the spec at Refer to the spec at
.Lk https://reproducible-builds.org/docs/source-date-epoch/ . .Lk https://reproducible-builds.org/docs/source-date-epoch/ .
.Sh DEFINING DATA .Sh DEFINING DATA
.Ss Declaring variables in a RAM section .Ss Statically allocating space in RAM
.Ic DS .Ic DS
allocates a number of empty bytes. statically allocates a number of empty bytes.
This is the preferred method of allocating space in a RAM section. This is the preferred method of allocating space in a RAM section.
You can also use You can also use
.Ic DB , DW .Ic DB , DW
and and
.Ic DL .Ic DL
without any arguments instead (see without any arguments instead (see
.Sx Defining constant data .Sx Defining constant data in ROM
below). below).
.Bd -literal -offset indent .Bd -literal -offset indent
DS 42 ;\ Allocates 42 bytes DS 42 ;\ Allocates 42 bytes
@@ -1333,7 +1371,7 @@ In ROM sections, it will be filled with the value passed to the
.Fl p .Fl p
command-line option, except when using overlays with command-line option, except when using overlays with
.Fl O . .Fl O .
.Ss Defining constant data .Ss Defining constant data in ROM
.Ic DB .Ic DB
defines a list of bytes that will be stored in the final image. defines a list of bytes that will be stored in the final image.
Ideal for tables and text. Ideal for tables and text.
@@ -1418,7 +1456,7 @@ INCBIN "data.bin",78,256
The length argument is optional. The length argument is optional.
If only the start position is specified, the bytes from the start position until the end of the file will be included. If only the start position is specified, the bytes from the start position until the end of the file will be included.
.Ss Unions .Ss Unions
Unions allow multiple memory allocations to overlap, like unions in C. Unions allow multiple static memory allocations to overlap, like unions in C.
This does not increase the amount of memory available, but allows re-using the same memory region for different purposes. This does not increase the amount of memory available, but allows re-using the same memory region for different purposes.
.Pp .Pp
A union starts with a A union starts with a
@@ -1466,7 +1504,7 @@ Nesting unions is possible, with each inner union's size being considered as des
Unions may be used in any section, but inside them may only be Unions may be used in any section, but inside them may only be
.Ic DS - .Ic DS -
like commands (see like commands (see
.Sx Declaring variables in a RAM section ) . .Sx Statically allocating space in RAM ) .
.Sh THE MACRO LANGUAGE .Sh THE MACRO LANGUAGE
.Ss Invoking macros .Ss Invoking macros
You execute the macro by inserting its name. You execute the macro by inserting its name.
@@ -1553,7 +1591,7 @@ The generated code will then reset all bytes in this range.
LoopyMacro MyVars,54 LoopyMacro MyVars,54
.Ed .Ed
.Pp .Pp
Arguments are passed as string equates, although there's no need to enclose them in quotes. Arguments are passed as string constants, although there's no need to enclose them in quotes.
Thus, an expression will not be evaluated first but kind of copy-pasted. Thus, an expression will not be evaluated first but kind of copy-pasted.
This means that it's probably a very good idea to use brackets around This means that it's probably a very good idea to use brackets around
.Ic \[rs]1 .Ic \[rs]1
@@ -1602,7 +1640,7 @@ also does not need escaping because string literals work as usual inside macro a
Since there are only nine digits, you can only access the first nine macro arguments like this. Since there are only nine digits, you can only access the first nine macro arguments like this.
To use the rest, you need to put the multi-digit argument number in angle brackets, like To use the rest, you need to put the multi-digit argument number in angle brackets, like
.Ql \[rs]<10> . .Ql \[rs]<10> .
This bracketed syntax supports decimal numbers and numeric symbol names. This bracketed syntax supports decimal numbers and numeric constant symbols.
For example, For example,
.Ql \[rs]<_NARG> .Ql \[rs]<_NARG>
will get the last argument. will get the last argument.
@@ -1718,7 +1756,7 @@ Everything between
and the matching and the matching
.Ic ENDR .Ic ENDR
will be repeated for each value of a given symbol. will be repeated for each value of a given symbol.
String equates are not expanded within the symbol name. String constants are not expanded within the symbol name.
For example, this code will produce a table of squared values from 0 to 255: For example, this code will produce a table of squared values from 0 to 255:
.Bd -literal -offset indent .Bd -literal -offset indent
FOR N, 256 FOR N, 256
@@ -1727,7 +1765,7 @@ ENDR
.Ed .Ed
.Pp .Pp
It acts just as if you had done: It acts just as if you had done:
.Bd -literal -offset ident .Bd -literal -offset indent
N = 0 N = 0
dw N * N dw N * N
N = 1 N = 1
@@ -1742,7 +1780,9 @@ N = 256
.Pp .Pp
You can customize the range of You can customize the range of
.Ic FOR .Ic FOR
values: values, similarly to Python's
.Ql range
function:
.Bl -column "FOR V, start, stop, step" .Bl -column "FOR V, start, stop, step"
.It Sy Code Ta Sy Range .It Sy Code Ta Sy Range
.It Ic FOR Ar V , stop Ta Ar V No increments from 0 to Ar stop .It Ic FOR Ar V , stop Ta Ar V No increments from 0 to Ar stop
@@ -1763,6 +1803,7 @@ FOR V, 4, 25, 5
ENDR ENDR
PRINTLN "done {d:V}" PRINTLN "done {d:V}"
.Ed .Ed
.Pp
This will print: This will print:
.Bd -literal -offset indent .Bd -literal -offset indent
4 9 14 19 24 done 29 4 9 14 19 24 done 29
@@ -1800,6 +1841,7 @@ FOR V, 1, 100
ENDR ENDR
PRINTLN "done {d:V}" PRINTLN "done {d:V}"
.Ed .Ed
.Pp
This will print: This will print:
.Bd -literal -offset indent .Bd -literal -offset indent
1, 2, 3, 4, 5 stop! done 5 1, 2, 3, 4, 5 stop! done 5
@@ -1828,16 +1870,18 @@ Syntax examples are given below:
.Bd -literal -offset indent .Bd -literal -offset indent
Function: Function:
xor a xor a
ASSERT LOW(Variable) == 0 ASSERT LOW(MyByte) == 0
ld h, HIGH(Variable) ld h, HIGH(MyByte)
ld l, a ld l, a
ld a, [hli] ld a, [hli]
; You can also indent this! ; You can also indent this!
ASSERT BANK(OtherFunction) == BANK(Function) ASSERT BANK(OtherFunction) == BANK(Function)
call OtherFunction call OtherFunction
; Lowercase also works ; Lowercase also works
assert Variable + 1 == OtherVariable ld hl, FirstByte
ld c, [hl] ld a, [hli]
assert FirstByte + 1 == SecondByte
ld b, [hl]
ret ret
\&.end \&.end
; If you specify one, a message will be printed ; If you specify one, a message will be printed
@@ -1987,19 +2031,19 @@ as presented in
.Sx SECTIONS .Sx SECTIONS
is often useful as-is, sometimes you instead want a particular piece of data (or code) in the middle of the section to be aligned. is often useful as-is, sometimes you instead want a particular piece of data (or code) in the middle of the section to be aligned.
This is made easier through the use of mid-section This is made easier through the use of mid-section
.Ic align Ar align , offset . .Ic ALIGN Ar align , offset .
It will alter the section's attributes to ensure that the location the It will alter the section's attributes to ensure that the location the
.Ic align .Ic ALIGN
directive is at, has its directive is at, has its
.Ar align .Ar align
lower bits equal to lower bits equal to
.Ar offset . .Ar offset .
.Pp .Pp
If the constraint cannot be met (for example because the section is fixed at an incompatible address), and error is produced. If the constraint cannot be met (for example because the section is fixed at an incompatible address), an error is produced.
Note that Note that
.Ic align Ar align .Ic ALIGN Ar align
is a shorthand for is a shorthand for
.Ic align Ar align , No 0 . .Ic ALIGN Ar align , No 0 .
.Sh SEE ALSO .Sh SEE ALSO
.Xr rgbasm 1 , .Xr rgbasm 1 ,
.Xr rgblink 1 , .Xr rgblink 1 ,

View File

@@ -205,6 +205,11 @@ void rpn_SizeOfSection(struct Expression *expr, char const *sectionName)
{ {
rpn_Init(expr); rpn_Init(expr);
struct Section *section = sect_FindSectionByName(sectionName);
if (section && sect_IsSizeKnown(section)) {
expr->val = section->size;
} else {
makeUnknown(expr, "Section \"%s\"'s size is not known", sectionName); makeUnknown(expr, "Section \"%s\"'s size is not known", sectionName);
size_t nameLen = strlen(sectionName) + 1; /* Room for NUL! */ size_t nameLen = strlen(sectionName) + 1; /* Room for NUL! */
@@ -214,11 +219,17 @@ void rpn_SizeOfSection(struct Expression *expr, char const *sectionName)
*ptr++ = RPN_SIZEOF_SECT; *ptr++ = RPN_SIZEOF_SECT;
memcpy(ptr, sectionName, nameLen); memcpy(ptr, sectionName, nameLen);
} }
}
void rpn_StartOfSection(struct Expression *expr, char const *sectionName) void rpn_StartOfSection(struct Expression *expr, char const *sectionName)
{ {
rpn_Init(expr); rpn_Init(expr);
struct Section *section = sect_FindSectionByName(sectionName);
if (section && section->org != (uint32_t)-1) {
expr->val = section->org;
} else {
makeUnknown(expr, "Section \"%s\"'s start is not known", sectionName); makeUnknown(expr, "Section \"%s\"'s start is not known", sectionName);
size_t nameLen = strlen(sectionName) + 1; /* Room for NUL! */ size_t nameLen = strlen(sectionName) + 1; /* Room for NUL! */
@@ -228,6 +239,7 @@ void rpn_StartOfSection(struct Expression *expr, char const *sectionName)
*ptr++ = RPN_STARTOF_SECT; *ptr++ = RPN_STARTOF_SECT;
memcpy(ptr, sectionName, nameLen); memcpy(ptr, sectionName, nameLen);
} }
}
void rpn_CheckHRAM(struct Expression *expr, const struct Expression *src) void rpn_CheckHRAM(struct Expression *expr, const struct Expression *src)
{ {
@@ -272,8 +284,10 @@ void rpn_CheckNBit(struct Expression const *expr, uint8_t n)
if (rpn_isKnown(expr)) { if (rpn_isKnown(expr)) {
int32_t val = expr->val; int32_t val = expr->val;
if (val < -(1 << (n - 1)) || val >= 1 << n) if (val <= -(1 << n) || val >= 1 << n)
warning(WARNING_TRUNCATION, "Expression must be %u-bit\n", n); warning(WARNING_TRUNCATION_1, "Expression must be %u-bit\n", n);
else if (val < -(1 << (n - 1)))
warning(WARNING_TRUNCATION_2, "Expression must be %u-bit\n", n);
} }
} }

View File

@@ -15,17 +15,24 @@
#include "asm/symbol.h" #include "asm/symbol.h"
#include "asm/warning.h" #include "asm/warning.h"
#include "extern/err.h" #include "error.h"
#include "platform.h" // strdup #include "platform.h" // strdup
uint8_t fillByte; uint8_t fillByte;
struct UnionStackEntry {
uint32_t start;
uint32_t size;
struct UnionStackEntry *next;
} *unionStack = NULL;
struct SectionStackEntry { struct SectionStackEntry {
struct Section *section; struct Section *section;
struct Section *loadSection; struct Section *loadSection;
char const *scope; /* Section's symbol scope */ char const *scope; /* Section's symbol scope */
uint32_t offset; uint32_t offset;
int32_t loadOffset; int32_t loadOffset;
struct UnionStackEntry *unionStack;
struct SectionStackEntry *next; struct SectionStackEntry *next;
}; };
@@ -35,12 +42,6 @@ struct Section *currentSection = NULL;
static struct Section *currentLoadSection = NULL; static struct Section *currentLoadSection = NULL;
int32_t loadOffset; /* Offset into the LOAD section's parent (see sect_GetOutputOffset) */ int32_t loadOffset; /* Offset into the LOAD section's parent (see sect_GetOutputOffset) */
struct UnionStackEntry {
uint32_t start;
uint32_t size;
struct UnionStackEntry *next;
} *unionStack = NULL;
/* /*
* A quick check to see if we have an initialized section * A quick check to see if we have an initialized section
*/ */
@@ -109,7 +110,7 @@ attr_(warn_unused_result) static bool reserveSpace(uint32_t delta_size)
&& (!currentLoadSection || currentLoadSection->size != UINT32_MAX); && (!currentLoadSection || currentLoadSection->size != UINT32_MAX);
} }
struct Section *sect_FindSectionByName(const char *name) struct Section *sect_FindSectionByName(char const *name)
{ {
for (struct Section *sect = sectionList; sect; sect = sect->next) { for (struct Section *sect = sectionList; sect; sect = sect->next) {
if (strcmp(name, sect->name) == 0) if (strcmp(name, sect->name) == 0)
@@ -159,9 +160,9 @@ static unsigned int mergeSectUnion(struct Section *sect, enum SectionType type,
/* Check if alignment offsets are compatible */ /* Check if alignment offsets are compatible */
} else if ((alignOffset & mask(sect->align)) } else if ((alignOffset & mask(sect->align))
!= (sect->alignOfs & mask(alignment))) { != (sect->alignOfs & mask(alignment))) {
fail("Section already declared with incompatible %" PRIu8 fail("Section already declared with incompatible %u"
"-byte alignment (offset %" PRIu16 ")\n", "-byte alignment (offset %" PRIu16 ")\n",
sect->align, sect->alignOfs); 1u << sect->align, sect->alignOfs);
} else if (alignment > sect->align) { } else if (alignment > sect->align) {
// If the section is not fixed, its alignment is the largest of both // If the section is not fixed, its alignment is the largest of both
sect->align = alignment; sect->align = alignment;
@@ -212,9 +213,9 @@ static unsigned int mergeFragments(struct Section *sect, enum SectionType type,
PRIx32 "\n", sect->org); PRIx32 "\n", sect->org);
/* Check if alignment offsets are compatible */ /* Check if alignment offsets are compatible */
} else if ((curOfs & mask(sect->align)) != (sect->alignOfs & mask(alignment))) { } else if ((curOfs & mask(sect->align)) != (sect->alignOfs & mask(alignment))) {
fail("Section already declared with incompatible %" PRIu8 fail("Section already declared with incompatible %u"
"-byte alignment (offset %" PRIu16 ")\n", "-byte alignment (offset %" PRIu16 ")\n",
sect->align, sect->alignOfs); 1u << sect->align, sect->alignOfs);
} else if (alignment > sect->align) { } else if (alignment > sect->align) {
// If the section is not fixed, its alignment is the largest of both // If the section is not fixed, its alignment is the largest of both
sect->align = alignment; sect->align = alignment;
@@ -418,6 +419,7 @@ void sect_NewSection(char const *name, uint32_t type, uint32_t org,
changeSection(); changeSection();
curOffset = mod == SECTION_UNION ? 0 : sect->size; curOffset = mod == SECTION_UNION ? 0 : sect->size;
loadOffset = 0; // This is still used when checking for section size overflow!
currentSection = sect; currentSection = sect;
} }
@@ -427,6 +429,11 @@ void sect_NewSection(char const *name, uint32_t type, uint32_t org,
void sect_SetLoadSection(char const *name, uint32_t type, uint32_t org, void sect_SetLoadSection(char const *name, uint32_t type, uint32_t org,
struct SectionSpec const *attribs, enum SectionModifier mod) struct SectionSpec const *attribs, enum SectionModifier mod)
{ {
// Important info: currently, UNION and LOAD cannot interact, since UNION is prohibited in
// "code" sections, whereas LOAD is restricted to them.
// Therefore, any interactions are NOT TESTED, so lift either of those restrictions at
// your own peril! ^^
if (!checkcodesection()) if (!checkcodesection())
return; return;
@@ -547,6 +554,11 @@ static void createPatch(enum PatchType type, struct Expression const *expr, uint
void sect_StartUnion(void) void sect_StartUnion(void)
{ {
// Important info: currently, UNION and LOAD cannot interact, since UNION is prohibited in
// "code" sections, whereas LOAD is restricted to them.
// Therefore, any interactions are NOT TESTED, so lift either of those restrictions at
// your own peril! ^^
if (!currentSection) { if (!currentSection) {
error("UNIONs must be inside a SECTION\n"); error("UNIONs must be inside a SECTION\n");
return; return;
@@ -616,7 +628,7 @@ void sect_AbsByte(uint8_t b)
writebyte(b); writebyte(b);
} }
void sect_AbsByteGroup(uint8_t const *s, int32_t length) void sect_AbsByteGroup(uint8_t const *s, size_t length)
{ {
if (!checkcodesection()) if (!checkcodesection())
return; return;
@@ -627,7 +639,7 @@ void sect_AbsByteGroup(uint8_t const *s, int32_t length)
writebyte(*s++); writebyte(*s++);
} }
void sect_AbsWordGroup(uint8_t const *s, int32_t length) void sect_AbsWordGroup(uint8_t const *s, size_t length)
{ {
if (!checkcodesection()) if (!checkcodesection())
return; return;
@@ -638,7 +650,7 @@ void sect_AbsWordGroup(uint8_t const *s, int32_t length)
writeword(*s++); writeword(*s++);
} }
void sect_AbsLongGroup(uint8_t const *s, int32_t length) void sect_AbsLongGroup(uint8_t const *s, size_t length)
{ {
if (!checkcodesection()) if (!checkcodesection())
return; return;
@@ -652,7 +664,7 @@ void sect_AbsLongGroup(uint8_t const *s, int32_t length)
/* /*
* Skip this many bytes * Skip this many bytes
*/ */
void sect_Skip(int32_t skip, bool ds) void sect_Skip(uint32_t skip, bool ds)
{ {
if (!checksection()) if (!checksection())
return; return;
@@ -939,9 +951,7 @@ void sect_BinaryFileSlice(char const *s, int32_t start_pos, int32_t length)
(void)fgetc(f); (void)fgetc(f);
} }
int32_t todo = length; while (length--) {
while (todo--) {
int byte = fgetc(f); int byte = fgetc(f);
if (byte != EOF) { if (byte != EOF) {
@@ -950,7 +960,7 @@ void sect_BinaryFileSlice(char const *s, int32_t start_pos, int32_t length)
error("Error reading INCBIN file '%s': %s\n", s, strerror(errno)); error("Error reading INCBIN file '%s': %s\n", s, strerror(errno));
} else { } else {
error("Premature end of file (%" PRId32 " bytes left to read)\n", error("Premature end of file (%" PRId32 " bytes left to read)\n",
todo + 1); length + 1);
} }
} }
@@ -963,22 +973,24 @@ cleanup:
*/ */
void sect_PushSection(void) void sect_PushSection(void)
{ {
struct SectionStackEntry *sect = malloc(sizeof(*sect)); struct SectionStackEntry *entry = malloc(sizeof(*entry));
if (sect == NULL) if (entry == NULL)
fatalerror("No memory for section stack: %s\n", strerror(errno)); fatalerror("No memory for section stack: %s\n", strerror(errno));
sect->section = currentSection; entry->section = currentSection;
sect->loadSection = currentLoadSection; entry->loadSection = currentLoadSection;
sect->scope = sym_GetCurrentSymbolScope(); entry->scope = sym_GetCurrentSymbolScope();
sect->offset = curOffset; entry->offset = curOffset;
sect->loadOffset = loadOffset; entry->loadOffset = loadOffset;
sect->next = sectionStack; entry->unionStack = unionStack;
sectionStack = sect; entry->next = sectionStack;
sectionStack = entry;
// Reset the section scope // Reset the section scope
currentSection = NULL; currentSection = NULL;
currentLoadSection = NULL; currentLoadSection = NULL;
sym_SetCurrentSymbolScope(NULL); sym_SetCurrentSymbolScope(NULL);
unionStack = NULL;
} }
void sect_PopSection(void) void sect_PopSection(void)
@@ -989,16 +1001,35 @@ void sect_PopSection(void)
if (currentLoadSection) if (currentLoadSection)
fatalerror("Cannot change the section within a `LOAD` block!\n"); fatalerror("Cannot change the section within a `LOAD` block!\n");
struct SectionStackEntry *sect; struct SectionStackEntry *entry = sectionStack;
sect = sectionStack;
changeSection(); changeSection();
currentSection = sect->section; currentSection = entry->section;
currentLoadSection = sect->loadSection; currentLoadSection = entry->loadSection;
sym_SetCurrentSymbolScope(sect->scope); sym_SetCurrentSymbolScope(entry->scope);
curOffset = sect->offset; curOffset = entry->offset;
loadOffset = sect->loadOffset; loadOffset = entry->loadOffset;
unionStack = entry->unionStack;
sectionStack = sect->next; sectionStack = entry->next;
free(sect); free(entry);
}
bool sect_IsSizeKnown(struct Section const NONNULL(sect))
{
// SECTION UNION and SECTION FRAGMENT can still grow
if (sect->modifier != SECTION_NORMAL)
return false;
// The current section (or current load section if within one) is still growing
if (sect == currentSection || sect == currentLoadSection)
return false;
// Any section on the stack is still growing
for (struct SectionStackEntry *stack = sectionStack; stack; stack = stack->next) {
if (stack->section && !strcmp(sect->name, stack->section->name))
return false;
}
return true;
} }

View File

@@ -29,8 +29,7 @@
#include "asm/util.h" #include "asm/util.h"
#include "asm/warning.h" #include "asm/warning.h"
#include "extern/err.h" #include "error.h"
#include "hashmap.h" #include "hashmap.h"
#include "helpers.h" #include "helpers.h"
#include "version.h" #include "version.h"
@@ -96,6 +95,7 @@ static char const *Callback__FILE__(void)
char const *fileName = fstk_GetFileName(); char const *fileName = fstk_GetFileName();
size_t j = 1; size_t j = 1;
assert(fileName[0]);
/* The assertion above ensures the loop runs at least once */ /* The assertion above ensures the loop runs at least once */
for (size_t i = 0; fileName[i]; i++, j++) { for (size_t i = 0; fileName[i]; i++, j++) {
/* Account for the extra backslash inserted below */ /* Account for the extra backslash inserted below */
@@ -244,18 +244,18 @@ struct Symbol *sym_FindUnscopedSymbol(char const *symName)
struct Symbol *sym_FindScopedSymbol(char const *symName) struct Symbol *sym_FindScopedSymbol(char const *symName)
{ {
char const *dotPtr = strchr(symName, '.'); char const *localName = strchr(symName, '.');
if (dotPtr) { if (localName) {
if (strchr(dotPtr + 1, '.')) if (strchr(localName + 1, '.'))
fatalerror("'%s' is a nonsensical reference to a nested local symbol\n", fatalerror("'%s' is a nonsensical reference to a nested local symbol\n",
symName); symName);
/* If auto-scoped local label, expand the name */ /* If auto-scoped local label, expand the name */
if (dotPtr == symName) { /* Meaning, the name begins with the dot */ if (localName == symName) { /* Meaning, the name begins with the dot */
char fullname[MAXSYMLEN + 1]; char fullName[MAXSYMLEN + 1];
fullSymbolName(fullname, sizeof(fullname), symName, labelScope); fullSymbolName(fullName, sizeof(fullName), symName, labelScope);
return sym_FindExactSymbol(fullname); return sym_FindExactSymbol(fullName);
} }
} }
return sym_FindExactSymbol(symName); return sym_FindExactSymbol(symName);
@@ -475,15 +475,15 @@ struct Symbol *sym_RedefString(char const *symName, char const *value)
} }
/* /*
* Alter a SET symbol's value * Alter a mutable symbol's value
*/ */
struct Symbol *sym_AddSet(char const *symName, int32_t value) struct Symbol *sym_AddVar(char const *symName, int32_t value)
{ {
struct Symbol *sym = sym_FindExactSymbol(symName); struct Symbol *sym = sym_FindExactSymbol(symName);
if (!sym) { if (!sym) {
sym = createsymbol(symName); sym = createsymbol(symName);
} else if (sym_IsDefined(sym) && sym->type != SYM_SET) { } else if (sym_IsDefined(sym) && sym->type != SYM_VAR) {
error("'%s' already defined as %s at ", error("'%s' already defined as %s at ",
symName, sym->type == SYM_LABEL ? "label" : "constant"); symName, sym->type == SYM_LABEL ? "label" : "constant");
dumpFilename(sym); dumpFilename(sym);
@@ -493,7 +493,7 @@ struct Symbol *sym_AddSet(char const *symName, int32_t value)
updateSymbolFilename(sym); updateSymbolFilename(sym);
} }
sym->type = SYM_SET; sym->type = SYM_VAR;
sym->value = value; sym->value = value;
return sym; return sym;
@@ -532,7 +532,7 @@ static struct Symbol *addLabel(char const *symName)
} }
/* /*
* Add a local (.name or Parent.name) relocatable symbol * Add a local (`.name` or `Parent.name`) relocatable symbol
*/ */
struct Symbol *sym_AddLocalLabel(char const *symName) struct Symbol *sym_AddLocalLabel(char const *symName)
{ {
@@ -540,34 +540,40 @@ struct Symbol *sym_AddLocalLabel(char const *symName)
error("Local label '%s' in main scope\n", symName); error("Local label '%s' in main scope\n", symName);
return NULL; return NULL;
} }
assert(!strchr(labelScope, '.')); /* Assuming no dots in `labelScope` */
char fullname[MAXSYMLEN + 1]; char fullName[MAXSYMLEN + 1];
char const *localName = strchr(symName, '.');
if (symName[0] == '.') { assert(localName); /* There should be at least one dot in `symName` */
/* If symbol is of the form `.name`, expand to the full `Parent.name` name */ /* Check for something after the dot in `localName` */
fullSymbolName(fullname, sizeof(fullname), symName, labelScope); if (localName[1] == '\0') {
symName = fullname; /* Use the expanded name instead */ fatalerror("'%s' is a nonsensical reference to an empty local label\n",
symName);
}
/* Check for more than one dot in `localName` */
if (strchr(localName + 1, '.'))
fatalerror("'%s' is a nonsensical reference to a nested local label\n",
symName);
if (localName == symName) {
/* Expand `symName` to the full `labelScope.symName` name */
fullSymbolName(fullName, sizeof(fullName), symName, labelScope);
symName = fullName;
} else { } else {
size_t i = 0; size_t i = 0;
/* Otherwise, check that `Parent` is in fact the current scope */ /* Find where `labelScope` and `symName` first differ */
while (labelScope[i] && symName[i] == labelScope[i]) while (labelScope[i] && symName[i] == labelScope[i])
i++; i++;
/* Assuming no dots in `labelScope` */
assert(strchr(&symName[i], '.')); /* There should be at least one dot, though */
size_t parentLen = i + (strchr(&symName[i], '.') - symName);
/* /* Check that `symName` starts with `labelScope` and then a '.' */
* Check that `labelScope[i]` ended the check, guaranteeing that `symName` is at
* least as long, and then that this was the entire `Parent` part of `symName`.
*/
if (labelScope[i] != '\0' || symName[i] != '.') { if (labelScope[i] != '\0' || symName[i] != '.') {
size_t parentLen = localName - symName;
assert(parentLen <= INT_MAX); assert(parentLen <= INT_MAX);
error("Not currently in the scope of '%.*s'\n", (int)parentLen, symName); error("Not currently in the scope of '%.*s'\n", (int)parentLen, symName);
} }
if (strchr(&symName[parentLen + 1], '.')) /* There will at least be a terminator */
fatalerror("'%s' is a nonsensical reference to a nested local label\n",
symName);
} }
return addLabel(symName); return addLabel(symName);
@@ -735,7 +741,7 @@ void sym_Init(time_t now)
__FILE__Symbol->type = SYM_EQUS; __FILE__Symbol->type = SYM_EQUS;
__FILE__Symbol->strCallback = Callback__FILE__; __FILE__Symbol->strCallback = Callback__FILE__;
sym_AddSet("_RS", 0)->isBuiltin = true; sym_AddVar("_RS", 0)->isBuiltin = true;
#define addNumber(name, val) sym_AddEqu(name, val)->isBuiltin = true #define addNumber(name, val) sym_AddEqu(name, val)->isBuiltin = true
#define addString(name, val) sym_AddString(name, val)->isBuiltin = true #define addString(name, val) sym_AddString(name, val)->isBuiltin = true

View File

@@ -6,6 +6,7 @@
* SPDX-License-Identifier: MIT * SPDX-License-Identifier: MIT
*/ */
#include <inttypes.h>
#include <limits.h> #include <limits.h>
#include <stdarg.h> #include <stdarg.h>
#include <stdint.h> #include <stdint.h>
@@ -17,11 +18,11 @@
#include "asm/main.h" #include "asm/main.h"
#include "asm/warning.h" #include "asm/warning.h"
#include "extern/err.h" #include "error.h"
unsigned int nbErrors = 0; unsigned int nbErrors = 0;
static enum WarningState const defaultWarnings[NB_WARNINGS] = { static const enum WarningState defaultWarnings[ARRAY_SIZE(warningStates)] = {
[WARNING_ASSERT] = WARNING_ENABLED, [WARNING_ASSERT] = WARNING_ENABLED,
[WARNING_BACKWARDS_FOR] = WARNING_DISABLED, [WARNING_BACKWARDS_FOR] = WARNING_DISABLED,
[WARNING_BUILTIN_ARG] = WARNING_DISABLED, [WARNING_BUILTIN_ARG] = WARNING_DISABLED,
@@ -37,11 +38,15 @@ static enum WarningState const defaultWarnings[NB_WARNINGS] = {
[WARNING_OBSOLETE] = WARNING_ENABLED, [WARNING_OBSOLETE] = WARNING_ENABLED,
[WARNING_SHIFT] = WARNING_DISABLED, [WARNING_SHIFT] = WARNING_DISABLED,
[WARNING_SHIFT_AMOUNT] = WARNING_DISABLED, [WARNING_SHIFT_AMOUNT] = WARNING_DISABLED,
[WARNING_TRUNCATION] = WARNING_ENABLED,
[WARNING_USER] = WARNING_ENABLED, [WARNING_USER] = WARNING_ENABLED,
[WARNING_NUMERIC_STRING_1] = WARNING_ENABLED,
[WARNING_NUMERIC_STRING_2] = WARNING_DISABLED,
[WARNING_TRUNCATION_1] = WARNING_ENABLED,
[WARNING_TRUNCATION_2] = WARNING_DISABLED,
}; };
enum WarningState warningStates[NB_WARNINGS]; enum WarningState warningStates[ARRAY_SIZE(warningStates)];
bool warningsAreErrors; /* Set if `-Werror` was specified */ bool warningsAreErrors; /* Set if `-Werror` was specified */
@@ -64,7 +69,7 @@ static enum WarningState warningState(enum WarningID id)
return state; return state;
} }
static char const *warningFlags[NB_WARNINGS_ALL] = { static const char * const warningFlags[NB_WARNINGS] = {
"assert", "assert",
"backwards-for", "backwards-for",
"builtin-args", "builtin-args",
@@ -80,15 +85,66 @@ static char const *warningFlags[NB_WARNINGS_ALL] = {
"obsolete", "obsolete",
"shift", "shift",
"shift-amount", "shift-amount",
"truncation",
"user", "user",
// Parametric warnings
"numeric-string",
"numeric-string",
"truncation",
"truncation",
/* Meta warnings */ /* Meta warnings */
"all", "all",
"extra", "extra",
"everything" /* Especially useful for testing */ "everything", /* Especially useful for testing */
}; };
static const struct {
char const *name;
uint8_t nbLevels;
uint8_t defaultLevel;
} paramWarnings[] = {
{ "numeric-string", 2, 1 },
{ "truncation", 2, 2 },
};
static bool tryProcessParamWarning(char const *flag, uint8_t param, enum WarningState state)
{
enum WarningID baseID = PARAM_WARNINGS_START;
for (size_t i = 0; i < ARRAY_SIZE(paramWarnings); i++) {
uint8_t maxParam = paramWarnings[i].nbLevels;
if (!strcmp(paramWarnings[i].name, flag)) { // Match!
// If making the warning an error but param is 0, set to the maximum
// This accommodates `-Werror=flag`, but also `-Werror=flag=0`, which is
// thus filtered out by the caller.
// A param of 0 makes sense for disabling everything, but neither for
// enabling nor "erroring". Use the default for those.
if (param == 0 && state != WARNING_DISABLED) {
param = paramWarnings[i].defaultLevel;
} else if (param > maxParam) {
if (param != 255) // Don't warn if already capped
warnx("Got parameter %" PRIu8
" for warning flag \"%s\", but the maximum is %"
PRIu8 "; capping.\n",
param, flag, maxParam);
param = maxParam;
}
// Set the first <param> to enabled/error, and disable the rest
for (uint8_t ofs = 0; ofs < maxParam; ofs++) {
warningStates[baseID + ofs] =
ofs < param ? state : WARNING_DISABLED;
}
return true;
}
baseID += maxParam;
}
return false;
}
enum MetaWarningCommand { enum MetaWarningCommand {
META_WARNING_DONE = NB_WARNINGS META_WARNING_DONE = NB_WARNINGS
}; };
@@ -102,6 +158,9 @@ static uint8_t const _wallCommands[] = {
WARNING_EMPTY_STRRPL, WARNING_EMPTY_STRRPL,
WARNING_LARGE_CONSTANT, WARNING_LARGE_CONSTANT,
WARNING_LONG_STR, WARNING_LONG_STR,
WARNING_NESTED_COMMENT,
WARNING_OBSOLETE,
WARNING_NUMERIC_STRING_1,
META_WARNING_DONE META_WARNING_DONE
}; };
@@ -110,6 +169,10 @@ static uint8_t const _wextraCommands[] = {
WARNING_EMPTY_MACRO_ARG, WARNING_EMPTY_MACRO_ARG,
WARNING_MACRO_SHIFT, WARNING_MACRO_SHIFT,
WARNING_NESTED_COMMENT, WARNING_NESTED_COMMENT,
WARNING_OBSOLETE,
WARNING_NUMERIC_STRING_2,
WARNING_TRUNCATION_1,
WARNING_TRUNCATION_2,
META_WARNING_DONE META_WARNING_DONE
}; };
@@ -128,7 +191,10 @@ static uint8_t const _weverythingCommands[] = {
WARNING_OBSOLETE, WARNING_OBSOLETE,
WARNING_SHIFT, WARNING_SHIFT,
WARNING_SHIFT_AMOUNT, WARNING_SHIFT_AMOUNT,
/* WARNING_TRUNCATION, */ WARNING_NUMERIC_STRING_1,
WARNING_NUMERIC_STRING_2,
WARNING_TRUNCATION_1,
WARNING_TRUNCATION_2,
/* WARNING_USER, */ /* WARNING_USER, */
META_WARNING_DONE META_WARNING_DONE
}; };
@@ -139,36 +205,33 @@ static uint8_t const *metaWarningCommands[NB_META_WARNINGS] = {
_weverythingCommands _weverythingCommands
}; };
void processWarningFlag(char const *flag) void processWarningFlag(char *flag)
{ {
static bool setError = false; static bool setError = false;
/* First, try to match against a "meta" warning */ /* First, try to match against a "meta" warning */
for (enum WarningID id = NB_WARNINGS; id < NB_WARNINGS_ALL; id++) { for (enum WarningID id = META_WARNINGS_START; id < NB_WARNINGS; id++) {
/* TODO: improve the matching performance? */ /* TODO: improve the matching performance? */
if (!strcmp(flag, warningFlags[id])) { if (!strcmp(flag, warningFlags[id])) {
/* We got a match! */ /* We got a match! */
if (setError) if (setError)
errx(1, "Cannot make meta warning \"%s\" into an error", errx("Cannot make meta warning \"%s\" into an error",
flag); flag);
uint8_t const *ptr = metaWarningCommands[id - NB_WARNINGS]; for (uint8_t const *ptr = metaWarningCommands[id - META_WARNINGS_START];
*ptr != META_WARNING_DONE; ptr++) {
for (;;) {
if (*ptr == META_WARNING_DONE)
return;
/* Warning flag, set without override */ /* Warning flag, set without override */
if (warningStates[*ptr] == WARNING_DEFAULT) if (warningStates[*ptr] == WARNING_DEFAULT)
warningStates[*ptr] = WARNING_ENABLED; warningStates[*ptr] = WARNING_ENABLED;
ptr++;
} }
return;
} }
} }
/* If it's not a meta warning, specially check against `-Werror` */ /* If it's not a meta warning, specially check against `-Werror` */
if (!strncmp(flag, "error", strlen("error"))) { if (!strncmp(flag, "error", strlen("error"))) {
char const *errorFlag = flag + strlen("error"); char *errorFlag = flag + strlen("error");
switch (*errorFlag) { switch (*errorFlag) {
case '\0': case '\0':
@@ -177,7 +240,7 @@ void processWarningFlag(char const *flag)
return; return;
case '=': case '=':
/* `-Werror=XXX */ /* `-Werror=XXX` */
setError = true; setError = true;
processWarningFlag(errorFlag + 1); /* Skip the `=` */ processWarningFlag(errorFlag + 1); /* Skip the `=` */
setError = false; setError = false;
@@ -189,14 +252,60 @@ void processWarningFlag(char const *flag)
/* Well, it's either a normal warning or a mistake */ /* Well, it's either a normal warning or a mistake */
/* Check if this is a negation */
bool isNegation = !strncmp(flag, "no-", strlen("no-")) && !setError;
char const *rootFlag = isNegation ? flag + strlen("no-") : flag;
enum WarningState state = setError ? WARNING_ERROR : enum WarningState state = setError ? WARNING_ERROR :
isNegation ? WARNING_DISABLED : WARNING_ENABLED; /* Not an error, then check if this is a negation */
strncmp(flag, "no-", strlen("no-")) ? WARNING_ENABLED
: WARNING_DISABLED;
char const *rootFlag = state == WARNING_DISABLED ? flag + strlen("no-") : flag;
// Is this a "parametric" warning?
if (state != WARNING_DISABLED) { // The `no-` form cannot be parametrized
// First, check if there is an "equals" sign followed by a decimal number
char *equals = strchr(rootFlag, '=');
if (equals && equals[1] != '\0') { // Ignore an equal sign at the very end as well
// Is the rest of the string a decimal number?
// We want to avoid `strtoul`'s whitespace and sign, so we parse manually
uint8_t param = 0;
char const *ptr = equals + 1;
bool warned = false;
// The `if`'s condition above ensures that this will run at least once
do {
// If we don't have a digit, bail
if (*ptr < '0' || *ptr > '9')
break;
// Avoid overflowing!
if (param > UINT8_MAX - (*ptr - '0')) {
if (!warned)
warnx("Invalid warning flag \"%s\": capping parameter at 255\n",
flag);
warned = true; // Only warn once, cap always
param = 255;
continue;
}
param = param * 10 + (*ptr - '0');
ptr++;
} while (*ptr);
// If we managed to the end of the string, check that the warning indeed
// accepts a parameter
if (*ptr == '\0') {
if (setError && param == 0) {
warnx("Ignoring nonsensical warning flag \"%s\"\n", flag);
return;
}
*equals = '\0'; // Truncate the param at the '='
if (tryProcessParamWarning(rootFlag, param,
param == 0 ? WARNING_DISABLED : state))
return;
}
}
}
/* Try to match the flag against a "normal" flag */ /* Try to match the flag against a "normal" flag */
for (enum WarningID id = 0; id < NB_WARNINGS; id++) { for (enum WarningID id = 0; id < NB_PLAIN_WARNINGS; id++) {
if (!strcmp(rootFlag, warningFlags[id])) { if (!strcmp(rootFlag, warningFlags[id])) {
/* We got a match! */ /* We got a match! */
warningStates[id] = state; warningStates[id] = state;
@@ -204,10 +313,15 @@ void processWarningFlag(char const *flag)
} }
} }
// Lastly, this might be a "parametric" warning without an equals sign
// If it is, treat the param as 1 if enabling, or 0 if disabling
if (tryProcessParamWarning(rootFlag, 0, state))
return;
warnx("Unknown warning `%s`", flag); warnx("Unknown warning `%s`", flag);
} }
void printDiag(const char *fmt, va_list args, char const *type, void printDiag(char const *fmt, va_list args, char const *type,
char const *flagfmt, char const *flag) char const *flagfmt, char const *flag)
{ {
fputs(type, stderr); fputs(type, stderr);
@@ -217,17 +331,17 @@ void printDiag(const char *fmt, va_list args, char const *type,
lexer_DumpStringExpansions(); lexer_DumpStringExpansions();
} }
void error(const char *fmt, ...) void error(char const *fmt, ...)
{ {
va_list args; va_list args;
va_start(args, fmt); va_start(args, fmt);
printDiag(fmt, args, "ERROR: ", ":\n ", NULL); printDiag(fmt, args, "error: ", ":\n ", NULL);
va_end(args); va_end(args);
nbErrors++; nbErrors++;
} }
_Noreturn void fatalerror(const char *fmt, ...) _Noreturn void fatalerror(char const *fmt, ...)
{ {
va_list args; va_list args;
@@ -250,7 +364,7 @@ void warning(enum WarningID id, char const *fmt, ...)
return; return;
case WARNING_ERROR: case WARNING_ERROR:
printDiag(fmt, args, "ERROR: ", ": [-Werror=%s]\n ", flag); printDiag(fmt, args, "error: ", ": [-Werror=%s]\n ", flag);
va_end(args); va_end(args);
return; return;

87
src/error.c Normal file
View File

@@ -0,0 +1,87 @@
/*
* This file is part of RGBDS.
*
* Copyright (c) 2005-2021, Rich Felker and RGBDS contributors.
*
* SPDX-License-Identifier: MIT
*/
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "error.h"
#include "platform.h"
static void vwarn(char const NONNULL(fmt), va_list ap)
{
fprintf(stderr, "warning: ");
vfprintf(stderr, fmt, ap);
fputs(": ", stderr);
perror(NULL);
}
static void vwarnx(char const NONNULL(fmt), va_list ap)
{
fprintf(stderr, "warning");
fputs(": ", stderr);
vfprintf(stderr, fmt, ap);
putc('\n', stderr);
}
_Noreturn static void verr(char const NONNULL(fmt), va_list ap)
{
fprintf(stderr, "error: ");
vfprintf(stderr, fmt, ap);
fputs(": ", stderr);
fputs(strerror(errno), stderr);
putc('\n', stderr);
exit(1);
}
_Noreturn static void verrx(char const NONNULL(fmt), va_list ap)
{
fprintf(stderr, "error");
fputs(": ", stderr);
vfprintf(stderr, fmt, ap);
putc('\n', stderr);
exit(1);
}
void warn(char const NONNULL(fmt), ...)
{
va_list ap;
va_start(ap, fmt);
vwarn(fmt, ap);
va_end(ap);
}
void warnx(char const NONNULL(fmt), ...)
{
va_list ap;
va_start(ap, fmt);
vwarnx(fmt, ap);
va_end(ap);
}
_Noreturn void err(char const NONNULL(fmt), ...)
{
va_list ap;
va_start(ap, fmt);
verr(fmt, ap);
va_end(ap);
}
_Noreturn void errx(char const NONNULL(fmt), ...)
{
va_list ap;
va_start(ap, fmt);
verrx(fmt, ap);
va_end(ap);
}

94
src/extern/err.c vendored
View File

@@ -1,94 +0,0 @@
/*
* This file is part of RGBDS.
*
* Copyright (c) 2005-2018, Rich Felker and RGBDS contributors.
*
* SPDX-License-Identifier: MIT
*/
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "extern/err.h"
void rgbds_vwarn(const char *fmt, va_list ap)
{
fprintf(stderr, "warning: ");
if (fmt) {
vfprintf(stderr, fmt, ap);
fputs(": ", stderr);
}
perror(NULL);
}
void rgbds_vwarnx(const char *fmt, va_list ap)
{
fprintf(stderr, "warning");
if (fmt) {
fputs(": ", stderr);
vfprintf(stderr, fmt, ap);
}
putc('\n', stderr);
}
_Noreturn void rgbds_verr(int status, const char *fmt, va_list ap)
{
fprintf(stderr, "error: ");
if (fmt) {
vfprintf(stderr, fmt, ap);
fputs(": ", stderr);
}
fputs(strerror(errno), stderr);
putc('\n', stderr);
exit(status);
}
_Noreturn void rgbds_verrx(int status, const char *fmt, va_list ap)
{
fprintf(stderr, "error");
if (fmt) {
fputs(": ", stderr);
vfprintf(stderr, fmt, ap);
}
putc('\n', stderr);
exit(status);
}
void rgbds_warn(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vwarn(fmt, ap);
va_end(ap);
}
void rgbds_warnx(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vwarnx(fmt, ap);
va_end(ap);
}
_Noreturn void rgbds_err(int status, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
verr(status, fmt, ap);
va_end(ap);
}
_Noreturn void rgbds_errx(int status, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
verrx(status, fmt, ap);
va_end(ap);
}

18
src/extern/getopt.c vendored
View File

@@ -37,7 +37,7 @@ int musl_optind = 1, musl_opterr = 1, musl_optopt;
int musl_optreset = 0; int musl_optreset = 0;
static int musl_optpos; static int musl_optpos;
static void musl_getopt_msg(const char *a, const char *b, const char *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;
@@ -47,7 +47,7 @@ static void musl_getopt_msg(const char *a, const char *b, const char *c, size_t
putc('\n', f); putc('\n', f);
} }
static int getopt(int argc, char *argv[], const char *optstring) static int getopt(int argc, char *argv[], char const *optstring)
{ {
int i; int i;
wchar_t c, d; wchar_t c, d;
@@ -140,9 +140,11 @@ static void permute(char **argv, int dest, int src)
argv[dest] = tmp; argv[dest] = tmp;
} }
static int musl_getopt_long_core(int argc, char **argv, const char *optstring, const struct option *longopts, int *idx, int longonly); static int musl_getopt_long_core(int argc, char **argv, char const *optstring,
const struct option *longopts, int *idx, int longonly);
static int musl_getopt_long(int argc, char **argv, const char *optstring, const struct option *longopts, int *idx, int longonly) static int musl_getopt_long(int argc, char **argv, char const *optstring,
const struct option *longopts, int *idx, int longonly)
{ {
int ret, skipped, resumed; int ret, skipped, resumed;
@@ -178,7 +180,8 @@ static int musl_getopt_long(int argc, char **argv, const char *optstring, const
return ret; return ret;
} }
static int musl_getopt_long_core(int argc, char **argv, const char *optstring, const struct option *longopts, int *idx, int longonly) static int musl_getopt_long_core(int argc, char **argv, char const *optstring,
const struct option *longopts, int *idx, int longonly)
{ {
musl_optarg = 0; musl_optarg = 0;
if (longopts && argv[musl_optind][0] == '-' && if (longopts && argv[musl_optind][0] == '-' &&
@@ -189,7 +192,7 @@ static int musl_getopt_long_core(int argc, char **argv, const char *optstring, c
char *arg = 0, *opt, *start = argv[musl_optind] + 1; char *arg = 0, *opt, *start = argv[musl_optind] + 1;
for (cnt = i = 0; longopts[i].name; i++) { for (cnt = i = 0; longopts[i].name; i++) {
const char *name = longopts[i].name; char const *name = longopts[i].name;
opt = start; opt = start;
if (*opt == '-') if (*opt == '-')
@@ -277,7 +280,8 @@ static int musl_getopt_long_core(int argc, char **argv, const char *optstring, c
return getopt(argc, argv, optstring); return getopt(argc, argv, optstring);
} }
int musl_getopt_long_only(int argc, char **argv, const char *optstring, const struct option *longopts, int *idx) int musl_getopt_long_only(int argc, char **argv, char const *optstring,
const struct option *longopts, int *idx)
{ {
return musl_getopt_long(argc, argv, optstring, longopts, idx, 1); return musl_getopt_long(argc, argv, optstring, longopts, idx, 1);
} }

View File

@@ -30,7 +30,7 @@
#define BANK_SIZE 0x4000 #define BANK_SIZE 0x4000
/* Short options */ /* Short options */
static const char *optstring = "Ccf:i:jk:l:m:n:p:r:st:Vv"; static const char *optstring = "Ccf:i:jk:l:m:n:Op:r:st:Vv";
/* /*
* Equivalent long options * Equivalent long options
@@ -52,6 +52,7 @@ static struct option const longopts[] = {
{ "old-licensee", required_argument, NULL, 'l' }, { "old-licensee", required_argument, NULL, 'l' },
{ "mbc-type", required_argument, NULL, 'm' }, { "mbc-type", required_argument, NULL, 'm' },
{ "rom-version", required_argument, NULL, 'n' }, { "rom-version", required_argument, NULL, 'n' },
{ "overwrite", no_argument, NULL, 'O' },
{ "pad-value", required_argument, NULL, 'p' }, { "pad-value", required_argument, NULL, 'p' },
{ "ram-size", required_argument, NULL, 'r' }, { "ram-size", required_argument, NULL, 'r' },
{ "sgb-compatible", no_argument, NULL, 's' }, { "sgb-compatible", no_argument, NULL, 's' },
@@ -64,7 +65,7 @@ static struct option const longopts[] = {
static void printUsage(void) static void printUsage(void)
{ {
fputs( fputs(
"Usage: rgbfix [-jsVv] [-C | -c] [-f <fix_spec>] [-i <game_id>] [-k <licensee>]\n" "Usage: rgbfix [-jOsVv] [-C | -c] [-f <fix_spec>] [-i <game_id>] [-k <licensee>]\n"
" [-l <licensee_byte>] [-m <mbc_type>] [-n <rom_version>]\n" " [-l <licensee_byte>] [-m <mbc_type>] [-n <rom_version>]\n"
" [-p <pad_value>] [-r <ram_size>] [-t <title_str>] [<file> ...]\n" " [-p <pad_value>] [-r <ram_size>] [-t <title_str>] [<file> ...]\n"
"Useful options:\n" "Useful options:\n"
@@ -143,18 +144,18 @@ enum MbcType {
TPP1_RUMBLE = 0x101, TPP1_RUMBLE = 0x101,
TPP1_MULTIRUMBLE = 0x102, // Should not be possible TPP1_MULTIRUMBLE = 0x102, // Should not be possible
TPP1_MULTIRUMBLE_RUMBLE = 0x103, TPP1_MULTIRUMBLE_RUMBLE = 0x103,
TPP1_RTC = 0x104, TPP1_TIMER = 0x104,
TPP1_RTC_RUMBLE = 0x105, TPP1_TIMER_RUMBLE = 0x105,
TPP1_RTC_MULTIRUMBLE = 0x106, // Should not be possible TPP1_TIMER_MULTIRUMBLE = 0x106, // Should not be possible
TPP1_RTC_MULTIRUMBLE_RUMBLE = 0x107, TPP1_TIMER_MULTIRUMBLE_RUMBLE = 0x107,
TPP1_BATTERY = 0x108, TPP1_BATTERY = 0x108,
TPP1_BATTERY_RUMBLE = 0x109, TPP1_BATTERY_RUMBLE = 0x109,
TPP1_BATTERY_MULTIRUMBLE = 0x10a, // Should not be possible TPP1_BATTERY_MULTIRUMBLE = 0x10A, // Should not be possible
TPP1_BATTERY_MULTIRUMBLE_RUMBLE = 0x10b, TPP1_BATTERY_MULTIRUMBLE_RUMBLE = 0x10B,
TPP1_BATTERY_RTC = 0x10c, TPP1_BATTERY_TIMER = 0x10C,
TPP1_BATTERY_RTC_RUMBLE = 0x10d, TPP1_BATTERY_TIMER_RUMBLE = 0x10D,
TPP1_BATTERY_RTC_MULTIRUMBLE = 0x10e, // Should not be possible TPP1_BATTERY_TIMER_MULTIRUMBLE = 0x10E, // Should not be possible
TPP1_BATTERY_RTC_MULTIRUMBLE_RUMBLE = 0x10f, TPP1_BATTERY_TIMER_MULTIRUMBLE_RUMBLE = 0x10F,
// Error values // Error values
MBC_NONE = UNSPECIFIED, // No MBC specified, do not act on it MBC_NONE = UNSPECIFIED, // No MBC specified, do not act on it
@@ -166,7 +167,7 @@ enum MbcType {
static void printAcceptedMBCNames(void) static void printAcceptedMBCNames(void)
{ {
fputs("\tROM ($00) [aka ROM_ONLY]\n", stderr); fputs("\tROM ($00) [aka ROM_ONLY]\n", stderr);
fputs("\tMBC1 ($02), MBC1+RAM ($02), MBC1+RAM+BATTERY ($03)\n", stderr); fputs("\tMBC1 ($01), MBC1+RAM ($02), MBC1+RAM+BATTERY ($03)\n", stderr);
fputs("\tMBC2 ($05), MBC2+BATTERY ($06)\n", stderr); fputs("\tMBC2 ($05), MBC2+BATTERY ($06)\n", stderr);
fputs("\tROM+RAM ($08) [deprecated], ROM+RAM+BATTERY ($09) [deprecated]\n", stderr); fputs("\tROM+RAM ($08) [deprecated], ROM+RAM+BATTERY ($09) [deprecated]\n", stderr);
fputs("\tMMM01 ($0B), MMM01+RAM ($0C), MMM01+RAM+BATTERY ($0D)\n", stderr); fputs("\tMMM01 ($0B), MMM01+RAM ($0C), MMM01+RAM+BATTERY ($0D)\n", stderr);
@@ -181,11 +182,11 @@ static void printAcceptedMBCNames(void)
fputs("\tHUC3 ($FE)\n", stderr); fputs("\tHUC3 ($FE)\n", stderr);
fputs("\tHUC1+RAM+BATTERY ($FF)\n", stderr); fputs("\tHUC1+RAM+BATTERY ($FF)\n", stderr);
fputs("\n\tTPP1_1.0, TPP1_1.0+RUMBLE, TPP1_1.0+MULTIRUMBLE, TPP1_1.0+RTC,\n", stderr); fputs("\n\tTPP1_1.0, TPP1_1.0+RUMBLE, TPP1_1.0+MULTIRUMBLE, TPP1_1.0+TIMER,\n", stderr);
fputs("\tTPP1_1.0+RTC+RUMBLE, TPP1_1.0+RTC+MULTIRUMBLE, TPP1_1.0+BATTERY,\n", stderr); fputs("\tTPP1_1.0+TIMER+RUMBLE, TPP1_1.0+TIMER+MULTIRUMBLE, TPP1_1.0+BATTERY,\n", stderr);
fputs("\tTPP1_1.0+BATTERY+RUMBLE, TPP1_1.0+BATTERY+MULTIRUMBLE,\n", stderr); fputs("\tTPP1_1.0+BATTERY+RUMBLE, TPP1_1.0+BATTERY+MULTIRUMBLE,\n", stderr);
fputs("\tTPP1_1.0+BATTERY+RTC, TPP1_1.0+BATTERY+RTC+RUMBLE,\n", stderr); fputs("\tTPP1_1.0+BATTERY+TIMER, TPP1_1.0+BATTERY+TIMER+RUMBLE,\n", stderr);
fputs("\tTPP1_1.0+BATTERY+RTC+MULTIRUMBLE\n", stderr); fputs("\tTPP1_1.0+BATTERY+TIMER+MULTIRUMBLE\n", stderr);
} }
static uint8_t tpp1Rev[2]; static uint8_t tpp1Rev[2];
@@ -493,7 +494,9 @@ do { \
case MBC3: case MBC3:
// Handle timer, which also requires battery // Handle timer, which also requires battery
if (features & (TIMER & BATTERY)) { if (features & TIMER) {
if (!(features & BATTERY))
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
@@ -642,13 +645,13 @@ static char const *mbcName(enum MbcType type)
case TPP1_MULTIRUMBLE: case TPP1_MULTIRUMBLE:
case TPP1_MULTIRUMBLE_RUMBLE: case TPP1_MULTIRUMBLE_RUMBLE:
return "TPP1+MULTIRUMBLE"; return "TPP1+MULTIRUMBLE";
case TPP1_RTC: case TPP1_TIMER:
return "TPP1+RTC"; return "TPP1+TIMER";
case TPP1_RTC_RUMBLE: case TPP1_TIMER_RUMBLE:
return "TPP1+RTC+RUMBLE"; return "TPP1+TIMER+RUMBLE";
case TPP1_RTC_MULTIRUMBLE: case TPP1_TIMER_MULTIRUMBLE:
case TPP1_RTC_MULTIRUMBLE_RUMBLE: case TPP1_TIMER_MULTIRUMBLE_RUMBLE:
return "TPP1+RTC+MULTIRUMBLE"; return "TPP1+TIMER+MULTIRUMBLE";
case TPP1_BATTERY: case TPP1_BATTERY:
return "TPP1+BATTERY"; return "TPP1+BATTERY";
case TPP1_BATTERY_RUMBLE: case TPP1_BATTERY_RUMBLE:
@@ -656,13 +659,13 @@ static char const *mbcName(enum MbcType type)
case TPP1_BATTERY_MULTIRUMBLE: case TPP1_BATTERY_MULTIRUMBLE:
case TPP1_BATTERY_MULTIRUMBLE_RUMBLE: case TPP1_BATTERY_MULTIRUMBLE_RUMBLE:
return "TPP1+BATTERY+MULTIRUMBLE"; return "TPP1+BATTERY+MULTIRUMBLE";
case TPP1_BATTERY_RTC: case TPP1_BATTERY_TIMER:
return "TPP1+BATTERY+RTC"; return "TPP1+BATTERY+TIMER";
case TPP1_BATTERY_RTC_RUMBLE: case TPP1_BATTERY_TIMER_RUMBLE:
return "TPP1+BATTERY+RTC+RUMBLE"; return "TPP1+BATTERY+TIMER+RUMBLE";
case TPP1_BATTERY_RTC_MULTIRUMBLE: case TPP1_BATTERY_TIMER_MULTIRUMBLE:
case TPP1_BATTERY_RTC_MULTIRUMBLE_RUMBLE: case TPP1_BATTERY_TIMER_MULTIRUMBLE_RUMBLE:
return "TPP1+BATTERY+RTC+MULTIRUMBLE"; return "TPP1+BATTERY+TIMER+MULTIRUMBLE";
// Error values // Error values
case MBC_NONE: case MBC_NONE:
@@ -719,18 +722,18 @@ static bool hasRAM(enum MbcType type)
case TPP1_RUMBLE: case TPP1_RUMBLE:
case TPP1_MULTIRUMBLE: case TPP1_MULTIRUMBLE:
case TPP1_MULTIRUMBLE_RUMBLE: case TPP1_MULTIRUMBLE_RUMBLE:
case TPP1_RTC: case TPP1_TIMER:
case TPP1_RTC_RUMBLE: case TPP1_TIMER_RUMBLE:
case TPP1_RTC_MULTIRUMBLE: case TPP1_TIMER_MULTIRUMBLE:
case TPP1_RTC_MULTIRUMBLE_RUMBLE: case TPP1_TIMER_MULTIRUMBLE_RUMBLE:
case TPP1_BATTERY: case TPP1_BATTERY:
case TPP1_BATTERY_RUMBLE: case TPP1_BATTERY_RUMBLE:
case TPP1_BATTERY_MULTIRUMBLE: case TPP1_BATTERY_MULTIRUMBLE:
case TPP1_BATTERY_MULTIRUMBLE_RUMBLE: case TPP1_BATTERY_MULTIRUMBLE_RUMBLE:
case TPP1_BATTERY_RTC: case TPP1_BATTERY_TIMER:
case TPP1_BATTERY_RTC_RUMBLE: case TPP1_BATTERY_TIMER_RUMBLE:
case TPP1_BATTERY_RTC_MULTIRUMBLE: case TPP1_BATTERY_TIMER_MULTIRUMBLE:
case TPP1_BATTERY_RTC_MULTIRUMBLE_RUMBLE: case TPP1_BATTERY_TIMER_MULTIRUMBLE_RUMBLE:
break; break;
} }
@@ -746,6 +749,15 @@ static const uint8_t ninLogo[] = {
0xDD, 0xDC, 0x99, 0x9F, 0xBB, 0xB9, 0x33, 0x3E 0xDD, 0xDC, 0x99, 0x9F, 0xBB, 0xB9, 0x33, 0x3E
}; };
static const uint8_t trashLogo[] = {
0xFF^0xCE, 0xFF^0xED, 0xFF^0x66, 0xFF^0x66, 0xFF^0xCC, 0xFF^0x0D, 0xFF^0x00, 0xFF^0x0B,
0xFF^0x03, 0xFF^0x73, 0xFF^0x00, 0xFF^0x83, 0xFF^0x00, 0xFF^0x0C, 0xFF^0x00, 0xFF^0x0D,
0xFF^0x00, 0xFF^0x08, 0xFF^0x11, 0xFF^0x1F, 0xFF^0x88, 0xFF^0x89, 0xFF^0x00, 0xFF^0x0E,
0xFF^0xDC, 0xFF^0xCC, 0xFF^0x6E, 0xFF^0xE6, 0xFF^0xDD, 0xFF^0xDD, 0xFF^0xD9, 0xFF^0x99,
0xFF^0xBB, 0xFF^0xBB, 0xFF^0x67, 0xFF^0x63, 0xFF^0x6E, 0xFF^0x0E, 0xFF^0xEC, 0xFF^0xCC,
0xFF^0xDD, 0xFF^0xDC, 0xFF^0x99, 0xFF^0x9F, 0xFF^0xBB, 0xFF^0xB9, 0xFF^0x33, 0xFF^0x3E
};
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
#define FIX_LOGO 0x80 #define FIX_LOGO 0x80
#define TRASH_LOGO 0x40 #define TRASH_LOGO 0x40
@@ -762,6 +774,7 @@ static uint8_t newLicenseeLen;
static uint16_t oldLicensee = UNSPECIFIED; static uint16_t oldLicensee = UNSPECIFIED;
static enum MbcType cartridgeType = MBC_NONE; static enum MbcType cartridgeType = MBC_NONE;
static uint16_t romVersion = UNSPECIFIED; static uint16_t romVersion = UNSPECIFIED;
static bool overwriteRom = false; // If false, warn when overwriting non-zero non-identical bytes
static uint16_t padValue = UNSPECIFIED; static uint16_t padValue = UNSPECIFIED;
static uint16_t ramSize = UNSPECIFIED; static uint16_t ramSize = UNSPECIFIED;
static bool sgb = false; // If false, SGB flags are left alone static bool sgb = false; // If false, SGB flags are left alone
@@ -824,22 +837,47 @@ static ssize_t writeBytes(int fd, void *buf, size_t len)
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)
{
uint8_t origByte = rom0[addr];
if (!overwriteRom && origByte != 0 && origByte != fixedByte)
fprintf(stderr, "warning: Overwrote a non-zero byte in the %s\n", areaName);
rom0[addr] = fixedByte;
}
/** /**
* @param rom0 A pointer to rom0 * @param rom0 A pointer to rom0
* @param startAddr What address to begin checking from * @param startAddr What address to begin checking from
* @param fixed The fixed bytes at the address
* @param size How many bytes to check * @param size How many bytes to check
* @param areaName Name to be displayed in the warning message * @param areaName Name to be displayed in the warning message
*/ */
static void warnNonZero(uint8_t *rom0, uint16_t startAddr, uint8_t size, char const *areaName) static void overwriteBytes(uint8_t *rom0, uint16_t startAddr, uint8_t const *fixed, uint8_t size,
char const *areaName)
{ {
if (!overwriteRom) {
for (uint8_t i = 0; i < size; i++) { for (uint8_t i = 0; i < size; i++) {
if (rom0[i + startAddr] != 0) { uint8_t origByte = rom0[i + startAddr];
fprintf(stderr, "warning: Overwrote a non-zero byte in the %s\n", areaName);
if (origByte != 0 && origByte != fixed[i]) {
fprintf(stderr, "warning: Overwrote a non-zero byte in the %s\n",
areaName);
break; break;
} }
} }
} }
memcpy(&rom0[startAddr], fixed, size);
}
/** /**
* @param input File descriptor to be used for reading * @param input File descriptor to be used for reading
* @param output File descriptor to be used for writing, may be equal to `input` * @param output File descriptor to be used for writing, may be equal to `input`
@@ -857,7 +895,7 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
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));
// Also used as how many bytes to write back when fixing in-place // Also used as how many bytes to write back when fixing in-place
ssize_t headerSize = (cartridgeType & 0xff00) == TPP1 ? 0x154 : 0x150; ssize_t headerSize = (cartridgeType & 0xFF00) == TPP1 ? 0x154 : 0x150;
if (rom0Len == -1) { if (rom0Len == -1) {
report("FATAL: Failed to read \"%s\"'s header: %s\n", name, strerror(errno)); report("FATAL: Failed to read \"%s\"'s header: %s\n", name, strerror(errno));
@@ -870,94 +908,68 @@ 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)) {
warnNonZero(rom0, 0x0104, sizeof(ninLogo), "Nintendo logo"); if (fixSpec & FIX_LOGO)
if (fixSpec & FIX_LOGO) { overwriteBytes(rom0, 0x0104, ninLogo, sizeof(ninLogo), "Nintendo logo");
memcpy(&rom0[0x104], ninLogo, sizeof(ninLogo)); else
} else { overwriteBytes(rom0, 0x0104, trashLogo, sizeof(trashLogo), "Nintendo logo");
for (uint8_t i = 0; i < sizeof(ninLogo); i++)
rom0[i + 0x104] = ~ninLogo[i];
}
} }
if (title) { if (title)
warnNonZero(rom0, 0x134, titleLen, "title"); overwriteBytes(rom0, 0x134, (uint8_t const *)title, titleLen, "title");
memcpy(&rom0[0x134], title, titleLen);
}
if (gameID) { if (gameID)
warnNonZero(rom0, 0x13f, gameIDLen, "manufacturer code"); overwriteBytes(rom0, 0x13F, (uint8_t const *)gameID, gameIDLen, "manufacturer code");
memcpy(&rom0[0x13f], gameID, gameIDLen);
}
if (model != DMG) { if (model != DMG)
warnNonZero(rom0, 0x143, 1, "CGB flag"); overwriteByte(rom0, 0x143, model == BOTH ? 0x80 : 0xC0, "CGB flag");
rom0[0x143] = model == BOTH ? 0x80 : 0xc0;
}
if (newLicensee) { if (newLicensee)
warnNonZero(rom0, 0x144, newLicenseeLen, "new licensee code"); overwriteBytes(rom0, 0x144, (uint8_t const *)newLicensee, newLicenseeLen,
memcpy(&rom0[0x144], newLicensee, newLicenseeLen); "new licensee code");
}
if (sgb) { if (sgb)
warnNonZero(rom0, 0x146, 1, "SGB flag"); overwriteByte(rom0, 0x146, 0x03, "SGB flag");
rom0[0x146] = 0x03;
}
// If a valid MBC was specified... // If a valid MBC was specified...
if (cartridgeType < MBC_NONE) { if (cartridgeType < MBC_NONE) {
warnNonZero(rom0, 0x147, 1, "cartridge type");
uint8_t byte = cartridgeType; uint8_t byte = cartridgeType;
if ((cartridgeType & 0xff00) == TPP1) { if ((cartridgeType & 0xFF00) == TPP1) {
// Cartridge type isn't directly actionable, translate it // Cartridge type isn't directly actionable, translate it
byte = 0xBC; byte = 0xBC;
// The other TPP1 identification bytes will be written below // The other TPP1 identification bytes will be written below
} }
rom0[0x147] = byte; overwriteByte(rom0, 0x147, byte, "cartridge type");
} }
// ROM size will be written last, after evaluating the file's size // ROM size will be written last, after evaluating the file's size
if ((cartridgeType & 0xff00) == TPP1) { if ((cartridgeType & 0xFF00) == TPP1) {
warnNonZero(rom0, 0x149, 2, "TPP1 identification code"); uint8_t const tpp1Code[2] = {0xC1, 0x65};
rom0[0x149] = 0xC1;
rom0[0x14a] = 0x65;
warnNonZero(rom0, 0x150, 2, "TPP1 revision number"); overwriteBytes(rom0, 0x149, tpp1Code, sizeof(tpp1Code), "TPP1 identification code");
rom0[0x150] = tpp1Rev[0];
rom0[0x151] = tpp1Rev[1];
if (ramSize != UNSPECIFIED) { overwriteBytes(rom0, 0x150, tpp1Rev, sizeof(tpp1Rev), "TPP1 revision number");
warnNonZero(rom0, 0x152, 1, "RAM size");
rom0[0x152] = ramSize;
}
warnNonZero(rom0, 0x153, 1, "TPP1 feature flags"); if (ramSize != UNSPECIFIED)
rom0[0x153] = cartridgeType & 0xFF; overwriteByte(rom0, 0x152, ramSize, "RAM size");
overwriteByte(rom0, 0x153, cartridgeType & 0xFF, "TPP1 feature flags");
} else { } else {
// Regular mappers // Regular mappers
if (ramSize != UNSPECIFIED) { if (ramSize != UNSPECIFIED)
warnNonZero(rom0, 0x149, 1, "RAM size"); overwriteByte(rom0, 0x149, ramSize, "RAM size");
rom0[0x149] = ramSize;
if (!japanese)
overwriteByte(rom0, 0x14A, 0x01, "destination code");
} }
if (!japanese) { if (oldLicensee != UNSPECIFIED)
warnNonZero(rom0, 0x14a, 1, "destination code"); overwriteByte(rom0, 0x14B, oldLicensee, "old licensee code");
rom0[0x14a] = 0x01;
}
}
if (oldLicensee != UNSPECIFIED) { if (romVersion != UNSPECIFIED)
warnNonZero(rom0, 0x14b, 1, "old licensee code"); overwriteByte(rom0, 0x14C, romVersion, "mask ROM version number");
rom0[0x14b] = oldLicensee;
}
if (romVersion != UNSPECIFIED) {
warnNonZero(rom0, 0x14c, 1, "mask ROM version number");
rom0[0x14c] = romVersion;
}
// 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.
@@ -1004,8 +1016,7 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
static_assert(0x10000 * BANK_SIZE <= SSIZE_MAX, "Max input file size too large for OS"); static_assert(0x10000 * BANK_SIZE <= SSIZE_MAX, "Max input file size too large for OS");
if (nbBanks == 0x10000) { if (nbBanks == 0x10000) {
report("FATAL: \"%s\" has more than 65536 banks\n", name); report("FATAL: \"%s\" has more than 65536 banks\n", name);
free(romx); goto cleanup;
return;
} }
nbBanks++; nbBanks++;
@@ -1055,17 +1066,19 @@ 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;
warnNonZero(rom0, 0x14d, 1, "header checksum");
rom0[0x14d] = fixSpec & TRASH_HEADER_SUM ? ~sum : sum; overwriteByte(rom0, 0x14D, fixSpec & TRASH_HEADER_SUM ? ~sum : sum,
"header checksum");
} }
if (fixSpec & (FIX_GLOBAL_SUM | TRASH_GLOBAL_SUM)) { if (fixSpec & (FIX_GLOBAL_SUM | TRASH_GLOBAL_SUM)) {
// Computation of the global checksum assumes 0s being stored in its place // Computation of the global checksum does not include the checksum bytes
rom0[0x14e] = 0; assert(rom0Len >= 0x14E);
rom0[0x14f] = 0; for (uint16_t i = 0; i < 0x14E; i++)
for (uint16_t i = 0; i < rom0Len; i++) globalSum += rom0[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) {
@@ -1081,9 +1094,10 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
if (fixSpec & TRASH_GLOBAL_SUM) if (fixSpec & TRASH_GLOBAL_SUM)
globalSum = ~globalSum; globalSum = ~globalSum;
warnNonZero(rom0, 0x14e, 2, "global checksum");
rom0[0x14e] = globalSum >> 8; uint8_t bytes[2] = {globalSum >> 8, globalSum & 0xFF};
rom0[0x14f] = globalSum & 0xff;
overwriteBytes(rom0, 0x14E, bytes, sizeof(bytes), "global checksum");
} }
// In case the output depends on the input, reset to the beginning of the file, and only // In case the output depends on the input, reset to the beginning of the file, and only
@@ -1091,7 +1105,7 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
if (input == output) { if (input == output) {
if (lseek(output, 0, SEEK_SET) == (off_t)-1) { if (lseek(output, 0, SEEK_SET) == (off_t)-1) {
report("FATAL: Failed to rewind \"%s\": %s\n", name, strerror(errno)); report("FATAL: Failed to rewind \"%s\": %s\n", name, strerror(errno));
goto free_romx; goto cleanup;
} }
// 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
@@ -1102,11 +1116,11 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
if (writeLen == -1) { if (writeLen == -1) {
report("FATAL: Failed to write \"%s\"'s ROM0: %s\n", name, strerror(errno)); report("FATAL: Failed to write \"%s\"'s ROM0: %s\n", name, strerror(errno));
goto free_romx; goto cleanup;
} else if (writeLen < rom0Len) { } else if (writeLen < rom0Len) {
report("FATAL: Could only write %jd of \"%s\"'s %jd ROM0 bytes\n", report("FATAL: Could only write %jd of \"%s\"'s %jd ROM0 bytes\n",
(intmax_t)writeLen, name, (intmax_t)rom0Len); (intmax_t)writeLen, name, (intmax_t)rom0Len);
goto free_romx; goto cleanup;
} }
// Output ROMX if it was buffered // Output ROMX if it was buffered
@@ -1116,11 +1130,11 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
writeLen = writeBytes(output, romx, totalRomxLen); writeLen = writeBytes(output, romx, totalRomxLen);
if (writeLen == -1) { if (writeLen == -1) {
report("FATAL: Failed to write \"%s\"'s ROMX: %s\n", name, strerror(errno)); report("FATAL: Failed to write \"%s\"'s ROMX: %s\n", name, strerror(errno));
goto free_romx; goto cleanup;
} else if ((size_t)writeLen < totalRomxLen) { } else if ((size_t)writeLen < totalRomxLen) {
report("FATAL: Could only write %jd of \"%s\"'s %zu ROMX bytes\n", report("FATAL: Could only write %jd of \"%s\"'s %zu ROMX bytes\n",
(intmax_t)writeLen, name, totalRomxLen); (intmax_t)writeLen, name, totalRomxLen);
goto free_romx; goto cleanup;
} }
} }
@@ -1130,7 +1144,7 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
if (lseek(output, 0, SEEK_END) == (off_t)-1) { if (lseek(output, 0, SEEK_END) == (off_t)-1) {
report("FATAL: Failed to seek to end of \"%s\": %s\n", report("FATAL: Failed to seek to end of \"%s\": %s\n",
name, strerror(errno)); name, strerror(errno));
goto free_romx; goto cleanup;
} }
} }
memset(bank, padValue, sizeof(bank)); memset(bank, padValue, sizeof(bank));
@@ -1152,7 +1166,7 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
} }
} }
free_romx: cleanup:
free(romx); free(romx);
} }
@@ -1177,7 +1191,7 @@ static bool processFilename(char const *name)
if (input == -1) { if (input == -1) {
report("FATAL: Failed to open \"%s\" for reading+writing: %s\n", report("FATAL: Failed to open \"%s\" for reading+writing: %s\n",
name, strerror(errno)); name, strerror(errno));
goto fail; goto finish;
} }
if (fstat(input, &stat) == -1) { if (fstat(input, &stat) == -1) {
@@ -1196,8 +1210,8 @@ static bool processFilename(char const *name)
close(input); close(input);
} }
finish:
if (nbErrors) if (nbErrors)
fail:
fprintf(stderr, "Fixing \"%s\" failed with %u error%s\n", fprintf(stderr, "Fixing \"%s\" failed with %u error%s\n",
name, nbErrors, nbErrors == 1 ? "" : "s"); name, nbErrors, nbErrors == 1 ? "" : "s");
return nbErrors; return nbErrors;
@@ -1349,6 +1363,10 @@ do { \
parseByte(romVersion, "n"); parseByte(romVersion, "n");
break; break;
case 'O':
overwriteRom = true;
break;
case 'p': case 'p':
parseByte(padValue, "p"); parseByte(padValue, "p");
break; break;
@@ -1390,11 +1408,11 @@ do { \
#undef parseByte #undef parseByte
} }
if ((cartridgeType & 0xff00) == TPP1 && !japanese) if ((cartridgeType & 0xFF00) == TPP1 && !japanese)
fprintf(stderr, "warning: TPP1 overwrites region flag for its identification code, ignoring `-j`\n"); fprintf(stderr, "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(stderr, "warning: MBC \"%s\" should have 2kiB of RAM (-r 1)\n", fprintf(stderr, "warning: MBC \"%s\" should have 2kiB of RAM (-r 1)\n",

View File

@@ -13,7 +13,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 jsVv .Op Fl jOsVv
.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
@@ -114,7 +114,9 @@ Set the MBC type
to a given value from 0 to 0xFF. to a given value from 0 to 0xFF.
.Pp .Pp
This value may also be an MBC name. This value may also be an MBC name.
The list of accepted names can be obtained by passing "help" as the argument. The list of accepted names can be obtained by passing
.Ql Cm help
as the argument.
Any amount of whitespace (space and tabs) is allowed around plus signs, and the order of "components" is free, as long as the MBC name is first. Any amount of whitespace (space and tabs) is allowed around plus signs, and the order of "components" is free, as long as the MBC name is first.
There are special considerations to take for the TPP1 mapper; see the There are special considerations to take for the TPP1 mapper; see the
.Sx TPP1 .Sx TPP1
@@ -123,6 +125,8 @@ section below.
Set the ROM version Set the ROM version
.Pq Ad 0x14C .Pq Ad 0x14C
to a given value from 0 to 0xFF. to a given value from 0 to 0xFF.
.It Fl O , Fl Fl overwrite
Allow overwriting different non-zero bytes in the header without a warning being emitted.
.It Fl p Ar pad_value , Fl Fl pad-value Ar pad_value .It Fl p Ar pad_value , Fl Fl pad-value Ar pad_value
Pad the ROM image to a valid size with a given pad value from 0 to 255 (0xFF). Pad the ROM image to a valid size with a given pad value from 0 to 255 (0xFF).
.Nm .Nm

View File

@@ -28,7 +28,7 @@ OR B
.Ed .Ed
.Sh LEGEND .Sh LEGEND
List of abbreviations used in this document. List of abbreviations used in this document.
.Bl -tag .Bl -tag -width Ds
.It Ar r8 .It Ar r8
Any of the 8-bit registers Any of the 8-bit registers
.Pq Sy A , B , C , D , E , H , L . .Pq Sy A , B , C , D , E , H , L .
@@ -53,7 +53,7 @@ to
.Pc . .Pc .
.It Ar cc .It Ar cc
Condition codes: Condition codes:
.Bl -tag -compact .Bl -tag -width Ds -compact
.It Sy Z .It Sy Z
Execute if Z is set. Execute if Z is set.
.It Sy NZ .It Sy NZ
@@ -62,15 +62,16 @@ Execute if Z is not set.
Execute if C is set. Execute if C is set.
.It Sy NC .It Sy NC
Execute if C is not set. Execute if C is not set.
.It Sy ! cc
Negates a condition code.
.El .El
.It Ar vec .It Ar vec
One of the One of the
.Sy RST .Sy RST
vectors vectors
.Po Sy 0x00 , 0x08 , 0x10 , 0x18 , 0x20 , 0x28 , 0x30 .Po Ad 0x00 , 0x08 , 0x10 , 0x18 , 0x20 , 0x28 , 0x30 ,
and and
.Sy 0x38 .Ad 0x38 Pc .
.Pc .
.El .El
.Sh INSTRUCTION OVERVIEW .Sh INSTRUCTION OVERVIEW
.Ss 8-bit Arithmetic and Logic Instructions .Ss 8-bit Arithmetic and Logic Instructions
@@ -810,7 +811,7 @@ Flags: None affected.
.Ss LD [HL],r8 .Ss LD [HL],r8
Store value in register Store value in register
.Ar r8 .Ar r8
into byte pointed to by register into the byte pointed to by register
.Sy HL . .Sy HL .
.Pp .Pp
Cycles: 2 Cycles: 2
@@ -821,7 +822,7 @@ Flags: None affected.
.Ss LD [HL],n8 .Ss LD [HL],n8
Store value Store value
.Ar n8 .Ar n8
into byte pointed to by register into the byte pointed to by register
.Sy HL . .Sy HL .
.Pp .Pp
Cycles: 3 Cycles: 3
@@ -832,7 +833,7 @@ Flags: None affected.
.Ss LD r8,[HL] .Ss LD r8,[HL]
Load value into register Load value into register
.Ar r8 .Ar r8
from byte pointed to by register from the byte pointed to by register
.Sy HL . .Sy HL .
.Pp .Pp
Cycles: 2 Cycles: 2
@@ -843,7 +844,7 @@ Flags: None affected.
.Ss LD [r16],A .Ss LD [r16],A
Store value in register Store value in register
.Sy A .Sy A
into byte pointed to by register into the byte pointed to by register
.Ar r16 . .Ar r16 .
.Pp .Pp
Cycles: 2 Cycles: 2
@@ -854,7 +855,7 @@ Flags: None affected.
.Ss LD [n16],A .Ss LD [n16],A
Store value in register Store value in register
.Sy A .Sy A
into byte at address into the byte at address
.Ar n16 . .Ar n16 .
.Pp .Pp
Cycles: 4 Cycles: 4
@@ -865,9 +866,9 @@ Flags: None affected.
.Ss LDH [n16],A .Ss LDH [n16],A
Store value in register Store value in register
.Sy A .Sy A
into byte at address into the byte at address
.Ar n16 , .Ar n16 ,
provided it is between provided the address is between
.Ad $FF00 .Ad $FF00
and and
.Ad $FFFF . .Ad $FFFF .
@@ -885,7 +886,7 @@ or
.Ss LDH [C],A .Ss LDH [C],A
Store value in register Store value in register
.Sy A .Sy A
into byte at address into the byte at address
.Ad $FF00+C . .Ad $FF00+C .
.Pp .Pp
Cycles: 2 Cycles: 2
@@ -901,7 +902,7 @@ or
.Ss LD A,[r16] .Ss LD A,[r16]
Load value in register Load value in register
.Sy A .Sy A
from byte pointed to by register from the byte pointed to by register
.Ar r16 . .Ar r16 .
.Pp .Pp
Cycles: 2 Cycles: 2
@@ -912,7 +913,7 @@ Flags: None affected.
.Ss LD A,[n16] .Ss LD A,[n16]
Load value in register Load value in register
.Sy A .Sy A
from byte at address from the byte at address
.Ar n16 . .Ar n16 .
.Pp .Pp
Cycles: 4 Cycles: 4
@@ -923,9 +924,9 @@ Flags: None affected.
.Ss LDH A,[n16] .Ss LDH A,[n16]
Load value in register Load value in register
.Sy A .Sy A
from byte at address from the byte at address
.Ar n16 , .Ar n16 ,
provided it is between provided the address is between
.Ad $FF00 .Ad $FF00
and and
.Ad $FFFF . .Ad $FFFF .
@@ -943,7 +944,7 @@ or
.Ss LDH A,[C] .Ss LDH A,[C]
Load value in register Load value in register
.Sy A .Sy A
from byte at address from the byte at address
.Ad $FF00+c . .Ad $FF00+c .
.Pp .Pp
Cycles: 2 Cycles: 2
@@ -959,7 +960,7 @@ or
.Ss LD [HLI],A .Ss LD [HLI],A
Store value in register Store value in register
.Sy A .Sy A
into byte pointed by into the byte pointed by
.Sy HL .Sy HL
and increment and increment
.Sy HL .Sy HL
@@ -978,7 +979,7 @@ or
.Ss LD [HLD],A .Ss LD [HLD],A
Store value in register Store value in register
.Sy A .Sy A
into byte pointed by into the byte pointed by
.Sy HL .Sy HL
and decrement and decrement
.Sy HL .Sy HL
@@ -997,7 +998,7 @@ or
.Ss LD A,[HLD] .Ss LD A,[HLD]
Load value into register Load value into register
.Sy A .Sy A
from byte pointed by from the byte pointed by
.Sy HL .Sy HL
and decrement and decrement
.Sy HL .Sy HL
@@ -1016,7 +1017,7 @@ or
.Ss LD A,[HLI] .Ss LD A,[HLI]
Load value into register Load value into register
.Sy A .Sy A
from byte pointed by from the byte pointed by
.Sy HL .Sy HL
and increment and increment
.Sy HL .Sy HL
@@ -1029,7 +1030,7 @@ Bytes: 1
Flags: None affected. Flags: None affected.
.Pp .Pp
This is sometimes written as This is sometimes written as
.Ql LD A,[HL+], , .Ql LD A,[HL+] ,
or or
.Ql LDI A,[HL] . .Ql LDI A,[HL] .
.Ss LD SP,n16 .Ss LD SP,n16
@@ -1328,7 +1329,7 @@ Set if result is 0.
Set according to result. Set according to result.
.El .El
.Ss RL [HL] .Ss RL [HL]
Rotate byte pointed to by Rotate the byte pointed to by
.Sy HL .Sy HL
left through carry. left through carry.
.Pp .Pp
@@ -1385,7 +1386,7 @@ Set if result is 0.
Set according to result. Set according to result.
.El .El
.Ss RLC [HL] .Ss RLC [HL]
Rotate byte pointed to by Rotate the byte pointed to by
.Sy HL .Sy HL
left. left.
.Pp .Pp
@@ -1442,7 +1443,7 @@ Set if result is 0.
Set according to result. Set according to result.
.El .El
.Ss RR [HL] .Ss RR [HL]
Rotate byte pointed to by Rotate the byte pointed to by
.Sy HL .Sy HL
right through carry. right through carry.
.Pp .Pp
@@ -1499,7 +1500,7 @@ Set if result is 0.
Set according to result. Set according to result.
.El .El
.Ss RRC [HL] .Ss RRC [HL]
Rotate byte pointed to by Rotate the byte pointed to by
.Sy HL .Sy HL
right. right.
.Pp .Pp
@@ -1638,7 +1639,7 @@ Bytes: 2
.Pp .Pp
Flags: None affected. Flags: None affected.
.Ss SLA r8 .Ss SLA r8
Shift Left Arithmetic register Shift Left Arithmetically register
.Ar r8 . .Ar r8 .
.Pp .Pp
.D1 C <- [7 <- 0] <- 0 .D1 C <- [7 <- 0] <- 0
@@ -1659,7 +1660,7 @@ Set if result is 0.
Set according to result. Set according to result.
.El .El
.Ss SLA [HL] .Ss SLA [HL]
Shift Left Arithmetic byte pointed to by Shift Left Arithmetically the byte pointed to by
.Sy HL . .Sy HL .
.Pp .Pp
.D1 C <- [7 <- 0] <- 0 .D1 C <- [7 <- 0] <- 0
@@ -1671,7 +1672,7 @@ Bytes: 2
Flags: See Flags: See
.Sx SLA r8 .Sx SLA r8
.Ss SRA r8 .Ss SRA r8
Shift Right Arithmetic register Shift Right Arithmetically register
.Ar r8 . .Ar r8 .
.Pp .Pp
.D1 [7] -> [7 -> 0] -> C .D1 [7] -> [7 -> 0] -> C
@@ -1692,7 +1693,7 @@ Set if result is 0.
Set according to result. Set according to result.
.El .El
.Ss SRA [HL] .Ss SRA [HL]
Shift Right Arithmetic byte pointed to by Shift Right Arithmetically the byte pointed to by
.Sy HL . .Sy HL .
.Pp .Pp
.D1 [7] -> [7 -> 0] -> C .D1 [7] -> [7 -> 0] -> C
@@ -1704,7 +1705,7 @@ Bytes: 2
Flags: See Flags: See
.Sx SRA r8 .Sx SRA r8
.Ss SRL r8 .Ss SRL r8
Shift Right Logic register Shift Right Logically register
.Ar r8 . .Ar r8 .
.Pp .Pp
.D1 0 -> [7 -> 0] -> C .D1 0 -> [7 -> 0] -> C
@@ -1725,7 +1726,7 @@ Set if result is 0.
Set according to result. Set according to result.
.El .El
.Ss SRL [HL] .Ss SRL [HL]
Shift Right Logic byte pointed to by Shift Right Logically the byte pointed to by
.Sy HL . .Sy HL .
.Pp .Pp
.D1 0 -> [7 -> 0] -> C .D1 0 -> [7 -> 0] -> C
@@ -1794,7 +1795,7 @@ Bytes: 2
Flags: See Flags: See
.Sx SUB A,r8 .Sx SUB A,r8
.Ss SWAP r8 .Ss SWAP r8
Swap upper 4 bits in register Swap the upper 4 bits in register
.Ar r8 .Ar r8
and the lower 4 ones. and the lower 4 ones.
.Pp .Pp
@@ -1814,7 +1815,7 @@ Set if result is 0.
0 0
.El .El
.Ss SWAP [HL] .Ss SWAP [HL]
Swap upper 4 bits in the byte pointed by Swap the upper 4 bits in the byte pointed by
.Sy HL .Sy HL
and the lower 4 ones. and the lower 4 ones.
.Pp .Pp

View File

@@ -20,7 +20,7 @@ void transpose_tiles(struct GBImage *gb, int width)
newdata = calloc(gb->size, 1); newdata = calloc(gb->size, 1);
if (!newdata) if (!newdata)
err(1, "%s: Failed to allocate memory for new data", __func__); err("%s: Failed to allocate memory for new data", __func__);
for (i = 0; i < gb->size; i++) { for (i = 0; i < gb->size; i++) {
newbyte = i / (8 * depth) * width * 8 * depth; newbyte = i / (8 * depth) * width * 8 * depth;
@@ -65,7 +65,7 @@ void output_file(const struct Options *opts, const struct GBImage *gb)
f = fopen(opts->outfile, "wb"); f = fopen(opts->outfile, "wb");
if (!f) if (!f)
err(1, "%s: Opening output file '%s' failed", __func__, err("%s: Opening output file '%s' failed", __func__,
opts->outfile); opts->outfile);
fwrite(gb->data, 1, gb->size - gb->trim * 8 * depth, f); fwrite(gb->data, 1, gb->size - gb->trim * 8 * depth, f);
@@ -141,7 +141,7 @@ int get_mirrored_tile_index(uint8_t *tile, uint8_t **tiles, int num_tiles,
tile_yflip = malloc(tile_size); tile_yflip = malloc(tile_size);
if (!tile_yflip) if (!tile_yflip)
err(1, "%s: Failed to allocate memory for Y flip of tile", err("%s: Failed to allocate memory for Y flip of tile",
__func__); __func__);
yflip(tile, tile_yflip, tile_size); yflip(tile, tile_yflip, tile_size);
index = get_tile_index(tile_yflip, tiles, num_tiles, tile_size); index = get_tile_index(tile_yflip, tiles, num_tiles, tile_size);
@@ -153,7 +153,7 @@ int get_mirrored_tile_index(uint8_t *tile, uint8_t **tiles, int num_tiles,
tile_xflip = malloc(tile_size); tile_xflip = malloc(tile_size);
if (!tile_xflip) if (!tile_xflip)
err(1, "%s: Failed to allocate memory for X flip of tile", err("%s: Failed to allocate memory for X flip of tile",
__func__); __func__);
xflip(tile, tile_xflip, tile_size); xflip(tile, tile_xflip, tile_size);
index = get_tile_index(tile_xflip, tiles, num_tiles, tile_size); index = get_tile_index(tile_xflip, tiles, num_tiles, tile_size);
@@ -198,13 +198,13 @@ void create_mapfiles(const struct Options *opts, struct GBImage *gb,
tiles = calloc(max_tiles, sizeof(*tiles)); tiles = calloc(max_tiles, sizeof(*tiles));
if (!tiles) if (!tiles)
err(1, "%s: Failed to allocate memory for tiles", __func__); err("%s: Failed to allocate memory for tiles", __func__);
num_tiles = 0; num_tiles = 0;
if (*opts->tilemapfile) { if (*opts->tilemapfile) {
tilemap->data = calloc(max_tiles, sizeof(*tilemap->data)); tilemap->data = calloc(max_tiles, sizeof(*tilemap->data));
if (!tilemap->data) if (!tilemap->data)
err(1, "%s: Failed to allocate memory for tilemap data", err("%s: Failed to allocate memory for tilemap data",
__func__); __func__);
tilemap->size = 0; tilemap->size = 0;
} }
@@ -212,7 +212,7 @@ void create_mapfiles(const struct Options *opts, struct GBImage *gb,
if (*opts->attrmapfile) { if (*opts->attrmapfile) {
attrmap->data = calloc(max_tiles, sizeof(*attrmap->data)); attrmap->data = calloc(max_tiles, sizeof(*attrmap->data));
if (!attrmap->data) if (!attrmap->data)
err(1, "%s: Failed to allocate memory for attrmap data", err("%s: Failed to allocate memory for attrmap data",
__func__); __func__);
attrmap->size = 0; attrmap->size = 0;
} }
@@ -222,7 +222,7 @@ void create_mapfiles(const struct Options *opts, struct GBImage *gb,
flags = 0; flags = 0;
tile = malloc(tile_size); tile = malloc(tile_size);
if (!tile) if (!tile)
err(1, "%s: Failed to allocate memory for tile", err("%s: Failed to allocate memory for tile",
__func__); __func__);
/* /*
* If the input image doesn't fill the last tile, * If the input image doesn't fill the last tile,
@@ -269,7 +269,7 @@ void create_mapfiles(const struct Options *opts, struct GBImage *gb,
free(gb->data); free(gb->data);
gb->data = malloc(tile_size * num_tiles); gb->data = malloc(tile_size * num_tiles);
if (!gb->data) if (!gb->data)
err(1, "%s: Failed to allocate memory for tile data", err("%s: Failed to allocate memory for tile data",
__func__); __func__);
for (i = 0; i < num_tiles; i++) { for (i = 0; i < num_tiles; i++) {
tile = tiles[i]; tile = tiles[i];
@@ -292,7 +292,7 @@ void output_tilemap_file(const struct Options *opts,
f = fopen(opts->tilemapfile, "wb"); f = fopen(opts->tilemapfile, "wb");
if (!f) if (!f)
err(1, "%s: Opening tilemap file '%s' failed", __func__, err("%s: Opening tilemap file '%s' failed", __func__,
opts->tilemapfile); opts->tilemapfile);
fwrite(tilemap->data, 1, tilemap->size, f); fwrite(tilemap->data, 1, tilemap->size, f);
@@ -309,7 +309,7 @@ void output_attrmap_file(const struct Options *opts,
f = fopen(opts->attrmapfile, "wb"); f = fopen(opts->attrmapfile, "wb");
if (!f) if (!f)
err(1, "%s: Opening attrmap file '%s' failed", __func__, err("%s: Opening attrmap file '%s' failed", __func__,
opts->attrmapfile); opts->attrmapfile);
fwrite(attrmap->data, 1, attrmap->size, f); fwrite(attrmap->data, 1, attrmap->size, f);
@@ -352,7 +352,7 @@ void output_palette_file(const struct Options *opts,
f = fopen(opts->palfile, "wb"); f = fopen(opts->palfile, "wb");
if (!f) if (!f)
err(1, "%s: Opening palette file '%s' failed", __func__, err("%s: Opening palette file '%s' failed", __func__,
opts->palfile); opts->palfile);
for (i = 0; i < raw_image->num_colors; i++) { for (i = 0; i < raw_image->num_colors; i++) {

View File

@@ -167,7 +167,7 @@ int main(int argc, char *argv[])
opts.infile = argv[argc - 1]; opts.infile = argv[argc - 1];
if (depth != 1 && depth != 2) if (depth != 1 && depth != 2)
errx(1, "Depth option must be either 1 or 2."); errx("Depth option must be either 1 or 2.");
colors = 1 << depth; colors = 1 << depth;
@@ -200,17 +200,17 @@ int main(int argc, char *argv[])
opts.trim = png_options.trim; opts.trim = png_options.trim;
if (raw_image->width % 8) { if (raw_image->width % 8) {
errx(1, "Input PNG file %s not sized correctly. The image's width must be a multiple of 8.", errx("Input PNG file %s not sized correctly. The image's width must be a multiple of 8.",
opts.infile); opts.infile);
} }
if (raw_image->width / 8 > 1 && raw_image->height % 8) { if (raw_image->width / 8 > 1 && raw_image->height % 8) {
errx(1, "Input PNG file %s not sized correctly. If the image is more than 1 tile wide, its height must be a multiple of 8.", errx("Input PNG file %s not sized correctly. If the image is more than 1 tile wide, its height must be a multiple of 8.",
opts.infile); opts.infile);
} }
if (opts.trim && if (opts.trim &&
opts.trim > (raw_image->width / 8) * (raw_image->height / 8) - 1) { opts.trim > (raw_image->width / 8) * (raw_image->height / 8) - 1) {
errx(1, "Trim (%d) for input raw_image file '%s' too large (max: %u)", errx("Trim (%d) for input raw_image file '%s' too large (max: %u)",
opts.trim, opts.infile, opts.trim, opts.infile,
(raw_image->width / 8) * (raw_image->height / 8) - 1); (raw_image->width / 8) * (raw_image->height / 8) - 1);
} }

View File

@@ -32,7 +32,7 @@ struct RawIndexedImage *input_png_file(const struct Options *opts,
f = fopen(opts->infile, "rb"); f = fopen(opts->infile, "rb");
if (!f) if (!f)
err(1, "Opening input png file '%s' failed", opts->infile); err("Opening input png file '%s' failed", opts->infile);
initialize_png(&img, f); initialize_png(&img, f);
@@ -54,7 +54,7 @@ struct RawIndexedImage *input_png_file(const struct Options *opts,
raw_image = truecolor_png_to_raw(&img); break; raw_image = truecolor_png_to_raw(&img); break;
default: default:
/* Shouldn't happen, but might as well handle just in case. */ /* Shouldn't happen, but might as well handle just in case. */
errx(1, "Input PNG file is of invalid color type."); errx("Input PNG file is of invalid color type.");
} }
get_text(&img, png_options); get_text(&img, png_options);
@@ -83,7 +83,7 @@ void output_png_file(const struct Options *opts,
if (opts->debug) { if (opts->debug) {
outfile = malloc(strlen(opts->infile) + 5); outfile = malloc(strlen(opts->infile) + 5);
if (!outfile) if (!outfile)
err(1, "%s: Failed to allocate memory for outfile", err("%s: Failed to allocate memory for outfile",
__func__); __func__);
strcpy(outfile, opts->infile); strcpy(outfile, opts->infile);
strcat(outfile, ".out"); strcat(outfile, ".out");
@@ -93,16 +93,16 @@ void output_png_file(const struct Options *opts,
f = fopen(outfile, "wb"); f = fopen(outfile, "wb");
if (!f) if (!f)
err(1, "Opening output png file '%s' failed", outfile); err("Opening output png file '%s' failed", outfile);
img.png = png_create_write_struct(PNG_LIBPNG_VER_STRING, img.png = png_create_write_struct(PNG_LIBPNG_VER_STRING,
NULL, NULL, NULL); NULL, NULL, NULL);
if (!img.png) if (!img.png)
errx(1, "Creating png structure failed"); errx("Creating png structure failed");
img.info = png_create_info_struct(img.png); img.info = png_create_info_struct(img.png);
if (!img.info) if (!img.info)
errx(1, "Creating png info structure failed"); errx("Creating png info structure failed");
if (setjmp(png_jmpbuf(img.png))) if (setjmp(png_jmpbuf(img.png)))
exit(1); exit(1);
@@ -115,7 +115,7 @@ void output_png_file(const struct Options *opts,
png_palette = malloc(sizeof(*png_palette) * raw_image->num_colors); png_palette = malloc(sizeof(*png_palette) * raw_image->num_colors);
if (!png_palette) if (!png_palette)
err(1, "%s: Failed to allocate memory for PNG palette", err("%s: Failed to allocate memory for PNG palette",
__func__); __func__);
for (i = 0; i < raw_image->num_colors; i++) { for (i = 0; i < raw_image->num_colors; i++) {
png_palette[i].red = raw_image->palette[i].red; png_palette[i].red = raw_image->palette[i].red;
@@ -159,11 +159,11 @@ static void initialize_png(struct PNGImage *img, FILE *f)
img->png = png_create_read_struct(PNG_LIBPNG_VER_STRING, img->png = png_create_read_struct(PNG_LIBPNG_VER_STRING,
NULL, NULL, NULL); NULL, NULL, NULL);
if (!img->png) if (!img->png)
errx(1, "Creating png structure failed"); errx("Creating png structure failed");
img->info = png_create_info_struct(img->png); img->info = png_create_info_struct(img->png);
if (!img->info) if (!img->info)
errx(1, "Creating png info structure failed"); errx("Creating png info structure failed");
if (setjmp(png_jmpbuf(img->png))) if (setjmp(png_jmpbuf(img->png)))
exit(1); exit(1);
@@ -182,7 +182,7 @@ static void read_png(struct PNGImage *img);
static struct RawIndexedImage *create_raw_image(int width, int height, static struct RawIndexedImage *create_raw_image(int width, int height,
int num_colors); int num_colors);
static void set_raw_image_palette(struct RawIndexedImage *raw_image, static void set_raw_image_palette(struct RawIndexedImage *raw_image,
const png_color *palette, int num_colors); png_color const *palette, int num_colors);
static struct RawIndexedImage *indexed_png_to_raw(struct PNGImage *img) static struct RawIndexedImage *indexed_png_to_raw(struct PNGImage *img)
{ {
@@ -215,13 +215,13 @@ static struct RawIndexedImage *indexed_png_to_raw(struct PNGImage *img)
original_palette = palette; original_palette = palette;
palette = malloc(sizeof(*palette) * colors_in_PLTE); palette = malloc(sizeof(*palette) * colors_in_PLTE);
if (!palette) if (!palette)
err(1, "%s: Failed to allocate memory for palette", err("%s: Failed to allocate memory for palette",
__func__); __func__);
colors_in_new_palette = 0; colors_in_new_palette = 0;
old_to_new_palette = malloc(sizeof(*old_to_new_palette) old_to_new_palette = malloc(sizeof(*old_to_new_palette)
* colors_in_PLTE); * colors_in_PLTE);
if (!old_to_new_palette) if (!old_to_new_palette)
err(1, "%s: Failed to allocate memory for new palette", err("%s: Failed to allocate memory for new palette",
__func__); __func__);
for (i = 0; i < num_trans; i++) { for (i = 0; i < num_trans; i++) {
@@ -243,7 +243,7 @@ static struct RawIndexedImage *indexed_png_to_raw(struct PNGImage *img)
sizeof(*palette) * sizeof(*palette) *
colors_in_new_palette); colors_in_new_palette);
if (!palette) if (!palette)
err(1, "%s: Failed to allocate memory for palette", err("%s: Failed to allocate memory for palette",
__func__); __func__);
} }
@@ -291,7 +291,7 @@ static void rgba_png_palette(struct PNGImage *img,
png_color **palette_ptr_ptr, int *num_colors); png_color **palette_ptr_ptr, int *num_colors);
static struct RawIndexedImage static struct RawIndexedImage
*processed_rgba_png_to_raw(const struct PNGImage *img, *processed_rgba_png_to_raw(const struct PNGImage *img,
const png_color *palette, png_color const *palette,
int colors_in_palette); int colors_in_palette);
static struct RawIndexedImage *truecolor_png_to_raw(struct PNGImage *img) static struct RawIndexedImage *truecolor_png_to_raw(struct PNGImage *img)
@@ -352,7 +352,7 @@ static void rgba_PLTE_palette(struct PNGImage *img,
} }
static void update_built_palette(png_color *palette, static void update_built_palette(png_color *palette,
const png_color *pixel_color, png_byte alpha, png_color const *pixel_color, png_byte alpha,
int *num_colors, bool *only_grayscale); int *num_colors, bool *only_grayscale);
static int fit_grayscale_palette(png_color *palette, int *num_colors); static int fit_grayscale_palette(png_color *palette, int *num_colors);
static void order_color_palette(png_color *palette, int num_colors); static void order_color_palette(png_color *palette, int num_colors);
@@ -372,7 +372,7 @@ static void rgba_build_palette(struct PNGImage *img,
*/ */
*palette_ptr_ptr = calloc(colors, sizeof(**palette_ptr_ptr)); *palette_ptr_ptr = calloc(colors, sizeof(**palette_ptr_ptr));
if (!*palette_ptr_ptr) if (!*palette_ptr_ptr)
err(1, "%s: Failed to allocate memory for palette", __func__); err("%s: Failed to allocate memory for palette", __func__);
palette = *palette_ptr_ptr; palette = *palette_ptr_ptr;
*num_colors = 0; *num_colors = 0;
@@ -398,7 +398,7 @@ static void rgba_build_palette(struct PNGImage *img,
} }
static void update_built_palette(png_color *palette, static void update_built_palette(png_color *palette,
const png_color *pixel_color, png_byte alpha, png_color const *pixel_color, png_byte alpha,
int *num_colors, bool *only_grayscale) int *num_colors, bool *only_grayscale)
{ {
bool color_exists; bool color_exists;
@@ -429,7 +429,7 @@ static void update_built_palette(png_color *palette,
} }
if (!color_exists) { if (!color_exists) {
if (*num_colors == colors) { if (*num_colors == colors) {
errx(1, "Too many colors in input PNG file to fit into a %d-bit palette (max %d).", errx("Too many colors in input PNG file to fit into a %d-bit palette (max %d).",
depth, colors); depth, colors);
} }
palette[*num_colors] = *pixel_color; palette[*num_colors] = *pixel_color;
@@ -445,9 +445,9 @@ static int fit_grayscale_palette(png_color *palette, int *num_colors)
int i, shade_index; int i, shade_index;
if (!fitted_palette) if (!fitted_palette)
err(1, "%s: Failed to allocate memory for palette", __func__); err("%s: Failed to allocate memory for palette", __func__);
if (!set_indices) if (!set_indices)
err(1, "%s: Failed to allocate memory for indices", __func__); err("%s: Failed to allocate memory for indices", __func__);
fitted_palette[0].red = 0xFF; fitted_palette[0].red = 0xFF;
fitted_palette[0].green = 0xFF; fitted_palette[0].green = 0xFF;
@@ -491,7 +491,7 @@ struct ColorWithLuminance {
int luminance; int luminance;
}; };
static int compare_luminance(const void *a, const void *b) static int compare_luminance(void const *a, void const *b)
{ {
const struct ColorWithLuminance *x, *y; const struct ColorWithLuminance *x, *y;
@@ -508,7 +508,7 @@ static void order_color_palette(png_color *palette, int num_colors)
malloc(sizeof(*palette_with_luminance) * num_colors); malloc(sizeof(*palette_with_luminance) * num_colors);
if (!palette_with_luminance) if (!palette_with_luminance)
err(1, "%s: Failed to allocate memory for palette", __func__); err("%s: Failed to allocate memory for palette", __func__);
for (i = 0; i < num_colors; i++) { for (i = 0; i < num_colors; i++) {
/* /*
@@ -531,12 +531,12 @@ static void order_color_palette(png_color *palette, int num_colors)
static void put_raw_image_pixel(struct RawIndexedImage *raw_image, static void put_raw_image_pixel(struct RawIndexedImage *raw_image,
const struct PNGImage *img, const struct PNGImage *img,
int *value_index, int x, int y, int *value_index, int x, int y,
const png_color *palette, png_color const *palette,
int colors_in_palette); int colors_in_palette);
static struct RawIndexedImage static struct RawIndexedImage
*processed_rgba_png_to_raw(const struct PNGImage *img, *processed_rgba_png_to_raw(const struct PNGImage *img,
const png_color *palette, png_color const *palette,
int colors_in_palette) int colors_in_palette)
{ {
struct RawIndexedImage *raw_image; struct RawIndexedImage *raw_image;
@@ -561,13 +561,13 @@ static struct RawIndexedImage
return raw_image; return raw_image;
} }
static uint8_t palette_index_of(const png_color *palette, static uint8_t palette_index_of(png_color const *palette,
int num_colors, const png_color *color); int num_colors, png_color const *color);
static void put_raw_image_pixel(struct RawIndexedImage *raw_image, static void put_raw_image_pixel(struct RawIndexedImage *raw_image,
const struct PNGImage *img, const struct PNGImage *img,
int *value_index, int x, int y, int *value_index, int x, int y,
const png_color *palette, png_color const *palette,
int colors_in_palette) int colors_in_palette)
{ {
png_color pixel_color; png_color pixel_color;
@@ -588,8 +588,8 @@ static void put_raw_image_pixel(struct RawIndexedImage *raw_image,
} }
} }
static uint8_t palette_index_of(const png_color *palette, static uint8_t palette_index_of(png_color const *palette,
int num_colors, const png_color *color) int num_colors, png_color const *color)
{ {
uint8_t i; uint8_t i;
@@ -600,7 +600,7 @@ static uint8_t palette_index_of(const png_color *palette,
return i; return i;
} }
} }
errx(1, "The input PNG file contains colors that don't appear in its embedded palette."); errx("The input PNG file contains colors that don't appear in its embedded palette.");
} }
static void read_png(struct PNGImage *img) static void read_png(struct PNGImage *img)
@@ -611,12 +611,12 @@ static void read_png(struct PNGImage *img)
img->data = malloc(sizeof(*img->data) * img->height); img->data = malloc(sizeof(*img->data) * img->height);
if (!img->data) if (!img->data)
err(1, "%s: Failed to allocate memory for image data", err("%s: Failed to allocate memory for image data",
__func__); __func__);
for (y = 0; y < img->height; y++) { for (y = 0; y < img->height; y++) {
img->data[y] = malloc(png_get_rowbytes(img->png, img->info)); img->data[y] = malloc(png_get_rowbytes(img->png, img->info));
if (!img->data[y]) if (!img->data[y])
err(1, "%s: Failed to allocate memory for image data", err("%s: Failed to allocate memory for image data",
__func__); __func__);
} }
@@ -632,7 +632,7 @@ static struct RawIndexedImage *create_raw_image(int width, int height,
raw_image = malloc(sizeof(*raw_image)); raw_image = malloc(sizeof(*raw_image));
if (!raw_image) if (!raw_image)
err(1, "%s: Failed to allocate memory for raw image", err("%s: Failed to allocate memory for raw image",
__func__); __func__);
raw_image->width = width; raw_image->width = width;
@@ -641,18 +641,18 @@ static struct RawIndexedImage *create_raw_image(int width, int height,
raw_image->palette = malloc(sizeof(*raw_image->palette) * num_colors); raw_image->palette = malloc(sizeof(*raw_image->palette) * num_colors);
if (!raw_image->palette) if (!raw_image->palette)
err(1, "%s: Failed to allocate memory for raw image palette", err("%s: Failed to allocate memory for raw image palette",
__func__); __func__);
raw_image->data = malloc(sizeof(*raw_image->data) * height); raw_image->data = malloc(sizeof(*raw_image->data) * height);
if (!raw_image->data) if (!raw_image->data)
err(1, "%s: Failed to allocate memory for raw image data", err("%s: Failed to allocate memory for raw image data",
__func__); __func__);
for (y = 0; y < height; y++) { for (y = 0; y < height; y++) {
raw_image->data[y] = malloc(sizeof(*raw_image->data[y]) raw_image->data[y] = malloc(sizeof(*raw_image->data[y])
* width); * width);
if (!raw_image->data[y]) if (!raw_image->data[y])
err(1, "%s: Failed to allocate memory for raw image data", err("%s: Failed to allocate memory for raw image data",
__func__); __func__);
} }
@@ -660,12 +660,12 @@ static struct RawIndexedImage *create_raw_image(int width, int height,
} }
static void set_raw_image_palette(struct RawIndexedImage *raw_image, static void set_raw_image_palette(struct RawIndexedImage *raw_image,
const png_color *palette, int num_colors) png_color const *palette, int num_colors)
{ {
int i; int i;
if (num_colors > raw_image->num_colors) { if (num_colors > raw_image->num_colors) {
errx(1, "Too many colors in input PNG file's palette to fit into a %d-bit palette (%d in input palette, max %d).", errx("Too many colors in input PNG file's palette to fit into a %d-bit palette (%d in input palette, max %d).",
raw_image->num_colors >> 1, raw_image->num_colors >> 1,
num_colors, raw_image->num_colors); num_colors, raw_image->num_colors);
} }
@@ -740,7 +740,7 @@ static void set_text(const struct PNGImage *img,
text = malloc(sizeof(*text)); text = malloc(sizeof(*text));
if (!text) if (!text)
err(1, "%s: Failed to allocate memory for PNG text", err("%s: Failed to allocate memory for PNG text",
__func__); __func__);
if (png_options->horizontal) { if (png_options->horizontal) {

View File

@@ -12,8 +12,8 @@
#include <string.h> #include <string.h>
#include <assert.h> #include <assert.h>
#include "error.h"
#include "hashmap.h" #include "hashmap.h"
#include "extern/err.h"
/* /*
* The lower half of the hash is used to index the "master" table, * The lower half of the hash is used to index the "master" table,
@@ -53,7 +53,7 @@ void **hash_AddElement(HashMap map, char const *key, void *element)
struct HashMapEntry *newEntry = malloc(sizeof(*newEntry)); struct HashMapEntry *newEntry = malloc(sizeof(*newEntry));
if (!newEntry) if (!newEntry)
err(1, "%s: Failed to allocate new entry", __func__); err("%s: Failed to allocate new entry", __func__);
newEntry->hash = hashedKey >> HALF_HASH_NB_BITS; newEntry->hash = hashedKey >> HALF_HASH_NB_BITS;
newEntry->key = key; newEntry->key = key;

View File

@@ -19,7 +19,7 @@
#include "link/script.h" #include "link/script.h"
#include "link/output.h" #include "link/output.h"
#include "extern/err.h" #include "error.h"
#include "helpers.h" #include "helpers.h"
struct MemoryLocation { struct MemoryLocation {
@@ -46,13 +46,13 @@ static void initFreeSpace(void)
for (enum SectionType type = 0; type < SECTTYPE_INVALID; type++) { for (enum SectionType type = 0; type < SECTTYPE_INVALID; type++) {
memory[type] = malloc(sizeof(*memory[type]) * nbbanks(type)); memory[type] = malloc(sizeof(*memory[type]) * nbbanks(type));
if (!memory[type]) if (!memory[type])
err(1, "Failed to init free space for region %d", type); err("Failed to init free space for region %d", type);
for (uint32_t bank = 0; bank < nbbanks(type); bank++) { for (uint32_t bank = 0; bank < nbbanks(type); bank++) {
memory[type][bank].next = memory[type][bank].next =
malloc(sizeof(*memory[type][0].next)); malloc(sizeof(*memory[type][0].next));
if (!memory[type][bank].next) if (!memory[type][bank].next)
err(1, "Failed to init free space for region %d bank %" PRIu32, err("Failed to init free space for region %d bank %" PRIu32,
type, bank); type, bank);
memory[type][bank].next->address = startaddr[type]; memory[type][bank].next->address = startaddr[type];
memory[type][bank].next->size = maxsize[type]; memory[type][bank].next->size = maxsize[type];
@@ -159,9 +159,28 @@ static bool isLocationSuitable(struct Section const *section,
static struct FreeSpace *getPlacement(struct Section const *section, static struct FreeSpace *getPlacement(struct Section const *section,
struct MemoryLocation *location) struct MemoryLocation *location)
{ {
location->bank = section->isBankFixed static uint16_t curScrambleROM = 1;
? section->bank static uint8_t curScrambleWRAM = 1;
: bankranges[section->type][0]; static uint8_t curScrambleSRAM = 1;
// Determine which bank we should start searching in
if (section->isBankFixed) {
location->bank = section->bank;
} else if (scrambleROMX && section->type == SECTTYPE_ROMX) {
location->bank = curScrambleROM++;
if (curScrambleROM > scrambleROMX)
curScrambleROM = 1;
} else if (scrambleWRAMX && section->type == SECTTYPE_WRAMX) {
location->bank = curScrambleWRAM++;
if (curScrambleWRAM > scrambleWRAMX)
curScrambleWRAM = 1;
} else if (scrambleSRAM && section->type == SECTTYPE_SRAM) {
location->bank = curScrambleSRAM++;
if (curScrambleSRAM > scrambleSRAM)
curScrambleSRAM = 0;
} else {
location->bank = bankranges[section->type][0];
}
struct FreeSpace *space; struct FreeSpace *space;
for (;;) { for (;;) {
@@ -282,7 +301,7 @@ static void placeSection(struct Section *section)
struct FreeSpace *newSpace = malloc(sizeof(*newSpace)); struct FreeSpace *newSpace = malloc(sizeof(*newSpace));
if (!newSpace) if (!newSpace)
err(1, "Failed to split new free space"); err("Failed to split new free space");
/* Append the new space after the chosen one */ /* Append the new space after the chosen one */
newSpace->prev = freeSpace; newSpace->prev = freeSpace;
newSpace->next = freeSpace->next; newSpace->next = freeSpace->next;
@@ -333,16 +352,16 @@ static void placeSection(struct Section *section)
/* 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(1, "Unable to place \"%s\" (%s section) %s", errx("Unable to place \"%s\" (%s section) %s",
section->name, typeNames[section->type], where); section->name, typeNames[section->type], 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(1, "Unable to place \"%s\" (%s section) %s: section runs past end of region ($%04x > $%04x)", errx("Unable to place \"%s\" (%s section) %s: section runs past end of region ($%04x > $%04x)",
section->name, typeNames[section->type], where, section->name, typeNames[section->type], where,
section->org + section->size, endaddr(section->type) + 1); section->org + section->size, endaddr(section->type) + 1);
/* Otherwise there is overlap with another section */ /* Otherwise there is overlap with another section */
else else
errx(1, "Unable to place \"%s\" (%s section) %s: section overlaps with \"%s\"", errx("Unable to place \"%s\" (%s section) %s: section overlaps with \"%s\"",
section->name, typeNames[section->type], where, section->name, typeNames[section->type], where,
out_OverlappingSection(section)->name); out_OverlappingSection(section)->name);
} }
@@ -399,7 +418,7 @@ void assign_AssignSections(void)
/* Generate linked lists of sections to assign */ /* Generate linked lists of sections to assign */
sections = malloc(sizeof(*sections) * nbSectionsToAssign + 1); sections = malloc(sizeof(*sections) * nbSectionsToAssign + 1);
if (!sections) if (!sections)
err(1, "Failed to allocate memory for section assignment"); err("Failed to allocate memory for section assignment");
initFreeSpace(); initFreeSpace();
@@ -428,7 +447,7 @@ void assign_AssignSections(void)
/* Overlaying requires only fully-constrained sections */ /* Overlaying requires only fully-constrained sections */
verbosePrint("Assigning other sections...\n"); verbosePrint("Assigning other sections...\n");
if (overlayFileName) if (overlayFileName)
errx(1, "All sections must be fixed when using an overlay file; %" PRIu64 " %sn't", errx("All sections must be fixed when using an overlay file; %" PRIu64 " %sn't",
nbSectionsToAssign, nbSectionsToAssign == 1 ? "is" : "are"); nbSectionsToAssign, nbSectionsToAssign == 1 ? "is" : "are");
/* Assign all remaining sections by decreasing constraint order */ /* Assign all remaining sections by decreasing constraint order */

View File

@@ -8,6 +8,7 @@
#include <assert.h> #include <assert.h>
#include <inttypes.h> #include <inttypes.h>
#include <limits.h>
#include <stdbool.h> #include <stdbool.h>
#include <stdarg.h> #include <stdarg.h>
#include <stdio.h> #include <stdio.h>
@@ -24,8 +25,10 @@
#include "link/patch.h" #include "link/patch.h"
#include "link/output.h" #include "link/output.h"
#include "extern/err.h"
#include "extern/getopt.h" #include "extern/getopt.h"
#include "error.h"
#include "platform.h"
#include "version.h" #include "version.h"
bool isDmgMode; /* -d */ bool isDmgMode; /* -d */
@@ -35,6 +38,10 @@ char const *symFileName; /* -n */
char const *overlayFileName; /* -O */ char const *overlayFileName; /* -O */
char const *outputFileName; /* -o */ char const *outputFileName; /* -o */
uint8_t padValue; /* -p */ uint8_t padValue; /* -p */
// Setting these three to 0 disables the functionality
uint16_t scrambleROMX = 0; /* -S */
uint8_t scrambleWRAMX = 0;
uint8_t scrambleSRAM = 0;
bool is32kMode; /* -t */ bool is32kMode; /* -t */
bool beVerbose; /* -v */ bool beVerbose; /* -v */
bool isWRA0Mode; /* -w */ bool isWRA0Mode; /* -w */
@@ -100,11 +107,25 @@ void error(struct FileStackNode const *where, uint32_t lineNo, char const *fmt,
nbErrors++; nbErrors++;
} }
void argErr(char flag, char const *fmt, ...)
{
va_list ap;
fprintf(stderr, "error: Invalid argument for option '%c': ", flag);
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
putc('\n', stderr);
if (nbErrors != UINT32_MAX)
nbErrors++;
}
_Noreturn void fatal(struct FileStackNode const *where, uint32_t lineNo, char const *fmt, ...) _Noreturn void fatal(struct FileStackNode const *where, uint32_t lineNo, char const *fmt, ...)
{ {
va_list ap; va_list ap;
fputs("fatal: ", stderr); fputs("FATAL: ", stderr);
if (where) { if (where) {
dumpFileStack(where); dumpFileStack(where);
fprintf(stderr, "(%" PRIu32 "): ", lineNo); fprintf(stderr, "(%" PRIu32 "): ", lineNo);
@@ -118,7 +139,7 @@ _Noreturn void fatal(struct FileStackNode const *where, uint32_t lineNo, char co
nbErrors++; nbErrors++;
fprintf(stderr, "Linking aborted after %" PRIu32 " error%s\n", nbErrors, fprintf(stderr, "Linking aborted after %" PRIu32 " error%s\n", nbErrors,
nbErrors != 1 ? "s" : ""); nbErrors == 1 ? "" : "s");
exit(1); exit(1);
} }
@@ -136,13 +157,13 @@ FILE *openFile(char const *fileName, char const *mode)
file = fdopen(1, mode); file = fdopen(1, mode);
if (!file) if (!file)
err(1, "Could not open file \"%s\"", fileName); err("Could not open file \"%s\"", fileName);
return file; return file;
} }
/* Short options */ /* Short options */
static char const *optstring = "dl:m:n:O:o:p:s:tVvwx"; static const char *optstring = "dl:m:n:O:o:p:S:s:tVvWwx";
/* /*
* Equivalent long options * Equivalent long options
@@ -162,6 +183,7 @@ static struct option const longopts[] = {
{ "overlay", required_argument, NULL, 'O' }, { "overlay", required_argument, NULL, 'O' },
{ "output", required_argument, NULL, 'o' }, { "output", required_argument, NULL, 'o' },
{ "pad", required_argument, NULL, 'p' }, { "pad", required_argument, NULL, 'p' },
{ "scramble", required_argument, NULL, 'S' },
{ "smart", required_argument, NULL, 's' }, { "smart", required_argument, NULL, 's' },
{ "tiny", no_argument, NULL, 't' }, { "tiny", no_argument, NULL, 't' },
{ "version", no_argument, NULL, 'V' }, { "version", no_argument, NULL, 'V' },
@@ -178,8 +200,8 @@ static void printUsage(void)
{ {
fputs( fputs(
"Usage: rgblink [-dtVvwx] [-l script] [-m map_file] [-n sym_file]\n" "Usage: rgblink [-dtVvwx] [-l script] [-m map_file] [-n sym_file]\n"
" [-O overlay_file] [-o out_file] [-p pad_value] [-s symbol]\n" " [-O overlay_file] [-o out_file] [-p pad_value]\n"
" <file> ...\n" " [-S spec] [-s symbol] <file> ...\n"
"Useful options:\n" "Useful options:\n"
" -l, --linkerscript <path> set the input linker script\n" " -l, --linkerscript <path> set the input linker script\n"
" -m, --map <path> set the output map file\n" " -m, --map <path> set the output map file\n"
@@ -202,6 +224,132 @@ static void cleanup(void)
obj_Cleanup(); obj_Cleanup();
} }
enum ScrambledRegion {
SCRAMBLE_ROMX,
SCRAMBLE_SRAM,
SCRAMBLE_WRAMX,
SCRAMBLE_UNK, // Used for errors
};
struct {
char const *name;
uint16_t max;
} scrambleSpecs[SCRAMBLE_UNK] = {
[SCRAMBLE_ROMX] = { "romx", 65535 },
[SCRAMBLE_SRAM] = { "sram", 255 },
[SCRAMBLE_WRAMX] = { "wramx", 7},
};
static void parseScrambleSpec(char const *spec)
{
// Skip any leading whitespace
spec += strspn(spec, " \t");
// The argument to `-S` should be a comma-separated list of sections followed by an '='
// indicating their scramble limit.
while (spec) {
// Invariant: we should not be pointing at whitespace at this point
assert(*spec != ' ' && *spec != '\t');
// Remember where the region's name begins and ends
char const *regionName = spec;
size_t regionNameLen = strcspn(spec, "=, \t");
// Length of region name string slice for printing, truncated if too long
int regionNamePrintLen = regionNameLen > INT_MAX ? INT_MAX : (int)regionNameLen;
// If this trips, `spec` must be pointing at a ',' or '=' (or NUL) due to the assert
if (regionNameLen == 0) {
argErr('S', "Missing region name");
if (*spec == '\0')
break;
if (*spec == '=') // Skip the limit, too
spec = strchr(&spec[1], ','); // Skip to next comma, if any
goto next;
}
// Find the next non-blank char after the region name's end
spec += regionNameLen + strspn(&spec[regionNameLen], " \t");
if (*spec != '\0' && *spec != ',' && *spec != '=') {
argErr('S', "Unexpected '%c' after region name \"%.*s\"",
regionNamePrintLen, regionName);
// Skip to next ',' or '=' (or NUL) and keep parsing
spec += 1 + strcspn(&spec[1], ",=");
}
// Now, determine which region type this is
enum ScrambledRegion region = 0;
while (region < SCRAMBLE_UNK) {
// If the strings match (case-insensitively), we got it!
// It's OK not to use `strncasecmp` because `regionName` is still
// NUL-terminated, since the encompassing spec is.
if (!strcasecmp(scrambleSpecs[region].name, regionName))
break;
region++;
}
if (region == SCRAMBLE_UNK)
argErr('S', "Unknown region \"%.*s\"", regionNamePrintLen, regionName);
if (*spec == '=') {
spec++; // `strtoul` will skip the whitespace on its own
unsigned long limit;
char *endptr;
if (*spec == '\0' || *spec == ',') {
argErr('S', "Empty limit for region \"%.*s\"",
regionNamePrintLen, regionName);
goto next;
}
limit = strtoul(spec, &endptr, 10);
endptr += strspn(endptr, " \t");
if (*endptr != '\0' && *endptr != ',') {
argErr('S', "Invalid non-numeric limit for region \"%.*s\"",
regionNamePrintLen, regionName);
endptr = strchr(endptr, ',');
}
spec = endptr;
if (region != SCRAMBLE_UNK && limit >= scrambleSpecs[region].max) {
argErr('S', "Limit for region \"%.*s\" may not exceed %" PRIu16,
regionNamePrintLen, regionName, scrambleSpecs[region].max);
limit = scrambleSpecs[region].max;
}
switch (region) {
case SCRAMBLE_ROMX:
scrambleROMX = limit;
break;
case SCRAMBLE_SRAM:
scrambleSRAM = limit;
break;
case SCRAMBLE_WRAMX:
scrambleWRAMX = limit;
break;
case SCRAMBLE_UNK: // The error has already been reported, do nothing
break;
}
} else if (region == SCRAMBLE_WRAMX) {
// Only WRAMX can be implied, since ROMX and SRAM size may vary
scrambleWRAMX = 7;
} else {
argErr('S', "Cannot imply limit for region \"%.*s\"",
regionNamePrintLen, regionName);
}
next:
if (spec) {
assert(*spec == ',' || *spec == '\0');
if (*spec == ',')
spec += 1 + strspn(&spec[1], " \t");
if (*spec == '\0')
break;
}
}
}
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
int optionChar; int optionChar;
@@ -234,15 +382,18 @@ int main(int argc, char *argv[])
case 'p': case 'p':
value = strtoul(musl_optarg, &endptr, 0); value = strtoul(musl_optarg, &endptr, 0);
if (musl_optarg[0] == '\0' || *endptr != '\0') { if (musl_optarg[0] == '\0' || *endptr != '\0') {
error(NULL, 0, "Invalid argument for option 'p'"); argErr('p', "");
value = 0xFF; value = 0xFF;
} }
if (value > 0xFF) { if (value > 0xFF) {
error(NULL, 0, "Argument for 'p' must be a byte (between 0 and 0xFF)"); argErr('p', "Argument for 'p' must be a byte (between 0 and 0xFF)");
value = 0xFF; value = 0xFF;
} }
padValue = value; padValue = value;
break; break;
case 'S':
parseScrambleSpec(musl_optarg);
break;
case 's': case 's':
/* FIXME: nobody knows what this does, figure it out */ /* FIXME: nobody knows what this does, figure it out */
(void)musl_optarg; (void)musl_optarg;
@@ -275,7 +426,7 @@ int main(int argc, char *argv[])
/* If no input files were specified, the user must have screwed up */ /* If no input files were specified, the user must have screwed up */
if (curArgIndex == argc) { if (curArgIndex == argc) {
fputs("fatal: no input files\n", stderr); fputs("FATAL: no input files\n", stderr);
printUsage(); printUsage();
exit(1); exit(1);
} }
@@ -304,7 +455,7 @@ int main(int argc, char *argv[])
patch_ApplyPatches(); patch_ApplyPatches();
if (nbErrors) { if (nbErrors) {
fprintf(stderr, "Linking failed with %" PRIu32 " error%s\n", fprintf(stderr, "Linking failed with %" PRIu32 " error%s\n",
nbErrors, nbErrors != 1 ? "s" : ""); nbErrors, nbErrors == 1 ? "" : "s");
exit(1); exit(1);
} }
out_WriteFiles(); out_WriteFiles();

View File

@@ -21,7 +21,7 @@
#include "link/section.h" #include "link/section.h"
#include "link/symbol.h" #include "link/symbol.h"
#include "extern/err.h" #include "error.h"
#include "helpers.h" #include "helpers.h"
#include "linkdefs.h" #include "linkdefs.h"
@@ -50,7 +50,7 @@ static struct Assertion *assertions;
type tmpVal = func(tmpFile); \ type tmpVal = func(tmpFile); \
/* TODO: maybe mark the condition as `unlikely`; how to do that portably? */ \ /* TODO: maybe mark the condition as `unlikely`; how to do that portably? */ \
if (tmpVal == (errval)) { \ if (tmpVal == (errval)) { \
errx(1, __VA_ARGS__, feof(tmpFile) \ errx(__VA_ARGS__, feof(tmpFile) \
? "Unexpected end of file" \ ? "Unexpected end of file" \
: strerror(errno)); \ : strerror(errno)); \
} \ } \
@@ -289,13 +289,13 @@ static void readPatch(FILE *file, struct Patch *patch, char const *fileName, cha
patch->rpnExpression = malloc(sizeof(*patch->rpnExpression) * patch->rpnSize); patch->rpnExpression = malloc(sizeof(*patch->rpnExpression) * patch->rpnSize);
if (!patch->rpnExpression) if (!patch->rpnExpression)
err(1, "%s: Failed to alloc \"%s\"'s patch #%" PRIu32 "'s RPN expression", err("%s: Failed to alloc \"%s\"'s patch #%" PRIu32 "'s RPN expression",
fileName, sectName, i); fileName, sectName, i);
size_t nbElementsRead = fread(patch->rpnExpression, sizeof(*patch->rpnExpression), size_t nbElementsRead = fread(patch->rpnExpression, sizeof(*patch->rpnExpression),
patch->rpnSize, file); patch->rpnSize, file);
if (nbElementsRead != patch->rpnSize) if (nbElementsRead != patch->rpnSize)
errx(1, "%s: Cannot read \"%s\"'s patch #%" PRIu32 "'s RPN expression: %s", errx("%s: Cannot read \"%s\"'s patch #%" PRIu32 "'s RPN expression: %s",
fileName, sectName, i, fileName, sectName, i,
feof(file) ? "Unexpected end of file" : strerror(errno)); feof(file) ? "Unexpected end of file" : strerror(errno));
} }
@@ -327,7 +327,7 @@ static void readSection(FILE *file, struct Section *section, char const *fileNam
tryReadlong(tmp, file, "%s: Cannot read \"%s\"'s' size: %s", tryReadlong(tmp, file, "%s: Cannot read \"%s\"'s' size: %s",
fileName, section->name); fileName, section->name);
if (tmp < 0 || tmp > UINT16_MAX) if (tmp < 0 || tmp > UINT16_MAX)
errx(1, "\"%s\"'s section size (%" PRId32 ") is invalid", errx("\"%s\"'s section size (%" PRId32 ") is invalid",
section->name, tmp); section->name, tmp);
section->size = tmp; section->size = tmp;
section->offset = 0; section->offset = 0;
@@ -373,13 +373,13 @@ static void readSection(FILE *file, struct Section *section, char const *fileNam
uint8_t *data = malloc(sizeof(*data) * section->size + 1); uint8_t *data = malloc(sizeof(*data) * section->size + 1);
if (!data) if (!data)
err(1, "%s: Unable to read \"%s\"'s data", fileName, err("%s: Unable to read \"%s\"'s data", fileName,
section->name); section->name);
if (section->size) { if (section->size) {
size_t nbElementsRead = fread(data, sizeof(*data), size_t nbElementsRead = fread(data, sizeof(*data),
section->size, file); section->size, file);
if (nbElementsRead != section->size) if (nbElementsRead != section->size)
errx(1, "%s: Cannot read \"%s\"'s data: %s", errx("%s: Cannot read \"%s\"'s data: %s",
fileName, section->name, fileName, section->name,
feof(file) ? "Unexpected end of file" feof(file) ? "Unexpected end of file"
: strerror(errno)); : strerror(errno));
@@ -394,7 +394,7 @@ static void readSection(FILE *file, struct Section *section, char const *fileNam
malloc(sizeof(*patches) * section->nbPatches + 1); malloc(sizeof(*patches) * section->nbPatches + 1);
if (!patches) if (!patches)
err(1, "%s: Unable to read \"%s\"'s patches", fileName, section->name); err("%s: Unable to read \"%s\"'s patches", fileName, section->name);
for (uint32_t i = 0; i < section->nbPatches; i++) for (uint32_t i = 0; i < section->nbPatches; i++)
readPatch(file, &patches[i], fileName, section->name, i, fileNodes); readPatch(file, &patches[i], fileName, section->name, i, fileNodes);
section->patches = patches; section->patches = patches;
@@ -462,7 +462,7 @@ void obj_ReadFile(char const *fileName, unsigned int fileID)
FILE *file = strcmp("-", fileName) ? fopen(fileName, "rb") : stdin; FILE *file = strcmp("-", fileName) ? fopen(fileName, "rb") : stdin;
if (!file) if (!file)
err(1, "Could not open file %s", fileName); err("Could not open file %s", fileName);
/* Begin by reading the magic bytes and version number */ /* Begin by reading the magic bytes and version number */
unsigned versionNumber; unsigned versionNumber;
@@ -470,13 +470,13 @@ void obj_ReadFile(char const *fileName, unsigned int fileID)
&versionNumber); &versionNumber);
if (matchedElems != 1) if (matchedElems != 1)
errx(1, "\"%s\" is not a RGBDS object file", fileName); errx("\"%s\" is not a RGBDS object file", fileName);
verbosePrint("Reading object file %s, version %u\n", verbosePrint("Reading object file %s, version %u\n",
fileName, versionNumber); fileName, versionNumber);
if (versionNumber != RGBDS_OBJECT_VERSION_NUMBER) if (versionNumber != RGBDS_OBJECT_VERSION_NUMBER)
errx(1, "\"%s\" is an incompatible version %u object file", errx("\"%s\" is an incompatible version %u object file",
fileName, versionNumber); fileName, versionNumber);
uint32_t revNum; uint32_t revNum;
@@ -484,7 +484,7 @@ void obj_ReadFile(char const *fileName, unsigned int fileID)
tryReadlong(revNum, file, "%s: Cannot read revision number: %s", tryReadlong(revNum, file, "%s: Cannot read revision number: %s",
fileName); fileName);
if (revNum != RGBDS_OBJECT_REV) if (revNum != RGBDS_OBJECT_REV)
errx(1, "%s is a revision 0x%04" PRIx32 " object file; only 0x%04x is supported", errx("%s is a revision 0x%04" PRIx32 " object file; only 0x%04x is supported",
fileName, revNum, RGBDS_OBJECT_REV); fileName, revNum, RGBDS_OBJECT_REV);
uint32_t nbSymbols; uint32_t nbSymbols;
@@ -500,7 +500,7 @@ void obj_ReadFile(char const *fileName, unsigned int fileID)
tryReadlong(nodes[fileID].nbNodes, file, "%s: Cannot read number of nodes: %s", fileName); tryReadlong(nodes[fileID].nbNodes, file, "%s: Cannot read number of nodes: %s", fileName);
nodes[fileID].nodes = calloc(nodes[fileID].nbNodes, sizeof(nodes[fileID].nodes[0])); nodes[fileID].nodes = calloc(nodes[fileID].nbNodes, sizeof(nodes[fileID].nodes[0]));
if (!nodes[fileID].nodes) if (!nodes[fileID].nodes)
err(1, "Failed to get memory for %s's nodes", fileName); err("Failed to get memory for %s's nodes", fileName);
verbosePrint("Reading %u nodes...\n", nodes[fileID].nbNodes); verbosePrint("Reading %u nodes...\n", nodes[fileID].nbNodes);
for (uint32_t i = nodes[fileID].nbNodes; i--; ) for (uint32_t i = nodes[fileID].nbNodes; i--; )
readFileStackNode(file, nodes[fileID].nodes, i, fileName); readFileStackNode(file, nodes[fileID].nodes, i, fileName);
@@ -510,12 +510,12 @@ void obj_ReadFile(char const *fileName, unsigned int fileID)
malloc(sizeof(*fileSymbols) * nbSymbols + 1); malloc(sizeof(*fileSymbols) * nbSymbols + 1);
if (!fileSymbols) if (!fileSymbols)
err(1, "Failed to get memory for %s's symbols", fileName); err("Failed to get memory for %s's symbols", fileName);
struct SymbolList *symbolList = malloc(sizeof(*symbolList)); struct SymbolList *symbolList = malloc(sizeof(*symbolList));
if (!symbolList) if (!symbolList)
err(1, "Failed to register %s's symbol list", fileName); err("Failed to register %s's symbol list", fileName);
symbolList->symbolList = fileSymbols; symbolList->symbolList = fileSymbols;
symbolList->nbSymbols = nbSymbols; symbolList->nbSymbols = nbSymbols;
symbolList->next = symbolLists; symbolList->next = symbolLists;
@@ -530,7 +530,7 @@ void obj_ReadFile(char const *fileName, unsigned int fileID)
struct Symbol *symbol = malloc(sizeof(*symbol)); struct Symbol *symbol = malloc(sizeof(*symbol));
if (!symbol) if (!symbol)
err(1, "%s: Couldn't create new symbol", fileName); err("%s: Couldn't create new symbol", fileName);
readSymbol(file, symbol, fileName, nodes[fileID].nodes); readSymbol(file, symbol, fileName, nodes[fileID].nodes);
fileSymbols[i] = symbol; fileSymbols[i] = symbol;
@@ -549,7 +549,7 @@ void obj_ReadFile(char const *fileName, unsigned int fileID)
/* Read section */ /* Read section */
fileSections[i] = malloc(sizeof(*fileSections[i])); fileSections[i] = malloc(sizeof(*fileSections[i]));
if (!fileSections[i]) if (!fileSections[i])
err(1, "%s: Couldn't create new section", fileName); err("%s: Couldn't create new section", fileName);
fileSections[i]->nextu = NULL; fileSections[i]->nextu = NULL;
readSection(file, fileSections[i], fileName, nodes[fileID].nodes); readSection(file, fileSections[i], fileName, nodes[fileID].nodes);
@@ -558,7 +558,7 @@ void obj_ReadFile(char const *fileName, unsigned int fileID)
fileSections[i]->symbols = malloc(nbSymPerSect[i] fileSections[i]->symbols = malloc(nbSymPerSect[i]
* sizeof(*fileSections[i]->symbols)); * sizeof(*fileSections[i]->symbols));
if (!fileSections[i]->symbols) if (!fileSections[i]->symbols)
err(1, "%s: Couldn't link to symbols", err("%s: Couldn't link to symbols",
fileName); fileName);
} else { } else {
fileSections[i]->symbols = NULL; fileSections[i]->symbols = NULL;
@@ -609,7 +609,7 @@ void obj_ReadFile(char const *fileName, unsigned int fileID)
struct Assertion *assertion = malloc(sizeof(*assertion)); struct Assertion *assertion = malloc(sizeof(*assertion));
if (!assertion) if (!assertion)
err(1, "%s: Couldn't create new assertion", fileName); err("%s: Couldn't create new assertion", fileName);
readAssertion(file, assertion, fileName, i, nodes[fileID].nodes); readAssertion(file, assertion, fileName, i, nodes[fileID].nodes);
linkPatchToPCSect(&assertion->patch, fileSections); linkPatchToPCSect(&assertion->patch, fileSections);
assertion->fileSymbols = fileSymbols; assertion->fileSymbols = fileSymbols;

View File

@@ -16,10 +16,8 @@
#include "link/section.h" #include "link/section.h"
#include "link/symbol.h" #include "link/symbol.h"
#include "extern/err.h" #include "error.h"
#include "linkdefs.h" #include "linkdefs.h"
#include "platform.h" // MIN_NB_ELMS #include "platform.h" // MIN_NB_ELMS
#define BANK_SIZE 0x4000 #define BANK_SIZE 0x4000
@@ -77,7 +75,7 @@ void out_AddSection(struct Section const *section)
uint32_t minNbBanks = targetBank + 1; uint32_t minNbBanks = targetBank + 1;
if (minNbBanks > maxNbBanks[section->type]) if (minNbBanks > maxNbBanks[section->type])
errx(1, "Section \"%s\" has an invalid bank range (%" PRIu32 " > %" PRIu32 ")", errx("Section \"%s\" has an invalid bank range (%" PRIu32 " > %" PRIu32 ")",
section->name, section->bank, section->name, section->bank,
maxNbBanks[section->type] - 1); maxNbBanks[section->type] - 1);
@@ -92,7 +90,7 @@ void out_AddSection(struct Section const *section)
sections[section->type].nbBanks = minNbBanks; sections[section->type].nbBanks = minNbBanks;
} }
if (!sections[section->type].banks) if (!sections[section->type].banks)
err(1, "Failed to realloc banks"); err("Failed to realloc banks");
struct SortedSection *newSection = malloc(sizeof(*newSection)); struct SortedSection *newSection = malloc(sizeof(*newSection));
struct SortedSection **ptr = section->size struct SortedSection **ptr = section->size
@@ -100,7 +98,7 @@ void out_AddSection(struct Section const *section)
: &sections[section->type].banks[targetBank].zeroLenSections; : &sections[section->type].banks[targetBank].zeroLenSections;
if (!newSection) if (!newSection)
err(1, "Failed to add new section \"%s\"", section->name); err("Failed to add new section \"%s\"", section->name);
newSection->section = section; newSection->section = section;
while (*ptr && (*ptr)->section->org < section->org) while (*ptr && (*ptr)->section->org < section->org)
@@ -145,15 +143,15 @@ static uint32_t checkOverlaySize(void)
fseek(overlayFile, 0, SEEK_SET); fseek(overlayFile, 0, SEEK_SET);
if (overlaySize % BANK_SIZE) if (overlaySize % BANK_SIZE)
errx(1, "Overlay file must have a size multiple of 0x4000"); errx("Overlay file must have a size multiple of 0x4000");
uint32_t nbOverlayBanks = overlaySize / BANK_SIZE; uint32_t nbOverlayBanks = overlaySize / BANK_SIZE;
if (is32kMode && nbOverlayBanks != 2) if (is32kMode && nbOverlayBanks != 2)
errx(1, "Overlay must be exactly 0x8000 bytes large"); errx("Overlay must be exactly 0x8000 bytes large");
if (nbOverlayBanks < 2) if (nbOverlayBanks < 2)
errx(1, "Overlay must be at least 0x8000 bytes large"); errx("Overlay must be at least 0x8000 bytes large");
return nbOverlayBanks; return nbOverlayBanks;
} }
@@ -178,7 +176,7 @@ static void coverOverlayBanks(uint32_t nbOverlayBanks)
realloc(sections[SECTTYPE_ROMX].banks, realloc(sections[SECTTYPE_ROMX].banks,
sizeof(*sections[SECTTYPE_ROMX].banks) * nbUncoveredBanks); sizeof(*sections[SECTTYPE_ROMX].banks) * nbUncoveredBanks);
if (!sections[SECTTYPE_ROMX].banks) if (!sections[SECTTYPE_ROMX].banks)
err(1, "Failed to realloc banks for overlay"); err("Failed to realloc banks for overlay");
for (uint32_t i = sections[SECTTYPE_ROMX].nbBanks; i < nbUncoveredBanks; i++) { for (uint32_t i = sections[SECTTYPE_ROMX].nbBanks; i < nbUncoveredBanks; i++) {
sections[SECTTYPE_ROMX].banks[i].sections = NULL; sections[SECTTYPE_ROMX].banks[i].sections = NULL;
sections[SECTTYPE_ROMX].banks[i].zeroLenSections = NULL; sections[SECTTYPE_ROMX].banks[i].zeroLenSections = NULL;
@@ -317,7 +315,7 @@ static void writeSymBank(struct SortedSections const *bankSections,
struct SortedSymbol *symList = malloc(sizeof(*symList) * nbSymbols); struct SortedSymbol *symList = malloc(sizeof(*symList) * nbSymbols);
if (!symList) if (!symList)
err(1, "Failed to allocate symbol list"); err("Failed to allocate symbol list");
uint32_t idx = 0; uint32_t idx = 0;

View File

@@ -17,11 +17,10 @@
#include "link/section.h" #include "link/section.h"
#include "link/symbol.h" #include "link/symbol.h"
#include "error.h"
#include "linkdefs.h" #include "linkdefs.h"
#include "opmath.h" #include "opmath.h"
#include "extern/err.h"
/* /*
* This is an "empty"-type stack. Apart from the actual values, we also remember * This is an "empty"-type stack. Apart from the actual values, we also remember
* whether the value is a placeholder inserted for error recovery. This allows * whether the value is a placeholder inserted for error recovery. This allows
@@ -43,7 +42,7 @@ static void initRPNStack(void)
stack.values = malloc(sizeof(*stack.values) * stack.capacity); stack.values = malloc(sizeof(*stack.values) * stack.capacity);
stack.errorFlags = malloc(sizeof(*stack.errorFlags) * stack.capacity); stack.errorFlags = malloc(sizeof(*stack.errorFlags) * stack.capacity);
if (!stack.values || !stack.errorFlags) if (!stack.values || !stack.errorFlags)
err(1, "Failed to init RPN stack"); err("Failed to init RPN stack");
} }
static void clearRPNStack(void) static void clearRPNStack(void)
@@ -57,7 +56,7 @@ static void pushRPN(int32_t value, bool comesFromError)
static const size_t increase_factor = 2; static const size_t increase_factor = 2;
if (stack.capacity > SIZE_MAX / increase_factor) if (stack.capacity > SIZE_MAX / increase_factor)
errx(1, "Overflow in RPN stack resize"); errx("Overflow in RPN stack resize");
stack.capacity *= increase_factor; stack.capacity *= increase_factor;
stack.values = stack.values =
@@ -70,7 +69,7 @@ static void pushRPN(int32_t value, bool comesFromError)
* the overflow check above. Hence the stringent check below. * the overflow check above. Hence the stringent check below.
*/ */
if (!stack.values || !stack.errorFlags || !stack.capacity) if (!stack.values || !stack.errorFlags || !stack.capacity)
err(1, "Failed to resize RPN stack"); err("Failed to resize RPN stack");
} }
stack.values[stack.size] = value; stack.values[stack.size] = value;

View File

@@ -20,6 +20,7 @@
.Op Fl O Ar overlay_file .Op Fl O Ar overlay_file
.Op Fl o Ar out_file .Op Fl o Ar out_file
.Op Fl p Ar pad_value .Op Fl p Ar pad_value
.Op Fl S Ar spec
.Op Fl s Ar symbol .Op Fl s Ar symbol
.Ar .Ar
.Sh DESCRIPTION .Sh DESCRIPTION
@@ -89,6 +90,14 @@ Has no effect if
.Fl O .Fl O
is specified. is specified.
The default is 0. The default is 0.
.It Fl S Ar spec , Fl Fl scramble Ar spec
Enables a different
.Dq scrambling
algorithm for placing sections.
See
.Sx Scrambling algorithm
below for an explanation and a description of
.Ar spec .
.It Fl s Ar symbol , Fl Fl smart Ar symbol .It Fl s Ar symbol , Fl Fl smart Ar symbol
This option is ignored. This option is ignored.
It was supposed to perform smart linking but fell into disrepair, and so has been removed. It was supposed to perform smart linking but fell into disrepair, and so has been removed.
@@ -113,6 +122,56 @@ When making a ROM, be careful that not using this is not a replacement for
.Xr rgbfix 1 Ap s Fl p .Xr rgbfix 1 Ap s Fl p
option! option!
.El .El
.Ss Scrambling algorithm
The default section placement algorithm tries to minimize the number of banks used;
.Dq scrambling
instead places sections into a given pool of banks, trying to minimize the number of sections sharing a given bank.
This is useful to catch broken bank assumptions, such as expecting two different sections to land in the same bank (that is not guaranteed unless both are manually assigned the same bank number).
.Pp
A scrambling spec is a comma-separated list of region specs.
A trailing comma is allowed, as well as whitespace between all specs and their components.
Each region spec has the following form:
.D1 Ar region Ns Op = Ns Ar size
.Ar region
must be one of the following (case-insensitive), while
.Ar size
must be a positive decimal integer between 1 and the corresponding maximum.
Certain regions allow omitting the size, in which case it defaults to its max value.
.Bl -column "Region name" "Max value" "Size optional"
Region name Ta Max size Ta Size optional
.Cm romx Ta 65535 Ta \&No
.Cm sram Ta 255 Ta \&No
.Cm wramx Ta 7 Ta Yes
.El
.Pp
A
.Ar size
of 0 disables scrambling for that region.
.Pp
For example,
.Ql romx=64,wramx=4
will scramble
.Ic ROMX
sections among ROM banks 1 to 64,
.Ic WRAMX
sections among RAM banks 1 to 4, and will not scramble
.Ic SRAM
sections.
.Pp
Later region specs override earlier ones; for example,
.Ql romx=42, Romx=0
disables scrambling for
.Cm romx .
.Pp
.Cm wramx
scrambling is silently ignored if
.Fl w
is passed (including if implied by
.Fl d ) ,
as
.Ic WRAMX
sections will be treated as
.Ic WRAM0 .
.Sh EXAMPLES .Sh EXAMPLES
All you need for a basic ROM is an object file, which can be made into a ROM image like so: All you need for a basic ROM is an object file, which can be made into a ROM image like so:
.Pp .Pp

View File

@@ -17,7 +17,7 @@
#include "link/script.h" #include "link/script.h"
#include "link/section.h" #include "link/section.h"
#include "extern/err.h" #include "error.h"
FILE *linkerScript; FILE *linkerScript;
char *includeFileName; char *includeFileName;
@@ -36,7 +36,7 @@ static uint32_t fileStackIndex;
static void pushFile(char *newFileName) static void pushFile(char *newFileName)
{ {
if (fileStackIndex == UINT32_MAX) if (fileStackIndex == UINT32_MAX)
errx(1, "%s(%" PRIu32 "): INCLUDE recursion limit reached", errx("%s(%" PRIu32 "): INCLUDE recursion limit reached",
linkerScriptName, lineNo); linkerScriptName, lineNo);
if (fileStackIndex == fileStackSize) { if (fileStackIndex == fileStackSize) {
@@ -45,7 +45,7 @@ static void pushFile(char *newFileName)
fileStackSize *= 2; fileStackSize *= 2;
fileStack = realloc(fileStack, sizeof(*fileStack) * fileStackSize); fileStack = realloc(fileStack, sizeof(*fileStack) * fileStackSize);
if (!fileStack) if (!fileStack)
err(1, "%s(%" PRIu32 "): Internal INCLUDE error", err("%s(%" PRIu32 "): Internal INCLUDE error",
linkerScriptName, lineNo); linkerScriptName, lineNo);
} }
@@ -56,7 +56,7 @@ static void pushFile(char *newFileName)
linkerScript = fopen(newFileName, "r"); linkerScript = fopen(newFileName, "r");
if (!linkerScript) if (!linkerScript)
err(1, "%s(%" PRIu32 "): Could not open \"%s\"", err("%s(%" PRIu32 "): Could not open \"%s\"",
linkerScriptName, lineNo, newFileName); linkerScriptName, lineNo, newFileName);
lineNo = 1; lineNo = 1;
linkerScriptName = newFileName; linkerScriptName = newFileName;
@@ -177,7 +177,7 @@ static int nextChar(void)
int curchar = getc(linkerScript); int curchar = getc(linkerScript);
if (curchar == EOF && ferror(linkerScript)) if (curchar == EOF && ferror(linkerScript))
err(1, "%s(%" PRIu32 "): Unexpected error in %s", err("%s(%" PRIu32 "): Unexpected error in %s",
linkerScriptName, lineNo, __func__); linkerScriptName, lineNo, __func__);
return curchar; return curchar;
} }
@@ -212,10 +212,8 @@ static struct LinkerScriptToken *nextToken(void)
if (curchar == '\r') { if (curchar == '\r') {
/* Handle CRLF */ /* Handle CRLF */
curchar = nextChar(); curchar = nextChar();
if (curchar != '\n') { if (curchar != '\n')
ungetc(curchar, linkerScript); ungetc(curchar, linkerScript);
curchar = '\r';
}
} }
} else if (curchar == '"') { } else if (curchar == '"') {
/* If we have a string start, this is a string */ /* If we have a string start, this is a string */
@@ -228,7 +226,7 @@ static struct LinkerScriptToken *nextToken(void)
do { do {
curchar = nextChar(); curchar = nextChar();
if (curchar == EOF || isNewline(curchar)) { if (curchar == EOF || isNewline(curchar)) {
errx(1, "%s(%" PRIu32 "): Unterminated string", errx("%s(%" PRIu32 "): Unterminated string",
linkerScriptName, lineNo); linkerScriptName, lineNo);
} else if (curchar == '"') { } else if (curchar == '"') {
/* Quotes force a string termination */ /* Quotes force a string termination */
@@ -237,7 +235,7 @@ static struct LinkerScriptToken *nextToken(void)
/* Backslashes are escape sequences */ /* Backslashes are escape sequences */
curchar = nextChar(); curchar = nextChar();
if (curchar == EOF || isNewline(curchar)) if (curchar == EOF || isNewline(curchar))
errx(1, "%s(%" PRIu32 "): Unterminated string", errx("%s(%" PRIu32 "): Unterminated string",
linkerScriptName, lineNo); linkerScriptName, lineNo);
else if (curchar == 'n') else if (curchar == 'n')
curchar = '\n'; curchar = '\n';
@@ -246,7 +244,7 @@ static struct LinkerScriptToken *nextToken(void)
else if (curchar == 't') else if (curchar == 't')
curchar = '\t'; curchar = '\t';
else if (curchar != '\\' && curchar != '"') else if (curchar != '\\' && curchar != '"')
errx(1, "%s(%" PRIu32 "): Illegal character escape", errx("%s(%" PRIu32 "): Illegal character escape",
linkerScriptName, lineNo); linkerScriptName, lineNo);
} }
@@ -254,7 +252,7 @@ static struct LinkerScriptToken *nextToken(void)
capacity *= 2; capacity *= 2;
token.attr.string = realloc(token.attr.string, capacity); token.attr.string = realloc(token.attr.string, capacity);
if (!token.attr.string) if (!token.attr.string)
err(1, "%s: Failed to allocate memory for string", err("%s: Failed to allocate memory for string",
__func__); __func__);
} }
token.attr.string[size++] = curchar; token.attr.string[size++] = curchar;
@@ -270,7 +268,7 @@ static struct LinkerScriptToken *nextToken(void)
capacity *= 2; capacity *= 2;
str = realloc(str, capacity); str = realloc(str, capacity);
if (!str) if (!str)
err(1, "%s: Failed to allocate memory for token", err("%s: Failed to allocate memory for token",
__func__); __func__);
} }
str[size] = toupper(curchar); str[size] = toupper(curchar);
@@ -320,7 +318,7 @@ static struct LinkerScriptToken *nextToken(void)
if (tryParseNumber(str, &token.attr.number)) if (tryParseNumber(str, &token.attr.number))
token.type = TOKEN_NUMBER; token.type = TOKEN_NUMBER;
else else
errx(1, "%s(%" PRIu32 "): Unknown token \"%s\"", errx("%s(%" PRIu32 "): Unknown token \"%s\"",
linkerScriptName, lineNo, str); linkerScriptName, lineNo, str);
} }
@@ -347,7 +345,7 @@ static void processCommand(enum LinkerScriptCommand command, uint16_t arg, uint1
} }
if (arg < *pc) if (arg < *pc)
errx(1, "%s(%" PRIu32 "): `%s` cannot be used to go backwards (currently at $%x)", errx("%s(%" PRIu32 "): `%s` cannot be used to go backwards (currently at $%x)",
linkerScriptName, lineNo, commands[command], *pc); linkerScriptName, lineNo, commands[command], *pc);
*pc = arg; *pc = arg;
} }
@@ -396,11 +394,11 @@ struct SectionPlacement *script_NextSection(void)
if (type != SECTTYPE_INVALID) { if (type != SECTTYPE_INVALID) {
if (curaddr[type][bankID] > endaddr(type) + 1) if (curaddr[type][bankID] > endaddr(type) + 1)
errx(1, "%s(%" PRIu32 "): Sections would extend past the end of %s ($%04" PRIx16 " > $%04" PRIx16 ")", errx("%s(%" PRIu32 "): Sections would extend past the end of %s ($%04" PRIx16 " > $%04" PRIx16 ")",
linkerScriptName, lineNo, typeNames[type], linkerScriptName, lineNo, typeNames[type],
curaddr[type][bankID], endaddr(type)); curaddr[type][bankID], endaddr(type));
if (curaddr[type][bankID] < startaddr[type]) if (curaddr[type][bankID] < startaddr[type])
errx(1, "%s(%" PRIu32 "): PC underflowed ($%04" PRIx16 " < $%04" PRIx16 ")", errx("%s(%" PRIu32 "): PC underflowed ($%04" PRIx16 " < $%04" PRIx16 ")",
linkerScriptName, lineNo, linkerScriptName, lineNo,
curaddr[type][bankID], startaddr[type]); curaddr[type][bankID], startaddr[type]);
} }
@@ -421,7 +419,7 @@ struct SectionPlacement *script_NextSection(void)
break; break;
case TOKEN_NUMBER: case TOKEN_NUMBER:
errx(1, "%s(%" PRIu32 "): stray number \"%" PRIu32 "\"", errx("%s(%" PRIu32 "): stray number \"%" PRIu32 "\"",
linkerScriptName, lineNo, linkerScriptName, lineNo,
token->attr.number); token->attr.number);
@@ -434,13 +432,13 @@ struct SectionPlacement *script_NextSection(void)
parserState = PARSER_LINEEND; parserState = PARSER_LINEEND;
if (type == SECTTYPE_INVALID) if (type == SECTTYPE_INVALID)
errx(1, "%s(%" PRIu32 "): Didn't specify a location before the section", errx("%s(%" PRIu32 "): Didn't specify a location before the section",
linkerScriptName, lineNo); linkerScriptName, lineNo);
section.section = section.section =
sect_GetSection(token->attr.string); sect_GetSection(token->attr.string);
if (!section.section) if (!section.section)
errx(1, "%s(%" PRIu32 "): Unknown section \"%s\"", errx("%s(%" PRIu32 "): Unknown section \"%s\"",
linkerScriptName, lineNo, linkerScriptName, lineNo,
token->attr.string); token->attr.string);
section.org = curaddr[type][bankID]; section.org = curaddr[type][bankID];
@@ -469,10 +467,10 @@ struct SectionPlacement *script_NextSection(void)
if (tokType == TOKEN_COMMAND) { if (tokType == TOKEN_COMMAND) {
if (type == SECTTYPE_INVALID) if (type == SECTTYPE_INVALID)
errx(1, "%s(%" PRIu32 "): Didn't specify a location before the command", errx("%s(%" PRIu32 "): Didn't specify a location before the command",
linkerScriptName, lineNo); linkerScriptName, lineNo);
if (!hasArg) if (!hasArg)
errx(1, "%s(%" PRIu32 "): Command specified without an argument", errx("%s(%" PRIu32 "): Command specified without an argument",
linkerScriptName, lineNo); linkerScriptName, lineNo);
processCommand(attr.command, arg, &curaddr[type][bankID]); processCommand(attr.command, arg, &curaddr[type][bankID]);
@@ -483,16 +481,16 @@ struct SectionPlacement *script_NextSection(void)
* specifying the number is optional. * specifying the number is optional.
*/ */
if (!hasArg && nbbanks(type) != 1) if (!hasArg && nbbanks(type) != 1)
errx(1, "%s(%" PRIu32 "): Didn't specify a bank number", errx("%s(%" PRIu32 "): Didn't specify a bank number",
linkerScriptName, lineNo); linkerScriptName, lineNo);
else if (!hasArg) else if (!hasArg)
arg = bankranges[type][0]; arg = bankranges[type][0];
else if (arg < bankranges[type][0]) else if (arg < bankranges[type][0])
errx(1, "%s(%" PRIu32 "): specified bank number is too low (%" PRIu32 " < %" PRIu32 ")", errx("%s(%" PRIu32 "): specified bank number is too low (%" PRIu32 " < %" PRIu32 ")",
linkerScriptName, lineNo, linkerScriptName, lineNo,
arg, bankranges[type][0]); arg, bankranges[type][0]);
else if (arg > bankranges[type][1]) else if (arg > bankranges[type][1])
errx(1, "%s(%" PRIu32 "): specified bank number is too high (%" PRIu32 " > %" PRIu32 ")", errx("%s(%" PRIu32 "): specified bank number is too high (%" PRIu32 " > %" PRIu32 ")",
linkerScriptName, lineNo, linkerScriptName, lineNo,
arg, bankranges[type][1]); arg, bankranges[type][1]);
bank = arg; bank = arg;
@@ -512,7 +510,7 @@ struct SectionPlacement *script_NextSection(void)
case PARSER_INCLUDE: case PARSER_INCLUDE:
if (token->type != TOKEN_STRING) if (token->type != TOKEN_STRING)
errx(1, "%s(%" PRIu32 "): Expected a file name after INCLUDE", errx("%s(%" PRIu32 "): Expected a file name after INCLUDE",
linkerScriptName, lineNo); linkerScriptName, lineNo);
/* Switch to that file */ /* Switch to that file */
@@ -532,7 +530,7 @@ lineend:
return NULL; return NULL;
parserState = PARSER_LINEEND; parserState = PARSER_LINEEND;
} else if (token->type != TOKEN_NEWLINE) } else if (token->type != TOKEN_NEWLINE)
errx(1, "%s(%" PRIu32 "): Unexpected %s at the end of the line", errx("%s(%" PRIu32 "): Unexpected %s at the end of the line",
linkerScriptName, lineNo, linkerScriptName, lineNo,
tokenTypes[token->type]); tokenTypes[token->type]);
break; break;

View File

@@ -14,8 +14,7 @@
#include "link/main.h" #include "link/main.h"
#include "link/section.h" #include "link/section.h"
#include "extern/err.h" #include "error.h"
#include "hashmap.h" #include "hashmap.h"
HashMap sections; HashMap sections;
@@ -44,12 +43,12 @@ static void checkSectUnionCompat(struct Section *target, struct Section *other)
if (other->isAddressFixed) { if (other->isAddressFixed) {
if (target->isAddressFixed) { if (target->isAddressFixed) {
if (target->org != other->org) if (target->org != other->org)
errx(1, "Section \"%s\" is defined with conflicting addresses $%04" errx("Section \"%s\" is defined with conflicting addresses $%04"
PRIx16 " and $%04" PRIx16, PRIx16 " and $%04" PRIx16,
other->name, target->org, other->org); other->name, target->org, other->org);
} else if (target->isAlignFixed) { } else if (target->isAlignFixed) {
if ((other->org - target->alignOfs) & target->alignMask) if ((other->org - target->alignOfs) & target->alignMask)
errx(1, "Section \"%s\" is defined with conflicting %d-byte alignment (offset %" errx("Section \"%s\" is defined with conflicting %d-byte alignment (offset %"
PRIu16 ") and address $%04" PRIx16, PRIu16 ") and address $%04" PRIx16,
other->name, target->alignMask + 1, other->name, target->alignMask + 1,
target->alignOfs, other->org); target->alignOfs, other->org);
@@ -60,14 +59,14 @@ static void checkSectUnionCompat(struct Section *target, struct Section *other)
} else if (other->isAlignFixed) { } else if (other->isAlignFixed) {
if (target->isAddressFixed) { if (target->isAddressFixed) {
if ((target->org - other->alignOfs) & other->alignMask) if ((target->org - other->alignOfs) & other->alignMask)
errx(1, "Section \"%s\" is defined with conflicting address $%04" errx("Section \"%s\" is defined with conflicting address $%04"
PRIx16 " and %d-byte alignment (offset %" PRIu16 ")", PRIx16 " and %d-byte alignment (offset %" PRIu16 ")",
other->name, target->org, other->name, target->org,
other->alignMask + 1, other->alignOfs); other->alignMask + 1, other->alignOfs);
} else if (target->isAlignFixed } else if (target->isAlignFixed
&& (other->alignMask & target->alignOfs) && (other->alignMask & target->alignOfs)
!= (target->alignMask & other->alignOfs)) { != (target->alignMask & other->alignOfs)) {
errx(1, "Section \"%s\" is defined with conflicting %d-byte alignment (offset %" errx("Section \"%s\" is defined with conflicting %d-byte alignment (offset %"
PRIu16 ") and %d-byte alignment (offset %" PRIu16 ")", PRIu16 ") and %d-byte alignment (offset %" PRIu16 ")",
other->name, target->alignMask + 1, target->alignOfs, other->name, target->alignMask + 1, target->alignOfs,
other->alignMask + 1, other->alignOfs); other->alignMask + 1, other->alignOfs);
@@ -85,13 +84,13 @@ static void checkFragmentCompat(struct Section *target, struct Section *other)
if (target->isAddressFixed) { if (target->isAddressFixed) {
if (target->org != org) if (target->org != org)
errx(1, "Section \"%s\" is defined with conflicting addresses $%04" errx("Section \"%s\" is defined with conflicting addresses $%04"
PRIx16 " and $%04" PRIx16, PRIx16 " and $%04" PRIx16,
other->name, target->org, other->org); other->name, target->org, other->org);
} else if (target->isAlignFixed) { } else if (target->isAlignFixed) {
if ((org - target->alignOfs) & target->alignMask) if ((org - target->alignOfs) & target->alignMask)
errx(1, "Section \"%s\" is defined with conflicting %d-byte alignment (offset %" errx("Section \"%s\" is defined with conflicting %d-byte alignment (offset %"
PRIu16 ") and address $%04" PRIx16, PRIu16 ") and address $%04" PRIx16,
other->name, target->alignMask + 1, other->name, target->alignMask + 1,
target->alignOfs, other->org); target->alignOfs, other->org);
@@ -107,14 +106,14 @@ static void checkFragmentCompat(struct Section *target, struct Section *other)
if (target->isAddressFixed) { if (target->isAddressFixed) {
if ((target->org - ofs) & other->alignMask) if ((target->org - ofs) & other->alignMask)
errx(1, "Section \"%s\" is defined with conflicting address $%04" errx("Section \"%s\" is defined with conflicting address $%04"
PRIx16 " and %d-byte alignment (offset %" PRIu16 ")", PRIx16 " and %d-byte alignment (offset %" PRIu16 ")",
other->name, target->org, other->name, target->org,
other->alignMask + 1, other->alignOfs); other->alignMask + 1, other->alignOfs);
} else if (target->isAlignFixed } else if (target->isAlignFixed
&& (other->alignMask & target->alignOfs) != (target->alignMask & ofs)) { && (other->alignMask & target->alignOfs) != (target->alignMask & ofs)) {
errx(1, "Section \"%s\" is defined with conflicting %d-byte alignment (offset %" errx("Section \"%s\" is defined with conflicting %d-byte alignment (offset %"
PRIu16 ") and %d-byte alignment (offset %" PRIu16 ")", PRIu16 ") and %d-byte alignment (offset %" PRIu16 ")",
other->name, target->alignMask + 1, target->alignOfs, other->name, target->alignMask + 1, target->alignOfs,
other->alignMask + 1, other->alignOfs); other->alignMask + 1, other->alignOfs);
@@ -132,7 +131,7 @@ static void mergeSections(struct Section *target, struct Section *other, enum Se
// Common checks // Common checks
if (target->type != other->type) if (target->type != other->type)
errx(1, "Section \"%s\" is defined with conflicting types %s and %s", errx("Section \"%s\" is defined with conflicting types %s and %s",
other->name, typeNames[target->type], typeNames[other->type]); other->name, typeNames[target->type], typeNames[other->type]);
if (other->isBankFixed) { if (other->isBankFixed) {
@@ -140,7 +139,7 @@ static void mergeSections(struct Section *target, struct Section *other, enum Se
target->isBankFixed = true; target->isBankFixed = true;
target->bank = other->bank; target->bank = other->bank;
} else if (target->bank != other->bank) { } else if (target->bank != other->bank) {
errx(1, "Section \"%s\" is defined with conflicting banks %" PRIu32 " and %" errx("Section \"%s\" is defined with conflicting banks %" PRIu32 " and %"
PRIu32, other->name, target->bank, other->bank); PRIu32, other->name, target->bank, other->bank);
} }
} }
@@ -161,7 +160,7 @@ static void mergeSections(struct Section *target, struct Section *other, enum Se
target->data = realloc(target->data, target->data = realloc(target->data,
sizeof(*target->data) * target->size + 1); sizeof(*target->data) * target->size + 1);
if (!target->data) if (!target->data)
errx(1, "Failed to concatenate \"%s\"'s fragments", target->name); errx("Failed to concatenate \"%s\"'s fragments", target->name);
memcpy(target->data + target->size - other->size, other->data, other->size); memcpy(target->data + target->size - other->size, other->data, other->size);
/* Adjust patches' PC offsets */ /* Adjust patches' PC offsets */
for (uint32_t patchID = 0; patchID < other->nbPatches; patchID++) for (uint32_t patchID = 0; patchID < other->nbPatches; patchID++)
@@ -184,14 +183,14 @@ void sect_AddSection(struct Section *section)
if (other) { if (other) {
if (section->modifier != other->modifier) if (section->modifier != other->modifier)
errx(1, "Section \"%s\" defined as %s and %s", section->name, errx("Section \"%s\" defined as %s and %s", section->name,
sectionModNames[section->modifier], sectionModNames[other->modifier]); sectionModNames[section->modifier], sectionModNames[other->modifier]);
else if (section->modifier == SECTION_NORMAL) else if (section->modifier == SECTION_NORMAL)
errx(1, "Section name \"%s\" is already in use", section->name); errx("Section name \"%s\" is already in use", section->name);
else else
mergeSections(other, section, section->modifier); mergeSections(other, section, section->modifier);
} else if (section->modifier == SECTION_UNION && sect_HasData(section->type)) { } else if (section->modifier == SECTION_UNION && sect_HasData(section->type)) {
errx(1, "Section \"%s\" is of type %s, which cannot be unionized", errx("Section \"%s\" is of type %s, which cannot be unionized",
section->name, typeNames[section->type]); section->name, typeNames[section->type]);
} else { } else {
/* If not, add it */ /* If not, add it */
@@ -302,5 +301,5 @@ void sect_DoSanityChecks(void)
{ {
sect_ForEach(doSanityChecks, NULL); sect_ForEach(doSanityChecks, NULL);
if (sanityChecksFailed) if (sanityChecksFailed)
errx(1, "Sanity checks failed"); errx("Sanity checks failed");
} }

View File

@@ -14,7 +14,7 @@
#include "link/symbol.h" #include "link/symbol.h"
#include "link/main.h" #include "link/main.h"
#include "extern/err.h" #include "error.h"
#include "hashmap.h" #include "hashmap.h"
HashMap symbols; HashMap symbols;

View File

@@ -232,7 +232,7 @@ with some bytes being special prefixes for integers and symbols.
.It Li $13 Ta Li unary ~ .It Li $13 Ta Li unary ~
.It Li $21 Ta Li && comparison .It Li $21 Ta Li && comparison
.It Li $22 Ta Li || comparison .It Li $22 Ta Li || comparison
.It Li $23 Ta Li unary\ ! .It Li $23 Ta Li unary \&!
.It Li $30 Ta Li == comparison .It Li $30 Ta Li == comparison
.It Li $31 Ta Li != comparison .It Li $31 Ta Li != comparison
.It Li $32 Ta Li > comparison .It Li $32 Ta Li > comparison

View File

@@ -12,7 +12,7 @@
#include "helpers.h" #include "helpers.h"
#include "version.h" #include "version.h"
const char *get_package_version_string(void) char const *get_package_version_string(void)
{ {
// The following conditional should be simplified by the compiler. // The following conditional should be simplified by the compiler.
if (strlen(BUILD_VERSION_STRING) == 0) { if (strlen(BUILD_VERSION_STRING) == 0) {

3
test/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
/pokecrystal/
/pokered/
/ucity/

View File

@@ -1,3 +1,3 @@
ERROR: align-large-ofs.asm(2): error: align-large-ofs.asm(2):
Alignment offset (2) must be smaller than alignment size (2) Alignment offset (2) must be smaller than alignment size (2)
error: Assembly aborted (1 error)! error: Assembly aborted (1 error)!

View File

@@ -1,3 +1,3 @@
ERROR: align-pc-outside-section.asm(1): error: align-pc-outside-section.asm(1):
Cannot output data outside of a SECTION Cannot output data outside of a SECTION
error: Assembly aborted (1 error)! error: Assembly aborted (1 error)!

View File

@@ -1,3 +1,3 @@
ERROR: align-unattainable.asm(3): error: align-unattainable.asm(3):
Section "X"'s alignment cannot be attained in WRAM0 Section "X"'s alignment cannot be attained in WRAM0
error: Assembly aborted (1 error)! error: Assembly aborted (1 error)!

View File

@@ -1,7 +1,7 @@
ERROR: anon-label-bad.asm(2): error: anon-label-bad.asm(2):
Label "!0" created outside of a SECTION Label "!0" created outside of a SECTION
ERROR: anon-label-bad.asm(6): error: anon-label-bad.asm(6):
Reference to anonymous label 2 before, when only 1 has been created so far Reference to anonymous label 2 before, when only 1 has been created so far
ERROR: anon-label-bad.asm(18): error: anon-label-bad.asm(18):
syntax error, unexpected : syntax error, unexpected :
error: Assembly aborted (3 errors)! error: Assembly aborted (3 errors)!

View File

@@ -1,7 +1,7 @@
ERROR: anon-label-bad.asm(2): error: anon-label-bad.asm(2):
Label "!0" created outside of a SECTION Label "!0" created outside of a SECTION
ERROR: anon-label-bad.asm(6): error: anon-label-bad.asm(6):
Reference to anonymous label 2 before, when only 1 has been created so far Reference to anonymous label 2 before, when only 1 has been created so far
ERROR: anon-label-bad.asm(18): error: anon-label-bad.asm(18):
syntax error syntax error
error: Assembly aborted (3 errors)! error: Assembly aborted (3 errors)!

View File

@@ -1,3 +1,3 @@
ERROR: assert-nosect-bank.asm(1): error: assert-nosect-bank.asm(1):
PC has no bank outside a section PC has no bank outside a section
error: Assembly aborted (1 error)! error: Assembly aborted (1 error)!

View File

@@ -1,10 +1,10 @@
ERROR: assert.asm(4): error: assert.asm(4):
Assertion failed Assertion failed
warning: assert.asm(10): [-Wassert] warning: assert.asm(10): [-Wassert]
Assertion failed Assertion failed
ERROR: assert.asm(18): error: assert.asm(18):
Expected constant expression: 'FloatingBase' is not constant at assembly time Expected constant expression: 'FloatingBase' is not constant at assembly time
ERROR: assert.asm(18): error: assert.asm(18):
Assertion failed Assertion failed
FATAL: assert.asm(21): FATAL: assert.asm(21):
Assertion failed Assertion failed

View File

@@ -1,3 +1,3 @@
ERROR: assert@-no-sect.asm(1): error: assert@-no-sect.asm(1):
PC has no value outside a section PC has no value outside a section
error: Assembly aborted (1 error)! error: Assembly aborted (1 error)!

View File

@@ -1,9 +1,9 @@
ERROR: bank.asm(13) -> bank.asm::def_sect(8): error: bank.asm(13) -> bank.asm::def_sect(8):
Expected constant expression: Section "ROMX_bad"'s bank is not known Expected constant expression: Section "ROMX_bad"'s bank is not known
ERROR: bank.asm(15) -> bank.asm::def_sect(8): error: bank.asm(15) -> bank.asm::def_sect(8):
Expected constant expression: Section "VRAM_bad"'s bank is not known Expected constant expression: Section "VRAM_bad"'s bank is not known
ERROR: bank.asm(17) -> bank.asm::def_sect(8): error: bank.asm(17) -> bank.asm::def_sect(8):
Expected constant expression: Section "SRAM_bad"'s bank is not known Expected constant expression: Section "SRAM_bad"'s bank is not known
ERROR: bank.asm(20) -> bank.asm::def_sect(8): error: bank.asm(20) -> bank.asm::def_sect(8):
Expected constant expression: Section "WRAMX_bad"'s bank is not known Expected constant expression: Section "WRAMX_bad"'s bank is not known
error: Assembly aborted (4 errors)! error: Assembly aborted (4 errors)!

View File

@@ -1,5 +1,5 @@
ERROR: block-comment-termination-error.asm(1): error: block-comment-termination-error.asm(1):
Unterminated block comment Unterminated block comment
ERROR: block-comment-termination-error.asm(1): error: block-comment-termination-error.asm(1):
syntax error, unexpected end of buffer syntax error, unexpected end of buffer
error: Assembly aborted (2 errors)! error: Assembly aborted (2 errors)!

View File

@@ -1,5 +1,5 @@
ERROR: block-comment-termination-error.asm(1): error: block-comment-termination-error.asm(1):
Unterminated block comment Unterminated block comment
ERROR: block-comment-termination-error.asm(1): error: block-comment-termination-error.asm(1):
syntax error syntax error
error: Assembly aborted (2 errors)! error: Assembly aborted (2 errors)!

View File

@@ -1,7 +1,7 @@
ERROR: bracketed-symbols.asm(16): error: bracketed-symbols.asm(16):
Formatting string as type 'X' Formatting string as type 'X'
ERROR: bracketed-symbols.asm(20): error: bracketed-symbols.asm(20):
"Label" does not have a constant value "Label" does not have a constant value
ERROR: bracketed-symbols.asm(21): error: bracketed-symbols.asm(21):
Expected constant PC but section is not fixed Expected constant PC but section is not fixed
error: Assembly aborted (3 errors)! error: Assembly aborted (3 errors)!

View File

@@ -1,57 +1,57 @@
ERROR: builtin-overwrite.asm(42) -> builtin-overwrite.asm::tickle(7): error: builtin-overwrite.asm(42) -> builtin-overwrite.asm::tickle(7):
'__LINE__' already defined as constant at <builtin> '__LINE__' already defined as constant at <builtin>
ERROR: builtin-overwrite.asm(42) -> builtin-overwrite.asm::tickle(8): error: builtin-overwrite.asm(42) -> builtin-overwrite.asm::tickle(8):
'__LINE__' already defined as constant at <builtin> '__LINE__' already defined as constant at <builtin>
ERROR: builtin-overwrite.asm(42) -> builtin-overwrite.asm::tickle(11): error: builtin-overwrite.asm(42) -> builtin-overwrite.asm::tickle(11):
'__LINE__' already defined at <builtin> '__LINE__' already defined at <builtin>
ERROR: builtin-overwrite.asm(42) -> builtin-overwrite.asm::tickle(12): error: builtin-overwrite.asm(42) -> builtin-overwrite.asm::tickle(12):
'__LINE__' already defined at <builtin> '__LINE__' already defined at <builtin>
ERROR: builtin-overwrite.asm(42) -> builtin-overwrite.asm::tickle(16): error: builtin-overwrite.asm(42) -> builtin-overwrite.asm::tickle(16):
Built-in symbol '__LINE__' cannot be purged Built-in symbol '__LINE__' cannot be purged
ERROR: builtin-overwrite.asm(42) -> builtin-overwrite.asm::tickle(17): error: builtin-overwrite.asm(42) -> builtin-overwrite.asm::tickle(17):
Built-in symbol '__LINE__' cannot be purged Built-in symbol '__LINE__' cannot be purged
ERROR: builtin-overwrite.asm(42) -> builtin-overwrite.asm::tickle(20): error: builtin-overwrite.asm(42) -> builtin-overwrite.asm::tickle(20):
'__LINE__' already defined at <builtin> '__LINE__' already defined at <builtin>
ERROR: builtin-overwrite.asm(42) -> builtin-overwrite.asm::tickle(21): error: builtin-overwrite.asm(42) -> builtin-overwrite.asm::tickle(21):
'__LINE__' already defined at <builtin> '__LINE__' already defined at <builtin>
ERROR: builtin-overwrite.asm(42) -> builtin-overwrite.asm::tickle(24): error: builtin-overwrite.asm(42) -> builtin-overwrite.asm::tickle(24):
'__LINE__' already defined as constant at <builtin> '__LINE__' already defined as constant at <builtin>
ERROR: builtin-overwrite.asm(42) -> builtin-overwrite.asm::tickle(25): error: builtin-overwrite.asm(42) -> builtin-overwrite.asm::tickle(25):
'__LINE__' already defined as constant at <builtin> '__LINE__' already defined as constant at <builtin>
ERROR: builtin-overwrite.asm(42) -> builtin-overwrite.asm::tickle(28): error: builtin-overwrite.asm(42) -> builtin-overwrite.asm::tickle(28):
'__LINE__' already defined at <builtin> '__LINE__' already defined at <builtin>
ERROR: builtin-overwrite.asm(42) -> builtin-overwrite.asm::tickle(29): error: builtin-overwrite.asm(42) -> builtin-overwrite.asm::tickle(29):
'__LINE__' already defined at <builtin> '__LINE__' already defined at <builtin>
ERROR: builtin-overwrite.asm(42) -> builtin-overwrite.asm::tickle(32): error: builtin-overwrite.asm(42) -> builtin-overwrite.asm::tickle(32):
'__LINE__' already defined as constant at <builtin> '__LINE__' already defined as constant at <builtin>
ERROR: builtin-overwrite.asm(42) -> builtin-overwrite.asm::tickle(33): error: builtin-overwrite.asm(42) -> builtin-overwrite.asm::tickle(33):
'__LINE__' already defined as constant at <builtin> '__LINE__' already defined as constant at <builtin>
ERROR: builtin-overwrite.asm(42) -> builtin-overwrite.asm::tickle(36): error: builtin-overwrite.asm(42) -> builtin-overwrite.asm::tickle(36):
'__LINE__' already defined as non-EQUS at <builtin> '__LINE__' already defined as non-EQUS at <builtin>
ERROR: builtin-overwrite.asm(42) -> builtin-overwrite.asm::tickle(37): error: builtin-overwrite.asm(42) -> builtin-overwrite.asm::tickle(37):
'__LINE__' already defined as non-EQUS at <builtin> '__LINE__' already defined as non-EQUS at <builtin>
ERROR: builtin-overwrite.asm(43) -> builtin-overwrite.asm::tickle(16): error: builtin-overwrite.asm(43) -> builtin-overwrite.asm::tickle(16):
Built-in symbol '__FILE__' cannot be purged Built-in symbol '__FILE__' cannot be purged
ERROR: builtin-overwrite.asm(43) -> builtin-overwrite.asm::tickle(17): error: builtin-overwrite.asm(43) -> builtin-overwrite.asm::tickle(17):
Built-in symbol '__FILE__' cannot be purged Built-in symbol '__FILE__' cannot be purged
ERROR: builtin-overwrite.asm(43) -> builtin-overwrite.asm::tickle(20): error: builtin-overwrite.asm(43) -> builtin-overwrite.asm::tickle(20):
'__FILE__' already defined at <builtin> '__FILE__' already defined at <builtin>
ERROR: builtin-overwrite.asm(43) -> builtin-overwrite.asm::tickle(21): error: builtin-overwrite.asm(43) -> builtin-overwrite.asm::tickle(21):
'__FILE__' already defined at <builtin> '__FILE__' already defined at <builtin>
ERROR: builtin-overwrite.asm(43) -> builtin-overwrite.asm::tickle(24): error: builtin-overwrite.asm(43) -> builtin-overwrite.asm::tickle(24):
'__FILE__' already defined as constant at <builtin> '__FILE__' already defined as constant at <builtin>
ERROR: builtin-overwrite.asm(43) -> builtin-overwrite.asm::tickle(25): error: builtin-overwrite.asm(43) -> builtin-overwrite.asm::tickle(25):
'__FILE__' already defined as constant at <builtin> '__FILE__' already defined as constant at <builtin>
ERROR: builtin-overwrite.asm(43) -> builtin-overwrite.asm::tickle(28): error: builtin-overwrite.asm(43) -> builtin-overwrite.asm::tickle(28):
'__FILE__' already defined at <builtin> '__FILE__' already defined at <builtin>
ERROR: builtin-overwrite.asm(43) -> builtin-overwrite.asm::tickle(29): error: builtin-overwrite.asm(43) -> builtin-overwrite.asm::tickle(29):
'__FILE__' already defined at <builtin> '__FILE__' already defined at <builtin>
ERROR: builtin-overwrite.asm(43) -> builtin-overwrite.asm::tickle(32): error: builtin-overwrite.asm(43) -> builtin-overwrite.asm::tickle(32):
'__FILE__' already defined as constant at <builtin> '__FILE__' already defined as constant at <builtin>
ERROR: builtin-overwrite.asm(43) -> builtin-overwrite.asm::tickle(33): error: builtin-overwrite.asm(43) -> builtin-overwrite.asm::tickle(33):
'__FILE__' already defined as constant at <builtin> '__FILE__' already defined as constant at <builtin>
ERROR: builtin-overwrite.asm(43) -> builtin-overwrite.asm::tickle(36): error: builtin-overwrite.asm(43) -> builtin-overwrite.asm::tickle(36):
Built-in symbol '__FILE__' cannot be redefined Built-in symbol '__FILE__' cannot be redefined
ERROR: builtin-overwrite.asm(43) -> builtin-overwrite.asm::tickle(37): error: builtin-overwrite.asm(43) -> builtin-overwrite.asm::tickle(37):
Built-in symbol '__FILE__' cannot be redefined Built-in symbol '__FILE__' cannot be redefined
error: Assembly aborted (28 errors)! error: Assembly aborted (28 errors)!

21
test/asm/ccode.asm Normal file
View File

@@ -0,0 +1,21 @@
SECTION "ccode test", ROM0[0]
Label:
.local1
jp z, Label
jr nz, .local1
call c, Label
call nc, Label
.local2
jp !nz, Label
jr !z, .local2
call !nc, Label
call !c, Label
.local3
jp !!z, Label
jr !!nz, .local3
call !!c, Label
call !!nc, Label

0
test/asm/ccode.err Normal file
View File

0
test/asm/ccode.out Normal file
View File

BIN
test/asm/ccode.out.bin Normal file

Binary file not shown.

View File

@@ -1,15 +1,15 @@
ERROR: code-after-endm-endr-endc.asm(6): error: code-after-endm-endr-endc.asm(6):
syntax error, unexpected PRINTLN, expecting newline or end of buffer syntax error, unexpected PRINTLN, expecting newline or end of buffer
ERROR: code-after-endm-endr-endc.asm(7): error: code-after-endm-endr-endc.asm(7):
Macro "mac" not defined Macro "mac" not defined
ERROR: code-after-endm-endr-endc.asm(12): error: code-after-endm-endr-endc.asm(12):
syntax error, unexpected PRINTLN, expecting newline or end of buffer syntax error, unexpected PRINTLN, expecting newline or end of buffer
ERROR: code-after-endm-endr-endc.asm(17): error: code-after-endm-endr-endc.asm(17):
syntax error, unexpected PRINTLN, expecting newline syntax error, unexpected PRINTLN, expecting newline
ERROR: code-after-endm-endr-endc.asm(19): error: code-after-endm-endr-endc.asm(19):
syntax error, unexpected PRINTLN, expecting newline or end of buffer syntax error, unexpected PRINTLN, expecting newline or end of buffer
ERROR: code-after-endm-endr-endc.asm(23): error: code-after-endm-endr-endc.asm(23):
syntax error, unexpected PRINTLN, expecting newline syntax error, unexpected PRINTLN, expecting newline
ERROR: code-after-endm-endr-endc.asm(25): error: code-after-endm-endr-endc.asm(25):
syntax error, unexpected PRINTLN, expecting newline or end of buffer syntax error, unexpected PRINTLN, expecting newline or end of buffer
error: Assembly aborted (7 errors)! error: Assembly aborted (7 errors)!

View File

@@ -0,0 +1,40 @@
macro try
println \1, "\2:"
def prefix equs \1
{prefix}\2 = 10
println \2 ; 10
{prefix}\2 += 5
println \2 ; 15
{prefix}\2 -= 1
println \2 ; 14
{prefix}\2 *= 2
println \2 ; 28
{prefix}\2 /= 4
println \2 ; 7
{prefix}\2 %= 3
println \2 ; 1
{prefix}\2 |= 11
println \2 ; 11
{prefix}\2 ^= 12
println \2 ; 7
{prefix}\2 &= 21
println \2 ; 5
{prefix}\2 <<= 2
println \2 ; 20
{prefix}\2 >>= 1
println \2 ; 10
purge prefix
endm
try "", p
try "def ", q
try "redef ", r
_RS += 100
println _RS
__LINE__ *= 200
println __LINE__
UnDeFiNeD ^= 300
println UnDeFiNeD

View File

@@ -0,0 +1,5 @@
error: compound-assignment.asm(36):
'__LINE__' already defined as constant at <builtin>
error: compound-assignment.asm(39):
Expected constant expression: 'UnDeFiNeD' is not constant at assembly time
error: Assembly aborted (2 errors)!

View File

@@ -0,0 +1,39 @@
p:
$A
$F
$E
$1C
$7
$1
$B
$7
$5
$14
$A
def q:
$A
$F
$E
$1C
$7
$1
$B
$7
$5
$14
$A
redef r:
$A
$F
$E
$1C
$7
$1
$B
$7
$5
$14
$A
$64
$25
$0

View File

@@ -12,6 +12,6 @@ SECTION "Test", ROM0
dw "A" + 1 dw "A" + 1
dl "A" + 1 dl "A" + 1
db 1, ("UVWXYZ") & $ff, -1 db 1, ("WXYZ") & $ff, -1
dw 1, ("UVWXYZ") & $ffff, -1 dw 1, ("WXYZ") & $ffff, -1
dl 1, ("UVWXYZ"), -1 dl 1, ("WXYZ"), -1

View File

@@ -0,0 +1,6 @@
warning: db-dw-dl-string.asm(15): [-Wnumeric-string]
Treating 4-character string as a number
warning: db-dw-dl-string.asm(16): [-Wnumeric-string]
Treating 4-character string as a number
warning: db-dw-dl-string.asm(17): [-Wnumeric-string]
Treating 4-character string as a number

View File

@@ -1,10 +1,10 @@
def variable = 1 def variable = 1
println variable println variable
def variable set 2 def variable = 2
println variable println variable
redef variable = 3 redef variable = 3
println variable println variable
redef variable set 4 redef variable = 4
println variable println variable
DEF constant EQU 42 DEF constant EQU 42

View File

@@ -1,3 +1,3 @@
ERROR: def.asm(23): error: def.asm(23):
'constant' already defined at def.asm(10) 'constant' already defined at def.asm(10)
error: Assembly aborted (1 error)! error: Assembly aborted (1 error)!

View File

@@ -1,5 +1,5 @@
ERROR: def.asm(23): error: def.asm(23):
'constant' already defined at def.asm(10) 'constant' already defined at def.asm(10)
ERROR: def.asm(29): error: def.asm(29):
syntax error syntax error
error: Assembly aborted (2 errors)! error: Assembly aborted (2 errors)!

View File

@@ -1,5 +1,5 @@
warning: deprecated-pi.asm(2): [-Wobsolete] warning: deprecated-pi.asm(2): [-Wobsolete]
`_PI` is deprecated; use 3.14159 `_PI` is deprecated; use 3.14159
ERROR: deprecated-pi.asm(3): error: deprecated-pi.asm(3):
Built-in symbol '_PI' cannot be purged Built-in symbol '_PI' cannot be purged
error: Assembly aborted (1 error)! error: Assembly aborted (1 error)!

View File

@@ -1,4 +1,4 @@
ERROR: ds-bad.asm(3): error: ds-bad.asm(3):
Expected constant expression: 'unknown' is not constant at assembly time Expected constant expression: 'unknown' is not constant at assembly time
warning: ds-bad.asm(4): [-Wtruncation] warning: ds-bad.asm(4): [-Wtruncation]
Expression must be 8-bit Expression must be 8-bit

View File

@@ -1,4 +1,4 @@
ERROR: duplicate-section.asm(4): error: duplicate-section.asm(4):
Section already defined previously at duplicate-section.asm(2) Section already defined previously at duplicate-section.asm(2)
FATAL: duplicate-section.asm(4): FATAL: duplicate-section.asm(4):
Cannot create section "sec" (1 error) Cannot create section "sec" (1 error)

View File

@@ -0,0 +1,2 @@
FATAL: empty-local.asm(5):
'Label.' is a nonsensical reference to an empty local label

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