Compare commits

..

134 Commits

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

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

- `-fsanitize=float-divide-by-zero` is an extra UBSan check
  (and reveals a UB bug to fix with fixed-point `DIV` and `LOG`)
2024-12-31 10:02:20 +01:00
Rangi
72b2a4d7c0 Use if constexpr to guarantee compile-time simplification (#1590) 2024-12-30 23:44:12 -05:00
Rangi
06daf2a9b5 Include <signal.h> in rgbgfx_test.cpp (#1589) 2024-12-30 23:22:14 +01:00
Rangi
ad95d2e06f Allow deduplicating tiles with neither an input nor output tileset (#1585) 2024-12-30 18:58:07 +01:00
Rangi
5197e6b79f Run gcc static analysis in CI (#1587) 2024-12-30 09:57:41 -05:00
Rangi42
b99ce3845e Fix RGBFIX writing bytes when one syscall is not sufficient 2024-12-30 11:25:20 +01:00
Rangi42
d63955eccd Release 0.9.0 2024-12-25 10:46:17 -05:00
Rangi42
2c4fc4cbe8 Update man page dates 2024-12-25 10:37:08 -05:00
Rangi42
7d3c31b6d8 Update CI test project commits
Note that libbet's latest commit updated some text, so its ROM hash changed
2024-12-25 10:29:06 -05:00
Rangi42
151f83db6d Using C++20 [[unlikely]] here would be excessive micro-optimization 2024-12-23 14:14:10 -05:00
Rangi42
22838ce2d8 Remove redundant 0xC7 masking for RST values (the parser handles it) 2024-12-23 10:10:01 -05:00
Rangi42
b058bb6e15 Sorting RGB palettes by luminance is not a "legacy" feature 2024-12-23 10:01:30 -05:00
Rangi42
36b04b5dea Rename parser value const to iconst to distinguish it from C++ keyword 2024-12-23 09:21:30 -05:00
Rangi42
a7296ecb31 Fix man page formatting 2024-12-21 00:44:33 -05:00
Rangi42
92917ceb2f List LDHL as an unsupported instruction alias 2024-12-13 11:35:59 +01:00
Sylvie
c1c5b10082 Deprecate LDH with $00-$FF (#1575) 2024-12-10 21:27:37 -05:00
Sylvie
f44de0c7ae Deprecate LD with [C] (#1574) 2024-12-10 21:13:09 -05:00
Rangi42
b18cfe6bdb Consistently use UINT32_MAX, not -1, for uint32_t values 2024-12-10 19:47:23 -05:00
Rangi42
a8ec9228d4 List post-release steps to take for other gbdev projects 2024-12-10 13:06:58 -05:00
Sylvie
c1b85554a8 Document obsolete syntax in rgbasm-old(5) (#1571) 2024-12-10 12:34:37 -05:00
Sylvie
b877c81c32 Use C++-style casts (#1576) 2024-12-09 21:56:54 -05:00
Rangi42
e66da4c8c7 Consistently capitalize C 2024-12-09 13:42:01 -05:00
Sylvie
573e044b30 Deprecate LDIO (#1567)
* Deprecate `LDIO`

* `ld [$ff00+n8], a` is not treated as `ldh [n8], a`
2024-12-05 12:49:13 -05:00
Rangi42
ceb43c7aa4 Remove sample comments in workflow 2024-12-04 15:38:49 -05:00
Antonio Vivace
eae1ecb77e ci: re-trigger build-container.yml with fine-grained PAT 2024-12-04 19:05:10 +01:00
Antonio Vivace
c4de6c402b ci: clean up untagged artifacts 2024-12-04 18:57:43 +01:00
Sylvie
0b147c9386 Fix ** right-associativity, and clarify docs (#1566) 2024-12-03 20:40:50 -05:00
Sylvie
6982c8a116 Improve the instruction documentation (#1561) 2024-12-02 15:41:57 -05:00
Eldred Habert
d5f39c8dce Remove the use of floating-point for palette packing (#1565)
This is primarily a correctness change, *not* a performance one.
The expected performance impact is minimal anyway.

The goal is to eliminate the use of platform-inconsistent floating-point operations
for this load-bearing task.
2024-11-29 13:44:19 -05:00
Sylvie
a5d18d62df Explain the DAA instruction algorithm (#1564) 2024-11-29 10:42:34 -05:00
Rangi42
a27f704c25 Implement -Wunmatched-directive 2024-11-28 20:30:38 +01:00
Rangi42
9216485bca Add TRACE-level verbose logging for efficiency calculations 2024-11-27 21:06:18 +01:00
Rangi42
c33acb905b Avoid precision loss from floating-point division in calculating efficiency
This was breaking test results on 32-bit MinGW
2024-11-27 21:06:18 +01:00
Rangi42
81c3521610 Add color_curve RGBGFX test 2024-11-27 21:06:18 +01:00
Sylvie
e0ee9dc3ad Add reverse_1bit RGBGFX test (#1555)
Fixes a bug to always use 2bpp `_data` in `TileData`
2024-11-24 19:30:49 -05:00
Rangi42
cb546f0cd8 Fix rgbasm(1) formatting 2024-11-08 22:29:52 -05:00
Rangi42
a60186db2f Document the RGBGFX -X and -Y options 2024-11-03 18:54:16 +01:00
Antonio Vivace
d9f87a5721 contributing: add paragraph regarding the container image 2024-10-29 00:43:48 +01:00
Sylvie
a7fdb2c3d3 Add more RGBGFX test coverage (#1553) 2024-10-27 11:32:21 -04:00
Sylvie
5efd303b7f Allow LOAD FRAGMENT (#1552)
This was implemented in #736 but removed after discussion in #869.

Fixes #1537
2024-10-24 19:45:44 -04:00
Rangi42
0d3980d039 Refactor how map file sections are printed
This makes size-0 sections print as "($0000 bytes)" instead of
"(0 bytes)", which is more consistent.
2024-10-23 17:10:39 +02:00
Rangi42
ab6244d81c Escape characters in section names in map files 2024-10-23 17:10:39 +02:00
Sylvie
7fcf4ba60f Correctly recover from syntax errors at the first token of a line (#1549) 2024-10-22 21:01:44 +02:00
Sylvie
f048cbbb11 Clean up some man pages (#1547) 2024-10-22 13:07:09 -04:00
Rangi42
4c495c31d9 Release 0.9.0-rc2 2024-10-21 22:52:18 -04:00
Sylvie
90286ccbbc Fix detection of tiles with too many colors (#1546) 2024-10-20 13:51:39 -04:00
Sylvie
b33aa31944 LOAD FRAGMENT is not allowed (#1536) 2024-10-17 14:42:19 -04:00
Quinn
dd6c741143 Swap manpage descriptions of HIGH(n) and LOW(n) (#1545) 2024-10-16 21:10:50 +02:00
Sylvie
3b3263273c Make ENDL optional like ENDSECTION (#1538)
Add warning for `LOAD` without `ENDL`
2024-10-15 21:13:50 -04:00
Sylvie
bc5a71ff88 Update some RGBLINK error messages (#1544) 2024-10-16 01:42:49 +02:00
JL2210
e623aeb85d Make tests work with CTest (#1539)
Adds option to disable non-free tests
2024-10-15 19:26:17 -04:00
Sylvie
a2ff653a83 Fix nested undefined interpolation segfault (#1542) 2024-10-16 00:09:47 +02:00
Sylvie
a13723c523 Implement 0x/0o/0b number prefixes (#1533) 2024-10-08 15:56:00 -04:00
Sylvie
cf85146353 Refactoring and enhancements to RGBASM warnings (#1526)
* Allow a `no-` prefix to negate "meta" warnings
  (`-Wno-all`, `-Wno-extra`, `-Wno-everything`)
* Allow `-Wno-error=...` to override `-Werror`
  (including for "meta" warnings)
2024-10-04 21:52:40 +02:00
Rangi42
a9e49a09fd Allow tab character after backslash line continuation 2024-10-01 22:41:55 -04:00
Antonio Vivace
cbe44fed9b ci: run only the "build tagged container image" step on tag pushes 2024-10-02 00:57:44 +02:00
Antonio Vivace
c439b8e27f ci: add descriptions to built container images 2024-10-01 22:57:30 +02:00
ISSOtm
86bf289452 Process the last line of textual palette specs even without a trailing newline
Fixes #1519
2024-09-30 22:26:00 +02:00
Rangi42
e1ac7f389d Correct some documentation of RGBASM warnings 2024-09-30 15:58:09 -04:00
Sylvie
d5159f66be -Wall enables -Wcharmap-redef, and document -Wnested-comment (#1528) 2024-09-30 14:34:58 -04:00
Rangi42
c7a029a051 Remove duplicated condition check 2024-09-30 10:47:57 -04:00
Sylvie
d5ded84501 Move definition of _POSIX_C_SOURCE to include/platform.hpp (#1524) 2024-09-29 23:53:15 +02:00
Sylvie
4cd0dd5314 Use setmode instead of fdopen (#1520) 2024-09-29 14:06:59 -04:00
Sylvie
9783671399 Simplify some C++ abstractions (#1518)
* Remove namespaces
* Prefer `bool operator==`, not `friend auto operator==`
* Prefer not to use `using`
* Use a `constexpr` function instead of a template for `flipTable`
2024-09-26 00:07:27 -04:00
Rangi42
8037b9e10a Run clang-format 2024-09-25 13:15:58 -04:00
Rangi42
7c74653aa1 Fix swapped warning comments 2024-09-25 11:25:03 -04:00
Sylvie
22767e36e2 Refer to "end of line", not "newline" (#1517) 2024-09-23 02:15:02 +02:00
Sylvie
6b89938da7 Avoid treating labels and macros differently in column 1 (#1515)
Fixes #1512
2024-09-23 01:26:25 +02:00
Sylvie
15919e550f Add test to demonstrate lack of expansions in skipIfBlock (#1516) 2024-09-22 15:31:12 -04:00
Antonio Vivace
f93587c805 ci: give packages/write permission to build container image action 2024-09-22 01:14:44 +02:00
Antonio Vivace
a870f7de10 ci: tag release container images 2024-09-22 01:06:33 +02:00
Antonio Vivace
6b72067387 ci: add explicit write permission to the build container image job 2024-09-22 01:05:22 +02:00
Sylvie
84c01f064f Refactor some workflows for consistency (#1510) 2024-09-21 11:12:09 -04:00
Rangi42
5d3e96662e Only publish container for gbdev 2024-09-21 10:06:23 -04:00
Rangi42
91580043e0 Use latest docker/login-action 2024-09-21 09:59:28 -04:00
Antonio Vivace
3e28e92622 ci: build "master" container image and publish it to ghcr on every push 2024-09-20 16:49:25 +02:00
ISSOtm
d494f73825 Document extra pre-release updates 2024-09-18 19:38:03 +02:00
Rangi42
b03a5b13b7 Clarify when to manually publish prerelease docs 2024-09-18 12:40:08 -04:00
323 changed files with 5693 additions and 3605 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

18
.github/workflows/analysis.yml vendored Normal file
View File

@@ -0,0 +1,18 @@
name: Static analysis
on:
- push
- pull_request
jobs:
analysis:
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@v4
- name: Install deps
shell: bash
run: |
./.github/scripts/install_deps.sh ubuntu-latest
- name: Static analysis
run: | # Silence warnings with too many false positives (https://stackoverflow.com/a/73913076)
make -kj CXX=g++-14 CXXFLAGS="-fanalyzer -fanalyzer-verbosity=0 -Wno-analyzer-use-of-uninitialized-value -DNDEBUG" Q=

51
.github/workflows/build-container.yml vendored Normal file
View File

@@ -0,0 +1,51 @@
name: Build container image
on:
push:
branches:
- master
tags:
- '*'
jobs:
publish-docker-image:
if: github.repository_owner == 'gbdev'
runs-on: ubuntu-latest
permissions:
packages: write
steps:
- name: Checkout repo
uses: actions/checkout@v4
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push the master container image
if: github.ref == 'refs/heads/master'
run: |
COMMIT_HASH=$(git rev-parse --short HEAD)
sed -i "2i LABEL org.opencontainers.image.description=\"RGBDS container image, containing the git version master:$COMMIT_HASH\"" Dockerfile
docker build . --tag ghcr.io/gbdev/rgbds:master
docker push ghcr.io/gbdev/rgbds:master
- name: Build and push the version-tagged container image
if: startsWith(github.ref, 'refs/tags/')
run: |
TAG_NAME=${GITHUB_REF#refs/tags/}
sed -i "2i LABEL org.opencontainers.image.description=\"RGBDS container image for the release version $TAG_NAME\"" Dockerfile
docker build . --tag ghcr.io/gbdev/rgbds:$TAG_NAME
docker push ghcr.io/gbdev/rgbds:$TAG_NAME
- name: Delete untagged container images
if: github.repository_owner == 'gbdev'
uses: Chizkiyahu/delete-untagged-ghcr-action@v5
with:
# Requires a personal access token with delete:packages permissions
token: ${{ secrets.PAT_TOKEN }}
package_name: 'rgbds'
untagged_only: true
except_untagged_multiplatform: true
owner_type: 'org'

View File

@@ -1,4 +1,4 @@
name: "Code coverage checking"
name: Code coverage checking
on: pull_request
jobs:

View File

@@ -1,4 +1,4 @@
name: "Create release artifacts"
name: Create release artifacts
on:
push:
tags:
@@ -24,30 +24,32 @@ jobs:
run: | # Turn "vX.Y.Z" into "X.Y.Z"
VERSION="${{ github.ref_name }}"
echo "version=${VERSION#v}" >> $GITHUB_ENV
- uses: actions/checkout@v4
- name: Checkout repo
uses: actions/checkout@v4
- name: Install deps
run: .github/scripts/get_win_deps.ps1
- uses: actions/cache@v4
- name: Check libraries cache
id: cache
uses: actions/cache@v4
with:
path: |
zbuild
pngbuild
key: ${{ matrix.arch }}-${{ hashFiles('zlib/**', 'libpng/**') }}
- name: Build zlib
if: steps.cache.outputs.cache-hit != 'true'
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
if: steps.cache.outputs.cache-hit != 'true'
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
cmake --build pngbuild --config Release -j
if: steps.cache.outputs.cache-hit != 'true'
- name: Install libpng
run: |
cmake --install pngbuild
@@ -74,7 +76,8 @@ jobs:
run: | # Turn "refs/tags/vX.Y.Z" into "X.Y.Z"
VERSION="${{ github.ref_name }}"
echo "version=${VERSION#v}" >> $GITHUB_ENV
- uses: actions/checkout@v4
- name: Checkout repo
uses: actions/checkout@v4
- name: Install deps
shell: bash
run: |
@@ -104,7 +107,8 @@ jobs:
run: | # Turn "refs/tags/vX.Y.Z" into "X.Y.Z"
VERSION="${{ github.ref_name }}"
echo "version=${VERSION#v}" >> $GITHUB_ENV
- uses: actions/checkout@v4
- name: Checkout repo
uses: actions/checkout@v4
- name: Install deps
shell: bash
run: |
@@ -133,17 +137,19 @@ jobs:
run: | # Turn "refs/tags/vX.Y.Z" into "X.Y.Z"
VERSION="${{ github.ref_name }}"
echo "version=${VERSION#v}" >> $GITHUB_ENV
- uses: actions/checkout@v4
- name: Checkout repo
uses: actions/checkout@v4
- name: Package sources
run: |
make dist Q=
ls
- uses: actions/download-artifact@v4
- name: Download Linux binaries
uses: actions/download-artifact@v4
- name: Release
uses: softprops/action-gh-release@v2
with:
body: |
Please ensure that the four packages below work properly.
Please ensure that the packages below work properly.
Once that's done, replace this text with the changelog, un-draft the release, and update the `release` branch.
By the way, if you forgot to update `include/version.hpp`, RGBASM's version test is gonna fail in the tag's regression testing! (Use `git push --delete origin <tag>` to delete it)
draft: true # Don't publish the release quite yet...

View File

@@ -1,4 +1,4 @@
name: "Create release docs"
name: Create release docs
on:
release:
types:
@@ -7,7 +7,7 @@ on:
jobs:
build:
if: github.repository_owner == 'gbdev'
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
steps:
- name: Checkout rgbds@release
uses: actions/checkout@v4

View File

@@ -1,4 +1,4 @@
name: "Regression testing"
name: Regression testing
on:
- push
- pull_request
@@ -17,7 +17,8 @@ jobs:
fail-fast: false
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- name: Checkout repo
uses: actions/checkout@v4
- name: Install deps
shell: bash
run: |
@@ -32,9 +33,7 @@ jobs:
run: |
cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_COMPILER=${{ matrix.cxx }} -DSANITIZERS=ON -DMORE_WARNINGS=ON
cmake --build build -j --verbose
cp build/src/rgb{asm,link,fix,gfx} .
sudo cmake --install build --verbose
cmake --install build --verbose --component "Test support programs"
- name: Package binaries
run: |
mkdir bins
@@ -58,8 +57,8 @@ jobs:
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
- name: Fetch test dependency repositories
if: steps.test-deps-cache.outputs.cache-hit != 'true'
continue-on-error: true
run: |
test/fetch-test-deps.sh
@@ -75,7 +74,8 @@ jobs:
macos-static:
runs-on: macos-14
steps:
- uses: actions/checkout@v4
- name: Checkout repo
uses: actions/checkout@v4
- name: Install deps
shell: bash
run: |
@@ -109,8 +109,8 @@ jobs:
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
- name: Fetch test dependency repositories
if: steps.test-deps-cache.outputs.cache-hit != 'true'
continue-on-error: true
run: |
test/fetch-test-deps.sh
@@ -138,30 +138,32 @@ jobs:
fail-fast: false
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- name: Checkout repo
uses: actions/checkout@v4
- name: Install deps
run: .github/scripts/get_win_deps.ps1
- uses: actions/cache@v4
- name: Check libraries cache
id: cache
uses: actions/cache@v4
with:
path: |
zbuild
pngbuild
key: ${{ matrix.arch }}-${{ hashFiles('zlib/**', 'libpng/**') }}
- name: Build zlib
if: steps.cache.outputs.cache-hit != 'true'
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
if: steps.cache.outputs.cache-hit != 'true'
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
cmake --build pngbuild --config Release -j
if: steps.cache.outputs.cache-hit != 'true'
- name: Install libpng
run: |
cmake --install pngbuild
@@ -171,7 +173,6 @@ jobs:
cmake -S . -B build -A ${{ matrix.platform }} -DCMAKE_INSTALL_PREFIX=install_dir -DCMAKE_BUILD_TYPE=Release
cmake --build build --config Release -j --verbose
cmake --install build --verbose --prefix install_dir
cmake --install build --verbose --component "Test support programs"
- name: Package binaries
shell: bash
run: |
@@ -196,8 +197,8 @@ jobs:
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
- name: Fetch test dependency repositories
if: steps.test-deps-cache.outputs.cache-hit != 'true'
shell: bash
continue-on-error: true
run: |
@@ -229,7 +230,8 @@ jobs:
env:
DIST_DIR: win${{ matrix.bits }}
steps:
- uses: actions/checkout@v4
- name: Checkout repo
uses: actions/checkout@v4
- name: Install deps
shell: bash
run: |
@@ -273,7 +275,8 @@ jobs:
fail-fast: false
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- name: Checkout repo
uses: actions/checkout@v4
- name: Retrieve binaries
uses: actions/download-artifact@v4
with:
@@ -303,8 +306,8 @@ jobs:
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
- name: Fetch test dependency repositories
if: steps.test-deps-cache.outputs.cache-hit != 'true'
shell: bash
continue-on-error: true
run: |
@@ -317,3 +320,69 @@ jobs:
shell: bash
run: |
test/run-tests.sh
cygwin:
strategy:
matrix:
bits: [32, 64]
include:
- bits: 32
arch: x86
- bits: 64
arch: x86_64
fail-fast: false
runs-on: windows-2019
timeout-minutes: 30
steps:
- name: Checkout repo
uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-tags: true
- name: Setup Cygwin
uses: cygwin/cygwin-install-action@v4
with:
platform: ${{ matrix.arch }}
packages: >-
bison
gcc-g++
git
libpng-devel
make
pkg-config
- name: Build & install using Make
shell: C:\cygwin\bin\env.exe CYGWIN_NOWINPATH=1 CHERE_INVOKING=1 C:\cygwin\bin\bash.exe -o igncr '{0}'
run: | # Cygwin does not support `make develop` sanitizers ASan or UBSan
make -kj Q=
make install -j Q=
- name: Run tests
shell: C:\cygwin\bin\env.exe CYGWIN_NOWINPATH=1 CHERE_INVOKING=1 C:\cygwin\bin\bash.exe -o igncr '{0}'
run: |
test/run-tests.sh --only-internal
freebsd:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout repo
uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-tags: true
- name: Build & test using CMake on FreeBSD
uses: vmactions/freebsd-vm@v1
with:
release: "15.0"
usesh: true
prepare: |
pkg install -y \
bash \
bison \
cmake \
git \
png
run: | # FreeBSD `c++` compiler does not support `make develop` sanitizers ASan or UBSan
cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_COMPILER=c++ -DUSE_EXTERNAL_TESTS=OFF
cmake --build build -j4 --verbose
cmake --install build --verbose
cmake --build build --target test

View File

@@ -1,4 +1,4 @@
name: "Update master docs"
name: Update master docs
on:
push:
branches:
@@ -9,6 +9,7 @@ on:
- man/rgbds.7
- man/rgbasm.1
- man/rgbasm.5
- man/rgbasm-old.5
- man/rgblink.1
- man/rgblink.5
- man/rgbfix.1
@@ -17,7 +18,7 @@ on:
jobs:
build:
if: github.repository_owner == 'gbdev'
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
steps:
- name: Checkout rgbds@master
uses: actions/checkout@v4

1
.gitignore vendored
View File

@@ -13,4 +13,5 @@
CMakeCache.txt
CMakeFiles/
cmake_install.cmake
build/
callgrind.out.*

View File

@@ -1,11 +1,14 @@
# SPDX-License-Identifier: MIT
# 3.9 required for LTO checks
cmake_minimum_required(VERSION 3.9 FATAL_ERROR)
# 3.17 optional for CMAKE_CTEST_ARGUMENTS
cmake_minimum_required(VERSION 3.9..3.17 FATAL_ERROR)
project(rgbds
LANGUAGES CXX)
include(CTest)
# get real path of source and binary directories
get_filename_component(srcdir "${CMAKE_SOURCE_DIR}" REALPATH)
get_filename_component(bindir "${CMAKE_BINARY_DIR}" REALPATH)
@@ -41,13 +44,9 @@ else()
# does not recognize this yet.
add_compile_options(-Wno-gnu-zero-variadic-macro-arguments)
endif()
add_definitions(-D_POSIX_C_SOURCE=200809L)
if(SANITIZERS)
set(SAN_FLAGS -fsanitize=shift -fsanitize=integer-divide-by-zero
-fsanitize=unreachable -fsanitize=vla-bound
-fsanitize=signed-integer-overflow -fsanitize=bounds
-fsanitize=object-size -fsanitize=bool -fsanitize=enum
-fsanitize=alignment -fsanitize=null -fsanitize=address)
set(SAN_FLAGS -fsanitize=address -fsanitize=undefined
-fsanitize=float-divide-by-zero)
add_compile_options(${SAN_FLAGS})
add_link_options(${SAN_FLAGS})
add_definitions(-D_GLIBCXX_ASSERTIONS)
@@ -59,7 +58,7 @@ else()
if(MORE_WARNINGS)
add_compile_options(-Werror -Wextra
-Walloc-zero -Wcast-align -Wcast-qual -Wduplicated-branches -Wduplicated-cond
-Wfloat-equal -Wlogical-op -Wnull-dereference -Wshift-overflow=2
-Wfloat-equal -Wlogical-op -Wnull-dereference -Wold-style-cast -Wshift-overflow=2
-Wstringop-overflow=4 -Wundef -Wuninitialized -Wunused
-Wshadow # TODO: -Wshadow=compatible-local?
-Wformat=2 -Wformat-overflow=2 -Wformat-truncation=1
@@ -76,7 +75,7 @@ endif()
find_program(GIT git)
if(GIT)
execute_process(COMMAND ${GIT} --git-dir=.git describe --tags --dirty --always
execute_process(COMMAND ${GIT} --git-dir=.git -c safe.directory='*' describe --tags --dirty --always
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
OUTPUT_VARIABLE GIT_REV OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_QUIET)
@@ -88,6 +87,8 @@ endif(GIT)
find_package(PkgConfig)
if(MSVC OR NOT PKG_CONFIG_FOUND)
# fallback to find_package
# cmake's FindPNG is very fragile; it breaks when multiple versions are installed
# this is most evident on macOS but can occur on Linux too
find_package(PNG REQUIRED)
else()
pkg_check_modules(LIBPNG REQUIRED libpng)
@@ -99,6 +100,7 @@ set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED True)
add_subdirectory(src)
set(CMAKE_CTEST_ARGUMENTS "--verbose")
add_subdirectory(test)
# By default, build in Release mode; Debug mode must be explicitly requested
@@ -121,6 +123,7 @@ set(man1 "man/rgbasm.1"
"man/rgbgfx.1"
"man/rgblink.1")
set(man5 "man/rgbasm.5"
"man/rgbasm-old.5"
"man/rgblink.5"
"man/rgbds.5")
set(man7 "man/gbz80.7"

View File

@@ -174,3 +174,17 @@ Each one is a binary RNG file which is passed to the `rgbgfx_test` program.
```sh
test_downstream <owner> <repo> <makefile target> <build file> <sha1 hash of build file>
```
## Container images
The CI will [take care](https://github.com/gbdev/rgbds/blob/master/.github/workflows/build-container.yml) of updating the [rgbds container](https://github.com/gbdev/rgbds/pkgs/container/rgbds) image tagged `master`.
When a git tag is pushed, the image is also tagged with that tag.
The image can be built locally and pushed to the GitHub container registry by manually running:
```bash
# e.g. to build and tag as 'master'
docker build . --tag ghcr.io/gbdev/rgbds:master
docker push ghcr.io/gbdev/rgbds:master
```

View File

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

View File

@@ -24,21 +24,18 @@ PNGLDFLAGS := `${PKG_CONFIG} --libs-only-L libpng`
PNGLDLIBS := `${PKG_CONFIG} --libs-only-l libpng`
# Note: if this comes up empty, `version.cpp` will automatically fall back to last release number
VERSION_STRING := `git --git-dir=.git describe --tags --dirty --always 2>/dev/null`
VERSION_STRING := `git --git-dir=.git -c safe.directory='*' describe --tags --dirty --always 2>/dev/null`
WARNFLAGS := -Wall -pedantic -Wno-unknown-warning-option \
-Wno-gnu-zero-variadic-macro-arguments
WARNFLAGS := -Wall -pedantic -Wno-unknown-warning-option -Wno-gnu-zero-variadic-macro-arguments
# Overridable CXXFLAGS
CXXFLAGS ?= -O3 -flto -DNDEBUG
# Non-overridable CXXFLAGS
REALCXXFLAGS := ${CXXFLAGS} ${WARNFLAGS} -std=c++2a -I include \
-D_POSIX_C_SOURCE=200809L -fno-exceptions -fno-rtti
REALCXXFLAGS := ${CXXFLAGS} ${WARNFLAGS} -std=c++2a -I include -fno-exceptions -fno-rtti
# Overridable LDFLAGS
LDFLAGS ?=
# Non-overridable LDFLAGS
REALLDFLAGS := ${LDFLAGS} ${WARNFLAGS} \
-DBUILD_VERSION_STRING=\"${VERSION_STRING}\"
REALLDFLAGS := ${LDFLAGS} ${WARNFLAGS} -DBUILD_VERSION_STRING=\"${VERSION_STRING}\"
# Wrapper around bison that passes flags depending on what the version supports
BISON := src/bison.sh
@@ -188,7 +185,7 @@ install: all
$Qinstall ${STRIP} -m ${BINMODE} rgbfix ${DESTDIR}${bindir}/rgbfix${SUFFIX}
$Qinstall ${STRIP} -m ${BINMODE} rgbgfx ${DESTDIR}${bindir}/rgbgfx${SUFFIX}
$Qinstall -m ${MANMODE} man/rgbasm.1 man/rgblink.1 man/rgbfix.1 man/rgbgfx.1 ${DESTDIR}${mandir}/man1/
$Qinstall -m ${MANMODE} man/rgbds.5 man/rgbasm.5 man/rgblink.5 ${DESTDIR}${mandir}/man5/
$Qinstall -m ${MANMODE} man/rgbds.5 man/rgbasm.5 man/rgbasm-old.5 man/rgblink.5 ${DESTDIR}${mandir}/man5/
$Qinstall -m ${MANMODE} man/rgbds.7 man/gbz80.7 ${DESTDIR}${mandir}/man7/
# Target used to check for suspiciously missing changed files.
@@ -204,17 +201,13 @@ checkdiff:
develop:
$Q${MAKE} WARNFLAGS="${WARNFLAGS} -Werror -Wextra \
-Walloc-zero -Wcast-align -Wcast-qual -Wduplicated-branches -Wduplicated-cond \
-Wfloat-equal -Wlogical-op -Wnull-dereference -Wshift-overflow=2 \
-Wfloat-equal -Wlogical-op -Wnull-dereference -Wold-style-cast -Wshift-overflow=2 \
-Wstringop-overflow=4 -Wundef -Wuninitialized -Wunused -Wshadow \
-Wformat=2 -Wformat-overflow=2 -Wformat-truncation=1 \
-Wno-format-nonliteral -Wno-strict-overflow -Wno-unused-but-set-variable \
-Wno-type-limits -Wno-tautological-constant-out-of-range-compare -Wvla \
-D_GLIBCXX_ASSERTIONS \
-fsanitize=shift -fsanitize=integer-divide-by-zero \
-fsanitize=unreachable -fsanitize=vla-bound \
-fsanitize=signed-integer-overflow -fsanitize=bounds \
-fsanitize=object-size -fsanitize=bool -fsanitize=enum \
-fsanitize=alignment -fsanitize=null -fsanitize=address" \
-D_GLIBCXX_ASSERTIONS -fsanitize=address -fsanitize=undefined \
-fsanitize=float-divide-by-zero" \
CXXFLAGS="-ggdb3 -Og -fno-omit-frame-pointer -fno-optimize-sibling-calls"
# This target is used during development in order to more easily debug with gdb.
@@ -272,4 +265,4 @@ wine-shim:
dist:
$Qgit ls-files | sed s~^~$${PWD##*/}/~ \
| tar -czf rgbds-`git describe --tags | cut -c 2-`.tar.gz -C .. -T -
| tar -czf rgbds-`git -c safe.directory='*' describe --tags | cut -c 2-`.tar.gz -C .. -T -

View File

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

View File

@@ -3,13 +3,16 @@
This describes for the maintainers of RGBDS how to publish a new release on
GitHub.
1. Update, commit, and push [include/version.hpp](include/version.hpp) with
values for `PACKAGE_VERSION_MAJOR`, `PACKAGE_VERSION_MINOR`,
`PACKAGE_VERSION_PATCH`, and `PACKAGE_VERSION_RC`, as well as
[Dockerfile](Dockerfile) with a value for `ARG version`. Only define
`PACKAGE_VERSION_RC` if you are publishing a release candidate! You can
use <code>git commit -m "Release <i>&lt;version&gt;</i>"</code> and
`git push origin master`.
1. Update the following files, then commit and push.
You can use <code>git commit -m "Release <i>&lt;version&gt;</i>"</code> and `git push origin master`.
- [include/version.hpp](include/version.hpp): set appropriate values for `PACKAGE_VERSION_MAJOR`,
`PACKAGE_VERSION_MINOR`, `PACKAGE_VERSION_PATCH`, and `PACKAGE_VERSION_RC`.
**Only** define `PACKAGE_VERSION_RC` if you are publishing a release candidate!
- [Dockerfile](Dockerfile): update `ARG version`.
- [test/fetch-test-deps.sh](test/fetch-test-deps.sh): update test dependency commits
(preferably, use the latest available).
- [man/\*](man/): update dates and authors.
2. Create a Git tag formatted as <code>v<i>&lt;MAJOR&gt;</i>.<i>&lt;MINOR&gt;</i>.<i>&lt;PATCH&gt;</i></code>,
or <code>v<i>&lt;MAJOR&gt;</i>.<i>&lt;MINOR&gt;</i>.<i>&lt;PATCH&gt;</i>-rc<i>&lt;RC&gt;</i></code>
@@ -38,7 +41,9 @@ GitHub.
4. GitHub Actions will run the [create-release-docs.yml](.github/workflows/create-release-docs.yml)
workflow to add the release documentation to [rgbds-www](https://github.com/gbdev/rgbds-www).
This is not done automatically for prereleases; if you want to do this manually,
This is not done automatically for prereleases, since we do not normally publish documentation
for them. If you want to manually publish prerelease documentation, such as for an April Fools
joke prerelease,
1. Clone [rgbds-www](https://github.com/gbdev/rgbds-www). You can use
`git clone https://github.com/gbdev/rgbds-www.git`.
@@ -52,8 +57,8 @@ GitHub.
If you do not have `groff` installed, you can change
`groff -Tpdf -mdoc -wall` to `mandoc -Tpdf -I os=Linux` in
[.github/actions/get-pages.sh](.github/actions/get-pages.sh) and it
will suffice.
[maintainer/man_to_html.sh](https://github.com/gbdev/rgbds-www/blob/master/maintainer/man_to_html.sh)
and it will suffice.
4. Commit and push the documentation. You can use <code>git commit -m
"Create RGBDS <i>&lt;tag&gt;</i> documentation"</code> and `git push origin master`
@@ -64,3 +69,12 @@ GitHub.
6. Click the "Publish release" button to publish it!
7. Update the `release` branch. You can use `git push origin release`.
8. Update the following related projects.
- [rgbobj](https://github.com/gbdev/rgbobj) and [rgbds-obj](https://github.com/gbdev/rgbds-obj):
make sure that object files created by the latest RGBASM can be parsed and displayed.
If the object file revision has been updated, rgbobj will need a corresponding release.
- [rgbds-www](https://github.com/gbdev/rgbds-www): update
[src/pages/versions.mdx](https://github.com/gbdev/rgbds-www/blob/master/src/pages/versions.mdx)
to list the new release.

View File

@@ -25,6 +25,7 @@ _rgbasm_completions() {
# See the `state` variable below for info about `state_after`
declare -A opts=(
[V]="version:normal"
[h]="help:normal"
[E]="export-all:normal"
[v]="verbose:normal"
[w]=":normal"
@@ -192,6 +193,8 @@ _rgbasm_completions() {
shift-amount
truncation
unmapped-char
unmatched-directive
unterminated-load
user
all
extra

View File

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

View File

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

View File

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

View File

@@ -26,6 +26,8 @@ _rgbasm_warnings() {
'shift-amount:Warn when a shift'\''s operand it negative or \> 32'
'truncation:Warn when implicit truncation loses bits'
'unmapped-char:Warn on unmapped character'
'unmatched-directive:Warn on unmatched directive pair'
'unterminated-load:Warn on LOAD without ENDL'
'user:Warn when executing the WARN built-in'
)
# TODO: handle `no-` and `error=` somehow?
@@ -34,8 +36,9 @@ _rgbasm_warnings() {
}
local args=(
# Arguments are listed here in the same order as in the manual, except for the version
'(- : * options)'{-V,--version}'[Print version number]'
# Arguments are listed here in the same order as in the manual, except for the version and help
'(- : * options)'{-V,--version}'[Print version number and exit]'
'(- : * options)'{-h,--help}'[Print help text and exit]'
'(-E --export-all)'{-E,--export-all}'[Export all symbols]'
'(-v --verbose)'{-v,--verbose}'[Print additional messages regarding progression]'

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: MIT */
// SPDX-License-Identifier: MIT
#ifndef RGBDS_ASM_CHARMAP_HPP
#define RGBDS_ASM_CHARMAP_HPP
@@ -18,6 +18,7 @@ void charmap_New(std::string const &name, std::string const *baseName);
void charmap_Set(std::string const &name);
void charmap_Push();
void charmap_Pop();
void charmap_CheckStack();
void charmap_Add(std::string const &mapping, std::vector<int32_t> &&value);
bool charmap_HasChar(std::string const &input);
std::vector<int32_t> charmap_Convert(std::string const &input);

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: MIT */
// SPDX-License-Identifier: MIT
// Contains some assembler-wide defines and externs
@@ -30,8 +30,8 @@ struct FileStackNode {
// Meaningless at the root level, but gets written to the object file anyway, so init it
uint32_t lineNo = 0;
// Set only if referenced: ID within the object file, -1 if not output yet
uint32_t ID = -1;
// Set only if referenced: ID within the object file, `UINT32_MAX` if not output yet
uint32_t ID = UINT32_MAX;
// REPT iteration counts since last named node, in reverse depth order
std::vector<uint32_t> &iters() { return data.get<std::vector<uint32_t>>(); }
@@ -41,15 +41,11 @@ struct FileStackNode {
std::string const &name() const { return data.get<std::string>(); }
FileStackNode(FileStackNodeType type_, Either<std::vector<uint32_t>, std::string> data_)
: type(type_), data(data_){};
: type(type_), data(data_) {}
std::string const &dump(uint32_t curLineNo) const;
// If true, entering this context generates a new unique ID.
bool generatesUniqueID() const { return type == NODE_REPT || type == NODE_MACRO; }
};
#define DEFAULT_MAX_DEPTH 64
extern size_t maxRecursionDepth;
struct MacroArgs;

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: MIT */
// SPDX-License-Identifier: MIT
#ifndef RGBDS_ASM_OPT_HPP
#define RGBDS_ASM_OPT_HPP
@@ -14,5 +14,6 @@ void opt_Parse(char const *option);
void opt_Push();
void opt_Pop();
void opt_CheckStack();
#endif // RGBDS_ASM_OPT_HPP

View File

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

View File

@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: MIT */
// SPDX-License-Identifier: MIT
#ifndef RGBDS_ASM_RPN_HPP
#define RGBDS_ASM_RPN_HPP
@@ -31,12 +31,8 @@ struct Expression {
Expression &operator=(Expression &&) = default;
bool isKnown() const {
return data.holds<int32_t>();
}
int32_t value() const {
return data.get<int32_t>();
}
bool isKnown() const { return data.holds<int32_t>(); }
int32_t value() const { return data.get<int32_t>(); }
int32_t getConstVal() const;
Symbol const *symbolOf() const;
@@ -53,7 +49,7 @@ struct Expression {
void makeUnaryOp(RPNCommand op, Expression &&src);
void makeBinaryOp(RPNCommand op, Expression &&src1, Expression const &src2);
void makeCheckHRAM();
bool makeCheckHRAM();
void makeCheckRST();
void checkNBit(uint8_t n) const;

View File

@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: MIT */
// SPDX-License-Identifier: MIT
#ifndef RGBDS_ASM_SECTION_HPP
#define RGBDS_ASM_SECTION_HPP
@@ -70,7 +70,8 @@ void sect_SetLoadSection(
SectionSpec const &attrs,
SectionModifier mod
);
void sect_EndLoadSection();
void sect_EndLoadSection(char const *cause);
void sect_CheckLoadClosed();
Section *sect_GetSymbolSection();
uint32_t sect_GetSymbolOffset();
@@ -101,5 +102,6 @@ void sect_BinaryFileSlice(std::string const &name, int32_t startPos, int32_t len
void sect_EndSection();
void sect_PushSection();
void sect_PopSection();
void sect_CheckStack();
#endif // RGBDS_ASM_SECTION_HPP

View File

@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: MIT */
// SPDX-License-Identifier: MIT
#ifndef RGBDS_ASM_SYMBOL_HPP
#define RGBDS_ASM_SYMBOL_HPP
@@ -45,7 +45,7 @@ struct Symbol {
>
data;
uint32_t ID; // ID of the symbol in the object file (-1 if none)
uint32_t ID; // ID of the symbol in the object file (`UINT32_MAX` if none)
uint32_t defIndex; // Ordering of the symbol in the state file
bool isDefined() const { return type != SYM_REF; }
@@ -55,7 +55,7 @@ struct Symbol {
bool isConstant() const {
if (type == SYM_LABEL) {
Section const *sect = getSection();
return sect && sect->org != (uint32_t)-1;
return sect && sect->org != UINT32_MAX;
}
return type == SYM_EQU || type == SYM_VAR;
}

View File

@@ -1,35 +1,34 @@
/* SPDX-License-Identifier: MIT */
// SPDX-License-Identifier: MIT
#ifndef RGBDS_ASM_WARNING_HPP
#define RGBDS_ASM_WARNING_HPP
extern unsigned int nbErrors, maxErrors;
enum WarningState { WARNING_DEFAULT, WARNING_DISABLED, WARNING_ENABLED, WARNING_ERROR };
enum WarningID {
WARNING_ASSERT, // Assertions
WARNING_BACKWARDS_FOR, // `for` loop with backwards range
WARNING_BACKWARDS_FOR, // `FOR` loop with backwards range
WARNING_BUILTIN_ARG, // Invalid args to builtins
WARNING_CHARMAP_REDEF, // Charmap entry re-definition
WARNING_DIV, // Division undefined behavior
WARNING_DIV, // Undefined division behavior
WARNING_EMPTY_DATA_DIRECTIVE, // `db`, `dw` or `dl` directive without data in ROM
WARNING_EMPTY_MACRO_ARG, // Empty macro argument
WARNING_EMPTY_STRRPL, // Empty second argument in `STRRPL`
WARNING_LARGE_CONSTANT, // Constants too large
WARNING_MACRO_SHIFT, // Shift past available arguments in macro
WARNING_MACRO_SHIFT, // `SHIFT` past available arguments in macro
WARNING_NESTED_COMMENT, // Comment-start delimiter in a block comment
WARNING_OBSOLETE, // Obsolete things
WARNING_SHIFT, // Shifting undefined behavior
WARNING_SHIFT_AMOUNT, // Strange shift amount
WARNING_USER, // User warnings
WARNING_OBSOLETE, // Obsolete/deprecated things
WARNING_SHIFT, // Undefined `SHIFT` behavior
WARNING_SHIFT_AMOUNT, // Strange `SHIFT` amount
WARNING_UNMATCHED_DIRECTIVE, // `PUSH[C|O|S]` without `POP[C|O|S]`
WARNING_UNTERMINATED_LOAD, // `LOAD` without `ENDL`
WARNING_USER, // User-defined `WARN`ings
NB_PLAIN_WARNINGS,
// Warnings past this point are "parametric" warnings, only mapping to a single flag
#define PARAM_WARNINGS_START NB_PLAIN_WARNINGS
// Warnings past this point are "parametric" warnings, only mapping to a single flag
// Treating string as number may lose some bits
WARNING_NUMERIC_STRING_1 = PARAM_WARNINGS_START,
WARNING_NUMERIC_STRING_1 = NB_PLAIN_WARNINGS,
WARNING_NUMERIC_STRING_2,
// Purging an exported symbol or label
WARNING_PURGE_1,
@@ -41,45 +40,46 @@ enum WarningID {
WARNING_UNMAPPED_CHAR_1,
WARNING_UNMAPPED_CHAR_2,
NB_PLAIN_AND_PARAM_WARNINGS,
#define NB_PARAM_WARNINGS (NB_PLAIN_AND_PARAM_WARNINGS - PARAM_WARNINGS_START)
// Warnings past this point are "meta" warnings
#define META_WARNINGS_START NB_PLAIN_AND_PARAM_WARNINGS
WARNING_ALL = META_WARNINGS_START,
WARNING_EXTRA,
WARNING_EVERYTHING,
NB_WARNINGS,
#define NB_META_WARNINGS (NB_WARNINGS - META_WARNINGS_START)
};
extern WarningState warningStates[NB_PLAIN_AND_PARAM_WARNINGS];
enum WarningAbled { WARNING_DEFAULT, WARNING_ENABLED, WARNING_DISABLED };
struct WarningState {
WarningAbled state;
WarningAbled error;
void update(WarningState other);
};
struct Diagnostics {
WarningState flagStates[NB_WARNINGS];
WarningState metaStates[NB_WARNINGS];
};
extern Diagnostics warningStates;
extern bool warningsAreErrors;
void processWarningFlag(char const *flag);
/*
* Used to warn the user about problems that don't prevent the generation of
* valid code.
*/
[[gnu::format(printf, 2, 3)]] void warning(WarningID id, char const *fmt, ...);
// Used to warn the user about problems that don't prevent the generation of
// valid code.
[[gnu::format(printf, 2, 3)]]
void warning(WarningID id, char const *fmt, ...);
/*
* Used for errors that compromise the whole assembly process by affecting the
* following code, potencially making the assembler generate errors caused by
* the first one and unrelated to the code that the assembler complains about.
* It is also used when the assembler goes into an invalid state (for example,
* when it fails to allocate memory).
*/
[[gnu::format(printf, 1, 2), noreturn]] void fatalerror(char const *fmt, ...);
// Used for errors that compromise the whole assembly process by affecting the
// following code, potencially making the assembler generate errors caused by
// the first one and unrelated to the code that the assembler complains about.
// It is also used when the assembler goes into an invalid state (for example,
// when it fails to allocate memory).
[[gnu::format(printf, 1, 2), noreturn]]
void fatalerror(char const *fmt, ...);
/*
* Used for errors that make it impossible to assemble correctly, but don't
* affect the following code. The code will fail to assemble but the user will
* get a list of all errors at the end, making it easier to fix all of them at
* once.
*/
[[gnu::format(printf, 1, 2)]] void error(char const *fmt, ...);
// Used for errors that make it impossible to assemble correctly, but don't
// affect the following code. The code will fail to assemble but the user will
// get a list of all errors at the end, making it easier to fix all of them at
// once.
[[gnu::format(printf, 1, 2)]]
void error(char const *fmt, ...);
#endif // RGBDS_ASM_WARNING_HPP

View File

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

View File

@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: MIT */
// SPDX-License-Identifier: MIT
#ifndef RGBDS_EITHER_HPP
#define RGBDS_EITHER_HPP
@@ -43,11 +43,11 @@ private:
// Generic field accessors; for internal use only.
template<typename T>
auto &field() {
return pick((T *)nullptr);
return pick(static_cast<T *>(nullptr));
}
template<typename T>
auto const &field() const {
return pick((T *)nullptr);
return pick(static_cast<T *>(nullptr));
}
public:

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: MIT */
// SPDX-License-Identifier: MIT
#ifndef RGBDS_GFX_MAIN_HPP
#define RGBDS_GFX_MAIN_HPP
@@ -48,14 +48,17 @@ struct Options {
std::string input{}; // positional arg
static constexpr uint8_t VERB_NONE = 0; // Normal, no extra output
static constexpr uint8_t VERB_CFG = 1; // Print configuration after parsing options
static constexpr uint8_t VERB_LOG_ACT = 2; // Log actions before doing them
static constexpr uint8_t VERB_INTERM = 3; // Print some intermediate results
static constexpr uint8_t VERB_DEBUG = 4; // Internals are logged
static constexpr uint8_t VERB_UNMAPPED = 5; // Unused so far
static constexpr uint8_t VERB_VVVVVV = 6; // What, can't I have a little fun?
[[gnu::format(printf, 3, 4)]] void verbosePrint(uint8_t level, char const *fmt, ...) const;
// clang-format off: vertically align values
static constexpr uint8_t VERB_NONE = 0; // Normal, no extra output
static constexpr uint8_t VERB_CFG = 1; // Print configuration after parsing options
static constexpr uint8_t VERB_LOG_ACT = 2; // Log actions before doing them
static constexpr uint8_t VERB_INTERM = 3; // Print some intermediate results
static constexpr uint8_t VERB_DEBUG = 4; // Internals are logged
static constexpr uint8_t VERB_TRACE = 5; // Step-by-step algorithm details
static constexpr uint8_t VERB_VVVVVV = 6; // What, can't I have a little fun?
// clang-format on
[[gnu::format(printf, 3, 4)]]
void verbosePrint(uint8_t level, char const *fmt, ...) const;
mutable bool hasTransparentPixels = false;
uint8_t maxOpaqueColors() const { return nbColorsPerPal - hasTransparentPixels; }
@@ -63,32 +66,24 @@ struct Options {
extern Options options;
/*
* Prints the error count, and exits with failure
*/
[[noreturn]] void giveUp();
/*
* If any error has been emitted thus far, calls `giveUp()`.
*/
// Prints the error count, and exits with failure
[[noreturn]]
void giveUp();
// If any error has been emitted thus far, calls `giveUp()`.
void requireZeroErrors();
/*
* Prints a warning, and does not change the error count
*/
[[gnu::format(printf, 1, 2)]] void warning(char const *fmt, ...);
/*
* Prints an error, and increments the error count
*/
[[gnu::format(printf, 1, 2)]] void error(char const *fmt, ...);
/*
* Prints an error, and increments the error count
* Does not take format arguments so `format_` and `-Wformat-security` won't complain about
* calling `errorMessage(msg)`.
*/
// Prints a warning, and does not change the error count
[[gnu::format(printf, 1, 2)]]
void warning(char const *fmt, ...);
// Prints an error, and increments the error count
[[gnu::format(printf, 1, 2)]]
void error(char const *fmt, ...);
// Prints an error, and increments the error count
// Does not take format arguments so `format_` and `-Wformat-security` won't complain about
// calling `errorMessage(msg)`.
void errorMessage(char const *msg);
/*
* Prints a fatal error, increments the error count, and gives up
*/
[[gnu::format(printf, 1, 2), noreturn]] void fatal(char const *fmt, ...);
// Prints a fatal error, increments the error count, and gives up
[[gnu::format(printf, 1, 2), noreturn]]
void fatal(char const *fmt, ...);
struct Palette {
// An array of 4 GBC-native (RGB555) colors
@@ -107,20 +102,18 @@ struct Palette {
uint8_t size() const;
};
namespace detail {
template<typename T, T... i>
static constexpr auto flipTable(std::integer_sequence<T, i...>) {
return std::array{[](uint8_t byte) {
// Flipping tends to happen fairly often, so take a bite out of dcache to speed it up
static constexpr auto flipTable = ([]() constexpr {
std::array<uint16_t, 256> table{};
for (uint16_t i = 0; i < table.size(); i++) {
// To flip all the bits, we'll flip both nibbles, then each nibble half, etc.
uint16_t byte = i;
byte = (byte & 0b0000'1111) << 4 | (byte & 0b1111'0000) >> 4;
byte = (byte & 0b0011'0011) << 2 | (byte & 0b1100'1100) >> 2;
byte = (byte & 0b0101'0101) << 1 | (byte & 0b1010'1010) >> 1;
return byte;
}(i)...};
}
} // namespace detail
// 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>());
table[i] = byte;
}
return table;
})();
#endif // RGBDS_GFX_MAIN_HPP

View File

@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: MIT */
// SPDX-License-Identifier: MIT
#ifndef RGBDS_GFX_PAL_PACKING_HPP
#define RGBDS_GFX_PAL_PACKING_HPP
@@ -6,19 +6,13 @@
#include <tuple>
#include <vector>
#include "defaultinitalloc.hpp"
#include "defaultinitvec.hpp"
struct Palette;
class ProtoPalette;
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>
overloadAndRemove(std::vector<ProtoPalette> const &protoPalettes);
} // namespace packing
#endif // RGBDS_GFX_PAL_PACKING_HPP

View File

@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: MIT */
// SPDX-License-Identifier: MIT
#ifndef RGBDS_GFX_PAL_SORTING_HPP
#define RGBDS_GFX_PAL_SORTING_HPP
@@ -10,22 +10,22 @@
#include "gfx/rgba.hpp"
// Allow a slot for every possible CGB color, plus one for transparency
// 32 (1 << 5) per channel, times 3 RGB channels = 32768 CGB colors
static constexpr size_t NB_COLOR_SLOTS = (1 << (5 * 3)) + 1;
struct Palette;
namespace sorting {
void indexed(
void sortIndexed(
std::vector<Palette> &palettes,
int palSize,
png_color const *palRGB,
int palAlphaSize,
png_byte *palAlpha
);
void grayscale(
std::vector<Palette> &palettes, std::array<std::optional<Rgba>, 0x8001> const &colors
void sortGrayscale(
std::vector<Palette> &palettes, std::array<std::optional<Rgba>, NB_COLOR_SLOTS> const &colors
);
void rgb(std::vector<Palette> &palettes);
} // namespace sorting
void sortRgb(std::vector<Palette> &palettes);
#endif // RGBDS_GFX_PAL_SORTING_HPP

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: MIT */
// SPDX-License-Identifier: MIT
#ifndef RGBDS_GFX_PROTO_PALETTE_HPP
#define RGBDS_GFX_PROTO_PALETTE_HPP
@@ -18,12 +18,8 @@ private:
std::array<uint16_t, capacity> _colorIndices{UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX};
public:
/*
* Adds the specified color to the set, or **silently drops it** if the set is full.
*
* Returns whether the color was unique.
*/
bool add(uint16_t color);
// Adds the specified color to the set, or **silently drops it** if the set is full.
void add(uint16_t color);
enum ComparisonResult {
NEITHER,

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: MIT */
// SPDX-License-Identifier: MIT
#ifndef RGBDS_ITERTOOLS_HPP
#define RGBDS_ITERTOOLS_HPP
@@ -6,52 +6,46 @@
#include <tuple>
#include <utility>
template<typename T>
class EnumSeqIterator {
T _value;
public:
explicit EnumSeqIterator(T value) : _value(value) {}
EnumSeqIterator &operator++() {
_value = (T)(_value + 1);
return *this;
}
auto operator*() const { return _value; }
friend auto operator==(EnumSeqIterator const &lhs, EnumSeqIterator const &rhs) {
return lhs._value == rhs._value;
}
friend auto operator!=(EnumSeqIterator const &lhs, EnumSeqIterator const &rhs) {
return lhs._value != rhs._value;
}
};
template<typename T>
class EnumSeq {
T _start;
T _stop;
class Iterator {
T _value;
public:
explicit Iterator(T value) : _value(value) {}
Iterator &operator++() {
_value = static_cast<T>(_value + 1);
return *this;
}
auto operator*() const { return _value; }
bool operator==(Iterator const &rhs) const { return _value == rhs._value; }
bool operator!=(Iterator const &rhs) const { return _value != rhs._value; }
};
public:
explicit EnumSeq(T stop) : _start((T)0), _stop(stop) {}
explicit EnumSeq(T stop) : _start(static_cast<T>(0)), _stop(stop) {}
explicit EnumSeq(T start, T stop) : _start(start), _stop(stop) {}
EnumSeqIterator<T> begin() { return EnumSeqIterator(_start); }
EnumSeqIterator<T> end() { return EnumSeqIterator(_stop); }
Iterator begin() { return Iterator(_start); }
Iterator end() { return Iterator(_stop); }
};
// This is not a fully generic implementation; its current use cases only require for-loop behavior.
// We also assume that all iterators have the same length.
template<typename... Iters>
class Zip {
std::tuple<Iters...> _iters;
template<typename... Ts>
class ZipIterator {
std::tuple<Ts...> _iters;
public:
explicit Zip(std::tuple<Iters...> &&iters) : _iters(iters) {}
explicit ZipIterator(std::tuple<Ts...> &&iters) : _iters(iters) {}
Zip &operator++() {
ZipIterator &operator++() {
std::apply([](auto &&...it) { (++it, ...); }, _iters);
return *this;
}
@@ -62,26 +56,24 @@ public:
);
}
friend auto operator==(Zip const &lhs, Zip const &rhs) {
return std::get<0>(lhs._iters) == std::get<0>(rhs._iters);
bool operator==(ZipIterator const &rhs) const {
return std::get<0>(_iters) == std::get<0>(rhs._iters);
}
friend auto operator!=(Zip const &lhs, Zip const &rhs) {
return std::get<0>(lhs._iters) != std::get<0>(rhs._iters);
bool operator!=(ZipIterator const &rhs) const {
return std::get<0>(_iters) != std::get<0>(rhs._iters);
}
};
namespace detail {
template<typename... Containers>
template<typename... Ts>
class ZipContainer {
std::tuple<Containers...> _containers;
std::tuple<Ts...> _containers;
public:
explicit ZipContainer(Containers &&...containers)
: _containers(std::forward<Containers>(containers)...) {}
explicit ZipContainer(Ts &&...containers) : _containers(std::forward<Ts>(containers)...) {}
auto begin() {
return Zip(std::apply(
return ZipIterator(std::apply(
[](auto &&...containers) {
using std::begin;
return std::make_tuple(begin(containers)...);
@@ -91,7 +83,7 @@ public:
}
auto end() {
return Zip(std::apply(
return ZipIterator(std::apply(
[](auto &&...containers) {
using std::end;
return std::make_tuple(end(containers)...);
@@ -105,12 +97,11 @@ public:
template<typename T>
using Holder = std::
conditional_t<std::is_lvalue_reference_v<T>, T, std::remove_cv_t<std::remove_reference_t<T>>>;
} // namespace detail
// Does the same number of iterations as the first container's iterator!
template<typename... Containers>
static constexpr auto zip(Containers &&...cs) {
return detail::ZipContainer<detail::Holder<Containers>...>(std::forward<Containers>(cs)...);
template<typename... Ts>
static constexpr auto zip(Ts &&...cs) {
return ZipContainer<Holder<Ts>...>(std::forward<Ts>(cs)...);
}
#endif // RGBDS_ITERTOOLS_HPP

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: MIT */
// SPDX-License-Identifier: MIT
// platform-specific hacks
@@ -56,4 +56,9 @@
#define setmode(fd, mode) (0)
#endif
// MingGW and Cygwin need POSIX functions which are not standard C explicitly enabled
#if defined(__MINGW32__) || defined(__CYGWIN__)
#define _POSIX_C_SOURCE 200809L
#endif
#endif // RGBDS_PLATFORM_HPP

View File

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

View File

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

File diff suppressed because it is too large Load Diff

445
man/rgbasm-old.5 Normal file
View File

@@ -0,0 +1,445 @@
'\" e
.\"
.\" SPDX-License-Identifier: MIT
.\"
.Dd February 2, 2025
.Dt RGBASM-OLD 5
.Os
.Sh NAME
.Nm rgbasm-old
.Nd obsolete language documentation
.Sh DESCRIPTION
This is the list of features that have been removed from the
.Xr rgbasm 5
assembly language over its decades of evolution, along with their modern alternatives.
Its goal is to be a reference for backwards incompatibility, when upgrading an old assembly codebase to work with the latest RGBDS release.
It does
.Em not
attempt to list every syntax bug that was ever fixed (with some notable exceptions), nor new reserved keywords that may conflict with old identifiers.
.Sh REMOVED
These are features which have been completely removed, without any direct alternatives.
Usually these features were limiting the addition of other features, or had awkward limits on their own intended effects.
.Ss Automatic LD to LDH conversion (rgbasm -l)
Deprecated in 0.7.0, removed in 0.8.0.
.Pp
.Xr rgbasm 1
used to automatically treat
.Ql LD
as
.Ql LDH
if the address was known to be in the
.Ad $FF00-$FFFF
range, with the
.Fl L
flag to opt out.
.Xr rgbasm 1
0.6.0 added a
.Fl l
flag to opt in instead.
.Pp
Instead, use
.Ql LDH ,
and remove the
.Fl L
and
.Fl l
flags from
.Xr rgbasm 1 .
.Ss Automatic NOP after HALT (rgbasm -H)
Deprecated in 0.7.0, removed in 0.8.0.
.Pp
.Xr rgbasm 1
used to automatically insert a
.Ql NOP
after
.Ql HALT ,
with the
.Fl h
flag to opt out.
.Xr rgbasm 1
0.6.0 added a
.Fl H
flag to opt in instead.
.Pp
Instead, use an explicit
.Ql NOP
after
.Ql HALT ,
and remove the
.Fl h
and
.Fl H
flags from
.Xr rgbasm 1 .
.Ss Nested macro definitions
Removed in 0.4.2.
.Pp
Instead, put the nested macro definition inside a quoted string (making sure that none of its lines start with
.Ic ENDM ) ,
then interpolate that string.
For example:
.Bd -literal -offset indent
MACRO outer
DEF definition EQUS """
MACRO inner
println (\e1) - (\e\e1)
\enENDM"""
{definition}
PURGE definition
ENDM
outer 10
inner 3 ; prints 7
.Ed
.Ss Negative DS
Removed in 0.3.2.
.Pp
This was used to "rewind" the value of
.Ic @
in RAM sections, allowing labeled space allocations to overlap.
.Pp
Instead, use
.Ic UNION .
.Ss __FILE__ and __LINE__
Deprecated in 0.6.0, removed in 0.7.0.
.Pp
Instead, use
.Ic WARN
or
.Ic FAIL
to print a complete trace of filenames and line numbers.
.Ss _PI
Deprecated in 0.5.0, removed in 0.6.0.
.Pp
Instead, use
.Ql 3.141592653 .
.Ss Treating multi-character strings as numbers
Deprecated in 0.9.0.
.Pp
Instead, use a multi-value
.Ic CHARMAP ,
or explicitly combine the values of individual characters.
.Ss rgbgfx -f/--fix and -F/--fix-and-save
Removed in 0.6.0.
.Pp
Instead, use
.Ql rgbgfx -c/--colors
to explicitly specify a color palette.
If using
.Ql -c embedded ,
arrange the PNG's indexed palette in a separate graphics editor.
.Ss rgbgfx -D/--debug
Removed in 0.6.0.
.Sh REPLACED
These are features whose syntax has been changed without affecting functionality.
They can generally be updated with a single search-and-replace.
.Ss Defining constants and variables without DEF
Deprecated in 0.7.0, removed in 0.8.0.
.Pp
.Ic EQU , EQUS , = , RB , RW ,
and
.Ic RL
definitions used to just start with the symbol name, but had to be typed in column 1.
.Pp
Instead, use
.Ic DEF
before constant and variable definitions.
Note that
.Ic EQUS
expansion does not occur for the symbol name, so you have to use explicit
.Ql {interpolation} .
.Ss Defining macros like labels
Deprecated in 0.6.0, removed in 0.7.0.
.Pp
Macros used to be defined as
.Ql name: MACRO .
.Pp
Instead, use
.Ql MACRO name .
Note that
.Ic EQUS
expansion does not occur for the macro name, so you have to use explicit
.Ql {interpolation} .
.Ss Defining variables with SET
Deprecated in 0.5.2, removed in 0.6.0.
.Pp
Variables used to be defined as
.Ql name SET value .
.Pp
Instead, use
.Ql DEF name = value .
.Ss Global labels without colons
Deprecated in 0.4.0, removed in 0.5.0.
.Pp
Labels used to be definable with just a name, but had to be typed in column 1.
.Pp
Instead, use explicit colons; for example,
.Ql Label:
or exported
.Ql Label:: .
.Ss '\e,' in strings within macro arguments
Deprecated in 0.5.0, removed in 0.7.0.
.Pp
Macro arguments now handle quoted strings and parenthesized expressions as single arguments, so commas inside them are not argument separators and do not need escaping.
.Pp
Instead, just use commas without backslashes.
.Ss '*' comments
Deprecated in 0.4.1, removed in 0.5.0.
.Pp
These comments had to have the
.Ql *
typed in column 1.
.Pp
Instead, use
.Ql \&;
comments.
.Ss PRINTT, PRINTI, PRINTV, and PRINTF
Deprecated in 0.5.0, removed in 0.6.0.
.Pp
These directives were each specific to one type of value.
.Pp
Instead, use
.Ic PRINT
and
.Ic PRINTLN ,
with
.Ic STRFMT
or
.Ql {interpolation}
for type-specific formatting.
.Ss IMPORT and XREF
Removed in 0.4.0.
.Pp
Symbols are now automatically resolved if they were exported from elsewhere.
.Pp
Instead, just remove these directives.
.Ss GLOBAL and XDEF
Deprecated in 0.4.2, removed in 0.5.0.
.Pp
Instead, use
.Ic EXPORT .
.Ss HOME, CODE, DATA, and BSS
Deprecated in 0.3.0, removed in 0.4.0.
.Pp
Instead of
.Ic HOME ,
use
.Ic ROM0 ;
instead of
.Ic CODE
and
.Ic DATA ,
use
.Ic ROMX ;
instead of
.Ic BSS ,
use
.Ic WRAM0 .
.Ss JP [HL]
Deprecated in 0.3.0, removed in 0.4.0.
.Pp
Instead, use
.Ql JP HL .
.Ss LDI A, HL and LDD A, HL
Deprecated in 0.3.0, removed in 0.4.0.
.Pp
Instead, use
.Ql LDI A, [HL]
and
.Ql LDD A, [HL]
(or
.Ql LD A, [HLI]
and
.Ql LD A, [HLD] ;
or
.Ql LD A, [HL+]
and
.Ql LD A, [HL-] ) .
.Ss LDIO
Deprecated in 0.9.0.
.Pp
Instead, use
.Ql LDH .
.Ss LD [C], A and LD A, [C]
Deprecated in 0.9.0.
.Pp
Instead, use
.Ql LDH [C], A
and
.Ql LDH A, [C] .
.Pp
Note that
.Ql LD [$FF00+C], A
and
.Ql LD A, [$FF00+C]
were also deprecated in 0.9.0, but were
.Em undeprecated
in 0.9.1.
.Ss LDH [n8], A and LDH A, [n8]
Deprecated in 0.9.0.
.Pp
.Ql LDH
used to treat "addresses" from
.Ad $00
to
.Ad $FF
as if they were the low byte of an address from
.Ad $FF00
to
.Ad $FFFF .
.Pp
Instead, use
.Ql LDH [n16], A
and
.Ql LDH A, [n16] .
.Ss LD HL, [SP + e8]
Deprecated in 0.3.0, removed in 0.4.0.
.Pp
Instead, use
.Ql LD HL, SP + e8 .
.Ss LDHL SP, e8
Supported in ASMotor, removed in RGBDS.
.Pp
Instead, use
.Ql LD HL, SP + e8 .
.Ss rgbasm -i
Deprecated in 0.6.0, removed in 0.8.0.
.Pp
Instead, use
.Fl I
or
.Fl -include .
.Ss rgbgfx -h
Removed in 0.6.0.
.Pp
Instead, use
.Fl Z
or
.Fl -columns .
.Ss rgbgfx --output-*
Deprecated in 0.7.0, removed in 0.8.0.
.Pp
Instead, use
.Fl -auto-* .
.Sh CHANGED
These are breaking changes that did not alter syntax, and so could not practically be deprecated.
.Ss Trigonometry function units
Changed in 0.6.0.
.Pp
Instead of dividing a circle into 65536.0 "binary degrees", it is now divided into 1.0 "turns".
.Pp
For example, previously we had:
.EQ
delim $$
.EN
.Bl -bullet -offset indent
.It
.Ql SIN(0.25) == 0.00002 ,
because 0.25 binary degrees = $0.25 / 65536.0$ turns = $0.000004 tau$ radians = $0.000008 pi$ radians, and $sin ( 0.000008 pi ) = 0.00002$
.It
.Ql SIN(16384.0) == 1.0 ,
because 16384.0 binary degrees = $16384.0 / 65536.0$ turns = $0.25 tau$ radians = $pi / 2$ radians, and $sin ( pi / 2 ) = 1$
.It
.Ql ASIN(1.0) == 16384.0
.El
.Pp
Instead, now we have:
.Bl -bullet -offset indent
.It
.Ql SIN(0.25) == 1.0 ,
because $0.25$ turns = $0.25 tau$ radians = $pi / 2$ radians, and $sin ( pi / 2 ) = 1$
.It
.Ql SIN(16384.0) == 0.0 ,
because $16384$ turns = $16384 tau$ radians = $32768 pi$ radians, and $sin ( 32768 pi ) = 0$
.It
.Ql ASIN(1.0) == 0.25
.El
.EQ
delim off
.EN
.Ss ** operator associativity
Changed in 0.9.0.
.Pp
Instead of being left-associative,
.Ql **
is now right-associative.
.Pp
Previously we had
.Ql p ** q ** r == (p ** q) ** r .
.Pp
Instead, now we have
.Ql p ** q ** r == p ** (q ** r) .
.Sh BUGS
These are misfeatures that may have been possible by mistake.
They do not get deprecated, just fixed.
.Ss Space between exported labels' colons
Fixed in 0.7.0.
.Pp
Labels with two colons used to ignore a space between them; for example,
.Ql Label:\ : .
.Pp
Instead, use
.Ql Label:: .
.Ss Space between label and colon
Fixed in 0.9.0.
.Pp
Space between a label and its colon(s) used to be ignored; for example,
.Ql Label\ :
and
.Ql Label\ :: .
Now they are treated as invocations of the
.Ql Label
macro with
.Ql \&:
and
.Ql ::
as arguments.
.Pp
Instead, use
.Ql Label:
and
.Ql Label:: .
.Ss ADD r16 with implicit first HL operand
Fixed in 0.5.0.
.Pp
For example,
.Ql ADD BC
used to be treated as
.Ql ADD HL, BC ,
and likewise for
.Ql DE ,
.Ql HL ,
and
.Ql SP .
.Pp
Instead, use an explicit first
.Ql HL
operand.
.Ss = instead of SET
Fixed in 0.4.0.
.Pp
The
.Ic =
operator used to be an alias for the
.Ic SET
keyword, which included using
.Ic =
for the
.Ic SET
.Em instruction .
.Pp
Instead, just use
.Ic SET
for the instruction.
.Sh SEE ALSO
.Xr rgbasm 1 ,
.Xr gbz80 7 ,
.Xr rgbds 5 ,
.Xr rgbds 7
.Sh HISTORY
.Xr rgbasm 1
was originally written by
.An Carsten S\(/orensen
as part of the ASMotor package, and was later repackaged in RGBDS by
.An Justin Lloyd .
It is now maintained by a number of contributors at
.Lk https://github.com/gbdev/rgbds .

View File

@@ -1,6 +1,6 @@
.\" SPDX-License-Identifier: MIT
.\"
.Dd September 18, 2024
.Dd February 2, 2025
.Dt RGBASM 1
.Os
.Sh NAME
@@ -8,7 +8,7 @@
.Nd Game Boy assembler
.Sh SYNOPSIS
.Nm
.Op Fl EVvw
.Op Fl EhVvw
.Op Fl b Ar chars
.Op Fl D Ar name Ns Op = Ns Ar value
.Op Fl g Ar chars
@@ -67,6 +67,8 @@ Export all labels, including unreferenced and local labels.
.It Fl g Ar chars , Fl \-gfx-chars Ar chars
Change the four characters used for gfx constants.
The defaults are 0123.
.It Fl h , Fl \-help
Print help text for the program and exit.
.It Fl I Ar path , Fl \-include Ar path
Add a new
.Dq include path ;
@@ -200,7 +202,7 @@ section for a list of warnings.
Disable all warning output, even when turned into errors.
.It Fl X Ar max_errors , Fl \-max-errors Ar max_errors
If more than this number of errors (not warnings) occur, then abort the assembly process;
.Fl X 0
.Fl X Ar 0
disables this behavior.
The default is 100 if
.Nm
@@ -212,13 +214,19 @@ The following options alter the way warnings are processed.
.Bl -tag -width Ds
.It Fl Werror
Make all warnings into errors.
This can be negated as
.Fl Wno-error
to prevent turning all warnings into errors.
.It Fl Werror=
Make the specified warning into an error.
Make the specified warning or meta warning into an error.
A warning's name is appended
.Pq example: Fl Werror=obsolete ,
and this warning is implicitly enabled and turned into an error.
This is an error if used with a meta warning, such as
.Fl Werror=all .
This can be negated as
.Fl Wno-error=
to prevent turning a specified warning into an error, even if
.Fl Werror
is in effect.
.El
.Pp
The following warnings are
@@ -240,6 +248,10 @@ Note that each of these flag also has a negation (for example,
.Fl Wcharmap-redef
enables the warning that
.Fl Wno-charmap-redef
disables; and
.Fl Wall
enables every warning that
.Fl Wno-all
disables).
Only the non-default flag is listed here.
Ignoring the
@@ -291,6 +303,13 @@ This warning is enabled by
Warn when shifting macro arguments past their limits.
This warning is enabled by
.Fl Wextra .
.It Fl Wno-nested-comment
Warn when the block comment start sequence
.Ql /*
is found inside of a block comment.
Block comments cannot be nested, so the first
.Ql */
will end the whole comment.
.It Fl Wno-obsolete
Warn when obsolete constructs such as the
.Ic _PI
@@ -344,7 +363,7 @@ also warns when an N-bit value is less than -2**(N-1), which will not fit in two
Warn when a character goes through charmap conversion but has no defined mapping.
.Fl Wunmapped-char=0
or
.Fl Wunmapped-char
.Fl Wno-unmapped-char
disables this warning.
.Fl Wunmapped-char=1
or just
@@ -353,6 +372,24 @@ only warns if the active charmap is not empty.
.Fl Wunmapped-char=2
warns if the active charmap is empty, and/or is not the default charmap
.Sq main .
.It Fl Wunmatched-directive
Warn when a
.Ic PUSHC , PUSHO ,
or
.Ic PUSHS
directive does not have a corresponding
.Ic POPC , POPO ,
or
.Ic POPS .
This warning is enabled by
.Fl Wextra .
.It Fl Wunterminated-load
Warn when a
.Ic LOAD
block is not terminated by an
.Ic ENDL .
This warning is enabled by
.Fl Wextra .
.It Fl Wno-user
Warn when the
.Ic WARN
@@ -392,6 +429,7 @@ Please report bugs on
.Xr rgbfix 1 ,
.Xr rgbgfx 1 ,
.Xr gbz80 7 ,
.Xr rgbasm-old 5 ,
.Xr rgbds 5 ,
.Xr rgbds 7
.Sh HISTORY

View File

@@ -2,7 +2,7 @@
.\"
.\" SPDX-License-Identifier: MIT
.\"
.Dd September 18, 2024
.Dd February 2, 2025
.Dt RGBASM 5
.Os
.Sh NAME
@@ -41,7 +41,6 @@ or
Labels tie a name to a specific location within a section (see
.Sx Labels
below).
They must come first in the line.
.Pp
Instructions are assembled into Game Boy opcodes.
Multiple instructions on one line can be separated by double colons
@@ -254,7 +253,7 @@ Although, for these examples,
.Ic STRFMT
would be more appropriate; see
.Sx String expressions
further below.
below.
.Sh EXPRESSIONS
An expression can be composed of many things.
Numeric expressions are always evaluated using signed 32-bit math.
@@ -268,21 +267,21 @@ This is generally always the case, unless a label is involved, as explained in t
section.
However, some operators can be constant even with non-constant operands, as explained in
.Sx Operators
further below.
below.
.Pp
The instructions in the macro-language generally require constant expressions.
.Ss Numeric formats
There are a number of numeric formats.
.Bl -column -offset indent "Precise fixed-point" "Prefix"
.It Sy Format type Ta Sy Prefix Ta Sy Accepted characters
.It Hexadecimal Ta $ Ta 0123456789ABCDEF
.Bl -column -offset indent "Precise fixed-point" "Possible prefixes"
.It Sy Format type Ta Sy Possible prefixes Ta Sy Accepted characters
.It Decimal Ta none Ta 0123456789
.It Octal Ta & Ta 01234567
.It Binary Ta % Ta 01
.It Hexadecimal Ta Li $ , 0x , 0X Ta 0123456789ABCDEF
.It Octal Ta Li & , 0o , 0O Ta 01234567
.It Binary Ta Li % , 0b , 0B Ta 01
.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 Game Boy graphics Ta \` Ta 0123
.It Game Boy graphics Ta Li \` Ta 0123
.El
.Pp
Underscores are also accepted in numbers, except at the beginning of one.
@@ -310,26 +309,35 @@ is equivalent to
.Pp
You can also use symbols, which are implicitly replaced with their value.
.Ss Operators
A great number of operators you can use in expressions are available (listed from highest to lowest precedence):
You can use these operators in numeric expressions (listed from highest to lowest precedence):
.Bl -column -offset indent "!= == <= >= < >"
.It Sy Operator Ta Sy Meaning
.It Li \&( \&) Ta Precedence override
.It Li \&( \&) Ta Grouping
.It Li FUNC() Ta Built-in function call
.It Li ** Ta Exponent
.It Li ~ + - Ta Unary complement/plus/minus
.It Li * / % Ta Multiply/divide/modulo
.It Li << Ta Shift left
.It Li >> Ta Signed shift right (sign-extension)
.It Li >>> Ta Unsigned shift right (zero-extension)
.It Li & \&| ^ Ta Binary and/or/xor
.It Li + - Ta Add/subtract
.It Li != == <= >= < > Ta Comparison
.It Li && || Ta Boolean and/or
.It Li \&! Ta Unary not
.It Li ** Ta Exponentiation
.It Li + - ~ \&! Ta Unary plus, minus (negation), complement (bitwise negation), and Boolean negation
.It Li * / % Ta Multiplication, division, and modulo (remainder)
.It Li << >> >>> Ta Bit shifts (left, sign-extended right, zero-extended right)
.It Li & \&| ^ Ta Bitwise AND/OR/XOR
.It Li + - Ta Addition and subtraction
.It Li == != < > <= >= Ta Comparisons
.It Li && Ta Boolean AND
.It Li || Ta Boolean OR
.El
.Pp
.Sq **
raises a number to a non-negative power. It is the only
.Em right-associative
operator, meaning that
.Ql p ** q ** r
is equal to
.Ql p ** (q ** r) ,
not
.Ql (p ** q) ** r .
All other binary operators are left-associative.
.Pp
.Sq ~
complements a value by inverting all its bits.
complements a value by inverting all 32 of its bits.
.Pp
.Sq %
is used to get the remainder of the corresponding division, so that
@@ -379,8 +387,8 @@ Even a non-constant operand with any non-zero bits will return 0.
Besides operators, there are also some functions which have more specialized uses.
.Bl -column "BITWIDTH(n)"
.It Sy Name Ta Sy Operation
.It Fn HIGH n Ta Equivalent to Ql Ar n No & $FF .
.It Fn LOW n Ta Equivalent to Ql Po Ns Ar n No & $FF00 Pc >> 8 .
.It Fn HIGH n Ta Equivalent to Ql Po Ns Ar n No & $FF00 Pc >> 8 .
.It Fn LOW n Ta Equivalent to Ql Ar n No & $FF .
.EQ
delim $$
.EN
@@ -904,7 +912,7 @@ SECTION "LOAD example", ROMX
CopyCode:
ld de, RAMCode
ld hl, RAMLocation
ld c, RAMLocation.end - RAMLocation
ld c, RAMCode.end - RAMCode
\&.loop
ld a, [de]
inc de
@@ -928,8 +936,8 @@ RAMLocation:
\&.string
db "Hello World!\e0"
\&.end
ENDL
\&.end
.Ed
.Pp
A
@@ -939,7 +947,9 @@ block feels similar to a
declaration because it creates a new one.
All data and code generated within such a block is placed in the current section like usual, but all labels are created as if they were placed in this newly-created section.
.Pp
In the example above, all of the code and data will end up in the "LOAD example" section.
In the example above, all of the code and data will end up in the
.Dq LOAD example
section.
You will notice the
.Sq RAMCode
and
@@ -951,15 +961,30 @@ You cannot nest
.Ic LOAD
blocks, nor can you change or stop the current section within them.
.Pp
The current
.Ic LOAD
block can be ended by using
.Ic ENDL .
This directive is only necessary if you want to resume writing code in its containing ROM section.
Any of
.Ic LOAD , SECTION , ENDSECTION ,
or
.Ic POPS
will end the current
.Ic LOAD
block before performing its own function.
.Pp
.Ic LOAD
blocks can use the
.Ic UNION
or
.Ic FRAGMENT
modifiers, as described below.
modifiers as described in
.Sx Unionized sections
below.
.Ss Unionized sections
When you're tight on RAM, you may want to define overlapping static memory allocations, as explained in the
.Sx Unions
.Sx Allocating overlapping spaces in RAM
section.
However, a
.Ic UNION
@@ -1000,7 +1025,7 @@ or
.El
.Pp
Different declarations of the same unionized section are not appended, but instead overlaid on top of each other, just like
.Sx Unions .
.Sx Allocating overlapping spaces in RAM .
Similarly, the size of an unionized section is the largest of all its declarations.
.Ss Section fragments
Section fragments are sections with a small twist: when several of the same name are encountered, they are concatenated instead of producing an error.
@@ -1123,7 +1148,7 @@ otherwise, it is said to be
.Dq exported ,
explained in
.Sx Exporting and importing symbols
further below).
below).
More than one dot in label names is not allowed.
.Pp
For convenience, local labels can use a shorthand syntax: when a symbol name starting with a dot is found (for example, inside an expression, or when declaring a label), then the current
@@ -1553,47 +1578,6 @@ environment variable if that is defined as a UNIX timestamp.
Refer to the spec at
.Lk https://reproducible-builds.org/docs/source-date-epoch/ reproducible-builds.org .
.Sh DEFINING DATA
.Ss Statically allocating space in RAM
.Ic DS
statically allocates a number of empty bytes.
This is the preferred method of allocating space in a RAM section.
You can also use
.Ic DB , DW
and
.Ic DL
without any arguments instead (see
.Sx Defining constant data in ROM
below).
.Bd -literal -offset indent
DS 42 ;\ Allocates 42 bytes
.Ed
.Pp
Empty space in RAM sections will not be initialized.
In ROM sections, it will be filled with the value passed to the
.Fl p
command-line option, except when using overlays with
.Fl O .
.Pp
Instead of an exact number of bytes, you can specify
.Ic ALIGN Ns Bq Ar align , offset
to allocate however many bytes are required to align the subsequent data.
Thus,
.Sq Ic DS ALIGN Ns Bo Ar align , offset Bc , No ...
is equivalent to
.Sq Ic DS Ar n , No ...
followed by
.Sq Ic ALIGN Ns Bq Ar align , offset ,
where
.Ar n
is the minimum value needed to satisfy the
.Ic ALIGN
constraint (see
.Sx Requesting alignment
below).
Note that
.Ic ALIGN Ns Bq Ar align
is a shorthand for
.Ic ALIGN Ns Bq Ar align , No 0 .
.Ss Defining constant data in ROM
.Ic DB
defines a list of bytes that will be stored in the final image.
@@ -1660,7 +1644,7 @@ can be used in a
/
.Ic SRAM
section.
.Ss Including binary files
.Ss Including binary data files
You probably have some graphics, level data, etc. you'd like to include.
Use
.Ic INCBIN
@@ -1684,7 +1668,48 @@ INCBIN "data.bin", 78, 256
.Pp
The length argument is optional.
If only the start position is specified, the bytes from the start position until the end of the file will be included.
.Ss Unions
.Ss Statically allocating space in RAM
.Ic DS
statically allocates a number of empty bytes.
This is the preferred method of allocating space in a RAM section.
You can also use
.Ic DB , DW
and
.Ic DL
without any arguments instead (see
.Sx Defining constant data in ROM
below).
.Bd -literal -offset indent
DS 42 ;\ Allocates 42 bytes
.Ed
.Pp
Empty space in RAM sections will not be initialized.
In ROM sections, it will be filled with the value passed to the
.Fl p
command-line option, except when using overlays with
.Fl O .
.Pp
Instead of an exact number of bytes, you can specify
.Ic ALIGN Ns Bq Ar align , offset
to allocate however many bytes are required to align the subsequent data.
Thus,
.Sq Ic DS ALIGN Ns Bo Ar align , offset Bc , No ...
is equivalent to
.Sq Ic DS Ar n , No ...
followed by
.Sq Ic ALIGN Ns Bq Ar align , offset ,
where
.Ar n
is the minimum value needed to satisfy the
.Ic ALIGN
constraint (see
.Sx Requesting alignment
below).
Note that
.Ic ALIGN Ns Bq Ar align
is a shorthand for
.Ic ALIGN Ns Bq Ar align , No 0 .
.Ss Allocating overlapping spaces in RAM
Unions allow multiple static memory allocations to overlap, like unions in C.
This does not increase the amount of memory available, but allows re-using the same memory region for different purposes.
.Pp
@@ -1745,6 +1770,37 @@ Unions may be used in any section, but they may only contain space-allocating di
.Ic DS
(see
.Sx Statically allocating space in RAM ) .
.Ss Requesting alignment
While
.Ic ALIGN
as presented in
.Sx SECTIONS
is often useful as-is, sometimes you instead want a particular piece of data (or code) in the middle of the section to be aligned.
This is made easier through the use of mid-section
.Ic ALIGN Ar align , offset .
It will retroactively alter the section's attributes to ensure that the location the
.Ic ALIGN
directive is at, has its
.Ar align
lower bits equal to
.Ar offset .
.Pp
If the constraint cannot be met (for example because the section is fixed at an incompatible address), an error is produced.
Note that
.Ic ALIGN Ar align
is a shorthand for
.Ic ALIGN Ar align , No 0 .
.Pp
There may be times when you don't just want to specify an alignment constraint at the current location, but also skip ahead until the constraint can be satisfied.
In that case, you can use
.Ic DS ALIGN Ns Bq Ar align , offset
to allocate however many bytes are required to align the subsequent data.
.Pp
If the constraint cannot be met by skipping any amount of space, an error is produced.
Note that
.Ic ALIGN Ns Bq Ar align
is a shorthand for
.Ic ALIGN Ns Bq Ar align , No 0 .
.Sh THE MACRO LANGUAGE
.Ss Invoking macros
A macro is invoked by using its name at the beginning of a line, like a directive, followed by any comma-separated arguments.
@@ -2283,9 +2339,9 @@ POPO
.Pp
.Ic OPT
can modify the options
.Cm b , g , p , Q ,
.Cm b , g , p , Q , r ,
and
.Cm r .
.Cm W .
.Pp
.Ic POPO
and
@@ -2306,37 +2362,6 @@ PUSHO b.X, g.oOX
DW `..ooOOXX
POPO
.Ed
.Ss Requesting alignment
While
.Ic ALIGN
as presented in
.Sx SECTIONS
is often useful as-is, sometimes you instead want a particular piece of data (or code) in the middle of the section to be aligned.
This is made easier through the use of mid-section
.Ic ALIGN Ar align , offset .
It will alter the section's attributes to ensure that the location the
.Ic ALIGN
directive is at, has its
.Ar align
lower bits equal to
.Ar offset .
.Pp
If the constraint cannot be met (for example because the section is fixed at an incompatible address), an error is produced.
Note that
.Ic ALIGN Ar align
is a shorthand for
.Ic ALIGN Ar align , No 0 .
.Pp
There may be times when you don't just want to specify an alignment constraint at the current location, but also skip ahead until the constraint can be satisfied.
In that case, you can use
.Ic DS ALIGN Ns Bq Ar align , offset
to allocate however many bytes are required to align the subsequent data.
.Pp
If the constraint cannot be met by skipping any amount of space, an error is produced.
Note that
.Ic ALIGN Ns Bq Ar align
is a shorthand for
.Ic ALIGN Ns Bq Ar align , No 0 .
.Sh SEE ALSO
.Xr rgbasm 1 ,
.Xr rgblink 1 ,
@@ -2344,6 +2369,7 @@ is a shorthand for
.Xr rgbfix 1 ,
.Xr rgbgfx 1 ,
.Xr gbz80 7 ,
.Xr rgbasm-old 5 ,
.Xr rgbds 5 ,
.Xr rgbds 7
.Sh HISTORY

View File

@@ -1,6 +1,6 @@
.\" SPDX-License-Identifier: MIT
.\"
.Dd September 18, 2024
.Dd February 2, 2025
.Dt RGBDS 5
.Os
.Sh NAME
@@ -254,7 +254,7 @@ Size of the
below.
.It Cm BYTE Ar RPNExpr Ns Bq RPNSize
The patch's value, encoded as a RPN expression
.Pq see Sx RPN EXPRESSIONS .
.Pq see Sx RPN expressions .
.El
.It Cm ENDR
.El
@@ -294,14 +294,14 @@ Size of the
below.
.It Cm BYTE Ar RPNExpr Ns Bq RPNSize
The patch's value, encoded as a RPN expression
.Pq see Sx RPN EXPRESSIONS .
.Pq see Sx RPN expressions .
.It Cm STRING Ar Message
The message displayed if the expression evaluates to a non-zero value.
If empty, a generic message is displayed instead.
.El
.It Cm ENDR
.El
.Ss RPN EXPRESSIONS
.Ss RPN expressions
Expressions in the object file are stored as RPN, or
.Dq Reverse Polish Notation ,
which is a notation that allows computing arbitrary expressions with just a simple stack.

View File

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

View File

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

View File

@@ -2,7 +2,7 @@
.\"
.\" SPDX-License-Identifier: MIT
.\"
.Dd September 18, 2024
.Dd February 2, 2025
.Dt RGBGFX 1
.Os
.Sh NAME
@@ -10,7 +10,7 @@
.Nd Game Boy graphics converter
.Sh SYNOPSIS
.Nm
.Op Fl CmOuVZ
.Op Fl CmhOuVXYZ
.Op Fl v Op Fl v No ...
.Op Fl a Ar attrmap | Fl A
.Op Fl b Ar base_ids
@@ -165,6 +165,8 @@ for a list of formats and their descriptions.
.It Fl d Ar depth , Fl \-depth Ar depth
Set the bit depth of the output tile data, in bits per pixel (bpp), either 1 or 2 (the default).
This changes how tile data is output, and the maximum number of colors per palette (2 and 4 respectively).
.It Fl h , Fl \-help
Print help text for the program and exit.
.It Fl i Ar input_tiles , Fl \-input-tileset Ar input_tiles
Use the specified input tiles in addition to having
.Nm
@@ -229,9 +231,8 @@ The second number pair specifies how many tiles to process horizontally and vert
.Pp
.Fl L Sy is ignored in reverse mode , No no padding is inserted .
.It Fl m , Fl \-mirror-tiles
Deduplicate tiles that are symmetrical mirror images of each other.
Deduplicate tiles that are horizontally and/or vertically symmetrical mirror images of each other.
Only one of each unique tile will be saved in the tile data file, with mirror images counting as duplicates.
Tiles are checked for horizontal, vertical, and horizontal-vertical mirroring.
Useful with a tile map and attribute map together (see
.Fl a
and
@@ -239,6 +240,8 @@ and
to keep track of the duplicated tiles and the dimension(s) mirrored.
Implies
.Fl u .
Equivalent to
.Fl XY .
.It Fl N Ar nb_tiles , Fl \-nb-tiles Ar nb_tiles
Set a maximum number of tiles that can be placed in each VRAM bank.
.Ar nb_tiles
@@ -353,6 +356,10 @@ Some internal debug printing is enabled.
The verbosity level does not go past 6.
.Pp
Note that verbose output is only intended to be consumed by humans, and may change without notice between RGBDS releases; relying on those for scripts is not advised.
.It Fl X , Fl \-mirror-x
Deduplicate tiles that are horizontally symmetrical mirror images of each other across the X axis.
Implies
.Fl u .
.It Fl x Ar quantity , Fl \-trim-end Ar quantity
Do not output the last
.Ar quantity
@@ -373,6 +380,10 @@ was enabled, so you probably don't want to use this option in combination with
Note also that the tiles that don't get output will not count towards
.Fl N Ap s
limit.
.It Fl Y , Fl \-mirror-y
Deduplicate tiles that are vertically symmetrical mirror images of each other across the Y axis.
Implies
.Fl u .
.It Fl Z , Fl \-columns
Read squares from the PNG in column-major order (column by column), instead of the default row-major order (line by line).
This primarily affects tile map and attribute map output, although it may also change generated tile data and palettes.

View File

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

View File

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

View File

@@ -91,10 +91,11 @@ foreach(PROG "asm" "fix" "gfx" "link")
${rgb${PROG}_src}
${common_src}
)
if(SUFFIX)
set_target_properties(rgb${PROG} PROPERTIES SUFFIX ${SUFFIX})
endif()
install(TARGETS rgb${PROG} RUNTIME DESTINATION bin)
# Required to run tests
set_target_properties(rgb${PROG} PROPERTIES
# hack for MSVC: no-op generator expression to stop generation of "per-configuration subdirectory"
RUNTIME_OUTPUT_DIRECTORY $<1:${CMAKE_SOURCE_DIR}>)
endforeach()
if(LIBPNG_FOUND) # pkg-config

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: MIT */
// SPDX-License-Identifier: MIT
#include <ctype.h>
#include <errno.h>
@@ -13,8 +13,6 @@
#include "asm/section.hpp"
#include "asm/warning.hpp"
static constexpr size_t numWarningStates = sizeof(warningStates);
struct OptStackEntry {
char binary[2];
char gbgfx[4];
@@ -22,7 +20,7 @@ struct OptStackEntry {
uint8_t fillByte;
bool warningsAreErrors;
size_t maxRecursionDepth;
WarningState warningStates[numWarningStates];
Diagnostics warningStates;
};
static std::stack<OptStackEntry> stack;
@@ -55,17 +53,19 @@ void opt_W(char const *flag) {
void opt_Parse(char const *s) {
switch (s[0]) {
case 'b':
if (strlen(&s[1]) == 2)
if (strlen(&s[1]) == 2) {
opt_B(&s[1]);
else
} else {
error("Must specify exactly 2 characters for option 'b'\n");
}
break;
case 'g':
if (strlen(&s[1]) == 4)
if (strlen(&s[1]) == 4) {
opt_G(&s[1]);
else
} else {
error("Must specify exactly 4 characters for option 'g'\n");
}
break;
case 'p':
@@ -74,12 +74,13 @@ void opt_Parse(char const *s) {
unsigned int padByte;
result = sscanf(&s[1], "%x", &padByte);
if (result != 1)
if (result != 1) {
error("Invalid argument for option 'p'\n");
else if (padByte > 0xFF)
} else if (padByte > 0xFF) {
error("Argument for option 'p' must be between 0 and 0xFF\n");
else
} else {
opt_P(padByte);
}
} else {
error("Invalid argument for option 'p'\n");
}
@@ -88,19 +89,21 @@ void opt_Parse(char const *s) {
char const *precisionArg;
case 'Q':
precisionArg = &s[1];
if (precisionArg[0] == '.')
if (precisionArg[0] == '.') {
precisionArg++;
}
if (strlen(precisionArg) <= 2) {
int result;
unsigned int precision;
result = sscanf(precisionArg, "%u", &precision);
if (result != 1)
if (result != 1) {
error("Invalid argument for option 'Q'\n");
else if (precision < 1 || precision > 31)
} else if (precision < 1 || precision > 31) {
error("Argument for option 'Q' must be between 1 and 31\n");
else
} else {
opt_Q(precision);
}
} else {
error("Invalid argument for option 'Q'\n");
}
@@ -108,8 +111,9 @@ void opt_Parse(char const *s) {
case 'r': {
++s; // Skip 'r'
while (isblank(*s))
while (isblank(*s)) {
++s; // Skip leading whitespace
}
if (s[0] == '\0') {
error("Missing argument to option 'r'\n");
@@ -130,10 +134,11 @@ void opt_Parse(char const *s) {
}
case 'W':
if (strlen(&s[1]) > 0)
if (strlen(&s[1]) > 0) {
opt_W(&s[1]);
else
} else {
error("Must specify an argument for option 'W'\n");
}
break;
default:
@@ -160,7 +165,7 @@ void opt_Push() {
// Both of these pulled from warning.hpp
entry.warningsAreErrors = warningsAreErrors;
memcpy(entry.warningStates, warningStates, numWarningStates);
entry.warningStates = warningStates;
entry.maxRecursionDepth = maxRecursionDepth; // Pulled from fstack.h
@@ -184,5 +189,11 @@ void opt_Pop() {
// opt_W does not apply a whole warning state; it processes one flag string
warningsAreErrors = entry.warningsAreErrors;
memcpy(warningStates, entry.warningStates, numWarningStates);
warningStates = entry.warningStates;
}
void opt_CheckStack() {
if (!stack.empty()) {
warning(WARNING_UNMATCHED_DIRECTIVE, "`PUSHO` without corresponding `POPO`\n");
}
}

View File

@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: MIT */
// SPDX-License-Identifier: MIT
#include "asm/output.hpp"
@@ -12,6 +12,7 @@
#include "error.hpp"
#include "helpers.hpp" // assume, Defer
#include "platform.hpp"
#include "asm/charmap.hpp"
#include "asm/fstack.hpp"
@@ -39,10 +40,10 @@ static std::deque<std::shared_ptr<FileStackNode>> fileStackNodes;
static void putLong(uint32_t n, FILE *file) {
uint8_t bytes[] = {
(uint8_t)n,
(uint8_t)(n >> 8),
(uint8_t)(n >> 16),
(uint8_t)(n >> 24),
static_cast<uint8_t>(n),
static_cast<uint8_t>(n >> 8),
static_cast<uint8_t>(n >> 16),
static_cast<uint8_t>(n >> 24),
};
fwrite(bytes, 1, sizeof(bytes), file);
}
@@ -54,25 +55,27 @@ static void putString(std::string const &s, FILE *file) {
void out_RegisterNode(std::shared_ptr<FileStackNode> node) {
// If node is not already registered, register it (and parents), and give it a unique ID
for (; node && node->ID == (uint32_t)-1; node = node->parent) {
for (; node && node->ID == UINT32_MAX; node = node->parent) {
node->ID = fileStackNodes.size();
fileStackNodes.push_front(node);
}
}
// Return a section's ID, or -1 if the section is not in the list
// Return a section's ID, or UINT32_MAX if the section is not in the list
static uint32_t getSectIDIfAny(Section *sect) {
if (!sect)
return (uint32_t)-1;
if (!sect) {
return UINT32_MAX;
}
if (auto search = sectionMap.find(sect->name); search != sectionMap.end())
return (uint32_t)(sectionMap.size() - search->second - 1);
if (auto search = sectionMap.find(sect->name); search != sectionMap.end()) {
return static_cast<uint32_t>(search->second);
}
fatalerror("Unknown section '%s'\n", sect->name.c_str());
}
static void writePatch(Patch const &patch, FILE *file) {
assume(patch.src->ID != (uint32_t)-1);
assume(patch.src->ID != UINT32_MAX);
putLong(patch.src->ID, file);
putLong(patch.lineNo, file);
@@ -85,7 +88,7 @@ static void writePatch(Patch const &patch, FILE *file) {
}
static void writeSection(Section const &sect, FILE *file) {
assume(sect.src->ID != (uint32_t)-1);
assume(sect.src->ID != UINT32_MAX);
putString(sect.name, file);
@@ -108,8 +111,9 @@ static void writeSection(Section const &sect, FILE *file) {
fwrite(sect.data.data(), 1, sect.size, file);
putLong(sect.patches.size(), file);
for (Patch const &patch : sect.patches)
for (Patch const &patch : sect.patches) {
writePatch(patch, file);
}
}
}
@@ -118,7 +122,7 @@ static void writeSymbol(Symbol const &sym, FILE *file) {
if (!sym.isDefined()) {
putc(SYMTYPE_IMPORT, file);
} else {
assume(sym.src->ID != (uint32_t)-1);
assume(sym.src->ID != UINT32_MAX);
putc(sym.isExported ? SYMTYPE_EXPORT : SYMTYPE_LOCAL, file);
putLong(sym.src->ID, file);
@@ -130,7 +134,7 @@ static void writeSymbol(Symbol const &sym, FILE *file) {
static void registerUnregisteredSymbol(Symbol &sym) {
// Check for `sym.src`, to skip any built-in symbol from rgbasm
if (sym.src && sym.ID == (uint32_t)-1 && !sym_IsPC(&sym)) {
if (sym.src && sym.ID == UINT32_MAX && !sym_IsPC(&sym)) {
sym.ID = objectSymbols.size(); // Set the symbol's ID within the object file
objectSymbols.push_back(&sym);
out_RegisterNode(sym.src);
@@ -161,8 +165,9 @@ static void writeRpn(std::vector<uint8_t> &rpnexpr, std::vector<uint8_t> const &
symName.clear();
for (;;) {
uint8_t c = rpn[offset++];
if (c == 0)
if (c == 0) {
break;
}
symName += c;
}
@@ -187,8 +192,9 @@ static void writeRpn(std::vector<uint8_t> &rpnexpr, std::vector<uint8_t> const &
symName.clear();
for (;;) {
uint8_t c = rpn[offset++];
if (c == 0)
if (c == 0) {
break;
}
symName += c;
}
@@ -281,13 +287,13 @@ void out_CreateAssert(
assertion.message = message;
}
static void writeAssert(Assertion &assert, FILE *file) {
static void writeAssert(Assertion const &assert, FILE *file) {
writePatch(assert.patch, file);
putString(assert.message, file);
}
static void writeFileStackNode(FileStackNode const &node, FILE *file) {
putLong(node.parent ? node.parent->ID : (uint32_t)-1, file);
putLong(node.parent ? node.parent->ID : UINT32_MAX, file);
putLong(node.lineNo, file);
putc(node.type, file);
if (node.type != NODE_REPT) {
@@ -297,30 +303,34 @@ static void writeFileStackNode(FileStackNode const &node, FILE *file) {
putLong(nodeIters.size(), file);
// Iters are stored by decreasing depth, so reverse the order for output
for (uint32_t i = nodeIters.size(); i--;)
for (uint32_t i = nodeIters.size(); i--;) {
putLong(nodeIters[i], file);
}
}
}
void out_WriteObject() {
if (objectFileName.empty())
if (objectFileName.empty()) {
return;
}
FILE *file;
if (objectFileName != "-") {
file = fopen(objectFileName.c_str(), "wb");
} else {
objectFileName = "<stdout>";
file = fdopen(STDOUT_FILENO, "wb");
(void)setmode(STDOUT_FILENO, O_BINARY);
file = stdout;
}
if (!file)
if (!file) {
err("Failed to open object file '%s'", objectFileName.c_str());
}
Defer closeFile{[&] { fclose(file); }};
// Also write symbols that weren't written above
sym_ForEach(registerUnregisteredSymbol);
fprintf(file, RGBDS_OBJECT_VERSION_STRING);
fputs(RGBDS_OBJECT_VERSION_STRING, file);
putLong(RGBDS_OBJECT_REV, file);
putLong(objectSymbols.size(), file);
@@ -333,33 +343,39 @@ void out_WriteObject() {
writeFileStackNode(node, file);
// The list is supposed to have decrementing IDs
if (it + 1 != fileStackNodes.end() && it[1]->ID != node.ID - 1)
if (it + 1 != fileStackNodes.end() && it[1]->ID != node.ID - 1) {
fatalerror(
"Internal error: fstack node #%" PRIu32 " follows #%" PRIu32
". Please report this to the developers!\n",
it[1]->ID,
node.ID
);
}
}
for (Symbol const *sym : objectSymbols)
for (Symbol const *sym : objectSymbols) {
writeSymbol(*sym, file);
}
for (auto it = sectionList.rbegin(); it != sectionList.rend(); it++)
writeSection(*it, file);
for (Section const &sect : sectionList) {
writeSection(sect, file);
}
putLong(assertions.size(), file);
for (Assertion &assert : assertions)
for (Assertion const &assert : assertions) {
writeAssert(assert, file);
}
}
void out_SetFileName(std::string const &name) {
if (!objectFileName.empty())
if (!objectFileName.empty()) {
warnx("Overriding output filename %s", objectFileName.c_str());
}
objectFileName = name;
if (verbose)
if (verbose) {
printf("Output filename %s\n", objectFileName.c_str());
}
}
static void dumpString(std::string const &escape, FILE *file) {
@@ -395,8 +411,9 @@ static bool dumpEquConstants(FILE *file) {
equConstants.clear();
sym_ForEach([](Symbol &sym) {
if (!sym.isBuiltin && sym.type == SYM_EQU)
if (!sym.isBuiltin && sym.type == SYM_EQU) {
equConstants.push_back(&sym);
}
});
// Constants are ordered by file, then by definition order
std::sort(RANGE(equConstants), [](Symbol *sym1, Symbol *sym2) -> bool {
@@ -416,8 +433,9 @@ static bool dumpVariables(FILE *file) {
variables.clear();
sym_ForEach([](Symbol &sym) {
if (!sym.isBuiltin && sym.type == SYM_VAR)
if (!sym.isBuiltin && sym.type == SYM_VAR) {
variables.push_back(&sym);
}
});
// Variables are ordered by file, then by definition order
std::sort(RANGE(variables), [](Symbol *sym1, Symbol *sym2) -> bool {
@@ -437,8 +455,9 @@ static bool dumpEqusConstants(FILE *file) {
equsConstants.clear();
sym_ForEach([](Symbol &sym) {
if (!sym.isBuiltin && sym.type == SYM_EQUS)
if (!sym.isBuiltin && sym.type == SYM_EQUS) {
equsConstants.push_back(&sym);
}
});
// Constants are ordered by file, then by definition order
std::sort(RANGE(equsConstants), [](Symbol *sym1, Symbol *sym2) -> bool {
@@ -465,8 +484,9 @@ static bool dumpCharmaps(FILE *file) {
fputs("charmap \"", charmapFile);
dumpString(mapping, charmapFile);
putc('"', charmapFile);
for (int32_t v : value)
for (int32_t v : value) {
fprintf(charmapFile, ", $%" PRIx32, v);
}
putc('\n', charmapFile);
}
);
@@ -477,8 +497,9 @@ static bool dumpMacros(FILE *file) {
macros.clear();
sym_ForEach([](Symbol &sym) {
if (!sym.isBuiltin && sym.type == SYM_MACRO)
if (!sym.isBuiltin && sym.type == SYM_MACRO) {
macros.push_back(&sym);
}
});
// Macros are ordered by file, then by definition order
std::sort(RANGE(macros), [](Symbol *sym1, Symbol *sym2) -> bool {
@@ -503,10 +524,12 @@ void out_WriteState(std::string name, std::vector<StateFeature> const &features)
file = fopen(name.c_str(), "wb");
} else {
name = "<stdout>";
file = fdopen(STDOUT_FILENO, "wb");
(void)setmode(STDOUT_FILENO, O_BINARY);
file = stdout;
}
if (!file)
if (!file) {
err("Failed to open state file '%s'", name.c_str());
}
Defer closeFile{[&] { fclose(file); }};
static char const *dumpHeadings[NB_STATE_FEATURES] = {
@@ -527,7 +550,8 @@ void out_WriteState(std::string name, std::vector<StateFeature> const &features)
fputs("; File generated by rgbasm\n", file);
for (StateFeature feature : features) {
fprintf(file, "\n; %s\n", dumpHeadings[feature]);
if (!dumpFuncs[feature](file))
if (!dumpFuncs[feature](file)) {
fprintf(file, "; No values\n");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: MIT */
// SPDX-License-Identifier: MIT
#include "asm/rpn.hpp"
@@ -46,17 +46,19 @@ int32_t Expression::getConstVal() const {
}
Symbol const *Expression::symbolOf() const {
if (!isSymbol)
if (!isSymbol) {
return nullptr;
return sym_FindScopedSymbol((char const *)&rpn[1]);
}
return sym_FindScopedSymbol(reinterpret_cast<char const *>(&rpn[1]));
}
bool Expression::isDiffConstant(Symbol const *sym) const {
// Check if both expressions only refer to a single symbol
Symbol const *sym1 = symbolOf();
if (!sym1 || !sym || sym1->type != SYM_LABEL || sym->type != SYM_LABEL)
if (!sym1 || !sym || sym1->type != SYM_LABEL || sym->type != SYM_LABEL) {
return false;
}
Section const *sect1 = sym1->getSection();
Section const *sect2 = sym->getSection();
@@ -65,7 +67,7 @@ bool Expression::isDiffConstant(Symbol const *sym) const {
void Expression::makeNumber(uint32_t value) {
clear();
data = (int32_t)value;
data = static_cast<int32_t>(value);
}
void Expression::makeSymbol(std::string const &symName) {
@@ -77,9 +79,9 @@ void Expression::makeSymbol(std::string const &symName) {
isSymbol = true;
data = sym_IsPC(sym) ? "PC is not constant at assembly time"
: sym_IsPurgedScoped(symName)
? "'"s + symName + "' is not constant at assembly time; it was purged"
: "'"s + symName + "' is not constant at assembly time";
: sym_IsPurgedScoped(symName)
? "'"s + symName + "' is not constant at assembly time; it was purged"
: "'"s + symName + "' is not constant at assembly time";
sym = sym_Ref(symName);
size_t nameLen = sym->name.length() + 1; // Don't forget NUL!
@@ -89,7 +91,7 @@ void Expression::makeSymbol(std::string const &symName) {
*ptr++ = RPN_SYM;
memcpy(ptr, sym->name.c_str(), nameLen);
} else {
data = (int32_t)sym_GetConstantValue(symName);
data = static_cast<int32_t>(sym_GetConstantValue(symName));
}
}
@@ -100,12 +102,12 @@ void Expression::makeBankSymbol(std::string const &symName) {
if (!currentSection) {
error("PC has no bank outside of a section\n");
data = 1;
} else if (currentSection->bank == (uint32_t)-1) {
} else if (currentSection->bank == UINT32_MAX) {
data = "Current section's bank is not known";
*reserveSpace(1) = RPN_BANK_SELF;
} else {
data = (int32_t)currentSection->bank;
data = static_cast<int32_t>(currentSection->bank);
}
return;
} else if (sym && !sym->isLabel()) {
@@ -115,13 +117,13 @@ void Expression::makeBankSymbol(std::string const &symName) {
sym = sym_Ref(symName);
assume(sym); // If the symbol didn't exist, it should have been created
if (sym->getSection() && sym->getSection()->bank != (uint32_t)-1) {
if (sym->getSection() && sym->getSection()->bank != UINT32_MAX) {
// Symbol's section is known and bank is fixed
data = (int32_t)sym->getSection()->bank;
data = static_cast<int32_t>(sym->getSection()->bank);
} else {
data = sym_IsPurgedScoped(symName)
? "\""s + symName + "\"'s bank is not known; it was purged"
: "\""s + symName + "\"'s bank is not known";
? "\""s + symName + "\"'s bank is not known; it was purged"
: "\""s + symName + "\"'s bank is not known";
size_t nameLen = sym->name.length() + 1; // Room for NUL!
@@ -135,8 +137,8 @@ void Expression::makeBankSymbol(std::string const &symName) {
void Expression::makeBankSection(std::string const &sectName) {
clear();
if (Section *sect = sect_FindSectionByName(sectName); sect && sect->bank != (uint32_t)-1) {
data = (int32_t)sect->bank;
if (Section *sect = sect_FindSectionByName(sectName); sect && sect->bank != UINT32_MAX) {
data = static_cast<int32_t>(sect->bank);
} else {
data = "Section \""s + sectName + "\"'s bank is not known";
@@ -151,7 +153,7 @@ void Expression::makeBankSection(std::string const &sectName) {
void Expression::makeSizeOfSection(std::string const &sectName) {
clear();
if (Section *sect = sect_FindSectionByName(sectName); sect && sect->isSizeKnown()) {
data = (int32_t)sect->size;
data = static_cast<int32_t>(sect->size);
} else {
data = "Section \""s + sectName + "\"'s size is not known";
@@ -165,8 +167,8 @@ void Expression::makeSizeOfSection(std::string const &sectName) {
void Expression::makeStartOfSection(std::string const &sectName) {
clear();
if (Section *sect = sect_FindSectionByName(sectName); sect && sect->org != (uint32_t)-1) {
data = (int32_t)sect->org;
if (Section *sect = sect_FindSectionByName(sectName); sect && sect->org != UINT32_MAX) {
data = static_cast<int32_t>(sect->org);
} else {
data = "Section \""s + sectName + "\"'s start is not known";
@@ -208,71 +210,68 @@ static bool tryConstNonzero(Expression const &lhs, Expression const &rhs) {
static bool tryConstLogNot(Expression const &expr) {
Symbol const *sym = expr.symbolOf();
if (!sym || !sym->getSection() || !sym->isDefined())
if (!sym || !sym->getSection() || !sym->isDefined()) {
return false;
}
assume(sym->isNumeric());
Section const &sect = *sym->getSection();
int32_t unknownBits = (1 << 16) - (1 << sect.align);
// `sym->getValue()` attempts to add the section's address, but that's "-1"
// `sym->getValue()` attempts to add the section's address, but that's `UINT32_MAX`
// because the section is floating (otherwise we wouldn't be here)
assume(sect.org == (uint32_t)-1);
assume(sect.org == UINT32_MAX);
int32_t symbolOfs = sym->getValue() + 1;
int32_t knownBits = (symbolOfs + sect.alignOfs) & ~unknownBits;
return knownBits != 0;
}
/*
* Attempts to compute a constant LOW() from non-constant argument
* This is possible if the argument is a symbol belonging to an `ALIGN[8]` section.
*
* @return The constant `LOW(expr)` result if it can be computed, or -1 otherwise.
*/
// Returns a constant LOW() from non-constant argument, or -1 if it cannot be computed.
// This is possible if the argument is a symbol belonging to an `ALIGN[8]` section.
static int32_t tryConstLow(Expression const &expr) {
Symbol const *sym = expr.symbolOf();
if (!sym || !sym->getSection() || !sym->isDefined())
if (!sym || !sym->getSection() || !sym->isDefined()) {
return -1;
}
assume(sym->isNumeric());
// The low byte must not cover any unknown bits
Section const &sect = *sym->getSection();
if (sect.align < 8)
if (sect.align < 8) {
return -1;
}
// `sym->getValue()` attempts to add the section's address, but that's "-1"
// `sym->getValue()` attempts to add the section's address, but that's `UINT32_MAX`
// because the section is floating (otherwise we wouldn't be here)
assume(sect.org == (uint32_t)-1);
assume(sect.org == UINT32_MAX);
int32_t symbolOfs = sym->getValue() + 1;
return (symbolOfs + sect.alignOfs) & 0xFF;
}
/*
* Attempts to compute a constant binary AND with one non-constant operands
* This is possible if one operand is a symbol belonging to an `ALIGN[N]` section, and the other is
* a constant that only keeps (some of) the lower N bits.
*
* @return The constant `lhs & rhs` result if it can be computed, or -1 otherwise.
*/
// Returns a constant binary AND with one non-constant operand, or -1 if it cannot be computed.
// This is possible if one operand is a symbol belonging to an `ALIGN[N]` section, and the other is
// a constant that only keeps (some of) the lower N bits.
static int32_t tryConstMask(Expression const &lhs, Expression const &rhs) {
Symbol const *lhsSymbol = lhs.symbolOf();
Symbol const *rhsSymbol = lhsSymbol ? nullptr : rhs.symbolOf();
bool lhsIsSymbol = lhsSymbol && lhsSymbol->getSection();
bool rhsIsSymbol = rhsSymbol && rhsSymbol->getSection();
if (!lhsIsSymbol && !rhsIsSymbol)
if (!lhsIsSymbol && !rhsIsSymbol) {
return -1;
}
// If the lhs isn't a symbol, try again the other way around
Symbol const &sym = lhsIsSymbol ? *lhsSymbol : *rhsSymbol;
Expression const &expr = lhsIsSymbol ? rhs : lhs; // Opposite side of `sym`
if (!sym.isDefined() || !expr.isKnown())
if (!sym.isDefined() || !expr.isKnown()) {
return -1;
}
assume(sym.isNumeric());
@@ -281,12 +280,13 @@ static int32_t tryConstMask(Expression const &lhs, Expression const &rhs) {
// The mask must not cover any unknown bits
Section const &sect = *sym.getSection();
if (int32_t unknownBits = (1 << 16) - (1 << sect.align); (unknownBits & mask) != 0)
if (int32_t unknownBits = (1 << 16) - (1 << sect.align); (unknownBits & mask) != 0) {
return -1;
}
// `sym.getValue()` attempts to add the section's address, but that's "-1"
// `sym.getValue()` attempts to add the section's address, but that's `UINT32_MAX`
// because the section is floating (otherwise we wouldn't be here)
assume(sect.org == (uint32_t)-1);
assume(sect.org == UINT32_MAX);
int32_t symbolOfs = sym.getValue() + 1;
return (symbolOfs + sect.alignOfs) & mask;
@@ -298,10 +298,11 @@ void Expression::makeUnaryOp(RPNCommand op, Expression &&src) {
if (src.isKnown()) {
// If the expressions is known, just compute the value
int32_t val = src.value();
uint32_t uval = static_cast<uint32_t>(val);
switch (op) {
case RPN_NEG:
data = (int32_t) - (uint32_t)val;
data = static_cast<int32_t>(-uval);
break;
case RPN_NOT:
data = ~val;
@@ -310,16 +311,16 @@ void Expression::makeUnaryOp(RPNCommand op, Expression &&src) {
data = !val;
break;
case RPN_HIGH:
data = (int32_t)((uint32_t)val >> 8 & 0xFF);
data = static_cast<int32_t>(uval >> 8 & 0xFF);
break;
case RPN_LOW:
data = val & 0xFF;
break;
case RPN_BITWIDTH:
data = val != 0 ? 32 - clz((uint32_t)val) : 0;
data = val != 0 ? 32 - clz(uval) : 0;
break;
case RPN_TZCOUNT:
data = val != 0 ? ctz((uint32_t)val) : 32;
data = val != 0 ? ctz(uval) : 32;
break;
case RPN_LOGOR:
@@ -374,6 +375,7 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const
if (src1.isKnown() && src2.isKnown()) {
// If both expressions are known, just compute the value
int32_t lval = src1.value(), rval = src2.value();
uint32_t ulval = static_cast<uint32_t>(lval), urval = static_cast<uint32_t>(rval);
switch (op) {
case RPN_LOGOR:
@@ -401,10 +403,10 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const
data = lval != rval;
break;
case RPN_ADD:
data = (int32_t)((uint32_t)lval + (uint32_t)rval);
data = static_cast<int32_t>(ulval + urval);
break;
case RPN_SUB:
data = (int32_t)((uint32_t)lval - (uint32_t)rval);
data = static_cast<int32_t>(ulval - urval);
break;
case RPN_XOR:
data = lval ^ rval;
@@ -416,47 +418,55 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const
data = lval & rval;
break;
case RPN_SHL:
if (rval < 0)
if (rval < 0) {
warning(
WARNING_SHIFT_AMOUNT, "Shifting left by negative amount %" PRId32 "\n", rval
);
}
if (rval >= 32)
if (rval >= 32) {
warning(WARNING_SHIFT_AMOUNT, "Shifting left by large amount %" PRId32 "\n", rval);
}
data = op_shift_left(lval, rval);
break;
case RPN_SHR:
if (lval < 0)
if (lval < 0) {
warning(WARNING_SHIFT, "Shifting right negative value %" PRId32 "\n", lval);
}
if (rval < 0)
if (rval < 0) {
warning(
WARNING_SHIFT_AMOUNT, "Shifting right by negative amount %" PRId32 "\n", rval
);
}
if (rval >= 32)
if (rval >= 32) {
warning(WARNING_SHIFT_AMOUNT, "Shifting right by large amount %" PRId32 "\n", rval);
}
data = op_shift_right(lval, rval);
break;
case RPN_USHR:
if (rval < 0)
if (rval < 0) {
warning(
WARNING_SHIFT_AMOUNT, "Shifting right by negative amount %" PRId32 "\n", rval
);
}
if (rval >= 32)
if (rval >= 32) {
warning(WARNING_SHIFT_AMOUNT, "Shifting right by large amount %" PRId32 "\n", rval);
}
data = op_shift_right_unsigned(lval, rval);
break;
case RPN_MUL:
data = (int32_t)((uint32_t)lval * (uint32_t)rval);
data = static_cast<int32_t>(ulval * urval);
break;
case RPN_DIV:
if (rval == 0)
if (rval == 0) {
fatalerror("Division by zero\n");
}
if (lval == INT32_MIN && rval == -1) {
warning(
@@ -471,17 +481,20 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const
}
break;
case RPN_MOD:
if (rval == 0)
if (rval == 0) {
fatalerror("Modulo by zero\n");
}
if (lval == INT32_MIN && rval == -1)
if (lval == INT32_MIN && rval == -1) {
data = 0;
else
} else {
data = op_modulo(lval, rval);
}
break;
case RPN_EXP:
if (rval < 0)
if (rval < 0) {
fatalerror("Exponentiation by negative power\n");
}
data = op_exponent(lval, rval);
break;
@@ -522,10 +535,10 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const
uint32_t lval = src1.value();
uint8_t bytes[] = {
RPN_CONST,
(uint8_t)lval,
(uint8_t)(lval >> 8),
(uint8_t)(lval >> 16),
(uint8_t)(lval >> 24),
static_cast<uint8_t>(lval),
static_cast<uint8_t>(lval >> 8),
static_cast<uint8_t>(lval >> 16),
static_cast<uint8_t>(lval >> 24),
};
rpn.clear();
rpnPatchSize = 0;
@@ -546,10 +559,10 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const
uint32_t rval = src2.value();
uint8_t bytes[] = {
RPN_CONST,
(uint8_t)rval,
(uint8_t)(rval >> 8),
(uint8_t)(rval >> 16),
(uint8_t)(rval >> 24),
static_cast<uint8_t>(rval),
static_cast<uint8_t>(rval >> 8),
static_cast<uint8_t>(rval >> 16),
static_cast<uint8_t>(rval >> 24),
};
uint8_t *ptr = reserveSpace(sizeof(bytes) + 1, sizeof(bytes) + 1);
memcpy(ptr, bytes, sizeof(bytes));
@@ -558,24 +571,29 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const
// Copy the right RPN and append the operator
uint32_t rightRpnSize = src2.rpn.size();
uint8_t *ptr = reserveSpace(rightRpnSize + 1, src2.rpnPatchSize + 1);
if (rightRpnSize > 0)
if (rightRpnSize > 0) {
// If `rightRpnSize == 0`, then `memcpy(ptr, nullptr, rightRpnSize)` would be UB
memcpy(ptr, src2.rpn.data(), rightRpnSize);
}
ptr[rightRpnSize] = op;
}
}
}
void Expression::makeCheckHRAM() {
bool Expression::makeCheckHRAM() {
isSymbol = false;
if (!isKnown()) {
*reserveSpace(1) = RPN_HRAM;
} else if (int32_t val = value(); val >= 0xFF00 && val <= 0xFFFF) {
// That range is valid, but only keep the lower byte
data = val & 0xFF;
} else if (val < 0 || val > 0xFF) {
} else if (val >= 0 && val <= 0xFF) {
// That range is valid, but deprecated
return true;
} else {
error("Source address $%" PRIx32 " not between $FF00 to $FFFF\n", val);
}
return false;
}
void Expression::makeCheckRST() {
@@ -584,16 +602,14 @@ void Expression::makeCheckRST() {
} else if (int32_t val = value(); val & ~0x38) {
// A valid RST address must be masked with 0x38
error("Invalid address $%" PRIx32 " for RST\n", val);
} else {
// The target is in the "0x38" bits, all other bits are set
data = val | 0xC7;
}
}
// Checks that an RPN expression's value fits within N bits (signed or unsigned)
void Expression::checkNBit(uint8_t n) const {
if (isKnown())
if (isKnown()) {
::checkNBit(value(), n, "Expression");
}
}
bool checkNBit(int32_t v, uint8_t n, char const *name) {

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: MIT */
// SPDX-License-Identifier: MIT
#include "asm/warning.hpp"
@@ -10,7 +10,7 @@
#include <string.h>
#include "error.hpp"
#include "helpers.hpp" // QUOTEDSTRLEN
#include "helpers.hpp"
#include "itertools.hpp"
#include "asm/fstack.hpp"
@@ -20,276 +20,188 @@
unsigned int nbErrors = 0;
unsigned int maxErrors = 0;
static WarningState const defaultWarnings[ARRAY_SIZE(warningStates)] = {
WARNING_ENABLED, // WARNING_ASSERT
WARNING_DISABLED, // WARNING_BACKWARDS_FOR
WARNING_DISABLED, // WARNING_BUILTIN_ARG
WARNING_DISABLED, // WARNING_CHARMAP_REDEF
WARNING_DISABLED, // WARNING_DIV
WARNING_DISABLED, // WARNING_EMPTY_DATA_DIRECTIVE
WARNING_DISABLED, // WARNING_EMPTY_MACRO_ARG
WARNING_DISABLED, // WARNING_EMPTY_STRRPL
WARNING_DISABLED, // WARNING_LARGE_CONSTANT
WARNING_DISABLED, // WARNING_MACRO_SHIFT
WARNING_ENABLED, // WARNING_NESTED_COMMENT
WARNING_ENABLED, // WARNING_OBSOLETE
WARNING_DISABLED, // WARNING_SHIFT
WARNING_DISABLED, // WARNING_SHIFT_AMOUNT
WARNING_ENABLED, // WARNING_USER
Diagnostics warningStates;
bool warningsAreErrors;
WARNING_DISABLED, // WARNING_NUMERIC_STRING_1
WARNING_DISABLED, // WARNING_NUMERIC_STRING_2
WARNING_ENABLED, // WARNING_TRUNCATION_1
WARNING_DISABLED, // WARNING_TRUNCATION_2
WARNING_ENABLED, // WARNING_PURGE_1
WARNING_DISABLED, // WARNING_PURGE_2
WARNING_ENABLED, // WARNING_UNMAPPED_CHAR_1
WARNING_DISABLED, // WARNING_UNMAPPED_CHAR_2
enum WarningLevel {
LEVEL_DEFAULT, // Warnings that are enabled by default
LEVEL_ALL, // Warnings that probably indicate an error
LEVEL_EXTRA, // Warnings that are less likely to indicate an error
LEVEL_EVERYTHING, // Literally every warning
};
WarningState warningStates[ARRAY_SIZE(warningStates)];
struct WarningFlag {
char const *name;
WarningLevel level;
};
bool warningsAreErrors; // Set if `-Werror` was specified
static WarningState warningState(WarningID id) {
// Check if warnings are globally disabled
if (!warnings)
return WARNING_DISABLED;
// Get the actual state
WarningState state = warningStates[id];
if (state == WARNING_DEFAULT)
// The state isn't set, grab its default state
state = defaultWarnings[id];
if (warningsAreErrors && state == WARNING_ENABLED)
state = WARNING_ERROR;
return state;
}
static char const * const warningFlags[NB_WARNINGS] = {
"assert",
"backwards-for",
"builtin-args",
"charmap-redef",
"div",
"empty-data-directive",
"empty-macro-arg",
"empty-strrpl",
"large-constant",
"macro-shift",
"nested-comment",
"obsolete",
"shift",
"shift-amount",
"user",
static WarningFlag const metaWarnings[] = {
{"all", LEVEL_ALL },
{"extra", LEVEL_EXTRA },
{"everything", LEVEL_EVERYTHING},
};
static WarningFlag const warningFlags[NB_WARNINGS] = {
{"assert", LEVEL_DEFAULT },
{"backwards-for", LEVEL_ALL },
{"builtin-args", LEVEL_ALL },
{"charmap-redef", LEVEL_ALL },
{"div", LEVEL_EVERYTHING},
{"empty-data-directive", LEVEL_ALL },
{"empty-macro-arg", LEVEL_EXTRA },
{"empty-strrpl", LEVEL_ALL },
{"large-constant", LEVEL_ALL },
{"macro-shift", LEVEL_EXTRA },
{"nested-comment", LEVEL_DEFAULT },
{"obsolete", LEVEL_DEFAULT },
{"shift", LEVEL_EVERYTHING},
{"shift-amount", LEVEL_EVERYTHING},
{"unmatched-directive", LEVEL_EXTRA },
{"unterminated-load", LEVEL_EXTRA },
{"user", LEVEL_DEFAULT },
// Parametric warnings
"numeric-string",
"numeric-string",
"purge",
"purge",
"truncation",
"truncation",
"unmapped-char",
"unmapped-char",
// Meta warnings
"all",
"extra",
"everything", // Especially useful for testing
{"numeric-string", LEVEL_EVERYTHING},
{"numeric-string", LEVEL_EVERYTHING},
{"purge", LEVEL_DEFAULT },
{"purge", LEVEL_ALL },
{"truncation", LEVEL_DEFAULT },
{"truncation", LEVEL_EXTRA },
{"unmapped-char", LEVEL_DEFAULT },
{"unmapped-char", LEVEL_ALL },
};
static const struct {
char const *name;
uint8_t nbLevels;
WarningID firstID;
WarningID lastID;
uint8_t defaultLevel;
} paramWarnings[] = {
{"numeric-string", 2, 1},
{"purge", 2, 1},
{"truncation", 2, 2},
{"unmapped-char", 2, 1},
{WARNING_NUMERIC_STRING_1, WARNING_NUMERIC_STRING_2, 1},
{WARNING_PURGE_1, WARNING_PURGE_2, 1},
{WARNING_TRUNCATION_1, WARNING_TRUNCATION_2, 2},
{WARNING_UNMAPPED_CHAR_1, WARNING_UNMAPPED_CHAR_2, 1},
};
static bool tryProcessParamWarning(char const *flag, uint8_t param, WarningState state) {
WarningID baseID = PARAM_WARNINGS_START;
enum WarningBehavior { DISABLED, ENABLED, ERROR };
for (size_t i = 0; i < ARRAY_SIZE(paramWarnings); i++) {
uint8_t maxParam = paramWarnings[i].nbLevels;
if (!strcmp(flag, paramWarnings[i].name)) { // Match!
if (!strcmp(flag, "numeric-string"))
warning(WARNING_OBSOLETE, "Warning flag \"numeric-string\" is deprecated\n");
// If making the warning an error but param is 0, set to the maximum
// This accommodates `-Werror=flag`, but also `-Werror=flag=0`, which is
// thus filtered out by the caller.
// A param of 0 makes sense for disabling everything, but neither for
// enabling nor "erroring". Use the default for those.
if (param == 0 && state != WARNING_DISABLED) {
param = paramWarnings[i].defaultLevel;
} else if (param > maxParam) {
if (param != 255) // Don't warn if already capped
warnx(
"Got parameter %" PRIu8
" for warning flag \"%s\", but the maximum is %" PRIu8 "; capping.\n",
param,
flag,
maxParam
);
param = maxParam;
}
// Set the first <param> to enabled/error, and disable the rest
for (uint8_t ofs = 0; ofs < maxParam; ofs++) {
warningStates[baseID + ofs] = ofs < param ? state : WARNING_DISABLED;
}
return true;
}
baseID = (WarningID)(baseID + maxParam);
static WarningBehavior getWarningBehavior(WarningID id) {
// Check if warnings are globally disabled
if (!warnings) {
return WarningBehavior::DISABLED;
}
return false;
// Get the state of this warning flag
WarningState const &flagState = warningStates.flagStates[id];
WarningState const &metaState = warningStates.metaStates[id];
// If subsequent checks determine that the warning flag is enabled, this checks whether it has
// -Werror without -Wno-error=<flag> or -Wno-error=<meta>, which makes it into an error
bool warningIsError = warningsAreErrors && flagState.error != WARNING_DISABLED
&& metaState.error != WARNING_DISABLED;
WarningBehavior enabledBehavior =
warningIsError ? WarningBehavior::ERROR : WarningBehavior::ENABLED;
// First, check the state of the specific warning flag
if (flagState.state == WARNING_DISABLED) { // -Wno-<flag>
return WarningBehavior::DISABLED;
}
if (flagState.error == WARNING_ENABLED) { // -Werror=<flag>
return WarningBehavior::ERROR;
}
if (flagState.state == WARNING_ENABLED) { // -W<flag>
return enabledBehavior;
}
// If no flag is specified, check the state of the "meta" flags that affect this warning flag
if (metaState.state == WARNING_DISABLED) { // -Wno-<meta>
return WarningBehavior::DISABLED;
}
if (metaState.error == WARNING_ENABLED) { // -Werror=<meta>
return WarningBehavior::ERROR;
}
if (metaState.state == WARNING_ENABLED) { // -W<meta>
return enabledBehavior;
}
// If no meta flag is specified, check the default state of this warning flag
if (warningFlags[id].level == LEVEL_DEFAULT) { // enabled by default
return enabledBehavior;
}
// No flag enables this warning, explicitly or implicitly
return WarningBehavior::DISABLED;
}
enum MetaWarningCommand { META_WARNING_DONE = NB_WARNINGS };
// Warnings that probably indicate an error
static uint8_t const _wallCommands[] = {
WARNING_BACKWARDS_FOR,
WARNING_BUILTIN_ARG,
WARNING_CHARMAP_REDEF,
WARNING_EMPTY_DATA_DIRECTIVE,
WARNING_EMPTY_STRRPL,
WARNING_LARGE_CONSTANT,
WARNING_NESTED_COMMENT,
WARNING_OBSOLETE,
WARNING_PURGE_1,
WARNING_PURGE_2,
WARNING_UNMAPPED_CHAR_1,
META_WARNING_DONE,
};
// Warnings that are less likely to indicate an error
static uint8_t const _wextraCommands[] = {
WARNING_EMPTY_MACRO_ARG,
WARNING_MACRO_SHIFT,
WARNING_NESTED_COMMENT,
WARNING_OBSOLETE,
WARNING_PURGE_1,
WARNING_PURGE_2,
WARNING_TRUNCATION_1,
WARNING_TRUNCATION_2,
WARNING_UNMAPPED_CHAR_1,
WARNING_UNMAPPED_CHAR_2,
META_WARNING_DONE,
};
// Literally everything. Notably useful for testing
static uint8_t const _weverythingCommands[] = {
WARNING_BACKWARDS_FOR,
WARNING_BUILTIN_ARG,
WARNING_DIV,
WARNING_EMPTY_DATA_DIRECTIVE,
WARNING_EMPTY_MACRO_ARG,
WARNING_EMPTY_STRRPL,
WARNING_LARGE_CONSTANT,
WARNING_MACRO_SHIFT,
WARNING_NESTED_COMMENT,
WARNING_OBSOLETE,
WARNING_PURGE_1,
WARNING_PURGE_2,
WARNING_SHIFT,
WARNING_SHIFT_AMOUNT,
WARNING_TRUNCATION_1,
WARNING_TRUNCATION_2,
WARNING_UNMAPPED_CHAR_1,
WARNING_UNMAPPED_CHAR_2,
// WARNING_USER,
META_WARNING_DONE,
};
static uint8_t const *metaWarningCommands[NB_META_WARNINGS] = {
_wallCommands,
_wextraCommands,
_weverythingCommands,
};
void WarningState::update(WarningState other) {
if (other.state != WARNING_DEFAULT) {
state = other.state;
}
if (other.error != WARNING_DEFAULT) {
error = other.error;
}
}
void processWarningFlag(char const *flag) {
static bool setError = false;
std::string rootFlag = flag;
// First, try to match against a "meta" warning
for (WarningID id : EnumSeq(META_WARNINGS_START, NB_WARNINGS)) {
// TODO: improve the matching performance?
if (!strcmp(flag, warningFlags[id])) {
// We got a match!
if (setError)
errx("Cannot make meta warning \"%s\" into an error", flag);
for (uint8_t const *ptr = metaWarningCommands[id - META_WARNINGS_START];
*ptr != META_WARNING_DONE;
ptr++) {
// Warning flag, set without override
if (warningStates[*ptr] == WARNING_DEFAULT)
warningStates[*ptr] = WARNING_ENABLED;
}
return;
}
// Check for `-Werror` or `-Wno-error` to return early
if (rootFlag == "error") {
// `-Werror` promotes warnings to errors
warningsAreErrors = true;
return;
} else if (rootFlag == "no-error") {
// `-Wno-error` disables promotion of warnings to errors
warningsAreErrors = false;
return;
}
// If it's not a meta warning, specially check against `-Werror`
if (!strncmp(flag, "error", QUOTEDSTRLEN("error"))) {
char const *errorFlag = flag + QUOTEDSTRLEN("error");
switch (*errorFlag) {
case '\0':
// `-Werror`
warningsAreErrors = true;
return;
case '=':
// `-Werror=XXX`
setError = true;
processWarningFlag(errorFlag + 1); // Skip the `=`
setError = false;
return;
// Otherwise, allow parsing as another flag
}
// Check for prefixes that affect what the flag does
WarningState state;
if (rootFlag.starts_with("error=")) {
// `-Werror=<flag>` enables the flag as an error
state = {.state = WARNING_ENABLED, .error = WARNING_ENABLED};
rootFlag.erase(0, literal_strlen("error="));
} else if (rootFlag.starts_with("no-error=")) {
// `-Wno-error=<flag>` prevents the flag from being an error,
// without affecting whether it is enabled
state = {.state = WARNING_DEFAULT, .error = WARNING_DISABLED};
rootFlag.erase(0, literal_strlen("no-error="));
} else if (rootFlag.starts_with("no-")) {
// `-Wno-<flag>` disables the flag
state = {.state = WARNING_DISABLED, .error = WARNING_DEFAULT};
rootFlag.erase(0, literal_strlen("no-"));
} else {
// `-W<flag>` enables the flag
state = {.state = WARNING_ENABLED, .error = WARNING_DEFAULT};
}
// Well, it's either a normal warning or a mistake
WarningState state = setError ? WARNING_ERROR
// Not an error, then check if this is a negation
: strncmp(flag, "no-", QUOTEDSTRLEN("no-")) ? WARNING_ENABLED
: WARNING_DISABLED;
char const *rootFlag = state == WARNING_DISABLED ? flag + QUOTEDSTRLEN("no-") : flag;
// Is this a "parametric" warning?
if (state != WARNING_DISABLED) { // The `no-` form cannot be parametrized
// Check for an `=` parameter to process as a parametric warning
// `-Wno-<flag>` and `-Wno-error=<flag>` negation cannot have an `=` parameter, but without a
// parameter, the 0 value will apply to all levels of a parametric warning
uint8_t param = 0;
bool hasParam = false;
if (state.state == WARNING_ENABLED) {
// First, check if there is an "equals" sign followed by a decimal number
char const *equals = strchr(rootFlag, '=');
// Ignore an equal sign at the very end of the string
if (auto equals = rootFlag.find('=');
equals != rootFlag.npos && equals != rootFlag.size() - 1) {
hasParam = true;
if (equals && equals[1] != '\0') { // Ignore an equal sign at the very end as well
// Is the rest of the string a decimal number?
// We want to avoid `strtoul`'s whitespace and sign, so we parse manually
uint8_t param = 0;
char const *ptr = equals + 1;
char const *ptr = rootFlag.c_str() + equals + 1;
bool warned = false;
// The `if`'s condition above ensures that this will run at least once
do {
// If we don't have a digit, bail
if (*ptr < '0' || *ptr > '9')
if (*ptr < '0' || *ptr > '9') {
break;
}
// Avoid overflowing!
if (param > UINT8_MAX - (*ptr - '0')) {
if (!warned)
warnx("Invalid warning flag \"%s\": capping parameter at 255\n", flag);
if (!warned) {
warnx("Invalid warning flag \"%s\": capping parameter at 255", flag);
}
warned = true; // Only warn once, cap always
param = 255;
continue;
@@ -299,40 +211,88 @@ void processWarningFlag(char const *flag) {
ptr++;
} while (*ptr);
// If we managed to the end of the string, check that the warning indeed
// accepts a parameter
// If we reached the end of the string, truncate it at the '='
if (*ptr == '\0') {
if (setError && param == 0) {
warnx("Ignoring nonsensical warning flag \"%s\"\n", flag);
return;
rootFlag.resize(equals);
// `-W<flag>=0` is equivalent to `-Wno-<flag>`
if (param == 0) {
state.state = WARNING_DISABLED;
}
std::string truncFlag = rootFlag;
truncFlag.resize(equals - rootFlag); // Truncate the param at the '='
if (tryProcessParamWarning(
truncFlag.c_str(), param, param == 0 ? WARNING_DISABLED : state
))
return;
}
}
}
// Try to match the flag against a "normal" flag
for (WarningID id : EnumSeq(NB_PLAIN_WARNINGS)) {
if (!strcmp(rootFlag, warningFlags[id])) {
// We got a match!
warningStates[id] = state;
// Try to match the flag against a parametric warning
// If there was an equals sign, it will have set `param`; if not, `param` will be 0, which
// applies to all levels
for (auto const &paramWarning : paramWarnings) {
WarningID baseID = paramWarning.firstID;
uint8_t maxParam = paramWarning.lastID - baseID + 1;
assume(paramWarning.defaultLevel <= maxParam);
if (rootFlag == warningFlags[baseID].name) { // Match!
if (rootFlag == "numeric-string") {
warning(WARNING_OBSOLETE, "Warning flag \"numeric-string\" is deprecated\n");
}
// If making the warning an error but param is 0, set to the maximum
// This accommodates `-Werror=<flag>`, but also `-Werror=<flag>=0`, which is
// thus filtered out by the caller.
// A param of 0 makes sense for disabling everything, but neither for
// enabling nor "erroring". Use the default for those.
if (param == 0) {
param = paramWarning.defaultLevel;
} else if (param > maxParam) {
if (param != 255) { // Don't warn if already capped
warnx(
"Invalid parameter %" PRIu8
" for warning flag \"%s\"; capping at maximum %" PRIu8,
param,
rootFlag.c_str(),
maxParam
);
}
param = maxParam;
}
// Set the first <param> to enabled/error, and disable the rest
for (uint8_t ofs = 0; ofs < maxParam; ofs++) {
WarningState &warning = warningStates.flagStates[baseID + ofs];
if (ofs < param) {
warning.update(state);
} else {
warning.state = WARNING_DISABLED;
}
}
return;
}
}
// Lastly, this might be a "parametric" warning without an equals sign
// If it is, treat the param as 1 if enabling, or 0 if disabling
if (tryProcessParamWarning(rootFlag, 0, state))
return;
// Try to match against a non-parametric warning, unless there was an equals sign
if (!hasParam) {
// Try to match against a "meta" warning
for (WarningFlag const &metaWarning : metaWarnings) {
if (rootFlag == metaWarning.name) {
// Set each of the warning flags that meets this level
for (WarningID id : EnumSeq(NB_WARNINGS)) {
if (metaWarning.level >= warningFlags[id].level) {
warningStates.metaStates[id].update(state);
}
}
return;
}
}
warnx("Unknown warning `%s`", flag);
// Try to match the flag against a "normal" flag
for (WarningID id : EnumSeq(NB_PLAIN_WARNINGS)) {
if (rootFlag == warningFlags[id].name) {
warningStates.flagStates[id].update(state);
return;
}
}
}
warnx("Unknown warning flag \"%s\"", flag);
}
void printDiag(
@@ -356,16 +316,18 @@ void error(char const *fmt, ...) {
// This intentionally makes 0 act as "unlimited" (or at least "limited to sizeof(unsigned)")
nbErrors++;
if (nbErrors == maxErrors)
if (nbErrors == maxErrors) {
errx(
"The maximum of %u error%s was reached (configure with \"-X/--max-errors\"); assembly "
"aborted!",
maxErrors,
maxErrors == 1 ? "" : "s"
);
}
}
[[noreturn]] void fatalerror(char const *fmt, ...) {
[[noreturn]]
void fatalerror(char const *fmt, ...) {
va_list args;
va_start(args, fmt);
@@ -376,26 +338,22 @@ void error(char const *fmt, ...) {
}
void warning(WarningID id, char const *fmt, ...) {
char const *flag = warningFlags[id];
char const *flag = warningFlags[id].name;
va_list args;
va_start(args, fmt);
switch (warningState(id)) {
case WARNING_DISABLED:
switch (getWarningBehavior(id)) {
case WarningBehavior::DISABLED:
break;
case WARNING_ENABLED:
case WarningBehavior::ENABLED:
printDiag(fmt, args, "warning", ": [-W%s]", flag);
break;
case WARNING_ERROR:
case WarningBehavior::ERROR:
printDiag(fmt, args, "error", ": [-Werror=%s]", flag);
break;
case WARNING_DEFAULT:
unreachable_();
// Not reached
}
va_end(args);

View File

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

77
src/extern/getopt.cpp vendored
View File

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

View File

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

View File

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

View File

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

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