Compare commits

..

106 Commits

Author SHA1 Message Date
Rangi
69a573923f Version 0.6.1 2022-12-02 22:48:37 -05:00
ISSOtm
7eb4ecea8b Remove use of std::filesystem
This appears to break compatibility with macOS 10.9
2022-12-03 01:51:14 +01:00
ISSOtm
599ce757a1 Force Windows builds to use our zlib and libpng
Otherwise we will have a few problems if, say, a system version was
detected and picked up instead of ours...
2022-12-03 00:58:50 +01:00
ISSOtm
75a07a90f8 Always initialise section->data to avoid an uninit read
The addition of SDCC objects required a change in the logic of
`mergeSections()` to dispatch based on `->data` instead of
`sect_HasData`, which implicitly assumes that `->data` is
always initialised (maybe NULL).
However, RGBDS sections did not do that!
2022-12-02 22:39:31 +01:00
ISSOtm
ec2d1312ef Remove auto parameter to -flto
Not all supported compilers support the argument;
the move was a bit premature.
2022-11-15 19:49:04 +01:00
ISSOtm
03b6dd9321 Only cache dependency directories instead of whole test/
Otherwise, changes made to the test suite are not picked up
2022-11-15 19:49:04 +01:00
ISSOtm
a16d3d6405 Fail RGBGFX test suite if support test programs fail to be built 2022-11-15 19:49:04 +01:00
ISSOtm
3e5cd8ce1a Use a special name for stdin/stdout in diagnostics 2022-11-15 19:49:04 +01:00
ISSOtm
6902387991 Allow rgbgfx - for stdin and stdout
Closes #1087
2022-11-15 19:49:04 +01:00
ISSOtm
62b4f2b264 Upgrade to checkout action v3
No code changes, just uses Node 16 instead of the
deprecated Node 12
2022-11-13 14:33:03 +01:00
Rangi
79748afdc4 Align the "; Next union/fragment" comments with their symbols 2022-11-06 23:55:15 +01:00
Rangi
32cb0558e4 Print "; Next union/fragment" between "pieces" in .map file
Resolves #1099
2022-11-06 23:55:15 +01:00
rlewicki
92b2ac3c8c Remove duplicated EMPTY label in case no bank memory is used 2022-11-03 19:54:21 +01:00
rlewicki
0e67298dff Fix indention when writing EMPTY label inside link output.c file 2022-11-03 19:54:21 +01:00
Robert Lewicki
f6d218ed36 Fix regression tests failing due to invalid cache being restored (#1104) 2022-11-01 14:27:40 +01:00
Robert Lewicki
1a9fc964df #1082 Add cache check for external repositories used during testing (#1100)
Co-authored-by: Rangi <35663410+Rangi42@users.noreply.github.com>
Co-authored-by: Eldred Habert <eldredhabert0@gmail.com>
2022-10-25 08:39:19 +02:00
ISSOtm
48248faab0 Suppress CMake project maintainer warnings in CI 2022-10-16 12:21:54 +02:00
ISSOtm
58181c2d73 Hoist common Windows dep grabbing code to a script
Function courtesy of @aaaaaa123456789
2022-10-16 12:21:54 +02:00
ISSOtm
0f86084e08 Rename actions folder as scripts
It's clearer this way.
2022-10-16 12:21:54 +02:00
ISSOtm
c8e602dec1 Mangle the name of absolute sections
They are unlikely to be unique across files, actually
2022-10-15 23:16:04 +02:00
ISSOtm
b168717e91 Update zlib to 1.2.13
1.2.12 is no longer provided by upstream anymore,
which fails Windows CI
2022-10-15 22:02:34 +02:00
ISSOtm
930c2ac328 Require Bison 3.0.0 in CMakeLists
We actually require that version, so be explicit about it
to provide better error messages.
2022-10-13 11:02:33 +02:00
ISSOtm
28737d5778 Enable GLIBCXX_ASSERTIONS in make develop
Not sure it's very portable, but this is only the dev config
2022-10-12 01:23:37 +02:00
ISSOtm
12ba057b4f Check that colour slot is non-empty before checking for gray-ness
This is otherwise UB, and trips a GLIBCXX assertion (when enabled).
2022-10-11 21:39:32 +02:00
ISSOtm
0e0876b17f Print addr ranges for empty blocks as well
Mirrors what sections do, for clarity & consistency
2022-10-07 16:04:02 +02:00
Eldred Habert
b28eea24fc Update .github/workflows/create-release-artifacts.yaml
Co-authored-by: Rangi <35663410+Rangi42@users.noreply.github.com>
2022-10-04 12:50:46 -04:00
ISSOtm
a1e59ddc3d Avoid -x c++ affecting ${PNGLDLIBS}
version.c doesn't link to anything from libpng, so it'll be fine
2022-10-04 12:50:46 -04:00
ISSOtm
3fbdba31bf Build macOS binaries as well for releases 2022-10-04 12:50:46 -04:00
ISSOtm
d90a7e4302 Terminate RGBGFX when opening a file fails
`std::filebuf::open`'s result must be checked, though that's not obvious.
2022-10-04 13:58:05 +02:00
ISSOtm
7377a14245 Improve RGBASM's "input files" error messages slightly 2022-10-03 17:17:19 +02:00
Eldred Habert
e2136d60b2 Print a more user-friendly error message for leftover diff marks (#1089) 2022-10-03 16:52:29 +02:00
ISSOtm
74e40654e6 Sync release docs CI workflow with master
We should look into reusable workflows, really.
https://docs.github.com/en/actions/using-workflows/reusing-workflows
2022-10-03 01:50:17 +02:00
Rangi
f90857032c Version 0.6.0 2022-10-02 19:08:13 -04:00
Rangi
1653a9a3f2 Use -flto=auto 2022-10-02 13:50:35 -04:00
Rangi
3c049983f1 Fixed-point functions can take specific precision (#1086) 2022-10-02 16:56:08 +02:00
Rangi
8553b61a94 Fixed-point values can use all 32-Q magnitude bits (#1085) 2022-10-02 11:08:38 +02:00
ISSOtm
ab12c474d2 Properly exclude GCC from macOS matrices 2022-10-02 02:50:38 -04:00
ISSOtm
8ccbd9dc36 Properly build and link against libpng
Doing it right this time.
Also bundling the newly required DLLs.
2022-10-02 02:50:38 -04:00
ISSOtm
b8307432b8 Fix use of bitwise OR instead of logical
Thanks, Clang!
2022-10-02 02:50:38 -04:00
ISSOtm
80a62a8a03 Update CI target OSes
Remove platforms deprecated by GitHub Actions
Add new platforms supported by the same
2022-10-02 02:50:38 -04:00
Rangi
bbe28faab4 Sort rgbgfx's -r option alphabetically 2022-10-01 21:22:39 -04:00
Rangi
106ad30e5a Allow fixed-point constants to have unsigned range (#1084)
For example with Q.4, $F0 is 15.0, which no longer warns
2022-10-01 23:32:34 +02:00
Rangi
a1107fc5cf Refactor !!x to x != 0
Also limit comments and docs to single "!"s
2022-10-01 14:09:02 -04:00
Rangi
969412af24 Parse HEX palettes (#1081)
Addresses one item of #1065
2022-10-01 12:45:00 -04:00
Eldred Habert
c10345f26d Comply with sym file spec (#1078)
Co-authored-by: Rangi <35663410+Rangi42@users.noreply.github.com>
2022-10-01 12:35:00 -04:00
ISSOtm
6fd5c94b27 Document gbc pal spec format
I *knew* I had forgotten something!
2022-10-01 10:48:40 +02:00
Rangi
ddb1d0b6aa Parse GPL palettes, and fix PSP palette parsing (#1080)
Addresses one item of #1065
2022-10-01 10:46:13 +02:00
Rangi
08545643cf Only define @ and _NARG when they have values (#1073)
Fixes #1069

Co-authored-by: Eldred Habert <eldredhabert0@gmail.com>
2022-10-01 01:04:03 +02:00
Rangi
140c6b169e Patch pokecrystal to use embedded palettes 2022-10-01 00:51:14 +02:00
Rangi
d86d24bdc1 Remove legacy support for generating a palette with unused colors
If you need an explicit set of colors, possibly including
unused ones, use `-c`.

Fixes #1062
2022-10-01 00:51:14 +02:00
Eldred Habert
a1a919579c Add support for GBC palette dumps to -c (#1075)
Fixes #1063
2022-09-30 17:09:28 -04:00
Rangi
a47da5f71f Deprecate __FILE__ and __LINE__ (#1072)
Unlike C, these constants are not convenient for logging in macros,
since they always report the same data (their location in the macro).

Fixes #1068
2022-09-30 19:48:30 +02:00
Rangi
68ad926279 Patch projects so CI will build (#1071)
Fixes #1070
2022-09-30 12:19:11 +02:00
Rangi
dec4133e84 SECTION(symbol) returns the name of a symbol's section (#1066)
Fixes #963

Co-authored-by: Eldred Habert <eldredhabert0@gmail.com>
2022-09-30 01:59:33 +02:00
Rangi
c35cb6ac32 Warning levels -Wunmapped-char=0/1/2 (#1061)
Fixes #1058
2022-09-29 18:14:04 -04:00
Rangi
023884d2b0 Redefine the trig functions to divide circles into 1.0 turns (#1060)
This makes their behavior consistent across Q settings

Fixes #1059
2022-09-29 10:57:29 +02:00
ISSOtm
3567faf395 Use backslash escape instead of "backwards slash" in man pages
The latter is in the "Lines" category, which seems inappropriate.
2022-09-26 09:45:25 +02:00
Eldred Habert
6502ed3919 Add -I as an alias for -i in rgbasm (#1056)
Co-authored-by: Rangi <35663410+Rangi42@users.noreply.github.com>
2022-09-26 03:42:30 -04:00
Rangi
b1a241233e Preserve Unix line endings for .bash and .flags files (#1054)
Fixes #955
2022-09-25 11:18:44 +02:00
Rangi
f88968ec20 Fix rgbasm -b and rgbasm -g (#1052)
Fixes #1051
2022-09-25 10:22:55 +02:00
Rangi
5ad8a8c958 Warn when a duplicate CLI argument overrides a previous one (#1053)
Fixes #1050
2022-09-25 10:04:30 +02:00
Rangi
2827374505 Use STD*_FILENO constants (#1055)
These are defined in platform.h, but not consistently used

Co-authored-by: Eldred Habert <eldredhabert0@gmail.com>
2022-09-25 10:02:53 +02:00
Rangi
b8385a50e3 Support -P/--preinclude to pre-INCLUDE a file (#1043)
Fixes #1041

Co-authored-by: ISSOtm <eldredhabert0@gmail.com>
2022-09-24 12:37:16 -04:00
ISSOtm
02923a67f3 Use tabs for indentation in map files
As requested by #1012
2022-09-24 12:58:48 +02:00
Rangi
f5b1990604 Document that symbol interpolation works outside of strings too 2022-09-22 01:15:17 -04:00
ISSOtm
0794da22bc Clarify at-files documentation 2022-09-13 08:34:41 +02:00
Rangi
6df75f7af3 Summarize used and free space at the end of the .map file
Fixes #1046
2022-09-12 23:16:09 +02:00
Rangi
7ae23e6cdb Release 0.6.0-rc2 2022-09-08 17:07:47 -04:00
Rangi
98a6dffbca Implement opt Q for fixed-point precision, and q literals (e.g. 12.34q8) (#958)
Fixes #957

Co-authored-by: ISSOtm <eldredhabert0@gmail.com>
2022-09-05 00:47:32 +02:00
Rangi
889302a9e2 Document the -H and -l flags
Fixes #1042
2022-09-02 08:43:41 +02:00
Rangi
c01317e08d Only increment the unique \@ ID when it is first used per context (#1030)
This avoids changes to generated `\@` labels just by adding or
removing macros or loops which do not actually use `\@`.

Fixes #1019
2022-08-31 17:45:21 -04:00
Rangi
a52a00a9ca macro_UndefUniqueID uses 0 to mean \@ is undefined 2022-08-30 16:50:34 -04:00
Rangi
fa13611bbf Make comments more consistent
- Changes most `/* comments */` to `// comments`
- Changes `/**` block comments consistently to `/*`
- Adds consistent license comments to all files

Also renames `T_POP_SET` to `T_Z80_SET`
2022-08-30 07:51:32 +02:00
Rangi
dca24a6d50 Test that OPT r fails immediately if the recursion depth is already exceeded
Fixes #1034
2022-08-28 22:21:24 +02:00
Rangi
4363ffcad4 Clarify the JR documentation based on its usage (#1032)
Fixes #1020
2022-08-28 15:42:04 -04:00
Rangi
14e6a79adc Deprecate the old macro syntax (#1025)
Fixes #1011
2022-08-28 15:22:21 -04:00
Rangi
7a2ee26792 rgbasm -r sets the maximum recursion depth (#1026)
Previously it set the minimum failure depth (off by one)

Fixes #978
2022-08-28 15:21:29 -04:00
Rangi
425339ccf6 Implement FMOD function for fixed-point modulo
Fixes #1021
2022-08-28 21:21:10 +02:00
Rangi
1a1f1365e6 Clarify the FOR loop documentation (#1031)
Clarify the FOR loop documentation

Fixes #1003

Co-authored-by: Eldred Habert <eldredhabert0@gmail.com>
2022-08-28 15:16:49 -04:00
Rangi
f97139461c Clarify the linkerscript example (#1028)
* Clarify the linkerscript example

* Explain that backslash escape sequences are supported

Fixes #1006
2022-08-28 15:13:43 -04:00
Rangi
8207dc57b7 Add a -Wunmapped-char warning for characters not in the charmap (#1023)
Fixes #1022
2022-08-28 15:12:43 -04:00
Rangi
d29057e747 Indent "SLACK:" to match the "SECTION" headers 2022-08-28 20:35:21 +02:00
Rangi
f1b74fa610 Report empty space between sections in map file 2022-08-28 20:35:21 +02:00
Rangi
c7a92d3104 rgblink -M omits symbol names from .map file 2022-08-28 20:35:21 +02:00
ISSOtm
0105779789 Fix incorrect Bash completions after --long-opt=
This is because `$COMP_CWORD` points to that `=` "word".
2022-08-10 08:44:41 +02:00
ISSOtm
9ef7954670 Fix some lint warnings in Bash completions 2022-08-10 00:16:05 +02:00
ISSOtm
d7d524294b Fix shebangs in Bash completion scripts
Not that you are really supposed to run them stand-alone?
2022-08-09 22:32:18 +02:00
ISSOtm
12fed4c68e Harden Bash completion scripts against invalid states
Printing an error message is better than locking the shell up, honestly.
2022-08-09 22:30:50 +02:00
ISSOtm
3db3421f07 Correct wrong state transitions in RGBGFX Bash completion script
Isn't strinly-typed programming just lovely?
Fixes #1018
2022-08-09 22:28:12 +02:00
ISSOtm
92eb0a133b Allow rgbgfx -r to read an infinite amount of tiles without tilemap
Previously, indices would be wrapped after 256 even without a tilemap;
since RGBGFX can generate arbitrarily large tile data if `-N` is not used,
it should be possible to read those in.

Of course, this won't work if a tilemap is provided, but such "big blobs"
can't generate a meaningful tilemap anyway.
2022-08-04 20:50:08 +02:00
ISSOtm
b02ccf8f4a Check before attempting to generate empty image
This causes a libpng warning then error, but print a better error message
Fixes #1016
2022-08-04 20:40:20 +02:00
ISSOtm
2e0991f32b Use proper 16-bit type for image reversing width
Fixes #1015
2022-08-04 20:40:20 +02:00
Eldred Habert
f3f2c2ca16 Improve object file format documentation (#1010)
Replacing the big pre-formatted text block with a list brings:
- Better accessibility, obviously
- Responsiveness
- Better formatting (bold, etc.)
- Sub-sections that can now be linked to
- Hyperlink cross-refs to other pages

The slight disadvantage is that `ENDC` etc. are now individual
list items, whereas they'd be better as part of the same item.
No big deal though, it was much worse before.

Some descriptions have been overhauled for clarity, and some
outright corrected (such as Assertions' "Offset" field).

Co-authored-by: Antonio Vivace <avivace4@gmail.com>
2022-07-29 22:48:55 +02:00
ISSOtm
9ec8186ac6 Switch linkdefs from scattered arrays to an array of structs
The info is better organized this way
2022-07-19 19:11:02 +02:00
ISSOtm
ab9945c1ee Avoid using fscanf to detect RGBDS object files
This function is made for text, e.g. accepts spaces, leading zeros, etc. before `%u`.
This way checks that the correct amount of bytes are read instead.
2022-07-19 19:10:58 +02:00
ISSOtm
18e4f132a8 Fix labels subtraction docs
Fixes #1009
2022-07-14 13:43:36 +02:00
ISSOtm
828b2adcdf Make RGBLINK able to link SDCC object files
This requires a LOT of tricky code, mostly due to the format itself being,
er, not the most straightforward.
Everything is converted to existing RGBLINK concepts (sections, patches,
etc.), so the core code is essentially unchanged.
(A couple of genuine RGBLINK bugs were uncovered along the way, so some of
the core code *is* changed, notably regarding `SECTION FRAGMENT`s.)

All of this code was clean-roomed, so SDCC's GPLv2 license does not apply.
2022-07-11 21:17:34 +02:00
ISSOtm
1c2965467d Process linker script before doing sanity checks 2022-07-11 21:17:34 +02:00
ISSOtm
d243e50390 Do not perform any sanity checks for bad section types
Otherwise, the arrays get overflowed
2022-07-11 21:17:34 +02:00
ISSOtm
acb33777c6 Sort RGB colors as specified in the docs 2022-07-10 12:36:10 +02:00
ISSOtm
d15916b1bd Clarify sorting order of RGB colors 2022-07-10 12:32:57 +02:00
ISSOtm
28fcef0ecd Improve some wording slightly 2022-07-10 12:30:18 +02:00
ISSOtm
b53c115ec2 Fix width and height being reported in wrong order 2022-07-10 12:14:24 +02:00
ISSOtm
6a51e39a5c Print error if reverse() fails to open a file 2022-07-10 11:55:56 +02:00
Antonio Vivace
e348f70866 Remove funding options, leaving only OpenCollective 2022-07-08 21:22:28 +02:00
ISSOtm
43a487f0bf Fix two inverted column widths 2022-07-02 17:48:28 +02:00
245 changed files with 5334 additions and 2945 deletions

4
.gitattributes vendored
View File

@@ -1,2 +1,6 @@
# Shell scripts need Unix line endings (see https://github.com/gbdev/rgbds/issues/841) # Shell scripts need Unix line endings (see https://github.com/gbdev/rgbds/issues/841)
*.sh text eol=lf *.sh text eol=lf
*.bash text eol=lf
# Flags also need Unix line endings (see https://github.com/gbdev/rgbds/issues/955)
*.flags text eol=lf

2
.github/FUNDING.yml vendored
View File

@@ -1,3 +1 @@
github: avivace
patreon: gbdev01
open_collective: gbdev open_collective: gbdev

View File

@@ -1,17 +0,0 @@
#!/bin/bash
source mingw-env @TRIPLE@
echo LAST IS: $last
# check if last arg is a path to configure, else use parent
for last; do true; done
if test -x "${last}/configure"; then
config_path="$last"
else
config_path=".."
fi
${config_path}/configure \
--host=@TRIPLE@ --target=@TRIPLE@ --build="$CHOST" \
--prefix=/usr/@TRIPLE@ --libdir=/usr/@TRIPLE@/lib --includedir=/usr/@TRIPLE@/include \
--enable-shared --enable-static "$@"

View File

@@ -1,16 +0,0 @@
#!/bin/sh
_arch=$1
default_mingw_pp_flags="-D_FORTIFY_SOURCE=2"
default_mingw_compiler_flags="$default_mingw_pp_flags -O2 -pipe -fno-plt -fexceptions --param=ssp-buffer-size=4"
default_mingw_linker_flags="-Wl,-O1,--sort-common,--as-needed -fstack-protector"
export CPPFLAGS="${MINGW_CPPFLAGS:-$default_mingw_pp_flags $CPPFLAGS}"
export CFLAGS="${MINGW_CFLAGS:-$default_mingw_compiler_flags $CFLAGS}"
export CXXFLAGS="${MINGW_CXXFLAGS:-$default_mingw_compiler_flags $CXXFLAGS}"
export LDFLAGS="${MINGW_LDFLAGS:-$default_mingw_linker_flags $LDFLAGS}"
mingw_prefix=/usr/${_arch}
export PKG_CONFIG_SYSROOT_DIR="${mingw_prefix}"
export PKG_CONFIG_LIBDIR="${mingw_prefix}/lib/pkgconfig:${mingw_prefix}/share/pkgconfig"

View File

@@ -1,44 +0,0 @@
#!/bin/sh
# This script was written by ISSOtm while looking at Arch Linux's PKGBUILD for
# the corresponding package. (And its dependencies)
# https://aur.archlinux.org/packages/mingw-w64-libpng/
set -e
pngver=1.6.37
_apngver=$pngver
_arch="$1"
## Install mingw-configure and mingw-env (both build dependencies)
install -m 755 .github/actions/mingw-env.sh /usr/bin/mingw-env
sed "s|@TRIPLE@|${_arch}|g" .github/actions/mingw-configure.sh > ${_arch}-configure
install -m 755 ${_arch}-configure /usr/bin/
## Grab sources and check them
wget http://downloads.sourceforge.net/sourceforge/libpng/libpng-$pngver.tar.xz
wget http://downloads.sourceforge.net/project/apng/libpng/libpng16/libpng-$_apngver-apng.patch.gz
sha256sum -c .github/actions/mingw-w64-libpng-dev.sha256sums
## Extract sources
tar -xf libpng-$pngver.tar.xz
gunzip libpng-$_apngver-apng.patch.gz
## Start building!
cd libpng-$pngver
# Patch in apng support
patch -p0 ../libpng-$_apngver-apng.patch
mkdir -p build-${_arch}
cd build-${_arch}
${_arch}-configure LDFLAGS=-static-libgcc
make
make install

23
.github/scripts/get_win_deps.ps1 vendored Normal file
View File

@@ -0,0 +1,23 @@
function getlibrary ([string] $URI, [string] $filename, [string] $hash, [string] $destdir) {
$wc = New-Object Net.WebClient
[string] $downloadhash = $null
try {
$wc.DownloadFile($URI, $filename)
$downloadhash = $(Get-FileHash $filename -Algorithm SHA256).Hash
} catch {
Write-Host "${filename}: failed to download"
exit 1
}
if ($hash -ne $downloadhash) {
Write-Host "${filename}: SHA256 mismatch ($downloadhash)"
exit 1
}
Expand-Archive -DestinationPath $destdir $filename
}
getlibrary 'https://www.zlib.net/zlib1213.zip' 'zlib.zip' 'd233fca7cf68db4c16dc5287af61f3cd01ab62495224c66639ca3da537701e42' .
getlibrary 'https://download.sourceforge.net/libpng/lpng1637.zip' 'libpng.zip' '3b4b1cbd0bae6822f749d39b1ccadd6297f05e2b85a83dd2ce6ecd7d09eabdf2' .
getlibrary 'https://github.com/lexxmark/winflexbison/releases/download/v2.5.24/win_flex_bison-2.5.24.zip' 'winflexbison.zip' '39c6086ce211d5415500acc5ed2d8939861ca1696aee48909c7f6daf5122b505' install_dir
Move-Item zlib-1.2.13 zlib
Move-Item lpng1637 libpng

6
.github/scripts/install.sh vendored Executable file
View File

@@ -0,0 +1,6 @@
#!/bin/bash
install -d /usr/local/bin/ /usr/local/share/man/man1/ /usr/local/share/man/man5/ /usr/local/share/man/man7/
install -s -m 755 rgbasm rgblink rgbfix rgbgfx /usr/local/bin/
install -m 644 rgbasm.1 rgblink.1 rgbfix.1 rgbgfx.1 /usr/local/share/man/man1/
install -m 644 rgbds.5 rgbasm.5 rgblink.5 /usr/local/share/man/man5/
install -m 644 rgbds.7 gbz80.7 /usr/local/share/man/man7/

34
.github/scripts/mingw-w64-libpng-dev.sh vendored Executable file
View File

@@ -0,0 +1,34 @@
#!/bin/sh
set -e
pngver=1.6.37
arch="$1"
## Grab sources and check them
wget http://downloads.sourceforge.net/project/libpng/libpng16/$pngver/libpng-$pngver.tar.xz
wget http://downloads.sourceforge.net/project/apng/libpng/libpng16/libpng-$pngver-apng.patch.gz
sha256sum -c .github/scripts/mingw-w64-libpng-dev.sha256sums
## Extract sources and patch them
tar -xf libpng-$pngver.tar.xz
gunzip libpng-$pngver-apng.patch.gz
# Patch in apng support
env -C libpng-$pngver patch -p0 ../libpng-$pngver-apng.patch
## Start building!
mkdir -p build
cd build
../libpng-$pngver/configure \
--host="$arch" --target="$arch" \
--prefix="/usr/$arch" \
--enable-shared --disable-static \
CPPFLAGS="-D_FORTIFY_SOURCE=2" \
CFLAGS="-O2 -pipe -fno-plt -fexceptions --param=ssp-buffer-size=4" \
LDFLAGS="-Wl,-O1,--sort-common,--as-needed -fstack-protector"
make -kj
make install

View File

@@ -6,78 +6,108 @@ on:
jobs: jobs:
windows: windows:
runs-on: windows-2019 runs-on: windows-2022
strategy:
matrix:
bits: [32, 64]
include:
- bits: 32
arch: x86
platform: Win32
- bits: 64
arch: x86_x64
platform: x64
fail-fast: false
steps:
- name: Get version from tag
shell: bash
run: | # Turn "vX.Y.Z" into "X.Y.Z"
VERSION="${{ github.ref_name }}"
echo "version=${VERSION#v}" >> $GITHUB_ENV
- uses: actions/checkout@v3
- name: Install deps
run: .github/scripts/get_win_deps.ps1
- uses: actions/cache@v3
id: cache
with:
path: |
zbuild
pngbuild
key: ${{ matrix.arch }}-${{ hashFiles('zlib/**', 'libpng/**') }}
- name: Build zlib
run: | # BUILD_SHARED_LIBS causes the output DLL to be correctly called `zlib1.dll`
cmake -S zlib -B zbuild -A ${{ matrix.platform }} -Wno-dev -DCMAKE_INSTALL_PREFIX=install_dir -DBUILD_SHARED_LIBS=ON
cmake --build zbuild --config Release -j
if: steps.cache.outputs.cache-hit != 'true'
- name: Install zlib
run: |
cmake --install zbuild
- name: Build libpng
shell: bash
run: |
cmake -S libpng -B pngbuild -A ${{ matrix.platform }} -Wno-dev -DCMAKE_INSTALL_PREFIX=install_dir -DPNG_SHARED=ON -DPNG_STATIC=OFF -DPNG_TESTS=OFF -DPNG_BUILD_ZLIB=ON -DZLIB_INCLUDE_DIR="$PWD"/install_dir/include -DZLIB_LIBRARY="$PWD"/install_dir/lib/zlib.lib
cmake --build pngbuild --config Release -j
if: steps.cache.outputs.cache-hit != 'true'
- name: Install libpng
run: |
cmake --install pngbuild
- name: Build Windows binaries
shell: bash
run: |
cmake -S . -B build -A ${{ matrix.platform }} -DCMAKE_INSTALL_PREFIX=install_dir -DCMAKE_BUILD_TYPE=Release -DZLIB_LIBRARY="$PWD"/install_dir/lib/zlib.lib -DZLIB_INCLUDE_DIR="$PWD"/install_dir/include -DPNG_LIBRARY="$PWD"/install_dir/lib/libpng16.lib -DPNG_INCLUDE_DIR="$PWD"/install_dir/include
cmake --build build --config Release -j --verbose
cmake --install build --verbose --prefix install_dir --strip
- name: Package binaries
run: |
Compress-Archive -LiteralPath @("install_dir/bin/rgbasm.exe", "install_dir/bin/rgblink.exe", "install_dir/bin/rgbfix.exe", "install_dir/bin/rgbgfx.exe", "install_dir/bin/zlib1.dll", "install_dir/bin/libpng16.dll") "rgbds-${{ env.version }}-win${{ matrix.bits }}.zip"
- name: Upload Windows binaries
uses: actions/upload-artifact@v3
with:
name: win${{ matrix.bits }}
path: rgbds-${{ env.version }}-win${{ matrix.bits }}.zip
macos:
runs-on: macos-12
steps: steps:
- uses: actions/checkout@v2
- name: Get version from tag - name: Get version from tag
shell: bash shell: bash
run: | # Turn "refs/tags/vX.Y.Z" into "X.Y.Z" run: | # Turn "refs/tags/vX.Y.Z" into "X.Y.Z"
VERSION="${{ github.ref }}" VERSION="${{ github.ref_name }}"
echo "version=${VERSION##*/v}" >> $GITHUB_ENV echo "version=${VERSION#v}" >> $GITHUB_ENV
- name: Get zlib, libpng and bison - uses: actions/checkout@v3
run: | # TODO: use an array - name: Install deps
$wc = New-Object System.Net.WebClient shell: bash
$wc.DownloadFile('https://www.zlib.net/zlib1212.zip', 'zlib.zip')
$hash = (Get-FileHash "zlib.zip" -Algorithm SHA256).Hash
if ($hash -ne '173e89893dcb8b4a150d7731cd72f0602f1d6b45e60e2a54efdf7f3fc3325fd7') {
Write-Host "zlib SHA256 mismatch! ($hash)"
exit 1
}
$wc.DownloadFile('https://download.sourceforge.net/libpng/lpng1637.zip', 'libpng.zip')
$hash = (Get-FileHash "libpng.zip" -Algorithm SHA256).Hash
if ($hash -ne '3b4b1cbd0bae6822f749d39b1ccadd6297f05e2b85a83dd2ce6ecd7d09eabdf2') {
Write-Host "libpng SHA256 mismatch! ($hash)"
exit 1
}
$wc.DownloadFile('https://github.com/lexxmark/winflexbison/releases/download/v2.5.24/win_flex_bison-2.5.24.zip', 'winflexbison.zip')
$hash = (Get-FileHash "winflexbison.zip" -Algorithm SHA256).Hash
if ($hash -ne '39c6086ce211d5415500acc5ed2d8939861ca1696aee48909c7f6daf5122b505') {
Write-Host "bison SHA256 mismatch! ($hash)"
}
Expand-Archive -DestinationPath . "zlib.zip"
Expand-Archive -DestinationPath . "libpng.zip"
Expand-Archive -DestinationPath install_dir "winflexbison.zip"
Move-Item zlib-1.2.12 zlib
Move-Item lpng1637 libpng
- name: Build 32-bit zlib
run: | # BUILD_SHARED_LIBS causes the output DLL to be correctly called `zlib1.dll`
cmake -S zlib -B zbuild32 -A Win32 -DCMAKE_INSTALL_PREFIX=install_dir -DBUILD_SHARED_LIBS=ON
cmake --build zbuild32 --config Release
cmake --install zbuild32
- name: Build 32-bit libpng
run: | run: |
cmake -S libpng -B pngbuild32 -A Win32 -DCMAKE_INSTALL_PREFIX=install_dir -DPNG_SHARED=ON -DPNG_STATIC=ON -DPNG_TESTS=OFF ./.github/scripts/install_deps.sh macos-latest
cmake --build pngbuild32 --config Release # We force linking libpng statically; the other libs are provided by macOS itself
cmake --install pngbuild32 - name: Build binaries
- name: Build 32-bit Windows binaries
run: | run: |
cmake -S . -B build32 -A Win32 -DCMAKE_INSTALL_PREFIX=install_dir -DCMAKE_BUILD_TYPE=Release export PATH="/usr/local/opt/bison/bin:$PATH"
cmake --build build32 --config Release make -j WARNFLAGS="-Wall -Wextra -mmacosx-version-min=10.9" PKG_CONFIG="pkg-config --static" PNGLDLIBS="$(pkg-config --static --libs-only-L libpng | cut -c 3-)/libpng.a $(pkg-config --static --libs-only-l libpng | sed s/-lpng[0-9]*//g)" Q=
cmake --install build32 - name: Package binaries
- name: Package 32-bit binaries
run: | run: |
Compress-Archive -LiteralPath @("install_dir/bin/rgbasm.exe", "install_dir/bin/rgblink.exe", "install_dir/bin/rgbfix.exe", "install_dir/bin/rgbgfx.exe", "install_dir/bin/zlib1.dll", "install_dir/bin/libpng16.dll") "rgbds-${{ env.version }}-win32.zip" zip --junk-paths rgbds-${{ env.version }}-macos-x86-64.zip rgb{asm,link,fix,gfx} man/* .github/scripts/install.sh
- name: Build 64-bit zlib - name: Upload macOS binaries
run: | # BUILD_SHARED_LIBS causes the output DLL to be correctly called `zlib1.dll` uses: actions/upload-artifact@v3
cmake -S zlib -B zbuild64 -A x64 -DCMAKE_INSTALL_PREFIX=install_dir -DBUILD_SHARED_LIBS=ON with:
cmake --build zbuild64 --config Release name: macos
cmake --install zbuild64 path: rgbds-${{ env.version }}-macos-x86-64.zip
- name: Build 64-bit libpng
run: | release:
cmake -S libpng -B pngbuild64 -A x64 -DCMAKE_INSTALL_PREFIX=install_dir -DPNG_SHARED=ON -DPNG_STATIC=ON -DPNG_TESTS=OFF runs-on: ubuntu-latest
cmake --build pngbuild64 --config Release needs: [windows, macos]
cmake --install pngbuild64 steps:
- name: Build 64-bit Windows binaries - name: Get version from tag
run: | shell: bash
cmake -S . -B build64 -A x64 -DCMAKE_INSTALL_PREFIX=install_dir -DCMAKE_BUILD_TYPE=Release run: | # Turn "refs/tags/vX.Y.Z" into "X.Y.Z"
cmake --build build64 --config Release VERSION="${{ github.ref_name }}"
cmake --install build64 echo "version=${VERSION#v}" >> $GITHUB_ENV
- name: Package 64-bit binaries - uses: actions/checkout@v3
run: |
Compress-Archive -LiteralPath @("install_dir/bin/rgbasm.exe", "install_dir/bin/rgblink.exe", "install_dir/bin/rgbfix.exe", "install_dir/bin/rgbgfx.exe", "install_dir/bin/zlib1.dll", "install_dir/bin/libpng16.dll") "rgbds-${{ env.version }}-win64.zip"
- name: Package sources - name: Package sources
run: | run: |
make dist make dist Q=
ls
- uses: actions/download-artifact@v3
- name: Release - name: Release
uses: softprops/action-gh-release@v1 uses: softprops/action-gh-release@v1
with: with:
@@ -88,8 +118,9 @@ jobs:
draft: true # Don't publish the release quite yet... draft: true # Don't publish the release quite yet...
prerelease: ${{ contains(github.ref, '-rc') }} prerelease: ${{ contains(github.ref, '-rc') }}
files: | files: |
rgbds-${{ env.version }}-win32.zip win32/rgbds-${{ env.version }}-win32.zip
rgbds-${{ env.version }}-win64.zip win64/rgbds-${{ env.version }}-win64.zip
macos/rgbds-${{ env.version }}-macos-x86-64.zip
rgbds-${{ env.version }}.tar.gz rgbds-${{ env.version }}.tar.gz
fail_on_unmatched_files: true fail_on_unmatched_files: true
env: env:

View File

@@ -7,32 +7,25 @@ on:
jobs: jobs:
build: build:
if: github.repository_owner == 'gbdev' if: github.repository_owner == 'gbdev'
runs-on: ubuntu-18.04 runs-on: ubuntu-22.04
steps: steps:
- name: Checkout rgbds@release - name: Checkout rgbds@release
uses: actions/checkout@v2 uses: actions/checkout@v3
with: with:
path: rgbds path: rgbds
- name: Checkout rgbds-www@master - name: Checkout rgbds-www@master
uses: actions/checkout@v2 uses: actions/checkout@v3
with: with:
repository: ${{ github.repository_owner }}/rgbds-www repository: ${{ github.repository_owner }}/rgbds-www
path: rgbds-www path: rgbds-www
# `-O toc` was added in 1.14.5, but the repos only have 1.14.4 - name: Install groff and mandoc
- name: Build and install mandoc + install groff
run: | run: |
sudo apt-get -qq update sudo apt-get -qq update
sudo apt-get install -yq groff zlib1g-dev sudo apt-get install -yq groff mandoc
wget 'http://mandoc.bsd.lv/snapshots/mandoc-1.14.6.tar.gz'
tar xf mandoc-1.14.6.tar.gz
cd mandoc-1.14.6
./configure
make
sudo make install
- name: Update pages - name: Update pages
working-directory: rgbds/man working-directory: rgbds/man
run: | # The ref appears to be in the format "refs/tags/<version>", so strip that run: | # The ref appears to be in the format "refs/tags/<version>", so strip that
../../rgbds-www/.github/actions/get-pages.sh ${GITHUB_REF##*/} * ../../rgbds-www/maintainer/man_to_html.sh ${GITHUB_REF##*/} *
- name: Push new pages - name: Push new pages
working-directory: rgbds-www working-directory: rgbds-www
run: | run: |

View File

@@ -7,14 +7,14 @@ jobs:
unix-testing: unix-testing:
strategy: strategy:
matrix: matrix:
os: [ubuntu-20.04, ubuntu-18.04, macos-11.0, macos-10.15] os: [ubuntu-20.04, ubuntu-22.04, macos-11, macos-12]
cc: [gcc, clang] cc: [gcc, clang]
buildsys: [make, cmake] buildsys: [make, cmake]
exclude: exclude:
# `gcc` is just an alias to `clang` on macOS, don't bother # `gcc` is just an alias to `clang` on macOS, don't bother
- os: macos-10.15 - os: macos-11
cc: gcc cc: gcc
- os: macos-11.0 - os: macos-12
cc: gcc cc: gcc
include: include:
- cc: gcc - cc: gcc
@@ -24,11 +24,11 @@ jobs:
fail-fast: false fail-fast: false
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: Install deps - name: Install deps
shell: bash shell: bash
run: | run: |
./.github/actions/install_deps.sh ${{ matrix.os }} ./.github/scripts/install_deps.sh ${{ matrix.os }}
# The `export` lines are to allow working on macOS... # The `export` lines are to allow working on macOS...
# Apple's base version is severely outdated, not even supporting -Wall, # Apple's base version is severely outdated, not even supporting -Wall,
# but it overrides Homebrew's version nonetheless... # but it overrides Homebrew's version nonetheless...
@@ -55,6 +55,25 @@ jobs:
with: with:
name: rgbds-canary-${{ matrix.os }}-${{ matrix.cc }}-${{ matrix.buildsys }} name: rgbds-canary-${{ matrix.os }}-${{ matrix.cc }}-${{ matrix.buildsys }}
path: bins path: bins
- name: Compute test dependency cache params
id: test-deps-cache-params
shell: bash
run: |
paths=$(test/fetch-test-deps.sh --get-paths)
hash=$(test/fetch-test-deps.sh --get-hash)
tee -a <<<"paths=\"${paths//,/\\n}\"" $GITHUB_OUTPUT
tee -a <<<"hash=${hash%-}" $GITHUB_OUTPUT
- name: Check test dependency repositories cache
id: test-deps-cache
uses: actions/cache@v3
with:
path: ${{ fromJSON(steps.test-deps-cache-params.outputs.paths) }}
key: ${{ matrix.os }}-${{ steps.test-deps-cache-params.outputs.hash }}
- if: steps.test-deps-cache.outputs.cache-hit != 'true'
name: Fetch test dependency repositories
continue-on-error: true
run: |
test/fetch-test-deps.sh
- name: Test - name: Test
shell: bash shell: bash
run: | run: |
@@ -64,6 +83,7 @@ jobs:
strategy: strategy:
matrix: matrix:
bits: [32, 64] bits: [32, 64]
os: [windows-2019, windows-2022]
include: include:
- bits: 32 - bits: 32
arch: x86 arch: x86
@@ -72,34 +92,11 @@ jobs:
arch: x86_x64 arch: x86_x64
platform: x64 platform: x64
fail-fast: false fail-fast: false
runs-on: windows-2019 runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: Get zlib, libpng and bison - name: Install deps
run: | # TODO: use an array; remember to update the versions being downloaded, *and* the paths being extracted! (`Move-Item`) run: .github/scripts/get_win_deps.ps1
$wc = New-Object System.Net.WebClient
$wc.DownloadFile('https://www.zlib.net/zlib1212.zip', 'zlib.zip')
$hash = (Get-FileHash "zlib.zip" -Algorithm SHA256).Hash
if ($hash -ne '173e89893dcb8b4a150d7731cd72f0602f1d6b45e60e2a54efdf7f3fc3325fd7') {
Write-Host "zlib SHA256 mismatch! ($hash)"
exit 1
}
$wc.DownloadFile('https://download.sourceforge.net/libpng/lpng1637.zip', 'libpng.zip')
$hash = (Get-FileHash "libpng.zip" -Algorithm SHA256).Hash
if ($hash -ne '3b4b1cbd0bae6822f749d39b1ccadd6297f05e2b85a83dd2ce6ecd7d09eabdf2') {
Write-Host "libpng SHA256 mismatch! ($hash)"
exit 1
}
$wc.DownloadFile('https://github.com/lexxmark/winflexbison/releases/download/v2.5.24/win_flex_bison-2.5.24.zip', 'winflexbison.zip')
$hash = (Get-FileHash "winflexbison.zip" -Algorithm SHA256).Hash
if ($hash -ne '39c6086ce211d5415500acc5ed2d8939861ca1696aee48909c7f6daf5122b505') {
Write-Host "bison SHA256 mismatch! ($hash)"
}
Expand-Archive -DestinationPath . "zlib.zip"
Expand-Archive -DestinationPath . "libpng.zip"
Expand-Archive -DestinationPath install_dir "winflexbison.zip"
Move-Item zlib-1.2.12 zlib
Move-Item lpng1637 libpng
- uses: actions/cache@v3 - uses: actions/cache@v3
id: cache id: cache
with: with:
@@ -109,23 +106,25 @@ jobs:
key: ${{ matrix.arch }}-${{ hashFiles('zlib/**', 'libpng/**') }} key: ${{ matrix.arch }}-${{ hashFiles('zlib/**', 'libpng/**') }}
- name: Build zlib - name: Build zlib
run: | # BUILD_SHARED_LIBS causes the output DLL to be correctly called `zlib1.dll` run: | # BUILD_SHARED_LIBS causes the output DLL to be correctly called `zlib1.dll`
cmake -S zlib -B zbuild -A ${{ matrix.platform }} -DCMAKE_INSTALL_PREFIX=install_dir -DBUILD_SHARED_LIBS=ON cmake -S zlib -B zbuild -A ${{ matrix.platform }} -Wno-dev -DCMAKE_INSTALL_PREFIX=install_dir -DBUILD_SHARED_LIBS=ON
cmake --build zbuild --config Release -j cmake --build zbuild --config Release -j
if: steps.cache.outputs.cache-hit != 'true' if: steps.cache.outputs.cache-hit != 'true'
- name: Install zlib - name: Install zlib
run: | run: |
cmake --install zbuild cmake --install zbuild
- name: Build libpng - name: Build libpng
shell: bash
run: | run: |
cmake -S libpng -B pngbuild -A ${{ matrix.platform }} -DCMAKE_INSTALL_PREFIX=install_dir -DPNG_SHARED=ON -DPNG_STATIC=ON -DPNG_TESTS=OFF cmake -S libpng -B pngbuild -A ${{ matrix.platform }} -Wno-dev -DCMAKE_INSTALL_PREFIX=install_dir -DPNG_SHARED=ON -DPNG_STATIC=OFF -DPNG_TESTS=OFF -DPNG_BUILD_ZLIB=ON -DZLIB_INCLUDE_DIR="$PWD"/install_dir/include -DZLIB_LIBRARY="$PWD"/install_dir/lib/zlib.lib
cmake --build pngbuild --config Release -j cmake --build pngbuild --config Release -j
if: steps.cache.outputs.cache-hit != 'true' if: steps.cache.outputs.cache-hit != 'true'
- name: Install libpng - name: Install libpng
run: | run: |
cmake --install pngbuild cmake --install pngbuild
- name: Build Windows binaries - name: Build Windows binaries
shell: bash
run: | run: |
cmake -S . -B build -A ${{ matrix.platform }} -DCMAKE_INSTALL_PREFIX=install_dir -DCMAKE_BUILD_TYPE=Release cmake -S . -B build -A ${{ matrix.platform }} -DCMAKE_INSTALL_PREFIX=install_dir -DCMAKE_BUILD_TYPE=Release -DZLIB_LIBRARY="$PWD"/install_dir/lib/zlib.lib -DZLIB_INCLUDE_DIR="$PWD"/install_dir/include -DPNG_LIBRARY="$PWD"/install_dir/lib/libpng16.lib -DPNG_INCLUDE_DIR="$PWD"/install_dir/include
cmake --build build --config Release -j --verbose cmake --build build --config Release -j --verbose
cmake --install build --verbose --prefix install_dir cmake --install build --verbose --prefix install_dir
- name: Package binaries - name: Package binaries
@@ -138,6 +137,26 @@ jobs:
with: with:
name: rgbds-canary-win${{ matrix.bits }} name: rgbds-canary-win${{ matrix.bits }}
path: bins path: bins
- name: Compute test dependency cache params
id: test-deps-cache-params
shell: bash
run: |
paths=$(test/fetch-test-deps.sh --get-paths)
hash=$(test/fetch-test-deps.sh --get-hash)
tee -a <<<"paths=\"${paths//,/\\n}\"" $GITHUB_OUTPUT
tee -a <<<"hash=${hash%-}" $GITHUB_OUTPUT
- name: Check test dependency repositories cache
id: test-deps-cache
uses: actions/cache@v3
with:
path: ${{ fromJSON(steps.test-deps-cache-params.outputs.paths) }}
key: ${{ matrix.os }}-${{ matrix.bits }}-${{ steps.test-deps-cache-params.outputs.hash }}
- if: steps.test-deps-cache.outputs.cache-hit != 'true'
name: Fetch test dependency repositories
shell: bash
continue-on-error: true
run: |
test/fetch-test-deps.sh
- name: Test - name: Test
shell: bash shell: bash
run: | run: |
@@ -149,7 +168,6 @@ jobs:
strategy: strategy:
matrix: matrix:
bits: [32, 64] bits: [32, 64]
os: [ubuntu-18.04]
include: include:
- bits: 32 - bits: 32
arch: i686 arch: i686
@@ -158,36 +176,31 @@ jobs:
arch: x86-64 arch: x86-64
triplet: x86_64-w64-mingw32 triplet: x86_64-w64-mingw32
fail-fast: false fail-fast: false
runs-on: ${{ matrix.os }} runs-on: ubuntu-22.04
env: env:
DIST_DIR: win${{ matrix.bits }} DIST_DIR: win${{ matrix.bits }}
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: Install deps - name: Install deps
shell: bash shell: bash
run: | run: |
./.github/actions/install_deps.sh ${{ matrix.os }} ./.github/scripts/install_deps.sh ${{ matrix.os }}
- name: Install MinGW - name: Install MinGW
run: | run: | # dpkg-dev is apparently required for pkg-config for cross-building
sudo apt-get install {gcc,g++}-mingw-w64-${{ matrix.arch }} mingw-w64-tools libz-mingw-w64-dev sudo apt-get install {gcc,g++}-mingw-w64-${{ matrix.arch }}-win32 mingw-w64-tools libz-mingw-w64-dev dpkg-dev
- name: Install libpng dev headers for MinGW - name: Install libpng dev headers for MinGW
run: | run: |
sudo ./.github/actions/mingw-w64-libpng-dev.sh ${{ matrix.triplet }} sudo ./.github/scripts/mingw-w64-libpng-dev.sh ${{ matrix.triplet }}
- name: Cross-build Windows binaries - name: Cross-build Windows binaries
run: | run: |
make mingw${{ matrix.bits }} -j Q= make mingw${{ matrix.bits }} -j Q=
- name: Package binaries - name: Package binaries
run: | run: | # DLL dependencies can be figured out using e.g. Dependency Walker
mkdir bins mkdir bins
mv rgbasm bins/rgbasm.exe mv -v rgb{asm,link,fix,gfx}.exe bins/
mv rgblink bins/rgblink.exe cp -v /usr/${{ matrix.triplet }}/lib/zlib1.dll bins
mv rgbfix bins/rgbfix.exe cp -v /usr/${{ matrix.triplet }}/bin/libpng16-16.dll bins
mv rgbgfx bins/rgbgfx.exe [ "${{ matrix.bits }}" -ne 32 ] || cp -v /usr/lib/gcc/${{ matrix.triplet }}/10-win32/lib{gcc_s_dw2-1,ssp-0,stdc++-6}.dll bins
cp /usr/${{ matrix.triplet }}/lib/zlib1.dll bins
cp /usr/${{ matrix.triplet }}/bin/libpng16-16.dll bins
if [ ${{ matrix.bits }} -eq 32 ]; then cp /usr/lib/gcc/${{ matrix.triplet }}/7.3-win32/lib{gcc_s_sjlj-1,stdc++-6}.dll bins; fi
mv test/gfx/randtilegen{,.exe}
mv test/gfx/rgbgfx_test{,.exe}
- name: Upload Windows binaries - name: Upload Windows binaries
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
@@ -205,11 +218,12 @@ jobs:
needs: windows-xbuild needs: windows-xbuild
strategy: strategy:
matrix: matrix:
os: [windows-2019, windows-2022]
bits: [32, 64] bits: [32, 64]
fail-fast: false fail-fast: false
runs-on: windows-2019 runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: Retrieve binaries - name: Retrieve binaries
uses: actions/download-artifact@v3 uses: actions/download-artifact@v3
with: with:
@@ -225,6 +239,26 @@ jobs:
run: | run: |
cp bins/* . cp bins/* .
cp bins/*.dll test/gfx cp bins/*.dll test/gfx
- name: Compute test dependency cache params
id: test-deps-cache-params
shell: bash
run: |
paths=$(test/fetch-test-deps.sh --get-paths)
hash=$(test/fetch-test-deps.sh --get-hash)
tee -a <<<"paths=\"${paths//,/\\n}\"" $GITHUB_OUTPUT
tee -a <<<"hash=${hash%-}" $GITHUB_OUTPUT
- name: Check test dependency repositories cache
id: test-deps-cache
uses: actions/cache@v3
with:
path: ${{ fromJSON(steps.test-deps-cache-params.outputs.paths) }}
key: mingw-${{ matrix.bits }}-${{ steps.test-deps-cache-params.outputs.hash }}
- if: steps.test-deps-cache.outputs.cache-hit != 'true'
name: Fetch test dependency repositories
shell: bash
continue-on-error: true
run: |
test/fetch-test-deps.sh
- name: Run tests - name: Run tests
shell: bash shell: bash
run: | run: |

View File

@@ -17,30 +17,24 @@ on:
jobs: jobs:
build: build:
if: github.repository_owner == 'gbdev' if: github.repository_owner == 'gbdev'
runs-on: ubuntu-18.04 runs-on: ubuntu-22.04
steps: steps:
- name: Checkout rgbds@master - name: Checkout rgbds@master
uses: actions/checkout@v2 uses: actions/checkout@v3
with: with:
repository: gbdev/rgbds repository: gbdev/rgbds
ref: master ref: master
path: rgbds path: rgbds
- name: Checkout rgbds-www@master - name: Checkout rgbds-www@master
uses: actions/checkout@v2 uses: actions/checkout@v3
with: with:
repository: gbdev/rgbds-www repository: gbdev/rgbds-www
ref: master ref: master
path: rgbds-www path: rgbds-www
- name: Build and install mandoc + install groff - name: Install groff and mandoc
run: | run: |
sudo apt-get -qq update sudo apt-get -qq update
sudo apt-get install -yq groff zlib1g-dev sudo apt-get install -yq groff mandoc
wget 'http://mandoc.bsd.lv/snapshots/mandoc-1.14.6.tar.gz'
tar xf mandoc-1.14.6.tar.gz
cd mandoc-1.14.6
./configure
make
sudo make install
- name: Update pages - name: Update pages
working-directory: rgbds/man working-directory: rgbds/man
run: | run: |

View File

@@ -52,6 +52,7 @@ else()
-fsanitize=alignment -fsanitize=null -fsanitize=address) -fsanitize=alignment -fsanitize=null -fsanitize=address)
add_compile_options(${SAN_FLAGS}) add_compile_options(${SAN_FLAGS})
add_link_options(${SAN_FLAGS}) add_link_options(${SAN_FLAGS})
add_definitions(-D_GLIBCXX_ASSERTIONS)
# A non-zero optimization level is desired in debug mode, but allow overriding it nonetheless # A non-zero optimization level is desired in debug mode, but allow overriding it nonetheless
# TODO: this overrides anything previously set... that's a bit sloppy! # TODO: this overrides anything previously set... that's a bit sloppy!
set(CMAKE_C_FLAGS_DEBUG "-g -Og -fno-omit-frame-pointer -fno-optimize-sibling-calls" CACHE STRING "" FORCE) set(CMAKE_C_FLAGS_DEBUG "-g -Og -fno-omit-frame-pointer -fno-optimize-sibling-calls" CACHE STRING "" FORCE)

View File

@@ -9,6 +9,8 @@
.SUFFIXES: .SUFFIXES:
.SUFFIXES: .h .y .c .cpp .o .SUFFIXES: .h .y .c .cpp .o
.PHONY: all clean install checkcodebase checkpatch checkdiff develop debug mingw32 mingw64 wine-shim dist
# User-defined variables # User-defined variables
Q := @ Q := @
@@ -91,9 +93,11 @@ rgblink_obj := \
src/link/output.o \ src/link/output.o \
src/link/patch.o \ src/link/patch.o \
src/link/script.o \ src/link/script.o \
src/link/sdas_obj.o \
src/link/section.o \ src/link/section.o \
src/link/symbol.o \ src/link/symbol.o \
src/extern/getopt.o \ src/extern/getopt.o \
src/extern/utf8decoder.o \
src/error.o \ src/error.o \
src/hashmap.o \ src/hashmap.o \
src/linkdefs.o \ src/linkdefs.o \
@@ -126,7 +130,7 @@ rgbfix: ${rgbfix_obj}
$Q${CC} ${REALLDFLAGS} -o $@ ${rgbfix_obj} ${REALCFLAGS} src/version.c $Q${CC} ${REALLDFLAGS} -o $@ ${rgbfix_obj} ${REALCFLAGS} src/version.c
rgbgfx: ${rgbgfx_obj} rgbgfx: ${rgbgfx_obj}
$Q${CXX} ${REALLDFLAGS} ${PNGLDFLAGS} -o $@ ${rgbgfx_obj} ${REALCXXFLAGS} -x c++ src/version.c ${PNGLDLIBS} $Q${CXX} ${REALLDFLAGS} ${PNGLDFLAGS} -o $@ ${rgbgfx_obj} ${REALCXXFLAGS} ${PNGLDLIBS} -x c++ src/version.c
test/gfx/randtilegen: test/gfx/randtilegen.c test/gfx/randtilegen: test/gfx/randtilegen.c
$Q${CC} ${REALLDFLAGS} ${PNGLDFLAGS} -o $@ $^ ${REALCFLAGS} ${PNGCFLAGS} ${PNGLDLIBS} $Q${CC} ${REALLDFLAGS} ${PNGLDFLAGS} -o $@ $^ ${REALCFLAGS} ${PNGCFLAGS} ${PNGLDLIBS}
@@ -179,21 +183,11 @@ clean:
# Target used to install the binaries and man pages. # Target used to install the binaries and man pages.
install: all install: all
$Qmkdir -p ${DESTDIR}${bindir} $Qinstall -d ${DESTDIR}${bindir}/ ${DESTDIR}${mandir}/man1/ ${DESTDIR}${mandir}/man5/ ${DESTDIR}${mandir}/man7/
$Qinstall ${STRIP} -m ${BINMODE} rgbasm ${DESTDIR}${bindir}/rgbasm $Qinstall ${STRIP} -m ${BINMODE} rgbasm rgblink rgbfix rgbgfx ${DESTDIR}${bindir}/
$Qinstall ${STRIP} -m ${BINMODE} rgbfix ${DESTDIR}${bindir}/rgbfix $Qinstall -m ${MANMODE} man/rgbasm.1 man/rgblink.1 man/rgbfix.1 man/rgbgfx.1 ${DESTDIR}${mandir}/man1/
$Qinstall ${STRIP} -m ${BINMODE} rgblink ${DESTDIR}${bindir}/rgblink $Qinstall -m ${MANMODE} man/rgbds.5 man/rgbasm.5 man/rgblink.5 ${DESTDIR}${mandir}/man5/
$Qinstall ${STRIP} -m ${BINMODE} rgbgfx ${DESTDIR}${bindir}/rgbgfx $Qinstall -m ${MANMODE} man/rgbds.7 man/gbz80.7 ${DESTDIR}${mandir}/man7/
$Qmkdir -p ${DESTDIR}${mandir}/man1 ${DESTDIR}${mandir}/man5 ${DESTDIR}${mandir}/man7
$Qinstall -m ${MANMODE} man/rgbds.7 ${DESTDIR}${mandir}/man7/rgbds.7
$Qinstall -m ${MANMODE} man/gbz80.7 ${DESTDIR}${mandir}/man7/gbz80.7
$Qinstall -m ${MANMODE} man/rgbds.5 ${DESTDIR}${mandir}/man5/rgbds.5
$Qinstall -m ${MANMODE} man/rgbasm.1 ${DESTDIR}${mandir}/man1/rgbasm.1
$Qinstall -m ${MANMODE} man/rgbasm.5 ${DESTDIR}${mandir}/man5/rgbasm.5
$Qinstall -m ${MANMODE} man/rgbfix.1 ${DESTDIR}${mandir}/man1/rgbfix.1
$Qinstall -m ${MANMODE} man/rgblink.1 ${DESTDIR}${mandir}/man1/rgblink.1
$Qinstall -m ${MANMODE} man/rgblink.5 ${DESTDIR}${mandir}/man5/rgblink.5
$Qinstall -m ${MANMODE} man/rgbgfx.1 ${DESTDIR}${mandir}/man1/rgbgfx.1
# Target used to check the coding style of the whole codebase. # Target used to check the coding style of the whole codebase.
# `extern/` is excluded, as it contains external code that should not be patched # `extern/` is excluded, as it contains external code that should not be patched
@@ -240,6 +234,7 @@ develop:
-Wno-type-limits -Wno-tautological-constant-out-of-range-compare \ -Wno-type-limits -Wno-tautological-constant-out-of-range-compare \
-Wvla \ -Wvla \
-Wno-unknown-warning-option \ -Wno-unknown-warning-option \
-D_GLIBCXX_ASSERTIONS \
-fsanitize=shift -fsanitize=integer-divide-by-zero \ -fsanitize=shift -fsanitize=integer-divide-by-zero \
-fsanitize=unreachable -fsanitize=vla-bound \ -fsanitize=unreachable -fsanitize=vla-bound \
-fsanitize=signed-integer-overflow -fsanitize=bounds \ -fsanitize=signed-integer-overflow -fsanitize=bounds \
@@ -248,6 +243,13 @@ develop:
CFLAGS="-ggdb3 -Og -fno-omit-frame-pointer -fno-optimize-sibling-calls" \ CFLAGS="-ggdb3 -Og -fno-omit-frame-pointer -fno-optimize-sibling-calls" \
CXXFLAGS="-ggdb3 -Og -fno-omit-frame-pointer -fno-optimize-sibling-calls" CXXFLAGS="-ggdb3 -Og -fno-omit-frame-pointer -fno-optimize-sibling-calls"
# This target is used during development in order to more easily debug with gdb.
debug:
$Qenv ${MAKE} \
CFLAGS="-ggdb3 -Og -fno-omit-frame-pointer -fno-optimize-sibling-calls" \
CXXFLAGS="-ggdb3 -Og -fno-omit-frame-pointer -fno-optimize-sibling-calls"
# Targets for the project maintainer to easily create Windows exes. # Targets for the project maintainer to easily create Windows exes.
# This is not for Windows users! # This is not for Windows users!
# If you're building on Windows with Cygwin or Mingw, just follow the Unix # If you're building on Windows with Cygwin or Mingw, just follow the Unix
@@ -256,12 +258,12 @@ develop:
mingw32: mingw32:
$Q${MAKE} all test/gfx/randtilegen test/gfx/rgbgfx_test \ $Q${MAKE} all test/gfx/randtilegen test/gfx/rgbgfx_test \
CC=i686-w64-mingw32-gcc CXX=i686-w64-mingw32-g++ \ CC=i686-w64-mingw32-gcc CXX=i686-w64-mingw32-g++ \
BISON=bison PKG_CONFIG=i686-w64-mingw32-pkg-config -j BISON=bison PKG_CONFIG="PKG_CONFIG_SYSROOT_DIR=/usr/i686-w64-mingw32 pkg-config"
mingw64: mingw64:
$Q${MAKE} all test/gfx/randtilegen test/gfx/rgbgfx_test \ $Q${MAKE} all test/gfx/randtilegen test/gfx/rgbgfx_test \
CC=x86_64-w64-mingw32-gcc CXX=x86_64-w64-mingw32-g++ \ CC=x86_64-w64-mingw32-gcc CXX=x86_64-w64-mingw32-g++ \
BISON=bison PKG_CONFIG=x86_64-w64-mingw32-pkg-config -j BISON=bison PKG_CONFIG="PKG_CONFIG_SYSROOT_DIR=/usr/x86_64-w64-mingw32 pkg-config"
wine-shim: wine-shim:
$Qecho '#!/bin/bash' > rgbshim.sh $Qecho '#!/bin/bash' > rgbshim.sh

View File

@@ -1,4 +1,4 @@
#/usr/bin/env bash #!/usr/bin/env bash
# Known bugs: # Known bugs:
# - Newlines in file/directory names break this script # - Newlines in file/directory names break this script
@@ -11,7 +11,7 @@
# - Directories are not completed as such in "coalesced" short-opt arguments. For example, # - Directories are not completed as such in "coalesced" short-opt arguments. For example,
# `rgbasm -M d<tab>` can autocomplete to `rgbasm -M dir/` (no space), but # `rgbasm -M d<tab>` can autocomplete to `rgbasm -M dir/` (no space), but
# `rgbasm -Md<tab>` would autocomplete to `rgbasm -Mdir ` (trailing space) instead. # `rgbasm -Md<tab>` would autocomplete to `rgbasm -Mdir ` (trailing space) instead.
# This is because dircetory handling is performed by Readline, whom we can't tell about the short # This is because directory handling is performed by Readline, whom we can't tell about the short
# opt kerfuffle. The user can work around by separating the argument, as shown above. # opt kerfuffle. The user can work around by separating the argument, as shown above.
# (Also, there might be more possible bugs if `-Mdir` is actually a directory. Ugh.) # (Also, there might be more possible bugs if `-Mdir` is actually a directory. Ugh.)
@@ -20,25 +20,27 @@
# Thus, we don't need to do much to handle that form of argument passing: skip '=' after long opts. # Thus, we don't need to do much to handle that form of argument passing: skip '=' after long opts.
_rgbasm_completions() { _rgbasm_completions() {
COMPREPLY=()
# Format: "long_opt:state_after" # Format: "long_opt:state_after"
# Empty long opt = it doesn't exit # Empty long opt = it doesn't exit
# See the `state` variable below for info about `state_after` # See the `state` variable below for info about `state_after`
declare -A opts=( declare -A opts=(
[V]="version:normal" [V]="version:normal"
[E]="export-all:normal" [E]="export-all:normal"
[H]="nop-after-halt:normal"
[h]="halt-without-nop:normal" [h]="halt-without-nop:normal"
[L]="preserve-ld:normal" [L]="preserve-ld:normal"
[l]="auto-ldh:normal"
[v]="verbose:normal" [v]="verbose:normal"
[w]=":normal" [w]=":normal"
[b]="binary-digits:unk" [b]="binary-digits:unk"
[D]="define:unk" [D]="define:unk"
[g]="gfx-chars:unk" [g]="gfx-chars:unk"
[i]="include:dir" [I]="include:dir"
[M]="dependfile:glob-*.mk *.d" [M]="dependfile:glob-*.mk *.d"
[o]="output:glob-*.o" [o]="output:glob-*.o"
[P]="preinclude:glob-*.asm *.inc"
[p]="pad-value:unk" [p]="pad-value:unk"
[Q]="q-precision:unk"
[r]="recursion-depth:unk" [r]="recursion-depth:unk"
[W]="warning:warning" [W]="warning:warning"
) )
@@ -58,6 +60,18 @@ _rgbasm_completions() {
# "normal" is not returned, `optlen` will be set to the length (dash included) of the "option" # "normal" is not returned, `optlen` will be set to the length (dash included) of the "option"
# part of the argument. # part of the argument.
parse_short_opt() { parse_short_opt() {
# These options act like a long option (= takes up the entire word), but only use a single dash
# So, they need some special handling
if [[ "$1" = "-M"[GP] ]]; then
state=normal
optlen=${#1}
return;
elif [[ "$1" = "-M"[QT] ]]; then
state='glob-*.d *.mk *.o'
optlen=${#1}
return;
fi
for (( i = 1; i < "${#1}"; i++ )); do for (( i = 1; i < "${#1}"; i++ )); do
# If the option is not known, assume it doesn't take an argument # If the option is not known, assume it doesn't take an argument
local opt="${opts["${1:$i:1}"]:-":normal"}" local opt="${opts["${1:$i:1}"]:-":normal"}"
@@ -71,7 +85,7 @@ _rgbasm_completions() {
optlen=0 optlen=0
} }
for (( i = 1; i < $COMP_CWORD; i++ )); do for (( i = 1; i < COMP_CWORD; i++ )); do
local word="${COMP_WORDS[$i]}" local word="${COMP_WORDS[$i]}"
# If currently processing an argument, skip this word # If currently processing an argument, skip this word
@@ -87,7 +101,7 @@ _rgbasm_completions() {
fi fi
# Check if it's a long option # Check if it's a long option
if [[ "${word:0:2}" = '--' ]]; then if [[ "$word" = '--'* ]]; then
# If the option is unknown, assume it takes no arguments: keep the state at "normal" # If the option is unknown, assume it takes no arguments: keep the state at "normal"
for long_opt in "${opts[@]}"; do for long_opt in "${opts[@]}"; do
if [[ "$word" = "--${long_opt%%:*}" ]]; then if [[ "$word" = "--${long_opt%%:*}" ]]; then
@@ -103,15 +117,7 @@ _rgbasm_completions() {
fi fi
done done
# Check if it's a short option # Check if it's a short option
elif [[ "${word:0:1}" = '-' ]]; then elif [[ "$word" = '-'* ]]; then
# The `-M?` ones are a mix of short and long, augh
# 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" parse_short_opt "$word"
# The last option takes an argument... # The last option takes an argument...
if [[ "$state" != 'normal' ]]; then if [[ "$state" != 'normal' ]]; then
@@ -124,34 +130,33 @@ _rgbasm_completions() {
fi fi
fi fi
fi fi
fi
done done
# Parse current word # Parse current word
# Careful that it might look like an option, so use `--` aggressively! # Careful that it might look like an option, so use `--` aggressively!
local cur_word="${COMP_WORDS[$COMP_CWORD]}" local cur_word="${COMP_WORDS[$i]}"
# Process options, as short ones may change the state # Process options, as short ones may change the state
if $opt_ena && [[ "$state" = 'normal' && "${cur_word:0:1}" = '-' ]]; then if $opt_ena && [[ "$state" = 'normal' && "$cur_word" = '-'* ]]; then
# We might want to complete to an option or an arg to that option # We might want to complete to an option or an arg to that option
# Parse the option word to check # Parse the option word to check
# There's no whitespace in the option names, so we can ride a little dirty... # There's no whitespace in the option names, so we can ride a little dirty...
# Is this a long option? # Is this a long option?
if [[ "${cur_word:1:1}" = '-' ]]; then if [[ "$cur_word" = '--'* ]]; then
# It is, try to complete one # It is, try to complete one
COMPREPLY+=( $(compgen -W "${opts[*]%%:*}" -P '--' -- "${cur_word#--}") ) mapfile -t COMPREPLY < <(compgen -W "${opts[*]%%:*}" -P '--' -- "${cur_word#--}")
return 0
elif [[ "$cur_word" = '-M'[GPQT] ]]; then
# These options act like long opts with no arguments, so return them and exactly them
COMPREPLY=( "$cur_word" )
return 0 return 0
else else
# Short options may be grouped, parse them to determine what to complete # Short options may be grouped, parse them to determine what to complete
# The `-M?` ones may not be followed by anything
if [[ "$1" != "-M"[GPTQ] ]]; then
parse_short_opt "$cur_word" parse_short_opt "$cur_word"
# We got some short options that behave like long ones
COMPREPLY+=( $(compgen -W '-MG -MP -MT -MQ' -- "$cur_word") )
if [[ "$state" = 'normal' ]]; then if [[ "$state" = 'normal' ]]; then
COMPREPLY+=( $(compgen -W "${!opts[*]}" -P "$cur_word" '') ) mapfile -t COMPREPLY < <(compgen -W "${!opts[*]}" -P "$cur_word" ''; compgen -W '-MG -MP -MQ -MT' "$cur_word")
return 0 return 0
elif [[ "$optlen" = "${#cur_word}" && "$state" != "warning" ]]; then elif [[ "$optlen" = "${#cur_word}" && "$state" != "warning" ]]; then
# This short option group only awaits its argument! # This short option group only awaits its argument!
@@ -159,18 +164,18 @@ _rgbasm_completions() {
# so that the next completion request switches to the argument # so that the next completion request switches to the argument
# An exception is made for warnings, since it's idiomatic to stick them to the # An exception is made for warnings, since it's idiomatic to stick them to the
# `-W`, and it doesn't break anything. # `-W`, and it doesn't break anything.
COMPREPLY+=( "$cur_word" ) COMPREPLY=( "$cur_word" )
return 0 return 0
fi fi
fi fi
fi fi
fi
COMPREPLY=()
case "$state" in case "$state" in
unk) # Return with no replies: no idea what to complete! unk) # Return with no replies: no idea what to complete!
;; ;;
warning) warning)
COMPREPLY+=( $(compgen -W " mapfile -t COMPREPLY < <(compgen -W "
assert assert
backwards-for backwards-for
builtin-args builtin-args
@@ -188,11 +193,12 @@ _rgbasm_completions() {
shift shift
shift-amount shift-amount
truncation truncation
unmapped-char
user user
all all
extra extra
everything everything
error" -P "${cur_word:0:$optlen}" -- "${cur_word:$optlen}") ) error" -P "${cur_word:0:$optlen}" -- "${cur_word:$optlen}")
;; ;;
normal) # Acts like a glob... normal) # Acts like a glob...
state="glob-*.asm *.inc *.sm83" state="glob-*.asm *.inc *.sm83"
@@ -209,6 +215,10 @@ _rgbasm_completions() {
done < <(compgen -A directory -- "${cur_word:$optlen}") done < <(compgen -A directory -- "${cur_word:$optlen}")
compopt -o filenames compopt -o filenames
;; ;;
*)
echo >&2 "Internal completion error: invalid state \"$state\", please report this bug"
return 1
;;
esac esac
} }

View File

@@ -1,10 +1,8 @@
#/usr/bin/env bash #!/usr/bin/env bash
# Same notes as RGBASM # Same notes as RGBASM
_rgbfix_completions() { _rgbfix_completions() {
COMPREPLY=()
# Format: "long_opt:state_after" # Format: "long_opt:state_after"
# Empty long opt = it doesn't exit # Empty long opt = it doesn't exit
# See the `state` variable below for info about `state_after` # See the `state` variable below for info about `state_after`
@@ -54,7 +52,7 @@ _rgbfix_completions() {
optlen=0 optlen=0
} }
for (( i = 1; i < $COMP_CWORD; i++ )); do for (( i = 1; i < COMP_CWORD; i++ )); do
local word="${COMP_WORDS[$i]}" local word="${COMP_WORDS[$i]}"
# If currently processing an argument, skip this word # If currently processing an argument, skip this word
@@ -70,7 +68,7 @@ _rgbfix_completions() {
fi fi
# Check if it's a long option # Check if it's a long option
if [[ "${word:0:2}" = '--' ]]; then if [[ "$word" = '--'* ]]; then
# If the option is unknown, assume it takes no arguments: keep the state at "normal" # If the option is unknown, assume it takes no arguments: keep the state at "normal"
for long_opt in "${opts[@]}"; do for long_opt in "${opts[@]}"; do
if [[ "$word" = "--${long_opt%%:*}" ]]; then if [[ "$word" = "--${long_opt%%:*}" ]]; then
@@ -86,7 +84,7 @@ _rgbfix_completions() {
fi fi
done done
# Check if it's a short option # Check if it's a short option
elif [[ "${word:0:1}" = '-' ]]; then elif [[ "$word" = '-'* ]]; then
parse_short_opt "$word" parse_short_opt "$word"
# The last option takes an argument... # The last option takes an argument...
if [[ "$state" != 'normal' ]]; then if [[ "$state" != 'normal' ]]; then
@@ -103,25 +101,25 @@ _rgbfix_completions() {
# Parse current word # Parse current word
# Careful that it might look like an option, so use `--` aggressively! # Careful that it might look like an option, so use `--` aggressively!
local cur_word="${COMP_WORDS[$COMP_CWORD]}" local cur_word="${COMP_WORDS[$i]}"
# Process options, as short ones may change the state # Process options, as short ones may change the state
if $opt_ena && [[ "$state" = 'normal' && "${cur_word:0:1}" = '-' ]]; then if $opt_ena && [[ "$state" = 'normal' && "$cur_word" = '-'* ]]; then
# We might want to complete to an option or an arg to that option # We might want to complete to an option or an arg to that option
# Parse the option word to check # Parse the option word to check
# There's no whitespace in the option names, so we can ride a little dirty... # There's no whitespace in the option names, so we can ride a little dirty...
# Is this a long option? # Is this a long option?
if [[ "${cur_word:1:1}" = '-' ]]; then if [[ "$cur_word" = '--'* ]]; then
# It is, try to complete one # It is, try to complete one
COMPREPLY+=( $(compgen -W "${opts[*]%%:*}" -P '--' -- "${cur_word#--}") ) mapfile -t COMPREPLY < <(compgen -W "${opts[*]%%:*}" -P '--' -- "${cur_word#--}")
return 0 return 0
else else
# Short options may be grouped, parse them to determine what to complete # Short options may be grouped, parse them to determine what to complete
parse_short_opt "$cur_word" parse_short_opt "$cur_word"
if [[ "$state" = 'normal' ]]; then if [[ "$state" = 'normal' ]]; then
COMPREPLY+=( $(compgen -W "${!opts[*]}" -P "$cur_word" '') ) mapfile -t COMPREPLY < <(compgen -W "${!opts[*]}" -P "$cur_word" '')
return 0 return 0
elif [[ "$optlen" = "${#cur_word}" && "$state" != "warning" ]]; then elif [[ "$optlen" = "${#cur_word}" && "$state" != "warning" ]]; then
# This short option group only awaits its argument! # This short option group only awaits its argument!
@@ -129,22 +127,25 @@ _rgbfix_completions() {
# so that the next completion request switches to the argument # so that the next completion request switches to the argument
# An exception is made for warnings, since it's idiomatic to stick them to the # An exception is made for warnings, since it's idiomatic to stick them to the
# `-W`, and it doesn't break anything. # `-W`, and it doesn't break anything.
COMPREPLY+=( "$cur_word" ) COMPREPLY=( "$cur_word" )
return 0 return 0
fi fi
fi fi
fi fi
COMPREPLY=()
case "$state" in case "$state" in
unk) # Return with no replies: no idea what to complete! unk) # Return with no replies: no idea what to complete!
;; ;;
fix-spec) fix-spec)
COMPREPLY+=( "${cur_word}"{l,h,g,L,H,G} ) COMPREPLY=( "${cur_word}"{l,h,g,L,H,G} )
;; ;;
mbc) mbc)
local cur_arg="${cur_word:$optlen}" local cur_arg="${cur_word:$optlen}"
cur_arg="${cur_arg@U}" cur_arg="${cur_arg@U}"
COMPREPLY=( $(compgen -W " compopt -o nosort # Keep `help` first in the list, mainly
mapfile -t COMPREPLY < <(compgen -W "help" -P "${cur_word:0:$optlen}" -- "${cur_word:$optlen}")
mapfile -t COMPREPLY -O ${#COMPREPLY} < <(compgen -W "
ROM_ONLY ROM_ONLY
MBC1{,+RAM,+RAM+BATTERY} MBC1{,+RAM,+RAM+BATTERY}
MBC2{,+BATTERY} MBC2{,+BATTERY}
@@ -157,8 +158,7 @@ _rgbfix_completions() {
BANDAI_TAMA5 BANDAI_TAMA5
HUC3 HUC3
HUC1+RAM+BATTERY HUC1+RAM+BATTERY
TPP1_1.0{,+BATTERY}{,+RTC}{,+RUMBLE,+MULTIRUMBLE}" -P "${cur_word:0:$optlen}" -- "`tr 'a-z ' 'A-Z_' <<<"${cur_word/ /_}"`") ) TPP1_1.0{,+BATTERY}{,+RTC}{,+RUMBLE,+MULTIRUMBLE}" -P "${cur_word:0:$optlen}" -- "${cur_word/ /_}")
COMPREPLY+=( $(compgen -W "help" -P "${cur_word:0:$optlen}" -- "${cur_word:$optlen}") )
;; ;;
normal) # Acts like a glob... normal) # Acts like a glob...
state="glob-*.gb *.gbc *.sgb" state="glob-*.gb *.gbc *.sgb"
@@ -175,6 +175,10 @@ _rgbfix_completions() {
done < <(compgen -A directory -- "${cur_word:$optlen}") done < <(compgen -A directory -- "${cur_word:$optlen}")
compopt -o filenames compopt -o filenames
;; ;;
*)
echo >&2 "Internal completion error: invalid state \"$state\", please report this bug"
return 1
;;
esac esac
} }

View File

@@ -1,10 +1,8 @@
#/usr/bin/env bash #!/usr/bin/env bash
# Same notes as RGBASM # Same notes as RGBASM
_rgbgfx_completions() { _rgbgfx_completions() {
COMPREPLY=()
# Format: "long_opt:state_after" # Format: "long_opt:state_after"
# Empty long opt = it doesn't exit # Empty long opt = it doesn't exit
# See the `state` variable below for info about `state_after` # See the `state` variable below for info about `state_after`
@@ -15,21 +13,21 @@ _rgbgfx_completions() {
[u]="unique-tiles:normal" [u]="unique-tiles:normal"
[v]="verbose:normal" [v]="verbose:normal"
[Z]="columns:normal" [Z]="columns:normal"
[a]="attr-map:*.attrmap" [a]="attr-map:glob-*.attrmap"
[A]="output-attr-map:normal" [A]="output-attr-map:normal"
[b]="base-tiles:unk" [b]="base-tiles:unk"
[d]="depth:unk" [d]="depth:unk"
[L]="slice:unk" [L]="slice:unk"
[N]="nb-tiles:unk" [N]="nb-tiles:unk"
[n]="nb-palettes:unk" [n]="nb-palettes:unk"
[o]="output:glob *.2bpp" [o]="output:glob-*.2bpp"
[p]="palette:glob *.pal" [p]="palette:glob-*.pal"
[P]="output-palette:normal" [P]="output-palette:normal"
[q]="palette-map:glob *.palmap" [q]="palette-map:glob-*.palmap"
[Q]="output-palette-map:normal" [Q]="output-palette-map:normal"
[r]="reverse:unk" [r]="reverse:unk"
[s]="palette-size:unk" [s]="palette-size:unk"
[t]="tilemap:glob *.tilemap" [t]="tilemap:glob-*.tilemap"
[T]="output-tilemap:normal" [T]="output-tilemap:normal"
[x]="trim-end:unk" [x]="trim-end:unk"
) )
@@ -62,7 +60,7 @@ _rgbgfx_completions() {
optlen=0 optlen=0
} }
for (( i = 1; i < $COMP_CWORD; i++ )); do for (( i = 1; i < COMP_CWORD; i++ )); do
local word="${COMP_WORDS[$i]}" local word="${COMP_WORDS[$i]}"
# If currently processing an argument, skip this word # If currently processing an argument, skip this word
@@ -78,7 +76,7 @@ _rgbgfx_completions() {
fi fi
# Check if it's a long option # Check if it's a long option
if [[ "${word:0:2}" = '--' ]]; then if [[ "$word" = '--'* ]]; then
# If the option is unknown, assume it takes no arguments: keep the state at "normal" # If the option is unknown, assume it takes no arguments: keep the state at "normal"
for long_opt in "${opts[@]}"; do for long_opt in "${opts[@]}"; do
if [[ "$word" = "--${long_opt%%:*}" ]]; then if [[ "$word" = "--${long_opt%%:*}" ]]; then
@@ -94,7 +92,7 @@ _rgbgfx_completions() {
fi fi
done done
# Check if it's a short option # Check if it's a short option
elif [[ "${word:0:1}" = '-' ]]; then elif [[ "$word" = '-'* ]]; then
parse_short_opt "$word" parse_short_opt "$word"
# The last option takes an argument... # The last option takes an argument...
if [[ "$state" != 'normal' ]]; then if [[ "$state" != 'normal' ]]; then
@@ -111,25 +109,25 @@ _rgbgfx_completions() {
# Parse current word # Parse current word
# Careful that it might look like an option, so use `--` aggressively! # Careful that it might look like an option, so use `--` aggressively!
local cur_word="${COMP_WORDS[$COMP_CWORD]}" local cur_word="${COMP_WORDS[$i]}"
# Process options, as short ones may change the state # Process options, as short ones may change the state
if $opt_ena && [[ "$state" = 'normal' && "${cur_word:0:1}" = '-' ]]; then if $opt_ena && [[ "$state" = 'normal' && "$cur_word" = '-'* ]]; then
# We might want to complete to an option or an arg to that option # We might want to complete to an option or an arg to that option
# Parse the option word to check # Parse the option word to check
# There's no whitespace in the option names, so we can ride a little dirty... # There's no whitespace in the option names, so we can ride a little dirty...
# Is this a long option? # Is this a long option?
if [[ "${cur_word:1:1}" = '-' ]]; then if [[ "$cur_word" = '--'* ]]; then
# It is, try to complete one # It is, try to complete one
COMPREPLY+=( $(compgen -W "${opts[*]%%:*}" -P '--' -- "${cur_word#--}") ) mapfile -t COMPREPLY < <(compgen -W "${opts[*]%%:*}" -P '--' -- "${cur_word#--}")
return 0 return 0
else else
# Short options may be grouped, parse them to determine what to complete # Short options may be grouped, parse them to determine what to complete
parse_short_opt "$cur_word" parse_short_opt "$cur_word"
if [[ "$state" = 'normal' ]]; then if [[ "$state" = 'normal' ]]; then
COMPREPLY+=( $(compgen -W "${!opts[*]}" -P "$cur_word" '') ) mapfile -t COMPREPLY < <(compgen -W "${!opts[*]}" -P "$cur_word" '')
return 0 return 0
elif [[ "$optlen" = "${#cur_word}" && "$state" != "warning" ]]; then elif [[ "$optlen" = "${#cur_word}" && "$state" != "warning" ]]; then
# This short option group only awaits its argument! # This short option group only awaits its argument!
@@ -137,12 +135,13 @@ _rgbgfx_completions() {
# so that the next completion request switches to the argument # so that the next completion request switches to the argument
# An exception is made for warnings, since it's idiomatic to stick them to the # An exception is made for warnings, since it's idiomatic to stick them to the
# `-W`, and it doesn't break anything. # `-W`, and it doesn't break anything.
COMPREPLY+=( "$cur_word" ) COMPREPLY=( "$cur_word" )
return 0 return 0
fi fi
fi fi
fi fi
COMPREPLY=()
case "$state" in case "$state" in
unk) # Return with no replies: no idea what to complete! unk) # Return with no replies: no idea what to complete!
;; ;;
@@ -161,6 +160,10 @@ _rgbgfx_completions() {
done < <(compgen -A directory -- "${cur_word:$optlen}") done < <(compgen -A directory -- "${cur_word:$optlen}")
compopt -o filenames compopt -o filenames
;; ;;
*)
echo >&2 "Internal completion error: invalid state \"$state\", please report this bug"
return 1
;;
esac esac
} }

View File

@@ -1,10 +1,8 @@
#/usr/bin/env bash #!/usr/bin/env bash
# Same notes as RGBASM # Same notes as RGBASM
_rgblink_completions() { _rgblink_completions() {
COMPREPLY=()
# Format: "long_opt:state_after" # Format: "long_opt:state_after"
# Empty long opt = it doesn't exit # Empty long opt = it doesn't exit
# See the `state` variable below for info about `state_after` # See the `state` variable below for info about `state_after`
@@ -16,6 +14,7 @@ _rgblink_completions() {
[w]="wramx:normal" [w]="wramx:normal"
[x]="nopad:normal" [x]="nopad:normal"
[l]="linkerscript:glob-*" [l]="linkerscript:glob-*"
[M]="no-sym-in-map:normal"
[m]="map:glob-*.map" [m]="map:glob-*.map"
[n]="sym:glob-*.sym" [n]="sym:glob-*.sym"
[O]="overlay:glob-*.gb *.gbc *.sgb" [O]="overlay:glob-*.gb *.gbc *.sgb"
@@ -52,7 +51,7 @@ _rgblink_completions() {
optlen=0 optlen=0
} }
for (( i = 1; i < $COMP_CWORD; i++ )); do for (( i = 1; i < COMP_CWORD; i++ )); do
local word="${COMP_WORDS[$i]}" local word="${COMP_WORDS[$i]}"
# If currently processing an argument, skip this word # If currently processing an argument, skip this word
@@ -68,7 +67,7 @@ _rgblink_completions() {
fi fi
# Check if it's a long option # Check if it's a long option
if [[ "${word:0:2}" = '--' ]]; then if [[ "$word" = '--'* ]]; then
# If the option is unknown, assume it takes no arguments: keep the state at "normal" # If the option is unknown, assume it takes no arguments: keep the state at "normal"
for long_opt in "${opts[@]}"; do for long_opt in "${opts[@]}"; do
if [[ "$word" = "--${long_opt%%:*}" ]]; then if [[ "$word" = "--${long_opt%%:*}" ]]; then
@@ -84,7 +83,7 @@ _rgblink_completions() {
fi fi
done done
# Check if it's a short option # Check if it's a short option
elif [[ "${word:0:1}" = '-' ]]; then elif [[ "$word" = '-'* ]]; then
parse_short_opt "$word" parse_short_opt "$word"
# The last option takes an argument... # The last option takes an argument...
if [[ "$state" != 'normal' ]]; then if [[ "$state" != 'normal' ]]; then
@@ -101,25 +100,25 @@ _rgblink_completions() {
# Parse current word # Parse current word
# Careful that it might look like an option, so use `--` aggressively! # Careful that it might look like an option, so use `--` aggressively!
local cur_word="${COMP_WORDS[$COMP_CWORD]}" local cur_word="${COMP_WORDS[$i]}"
# Process options, as short ones may change the state # Process options, as short ones may change the state
if $opt_ena && [[ "$state" = 'normal' && "${cur_word:0:1}" = '-' ]]; then if $opt_ena && [[ "$state" = 'normal' && "$cur_word" = '-'* ]]; then
# We might want to complete to an option or an arg to that option # We might want to complete to an option or an arg to that option
# Parse the option word to check # Parse the option word to check
# There's no whitespace in the option names, so we can ride a little dirty... # There's no whitespace in the option names, so we can ride a little dirty...
# Is this a long option? # Is this a long option?
if [[ "${cur_word:1:1}" = '-' ]]; then if [[ "$cur_word" = '--'* ]]; then
# It is, try to complete one # It is, try to complete one
COMPREPLY+=( $(compgen -W "${opts[*]%%:*}" -P '--' -- "${cur_word#--}") ) mapfile -t COMPREPLY < <(compgen -W "${opts[*]%%:*}" -P '--' -- "${cur_word#--}")
return 0 return 0
else else
# Short options may be grouped, parse them to determine what to complete # Short options may be grouped, parse them to determine what to complete
parse_short_opt "$cur_word" parse_short_opt "$cur_word"
if [[ "$state" = 'normal' ]]; then if [[ "$state" = 'normal' ]]; then
COMPREPLY+=( $(compgen -W "${!opts[*]}" -P "$cur_word" '') ) mapfile -t COMPREPLY < <(compgen -W "${!opts[*]}" -P "$cur_word" '')
return 0 return 0
elif [[ "$optlen" = "${#cur_word}" && "$state" != "warning" ]]; then elif [[ "$optlen" = "${#cur_word}" && "$state" != "warning" ]]; then
# This short option group only awaits its argument! # This short option group only awaits its argument!
@@ -127,12 +126,13 @@ _rgblink_completions() {
# so that the next completion request switches to the argument # so that the next completion request switches to the argument
# An exception is made for warnings, since it's idiomatic to stick them to the # An exception is made for warnings, since it's idiomatic to stick them to the
# `-W`, and it doesn't break anything. # `-W`, and it doesn't break anything.
COMPREPLY+=( "$cur_word" ) COMPREPLY=( "$cur_word" )
return 0 return 0
fi fi
fi fi
fi fi
COMPREPLY=()
case "$state" in case "$state" in
unk) # Return with no replies: no idea what to complete! unk) # Return with no replies: no idea what to complete!
;; ;;
@@ -151,6 +151,10 @@ _rgblink_completions() {
done < <(compgen -A directory -- "${cur_word:$optlen}") done < <(compgen -A directory -- "${cur_word:$optlen}")
compopt -o filenames compopt -o filenames
;; ;;
*)
echo >&2 "Internal completion error: invalid state \"$state\", please report this bug"
return 1
;;
esac esac
} }

View File

@@ -25,10 +25,11 @@ _rgbasm_warnings() {
'shift:Warn when shifting negative values' 'shift:Warn when shifting negative values'
'shift-amount:Warn when a shift'\''s operand it negative or \> 32' 'shift-amount:Warn when a shift'\''s operand it negative or \> 32'
'truncation:Warn when implicit truncation loses bits' 'truncation:Warn when implicit truncation loses bits'
'unmapped-char:Warn on unmapped character'
'user:Warn when executing the WARN built-in' 'user:Warn when executing the WARN built-in'
) )
# TODO: handle `no-` and `error=` somehow? # TODO: handle `no-` and `error=` somehow?
# TODO: handle `=0|1|2` levels for `numeric-string` and `truncation`? # TODO: handle `=0|1|2` levels for `numeric-string`, `truncation`, and `unmapped-char`?
_describe warning warnings _describe warning warnings
} }
@@ -37,22 +38,26 @@ local args=(
'(- : * options)'{-V,--version}'[Print version number]' '(- : * options)'{-V,--version}'[Print version number]'
'(-E --export-all)'{-E,--export-all}'[Export all symbols]' '(-E --export-all)'{-E,--export-all}'[Export all symbols]'
'(-h --halt-without-nop)'{-h,--halt-without-nop}'[Avoid outputting a `nop` after `halt`]' '(-H --nop-after-halt)'{-H,--nop-after-halt}'[Output a `nop` after `halt`]'
'(-L ---preserve-ld)'{-L,--preserve-ld}'[Prevent auto-optimizing `ld` into `ldh`]' '(-h --halt-without-nop)'{-h,--halt-without-nop}'[Prevent outputting a `nop` after `halt`]'
'(-L --preserve-ld)'{-L,--preserve-ld}'[Prevent optimizing `ld` into `ldh`]'
'(-l --auto-ldh)'{-l,--auto-ldh}'[Optimize `ld` into `ldh`]'
'(-v --verbose)'{-v,--verbose}'[Print additional messages regarding progression]' '(-v --verbose)'{-v,--verbose}'[Print additional messages regarding progression]'
-w'[Disable all warnings]' -w'[Disable all warnings]'
'(-b --binary-digits)'{-b,--binary-digits}'+[Change chars for binary constants]:digit spec:' '(-b --binary-digits)'{-b,--binary-digits}'+[Change chars for binary constants]:digit spec:'
'*'{-D,--define}'+[Define a string symbol]:name + value (default 1):' '*'{-D,--define}'+[Define a string symbol]:name + value (default 1):'
'(-g --gfx-chars)'{-g,--gfx-chars}'+[Change chars for gfx constants]:chars spec:' '(-g --gfx-chars)'{-g,--gfx-chars}'+[Change chars for gfx constants]:chars spec:'
'(-i --include)'{-i,--include}'+[Add an include directory]:include path:_files -/' '(-I --include)'{-I,--include}'+[Add an include directory]:include path:_files -/'
'(-M --dependfile)'{-M,--dependfile}"+[List deps in make format]:output file:_files -g '*.{d,mk}'" '(-M --dependfile)'{-M,--dependfile}"+[List deps in make format]:output file:_files -g '*.{d,mk}'"
-MG'[Assume missing files should be generated]' -MG'[Assume missing files should be generated]'
-MP'[Add phony targets to all deps]' -MP'[Add phony targets to all deps]'
'*'-MT"+[Add a target to the rules]:target:_files -g '*.{d,mk,o}'" '*'-MT"+[Add a target to the rules]:target:_files -g '*.{d,mk,o}'"
'*'-MQ"+[Add a target to the rules]:target:_files -g '*.{d,mk,o}'" '*'-MQ"+[Add a target to the rules]:target:_files -g '*.{d,mk,o}'"
'(-o --output)'{-o,--output}'+[Output file]:output file:_files' '(-o --output)'{-o,--output}'+[Output file]:output file:_files'
'(-P --preinclude)'{-P,--preinclude}"+[Pre-include a file]:include file:_files -g '*.{asm,inc}'"
'(-p --pad-value)'{-p,--pad-value}'+[Set padding byte]:padding byte:' '(-p --pad-value)'{-p,--pad-value}'+[Set padding byte]:padding byte:'
'(-Q --q-precision)'{-Q,--q-precision}'+[Set fixed-point precision]:precision:'
'(-r --recursion-depth)'{-r,--recursion-depth}'+[Set maximum recursion depth]:depth:' '(-r --recursion-depth)'{-r,--recursion-depth}'+[Set maximum recursion depth]:depth:'
'(-W --warning)'{-W,--warning}'+[Toggle warning flags]:warning flag:_rgbasm_warnings' '(-W --warning)'{-W,--warning}'+[Toggle warning flags]:warning flag:_rgbasm_warnings'

View File

@@ -11,6 +11,7 @@ local args=(
'(-x --nopad)'{-x,--nopad}'[Disable padding the end of the final file]' '(-x --nopad)'{-x,--nopad}'[Disable padding the end of the final file]'
'(-l --linkerscript)'{-l,--linkerscript}"+[Use a linker script]:linker script:_files -g '*.link'" '(-l --linkerscript)'{-l,--linkerscript}"+[Use a linker script]:linker script:_files -g '*.link'"
'(-M --no-sym-in-map)'{-M,--no-sym-in-map}'[Do not output symbol names in map file]'
'(-m --map)'{-m,--map}"+[Produce a map file]:map file:_files -g '*.map'" '(-m --map)'{-m,--map}"+[Produce a map file]:map file:_files -g '*.map'"
'(-n --sym)'(-n,--sym)"+[Produce a symbol file]:sym file:_files -g '*.sym'" '(-n --sym)'(-n,--sym)"+[Produce a symbol file]:sym file:_files -g '*.sym'"
'(-O --overlay)'{-O,--overlay}'+[Overlay sections over on top of bin file]:base overlay:_files' '(-O --overlay)'{-O,--overlay}'+[Overlay sections over on top of bin file]:base overlay:_files'

View File

@@ -11,6 +11,8 @@
#include <stdint.h> #include <stdint.h>
#define DEFAULT_CHARMAP_NAME "main"
struct Charmap *charmap_New(char const *name, char const *baseName); struct Charmap *charmap_New(char const *name, char const *baseName);
void charmap_Delete(struct Charmap *charmap); void charmap_Delete(struct Charmap *charmap);
void charmap_Set(char const *name); void charmap_Set(char const *name);
@@ -20,4 +22,4 @@ void charmap_Add(char *mapping, uint8_t value);
size_t charmap_Convert(char const *input, uint8_t *output); size_t charmap_Convert(char const *input, uint8_t *output);
size_t charmap_ConvertNext(char const **input, uint8_t **output); size_t charmap_ConvertNext(char const **input, uint8_t **output);
#endif /* RGBDS_ASM_CHARMAP_H */ #endif // RGBDS_ASM_CHARMAP_H

View File

@@ -11,20 +11,24 @@
#include <stdint.h> #include <stdint.h>
void fix_Print(int32_t i); extern uint8_t fixPrecision;
int32_t fix_Sin(int32_t i);
int32_t fix_Cos(int32_t i);
int32_t fix_Tan(int32_t i);
int32_t fix_ASin(int32_t i);
int32_t fix_ACos(int32_t i);
int32_t fix_ATan(int32_t i);
int32_t fix_ATan2(int32_t i, int32_t j);
int32_t fix_Mul(int32_t i, int32_t j);
int32_t fix_Div(int32_t i, int32_t j);
int32_t fix_Pow(int32_t i, int32_t j);
int32_t fix_Log(int32_t i, int32_t j);
int32_t fix_Round(int32_t i);
int32_t fix_Ceil(int32_t i);
int32_t fix_Floor(int32_t i);
#endif /* RGBDS_ASM_FIXPOINT_H */ uint8_t fix_Precision(void);
double fix_PrecisionFactor(void);
int32_t fix_Sin(int32_t i, int32_t q);
int32_t fix_Cos(int32_t i, int32_t q);
int32_t fix_Tan(int32_t i, int32_t q);
int32_t fix_ASin(int32_t i, int32_t q);
int32_t fix_ACos(int32_t i, int32_t q);
int32_t fix_ATan(int32_t i, int32_t q);
int32_t fix_ATan2(int32_t i, int32_t j, int32_t q);
int32_t fix_Mul(int32_t i, int32_t j, int32_t q);
int32_t fix_Mod(int32_t i, int32_t j, int32_t q);
int32_t fix_Div(int32_t i, int32_t j, int32_t q);
int32_t fix_Pow(int32_t i, int32_t j, int32_t q);
int32_t fix_Log(int32_t i, int32_t j, int32_t q);
int32_t fix_Round(int32_t i, int32_t q);
int32_t fix_Ceil(int32_t i, int32_t q);
int32_t fix_Floor(int32_t i, int32_t q);
#endif // RGBDS_ASM_FIXPOINT_H

View File

@@ -60,4 +60,4 @@ void fmt_FinishCharacters(struct FormatSpec *fmt);
void fmt_PrintString(char *buf, size_t bufLen, struct FormatSpec const *fmt, char const *value); void fmt_PrintString(char *buf, size_t bufLen, struct FormatSpec const *fmt, char const *value);
void fmt_PrintNumber(char *buf, size_t bufLen, struct FormatSpec const *fmt, uint32_t value); void fmt_PrintNumber(char *buf, size_t bufLen, struct FormatSpec const *fmt, uint32_t value);
#endif /* RGBDS_FORMAT_SPEC_H */ #endif // RGBDS_FORMAT_SPEC_H

View File

@@ -6,9 +6,7 @@
* SPDX-License-Identifier: MIT * SPDX-License-Identifier: MIT
*/ */
/* // Contains some assembler-wide defines and externs
* Contains some assembler-wide defines and externs
*/
#ifndef RGBDS_ASM_FSTACK_H #ifndef RGBDS_ASM_FSTACK_H
#define RGBDS_ASM_FSTACK_H #define RGBDS_ASM_FSTACK_H
@@ -21,13 +19,13 @@
struct FileStackNode { struct FileStackNode {
struct FileStackNode *parent; /* Pointer to parent node, for error reporting */ struct FileStackNode *parent; // Pointer to parent node, for error reporting
/* Line at which the parent context was exited; meaningless for the root level */ // Line at which the parent context was exited; meaningless for the root level
uint32_t lineNo; uint32_t lineNo;
struct FileStackNode *next; /* Next node in the output linked list */ struct FileStackNode *next; // Next node in the output linked list
bool referenced; /* If referenced, don't free! */ bool referenced; // If referenced, don't free!
uint32_t ID; /* Set only if referenced: ID within the object file, -1 if not output yet */ uint32_t ID; // Set only if referenced: ID within the object file, -1 if not output yet
enum { enum {
NODE_REPT, NODE_REPT,
@@ -36,18 +34,19 @@ struct FileStackNode {
} type; } type;
}; };
struct FileStackReptNode { /* NODE_REPT */ struct FileStackReptNode { // NODE_REPT
struct FileStackNode node; struct FileStackNode node;
uint32_t reptDepth; uint32_t reptDepth;
/* WARNING: if changing this type, change overflow check in `fstk_Init` */ // WARNING: if changing this type, change overflow check in `fstk_Init`
uint32_t iters[]; /* REPT iteration counts since last named node, in reverse depth order */ uint32_t iters[]; // REPT iteration counts since last named node, in reverse depth order
}; };
struct FileStackNamedNode { /* NODE_FILE, NODE_MACRO */ struct FileStackNamedNode { // NODE_FILE, NODE_MACRO
struct FileStackNode node; struct FileStackNode node;
char name[]; /* File name for files, file::macro name for macros */ char name[]; // File name for files, file::macro name for macros
}; };
#define DEFAULT_MAX_DEPTH 64
extern size_t maxRecursionDepth; extern size_t maxRecursionDepth;
struct MacroArgs; struct MacroArgs;
@@ -55,11 +54,12 @@ struct MacroArgs;
void fstk_Dump(struct FileStackNode const *node, uint32_t lineNo); void fstk_Dump(struct FileStackNode const *node, uint32_t lineNo);
void fstk_DumpCurrent(void); void fstk_DumpCurrent(void);
struct FileStackNode *fstk_GetFileStack(void); struct FileStackNode *fstk_GetFileStack(void);
/* The lifetime of the returned chars is until reaching the end of that file */ // The lifetime of the returned chars is until reaching the end of that file
char const *fstk_GetFileName(void); char const *fstk_GetFileName(void);
void fstk_AddIncludePath(char const *s); void fstk_AddIncludePath(char const *s);
/** void fstk_SetPreIncludeFile(char const *s);
/*
* @param path The user-provided file name * @param path The user-provided file name
* @param fullPath The address of a pointer, which will be made to point at the full path * @param fullPath The address of a pointer, which will be made to point at the full path
* The pointer's value must be a valid argument to `realloc`, including NULL * The pointer's value must be a valid argument to `realloc`, including NULL
@@ -80,4 +80,4 @@ bool fstk_Break(void);
void fstk_NewRecursionDepth(size_t newDepth); void fstk_NewRecursionDepth(size_t newDepth);
void fstk_Init(char const *mainPath, size_t maxDepth); void fstk_Init(char const *mainPath, size_t maxDepth);
#endif /* RGBDS_ASM_FSTACK_H */ #endif // RGBDS_ASM_FSTACK_H

View File

@@ -49,9 +49,7 @@ static inline void lexer_SetGfxDigits(char const digits[4])
gfxDigits[3] = digits[3]; gfxDigits[3] = digits[3];
} }
/* // `path` is referenced, but not held onto..!
* `path` is referenced, but not held onto..!
*/
struct LexerState *lexer_OpenFile(char const *path); struct LexerState *lexer_OpenFile(char const *path);
struct LexerState *lexer_OpenFileView(char const *path, char *buf, size_t size, uint32_t lineNo); struct LexerState *lexer_OpenFileView(char const *path, char *buf, size_t size, uint32_t lineNo);
void lexer_RestartRept(uint32_t lineNo); void lexer_RestartRept(uint32_t lineNo);
@@ -99,4 +97,4 @@ struct DsArgList {
struct Expression *args; struct Expression *args;
}; };
#endif /* RGBDS_ASM_LEXER_H */ #endif // RGBDS_ASM_LEXER_H

View File

@@ -31,7 +31,8 @@ uint32_t macro_GetUniqueID(void);
char const *macro_GetUniqueIDStr(void); char const *macro_GetUniqueIDStr(void);
void macro_SetUniqueID(uint32_t id); void macro_SetUniqueID(uint32_t id);
uint32_t macro_UseNewUniqueID(void); uint32_t macro_UseNewUniqueID(void);
uint32_t macro_UndefUniqueID(void);
void macro_ShiftCurrentArgs(int32_t count); void macro_ShiftCurrentArgs(int32_t count);
uint32_t macro_NbArgs(void); uint32_t macro_NbArgs(void);
#endif #endif // RGBDS_MACRO_H

View File

@@ -20,7 +20,7 @@ extern bool warnOnHaltNop;
extern bool optimizeLoads; extern bool optimizeLoads;
extern bool warnOnLdOpt; extern bool warnOnLdOpt;
extern bool verbose; extern bool verbose;
extern bool warnings; /* True to enable warnings, false to disable them. */ extern bool warnings; // True to enable warnings, false to disable them.
extern FILE *dependfile; extern FILE *dependfile;
extern char *targetFileName; extern char *targetFileName;
@@ -28,4 +28,4 @@ extern bool generatedMissingIncludes;
extern bool failedOnMissingInclude; extern bool failedOnMissingInclude;
extern bool generatePhonyDeps; extern bool generatePhonyDeps;
#endif /* RGBDS_MAIN_H */ #endif // RGBDS_MAIN_H

View File

@@ -14,7 +14,8 @@
void opt_B(char const chars[2]); void opt_B(char const chars[2]);
void opt_G(char const chars[4]); void opt_G(char const chars[4]);
void opt_P(uint8_t fill); void opt_P(uint8_t padByte);
void opt_Q(uint8_t precision);
void opt_L(bool optimize); void opt_L(bool optimize);
void opt_W(char const *flag); void opt_W(char const *flag);
void opt_Parse(char const *option); void opt_Parse(char const *option);
@@ -22,4 +23,4 @@ void opt_Parse(char const *option);
void opt_Push(void); void opt_Push(void);
void opt_Pop(void); void opt_Pop(void);
#endif #endif // RGBDS_OPT_H

View File

@@ -27,4 +27,4 @@ bool out_CreateAssert(enum AssertionType type, struct Expression const *expr,
char const *message, uint32_t ofs); char const *message, uint32_t ofs);
void out_WriteObject(void); void out_WriteObject(void);
#endif /* RGBDS_ASM_OUTPUT_H */ #endif // RGBDS_ASM_OUTPUT_H

View File

@@ -27,17 +27,13 @@ struct Expression {
uint32_t rpnPatchSize; // Size the expression will take in the object file uint32_t rpnPatchSize; // Size the expression will take in the object file
}; };
/* // Determines if an expression is known at assembly time
* Determines if an expression is known at assembly time
*/
static inline bool rpn_isKnown(struct Expression const *expr) static inline bool rpn_isKnown(struct Expression const *expr)
{ {
return expr->isKnown; return expr->isKnown;
} }
/* // Determines if an expression is a symbol suitable for const diffing
* Determines if an expression is a symbol suitable for const diffing
*/
static inline bool rpn_isSymbol(const struct Expression *expr) static inline bool rpn_isSymbol(const struct Expression *expr)
{ {
return expr->isSymbol; return expr->isSymbol;
@@ -67,4 +63,4 @@ void rpn_CheckRST(struct Expression *expr, const struct Expression *src);
void rpn_CheckNBit(struct Expression const *expr, uint8_t n); void rpn_CheckNBit(struct Expression const *expr, uint8_t n);
int32_t rpn_GetConstVal(struct Expression const *expr); int32_t rpn_GetConstVal(struct Expression const *expr);
#endif /* RGBDS_ASM_RPN_H */ #endif // RGBDS_ASM_RPN_H

View File

@@ -23,8 +23,8 @@ struct Section {
char *name; char *name;
enum SectionType type; enum SectionType type;
enum SectionModifier modifier; enum SectionModifier modifier;
struct FileStackNode *src; /* Where the section was defined */ struct FileStackNode *src; // Where the section was defined
uint32_t fileLine; /* Line where the section was defined */ uint32_t fileLine; // Line where the section was defined
uint32_t size; uint32_t size;
uint32_t org; uint32_t org;
uint32_t bank; uint32_t bank;
@@ -79,4 +79,4 @@ void sect_PopSection(void);
bool sect_IsSizeKnown(struct Section const NONNULL(name)); bool sect_IsSizeKnown(struct Section const NONNULL(name));
#endif #endif // RGBDS_SECTION_H

View File

@@ -32,28 +32,28 @@ enum SymbolType {
struct Symbol { struct Symbol {
char name[MAXSYMLEN + 1]; char name[MAXSYMLEN + 1];
enum SymbolType type; enum SymbolType type;
bool isExported; /* Whether the symbol is to be exported */ bool isExported; // Whether the symbol is to be exported
bool isBuiltin; /* Whether the symbol is a built-in */ bool isBuiltin; // Whether the symbol is a built-in
struct Section *section; struct Section *section;
struct FileStackNode *src; /* Where the symbol was defined */ struct FileStackNode *src; // Where the symbol was defined
uint32_t fileLine; /* Line where the symbol was defined */ uint32_t fileLine; // Line where the symbol was defined
bool hasCallback; bool hasCallback;
union { union {
/* If sym_IsNumeric */ // If sym_IsNumeric
int32_t value; int32_t value;
int32_t (*numCallback)(void); int32_t (*numCallback)(void);
/* For SYM_MACRO and SYM_EQUS; TODO: have separate fields */ // For SYM_MACRO and SYM_EQUS; TODO: have separate fields
struct { struct {
size_t macroSize; size_t macroSize;
char *macro; char *macro;
}; };
/* For SYM_EQUS */ // For SYM_EQUS
char const *(*strCallback)(void); char const *(*strCallback)(void);
}; };
uint32_t ID; /* ID of the symbol in the object file (-1 if none) */ uint32_t ID; // ID of the symbol in the object file (-1 if none)
struct Symbol *next; /* Next object to output in the object file */ struct Symbol *next; // Next object to output in the object file
}; };
bool sym_IsPC(struct Symbol const *sym); bool sym_IsPC(struct Symbol const *sym);
@@ -98,9 +98,7 @@ static inline bool sym_IsExported(struct Symbol const *sym)
return sym->isExported; return sym->isExported;
} }
/* // Get a string equate's value
* Get a string equate's value
*/
static inline char const *sym_GetStringValue(struct Symbol const *sym) static inline char const *sym_GetStringValue(struct Symbol const *sym)
{ {
if (sym->hasCallback) if (sym->hasCallback)
@@ -123,18 +121,14 @@ struct Symbol *sym_AddVar(char const *symName, int32_t value);
uint32_t sym_GetPCValue(void); uint32_t sym_GetPCValue(void);
uint32_t sym_GetConstantSymValue(struct Symbol const *sym); uint32_t sym_GetConstantSymValue(struct Symbol const *sym);
uint32_t sym_GetConstantValue(char const *symName); uint32_t sym_GetConstantValue(char const *symName);
/* // Find a symbol by exact name, bypassing expansion checks
* Find a symbol by exact name, bypassing expansion checks
*/
struct Symbol *sym_FindExactSymbol(char const *symName); struct Symbol *sym_FindExactSymbol(char const *symName);
/* // Find a symbol by exact name; may not be scoped, produces an error if it is
* Find a symbol by exact name; may not be scoped, produces an error if it is
*/
struct Symbol *sym_FindUnscopedSymbol(char const *symName); struct Symbol *sym_FindUnscopedSymbol(char const *symName);
/* // Find a symbol, possibly scoped, by name
* Find a symbol, possibly scoped, by name
*/
struct Symbol *sym_FindScopedSymbol(char const *symName); struct Symbol *sym_FindScopedSymbol(char const *symName);
// Find a scoped symbol by name; do not return `@` or `_NARG` when they have no value
struct Symbol *sym_FindScopedValidSymbol(char const *symName);
struct Symbol const *sym_GetPC(void); struct Symbol const *sym_GetPC(void);
struct Symbol *sym_AddMacro(char const *symName, int32_t defLineNo, char *body, size_t size); struct Symbol *sym_AddMacro(char const *symName, int32_t defLineNo, char *body, size_t size);
struct Symbol *sym_Ref(char const *symName); struct Symbol *sym_Ref(char const *symName);
@@ -143,8 +137,8 @@ struct Symbol *sym_RedefString(char const *symName, char const *value);
void sym_Purge(char const *symName); void sym_Purge(char const *symName);
void sym_Init(time_t now); void sym_Init(time_t now);
/* Functions to save and restore the current symbol scope. */ // Functions to save and restore the current symbol scope.
char const *sym_GetCurrentSymbolScope(void); char const *sym_GetCurrentSymbolScope(void);
void sym_SetCurrentSymbolScope(char const *newScope); void sym_SetCurrentSymbolScope(char const *newScope);
#endif /* RGBDS_SYMBOL_H */ #endif // RGBDS_SYMBOL_H

View File

@@ -18,4 +18,4 @@ char const *printChar(int c);
*/ */
size_t readUTF8Char(uint8_t *dest, char const *src); size_t readUTF8Char(uint8_t *dest, char const *src);
#endif /* RGBDS_UTIL_H */ #endif // RGBDS_UTIL_H

View File

@@ -48,6 +48,9 @@ enum WarningID {
// Implicit truncation loses some bits // Implicit truncation loses some bits
WARNING_TRUNCATION_1, WARNING_TRUNCATION_1,
WARNING_TRUNCATION_2, WARNING_TRUNCATION_2,
// Character without charmap entry
WARNING_UNMAPPED_CHAR_1,
WARNING_UNMAPPED_CHAR_2,
NB_PLAIN_AND_PARAM_WARNINGS, NB_PLAIN_AND_PARAM_WARNINGS,
#define NB_PARAM_WARNINGS (NB_PLAIN_AND_PARAM_WARNINGS - PARAM_WARNINGS_START) #define NB_PARAM_WARNINGS (NB_PLAIN_AND_PARAM_WARNINGS - PARAM_WARNINGS_START)
@@ -90,4 +93,4 @@ _Noreturn void fatalerror(char const *fmt, ...) format_(printf, 1, 2);
*/ */
void error(char const *fmt, ...) format_(printf, 1, 2); void error(char const *fmt, ...) format_(printf, 1, 2);
#endif #endif // WARNING_H

View File

@@ -1,4 +1,4 @@
/** /*
* Allocator adaptor that interposes construct() calls to convert value-initialization * Allocator adaptor that interposes construct() calls to convert value-initialization
* (which is what you get with e.g. `vector::resize`) into default-initialization (which does not * (which is what you get with e.g. `vector::resize`) into default-initialization (which does not
* zero out non-class types). * zero out non-class types).
@@ -36,4 +36,4 @@ public:
template<typename T> template<typename T>
using DefaultInitVec = std::vector<T, default_init_allocator<T>>; using DefaultInitVec = std::vector<T, default_init_allocator<T>>;
#endif #endif // DEFAULT_INIT_ALLOC_H

View File

@@ -26,4 +26,4 @@ _Noreturn void errx(char const NONNULL(fmt), ...) format_(printf, 1, 2);
} }
#endif #endif
#endif /* RGBDS_ERROR_H */ #endif // RGBDS_ERROR_H

View File

@@ -11,4 +11,4 @@
uint32_t decode(uint32_t *state, uint32_t *codep, uint8_t byte); uint32_t decode(uint32_t *state, uint32_t *codep, uint8_t byte);
#endif /* EXTERN_UTF8DECODER_H */ #endif // EXTERN_UTF8DECODER_H

103
include/file.hpp Normal file
View File

@@ -0,0 +1,103 @@
/*
* This file is part of RGBDS.
*
* Copyright (c) 2022, Eldred Habert and RGBDS contributors.
*
* SPDX-License-Identifier: MIT
*/
#ifndef RGBDS_FILE_HPP
#define RGBDS_FILE_HPP
#include <array>
#include <assert.h>
#include <cassert>
#include <fcntl.h>
#include <fstream>
#include <ios>
#include <iostream>
#include <streambuf>
#include <string>
#include <string.h>
#include <string_view>
#include <variant>
#include "helpers.h"
#include "platform.h"
#include "gfx/main.hpp"
// Convenience feature for visiting the below.
template<typename... Ts>
struct Visitor : Ts... {
using Ts::operator()...;
};
template<typename... Ts>
Visitor(Ts...) -> Visitor<Ts...>;
class File {
// Construct a `std::streambuf *` by default, since it's probably lighter than a `filebuf`.
std::variant<std::streambuf *, std::filebuf> _file;
public:
File() {}
~File() { close(); }
/**
* This should only be called once, and before doing any `->` operations.
* Returns `nullptr` on error, and a non-null pointer otherwise.
*/
File *open(std::string const &path, std::ios_base::openmode mode) {
if (path != "-") {
return _file.emplace<std::filebuf>().open(path, mode) ? this : nullptr;
} else if (mode & std::ios_base::in) {
assert(!(mode & std::ios_base::out));
_file.emplace<std::streambuf *>(std::cin.rdbuf());
if (setmode(STDIN_FILENO, mode & std::ios_base::binary ? O_BINARY : O_TEXT) == -1) {
fatal("Failed to set stdin to %s mode: %s",
mode & std::ios_base::binary ? "binary" : "text", strerror(errno));
}
} else {
assert(mode & std::ios_base::out);
_file.emplace<std::streambuf *>(std::cout.rdbuf());
}
return this;
}
std::streambuf &operator*() {
return std::visit(Visitor{[](std::filebuf &file) -> std::streambuf & { return file; },
[](std::streambuf *buf) -> std::streambuf & { return *buf; }},
_file);
}
std::streambuf const &operator*() const {
// The non-`const` version does not perform any modifications, so it's okay.
return **const_cast<File *>(this);
}
std::streambuf *operator->() { return &**this; }
std::streambuf const *operator->() const {
// See the `operator*` equivalent.
return const_cast<File *>(this)->operator->();
}
File *close() {
return std::visit(Visitor{[this](std::filebuf &file) {
// This is called by the destructor, and an explicit `close`
// shouldn't close twice.
_file.emplace<std::streambuf *>(nullptr);
return file.close() != nullptr;
},
[](std::streambuf *buf) { return buf != nullptr; }},
_file)
? this
: nullptr;
}
char const *c_str(std::string const &path) const {
return std::visit(Visitor{[&path](std::filebuf const &) { return path.c_str(); },
[](std::streambuf const *buf) {
return buf == std::cin.rdbuf() ? "<stdin>" : "<stdout>";
}},
_file);
}
};
#endif // RGBDS_FILE_HPP

View File

@@ -21,7 +21,7 @@
#include "gfx/rgba.hpp" #include "gfx/rgba.hpp"
struct Options { struct Options {
uint8_t reversedWidth = 0; // -r, in pixels uint16_t reversedWidth = 0; // -r, in tiles
bool reverse() const { return reversedWidth != 0; } bool reverse() const { return reversedWidth != 0; }
bool useColorCurve = false; // -C bool useColorCurve = false; // -C
@@ -71,19 +71,19 @@ struct Options {
extern Options options; extern Options options;
/** /*
* Prints the error count, and exits with failure * Prints the error count, and exits with failure
*/ */
[[noreturn]] void giveUp(); [[noreturn]] void giveUp();
/** /*
* Prints a warning, and does not change the error count * Prints a warning, and does not change the error count
*/ */
void warning(char const *fmt, ...); void warning(char const *fmt, ...);
/** /*
* Prints an error, and increments the error count * Prints an error, and increments the error count
*/ */
void error(char const *fmt, ...); void error(char const *fmt, ...);
/** /*
* Prints a fatal error, increments the error count, and gives up * Prints a fatal error, increments the error count, and gives up
*/ */
[[noreturn]] void fatal(char const *fmt, ...); [[noreturn]] void fatal(char const *fmt, ...);
@@ -120,4 +120,4 @@ static constexpr auto flipTable(std::integer_sequence<T, i...>) {
// Flipping tends to happen fairly often, so take a bite out of dcache to speed it up // Flipping tends to happen fairly often, so take a bite out of dcache to speed it up
static constexpr auto flipTable = detail::flipTable(std::make_integer_sequence<uint16_t, 256>()); static constexpr auto flipTable = detail::flipTable(std::make_integer_sequence<uint16_t, 256>());
#endif /* RGBDS_GFX_MAIN_HPP */ #endif // RGBDS_GFX_MAIN_HPP

View File

@@ -21,7 +21,7 @@ class ProtoPalette;
namespace packing { namespace packing {
/** /*
* Returns which palette each proto-palette maps to, and how many palettes are necessary * Returns which palette each proto-palette maps to, and how many palettes are necessary
*/ */
std::tuple<DefaultInitVec<size_t>, size_t> std::tuple<DefaultInitVec<size_t>, size_t>
@@ -29,4 +29,4 @@ std::tuple<DefaultInitVec<size_t>, size_t>
} }
#endif /* RGBDS_GFX_PAL_PACKING_HPP */ #endif // RGBDS_GFX_PAL_PACKING_HPP

View File

@@ -29,4 +29,4 @@ void rgb(std::vector<Palette> &palettes);
} }
#endif /* RGBDS_GFX_PAL_SORTING_HPP */ #endif // RGBDS_GFX_PAL_SORTING_HPP

View File

@@ -12,4 +12,4 @@
void parseInlinePalSpec(char const * const arg); void parseInlinePalSpec(char const * const arg);
void parseExternalPalSpec(char const *arg); void parseExternalPalSpec(char const *arg);
#endif /* RGBDS_GFX_PAL_SPEC_HPP */ #endif // RGBDS_GFX_PAL_SPEC_HPP

View File

@@ -11,4 +11,4 @@
void process(); void process();
#endif /* RGBDS_GFX_CONVERT_HPP */ #endif // RGBDS_GFX_CONVERT_HPP

View File

@@ -21,7 +21,7 @@ class ProtoPalette {
std::array<uint16_t, 4> _colorIndices{UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX}; std::array<uint16_t, 4> _colorIndices{UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX};
public: public:
/** /*
* Adds the specified color to the set * Adds the specified color to the set
* Returns false if the set is full * Returns false if the set is full
*/ */
@@ -41,4 +41,4 @@ public:
decltype(_colorIndices)::const_iterator end() const; decltype(_colorIndices)::const_iterator end() const;
}; };
#endif /* RGBDS_GFX_PROTO_PALETTE_HPP */ #endif // RGBDS_GFX_PROTO_PALETTE_HPP

View File

@@ -11,4 +11,4 @@
void reverse(); void reverse();
#endif /* RGBDS_GFX_REVERSE_HPP */ #endif // RGBDS_GFX_REVERSE_HPP

View File

@@ -20,7 +20,7 @@ struct Rgba {
constexpr Rgba(uint8_t r, uint8_t g, uint8_t b, uint8_t a) constexpr Rgba(uint8_t r, uint8_t g, uint8_t b, uint8_t a)
: red(r), green(g), blue(b), alpha(a) {} : red(r), green(g), blue(b), alpha(a) {}
/** /*
* Constructs the color from a "packed" RGBA representation (0xRRGGBBAA) * Constructs the color from a "packed" RGBA representation (0xRRGGBBAA)
*/ */
explicit constexpr Rgba(uint32_t rgba = 0) explicit constexpr Rgba(uint32_t rgba = 0)
@@ -35,7 +35,7 @@ struct Rgba {
(uint8_t)(cgbColor & 0x8000 ? 0x00 : 0xFF)}; (uint8_t)(cgbColor & 0x8000 ? 0x00 : 0xFF)};
} }
/** /*
* Returns this RGBA as a 32-bit number that can be printed in hex (`%08x`) to yield its CSS * Returns this RGBA as a 32-bit number that can be printed in hex (`%08x`) to yield its CSS
* representation * representation
*/ */
@@ -45,7 +45,7 @@ struct Rgba {
} }
friend bool operator!=(Rgba const &lhs, Rgba const &rhs) { return lhs.toCSS() != rhs.toCSS(); } friend bool operator!=(Rgba const &lhs, Rgba const &rhs) { return lhs.toCSS() != rhs.toCSS(); }
/** /*
* CGB colors are RGB555, so we use bit 15 to signify that the color is transparent instead * CGB colors are RGB555, so we use bit 15 to signify that the color is transparent instead
* Since the rest of the bits don't matter then, we return 0x8000 exactly. * Since the rest of the bits don't matter then, we return 0x8000 exactly.
*/ */
@@ -55,7 +55,7 @@ struct Rgba {
bool isTransparent() const { return alpha < transparency_threshold; } bool isTransparent() const { return alpha < transparency_threshold; }
static constexpr uint8_t opacity_threshold = 0xF0; static constexpr uint8_t opacity_threshold = 0xF0;
bool isOpaque() const { return alpha >= opacity_threshold; } bool isOpaque() const { return alpha >= opacity_threshold; }
/** /*
* Computes the equivalent CGB color, respects the color curve depending on options * Computes the equivalent CGB color, respects the color curve depending on options
*/ */
uint16_t cgbColor() const; uint16_t cgbColor() const;
@@ -64,4 +64,4 @@ struct Rgba {
uint8_t grayIndex() const; uint8_t grayIndex() const;
}; };
#endif /* RGBDS_GFX_RGBA_HPP */ #endif // RGBDS_GFX_RGBA_HPP

View File

@@ -6,7 +6,7 @@
* SPDX-License-Identifier: MIT * SPDX-License-Identifier: MIT
*/ */
/* Generic hashmap implementation (C++ templates are calling...) */ // Generic hashmap implementation (C++ templates are calling...)
#ifndef RGBDS_LINK_HASHMAP_H #ifndef RGBDS_LINK_HASHMAP_H
#define RGBDS_LINK_HASHMAP_H #define RGBDS_LINK_HASHMAP_H
@@ -18,10 +18,10 @@
static_assert(HALF_HASH_NB_BITS * 2 == HASH_NB_BITS, ""); static_assert(HALF_HASH_NB_BITS * 2 == HASH_NB_BITS, "");
#define HASHMAP_NB_BUCKETS (1 << HALF_HASH_NB_BITS) #define HASHMAP_NB_BUCKETS (1 << HALF_HASH_NB_BITS)
/* HashMapEntry is internal, please do not attempt to use it */ // HashMapEntry is internal, please do not attempt to use it
typedef struct HashMapEntry *HashMap[HASHMAP_NB_BUCKETS]; typedef struct HashMapEntry *HashMap[HASHMAP_NB_BUCKETS];
/** /*
* Adds an element to a hashmap. * Adds an element to a hashmap.
* @warning Adding a new element with an already-present key will not cause an * @warning Adding a new element with an already-present key will not cause an
* error, this must be handled externally. * error, this must be handled externally.
@@ -33,7 +33,7 @@ typedef struct HashMapEntry *HashMap[HASHMAP_NB_BUCKETS];
*/ */
void **hash_AddElement(HashMap map, char const *key, void *element); void **hash_AddElement(HashMap map, char const *key, void *element);
/** /*
* Removes an element from a hashmap. * Removes an element from a hashmap.
* @param map The HashMap to remove the element from * @param map The HashMap to remove the element from
* @param key The key to search the element with * @param key The key to search the element with
@@ -41,7 +41,7 @@ void **hash_AddElement(HashMap map, char const *key, void *element);
*/ */
bool hash_RemoveElement(HashMap map, char const *key); bool hash_RemoveElement(HashMap map, char const *key);
/** /*
* Finds an element in a hashmap, and returns a pointer to its value field. * Finds an element in a hashmap, and returns a pointer to its value field.
* @param map The map to consider the elements of * @param map The map to consider the elements of
* @param key The key to search an element for * @param key The key to search an element for
@@ -49,7 +49,7 @@ bool hash_RemoveElement(HashMap map, char const *key);
*/ */
void **hash_GetNode(HashMap const map, char const *key); void **hash_GetNode(HashMap const map, char const *key);
/** /*
* Finds an element in a hashmap. * Finds an element in a hashmap.
* @param map The map to consider the elements of * @param map The map to consider the elements of
* @param key The key to search an element for * @param key The key to search an element for
@@ -58,7 +58,7 @@ void **hash_GetNode(HashMap const map, char const *key);
*/ */
void *hash_GetElement(HashMap const map, char const *key); void *hash_GetElement(HashMap const map, char const *key);
/** /*
* Executes a function on each element in a hashmap. * Executes a function on each element in a hashmap.
* @param map The map to consider the elements of * @param map The map to consider the elements of
* @param func The function to run. The first argument will be the element, * @param func The function to run. The first argument will be the element,
@@ -67,11 +67,11 @@ void *hash_GetElement(HashMap const map, char const *key);
*/ */
void hash_ForEach(HashMap const map, void (*func)(void *, void *), void *arg); void hash_ForEach(HashMap const map, void (*func)(void *, void *), void *arg);
/** /*
* Cleanly empties a hashmap from its contents. * Cleanly empties a hashmap from its contents.
* This does not `free` the data structure itself! * This does not `free` the data structure itself!
* @param map The map to empty * @param map The map to empty
*/ */
void hash_EmptyMap(HashMap map); void hash_EmptyMap(HashMap map);
#endif /* RGBDS_LINK_HASHMAP_H */ #endif // RGBDS_LINK_HASHMAP_H

View File

@@ -93,4 +93,4 @@
// (Having two instances of `arr` is OK because the contents of `sizeof` are not evaluated.) // (Having two instances of `arr` is OK because the contents of `sizeof` are not evaluated.)
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof *(arr)) #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof *(arr))
#endif /* HELPERS_H */ #endif // HELPERS_H

View File

@@ -79,12 +79,10 @@ using Holder = std::conditional_t<std::is_lvalue_reference_v<T>, T,
std::remove_cv_t<std::remove_reference_t<T>>>; std::remove_cv_t<std::remove_reference_t<T>>>;
} }
/** // Does the same number of iterations as the first container's iterator!
* Does the same number of iterations as the first container's iterator!
*/
template<typename... Containers> template<typename... Containers>
static constexpr auto zip(Containers &&...cs) { static constexpr auto zip(Containers &&...cs) {
return detail::ZipContainer<detail::Holder<Containers>...>(std::forward<Containers>(cs)...); return detail::ZipContainer<detail::Holder<Containers>...>(std::forward<Containers>(cs)...);
} }
#endif /* RGBDS_ITERTOOLS_HPP */ #endif // RGBDS_ITERTOOLS_HPP

View File

@@ -6,7 +6,7 @@
* SPDX-License-Identifier: MIT * SPDX-License-Identifier: MIT
*/ */
/* Assigning all sections a place */ // Assigning all sections a place
#ifndef RGBDS_LINK_ASSIGN_H #ifndef RGBDS_LINK_ASSIGN_H
#define RGBDS_LINK_ASSIGN_H #define RGBDS_LINK_ASSIGN_H
@@ -14,14 +14,10 @@
extern uint64_t nbSectionsToAssign; extern uint64_t nbSectionsToAssign;
/** // Assigns all sections a slice of the address space
* Assigns all sections a slice of the address space
*/
void assign_AssignSections(void); void assign_AssignSections(void);
/** // `free`s all assignment memory that was allocated
* `free`s all assignment memory that was allocated.
*/
void assign_Cleanup(void); void assign_Cleanup(void);
#endif /* RGBDS_LINK_ASSIGN_H */ #endif // RGBDS_LINK_ASSIGN_H

View File

@@ -6,7 +6,7 @@
* SPDX-License-Identifier: MIT * SPDX-License-Identifier: MIT
*/ */
/* Declarations that all modules use, as well as `main` and related */ // Declarations that all modules use, as well as `main` and related
#ifndef RGBDS_LINK_MAIN_H #ifndef RGBDS_LINK_MAIN_H
#define RGBDS_LINK_MAIN_H #define RGBDS_LINK_MAIN_H
@@ -16,10 +16,11 @@
#include "helpers.h" #include "helpers.h"
/* Variables related to CLI options */ // Variables related to CLI options
extern bool isDmgMode; extern bool isDmgMode;
extern char *linkerScriptName; extern char *linkerScriptName;
extern char const *mapFileName; extern char const *mapFileName;
extern bool noSymInMap;
extern char const *symFileName; extern char const *symFileName;
extern char const *overlayFileName; extern char const *overlayFileName;
extern char const *outputFileName; extern char const *outputFileName;
@@ -34,7 +35,7 @@ extern bool disablePadding;
struct FileStackNode { struct FileStackNode {
struct FileStackNode *parent; struct FileStackNode *parent;
/* Line at which the parent context was exited; meaningless for the root level */ // Line at which the parent context was exited; meaningless for the root level
uint32_t lineNo; uint32_t lineNo;
enum { enum {
@@ -43,21 +44,21 @@ struct FileStackNode {
NODE_MACRO, NODE_MACRO,
} type; } type;
union { union {
char *name; /* NODE_FILE, NODE_MACRO */ char *name; // NODE_FILE, NODE_MACRO
struct { /* NODE_REPT */ struct { // NODE_REPT
uint32_t reptDepth; uint32_t reptDepth;
uint32_t *iters; uint32_t *iters;
}; };
}; };
}; };
/* Helper macro for printing verbose-mode messages */ // Helper macro for printing verbose-mode messages
#define verbosePrint(...) do { \ #define verbosePrint(...) do { \
if (beVerbose) \ if (beVerbose) \
fprintf(stderr, __VA_ARGS__); \ fprintf(stderr, __VA_ARGS__); \
} while (0) } while (0)
/** /*
* Dump a file stack to stderr * Dump a file stack to stderr
* @param node The leaf node to dump the context of * @param node The leaf node to dump the context of
*/ */
@@ -72,7 +73,7 @@ void error(struct FileStackNode const *where, uint32_t lineNo,
_Noreturn void fatal(struct FileStackNode const *where, uint32_t lineNo, _Noreturn void fatal(struct FileStackNode const *where, uint32_t lineNo,
char const *fmt, ...) format_(printf, 3, 4); char const *fmt, ...) format_(printf, 3, 4);
/** /*
* Opens a file if specified, and aborts on error. * Opens a file if specified, and aborts on error.
* @param fileName The name of the file to open; if NULL, no file will be opened * @param fileName The name of the file to open; if NULL, no file will be opened
* @param mode The mode to open the file with * @param mode The mode to open the file with
@@ -86,4 +87,4 @@ FILE *openFile(char const *fileName, char const *mode);
fclose(tmp); \ fclose(tmp); \
} while (0) } while (0)
#endif /* RGBDS_LINK_MAIN_H */ #endif // RGBDS_LINK_MAIN_H

View File

@@ -6,37 +6,37 @@
* SPDX-License-Identifier: MIT * SPDX-License-Identifier: MIT
*/ */
/* Declarations related to processing of object (.o) files */ // Declarations related to processing of object (.o) files
#ifndef RGBDS_LINK_OBJECT_H #ifndef RGBDS_LINK_OBJECT_H
#define RGBDS_LINK_OBJECT_H #define RGBDS_LINK_OBJECT_H
/** /*
* Read an object (.o) file, and add its info to the data structures. * Read an object (.o) file, and add its info to the data structures.
* @param fileName A path to the object file to be read * @param fileName A path to the object file to be read
* @param i The ID of the file * @param i The ID of the file
*/ */
void obj_ReadFile(char const *fileName, unsigned int i); void obj_ReadFile(char const *fileName, unsigned int i);
/** /*
* Perform validation on the object files' contents * Perform validation on the object files' contents
*/ */
void obj_DoSanityChecks(void); void obj_DoSanityChecks(void);
/** /*
* Evaluate all assertions * Evaluate all assertions
*/ */
void obj_CheckAssertions(void); void obj_CheckAssertions(void);
/** /*
* Sets up object file reading * Sets up object file reading
* @param nbFiles The number of object files that will be read * @param nbFiles The number of object files that will be read
*/ */
void obj_Setup(unsigned int nbFiles); void obj_Setup(unsigned int nbFiles);
/** /*
* `free`s all object memory that was allocated. * `free`s all object memory that was allocated.
*/ */
void obj_Cleanup(void); void obj_Cleanup(void);
#endif /* RGBDS_LINK_OBJECT_H */ #endif // RGBDS_LINK_OBJECT_H

View File

@@ -6,7 +6,7 @@
* SPDX-License-Identifier: MIT * SPDX-License-Identifier: MIT
*/ */
/* Outputting the result of linking */ // Outputting the result of linking
#ifndef RGBDS_LINK_OUTPUT_H #ifndef RGBDS_LINK_OUTPUT_H
#define RGBDS_LINK_OUTPUT_H #define RGBDS_LINK_OUTPUT_H
@@ -14,22 +14,22 @@
#include "link/section.h" #include "link/section.h"
/** /*
* Registers a section for output. * Registers a section for output.
* @param section The section to add * @param section The section to add
*/ */
void out_AddSection(struct Section const *section); void out_AddSection(struct Section const *section);
/** /*
* Finds an assigned section overlapping another one. * Finds an assigned section overlapping another one.
* @param section The section that is being overlapped * @param section The section that is being overlapped
* @return A section overlapping it * @return A section overlapping it
*/ */
struct Section const *out_OverlappingSection(struct Section const *section); struct Section const *out_OverlappingSection(struct Section const *section);
/** /*
* Writes all output (bin, sym, map) files. * Writes all output (bin, sym, map) files.
*/ */
void out_WriteFiles(void); void out_WriteFiles(void);
#endif /* RGBDS_LINK_OUTPUT_H */ #endif // RGBDS_LINK_OUTPUT_H

View File

@@ -6,7 +6,7 @@
* SPDX-License-Identifier: MIT * SPDX-License-Identifier: MIT
*/ */
/* Applying patches to SECTIONs */ // Applying patches to SECTIONs
#ifndef RGBDS_LINK_PATCH_H #ifndef RGBDS_LINK_PATCH_H
#define RGBDS_LINK_PATCH_H #define RGBDS_LINK_PATCH_H
@@ -27,15 +27,15 @@ struct Assertion {
struct Assertion *next; struct Assertion *next;
}; };
/** /*
* Checks all assertions * Checks all assertions
* @return true if assertion failed * @return true if assertion failed
*/ */
void patch_CheckAssertions(struct Assertion *assertion); void patch_CheckAssertions(struct Assertion *assertion);
/** /*
* Applies all SECTIONs' patches to them * Applies all SECTIONs' patches to them
*/ */
void patch_ApplyPatches(void); void patch_ApplyPatches(void);
#endif /* RGBDS_LINK_PATCH_H */ #endif // RGBDS_LINK_PATCH_H

View File

@@ -6,31 +6,33 @@
* SPDX-License-Identifier: MIT * SPDX-License-Identifier: MIT
*/ */
/* Parsing a linker script */ // Parsing a linker script
#ifndef RGBDS_LINK_SCRIPT_H #ifndef RGBDS_LINK_SCRIPT_H
#define RGBDS_LINK_SCRIPT_H #define RGBDS_LINK_SCRIPT_H
#include <stdint.h> #include <stdint.h>
#include "linkdefs.h"
extern FILE * linkerScript; extern FILE * linkerScript;
struct SectionPlacement { struct SectionPlacement {
struct Section *section; struct Section *section;
enum SectionType type;
uint16_t org; uint16_t org;
uint32_t bank; uint32_t bank;
}; };
extern uint64_t script_lineNo; extern uint64_t script_lineNo;
/** /*
* Parses the linker script to return the next section constraint * Parses the linker script to return the next section constraint
* @return A pointer to a struct, or NULL on EOF. The pointer shouldn't be freed * @return A pointer to a struct, or NULL on EOF. The pointer shouldn't be freed
*/ */
struct SectionPlacement *script_NextSection(void); struct SectionPlacement *script_NextSection(void);
/** /*
* `free`s all assignment memory that was allocated. * `free`s all assignment memory that was allocated.
*/ */
void script_Cleanup(void); void script_Cleanup(void);
#endif /* RGBDS_LINK_SCRIPT_H */ #endif // RGBDS_LINK_SCRIPT_H

19
include/link/sdas_obj.h Normal file
View File

@@ -0,0 +1,19 @@
/*
* This file is part of RGBDS.
*
* Copyright (c) 2022, Eldred Habert and RGBDS contributors.
*
* SPDX-License-Identifier: MIT
*/
// Assigning all sections a place
#ifndef RGBDS_LINK_SDAS_OBJ_H
#define RGBDS_LINK_SDAS_OBJ_H
#include <stdio.h>
struct FileStackNode;
void sdobj_ReadFile(struct FileStackNode const *fileName, FILE *file);
#endif // RGBDS_LINK_SDAS_OBJ_H

View File

@@ -6,11 +6,11 @@
* SPDX-License-Identifier: MIT * SPDX-License-Identifier: MIT
*/ */
/* Declarations manipulating symbols */ // Declarations manipulating symbols
#ifndef RGBDS_LINK_SECTION_H #ifndef RGBDS_LINK_SECTION_H
#define RGBDS_LINK_SECTION_H #define RGBDS_LINK_SECTION_H
/* GUIDELINE: external code MUST NOT BE AWARE of the data structure used!! */ // GUIDELINE: external code MUST NOT BE AWARE of the data structure used!
#include <stdint.h> #include <stdint.h>
#include <stdbool.h> #include <stdbool.h>
@@ -41,27 +41,29 @@ struct Patch {
}; };
struct Section { struct Section {
/* Info contained in the object files */ // Info contained in the object files
char *name; char *name;
uint16_t size; uint16_t size;
uint16_t offset; uint16_t offset;
enum SectionType type; enum SectionType type;
enum SectionModifier modifier; enum SectionModifier modifier;
bool isAddressFixed; bool isAddressFixed;
// This `struct`'s address in ROM.
// Importantly for fragments, this does not include `offset`!
uint16_t org; uint16_t org;
bool isBankFixed; bool isBankFixed;
uint32_t bank; uint32_t bank;
bool isAlignFixed; bool isAlignFixed;
uint16_t alignMask; uint16_t alignMask;
uint16_t alignOfs; uint16_t alignOfs;
uint8_t *data; /* Array of size `size`*/ uint8_t *data; // Array of size `size`
uint32_t nbPatches; uint32_t nbPatches;
struct Patch *patches; struct Patch *patches;
/* Extra info computed during linking */ // Extra info computed during linking
struct Symbol **fileSymbols; struct Symbol **fileSymbols;
uint32_t nbSymbols; uint32_t nbSymbols;
struct Symbol const **symbols; struct Symbol **symbols;
struct Section *nextu; /* The next "component" of this unionized sect */ struct Section *nextu; // The next "component" of this unionized sect
}; };
/* /*
@@ -74,27 +76,27 @@ struct Section {
*/ */
void sect_ForEach(void (*callback)(struct Section *, void *), void *arg); void sect_ForEach(void (*callback)(struct Section *, void *), void *arg);
/** /*
* Registers a section to be processed. * Registers a section to be processed.
* @param section The section to register. * @param section The section to register.
*/ */
void sect_AddSection(struct Section *section); void sect_AddSection(struct Section *section);
/** /*
* Finds a section by its name. * Finds a section by its name.
* @param name The name of the section to look for * @param name The name of the section to look for
* @return A pointer to the section, or NULL if it wasn't found * @return A pointer to the section, or NULL if it wasn't found
*/ */
struct Section *sect_GetSection(char const *name); struct Section *sect_GetSection(char const *name);
/** /*
* `free`s all section memory that was allocated. * `free`s all section memory that was allocated.
*/ */
void sect_CleanupSections(void); void sect_CleanupSections(void);
/** /*
* Checks if all sections meet reasonable criteria, such as max size * Checks if all sections meet reasonable criteria, such as max size
*/ */
void sect_DoSanityChecks(void); void sect_DoSanityChecks(void);
#endif /* RGBDS_LINK_SECTION_H */ #endif // RGBDS_LINK_SECTION_H

View File

@@ -6,11 +6,11 @@
* SPDX-License-Identifier: MIT * SPDX-License-Identifier: MIT
*/ */
/* Declarations manipulating symbols */ // Declarations manipulating symbols
#ifndef RGBDS_LINK_SYMBOL_H #ifndef RGBDS_LINK_SYMBOL_H
#define RGBDS_LINK_SYMBOL_H #define RGBDS_LINK_SYMBOL_H
/* GUIDELINE: external code MUST NOT BE AWARE of the data structure used!! */ // GUIDELINE: external code MUST NOT BE AWARE of the data structure used!
#include <stdint.h> #include <stdint.h>
@@ -19,7 +19,7 @@
struct FileStackNode; struct FileStackNode;
struct Symbol { struct Symbol {
/* Info contained in the object files */ // Info contained in the object files
char *name; char *name;
enum ExportLevel type; enum ExportLevel type;
char const *objFileName; char const *objFileName;
@@ -27,10 +27,11 @@ struct Symbol {
int32_t lineNo; int32_t lineNo;
int32_t sectionID; int32_t sectionID;
union { union {
// Both types must be identical
int32_t offset; int32_t offset;
int32_t value; int32_t value;
}; };
/* Extra info computed during linking */ // Extra info computed during linking
struct Section *section; struct Section *section;
}; };
@@ -46,16 +47,16 @@ void sym_ForEach(void (*callback)(struct Symbol *, void *), void *arg);
void sym_AddSymbol(struct Symbol *symbol); void sym_AddSymbol(struct Symbol *symbol);
/** /*
* Finds a symbol in all the defined symbols. * Finds a symbol in all the defined symbols.
* @param name The name of the symbol to look for * @param name The name of the symbol to look for
* @return A pointer to the symbol, or NULL if not found. * @return A pointer to the symbol, or NULL if not found.
*/ */
struct Symbol *sym_GetSymbol(char const *name); struct Symbol *sym_GetSymbol(char const *name);
/** /*
* `free`s all symbol memory that was allocated. * `free`s all symbol memory that was allocated.
*/ */
void sym_CleanupSymbols(void); void sym_CleanupSymbols(void);
#endif /* RGBDS_LINK_SYMBOL_H */ #endif // RGBDS_LINK_SYMBOL_H

View File

@@ -9,11 +9,11 @@
#ifndef RGBDS_LINKDEFS_H #ifndef RGBDS_LINKDEFS_H
#define RGBDS_LINKDEFS_H #define RGBDS_LINKDEFS_H
#include <assert.h>
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>
#define RGBDS_OBJECT_VERSION_STRING "RGB%1u" #define RGBDS_OBJECT_VERSION_STRING "RGB9"
#define RGBDS_OBJECT_VERSION_NUMBER 9U
#define RGBDS_OBJECT_REV 9U #define RGBDS_OBJECT_REV 9U
enum AssertionType { enum AssertionType {
@@ -74,9 +74,50 @@ enum SectionType {
SECTTYPE_SRAM, SECTTYPE_SRAM,
SECTTYPE_OAM, SECTTYPE_OAM,
// In RGBLINK, this is used for "indeterminate" sections; this is primarily for SDCC
// areas, which do not carry any section type info and must be told from the linker script
SECTTYPE_INVALID SECTTYPE_INVALID
}; };
// Nont-`const` members may be patched in RGBLINK depending on CLI flags
extern struct SectionTypeInfo {
char const *const name;
uint16_t const startAddr;
uint16_t size;
uint32_t const firstBank;
uint32_t lastBank;
} sectionTypeInfo[SECTTYPE_INVALID];
/*
* Tells whether a section has data in its object file definition,
* depending on type.
* @param type The section's type
* @return `true` if the section's definition includes data
*/
static inline bool sect_HasData(enum SectionType type)
{
assert(type != SECTTYPE_INVALID);
return type == SECTTYPE_ROM0 || type == SECTTYPE_ROMX;
}
/*
* Computes a memory region's end address (last byte), eg. 0x7FFF
* @return The address of the last byte in that memory region
*/
static inline uint16_t endaddr(enum SectionType type)
{
return sectionTypeInfo[type].startAddr + sectionTypeInfo[type].size - 1;
}
/*
* Computes a memory region's number of banks
* @return The number of banks, 1 for regions without banking
*/
static inline uint32_t nbbanks(enum SectionType type)
{
return sectionTypeInfo[type].lastBank - sectionTypeInfo[type].firstBank + 1;
}
enum SectionModifier { enum SectionModifier {
SECTION_NORMAL, SECTION_NORMAL,
SECTION_UNION, SECTION_UNION,
@@ -85,17 +126,6 @@ enum SectionModifier {
extern char const * const sectionModNames[]; extern char const * const sectionModNames[];
/**
* Tells whether a section has data in its object file definition,
* depending on type.
* @param type The section's type
* @return `true` if the section's definition includes data
*/
static inline bool sect_HasData(enum SectionType type)
{
return type == SECTTYPE_ROM0 || type == SECTTYPE_ROMX;
}
enum ExportLevel { enum ExportLevel {
SYMTYPE_LOCAL, SYMTYPE_LOCAL,
SYMTYPE_IMPORT, SYMTYPE_IMPORT,
@@ -111,45 +141,4 @@ enum PatchType {
PATCHTYPE_INVALID PATCHTYPE_INVALID
}; };
#define BANK_MIN_ROM0 0 #endif // RGBDS_LINKDEFS_H
#define BANK_MAX_ROM0 0
#define BANK_MIN_ROMX 1
#define BANK_MAX_ROMX 511
#define BANK_MIN_VRAM 0
#define BANK_MAX_VRAM 1
#define BANK_MIN_SRAM 0
#define BANK_MAX_SRAM 15
#define BANK_MIN_WRAM0 0
#define BANK_MAX_WRAM0 0
#define BANK_MIN_WRAMX 1
#define BANK_MAX_WRAMX 7
#define BANK_MIN_OAM 0
#define BANK_MAX_OAM 0
#define BANK_MIN_HRAM 0
#define BANK_MAX_HRAM 0
extern uint16_t startaddr[];
extern uint16_t maxsize[];
extern uint32_t bankranges[][2];
/**
* Computes a memory region's end address (last byte), eg. 0x7FFF
* @return The address of the last byte in that memory region
*/
static inline uint16_t endaddr(enum SectionType type)
{
return startaddr[type] + maxsize[type] - 1;
}
/**
* Computes a memory region's number of banks
* @return The number of banks, 1 for regions without banking
*/
static inline uint32_t nbbanks(enum SectionType type)
{
return bankranges[type][1] - bankranges[type][0] + 1;
}
extern char const * const typeNames[SECTTYPE_INVALID];
#endif /* RGBDS_LINKDEFS_H */

View File

@@ -18,4 +18,4 @@ int32_t op_shift_left(int32_t value, int32_t amount);
int32_t op_shift_right(int32_t value, int32_t amount); int32_t op_shift_right(int32_t value, int32_t amount);
int32_t op_shift_right_unsigned(int32_t value, int32_t amount); int32_t op_shift_right_unsigned(int32_t value, int32_t amount);
#endif /* RGBDS_OP_MATH_H */ #endif // RGBDS_OP_MATH_H

View File

@@ -6,7 +6,7 @@
* SPDX-License-Identifier: MIT * SPDX-License-Identifier: MIT
*/ */
/* platform-specific hacks */ // platform-specific hacks
#ifndef RGBDS_PLATFORM_H #ifndef RGBDS_PLATFORM_H
#define RGBDS_PLATFORM_H #define RGBDS_PLATFORM_H
@@ -20,20 +20,20 @@
# include <strings.h> # include <strings.h>
#endif #endif
/* MSVC has deprecated strdup in favor of _strdup */ // MSVC has deprecated strdup in favor of _strdup
#ifdef _MSC_VER #ifdef _MSC_VER
# define strdup _strdup # define strdup _strdup
#endif #endif
/* MSVC prefixes the names of S_* macros with underscores, // MSVC prefixes the names of S_* macros with underscores,
and doesn't define any S_IS* macros. Define them ourselves */ // and doesn't define any S_IS* macros; define them ourselves
#ifdef _MSC_VER #ifdef _MSC_VER
# define S_IFMT _S_IFMT # define S_IFMT _S_IFMT
# define S_IFDIR _S_IFDIR # define S_IFDIR _S_IFDIR
# define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR) # define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR)
#endif #endif
/* MSVC doesn't use POSIX types or defines for `read` */ // MSVC doesn't use POSIX types or defines for `read`
#ifdef _MSC_VER #ifdef _MSC_VER
# include <io.h> # include <io.h>
# define STDIN_FILENO 0 # define STDIN_FILENO 0
@@ -46,7 +46,7 @@
# include <unistd.h> # include <unistd.h>
#endif #endif
/* MSVC doesn't support `[static N]` for array arguments from C99 or C11 */ // MSVC doesn't support `[static N]` for array arguments from C99 or C11
#ifdef _MSC_VER #ifdef _MSC_VER
# define MIN_NB_ELMS(N) # define MIN_NB_ELMS(N)
# define ARR_QUALS(...) # define ARR_QUALS(...)
@@ -63,8 +63,10 @@
# define O_RDWR _O_RDWR # define O_RDWR _O_RDWR
# define S_ISREG(field) ((field) & _S_IFREG) # define S_ISREG(field) ((field) & _S_IFREG)
# define O_BINARY _O_BINARY # define O_BINARY _O_BINARY
# define O_TEXT _O_TEXT
#elif !defined(O_BINARY) // Cross-compilers define O_BINARY #elif !defined(O_BINARY) // Cross-compilers define O_BINARY
# define O_BINARY 0 // POSIX says we shouldn't care! # define O_BINARY 0 // POSIX says we shouldn't care!
# define O_TEXT 0 // Assume that it's not defined either
#endif // _MSC_VER #endif // _MSC_VER
// Windows has stdin and stdout open as text by default, which we may not want // Windows has stdin and stdout open as text by default, which we may not want
@@ -72,7 +74,7 @@
# include <io.h> # include <io.h>
# define setmode(fd, mode) _setmode(fd, mode) # define setmode(fd, mode) _setmode(fd, mode)
#else #else
# define setmode(fd, mode) ((void)0) # define setmode(fd, mode) (0)
#endif #endif
#endif /* RGBDS_PLATFORM_H */ #endif // RGBDS_PLATFORM_H

View File

@@ -15,8 +15,7 @@ extern "C" {
#define PACKAGE_VERSION_MAJOR 0 #define PACKAGE_VERSION_MAJOR 0
#define PACKAGE_VERSION_MINOR 6 #define PACKAGE_VERSION_MINOR 6
#define PACKAGE_VERSION_PATCH 0 #define PACKAGE_VERSION_PATCH 1
#define PACKAGE_VERSION_RC 1
char const *get_package_version_string(void); char const *get_package_version_string(void);
@@ -24,4 +23,4 @@ char const *get_package_version_string(void);
} }
#endif #endif
#endif /* EXTERN_VERSION_H */ #endif // EXTERN_VERSION_H

View File

@@ -171,8 +171,8 @@ and
.It Sx JP HL .It Sx JP HL
.It Sx JP n16 .It Sx JP n16
.It Sx JP cc,n16 .It Sx JP cc,n16
.It Sx JR e8 .It Sx JR n16
.It Sx JR cc,e8 .It Sx JR cc,n16
.It Sx RET cc .It Sx RET cc
.It Sx RET .It Sx RET
.It Sx RETI .It Sx RETI
@@ -754,22 +754,34 @@ Cycles: 1
Bytes: 1 Bytes: 1
.Pp .Pp
Flags: None affected. Flags: None affected.
.Ss JR e8 .Ss JR n16
Relative Jump by adding Relative Jump to address
.Ar e8 .Ar n16 .
to the address of the instruction following the The address is encoded as a signed 8-bit offset from the address immediately following the
.Sy JR . .Ic JR
To clarify, an operand of 0 is equivalent to no jumping. instruction, so the target address
.Ar n16
must be between
.Sy -128
and
.Sy 127
bytes away.
For example:
.Bd -literal -offset indent
JR Label ; no-op; encoded offset of 0
Label:
JR Label ; infinite loop; encoded offset of -2
.Ed
.Pp .Pp
Cycles: 3 Cycles: 3
.Pp .Pp
Bytes: 2 Bytes: 2
.Pp .Pp
Flags: None affected. Flags: None affected.
.Ss JR cc,e8 .Ss JR cc,n16
Relative Jump by adding Relative Jump to address
.Ar e8 .Ar n16
to the current address if condition if condition
.Ar cc .Ar cc
is met. is met.
.Pp .Pp

View File

@@ -13,31 +13,32 @@
.Nd Game Boy assembler .Nd Game Boy assembler
.Sh SYNOPSIS .Sh SYNOPSIS
.Nm .Nm
.Op Fl EhLVvw .Op Fl EHhLlVvw
.Op Fl b Ar chars .Op Fl b Ar chars
.Op Fl D Ar name Ns Op = Ns Ar value .Op Fl D Ar name Ns Op = Ns Ar value
.Op Fl g Ar chars .Op Fl g Ar chars
.Op Fl i Ar path .Op Fl I Ar path
.Op Fl M Ar depend_file .Op Fl M Ar depend_file
.Op Fl MG .Op Fl MG
.Op Fl MP .Op Fl MP
.Op Fl MT Ar target_file .Op Fl MT Ar target_file
.Op Fl MQ Ar target_file .Op Fl MQ Ar target_file
.Op Fl o Ar out_file .Op Fl o Ar out_file
.Op Fl P Ar include_file
.Op Fl p Ar pad_value .Op Fl p Ar pad_value
.Op Fl Q Ar fix_precision
.Op Fl r Ar recursion_depth .Op Fl r Ar recursion_depth
.Op Fl W Ar warning .Op Fl W Ar warning
.Ar .Ar asmfile
.Sh DESCRIPTION .Sh DESCRIPTION
The The
.Nm .Nm
program creates an RGB object file from an assembly source file. program creates an RGB object file from an assembly source file.
The input The input
.Ar file .Ar asmfile
can be a file path, or can be a path to a file, or
.Cm \- .Cm \-
denoting to read from standard input.
.Cm stdin .
.Pp .Pp
Note that options can be abbreviated as long as the abbreviation is unambiguous: Note that options can be abbreviated as long as the abbreviation is unambiguous:
.Fl Fl verb .Fl Fl verb
@@ -66,25 +67,55 @@ Export all labels, including unreferenced and local labels.
.It Fl g Ar chars , Fl Fl gfx-chars Ar chars .It Fl g Ar chars , Fl Fl gfx-chars Ar chars
Change the four characters used for gfx constants. Change the four characters used for gfx constants.
The defaults are 0123. The defaults are 0123.
.It Fl h , Fl Fl halt-without-nop .It Fl H , Fl Fl nop-after-halt
By default, By default,
.Nm .Nm
inserts a inserts a
.Ic nop .Ic nop
instruction immediately after any instruction immediately after any
.Ic halt .Ic halt
instruction. instruction,
but this has been deprecated and prints a warning message the first time it occurs.
The The
.Fl h .Fl H
option disables this behavior. option opts into this insertion,
.It Fl i Ar path , Fl Fl include Ar path so no warning will be printed.
Add an include path. .It Fl h , Fl Fl halt-without-nop
Disables inserting a
.Ic nop
instruction immediately after any
.Ic halt
instruction.
.It Fl I Ar path , Fl Fl include Ar path
Add a new
.Dq include path ; Ar path
must point to a directory.
When a
.Ic INCLUDE
.Pq including the implicit one from Fl P
or
.Ic INCBIN
is attempted,
.Nm
first looks up the provided path from its working directory; if this fails, it tries again from each of the
.Dq include path
directories, in the order they were provided.
.It Fl L , Fl Fl preserve-ld .It Fl L , Fl Fl preserve-ld
Disable the optimization that turns loads of the form By default,
.Nm
optimizes loads of the form
.Ic LD [$FF00+n8],A .Ic LD [$FF00+n8],A
into the opcode into the opcode
.Ic LDH [$FF00+n8],A .Ic LDH [$FF00+n8],A ,
in order to have full control of the result in the final ROM. but this has been deprecated and prints a warning message the first time it occurs.
The
.Fl L
option disables this optimization.
.It Fl l , Fl Fl auto-ldh
Optimize loads of the form
.Ic LD [$FF00+n8],A
into the opcode
.Ic LDH [$FF00+n8],A .
.It Fl M Ar depend_file , Fl Fl dependfile Ar depend_file .It Fl M Ar depend_file , Fl Fl dependfile Ar depend_file
Print Print
.Xr make 1 .Xr make 1
@@ -97,6 +128,7 @@ This makes
.Nm .Nm
assume that missing files are auto-generated: when assume that missing files are auto-generated: when
.Ic INCLUDE .Ic INCLUDE
.Pq including the implicit one from Fl P
or or
.Ic INCBIN .Ic INCBIN
is attempted on a non-existent file, it is added as a dependency, then is attempted on a non-existent file, it is added as a dependency, then
@@ -127,11 +159,25 @@ characters, essentially
.Sq $ . .Sq $ .
.It Fl o Ar out_file , Fl Fl output Ar out_file .It Fl o Ar out_file , Fl Fl output Ar out_file
Write an object file to the given filename. Write an object file to the given filename.
.It Fl P Ar include_file , Fl Fl preinclude Ar include_file
Pre-include a file.
This acts as if a
.Ql Ic INCLUDE Qq Ar include_file
was read before the input
.Ar asmfile .
.It Fl p Ar pad_value , Fl Fl pad-value Ar pad_value .It Fl p Ar pad_value , Fl Fl pad-value Ar pad_value
When padding an image, pad with this value. When padding an image, pad with this value.
The default is 0x00. The default is 0x00.
.It Fl Q Ar fix_precision , Fl Fl q-precision Ar fix_precision
Use this as the precision of fixed-point numbers after the decimal point, unless they specify their own precision.
The default is 16, so fixed-point numbers are Q16.16 (since they are 32-bit integers).
The argument may start with a
.Ql \&.
to match the Q notation, for example,
.Ql Fl Q Ar .16 .
.It Fl r Ar recursion_depth , Fl Fl recursion-depth Ar recursion_depth .It Fl r Ar recursion_depth , Fl Fl recursion-depth Ar recursion_depth
Specifies the recursion depth at which RGBASM will assume being in an infinite loop. Specifies the recursion depth past which RGBASM will assume being in an infinite loop.
The default is 64.
.It Fl V , Fl Fl version .It Fl V , Fl Fl version
Print the version of the program and exit. Print the version of the program and exit.
.It Fl v , Fl Fl verbose .It Fl v , Fl Fl verbose
@@ -274,6 +320,19 @@ warns when an N-bit value's absolute value is 2**N or greater.
or just or just
.Fl Wtruncation .Fl Wtruncation
also warns when an N-bit value is less than -2**(N-1), which will not fit in two's complement encoding. also warns when an N-bit value is less than -2**(N-1), which will not fit in two's complement encoding.
.It Fl Wunmapped-char=
Warn when a character goes through charmap conversion but has no defined mapping.
.Fl Wunmapped-char=0
or
.Fl Wunmapped-char
disables this warning.
.Fl Wunmapped-char=1
or just
.Fl Wunmapped-char
only warns if the active charmap is not empty.
.Fl Wunmapped-char=2
warns if the active charmap is empty, and/or is not the default charmap
.Sq main .
.It Fl Wno-user .It Fl Wno-user
Warn when the Warn when the
.Ic WARN .Ic WARN

View File

@@ -18,7 +18,7 @@ This is the full description of the language used by
The description of the instructions supported by the Game Boy CPU is in The description of the instructions supported by the Game Boy CPU is in
.Xr gbz80 7 . .Xr gbz80 7 .
.Pp .Pp
It is strongly recommended to have some familiarity with the Game Boy hardware before reading this document. It is advisable to have some familiarity with the Game Boy hardware before reading this document.
RGBDS is specifically targeted at the Game Boy, and thus a lot of its features tie directly to its concepts. RGBDS is specifically targeted at the Game Boy, and thus a lot of its features tie directly to its concepts.
This document is not intended to be a Game Boy hardware reference. This document is not intended to be a Game Boy hardware reference.
.Pp .Pp
@@ -57,27 +57,25 @@ and ending with
.Ql */ . .Ql */ .
It can be split across multiple lines, or occur in the middle of an expression: It can be split across multiple lines, or occur in the middle of an expression:
.Bd -literal -offset indent .Bd -literal -offset indent
X = /* the value of x DEF X = /* the value of x
should be 3 */ 3 should be 3 */ 3
.Ed .Ed
.Pp .Pp
Sometimes lines can be too long and it may be necessary to split them. Sometimes lines can be too long and it may be necessary to split them.
To do so, put a backslash at the end of the line: To do so, put a backslash at the end of the line:
.Bd -literal -offset indent .Bd -literal -offset indent
DB 1, 2, 3,\ \[rs] DB 1, 2, 3,\ \e
4, 5, 6,\ \[rs]\ ;\ Put it before any comments 4, 5, 6,\ \e\ ;\ Put it before any comments
7, 8, 9 7, 8, 9
DB "Hello,\ \[rs]\ \ ;\ Space before the \[rs] is included DB "Hello,\ \e\ \ ;\ Space before the \e is included
world!"\ \ \ \ \ \ \ \ \ \ \ ;\ Any leading space is included world!"\ \ \ \ \ \ \ \ \ \ \ ;\ Any leading space is included
.Ed .Ed
.Ss Symbol interpolation .Ss Symbol interpolation
A funky feature is A funky feature is writing a symbol between
.Ql {symbol} .Ql {braces} ,
within a string, called called
.Dq symbol interpolation . .Dq symbol interpolation .
This will paste the contents of This will paste the symbol's contents as if they were part of the source file.
.Ql symbol
as if they were part of the source file.
If it is a string symbol, its characters are simply inserted as-is. If it is a string symbol, its characters are simply inserted as-is.
If it is a numeric symbol, its value is converted to hexadecimal notation with a dollar sign If it is a numeric symbol, its value is converted to hexadecimal notation with a dollar sign
.Sq $ .Sq $
@@ -85,7 +83,7 @@ prepended.
.Pp .Pp
Symbol interpolations can be nested, too! Symbol interpolations can be nested, too!
.Bd -literal -offset indent .Bd -literal -offset indent
DEF topic EQUS "life, the universe, and \[rs]"everything\[rs]"" DEF topic EQUS "life, the universe, and \e"everything\e""
DEF meaning EQUS "answer" DEF meaning EQUS "answer"
;\ Defines answer = 42 ;\ Defines answer = 42
DEF {meaning} = 42 DEF {meaning} = 42
@@ -171,19 +169,19 @@ Examples:
.Bd -literal -offset indent .Bd -literal -offset indent
SECTION "Test", ROM0[2] SECTION "Test", ROM0[2]
X: ;\ This works with labels **whose address is known** X: ;\ This works with labels **whose address is known**
Y = 3 ;\ This also works with variables DEF Y = 3 ;\ This also works with variables
SUM equ X + Y ;\ And likewise with numeric constants DEF SUM EQU X + Y ;\ And likewise with numeric constants
; Prints "%0010 + $3 == 5" ; Prints "%0010 + $3 == 5"
PRINTLN "{#05b:X} + {#x:Y} == {d:SUM}" PRINTLN "{#05b:X} + {#x:Y} == {d:SUM}"
rsset 32 rsset 32
PERCENT rb 1 ;\ Same with offset constants DEF PERCENT rb 1 ;\ Same with offset constants
VALUE = 20 DEF VALUE = 20
RESULT = MUL(20.0, 0.32) DEF RESULT = MUL(20.0, 0.32)
; Prints "32% of 20 = 6.40" ; Prints "32% of 20 = 6.40"
PRINTLN "{d:PERCENT}% of {d:VALUE} = {f:RESULT}" PRINTLN "{d:PERCENT}% of {d:VALUE} = {f:RESULT}"
WHO equs STRLWR("WORLD") DEF WHO EQUS STRLWR("WORLD")
; Prints "Hello world!" ; Prints "Hello world!"
PRINTLN "Hello {s:WHO}!" PRINTLN "Hello {s:WHO}!"
.Ed .Ed
@@ -208,13 +206,14 @@ section.
The instructions in the macro-language generally require constant expressions. The instructions in the macro-language generally require constant expressions.
.Ss Numeric formats .Ss Numeric formats
There are a number of numeric formats. There are a number of numeric formats.
.Bl -column -offset indent "Fixed point (Q16.16)" "Prefix" .Bl -column -offset indent "Precise fixed-point" "Prefix"
.It Sy Format type Ta Sy Prefix Ta Sy Accepted characters .It Sy Format type Ta Sy Prefix Ta Sy Accepted characters
.It Hexadecimal Ta $ Ta 0123456789ABCDEF .It Hexadecimal Ta $ Ta 0123456789ABCDEF
.It Decimal Ta none Ta 0123456789 .It Decimal Ta none Ta 0123456789
.It Octal Ta & Ta 01234567 .It Octal Ta & Ta 01234567
.It Binary Ta % Ta 01 .It Binary Ta % Ta 01
.It Fixed point (Q16.16) Ta none Ta 01234.56789 .It Fixed-point Ta none Ta 01234.56789
.It Precise fixed-point Ta none Ta 12.34q8
.It Character constant Ta none Ta \(dqABYZ\(dq .It Character constant Ta none Ta \(dqABYZ\(dq
.It Gameboy graphics Ta \` Ta 0123 .It Gameboy graphics Ta \` Ta 0123
.El .El
@@ -301,9 +300,19 @@ and
.Ic \&! .Ic \&!
returns 1 if the operand was 0, and 0 otherwise. returns 1 if the operand was 0, and 0 otherwise.
.Ss Fixed-point expressions .Ss Fixed-point expressions
Fixed-point numbers are basically normal (32-bit) integers, which count 65536ths instead of entire units, offering better precision than integers but limiting the range of values. Fixed-point numbers are basically normal (32-bit) integers, which count fractions instead of whole numbers.
The upper 16 bits are used for the integer part and the lower 16 bits are used for the fraction (65536ths). They offer better precision than integers but limit the range of values.
Since they are still akin to integers, you can use them in normal integer expressions, and some integer operators like By default, the upper 16 bits are used for the integer part and the lower 16 bits are used for the fraction (65536ths).
The default number of fractional bits can be changed with the
.Fl Q
command-line option.
You can also specify a precise fixed-point value by appending a
.Dq q
to it followed by the number of fractional bits, such as
.Ql 12.34q8 .
.Pp
Since fixed-point values are still just integers, you can use them in normal integer expressions.
Some integer operators like
.Sq + .Sq +
and and
.Sq - .Sq -
@@ -317,8 +326,9 @@ delim $$
.EN .EN
.Bl -column -offset indent "ATAN2(x, y)" .Bl -column -offset indent "ATAN2(x, y)"
.It Sy Name Ta Sy Operation .It Sy Name Ta Sy Operation
.It Fn DIV x y Ta $x \[di] y$ .It Fn DIV x y Ta Fixed-point division $( x \[di] y ) \[mu] ( 2 ^ precision )$
.It Fn MUL x y Ta $x \[mu] y$ .It Fn MUL x y Ta Fixed-point multiplication $( x \[mu] y ) \[di] ( 2 ^ precision )$
.It Fn FMOD x y Ta Fixed-point modulo $( x % y ) \[di] ( 2 ^ precision )$
.It Fn POW x y Ta $x$ to the $y$ power .It Fn POW x y Ta $x$ to the $y$ power
.It Fn LOG x y Ta Logarithm of $x$ to the base $y$ .It Fn LOG x y Ta Logarithm of $x$ to the base $y$
.It Fn ROUND x Ta Round $x$ to the nearest integer .It Fn ROUND x Ta Round $x$ to the nearest integer
@@ -336,44 +346,53 @@ delim $$
delim off delim off
.EN .EN
.Pp .Pp
All of these fixed-point functions can take an optional final argument, which is the precision to use.
For example,
.Ql MUL(6.0q8, 7.0q8, 8)
will evaluate to
.Ql 42.0q8
no matter what value is set as the current
.Cm Q
option.
.Pp
The trigonometry functions ( The trigonometry functions (
.Ic SIN , .Ic SIN ,
.Ic COS , .Ic COS ,
.Ic TAN , .Ic TAN ,
etc) are defined in terms of a circle divided into 65535.0 degrees. etc) are defined in terms of a circle divided into 1.0 "turns" (equal to 2pi radians or 360 degrees).
.Pp .Pp
These functions are useful for automatic generation of various tables. These functions are useful for automatic generation of various tables.
For example: For example:
.Bd -literal -offset indent .Bd -literal -offset indent
; Generate a 256-byte sine table with values in the range [0, 128] ; Generate a table of sine values from sin(0.0) to sin(1.0), with
; (shifted and scaled from the range [-1.0, 1.0]) ; amplitude scaled from [-1.0, 1.0] to [0.0, 128.0]
ANGLE = 0.0 DEF turns = 0.0
REPT 256 REPT 256
db (MUL(64.0, SIN(ANGLE)) + 64.0) >> 16 db MUL(64.0, SIN(turns) + 1.0) >> 16
ANGLE = ANGLE + 256.0 ; 256.0 = 65536 degrees / 256 entries DEF turns += 1.0 / 256
ENDR ENDR
.Ed .Ed
.Ss String expressions .Ss String expressions
The most basic string expression is any number of characters contained in double quotes The most basic string expression is any number of characters contained in double quotes
.Pq Ql \&"for instance" . .Pq Ql \&"for instance" .
The backslash character The backslash character
.Ql \[rs] .Ql \e
is special in that it causes the character following it to be is special in that it causes the character following it to be
.Dq escaped , .Dq escaped ,
meaning that it is treated differently from normal. meaning that it is treated differently from normal.
There are a number of escape sequences you can use within a string: There are a number of escape sequences you can use within a string:
.Bl -column -offset indent "Qo \[rs]1 Qc \[en] Qo \[rs]9 Qc" .Bl -column -offset indent "Qo \e1 Qc \[en] Qo \e9 Qc"
.It Sy String Ta Sy Meaning .It Sy String Ta Sy Meaning
.It Ql \[rs]\[rs] Ta Produces a backslash .It Ql \e\e Ta Produces a backslash
.It Ql \[rs]" Ta Produces a double quote without terminating .It Ql \e" Ta Produces a double quote without terminating
.It Ql \[rs]{ Ta Curly bracket left .It Ql \e{ Ta Curly bracket left
.It Ql \[rs]} Ta Curly bracket right .It Ql \e} Ta Curly bracket right
.It Ql \[rs]n Ta Newline ($0A) .It Ql \en Ta Newline ($0A)
.It Ql \[rs]r Ta Carriage return ($0D) .It Ql \er Ta Carriage return ($0D)
.It Ql \[rs]t Ta Tab ($09) .It Ql \et Ta Tab ($09)
.It Qo \[rs]1 Qc \[en] Qo \[rs]9 Qc Ta Macro argument (Only in the body of a macro; see Sx Invoking macros ) .It Qo \e1 Qc \[en] Qo \e9 Qc Ta Macro argument (Only in the body of a macro; see Sx Invoking macros )
.It Ql \[rs]# Ta All Dv _NARG No macro arguments, separated by commas (Only in the body of a macro) .It Ql \e# Ta All Dv _NARG No macro arguments, separated by commas (Only in the body of a macro)
.It Ql \[rs]@ Ta Label name suffix (Only in the body of a macro or a Ic REPT No block) .It Ql \e@ Ta Label name suffix (Only in the body of a macro or a Ic REPT No block)
.El .El
(Note that some of those can be used outside of strings, when noted further in this document.) (Note that some of those can be used outside of strings, when noted further in this document.)
.Pp .Pp
@@ -381,9 +400,9 @@ Multi-line strings are contained in triple quotes
.Pq Ql \&"\&"\&"for instance\&"\&"\&" . .Pq Ql \&"\&"\&"for instance\&"\&"\&" .
Escape sequences work the same way in multi-line strings; however, literal newline Escape sequences work the same way in multi-line strings; however, literal newline
characters will be included as-is, without needing to escape them with characters will be included as-is, without needing to escape them with
.Ql \[rs]r .Ql \er
or or
.Ql \[rs]n . .Ql \en .
.Pp .Pp
The following functions operate on string expressions. The following functions operate on string expressions.
Most of them return a string, however some of these functions actually return an integer and can be used as part of an integer expression! Most of them return a string, however some of these functions actually return an integer and can be used as part of an integer expression!
@@ -462,6 +481,11 @@ is a label, it returns the bank number the label is in.
The result may be constant if The result may be constant if
.Nm .Nm
is able to compute it. is able to compute it.
.It Fn SECTION symbol Ta Returns the name of the section that
.Ar symbol
is in.
.Ar symbol
must have been defined already.
.It Fn SIZEOF arg Ta Returns the size of the section named .It Fn SIZEOF arg Ta Returns the size of the section named
.Ar arg . .Ar arg .
The result is not constant, since only RGBLINK can compute its value. The result is not constant, since only RGBLINK can compute its value.
@@ -945,9 +969,9 @@ assuming the section ends up at
.Ad $80C0 : .Ad $80C0 :
.Bd -literal -offset indent .Bd -literal -offset indent
SECTION "Player tiles", VRAM SECTION "Player tiles", VRAM
PlayerTiles: vPlayerTiles:
ds 6 * 16 ds 6 * 16
.end \&.end
.Ed .Ed
.Pp .Pp
A label's location (and thus value) is usually not determined until the linking stage, so labels usually cannot be used as constants. A label's location (and thus value) is usually not determined until the linking stage, so labels usually cannot be used as constants.
@@ -1001,7 +1025,7 @@ DEF ARRAY_SIZE EQU 4
DEF COUNT = 2 DEF COUNT = 2
DEF COUNT = 3 DEF COUNT = 3
DEF COUNT = ARRAY_SIZE + COUNT DEF COUNT = ARRAY_SIZE + COUNT
COUNT = COUNT*2 DEF COUNT *= 2
;\ COUNT now has the value 14 ;\ COUNT now has the value 14
.Ed .Ed
.Pp .Pp
@@ -1054,7 +1078,7 @@ This can be used, for example, to update a constant using a macro, without makin
def NUM_ITEMS equ 0 def NUM_ITEMS equ 0
MACRO add_item MACRO add_item
redef NUM_ITEMS equ NUM_ITEMS + 1 redef NUM_ITEMS equ NUM_ITEMS + 1
def ITEM_{02x:NUM_ITEMS} equ \[rs]1 def ITEM_{02x:NUM_ITEMS} equ \e1
ENDM ENDM
add_item 1 add_item 1
add_item 4 add_item 4
@@ -1119,7 +1143,7 @@ will not expand string constants in their names.
DEF COUNTREG EQUS "[hl+]" DEF COUNTREG EQUS "[hl+]"
ld a,COUNTREG ld a,COUNTREG
DEF PLAYER_NAME EQUS "\[rs]"John\[rs]"" DEF PLAYER_NAME EQUS "\e"John\e""
db PLAYER_NAME db PLAYER_NAME
.Ed .Ed
.Pp .Pp
@@ -1131,7 +1155,7 @@ This will be interpreted as:
.Pp .Pp
String constants can also be used to define small one-line macros: String constants can also be used to define small one-line macros:
.Bd -literal -offset indent .Bd -literal -offset indent
DEF pusha EQUS "push af\[rs]npush bc\[rs]npush de\[rs]npush hl\[rs]n" DEF pusha EQUS "push af\enpush bc\enpush de\enpush hl\en"
.Ed .Ed
.Pp .Pp
Note that colons Note that colons
@@ -1213,14 +1237,16 @@ The example above defines
.Ql MyMacro .Ql MyMacro
as a new macro. as a new macro.
String constants are not expanded within the name of the macro. String constants are not expanded within the name of the macro.
You may use the older syntax .Pp
(Using the
.Em deprecated
older syntax
.Ql MyMacro: MACRO .Ql MyMacro: MACRO
instead of instead of
.Ql MACRO MyMacro , .Ql MACRO MyMacro ,
with a single colon with a single colon
.Ql \&: .Ql \&:
following the macro's name. following the macro's name, string constants may be expanded for the name.)
With the older syntax, string constants may be expanded for the name.
.Pp .Pp
Macros can't be exported or imported. Macros can't be exported or imported.
.Pp .Pp
@@ -1238,18 +1264,18 @@ ENDM
But this will: But this will:
.Bd -literal -offset indent .Bd -literal -offset indent
MACRO outer MACRO outer
DEF definition EQUS "MACRO inner\[rs]nPRINTLN \[rs]"Hello!\[rs]"\[rs]nENDM" DEF definition EQUS "MACRO inner\enPRINTLN \e"Hello!\e"\enENDM"
definition definition
PURGE definition PURGE definition
ENDM ENDM
.Ed .Ed
.Pp .Pp
Macro arguments support all the escape sequences of strings, as well as Macro arguments support all the escape sequences of strings, as well as
.Ql \[rs], .Ql \e,
to escape commas, as well as to escape commas, as well as
.Ql \[rs]( .Ql \e(
and and
.Ql \[rs]) .Ql \e)
to escape parentheses, since those otherwise separate and enclose arguments, respectively. to escape parentheses, since those otherwise separate and enclose arguments, respectively.
.Ss Exporting and importing symbols .Ss Exporting and importing symbols
Importing and exporting of symbols is a feature that is very useful when your project spans many source files and, for example, you need to jump to a routine defined in another file. Importing and exporting of symbols is a feature that is very useful when your project spans many source files and, for example, you need to jump to a routine defined in another file.
@@ -1310,7 +1336,7 @@ Note also that only exported symbols will appear in symbol and map files produce
.Ss Purging symbols .Ss Purging symbols
.Ic PURGE .Ic PURGE
allows you to completely remove a symbol from the symbol table as if it had never existed. allows you to completely remove a symbol from the symbol table as if it had never existed.
.Em USE WITH EXTREME CAUTION!!! .Em USE WITH EXTREME CAUTION!
I can't stress this enough, I can't stress this enough,
.Sy you seriously need to know what you are doing . .Sy you seriously need to know what you are doing .
DON'T purge a symbol that you use in expressions the linker needs to calculate. DON'T purge a symbol that you use in expressions the linker needs to calculate.
@@ -1324,13 +1350,11 @@ DEF AOLer EQUS "Me too"
String constants are not expanded within the symbol names. String constants are not expanded within the symbol names.
.Ss Predeclared symbols .Ss Predeclared symbols
The following symbols are defined by the assembler: The following symbols are defined by the assembler:
.Bl -column -offset indent "EQUS" "__ISO_8601_LOCAL__" .Bl -column -offset indent "__ISO_8601_LOCAL__" "EQUS"
.It Sy Name Ta Sy Type Ta Sy Contents .It Sy Name Ta Sy Type Ta Sy Contents
.It Dv @ Ta Ic EQU Ta PC value (essentially, the current memory address) .It Dv @ Ta Ic EQU Ta PC value (essentially, the current memory address)
.It Dv _RS Ta Ic = Ta _RS Counter .It Dv _RS Ta Ic = Ta _RS Counter
.It Dv _NARG Ta Ic EQU Ta Number of arguments passed to macro, updated by Ic SHIFT .It Dv _NARG Ta Ic EQU Ta Number of arguments passed to macro, updated by Ic SHIFT
.It Dv __LINE__ Ta Ic EQU Ta The current line number
.It Dv __FILE__ Ta Ic EQUS Ta The current filename
.It Dv __DATE__ Ta Ic EQUS Ta Today's date .It Dv __DATE__ Ta Ic EQUS Ta Today's date
.It Dv __TIME__ Ta Ic EQUS Ta The current time .It Dv __TIME__ Ta Ic EQUS Ta The current time
.It Dv __ISO_8601_LOCAL__ Ta Ic EQUS Ta ISO 8601 timestamp (local) .It Dv __ISO_8601_LOCAL__ Ta Ic EQUS Ta ISO 8601 timestamp (local)
@@ -1547,19 +1571,19 @@ ENDM
.Pp .Pp
This is fine, but only if you use the macro no more than once per scope. This is fine, but only if you use the macro no more than once per scope.
To get around this problem, there is the escape sequence To get around this problem, there is the escape sequence
.Ic \[rs]@ .Ic \e@
that expands to a unique string. that expands to a unique string.
.Pp .Pp
.Ic \[rs]@ .Ic \e@
also works in also works in
.Ic REPT .Ic REPT
blocks. blocks.
.Bd -literal -offset indent .Bd -literal -offset indent
MACRO LoopyMacro MACRO LoopyMacro
xor a,a xor a,a
\&.loop\[rs]@ ld [hl+],a \&.loop\e@ ld [hl+],a
dec c dec c
jr nz,.loop\[rs]@ jr nz,.loop\e@
ENDM ENDM
.Ed .Ed
.Pp .Pp
@@ -1579,18 +1603,18 @@ which references the same macro, which has the same problem.
.Pp .Pp
It's possible to pass arguments to macros as well! It's possible to pass arguments to macros as well!
You retrieve the arguments by using the escape sequences You retrieve the arguments by using the escape sequences
.Ic \[rs]1 .Ic \e1
through through
.Ic \[rs]9 , \[rs]1 .Ic \e9 , \e1
being the first argument specified on the macro invocation. being the first argument specified on the macro invocation.
.Bd -literal -offset indent .Bd -literal -offset indent
MACRO LoopyMacro MACRO LoopyMacro
ld hl,\[rs]1 ld hl,\e1
ld c,\[rs]2 ld c,\e2
xor a,a xor a,a
\&.loop\[rs]@ ld [hl+],a \&.loop\e@ ld [hl+],a
dec c dec c
jr nz,.loop\[rs]@ jr nz,.loop\e@
ENDM ENDM
.Ed .Ed
.Pp .Pp
@@ -1603,14 +1627,14 @@ LoopyMacro MyVars,54
Arguments are passed as string constants, although there's no need to enclose them in quotes. Arguments are passed as string constants, although there's no need to enclose them in quotes.
Thus, an expression will not be evaluated first but kind of copy-pasted. Thus, an expression will not be evaluated first but kind of copy-pasted.
This means that it's probably a very good idea to use brackets around This means that it's probably a very good idea to use brackets around
.Ic \[rs]1 .Ic \e1
to to
.Ic \[rs]9 .Ic \e9
if you perform further calculations on them. if you perform further calculations on them.
For instance, consider the following: For instance, consider the following:
.Bd -literal -offset indent .Bd -literal -offset indent
MACRO print_double MACRO print_double
PRINTLN \[rs]1 * 2 PRINTLN \e1 * 2
ENDM ENDM
print_double 1 + 2 print_double 1 + 2
.Ed .Ed
@@ -1625,15 +1649,15 @@ Line continuations work as usual inside macros or lists of macro arguments.
However, some characters need to be escaped, as in the following example: However, some characters need to be escaped, as in the following example:
.Bd -literal -offset indent .Bd -literal -offset indent
MACRO PrintMacro1 MACRO PrintMacro1
PRINTLN STRCAT(\[rs]1) PRINTLN STRCAT(\e1)
ENDM ENDM
PrintMacro1 "Hello "\[rs], \[rs] PrintMacro1 "Hello "\e, \e
"world" "world"
MACRO PrintMacro2 MACRO PrintMacro2
PRINT \[rs]1 PRINT \e1
ENDM ENDM
PrintMacro2 STRCAT("Hello ", \[rs] PrintMacro2 STRCAT("Hello ", \e
"world\[rs]n") "world\en")
.Ed .Ed
.Pp .Pp
The comma in The comma in
@@ -1643,34 +1667,34 @@ The comma in
.Ql PrintMacro2 .Ql PrintMacro2
does not need escaping because it is inside parentheses, similar to macro arguments in C. does not need escaping because it is inside parentheses, similar to macro arguments in C.
The backslash in The backslash in
.Ql \[rs]n .Ql \en
also does not need escaping because string literals work as usual inside macro arguments. also does not need escaping because string literals work as usual inside macro arguments.
.Pp .Pp
Since there are only nine digits, you can only access the first nine macro arguments like this. Since there are only nine digits, you can only access the first nine macro arguments like this.
To use the rest, you need to put the multi-digit argument number in angle brackets, like To use the rest, you need to put the multi-digit argument number in angle brackets, like
.Ql \[rs]<10> . .Ql \e<10> .
This bracketed syntax supports decimal numbers and numeric constant symbols. This bracketed syntax supports decimal numbers and numeric constant symbols.
For example, For example,
.Ql \[rs]<_NARG> .Ql \e<_NARG>
will get the last argument. will get the last argument.
.Pp .Pp
Other macro arguments and symbol interpolations will be expanded inside the angle brackets. Other macro arguments and symbol interpolations will be expanded inside the angle brackets.
For example, if For example, if
.Ql \[rs]1 .Ql \e1
is is
.Ql 13 , .Ql 13 ,
then then
.Ql \[rs]<\[rs]1> .Ql \e<\e1>
will expand to will expand to
.Ql \[rs]<13> . .Ql \e<13> .
Or if Or if
.Ql v10 = 42 .Ql v10 = 42
and and
.Ql x = 10 , .Ql x = 10 ,
then then
.Ql \[rs]<v{d:x}> .Ql \e<v{d:x}>
will expand to will expand to
.Ql \[rs]<42> . .Ql \e<42> .
.Pp .Pp
Another way to access more than nine macro arguments is the Another way to access more than nine macro arguments is the
.Ic SHIFT .Ic SHIFT
@@ -1678,11 +1702,11 @@ command, a special command only available in macros.
It will shift the arguments by one to the left, and decrease It will shift the arguments by one to the left, and decrease
.Dv _NARG .Dv _NARG
by 1. by 1.
.Ic \[rs]1 .Ic \e1
will get the value of will get the value of
.Ic \[rs]2 , \[rs]2 .Ic \e2 , \e2
will get the value of will get the value of
.Ic \[rs]3 , .Ic \e3 ,
and so forth. and so forth.
.Pp .Pp
.Ic SHIFT .Ic SHIFT
@@ -1701,9 +1725,9 @@ and
commands print text and values to the standard output. commands print text and values to the standard output.
Useful for debugging macros, or wherever you may feel the need to tell yourself some important information. Useful for debugging macros, or wherever you may feel the need to tell yourself some important information.
.Bd -literal -offset indent .Bd -literal -offset indent
PRINT "Hello world!\[rs]n" PRINT "Hello world!\en"
PRINTLN "Hello world!" PRINTLN "Hello world!"
PRINT _NARG, " arguments\[rs]n" PRINT _NARG, " arguments\en"
PRINTLN "sum: ", 2+3, " product: ", 2*3 PRINTLN "sum: ", 2+3, " product: ", 2*3
PRINTLN "Line #", __LINE__ PRINTLN "Line #", __LINE__
PRINTLN STRFMT("E = %f", 2.718) PRINTLN STRFMT("E = %f", 2.718)
@@ -1717,7 +1741,7 @@ For different formats, use
.Ic STRFMT . .Ic STRFMT .
.It Ic PRINTLN .It Ic PRINTLN
prints out each of its comma-separated arguments, if any, followed by a line feed prints out each of its comma-separated arguments, if any, followed by a line feed
.Pq Ql \[rs]n . .Pq Ql \en .
.El .El
.Ss Automatically repeating blocks of code .Ss Automatically repeating blocks of code
Suppose you want to unroll a time consuming loop without copy-pasting it. Suppose you want to unroll a time consuming loop without copy-pasting it.
@@ -1741,17 +1765,16 @@ You can also use
.Ic REPT .Ic REPT
to generate tables on the fly: to generate tables on the fly:
.Bd -literal -offset indent .Bd -literal -offset indent
; Generate a 256-byte sine table with values in the range [0, 128] ; Generate a table of square values from 0**2 = 0 to 100**2 = 10000
; (shifted and scaled from the range [-1.0, 1.0]) DEF x = 0
ANGLE = 0.0 REPT 101
REPT 256 dw x * x
db (MUL(64.0, SIN(ANGLE)) + 64.0) >> 16 DEF x += 1
ANGLE = ANGLE + 256.0 ; 256.0 = 65536 degrees / 256 entries
ENDR ENDR
.Ed .Ed
.Pp .Pp
As in macros, you can also use the escape sequence As in macros, you can also use the escape sequence
.Ic \[rs]@ . .Ic \e@ .
.Ic REPT .Ic REPT
blocks can be nested. blocks can be nested.
.Pp .Pp
@@ -1804,11 +1827,29 @@ The
value will be updated by value will be updated by
.Ar step .Ar step
until it reaches or exceeds until it reaches or exceeds
.Ar stop . .Ar stop ,
i.e. it covers the half-open range from
.Ar start
(inclusive) to
.Ar stop
(exclusive).
The variable
.Ar V
will be assigned this value at the beginning of each new iteration; any changes made to it within the
.Ic FOR
loop's body will be overwritten.
So the symbol
.Ar V
need not be already defined before any iterations of the
.Ic FOR
loop, but it must be a variable
.Pq Sx Variables
if so.
For example: For example:
.Bd -literal -offset indent .Bd -literal -offset indent
FOR V, 4, 25, 5 FOR V, 4, 25, 5
PRINT "{d:V} " PRINT "{d:V} "
DEF V *= 2
ENDR ENDR
PRINTLN "done {d:V}" PRINTLN "done {d:V}"
.Ed .Ed
@@ -1821,7 +1862,7 @@ This will print:
Just like with Just like with
.Ic REPT .Ic REPT
blocks, you can use the escape sequence blocks, you can use the escape sequence
.Ic \[rs]@ .Ic \e@
inside of inside of
.Ic FOR .Ic FOR
blocks, and they can be nested. blocks, and they can be nested.
@@ -1942,6 +1983,13 @@ calls infinitely (or until you run out of memory, whichever comes first).
.Bd -literal -offset indent .Bd -literal -offset indent
INCLUDE "irq.inc" INCLUDE "irq.inc"
.Ed .Ed
.Pp
You may also implicitly
.Ic INCLUDE
a file before the source file with the
.Fl P
option of
.Xr rgbasm 1 .
.Ss Conditional assembling .Ss Conditional assembling
The four commands The four commands
.Ic IF , ELIF , ELSE , .Ic IF , ELIF , ELSE ,
@@ -2013,17 +2061,15 @@ POPO
The options that The options that
.Ic OPT .Ic OPT
can modify are currently: can modify are currently:
.Cm b , g , p , r , h , L , .Cm b , g , p , Q , r , h , L ,
and and
.Cm W . .Cm W .
The Boolean flag options The Boolean flag options
.Cm h .Cm H , h , L ,
and and
.Cm L .Cm l
can be negated as can be negated like
.Ql OPT !h .Ql OPT !H
and
.Ql OPT !L
to act like omitting them from the command-line. to act like omitting them from the command-line.
.Pp .Pp
.Ic POPO .Ic POPO

View File

@@ -16,251 +16,381 @@ This is the description of the object files used by
.Xr rgbasm 1 .Xr rgbasm 1
and and
.Xr rgblink 1 . .Xr rgblink 1 .
.Em Please note that the specifications may change . .Em Please note that the specification is not stable yet.
This toolchain is in development and new features may require adding more information to the current format, or modifying some fields, which would break compatibility with older versions. RGBDS is still in active development, and some new features require adding more information to the object file, or modifying some fields, both of which break compatibility with older versions.
.Sh FILE STRUCTURE .Sh FILE STRUCTURE
The following types are used: The following types are used:
.Pp .Pp
.Ar LONG .Cm LONG
is a 32-bit integer stored in little-endian format. is a 32-bit integer stored in little-endian format.
.Ar BYTE .Cm BYTE
is an 8-bit integer. is an 8-bit integer.
.Ar STRING .Cm STRING
is a 0-terminated string of is a 0-terminated string of
.Ar BYTE . .Cm BYTE .
.Bd -literal Brackets after a type
; Header .Pq e.g. Cm LONG Ns Bq Ar n
indicate
BYTE ID[4] ; "RGB9" .Ar n
LONG RevisionNumber ; The format's revision number this file uses. consecutive elements
LONG NumberOfSymbols ; The number of symbols used in this file. .Pq here, Cm LONG Ns s .
LONG NumberOfSections ; The number of sections used in this file. All items are contiguous, with no padding anywhere\(emthis also means that they may not be aligned in the file!
.Pp
; File info .Cm REPT Ar n
indicates that the fields between the
LONG NumberOfNodes ; The number of nodes contained in this file. .Cm REPT
and corresponding
REPT NumberOfNodes ; IMPORTANT NOTE: the nodes are actually written in .Cm ENDR
; **reverse** order, meaning the node with ID 0 is are repeated
; the last one in the file! .Ar n
times.
LONG ParentID ; ID of the parent node, -1 means this is the root. .Pp
All IDs refer to objects within the file; for example, symbol ID $0001 refers to the second symbol defined in
LONG ParentLineNo ; Line at which the parent context was exited. .Em this
; Meaningless on the root node. object file's
.Sx Symbols
BYTE Type ; 0 = REPT node array.
; 1 = File node The only exception is the
; 2 = Macro node .Sx Source file info
nodes, whose IDs are backwards, i.e. source node ID $0000 refers to the
IF Type != 0 ; If the node is not a REPT... .Em last
node in the array, not the first one.
STRING Name ; The node's name: either a file name, or macro name References to other object files are made by imports (symbols), by name (sections), etc.\(embut never by ID.
; prefixed by its definition file name. .Ss Header
.Bl -tag -width Ds -compact
ELSE ; If the node is a REPT, it also contains the iter .It Cm BYTE Ar Magic[4]
; counts of all the parent REPTs. "RGB9"
.It Cm LONG Ar RevisionNumber
LONG Depth ; Size of the array below. The format's revision number this file uses.
.Pq This is always in the same place in all revisions.
LONG Iter[Depth] ; The number of REPT iterations by increasing depth. .It Cm LONG Ar NumberOfSymbols
How many symbols are defined in this object file.
ENDC .It Cm LONG Ar NumberOfSections
How many sections are defined in this object file.
ENDR .El
.Ss Source file info
; Symbols .Bl -tag -width Ds -compact
.It Cm LONG Ar NumberOfNodes
REPT NumberOfSymbols ; Number of symbols defined in this object file. The number of source context nodes contained in this file.
.It Cm REPT Ar NumberOfNodes
STRING Name ; The name of this symbol. Local symbols are stored .Bl -tag -width Ds -compact
; as "Scope.Symbol". .It Cm LONG Ar ParentID
ID of the parent node, -1 meaning that this is the root node.
BYTE Type ; 0 = LOCAL symbol only used in this file. .Pp
; 1 = IMPORT this symbol from elsewhere .Sy Important :
; 2 = EXPORT this symbol to other objects. the nodes are actually written in
.Sy reverse
IF (Type & 0x7F) != 1 ; If symbol is defined in this object file. order, meaning the node with ID 0 is the last one in the list!
.It Cm LONG Ar ParentLineNo
LONG SourceFile ; File where the symbol is defined. Line at which the parent node's context was exited; meaningless for the root node.
.It Cm BYTE Ar Type
LONG LineNum ; Line number in the file where the symbol is defined. .Bl -column "Value" -compact
.It Sy Value Ta Sy Meaning
LONG SectionID ; The section number (of this object file) in which .It 0 Ta REPT node
; this symbol is defined. If it doesn't belong to any .It 1 Ta File node
; specific section (like a constant), this field has .It 2 Ta Macro node
; the value -1. .El
.It Cm IF Ar Type No \(!= 0
LONG Value ; The symbols value. It's the offset into that If the node is not a REPT node...
; symbol's section. .Pp
.Bl -tag -width Ds -compact
ENDC .It Cm STRING Ar Name
The node's name: either a file name, or the macro's name prefixes by its definition's file name
ENDR .Pq e.g. Ql src/includes/defines.asm::error .
.El
; Sections .It Cm ELSE
If the node is a REPT, it also contains the iteration counter of all parent REPTs.
REPT NumberOfSections .Pp
STRING Name ; Name of the section .Bl -tag -width Ds -compact
.It Cm LONG Ar Depth
LONG Size ; Size in bytes of this section .It Cm LONG Ar Iter Ns Bq Ar Depth
The number of REPT iterations, by increasing depth.
BYTE Type ; 0 = WRAM0 .El
; 1 = VRAM .It Cm ENDC
; 2 = ROMX .El
; 3 = ROM0 .It Cm ENDR
; 4 = HRAM .El
; 5 = WRAMX .Ss Symbols
; 6 = SRAM .Bl -tag -width Ds -compact
; 7 = OAM .It Cm REPT Ar NumberOfSymbols
; Bits 7 and 6 are independent from the above value: .Bl -tag -width Ds -compact
; Bit 7 encodes whether the section is unionized .It Cm STRING Ar Name
; Bit 6 encodes whether the section is a fragment This symbol's name.
; Bits 6 and 7 may not be both set at the same time! Local symbols are stored as their full name
.Pq Ql Scope.symbol .
LONG Org ; Address to fix this section at. -1 if the linker should .It Cm BYTE Ar Type
; decide (floating address). .Bl -column "Value" -compact
.It Sy Value Ta Sy Meaning
LONG Bank ; Bank to load this section into. -1 if the linker should .It 0 Ta Sy Local No symbol only used in this file.
; decide (floating bank). This field is only valid for ROMX, .It 1 Ta Sy Import No of an exported symbol (by name) from another object file.
; VRAM, WRAMX and SRAM sections. .It 2 Ta Sy Exported No symbol visible from other object files.
.El
BYTE Align ; Alignment of this section, as N bits. 0 when not specified. .It Cm IF Ar Type No \(!= 1
If the symbol is defined in this object file...
LONG Ofs ; Offset relative to the alignment specified above. .Pp
; Must be below 1 << Align. .Bl -tag -width Ds -compact
.It Cm LONG Ar NodeID
IF (Type == ROMX) || (Type == ROM0) ; Sections that can contain data. Context in which the symbol was defined.
.It Cm LONG Ar LineNo
BYTE Data[Size] ; Raw data of the section. Line number in the context at which the symbol was defined.
.It Cm LONG Ar SectionID
LONG NumberOfPatches ; Number of patches to apply. The ID of the section in which the symbol is defined.
If the symbol doesn't belong to any specific section (i.e. it's a constant), this field contains -1.
REPT NumberOfPatches .It Cm LONG Ar Value
The symbol's value.
LONG SourceFile ; ID of the source file node (for printing If the symbol belongs to a section, this is the offset within that symbol's section.
; error messages). .El
.It Cm ENDC
LONG LineNo ; Line at which the patch was created. .El
.It Cm ENDR
LONG Offset ; Offset into the section where patch should .El
; be applied (in bytes). .Ss Sections
.Bl -tag -width Ds -compact
LONG PCSectionID ; Index within the file of the section in which .It Cm REPT Ar NumberOfSections
; PC is located. .Bl -tag -width Ds -compact
; This is usually the same section that the .It Cm STRING Ar Name
; patch should be applied into, except e.g. The section's name.
; with LOAD blocks. .It Cm LONG Ar Size
The section's size, in bytes.
LONG PCOffset ; PC's offset into the above section. .It Cm BYTE Ar Type
; Used because the section may be floating, so Bits 0\(en2 indicate the section's type:
; PC's value is not known to RGBASM. .Bl -column "Value" -compact
.It Sy Value Ta Sy Meaning
BYTE Type ; 0 = BYTE patch. .It 0 Ta WRAM0
; 1 = little endian WORD patch. .It 1 Ta VRAM
; 2 = little endian LONG patch. .It 2 Ta ROMX
; 3 = JR offset value BYTE patch. .It 3 Ta ROM0
.It 4 Ta HRAM
LONG RPNSize ; Size of the buffer with the RPN. .It 5 Ta WRAMX
; expression. .It 6 Ta SRAM
.It 7 Ta OAM
BYTE RPN[RPNSize] ; RPN expression. Definition below. .El
.Pp
ENDR Bit\ 7 being set means that the section is a "union"
.Pq see Do Unionized sections Dc in Xr rgbasm 5 .
ENDC Bit\ 6 being set means that the section is a "fragment"
.Pq see Do Section fragments Dc in Xr rgbasm 5 .
ENDR These two bits are mutually exclusive.
.It Cm LONG Ar Address
; Assertions Address this section must be placed at.
This must either be valid for the section's
LONG NumberOfAssertions .Ar Type
(as affected by flags like
REPT NumberOfAssertions .Fl t
or
LONG SourceFile ; ID of the source file node (for printing the failure). .Fl d
in
LONG LineNo ; Line at which the assertion was created. .Xr rgblink 1 ) ,
or -1 to indicate that the linker should automatically decide
LONG Offset ; Offset into the section where the assertion is located. .Pq the section is Dq floating .
.It Cm LONG Ar Bank
LONG SectionID ; Index within the file of the section in which PC is ID of the bank this section must be placed in.
; located, or -1 if defined outside a section. This must either be valid for the section's
.Ar Type
LONG PCOffset ; PC's offset into the above section. (with the same caveats as for the
; Used because the section may be floating, so PC's value .Ar Address ) ,
; is not known to RGBASM. or -1 to indicate that the linker should automatically decide.
.It Cm BYTE Ar Alignment
BYTE Type ; 0 = Prints the message but allows linking to continue How many bits of the section's address should be equal to
; 1 = Prints the message and evaluates other assertions, .Ar AlignOfs ,
; but linking fails afterwards starting from the least-significant bit.
; 2 = Prints the message and immediately fails linking .It Cm LONG Ar AlignOfs
Alignment offset.
LONG RPNSize ; Size of the RPN expression's buffer. Must be strictly less than
.Ql 1 << Ar Alignment .
BYTE RPN[RPNSize] ; RPN expression, same as patches. Assert fails if == 0. .It Cm IF Ar Type No \(eq 2 || Ar Type No \(eq 3
If the section has ROM type, it contains data.
STRING Message ; A message displayed when the assert fails. If set to .Pp
; the empty string, a generic message is printed instead. .Bl -tag -width Ds -compact
.It Cm BYTE Ar Data Ns Bq Size
ENDR The section's raw data.
.Ed Bytes that will be patched over must be present, even though their contents will be overwritten.
.Ss RPN DATA .It Cm LONG Ar NumberOfPatches
Expressions in the object file are stored as RPN. How many patches must be applied to this section's
This is an expression of the form .Ar Data .
.Dq 2 5 + . .It Cm REPT Ar NumberOfPatches
This will first push the value .Bl -tag -width Ds -compact
.Do 2 Dc to the stack, then .It Cm LONG Ar NodeID
Context in which the patch was defined.
.It Cm LONG Ar LineNo
Line number in the context at which the patch was defined.
.It Cm LONG Ar Offset
Offset within the section's
.Ar Data
at which the patch should be applied.
Must not be greater than the section's
.Ar Size
minus the patch's size
.Pq see Ar Type No below .
.It Cm LONG Ar PCSectionID
ID of the section in which PC is located.
(This is usually the same section within which the patch is applied, except for e.g.\&
.Ql LOAD
blocks, see
.Do RAM code Dc in Xr rgbasm 5 . )
.It Cm LONG Ar PCOffset
Offset of the PC symbol within the section designated by
.Ar PCSectionID .
It is expected that PC points to the instruction's first byte for instruction operands (i.e.\&
.Ql jp @
must be an infinite loop), and to the patch's first byte otherwise
.Ql ( db ,
.Ql dw ,
.Ql dl ) .
.It Cm BYTE Ar Type
.Bl -column "Value" -compact
.It Sy Value Ta Sy Meaning
.It 0 Ta Single-byte patch
.It 1 Ta Little-endian two-byte patch
.It 2 Ta Little-endian four-byte patch
.It 3 Ta Single-byte Ql jr
patch; the patch's value will be subtracted to PC + 2 (i.e.\&
.Ql jr @
must be the infinite loop
.Ql 18 FE ) .
.El
.It Cm LONG Ar RPNSize
Size of the
.Ar RPNExpr
below.
.It Cm BYTE Ar RPNExpr Ns Bq RPNSize
The patch's value, encoded as a RPN expression
.Pq see Sx RPN EXPRESSIONS .
.El
.It Cm ENDR
.El
.It Cm ENDC
.El
.El
.Ss Assertions
.Bl -tag -width Ds -compact
.It Cm LONG Ar NumberOfAssertions
How many assertions this object file contains.
.It Cm REPT Ar NumberOfAssertions
Assertions are essentially patches with a message.
.Pp
.Bl -tag -width Ds -compact
.It Cm LONG Ar NodeID
Context in which the assertions was defined.
.It Cm LONG Ar LineNo
Line number in the context at which the assertion was defined.
.It Cm LONG Ar Offset
Unused leftover from the patch structure.
.It Cm LONG Ar PCSectionID
ID of the section in which PC is located.
.It Cm LONG Ar PCOffset
Offset of the PC symbol within the section designated by
.Ar PCSectionID .
.It Cm BYTE Ar Type
Describes what should happen if the expression evaluates to a non-zero value.
.Bl -column "Value" -compact
.It Sy Value Ta Sy Meaning
.It 0 Ta Print a warning message, and continue linking normally.
.It 1 Ta Print an error message, so linking will fail, but allow other assertions to be evaluated.
.It 2 Ta Print a fatal error message, and abort immediately.
.El
.It Cm LONG Ar RPNSize
Size of the
.Ar RPNExpr
below.
.It Cm BYTE Ar RPNExpr Ns Bq RPNSize
The patch's value, encoded as a RPN expression
.Pq see Sx RPN EXPRESSIONS .
.It Cm STRING Ar Message
The message displayed if the expression evaluates to a non-zero value.
If empty, a generic message is displayed instead.
.El
.It Cm ENDR
.El
.Ss RPN EXPRESSIONS
Expressions in the object file are stored as RPN, or
.Dq Reverse Polish Notation ,
which is a notation that allows computing arbitrary expressions with just a simple stack.
For example, the expression
.Ql 2 5 -
will first push the value
.Dq 2
to the stack, then
.Dq 5 . .Dq 5 .
The The
.Do + Dc operator pops two arguments from the stack, adds them, and then pushes the result on the stack, effectively replacing the two top arguments with their sum. .Ql -
In the RGB format, RPN expressions are stored as operator pops two arguments from the stack, subtracts them, and then pushes back the result
.Ar BYTE Ns s .Pq Dq 3
with some bytes being special prefixes for integers and symbols. on the stack.
.Bl -column -offset indent "Sy String" "Sy String" A well-formed RPN expression never tries to pop from an empty stack, and leaves exactly one value in it at the end.
.Pp
RGBDS encodes RPN expressions as an array of
.Cm BYTE Ns s .
The first byte encodes either an operator, or a literal, which consumes more
.Cm BYTE Ns s
after it.
.Bl -column -offset Ds "Value"
.It Sy Value Ta Sy Meaning .It Sy Value Ta Sy Meaning
.It Li $00 Ta Li + operator .It Li $00 Ta Addition operator Pq Ql +
.It Li $01 Ta Li - operator .It Li $01 Ta Subtraction operator Pq Ql -
.It Li $02 Ta Li * operator .It Li $02 Ta Multiplication operator Pq Ql *
.It Li $03 Ta Li / operator .It Li $03 Ta Division operator Pq Ql /
.It Li $04 Ta Li % operator .It Li $04 Ta Modulo operator Pq Ql %
.It Li $05 Ta Li unary - .It Li $05 Ta Negation Pq unary Ql -
.It Li $06 Ta Li ** operator .It Li $06 Ta Exponent operator Pq Ql **
.It Li $10 Ta Li \&| operator .It Li $10 Ta Bitwise OR operator Pq Ql \&|
.It Li $11 Ta Li & operator .It Li $11 Ta Bitwise AND operator Pq Ql &
.It Li $12 Ta Li ^ operator .It Li $12 Ta Bitwise XOR operator Pq Ql ^
.It Li $13 Ta Li unary ~ .It Li $13 Ta Bitwise complement operator Pq unary Ql ~
.It Li $21 Ta Li && comparison .It Li $21 Ta Logical AND operator Pq Ql &&
.It Li $22 Ta Li || comparison .It Li $22 Ta Logical OR operator Pq Ql ||
.It Li $23 Ta Li unary \&! .It Li $23 Ta Logical complement operator Pq unary Ql \&!
.It Li $30 Ta Li == comparison .It Li $30 Ta Equality operator Pq Ql ==
.It Li $31 Ta Li != comparison .It Li $31 Ta Non-equality operator Pq Ql !=
.It Li $32 Ta Li > comparison .It Li $32 Ta Greater-than operator Pq Ql >
.It Li $33 Ta Li < comparison .It Li $33 Ta Less-than operator Pq Ql <
.It Li $34 Ta Li >= comparison .It Li $34 Ta Greater-than-or-equal operator Pq Ql >=
.It Li $35 Ta Li <= comparison .It Li $35 Ta Less-than-or-equal operator Pq Ql <=
.It Li $40 Ta Li << operator .It Li $40 Ta Left shift operator Pq Ql <<
.It Li $41 Ta Li >> operator .It Li $41 Ta Arithmetic/signed right shift operator Pq Ql >>
.It Li $42 Ta Li >>> operator .It Li $42 Ta Logical/unsigned right shift operator Pq Ql >>>
.It Li $50 Ta Li BANK(symbol) , .It Li $50 Ta Fn BANK symbol ,
a followed by the
.Ar LONG .Ar symbol Ap s Cm LONG
Symbol ID follows, where -1 means PC ID.
.It Li $51 Ta Li BANK(section_name) , .It Li $51 Ta Fn BANK section ,
a null-terminated string follows. followed by the
.It Li $52 Ta Li Current BANK() .Ar section Ap s Cm STRING
.It Li $53 Ta Li SIZEOF(section_name) , name.
a null-terminated string follows. .It Li $52 Ta PC's Fn BANK Pq i.e. Ql BANK(@) .
.It Li $54 Ta Li STARTOF(section_name) , .It Li $53 Ta Fn SIZEOF section ,
a null-terminated string follows. followed by the
.It Li $60 Ta Li HRAMCheck . .Ar section Ap s Cm STRING
Checks if the value is in HRAM, ANDs it with 0xFF. name.
.It Li $61 Ta Li RSTCheck . .It Li $54 Ta Fn STARTOF section ,
Checks if the value is a RST vector, ORs it with 0xC7. followed by the
.It Li $80 Ta Ar LONG .Ar section Ap s Cm STRING
integer follows. name.
.It Li $81 Ta Ar LONG .It Li $60 Ta Ql ldh
symbol ID follows. check.
Checks if the value is a valid
.Ql ldh
operand
.Pq see Do Load Instructions Dc in Xr gbz80 7 ,
i.e. that it is between either $00 and $FF, or $FF00 and $FFFF, both inclusive.
The value is then ANDed with $00FF
.Pq Ql & $FF .
.It Li $61 Ta Ql rst
check.
Checks if the value is a valid
.Ql rst
.Pq see Do RST vec Dc in Xr gbz80 7
vector, that is one of $00, $08, $10, $18, $20, $28, $30, or $38.
The value is then ORed with $C7
.Pq Ql \&| $C7 .
.It Li $80 Ta Integer literal.
Followed by the
.Cm LONG
integer.
.It Li $81 Ta A symbol's value.
Followed by the symbol's
.Cm LONG
ID.
.El .El
.Sh SEE ALSO .Sh SEE ALSO
.Xr rgbasm 1 , .Xr rgbasm 1 ,

View File

@@ -14,7 +14,6 @@
.Nd Game Boy graphics converter .Nd Game Boy graphics converter
.Sh SYNOPSIS .Sh SYNOPSIS
.Nm .Nm
.Op Fl r Ar stride
.Op Fl CmuVZ .Op Fl CmuVZ
.Op Fl v Op Fl v No ... .Op Fl v Op Fl v No ...
.Op Fl a Ar attrmap | Fl A .Op Fl a Ar attrmap | Fl A
@@ -27,6 +26,7 @@
.Op Fl o Ar out_file .Op Fl o Ar out_file
.Op Fl p Ar pal_file | Fl P .Op Fl p Ar pal_file | Fl P
.Op Fl q Ar pal_map | Fl Q .Op Fl q Ar pal_map | Fl Q
.Op Fl r Ar stride
.Op Fl s Ar nb_colors .Op Fl s Ar nb_colors
.Op Fl t Ar tilemap | Fl T .Op Fl t Ar tilemap | Fl T
.Op Fl x Ar quantity .Op Fl x Ar quantity
@@ -72,6 +72,18 @@ All of these are equivalent:
.Ql 0X2A , .Ql 0X2A ,
.Ql 0x2a . .Ql 0x2a .
.Pp .Pp
Unless otherwise noted, passing
.Ql -
(a single dash) as a file name makes
.Nm
use standard input (for input files) or standard output (for output files).
To suppress this behavior, and open a file in the current directory actually called
.Ql - ,
pass
.Ql ./-
instead.
Using standard input or output more than once in a single command will likely produce unexpected results.
.Pp
The following options are accepted: The following options are accepted:
.Bl -tag -width Ds .Bl -tag -width Ds
.It Fl a Ar attrmap , Fl Fl attr-map Ar attrmap .It Fl a Ar attrmap , Fl Fl attr-map Ar attrmap
@@ -145,7 +157,9 @@ The expected format is
.Ql format:path , .Ql format:path ,
where where
.Ar path .Ar path
is a path to a file, which will be processed according to the is a path to a file
.Ql ( -
is not treated specially), which will be processed according to the
.Ar format . .Ar format .
See See
.Sx PALETTE SPECIFICATION FORMATS .Sx PALETTE SPECIFICATION FORMATS
@@ -321,7 +335,22 @@ any command-line argument that begins with an at sign
.Pq Ql @ .Pq Ql @
is interpreted as one. is interpreted as one.
The rest of the argument (without the @, that is) is interpreted as the path to a file, whose contents are interpreted as if given on the command line. The rest of the argument (without the @, that is) is interpreted as the path to a file, whose contents are interpreted as if given on the command line.
At-files can be stored right next to the corresponding image, for example. At-files can be stored right next to the corresponding image, for example:
.Pp
.Dl $ rgbgfx -o image.2bpp -t image.tilemap @image.flags image.png
.Pp
This will read additional flags from file
.Ql image.flags ,
which could contains for example
.Ql -b 128
to specify a base offset for the image's tiles.
The above command could be generated from the following
.Xr make 1
rule, for example:
.Bd -literal -offset indent
%.2bpp %.tilemap: %.flags %.png
rgbgfx -o $*.2bpp -t $*.tilemap @$*.flags $*.png
.Ed
.Pp .Pp
Since the contents of at-files are interpreted by Since the contents of at-files are interpreted by
.Nm , .Nm ,
@@ -336,17 +365,19 @@ optionally preceded by whitespace, are considered comments and also ignored.
Each line can contain any number of arguments, which are separated by whitespace. Each line can contain any number of arguments, which are separated by whitespace.
.Pq \&No quoting feature to prevent this is provided. .Pq \&No quoting feature to prevent this is provided.
.Pp .Pp
Note that this special meaning given to arguments has less precedence than option arguments, and that the standard Note that a leading
.Ql @
has no special meaning on option arguments, and that the standard
.Ql -- .Ql --
to stop option processing also disables at-file processing. to stop option processing also disables at-file processing.
For example, the following command line processes For example, the following command line reads command-line options from
.Ql @tilesets/town.png ,
outputs tile data to
.Ql @tilesets/town.2bpp ,
and reads command-line options from
.Ql tilesets/town.flags .Ql tilesets/town.flags
then then
.Ql tilesets.flags : .Ql tilesets.flags ,
but processes
.Ql @tilesets/town.png
as the input image and outputs tile data to
.Ql @tilesets/town.2bpp :
.Pp .Pp
.Dl $ rgbgfx -o @tilesets/town.2bpp @tilesets/town.flags @tilesets.flags -- @tilesets/town.png .Dl $ rgbgfx -o @tilesets/town.2bpp @tilesets/town.flags @tilesets.flags -- @tilesets/town.png
.Pp .Pp
@@ -357,11 +388,21 @@ can be used in an at-file (with identical semantics), it is only effective insid
.Sh PALETTE SPECIFICATION FORMATS .Sh PALETTE SPECIFICATION FORMATS
The following formats are supported: The following formats are supported:
.Bl -tag -width Ds .Bl -tag -width Ds
.It Sy act .It Cm act
.Lk https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577411_pgfId-1070626 Adobe Photoshop color table . .Lk https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577411_pgfId-1070626 Adobe Photoshop color table .
.It Sy aco .It Cm aco
.Lk https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577411_pgfId-1055819 Adobe Photoshop color swatch . .Lk https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577411_pgfId-1055819 Adobe Photoshop color swatch .
.It Sy psp .It Cm gbc
A GBC palette memory dump, as emitted by
.Nm Fl p .
Useful to force several images to share the same palette.
.It Cm gpl
.Lk https://docs.gimp.org/2.10/en/gimp-concepts-palettes.html GIMP palette .
.It Cm hex
Plaintext lines of hexadecimal colors in
.Ql rrggbb
format.
.It Cm psp
.Lk https://www.selapa.net/swatches/colors/fileformats.php#psp_pal Paint Shop Pro palette . .Lk https://www.selapa.net/swatches/colors/fileformats.php#psp_pal Paint Shop Pro palette .
.El .El
.Pp .Pp
@@ -421,7 +462,7 @@ be
.Em exactly .Em exactly
the same. the same.
.It .It
If none of the above apply, colors are sorted from lightest to darkest. If none of the above apply, colors are sorted from lightest (first) to darkest (last).
The definition of luminance that The definition of luminance that
.Nm .Nm
uses is uses is
@@ -472,7 +513,7 @@ delim off
.EN .EN
Note that Note that
.Fl n .Fl n
only puts a limit on the amount of palettes, but does not fix this file's size. only caps how many palettes are generated (and thus this file's size), but fewer may be generated still.
.Ss Tile map data .Ss Tile map data
A tile map is an array of tile IDs, with one byte per tile ID. A tile map is an array of tile IDs, with one byte per tile ID.
The first byte always corresponds to the ID of the tile in top-left corner of the input image; the second byte is either the ID of the tile to its right (by default), or below it The first byte always corresponds to the ID of the tile in top-left corner of the input image; the second byte is either the ID of the tile to its right (by default), or below it

View File

@@ -13,7 +13,7 @@
.Nd Game Boy linker .Nd Game Boy linker
.Sh SYNOPSIS .Sh SYNOPSIS
.Nm .Nm
.Op Fl dtVvwx .Op Fl dMtVvwx
.Op Fl l Ar linker_script .Op Fl l Ar linker_script
.Op Fl m Ar map_file .Op Fl m Ar map_file
.Op Fl n Ar sym_file .Op Fl n Ar sym_file
@@ -73,6 +73,8 @@ The attributes assigned in the linker script must be consistent with any assigne
See See
.Xr rgblink 5 .Xr rgblink 5
for more information about the linker script format. for more information about the linker script format.
.It Fl M , Fl Fl no-sym-in-map
If specified, the map file will not list symbols, only sections.
.It Fl m Ar map_file , Fl Fl map Ar map_file .It Fl m Ar map_file , Fl Fl map Ar map_file
Write a map file to the given filename, listing how sections and symbols were assigned. Write a map file to the given filename, listing how sections and symbols were assigned.
.It Fl n Ar sym_file , Fl Fl sym Ar sym_file .It Fl n Ar sym_file , Fl Fl sym Ar sym_file

View File

@@ -14,18 +14,22 @@
.Sh DESCRIPTION .Sh DESCRIPTION
The linker script is an external file that allows the user to specify the order of sections at link time and in a centralized manner. The linker script is an external file that allows the user to specify the order of sections at link time and in a centralized manner.
.Pp .Pp
A linker script consists on a series of banks followed by a list of sections and, optionally, commands. A linker script consists of a series of bank declarations, each optionally followed by a list of section names (in double quotes) or commands.
They can be lowercase or uppercase, it is ignored. All reserved keywords (bank types and command names) are case-insensitive; all section names are case-sensitive.
.Pp
Any line can contain a comment starting with Any line can contain a comment starting with
.Ql \&; .Ql \&;
that ends at the end of the line: that ends at the end of the line.
.Pp
.Bd -literal -offset indent .Bd -literal -offset indent
ROMX $F ; This is a comment ; This line is a comment
"Functions to read array" ROMX $F ; start a bank
ALIGN 8 "Some functions" ; a section name
"Array aligned to 256 bytes" ALIGN 8 ; a command
"Some array"
WRAMX 2 WRAMX 2 ; start another bank
org $d123 ; another command
"Some variables" "Some variables"
.Ed .Ed
.Pp .Pp
@@ -47,6 +51,19 @@ and
.Cm WRAMX , .Cm WRAMX ,
it is needed to specify a bank number after the type. it is needed to specify a bank number after the type.
.Pp .Pp
Section names in double quotes support the same character escape sequences as strings in
.Xr rgbasm 5 ,
specifically
.Ql \e\e ,
.Ql \e" ,
.Ql \en ,
.Ql \er ,
and
.Ql \et .
Other backslash escape sequences in
.Xr rgbasm 5
are only relevant to assembly code and do not apply in section names.
.Pp
When a new bank statement is found, sections found after it will be placed right from the beginning of that bank. When a new bank statement is found, sections found after it will be placed right from the beginning of that bank.
If the linker script switches to a different bank and then comes back to a previous one, it will continue from the last address that was used. If the linker script switches to a different bank and then comes back to a previous one, it will continue from the last address that was used.
.Pp .Pp

View File

@@ -14,21 +14,20 @@ set(common_src
"_version.c" "_version.c"
) )
find_package(BISON REQUIRED) find_package(BISON 3.0.0 REQUIRED)
set(BISON_FLAGS "-Wall") set(BISON_FLAGS "-Wall")
# Set sompe optimization flags on versions that support them # Set some optimization flags on versions that support them
if(BISON_VERSION VERSION_GREATER_EQUAL "3.5") if(BISON_VERSION VERSION_GREATER_EQUAL "3.5")
set(BISON_FLAGS "${BISON_FLAGS} -Dapi.token.raw=true") set(BISON_FLAGS "${BISON_FLAGS} -Dapi.token.raw=true")
endif() endif()
if(BISON_VERSION VERSION_GREATER_EQUAL "3.6") if(BISON_VERSION VERSION_GREATER_EQUAL "3.6")
set(BISON_FLAGS "${BISON_FLAGS} -Dparse.error=detailed") set(BISON_FLAGS "${BISON_FLAGS} -Dparse.error=detailed")
elseif(BISON_VERSION VERSION_GREATER_EQUAL "3.0") else()
set(BISON_FLAGS "${BISON_FLAGS} -Dparse.error=verbose") set(BISON_FLAGS "${BISON_FLAGS} -Dparse.error=verbose")
endif() endif()
if(BISON_VERSION VERSION_GREATER_EQUAL "3.0")
set(BISON_FLAGS "${BISON_FLAGS} -Dparse.lac=full") set(BISON_FLAGS "${BISON_FLAGS} -Dparse.lac=full")
set(BISON_FLAGS "${BISON_FLAGS} -Dlr.type=ielr") set(BISON_FLAGS "${BISON_FLAGS} -Dlr.type=ielr")
endif()
BISON_TARGET(PARSER "asm/parser.y" BISON_TARGET(PARSER "asm/parser.y"
"${PROJECT_SOURCE_DIR}/src/asm/parser.c" "${PROJECT_SOURCE_DIR}/src/asm/parser.c"
COMPILE_FLAGS "${BISON_FLAGS}" COMPILE_FLAGS "${BISON_FLAGS}"
@@ -81,8 +80,10 @@ set(rgblink_src
"link/output.c" "link/output.c"
"link/patch.c" "link/patch.c"
"link/script.c" "link/script.c"
"link/sdas_obj.c"
"link/section.c" "link/section.c"
"link/symbol.c" "link/symbol.c"
"extern/utf8decoder.c"
"hashmap.c" "hashmap.c"
"linkdefs.c" "linkdefs.c"
"opmath.c" "opmath.c"

View File

@@ -21,33 +21,29 @@
#include "hashmap.h" #include "hashmap.h"
/* // Charmaps are stored using a structure known as "trie".
* Charmaps are stored using a structure known as "trie". // Essentially a tree, where each nodes stores a single character's worth of info:
* Essentially a tree, where each nodes stores a single character's worth of info: // whether there exists a mapping that ends at the current character,
* whether there exists a mapping that ends at the current character,
*/
struct Charnode { struct Charnode {
bool isTerminal; /* Whether there exists a mapping that ends here */ bool isTerminal; // Whether there exists a mapping that ends here
uint8_t value; /* If the above is true, its corresponding value */ uint8_t value; // If the above is true, its corresponding value
/* This MUST be indexes and not pointers, because pointers get invalidated by `realloc`!! */ // This MUST be indexes and not pointers, because pointers get invalidated by `realloc`!
size_t next[255]; /* Indexes of where to go next, 0 = nowhere */ size_t next[255]; // Indexes of where to go next, 0 = nowhere
}; };
#define INITIAL_CAPACITY 32 #define INITIAL_CAPACITY 32
struct Charmap { struct Charmap {
char *name; char *name;
size_t usedNodes; /* How many nodes are being used */ size_t usedNodes; // How many nodes are being used
size_t capacity; /* How many nodes have been allocated */ size_t capacity; // How many nodes have been allocated
struct Charnode nodes[]; /* first node is reserved for the root node */ struct Charnode nodes[]; // first node is reserved for the root node
}; };
static HashMap charmaps; static HashMap charmaps;
/* // Store pointers to hashmap nodes, so that there is only one pointer to the memory block
* Store pointers to hashmap nodes, so that there is only one pointer to the memory block // that gets reallocated.
* that gets reallocated.
*/
static struct Charmap **currentCharmap; static struct Charmap **currentCharmap;
struct CharmapStackEntry { struct CharmapStackEntry {
@@ -96,7 +92,7 @@ struct Charmap *charmap_New(char const *name, char const *baseName)
return charmap; return charmap;
} }
/* Init the new charmap's fields */ // Init the new charmap's fields
if (base) { if (base) {
resizeCharmap(&charmap, base->capacity); resizeCharmap(&charmap, base->capacity);
charmap->usedNodes = base->usedNodes; charmap->usedNodes = base->usedNodes;
@@ -105,7 +101,7 @@ struct Charmap *charmap_New(char const *name, char const *baseName)
} else { } else {
resizeCharmap(&charmap, INITIAL_CAPACITY); resizeCharmap(&charmap, INITIAL_CAPACITY);
charmap->usedNodes = 1; charmap->usedNodes = 1;
initNode(&charmap->nodes[0]); /* Init the root node */ initNode(&charmap->nodes[0]); // Init the root node
} }
charmap->name = strdup(name); charmap->name = strdup(name);
@@ -169,16 +165,16 @@ void charmap_Add(char *mapping, uint8_t value)
if (node->next[c]) { if (node->next[c]) {
node = &charmap->nodes[node->next[c]]; node = &charmap->nodes[node->next[c]];
} else { } else {
/* Register next available node */ // Register next available node
node->next[c] = charmap->usedNodes; node->next[c] = charmap->usedNodes;
/* If no more nodes are available, get new ones */ // If no more nodes are available, get new ones
if (charmap->usedNodes == charmap->capacity) { if (charmap->usedNodes == charmap->capacity) {
charmap->capacity *= 2; charmap->capacity *= 2;
resizeCharmap(currentCharmap, charmap->capacity); resizeCharmap(currentCharmap, charmap->capacity);
charmap = *currentCharmap; charmap = *currentCharmap;
} }
/* Switch to and init new node */ // Switch to and init new node
node = &charmap->nodes[charmap->usedNodes++]; node = &charmap->nodes[charmap->usedNodes++];
initNode(node); initNode(node);
} }
@@ -203,12 +199,10 @@ size_t charmap_Convert(char const *input, uint8_t *output)
size_t charmap_ConvertNext(char const **input, uint8_t **output) size_t charmap_ConvertNext(char const **input, uint8_t **output)
{ {
/* // The goal is to match the longest mapping possible.
* The goal is to match the longest mapping possible. // For that, advance through the trie with each character read.
* For that, advance through the trie with each character read. // If that would lead to a dead end, rewind characters until the last match, and output.
* If that would lead to a dead end, rewind characters until the last match, and output. // If no match, read a UTF-8 codepoint and output that.
* If no match, read a UTF-8 codepoint and output that.
*/
struct Charmap const *charmap = *currentCharmap; struct Charmap const *charmap = *currentCharmap;
struct Charnode const *node = &charmap->nodes[0]; struct Charnode const *node = &charmap->nodes[0];
struct Charnode const *match = NULL; struct Charnode const *match = NULL;
@@ -242,6 +236,7 @@ size_t charmap_ConvertNext(char const **input, uint8_t **output)
return 1; return 1;
} else if (**input) { // No match found, but there is some input left } else if (**input) { // No match found, but there is some input left
int firstChar = **input;
// This will write the codepoint's value to `output`, little-endian // This will write the codepoint's value to `output`, little-endian
size_t codepointLen = readUTF8Char(output ? *output : NULL, size_t codepointLen = readUTF8Char(output ? *output : NULL,
*input); *input);
@@ -254,6 +249,15 @@ size_t charmap_ConvertNext(char const **input, uint8_t **output)
if (output) if (output)
*output += codepointLen; *output += codepointLen;
// Warn if this character is not mapped but any others are
if (charmap->usedNodes > 1)
warning(WARNING_UNMAPPED_CHAR_1,
"Unmapped character %s\n", printChar(firstChar));
else if (strcmp(charmap->name, DEFAULT_CHARMAP_NAME))
warning(WARNING_UNMAPPED_CHAR_2,
"Unmapped character %s not in " DEFAULT_CHARMAP_NAME
" charmap\n", printChar(firstChar));
return codepointLen; return codepointLen;
} else { // End of input } else { // End of input

View File

@@ -6,155 +6,110 @@
* SPDX-License-Identifier: MIT * SPDX-License-Identifier: MIT
*/ */
/* // Fixed-point math routines
* Fixed-point math routines
*/
#include <inttypes.h> #include <inttypes.h>
#include <math.h> #include <math.h>
#include <stdint.h> #include <stdint.h>
#include <stdio.h>
#include "asm/fixpoint.h" #include "asm/fixpoint.h"
#include "asm/symbol.h" #include "asm/symbol.h"
#include "asm/warning.h" #include "asm/warning.h"
#define fix2double(i) ((double)((i) / 65536.0))
#define double2fix(d) ((int32_t)round((d) * 65536.0))
// pi radians == 32768 fixed-point "degrees"
#define fdeg2rad(f) ((f) * (M_PI / 32768.0))
#define rad2fdeg(r) ((r) * (32768.0 / M_PI))
#ifndef M_PI #ifndef M_PI
#define M_PI 3.14159265358979323846 #define M_PI 3.14159265358979323846
#endif #endif
/* #define fix2double(i, q) ((double)((i) / pow(2.0, q)))
* Print a fixed point value #define double2fix(d, q) ((int32_t)round((d) * pow(2.0, q)))
*/
void fix_Print(int32_t i) // 2*pi radians == 1 turn
#define turn2rad(f) ((f) * (M_PI * 2))
#define rad2turn(r) ((r) / (M_PI * 2))
uint8_t fixPrecision;
uint8_t fix_Precision(void)
{ {
uint32_t u = i; return fixPrecision;
char const *sign = "";
if (i < 0) {
u = -u;
sign = "-";
} }
printf("%s%" PRIu32 ".%05" PRIu32, sign, u >> 16, double fix_PrecisionFactor(void)
((uint32_t)(fix2double(u) * 100000 + 0.5)) % 100000);
}
/*
* Calculate sine
*/
int32_t fix_Sin(int32_t i)
{ {
return double2fix(sin(fdeg2rad(fix2double(i)))); return pow(2.0, fixPrecision);
} }
/* int32_t fix_Sin(int32_t i, int32_t q)
* Calculate cosine
*/
int32_t fix_Cos(int32_t i)
{ {
return double2fix(cos(fdeg2rad(fix2double(i)))); return double2fix(sin(turn2rad(fix2double(i, q))), q);
} }
/* int32_t fix_Cos(int32_t i, int32_t q)
* Calculate tangent
*/
int32_t fix_Tan(int32_t i)
{ {
return double2fix(tan(fdeg2rad(fix2double(i)))); return double2fix(cos(turn2rad(fix2double(i, q))), q);
} }
/* int32_t fix_Tan(int32_t i, int32_t q)
* Calculate arcsine
*/
int32_t fix_ASin(int32_t i)
{ {
return double2fix(rad2fdeg(asin(fix2double(i)))); return double2fix(tan(turn2rad(fix2double(i, q))), q);
} }
/* int32_t fix_ASin(int32_t i, int32_t q)
* Calculate arccosine
*/
int32_t fix_ACos(int32_t i)
{ {
return double2fix(rad2fdeg(acos(fix2double(i)))); return double2fix(rad2turn(asin(fix2double(i, q))), q);
} }
/* int32_t fix_ACos(int32_t i, int32_t q)
* Calculate arctangent
*/
int32_t fix_ATan(int32_t i)
{ {
return double2fix(rad2fdeg(atan(fix2double(i)))); return double2fix(rad2turn(acos(fix2double(i, q))), q);
} }
/* int32_t fix_ATan(int32_t i, int32_t q)
* Calculate atan2
*/
int32_t fix_ATan2(int32_t i, int32_t j)
{ {
return double2fix(rad2fdeg(atan2(fix2double(i), fix2double(j)))); return double2fix(rad2turn(atan(fix2double(i, q))), q);
} }
/* int32_t fix_ATan2(int32_t i, int32_t j, int32_t q)
* Multiplication
*/
int32_t fix_Mul(int32_t i, int32_t j)
{ {
return double2fix(fix2double(i) * fix2double(j)); return double2fix(rad2turn(atan2(fix2double(i, q), fix2double(j, q))), q);
} }
/* int32_t fix_Mul(int32_t i, int32_t j, int32_t q)
* Division
*/
int32_t fix_Div(int32_t i, int32_t j)
{ {
return double2fix(fix2double(i) / fix2double(j)); return double2fix(fix2double(i, q) * fix2double(j, q), q);
} }
/* int32_t fix_Div(int32_t i, int32_t j, int32_t q)
* Power
*/
int32_t fix_Pow(int32_t i, int32_t j)
{ {
return double2fix(pow(fix2double(i), fix2double(j))); return double2fix(fix2double(i, q) / fix2double(j, q), q);
} }
/* int32_t fix_Mod(int32_t i, int32_t j, int32_t q)
* Logarithm
*/
int32_t fix_Log(int32_t i, int32_t j)
{ {
return double2fix(log(fix2double(i)) / log(fix2double(j))); return double2fix(fmod(fix2double(i, q), fix2double(j, q)), q);
} }
/* int32_t fix_Pow(int32_t i, int32_t j, int32_t q)
* Round
*/
int32_t fix_Round(int32_t i)
{ {
return double2fix(round(fix2double(i))); return double2fix(pow(fix2double(i, q), fix2double(j, q)), q);
} }
/* int32_t fix_Log(int32_t i, int32_t j, int32_t q)
* Ceil
*/
int32_t fix_Ceil(int32_t i)
{ {
return double2fix(ceil(fix2double(i))); return double2fix(log(fix2double(i, q)) / log(fix2double(j, q)), q);
} }
/* int32_t fix_Round(int32_t i, int32_t q)
* Floor
*/
int32_t fix_Floor(int32_t i)
{ {
return double2fix(floor(fix2double(i))); return double2fix(round(fix2double(i, q)), q);
}
int32_t fix_Ceil(int32_t i, int32_t q)
{
return double2fix(ceil(fix2double(i, q)), q);
}
int32_t fix_Floor(int32_t i, int32_t q)
{
return double2fix(floor(fix2double(i, q)), q);
} }

View File

@@ -15,6 +15,7 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include "asm/fixpoint.h"
#include "asm/format.h" #include "asm/format.h"
#include "asm/warning.h" #include "asm/warning.h"
@@ -46,7 +47,7 @@ void fmt_UseCharacter(struct FormatSpec *fmt, int c)
return; return;
switch (c) { switch (c) {
/* sign */ // sign
case ' ': case ' ':
case '+': case '+':
if (fmt->state > FORMAT_SIGN) if (fmt->state > FORMAT_SIGN)
@@ -55,7 +56,7 @@ void fmt_UseCharacter(struct FormatSpec *fmt, int c)
fmt->sign = c; fmt->sign = c;
break; break;
/* prefix */ // prefix
case '#': case '#':
if (fmt->state > FORMAT_PREFIX) if (fmt->state > FORMAT_PREFIX)
goto invalid; goto invalid;
@@ -63,7 +64,7 @@ void fmt_UseCharacter(struct FormatSpec *fmt, int c)
fmt->prefix = true; fmt->prefix = true;
break; break;
/* align */ // align
case '-': case '-':
if (fmt->state > FORMAT_ALIGN) if (fmt->state > FORMAT_ALIGN)
goto invalid; goto invalid;
@@ -71,11 +72,11 @@ void fmt_UseCharacter(struct FormatSpec *fmt, int c)
fmt->alignLeft = true; fmt->alignLeft = true;
break; break;
/* pad and width */ // pad and width
case '0': case '0':
if (fmt->state < FORMAT_WIDTH) if (fmt->state < FORMAT_WIDTH)
fmt->padZero = true; fmt->padZero = true;
/* fallthrough */ // fallthrough
case '1': case '1':
case '2': case '2':
case '3': case '3':
@@ -104,7 +105,7 @@ void fmt_UseCharacter(struct FormatSpec *fmt, int c)
fmt->hasFrac = true; fmt->hasFrac = true;
break; break;
/* type */ // type
case 'd': case 'd':
case 'u': case 'u':
case 'X': case 'X':
@@ -149,7 +150,7 @@ void fmt_PrintString(char *buf, size_t bufLen, struct FormatSpec const *fmt, cha
size_t len = strlen(value); size_t len = strlen(value);
size_t totalLen = fmt->width > len ? fmt->width : len; size_t totalLen = fmt->width > len ? fmt->width : len;
if (totalLen > bufLen - 1) { /* bufLen includes terminator */ if (totalLen > bufLen - 1) { // bufLen includes terminator
error("Formatted string value too long\n"); error("Formatted string value too long\n");
totalLen = bufLen - 1; totalLen = bufLen - 1;
if (len > totalLen) if (len > totalLen)
@@ -182,7 +183,7 @@ void fmt_PrintNumber(char *buf, size_t bufLen, struct FormatSpec const *fmt, uin
if (fmt->type == 's') if (fmt->type == 's')
error("Formatting number as type 's'\n"); error("Formatting number as type 's'\n");
char sign = fmt->sign; /* 0 or ' ' or '+' */ char sign = fmt->sign; // 0 or ' ' or '+'
if (fmt->type == 'd' || fmt->type == 'f') { if (fmt->type == 'd' || fmt->type == 'f') {
int32_t v = value; int32_t v = value;
@@ -200,10 +201,10 @@ void fmt_PrintNumber(char *buf, size_t bufLen, struct FormatSpec const *fmt, uin
: fmt->type == 'o' ? '&' : fmt->type == 'o' ? '&'
: 0; : 0;
char valueBuf[262]; /* Max 5 digits + decimal + 255 fraction digits + terminator */ char valueBuf[262]; // Max 5 digits + decimal + 255 fraction digits + terminator
if (fmt->type == 'b') { if (fmt->type == 'b') {
/* Special case for binary */ // Special case for binary
char *ptr = valueBuf; char *ptr = valueBuf;
do { do {
@@ -213,7 +214,7 @@ void fmt_PrintNumber(char *buf, size_t bufLen, struct FormatSpec const *fmt, uin
*ptr = '\0'; *ptr = '\0';
/* Reverse the digits */ // Reverse the digits
size_t valueLen = ptr - valueBuf; size_t valueLen = ptr - valueBuf;
for (size_t i = 0, j = valueLen - 1; i < j; i++, j--) { for (size_t i = 0, j = valueLen - 1; i < j; i++, j--) {
@@ -223,9 +224,9 @@ void fmt_PrintNumber(char *buf, size_t bufLen, struct FormatSpec const *fmt, uin
valueBuf[j] = c; valueBuf[j] = c;
} }
} else if (fmt->type == 'f') { } else if (fmt->type == 'f') {
/* Special case for fixed-point */ // Special case for fixed-point
/* Default fractional width (C's is 6 for "%f"; here 5 is enough) */ // Default fractional width (C's is 6 for "%f"; here 5 is enough for Q16.16)
size_t fracWidth = fmt->hasFrac ? fmt->fracWidth : 5; size_t fracWidth = fmt->hasFrac ? fmt->fracWidth : 5;
if (fracWidth > 255) { if (fracWidth > 255) {
@@ -234,7 +235,8 @@ void fmt_PrintNumber(char *buf, size_t bufLen, struct FormatSpec const *fmt, uin
fracWidth = 255; fracWidth = 255;
} }
snprintf(valueBuf, sizeof(valueBuf), "%.*f", (int)fracWidth, value / 65536.0); snprintf(valueBuf, sizeof(valueBuf), "%.*f", (int)fracWidth,
value / fix_PrecisionFactor());
} else { } else {
char const *spec = fmt->type == 'd' ? "%" PRId32 char const *spec = fmt->type == 'd' ? "%" PRId32
: fmt->type == 'u' ? "%" PRIu32 : fmt->type == 'u' ? "%" PRIu32
@@ -247,10 +249,10 @@ void fmt_PrintNumber(char *buf, size_t bufLen, struct FormatSpec const *fmt, uin
} }
size_t len = strlen(valueBuf); size_t len = strlen(valueBuf);
size_t numLen = !!sign + !!prefix + len; size_t numLen = (sign != 0) + (prefix != 0) + len;
size_t totalLen = fmt->width > numLen ? fmt->width : numLen; size_t totalLen = fmt->width > numLen ? fmt->width : numLen;
if (totalLen > bufLen - 1) { /* bufLen includes terminator */ if (totalLen > bufLen - 1) { // bufLen includes terminator
error("Formatted numeric value too long\n"); error("Formatted numeric value too long\n");
totalLen = bufLen - 1; totalLen = bufLen - 1;
if (numLen > totalLen) { if (numLen > totalLen) {
@@ -273,7 +275,7 @@ void fmt_PrintNumber(char *buf, size_t bufLen, struct FormatSpec const *fmt, uin
buf[i] = ' '; buf[i] = ' ';
} else { } else {
if (fmt->padZero) { if (fmt->padZero) {
/* sign, then prefix, then zero padding */ // sign, then prefix, then zero padding
if (sign) if (sign)
buf[pos++] = sign; buf[pos++] = sign;
if (prefix) if (prefix)
@@ -281,7 +283,7 @@ void fmt_PrintNumber(char *buf, size_t bufLen, struct FormatSpec const *fmt, uin
for (size_t i = 0; i < padLen; i++) for (size_t i = 0; i < padLen; i++)
buf[pos++] = '0'; buf[pos++] = '0';
} else { } else {
/* space padding, then sign, then prefix */ // space padding, then sign, then prefix
for (size_t i = 0; i < padLen; i++) for (size_t i = 0; i < padLen; i++)
buf[pos++] = ' '; buf[pos++] = ' ';
if (sign) if (sign)

View File

@@ -19,7 +19,8 @@
#include "asm/main.h" #include "asm/main.h"
#include "asm/symbol.h" #include "asm/symbol.h"
#include "asm/warning.h" #include "asm/warning.h"
#include "platform.h" /* S_ISDIR (stat macro) */ #include "error.h"
#include "platform.h" // S_ISDIR (stat macro)
#define MAXINCPATHS 128 #define MAXINCPATHS 128
@@ -28,7 +29,7 @@ struct Context {
struct FileStackNode *fileInfo; struct FileStackNode *fileInfo;
struct LexerState *lexerState; struct LexerState *lexerState;
uint32_t uniqueID; uint32_t uniqueID;
struct MacroArgs *macroArgs; /* Macro args are *saved* here */ struct MacroArgs *macroArgs; // Macro args are *saved* here
uint32_t nbReptIters; uint32_t nbReptIters;
int32_t forValue; int32_t forValue;
int32_t forStep; int32_t forStep;
@@ -37,18 +38,19 @@ struct Context {
static struct Context *contextStack; static struct Context *contextStack;
static size_t contextDepth = 0; static size_t contextDepth = 0;
#define DEFAULT_MAX_DEPTH 64
size_t maxRecursionDepth; size_t maxRecursionDepth;
static unsigned int nbIncPaths = 0; static unsigned int nbIncPaths = 0;
static char const *includePaths[MAXINCPATHS]; static char const *includePaths[MAXINCPATHS];
static const char *preIncludeName;
static const char *dumpNodeAndParents(struct FileStackNode const *node) static const char *dumpNodeAndParents(struct FileStackNode const *node)
{ {
char const *name; char const *name;
if (node->type == NODE_REPT) { if (node->type == NODE_REPT) {
assert(node->parent); /* REPT nodes should always have a parent */ assert(node->parent); // REPT nodes should always have a parent
struct FileStackReptNode const *reptInfo = (struct FileStackReptNode const *)node; struct FileStackReptNode const *reptInfo = (struct FileStackReptNode const *)node;
name = dumpNodeAndParents(node->parent); name = dumpNodeAndParents(node->parent);
@@ -89,7 +91,7 @@ struct FileStackNode *fstk_GetFileStack(void)
struct FileStackNode *node = contextStack->fileInfo; struct FileStackNode *node = contextStack->fileInfo;
/* Mark node and all of its parents as referenced if not already so they don't get freed */ // Mark node and all of its parents as referenced if not already so they don't get freed
while (node && !node->referenced) { while (node && !node->referenced) {
node->ID = -1; node->ID = -1;
node->referenced = true; node->referenced = true;
@@ -100,7 +102,7 @@ struct FileStackNode *fstk_GetFileStack(void)
char const *fstk_GetFileName(void) char const *fstk_GetFileName(void)
{ {
/* Iterating via the nodes themselves skips nested REPTs */ // Iterating via the nodes themselves skips nested REPTs
struct FileStackNode const *node = contextStack->fileInfo; struct FileStackNode const *node = contextStack->fileInfo;
while (node->type != NODE_FILE) while (node->type != NODE_FILE)
@@ -121,7 +123,7 @@ void fstk_AddIncludePath(char const *path)
char *str = malloc(allocSize); char *str = malloc(allocSize);
if (!str) { if (!str) {
/* Attempt to continue without that path */ // Attempt to continue without that path
error("Failed to allocate new include path: %s\n", strerror(errno)); error("Failed to allocate new include path: %s\n", strerror(errno));
return; return;
} }
@@ -134,6 +136,15 @@ void fstk_AddIncludePath(char const *path)
includePaths[nbIncPaths++] = str; includePaths[nbIncPaths++] = str;
} }
void fstk_SetPreIncludeFile(char const *path)
{
if (preIncludeName)
warnx("Overriding pre-included filename %s", preIncludeName);
preIncludeName = path;
if (verbose)
printf("Pre-included filename %s\n", preIncludeName);
}
static void printDep(char const *path) static void printDep(char const *path)
{ {
if (dependfile) { if (dependfile) {
@@ -150,14 +161,14 @@ static bool isPathValid(char const *path)
if (stat(path, &statbuf) != 0) if (stat(path, &statbuf) != 0)
return false; return false;
/* Reject directories */ // Reject directories
return !S_ISDIR(statbuf.st_mode); return !S_ISDIR(statbuf.st_mode);
} }
bool fstk_FindFile(char const *path, char **fullPath, size_t *size) bool fstk_FindFile(char const *path, char **fullPath, size_t *size)
{ {
if (!*size) { if (!*size) {
*size = 64; /* This is arbitrary, really */ *size = 64; // This is arbitrary, really
*fullPath = realloc(*fullPath, *size); *fullPath = realloc(*fullPath, *size);
if (!*fullPath) if (!*fullPath)
error("realloc error during include path search: %s\n", error("realloc error during include path search: %s\n",
@@ -175,8 +186,8 @@ bool fstk_FindFile(char const *path, char **fullPath, size_t *size)
break; break;
} }
/* Oh how I wish `asnprintf` was standard... */ // Oh how I wish `asnprintf` was standard...
if ((size_t)len >= *size) { /* `len` doesn't include the terminator, `size` does */ if ((size_t)len >= *size) { // `size` includes the terminator, `len` doesn't
*size = len + 1; *size = len + 1;
*fullPath = realloc(*fullPath, *size); *fullPath = realloc(*fullPath, *size);
if (!*fullPath) { if (!*fullPath) {
@@ -213,17 +224,18 @@ bool yywrap(void)
fatalerror("Ended block with %" PRIu32 " unterminated IF construct%s\n", fatalerror("Ended block with %" PRIu32 " unterminated IF construct%s\n",
ifDepth, ifDepth == 1 ? "" : "s"); ifDepth, ifDepth == 1 ? "" : "s");
if (contextStack->fileInfo->type == NODE_REPT) { /* The context is a REPT block, which may loop */ if (contextStack->fileInfo->type == NODE_REPT) {
// The context is a REPT or FOR block, which may loop
struct FileStackReptNode *fileInfo = (struct FileStackReptNode *)contextStack->fileInfo; struct FileStackReptNode *fileInfo = (struct FileStackReptNode *)contextStack->fileInfo;
/* If the node is referenced, we can't edit it; duplicate it */ // If the node is referenced, we can't edit it; duplicate it
if (contextStack->fileInfo->referenced) { if (contextStack->fileInfo->referenced) {
size_t size = sizeof(*fileInfo) + sizeof(fileInfo->iters[0]) * fileInfo->reptDepth; size_t size = sizeof(*fileInfo) + sizeof(fileInfo->iters[0]) * fileInfo->reptDepth;
struct FileStackReptNode *copy = malloc(size); struct FileStackReptNode *copy = malloc(size);
if (!copy) if (!copy)
fatalerror("Failed to duplicate REPT file node: %s\n", strerror(errno)); fatalerror("Failed to duplicate REPT file node: %s\n", strerror(errno));
/* Copy all info but the referencing */ // Copy all info but the referencing
memcpy(copy, fileInfo, size); memcpy(copy, fileInfo, size);
copy->node.next = NULL; copy->node.next = NULL;
copy->node.referenced = false; copy->node.referenced = false;
@@ -232,19 +244,19 @@ bool yywrap(void)
contextStack->fileInfo = (struct FileStackNode *)fileInfo; contextStack->fileInfo = (struct FileStackNode *)fileInfo;
} }
/* If this is a FOR, update the symbol value */ // If this is a FOR, update the symbol value
if (contextStack->forName && fileInfo->iters[0] <= contextStack->nbReptIters) { if (contextStack->forName && fileInfo->iters[0] <= contextStack->nbReptIters) {
contextStack->forValue += contextStack->forStep; contextStack->forValue += contextStack->forStep;
struct Symbol *sym = sym_AddVar(contextStack->forName, struct Symbol *sym = sym_AddVar(contextStack->forName,
contextStack->forValue); contextStack->forValue);
/* This error message will refer to the current iteration */ // This error message will refer to the current iteration
if (sym->type != SYM_VAR) if (sym->type != SYM_VAR)
fatalerror("Failed to update FOR symbol value\n"); fatalerror("Failed to update FOR symbol value\n");
} }
/* Advance to the next iteration */ // Advance to the next iteration
fileInfo->iters[0]++; fileInfo->iters[0]++;
/* If this wasn't the last iteration, wrap instead of popping */ // If this wasn't the last iteration, wrap instead of popping
if (fileInfo->iters[0] <= contextStack->nbReptIters) { if (fileInfo->iters[0] <= contextStack->nbReptIters) {
lexer_RestartRept(contextStack->fileInfo->lineNo); lexer_RestartRept(contextStack->fileInfo->lineNo);
contextStack->uniqueID = macro_UseNewUniqueID(); contextStack->uniqueID = macro_UseNewUniqueID();
@@ -261,46 +273,47 @@ bool yywrap(void)
contextDepth--; contextDepth--;
lexer_DeleteState(context->lexerState); lexer_DeleteState(context->lexerState);
/* Restore args if a macro (not REPT) saved them */ // Restore args if a macro (not REPT) saved them
if (context->fileInfo->type == NODE_MACRO) if (context->fileInfo->type == NODE_MACRO)
macro_UseNewArgs(contextStack->macroArgs); macro_UseNewArgs(contextStack->macroArgs);
/* Free the file stack node */ // Free the file stack node
if (!context->fileInfo->referenced) if (!context->fileInfo->referenced)
free(context->fileInfo); free(context->fileInfo);
/* Free the FOR symbol name */ // Free the FOR symbol name
free(context->forName); free(context->forName);
/* Free the entry and make its parent the current entry */ // Free the entry and make its parent the current entry
free(context); free(context);
lexer_SetState(contextStack->lexerState); lexer_SetState(contextStack->lexerState);
macro_SetUniqueID(contextStack->uniqueID); macro_SetUniqueID(contextStack->uniqueID);
return false; return false;
} }
/* // Make sure not to switch the lexer state before calling this, so the saved line no is correct.
* Make sure not to switch the lexer state before calling this, so the saved line no is correct // BE CAREFUL! This modifies the file stack directly, you should have set up the file info first.
* BE CAREFUL!! This modifies the file stack directly, you should have set up the file info first // Callers should set contextStack->lexerState after this so it is not NULL.
* Callers should set contextStack->lexerState after this so it is not NULL
*/
static void newContext(struct FileStackNode *fileInfo) static void newContext(struct FileStackNode *fileInfo)
{ {
++contextDepth; ++contextDepth;
fstk_NewRecursionDepth(maxRecursionDepth); // Only checks if the max depth was exceeded fstk_NewRecursionDepth(maxRecursionDepth); // Only checks if the max depth was exceeded
// Save the current `\@` value, to be restored when this context ends
contextStack->uniqueID = macro_GetUniqueID();
struct Context *context = malloc(sizeof(*context)); struct Context *context = malloc(sizeof(*context));
if (!context) if (!context)
fatalerror("Failed to allocate memory for new context: %s\n", strerror(errno)); fatalerror("Failed to allocate memory for new context: %s\n", strerror(errno));
fileInfo->parent = contextStack->fileInfo; fileInfo->parent = contextStack->fileInfo;
fileInfo->lineNo = 0; /* Init to a default value, see struct definition for info */ fileInfo->lineNo = 0; // Init to a default value, see struct definition for info
fileInfo->referenced = false; fileInfo->referenced = false;
fileInfo->lineNo = lexer_GetLineNo(); fileInfo->lineNo = lexer_GetLineNo();
context->fileInfo = fileInfo; context->fileInfo = fileInfo;
context->forName = NULL; context->forName = NULL;
/*
* Link new entry to its parent so it's reachable later // Link new entry to its parent so it's reachable later
* ERRORS SHOULD NOT OCCUR AFTER THIS!! // ERRORS SHOULD NOT OCCUR AFTER THIS!
*/
context->parent = contextStack; context->parent = contextStack;
contextStack = context; contextStack = context;
} }
@@ -338,9 +351,43 @@ void fstk_RunInclude(char const *path)
if (!contextStack->lexerState) if (!contextStack->lexerState)
fatalerror("Failed to set up lexer for file include\n"); fatalerror("Failed to set up lexer for file include\n");
lexer_SetStateAtEOL(contextStack->lexerState); lexer_SetStateAtEOL(contextStack->lexerState);
/* We're back at top-level, so most things are reset */ // We're back at top-level, so most things are reset
contextStack->uniqueID = 0; contextStack->uniqueID = macro_UndefUniqueID();
macro_SetUniqueID(0); }
// Similar to `fstk_RunInclude`, but not subject to `-MG`, and
// calling `lexer_SetState` instead of `lexer_SetStateAtEOL`.
static void runPreIncludeFile(void)
{
if (!preIncludeName)
return;
char *fullPath = NULL;
size_t size = 0;
if (!fstk_FindFile(preIncludeName, &fullPath, &size)) {
free(fullPath);
error("Unable to open included file '%s': %s\n", preIncludeName, strerror(errno));
return;
}
struct FileStackNamedNode *fileInfo = malloc(sizeof(*fileInfo) + size);
if (!fileInfo) {
error("Failed to alloc file info for pre-include: %s\n", strerror(errno));
return;
}
fileInfo->node.type = NODE_FILE;
strcpy(fileInfo->name, fullPath);
free(fullPath);
newContext((struct FileStackNode *)fileInfo);
contextStack->lexerState = lexer_OpenFile(fileInfo->name);
if (!contextStack->lexerState)
fatalerror("Failed to set up lexer for file include\n");
lexer_SetState(contextStack->lexerState);
// We're back at top-level, so most things are reset
contextStack->uniqueID = macro_UndefUniqueID();
} }
void fstk_RunMacro(char const *macroName, struct MacroArgs *args) void fstk_RunMacro(char const *macroName, struct MacroArgs *args)
@@ -357,16 +404,16 @@ void fstk_RunMacro(char const *macroName, struct MacroArgs *args)
} }
contextStack->macroArgs = macro_GetCurrentArgs(); contextStack->macroArgs = macro_GetCurrentArgs();
/* Compute total length of this node's name: <base name>::<macro> */ // Compute total length of this node's name: <base name>::<macro>
size_t reptNameLen = 0; size_t reptNameLen = 0;
struct FileStackNode const *node = macro->src; struct FileStackNode const *node = macro->src;
if (node->type == NODE_REPT) { if (node->type == NODE_REPT) {
struct FileStackReptNode const *reptNode = (struct FileStackReptNode const *)node; struct FileStackReptNode const *reptNode = (struct FileStackReptNode const *)node;
/* 4294967295 = 2^32 - 1, aka UINT32_MAX */ // 4294967295 = 2^32 - 1, aka UINT32_MAX
reptNameLen += reptNode->reptDepth * strlen("::REPT~4294967295"); reptNameLen += reptNode->reptDepth * strlen("::REPT~4294967295");
/* Look for next named node */ // Look for next named node
do { do {
node = node->parent; node = node->parent;
} while (node->type == NODE_REPT); } while (node->type == NODE_REPT);
@@ -382,7 +429,7 @@ void fstk_RunMacro(char const *macroName, struct MacroArgs *args)
return; return;
} }
fileInfo->node.type = NODE_MACRO; fileInfo->node.type = NODE_MACRO;
/* Print the name... */ // Print the name...
char *dest = fileInfo->name; char *dest = fileInfo->name;
memcpy(dest, baseNode->name, baseLen); memcpy(dest, baseNode->name, baseLen);
@@ -429,13 +476,13 @@ static bool newReptContext(int32_t reptLineNo, char *body, size_t size)
fileInfo->reptDepth = reptDepth + 1; fileInfo->reptDepth = reptDepth + 1;
fileInfo->iters[0] = 1; fileInfo->iters[0] = 1;
if (reptDepth) if (reptDepth)
/* Copy all parent iter counts */ // Copy all parent iter counts
memcpy(&fileInfo->iters[1], memcpy(&fileInfo->iters[1],
((struct FileStackReptNode *)contextStack->fileInfo)->iters, ((struct FileStackReptNode *)contextStack->fileInfo)->iters,
reptDepth * sizeof(fileInfo->iters[0])); reptDepth * sizeof(fileInfo->iters[0]));
newContext((struct FileStackNode *)fileInfo); newContext((struct FileStackNode *)fileInfo);
/* Correct our line number, which currently points to the `ENDR` line */ // Correct our line number, which currently points to the `ENDR` line
contextStack->fileInfo->lineNo = reptLineNo; contextStack->fileInfo->lineNo = reptLineNo;
contextStack->lexerState = lexer_OpenFileView("REPT", body, size, reptLineNo); contextStack->lexerState = lexer_OpenFileView("REPT", body, size, reptLineNo);
@@ -493,7 +540,7 @@ void fstk_RunFor(char const *symName, int32_t start, int32_t stop, int32_t step,
void fstk_StopRept(void) void fstk_StopRept(void)
{ {
/* Prevent more iterations */ // Prevent more iterations
contextStack->nbReptIters = 0; contextStack->nbReptIters = 0;
} }
@@ -510,7 +557,7 @@ bool fstk_Break(void)
void fstk_NewRecursionDepth(size_t newDepth) void fstk_NewRecursionDepth(size_t newDepth)
{ {
if (contextDepth >= newDepth) if (contextDepth > newDepth)
fatalerror("Recursion limit (%zu) exceeded\n", newDepth); fatalerror("Recursion limit (%zu) exceeded\n", newDepth);
maxRecursionDepth = newDepth; maxRecursionDepth = newDepth;
} }
@@ -533,7 +580,7 @@ void fstk_Init(char const *mainPath, size_t maxDepth)
fatalerror("Failed to allocate memory for main file info: %s\n", strerror(errno)); fatalerror("Failed to allocate memory for main file info: %s\n", strerror(errno));
context->fileInfo = (struct FileStackNode *)fileInfo; context->fileInfo = (struct FileStackNode *)fileInfo;
/* lineNo and reptIter are unused on the top-level context */ // lineNo and reptIter are unused on the top-level context
context->fileInfo->parent = NULL; context->fileInfo->parent = NULL;
context->fileInfo->lineNo = 0; // This still gets written to the object file, so init it context->fileInfo->lineNo = 0; // This still gets written to the object file, so init it
context->fileInfo->referenced = false; context->fileInfo->referenced = false;
@@ -542,20 +589,17 @@ void fstk_Init(char const *mainPath, size_t maxDepth)
context->parent = NULL; context->parent = NULL;
context->lexerState = state; context->lexerState = state;
context->uniqueID = 0; context->uniqueID = macro_UndefUniqueID();
macro_SetUniqueID(0);
context->nbReptIters = 0; context->nbReptIters = 0;
context->forValue = 0; context->forValue = 0;
context->forStep = 0; context->forStep = 0;
context->forName = NULL; context->forName = NULL;
/* Now that it's set up properly, register the context */ // Now that it's set up properly, register the context
contextStack = context; contextStack = context;
/* // Check that max recursion depth won't allow overflowing node `malloc`s
* Check that max recursion depth won't allow overflowing node `malloc`s // This assumes that the rept node is larger
* This assumes that the rept node is larger
*/
#define DEPTH_LIMIT ((SIZE_MAX - sizeof(struct FileStackReptNode)) / sizeof(uint32_t)) #define DEPTH_LIMIT ((SIZE_MAX - sizeof(struct FileStackReptNode)) / sizeof(uint32_t))
if (maxDepth > DEPTH_LIMIT) { if (maxDepth > DEPTH_LIMIT) {
error("Recursion depth may not be higher than %zu, defaulting to " error("Recursion depth may not be higher than %zu, defaulting to "
@@ -564,7 +608,9 @@ void fstk_Init(char const *mainPath, size_t maxDepth)
} else { } else {
maxRecursionDepth = maxDepth; maxRecursionDepth = maxDepth;
} }
/* Make sure that the default of 64 is OK, though */ // Make sure that the default of 64 is OK, though
assert(DEPTH_LIMIT >= DEFAULT_MAX_DEPTH); assert(DEPTH_LIMIT >= DEFAULT_MAX_DEPTH);
#undef DEPTH_LIMIT #undef DEPTH_LIMIT
runPreIncludeFile();
} }

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,11 @@
/*
* This file is part of RGBDS.
*
* Copyright (c) 2022, Eldred Habert and RGBDS contributors.
*
* SPDX-License-Identifier: MIT
*/
#include <assert.h>
#include <errno.h> #include <errno.h>
#include <inttypes.h> #include <inttypes.h>
#include <stdbool.h> #include <stdbool.h>
@@ -12,13 +18,11 @@
#define MAXMACROARGS 99999 #define MAXMACROARGS 99999
/* // Your average macro invocation does not go past the tens, but some go further
* Your average macro invocation does not go past the tens, but some go further // This ensures that sane and slightly insane invocations suffer no penalties,
* This ensures that sane and slightly insane invocations suffer no penalties, // and the rest is insane and thus will assume responsibility.
* and the rest is insane and thus will assume responsibility. // Additionally, ~300 bytes (on x64) of memory per level of nesting has been
* Additionally, ~300 bytes (on x64) of memory per level of nesting has been // deemed reasonable. (Halve that on x86.)
* deemed reasonable. (Halve that on x86.)
*/
#define INITIAL_ARG_SIZE 32 #define INITIAL_ARG_SIZE 32
struct MacroArgs { struct MacroArgs {
unsigned int nbArgs; unsigned int nbArgs;
@@ -33,11 +37,9 @@ struct MacroArgs {
static struct MacroArgs *macroArgs = NULL; static struct MacroArgs *macroArgs = NULL;
static uint32_t uniqueID = 0; static uint32_t uniqueID = 0;
static uint32_t maxUniqueID = 0; static uint32_t maxUniqueID = 0;
/* // The initialization is somewhat harmful, since it is never used, but it
* The initialization is somewhat harmful, since it is never used, but it // guarantees the size of the buffer will be correct. I was unable to find a
* guarantees the size of the buffer will be correct. I was unable to find a // better solution, but if you have one, please feel free!
* better solution, but if you have one, please feel free!
*/
static char uniqueIDBuf[] = "_u4294967295"; // UINT32_MAX static char uniqueIDBuf[] = "_u4294967295"; // UINT32_MAX
static char *uniqueIDPtr = NULL; static char *uniqueIDPtr = NULL;
@@ -68,7 +70,7 @@ void macro_AppendArg(struct MacroArgs **argPtr, char *s)
error("A maximum of " EXPAND_AND_STR(MAXMACROARGS) " arguments is allowed\n"); error("A maximum of " EXPAND_AND_STR(MAXMACROARGS) " arguments is allowed\n");
if (macArgs->nbArgs >= macArgs->capacity) { if (macArgs->nbArgs >= macArgs->capacity) {
macArgs->capacity *= 2; macArgs->capacity *= 2;
/* Check that overflow didn't roll us back */ // Check that overflow didn't roll us back
if (macArgs->capacity <= macArgs->nbArgs) if (macArgs->capacity <= macArgs->nbArgs)
fatalerror("Failed to add new macro argument: capacity overflow\n"); fatalerror("Failed to add new macro argument: capacity overflow\n");
macArgs = realloc(macArgs, SIZEOF_ARGS(macArgs->capacity)); macArgs = realloc(macArgs, SIZEOF_ARGS(macArgs->capacity));
@@ -112,9 +114,9 @@ char const *macro_GetAllArgs(void)
size_t len = 0; size_t len = 0;
for (uint32_t i = macroArgs->shift; i < macroArgs->nbArgs; i++) for (uint32_t i = macroArgs->shift; i < macroArgs->nbArgs; i++)
len += strlen(macroArgs->args[i]) + 1; /* 1 for comma */ len += strlen(macroArgs->args[i]) + 1; // 1 for comma
char *str = malloc(len + 1); /* 1 for '\0' */ char *str = malloc(len + 1); // 1 for '\0'
char *ptr = str; char *ptr = str;
if (!str) if (!str)
@@ -126,9 +128,9 @@ char const *macro_GetAllArgs(void)
memcpy(ptr, macroArgs->args[i], n); memcpy(ptr, macroArgs->args[i], n);
ptr += n; ptr += n;
/* Commas go between args and after a last empty arg */ // Commas go between args and after a last empty arg
if (i < macroArgs->nbArgs - 1 || n == 0) if (i < macroArgs->nbArgs - 1 || n == 0)
*ptr++ = ','; /* no space after comma */ *ptr++ = ','; // no space after comma
} }
*ptr = '\0'; *ptr = '\0';
@@ -142,19 +144,21 @@ uint32_t macro_GetUniqueID(void)
char const *macro_GetUniqueIDStr(void) char const *macro_GetUniqueIDStr(void)
{ {
// Generate a new unique ID on the first use of `\@`
if (uniqueID == 0)
macro_SetUniqueID(++maxUniqueID);
return uniqueIDPtr; return uniqueIDPtr;
} }
void macro_SetUniqueID(uint32_t id) void macro_SetUniqueID(uint32_t id)
{ {
uniqueID = id; uniqueID = id;
if (id == 0) { if (id == 0 || id == (uint32_t)-1) {
uniqueIDPtr = NULL; uniqueIDPtr = NULL;
} else { } else {
if (uniqueID > maxUniqueID) // The buffer is guaranteed to be the correct size
maxUniqueID = uniqueID; // This is a valid label fragment, but not a valid numeric
/* The buffer is guaranteed to be the correct size */
/* This is a valid label fragment, but not a valid numeric */
sprintf(uniqueIDBuf, "_u%" PRIu32, id); sprintf(uniqueIDBuf, "_u%" PRIu32, id);
uniqueIDPtr = uniqueIDBuf; uniqueIDPtr = uniqueIDBuf;
} }
@@ -162,8 +166,16 @@ void macro_SetUniqueID(uint32_t id)
uint32_t macro_UseNewUniqueID(void) uint32_t macro_UseNewUniqueID(void)
{ {
macro_SetUniqueID(++maxUniqueID); // A new ID will be generated on the first use of `\@`
return maxUniqueID; macro_SetUniqueID(0);
return uniqueID;
}
uint32_t macro_UndefUniqueID(void)
{
// No ID will be generated; use of `\@` is an error
macro_SetUniqueID((uint32_t)-1);
return uniqueID;
} }
void macro_ShiftCurrentArgs(int32_t count) void macro_ShiftCurrentArgs(int32_t count)

View File

@@ -19,6 +19,7 @@
#include <time.h> #include <time.h>
#include "asm/charmap.h" #include "asm/charmap.h"
#include "asm/fixpoint.h"
#include "asm/format.h" #include "asm/format.h"
#include "asm/fstack.h" #include "asm/fstack.h"
#include "asm/lexer.h" #include "asm/lexer.h"
@@ -39,8 +40,8 @@
#ifdef __clang__ #ifdef __clang__
#if __has_feature(address_sanitizer) && !defined(__SANITIZE_ADDRESS__) #if __has_feature(address_sanitizer) && !defined(__SANITIZE_ADDRESS__)
#define __SANITIZE_ADDRESS__ #define __SANITIZE_ADDRESS__
#endif /* __has_feature(address_sanitizer) && !defined(__SANITIZE_ADDRESS__) */ #endif // __has_feature(address_sanitizer) && !defined(__SANITIZE_ADDRESS__)
#endif /* __clang__ */ #endif // __clang__
#ifdef __SANITIZE_ADDRESS__ #ifdef __SANITIZE_ADDRESS__
// There are known, non-trivial to fix leaks. We would still like to have `make develop' // There are known, non-trivial to fix leaks. We would still like to have `make develop'
@@ -52,20 +53,20 @@ char const *__asan_default_options(void) { return "detect_leaks=0"; }
// Unfortunately, macOS still ships 2.3, which is from 2008... // Unfortunately, macOS still ships 2.3, which is from 2008...
int yyparse(void); int yyparse(void);
FILE * dependfile; FILE *dependfile = NULL;
bool generatedMissingIncludes; bool generatedMissingIncludes = false;
bool failedOnMissingInclude; bool failedOnMissingInclude = false;
bool generatePhonyDeps; bool generatePhonyDeps = false;
char *targetFileName; char *targetFileName = NULL;
bool haltnop; bool haltnop;
bool warnOnHaltNop; bool warnOnHaltNop;
bool optimizeLoads; bool optimizeLoads;
bool warnOnLdOpt; bool warnOnLdOpt;
bool verbose; bool verbose;
bool warnings; /* True to enable warnings, false to disable them. */ bool warnings; // True to enable warnings, false to disable them.
/* Escapes Make-special chars from a string */ // Escapes Make-special chars from a string
static char *make_escape(char const *str) static char *make_escape(char const *str)
{ {
char * const escaped_str = malloc(strlen(str) * 2 + 1); char * const escaped_str = malloc(strlen(str) * 2 + 1);
@@ -75,7 +76,7 @@ static char *make_escape(char const *str)
err("%s: Failed to allocate memory", __func__); err("%s: Failed to allocate memory", __func__);
while (*str) { while (*str) {
/* All dollars needs to be doubled */ // All dollars needs to be doubled
if (*str == '$') if (*str == '$')
*dest++ = '$'; *dest++ = '$';
*dest++ = *str++; *dest++ = *str++;
@@ -85,22 +86,20 @@ static char *make_escape(char const *str)
return escaped_str; return escaped_str;
} }
/* Short options */ // Short options
static const char *optstring = "b:D:Eg:Hhi:LlM:o:p:r:VvW:w"; static const char *optstring = "b:D:Eg:Hhi:I:LlM:o:P:p:Q:r:VvW:w";
/* Variables for the long-only options */ // Variables for the long-only options
static int depType; /* Variants of `-M` */ static int depType; // Variants of `-M`
/* // Equivalent long options
* Equivalent long options // Please keep in the same order as short opts
* Please keep in the same order as short opts //
* // Also, make sure long opts don't create ambiguity:
* Also, make sure long opts don't create ambiguity: // A long opt's name should start with the same letter as its short opt,
* A long opt's name should start with the same letter as its short opt, // except if it doesn't create any ambiguity (`verbose` versus `version`).
* except if it doesn't create any ambiguity (`verbose` versus `version`). // This is because long opt matching, even to a single char, is prioritized
* This is because long opt matching, even to a single char, is prioritized // over short opt matching
* over short opt matching
*/
static struct option const longopts[] = { static struct option const longopts[] = {
{ "binary-digits", required_argument, NULL, 'b' }, { "binary-digits", required_argument, NULL, 'b' },
{ "define", required_argument, NULL, 'D' }, { "define", required_argument, NULL, 'D' },
@@ -108,7 +107,7 @@ static struct option const longopts[] = {
{ "gfx-chars", required_argument, NULL, 'g' }, { "gfx-chars", required_argument, NULL, 'g' },
{ "nop-after-halt", no_argument, NULL, 'H' }, { "nop-after-halt", no_argument, NULL, 'H' },
{ "halt-without-nop", no_argument, NULL, 'h' }, { "halt-without-nop", no_argument, NULL, 'h' },
{ "include", required_argument, NULL, 'i' }, { "include", required_argument, NULL, 'I' },
{ "preserve-ld", no_argument, NULL, 'L' }, { "preserve-ld", no_argument, NULL, 'L' },
{ "auto-ldh", no_argument, NULL, 'l' }, { "auto-ldh", no_argument, NULL, 'l' },
{ "dependfile", required_argument, NULL, 'M' }, { "dependfile", required_argument, NULL, 'M' },
@@ -117,7 +116,9 @@ static struct option const longopts[] = {
{ "MT", required_argument, &depType, 'T' }, { "MT", required_argument, &depType, 'T' },
{ "MQ", required_argument, &depType, 'Q' }, { "MQ", required_argument, &depType, 'Q' },
{ "output", required_argument, NULL, 'o' }, { "output", required_argument, NULL, 'o' },
{ "preinclude", required_argument, NULL, 'P' },
{ "pad-value", required_argument, NULL, 'p' }, { "pad-value", required_argument, NULL, 'p' },
{ "q-precision", required_argument, NULL, 'Q' },
{ "recursion-depth", required_argument, NULL, 'r' }, { "recursion-depth", required_argument, NULL, 'r' },
{ "version", no_argument, NULL, 'V' }, { "version", no_argument, NULL, 'V' },
{ "verbose", no_argument, NULL, 'v' }, { "verbose", no_argument, NULL, 'v' },
@@ -128,9 +129,10 @@ static struct option const longopts[] = {
static void print_usage(void) static void print_usage(void)
{ {
fputs( fputs(
"Usage: rgbasm [-EhLVvw] [-b chars] [-D name[=value]] [-g chars] [-i path]\n" "Usage: rgbasm [-EHhLlVvw] [-b chars] [-D name[=value]] [-g chars] [-I path]\n"
" [-M depend_file] [-MG] [-MP] [-MT target_file] [-MQ target_file]\n" " [-M depend_file] [-MG] [-MP] [-MT target_file] [-MQ target_file]\n"
" [-o out_file] [-p pad_value] [-r depth] [-W warning] <file>\n" " [-o out_file] [-P include_file] [-p pad_value] [-Q precision]\n"
" [-r depth] [-W warning] <file>\n"
"Useful options:\n" "Useful options:\n"
" -E, --export-all export all labels\n" " -E, --export-all export all labels\n"
" -M, --dependfile <path> set the output dependency file\n" " -M, --dependfile <path> set the output dependency file\n"
@@ -146,34 +148,23 @@ static void print_usage(void)
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
int ch;
char *ep;
time_t now = time(NULL); time_t now = time(NULL);
char const *sourceDateEpoch = getenv("SOURCE_DATE_EPOCH"); char const *sourceDateEpoch = getenv("SOURCE_DATE_EPOCH");
/* // Support SOURCE_DATE_EPOCH for reproducible builds
* Support SOURCE_DATE_EPOCH for reproducible builds // https://reproducible-builds.org/docs/source-date-epoch/
* https://reproducible-builds.org/docs/source-date-epoch/
*/
if (sourceDateEpoch) if (sourceDateEpoch)
now = (time_t)strtoul(sourceDateEpoch, NULL, 0); now = (time_t)strtoul(sourceDateEpoch, NULL, 0);
dependfile = NULL;
// Perform some init for below // Perform some init for below
sym_Init(now); sym_Init(now);
// Set defaults // Set defaults
generatePhonyDeps = false;
generatedMissingIncludes = false;
failedOnMissingInclude = false;
targetFileName = NULL;
opt_B("01"); opt_B("01");
opt_G("0123"); opt_G("0123");
opt_P(0); opt_P(0);
opt_Q(16);
haltnop = true; haltnop = true;
warnOnHaltNop = true; warnOnHaltNop = true;
optimizeLoads = true; optimizeLoads = true;
@@ -181,14 +172,17 @@ int main(int argc, char *argv[])
verbose = false; verbose = false;
warnings = true; warnings = true;
sym_SetExportAll(false); sym_SetExportAll(false);
uint32_t maxDepth = 64; uint32_t maxDepth = DEFAULT_MAX_DEPTH;
char *dependFileName = NULL;
size_t targetFileNameLen = 0; size_t targetFileNameLen = 0;
int ch;
char *ep;
while ((ch = musl_getopt_long_only(argc, argv, optstring, longopts, NULL)) != -1) { while ((ch = musl_getopt_long_only(argc, argv, optstring, longopts, NULL)) != -1) {
switch (ch) { switch (ch) {
case 'b': case 'b':
if (strlen(musl_optarg) == 2) if (strlen(musl_optarg) == 2)
opt_B(&musl_optarg[1]); opt_B(musl_optarg);
else else
errx("Must specify exactly 2 characters for option 'b'"); errx("Must specify exactly 2 characters for option 'b'");
break; break;
@@ -210,7 +204,7 @@ int main(int argc, char *argv[])
case 'g': case 'g':
if (strlen(musl_optarg) == 4) if (strlen(musl_optarg) == 4)
opt_G(&musl_optarg[1]); opt_G(musl_optarg);
else else
errx("Must specify exactly 4 characters for option 'g'"); errx("Must specify exactly 4 characters for option 'g'");
break; break;
@@ -226,6 +220,10 @@ int main(int argc, char *argv[])
haltnop = false; haltnop = false;
break; break;
// `-i` was the only short option for `--include` until `-I` was
// introduced to better match the `-I dir` option of gcc and clang.
// `-i` is now undocumented but still supported for now.
case 'I':
case 'i': case 'i':
fstk_AddIncludePath(musl_optarg); fstk_AddIncludePath(musl_optarg);
break; break;
@@ -242,29 +240,55 @@ int main(int argc, char *argv[])
break; break;
case 'M': case 'M':
if (!strcmp("-", musl_optarg)) if (dependfile)
warnx("Overriding dependfile %s", dependFileName);
if (!strcmp("-", musl_optarg)) {
dependfile = stdout; dependfile = stdout;
else dependFileName = "<stdout>";
} else {
dependfile = fopen(musl_optarg, "w"); dependfile = fopen(musl_optarg, "w");
dependFileName = musl_optarg;
}
if (dependfile == NULL) if (dependfile == NULL)
err("Could not open dependfile %s", musl_optarg); err("Could not open dependfile %s", dependFileName);
break; break;
case 'o': case 'o':
out_SetFileName(musl_optarg); out_SetFileName(musl_optarg);
break; break;
unsigned long fill; case 'P':
fstk_SetPreIncludeFile(musl_optarg);
break;
unsigned long padByte;
case 'p': case 'p':
fill = strtoul(musl_optarg, &ep, 0); padByte = strtoul(musl_optarg, &ep, 0);
if (musl_optarg[0] == '\0' || *ep != '\0') if (musl_optarg[0] == '\0' || *ep != '\0')
errx("Invalid argument for option 'p'"); errx("Invalid argument for option 'p'");
if (fill > 0xFF) if (padByte > 0xFF)
errx("Argument for option 'p' must be between 0 and 0xFF"); errx("Argument for option 'p' must be between 0 and 0xFF");
opt_P(fill); opt_P(padByte);
break;
unsigned long precision;
const char *precisionArg;
case 'Q':
precisionArg = musl_optarg;
if (precisionArg[0] == '.')
precisionArg++;
precision = strtoul(precisionArg, &ep, 0);
if (musl_optarg[0] == '\0' || *ep != '\0')
errx("Invalid argument for option 'Q'");
if (precision < 1 || precision > 31)
errx("Argument for option 'Q' must be between 1 and 31");
opt_Q(precision);
break; break;
case 'r': case 'r':
@@ -277,6 +301,7 @@ int main(int argc, char *argv[])
case 'V': case 'V':
printf("rgbasm %s\n", get_package_version_string()); printf("rgbasm %s\n", get_package_version_string());
exit(0); exit(0);
case 'v': case 'v':
verbose = true; verbose = true;
break; break;
@@ -289,7 +314,7 @@ int main(int argc, char *argv[])
warnings = false; warnings = false;
break; break;
/* Long-only options */ // Long-only options
case 0: case 0:
switch (depType) { switch (depType) {
case 'G': case 'G':
@@ -321,10 +346,10 @@ int main(int argc, char *argv[])
} }
break; break;
/* Unrecognized options */ // Unrecognized options
default: default:
print_usage(); print_usage();
/* NOTREACHED */ // NOTREACHED
} }
} }
@@ -334,10 +359,10 @@ int main(int argc, char *argv[])
targetFileName[targetFileNameLen - 1] = '\0'; // Overwrite the last space targetFileName[targetFileNameLen - 1] = '\0'; // Overwrite the last space
if (argc == musl_optind) { if (argc == musl_optind) {
fputs("FATAL: No input files\n", stderr); fputs("FATAL: Please specify an input file (pass `-` to read from standard input)\n", stderr);
print_usage(); print_usage();
} else if (argc != musl_optind + 1) { } else if (argc != musl_optind + 1) {
fputs("FATAL: More than one input file given\n", stderr); fputs("FATAL: More than one input file specified\n", stderr);
print_usage(); print_usage();
} }
@@ -353,9 +378,9 @@ int main(int argc, char *argv[])
fprintf(dependfile, "%s: %s\n", targetFileName, mainFileName); fprintf(dependfile, "%s: %s\n", targetFileName, mainFileName);
} }
charmap_New("main", NULL); charmap_New(DEFAULT_CHARMAP_NAME, NULL);
// Init lexer and file stack, prodiving file info // Init lexer and file stack, providing file info
lexer_Init(); lexer_Init();
fstk_Init(mainFileName, maxDepth); fstk_Init(mainFileName, maxDepth);
@@ -376,7 +401,7 @@ int main(int argc, char *argv[])
if (failedOnMissingInclude) if (failedOnMissingInclude)
return 0; return 0;
/* If no path specified, don't write file */ // If no path specified, don't write file
if (objectName != NULL) if (objectName != NULL)
out_WriteObject(); out_WriteObject();
return 0; return 0;

View File

@@ -1,3 +1,11 @@
/*
* This file is part of RGBDS.
*
* Copyright (c) 2022, RGBDS contributors.
*
* SPDX-License-Identifier: MIT
*/
#include <ctype.h> #include <ctype.h>
#include <errno.h> #include <errno.h>
#include <stdbool.h> #include <stdbool.h>
@@ -6,6 +14,7 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include "asm/fixpoint.h"
#include "asm/fstack.h" #include "asm/fstack.h"
#include "asm/lexer.h" #include "asm/lexer.h"
#include "asm/main.h" #include "asm/main.h"
@@ -15,7 +24,8 @@
struct OptStackEntry { struct OptStackEntry {
char binary[2]; char binary[2];
char gbgfx[4]; char gbgfx[4];
int32_t fillByte; uint8_t fixPrecision;
uint8_t fillByte;
bool haltnop; bool haltnop;
bool warnOnHaltNop; bool warnOnHaltNop;
bool optimizeLoads; bool optimizeLoads;
@@ -39,9 +49,14 @@ void opt_G(char const chars[4])
lexer_SetGfxDigits(chars); lexer_SetGfxDigits(chars);
} }
void opt_P(uint8_t fill) void opt_P(uint8_t padByte)
{ {
fillByte = fill; fillByte = padByte;
}
void opt_Q(uint8_t precision)
{
fixPrecision = precision;
} }
void opt_R(size_t newDepth) void opt_R(size_t newDepth)
@@ -95,18 +110,41 @@ void opt_Parse(char *s)
case 'p': case 'p':
if (strlen(&s[1]) <= 2) { if (strlen(&s[1]) <= 2) {
int result; int result;
unsigned int fillchar; unsigned int padByte;
result = sscanf(&s[1], "%x", &fillchar); result = sscanf(&s[1], "%x", &padByte);
if (result != EOF && result != 1) if (result != 1)
error("Invalid argument for option 'p'\n"); error("Invalid argument for option 'p'\n");
else if (padByte > 0xFF)
error("Argument for option 'p' must be between 0 and 0xFF\n");
else else
opt_P(fillchar); opt_P(padByte);
} else { } else {
error("Invalid argument for option 'p'\n"); error("Invalid argument for option 'p'\n");
} }
break; break;
const char *precisionArg;
case 'Q':
precisionArg = &s[1];
if (precisionArg[0] == '.')
precisionArg++;
if (strlen(precisionArg) <= 2) {
int result;
unsigned int precision;
result = sscanf(precisionArg, "%u", &precision);
if (result != 1)
error("Invalid argument for option 'Q'\n");
else if (precision < 1 || precision > 31)
error("Argument for option 'Q' must be between 1 and 31\n");
else
opt_Q(precision);
} else {
error("Invalid argument for option 'Q'\n");
}
break;
case 'r': { case 'r': {
++s; // Skip 'r' ++s; // Skip 'r'
while (isblank(*s)) while (isblank(*s))
@@ -167,6 +205,13 @@ void opt_Parse(char *s)
case '!': // negates flag options that do not take an argument case '!': // negates flag options that do not take an argument
switch (s[1]) { switch (s[1]) {
case 'H':
if (s[2] == '\0')
opt_H(true);
else
error("Option '!H' does not take an argument\n");
break;
case 'h': case 'h':
if (s[2] == '\0') if (s[2] == '\0')
opt_h(true); opt_h(true);
@@ -181,6 +226,13 @@ void opt_Parse(char *s)
error("Option '!L' does not take an argument\n"); error("Option '!L' does not take an argument\n");
break; break;
case 'l':
if (s[2] == '\0')
opt_l(true);
else
error("Option '!l' does not take an argument\n");
break;
default: default:
error("Unknown option '!%c'\n", s[1]); error("Unknown option '!%c'\n", s[1]);
break; break;
@@ -209,6 +261,8 @@ void opt_Push(void)
entry->gbgfx[2] = gfxDigits[2]; entry->gbgfx[2] = gfxDigits[2];
entry->gbgfx[3] = gfxDigits[3]; entry->gbgfx[3] = gfxDigits[3];
entry->fixPrecision = fixPrecision; // Pulled from fixpoint.h
entry->fillByte = fillByte; // Pulled from section.h entry->fillByte = fillByte; // Pulled from section.h
entry->haltnop = haltnop; // Pulled from main.h entry->haltnop = haltnop; // Pulled from main.h
@@ -237,6 +291,7 @@ void opt_Pop(void)
opt_B(entry->binary); opt_B(entry->binary);
opt_G(entry->gbgfx); opt_G(entry->gbgfx);
opt_P(entry->fillByte); opt_P(entry->fillByte);
opt_Q(entry->fixPrecision);
opt_H(entry->warnOnHaltNop); opt_H(entry->warnOnHaltNop);
opt_h(entry->haltnop); opt_h(entry->haltnop);
opt_L(entry->optimizeLoads); opt_L(entry->optimizeLoads);

View File

@@ -6,9 +6,7 @@
* SPDX-License-Identifier: MIT * SPDX-License-Identifier: MIT
*/ */
/* // Outputs an objectfile
* Outputs an objectfile
*/
#include <assert.h> #include <assert.h>
#include <errno.h> #include <errno.h>
@@ -54,18 +52,16 @@ char *objectName;
struct Section *sectionList; struct Section *sectionList;
/* Linked list of symbols to put in the object file */ // Linked list of symbols to put in the object file
static struct Symbol *objectSymbols = NULL; static struct Symbol *objectSymbols = NULL;
static struct Symbol **objectSymbolsTail = &objectSymbols; static struct Symbol **objectSymbolsTail = &objectSymbols;
static uint32_t nbSymbols = 0; /* Length of the above list */ static uint32_t nbSymbols = 0; // Length of the above list
static struct Assertion *assertions = NULL; static struct Assertion *assertions = NULL;
static struct FileStackNode *fileStackNodes = NULL; static struct FileStackNode *fileStackNodes = NULL;
/* // Count the number of sections used in this object
* Count the number of sections used in this object
*/
static uint32_t countSections(void) static uint32_t countSections(void)
{ {
uint32_t count = 0; uint32_t count = 0;
@@ -76,9 +72,7 @@ static uint32_t countSections(void)
return count; return count;
} }
/* // Count the number of patches used in this object
* Count the number of patches used in this object
*/
static uint32_t countPatches(struct Section const *sect) static uint32_t countPatches(struct Section const *sect)
{ {
uint32_t r = 0; uint32_t r = 0;
@@ -90,9 +84,7 @@ static uint32_t countPatches(struct Section const *sect)
return r; return r;
} }
/** // Count the number of assertions used in this object
* Count the number of assertions used in this object
*/
static uint32_t countAsserts(void) static uint32_t countAsserts(void)
{ {
struct Assertion *assert = assertions; struct Assertion *assert = assertions;
@@ -105,9 +97,7 @@ static uint32_t countAsserts(void)
return count; return count;
} }
/* // Write a long to a file (little-endian)
* Write a long to a file (little-endian)
*/
static void putlong(uint32_t i, FILE *f) static void putlong(uint32_t i, FILE *f)
{ {
putc(i, f); putc(i, f);
@@ -116,9 +106,7 @@ static void putlong(uint32_t i, FILE *f)
putc(i >> 24, f); putc(i >> 24, f);
} }
/* // Write a NULL-terminated string to a file
* Write a NULL-terminated string to a file
*/
static void putstring(char const *s, FILE *f) static void putstring(char const *s, FILE *f)
{ {
while (*s) while (*s)
@@ -133,7 +121,7 @@ static uint32_t getNbFileStackNodes(void)
void out_RegisterNode(struct FileStackNode *node) void out_RegisterNode(struct FileStackNode *node)
{ {
/* If node is not already registered, register it (and parents), and give it a unique ID */ // If node is not already registered, register it (and parents), and give it a unique ID
while (node->ID == (uint32_t)-1) { while (node->ID == (uint32_t)-1) {
node->ID = getNbFileStackNodes(); node->ID = getNbFileStackNodes();
if (node->ID == (uint32_t)-1) if (node->ID == (uint32_t)-1)
@@ -141,7 +129,7 @@ void out_RegisterNode(struct FileStackNode *node)
node->next = fileStackNodes; node->next = fileStackNodes;
fileStackNodes = node; fileStackNodes = node;
/* Also register the node's parents */ // Also register the node's parents
node = node->parent; node = node->parent;
if (!node) if (!node)
break; break;
@@ -156,25 +144,21 @@ This is code intended to replace a node, which is pretty useless until ref count
struct FileStackNode **ptr = &fileStackNodes; struct FileStackNode **ptr = &fileStackNodes;
/* // The linked list is supposed to have decrementing IDs, so iterate with less memory reads,
* The linked list is supposed to have decrementing IDs, so iterate with less memory reads, // to hopefully hit the cache less. A debug check is added after, in case a change is made
* to hopefully hit the cache less. A debug check is added after, in case a change is made // that breaks this assumption.
* that breaks this assumption.
*/
for (uint32_t i = fileStackNodes->ID; i != node->ID; i--) for (uint32_t i = fileStackNodes->ID; i != node->ID; i--)
ptr = &(*ptr)->next; ptr = &(*ptr)->next;
assert((*ptr)->ID == node->ID); assert((*ptr)->ID == node->ID);
node->next = (*ptr)->next; node->next = (*ptr)->next;
assert(!node->next || node->next->ID == node->ID - 1); /* Catch inconsistencies early */ assert(!node->next || node->next->ID == node->ID - 1); // Catch inconsistencies early
/* TODO: unreference the node */ // TODO: unreference the node
*ptr = node; *ptr = node;
#endif #endif
} }
/* // Return a section's ID
* Return a section's ID
*/
static uint32_t getsectid(struct Section const *sect) static uint32_t getsectid(struct Section const *sect)
{ {
struct Section const *sec = sectionList; struct Section const *sec = sectionList;
@@ -195,9 +179,7 @@ static uint32_t getSectIDIfAny(struct Section const *sect)
return sect ? getsectid(sect) : (uint32_t)-1; return sect ? getsectid(sect) : (uint32_t)-1;
} }
/* // Write a patch to a file
* Write a patch to a file
*/
static void writepatch(struct Patch const *patch, FILE *f) static void writepatch(struct Patch const *patch, FILE *f)
{ {
assert(patch->src->ID != (uint32_t)-1); assert(patch->src->ID != (uint32_t)-1);
@@ -211,9 +193,7 @@ static void writepatch(struct Patch const *patch, FILE *f)
fwrite(patch->rpn, 1, patch->rpnSize, f); fwrite(patch->rpn, 1, patch->rpnSize, f);
} }
/* // Write a section to a file
* Write a section to a file
*/
static void writesection(struct Section const *sect, FILE *f) static void writesection(struct Section const *sect, FILE *f)
{ {
putstring(sect->name, f); putstring(sect->name, f);
@@ -255,9 +235,7 @@ static void freesection(struct Section const *sect)
} }
} }
/* // Write a symbol to a file
* Write a symbol to a file
*/
static void writesymbol(struct Symbol const *sym, FILE *f) static void writesymbol(struct Symbol const *sym, FILE *f)
{ {
putstring(sym->name, f); putstring(sym->name, f);
@@ -285,10 +263,8 @@ static void registerSymbol(struct Symbol *sym)
sym->ID = nbSymbols++; sym->ID = nbSymbols++;
} }
/* // Returns a symbol's ID within the object file
* Returns a symbol's ID within the object file // If the symbol does not have one, one is assigned by registering the symbol
* If the symbol does not have one, one is assigned by registering the symbol
*/
static uint32_t getSymbolID(struct Symbol *sym) static uint32_t getSymbolID(struct Symbol *sym)
{ {
if (sym->ID == (uint32_t)-1 && !sym_IsPC(sym)) if (sym->ID == (uint32_t)-1 && !sym_IsPC(sym))
@@ -392,10 +368,8 @@ static void writerpn(uint8_t *rpnexpr, uint32_t *rpnptr, uint8_t *rpn,
} }
} }
/* // Allocate a new patch structure and link it into the list
* Allocate a new patch structure and link it into the list // WARNING: all patches are assumed to eventually be written, so the file stack node is registered
* WARNING: all patches are assumed to eventually be written, so the file stack node is registered
*/
static struct Patch *allocpatch(uint32_t type, struct Expression const *expr, uint32_t ofs) static struct Patch *allocpatch(uint32_t type, struct Expression const *expr, uint32_t ofs)
{ {
struct Patch *patch = malloc(sizeof(struct Patch)); struct Patch *patch = malloc(sizeof(struct Patch));
@@ -417,10 +391,10 @@ static struct Patch *allocpatch(uint32_t type, struct Expression const *expr, ui
patch->pcSection = sect_GetSymbolSection(); patch->pcSection = sect_GetSymbolSection();
patch->pcOffset = sect_GetSymbolOffset(); patch->pcOffset = sect_GetSymbolOffset();
/* If the rpnSize's value is known, output a constant RPN rpnSize directly */ // If the rpnSize's value is known, output a constant RPN rpnSize directly
if (expr->isKnown) { if (expr->isKnown) {
patch->rpnSize = rpnSize; patch->rpnSize = rpnSize;
/* Make sure to update `rpnSize` above if modifying this! */ // Make sure to update `rpnSize` above if modifying this!
patch->rpn[0] = RPN_CONST; patch->rpn[0] = RPN_CONST;
patch->rpn[1] = (uint32_t)(expr->val) & 0xFF; patch->rpn[1] = (uint32_t)(expr->val) & 0xFF;
patch->rpn[2] = (uint32_t)(expr->val) >> 8; patch->rpn[2] = (uint32_t)(expr->val) >> 8;
@@ -435,9 +409,7 @@ static struct Patch *allocpatch(uint32_t type, struct Expression const *expr, ui
return patch; return patch;
} }
/* // Create a new patch (includes the rpn expr)
* Create a new patch (includes the rpn expr)
*/
void out_CreatePatch(uint32_t type, struct Expression const *expr, uint32_t ofs, uint32_t pcShift) void out_CreatePatch(uint32_t type, struct Expression const *expr, uint32_t ofs, uint32_t pcShift)
{ {
struct Patch *patch = allocpatch(type, expr, ofs); struct Patch *patch = allocpatch(type, expr, ofs);
@@ -451,9 +423,7 @@ void out_CreatePatch(uint32_t type, struct Expression const *expr, uint32_t ofs,
currentSection->patches = patch; currentSection->patches = patch;
} }
/** // Creates an assert that will be written to the object file
* Creates an assert that will be written to the object file
*/
bool out_CreateAssert(enum AssertionType type, struct Expression const *expr, bool out_CreateAssert(enum AssertionType type, struct Expression const *expr,
char const *message, uint32_t ofs) char const *message, uint32_t ofs)
{ {
@@ -499,7 +469,7 @@ static void writeFileStackNode(struct FileStackNode const *node, FILE *f)
struct FileStackReptNode const *reptNode = (struct FileStackReptNode const *)node; struct FileStackReptNode const *reptNode = (struct FileStackReptNode const *)node;
putlong(reptNode->reptDepth, f); putlong(reptNode->reptDepth, f);
/* Iters are stored by decreasing depth, so reverse the order for output */ // Iters are stored by decreasing depth, so reverse the order for output
for (uint32_t i = reptNode->reptDepth; i--; ) for (uint32_t i = reptNode->reptDepth; i--; )
putlong(reptNode->iters[i], f); putlong(reptNode->iters[i], f);
} }
@@ -515,24 +485,22 @@ static void registerUnregisteredSymbol(struct Symbol *symbol, void *arg)
} }
} }
/* // Write an objectfile
* Write an objectfile
*/
void out_WriteObject(void) void out_WriteObject(void)
{ {
FILE *f; FILE *f;
if (strcmp(objectName, "-") != 0) if (strcmp(objectName, "-") != 0)
f = fopen(objectName, "wb"); f = fopen(objectName, "wb");
else else
f = fdopen(1, "wb"); f = fdopen(STDOUT_FILENO, "wb");
if (!f) if (!f)
err("Couldn't write file '%s'", objectName); err("Couldn't write file '%s'", objectName);
/* Also write symbols that weren't written above */ // Also write symbols that weren't written above
sym_ForEach(registerUnregisteredSymbol, NULL); sym_ForEach(registerUnregisteredSymbol, NULL);
fprintf(f, RGBDS_OBJECT_VERSION_STRING, RGBDS_OBJECT_VERSION_NUMBER); fprintf(f, RGBDS_OBJECT_VERSION_STRING);
putlong(RGBDS_OBJECT_REV, f); putlong(RGBDS_OBJECT_REV, f);
putlong(nbSymbols, f); putlong(nbSymbols, f);
@@ -569,12 +537,12 @@ void out_WriteObject(void)
fclose(f); fclose(f);
} }
/* // Set the objectfilename
* Set the objectfilename
*/
void out_SetFileName(char *s) void out_SetFileName(char *s)
{ {
if (objectName)
warnx("Overriding output filename %s", objectName);
objectName = s; objectName = s;
if (verbose) if (verbose)
printf("Output filename %s\n", s); printf("Output filename %s\n", objectName);
} }

View File

@@ -36,7 +36,7 @@
#include "linkdefs.h" #include "linkdefs.h"
#include "platform.h" // strncasecmp, strdup #include "platform.h" // strncasecmp, strdup
static struct CaptureBody captureBody; /* Captures a REPT/FOR or MACRO */ static struct CaptureBody captureBody; // Captures a REPT/FOR or MACRO
static void upperstring(char *dest, char const *src) static void upperstring(char *dest, char const *src)
{ {
@@ -104,14 +104,14 @@ static size_t strlenUTF8(char const *s)
case 1: case 1:
errorInvalidUTF8Byte(byte, "STRLEN"); errorInvalidUTF8Byte(byte, "STRLEN");
state = 0; state = 0;
/* fallthrough */ // fallthrough
case 0: case 0:
len++; len++;
break; break;
} }
} }
/* Check for partial code point. */ // Check for partial code point.
if (state != 0) if (state != 0)
error("STRLEN: Incomplete UTF-8 character\n"); error("STRLEN: Incomplete UTF-8 character\n");
@@ -127,13 +127,13 @@ static void strsubUTF8(char *dest, size_t destLen, char const *src, uint32_t pos
uint32_t curLen = 0; uint32_t curLen = 0;
uint32_t curPos = 1; uint32_t curPos = 1;
/* Advance to starting position in source string. */ // Advance to starting position in source string.
while (src[srcIndex] && curPos < pos) { while (src[srcIndex] && curPos < pos) {
switch (decode(&state, &codep, src[srcIndex])) { switch (decode(&state, &codep, src[srcIndex])) {
case 1: case 1:
errorInvalidUTF8Byte(src[srcIndex], "STRSUB"); errorInvalidUTF8Byte(src[srcIndex], "STRSUB");
state = 0; state = 0;
/* fallthrough */ // fallthrough
case 0: case 0:
curPos++; curPos++;
break; break;
@@ -141,21 +141,19 @@ static void strsubUTF8(char *dest, size_t destLen, char const *src, uint32_t pos
srcIndex++; srcIndex++;
} }
/* // A position 1 past the end of the string is allowed, but will trigger the
* A position 1 past the end of the string is allowed, but will trigger the // "Length too big" warning below if the length is nonzero.
* "Length too big" warning below if the length is nonzero.
*/
if (!src[srcIndex] && pos > curPos) if (!src[srcIndex] && pos > curPos)
warning(WARNING_BUILTIN_ARG, warning(WARNING_BUILTIN_ARG,
"STRSUB: Position %" PRIu32 " is past the end of the string\n", pos); "STRSUB: Position %" PRIu32 " is past the end of the string\n", pos);
/* Copy from source to destination. */ // Copy from source to destination.
while (src[srcIndex] && destIndex < destLen - 1 && curLen < len) { while (src[srcIndex] && destIndex < destLen - 1 && curLen < len) {
switch (decode(&state, &codep, src[srcIndex])) { switch (decode(&state, &codep, src[srcIndex])) {
case 1: case 1:
errorInvalidUTF8Byte(src[srcIndex], "STRSUB"); errorInvalidUTF8Byte(src[srcIndex], "STRSUB");
state = 0; state = 0;
/* fallthrough */ // fallthrough
case 0: case 0:
curLen++; curLen++;
break; break;
@@ -166,7 +164,7 @@ static void strsubUTF8(char *dest, size_t destLen, char const *src, uint32_t pos
if (curLen < len) if (curLen < len)
warning(WARNING_BUILTIN_ARG, "STRSUB: Length too big: %" PRIu32 "\n", len); warning(WARNING_BUILTIN_ARG, "STRSUB: Length too big: %" PRIu32 "\n", len);
/* Check for partial code point. */ // Check for partial code point.
if (state != 0) if (state != 0)
error("STRSUB: Incomplete UTF-8 character\n"); error("STRSUB: Incomplete UTF-8 character\n");
@@ -187,7 +185,7 @@ static void charsubUTF8(char *dest, char const *src, uint32_t pos)
{ {
size_t charLen = 1; size_t charLen = 1;
/* Advance to starting position in source string. */ // Advance to starting position in source string.
for (uint32_t curPos = 1; charLen && curPos < pos; curPos++) for (uint32_t curPos = 1; charLen && curPos < pos; curPos++)
charLen = charmap_ConvertNext(&src, NULL); charLen = charmap_ConvertNext(&src, NULL);
@@ -197,7 +195,7 @@ static void charsubUTF8(char *dest, char const *src, uint32_t pos)
warning(WARNING_BUILTIN_ARG, warning(WARNING_BUILTIN_ARG,
"CHARSUB: Position %" PRIu32 " is past the end of the string\n", pos); "CHARSUB: Position %" PRIu32 " is past the end of the string\n", pos);
/* Copy from source to destination. */ // Copy from source to destination.
memcpy(dest, start, src - start); memcpy(dest, start, src - start);
dest[src - start] = '\0'; dest[src - start] = '\0';
@@ -205,10 +203,8 @@ static void charsubUTF8(char *dest, char const *src, uint32_t pos)
static uint32_t adjustNegativePos(int32_t pos, size_t len, char const *functionName) static uint32_t adjustNegativePos(int32_t pos, size_t len, char const *functionName)
{ {
/* // STRSUB and CHARSUB adjust negative `pos` arguments the same way,
* STRSUB and CHARSUB adjust negative `pos` arguments the same way, // such that position -1 is the last character of a string.
* such that position -1 is the last character of a string.
*/
if (pos < 0) if (pos < 0)
pos += len + 1; pos += len + 1;
if (pos < 1) { if (pos < 1) {
@@ -545,7 +541,7 @@ enum {
%left T_OP_SHL T_OP_SHR T_OP_USHR %left T_OP_SHL T_OP_SHR T_OP_USHR
%left T_OP_MUL T_OP_DIV T_OP_MOD %left T_OP_MUL T_OP_DIV T_OP_MOD
%precedence NEG /* negation -- unary minus */ %precedence NEG // negation -- unary minus
%token T_OP_EXP "**" %token T_OP_EXP "**"
%left T_OP_EXP %left T_OP_EXP
@@ -554,14 +550,17 @@ enum {
%token T_OP_BANK "BANK" %token T_OP_BANK "BANK"
%token T_OP_ALIGN "ALIGN" %token T_OP_ALIGN "ALIGN"
%token T_OP_SIZEOF "SIZEOF" T_OP_STARTOF "STARTOF" %token T_OP_SIZEOF "SIZEOF" T_OP_STARTOF "STARTOF"
%token T_OP_SIN "SIN" T_OP_COS "COS" T_OP_TAN "TAN" %token T_OP_SIN "SIN" T_OP_COS "COS" T_OP_TAN "TAN"
%token T_OP_ASIN "ASIN" T_OP_ACOS "ACOS" T_OP_ATAN "ATAN" T_OP_ATAN2 "ATAN2" %token T_OP_ASIN "ASIN" T_OP_ACOS "ACOS" T_OP_ATAN "ATAN" T_OP_ATAN2 "ATAN2"
%token T_OP_FDIV "FDIV" %token T_OP_FDIV "FDIV"
%token T_OP_FMUL "FMUL" %token T_OP_FMUL "FMUL"
%token T_OP_FMOD "FMOD"
%token T_OP_POW "POW" %token T_OP_POW "POW"
%token T_OP_LOG "LOG" %token T_OP_LOG "LOG"
%token T_OP_ROUND "ROUND" %token T_OP_ROUND "ROUND"
%token T_OP_CEIL "CEIL" T_OP_FLOOR "FLOOR" %token T_OP_CEIL "CEIL" T_OP_FLOOR "FLOOR"
%type <constValue> opt_q_arg
%token T_OP_HIGH "HIGH" T_OP_LOW "LOW" %token T_OP_HIGH "HIGH" T_OP_LOW "LOW"
%token T_OP_ISCONST "ISCONST" %token T_OP_ISCONST "ISCONST"
@@ -587,7 +586,6 @@ enum {
%type <symName> scoped_id %type <symName> scoped_id
%type <symName> scoped_anon_id %type <symName> scoped_anon_id
%token T_POP_EQU "EQU" %token T_POP_EQU "EQU"
%token T_POP_SET "SET"
%token T_POP_EQUAL "=" %token T_POP_EQUAL "="
%token T_POP_EQUS "EQUS" %token T_POP_EQUS "EQUS"
@@ -641,7 +639,7 @@ enum {
%type <forArgs> for_args %type <forArgs> for_args
%token T_Z80_ADC "adc" T_Z80_ADD "add" T_Z80_AND "and" %token T_Z80_ADC "adc" T_Z80_ADD "add" T_Z80_AND "and"
%token T_Z80_BIT "bit" // There is no T_Z80_SET, only T_POP_SET %token T_Z80_BIT "bit"
%token T_Z80_CALL "call" T_Z80_CCF "ccf" T_Z80_CP "cp" T_Z80_CPL "cpl" %token T_Z80_CALL "call" T_Z80_CCF "ccf" T_Z80_CP "cp" T_Z80_CPL "cpl"
%token T_Z80_DAA "daa" T_Z80_DEC "dec" T_Z80_DI "di" %token T_Z80_DAA "daa" T_Z80_DEC "dec" T_Z80_DI "di"
%token T_Z80_EI "ei" %token T_Z80_EI "ei"
@@ -658,7 +656,7 @@ enum {
%token T_Z80_RES "res" T_Z80_RET "ret" T_Z80_RETI "reti" T_Z80_RST "rst" %token T_Z80_RES "res" T_Z80_RET "ret" T_Z80_RETI "reti" T_Z80_RST "rst"
%token T_Z80_RL "rl" T_Z80_RLA "rla" T_Z80_RLC "rlc" T_Z80_RLCA "rlca" %token T_Z80_RL "rl" T_Z80_RLA "rla" T_Z80_RLC "rlc" T_Z80_RLCA "rlca"
%token T_Z80_RR "rr" T_Z80_RRA "rra" T_Z80_RRC "rrc" T_Z80_RRCA "rrca" %token T_Z80_RR "rr" T_Z80_RRA "rra" T_Z80_RRC "rrc" T_Z80_RRCA "rrca"
%token T_Z80_SBC "sbc" T_Z80_SCF "scf" T_Z80_STOP "stop" %token T_Z80_SBC "sbc" T_Z80_SCF "scf" T_Z80_SET "set" T_Z80_STOP "stop"
%token T_Z80_SLA "sla" T_Z80_SRA "sra" T_Z80_SRL "srl" T_Z80_SUB "sub" %token T_Z80_SLA "sla" T_Z80_SRA "sra" T_Z80_SRL "srl" T_Z80_SUB "sub"
%token T_Z80_SWAP "swap" %token T_Z80_SWAP "swap"
%token T_Z80_XOR "xor" %token T_Z80_XOR "xor"
@@ -692,22 +690,24 @@ asmfile : lines
; ;
lines : %empty lines : %empty
| lines line | lines opt_diff_mark line
; ;
endofline : T_NEWLINE | T_EOB endofline : T_NEWLINE | T_EOB
; ;
plain_directive : label opt_diff_mark : %empty // OK
| label cpu_command | T_OP_ADD {
| label macro error("syntax error, unexpected + at the beginning of the line (is it a leftover diff mark?)\n");
| label directive }
| assignment_directive | T_OP_SUB {
error("syntax error, unexpected - at the beginning of the line (is it a leftover diff mark?)\n");
}
; ;
line : plain_directive endofline line : plain_directive endofline
| line_directive /* Directives that manage newlines themselves */ | line_directive // Directives that manage newlines themselves
/* Continue parsing the next line on a syntax error */ // Continue parsing the next line on a syntax error
| error { | error {
lexer_SetMode(LEXER_NORMAL); lexer_SetMode(LEXER_NORMAL);
lexer_ToggleStringExpansion(true); lexer_ToggleStringExpansion(true);
@@ -715,7 +715,7 @@ line : plain_directive endofline
fstk_StopRept(); fstk_StopRept();
yyerrok; yyerrok;
} }
/* Hint about unindented macros parsed as labels */ // Hint about unindented macros parsed as labels
| T_LABEL error { | T_LABEL error {
lexer_SetMode(LEXER_NORMAL); lexer_SetMode(LEXER_NORMAL);
lexer_ToggleStringExpansion(true); lexer_ToggleStringExpansion(true);
@@ -730,19 +730,17 @@ line : plain_directive endofline
} }
; ;
/* // For "logistical" reasons, these directives must manage newlines themselves.
* For "logistical" reasons, these directives must manage newlines themselves. // This is because we need to switch the lexer's mode *after* the newline has been read,
* This is because we need to switch the lexer's mode *after* the newline has been read, // and to avoid causing some grammar conflicts (token reducing is finicky).
* and to avoid causing some grammar conflicts (token reducing is finicky). // This is DEFINITELY one of the more FRAGILE parts of the codebase, handle with care.
* This is DEFINITELY one of the more FRAGILE parts of the codebase, handle with care.
*/
line_directive : macrodef line_directive : macrodef
| rept | rept
| for | for
| break | break
| include | include
| if | if
/* It's important that all of these require being at line start for `skipIfBlock` */ // It's important that all of these require being at line start for `skipIfBlock`
| elif | elif
| else | else
; ;
@@ -790,6 +788,13 @@ else : T_POP_ELSE T_NEWLINE {
} }
; ;
plain_directive : label
| label cpu_command
| label macro
| label directive
| assignment_directive
;
endc : T_POP_ENDC { endc : T_POP_ENDC {
lexer_DecIFDepth(); lexer_DecIFDepth();
} }
@@ -853,7 +858,7 @@ macroargs : %empty {
} }
; ;
/* These commands start with a T_LABEL. */ // These commands start with a T_LABEL.
assignment_directive : equ assignment_directive : equ
| assignment | assignment
| rb | rb
@@ -1099,6 +1104,7 @@ macrodef : T_POP_MACRO {
captureBody.size); captureBody.size);
} }
| T_LABEL T_COLON T_POP_MACRO T_NEWLINE { | T_LABEL T_COLON T_POP_MACRO T_NEWLINE {
warning(WARNING_OBSOLETE, "`%s: MACRO` is deprecated; use `MACRO %s`\n", $1, $1);
$<captureTerminated>$ = lexer_CaptureMacroBody(&captureBody); $<captureTerminated>$ = lexer_CaptureMacroBody(&captureBody);
} endofline { } endofline {
if ($<captureTerminated>5) if ($<captureTerminated>5)
@@ -1298,7 +1304,7 @@ constlist_8bit_entry : reloc_8bit_no_str {
sect_RelByte(&$1, 0); sect_RelByte(&$1, 0);
} }
| string { | string {
uint8_t *output = malloc(strlen($1)); /* Cannot be larger than that */ uint8_t *output = malloc(strlen($1)); // Cannot be larger than that
size_t length = charmap_Convert($1, output); size_t length = charmap_Convert($1, output);
sect_AbsByteGroup(output, length); sect_AbsByteGroup(output, length);
@@ -1314,7 +1320,7 @@ constlist_16bit_entry : reloc_16bit_no_str {
sect_RelWord(&$1, 0); sect_RelWord(&$1, 0);
} }
| string { | string {
uint8_t *output = malloc(strlen($1)); /* Cannot be larger than that */ uint8_t *output = malloc(strlen($1)); // Cannot be larger than that
size_t length = charmap_Convert($1, output); size_t length = charmap_Convert($1, output);
sect_AbsWordGroup(output, length); sect_AbsWordGroup(output, length);
@@ -1467,53 +1473,54 @@ relocexpr_no_str : scoped_anon_id { rpn_Symbol(&$$, $1); }
| T_OP_DEF { | T_OP_DEF {
lexer_ToggleStringExpansion(false); lexer_ToggleStringExpansion(false);
} T_LPAREN scoped_anon_id T_RPAREN { } T_LPAREN scoped_anon_id T_RPAREN {
struct Symbol const *sym = sym_FindScopedSymbol($4); rpn_Number(&$$, sym_FindScopedValidSymbol($4) != NULL);
rpn_Number(&$$, !!sym);
lexer_ToggleStringExpansion(true); lexer_ToggleStringExpansion(true);
} }
| T_OP_ROUND T_LPAREN const T_RPAREN { | T_OP_ROUND T_LPAREN const opt_q_arg T_RPAREN {
rpn_Number(&$$, fix_Round($3)); rpn_Number(&$$, fix_Round($3, $4));
} }
| T_OP_CEIL T_LPAREN const T_RPAREN { | T_OP_CEIL T_LPAREN const opt_q_arg T_RPAREN {
rpn_Number(&$$, fix_Ceil($3)); rpn_Number(&$$, fix_Ceil($3, $4));
} }
| T_OP_FLOOR T_LPAREN const T_RPAREN { | T_OP_FLOOR T_LPAREN const opt_q_arg T_RPAREN {
rpn_Number(&$$, fix_Floor($3)); rpn_Number(&$$, fix_Floor($3, $4));
} }
| T_OP_FDIV T_LPAREN const T_COMMA const T_RPAREN { | T_OP_FDIV T_LPAREN const T_COMMA const opt_q_arg T_RPAREN {
rpn_Number(&$$, fix_Div($3, $5)); rpn_Number(&$$, fix_Div($3, $5, $6));
} }
| T_OP_FMUL T_LPAREN const T_COMMA const T_RPAREN { | T_OP_FMUL T_LPAREN const T_COMMA const opt_q_arg T_RPAREN {
rpn_Number(&$$, fix_Mul($3, $5)); rpn_Number(&$$, fix_Mul($3, $5, $6));
} }
| T_OP_POW T_LPAREN const T_COMMA const T_RPAREN { | T_OP_FMOD T_LPAREN const T_COMMA const opt_q_arg T_RPAREN {
rpn_Number(&$$, fix_Pow($3, $5)); rpn_Number(&$$, fix_Mod($3, $5, $6));
} }
| T_OP_LOG T_LPAREN const T_COMMA const T_RPAREN { | T_OP_POW T_LPAREN const T_COMMA const opt_q_arg T_RPAREN {
rpn_Number(&$$, fix_Log($3, $5)); rpn_Number(&$$, fix_Pow($3, $5, $6));
} }
| T_OP_SIN T_LPAREN const T_RPAREN { | T_OP_LOG T_LPAREN const T_COMMA const opt_q_arg T_RPAREN {
rpn_Number(&$$, fix_Sin($3)); rpn_Number(&$$, fix_Log($3, $5, $6));
} }
| T_OP_COS T_LPAREN const T_RPAREN { | T_OP_SIN T_LPAREN const opt_q_arg T_RPAREN {
rpn_Number(&$$, fix_Cos($3)); rpn_Number(&$$, fix_Sin($3, $4));
} }
| T_OP_TAN T_LPAREN const T_RPAREN { | T_OP_COS T_LPAREN const opt_q_arg T_RPAREN {
rpn_Number(&$$, fix_Tan($3)); rpn_Number(&$$, fix_Cos($3, $4));
} }
| T_OP_ASIN T_LPAREN const T_RPAREN { | T_OP_TAN T_LPAREN const opt_q_arg T_RPAREN {
rpn_Number(&$$, fix_ASin($3)); rpn_Number(&$$, fix_Tan($3, $4));
} }
| T_OP_ACOS T_LPAREN const T_RPAREN { | T_OP_ASIN T_LPAREN const opt_q_arg T_RPAREN {
rpn_Number(&$$, fix_ACos($3)); rpn_Number(&$$, fix_ASin($3, $4));
} }
| T_OP_ATAN T_LPAREN const T_RPAREN { | T_OP_ACOS T_LPAREN const opt_q_arg T_RPAREN {
rpn_Number(&$$, fix_ATan($3)); rpn_Number(&$$, fix_ACos($3, $4));
} }
| T_OP_ATAN2 T_LPAREN const T_COMMA const T_RPAREN { | T_OP_ATAN T_LPAREN const opt_q_arg T_RPAREN {
rpn_Number(&$$, fix_ATan2($3, $5)); rpn_Number(&$$, fix_ATan($3, $4));
}
| T_OP_ATAN2 T_LPAREN const T_COMMA const opt_q_arg T_RPAREN {
rpn_Number(&$$, fix_ATan2($3, $5, $6));
} }
| T_OP_STRCMP T_LPAREN string T_COMMA string T_RPAREN { | T_OP_STRCMP T_LPAREN string T_COMMA string T_RPAREN {
rpn_Number(&$$, strcmp($3, $5)); rpn_Number(&$$, strcmp($3, $5));
@@ -1540,7 +1547,7 @@ relocexpr_no_str : scoped_anon_id { rpn_Symbol(&$$, $1); }
uconst : const { uconst : const {
$$ = $1; $$ = $1;
if ($$ < 0) if ($$ < 0)
fatalerror("Constant mustn't be negative: %d\n", $1); fatalerror("Constant must not be negative: %d\n", $1);
} }
; ;
@@ -1553,6 +1560,17 @@ const_no_str : relocexpr_no_str { $$ = rpn_GetConstVal(&$1); }
const_8bit : reloc_8bit { $$ = rpn_GetConstVal(&$1); } const_8bit : reloc_8bit { $$ = rpn_GetConstVal(&$1); }
; ;
opt_q_arg : %empty { $$ = fix_Precision(); }
| T_COMMA const {
if ($2 >= 1 && $2 <= 31) {
$$ = $2;
} else {
error("Fixed-point precision must be between 1 and 31\n");
$$ = fix_Precision();
}
}
;
string : T_STRING string : T_STRING
| T_OP_STRSUB T_LPAREN string T_COMMA const T_COMMA uconst T_RPAREN { | T_OP_STRSUB T_LPAREN string T_COMMA const T_COMMA uconst T_RPAREN {
size_t len = strlenUTF8($3); size_t len = strlenUTF8($3);
@@ -1591,6 +1609,19 @@ string : T_STRING
strfmt($$, sizeof($$), $3.format, $3.nbArgs, $3.args); strfmt($$, sizeof($$), $3.format, $3.nbArgs, $3.args);
freeStrFmtArgList(&$3); freeStrFmtArgList(&$3);
} }
| T_POP_SECTION T_LPAREN scoped_anon_id T_RPAREN {
struct Symbol *sym = sym_FindScopedValidSymbol($3);
if (!sym)
fatalerror("Unknown symbol \"%s\"\n", $3);
struct Section const *section = sym_GetSection(sym);
if (!section)
fatalerror("\"%s\" does not belong to any section\n", sym->name);
// Section names are capped by rgbasm's maximum string length,
// so this currently can't overflow.
strcpy($$, section->name);
}
; ;
strcat_args : string strcat_args : string
@@ -1676,7 +1707,7 @@ sectattrs : %empty {
$$.alignOfs = $7; $$.alignOfs = $7;
} }
| sectattrs T_COMMA T_OP_BANK T_LBRACK uconst T_RBRACK { | sectattrs T_COMMA T_OP_BANK T_LBRACK uconst T_RBRACK {
/* We cannot check the validity of this now */ // We cannot check the validity of this now
$$.bank = $5; $$.bank = $5;
} }
; ;
@@ -1993,10 +2024,8 @@ z80_ld_ss : T_Z80_LD T_MODE_BC T_COMMA reloc_16bit {
sect_AbsByte(0x01 | (REG_DE << 4)); sect_AbsByte(0x01 | (REG_DE << 4));
sect_RelWord(&$4, 1); sect_RelWord(&$4, 1);
} }
/* // HL is taken care of in z80_ld_hl
* HL is taken care of in z80_ld_hl // SP is taken care of in z80_ld_sp
* SP is taken care of in z80_ld_sp
*/
; ;
z80_nop : T_Z80_NOP { sect_AbsByte(0x00); } z80_nop : T_Z80_NOP { sect_AbsByte(0x00); }
@@ -2084,7 +2113,7 @@ z80_sbc : T_Z80_SBC op_a_n {
z80_scf : T_Z80_SCF { sect_AbsByte(0x37); } z80_scf : T_Z80_SCF { sect_AbsByte(0x37); }
; ;
z80_set : T_POP_SET const_3bit T_COMMA reg_r { z80_set : T_Z80_SET const_3bit T_COMMA reg_r {
sect_AbsByte(0xCB); sect_AbsByte(0xCB);
sect_AbsByte(0xC0 | ($2 << 3) | $4); sect_AbsByte(0xC0 | ($2 << 3) | $4);
} }

View File

@@ -6,9 +6,7 @@
* SPDX-License-Identifier: MIT * SPDX-License-Identifier: MIT
*/ */
/* // Controls RPN expressions for objectfiles
* Controls RPN expressions for objectfiles
*/
#include <assert.h> #include <assert.h>
#include <errno.h> #include <errno.h>
@@ -28,7 +26,7 @@
#include "opmath.h" #include "opmath.h"
/* Makes an expression "not known", also setting its error message */ // Makes an expression "not known", also setting its error message
#define makeUnknown(expr_, ...) do { \ #define makeUnknown(expr_, ...) do { \
struct Expression *_expr = expr_; \ struct Expression *_expr = expr_; \
_expr->isKnown = false; \ _expr->isKnown = false; \
@@ -47,17 +45,15 @@
static uint8_t *reserveSpace(struct Expression *expr, uint32_t size) static uint8_t *reserveSpace(struct Expression *expr, uint32_t size)
{ {
/* This assumes the RPN length is always less than the capacity */ // This assumes the RPN length is always less than the capacity
if (expr->rpnCapacity - expr->rpnLength < size) { if (expr->rpnCapacity - expr->rpnLength < size) {
/* If there isn't enough room to reserve the space, realloc */ // If there isn't enough room to reserve the space, realloc
if (!expr->rpn) if (!expr->rpn)
expr->rpnCapacity = 256; /* Initial size */ expr->rpnCapacity = 256; // Initial size
while (expr->rpnCapacity - expr->rpnLength < size) { while (expr->rpnCapacity - expr->rpnLength < size) {
if (expr->rpnCapacity >= MAXRPNLEN) if (expr->rpnCapacity >= MAXRPNLEN)
/* // To avoid generating humongous object files, cap the
* To avoid generating humongous object files, cap the // size of RPN expressions
* size of RPN expressions
*/
fatalerror("RPN expression cannot grow larger than " fatalerror("RPN expression cannot grow larger than "
EXPAND_AND_STR(MAXRPNLEN) " bytes\n"); EXPAND_AND_STR(MAXRPNLEN) " bytes\n");
else if (expr->rpnCapacity > MAXRPNLEN / 2) else if (expr->rpnCapacity > MAXRPNLEN / 2)
@@ -77,9 +73,7 @@ static uint8_t *reserveSpace(struct Expression *expr, uint32_t size)
return ptr; return ptr;
} }
/* // Init a RPN expression
* Init a RPN expression
*/
static void rpn_Init(struct Expression *expr) static void rpn_Init(struct Expression *expr)
{ {
expr->reason = NULL; expr->reason = NULL;
@@ -91,9 +85,7 @@ static void rpn_Init(struct Expression *expr)
expr->rpnPatchSize = 0; expr->rpnPatchSize = 0;
} }
/* // Free the RPN expression
* Free the RPN expression
*/
void rpn_Free(struct Expression *expr) void rpn_Free(struct Expression *expr)
{ {
free(expr->rpn); free(expr->rpn);
@@ -101,9 +93,7 @@ void rpn_Free(struct Expression *expr)
rpn_Init(expr); rpn_Init(expr);
} }
/* // Add symbols, constants and operators to expression
* Add symbols, constants and operators to expression
*/
void rpn_Number(struct Expression *expr, uint32_t i) void rpn_Number(struct Expression *expr, uint32_t i)
{ {
rpn_Init(expr); rpn_Init(expr);
@@ -124,9 +114,9 @@ void rpn_Symbol(struct Expression *expr, char const *symName)
makeUnknown(expr, sym_IsPC(sym) ? "PC is not constant at assembly time" makeUnknown(expr, sym_IsPC(sym) ? "PC is not constant at assembly time"
: "'%s' is not constant at assembly time", symName); : "'%s' is not constant at assembly time", symName);
sym = sym_Ref(symName); sym = sym_Ref(symName);
expr->rpnPatchSize += 5; /* 1-byte opcode + 4-byte symbol ID */ expr->rpnPatchSize += 5; // 1-byte opcode + 4-byte symbol ID
size_t nameLen = strlen(sym->name) + 1; /* Don't forget NUL! */ size_t nameLen = strlen(sym->name) + 1; // Don't forget NUL!
uint8_t *ptr = reserveSpace(expr, nameLen + 1); uint8_t *ptr = reserveSpace(expr, nameLen + 1);
*ptr++ = RPN_SYM; *ptr++ = RPN_SYM;
memcpy(ptr, sym->name, nameLen); memcpy(ptr, sym->name, nameLen);
@@ -155,7 +145,7 @@ void rpn_BankSymbol(struct Expression *expr, char const *symName)
{ {
struct Symbol const *sym = sym_FindScopedSymbol(symName); struct Symbol const *sym = sym_FindScopedSymbol(symName);
/* The @ symbol is treated differently. */ // The @ symbol is treated differently.
if (sym_IsPC(sym)) { if (sym_IsPC(sym)) {
rpn_BankSelf(expr); rpn_BankSelf(expr);
return; return;
@@ -169,13 +159,13 @@ void rpn_BankSymbol(struct Expression *expr, char const *symName)
assert(sym); // If the symbol didn't exist, it should have been created assert(sym); // If the symbol didn't exist, it should have been created
if (sym_GetSection(sym) && sym_GetSection(sym)->bank != (uint32_t)-1) { if (sym_GetSection(sym) && sym_GetSection(sym)->bank != (uint32_t)-1) {
/* Symbol's section is known and bank is fixed */ // Symbol's section is known and bank is fixed
expr->val = sym_GetSection(sym)->bank; expr->val = sym_GetSection(sym)->bank;
} else { } else {
makeUnknown(expr, "\"%s\"'s bank is not known", symName); makeUnknown(expr, "\"%s\"'s bank is not known", symName);
expr->rpnPatchSize += 5; /* opcode + 4-byte sect ID */ expr->rpnPatchSize += 5; // opcode + 4-byte sect ID
size_t nameLen = strlen(sym->name) + 1; /* Room for NUL! */ size_t nameLen = strlen(sym->name) + 1; // Room for NUL!
uint8_t *ptr = reserveSpace(expr, nameLen + 1); uint8_t *ptr = reserveSpace(expr, nameLen + 1);
*ptr++ = RPN_BANK_SYM; *ptr++ = RPN_BANK_SYM;
memcpy(ptr, sym->name, nameLen); memcpy(ptr, sym->name, nameLen);
@@ -194,7 +184,7 @@ void rpn_BankSection(struct Expression *expr, char const *sectionName)
} else { } else {
makeUnknown(expr, "Section \"%s\"'s bank is not known", sectionName); makeUnknown(expr, "Section \"%s\"'s bank is not known", sectionName);
size_t nameLen = strlen(sectionName) + 1; /* Room for NUL! */ size_t nameLen = strlen(sectionName) + 1; // Room for NUL!
uint8_t *ptr = reserveSpace(expr, nameLen + 1); uint8_t *ptr = reserveSpace(expr, nameLen + 1);
expr->rpnPatchSize += nameLen + 1; expr->rpnPatchSize += nameLen + 1;
@@ -214,7 +204,7 @@ void rpn_SizeOfSection(struct Expression *expr, char const *sectionName)
} else { } else {
makeUnknown(expr, "Section \"%s\"'s size is not known", sectionName); makeUnknown(expr, "Section \"%s\"'s size is not known", sectionName);
size_t nameLen = strlen(sectionName) + 1; /* Room for NUL! */ size_t nameLen = strlen(sectionName) + 1; // Room for NUL!
uint8_t *ptr = reserveSpace(expr, nameLen + 1); uint8_t *ptr = reserveSpace(expr, nameLen + 1);
expr->rpnPatchSize += nameLen + 1; expr->rpnPatchSize += nameLen + 1;
@@ -234,7 +224,7 @@ void rpn_StartOfSection(struct Expression *expr, char const *sectionName)
} else { } else {
makeUnknown(expr, "Section \"%s\"'s start is not known", sectionName); makeUnknown(expr, "Section \"%s\"'s start is not known", sectionName);
size_t nameLen = strlen(sectionName) + 1; /* Room for NUL! */ size_t nameLen = strlen(sectionName) + 1; // Room for NUL!
uint8_t *ptr = reserveSpace(expr, nameLen + 1); uint8_t *ptr = reserveSpace(expr, nameLen + 1);
expr->rpnPatchSize += nameLen + 1; expr->rpnPatchSize += nameLen + 1;
@@ -252,7 +242,7 @@ void rpn_CheckHRAM(struct Expression *expr, const struct Expression *src)
expr->rpnPatchSize++; expr->rpnPatchSize++;
*reserveSpace(expr, 1) = RPN_HRAM; *reserveSpace(expr, 1) = RPN_HRAM;
} else if (expr->val >= 0xFF00 && expr->val <= 0xFFFF) { } else if (expr->val >= 0xFF00 && expr->val <= 0xFFFF) {
/* That range is valid, but only keep the lower byte */ // That range is valid, but only keep the lower byte
expr->val &= 0xFF; expr->val &= 0xFF;
} else if (expr->val < 0 || expr->val > 0xFF) { } else if (expr->val < 0 || expr->val > 0xFF) {
error("Source address $%" PRIx32 " not between $FF00 to $FFFF\n", expr->val); error("Source address $%" PRIx32 " not between $FF00 to $FFFF\n", expr->val);
@@ -264,10 +254,10 @@ void rpn_CheckRST(struct Expression *expr, const struct Expression *src)
*expr = *src; *expr = *src;
if (rpn_isKnown(expr)) { if (rpn_isKnown(expr)) {
/* A valid RST address must be masked with 0x38 */ // A valid RST address must be masked with 0x38
if (expr->val & ~0x38) if (expr->val & ~0x38)
error("Invalid address $%" PRIx32 " for RST\n", expr->val); error("Invalid address $%" PRIx32 " for RST\n", expr->val);
/* The target is in the "0x38" bits, all other bits are set */ // The target is in the "0x38" bits, all other bits are set
expr->val |= 0xC7; expr->val |= 0xC7;
} else { } else {
expr->rpnPatchSize++; expr->rpnPatchSize++;
@@ -275,9 +265,7 @@ void rpn_CheckRST(struct Expression *expr, const struct Expression *src)
} }
} }
/* // Checks that an RPN expression's value fits within N bits (signed or unsigned)
* Checks that an RPN expression's value fits within N bits (signed or unsigned)
*/
void rpn_CheckNBit(struct Expression const *expr, uint8_t n) void rpn_CheckNBit(struct Expression const *expr, uint8_t n)
{ {
assert(n != 0); // That doesn't make sense assert(n != 0); // That doesn't make sense
@@ -324,7 +312,7 @@ struct Symbol const *rpn_SymbolOf(struct Expression const *expr)
bool rpn_IsDiffConstant(struct Expression const *src, struct Symbol const *sym) bool rpn_IsDiffConstant(struct Expression const *src, struct Symbol const *sym)
{ {
/* Check if both expressions only refer to a single symbol */ // Check if both expressions only refer to a single symbol
struct Symbol const *sym1 = rpn_SymbolOf(src); struct Symbol const *sym1 = rpn_SymbolOf(src);
if (!sym1 || !sym || sym1->type != SYM_LABEL || sym->type != SYM_LABEL) if (!sym1 || !sym || sym1->type != SYM_LABEL || sym->type != SYM_LABEL)
@@ -341,7 +329,7 @@ static bool isDiffConstant(struct Expression const *src1,
return rpn_IsDiffConstant(src1, rpn_SymbolOf(src2)); return rpn_IsDiffConstant(src1, rpn_SymbolOf(src2));
} }
/** /*
* Attempts to compute a constant binary AND from non-constant operands * Attempts to compute a constant binary AND from non-constant operands
* This is possible if one operand is a symbol belonging to an `ALIGN[N]` section, and the other is * This is possible if one operand is a symbol belonging to an `ALIGN[N]` section, and the other is
* a constant that only keeps (some of) the lower N bits. * a constant that only keeps (some of) the lower N bits.
@@ -387,12 +375,12 @@ void rpn_BinaryOp(enum RPNCommand op, struct Expression *expr,
expr->isSymbol = false; expr->isSymbol = false;
int32_t constMaskVal; int32_t constMaskVal;
/* First, check if the expression is known */ // First, check if the expression is known
expr->isKnown = src1->isKnown && src2->isKnown; expr->isKnown = src1->isKnown && src2->isKnown;
if (expr->isKnown) { if (expr->isKnown) {
rpn_Init(expr); /* Init the expression to something sane */ rpn_Init(expr); // Init the expression to something sane
/* If both expressions are known, just compute the value */ // If both expressions are known, just compute the value
uint32_t uleft = src1->val, uright = src2->val; uint32_t uleft = src1->val, uright = src2->val;
switch (op) { switch (op) {
@@ -537,9 +525,9 @@ void rpn_BinaryOp(enum RPNCommand op, struct Expression *expr,
expr->val = constMaskVal; expr->val = constMaskVal;
expr->isKnown = true; expr->isKnown = true;
} else { } else {
/* If it's not known, start computing the RPN expression */ // If it's not known, start computing the RPN expression
/* Convert the left-hand expression if it's constant */ // Convert the left-hand expression if it's constant
if (src1->isKnown) { if (src1->isKnown) {
uint32_t lval = src1->val; uint32_t lval = src1->val;
uint8_t bytes[] = {RPN_CONST, lval, lval >> 8, uint8_t bytes[] = {RPN_CONST, lval, lval >> 8,
@@ -551,11 +539,11 @@ void rpn_BinaryOp(enum RPNCommand op, struct Expression *expr,
memcpy(reserveSpace(expr, sizeof(bytes)), bytes, memcpy(reserveSpace(expr, sizeof(bytes)), bytes,
sizeof(bytes)); sizeof(bytes));
/* Use the other expression's un-const reason */ // Use the other expression's un-const reason
expr->reason = src2->reason; expr->reason = src2->reason;
free(src1->reason); free(src1->reason);
} else { } else {
/* Otherwise just reuse its RPN buffer */ // Otherwise just reuse its RPN buffer
expr->rpnPatchSize = src1->rpnPatchSize; expr->rpnPatchSize = src1->rpnPatchSize;
expr->rpn = src1->rpn; expr->rpn = src1->rpn;
expr->rpnCapacity = src1->rpnCapacity; expr->rpnCapacity = src1->rpnCapacity;
@@ -564,12 +552,12 @@ void rpn_BinaryOp(enum RPNCommand op, struct Expression *expr,
free(src2->reason); free(src2->reason);
} }
/* Now, merge the right expression into the left one */ // Now, merge the right expression into the left one
uint8_t *ptr = src2->rpn; /* Pointer to the right RPN */ uint8_t *ptr = src2->rpn; // Pointer to the right RPN
uint32_t len = src2->rpnLength; /* Size of the right RPN */ uint32_t len = src2->rpnLength; // Size of the right RPN
uint32_t patchSize = src2->rpnPatchSize; uint32_t patchSize = src2->rpnPatchSize;
/* If the right expression is constant, merge a shim instead */ // If the right expression is constant, merge a shim instead
uint32_t rval = src2->val; uint32_t rval = src2->val;
uint8_t bytes[] = {RPN_CONST, rval, rval >> 8, rval >> 16, uint8_t bytes[] = {RPN_CONST, rval, rval >> 8, rval >> 16,
rval >> 24}; rval >> 24};
@@ -578,13 +566,13 @@ void rpn_BinaryOp(enum RPNCommand op, struct Expression *expr,
len = sizeof(bytes); len = sizeof(bytes);
patchSize = sizeof(bytes); patchSize = sizeof(bytes);
} }
/* Copy the right RPN and append the operator */ // Copy the right RPN and append the operator
uint8_t *buf = reserveSpace(expr, len + 1); uint8_t *buf = reserveSpace(expr, len + 1);
memcpy(buf, ptr, len); memcpy(buf, ptr, len);
buf[len] = op; buf[len] = op;
free(src2->rpn); /* If there was none, this is `free(NULL)` */ free(src2->rpn); // If there was none, this is `free(NULL)`
expr->rpnPatchSize += patchSize + 1; expr->rpnPatchSize += patchSize + 1;
} }
} }

View File

@@ -1,3 +1,10 @@
/*
* This file is part of RGBDS.
*
* Copyright (c) 2022, RGBDS contributors.
*
* SPDX-License-Identifier: MIT
*/
#include <assert.h> #include <assert.h>
#include <errno.h> #include <errno.h>
@@ -16,6 +23,7 @@
#include "asm/warning.h" #include "asm/warning.h"
#include "error.h" #include "error.h"
#include "linkdefs.h"
#include "platform.h" // strdup #include "platform.h" // strdup
uint8_t fillByte; uint8_t fillByte;
@@ -29,7 +37,7 @@ struct UnionStackEntry {
struct SectionStackEntry { struct SectionStackEntry {
struct Section *section; struct Section *section;
struct Section *loadSection; struct Section *loadSection;
char const *scope; /* Section's symbol scope */ char const *scope; // Section's symbol scope
uint32_t offset; uint32_t offset;
int32_t loadOffset; int32_t loadOffset;
struct UnionStackEntry *unionStack; struct UnionStackEntry *unionStack;
@@ -37,14 +45,12 @@ struct SectionStackEntry {
}; };
struct SectionStackEntry *sectionStack; struct SectionStackEntry *sectionStack;
uint32_t curOffset; /* Offset into the current section (see sect_GetSymbolOffset) */ uint32_t curOffset; // Offset into the current section (see sect_GetSymbolOffset)
struct Section *currentSection = NULL; struct Section *currentSection = NULL;
static struct Section *currentLoadSection = NULL; static struct Section *currentLoadSection = NULL;
int32_t loadOffset; /* Offset into the LOAD section's parent (see sect_GetOutputOffset) */ int32_t loadOffset; // Offset into the LOAD section's parent (see sect_GetOutputOffset)
/* // A quick check to see if we have an initialized section
* A quick check to see if we have an initialized section
*/
attr_(warn_unused_result) static bool checksection(void) attr_(warn_unused_result) static bool checksection(void)
{ {
if (currentSection) if (currentSection)
@@ -54,10 +60,8 @@ attr_(warn_unused_result) static bool checksection(void)
return false; return false;
} }
/* // A quick check to see if we have an initialized section that can contain
* A quick check to see if we have an initialized section that can contain // this much initialized data
* this much initialized data
*/
attr_(warn_unused_result) static bool checkcodesection(void) attr_(warn_unused_result) static bool checkcodesection(void)
{ {
if (!checksection()) if (!checksection())
@@ -73,7 +77,7 @@ attr_(warn_unused_result) static bool checkcodesection(void)
attr_(warn_unused_result) static bool checkSectionSize(struct Section const *sect, uint32_t size) attr_(warn_unused_result) static bool checkSectionSize(struct Section const *sect, uint32_t size)
{ {
uint32_t maxSize = maxsize[sect->type]; uint32_t maxSize = sectionTypeInfo[sect->type].size;
// If the new size is reasonable, keep going // If the new size is reasonable, keep going
if (size <= maxSize) if (size <= maxSize)
@@ -84,17 +88,13 @@ attr_(warn_unused_result) static bool checkSectionSize(struct Section const *sec
return false; return false;
} }
/* // Check if the section has grown too much.
* Check if the section has grown too much.
*/
attr_(warn_unused_result) static bool reserveSpace(uint32_t delta_size) attr_(warn_unused_result) static bool reserveSpace(uint32_t delta_size)
{ {
/* // This check is here to trap broken code that generates sections that are too big and to
* This check is here to trap broken code that generates sections that are too big and to // prevent the assembler from generating huge object files or trying to allocate too much
* prevent the assembler from generating huge object files or trying to allocate too much // memory.
* memory. // A check at the linking stage is still necessary.
* A check at the linking stage is still necessary.
*/
// If the section has already overflowed, skip the check to avoid erroring out ad nauseam // If the section has already overflowed, skip the check to avoid erroring out ad nauseam
if (currentSection->size != UINT32_MAX if (currentSection->size != UINT32_MAX
@@ -132,15 +132,13 @@ static unsigned int mergeSectUnion(struct Section *sect, enum SectionType type,
assert(alignment < 16); // Should be ensured by the caller assert(alignment < 16); // Should be ensured by the caller
unsigned int nbSectErrors = 0; unsigned int nbSectErrors = 0;
/* // Unionized sections only need "compatible" constraints, and they end up with the strictest
* Unionized sections only need "compatible" constraints, and they end up with the strictest // combination of both.
* combination of both.
*/
if (sect_HasData(type)) if (sect_HasData(type))
fail("Cannot declare ROM sections as UNION\n"); fail("Cannot declare ROM sections as UNION\n");
if (org != (uint32_t)-1) { if (org != (uint32_t)-1) {
/* If both are fixed, they must be the same */ // If both are fixed, they must be the same
if (sect->org != (uint32_t)-1 && sect->org != org) if (sect->org != (uint32_t)-1 && sect->org != org)
fail("Section already declared as fixed at different address $%04" fail("Section already declared as fixed at different address $%04"
PRIx32 "\n", sect->org); PRIx32 "\n", sect->org);
@@ -148,16 +146,16 @@ static unsigned int mergeSectUnion(struct Section *sect, enum SectionType type,
fail("Section already declared as aligned to %u bytes (offset %" fail("Section already declared as aligned to %u bytes (offset %"
PRIu16 ")\n", 1U << sect->align, sect->alignOfs); PRIu16 ")\n", 1U << sect->align, sect->alignOfs);
else else
/* Otherwise, just override */ // Otherwise, just override
sect->org = org; sect->org = org;
} else if (alignment != 0) { } else if (alignment != 0) {
/* Make sure any fixed address given is compatible */ // Make sure any fixed address given is compatible
if (sect->org != (uint32_t)-1) { if (sect->org != (uint32_t)-1) {
if ((sect->org - alignOffset) & mask(alignment)) if ((sect->org - alignOffset) & mask(alignment))
fail("Section already declared as fixed at incompatible address $%04" fail("Section already declared as fixed at incompatible address $%04"
PRIx32 "\n", sect->org); PRIx32 "\n", sect->org);
/* Check if alignment offsets are compatible */ // Check if alignment offsets are compatible
} else if ((alignOffset & mask(sect->align)) } else if ((alignOffset & mask(sect->align))
!= (sect->alignOfs & mask(alignment))) { != (sect->alignOfs & mask(alignment))) {
fail("Section already declared with incompatible %u" fail("Section already declared with incompatible %u"
@@ -180,15 +178,13 @@ static unsigned int mergeFragments(struct Section *sect, enum SectionType type,
assert(alignment < 16); // Should be ensured by the caller assert(alignment < 16); // Should be ensured by the caller
unsigned int nbSectErrors = 0; unsigned int nbSectErrors = 0;
/* // Fragments only need "compatible" constraints, and they end up with the strictest
* Fragments only need "compatible" constraints, and they end up with the strictest // combination of both.
* combination of both. // The merging is however performed at the *end* of the original section!
* The merging is however performed at the *end* of the original section!
*/
if (org != (uint32_t)-1) { if (org != (uint32_t)-1) {
uint16_t curOrg = org - sect->size; uint16_t curOrg = org - sect->size;
/* If both are fixed, they must be the same */ // If both are fixed, they must be the same
if (sect->org != (uint32_t)-1 && sect->org != curOrg) if (sect->org != (uint32_t)-1 && sect->org != curOrg)
fail("Section already declared as fixed at incompatible address $%04" fail("Section already declared as fixed at incompatible address $%04"
PRIx32 " (cur addr = %04" PRIx32 ")\n", PRIx32 " (cur addr = %04" PRIx32 ")\n",
@@ -197,7 +193,7 @@ static unsigned int mergeFragments(struct Section *sect, enum SectionType type,
fail("Section already declared as aligned to %u bytes (offset %" fail("Section already declared as aligned to %u bytes (offset %"
PRIu16 ")\n", 1U << sect->align, sect->alignOfs); PRIu16 ")\n", 1U << sect->align, sect->alignOfs);
else else
/* Otherwise, just override */ // Otherwise, just override
sect->org = curOrg; sect->org = curOrg;
} else if (alignment != 0) { } else if (alignment != 0) {
@@ -206,12 +202,12 @@ static unsigned int mergeFragments(struct Section *sect, enum SectionType type,
if (curOfs < 0) if (curOfs < 0)
curOfs += 1U << alignment; curOfs += 1U << alignment;
/* Make sure any fixed address given is compatible */ // Make sure any fixed address given is compatible
if (sect->org != (uint32_t)-1) { if (sect->org != (uint32_t)-1) {
if ((sect->org - curOfs) & mask(alignment)) if ((sect->org - curOfs) & mask(alignment))
fail("Section already declared as fixed at incompatible address $%04" fail("Section already declared as fixed at incompatible address $%04"
PRIx32 "\n", sect->org); PRIx32 "\n", sect->org);
/* Check if alignment offsets are compatible */ // Check if alignment offsets are compatible
} else if ((curOfs & mask(sect->align)) != (sect->alignOfs & mask(alignment))) { } else if ((curOfs & mask(sect->align)) != (sect->alignOfs & mask(alignment))) {
fail("Section already declared with incompatible %u" fail("Section already declared with incompatible %u"
"-byte alignment (offset %" PRIu16 ")\n", "-byte alignment (offset %" PRIu16 ")\n",
@@ -232,7 +228,7 @@ static void mergeSections(struct Section *sect, enum SectionType type, uint32_t
unsigned int nbSectErrors = 0; unsigned int nbSectErrors = 0;
if (type != sect->type) if (type != sect->type)
fail("Section already exists but with type %s\n", typeNames[sect->type]); fail("Section already exists but with type %s\n", sectionTypeInfo[sect->type].name);
if (sect->modifier != mod) { if (sect->modifier != mod) {
fail("Section already declared as %s section\n", sectionModNames[sect->modifier]); fail("Section already declared as %s section\n", sectionModNames[sect->modifier]);
@@ -245,10 +241,10 @@ static void mergeSections(struct Section *sect, enum SectionType type, uint32_t
// Common checks // Common checks
/* If the section's bank is unspecified, override it */ // If the section's bank is unspecified, override it
if (sect->bank == (uint32_t)-1) if (sect->bank == (uint32_t)-1)
sect->bank = bank; sect->bank = bank;
/* If both specify a bank, it must be the same one */ // If both specify a bank, it must be the same one
else if (bank != (uint32_t)-1 && sect->bank != bank) else if (bank != (uint32_t)-1 && sect->bank != bank)
fail("Section already declared with different bank %" PRIu32 "\n", fail("Section already declared with different bank %" PRIu32 "\n",
sect->bank); sect->bank);
@@ -269,9 +265,7 @@ static void mergeSections(struct Section *sect, enum SectionType type, uint32_t
#undef fail #undef fail
/* // Create a new section, not yet in the list.
* Create a new section, not yet in the list.
*/
static struct Section *createSection(char const *name, enum SectionType type, static struct Section *createSection(char const *name, enum SectionType type,
uint32_t org, uint32_t bank, uint8_t alignment, uint32_t org, uint32_t bank, uint8_t alignment,
uint16_t alignOffset, enum SectionModifier mod) uint16_t alignOffset, enum SectionModifier mod)
@@ -297,9 +291,9 @@ static struct Section *createSection(char const *name, enum SectionType type,
sect->next = NULL; sect->next = NULL;
sect->patches = NULL; sect->patches = NULL;
/* It is only needed to allocate memory for ROM sections. */ // It is only needed to allocate memory for ROM sections.
if (sect_HasData(type)) { if (sect_HasData(type)) {
sect->data = malloc(maxsize[type]); sect->data = malloc(sectionTypeInfo[type].size);
if (sect->data == NULL) if (sect->data == NULL)
fatalerror("Not enough memory for section: %s\n", strerror(errno)); fatalerror("Not enough memory for section: %s\n", strerror(errno));
} else { } else {
@@ -309,9 +303,7 @@ static struct Section *createSection(char const *name, enum SectionType type,
return sect; return sect;
} }
/* // Find a section by name and type. If it doesn't exist, create it.
* Find a section by name and type. If it doesn't exist, create it.
*/
static struct Section *getSection(char const *name, enum SectionType type, uint32_t org, static struct Section *getSection(char const *name, enum SectionType type, uint32_t org,
struct SectionSpec const *attrs, enum SectionModifier mod) struct SectionSpec const *attrs, enum SectionModifier mod)
{ {
@@ -325,14 +317,13 @@ static struct Section *getSection(char const *name, enum SectionType type, uint3
if (type != SECTTYPE_ROMX && type != SECTTYPE_VRAM if (type != SECTTYPE_ROMX && type != SECTTYPE_VRAM
&& type != SECTTYPE_SRAM && type != SECTTYPE_WRAMX) && type != SECTTYPE_SRAM && type != SECTTYPE_WRAMX)
error("BANK only allowed for ROMX, WRAMX, SRAM, or VRAM sections\n"); error("BANK only allowed for ROMX, WRAMX, SRAM, or VRAM sections\n");
else if (bank < bankranges[type][0] else if (bank < sectionTypeInfo[type].firstBank || bank > sectionTypeInfo[type].lastBank)
|| bank > bankranges[type][1])
error("%s bank value $%04" PRIx32 " out of range ($%04" PRIx32 " to $%04" error("%s bank value $%04" PRIx32 " out of range ($%04" PRIx32 " to $%04"
PRIx32 ")\n", typeNames[type], bank, PRIx32 ")\n", sectionTypeInfo[type].name, bank,
bankranges[type][0], bankranges[type][1]); sectionTypeInfo[type].firstBank, sectionTypeInfo[type].lastBank);
} else if (nbbanks(type) == 1) { } else if (nbbanks(type) == 1) {
// If the section type only has a single bank, implicitly force it // If the section type only has a single bank, implicitly force it
bank = bankranges[type][0]; bank = sectionTypeInfo[type].firstBank;
} }
if (alignOffset >= 1 << alignment) { if (alignOffset >= 1 << alignment) {
@@ -342,10 +333,10 @@ static struct Section *getSection(char const *name, enum SectionType type, uint3
} }
if (org != (uint32_t)-1) { if (org != (uint32_t)-1) {
if (org < startaddr[type] || org > endaddr(type)) if (org < sectionTypeInfo[type].startAddr || org > endaddr(type))
error("Section \"%s\"'s fixed address %#" PRIx32 error("Section \"%s\"'s fixed address %#" PRIx32
" is outside of range [%#" PRIx16 "; %#" PRIx16 "]\n", " is outside of range [%#" PRIx16 "; %#" PRIx16 "]\n",
name, org, startaddr[type], endaddr(type)); name, org, sectionTypeInfo[type].startAddr, endaddr(type));
} }
if (alignment != 0) { if (alignment != 0) {
@@ -353,18 +344,18 @@ static struct Section *getSection(char const *name, enum SectionType type, uint3
error("Alignment must be between 0 and 16, not %u\n", alignment); error("Alignment must be between 0 and 16, not %u\n", alignment);
alignment = 16; alignment = 16;
} }
/* It doesn't make sense to have both alignment and org set */ // It doesn't make sense to have both alignment and org set
uint32_t mask = mask(alignment); uint32_t mask = mask(alignment);
if (org != (uint32_t)-1) { if (org != (uint32_t)-1) {
if ((org - alignOffset) & mask) if ((org - alignOffset) & mask)
error("Section \"%s\"'s fixed address doesn't match its alignment\n", error("Section \"%s\"'s fixed address doesn't match its alignment\n",
name); name);
alignment = 0; /* Ignore it if it's satisfied */ alignment = 0; // Ignore it if it's satisfied
} else if (startaddr[type] & mask) { } else if (sectionTypeInfo[type].startAddr & mask) {
error("Section \"%s\"'s alignment cannot be attained in %s\n", error("Section \"%s\"'s alignment cannot be attained in %s\n",
name, typeNames[type]); name, sectionTypeInfo[type].name);
alignment = 0; /* Ignore it if it's unattainable */ alignment = 0; // Ignore it if it's unattainable
org = 0; org = 0;
} else if (alignment == 16) { } else if (alignment == 16) {
// Treat an alignment of 16 as being fixed at address 0 // Treat an alignment of 16 as being fixed at address 0
@@ -390,9 +381,7 @@ static struct Section *getSection(char const *name, enum SectionType type, uint3
return sect; return sect;
} }
/* // Set the current section
* Set the current section
*/
static void changeSection(void) static void changeSection(void)
{ {
if (unionStack) if (unionStack)
@@ -401,9 +390,7 @@ static void changeSection(void)
sym_SetCurrentSymbolScope(NULL); sym_SetCurrentSymbolScope(NULL);
} }
/* // Set the current section by name and type
* Set the current section by name and type
*/
void sect_NewSection(char const *name, uint32_t type, uint32_t org, void sect_NewSection(char const *name, uint32_t type, uint32_t org,
struct SectionSpec const *attribs, enum SectionModifier mod) struct SectionSpec const *attribs, enum SectionModifier mod)
{ {
@@ -423,9 +410,7 @@ void sect_NewSection(char const *name, uint32_t type, uint32_t org,
currentSection = sect; currentSection = sect;
} }
/* // Set the current section by name and type
* Set the current section by name and type
*/
void sect_SetLoadSection(char const *name, uint32_t type, uint32_t org, void sect_SetLoadSection(char const *name, uint32_t type, uint32_t org,
struct SectionSpec const *attribs, enum SectionModifier mod) struct SectionSpec const *attribs, enum SectionModifier mod)
{ {
@@ -478,9 +463,7 @@ struct Section *sect_GetSymbolSection(void)
return currentLoadSection ? currentLoadSection : currentSection; return currentLoadSection ? currentLoadSection : currentSection;
} }
/* // The offset into the section above
* The offset into the section above
*/
uint32_t sect_GetSymbolOffset(void) uint32_t sect_GetSymbolOffset(void)
{ {
return curOffset; return curOffset;
@@ -615,9 +598,7 @@ void sect_CheckUnionClosed(void)
error("Unterminated UNION construct!\n"); error("Unterminated UNION construct!\n");
} }
/* // Output an absolute byte
* Output an absolute byte
*/
void sect_AbsByte(uint8_t b) void sect_AbsByte(uint8_t b)
{ {
if (!checkcodesection()) if (!checkcodesection())
@@ -661,9 +642,7 @@ void sect_AbsLongGroup(uint8_t const *s, size_t length)
writelong(*s++); writelong(*s++);
} }
/* // Skip this many bytes
* Skip this many bytes
*/
void sect_Skip(uint32_t skip, bool ds) void sect_Skip(uint32_t skip, bool ds)
{ {
if (!checksection()) if (!checksection())
@@ -683,9 +662,7 @@ void sect_Skip(uint32_t skip, bool ds)
} }
} }
/* // Output a NULL terminated string (excluding the NULL-character)
* Output a NULL terminated string (excluding the NULL-character)
*/
void sect_String(char const *s) void sect_String(char const *s)
{ {
if (!checkcodesection()) if (!checkcodesection())
@@ -697,10 +674,8 @@ void sect_String(char const *s)
writebyte(*s++); writebyte(*s++);
} }
/* // Output a relocatable byte. Checking will be done to see if it
* Output a relocatable byte. Checking will be done to see if it // is an absolute value in disguise.
* is an absolute value in disguise.
*/
void sect_RelByte(struct Expression *expr, uint32_t pcShift) void sect_RelByte(struct Expression *expr, uint32_t pcShift)
{ {
if (!checkcodesection()) if (!checkcodesection())
@@ -717,10 +692,8 @@ void sect_RelByte(struct Expression *expr, uint32_t pcShift)
rpn_Free(expr); rpn_Free(expr);
} }
/* // Output several copies of a relocatable byte. Checking will be done to see if
* Output several copies of a relocatable byte. Checking will be done to see if // it is an absolute value in disguise.
* it is an absolute value in disguise.
*/
void sect_RelBytes(uint32_t n, struct Expression *exprs, size_t size) void sect_RelBytes(uint32_t n, struct Expression *exprs, size_t size)
{ {
if (!checkcodesection()) if (!checkcodesection())
@@ -743,10 +716,8 @@ void sect_RelBytes(uint32_t n, struct Expression *exprs, size_t size)
rpn_Free(&exprs[i]); rpn_Free(&exprs[i]);
} }
/* // Output a relocatable word. Checking will be done to see if
* Output a relocatable word. Checking will be done to see if // it's an absolute value in disguise.
* it's an absolute value in disguise.
*/
void sect_RelWord(struct Expression *expr, uint32_t pcShift) void sect_RelWord(struct Expression *expr, uint32_t pcShift)
{ {
if (!checkcodesection()) if (!checkcodesection())
@@ -763,10 +734,8 @@ void sect_RelWord(struct Expression *expr, uint32_t pcShift)
rpn_Free(expr); rpn_Free(expr);
} }
/* // Output a relocatable longword. Checking will be done to see if
* Output a relocatable longword. Checking will be done to see if // is an absolute value in disguise.
* is an absolute value in disguise.
*/
void sect_RelLong(struct Expression *expr, uint32_t pcShift) void sect_RelLong(struct Expression *expr, uint32_t pcShift)
{ {
if (!checkcodesection()) if (!checkcodesection())
@@ -783,10 +752,8 @@ void sect_RelLong(struct Expression *expr, uint32_t pcShift)
rpn_Free(expr); rpn_Free(expr);
} }
/* // Output a PC-relative relocatable byte. Checking will be done to see if it
* Output a PC-relative relocatable byte. Checking will be done to see if it // is an absolute value in disguise.
* is an absolute value in disguise.
*/
void sect_PCRelByte(struct Expression *expr, uint32_t pcShift) void sect_PCRelByte(struct Expression *expr, uint32_t pcShift)
{ {
if (!checkcodesection()) if (!checkcodesection())
@@ -800,12 +767,12 @@ void sect_PCRelByte(struct Expression *expr, uint32_t pcShift)
writebyte(0); writebyte(0);
} else { } else {
struct Symbol const *sym = rpn_SymbolOf(expr); struct Symbol const *sym = rpn_SymbolOf(expr);
/* The offset wraps (jump from ROM to HRAM, for example) */ // The offset wraps (jump from ROM to HRAM, for example)
int16_t offset; int16_t offset;
/* Offset is relative to the byte *after* the operand */ // Offset is relative to the byte *after* the operand
if (sym == pc) if (sym == pc)
offset = -2; /* PC as operand to `jr` is lower than reference PC by 2 */ offset = -2; // PC as operand to `jr` is lower than reference PC by 2
else else
offset = sym_GetValue(sym) - (sym_GetValue(pc) + 1); offset = sym_GetValue(sym) - (sym_GetValue(pc) + 1);
@@ -820,9 +787,7 @@ void sect_PCRelByte(struct Expression *expr, uint32_t pcShift)
rpn_Free(expr); rpn_Free(expr);
} }
/* // Output a binary file
* Output a binary file
*/
void sect_BinaryFile(char const *s, int32_t startPos) void sect_BinaryFile(char const *s, int32_t startPos)
{ {
if (startPos < 0) { if (startPos < 0) {
@@ -869,7 +834,7 @@ void sect_BinaryFile(char const *s, int32_t startPos)
if (errno != ESPIPE) if (errno != ESPIPE)
error("Error determining size of INCBIN file '%s': %s\n", error("Error determining size of INCBIN file '%s': %s\n",
s, strerror(errno)); s, strerror(errno));
/* The file isn't seekable, so we'll just skip bytes */ // The file isn't seekable, so we'll just skip bytes
while (startPos--) while (startPos--)
(void)fgetc(f); (void)fgetc(f);
} }
@@ -901,7 +866,7 @@ void sect_BinaryFileSlice(char const *s, int32_t start_pos, int32_t length)
if (!checkcodesection()) if (!checkcodesection())
return; return;
if (length == 0) /* Don't even bother with 0-byte slices */ if (length == 0) // Don't even bother with 0-byte slices
return; return;
if (!reserveSpace(length)) if (!reserveSpace(length))
return; return;
@@ -946,7 +911,7 @@ void sect_BinaryFileSlice(char const *s, int32_t start_pos, int32_t length)
if (errno != ESPIPE) if (errno != ESPIPE)
error("Error determining size of INCBIN file '%s': %s\n", error("Error determining size of INCBIN file '%s': %s\n",
s, strerror(errno)); s, strerror(errno));
/* The file isn't seekable, so we'll just skip bytes */ // The file isn't seekable, so we'll just skip bytes
while (start_pos--) while (start_pos--)
(void)fgetc(f); (void)fgetc(f);
} }
@@ -968,9 +933,7 @@ cleanup:
fclose(f); fclose(f);
} }
/* // Section stack routines
* Section stack routines
*/
void sect_PushSection(void) void sect_PushSection(void)
{ {
struct SectionStackEntry *entry = malloc(sizeof(*entry)); struct SectionStackEntry *entry = malloc(sizeof(*entry));

View File

@@ -6,9 +6,7 @@
* SPDX-License-Identifier: MIT * SPDX-License-Identifier: MIT
*/ */
/* // Symboltable and macroargs stuff
* Symboltable and macroargs stuff
*/
#include <assert.h> #include <assert.h>
#include <errno.h> #include <errno.h>
@@ -36,8 +34,9 @@
HashMap symbols; HashMap symbols;
static char const *labelScope; /* Current section's label scope */ static const char *labelScope; // Current section's label scope
static struct Symbol *PCSymbol; static struct Symbol *PCSymbol;
static struct Symbol *_NARGSymbol;
static char savedTIME[256]; static char savedTIME[256];
static char savedDATE[256]; static char savedDATE[256];
static char savedTIMESTAMP_ISO8601_LOCAL[256]; static char savedTIMESTAMP_ISO8601_LOCAL[256];
@@ -80,41 +79,42 @@ static int32_t Callback_NARG(void)
static int32_t Callback__LINE__(void) static int32_t Callback__LINE__(void)
{ {
warning(WARNING_OBSOLETE, "`__LINE__` is deprecated\n");
return lexer_GetLineNo(); return lexer_GetLineNo();
} }
static char const *Callback__FILE__(void) static char const *Callback__FILE__(void)
{ {
/* warning(WARNING_OBSOLETE, "`__FILE__` is deprecated\n");
* FIXME: this is dangerous, and here's why this is CURRENTLY okay. It's still bad, fix it.
* There are only two call sites for this; one copies the contents directly, the other is // There are only two call sites for this; one copies the contents directly, the other is
* EQUS expansions, which cannot straddle file boundaries. So this should be fine. // EQUS expansions, which cannot straddle file boundaries. So this should be fine.
*/
static char *buf = NULL; static char *buf = NULL;
static size_t bufsize = 0; static size_t bufsize = 0;
char const *fileName = fstk_GetFileName(); char const *fileName = fstk_GetFileName();
size_t j = 1; size_t j = 1;
assert(fileName[0]); assert(fileName[0]);
/* The assertion above ensures the loop runs at least once */ // The assertion above ensures the loop runs at least once
for (size_t i = 0; fileName[i]; i++, j++) { for (size_t i = 0; fileName[i]; i++, j++) {
/* Account for the extra backslash inserted below */ // Account for the extra backslash inserted below
if (fileName[i] == '"') if (fileName[i] == '"')
j++; j++;
/* Ensure there will be enough room; DO NOT PRINT ANYTHING ABOVE THIS!! */ // Ensure there will be enough room; DO NOT PRINT ANYTHING ABOVE THIS!
if (j + 2 >= bufsize) { /* Always keep room for 2 tail chars */ if (j + 2 >= bufsize) { // Always keep room for 2 tail chars
bufsize = bufsize ? bufsize * 2 : 64; bufsize = bufsize ? bufsize * 2 : 64;
buf = realloc(buf, bufsize); buf = realloc(buf, bufsize);
if (!buf) if (!buf)
fatalerror("Failed to grow buffer for file name: %s\n", fatalerror("Failed to grow buffer for file name: %s\n",
strerror(errno)); strerror(errno));
} }
/* Escape quotes, since we're returning a string */ // Escape quotes, since we're returning a string
if (fileName[i] == '"') if (fileName[i] == '"')
buf[j - 1] = '\\'; buf[j - 1] = '\\';
buf[j] = fileName[i]; buf[j] = fileName[i];
} }
/* Write everything after the loop, to ensure the buffer has been allocated */ // Write everything after the loop, to ensure the buffer has been allocated
buf[0] = '"'; buf[0] = '"';
buf[j++] = '"'; buf[j++] = '"';
buf[j] = '\0'; buf[j] = '\0';
@@ -128,16 +128,14 @@ static int32_t CallbackPC(void)
return section ? section->org + sect_GetSymbolOffset() : 0; return section ? section->org + sect_GetSymbolOffset() : 0;
} }
/* // Get the value field of a symbol
* Get the value field of a symbol
*/
int32_t sym_GetValue(struct Symbol const *sym) int32_t sym_GetValue(struct Symbol const *sym)
{ {
if (sym_IsNumeric(sym) && sym->hasCallback) if (sym_IsNumeric(sym) && sym->hasCallback)
return sym->numCallback(); return sym->numCallback();
if (sym->type == SYM_LABEL) if (sym->type == SYM_LABEL)
/* TODO: do not use section's org directly */ // TODO: do not use section's org directly
return sym->value + sym_GetSection(sym)->org; return sym->value + sym_GetSection(sym)->org;
return sym->value; return sym->value;
@@ -153,32 +151,26 @@ static void dumpFilename(struct Symbol const *sym)
fputs("<builtin>", stderr); fputs("<builtin>", stderr);
} }
/* // Set a symbol's definition filename and line
* Set a symbol's definition filename and line
*/
static void setSymbolFilename(struct Symbol *sym) static void setSymbolFilename(struct Symbol *sym)
{ {
sym->src = fstk_GetFileStack(); sym->src = fstk_GetFileStack();
sym->fileLine = sym->src ? lexer_GetLineNo() : 0; // This is (NULL, 1) for built-ins sym->fileLine = sym->src ? lexer_GetLineNo() : 0; // This is (NULL, 1) for built-ins
} }
/* // Update a symbol's definition filename and line
* Update a symbol's definition filename and line
*/
static void updateSymbolFilename(struct Symbol *sym) static void updateSymbolFilename(struct Symbol *sym)
{ {
struct FileStackNode *oldSrc = sym->src; struct FileStackNode *oldSrc = sym->src;
setSymbolFilename(sym); setSymbolFilename(sym);
/* If the old node was referenced, ensure the new one is */ // If the old node was referenced, ensure the new one is
if (oldSrc && oldSrc->referenced && oldSrc->ID != (uint32_t)-1) if (oldSrc && oldSrc->referenced && oldSrc->ID != (uint32_t)-1)
out_RegisterNode(sym->src); out_RegisterNode(sym->src);
/* TODO: unref the old node, and use `out_ReplaceNode` instead of deleting it */ // TODO: unref the old node, and use `out_ReplaceNode` instead of deleting it
} }
/* // Create a new symbol by name
* Create a new symbol by name
*/
static struct Symbol *createsymbol(char const *symName) static struct Symbol *createsymbol(char const *symName)
{ {
struct Symbol *sym = malloc(sizeof(*sym)); struct Symbol *sym = malloc(sizeof(*sym));
@@ -201,10 +193,8 @@ static struct Symbol *createsymbol(char const *symName)
return sym; return sym;
} }
/* // Creates the full name of a local symbol in a given scope, by prepending
* Creates the full name of a local symbol in a given scope, by prepending // the name with the parent symbol's name.
* the name with the parent symbol's name.
*/
static void fullSymbolName(char *output, size_t outputSize, static void fullSymbolName(char *output, size_t outputSize,
char const *localName, char const *scopeName) char const *localName, char const *scopeName)
{ {
@@ -250,8 +240,8 @@ struct Symbol *sym_FindScopedSymbol(char const *symName)
if (strchr(localName + 1, '.')) if (strchr(localName + 1, '.'))
fatalerror("'%s' is a nonsensical reference to a nested local symbol\n", fatalerror("'%s' is a nonsensical reference to a nested local symbol\n",
symName); symName);
/* If auto-scoped local label, expand the name */ // If auto-scoped local label, expand the name
if (localName == symName) { /* Meaning, the name begins with the dot */ if (localName == symName) { // Meaning, the name begins with the dot
char fullName[MAXSYMLEN + 1]; char fullName[MAXSYMLEN + 1];
fullSymbolName(fullName, sizeof(fullName), symName, labelScope); fullSymbolName(fullName, sizeof(fullName), symName, labelScope);
@@ -261,6 +251,21 @@ struct Symbol *sym_FindScopedSymbol(char const *symName)
return sym_FindExactSymbol(symName); return sym_FindExactSymbol(symName);
} }
struct Symbol *sym_FindScopedValidSymbol(char const *symName)
{
struct Symbol *sym = sym_FindScopedSymbol(symName);
// `@` has no value outside a section
if (sym == PCSymbol && !sect_GetSymbolSection()) {
return NULL;
}
// `_NARG` has no value outside a macro
if (sym == _NARGSymbol && !macro_GetCurrentArgs()) {
return NULL;
}
return sym;
}
struct Symbol const *sym_GetPC(void) struct Symbol const *sym_GetPC(void)
{ {
return PCSymbol; return PCSymbol;
@@ -271,12 +276,10 @@ static bool isReferenced(struct Symbol const *sym)
return sym->ID != (uint32_t)-1; return sym->ID != (uint32_t)-1;
} }
/* // Purge a symbol
* Purge a symbol
*/
void sym_Purge(char const *symName) void sym_Purge(char const *symName)
{ {
struct Symbol *sym = sym_FindScopedSymbol(symName); struct Symbol *sym = sym_FindScopedValidSymbol(symName);
if (!sym) { if (!sym) {
error("'%s' not defined\n", symName); error("'%s' not defined\n", symName);
@@ -285,16 +288,14 @@ void sym_Purge(char const *symName)
} else if (isReferenced(sym)) { } else if (isReferenced(sym)) {
error("Symbol \"%s\" is referenced and thus cannot be purged\n", symName); error("Symbol \"%s\" is referenced and thus cannot be purged\n", symName);
} else { } else {
/* Do not keep a reference to the label's name after purging it */ // Do not keep a reference to the label's name after purging it
if (sym->name == labelScope) if (sym->name == labelScope)
sym_SetCurrentSymbolScope(NULL); sym_SetCurrentSymbolScope(NULL);
/* // FIXME: this leaks sym->macro for SYM_EQUS and SYM_MACRO, but this can't
* FIXME: this leaks sym->macro for SYM_EQUS and SYM_MACRO, but this can't // free(sym->macro) because the expansion may be purging itself.
* free(sym->macro) because the expansion may be purging itself.
*/
hash_RemoveElement(symbols, sym->name); hash_RemoveElement(symbols, sym->name);
/* TODO: ideally, also unref the file stack nodes */ // TODO: ideally, also unref the file stack nodes
free(sym); free(sym);
} }
} }
@@ -312,9 +313,7 @@ uint32_t sym_GetPCValue(void)
return 0; return 0;
} }
/* // Return a constant symbol's value, assuming it's defined
* Return a constant symbol's value, assuming it's defined
*/
uint32_t sym_GetConstantSymValue(struct Symbol const *sym) uint32_t sym_GetConstantSymValue(struct Symbol const *sym)
{ {
if (sym == PCSymbol) if (sym == PCSymbol)
@@ -327,9 +326,7 @@ uint32_t sym_GetConstantSymValue(struct Symbol const *sym)
return 0; return 0;
} }
/* // Return a constant symbol's value
* Return a constant symbol's value
*/
uint32_t sym_GetConstantValue(char const *symName) uint32_t sym_GetConstantValue(char const *symName)
{ {
struct Symbol const *sym = sym_FindScopedSymbol(symName); struct Symbol const *sym = sym_FindScopedSymbol(symName);
@@ -381,9 +378,7 @@ static struct Symbol *createNonrelocSymbol(char const *symName, bool numeric)
return sym; return sym;
} }
/* // Add an equated symbol
* Add an equated symbol
*/
struct Symbol *sym_AddEqu(char const *symName, int32_t value) struct Symbol *sym_AddEqu(char const *symName, int32_t value)
{ {
struct Symbol *sym = createNonrelocSymbol(symName, true); struct Symbol *sym = createNonrelocSymbol(symName, true);
@@ -465,18 +460,14 @@ struct Symbol *sym_RedefString(char const *symName, char const *value)
} }
updateSymbolFilename(sym); updateSymbolFilename(sym);
/* // FIXME: this leaks the previous sym->macro value, but this can't
* FIXME: this leaks the previous sym->macro value, but this can't // free(sym->macro) because the expansion may be redefining itself.
* free(sym->macro) because the expansion may be redefining itself.
*/
assignStringSymbol(sym, value); assignStringSymbol(sym, value);
return sym; return sym;
} }
/* // Alter a mutable symbol's value
* Alter a mutable symbol's value
*/
struct Symbol *sym_AddVar(char const *symName, int32_t value) struct Symbol *sym_AddVar(char const *symName, int32_t value)
{ {
struct Symbol *sym = sym_FindExactSymbol(symName); struct Symbol *sym = sym_FindExactSymbol(symName);
@@ -506,7 +497,7 @@ struct Symbol *sym_AddVar(char const *symName, int32_t value)
*/ */
static struct Symbol *addLabel(char const *symName) static struct Symbol *addLabel(char const *symName)
{ {
assert(symName[0] != '.'); /* The symbol name must have been expanded prior */ assert(symName[0] != '.'); // The symbol name must have been expanded prior
struct Symbol *sym = sym_FindExactSymbol(symName); struct Symbol *sym = sym_FindExactSymbol(symName);
if (!sym) { if (!sym) {
@@ -519,7 +510,7 @@ static struct Symbol *addLabel(char const *symName)
} else { } else {
updateSymbolFilename(sym); updateSymbolFilename(sym);
} }
/* If the symbol already exists as a ref, just "take over" it */ // If the symbol already exists as a ref, just "take over" it
sym->type = SYM_LABEL; sym->type = SYM_LABEL;
sym->value = sect_GetSymbolOffset(); sym->value = sect_GetSymbolOffset();
if (exportall) if (exportall)
@@ -531,43 +522,41 @@ static struct Symbol *addLabel(char const *symName)
return sym; return sym;
} }
/* // Add a local (`.name` or `Parent.name`) relocatable symbol
* Add a local (`.name` or `Parent.name`) relocatable symbol
*/
struct Symbol *sym_AddLocalLabel(char const *symName) struct Symbol *sym_AddLocalLabel(char const *symName)
{ {
if (!labelScope) { if (!labelScope) {
error("Local label '%s' in main scope\n", symName); error("Local label '%s' in main scope\n", symName);
return NULL; return NULL;
} }
assert(!strchr(labelScope, '.')); /* Assuming no dots in `labelScope` */ assert(!strchr(labelScope, '.')); // Assuming no dots in `labelScope`
char fullName[MAXSYMLEN + 1]; char fullName[MAXSYMLEN + 1];
char const *localName = strchr(symName, '.'); char const *localName = strchr(symName, '.');
assert(localName); /* There should be at least one dot in `symName` */ assert(localName); // There should be at least one dot in `symName`
/* Check for something after the dot in `localName` */ // Check for something after the dot in `localName`
if (localName[1] == '\0') { if (localName[1] == '\0') {
fatalerror("'%s' is a nonsensical reference to an empty local label\n", fatalerror("'%s' is a nonsensical reference to an empty local label\n",
symName); symName);
} }
/* Check for more than one dot in `localName` */ // Check for more than one dot in `localName`
if (strchr(localName + 1, '.')) if (strchr(localName + 1, '.'))
fatalerror("'%s' is a nonsensical reference to a nested local label\n", fatalerror("'%s' is a nonsensical reference to a nested local label\n",
symName); symName);
if (localName == symName) { if (localName == symName) {
/* Expand `symName` to the full `labelScope.symName` name */ // Expand `symName` to the full `labelScope.symName` name
fullSymbolName(fullName, sizeof(fullName), symName, labelScope); fullSymbolName(fullName, sizeof(fullName), symName, labelScope);
symName = fullName; symName = fullName;
} else { } else {
size_t i = 0; size_t i = 0;
/* Find where `labelScope` and `symName` first differ */ // Find where `labelScope` and `symName` first differ
while (labelScope[i] && symName[i] == labelScope[i]) while (labelScope[i] && symName[i] == labelScope[i])
i++; i++;
/* Check that `symName` starts with `labelScope` and then a '.' */ // Check that `symName` starts with `labelScope` and then a '.'
if (labelScope[i] != '\0' || symName[i] != '.') { if (labelScope[i] != '\0' || symName[i] != '.') {
size_t parentLen = localName - symName; size_t parentLen = localName - symName;
@@ -579,14 +568,12 @@ struct Symbol *sym_AddLocalLabel(char const *symName)
return addLabel(symName); return addLabel(symName);
} }
/* // Add a relocatable symbol
* Add a relocatable symbol
*/
struct Symbol *sym_AddLabel(char const *symName) struct Symbol *sym_AddLabel(char const *symName)
{ {
struct Symbol *sym = addLabel(symName); struct Symbol *sym = addLabel(symName);
/* Set the symbol as the new scope */ // Set the symbol as the new scope
if (sym) if (sym)
sym_SetCurrentSymbolScope(sym->name); sym_SetCurrentSymbolScope(sym->name);
return sym; return sym;
@@ -594,9 +581,7 @@ struct Symbol *sym_AddLabel(char const *symName)
static uint32_t anonLabelID; static uint32_t anonLabelID;
/* // Add an anonymous label
* Add an anonymous label
*/
struct Symbol *sym_AddAnonLabel(void) struct Symbol *sym_AddAnonLabel(void)
{ {
if (anonLabelID == UINT32_MAX) { if (anonLabelID == UINT32_MAX) {
@@ -605,14 +590,12 @@ struct Symbol *sym_AddAnonLabel(void)
} }
char name[MAXSYMLEN + 1]; char name[MAXSYMLEN + 1];
sym_WriteAnonLabelName(name, 0, true); // The direction is important!! sym_WriteAnonLabelName(name, 0, true); // The direction is important!
anonLabelID++; anonLabelID++;
return addLabel(name); return addLabel(name);
} }
/* // Write an anonymous label's name to a buffer
* Write an anonymous label's name to a buffer
*/
void sym_WriteAnonLabelName(char buf[MIN_NB_ELMS(MAXSYMLEN + 1)], uint32_t ofs, bool neg) void sym_WriteAnonLabelName(char buf[MIN_NB_ELMS(MAXSYMLEN + 1)], uint32_t ofs, bool neg)
{ {
uint32_t id = 0; uint32_t id = 0;
@@ -636,9 +619,7 @@ void sym_WriteAnonLabelName(char buf[MIN_NB_ELMS(MAXSYMLEN + 1)], uint32_t ofs,
sprintf(buf, "!%u", id); sprintf(buf, "!%u", id);
} }
/* // Export a symbol
* Export a symbol
*/
void sym_Export(char const *symName) void sym_Export(char const *symName)
{ {
if (symName[0] == '!') { if (symName[0] == '!') {
@@ -648,15 +629,13 @@ void sym_Export(char const *symName)
struct Symbol *sym = sym_FindScopedSymbol(symName); struct Symbol *sym = sym_FindScopedSymbol(symName);
/* If the symbol doesn't exist, create a ref that can be purged */ // If the symbol doesn't exist, create a ref that can be purged
if (!sym) if (!sym)
sym = sym_Ref(symName); sym = sym_Ref(symName);
sym->isExported = true; sym->isExported = true;
} }
/* // Add a macro definition
* Add a macro definition
*/
struct Symbol *sym_AddMacro(char const *symName, int32_t defLineNo, char *body, size_t size) struct Symbol *sym_AddMacro(char const *symName, int32_t defLineNo, char *body, size_t size)
{ {
struct Symbol *sym = createNonrelocSymbol(symName, false); struct Symbol *sym = createNonrelocSymbol(symName, false);
@@ -667,20 +646,16 @@ struct Symbol *sym_AddMacro(char const *symName, int32_t defLineNo, char *body,
sym->type = SYM_MACRO; sym->type = SYM_MACRO;
sym->macroSize = size; sym->macroSize = size;
sym->macro = body; sym->macro = body;
setSymbolFilename(sym); /* TODO: is this really necessary? */ setSymbolFilename(sym); // TODO: is this really necessary?
/* // The symbol is created at the line after the `endm`,
* The symbol is created at the line after the `endm`, // override this with the actual definition line
* override this with the actual definition line
*/
sym->fileLine = defLineNo; sym->fileLine = defLineNo;
return sym; return sym;
} }
/* // Flag that a symbol is referenced in an RPN expression
* Flag that a symbol is referenced in an RPN expression // and create it if it doesn't exist yet
* and create it if it doesn't exist yet
*/
struct Symbol *sym_Ref(char const *symName) struct Symbol *sym_Ref(char const *symName)
{ {
struct Symbol *sym = sym_FindScopedSymbol(symName); struct Symbol *sym = sym_FindScopedSymbol(symName);
@@ -702,9 +677,7 @@ struct Symbol *sym_Ref(char const *symName)
return sym; return sym;
} }
/* // Set whether to export all relocatable symbols by default
* Set whether to export all relocatable symbols by default
*/
void sym_SetExportAll(bool set) void sym_SetExportAll(bool set)
{ {
exportall = set; exportall = set;
@@ -721,14 +694,14 @@ static struct Symbol *createBuiltinSymbol(char const *symName)
return sym; return sym;
} }
/* // Initialize the symboltable
* Initialize the symboltable
*/
void sym_Init(time_t now) void sym_Init(time_t now)
{ {
PCSymbol = createBuiltinSymbol("@"); PCSymbol = createBuiltinSymbol("@");
struct Symbol *_NARGSymbol = createBuiltinSymbol("_NARG"); _NARGSymbol = createBuiltinSymbol("_NARG");
// __LINE__ is deprecated
struct Symbol *__LINE__Symbol = createBuiltinSymbol("__LINE__"); struct Symbol *__LINE__Symbol = createBuiltinSymbol("__LINE__");
// __FILE__ is deprecated
struct Symbol *__FILE__Symbol = createBuiltinSymbol("__FILE__"); struct Symbol *__FILE__Symbol = createBuiltinSymbol("__FILE__");
PCSymbol->type = SYM_LABEL; PCSymbol->type = SYM_LABEL;
@@ -761,7 +734,7 @@ void sym_Init(time_t now)
if (now == (time_t)-1) { if (now == (time_t)-1) {
warn("Couldn't determine current time"); warn("Couldn't determine current time");
/* Fall back by pretending we are at the Epoch */ // Fall back by pretending we are at the Epoch
now = 0; now = 0;
} }

View File

@@ -44,7 +44,7 @@ char const *printChar(int c)
buf[2] = 't'; buf[2] = 't';
break; break;
default: /* Print as hex */ default: // Print as hex
buf[0] = '0'; buf[0] = '0';
buf[1] = 'x'; buf[1] = 'x';
snprintf(&buf[2], 3, "%02hhX", (uint8_t)c); // includes the '\0' snprintf(&buf[2], 3, "%02hhX", (uint8_t)c); // includes the '\0'

View File

@@ -44,23 +44,25 @@ static const enum WarningState defaultWarnings[ARRAY_SIZE(warningStates)] = {
[WARNING_NUMERIC_STRING_2] = WARNING_DISABLED, [WARNING_NUMERIC_STRING_2] = WARNING_DISABLED,
[WARNING_TRUNCATION_1] = WARNING_ENABLED, [WARNING_TRUNCATION_1] = WARNING_ENABLED,
[WARNING_TRUNCATION_2] = WARNING_DISABLED, [WARNING_TRUNCATION_2] = WARNING_DISABLED,
[WARNING_UNMAPPED_CHAR_1] = WARNING_ENABLED,
[WARNING_UNMAPPED_CHAR_2] = WARNING_DISABLED,
}; };
enum WarningState warningStates[ARRAY_SIZE(warningStates)]; enum WarningState warningStates[ARRAY_SIZE(warningStates)];
bool warningsAreErrors; /* Set if `-Werror` was specified */ bool warningsAreErrors; // Set if `-Werror` was specified
static enum WarningState warningState(enum WarningID id) static enum WarningState warningState(enum WarningID id)
{ {
/* Check if warnings are globally disabled */ // Check if warnings are globally disabled
if (!warnings) if (!warnings)
return WARNING_DISABLED; return WARNING_DISABLED;
/* Get the actual state */ // Get the actual state
enum WarningState state = warningStates[id]; enum WarningState state = warningStates[id];
if (state == WARNING_DEFAULT) if (state == WARNING_DEFAULT)
/* The state isn't set, grab its default state */ // The state isn't set, grab its default state
state = defaultWarnings[id]; state = defaultWarnings[id];
if (warningsAreErrors && state == WARNING_ENABLED) if (warningsAreErrors && state == WARNING_ENABLED)
@@ -92,11 +94,13 @@ static const char * const warningFlags[NB_WARNINGS] = {
"numeric-string", "numeric-string",
"truncation", "truncation",
"truncation", "truncation",
"unmapped-char",
"unmapped-char",
/* Meta warnings */ // Meta warnings
"all", "all",
"extra", "extra",
"everything", /* Especially useful for testing */ "everything", // Especially useful for testing
}; };
static const struct { static const struct {
@@ -106,6 +110,7 @@ static const struct {
} paramWarnings[] = { } paramWarnings[] = {
{ "numeric-string", 2, 1 }, { "numeric-string", 2, 1 },
{ "truncation", 2, 2 }, { "truncation", 2, 2 },
{ "unmapped-char", 2, 1 },
}; };
static bool tryProcessParamWarning(char const *flag, uint8_t param, enum WarningState state) static bool tryProcessParamWarning(char const *flag, uint8_t param, enum WarningState state)
@@ -149,7 +154,7 @@ enum MetaWarningCommand {
META_WARNING_DONE = NB_WARNINGS META_WARNING_DONE = NB_WARNINGS
}; };
/* Warnings that probably indicate an error */ // Warnings that probably indicate an error
static uint8_t const _wallCommands[] = { static uint8_t const _wallCommands[] = {
WARNING_BACKWARDS_FOR, WARNING_BACKWARDS_FOR,
WARNING_BUILTIN_ARG, WARNING_BUILTIN_ARG,
@@ -161,10 +166,11 @@ static uint8_t const _wallCommands[] = {
WARNING_NESTED_COMMENT, WARNING_NESTED_COMMENT,
WARNING_OBSOLETE, WARNING_OBSOLETE,
WARNING_NUMERIC_STRING_1, WARNING_NUMERIC_STRING_1,
WARNING_UNMAPPED_CHAR_1,
META_WARNING_DONE META_WARNING_DONE
}; };
/* Warnings that are less likely to indicate an error */ // Warnings that are less likely to indicate an error
static uint8_t const _wextraCommands[] = { static uint8_t const _wextraCommands[] = {
WARNING_EMPTY_MACRO_ARG, WARNING_EMPTY_MACRO_ARG,
WARNING_MACRO_SHIFT, WARNING_MACRO_SHIFT,
@@ -173,10 +179,12 @@ static uint8_t const _wextraCommands[] = {
WARNING_NUMERIC_STRING_2, WARNING_NUMERIC_STRING_2,
WARNING_TRUNCATION_1, WARNING_TRUNCATION_1,
WARNING_TRUNCATION_2, WARNING_TRUNCATION_2,
WARNING_UNMAPPED_CHAR_1,
WARNING_UNMAPPED_CHAR_2,
META_WARNING_DONE META_WARNING_DONE
}; };
/* Literally everything. Notably useful for testing */ // Literally everything. Notably useful for testing
static uint8_t const _weverythingCommands[] = { static uint8_t const _weverythingCommands[] = {
WARNING_BACKWARDS_FOR, WARNING_BACKWARDS_FOR,
WARNING_BUILTIN_ARG, WARNING_BUILTIN_ARG,
@@ -195,7 +203,9 @@ static uint8_t const _weverythingCommands[] = {
WARNING_NUMERIC_STRING_2, WARNING_NUMERIC_STRING_2,
WARNING_TRUNCATION_1, WARNING_TRUNCATION_1,
WARNING_TRUNCATION_2, WARNING_TRUNCATION_2,
/* WARNING_USER, */ WARNING_UNMAPPED_CHAR_1,
WARNING_UNMAPPED_CHAR_2,
// WARNING_USER,
META_WARNING_DONE META_WARNING_DONE
}; };
@@ -209,18 +219,18 @@ void processWarningFlag(char *flag)
{ {
static bool setError = false; static bool setError = false;
/* First, try to match against a "meta" warning */ // First, try to match against a "meta" warning
for (enum WarningID id = META_WARNINGS_START; id < NB_WARNINGS; id++) { for (enum WarningID id = META_WARNINGS_START; id < NB_WARNINGS; id++) {
/* TODO: improve the matching performance? */ // TODO: improve the matching performance?
if (!strcmp(flag, warningFlags[id])) { if (!strcmp(flag, warningFlags[id])) {
/* We got a match! */ // We got a match!
if (setError) if (setError)
errx("Cannot make meta warning \"%s\" into an error", errx("Cannot make meta warning \"%s\" into an error",
flag); flag);
for (uint8_t const *ptr = metaWarningCommands[id - META_WARNINGS_START]; for (uint8_t const *ptr = metaWarningCommands[id - META_WARNINGS_START];
*ptr != META_WARNING_DONE; ptr++) { *ptr != META_WARNING_DONE; ptr++) {
/* Warning flag, set without override */ // Warning flag, set without override
if (warningStates[*ptr] == WARNING_DEFAULT) if (warningStates[*ptr] == WARNING_DEFAULT)
warningStates[*ptr] = WARNING_ENABLED; warningStates[*ptr] = WARNING_ENABLED;
} }
@@ -229,31 +239,31 @@ void processWarningFlag(char *flag)
} }
} }
/* If it's not a meta warning, specially check against `-Werror` */ // If it's not a meta warning, specially check against `-Werror`
if (!strncmp(flag, "error", strlen("error"))) { if (!strncmp(flag, "error", strlen("error"))) {
char *errorFlag = flag + strlen("error"); char *errorFlag = flag + strlen("error");
switch (*errorFlag) { switch (*errorFlag) {
case '\0': case '\0':
/* `-Werror` */ // `-Werror`
warningsAreErrors = true; warningsAreErrors = true;
return; return;
case '=': case '=':
/* `-Werror=XXX` */ // `-Werror=XXX`
setError = true; setError = true;
processWarningFlag(errorFlag + 1); /* Skip the `=` */ processWarningFlag(errorFlag + 1); // Skip the `=`
setError = false; setError = false;
return; return;
/* Otherwise, allow parsing as another flag */ // Otherwise, allow parsing as another flag
} }
} }
/* Well, it's either a normal warning or a mistake */ // Well, it's either a normal warning or a mistake
enum WarningState state = setError ? WARNING_ERROR : enum WarningState state = setError ? WARNING_ERROR :
/* Not an error, then check if this is a negation */ // Not an error, then check if this is a negation
strncmp(flag, "no-", strlen("no-")) ? WARNING_ENABLED strncmp(flag, "no-", strlen("no-")) ? WARNING_ENABLED
: WARNING_DISABLED; : WARNING_DISABLED;
char const *rootFlag = state == WARNING_DISABLED ? flag + strlen("no-") : flag; char const *rootFlag = state == WARNING_DISABLED ? flag + strlen("no-") : flag;
@@ -304,10 +314,10 @@ void processWarningFlag(char *flag)
} }
} }
/* Try to match the flag against a "normal" flag */ // Try to match the flag against a "normal" flag
for (enum WarningID id = 0; id < NB_PLAIN_WARNINGS; id++) { for (enum WarningID id = 0; id < NB_PLAIN_WARNINGS; id++) {
if (!strcmp(rootFlag, warningFlags[id])) { if (!strcmp(rootFlag, warningFlags[id])) {
/* We got a match! */ // We got a match!
warningStates[id] = state; warningStates[id] = state;
return; return;
} }
@@ -370,7 +380,7 @@ void warning(enum WarningID id, char const *fmt, ...)
case WARNING_DEFAULT: case WARNING_DEFAULT:
unreachable_(); unreachable_();
/* Not reached */ // Not reached
case WARNING_ENABLED: case WARNING_ENABLED:
break; break;

View File

@@ -29,7 +29,7 @@
#define BANK_SIZE 0x4000 #define BANK_SIZE 0x4000
/* Short options */ // Short options
static const char *optstring = "Ccf:i:jk:l:m:n:Op:r:st:Vv"; static const char *optstring = "Ccf:i:jk:l:m:n:Op:r:st:Vv";
/* /*
@@ -191,7 +191,7 @@ static void printAcceptedMBCNames(void)
static uint8_t tpp1Rev[2]; static uint8_t tpp1Rev[2];
/** /*
* @return False on failure * @return False on failure
*/ */
static bool readMBCSlice(char const **name, char const *expected) static bool readMBCSlice(char const **name, char const *expected)
@@ -837,7 +837,7 @@ static ssize_t writeBytes(int fd, void *buf, size_t len)
return total; return total;
} }
/** /*
* @param rom0 A pointer to rom0 * @param rom0 A pointer to rom0
* @param addr What address to check * @param addr What address to check
* @param fixedByte The fixed byte at the address * @param fixedByte The fixed byte at the address
@@ -853,7 +853,7 @@ static void overwriteByte(uint8_t *rom0, uint16_t addr, uint8_t fixedByte, char
rom0[addr] = fixedByte; rom0[addr] = fixedByte;
} }
/** /*
* @param rom0 A pointer to rom0 * @param rom0 A pointer to rom0
* @param startAddr What address to begin checking from * @param startAddr What address to begin checking from
* @param fixed The fixed bytes at the address * @param fixed The fixed bytes at the address
@@ -878,7 +878,7 @@ static void overwriteBytes(uint8_t *rom0, uint16_t startAddr, uint8_t const *fix
memcpy(&rom0[startAddr], fixed, size); memcpy(&rom0[startAddr], fixed, size);
} }
/** /*
* @param input File descriptor to be used for reading * @param input File descriptor to be used for reading
* @param output File descriptor to be used for writing, may be equal to `input` * @param output File descriptor to be used for writing, may be equal to `input`
* @param name The file's name, to be displayed for error output * @param name The file's name, to be displayed for error output
@@ -1174,8 +1174,8 @@ static bool processFilename(char const *name)
{ {
nbErrors = 0; nbErrors = 0;
if (!strcmp(name, "-")) { if (!strcmp(name, "-")) {
setmode(STDIN_FILENO, O_BINARY); (void)setmode(STDIN_FILENO, O_BINARY);
setmode(STDOUT_FILENO, O_BINARY); (void)setmode(STDOUT_FILENO, O_BINARY);
name = "<stdin>"; name = "<stdin>";
processFile(STDIN_FILENO, STDOUT_FILENO, name, 0); processFile(STDIN_FILENO, STDOUT_FILENO, name, 0);

View File

@@ -23,8 +23,10 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <string_view> #include <string_view>
#include <type_traits>
#include "extern/getopt.h" #include "extern/getopt.h"
#include "file.hpp"
#include "platform.h" #include "platform.h"
#include "version.h" #include "version.h"
@@ -153,7 +155,7 @@ static void printUsage(void) {
exit(1); exit(1);
} }
/** /*
* Parses a number at the beginning of a string, moving the pointer to skip the parsed characters * Parses a number at the beginning of a string, moving the pointer to skip the parsed characters
* Returns the provided errVal on error * Returns the provided errVal on error
*/ */
@@ -179,7 +181,7 @@ static uint16_t parseNumber(char *&string, char const *errPrefix, uint16_t errVa
} }
} }
/** /*
* Turns a digit into its numeric value in the current base, if it has one. * Turns a digit into its numeric value in the current base, if it has one.
* Maximum is inclusive. The string_view is modified to "consume" all digits. * Maximum is inclusive. The string_view is modified to "consume" all digits.
* Returns 255 on parse failure (including wrong char for base), in which case * Returns 255 on parse failure (including wrong char for base), in which case
@@ -248,15 +250,18 @@ static void registerInput(char const *arg) {
} }
} }
/** /*
* Turn an "at-file"'s contents into an argv that `getopt` can handle * Turn an "at-file"'s contents into an argv that `getopt` can handle
* @param argPool Argument characters will be appended to this vector, for storage purposes. * @param argPool Argument characters will be appended to this vector, for storage purposes.
*/ */
static std::vector<size_t> readAtFile(std::string const &path, std::vector<char> &argPool) { static std::vector<size_t> readAtFile(std::string const &path, std::vector<char> &argPool) {
std::filebuf file; File file;
file.open(path, std::ios_base::in); if (!file.open(path, std::ios_base::in)) {
fatal("Error reading @%s: %s", file.c_str(path), strerror(errno));
}
static_assert(decltype(file)::traits_type::eof() == EOF, // We only filter out `EOF`, but calling `isblank()` on anything else is UB!
static_assert(std::remove_reference_t<decltype(*file)>::traits_type::eof() == EOF,
"isblank(char_traits<...>::eof()) is UB!"); "isblank(char_traits<...>::eof()) is UB!");
std::vector<size_t> argvOfs; std::vector<size_t> argvOfs;
@@ -265,7 +270,7 @@ static std::vector<size_t> readAtFile(std::string const &path, std::vector<char>
// First, discard any leading whitespace // First, discard any leading whitespace
do { do {
c = file.sbumpc(); c = file->sbumpc();
if (c == EOF) { if (c == EOF) {
return argvOfs; return argvOfs;
} }
@@ -273,7 +278,7 @@ static std::vector<size_t> readAtFile(std::string const &path, std::vector<char>
switch (c) { switch (c) {
case '#': // If it's a comment, discard everything until EOL case '#': // If it's a comment, discard everything until EOL
while ((c = file.sbumpc()) != '\n') { while ((c = file->sbumpc()) != '\n') {
if (c == EOF) { if (c == EOF) {
return argvOfs; return argvOfs;
} }
@@ -281,7 +286,7 @@ static std::vector<size_t> readAtFile(std::string const &path, std::vector<char>
continue; // Start processing the next line continue; // Start processing the next line
// If it's an empty line, ignore it // If it's an empty line, ignore it
case '\r': // Assuming CRLF here case '\r': // Assuming CRLF here
file.sbumpc(); // Discard the upcoming '\n' file->sbumpc(); // Discard the upcoming '\n'
[[fallthrough]]; [[fallthrough]];
case '\n': case '\n':
continue; // Start processing the next line continue; // Start processing the next line
@@ -296,11 +301,11 @@ static std::vector<size_t> readAtFile(std::string const &path, std::vector<char>
// on `vector` and `sbumpc` to do the right thing here. // on `vector` and `sbumpc` to do the right thing here.
argPool.push_back(c); // Push the character we've already read argPool.push_back(c); // Push the character we've already read
for (;;) { for (;;) {
c = file.sbumpc(); c = file->sbumpc();
if (isblank(c) || c == '\n' || c == EOF) { if (c == EOF || c == '\n' || isblank(c)) {
break; break;
} else if (c == '\r') { } else if (c == '\r') {
file.sbumpc(); // Discard the '\n' file->sbumpc(); // Discard the '\n'
break; break;
} }
argPool.push_back(c); argPool.push_back(c);
@@ -309,15 +314,15 @@ static std::vector<size_t> readAtFile(std::string const &path, std::vector<char>
// Discard whitespace until the next argument (candidate) // Discard whitespace until the next argument (candidate)
while (isblank(c)) { while (isblank(c)) {
c = file.sbumpc(); c = file->sbumpc();
} }
if (c == '\r') { if (c == '\r') {
c = file.sbumpc(); // Skip the '\n' c = file->sbumpc(); // Skip the '\n'
} }
} while (c != '\n' && c != EOF); // End if we reached EOL } while (c != '\n' && c != EOF); // End if we reached EOL
} }
} }
/** /*
* Parses an arg vector, modifying `options` as options are read. * Parses an arg vector, modifying `options` as options are read.
* The three booleans are for the "auto path" flags, since their processing must be deferred to the * The three booleans are for the "auto path" flags, since their processing must be deferred to the
* end of option parsing. * end of option parsing.
@@ -337,6 +342,8 @@ static char *parseArgv(int argc, char **argv, bool &autoAttrmap, bool &autoTilem
break; break;
case 'a': case 'a':
autoAttrmap = false; autoAttrmap = false;
if (!options.attrmap.empty())
warning("Overriding attrmap file %s", options.attrmap.c_str());
options.attrmap = musl_optarg; options.attrmap = musl_optarg;
break; break;
case 'b': case 'b':
@@ -479,6 +486,8 @@ static char *parseArgv(int argc, char **argv, bool &autoAttrmap, bool &autoTilem
} }
break; break;
case 'o': case 'o':
if (!options.output.empty())
warning("Overriding tile data file %s", options.output.c_str());
options.output = musl_optarg; options.output = musl_optarg;
break; break;
case 'P': case 'P':
@@ -486,6 +495,8 @@ static char *parseArgv(int argc, char **argv, bool &autoAttrmap, bool &autoTilem
break; break;
case 'p': case 'p':
autoPalettes = false; autoPalettes = false;
if (!options.palettes.empty())
warning("Overriding palettes file %s", options.palettes.c_str());
options.palettes = musl_optarg; options.palettes = musl_optarg;
break; break;
case 'Q': case 'Q':
@@ -493,6 +504,8 @@ static char *parseArgv(int argc, char **argv, bool &autoAttrmap, bool &autoTilem
break; break;
case 'q': case 'q':
autoPalmap = false; autoPalmap = false;
if (!options.palmap.empty())
warning("Overriding palette map file %s", options.palmap.c_str());
options.palmap = musl_optarg; options.palmap = musl_optarg;
break; break;
case 'r': case 'r':
@@ -520,6 +533,8 @@ static char *parseArgv(int argc, char **argv, bool &autoAttrmap, bool &autoTilem
break; break;
case 't': case 't':
autoTilemap = false; autoTilemap = false;
if (!options.tilemap.empty())
warning("Overriding tilemap file %s", options.tilemap.c_str());
options.tilemap = musl_optarg; options.tilemap = musl_optarg;
break; break;
case 'V': case 'V':
@@ -785,7 +800,7 @@ void Palette::addColor(uint16_t color) {
} }
} }
/** /*
* Returns the ID of the color in the palette, or `size()` if the color is not in * Returns the ID of the color in the palette, or `size()` if the color is not in
*/ */
uint8_t Palette::indexOf(uint16_t color) const { uint8_t Palette::indexOf(uint16_t color) const {

View File

@@ -39,12 +39,12 @@ namespace packing {
// Tile | Proto-palette // Tile | Proto-palette
// Page | Palette // Page | Palette
/** /*
* A reference to a proto-palette, and attached attributes for sorting purposes * A reference to a proto-palette, and attached attributes for sorting purposes
*/ */
struct ProtoPalAttrs { struct ProtoPalAttrs {
size_t const protoPalIndex; size_t const protoPalIndex;
/** /*
* Pages from which we are banned (to prevent infinite loops) * Pages from which we are banned (to prevent infinite loops)
* This is dynamic because we wish not to hard-cap the amount of palettes * This is dynamic because we wish not to hard-cap the amount of palettes
*/ */
@@ -62,7 +62,7 @@ struct ProtoPalAttrs {
} }
}; };
/** /*
* A collection of proto-palettes assigned to a palette * A collection of proto-palettes assigned to a palette
* Does not contain the actual color indices because we need to be able to remove elements * Does not contain the actual color indices because we need to be able to remove elements
*/ */
@@ -139,7 +139,7 @@ public:
} }
const_iterator end() const { return const_iterator{&_assigned, _assigned.end()}; } const_iterator end() const { return const_iterator{&_assigned, _assigned.end()}; }
/** /*
* Assigns a new ProtoPalAttrs in a free slot, assuming there is one * Assigns a new ProtoPalAttrs in a free slot, assuming there is one
* Args are passed to the `ProtoPalAttrs`'s constructor * Args are passed to the `ProtoPalAttrs`'s constructor
*/ */
@@ -198,7 +198,7 @@ private:
return colors; return colors;
} }
public: public:
/** /*
* Returns the number of distinct colors * Returns the number of distinct colors
*/ */
size_t volume() const { return uniqueColors().size(); } size_t volume() const { return uniqueColors().size(); }
@@ -208,7 +208,7 @@ public:
return colors.size() <= options.maxOpaqueColors(); return colors.size() <= options.maxOpaqueColors();
} }
/** /*
* Computes the "relative size" of a proto-palette on this palette * Computes the "relative size" of a proto-palette on this palette
*/ */
double relSizeOf(ProtoPalette const &protoPal) const { double relSizeOf(ProtoPalette const &protoPal) const {
@@ -227,7 +227,7 @@ public:
return relSize; return relSize;
} }
/** /*
* Computes the "relative size" of a set of proto-palettes on this palette * Computes the "relative size" of a set of proto-palettes on this palette
*/ */
template<typename Iter> template<typename Iter>
@@ -237,7 +237,7 @@ public:
addUniqueColors(colors, std::forward<Iter>(begin), end, protoPals); addUniqueColors(colors, std::forward<Iter>(begin), end, protoPals);
return colors.size(); return colors.size();
} }
/** /*
* Computes the "relative size" of a set of colors on this palette * Computes the "relative size" of a set of colors on this palette
*/ */
template<typename Iter> template<typename Iter>

View File

@@ -1,3 +1,10 @@
/*
* This file is part of RGBDS.
*
* Copyright (c) 2022, Eldred Habert and RGBDS contributors.
*
* SPDX-License-Identifier: MIT
*/
#include "gfx/pal_sorting.hpp" #include "gfx/pal_sorting.hpp"
@@ -21,31 +28,6 @@ void indexed(std::vector<Palette> &palettes, int palSize, png_color const *palRG
return Rgba(c.red, c.green, c.blue, palAlpha ? palAlpha[index] : 0xFF); return Rgba(c.red, c.green, c.blue, palAlpha ? palAlpha[index] : 0xFF);
}; };
// HACK: for compatibility with old versions, add unused colors if:
// - there is only one palette, and
// - only some of the first N colors are being used
if (palettes.size() == 1) {
Palette &palette = palettes[0];
// Build our candidate array of colors
decltype(palette.colors) colors{UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX};
for (int i = 0; i < options.maxOpaqueColors(); ++i) {
colors[i + options.hasTransparentPixels] = pngToRgb(i).cgbColor();
}
// Check that the palette only uses those colors
if (std::all_of(palette.begin(), palette.end(), [&colors](uint16_t color) {
return std::find(colors.begin(), colors.end(), color) != colors.end();
})) {
if (palette.size() != options.maxOpaqueColors()) {
warning("Unused color in PNG embedded palette was re-added; please use `-c "
"embedded` to get this in future versions");
}
// Overwrite the palette, and return with that (it's already sorted)
palette.colors = colors;
return;
}
}
for (Palette &pal : palettes) { for (Palette &pal : palettes) {
std::sort(pal.begin(), pal.end(), [&](uint16_t lhs, uint16_t rhs) { std::sort(pal.begin(), pal.end(), [&](uint16_t lhs, uint16_t rhs) {
// Iterate through the PNG's palette, looking for either of the two // Iterate through the PNG's palette, looking for either of the two
@@ -97,7 +79,7 @@ void rgb(std::vector<Palette> &palettes) {
for (Palette &pal : palettes) { for (Palette &pal : palettes) {
std::sort(pal.begin(), pal.end(), [](uint16_t lhs, uint16_t rhs) { std::sort(pal.begin(), pal.end(), [](uint16_t lhs, uint16_t rhs) {
return legacyLuminance(lhs) < legacyLuminance(rhs); return legacyLuminance(lhs) > legacyLuminance(rhs);
}); });
} }
} }

View File

@@ -16,6 +16,8 @@
#include <cstdio> #include <cstdio>
#include <cstring> #include <cstring>
#include <fstream> #include <fstream>
#include <limits>
#include <optional>
#include <ostream> #include <ostream>
#include <streambuf> #include <streambuf>
#include <string> #include <string>
@@ -53,7 +55,7 @@ constexpr uint8_t singleToHex(char c) {
template<typename Str> // Should be std::string or std::string_view template<typename Str> // Should be std::string or std::string_view
static void skipWhitespace(Str const &str, typename Str::size_type &pos) { static void skipWhitespace(Str const &str, typename Str::size_type &pos) {
pos = std::min(str.find_first_not_of(" \t", pos), str.length()); pos = std::min(str.find_first_not_of(" \t"sv, pos), str.length());
} }
void parseInlinePalSpec(char const * const rawArg) { void parseInlinePalSpec(char const * const rawArg) {
@@ -165,7 +167,7 @@ void parseInlinePalSpec(char const * const rawArg) {
} }
} }
/** /*
* Tries to read some magic bytes from the provided `file`. * Tries to read some magic bytes from the provided `file`.
* Returns whether the magic was correctly read. * Returns whether the magic was correctly read.
*/ */
@@ -191,7 +193,16 @@ static T readBE(U const *bytes) {
return val; return val;
} }
/** template<typename T, typename U>
static T readLE(U const *bytes) {
T val = 0;
for (size_t i = 0; i < sizeof(val); ++i) {
val |= static_cast<uint8_t>(bytes[i]) << (i * 8);
}
return val;
}
/*
* **Appends** the first line read from `file` to the end of the provided `buffer`. * **Appends** the first line read from `file` to the end of the provided `buffer`.
*/ */
static void readLine(std::filebuf &file, std::string &buffer) { static void readLine(std::filebuf &file, std::string &buffer) {
@@ -214,16 +225,56 @@ static void readLine(std::filebuf &file, std::string &buffer) {
} }
// FIXME: Normally we'd use `std::from_chars`, but that's not available with GCC 7 // FIXME: Normally we'd use `std::from_chars`, but that's not available with GCC 7
/** /*
* Parses the initial part of a string_view, advancing the "read index" as it does * Parses the initial part of a string_view, advancing the "read index" as it does
*/ */
static uint16_t parseDec(std::string const &str, std::string::size_type &n) { template<typename U> // Should be uint*_t
uint32_t value = 0; // Use a larger type to handle overflow more easily static std::optional<U> parseDec(std::string const &str, std::string::size_type &n) {
for (auto end = std::min(str.length(), str.find_first_not_of("0123456789", n)); n < end; ++n) { std::string::size_type start = n;
value = std::min<uint32_t>(value * 10 + (str[n] - '0'), UINT16_MAX);
uintmax_t value = 0; // Use a larger type to handle overflow more easily
for (auto end = std::min(str.length(), str.find_first_not_of("0123456789"sv, n)); n < end;
++n) {
value = std::min(value * 10 + (str[n] - '0'), (uintmax_t)std::numeric_limits<U>::max);
} }
return value; return n > start ? std::optional<U>{value} : std::nullopt;
}
static std::optional<Rgba> parseColor(std::string const &str, std::string::size_type &n,
uint16_t i) {
std::optional<uint8_t> r = parseDec<uint8_t>(str, n);
if (!r) {
error("Failed to parse color #%" PRIu16 " (\"%s\"): invalid red component", i + 1,
str.c_str());
return std::nullopt;
}
skipWhitespace(str, n);
if (n == str.length()) {
error("Failed to parse color #%" PRIu16 " (\"%s\"): missing green component", i + 1,
str.c_str());
return std::nullopt;
}
std::optional<uint8_t> g = parseDec<uint8_t>(str, n);
if (!g) {
error("Failed to parse color #%" PRIu16 " (\"%s\"): invalid green component", i + 1,
str.c_str());
return std::nullopt;
}
skipWhitespace(str, n);
if (n == str.length()) {
error("Failed to parse color #%" PRIu16 " (\"%s\"): missing blue component", i + 1,
str.c_str());
return std::nullopt;
}
std::optional<uint8_t> b = parseDec<uint8_t>(str, n);
if (!b) {
error("Failed to parse color #%" PRIu16 " (\"%s\"): invalid blue component", i + 1,
str.c_str());
return std::nullopt;
}
return std::optional<Rgba>{Rgba(*r, *g, *b, 0xFF)};
} }
static void parsePSPFile(std::filebuf &file) { static void parsePSPFile(std::filebuf &file) {
@@ -246,41 +297,30 @@ static void parsePSPFile(std::filebuf &file) {
line.clear(); line.clear();
readLine(file, line); readLine(file, line);
std::string::size_type n = 0; std::string::size_type n = 0;
uint16_t nbColors = parseDec(line, n); std::optional<uint16_t> nbColors = parseDec<uint16_t>(line, n);
if (n != line.length()) { if (!nbColors || n != line.length()) {
error("Invalid \"number of colors\" line in PSP file (%s)", line.c_str()); error("Invalid \"number of colors\" line in PSP file (%s)", line.c_str());
return; return;
} }
if (nbColors > options.nbColorsPerPal * options.nbPalettes) { if (*nbColors > options.nbColorsPerPal * options.nbPalettes) {
warning("PSP file contains %" PRIu16 " colors, but there can only be %" PRIu16 warning("PSP file contains %" PRIu16 " colors, but there can only be %" PRIu16
"; ignoring extra", "; ignoring extra",
nbColors, options.nbColorsPerPal * options.nbPalettes); *nbColors, options.nbColorsPerPal * options.nbPalettes);
nbColors = options.nbColorsPerPal * options.nbPalettes; nbColors = options.nbColorsPerPal * options.nbPalettes;
} }
options.palSpec.clear(); options.palSpec.clear();
for (uint16_t i = 0; i < nbColors; ++i) { for (uint16_t i = 0; i < *nbColors; ++i) {
line.clear(); line.clear();
readLine(file, line); readLine(file, line);
n = 0;
uint8_t r = parseDec(line, n); n = 0;
skipWhitespace(line, n); std::optional<Rgba> color = parseColor(line, n, i + 1);
if (n == line.length()) { if (!color) {
error("Failed to parse color #%" PRIu16 " (\"%s\"): missing green component", i + 1,
line.c_str());
return; return;
} }
uint8_t g = parseDec(line, n);
if (n == line.length()) {
error("Failed to parse color #%" PRIu16 " (\"%s\"): missing green component", i + 1,
line.c_str());
return;
}
skipWhitespace(line, n);
uint8_t b = parseDec(line, n);
if (n != line.length()) { if (n != line.length()) {
error("Failed to parse color #%" PRIu16 error("Failed to parse color #%" PRIu16
" (\"%s\"): trailing characters after blue component", " (\"%s\"): trailing characters after blue component",
@@ -291,11 +331,98 @@ static void parsePSPFile(std::filebuf &file) {
if (i % options.nbColorsPerPal == 0) { if (i % options.nbColorsPerPal == 0) {
options.palSpec.emplace_back(); options.palSpec.emplace_back();
} }
options.palSpec.back()[i % options.nbColorsPerPal] = Rgba(r, g, b, 0xFF); options.palSpec.back()[i % options.nbColorsPerPal] = *color;
} }
} }
void parseACTFile(std::filebuf &file) { static void parseGPLFile(std::filebuf &file) {
// https://gitlab.gnome.org/GNOME/gimp/-/blob/gimp-2-10/app/core/gimppalette-load.c#L39
std::string line;
readLine(file, line);
// FIXME: C++20 will allow `!line.starts_with` instead of `line.rfind` with 0
if (line.rfind("GIMP Palette", 0)) {
error("Palette file does not appear to be a GPL palette file");
return;
}
uint16_t nbColors = 0;
uint16_t maxNbColors = options.nbColorsPerPal * options.nbPalettes;
for (;;) {
line.clear();
readLine(file, line);
if (!line.length()) {
break;
}
// FIXME: C++20 will allow `line.starts_with` instead of `!line.rfind` with 0
if (!line.rfind("#", 0) || !line.rfind("Name:", 0) || !line.rfind("Column:", 0)) {
continue;
}
std::string::size_type n = 0;
std::optional<Rgba> color = parseColor(line, n, nbColors + 1);
if (!color) {
return;
}
++nbColors;
if (nbColors < maxNbColors) {
if (nbColors % options.nbColorsPerPal == 1) {
options.palSpec.emplace_back();
}
options.palSpec.back()[nbColors % options.nbColorsPerPal] = *color;
}
}
if (nbColors > maxNbColors) {
warning("GPL file contains %" PRIu16 " colors, but there can only be %" PRIu16
"; ignoring extra",
nbColors, maxNbColors);
}
}
static void parseHEXFile(std::filebuf &file) {
// https://lospec.com/palette-list/tag/gbc
uint16_t nbColors = 0;
uint16_t maxNbColors = options.nbColorsPerPal * options.nbPalettes;
for (;;) {
std::string line;
readLine(file, line);
if (!line.length()) {
break;
}
if (line.length() != 6
|| line.find_first_not_of("0123456789ABCDEFabcdef"sv) != std::string::npos) {
error("Failed to parse color #%" PRIu16 " (\"%s\"): invalid \"rrggbb\" line",
nbColors + 1, line.c_str());
return;
}
Rgba color =
Rgba(toHex(line[0], line[1]), toHex(line[2], line[3]), toHex(line[4], line[5]), 0xFF);
++nbColors;
if (nbColors < maxNbColors) {
if (nbColors % options.nbColorsPerPal == 1) {
options.palSpec.emplace_back();
}
options.palSpec.back()[nbColors % options.nbColorsPerPal] = color;
}
}
if (nbColors > maxNbColors) {
warning("HEX file contains %" PRIu16 " colors, but there can only be %" PRIu16
"; ignoring extra",
nbColors, maxNbColors);
}
}
static void parseACTFile(std::filebuf &file) {
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577411_pgfId-1070626 // https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577411_pgfId-1070626
std::array<char, 772> buf; std::array<char, 772> buf;
@@ -344,8 +471,8 @@ void parseACTFile(std::filebuf &file) {
} }
} }
void parseACOFile(std::filebuf &file) { static void parseACOFile(std::filebuf &file) {
// rhttps://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577411_pgfId-1055819 // https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577411_pgfId-1055819
// http://www.nomodes.com/aco.html // http://www.nomodes.com/aco.html
char buf[10]; char buf[10];
@@ -412,6 +539,29 @@ void parseACOFile(std::filebuf &file) {
// `codecvt` can be used to convert from UTF-16 to UTF-8 // `codecvt` can be used to convert from UTF-16 to UTF-8
} }
static void parseGBCFile(std::filebuf &file) {
// This only needs to be able to read back files generated by `rgbgfx -p`
options.palSpec.clear();
for (;;) {
char buf[2 * 4];
auto len = file.sgetn(buf, sizeof(buf));
if (len == 0) {
break;
} else if (len != sizeof(buf)) {
error("GBC palette dump contains %zu 8-byte palette%s, plus %zu byte%s",
options.palSpec.size(), options.palSpec.size() == 1 ? "" : "s", len,
len == 1 ? "" : "s");
break;
}
options.palSpec.push_back({Rgba::fromCGBColor(readLE<uint16_t>(&buf[0])),
Rgba::fromCGBColor(readLE<uint16_t>(&buf[2])),
Rgba::fromCGBColor(readLE<uint16_t>(&buf[4])),
Rgba::fromCGBColor(readLE<uint16_t>(&buf[6]))});
}
}
void parseExternalPalSpec(char const *arg) { void parseExternalPalSpec(char const *arg) {
// `fmt:path`, parse the file according to the given format // `fmt:path`, parse the file according to the given format
@@ -425,8 +575,11 @@ void parseExternalPalSpec(char const *arg) {
static std::array parsers{ static std::array parsers{
std::tuple{"PSP", &parsePSPFile, std::ios::in }, std::tuple{"PSP", &parsePSPFile, std::ios::in },
std::tuple{"GPL", &parseGPLFile, std::ios::in },
std::tuple{"HEX", &parseHEXFile, std::ios::in },
std::tuple{"ACT", &parseACTFile, std::ios::binary}, std::tuple{"ACT", &parseACTFile, std::ios::binary},
std::tuple{"ACO", &parseACOFile, std::ios::binary}, std::tuple{"ACO", &parseACOFile, std::ios::binary},
std::tuple{"GBC", &parseGBCFile, std::ios::binary},
}; };
auto iter = std::find_if(parsers.begin(), parsers.end(), auto iter = std::find_if(parsers.begin(), parsers.end(),

View File

@@ -27,6 +27,7 @@
#include <vector> #include <vector>
#include "defaultinitalloc.hpp" #include "defaultinitalloc.hpp"
#include "file.hpp"
#include "helpers.h" #include "helpers.h"
#include "itertools.hpp" #include "itertools.hpp"
@@ -42,7 +43,7 @@ class ImagePalette {
public: public:
ImagePalette() = default; ImagePalette() = default;
/** /*
* Registers a color in the palette. * Registers a color in the palette.
* If the newly inserted color "conflicts" with another one (different color, but same CGB * If the newly inserted color "conflicts" with another one (different color, but same CGB
* color), then the other color is returned. Otherwise, `nullptr` is returned. * color), then the other color is returned. Otherwise, `nullptr` is returned.
@@ -77,7 +78,7 @@ public:
class Png { class Png {
std::string const &path; std::string const &path;
std::filebuf file{}; File file{};
png_structp png = nullptr; png_structp png = nullptr;
png_infop info = nullptr; png_infop info = nullptr;
@@ -93,25 +94,26 @@ class Png {
[[noreturn]] static void handleError(png_structp png, char const *msg) { [[noreturn]] static void handleError(png_structp png, char const *msg) {
Png *self = reinterpret_cast<Png *>(png_get_error_ptr(png)); Png *self = reinterpret_cast<Png *>(png_get_error_ptr(png));
fatal("Error reading input image (\"%s\"): %s", self->path.c_str(), msg); fatal("Error reading input image (\"%s\"): %s", self->file.c_str(self->path), msg);
} }
static void handleWarning(png_structp png, char const *msg) { static void handleWarning(png_structp png, char const *msg) {
Png *self = reinterpret_cast<Png *>(png_get_error_ptr(png)); Png *self = reinterpret_cast<Png *>(png_get_error_ptr(png));
warning("In input image (\"%s\"): %s", self->path.c_str(), msg); warning("In input image (\"%s\"): %s", self->file.c_str(self->path), msg);
} }
static void readData(png_structp png, png_bytep data, size_t length) { static void readData(png_structp png, png_bytep data, size_t length) {
Png *self = reinterpret_cast<Png *>(png_get_io_ptr(png)); Png *self = reinterpret_cast<Png *>(png_get_io_ptr(png));
std::streamsize expectedLen = length; std::streamsize expectedLen = length;
std::streamsize nbBytesRead = self->file.sgetn(reinterpret_cast<char *>(data), expectedLen); std::streamsize nbBytesRead =
self->file->sgetn(reinterpret_cast<char *>(data), expectedLen);
if (nbBytesRead != expectedLen) { if (nbBytesRead != expectedLen) {
fatal("Error reading input image (\"%s\"): file too short (expected at least %zd more " fatal("Error reading input image (\"%s\"): file too short (expected at least %zd more "
"bytes after reading %lld)", "bytes after reading %lld)",
self->path.c_str(), length - nbBytesRead, self->file.c_str(self->path), length - nbBytesRead,
self->file.pubseekoff(0, std::ios_base::cur)); self->file->pubseekoff(0, std::ios_base::cur));
} }
} }
@@ -142,7 +144,7 @@ public:
} }
uint8_t bins = 0; uint8_t bins = 0;
for (auto const &color : colors) { for (auto const &color : colors) {
if (color->isTransparent()) { if (!color.has_value() || color->isTransparent()) {
continue; continue;
} }
if (!color->isGray()) { if (!color->isGray()) {
@@ -164,7 +166,7 @@ public:
return true; return true;
} }
/** /*
* Reads a PNG and notes all of its colors * Reads a PNG and notes all of its colors
* *
* This code is more complicated than strictly necessary, but that's because of the API * This code is more complicated than strictly necessary, but that's because of the API
@@ -175,17 +177,17 @@ public:
*/ */
explicit Png(std::string const &filePath) : path(filePath), colors() { explicit Png(std::string const &filePath) : path(filePath), colors() {
if (file.open(path, std::ios_base::in | std::ios_base::binary) == nullptr) { if (file.open(path, std::ios_base::in | std::ios_base::binary) == nullptr) {
fatal("Failed to open input image (\"%s\"): %s", path.c_str(), strerror(errno)); fatal("Failed to open input image (\"%s\"): %s", file.c_str(path), strerror(errno));
} }
options.verbosePrint(Options::VERB_LOG_ACT, "Opened input file\n"); options.verbosePrint(Options::VERB_LOG_ACT, "Opened input file\n");
std::array<unsigned char, 8> pngHeader; std::array<unsigned char, 8> pngHeader;
if (file.sgetn(reinterpret_cast<char *>(pngHeader.data()), pngHeader.size()) if (file->sgetn(reinterpret_cast<char *>(pngHeader.data()), pngHeader.size())
!= static_cast<std::streamsize>(pngHeader.size()) // Not enough bytes? != static_cast<std::streamsize>(pngHeader.size()) // Not enough bytes?
|| png_sig_cmp(pngHeader.data(), 0, pngHeader.size()) != 0) { || png_sig_cmp(pngHeader.data(), 0, pngHeader.size()) != 0) {
fatal("Input file (\"%s\") is not a PNG image!", path.c_str()); fatal("Input file (\"%s\") is not a PNG image!", file.c_str(path));
} }
options.verbosePrint(Options::VERB_INTERM, "PNG header signature is OK\n"); options.verbosePrint(Options::VERB_INTERM, "PNG header signature is OK\n");
@@ -254,8 +256,8 @@ public:
} }
}; };
options.verbosePrint(Options::VERB_INTERM, options.verbosePrint(Options::VERB_INTERM,
"Input image: %" PRIu32 "x%" PRIu32 " pixels, %dbpp %s, %s\n", height, "Input image: %" PRIu32 "x%" PRIu32 " pixels, %dbpp %s, %s\n", width,
width, bitDepth, colorTypeName(), interlaceTypeName()); height, bitDepth, colorTypeName(), interlaceTypeName());
if (png_get_PLTE(png, info, &embeddedPal, &nbColors) != 0) { if (png_get_PLTE(png, info, &embeddedPal, &nbColors) != 0) {
int nbTransparentEntries; int nbTransparentEntries;
@@ -466,7 +468,7 @@ public:
}; };
class RawTiles { class RawTiles {
/** /*
* A tile which only contains indices into the image's global palette * A tile which only contains indices into the image's global palette
*/ */
class RawTile { class RawTile {
@@ -481,7 +483,7 @@ private:
std::vector<RawTile> _tiles; std::vector<RawTile> _tiles;
public: public:
/** /*
* Creates a new raw tile, and returns a reference to it so it can be filled in * Creates a new raw tile, and returns a reference to it so it can be filled in
*/ */
RawTile &newTile() { RawTile &newTile() {
@@ -491,7 +493,7 @@ public:
}; };
struct AttrmapEntry { struct AttrmapEntry {
/** /*
* This field can either be a proto-palette ID, or `transparent` to indicate that the * This field can either be a proto-palette ID, or `transparent` to indicate that the
* corresponding tile is fully transparent. If you are looking to get the palette ID for this * corresponding tile is fully transparent. If you are looking to get the palette ID for this
* attrmap entry while correctly handling the above, use `getPalID`. * attrmap entry while correctly handling the above, use `getPalID`.
@@ -624,14 +626,16 @@ static std::tuple<DefaultInitVec<size_t>, std::vector<Palette>>
} }
static void outputPalettes(std::vector<Palette> const &palettes) { static void outputPalettes(std::vector<Palette> const &palettes) {
std::filebuf output; File output;
output.open(options.palettes, std::ios_base::out | std::ios_base::binary); if (!output.open(options.palettes, std::ios_base::out | std::ios_base::binary)) {
fatal("Failed to open \"%s\": %s", output.c_str(options.palettes), strerror(errno));
}
for (Palette const &palette : palettes) { for (Palette const &palette : palettes) {
for (uint8_t i = 0; i < options.nbColorsPerPal; ++i) { for (uint8_t i = 0; i < options.nbColorsPerPal; ++i) {
uint16_t color = palette.colors[i]; // Will return `UINT16_MAX` for unused slots uint16_t color = palette.colors[i]; // Will return `UINT16_MAX` for unused slots
output.sputc(color & 0xFF); output->sputc(color & 0xFF);
output.sputc(color >> 8); output->sputc(color >> 8);
} }
} }
} }
@@ -750,8 +754,10 @@ namespace unoptimized {
static void outputTileData(Png const &png, DefaultInitVec<AttrmapEntry> const &attrmap, static void outputTileData(Png const &png, DefaultInitVec<AttrmapEntry> const &attrmap,
std::vector<Palette> const &palettes, std::vector<Palette> const &palettes,
DefaultInitVec<size_t> const &mappings) { DefaultInitVec<size_t> const &mappings) {
std::filebuf output; File output;
output.open(options.output, std::ios_base::out | std::ios_base::binary); if (!output.open(options.output, std::ios_base::out | std::ios_base::binary)) {
fatal("Failed to open \"%s\": %s", output.c_str(options.output), strerror(errno));
}
uint64_t remainingTiles = (png.getWidth() / 8) * (png.getHeight() / 8); uint64_t remainingTiles = (png.getWidth() / 8) * (png.getHeight() / 8);
if (remainingTiles <= options.trim) { if (remainingTiles <= options.trim) {
@@ -764,9 +770,9 @@ static void outputTileData(Png const &png, DefaultInitVec<AttrmapEntry> const &a
Palette const &palette = palettes[attr.getPalID(mappings)]; Palette const &palette = palettes[attr.getPalID(mappings)];
for (uint32_t y = 0; y < 8; ++y) { for (uint32_t y = 0; y < 8; ++y) {
uint16_t bitplanes = TileData::rowBitplanes(tile, palette, y); uint16_t bitplanes = TileData::rowBitplanes(tile, palette, y);
output.sputc(bitplanes & 0xFF); output->sputc(bitplanes & 0xFF);
if (options.bitDepth == 2) { if (options.bitDepth == 2) {
output.sputc(bitplanes >> 8); output->sputc(bitplanes >> 8);
} }
} }
@@ -780,18 +786,27 @@ static void outputTileData(Png const &png, DefaultInitVec<AttrmapEntry> const &a
static void outputMaps(DefaultInitVec<AttrmapEntry> const &attrmap, static void outputMaps(DefaultInitVec<AttrmapEntry> const &attrmap,
DefaultInitVec<size_t> const &mappings) { DefaultInitVec<size_t> const &mappings) {
std::optional<std::filebuf> tilemapOutput, attrmapOutput, palmapOutput; std::optional<File> tilemapOutput, attrmapOutput, palmapOutput;
if (!options.tilemap.empty()) { if (!options.tilemap.empty()) {
tilemapOutput.emplace(); tilemapOutput.emplace();
tilemapOutput->open(options.tilemap, std::ios_base::out | std::ios_base::binary); if (!tilemapOutput->open(options.tilemap, std::ios_base::out | std::ios_base::binary)) {
fatal("Failed to open \"%s\": %s", tilemapOutput->c_str(options.tilemap),
strerror(errno));
}
} }
if (!options.attrmap.empty()) { if (!options.attrmap.empty()) {
attrmapOutput.emplace(); attrmapOutput.emplace();
attrmapOutput->open(options.attrmap, std::ios_base::out | std::ios_base::binary); if (!attrmapOutput->open(options.attrmap, std::ios_base::out | std::ios_base::binary)) {
fatal("Failed to open \"%s\": %s", attrmapOutput->c_str(options.attrmap),
strerror(errno));
}
} }
if (!options.palmap.empty()) { if (!options.palmap.empty()) {
palmapOutput.emplace(); palmapOutput.emplace();
palmapOutput->open(options.palmap, std::ios_base::out | std::ios_base::binary); if (!palmapOutput->open(options.palmap, std::ios_base::out | std::ios_base::binary)) {
fatal("Failed to open \"%s\": %s", palmapOutput->c_str(options.palmap),
strerror(errno));
}
} }
uint8_t tileID = 0; uint8_t tileID = 0;
@@ -804,14 +819,14 @@ static void outputMaps(DefaultInitVec<AttrmapEntry> const &attrmap,
} }
if (tilemapOutput.has_value()) { if (tilemapOutput.has_value()) {
tilemapOutput->sputc(tileID + options.baseTileIDs[bank]); (*tilemapOutput)->sputc(tileID + options.baseTileIDs[bank]);
} }
if (attrmapOutput.has_value()) { if (attrmapOutput.has_value()) {
uint8_t palID = attr.getPalID(mappings) & 7; uint8_t palID = attr.getPalID(mappings) & 7;
attrmapOutput->sputc(palID | bank << 3); // The other flags are all 0 (*attrmapOutput)->sputc(palID | bank << 3); // The other flags are all 0
} }
if (palmapOutput.has_value()) { if (palmapOutput.has_value()) {
palmapOutput->sputc(attr.getPalID(mappings)); (*palmapOutput)->sputc(attr.getPalID(mappings));
} }
++tileID; ++tileID;
} }
@@ -831,7 +846,7 @@ struct UniqueTiles {
UniqueTiles(UniqueTiles const &) = delete; UniqueTiles(UniqueTiles const &) = delete;
UniqueTiles(UniqueTiles &&) = default; UniqueTiles(UniqueTiles &&) = default;
/** /*
* Adds a tile to the collection, and returns its ID * Adds a tile to the collection, and returns its ID
*/ */
std::tuple<uint16_t, TileData::MatchType> addTile(Png::TilesVisitor::Tile const &tile, std::tuple<uint16_t, TileData::MatchType> addTile(Png::TilesVisitor::Tile const &tile,
@@ -857,7 +872,7 @@ struct UniqueTiles {
auto end() const { return tiles.end(); } auto end() const { return tiles.end(); }
}; };
/** /*
* Generate tile data while deduplicating unique tiles (via mirroring if enabled) * Generate tile data while deduplicating unique tiles (via mirroring if enabled)
* Additionally, while we have the info handy, convert from the 16-bit "global" tile IDs to * Additionally, while we have the info handy, convert from the 16-bit "global" tile IDs to
* 8-bit tile IDs + the bank bit; this will save the work when we output the data later (potentially * 8-bit tile IDs + the bank bit; this will save the work when we output the data later (potentially
@@ -886,47 +901,55 @@ static UniqueTiles dedupTiles(Png const &png, DefaultInitVec<AttrmapEntry> &attr
} }
static void outputTileData(UniqueTiles const &tiles) { static void outputTileData(UniqueTiles const &tiles) {
std::filebuf output; File output;
output.open(options.output, std::ios_base::out | std::ios_base::binary); if (!output.open(options.output, std::ios_base::out | std::ios_base::binary)) {
fatal("Failed to create \"%s\": %s", output.c_str(options.output), strerror(errno));
}
uint16_t tileID = 0; uint16_t tileID = 0;
for (auto iter = tiles.begin(), end = tiles.end() - options.trim; iter != end; ++iter) { for (auto iter = tiles.begin(), end = tiles.end() - options.trim; iter != end; ++iter) {
TileData const *tile = *iter; TileData const *tile = *iter;
assert(tile->tileID == tileID); assert(tile->tileID == tileID);
++tileID; ++tileID;
output.sputn(reinterpret_cast<char const *>(tile->data().data()), options.bitDepth * 8); output->sputn(reinterpret_cast<char const *>(tile->data().data()), options.bitDepth * 8);
} }
} }
static void outputTilemap(DefaultInitVec<AttrmapEntry> const &attrmap) { static void outputTilemap(DefaultInitVec<AttrmapEntry> const &attrmap) {
std::filebuf output; File output;
output.open(options.tilemap, std::ios_base::out | std::ios_base::binary); if (!output.open(options.tilemap, std::ios_base::out | std::ios_base::binary)) {
fatal("Failed to create \"%s\": %s", output.c_str(options.tilemap), strerror(errno));
}
for (AttrmapEntry const &entry : attrmap) { for (AttrmapEntry const &entry : attrmap) {
output.sputc(entry.tileID); // The tile ID has already been converted output->sputc(entry.tileID); // The tile ID has already been converted
} }
} }
static void outputAttrmap(DefaultInitVec<AttrmapEntry> const &attrmap, static void outputAttrmap(DefaultInitVec<AttrmapEntry> const &attrmap,
DefaultInitVec<size_t> const &mappings) { DefaultInitVec<size_t> const &mappings) {
std::filebuf output; File output;
output.open(options.attrmap, std::ios_base::out | std::ios_base::binary); if (!output.open(options.attrmap, std::ios_base::out | std::ios_base::binary)) {
fatal("Failed to create \"%s\": %s", output.c_str(options.attrmap), strerror(errno));
}
for (AttrmapEntry const &entry : attrmap) { for (AttrmapEntry const &entry : attrmap) {
uint8_t attr = entry.xFlip << 5 | entry.yFlip << 6; uint8_t attr = entry.xFlip << 5 | entry.yFlip << 6;
attr |= entry.bank << 3; attr |= entry.bank << 3;
attr |= entry.getPalID(mappings) & 7; attr |= entry.getPalID(mappings) & 7;
output.sputc(attr); output->sputc(attr);
} }
} }
static void outputPalmap(DefaultInitVec<AttrmapEntry> const &attrmap, static void outputPalmap(DefaultInitVec<AttrmapEntry> const &attrmap,
DefaultInitVec<size_t> const &mappings) { DefaultInitVec<size_t> const &mappings) {
std::filebuf output; File output;
output.open(options.attrmap, std::ios_base::out | std::ios_base::binary); if (!output.open(options.attrmap, std::ios_base::out | std::ios_base::binary)) {
fatal("Failed to create \"%s\": %s", output.c_str(options.attrmap), strerror(errno));
}
for (AttrmapEntry const &entry : attrmap) { for (AttrmapEntry const &entry : attrmap) {
output.sputc(entry.getPalID(mappings)); output->sputc(entry.getPalID(mappings));
} }
} }
@@ -986,16 +1009,17 @@ void process() {
protoPalettes[n] = tileColors; // Override them protoPalettes[n] = tileColors; // Override them
// Remove any other proto-palettes that we encompass // Remove any other proto-palettes that we encompass
// (Example [(0, 1), (0, 2)], inserting (0, 1, 2)) // (Example [(0, 1), (0, 2)], inserting (0, 1, 2))
/* The following code does its job, except that references to the removed /*
* The following code does its job, except that references to the removed
* proto-palettes are not updated, causing issues. * proto-palettes are not updated, causing issues.
* TODO: overlap might not be detrimental to the packing algorithm. * TODO: overlap might not be detrimental to the packing algorithm.
* Investigation is necessary, especially if pathological cases are found. * Investigation is necessary, especially if pathological cases are found.
*
for (size_t i = protoPalettes.size(); --i != n;) { * for (size_t i = protoPalettes.size(); --i != n;) {
if (tileColors.compare(protoPalettes[i]) == ProtoPalette::WE_BIGGER) { * if (tileColors.compare(protoPalettes[i]) == ProtoPalette::WE_BIGGER) {
protoPalettes.erase(protoPalettes.begin() + i); * protoPalettes.erase(protoPalettes.begin() + i);
} * }
} * }
*/ */
[[fallthrough]]; [[fallthrough]];

View File

@@ -21,14 +21,17 @@
#include <vector> #include <vector>
#include "defaultinitalloc.hpp" #include "defaultinitalloc.hpp"
#include "file.hpp"
#include "helpers.h" #include "helpers.h"
#include "itertools.hpp" #include "itertools.hpp"
#include "gfx/main.hpp" #include "gfx/main.hpp"
static DefaultInitVec<uint8_t> readInto(std::string path) { static DefaultInitVec<uint8_t> readInto(std::string path) {
std::filebuf file; File file;
file.open(path, std::ios::in | std::ios::binary); if (!file.open(path, std::ios::in | std::ios::binary)) {
fatal("Failed to open \"%s\": %s", file.c_str(path), strerror(errno));
}
DefaultInitVec<uint8_t> data(128 * 16); // Begin with some room pre-allocated DefaultInitVec<uint8_t> data(128 * 16); // Begin with some room pre-allocated
size_t curSize = 0; size_t curSize = 0;
@@ -38,7 +41,7 @@ static DefaultInitVec<uint8_t> readInto(std::string path) {
// Fill the new area ([oldSize; curSize[) with bytes // Fill the new area ([oldSize; curSize[) with bytes
size_t nbRead = size_t nbRead =
file.sgetn(reinterpret_cast<char *>(&data.data()[oldSize]), curSize - oldSize); file->sgetn(reinterpret_cast<char *>(&data.data()[oldSize]), curSize - oldSize);
if (nbRead != curSize - oldSize) { if (nbRead != curSize - oldSize) {
// Shrink the vector to discard bytes that weren't read // Shrink the vector to discard bytes that weren't read
data.resize(oldSize + nbRead); data.resize(oldSize + nbRead);
@@ -66,13 +69,13 @@ static void pngWarning(png_structp png, char const *msg) {
} }
void writePng(png_structp png, png_bytep data, size_t length) { void writePng(png_structp png, png_bytep data, size_t length) {
auto &pngFile = *static_cast<std::filebuf *>(png_get_io_ptr(png)); auto &pngFile = *static_cast<File *>(png_get_io_ptr(png));
pngFile.sputn(reinterpret_cast<char *>(data), length); pngFile->sputn(reinterpret_cast<char *>(data), length);
} }
void flushPng(png_structp png) { void flushPng(png_structp png) {
auto &pngFile = *static_cast<std::filebuf *>(png_get_io_ptr(png)); auto &pngFile = *static_cast<File *>(png_get_io_ptr(png));
pngFile.pubsync(); pngFile->pubsync();
} }
void reverse() { void reverse() {
@@ -117,8 +120,12 @@ void reverse() {
if (!options.tilemap.empty()) { if (!options.tilemap.empty()) {
tilemap = readInto(options.tilemap); tilemap = readInto(options.tilemap);
nbTileInstances = tilemap->size(); nbTileInstances = tilemap->size();
options.verbosePrint(Options::VERB_INTERM, "Read %zu tilemap entries.\n", nbTileInstances);
} }
if (nbTileInstances == 0) {
fatal("Cannot generate empty image");
}
if (nbTileInstances > options.maxNbTiles[0] + options.maxNbTiles[1]) { if (nbTileInstances > options.maxNbTiles[0] + options.maxNbTiles[1]) {
warning("Read %zu tiles, more than the limit of %zu + %zu", nbTileInstances, warning("Read %zu tiles, more than the limit of %zu + %zu", nbTileInstances,
options.maxNbTiles[0], options.maxNbTiles[1]); options.maxNbTiles[0], options.maxNbTiles[1]);
@@ -140,14 +147,16 @@ void reverse() {
{Rgba(0xffffffff), Rgba(0xaaaaaaff), Rgba(0x555555ff), Rgba(0x000000ff)} {Rgba(0xffffffff), Rgba(0xaaaaaaff), Rgba(0x555555ff), Rgba(0x000000ff)}
}; };
if (!options.palettes.empty()) { if (!options.palettes.empty()) {
std::filebuf file; File file;
file.open(options.palettes, std::ios::in | std::ios::binary); if (!file.open(options.palettes, std::ios::in | std::ios::binary)) {
fatal("Failed to open \"%s\": %s", file.c_str(options.palettes), strerror(errno));
}
palettes.clear(); palettes.clear();
std::array<uint8_t, sizeof(uint16_t) * 4> buf; // 4 colors std::array<uint8_t, sizeof(uint16_t) * 4> buf; // 4 colors
size_t nbRead; size_t nbRead;
do { do {
nbRead = file.sgetn(reinterpret_cast<char *>(buf.data()), buf.size()); nbRead = file->sgetn(reinterpret_cast<char *>(buf.data()), buf.size());
if (nbRead == buf.size()) { if (nbRead == buf.size()) {
// Expand the colors // Expand the colors
auto &palette = palettes.emplace_back(); auto &palette = palettes.emplace_back();
@@ -225,11 +234,13 @@ void reverse() {
} }
options.verbosePrint(Options::VERB_LOG_ACT, "Writing image...\n"); options.verbosePrint(Options::VERB_LOG_ACT, "Writing image...\n");
std::filebuf pngFile; File pngFile;
pngFile.open(options.input, std::ios::out | std::ios::binary); if (!pngFile.open(options.input, std::ios::out | std::ios::binary)) {
fatal("Failed to create \"%s\": %s", pngFile.c_str(options.input), strerror(errno));
}
png_structp png = png_create_write_struct( png_structp png = png_create_write_struct(
PNG_LIBPNG_VER_STRING, PNG_LIBPNG_VER_STRING,
const_cast<png_voidp>(static_cast<void const *>(options.input.c_str())), pngError, const_cast<png_voidp>(static_cast<void const *>(pngFile.c_str(options.input))), pngError,
pngWarning); pngWarning);
if (!png) { if (!png) {
fatal("Couldn't create PNG write struct: %s", strerror(errno)); fatal("Couldn't create PNG write struct: %s", strerror(errno));
@@ -268,7 +279,7 @@ void reverse() {
uint8_t attribute = attrmap.has_value() ? (*attrmap)[index] : 0x00; uint8_t attribute = attrmap.has_value() ? (*attrmap)[index] : 0x00;
bool bank = attribute & 0x08; bool bank = attribute & 0x08;
// Get the tile ID at this location // Get the tile ID at this location
uint8_t tileID = index; size_t tileID = index;
if (tilemap.has_value()) { if (tilemap.has_value()) {
tileID = tileID =
(*tilemap)[index] - options.baseTileIDs[bank] + bank * options.maxNbTiles[0]; (*tilemap)[index] - options.baseTileIDs[bank] + bank * options.maxNbTiles[0];

View File

@@ -1,3 +1,11 @@
/*
* This file is part of RGBDS.
*
* Copyright (c) 2022, Eldred Habert and RGBDS contributors.
*
* SPDX-License-Identifier: MIT
*/
#include "gfx/rgba.hpp" #include "gfx/rgba.hpp"
#include <assert.h> #include <assert.h>

View File

@@ -15,10 +15,8 @@
#include "error.h" #include "error.h"
#include "hashmap.h" #include "hashmap.h"
/* // The lower half of the hash is used to index the "master" table,
* The lower half of the hash is used to index the "master" table, // the upper half is used to help resolve collisions more quickly
* the upper half is used to help resolve collisions more quickly
*/
#define UINT_BITS_(NB_BITS) uint##NB_BITS##_t #define UINT_BITS_(NB_BITS) uint##NB_BITS##_t
#define UINT_BITS(NB_BITS) UINT_BITS_(NB_BITS) #define UINT_BITS(NB_BITS) UINT_BITS_(NB_BITS)
typedef UINT_BITS(HASH_NB_BITS) HashType; typedef UINT_BITS(HASH_NB_BITS) HashType;
@@ -34,7 +32,7 @@ struct HashMapEntry {
#define FNV_OFFSET_BASIS 0x811c9dc5 #define FNV_OFFSET_BASIS 0x811c9dc5
#define FNV_PRIME 16777619 #define FNV_PRIME 16777619
/* FNV-1a hash */ // FNV-1a hash
static HashType hash(char const *str) static HashType hash(char const *str)
{ {
HashType hash = FNV_OFFSET_BASIS; HashType hash = FNV_OFFSET_BASIS;

View File

@@ -17,11 +17,11 @@
#include "link/symbol.h" #include "link/symbol.h"
#include "link/object.h" #include "link/object.h"
#include "link/main.h" #include "link/main.h"
#include "link/script.h"
#include "link/output.h" #include "link/output.h"
#include "error.h" #include "error.h"
#include "helpers.h" #include "helpers.h"
#include "linkdefs.h"
struct MemoryLocation { struct MemoryLocation {
uint16_t address; uint16_t address;
@@ -34,14 +34,12 @@ struct FreeSpace {
struct FreeSpace *next, *prev; struct FreeSpace *next, *prev;
}; };
/* Table of free space for each bank */ // Table of free space for each bank
struct FreeSpace *memory[SECTTYPE_INVALID]; struct FreeSpace *memory[SECTTYPE_INVALID];
uint64_t nbSectionsToAssign; uint64_t nbSectionsToAssign;
/** // Init the free space-modelling structs
* Init the free space-modelling structs
*/
static void initFreeSpace(void) static void initFreeSpace(void)
{ {
for (enum SectionType type = 0; type < SECTTYPE_INVALID; type++) { for (enum SectionType type = 0; type < SECTTYPE_INVALID; type++) {
@@ -55,54 +53,15 @@ static void initFreeSpace(void)
if (!memory[type][bank].next) if (!memory[type][bank].next)
err("Failed to init free space for region %d bank %" PRIu32, err("Failed to init free space for region %d bank %" PRIu32,
type, bank); type, bank);
memory[type][bank].next->address = startaddr[type]; memory[type][bank].next->address = sectionTypeInfo[type].startAddr;
memory[type][bank].next->size = maxsize[type]; memory[type][bank].next->size = sectionTypeInfo[type].size;
memory[type][bank].next->next = NULL; memory[type][bank].next->next = NULL;
memory[type][bank].next->prev = &memory[type][bank]; memory[type][bank].next->prev = &memory[type][bank];
} }
} }
} }
/** /*
* Alter sections' attributes based on the linker script
*/
static void processLinkerScript(void)
{
if (!linkerScriptName)
return;
verbosePrint("Reading linker script...\n");
linkerScript = openFile(linkerScriptName, "r");
/* Modify all sections according to the linker script */
struct SectionPlacement *placement;
while ((placement = script_NextSection())) {
struct Section *section = placement->section;
/* Check if this doesn't conflict with what the code says */
if (section->isBankFixed && placement->bank != section->bank)
error(NULL, 0, "Linker script contradicts \"%s\"'s bank placement",
section->name);
if (section->isAddressFixed && placement->org != section->org)
error(NULL, 0, "Linker script contradicts \"%s\"'s address placement",
section->name);
if (section->isAlignFixed
&& (placement->org & section->alignMask) != 0)
error(NULL, 0, "Linker script contradicts \"%s\"'s alignment",
section->name);
section->isAddressFixed = true;
section->org = placement->org;
section->isBankFixed = true;
section->bank = placement->bank;
section->isAlignFixed = false; /* The alignment is satisfied */
}
fclose(linkerScript);
}
/**
* Assigns a section to a given memory location * Assigns a section to a given memory location
* @param section The section to assign * @param section The section to assign
* @param location The location to assign the section to * @param location The location to assign the section to
@@ -124,7 +83,7 @@ static void assignSection(struct Section *section, struct MemoryLocation const *
out_AddSection(section); out_AddSection(section);
} }
/** /*
* Checks whether a given location is suitable for placing a given section * Checks whether a given location is suitable for placing a given section
* This checks not only that the location has enough room for the section, but * This checks not only that the location has enough room for the section, but
* also that the constraints (alignment...) are respected. * also that the constraints (alignment...) are respected.
@@ -150,7 +109,7 @@ static bool isLocationSuitable(struct Section const *section,
<= freeSpace->address + freeSpace->size; <= freeSpace->address + freeSpace->size;
} }
/** /*
* Finds a suitable location to place a section at. * Finds a suitable location to place a section at.
* @param section The section to be placed * @param section The section to be placed
* @param location A pointer to a location struct that will be filled * @param location A pointer to a location struct that will be filled
@@ -180,74 +139,70 @@ static struct FreeSpace *getPlacement(struct Section const *section,
if (curScrambleSRAM > scrambleSRAM) if (curScrambleSRAM > scrambleSRAM)
curScrambleSRAM = 0; curScrambleSRAM = 0;
} else { } else {
location->bank = bankranges[section->type][0]; location->bank = sectionTypeInfo[section->type].firstBank;
} }
struct FreeSpace *space; struct FreeSpace *space;
for (;;) { for (;;) {
/* Switch to the beginning of the next bank */ // Switch to the beginning of the next bank
#define BANK_INDEX (location->bank - bankranges[section->type][0]) #define BANK_INDEX (location->bank - sectionTypeInfo[section->type].firstBank)
space = memory[section->type][BANK_INDEX].next; space = memory[section->type][BANK_INDEX].next;
if (space) if (space)
location->address = space->address; location->address = space->address;
/* Process locations in that bank */ // Process locations in that bank
while (space) { while (space) {
/* If that location is OK, return it */ // If that location is OK, return it
if (isLocationSuitable(section, space, location)) if (isLocationSuitable(section, space, location))
return space; return space;
/* Go to the next *possible* location */ // Go to the next *possible* location
if (section->isAddressFixed) { if (section->isAddressFixed) {
/* // If the address is fixed, there can be only
* If the address is fixed, there can be only // one candidate block per bank; if we already
* one candidate block per bank; if we already // reached it, give up.
* reached it, give up.
*/
if (location->address < section->org) if (location->address < section->org)
location->address = section->org; location->address = section->org;
else else
/* Try again in next bank */ // Try again in next bank
space = NULL; space = NULL;
} else if (section->isAlignFixed) { } else if (section->isAlignFixed) {
/* Move to next aligned location */ // Move to next aligned location
/* Move back to alignment boundary */ // Move back to alignment boundary
location->address -= section->alignOfs; location->address -= section->alignOfs;
/* Ensure we're there (e.g. on first check) */ // Ensure we're there (e.g. on first check)
location->address &= ~section->alignMask; location->address &= ~section->alignMask;
/* Go to next align boundary and add offset */ // Go to next align boundary and add offset
location->address += section->alignMask + 1 location->address += section->alignMask + 1
+ section->alignOfs; + section->alignOfs;
} else { } else {
/* Any location is fine, so, next free block */ // Any location is fine, so, next free block
space = space->next; space = space->next;
if (space) if (space)
location->address = space->address; location->address = space->address;
} }
/* // If that location is past the current block's end,
* If that location is past the current block's end, // go forwards until that is no longer the case.
* go forwards until that is no longer the case.
*/
while (space && location->address >= while (space && location->address >=
space->address + space->size) space->address + space->size)
space = space->next; space = space->next;
/* Try again with the new location/free space combo */ // Try again with the new location/free space combo
} }
if (section->isBankFixed) if (section->isBankFixed)
return NULL; return NULL;
/* Try again in the next bank */ // Try again in the next bank
location->bank++; location->bank++;
if (location->bank > bankranges[section->type][1]) if (location->bank > sectionTypeInfo[section->type].lastBank)
return NULL; return NULL;
#undef BANK_INDEX #undef BANK_INDEX
} }
} }
/** /*
* Places a section in a suitable location, or error out if it fails to. * Places a section in a suitable location, or error out if it fails to.
* @warning Due to the implemented algorithm, this should be called with * @warning Due to the implemented algorithm, this should be called with
* sections of decreasing size. * sections of decreasing size.
@@ -257,76 +212,70 @@ static void placeSection(struct Section *section)
{ {
struct MemoryLocation location; struct MemoryLocation location;
/* Specially handle 0-byte SECTIONs, as they can't overlap anything */ // Specially handle 0-byte SECTIONs, as they can't overlap anything
if (section->size == 0) { if (section->size == 0) {
/* // Unless the SECTION's address was fixed, the starting address
* Unless the SECTION's address was fixed, the starting address // is fine for any alignment, as checked in sect_DoSanityChecks.
* is fine for any alignment, as checked in sect_DoSanityChecks.
*/
location.address = section->isAddressFixed location.address = section->isAddressFixed
? section->org ? section->org
: startaddr[section->type]; : sectionTypeInfo[section->type].startAddr;
location.bank = section->isBankFixed location.bank = section->isBankFixed
? section->bank ? section->bank
: bankranges[section->type][0]; : sectionTypeInfo[section->type].firstBank;
assignSection(section, &location); assignSection(section, &location);
return; return;
} }
/* // Place section using first-fit decreasing algorithm
* Place section using first-fit decreasing algorithm // https://en.wikipedia.org/wiki/Bin_packing_problem#First-fit_algorithm
* https://en.wikipedia.org/wiki/Bin_packing_problem#First-fit_algorithm
*/
struct FreeSpace *freeSpace = getPlacement(section, &location); struct FreeSpace *freeSpace = getPlacement(section, &location);
if (freeSpace) { if (freeSpace) {
assignSection(section, &location); assignSection(section, &location);
/* Split the free space */ // Split the free space
bool noLeftSpace = freeSpace->address == section->org; bool noLeftSpace = freeSpace->address == section->org;
bool noRightSpace = freeSpace->address + freeSpace->size bool noRightSpace = freeSpace->address + freeSpace->size
== section->org + section->size; == section->org + section->size;
if (noLeftSpace && noRightSpace) { if (noLeftSpace && noRightSpace) {
/* The free space is entirely deleted */ // The free space is entirely deleted
freeSpace->prev->next = freeSpace->next; freeSpace->prev->next = freeSpace->next;
if (freeSpace->next) if (freeSpace->next)
freeSpace->next->prev = freeSpace->prev; freeSpace->next->prev = freeSpace->prev;
/* // If the space is the last one on the list, set its
* If the space is the last one on the list, set its // size to 0 so it doesn't get picked, but don't free()
* size to 0 so it doesn't get picked, but don't free() // it as it will be freed when cleaning up
* it as it will be freed when cleaning up
*/
free(freeSpace); free(freeSpace);
} else if (!noLeftSpace && !noRightSpace) { } else if (!noLeftSpace && !noRightSpace) {
/* The free space is split in two */ // The free space is split in two
struct FreeSpace *newSpace = malloc(sizeof(*newSpace)); struct FreeSpace *newSpace = malloc(sizeof(*newSpace));
if (!newSpace) if (!newSpace)
err("Failed to split new free space"); err("Failed to split new free space");
/* Append the new space after the chosen one */ // Append the new space after the chosen one
newSpace->prev = freeSpace; newSpace->prev = freeSpace;
newSpace->next = freeSpace->next; newSpace->next = freeSpace->next;
if (freeSpace->next) if (freeSpace->next)
freeSpace->next->prev = newSpace; freeSpace->next->prev = newSpace;
freeSpace->next = newSpace; freeSpace->next = newSpace;
/* Set its parameters */ // Set its parameters
newSpace->address = section->org + section->size; newSpace->address = section->org + section->size;
newSpace->size = freeSpace->address + freeSpace->size - newSpace->size = freeSpace->address + freeSpace->size -
newSpace->address; newSpace->address;
/* Set the original space's new parameters */ // Set the original space's new parameters
freeSpace->size = section->org - freeSpace->address; freeSpace->size = section->org - freeSpace->address;
/* address is unmodified */ // address is unmodified
} else { } else {
/* The amount of free spaces doesn't change: resize! */ // The amount of free spaces doesn't change: resize!
freeSpace->size -= section->size; freeSpace->size -= section->size;
if (noLeftSpace) if (noLeftSpace)
/* The free space is moved *and* resized */ // The free space is moved *and* resized
freeSpace->address += section->size; freeSpace->address += section->size;
} }
return; return;
} }
/* Please adjust depending on longest message below */ // Please adjust depending on longest message below
char where[64]; char where[64];
if (section->isBankFixed && nbbanks(section->type) != 1) { if (section->isBankFixed && nbbanks(section->type) != 1) {
@@ -351,19 +300,19 @@ static void placeSection(struct Section *section)
strcpy(where, "anywhere"); strcpy(where, "anywhere");
} }
/* If a section failed to go to several places, nothing we can report */ // If a section failed to go to several places, nothing we can report
if (!section->isBankFixed || !section->isAddressFixed) if (!section->isBankFixed || !section->isAddressFixed)
errx("Unable to place \"%s\" (%s section) %s", errx("Unable to place \"%s\" (%s section) %s",
section->name, typeNames[section->type], where); section->name, sectionTypeInfo[section->type].name, where);
/* If the section just can't fit the bank, report that */ // If the section just can't fit the bank, report that
else if (section->org + section->size > endaddr(section->type) + 1) else if (section->org + section->size > endaddr(section->type) + 1)
errx("Unable to place \"%s\" (%s section) %s: section runs past end of region ($%04x > $%04x)", errx("Unable to place \"%s\" (%s section) %s: section runs past end of region ($%04x > $%04x)",
section->name, typeNames[section->type], where, section->name, sectionTypeInfo[section->type].name, where,
section->org + section->size, endaddr(section->type) + 1); section->org + section->size, endaddr(section->type) + 1);
/* Otherwise there is overlap with another section */ // Otherwise there is overlap with another section
else else
errx("Unable to place \"%s\" (%s section) %s: section overlaps with \"%s\"", errx("Unable to place \"%s\" (%s section) %s: section overlaps with \"%s\"",
section->name, typeNames[section->type], where, section->name, sectionTypeInfo[section->type].name, where,
out_OverlappingSection(section)->name); out_OverlappingSection(section)->name);
} }
@@ -378,7 +327,7 @@ struct UnassignedSection {
static struct UnassignedSection *unassignedSections[1 << 3] = {0}; static struct UnassignedSection *unassignedSections[1 << 3] = {0};
static struct UnassignedSection *sections; static struct UnassignedSection *sections;
/** /*
* Categorize a section depending on how constrained it is * Categorize a section depending on how constrained it is
* This is so the most-constrained sections are placed first * This is so the most-constrained sections are placed first
* @param section The section to categorize * @param section The section to categorize
@@ -393,13 +342,13 @@ static void categorizeSection(struct Section *section, void *arg)
constraints |= BANK_CONSTRAINED; constraints |= BANK_CONSTRAINED;
if (section->isAddressFixed) if (section->isAddressFixed)
constraints |= ORG_CONSTRAINED; constraints |= ORG_CONSTRAINED;
/* Can't have both! */ // Can't have both!
else if (section->isAlignFixed) else if (section->isAlignFixed)
constraints |= ALIGN_CONSTRAINED; constraints |= ALIGN_CONSTRAINED;
struct UnassignedSection **ptr = &unassignedSections[constraints]; struct UnassignedSection **ptr = &unassignedSections[constraints];
/* Insert section while keeping the list sorted by decreasing size */ // Insert section while keeping the list sorted by decreasing size
while (*ptr && (*ptr)->section->size > section->size) while (*ptr && (*ptr)->section->size > section->size)
ptr = &(*ptr)->next; ptr = &(*ptr)->next;
@@ -414,24 +363,21 @@ void assign_AssignSections(void)
{ {
verbosePrint("Beginning assignment...\n"); verbosePrint("Beginning assignment...\n");
/** Initialize assignment **/ // Initialize assignment
/* Generate linked lists of sections to assign */ // Generate linked lists of sections to assign
sections = malloc(sizeof(*sections) * nbSectionsToAssign + 1); sections = malloc(sizeof(*sections) * nbSectionsToAssign + 1);
if (!sections) if (!sections)
err("Failed to allocate memory for section assignment"); err("Failed to allocate memory for section assignment");
initFreeSpace(); initFreeSpace();
/* Process linker script, if any */
processLinkerScript();
nbSectionsToAssign = 0; nbSectionsToAssign = 0;
sect_ForEach(categorizeSection, NULL); sect_ForEach(categorizeSection, NULL);
/** Place sections, starting with the most constrained **/ // Place sections, starting with the most constrained
/* Specially process fully-constrained sections because of overlaying */ // Specially process fully-constrained sections because of overlaying
struct UnassignedSection *sectionPtr = struct UnassignedSection *sectionPtr =
unassignedSections[BANK_CONSTRAINED | ORG_CONSTRAINED]; unassignedSections[BANK_CONSTRAINED | ORG_CONSTRAINED];
@@ -441,11 +387,11 @@ void assign_AssignSections(void)
sectionPtr = sectionPtr->next; sectionPtr = sectionPtr->next;
} }
/* If all sections were fully constrained, we have nothing left to do */ // If all sections were fully constrained, we have nothing left to do
if (!nbSectionsToAssign) if (!nbSectionsToAssign)
return; return;
/* Overlaying requires only fully-constrained sections */ // Overlaying requires only fully-constrained sections
verbosePrint("Assigning other sections...\n"); verbosePrint("Assigning other sections...\n");
if (overlayFileName) { if (overlayFileName) {
fprintf(stderr, "FATAL: All sections must be fixed when using an overlay file"); fprintf(stderr, "FATAL: All sections must be fixed when using an overlay file");
@@ -469,7 +415,7 @@ max_out:
exit(1); exit(1);
} }
/* Assign all remaining sections by decreasing constraint order */ // Assign all remaining sections by decreasing constraint order
for (int8_t constraints = BANK_CONSTRAINED | ALIGN_CONSTRAINED; for (int8_t constraints = BANK_CONSTRAINED | ALIGN_CONSTRAINED;
constraints >= 0; constraints--) { constraints >= 0; constraints--) {
sectionPtr = unassignedSections[constraints]; sectionPtr = unassignedSections[constraints];
@@ -505,6 +451,4 @@ void assign_Cleanup(void)
} }
free(sections); free(sections);
script_Cleanup();
} }

View File

@@ -18,46 +18,48 @@
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/types.h> #include <sys/types.h>
#include "link/object.h"
#include "link/symbol.h"
#include "link/section.h"
#include "link/assign.h" #include "link/assign.h"
#include "link/patch.h" #include "link/object.h"
#include "link/output.h" #include "link/output.h"
#include "link/patch.h"
#include "link/section.h"
#include "link/script.h"
#include "link/symbol.h"
#include "extern/getopt.h" #include "extern/getopt.h"
#include "error.h" #include "error.h"
#include "linkdefs.h"
#include "platform.h" #include "platform.h"
#include "version.h" #include "version.h"
bool isDmgMode; /* -d */ bool isDmgMode; // -d
char *linkerScriptName; /* -l */ char *linkerScriptName; // -l
char const *mapFileName; /* -m */ char const *mapFileName; // -m
char const *symFileName; /* -n */ bool noSymInMap; // -M
char const *overlayFileName; /* -O */ char const *symFileName; // -n
char const *outputFileName; /* -o */ char const *overlayFileName; // -O
uint8_t padValue; /* -p */ char const *outputFileName; // -o
uint8_t padValue; // -p
// Setting these three to 0 disables the functionality // Setting these three to 0 disables the functionality
uint16_t scrambleROMX = 0; /* -S */ uint16_t scrambleROMX = 0; // -S
uint8_t scrambleWRAMX = 0; uint8_t scrambleWRAMX = 0;
uint8_t scrambleSRAM = 0; uint8_t scrambleSRAM = 0;
bool is32kMode; /* -t */ bool is32kMode; // -t
bool beVerbose; /* -v */ bool beVerbose; // -v
bool isWRA0Mode; /* -w */ bool isWRA0Mode; // -w
bool disablePadding; /* -x */ bool disablePadding; // -x
static uint32_t nbErrors = 0; static uint32_t nbErrors = 0;
/***** Helper function to dump a file stack to stderr *****/ // Helper function to dump a file stack to stderr
char const *dumpFileStack(struct FileStackNode const *node) char const *dumpFileStack(struct FileStackNode const *node)
{ {
char const *lastName; char const *lastName;
if (node->parent) { if (node->parent) {
lastName = dumpFileStack(node->parent); lastName = dumpFileStack(node->parent);
/* REPT nodes use their parent's name */ // REPT nodes use their parent's name
if (node->type != NODE_REPT) if (node->type != NODE_REPT)
lastName = node->name; lastName = node->name;
fprintf(stderr, "(%" PRIu32 ") -> %s", node->lineNo, lastName); fprintf(stderr, "(%" PRIu32 ") -> %s", node->lineNo, lastName);
@@ -152,9 +154,9 @@ FILE *openFile(char const *fileName, char const *mode)
if (strcmp(fileName, "-") != 0) if (strcmp(fileName, "-") != 0)
file = fopen(fileName, mode); file = fopen(fileName, mode);
else if (mode[0] == 'r') else if (mode[0] == 'r')
file = fdopen(0, mode); file = fdopen(STDIN_FILENO, mode);
else else
file = fdopen(1, mode); file = fdopen(STDOUT_FILENO, mode);
if (!file) if (!file)
err("Could not open file \"%s\"", fileName); err("Could not open file \"%s\"", fileName);
@@ -162,8 +164,8 @@ FILE *openFile(char const *fileName, char const *mode)
return file; return file;
} }
/* Short options */ // Short options
static const char *optstring = "dl:m:n:O:o:p:S:s:tVvWwx"; static const char *optstring = "dl:m:Mn:O:o:p:S:s:tVvWwx";
/* /*
* Equivalent long options * Equivalent long options
@@ -179,6 +181,7 @@ static struct option const longopts[] = {
{ "dmg", no_argument, NULL, 'd' }, { "dmg", no_argument, NULL, 'd' },
{ "linkerscript", required_argument, NULL, 'l' }, { "linkerscript", required_argument, NULL, 'l' },
{ "map", required_argument, NULL, 'm' }, { "map", required_argument, NULL, 'm' },
{ "no-sym-in-map", no_argument, NULL, 'M' },
{ "sym", required_argument, NULL, 'n' }, { "sym", required_argument, NULL, 'n' },
{ "overlay", required_argument, NULL, 'O' }, { "overlay", required_argument, NULL, 'O' },
{ "output", required_argument, NULL, 'o' }, { "output", required_argument, NULL, 'o' },
@@ -193,13 +196,11 @@ static struct option const longopts[] = {
{ NULL, no_argument, NULL, 0 } { NULL, no_argument, NULL, 0 }
}; };
/** // Prints the program's usage to stdout.
* Prints the program's usage to stdout.
*/
static void printUsage(void) static void printUsage(void)
{ {
fputs( fputs(
"Usage: rgblink [-dtVvwx] [-l script] [-m map_file] [-n sym_file]\n" "Usage: rgblink [-dMtVvwx] [-l script] [-m map_file] [-n sym_file]\n"
" [-O overlay_file] [-o out_file] [-p pad_value]\n" " [-O overlay_file] [-o out_file] [-p pad_value]\n"
" [-S spec] [-s symbol] <file> ...\n" " [-S spec] [-s symbol] <file> ...\n"
"Useful options:\n" "Useful options:\n"
@@ -215,10 +216,8 @@ static void printUsage(void)
stderr); stderr);
} }
/** // Cleans up what has been done
* Cleans up what has been done // Mostly here to please tools such as `valgrind` so actual errors can be seen
* Mostly here to please tools such as `valgrind` so actual errors can be seen
*/
static void cleanup(void) static void cleanup(void)
{ {
obj_Cleanup(); obj_Cleanup();
@@ -349,13 +348,19 @@ next:
} }
} }
_Noreturn void reportErrors(void) {
fprintf(stderr, "Linking failed with %" PRIu32 " error%s\n",
nbErrors, nbErrors == 1 ? "" : "s");
exit(1);
}
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
int optionChar; int optionChar;
char *endptr; /* For error checking with `strtoul` */ char *endptr; // For error checking with `strtoul`
unsigned long value; /* For storing `strtoul`'s return value */ unsigned long value; // For storing `strtoul`'s return value
/* Parse options */ // Parse options
while ((optionChar = musl_getopt_long_only(argc, argv, optstring, while ((optionChar = musl_getopt_long_only(argc, argv, optstring,
longopts, NULL)) != -1) { longopts, NULL)) != -1) {
switch (optionChar) { switch (optionChar) {
@@ -364,18 +369,31 @@ int main(int argc, char *argv[])
isWRA0Mode = true; isWRA0Mode = true;
break; break;
case 'l': case 'l':
if (linkerScriptName)
warnx("Overriding linkerscript %s", musl_optarg);
linkerScriptName = musl_optarg; linkerScriptName = musl_optarg;
break; break;
case 'M':
noSymInMap = true;
break;
case 'm': case 'm':
if (mapFileName)
warnx("Overriding mapfile %s", musl_optarg);
mapFileName = musl_optarg; mapFileName = musl_optarg;
break; break;
case 'n': case 'n':
if (symFileName)
warnx("Overriding symfile %s", musl_optarg);
symFileName = musl_optarg; symFileName = musl_optarg;
break; break;
case 'O': case 'O':
if (overlayFileName)
warnx("Overriding overlay file %s", musl_optarg);
overlayFileName = musl_optarg; overlayFileName = musl_optarg;
break; break;
case 'o': case 'o':
if (outputFileName)
warnx("Overriding output file %s", musl_optarg);
outputFileName = musl_optarg; outputFileName = musl_optarg;
break; break;
case 'p': case 'p':
@@ -394,7 +412,7 @@ int main(int argc, char *argv[])
parseScrambleSpec(musl_optarg); parseScrambleSpec(musl_optarg);
break; break;
case 's': case 's':
/* FIXME: nobody knows what this does, figure it out */ // FIXME: nobody knows what this does, figure it out
(void)musl_optarg; (void)musl_optarg;
warning(NULL, 0, "Nobody has any idea what `-s` does"); warning(NULL, 0, "Nobody has any idea what `-s` does");
break; break;
@@ -412,7 +430,7 @@ int main(int argc, char *argv[])
break; break;
case 'x': case 'x':
disablePadding = true; disablePadding = true;
/* implies tiny mode */ // implies tiny mode
is32kMode = true; is32kMode = true;
break; break;
default: default:
@@ -423,42 +441,90 @@ int main(int argc, char *argv[])
int curArgIndex = musl_optind; int curArgIndex = musl_optind;
/* If no input files were specified, the user must have screwed up */ // If no input files were specified, the user must have screwed up
if (curArgIndex == argc) { if (curArgIndex == argc) {
fputs("FATAL: no input files\n", stderr); fputs("FATAL: no input files\n", stderr);
printUsage(); printUsage();
exit(1); exit(1);
} }
/* Patch the size array depending on command-line options */ // Patch the size array depending on command-line options
if (!is32kMode) if (!is32kMode)
maxsize[SECTTYPE_ROM0] = 0x4000; sectionTypeInfo[SECTTYPE_ROM0].size = 0x4000;
if (!isWRA0Mode) if (!isWRA0Mode)
maxsize[SECTTYPE_WRAM0] = 0x1000; sectionTypeInfo[SECTTYPE_WRAM0].size = 0x1000;
/* Patch the bank ranges array depending on command-line options */ // Patch the bank ranges array depending on command-line options
if (isDmgMode) if (isDmgMode)
bankranges[SECTTYPE_VRAM][1] = BANK_MIN_VRAM; sectionTypeInfo[SECTTYPE_VRAM].lastBank = 0;
/* Read all object files first, */ // Read all object files first,
for (obj_Setup(argc - curArgIndex); curArgIndex < argc; curArgIndex++) for (obj_Setup(argc - curArgIndex); curArgIndex < argc; curArgIndex++)
obj_ReadFile(argv[curArgIndex], argc - curArgIndex - 1); obj_ReadFile(argv[curArgIndex], argc - curArgIndex - 1);
/* then process them, */ // apply the linker script's modifications,
if (linkerScriptName) {
verbosePrint("Reading linker script...\n");
linkerScript = openFile(linkerScriptName, "r");
// Modify all sections according to the linker script
struct SectionPlacement *placement;
while ((placement = script_NextSection())) {
struct Section *section = placement->section;
assert(section->offset == 0);
// Check if this doesn't conflict with what the code says
if (section->type == SECTTYPE_INVALID) {
for (struct Section *sect = section; sect; sect = sect->nextu)
sect->type = placement->type; // SDCC "unknown" sections
} else if (section->type != placement->type) {
error(NULL, 0, "Linker script contradicts \"%s\"'s type",
section->name);
}
if (section->isBankFixed && placement->bank != section->bank)
error(NULL, 0, "Linker script contradicts \"%s\"'s bank placement",
section->name);
if (section->isAddressFixed && placement->org != section->org)
error(NULL, 0, "Linker script contradicts \"%s\"'s address placement",
section->name);
if (section->isAlignFixed
&& (placement->org & section->alignMask) != 0)
error(NULL, 0, "Linker script contradicts \"%s\"'s alignment",
section->name);
section->isAddressFixed = true;
section->org = placement->org;
section->isBankFixed = true;
section->bank = placement->bank;
section->isAlignFixed = false; // The alignment is satisfied
}
fclose(linkerScript);
script_Cleanup();
// If the linker script produced any errors, some sections may be in an invalid state
if (nbErrors != 0)
reportErrors();
}
// then process them,
obj_DoSanityChecks(); obj_DoSanityChecks();
if (nbErrors != 0)
reportErrors();
assign_AssignSections(); assign_AssignSections();
obj_CheckAssertions(); obj_CheckAssertions();
assign_Cleanup(); assign_Cleanup();
/* and finally output the result. */ // and finally output the result.
patch_ApplyPatches(); patch_ApplyPatches();
if (nbErrors) { if (nbErrors != 0)
fprintf(stderr, "Linking failed with %" PRIu32 " error%s\n", reportErrors();
nbErrors, nbErrors == 1 ? "" : "s");
exit(1);
}
out_WriteFiles(); out_WriteFiles();
/* Do cleanup before quitting, though. */ // Do cleanup before quitting, though.
cleanup(); cleanup();
} }

View File

@@ -18,6 +18,7 @@
#include "link/main.h" #include "link/main.h"
#include "link/object.h" #include "link/object.h"
#include "link/patch.h" #include "link/patch.h"
#include "link/sdas_obj.h"
#include "link/section.h" #include "link/section.h"
#include "link/symbol.h" #include "link/symbol.h"
@@ -38,12 +39,10 @@ static struct {
} *nodes; } *nodes;
static struct Assertion *assertions; static struct Assertion *assertions;
/***** Helper functions for reading object files *****/ // Helper functions for reading object files
/* // Internal, DO NOT USE.
* Internal, DO NOT USE. // For helper wrapper macros defined below, such as `tryReadlong`
* For helper wrapper macros defined below, such as `tryReadlong`
*/
#define tryRead(func, type, errval, var, file, ...) \ #define tryRead(func, type, errval, var, file, ...) \
do { \ do { \
FILE *tmpFile = file; \ FILE *tmpFile = file; \
@@ -57,7 +56,7 @@ static struct Assertion *assertions;
var = tmpVal; \ var = tmpVal; \
} while (0) } while (0)
/** /*
* Reads an unsigned long (32-bit) value from a file. * Reads an unsigned long (32-bit) value from a file.
* @param file The file to read from. This will read 4 bytes from the file. * @param file The file to read from. This will read 4 bytes from the file.
* @return The value read, cast to a int64_t, or -1 on failure. * @return The value read, cast to a int64_t, or -1 on failure.
@@ -66,25 +65,24 @@ static int64_t readlong(FILE *file)
{ {
uint32_t value = 0; uint32_t value = 0;
/* Read the little-endian value byte by byte */ // Read the little-endian value byte by byte
for (uint8_t shift = 0; shift < sizeof(value) * CHAR_BIT; shift += 8) { for (uint8_t shift = 0; shift < sizeof(value) * CHAR_BIT; shift += 8) {
int byte = getc(file); int byte = getc(file);
if (byte == EOF) if (byte == EOF)
return INT64_MAX; return INT64_MAX;
/* This must be casted to `unsigned`, not `uint8_t`. Rationale: // This must be casted to `unsigned`, not `uint8_t`. Rationale:
* the type of the shift is the type of `byte` after undergoing // the type of the shift is the type of `byte` after undergoing
* integer promotion, which would be `int` if this was casted to // integer promotion, which would be `int` if this was casted to
* `uint8_t`, because int is large enough to hold a byte. This // `uint8_t`, because int is large enough to hold a byte. This
* however causes values larger than 127 to be too large when // however causes values larger than 127 to be too large when
* shifted, potentially triggering undefined behavior. // shifted, potentially triggering undefined behavior.
*/
value |= (unsigned int)byte << shift; value |= (unsigned int)byte << shift;
} }
return value; return value;
} }
/** /*
* Helper macro for reading longs from a file, and errors out if it fails to. * Helper macro for reading longs from a file, and errors out if it fails to.
* Not as a function to avoid overhead in the general case. * Not as a function to avoid overhead in the general case.
* @param var The variable to stash the number into * @param var The variable to stash the number into
@@ -95,9 +93,9 @@ static int64_t readlong(FILE *file)
#define tryReadlong(var, file, ...) \ #define tryReadlong(var, file, ...) \
tryRead(readlong, int64_t, INT64_MAX, var, file, __VA_ARGS__) tryRead(readlong, int64_t, INT64_MAX, var, file, __VA_ARGS__)
/* There is no `readbyte`, just use `fgetc` or `getc`. */ // There is no `readbyte`, just use `fgetc` or `getc`.
/** /*
* Helper macro for reading bytes from a file, and errors out if it fails to. * Helper macro for reading bytes from a file, and errors out if it fails to.
* Differs from `tryGetc` in that the backing function is fgetc(1). * Differs from `tryGetc` in that the backing function is fgetc(1).
* Not as a function to avoid overhead in the general case. * Not as a function to avoid overhead in the general case.
@@ -109,7 +107,7 @@ static int64_t readlong(FILE *file)
#define tryFgetc(var, file, ...) \ #define tryFgetc(var, file, ...) \
tryRead(fgetc, int, EOF, var, file, __VA_ARGS__) tryRead(fgetc, int, EOF, var, file, __VA_ARGS__)
/** /*
* Helper macro for reading bytes from a file, and errors out if it fails to. * Helper macro for reading bytes from a file, and errors out if it fails to.
* Differs from `tryGetc` in that the backing function is fgetc(1). * Differs from `tryGetc` in that the backing function is fgetc(1).
* Not as a function to avoid overhead in the general case. * Not as a function to avoid overhead in the general case.
@@ -121,7 +119,7 @@ static int64_t readlong(FILE *file)
#define tryGetc(var, file, ...) \ #define tryGetc(var, file, ...) \
tryRead(getc, int, EOF, var, file, __VA_ARGS__) tryRead(getc, int, EOF, var, file, __VA_ARGS__)
/** /*
* Reads a '\0'-terminated string from a file. * Reads a '\0'-terminated string from a file.
* @param file The file to read from. The file position will be advanced. * @param file The file to read from. The file position will be advanced.
* @return The string read, or NULL on failure. * @return The string read, or NULL on failure.
@@ -129,26 +127,26 @@ static int64_t readlong(FILE *file)
*/ */
static char *readstr(FILE *file) static char *readstr(FILE *file)
{ {
/* Default buffer size, have it close to the average string length */ // Default buffer size, have it close to the average string length
size_t capacity = 32 / 2; size_t capacity = 32 / 2;
size_t index = -1; size_t index = -1;
/* Force the first iteration to allocate */ // Force the first iteration to allocate
char *str = NULL; char *str = NULL;
do { do {
/* Prepare going to next char */ // Prepare going to next char
index++; index++;
/* If the buffer isn't suitable to write the next char... */ // If the buffer isn't suitable to write the next char...
if (index >= capacity || !str) { if (index >= capacity || !str) {
capacity *= 2; capacity *= 2;
str = realloc(str, capacity); str = realloc(str, capacity);
/* End now in case of error */ // End now in case of error
if (!str) if (!str)
return NULL; return NULL;
} }
/* Read char */ // Read char
int byte = getc(file); int byte = getc(file);
if (byte == EOF) { if (byte == EOF) {
@@ -160,7 +158,7 @@ static char *readstr(FILE *file)
return str; return str;
} }
/** /*
* Helper macro for reading bytes from a file, and errors out if it fails to. * Helper macro for reading bytes from a file, and errors out if it fails to.
* Not as a function to avoid overhead in the general case. * Not as a function to avoid overhead in the general case.
* @param var The variable to stash the string into * @param var The variable to stash the string into
@@ -171,9 +169,9 @@ static char *readstr(FILE *file)
#define tryReadstr(var, file, ...) \ #define tryReadstr(var, file, ...) \
tryRead(readstr, char*, NULL, var, file, __VA_ARGS__) tryRead(readstr, char*, NULL, var, file, __VA_ARGS__)
/***** Functions to parse object files *****/ // Functions to parse object files
/** /*
* Reads a file stack node form a file. * Reads a file stack node form a file.
* @param file The file to read from * @param file The file to read from
* @param nodes The file's array of nodes * @param nodes The file's array of nodes
@@ -216,7 +214,7 @@ static void readFileStackNode(FILE *file, struct FileStackNode fileNodes[], uint
} }
} }
/** /*
* Reads a symbol from a file. * Reads a symbol from a file.
* @param file The file to read from * @param file The file to read from
* @param symbol The struct to fill * @param symbol The struct to fill
@@ -229,7 +227,7 @@ static void readSymbol(FILE *file, struct Symbol *symbol,
fileName); fileName);
tryGetc(symbol->type, file, "%s: Cannot read \"%s\"'s type: %s", tryGetc(symbol->type, file, "%s: Cannot read \"%s\"'s type: %s",
fileName, symbol->name); fileName, symbol->name);
/* If the symbol is defined in this file, read its definition */ // If the symbol is defined in this file, read its definition
if (symbol->type != SYMTYPE_IMPORT) { if (symbol->type != SYMTYPE_IMPORT) {
symbol->objFileName = fileName; symbol->objFileName = fileName;
uint32_t nodeID; uint32_t nodeID;
@@ -252,7 +250,7 @@ static void readSymbol(FILE *file, struct Symbol *symbol,
} }
} }
/** /*
* Reads a patch from a file. * Reads a patch from a file.
* @param file The file to read from * @param file The file to read from
* @param patch The struct to fill * @param patch The struct to fill
@@ -302,7 +300,7 @@ static void readPatch(FILE *file, struct Patch *patch, char const *fileName, cha
feof(file) ? "Unexpected end of file" : strerror(errno)); feof(file) ? "Unexpected end of file" : strerror(errno));
} }
/** /*
* Sets a patch's pcSection from its pcSectionID. * Sets a patch's pcSection from its pcSectionID.
* @param patch The struct to fix * @param patch The struct to fix
*/ */
@@ -312,7 +310,7 @@ static void linkPatchToPCSect(struct Patch *patch, struct Section *fileSections[
: fileSections[patch->pcSectionID]; : fileSections[patch->pcSectionID];
} }
/** /*
* Reads a section from a file. * Reads a section from a file.
* @param file The file to read from * @param file The file to read from
* @param section The struct to fill * @param section The struct to fill
@@ -371,7 +369,7 @@ static void readSection(FILE *file, struct Section *section, char const *fileNam
section->alignOfs = tmp; section->alignOfs = tmp;
if (sect_HasData(section->type)) { if (sect_HasData(section->type)) {
/* Ensure we never allocate 0 bytes */ // Ensure we never allocate 0 bytes
uint8_t *data = malloc(sizeof(*data) * section->size + 1); uint8_t *data = malloc(sizeof(*data) * section->size + 1);
if (!data) if (!data)
@@ -400,15 +398,17 @@ static void readSection(FILE *file, struct Section *section, char const *fileNam
for (uint32_t i = 0; i < section->nbPatches; i++) for (uint32_t i = 0; i < section->nbPatches; i++)
readPatch(file, &patches[i], fileName, section->name, i, fileNodes); readPatch(file, &patches[i], fileName, section->name, i, fileNodes);
section->patches = patches; section->patches = patches;
} else {
section->data = NULL; // `mergeSections()` expects to be able to always read the ptr
} }
} }
/** /*
* Links a symbol to a section, keeping the section's symbol list sorted. * Links a symbol to a section, keeping the section's symbol list sorted.
* @param symbol The symbol to link * @param symbol The symbol to link
* @param section The section to link * @param section The section to link
*/ */
static void linkSymToSect(struct Symbol const *symbol, struct Section *section) static void linkSymToSect(struct Symbol *symbol, struct Section *section)
{ {
uint32_t a = 0, b = section->nbSymbols; uint32_t a = 0, b = section->nbSymbols;
@@ -421,7 +421,7 @@ static void linkSymToSect(struct Symbol const *symbol, struct Section *section)
a = c + 1; a = c + 1;
} }
struct Symbol const *tmp = symbol; struct Symbol *tmp = symbol;
for (uint32_t i = a; i <= section->nbSymbols; i++) { for (uint32_t i = a; i <= section->nbSymbols; i++) {
symbol = tmp; symbol = tmp;
@@ -432,7 +432,7 @@ static void linkSymToSect(struct Symbol const *symbol, struct Section *section)
section->nbSymbols++; section->nbSymbols++;
} }
/** /*
* Reads an assertion from a file * Reads an assertion from a file
* @param file The file to read from * @param file The file to read from
* @param assert The struct to fill * @param assert The struct to fill
@@ -461,25 +461,58 @@ static struct Section *getMainSection(struct Section *section)
void obj_ReadFile(char const *fileName, unsigned int fileID) void obj_ReadFile(char const *fileName, unsigned int fileID)
{ {
FILE *file = strcmp("-", fileName) ? fopen(fileName, "rb") : stdin; FILE *file;
if (strcmp("-", fileName) != 0)
file = fopen(fileName, "rb");
else
file = fdopen(STDIN_FILENO, "rb"); // `stdin` is in text mode by default
if (!file) if (!file)
err("Could not open file %s", fileName); err("Could not open file %s", fileName);
/* Begin by reading the magic bytes and version number */ // First, check if the object is a RGBDS object or a SDCC one. If the first byte is 'R',
unsigned versionNumber; // we'll assume it's a RGBDS object file, and otherwise, that it's a SDCC object file.
int matchedElems = fscanf(file, RGBDS_OBJECT_VERSION_STRING, int c = getc(file);
&versionNumber);
if (matchedElems != 1) ungetc(c, file); // Guaranteed to work
switch (c) {
case EOF:
fatal(NULL, 0, "File \"%s\" is empty!", fileName);
case 'R':
break;
default: // This is (probably) a SDCC object file, defer the rest of detection to it
// Since SDCC does not provide line info, everything will be reported as coming from the
// object file. It's better than nothing.
nodes[fileID].nbNodes = 1;
nodes[fileID].nodes = malloc(sizeof(nodes[fileID].nodes[0]) * nodes[fileID].nbNodes);
if (!nodes[fileID].nodes)
err("Failed to get memory for %s's nodes", fileName);
struct FileStackNode *where = &nodes[fileID].nodes[0];
if (!where)
fatal(NULL, 0, "Failed to alloc fstack node for \"%s\": %s", fileName, strerror(errno));
where->parent = NULL;
where->type = NODE_FILE;
where->name = strdup(fileName);
if (!where->name)
fatal(NULL, 0, "Failed to duplicate \"%s\"'s name: %s", fileName, strerror(errno));
sdobj_ReadFile(where, file);
return;
}
// Begin by reading the magic bytes
int matchedElems;
if (fscanf(file, RGBDS_OBJECT_VERSION_STRING "%n", &matchedElems) == 1
&& matchedElems != strlen(RGBDS_OBJECT_VERSION_STRING))
errx("\"%s\" is not a RGBDS object file", fileName); errx("\"%s\" is not a RGBDS object file", fileName);
verbosePrint("Reading object file %s, version %u\n", verbosePrint("Reading object file %s\n",
fileName, versionNumber); fileName);
if (versionNumber != RGBDS_OBJECT_VERSION_NUMBER)
errx("\"%s\" is an incompatible version %u object file",
fileName, versionNumber);
uint32_t revNum; uint32_t revNum;
@@ -507,9 +540,8 @@ void obj_ReadFile(char const *fileName, unsigned int fileID)
for (uint32_t i = nodes[fileID].nbNodes; i--; ) for (uint32_t i = nodes[fileID].nbNodes; i--; )
readFileStackNode(file, nodes[fileID].nodes, i, fileName); readFileStackNode(file, nodes[fileID].nodes, i, fileName);
/* This file's symbols, kept to link sections to them */ // This file's symbols, kept to link sections to them
struct Symbol **fileSymbols = struct Symbol **fileSymbols = malloc(sizeof(*fileSymbols) * nbSymbols + 1);
malloc(sizeof(*fileSymbols) * nbSymbols + 1);
if (!fileSymbols) if (!fileSymbols)
err("Failed to get memory for %s's symbols", fileName); err("Failed to get memory for %s's symbols", fileName);
@@ -528,7 +560,7 @@ void obj_ReadFile(char const *fileName, unsigned int fileID)
verbosePrint("Reading %" PRIu32 " symbols...\n", nbSymbols); verbosePrint("Reading %" PRIu32 " symbols...\n", nbSymbols);
for (uint32_t i = 0; i < nbSymbols; i++) { for (uint32_t i = 0; i < nbSymbols; i++) {
/* Read symbol */ // Read symbol
struct Symbol *symbol = malloc(sizeof(*symbol)); struct Symbol *symbol = malloc(sizeof(*symbol));
if (!symbol) if (!symbol)
@@ -542,13 +574,13 @@ void obj_ReadFile(char const *fileName, unsigned int fileID)
nbSymPerSect[symbol->sectionID]++; nbSymPerSect[symbol->sectionID]++;
} }
/* This file's sections, stored in a table to link symbols to them */ // This file's sections, stored in a table to link symbols to them
struct Section **fileSections = malloc(sizeof(*fileSections) struct Section **fileSections = malloc(sizeof(*fileSections)
* (nbSections ? nbSections : 1)); * (nbSections ? nbSections : 1));
verbosePrint("Reading %" PRIu32 " sections...\n", nbSections); verbosePrint("Reading %" PRIu32 " sections...\n", nbSections);
for (uint32_t i = 0; i < nbSections; i++) { for (uint32_t i = 0; i < nbSections; i++) {
/* Read section */ // Read section
fileSections[i] = malloc(sizeof(*fileSections[i])); fileSections[i] = malloc(sizeof(*fileSections[i]));
if (!fileSections[i]) if (!fileSections[i])
err("%s: Couldn't create new section", fileName); err("%s: Couldn't create new section", fileName);
@@ -572,7 +604,7 @@ void obj_ReadFile(char const *fileName, unsigned int fileID)
free(nbSymPerSect); free(nbSymPerSect);
/* Give patches' PC section pointers to their sections */ // Give patches' PC section pointers to their sections
for (uint32_t i = 0; i < nbSections; i++) { for (uint32_t i = 0; i < nbSections; i++) {
if (sect_HasData(fileSections[i]->type)) { if (sect_HasData(fileSections[i]->type)) {
for (uint32_t j = 0; j < fileSections[i]->nbPatches; j++) for (uint32_t j = 0; j < fileSections[i]->nbPatches; j++)
@@ -580,7 +612,7 @@ void obj_ReadFile(char const *fileName, unsigned int fileID)
} }
} }
/* Give symbols' section pointers to their sections */ // Give symbols' section pointers to their sections
for (uint32_t i = 0; i < nbSymbols; i++) { for (uint32_t i = 0; i < nbSymbols; i++) {
int32_t sectionID = fileSymbols[i]->sectionID; int32_t sectionID = fileSymbols[i]->sectionID;
@@ -589,12 +621,12 @@ void obj_ReadFile(char const *fileName, unsigned int fileID)
} else { } else {
struct Section *section = fileSections[sectionID]; struct Section *section = fileSections[sectionID];
/* Give the section a pointer to the symbol as well */ // Give the section a pointer to the symbol as well
linkSymToSect(fileSymbols[i], section); linkSymToSect(fileSymbols[i], section);
if (section->modifier != SECTION_NORMAL) { if (section->modifier != SECTION_NORMAL) {
if (section->modifier == SECTION_FRAGMENT) if (section->modifier == SECTION_FRAGMENT)
/* Add the fragment's offset to the symbol's */ // Add the fragment's offset to the symbol's
fileSymbols[i]->offset += section->offset; fileSymbols[i]->offset += section->offset;
section = getMainSection(section); section = getMainSection(section);
} }

View File

@@ -9,6 +9,7 @@
#include <assert.h> #include <assert.h>
#include <inttypes.h> #include <inttypes.h>
#include <stdint.h> #include <stdint.h>
#include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include "link/output.h" #include "link/output.h"
@@ -16,6 +17,8 @@
#include "link/section.h" #include "link/section.h"
#include "link/symbol.h" #include "link/symbol.h"
#include "extern/utf8decoder.h"
#include "error.h" #include "error.h"
#include "linkdefs.h" #include "linkdefs.h"
#include "platform.h" // MIN_NB_ELMS #include "platform.h" // MIN_NB_ELMS
@@ -46,7 +49,7 @@ static struct {
} *banks; } *banks;
} sections[SECTTYPE_INVALID]; } sections[SECTTYPE_INVALID];
/* Defines the order in which types are output to the sym and map files */ // Defines the order in which types are output to the sym and map files
static enum SectionType typeMap[SECTTYPE_INVALID] = { static enum SectionType typeMap[SECTTYPE_INVALID] = {
SECTTYPE_ROM0, SECTTYPE_ROM0,
SECTTYPE_ROMX, SECTTYPE_ROMX,
@@ -71,7 +74,7 @@ void out_AddSection(struct Section const *section)
[SECTTYPE_HRAM] = 1 [SECTTYPE_HRAM] = 1
}; };
uint32_t targetBank = section->bank - bankranges[section->type][0]; uint32_t targetBank = section->bank - sectionTypeInfo[section->type].firstBank;
uint32_t minNbBanks = targetBank + 1; uint32_t minNbBanks = targetBank + 1;
if (minNbBanks > maxNbBanks[section->type]) if (minNbBanks > maxNbBanks[section->type])
@@ -112,7 +115,7 @@ struct Section const *out_OverlappingSection(struct Section const *section)
{ {
struct SortedSections *banks = sections[section->type].banks; struct SortedSections *banks = sections[section->type].banks;
struct SortedSection *ptr = struct SortedSection *ptr =
banks[section->bank - bankranges[section->type][0]].sections; banks[section->bank - sectionTypeInfo[section->type].firstBank].sections;
while (ptr) { while (ptr) {
if (ptr->section->org < section->org + section->size if (ptr->section->org < section->org + section->size
@@ -123,7 +126,7 @@ struct Section const *out_OverlappingSection(struct Section const *section)
return NULL; return NULL;
} }
/** /*
* Performs sanity checks on the overlay file. * Performs sanity checks on the overlay file.
* @return The number of ROM banks in the overlay file * @return The number of ROM banks in the overlay file
*/ */
@@ -139,7 +142,7 @@ static uint32_t checkOverlaySize(void)
long overlaySize = ftell(overlayFile); long overlaySize = ftell(overlayFile);
/* Reset back to beginning */ // Reset back to beginning
fseek(overlayFile, 0, SEEK_SET); fseek(overlayFile, 0, SEEK_SET);
if (overlaySize % BANK_SIZE) if (overlaySize % BANK_SIZE)
@@ -156,7 +159,7 @@ static uint32_t checkOverlaySize(void)
return nbOverlayBanks; return nbOverlayBanks;
} }
/** /*
* Expand sections[SECTTYPE_ROMX].banks to cover all the overlay banks. * Expand sections[SECTTYPE_ROMX].banks to cover all the overlay banks.
* This ensures that writeROM will output each bank, even if some are not * This ensures that writeROM will output each bank, even if some are not
* covered by any sections. * covered by any sections.
@@ -164,9 +167,9 @@ static uint32_t checkOverlaySize(void)
*/ */
static void coverOverlayBanks(uint32_t nbOverlayBanks) static void coverOverlayBanks(uint32_t nbOverlayBanks)
{ {
/* 2 if is32kMode, 1 otherwise */ // 2 if is32kMode, 1 otherwise
uint32_t nbRom0Banks = maxsize[SECTTYPE_ROM0] / BANK_SIZE; uint32_t nbRom0Banks = sectionTypeInfo[SECTTYPE_ROM0].size / BANK_SIZE;
/* Discount ROM0 banks to avoid outputting too much */ // Discount ROM0 banks to avoid outputting too much
uint32_t nbUncoveredBanks = nbOverlayBanks - nbRom0Banks > sections[SECTTYPE_ROMX].nbBanks uint32_t nbUncoveredBanks = nbOverlayBanks - nbRom0Banks > sections[SECTTYPE_ROMX].nbBanks
? nbOverlayBanks - nbRom0Banks ? nbOverlayBanks - nbRom0Banks
: 0; : 0;
@@ -185,7 +188,7 @@ static void coverOverlayBanks(uint32_t nbOverlayBanks)
} }
} }
/** /*
* Write a ROM bank's sections to the output file. * Write a ROM bank's sections to the output file.
* @param bankSections The bank's sections, ordered by increasing address * @param bankSections The bank's sections, ordered by increasing address
* @param baseOffset The address of the bank's first byte in GB address space * @param baseOffset The address of the bank's first byte in GB address space
@@ -199,18 +202,19 @@ static void writeBank(struct SortedSection *bankSections, uint16_t baseOffset,
while (bankSections) { while (bankSections) {
struct Section const *section = bankSections->section; struct Section const *section = bankSections->section;
/* Output padding up to the next SECTION */ assert(section->offset == 0);
// Output padding up to the next SECTION
while (offset + baseOffset < section->org) { while (offset + baseOffset < section->org) {
putc(overlayFile ? getc(overlayFile) : padValue, putc(overlayFile ? getc(overlayFile) : padValue,
outputFile); outputFile);
offset++; offset++;
} }
/* Output the section itself */ // Output the section itself
fwrite(section->data, sizeof(*section->data), section->size, fwrite(section->data, sizeof(*section->data), section->size,
outputFile); outputFile);
if (overlayFile) { if (overlayFile) {
/* Skip bytes even with pipes */ // Skip bytes even with pipes
for (uint16_t i = 0; i < section->size; i++) for (uint16_t i = 0; i < section->size; i++)
getc(overlayFile); getc(overlayFile);
} }
@@ -229,9 +233,7 @@ static void writeBank(struct SortedSection *bankSections, uint16_t baseOffset,
} }
} }
/** // Writes a ROM file to the output.
* Writes a ROM file to the output.
*/
static void writeROM(void) static void writeROM(void)
{ {
outputFile = openFile(outputFileName, "wb"); outputFile = openFile(outputFileName, "wb");
@@ -245,18 +247,18 @@ static void writeROM(void)
if (outputFile) { if (outputFile) {
writeBank(sections[SECTTYPE_ROM0].banks ? sections[SECTTYPE_ROM0].banks[0].sections writeBank(sections[SECTTYPE_ROM0].banks ? sections[SECTTYPE_ROM0].banks[0].sections
: NULL, : NULL,
startaddr[SECTTYPE_ROM0], maxsize[SECTTYPE_ROM0]); sectionTypeInfo[SECTTYPE_ROM0].startAddr, sectionTypeInfo[SECTTYPE_ROM0].size);
for (uint32_t i = 0 ; i < sections[SECTTYPE_ROMX].nbBanks; i++) for (uint32_t i = 0 ; i < sections[SECTTYPE_ROMX].nbBanks; i++)
writeBank(sections[SECTTYPE_ROMX].banks[i].sections, writeBank(sections[SECTTYPE_ROMX].banks[i].sections,
startaddr[SECTTYPE_ROMX], maxsize[SECTTYPE_ROMX]); sectionTypeInfo[SECTTYPE_ROMX].startAddr, sectionTypeInfo[SECTTYPE_ROMX].size);
} }
closeFile(outputFile); closeFile(outputFile);
closeFile(overlayFile); closeFile(overlayFile);
} }
/** /*
* Get the lowest section by address out of the two * Get the lowest section by address out of the two
* @param s1 One choice * @param s1 One choice
* @param s2 The other * @param s2 The other
@@ -273,10 +275,57 @@ static struct SortedSection const **nextSection(struct SortedSection const **s1,
return (*s1)->section->org < (*s2)->section->org ? s1 : s2; return (*s1)->section->org < (*s2)->section->org ? s1 : s2;
} }
/* // Checks whether this character is legal as the first character of a symbol's name in a sym file
* Comparator function for `qsort` to sort symbols static bool canStartSymName(char c)
* Symbols are ordered by address, or else by original index for a stable sort {
*/ return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_';
}
// Checks whether this character is legal in a symbol's name in a sym file
static bool isLegalForSymName(char c)
{
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') ||
c == '_' || c == '@' || c == '#' || c == '$' || c == '.';
}
// Prints a symbol's name to `symFile`, assuming that the first character is legal.
// Illegal characters are UTF-8-decoded (errors are replaced by U+FFFD) and emitted as `\u`/`\U`.
static void printSymName(char const *name)
{
for (char const *ptr = name; *ptr != '\0'; ) {
char c = *ptr;
if (isLegalForSymName(c)) {
// Output legal ASCII characters as-is
fputc(c, symFile);
++ptr;
} else {
// Output illegal characters using Unicode escapes
// Decode the UTF-8 codepoint; or at least attempt to
uint32_t state = 0, codepoint;
do {
decode(&state, &codepoint, *ptr);
if (state == 1) {
// This sequence was invalid; emit a U+FFFD, and recover
codepoint = 0xFFFD;
// Skip continuation bytes
// A NUL byte does not qualify, so we're good
while ((*ptr & 0xC0) == 0x80)
++ptr;
break;
}
++ptr;
} while (state != 0);
fprintf(symFile, codepoint <= 0xFFFF ? "\\u%04" PRIx32 : "\\U%08" PRIx32,
codepoint);
}
}
}
// Comparator function for `qsort` to sort symbols
// Symbols are ordered by address, or else by original index for a stable sort
static int compareSymbols(void const *a, void const *b) static int compareSymbols(void const *a, void const *b)
{ {
struct SortedSymbol const *sym1 = (struct SortedSymbol const *)a; struct SortedSymbol const *sym1 = (struct SortedSymbol const *)a;
@@ -288,7 +337,7 @@ static int compareSymbols(void const *a, void const *b)
return sym1->idx < sym2->idx ? -1 : sym1->idx > sym2->idx ? 1 : 0; return sym1->idx < sym2->idx ? -1 : sym1->idx > sym2->idx ? 1 : 0;
} }
/** /*
* Write a bank's contents to the sym file * Write a bank's contents to the sym file
* @param bankSections The bank's sections * @param bankSections The bank's sections
*/ */
@@ -298,16 +347,22 @@ static void writeSymBank(struct SortedSections const *bankSections,
if (!symFile) if (!symFile)
return; return;
#define forEachSortedSection(sect, ...) do { \
for (struct SortedSection const *ssp = bankSections->zeroLenSections; ssp; ssp = ssp->next) { \
for (struct Section const *sect = ssp->section; sect; sect = sect->nextu) \
__VA_ARGS__ \
} \
for (struct SortedSection const *ssp = bankSections->sections; ssp; ssp = ssp->next) { \
for (struct Section const *sect = ssp->section; sect; sect = sect->nextu) \
__VA_ARGS__ \
} \
} while (0)
uint32_t nbSymbols = 0; uint32_t nbSymbols = 0;
for (struct SortedSection const *ptr = bankSections->zeroLenSections; ptr; ptr = ptr->next) { forEachSortedSection(sect, {
for (struct Section const *sect = ptr->section; sect; sect = sect->nextu)
nbSymbols += sect->nbSymbols; nbSymbols += sect->nbSymbols;
} });
for (struct SortedSection const *ptr = bankSections->sections; ptr; ptr = ptr->next) {
for (struct Section const *sect = ptr->section; sect; sect = sect->nextu)
nbSymbols += sect->nbSymbols;
}
if (!nbSymbols) if (!nbSymbols)
return; return;
@@ -317,45 +372,49 @@ static void writeSymBank(struct SortedSections const *bankSections,
if (!symList) if (!symList)
err("Failed to allocate symbol list"); err("Failed to allocate symbol list");
uint32_t idx = 0; nbSymbols = 0;
for (struct SortedSection const *ptr = bankSections->zeroLenSections; ptr; ptr = ptr->next) { forEachSortedSection(sect, {
for (struct Section const *sect = ptr->section; sect; sect = sect->nextu) {
for (uint32_t i = 0; i < sect->nbSymbols; i++) { for (uint32_t i = 0; i < sect->nbSymbols; i++) {
symList[idx].idx = idx; if (!canStartSymName(sect->symbols[i]->name[0]))
symList[idx].sym = sect->symbols[i]; // Don't output symbols that begin with an illegal character
symList[idx].addr = symList[idx].sym->offset + sect->org; continue;
idx++; symList[nbSymbols].idx = nbSymbols;
symList[nbSymbols].sym = sect->symbols[i];
symList[nbSymbols].addr = symList[nbSymbols].sym->offset + sect->org;
nbSymbols++;
} }
} });
}
for (struct SortedSection const *ptr = bankSections->sections; ptr; ptr = ptr->next) { #undef forEachSortedSection
for (struct Section const *sect = ptr->section; sect; sect = sect->nextu) {
for (uint32_t i = 0; i < sect->nbSymbols; i++) {
symList[idx].idx = idx;
symList[idx].sym = sect->symbols[i];
symList[idx].addr = symList[idx].sym->offset + sect->org;
idx++;
}
}
}
assert(idx == nbSymbols);
qsort(symList, nbSymbols, sizeof(*symList), compareSymbols); qsort(symList, nbSymbols, sizeof(*symList), compareSymbols);
uint32_t symBank = bank + bankranges[type][0]; uint32_t symBank = bank + sectionTypeInfo[type].firstBank;
for (uint32_t i = 0; i < nbSymbols; i++) { for (uint32_t i = 0; i < nbSymbols; i++) {
struct SortedSymbol *sym = &symList[i]; struct SortedSymbol *sym = &symList[i];
fprintf(symFile, "%02" PRIx32 ":%04" PRIx16 " %s\n", fprintf(symFile, "%02" PRIx32 ":%04" PRIx16 " ", symBank, sym->addr);
symBank, sym->addr, sym->sym->name); printSymName(sym->sym->name);
fputc('\n', symFile);
} }
free(symList); free(symList);
} }
/** static void writeEmptySpace(uint16_t begin, uint16_t end)
{
if (begin < end) {
uint16_t len = end - begin;
fprintf(mapFile, "\tEMPTY: $%04x-$%04x ($%04" PRIx16 " byte%s)\n",
begin, end - 1, len, len == 1 ? "" : "s");
}
}
/*
* Write a bank's contents to the map file * Write a bank's contents to the map file
* @param bankSections The bank's sections * @param bankSections The bank's sections
* @return The bank's used space * @return The bank's used space
@@ -369,10 +428,11 @@ static uint16_t writeMapBank(struct SortedSections const *sectList,
struct SortedSection const *section = sectList->sections; struct SortedSection const *section = sectList->sections;
struct SortedSection const *zeroLenSection = sectList->zeroLenSections; struct SortedSection const *zeroLenSection = sectList->zeroLenSections;
fprintf(mapFile, "%s bank #%" PRIu32 ":\n", typeNames[type], fprintf(mapFile, "%s bank #%" PRIu32 ":\n", sectionTypeInfo[type].name,
bank + bankranges[type][0]); bank + sectionTypeInfo[type].firstBank);
uint16_t used = 0; uint16_t used = 0;
uint16_t prevEndAddr = sectionTypeInfo[type].startAddr;
while (section || zeroLenSection) { while (section || zeroLenSection) {
struct SortedSection const **pickedSection = struct SortedSection const **pickedSection =
@@ -380,54 +440,75 @@ static uint16_t writeMapBank(struct SortedSections const *sectList,
struct Section const *sect = (*pickedSection)->section; struct Section const *sect = (*pickedSection)->section;
used += sect->size; used += sect->size;
assert(sect->offset == 0);
writeEmptySpace(prevEndAddr, sect->org);
prevEndAddr = sect->org + sect->size;
if (sect->size != 0) if (sect->size != 0)
fprintf(mapFile, " SECTION: $%04" PRIx16 "-$%04x ($%04" PRIx16 fprintf(mapFile, "\tSECTION: $%04" PRIx16 "-$%04x ($%04" PRIx16
" byte%s) [\"%s\"]\n", " byte%s) [\"%s\"]\n",
sect->org, sect->org + sect->size - 1, sect->org, prevEndAddr - 1,
sect->size, sect->size == 1 ? "" : "s", sect->size, sect->size == 1 ? "" : "s",
sect->name); sect->name);
else else
fprintf(mapFile, " SECTION: $%04" PRIx16 " (0 bytes) [\"%s\"]\n", fprintf(mapFile, "\tSECTION: $%04" PRIx16 " (0 bytes) [\"%s\"]\n",
sect->org, sect->name); sect->org, sect->name);
if (!noSymInMap) {
uint16_t org = sect->org; uint16_t org = sect->org;
while (sect) { while (sect) {
for (size_t i = 0; i < sect->nbSymbols; i++) for (size_t i = 0; i < sect->nbSymbols; i++)
fprintf(mapFile, " $%04" PRIx32 " = %s\n", // Space matches "\tSECTION: $xxxx ..."
fprintf(mapFile, "\t $%04" PRIx32 " = %s\n",
sect->symbols[i]->offset + org, sect->symbols[i]->offset + org,
sect->symbols[i]->name); sect->symbols[i]->name);
if (sect->nextu) {
// Announce the following "piece"
if (sect->nextu->modifier == SECTION_UNION)
fprintf(mapFile,
"\t ; Next union\n");
else if (sect->nextu->modifier == SECTION_FRAGMENT)
fprintf(mapFile,
"\t ; Next fragment\n");
}
sect = sect->nextu; // Also print symbols in the following "pieces" sect = sect->nextu; // Also print symbols in the following "pieces"
} }
}
*pickedSection = (*pickedSection)->next; *pickedSection = (*pickedSection)->next;
} }
uint16_t bankEndAddr = sectionTypeInfo[type].startAddr + sectionTypeInfo[type].size;
if (used == 0) { if (used == 0) {
fputs(" EMPTY\n\n", mapFile); fputs("\tEMPTY\n\n", mapFile);
} else { } else {
uint16_t slack = maxsize[type] - used; writeEmptySpace(prevEndAddr, bankEndAddr);
fprintf(mapFile, " SLACK: $%04" PRIx16 " byte%s\n\n", slack, uint16_t slack = sectionTypeInfo[type].size - used;
fprintf(mapFile, "\tTOTAL EMPTY: $%04" PRIx16 " byte%s\n\n", slack,
slack == 1 ? "" : "s"); slack == 1 ? "" : "s");
} }
return used; return used;
} }
/** /*
* Write the total used space by section type to the map file * Write the total used and free space by section type to the map file
* @param usedMap The total used space by section type * @param usedMap The total used space by section type
*/ */
static void writeMapUsed(uint32_t usedMap[MIN_NB_ELMS(SECTTYPE_INVALID)]) static void writeMapSummary(uint32_t usedMap[MIN_NB_ELMS(SECTTYPE_INVALID)])
{ {
if (!mapFile) if (!mapFile)
return; return;
fputs("USED:\n", mapFile); fputs("SUMMARY:\n", mapFile);
for (uint8_t i = 0; i < SECTTYPE_INVALID; i++) { for (uint8_t i = 0; i < SECTTYPE_INVALID; i++) {
enum SectionType type = typeMap[i]; enum SectionType type = typeMap[i];
@@ -436,17 +517,22 @@ static void writeMapUsed(uint32_t usedMap[MIN_NB_ELMS(SECTTYPE_INVALID)])
if (type == SECTTYPE_VRAM || type == SECTTYPE_OAM) if (type == SECTTYPE_VRAM || type == SECTTYPE_OAM)
continue; continue;
if (sections[type].nbBanks > 0) { // Do not output unused section types
fprintf(mapFile, " %s: $%04" PRIx32 " byte%s in %" PRIu32 " bank%s\n", if (sections[type].nbBanks == 0)
typeNames[type], usedMap[type], usedMap[type] == 1 ? "" : "s", continue;
sections[type].nbBanks, sections[type].nbBanks == 1 ? "" : "s");
} fprintf(mapFile, "\t%s: %" PRId32 " byte%s used / %" PRId32 " free",
sectionTypeInfo[type].name, usedMap[type], usedMap[type] == 1 ? "" : "s",
sections[type].nbBanks * sectionTypeInfo[type].size - usedMap[type]);
if (sectionTypeInfo[type].firstBank != sectionTypeInfo[type].lastBank ||
sections[type].nbBanks > 1)
fprintf(mapFile, " in %d bank%s", sections[type].nbBanks,
sections[type].nbBanks == 1 ? "" : "s");
fputc('\n', mapFile);
} }
} }
/** // Writes the sym and/or map files, if applicable.
* Writes the sym and/or map files, if applicable.
*/
static void writeSymAndMap(void) static void writeSymAndMap(void)
{ {
if (!symFileName && !mapFileName) if (!symFileName && !mapFileName)
@@ -471,7 +557,7 @@ static void writeSymAndMap(void)
} }
} }
writeMapUsed(usedMap); writeMapSummary(usedMap);
closeFile(symFile); closeFile(symFile);
closeFile(mapFile); closeFile(mapFile);

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